BLIP/BLIPProperties.m
author Jens Alfke <jens@mooseyard.com>
Fri May 23 17:37:36 2008 -0700 (2008-05-23)
changeset 0 9d67172bb323
child 1 8267d5c429c4
permissions -rw-r--r--
First checkin after breaking out of Cloudy
jens@0
     1
//
jens@0
     2
//  BLIPProperties.m
jens@0
     3
//  MYNetwork
jens@0
     4
//
jens@0
     5
//  Created by Jens Alfke on 5/13/08.
jens@0
     6
//  Copyright 2008 Jens Alfke. All rights reserved.
jens@0
     7
//
jens@0
     8
jens@0
     9
#import "BLIPProperties.h"
jens@0
    10
jens@0
    11
jens@0
    12
/** Common strings are abbreviated as single-byte strings in the packed form.
jens@0
    13
    The ascii value of the single character minus one is the index into this table. */
jens@0
    14
static const char* kAbbreviations[] = {
jens@0
    15
    "Content-Type",
jens@0
    16
    "Profile",
jens@0
    17
    "Channel"
jens@0
    18
    "Error-Code"
jens@0
    19
    "Error-Domain",
jens@0
    20
    "application/octet-stream",
jens@0
    21
    "text/plain",
jens@0
    22
    "text/xml",
jens@0
    23
    "text/yaml",
jens@0
    24
    "application/x-cloudy-signed+yaml",
jens@0
    25
};
jens@0
    26
#define kNAbbreviations ((sizeof(kAbbreviations)/sizeof(const char*)))  // cannot exceed 31!
jens@0
    27
jens@0
    28
jens@0
    29
jens@0
    30
@interface BLIPPackedProperties : BLIPProperties
jens@0
    31
{
jens@0
    32
    NSData *_data;
jens@0
    33
    int _count;
jens@0
    34
    const char **_strings;
jens@0
    35
    int _nStrings;
jens@0
    36
}
jens@0
    37
jens@0
    38
@end
jens@0
    39
jens@0
    40
jens@0
    41
jens@0
    42
// The base class just represents an immutable empty collection.
jens@0
    43
@implementation BLIPProperties
jens@0
    44
jens@0
    45
jens@0
    46
+ (BLIPProperties*) propertiesWithEncodedData: (NSData*)data usedLength: (ssize_t*)usedLength
jens@0
    47
{
jens@0
    48
    size_t available = data.length;
jens@0
    49
    if( available < sizeof(UInt16) ) {
jens@0
    50
        // Not enough available to read length:
jens@0
    51
        *usedLength = 0;
jens@0
    52
        return nil;
jens@0
    53
    }
jens@0
    54
    
jens@0
    55
    // Read the length:
jens@0
    56
    const char *bytes = data.bytes;
jens@0
    57
    size_t length = EndianU16_BtoN( *(UInt16*)bytes ) + sizeof(UInt16);
jens@0
    58
    if( length > available ) {
jens@0
    59
        // Properties not complete yet.
jens@0
    60
        *usedLength = 0;
jens@0
    61
        return nil;
jens@0
    62
    }
jens@0
    63
    
jens@0
    64
    // Complete -- try to create an object:
jens@0
    65
    BLIPProperties *props;
jens@0
    66
    if( length > sizeof(UInt16) )
jens@0
    67
        props = [[[BLIPPackedProperties alloc] initWithBytes: bytes length: length] autorelease];
jens@0
    68
    else
jens@0
    69
        props = [BLIPProperties properties];
jens@0
    70
    
jens@0
    71
    *usedLength = props ?length :-1;
jens@0
    72
    return props;
jens@0
    73
}
jens@0
    74
jens@0
    75
jens@0
    76
- (id) copyWithZone: (NSZone*)zone
jens@0
    77
{
jens@0
    78
    return [self retain];
jens@0
    79
}
jens@0
    80
jens@0
    81
- (id) mutableCopyWithZone: (NSZone*)zone
jens@0
    82
{
jens@0
    83
    return [[BLIPMutableProperties allocWithZone: zone] initWithDictionary: self.allProperties];
jens@0
    84
}
jens@0
    85
jens@0
    86
- (BOOL) isEqual: (id)other
jens@0
    87
{
jens@0
    88
    return [other isKindOfClass: [BLIPProperties class]]
jens@0
    89
        && [self.allProperties isEqual: [other allProperties]];
jens@0
    90
}
jens@0
    91
jens@0
    92
- (NSString*) valueOfProperty: (NSString*)prop  {return nil;}
jens@0
    93
- (NSDictionary*) allProperties                 {return [NSDictionary dictionary];}
jens@0
    94
- (NSUInteger) count                            {return 0;}
jens@0
    95
- (NSUInteger) dataLength                       {return sizeof(UInt16);}
jens@0
    96
jens@0
    97
- (NSData*) encodedData
jens@0
    98
{
jens@0
    99
    UInt16 len = 0;
jens@0
   100
    return [NSData dataWithBytes: &len length: sizeof(len)];
jens@0
   101
}
jens@0
   102
jens@0
   103
jens@0
   104
+ (BLIPProperties*) properties
jens@0
   105
{
jens@0
   106
    static BLIPProperties *sEmptyInstance;
jens@0
   107
    if( ! sEmptyInstance )
jens@0
   108
        sEmptyInstance = [[self alloc] init];
jens@0
   109
    return sEmptyInstance;
jens@0
   110
}
jens@0
   111
jens@0
   112
jens@0
   113
@end
jens@0
   114
jens@0
   115
jens@0
   116
jens@0
   117
/** Internal immutable subclass that keeps its contents in the packed data representation. */
jens@0
   118
@implementation BLIPPackedProperties
jens@0
   119
jens@0
   120
jens@0
   121
- (id) initWithBytes: (const char*)bytes length: (size_t)length
jens@0
   122
{
jens@0
   123
    self = [super init];
jens@0
   124
    if (self != nil) {
jens@0
   125
        // Copy data, then skip the length field:
jens@0
   126
        _data = [[NSData alloc] initWithBytes: bytes length: length];
jens@0
   127
        bytes = (const char*)_data.bytes + sizeof(UInt16);
jens@0
   128
        length -= sizeof(UInt16);
jens@0
   129
        
jens@0
   130
        if( bytes[length-1]!='\0' )
jens@0
   131
            goto fail;
jens@0
   132
jens@0
   133
        // The data consists of consecutive NUL-terminated strings, alternating key/value:
jens@0
   134
        unsigned capacity = 0;
jens@0
   135
        const char *end = bytes+length;
jens@0
   136
        for( const char *str=bytes; str < end; str += strlen(str)+1, _nStrings++ ) {
jens@0
   137
            if( _nStrings >= capacity ) {
jens@0
   138
                capacity = capacity ?(2*capacity) :4;
jens@0
   139
                _strings = realloc(_strings, capacity*sizeof(const char**));
jens@0
   140
            }
jens@0
   141
            UInt8 first = (UInt8)str[0];
jens@0
   142
            if( first>'\0' && first<' ' && str[1]=='\0' ) {
jens@0
   143
                // Single-control-character property string is an abbreviation:
jens@0
   144
                if( first > kNAbbreviations )
jens@0
   145
                    goto fail;
jens@0
   146
                _strings[_nStrings] = kAbbreviations[first-1];
jens@0
   147
            } else
jens@0
   148
                _strings[_nStrings] = str;
jens@0
   149
        }
jens@0
   150
        
jens@0
   151
        // It's illegal for the data to end with a non-NUL or for there to be an odd number of strings:
jens@0
   152
        if( (_nStrings & 1) )
jens@0
   153
            goto fail;
jens@0
   154
        
jens@0
   155
        return self;
jens@0
   156
            
jens@0
   157
    fail:
jens@0
   158
        Warn(@"BLIPProperties: invalid data");
jens@0
   159
        [self release];
jens@0
   160
        return nil;
jens@0
   161
    }
jens@0
   162
    return self;
jens@0
   163
}
jens@0
   164
jens@0
   165
jens@0
   166
- (void) dealloc
jens@0
   167
{
jens@0
   168
    if( _strings ) free(_strings);
jens@0
   169
    [_data release];
jens@0
   170
    [super dealloc];
jens@0
   171
}
jens@0
   172
jens@0
   173
- (id) copyWithZone: (NSZone*)zone
jens@0
   174
{
jens@0
   175
    return [self retain];
jens@0
   176
}
jens@0
   177
jens@0
   178
- (id) mutableCopyWithZone: (NSZone*)zone
jens@0
   179
{
jens@0
   180
    return [[BLIPMutableProperties allocWithZone: zone] initWithDictionary: self.allProperties];
jens@0
   181
}
jens@0
   182
jens@0
   183
jens@0
   184
- (NSString*) valueOfProperty: (NSString*)prop
jens@0
   185
{
jens@0
   186
    const char *propStr = [prop UTF8String];
jens@0
   187
    Assert(propStr);
jens@0
   188
    // Search in reverse order so that later values will take precedence over earlier ones.
jens@0
   189
    for( int i=_nStrings-2; i>=0; i-=2 ) {
jens@0
   190
        if( strcmp(propStr, _strings[i]) == 0 )
jens@0
   191
            return [NSString stringWithUTF8String: _strings[i+1]];
jens@0
   192
    }
jens@0
   193
    return nil;
jens@0
   194
}
jens@0
   195
jens@0
   196
jens@0
   197
- (NSDictionary*) allProperties
jens@0
   198
{
jens@0
   199
    NSMutableDictionary *props = [NSMutableDictionary dictionaryWithCapacity: _nStrings/2];
jens@0
   200
    // Add values in forward order so that later ones will overwrite (take precedence over)
jens@0
   201
    // earlier ones, which matches the behavior of -valueOfProperty.
jens@0
   202
    // (However, note that unlike -valueOfProperty, this dictionary is case-sensitive!)
jens@0
   203
    for( int i=0; i<_nStrings; i+=2 ) {
jens@0
   204
        NSString *key = [[NSString alloc] initWithUTF8String: _strings[i]];
jens@0
   205
        NSString *value = [[NSString alloc] initWithUTF8String: _strings[i+1]];
jens@0
   206
        if( key && value )
jens@0
   207
            [props setObject: value forKey: key];
jens@0
   208
        [key release];
jens@0
   209
        [value release];
jens@0
   210
    }
jens@0
   211
    return props;
jens@0
   212
}
jens@0
   213
jens@0
   214
jens@0
   215
- (NSUInteger) count        {return _nStrings/2;}
jens@0
   216
- (NSData*) encodedData            {return _data;}
jens@0
   217
- (NSUInteger) dataLength   {return _data.length;}
jens@0
   218
jens@0
   219
jens@0
   220
@end
jens@0
   221
jens@0
   222
jens@0
   223
jens@0
   224
/** Mutable subclass that stores its properties in an NSMutableDictionary. */
jens@0
   225
@implementation BLIPMutableProperties
jens@0
   226
jens@0
   227
jens@0
   228
+ (BLIPProperties*) properties
jens@0
   229
{
jens@0
   230
    return [[self alloc] initWithDictionary: nil];
jens@0
   231
}
jens@0
   232
jens@0
   233
- (id) init
jens@0
   234
{
jens@0
   235
    self = [super init];
jens@0
   236
    if (self != nil) {
jens@0
   237
        _properties = [[NSMutableDictionary alloc] init];
jens@0
   238
    }
jens@0
   239
    return self;
jens@0
   240
}
jens@0
   241
jens@0
   242
- (id) initWithDictionary: (NSDictionary*)dict
jens@0
   243
{
jens@0
   244
    self = [super init];
jens@0
   245
    if (self != nil) {
jens@0
   246
        _properties = dict ?[dict mutableCopy] :[[NSMutableDictionary alloc] init];
jens@0
   247
    }
jens@0
   248
    return self;
jens@0
   249
}
jens@0
   250
jens@0
   251
- (id) initWithProperties: (BLIPProperties*)properties
jens@0
   252
{
jens@0
   253
    return [self initWithDictionary: [properties allProperties]];
jens@0
   254
}
jens@0
   255
jens@0
   256
- (void) dealloc
jens@0
   257
{
jens@0
   258
    [_properties release];
jens@0
   259
    [super dealloc];
jens@0
   260
}
jens@0
   261
jens@0
   262
- (id) copyWithZone: (NSZone*)zone
jens@0
   263
{
jens@0
   264
    ssize_t usedLength;
jens@0
   265
    BLIPProperties *copy = [BLIPProperties propertiesWithEncodedData: self.encodedData usedLength: &usedLength];
jens@0
   266
    Assert(copy);
jens@0
   267
    return [copy retain];
jens@0
   268
}
jens@0
   269
jens@0
   270
jens@0
   271
- (NSString*) valueOfProperty: (NSString*)prop
jens@0
   272
{
jens@0
   273
    return [_properties objectForKey: prop];
jens@0
   274
}
jens@0
   275
jens@0
   276
- (NSDictionary*) allProperties
jens@0
   277
{
jens@0
   278
    return _properties;
jens@0
   279
}
jens@0
   280
jens@0
   281
- (NSUInteger) count        {return _properties.count;}
jens@0
   282
jens@0
   283
jens@0
   284
static void appendStr( NSMutableData *data, NSString *str ) {
jens@0
   285
    const char *utf8 = [str UTF8String];
jens@0
   286
    size_t size = strlen(utf8)+1;
jens@0
   287
    for( int i=0; i<kNAbbreviations; i++ )
jens@0
   288
        if( memcmp(utf8,kAbbreviations[i],size)==0 ) {
jens@0
   289
            const UInt8 abbrev[2] = {i+1,0};
jens@0
   290
            [data appendBytes: &abbrev length: 2];
jens@0
   291
            return;
jens@0
   292
        }
jens@0
   293
    [data appendBytes: utf8 length: size];
jens@0
   294
}
jens@0
   295
jens@0
   296
- (NSData*) encodedData
jens@0
   297
{
jens@0
   298
    NSMutableData *data = [NSMutableData dataWithCapacity: 16*_properties.count];
jens@0
   299
    [data setLength: sizeof(UInt16)]; // leave room for length
jens@0
   300
    for( NSString *name in _properties ) {
jens@0
   301
        appendStr(data,name);
jens@0
   302
        appendStr(data,[_properties objectForKey: name]);
jens@0
   303
    }
jens@0
   304
    
jens@0
   305
    NSUInteger length = data.length - sizeof(UInt16);
jens@0
   306
    if( length > 0xFFFF )
jens@0
   307
        return nil;
jens@0
   308
    *(UInt16*)[data mutableBytes] = EndianU16_NtoB((UInt16)length);
jens@0
   309
    return data;
jens@0
   310
}
jens@0
   311
jens@0
   312
    
jens@0
   313
- (void) setValue: (NSString*)value ofProperty: (NSString*)prop
jens@0
   314
{
jens@0
   315
    Assert(prop.length>0);
jens@0
   316
    if( value )
jens@0
   317
        [_properties setObject: value forKey: prop];
jens@0
   318
    else
jens@0
   319
        [_properties removeObjectForKey: prop];
jens@0
   320
}
jens@0
   321
jens@0
   322
jens@0
   323
- (void) setAllProperties: (NSDictionary*)properties
jens@0
   324
{
jens@0
   325
    if( properties.count ) {
jens@0
   326
        for( id key in properties ) {
jens@0
   327
            Assert([key isKindOfClass: [NSString class]]);
jens@0
   328
            Assert([key length] > 0);
jens@0
   329
            Assert([[properties objectForKey: key] isKindOfClass: [NSString class]]);
jens@0
   330
        }
jens@0
   331
        [_properties setDictionary: properties];
jens@0
   332
    } else
jens@0
   333
        [_properties removeAllObjects];
jens@0
   334
}
jens@0
   335
jens@0
   336
jens@0
   337
@end
jens@0
   338
jens@0
   339
jens@0
   340
jens@0
   341
jens@0
   342
TestCase(BLIPProperties) {
jens@0
   343
    BLIPProperties *props;
jens@0
   344
    
jens@0
   345
    props = [BLIPProperties properties];
jens@0
   346
    CAssert(props);
jens@0
   347
    CAssertEq(props.count,0);
jens@0
   348
    Log(@"Empty properties:\n%@", props.allProperties);
jens@0
   349
    NSData *data = props.encodedData;
jens@0
   350
    Log(@"As data: %@", data);
jens@0
   351
    CAssertEqual(data,[NSMutableData dataWithLength: 2]);
jens@0
   352
    
jens@0
   353
    BLIPMutableProperties *mprops = [props mutableCopy];
jens@0
   354
    Log(@"Mutable copy:\n%@", mprops.allProperties);
jens@0
   355
    data = mprops.encodedData;
jens@0
   356
    Log(@"As data: %@", data);
jens@0
   357
    CAssertEqual(data,[NSMutableData dataWithLength: 2]);
jens@0
   358
    
jens@0
   359
    ssize_t used;
jens@0
   360
    props = [BLIPProperties propertiesWithEncodedData: data usedLength: &used];
jens@0
   361
    CAssertEq(used,data.length);
jens@0
   362
    CAssertEqual(props,mprops);
jens@0
   363
    
jens@0
   364
    [mprops setValue: @"Jens" ofProperty: @"First-Name"];
jens@0
   365
    [mprops setValue: @"Alfke" ofProperty: @"Last-Name"];
jens@0
   366
    [mprops setValue: @"" ofProperty: @"Empty-String"];
jens@0
   367
    [mprops setValue: @"Z" ofProperty: @"A"];
jens@0
   368
    Log(@"With properties:\n%@", mprops.allProperties);
jens@0
   369
    data = mprops.encodedData;
jens@0
   370
    Log(@"As data: %@", data);
jens@0
   371
    
jens@0
   372
    for( unsigned len=0; len<data.length; len++ ) {
jens@0
   373
        props = [BLIPProperties propertiesWithEncodedData: [data subdataWithRange: NSMakeRange(0,len)]
jens@0
   374
                                                                usedLength: &used];
jens@0
   375
        CAssertEq(props,nil);
jens@0
   376
        CAssertEq(used,0);
jens@0
   377
    }
jens@0
   378
    props = [BLIPProperties propertiesWithEncodedData: data usedLength: &used];
jens@0
   379
    CAssertEq(used,data.length);
jens@0
   380
    Log(@"Read back in:\n%@",props.allProperties);
jens@0
   381
    CAssertEqual(props,mprops);
jens@0
   382
    
jens@0
   383
    NSDictionary *all = mprops.allProperties;
jens@0
   384
    for( NSString *prop in all )
jens@0
   385
        CAssertEqual([props valueOfProperty: prop],[all objectForKey: prop]);
jens@0
   386
}
jens@0
   387
jens@0
   388
jens@0
   389
/*
jens@0
   390
 Copyright (c) 2008, Jens Alfke <jens@mooseyard.com>. All rights reserved.
jens@0
   391
 
jens@0
   392
 Redistribution and use in source and binary forms, with or without modification, are permitted
jens@0
   393
 provided that the following conditions are met:
jens@0
   394
 
jens@0
   395
 * Redistributions of source code must retain the above copyright notice, this list of conditions
jens@0
   396
 and the following disclaimer.
jens@0
   397
 * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
jens@0
   398
 and the following disclaimer in the documentation and/or other materials provided with the
jens@0
   399
 distribution.
jens@0
   400
 
jens@0
   401
 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
jens@0
   402
 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 
jens@0
   403
 FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI-
jens@0
   404
 BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
jens@0
   405
 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
jens@0
   406
  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
jens@0
   407
 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 
jens@0
   408
 THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
jens@0
   409
 */