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.