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