Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support and options for parsing and packing NSData #5

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions MessagePackPacker.m
Original file line number Diff line number Diff line change
Expand Up @@ -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];
}
}

Expand All @@ -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];
}
}

Expand Down
1 change: 1 addition & 0 deletions MessagePackParser+Streaming.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
- (id)init;
- (id)initWithBufferSize:(int)bufferSize;
- (void)feed:(NSData*)rawData;
- (id)nextWith:(MPRawHandling)rawHandling;
- (id)next;

@end
10 changes: 7 additions & 3 deletions MessagePackParser+Streaming.m
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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);

Expand All @@ -53,4 +53,8 @@ - (id)next {
#endif
}

- (id)next {
return [self nextWith:MPRawsAsNSString_ExceptionOnFail];
}

@end
22 changes: 22 additions & 0 deletions MessagePackParser.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,33 @@
#import <Foundation/Foundation.h>
#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
61 changes: 43 additions & 18 deletions MessagePackParser.m
Original file line number Diff line number Diff line change
Expand Up @@ -11,44 +11,63 @@
@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];
#endif
}
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];
Expand All @@ -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
16 changes: 15 additions & 1 deletion NSData+MessagePack.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,25 @@
//

#import <Foundation/Foundation.h>
#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
26 changes: 24 additions & 2 deletions NSData+MessagePack.m
Original file line number Diff line number Diff line change
Expand Up @@ -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
27 changes: 26 additions & 1 deletion readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand All @@ -15,20 +15,45 @@ 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
----

#import "MessagePack.h"
..
NSData* packed = [someArray messagePack];
NSData* packed = [someDictionary messagePack];
NSData* packed = [someData messagePack];

Authors
-------

* Sugendran Ganess
* Chris Hulbert
* Bugfixes by Matzo: https://github.com/Matzo
* NSData handling by csaftoiu: https://github.com/csaftoiu

License
-------
Expand Down