Using name abbreviation in Objective-C is discouraged as it stated in the following link: https://developer.apple.com/library/prerelease/ios/documentation/Cocoa/Reference/Foundation/Classes/NSTimeZone_Class/index.html#//apple_ref/occ/clm/NSTimeZone/timeZoneWithAbbreviation: Indeed, the GNUStep library is generating the name abbreviation using the system tzdata information but parsing this data using GNUStep resources making changes like this: http://mm.icann.org/pipermail/tz-announce/2014-August/000023.html Not working until GNUStep reaches these tzdata changes. Using the UTC offset we are losing the position in planet Earth but it could be considered valid looking at other bson implementations.
638 lines
16 KiB
Objective-C
638 lines
16 KiB
Objective-C
/*
|
|
* BSONCodec.m
|
|
* BSON Codec for Objective-C.
|
|
*
|
|
* Created by Martin Kou on 8/17/10.
|
|
* MIT License, see LICENSE file for details.
|
|
*
|
|
* Adapted by Ludovic Marcotte and Enrique J. Hernández
|
|
*/
|
|
|
|
#import "BSONCodec.h"
|
|
#import <ctype.h>
|
|
#import <string.h>
|
|
#import <objc/objc.h>
|
|
|
|
#import <NGExtensions/NSObject+Logs.h>
|
|
|
|
static NSMutableDictionary *timezoneCache = nil;
|
|
|
|
#define BSONTYPE(tag,className) [className class], [NSNumber numberWithChar: (tag)]
|
|
|
|
#ifndef objc_msgSend
|
|
#define objc_msgSend(obj, sel, ...) \
|
|
objc_msg_lookup(obj, sel)(obj, sel, ## __VA_ARGS__)
|
|
#endif
|
|
|
|
static NSDictionary *BSONTypes()
|
|
{
|
|
static NSDictionary *retval = nil;
|
|
|
|
if (retval == nil)
|
|
{
|
|
retval = [[NSDictionary dictionaryWithObjectsAndKeys:
|
|
BSONTYPE(0x01, NSNumber),
|
|
BSONTYPE(0x02, NSString),
|
|
BSONTYPE(0x03, NSDictionary),
|
|
BSONTYPE(0x04, NSArray),
|
|
BSONTYPE(0x05, NSData),
|
|
BSONTYPE(0x08, NSNumber),
|
|
BSONTYPE(0x0A, NSNull),
|
|
BSONTYPE(0x10, NSNumber),
|
|
BSONTYPE(0x11, NSCalendarDate),
|
|
BSONTYPE(0x12, NSNumber),
|
|
nil] retain];
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
|
|
#define SWAP16(x) \
|
|
((uint16_t)((((uint16_t)(x) & 0xff00) >> 8) | \
|
|
(((uint16_t)(x) & 0x00ff) << 8)))
|
|
|
|
#define SWAP32(x) \
|
|
((uint32_t)((((uint32_t)(x) & 0xff000000) >> 24) | \
|
|
(((uint32_t)(x) & 0x00ff0000) >> 8) | \
|
|
(((uint32_t)(x) & 0x0000ff00) << 8) | \
|
|
(((uint32_t)(x) & 0x000000ff) << 24)))
|
|
|
|
#define SWAP64(x) \
|
|
((uint64_t)((((uint64_t)(x) & 0xff00000000000000ULL) >> 56) | \
|
|
(((uint64_t)(x) & 0x00ff000000000000ULL) >> 40) | \
|
|
(((uint64_t)(x) & 0x0000ff0000000000ULL) >> 24) | \
|
|
(((uint64_t)(x) & 0x000000ff00000000ULL) >> 8) | \
|
|
(((uint64_t)(x) & 0x00000000ff000000ULL) << 8) | \
|
|
(((uint64_t)(x) & 0x0000000000ff0000ULL) << 24) | \
|
|
(((uint64_t)(x) & 0x000000000000ff00ULL) << 40) | \
|
|
(((uint64_t)(x) & 0x00000000000000ffULL) << 56)))
|
|
|
|
|
|
#if BYTE_ORDER == LITTLE_ENDIAN
|
|
#define BSONTOHOST16(x) (x)
|
|
#define BSONTOHOST32(x) (x)
|
|
#define BSONTOHOST64(x) (x)
|
|
#define HOSTTOBSON16(x) (x)
|
|
#define HOSTTOBSON32(x) (x)
|
|
#define HOSTTOBSON64(x) (x)
|
|
|
|
#elif BYTE_ORDER == BIG_ENDIAN
|
|
#define BSONTOHOST16(x) SWAP16(x)
|
|
#define BSONTOHOST32(x) SWAP32(x)
|
|
#define BSONTOHOST64(x) SWAP64(x)
|
|
#define HOSTTOBSON16(x) SWAP16(x)
|
|
#define HOSTTOBSON32(x) SWAP16(x)
|
|
#define HOSTTOBSON64(x) SWAP16(x)
|
|
|
|
#endif
|
|
|
|
#define CLASS_NAME_MARKER @"$$__CLASS_NAME__$$"
|
|
|
|
@implementation NSObject (BSONObjectCoding)
|
|
- (NSData *) BSONEncode
|
|
{
|
|
if (![self conformsToProtocol: @protocol(BSONObjectCoding)])
|
|
[NSException raise: NSInvalidArgumentException format: @"BSON encoding is only valid on objects conforming to the BSONObjectEncoding protocol."];
|
|
|
|
id <BSONObjectCoding> myself = (id <BSONObjectCoding>) self;
|
|
NSMutableDictionary *values = [[myself BSONDictionary] mutableCopy];
|
|
|
|
#if (defined(__GNU_LIBOBJC__) && (__GNU_LIBOBJC__ >= 20100911)) || defined(APPLE_RUNTIME) || defined(__GNUSTEP_RUNTIME__)
|
|
const char* className = class_getName([self class]);
|
|
#else
|
|
const char* className = [self class]->name;
|
|
#endif
|
|
[values setObject: [NSData dataWithBytes: (void *)className length: strlen(className)] forKey: CLASS_NAME_MARKER];
|
|
NSData *retval = [values BSONEncode];
|
|
[values release];
|
|
|
|
return retval;
|
|
}
|
|
|
|
- (NSData *) BSONRepresentation
|
|
{
|
|
return [self BSONEncode];
|
|
}
|
|
@end
|
|
|
|
|
|
@implementation NSDictionary (BSON)
|
|
|
|
- (uint8_t) BSONTypeID
|
|
{
|
|
return 0x03;
|
|
}
|
|
|
|
- (NSData *) BSONEncode
|
|
{
|
|
// Initialize the components structure.
|
|
NSMutableArray *components = [[NSMutableArray alloc] init];
|
|
|
|
NSMutableData *lengthData = [[NSMutableData alloc] initWithLength: 4];
|
|
[components addObject: lengthData];
|
|
[lengthData release];
|
|
|
|
NSMutableData *contentsData = [[NSMutableData alloc] init];
|
|
[components addObject: contentsData];
|
|
[contentsData release];
|
|
|
|
[components addObject: [NSData dataWithBytes: "\x00" length: 1]];
|
|
|
|
// Ensure ordered keys. not in BSON spec, but ensures all BSONRepresentations
|
|
// of the same dict will be the same.
|
|
NSMutableArray *keys = [[NSMutableArray alloc] init];
|
|
[keys addObjectsFromArray: [self allKeys]];
|
|
//[keys sortUsingSelector: @selector(caseInsensitiveCompare:)];
|
|
|
|
// Encode data.- (NSData *) BSONEncode;
|
|
uint8_t elementType = 0;
|
|
int i;
|
|
|
|
for (i = 0; i < [keys count]; i++)
|
|
{
|
|
NSString *key = [keys objectAtIndex: i];
|
|
NSObject *value = [self objectForKey: key];
|
|
|
|
if ([value respondsToSelector: @selector(BSONTypeID)])
|
|
elementType = [(id <BSONCoding>) value BSONTypeID];
|
|
else
|
|
elementType = 3;
|
|
|
|
[contentsData appendBytes: &elementType length: 1];
|
|
[contentsData appendData: [key dataUsingEncoding: NSUTF8StringEncoding]];
|
|
[contentsData appendBytes: "\x00" length: 1];
|
|
[contentsData appendData: [value BSONEncode]];
|
|
}
|
|
[keys release];
|
|
|
|
// Write length.
|
|
uint32_t *length = (uint32_t *)[lengthData mutableBytes];
|
|
*length = HOSTTOBSON32([contentsData length]) + 4 + 1;
|
|
|
|
// Assemble the output data.
|
|
NSMutableData *retval = [NSMutableData data];
|
|
for (i = 0; i < [components count]; i++)
|
|
[retval appendData: [components objectAtIndex: i]];
|
|
[components release];
|
|
|
|
return retval;
|
|
}
|
|
|
|
- (NSData *) BSONRepresentation
|
|
{
|
|
return [self BSONEncode];
|
|
}
|
|
|
|
+ (id) BSONFragment: (NSData *) data at: (const void **) base ofType: (uint8_t) t
|
|
{
|
|
const void *current = [data bytes];
|
|
if ((id)base != nil)
|
|
current = *base;
|
|
else
|
|
base = ¤t;
|
|
|
|
uint32_t length = BSONTOHOST32(*((uint32_t *)current));
|
|
const void *endPoint = current + length;
|
|
current += 4;
|
|
|
|
NSMutableDictionary *retval = [NSMutableDictionary dictionary];
|
|
while (current < endPoint - 1)
|
|
{
|
|
uint8_t typeID = *((uint8_t *)current);
|
|
current++;
|
|
|
|
char *utf8Key = (char *) current;
|
|
while (*((char *)current) != 0 && current < endPoint - 1)
|
|
current++;
|
|
current++;
|
|
NSString *key = [NSString stringWithUTF8String: utf8Key];
|
|
|
|
*base = current;
|
|
Class typeClass = [BSONTypes() objectForKey: [NSNumber numberWithChar: typeID]];
|
|
id value = objc_msgSend(typeClass, @selector(BSONFragment:at:ofType:), data, base, typeID);
|
|
current = *base;
|
|
|
|
[retval setObject: value forKey: key];
|
|
}
|
|
|
|
*base = current + 1;
|
|
|
|
// If the dictionary has a class name marker, then it is to be converted to an object.
|
|
if ([retval objectForKey: CLASS_NAME_MARKER] != nil)
|
|
{
|
|
NSData *classNameData = [retval objectForKey: CLASS_NAME_MARKER];
|
|
char *className = malloc([classNameData length] + 1);
|
|
memcpy(className, [classNameData bytes], [classNameData length]);
|
|
className[[classNameData length]] = 0;
|
|
|
|
Class targetClass = objc_getClass(className);
|
|
if (targetClass == nil)
|
|
[NSException raise: NSInvalidArgumentException format: @"Class %s found in incoming data is undefined.", className];
|
|
|
|
id obj = [[targetClass alloc] initWithBSONDictionary: retval];
|
|
return obj;
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
@end
|
|
|
|
@implementation NSData (BSON)
|
|
- (uint8_t) BSONTypeID
|
|
{
|
|
return 0x05;
|
|
}
|
|
|
|
- (NSData *) BSONEncode
|
|
{
|
|
uint32_t length = HOSTTOBSON32([self length]);
|
|
NSMutableData *retval = [NSMutableData data];
|
|
[retval appendBytes: &length length: 4];
|
|
[retval appendBytes: "\x00" length: 1];
|
|
[retval appendData: self];
|
|
|
|
return retval;
|
|
}
|
|
|
|
- (NSData *) BSONRepresentation
|
|
{
|
|
return [self BSONEncode];
|
|
}
|
|
|
|
+ (id) BSONFragment: (NSData *) data at: (const void **) base ofType: (uint8_t) t
|
|
{
|
|
const void *current = [data bytes];
|
|
if ((id)base != nil)
|
|
current = *base;
|
|
else
|
|
base = ¤t;
|
|
|
|
uint32_t length = BSONTOHOST32(*((uint32_t *)current));
|
|
current += 4 + 1;
|
|
|
|
NSData *retval = [NSData dataWithBytes: current length: length];
|
|
current += length;
|
|
*base = current;
|
|
return retval;
|
|
}
|
|
|
|
- (NSDictionary *) BSONValue
|
|
{
|
|
return [NSDictionary BSONFragment: self at: NULL ofType: 0x03];
|
|
}
|
|
@end
|
|
|
|
@implementation NSNumber (BSON)
|
|
- (uint8_t) BSONTypeID
|
|
{
|
|
const char encoding = tolower(*([self objCType]));
|
|
|
|
switch (encoding) {
|
|
case 'f':
|
|
case 'd': return 0x01;
|
|
case 'b': return 0x08;
|
|
case 'c':
|
|
case 's': return 0x10;
|
|
case 'i':
|
|
// Ok, if you're running Objective-C on 16-bit platforms...
|
|
// Then YOU have issues.
|
|
// So, yeah, we won't handle that case.
|
|
if (sizeof(int) == 4)
|
|
return 0x10;
|
|
else if (sizeof(int) == 8)
|
|
return 0x12;
|
|
|
|
case 'l':
|
|
if (sizeof(long) == 4)
|
|
return 0x10;
|
|
else if (sizeof(long) == 8)
|
|
return 0x12;
|
|
|
|
case 'q': return 0x12;
|
|
default:
|
|
[NSException raise: NSInvalidArgumentException format: @"%@::%s - invalid encoding type '%c'", [self class], _cmd, encoding];
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
- (NSData *) BSONEncode
|
|
{
|
|
const char encoding = *([self objCType]);
|
|
|
|
if (encoding == 'd' || encoding == 'D')
|
|
{
|
|
double value = [self doubleValue];
|
|
return [NSData dataWithBytes: &value length: 8];
|
|
}
|
|
|
|
if (encoding == 'f' || encoding == 'F')
|
|
{
|
|
double value = [self floatValue];
|
|
return [NSData dataWithBytes: &value length: 8];
|
|
}
|
|
|
|
if (encoding == 'b' || encoding == 'B')
|
|
{
|
|
char value = [self boolValue];
|
|
return [NSData dataWithBytes: &value length: 1];
|
|
}
|
|
|
|
if (encoding == 'c' || encoding == 'C')
|
|
{
|
|
int32_t value = [self charValue];
|
|
value = HOSTTOBSON32(value);
|
|
return [NSData dataWithBytes: &value length: 4];
|
|
}
|
|
|
|
if (encoding == 's' || encoding == 'S')
|
|
{
|
|
int32_t value = [self shortValue];
|
|
value = HOSTTOBSON32(value);
|
|
return [NSData dataWithBytes: &value length: 4];
|
|
}
|
|
|
|
if (encoding == 'i' || encoding == 'I')
|
|
{
|
|
int value = [self intValue];
|
|
if (sizeof(int) == 4)
|
|
value = HOSTTOBSON32(value);
|
|
else if (sizeof(int) == 8)
|
|
value = HOSTTOBSON64(value);
|
|
return [NSData dataWithBytes: &value length: sizeof(int)];
|
|
}
|
|
|
|
if (encoding == 'l' || encoding == 'L')
|
|
{
|
|
long value = [self longValue];
|
|
if (sizeof(long) == 4)
|
|
value = HOSTTOBSON32(value);
|
|
else if (sizeof(long) == 8)
|
|
value = HOSTTOBSON64(value);
|
|
|
|
return [NSData dataWithBytes: &value length: sizeof(long)];
|
|
}
|
|
|
|
if (encoding == 'q')
|
|
{
|
|
long long value = HOSTTOBSON64([self longLongValue]);
|
|
return [NSData dataWithBytes: &value length: 8];
|
|
}
|
|
|
|
if (encoding == 'Q')
|
|
{
|
|
long long value = HOSTTOBSON64([self unsignedLongLongValue]);
|
|
return [NSData dataWithBytes: &value length: 8];
|
|
}
|
|
|
|
|
|
[NSException raise: NSInvalidArgumentException format: @"%@::%s - invalid encoding type '%c'", [self class], _cmd, encoding];
|
|
return nil;
|
|
}
|
|
|
|
- (NSData *) BSONRepresentation
|
|
{
|
|
return [self BSONEncode];
|
|
}
|
|
|
|
+ (id) BSONFragment: (NSData *) data at: (const void **) base ofType: (uint8_t) t
|
|
{
|
|
if (t == 0x01)
|
|
{
|
|
// #5: LLVM GCC requires double pointers to have a certain alignment in ARM CPUs.
|
|
// So we can't just read the double off directly from the data - need to copy it.
|
|
double value;
|
|
memcpy(&value, *base, sizeof(double));
|
|
*base += 8;
|
|
return [NSNumber numberWithDouble: value];
|
|
}
|
|
|
|
if (t == 0x08)
|
|
{
|
|
char value = ((char *) *base)[0];
|
|
*base += 1;
|
|
return [NSNumber numberWithBool: value];
|
|
}
|
|
|
|
if (t == 0x10)
|
|
{
|
|
int32_t value = BSONTOHOST32(((int32_t *) *base)[0]);
|
|
*base += 4;
|
|
|
|
if (sizeof(int) == 4)
|
|
return [NSNumber numberWithInt: value];
|
|
|
|
return [NSNumber numberWithLong: value];
|
|
}
|
|
|
|
if (t == 0x12)
|
|
{
|
|
int64_t value = BSONTOHOST64(((int64_t *) *base)[0]);
|
|
*base += 8;
|
|
|
|
return [NSNumber numberWithUnsignedLongLong: value];
|
|
}
|
|
|
|
return nil;
|
|
}
|
|
@end
|
|
|
|
@implementation NSString (BSON)
|
|
- (uint8_t) BSONTypeID
|
|
{
|
|
return 0x02;
|
|
}
|
|
|
|
- (NSData *) BSONEncode
|
|
{
|
|
NSData *utf8Data = [self dataUsingEncoding: NSUTF8StringEncoding];
|
|
uint32_t length = HOSTTOBSON32([utf8Data length] + 1);
|
|
|
|
NSMutableData *retval = [NSMutableData data];
|
|
[retval appendBytes: &length length: 4];
|
|
[retval appendData: utf8Data];
|
|
[retval appendBytes: "\x00" length: 1];
|
|
return retval;
|
|
}
|
|
|
|
- (NSData *) BSONRepresentation
|
|
{
|
|
return [self BSONEncode];
|
|
}
|
|
|
|
+ (id) BSONFragment: (NSData *) data at: (const void **) base ofType: (uint8_t) typeID
|
|
{
|
|
uint32_t length = BSONTOHOST32(((const uint32_t *) *base)[0]);
|
|
*base += 4;
|
|
|
|
const char *utf8Str = (const char *) *base;
|
|
*base += length;
|
|
|
|
return [NSString stringWithUTF8String: utf8Str];
|
|
}
|
|
@end
|
|
|
|
@implementation NSArray (BSON)
|
|
- (uint8_t) BSONTypeID
|
|
{
|
|
return 0x04;
|
|
}
|
|
|
|
- (NSData *) BSONEncode
|
|
{
|
|
// Initialize the components structure.
|
|
NSMutableArray *components = [[NSMutableArray alloc] init];
|
|
|
|
NSMutableData *lengthData = [[NSMutableData alloc] initWithLength: 4];
|
|
[components addObject: lengthData];
|
|
[lengthData release];
|
|
|
|
NSMutableData *contentsData = [[NSMutableData alloc] init];
|
|
[components addObject: contentsData];
|
|
[contentsData release];
|
|
|
|
[components addObject: [NSData dataWithBytes: "\x00" length: 1]];
|
|
|
|
// Encode data.
|
|
uint8_t elementType = 0;
|
|
int i, count = [self count];
|
|
for (i = 0 ; i < count ; i++)
|
|
{
|
|
NSObject *value = [self objectAtIndex: i];
|
|
|
|
if ([value respondsToSelector: @selector(BSONTypeID)])
|
|
elementType = [(id <BSONCoding>) value BSONTypeID];
|
|
else
|
|
elementType = 3;
|
|
|
|
[contentsData appendBytes: &elementType length: 1];
|
|
[contentsData appendData: [[NSString stringWithFormat: @"%d", i] dataUsingEncoding: NSUTF8StringEncoding]];
|
|
[contentsData appendBytes: "\x00" length: 1];
|
|
[contentsData appendData: [value BSONEncode]];
|
|
}
|
|
|
|
// Write length.
|
|
uint32_t *length = (uint32_t *)[lengthData mutableBytes];
|
|
*length = HOSTTOBSON32([contentsData length]) + 4 + 1;
|
|
|
|
// Assemble the output data.
|
|
NSMutableData *retval = [NSMutableData data];
|
|
for (i = 0; i < [components count]; i++)
|
|
[retval appendData: [components objectAtIndex: i]];
|
|
[components release];
|
|
|
|
return retval;
|
|
}
|
|
|
|
- (NSData *) BSONRepresentation
|
|
{
|
|
return [self BSONEncode];
|
|
}
|
|
|
|
+ (id) BSONFragment: (NSData *) data at: (const void **) base ofType: (uint8_t) typeID
|
|
{
|
|
NSDictionary *tmp = [NSDictionary BSONFragment: data at: base ofType: 0x03];
|
|
NSMutableArray *retval = [NSMutableArray arrayWithCapacity: [tmp count]];
|
|
int i;
|
|
for (i = 0; i < [tmp count]; i++)
|
|
[retval addObject: [tmp objectForKey: [NSString stringWithFormat: @"%d", i]]];
|
|
|
|
return retval;
|
|
}
|
|
@end
|
|
|
|
@implementation NSNull (BSON)
|
|
- (uint8_t) BSONTypeID
|
|
{
|
|
return 0x0a;
|
|
}
|
|
|
|
- (NSData *) BSONEncode
|
|
{
|
|
return [NSData data];
|
|
}
|
|
|
|
- (NSData *) BSONRepresentation
|
|
{
|
|
return [self BSONEncode];
|
|
}
|
|
|
|
+ (id) BSONFragment: (NSData *) data at: (const void **) base ofType: (uint8_t) typeID
|
|
{
|
|
return [NSNull null];
|
|
}
|
|
@end
|
|
|
|
@implementation NSCalendarDate (BSON)
|
|
- (uint8_t) BSONTypeID
|
|
{
|
|
return 0x11;
|
|
}
|
|
|
|
- (NSData *) BSONEncode
|
|
{
|
|
NSString *v;
|
|
|
|
v = [self descriptionWithCalendarFormat: @"%Y-%m-%d %H:%M:%S %z"
|
|
locale: nil];
|
|
|
|
return [v BSONEncode];
|
|
}
|
|
|
|
- (NSData *) BSONRepresentation
|
|
{
|
|
return [self BSONEncode];
|
|
}
|
|
|
|
+ (id) BSONFragment: (NSData *) data at: (const void **) base ofType: (uint8_t) typeID
|
|
{
|
|
NSTimeZone *tz;
|
|
NSString *key;
|
|
|
|
unsigned int year, month, day, hour, minute, second;
|
|
char timezone[64];
|
|
const char *v;
|
|
|
|
if (!timezoneCache)
|
|
timezoneCache = [[NSMutableDictionary alloc] init];
|
|
|
|
v = [[NSString BSONFragment: data at: base ofType: 0x02] cStringUsingEncoding: NSASCIIStringEncoding];
|
|
|
|
sscanf(v, "%d-%d-%d %d:%d:%d %s", &year, &month, &day, &hour, &minute, &second, timezone);
|
|
|
|
key = [NSString stringWithFormat: @"%s", timezone];
|
|
|
|
/* We may have the zone using the UTC offset
|
|
or abbreviation (deprecated) */
|
|
if (timezone && strlen(timezone) > 0 && (timezone[0] == '+' || timezone[0] == '-'))
|
|
{
|
|
NSCalendarDate *tzDate;
|
|
|
|
tzDate = [[NSCalendarDate alloc] initWithString: key
|
|
calendarFormat: @"%z"
|
|
locale: nil];
|
|
[tzDate autorelease];
|
|
if (tzDate)
|
|
tz = [tzDate timeZone];
|
|
else
|
|
tz = nil;
|
|
}
|
|
else if (!(tz = [timezoneCache objectForKey: key]))
|
|
{
|
|
tz = [NSTimeZone timeZoneWithAbbreviation: key];
|
|
|
|
if (tz)
|
|
[timezoneCache setObject: tz forKey: key];
|
|
else
|
|
[self errorWithFormat: @"BSON error: timezone (%@) not found when deserializing BSON data", key];
|
|
}
|
|
|
|
return [NSCalendarDate dateWithYear: year
|
|
month: month
|
|
day: day
|
|
hour: hour
|
|
minute: minute
|
|
second: second
|
|
timeZone: tz];
|
|
}
|
|
@end
|