iOS’ Hidden Base64 Routines

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.