diff --git a/MessagePackPacker.m b/MessagePackPacker.m index 28af09b..af26222 100644 --- a/MessagePackPacker.m +++ b/MessagePackPacker.m @@ -53,7 +53,7 @@ + (void)packNumber:(NSNumber*)num into:(msgpack_packer*)pk { } break; default: - NSLog(@"Could not messagepack number, cannot recognise type: %@", num); + [NSException raise:@"Failed to messagepack number" format:@"Cannot recognize type: %@", num]; } } @@ -75,12 +75,17 @@ + (void)packObject:(id)obj into:(msgpack_packer*)pk { int len = strlen(str); msgpack_pack_raw(pk, len); msgpack_pack_raw_body(pk, str, len); + } else if ([obj isKindOfClass:[NSData class]]) { + const void *bytes = [((NSData *)obj) bytes]; + int len = [((NSData *)obj) length]; + msgpack_pack_raw(pk, len); + msgpack_pack_raw_body(pk, bytes, len); } else if ([obj isKindOfClass:[NSNumber class]]) { [self packNumber:obj into:pk]; } else if (obj==[NSNull null]) { msgpack_pack_nil(pk); } else { - NSLog(@"Could not messagepack object: %@", obj); + [NSException raise:@"Failed to messagepack object" format:@"Object was: %@", obj]; } } diff --git a/MessagePackParser+Streaming.h b/MessagePackParser+Streaming.h index 7d679a0..7388727 100644 --- a/MessagePackParser+Streaming.h +++ b/MessagePackParser+Streaming.h @@ -14,6 +14,7 @@ - (id)init; - (id)initWithBufferSize:(int)bufferSize; - (void)feed:(NSData*)rawData; +- (id)nextWith:(MPRawHandling)rawHandling; - (id)next; @end diff --git a/MessagePackParser+Streaming.m b/MessagePackParser+Streaming.m index 58441ea..086b3e1 100644 --- a/MessagePackParser+Streaming.m +++ b/MessagePackParser+Streaming.m @@ -12,7 +12,7 @@ @interface MessagePackParser () // Implemented in MessagePackParser.m -+(id) createUnpackedObject:(msgpack_object)obj; ++(id) createUnpackedObject:(msgpack_object)obj rawHandling:(MPRawHandling)rawHandling; @end @implementation MessagePackParser (Streaming) @@ -36,13 +36,13 @@ - (void)feed:(NSData*)chunk { } // Put next parsed messagepack data. If there is not sufficient data, return nil. -- (id)next { +- (id)nextWith:(MPRawHandling) rawHandling { id unpackedObject; msgpack_unpacked result; msgpack_unpacked_init(&result); if (msgpack_unpacker_next(&unpacker, &result)) { msgpack_object obj = result.data; - unpackedObject = [MessagePackParser createUnpackedObject:obj]; + unpackedObject = [MessagePackParser createUnpackedObject:obj rawHandling:rawHandling]; } msgpack_unpacked_destroy(&result); @@ -53,4 +53,8 @@ - (id)next { #endif } +- (id)next { + return [self nextWith:MPRawsAsNSString_ExceptionOnFail]; +} + @end diff --git a/MessagePackParser.h b/MessagePackParser.h index 5aede5c..9d48725 100644 --- a/MessagePackParser.h +++ b/MessagePackParser.h @@ -9,11 +9,33 @@ #import #include "msgpack_src/msgpack.h" +typedef enum +{ + MPRawsAsNSString_NSNullOnFail, + MPRawsAsNSString_NSDataOnFail, + MPRawsAsNSString_ExceptionOnFail, + MPRawsAsNSData, +} MPRawHandling; + + @interface MessagePackParser : NSObject { // This is only for MessagePackParser+Streaming category. msgpack_unpacker unpacker; } +//parse the data into an NSObject. handle raw bytes as specified: +// - MPRawsAsNSString_ExceptionOnFail: try to decode the bytes with utf8. +// if the decoding fails, raise an exception +// - MPRawsAsNSString_NSNullOnFail: try to decode the bytes with utf8. +// if the decoding fails, put an NSNull in that part of the message. +// - MPRawsAsNSString_NSDataOnFail: try to decode the bytes with utf8. +// if the decoding fails, put an NSData in that part of the message. +// - MPRawsAsNSData: always leave bytes as they are, leaving them as +// NSDatas. + ++ (id)parseData:(NSData*)data rawHandling:(MPRawHandling)rawHandling; + +//parse the data into an NSObject, handling raws with MPRawsAsNSString_ExceptionOnFail + (id)parseData:(NSData*)data; @end diff --git a/MessagePackParser.m b/MessagePackParser.m index 61078cc..c73891f 100644 --- a/MessagePackParser.m +++ b/MessagePackParser.m @@ -11,29 +11,49 @@ @implementation MessagePackParser // This function returns a parsed object that you have the responsibility to release/autorelease (see 'create rule' in apple docs) -+(id) createUnpackedObject:(msgpack_object)obj { ++(id) createUnpackedObject:(msgpack_object)obj rawHandling:(MPRawHandling)rawHandling { switch (obj.type) { case MSGPACK_OBJECT_BOOLEAN: return [[NSNumber alloc] initWithBool:obj.via.boolean]; - break; case MSGPACK_OBJECT_POSITIVE_INTEGER: return [[NSNumber alloc] initWithUnsignedLongLong:obj.via.u64]; - break; case MSGPACK_OBJECT_NEGATIVE_INTEGER: return [[NSNumber alloc] initWithLongLong:obj.via.i64]; - break; case MSGPACK_OBJECT_DOUBLE: return [[NSNumber alloc] initWithDouble:obj.via.dec]; - break; case MSGPACK_OBJECT_RAW: - return [[NSString alloc] initWithBytes:obj.via.raw.ptr length:obj.via.raw.size encoding:NSUTF8StringEncoding]; - break; + { + if (rawHandling == MPRawsAsNSData) { + return [[NSData alloc] initWithBytes:obj.via.raw.ptr length:obj.via.raw.size]; + } + + NSString *res = [[NSString alloc] initWithBytes:obj.via.raw.ptr + length:obj.via.raw.size + encoding:NSUTF8StringEncoding]; + if (res) { + return res; + } + + switch (rawHandling) { + case MPRawsAsNSString_NSNullOnFail: + return [NSNull null]; + case MPRawsAsNSString_ExceptionOnFail: + [NSException raise:@"Invalid string encountered" + format:@"Raw bytes did not decode into utf8"]; + return res; + case MPRawsAsNSString_NSDataOnFail: { + return [[NSData alloc] initWithBytes:obj.via.raw.ptr length:obj.via.raw.size]; + case MPRawsAsNSData: //suppress compiler warning + return res; + } + } + } case MSGPACK_OBJECT_ARRAY: { NSMutableArray *arr = [[NSMutableArray alloc] initWithCapacity:obj.via.array.size]; msgpack_object* const pend = obj.via.array.ptr + obj.via.array.size; for(msgpack_object *p= obj.via.array.ptr;p < pend;p++){ - id newArrayItem = [self createUnpackedObject:*p]; + id newArrayItem = [self createUnpackedObject:*p rawHandling:rawHandling]; [arr addObject:newArrayItem]; #if !__has_feature(objc_arc) [newArrayItem release]; @@ -41,14 +61,13 @@ +(id) createUnpackedObject:(msgpack_object)obj { } return arr; } - break; case MSGPACK_OBJECT_MAP: { NSMutableDictionary *dict = [[NSMutableDictionary alloc] initWithCapacity:obj.via.map.size]; msgpack_object_kv* const pend = obj.via.map.ptr + obj.via.map.size; for(msgpack_object_kv* p = obj.via.map.ptr; p < pend; p++){ - id key = [self createUnpackedObject:p->key]; - id val = [self createUnpackedObject:p->val]; + id key = [self createUnpackedObject:p->key rawHandling:rawHandling]; + id val = [self createUnpackedObject:p->val rawHandling:rawHandling]; [dict setValue:val forKey:key]; #if !__has_feature(objc_arc) [key release]; @@ -57,26 +76,32 @@ +(id) createUnpackedObject:(msgpack_object)obj { } return dict; } - break; case MSGPACK_OBJECT_NIL: - default: return [NSNull null]; // Since nsnull is a system singleton, we don't have to worry about ownership of it - break; + default: + [NSException raise:@"Unsupported object type" + format:@"Unrecognized msgpack object type %d", obj.type]; + return [NSNull null]; // suppress compiler warning } } -// Parse the given messagepack data into a NSDictionary or NSArray typically -+ (id)parseData:(NSData*)data { ++ (id)parseData:(NSData*)data rawHandling:(MPRawHandling)rawHandling { msgpack_unpacked msg; msgpack_unpacked_init(&msg); bool success = msgpack_unpack_next(&msg, data.bytes, data.length, NULL); // Parse it into C-land - id results = success ? [self createUnpackedObject:msg.data] : nil; // Convert from C-land to Obj-c-land + // Convert from C-land to Obj-c-land + id results = success ? [self createUnpackedObject:msg.data rawHandling:rawHandling] : nil; msgpack_unpacked_destroy(&msg); // Free the parser #if !__has_feature(objc_arc) return [results autorelease]; #else return results; -#endif +#endif +} + +// Parse the given messagepack data into a NSDictionary or NSArray typically ++ (id)parseData:(NSData*)data { + return [self parseData:data rawHandling:MPRawsAsNSString_ExceptionOnFail]; } @end diff --git a/NSData+MessagePack.h b/NSData+MessagePack.h index 41e2fe5..843d507 100644 --- a/NSData+MessagePack.h +++ b/NSData+MessagePack.h @@ -7,11 +7,25 @@ // #import +#import "MessagePackParser.h" // Adds MessagePack parsing to NSData @interface NSData (NSData_MessagePack) -// Parses the receiver's data into a message pack array or dictionary +// **Packs** the receiver's data into message pack data +- (NSData*)messagePack; + +// Parses the receiver's data into a message pack array or dictionary, +// decoding raw bytes into utf8 strings - (id)messagePackParse; +// Parses the receiver's data into a message pack array or dictionary, +// without decoding raw bytes into utf8 strings +- (id)messagePackParseWith:(MPRawHandling)rawHandling; + +// If obj is NSData, return obj. If obj is NSString, return +// NSData of the utf8-encoded bytes. Otherwise, raise an exception. +// useful when using MPRawsAsNSString_NSDataOnFail ++ (NSData *)expectData:(id) obj; + @end diff --git a/NSData+MessagePack.m b/NSData+MessagePack.m index 2b08ce8..5695830 100644 --- a/NSData+MessagePack.m +++ b/NSData+MessagePack.m @@ -7,13 +7,35 @@ // #import "NSData+MessagePack.h" - +#import "MessagePackPacker.h" #import "MessagePackParser.h" @implementation NSData (NSData_MessagePack) +- (NSData*)messagePack { + return [MessagePackPacker pack:self]; +} + -(id)messagePackParse { - return [MessagePackParser parseData:self]; + return [MessagePackParser parseData:self]; +} + +- (id)messagePackParseWith:(MPRawHandling)rawHandling { + return [MessagePackParser parseData:self rawHandling:rawHandling]; +} + ++ (NSData *)expectData:(id) dataOrString { + if ([dataOrString isKindOfClass:[NSData class]]) { + return dataOrString; + } + else if ([dataOrString isKindOfClass:[NSString class]]) { + return [dataOrString dataUsingEncoding:NSUTF8StringEncoding]; + } + else { + [NSException raise:@"Unexpected object in message" + format:@"Expected data or string, not %@", dataOrString]; + return nil; //suppress warning + } } @end diff --git a/readme.md b/readme.md index 29780c4..a4df5c8 100644 --- a/readme.md +++ b/readme.md @@ -2,7 +2,7 @@ MessagePack for Objective-C / iPhone ============ This is a wrapper for the C MessagePack parser, building the bridge to Objective-C. -In a similar way to the JSON framework, this parses MessagePack into NSDictionaries, NSArrays, NSNumbers, NSStrings, and NSNulls. +In a similar way to the JSON framework, this parses MessagePack into NSDictionaries, NSArrays, NSNumbers, NSStrings, NSDatas and NSNulls. This contains a small patch to the C library so that it doesn't segfault with a byte alignment error when running on the iPhone in armv7 mode. Please note that the parser has been extensively tested, however the packer has not been. Please get in touch if it has issues. @@ -15,6 +15,29 @@ Parsing Usage NSDictionary* parsed = [myData messagePackParse]; NSLog(@"%@", [parsed description]); +Handling Raw Data +----- + +When using `messagePackParse`, bytes are decoded with utf8 and parsed into `NSString`s, and an exception is raised if that encoding is invalid. This behavior can be changed as follows: + + NSData* myData = ... + + //try to decode, parse to NSData of the original bytes on fail + NSDictionary *parsed = [myData messagePackParseWith:MPRawsAsNSString_NSDataOnFail]; + + //try to decode raw bytes into utf8 strings, parse to NSNull on fail + NSDictionary *parsed = [myData messagePackParseWith:MPRawsAsNSString_NSNullOnFail]; + + //always parse to NSData + NSDictionary *parsed = [myData messagePackParseWith:MPRawsAsNSData]; + + //(same as `messagePackParse`): try to decode, raise an exception on fail + NSDictionary *parsed = [myData messagePackParseWith:MPRawsAsNSString_ExceptionOnFail]; + + //if using MPRawsAsNSString_NSDataOnFail, NSData+MessagePack.h provides a useful + //helper function when you expect bytes, just in case they were valid utf8 bytes. + NSData *data = [NSData expectData:[parsed objectForKey:@"bytes"]]; + Packing Usage ---- @@ -22,6 +45,7 @@ Packing Usage .. NSData* packed = [someArray messagePack]; NSData* packed = [someDictionary messagePack]; + NSData* packed = [someData messagePack]; Authors ------- @@ -29,6 +53,7 @@ Authors * Sugendran Ganess * Chris Hulbert * Bugfixes by Matzo: https://github.com/Matzo +* NSData handling by csaftoiu: https://github.com/csaftoiu License -------