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