So far, the below code appears to work well. It operates pretty fast, but I was wondering if it's possible to make it faster. I'm also looking for general tips on what I might be doing wrong and what I could do better. I have added in comments to explain certain decisions.
b64.h
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static const char encode_table[] = \
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
char *ascii_to_base64(char *asciistr)
{
int asciistrlen = strlen(asciistr);
int convertedlen = 4 * ceil((double)asciistrlen / 3.0);
char *asciibuff = (char*)malloc(convertedlen + 1);
if (asciibuff == NULL)
goto exception;
char *converted = malloc(convertedlen + 1);
if (converted == NULL)
goto exception;
/*
* By zero-ing out, I can just check 0x00 bytes and convert to padding
* without any extra checks.
*/
memset(asciibuff, 0x00, asciistrlen + 1);
memset(converted, 0x00, convertedlen + 1);
/*
* Can't realloc the asciistr var so have to create individual buffer
* with same values.
*/
strcpy(asciibuff, asciistr);
/*
* Allows me to loop through original string input AND be able to check
* for null bytes and pad accordingly without running into overwriting
* problems caused by using the "converted" variable for both jobs.
*/
int i, j;
for (i = 0, j = 0; i < convertedlen; i+=4, j+=3)
{
converted[i+3] = encode_table[asciibuff[j+2] != 0x00 ? asciistr[j+2] & 0xbf : 64];
converted[i+2] = encode_table[asciibuff[j+1] != 0x00 ?
((asciistr[j+1] & 0x0f) << 2) + (asciibuff[j+2] >> 6) : 64];
converted[i+1] = encode_table[asciibuff[j] != 0x00 ?
((asciistr[j] & 0x03) << 4) + (asciibuff[j+1] >> 4) : 64];
converted[i] = encode_table[asciibuff[j] != 0x00 ? asciibuff[j] >> 2 : 64];
}
free(asciibuff);
return (converted);
exception:
return (NULL);
}
/* Derive encoding index */
static unsigned int enci(int base64ascii)
{
if (base64ascii <= 0x5a && base64ascii >= 0x41)
return ((unsigned)(base64ascii - 0x41));
else if (base64ascii <= 0x7a && base64ascii >= 0x61)
return ((unsigned)(base64ascii - 0x47));
else if (base64ascii <= 0x39 && base64ascii >= 0x30)
return ((unsigned)(base64ascii + 0x04));
else if (base64ascii == 0x2b)
return ((unsigned)0x3e);
else if (base64ascii == 0x2f)
return ((unsigned)0x3f);
else if (base64ascii == 0x3d)
return ((unsigned)0x00);
else
return ((unsigned)0xff);
}
char *base64_to_ascii(char *base64str)
{
int base64strlen = strlen(base64str);
int convertedlen = (base64strlen / 4) * 3;
/* If an end character is '=', there is one less byte to be generated. */
if (base64str[base64strlen - 1] == '=')
convertedlen--;
if (base64str[base64strlen - 2] == '=')
convertedlen--;
char *converted = (char*)malloc(base64strlen + 1);
if (converted == NULL)
goto exception;
memset(converted, 0x00, base64strlen + 1);
int i, j;
for (i = 0, j = 0; i < base64strlen; i+=4, j+=3)
{
converted[j] = (enci(base64str[i]) << 2) + (enci(base64str[i+1]) >> 4);
converted[j+1] = ((enci(base64str[i+1]) & 0x0f) << 4) + (enci(base64str[i+2]) >> 2);
converted[j+2] = ((enci(base64str[i+2]) & 0x03) << 6) + enci(base64str[i+3]);
}
/*
* By making "converted" as long as the base64 encoded string
* (when it really should be smaller.) I can loop through the base64
* encoding with no invalid writes or reads and just cut off the excess
* bytes with a realloc. Don't know if this is the best solution, but it
* satiates valgrind. After running through many inputs, it appears to
* always cut the converted string with its null-terminating character.
* I always get valid c-strings.
*/
converted = realloc(converted, convertedlen + 1);
return (converted);
exception:
return (NULL);
}