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