iOS' Hidden Base64 Routines
Posted on May 18, 2012
It’s commonly held that iOS has no built in Base64 routines (a strange omission, if you ask me). Pootling around in the BSD headers today though, I discovered this is not entirely true. There are a couple of functions hidden away in libresolv.dylib. That’s the, err, BIND-9 DNS resolution library… If you’re not put off by linking to BIND just to get Base64 translation, it’s easy to use. Here’s the interface (publicly declared in a less readable fashion in resolv.h):
// To encode:
//
// Returns the byte length of the ASCII Base64 encoded data, or -1 on failure.
// Encoded data will be nul-terminated, but the nul-termination byte is not
// included in returned byte length (i.e. it's like a length returned by
// strlen()).
//
// Arguments:
// data: Data to encode.
// datalength: Byte length of data to encode.
// encoded: Buffer to hold encoded data (must be large enough, including
// space for a trailing nul-termination byte).
// encodedsize: Byte capacity of 'encoded' buffer.
//
int b64_ntop(uint8_t const *data, size_t datalength,
char *encoded, size_t encodedsize);
// To decode:
//
// Returns byte length of decoded data, or -1 on failure
//
// encoded: Base64 data to decode. Must be nul-terminated.
// unencoded: Buffer to hold decoded data (must be large enough!).
// unencodedsize: Byte capacity of 'unencoded' buffer.
//
int b64_pton(char const *encoded, uint8_t *unencoded, size_t unencodedsize)
Semi-pseudocode examples (typed direct, so you might want to double-check my math if you’re going to use this!):
#import <resolv.h>
// To encode:
NSData *dataToEncode = /* Data to encode */;
NSData *encodedData = nil;
NSUInteger dataToEncodeLength = dataToEncode.length;
// Last +1 below to accommodate trailing '\0':
NSUInteger encodedBufferLength = ((dataToEncodeLength + 2) / 3) * 4 + 1;
char *encodedBuffer = malloc(encodedBufferLength);
int encodedRealLength = b64_ntop(dataToEncode.bytes, dataToEncodeLength,
encodedBuffer, encodedBufferLength);
if(encodedRealLength >= 0) {
// In real life, you might not want the nul-termination byte, so you
// might not want the '+ 1'.
encodedData = [NSData dataWithBytesNoCopy:encodedBuffer
length:encodedRealLength + 1
freeWhenDone:YES];
} else {
free(encodedBuffer);
}
#import <resolv.h>
// To decode:
// Assuming dataToDecode's nul-terminated. In real life, you might need to copy
// out the data and nul-terminate it yourself :-(.
NSData *dataToDecode = /* Data to decode */;
NSData *decodedData = nil;
NSUInteger decodedBufferLength = dataToDecode.length * 3 / 4;
uint8_t* decodedBuffer = malloc(decodedBufferLength);
int decodedBufferRealLength = b64_pton(dataToDecode.bytes,
decodedBuffer,
decodedBufferLength);
if(decodedBufferRealLength >= 0) {
decodedData = [NSData dataWithBytesNoCopy:decodedBuffer
length:decodedBufferRealLength
freeWhenDone:YES];
} else {
free(convertedBuffer);
}
Remember to make your targets link to libresolv.dylib to avoid cryptic link-time errors.