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 &gt;= 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 &gt;= 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.