Skip to content

Instantly share code, notes, and snippets.

@jonhyman
Last active April 17, 2018 18:32
Show Gist options
  • Save jonhyman/4149805 to your computer and use it in GitHub Desktop.
Save jonhyman/4149805 to your computer and use it in GitHub Desktop.
BSON Object Id generator in Objective-C
/*
# Copyright 2018 Jon Hyman
# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#import <Foundation/Foundation.h>
typedef union {
char bytes[12];
int ints[3];
} bson_oid_t;
@interface BSONIdGenerator : NSObject
+ (NSString *) generate;
@end
/*
# Copyright 2018 Jon Hyman
# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#import "BSONIdGenerator.h"
#import <CommonCrypto/CommonDigest.h> // Need to import for CC_MD5 access
@implementation BSONIdGenerator
static int _incr = 0;
/*!
* @discussion Generates a BSON Object Id. Code sampled from
* https://github.com/mongodb/mongo-c-driver/blob/master/src/bson.c and the BSON Object Id specification
* http://www.mongodb.org/display/DOCS/Object+IDs
*/
+ (NSString *) generate {
int i = _incr++;
bson_oid_t *oid = malloc(sizeof(bson_oid_t));
time_t t = time(NULL);
// Grab the PID
int pid = [NSProcessInfo processInfo].processIdentifier;
// Get a device identifier. The specification usually has this as the MAC address
// or hostname but we already have a unique device identifier.
//
// NOTE THAT UDID IS DEPRECATED. YOU SHOULD USE SOME OTHER IDENTIFIER HERE SUCH AS OPENUDID OR MAC ADDRESS.
NSString *identifier = [[UIDevice currentDevice] uniqueIdentifier];
// MD5 hash the device identifier
NSString *md5HashOfIdentifier = [self md5HashFromString:identifier];
const char *cIdentifier = [md5HashOfIdentifier cStringUsingEncoding:NSUTF8StringEncoding];
// Copy bytes over to our object id. Specification taken from http://www.mongodb.org/display/DOCS/Object+IDs
bson_swap_endian_len(&oid->bytes[0], &t, 4);
bson_swap_endian_len(&oid->bytes[4], &cIdentifier, 3);
bson_swap_endian_len(&oid->bytes[7], &pid, 2);
bson_swap_endian_len(&oid->bytes[9], &i, 3);
NSString *str = [self bson_oid_to_string:oid];
free(oid);
return str;
}
/*!
* @discussion Given an NSString, returns the MD5 hash of it. Taken from
* http://stackoverflow.com/questions/1524604/md5-algorithm-in-objective-c
* @param source The source string
* @return MD5 hash as a string
*/
+ (NSString *) md5HashFromString:(NSString *)source {
const char *cStr = [source UTF8String];
unsigned char result[16];
CC_MD5(cStr, strlen(cStr), result);
return [NSString stringWithFormat:
@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
result[0], result[1], result[2], result[3],
result[4], result[5], result[6], result[7],
result[8], result[9], result[10], result[11],
result[12], result[13], result[14], result[15]
];
}
/*!
* @discussion Converts a bson_oid_t to an NSString. Mostly taken from
* https://github.com/mongodb/mongo-c-driver/blob/master/src/bson.c
* @param oid The bson_oid_t to convert
* @return Autoreleased NSString of 24 hex characters
*/
+ (NSString *) bson_oid_to_string:(bson_oid_t *)oid {
char *str = malloc(sizeof(char) * 25);
static const char hex[16] = {'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'};
int i;
for ( i=0; i<12; i++ ) {
str[2*i] = hex[( oid->bytes[i] & 0xf0 ) >> 4];
str[2*i + 1] = hex[ oid->bytes[i] & 0x0f ];
}
str[24] = '\0';
NSString *string = [NSString stringWithCString:str encoding:NSUTF8StringEncoding];
free(str);
return string;
}
/*!
* @discussion The ARM architecture is little Endian while Intel Macs are big Endian,
* so we need to swap endianness if we're compiling on a big Endian architecture.
* @param outp The destination pointer
* @param inp The source pointer
* @param len The length to copy
*/
void bson_swap_endian_len(void *outp, const void *inp, int len) {
const char *in = (const char *)inp;
char *out = (char *)outp;
for (int i = 0; i < len; i ++) {
#if __DARWIN_BIG_ENDIAN
out[i] = in[len - 1 - i];
#elif __DARWIN_LITTLE_ENDIAN
out[i] = in[i];
#endif
}
}
@end
/*
# Copyright 2018 Jon Hyman
# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#import "BSONIdGeneratorTest.h"
#import "BSONIdGenerator.h"
@implementation BSONIdGeneratorTest
#pragma generate
- (void) testGenerateCreatesBsonIds {
NSString *oid = [BSONIdGenerator generate];
NSString *oid2 = [BSONIdGenerator generate];
NSString *oid3 = [BSONIdGenerator generate];
STAssertFalse([oid isEqualToString:oid2], nil);
STAssertFalse([oid isEqualToString:oid3], nil);
STAssertFalse([oid2 isEqualToString:oid3], nil);
// Test time portion of the BSON id
NSString *time1 = [oid substringWithRange:NSMakeRange(0, 8)];
unsigned timeInt = 0;
NSScanner *scanner = [NSScanner scannerWithString:time1];
[scanner scanHexInt:&timeInt];
int timeDelta = (((int)time(NULL)) - timeInt);
// This just tests that the time between now and the hex value of the first 8 bytes
// is between [0, 2) seconds.
STAssertTrue(timeDelta >= 0, nil);
STAssertTrue(timeDelta < 2, nil);
// Test increment
NSString *incr1 = [oid substringFromIndex:18];
NSString *incr2 = [oid2 substringFromIndex:18];
NSString *incr3 = [oid3 substringFromIndex:18];
STAssertEquals([incr1 integerValue], 0, nil);
STAssertEquals([incr2 integerValue], 1, nil);
STAssertEquals([incr3 integerValue], 2, nil);
// Test length
STAssertEqualObjects([oid length], 24, nil);
STAssertEqualObjects([oid2 length], 24, nil);
STAssertEqualObjects([oid3 length], 24, nil);
}
@end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment