jens@0: // jens@0: // BLIPProperties.m jens@0: // MYNetwork jens@0: // jens@0: // Created by Jens Alfke on 5/13/08. jens@0: // Copyright 2008 Jens Alfke. All rights reserved. jens@0: // jens@0: jens@0: #import "BLIPProperties.h" jens@1: #import "Logging.h" jens@1: #import "Test.h" jens@0: jens@0: jens@0: /** Common strings are abbreviated as single-byte strings in the packed form. jens@0: The ascii value of the single character minus one is the index into this table. */ jens@0: static const char* kAbbreviations[] = { jens@0: "Content-Type", jens@0: "Profile", jens@0: "application/octet-stream", jens@2: "text/plain; charset=UTF-8", jens@0: "text/xml", jens@0: "text/yaml", jens@11: "Channel", jens@11: "Error-Code", jens@11: "Error-Domain", jens@0: }; jens@0: #define kNAbbreviations ((sizeof(kAbbreviations)/sizeof(const char*))) // cannot exceed 31! jens@0: jens@0: jens@0: jens@0: @interface BLIPPackedProperties : BLIPProperties jens@0: { jens@0: NSData *_data; jens@0: int _count; jens@0: const char **_strings; jens@0: int _nStrings; jens@0: } jens@0: jens@0: @end jens@0: jens@0: jens@0: jens@0: // The base class just represents an immutable empty collection. jens@0: @implementation BLIPProperties jens@0: jens@0: jens@0: + (BLIPProperties*) propertiesWithEncodedData: (NSData*)data usedLength: (ssize_t*)usedLength jens@0: { jens@0: size_t available = data.length; jens@0: if( available < sizeof(UInt16) ) { jens@0: // Not enough available to read length: jens@0: *usedLength = 0; jens@0: return nil; jens@0: } jens@0: jens@0: // Read the length: jens@0: const char *bytes = data.bytes; jens@8: size_t length = NSSwapBigShortToHost( *(UInt16*)bytes ) + sizeof(UInt16); jens@0: if( length > available ) { jens@0: // Properties not complete yet. jens@0: *usedLength = 0; jens@0: return nil; jens@0: } jens@0: jens@0: // Complete -- try to create an object: jens@0: BLIPProperties *props; jens@0: if( length > sizeof(UInt16) ) jens@0: props = [[[BLIPPackedProperties alloc] initWithBytes: bytes length: length] autorelease]; jens@0: else jens@0: props = [BLIPProperties properties]; jens@0: jens@0: *usedLength = props ?length :-1; jens@0: return props; jens@0: } jens@0: jens@0: jens@0: - (id) copyWithZone: (NSZone*)zone jens@0: { jens@0: return [self retain]; jens@0: } jens@0: jens@0: - (id) mutableCopyWithZone: (NSZone*)zone jens@0: { jens@0: return [[BLIPMutableProperties allocWithZone: zone] initWithDictionary: self.allProperties]; jens@0: } jens@0: jens@0: - (BOOL) isEqual: (id)other jens@0: { jens@0: return [other isKindOfClass: [BLIPProperties class]] jens@0: && [self.allProperties isEqual: [other allProperties]]; jens@0: } jens@0: jens@0: - (NSString*) valueOfProperty: (NSString*)prop {return nil;} jens@0: - (NSDictionary*) allProperties {return [NSDictionary dictionary];} jens@0: - (NSUInteger) count {return 0;} jens@0: - (NSUInteger) dataLength {return sizeof(UInt16);} jens@0: jens@0: - (NSData*) encodedData jens@0: { jens@0: UInt16 len = 0; jens@0: return [NSData dataWithBytes: &len length: sizeof(len)]; jens@0: } jens@0: jens@0: jens@0: + (BLIPProperties*) properties jens@0: { jens@0: static BLIPProperties *sEmptyInstance; jens@0: if( ! sEmptyInstance ) jens@0: sEmptyInstance = [[self alloc] init]; jens@0: return sEmptyInstance; jens@0: } jens@0: jens@0: jens@0: @end jens@0: jens@0: jens@0: jens@0: /** Internal immutable subclass that keeps its contents in the packed data representation. */ jens@0: @implementation BLIPPackedProperties jens@0: jens@0: jens@0: - (id) initWithBytes: (const char*)bytes length: (size_t)length jens@0: { jens@0: self = [super init]; jens@0: if (self != nil) { jens@0: // Copy data, then skip the length field: jens@0: _data = [[NSData alloc] initWithBytes: bytes length: length]; jens@0: bytes = (const char*)_data.bytes + sizeof(UInt16); jens@0: length -= sizeof(UInt16); jens@0: jens@0: if( bytes[length-1]!='\0' ) jens@0: goto fail; jens@0: jens@0: // The data consists of consecutive NUL-terminated strings, alternating key/value: jens@0: unsigned capacity = 0; jens@0: const char *end = bytes+length; jens@0: for( const char *str=bytes; str < end; str += strlen(str)+1, _nStrings++ ) { jens@0: if( _nStrings >= capacity ) { jens@0: capacity = capacity ?(2*capacity) :4; jens@0: _strings = realloc(_strings, capacity*sizeof(const char**)); jens@0: } jens@0: UInt8 first = (UInt8)str[0]; jens@0: if( first>'\0' && first<' ' && str[1]=='\0' ) { jens@0: // Single-control-character property string is an abbreviation: jens@0: if( first > kNAbbreviations ) jens@0: goto fail; jens@0: _strings[_nStrings] = kAbbreviations[first-1]; jens@0: } else jens@0: _strings[_nStrings] = str; jens@0: } jens@0: jens@0: // It's illegal for the data to end with a non-NUL or for there to be an odd number of strings: jens@0: if( (_nStrings & 1) ) jens@0: goto fail; jens@0: jens@0: return self; jens@0: jens@0: fail: jens@0: Warn(@"BLIPProperties: invalid data"); jens@0: [self release]; jens@0: return nil; jens@0: } jens@0: return self; jens@0: } jens@0: jens@0: jens@0: - (void) dealloc jens@0: { jens@0: if( _strings ) free(_strings); jens@0: [_data release]; jens@0: [super dealloc]; jens@0: } jens@0: jens@0: - (id) copyWithZone: (NSZone*)zone jens@0: { jens@0: return [self retain]; jens@0: } jens@0: jens@0: - (id) mutableCopyWithZone: (NSZone*)zone jens@0: { jens@0: return [[BLIPMutableProperties allocWithZone: zone] initWithDictionary: self.allProperties]; jens@0: } jens@0: jens@0: jens@0: - (NSString*) valueOfProperty: (NSString*)prop jens@0: { jens@0: const char *propStr = [prop UTF8String]; jens@0: Assert(propStr); jens@0: // Search in reverse order so that later values will take precedence over earlier ones. jens@0: for( int i=_nStrings-2; i>=0; i-=2 ) { jens@0: if( strcmp(propStr, _strings[i]) == 0 ) jens@0: return [NSString stringWithUTF8String: _strings[i+1]]; jens@0: } jens@0: return nil; jens@0: } jens@0: jens@0: jens@0: - (NSDictionary*) allProperties jens@0: { jens@0: NSMutableDictionary *props = [NSMutableDictionary dictionaryWithCapacity: _nStrings/2]; jens@0: // Add values in forward order so that later ones will overwrite (take precedence over) jens@0: // earlier ones, which matches the behavior of -valueOfProperty. jens@0: // (However, note that unlike -valueOfProperty, this dictionary is case-sensitive!) jens@0: for( int i=0; i<_nStrings; i+=2 ) { jens@0: NSString *key = [[NSString alloc] initWithUTF8String: _strings[i]]; jens@0: NSString *value = [[NSString alloc] initWithUTF8String: _strings[i+1]]; jens@0: if( key && value ) jens@0: [props setObject: value forKey: key]; jens@0: [key release]; jens@0: [value release]; jens@0: } jens@0: return props; jens@0: } jens@0: jens@0: jens@0: - (NSUInteger) count {return _nStrings/2;} jens@0: - (NSData*) encodedData {return _data;} jens@0: - (NSUInteger) dataLength {return _data.length;} jens@0: jens@0: jens@0: @end jens@0: jens@0: jens@0: jens@0: /** Mutable subclass that stores its properties in an NSMutableDictionary. */ jens@0: @implementation BLIPMutableProperties jens@0: jens@0: jens@0: + (BLIPProperties*) properties jens@0: { jens@0: return [[self alloc] initWithDictionary: nil]; jens@0: } jens@0: jens@0: - (id) init jens@0: { jens@0: self = [super init]; jens@0: if (self != nil) { jens@0: _properties = [[NSMutableDictionary alloc] init]; jens@0: } jens@0: return self; jens@0: } jens@0: jens@0: - (id) initWithDictionary: (NSDictionary*)dict jens@0: { jens@0: self = [super init]; jens@0: if (self != nil) { jens@0: _properties = dict ?[dict mutableCopy] :[[NSMutableDictionary alloc] init]; jens@0: } jens@0: return self; jens@0: } jens@0: jens@0: - (id) initWithProperties: (BLIPProperties*)properties jens@0: { jens@0: return [self initWithDictionary: [properties allProperties]]; jens@0: } jens@0: jens@0: - (void) dealloc jens@0: { jens@0: [_properties release]; jens@0: [super dealloc]; jens@0: } jens@0: jens@0: - (id) copyWithZone: (NSZone*)zone jens@0: { jens@0: ssize_t usedLength; jens@0: BLIPProperties *copy = [BLIPProperties propertiesWithEncodedData: self.encodedData usedLength: &usedLength]; jens@0: Assert(copy); jens@0: return [copy retain]; jens@0: } jens@0: jens@0: jens@0: - (NSString*) valueOfProperty: (NSString*)prop jens@0: { jens@0: return [_properties objectForKey: prop]; jens@0: } jens@0: jens@0: - (NSDictionary*) allProperties jens@0: { jens@0: return _properties; jens@0: } jens@0: jens@0: - (NSUInteger) count {return _properties.count;} jens@0: jens@0: jens@0: static void appendStr( NSMutableData *data, NSString *str ) { jens@0: const char *utf8 = [str UTF8String]; jens@0: size_t size = strlen(utf8)+1; jens@0: for( int i=0; i 0xFFFF ) jens@0: return nil; jens@8: *(UInt16*)[data mutableBytes] = NSSwapHostShortToBig((UInt16)length); jens@0: return data; jens@0: } jens@0: jens@0: jens@0: - (void) setValue: (NSString*)value ofProperty: (NSString*)prop jens@0: { jens@0: Assert(prop.length>0); jens@0: if( value ) jens@0: [_properties setObject: value forKey: prop]; jens@0: else jens@0: [_properties removeObjectForKey: prop]; jens@0: } jens@0: jens@0: jens@0: - (void) setAllProperties: (NSDictionary*)properties jens@0: { jens@0: if( properties.count ) { jens@0: for( id key in properties ) { jens@0: Assert([key isKindOfClass: [NSString class]]); jens@0: Assert([key length] > 0); jens@0: Assert([[properties objectForKey: key] isKindOfClass: [NSString class]]); jens@0: } jens@0: [_properties setDictionary: properties]; jens@0: } else jens@0: [_properties removeAllObjects]; jens@0: } jens@0: jens@0: jens@0: @end jens@0: jens@0: jens@0: jens@0: jens@0: TestCase(BLIPProperties) { jens@0: BLIPProperties *props; jens@0: jens@0: props = [BLIPProperties properties]; jens@0: CAssert(props); jens@0: CAssertEq(props.count,0); jens@0: Log(@"Empty properties:\n%@", props.allProperties); jens@0: NSData *data = props.encodedData; jens@0: Log(@"As data: %@", data); jens@0: CAssertEqual(data,[NSMutableData dataWithLength: 2]); jens@0: jens@0: BLIPMutableProperties *mprops = [props mutableCopy]; jens@0: Log(@"Mutable copy:\n%@", mprops.allProperties); jens@0: data = mprops.encodedData; jens@0: Log(@"As data: %@", data); jens@0: CAssertEqual(data,[NSMutableData dataWithLength: 2]); jens@0: jens@0: ssize_t used; jens@0: props = [BLIPProperties propertiesWithEncodedData: data usedLength: &used]; jens@0: CAssertEq(used,data.length); jens@0: CAssertEqual(props,mprops); jens@0: jens@0: [mprops setValue: @"Jens" ofProperty: @"First-Name"]; jens@0: [mprops setValue: @"Alfke" ofProperty: @"Last-Name"]; jens@0: [mprops setValue: @"" ofProperty: @"Empty-String"]; jens@0: [mprops setValue: @"Z" ofProperty: @"A"]; jens@0: Log(@"With properties:\n%@", mprops.allProperties); jens@0: data = mprops.encodedData; jens@0: Log(@"As data: %@", data); jens@0: jens@0: for( unsigned len=0; len. All rights reserved. jens@0: jens@0: Redistribution and use in source and binary forms, with or without modification, are permitted jens@0: provided that the following conditions are met: jens@0: jens@0: * Redistributions of source code must retain the above copyright notice, this list of conditions jens@0: and the following disclaimer. jens@0: * Redistributions in binary form must reproduce the above copyright notice, this list of conditions jens@0: and the following disclaimer in the documentation and/or other materials provided with the jens@0: distribution. jens@0: jens@0: THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR jens@0: IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND jens@0: FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI- jens@0: BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES jens@0: (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR jens@0: PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN jens@0: CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF jens@0: THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. jens@0: */