Last active
March 18, 2022 02:38
-
-
Save jstedfast/fbce0821c34ad5b3f19e767683528f4a to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <stdio.h> | |
#include <stdlib.h> | |
#include <sys/types.h> | |
#include <sys/stat.h> | |
#include <sys/uio.h> | |
#include <unistd.h> | |
#include <fcntl.h> | |
#define HAVE_STDINT_H 1 | |
#define ENABLE_ZENTIMER 1 | |
#include "zentimer.h" | |
#define d(x) | |
/** | |
* GMIME_BASE64_ENCODE_LEN: | |
* @x: Length of the input data to encode | |
* | |
* Calculates the maximum number of bytes needed to base64 encode the | |
* full input buffer of length @x. | |
* | |
* Returns: the number of output bytes needed to base64 encode an input | |
* buffer of size @x. | |
**/ | |
#define GMIME_BASE64_ENCODE_LEN(x) ((size_t) (((((x) + 2) / 57) * 77) + 77)) | |
static char base64_alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; | |
static unsigned char base64_rank[256] = { | |
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, | |
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, | |
255,255,255,255,255,255,255,255,255,255,255, 62,255,255,255, 63, | |
52, 53, 54, 55, 56, 57, 58, 59, 60, 61,255,255,255, 0,255,255, | |
255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, | |
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,255,255,255,255,255, | |
255, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, | |
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51,255,255,255,255,255, | |
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, | |
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, | |
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, | |
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, | |
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, | |
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, | |
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, | |
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, | |
}; | |
/* do incremental base64 (de/en)coding */ | |
size_t base64_decode_step (const unsigned char *inbuf, size_t inlen, unsigned char *outbuf, int *state, unsigned int *save); | |
size_t base64_encode_step (const unsigned char *inbuf, size_t inlen, unsigned char *outbuf, int *state, unsigned int *save); | |
size_t base64_encode_close (const unsigned char *inbuf, size_t inlen, unsigned char *outbuf, int *state, unsigned int *save); | |
/** | |
* base64_encode_close: | |
* @inbuf: input buffer | |
* @inlen: input buffer length | |
* @outbuf: output buffer | |
* @state: holds the number of bits that are stored in @save | |
* @save: leftover bits that have not yet been encoded | |
* | |
* Base64 encodes the input stream to the output stream. Call this | |
* when finished encoding data with base64_encode_step() | |
* to flush off the last little bit. | |
* | |
* Returns: the number of bytes encoded. | |
**/ | |
size_t | |
base64_encode_close (const unsigned char *inbuf, size_t inlen, unsigned char *outbuf, int *state, unsigned int *save) | |
{ | |
unsigned char *outptr = outbuf; | |
int c1, c2; | |
if (inlen > 0) | |
outptr += base64_encode_step (inbuf, inlen, outptr, state, save); | |
c1 = ((unsigned char *)save)[1]; | |
c2 = ((unsigned char *)save)[2]; | |
switch (((unsigned char *)save)[0]) { | |
case 2: | |
outptr[2] = base64_alphabet [(c2 & 0x0f) << 2]; | |
goto skip; | |
case 1: | |
outptr[2] = '='; | |
skip: | |
outptr[0] = base64_alphabet [c1 >> 2]; | |
outptr[1] = base64_alphabet [c2 >> 4 | ((c1 & 0x3) << 4)]; | |
outptr[3] = '='; | |
outptr += 4; | |
break; | |
} | |
*outptr++ = '\n'; | |
*save = 0; | |
*state = 0; | |
return (outptr - outbuf); | |
} | |
/** | |
* base64_encode_step: | |
* @inbuf: input buffer | |
* @inlen: input buffer length | |
* @outbuf: output buffer | |
* @state: holds the number of bits that are stored in @save | |
* @save: leftover bits that have not yet been encoded | |
* | |
* Base64 encodes a chunk of data. Performs an 'encode step', only | |
* encodes blocks of 3 characters to the output at a time, saves | |
* left-over state in state and save (initialize to 0 on first | |
* invocation). | |
* | |
* Returns: the number of bytes encoded. | |
**/ | |
size_t | |
base64_encode_step (const unsigned char *inbuf, size_t inlen, unsigned char *outbuf, int *state, unsigned int *save) | |
{ | |
register const unsigned char *inptr; | |
register unsigned char *outptr; | |
if (inlen == 0) | |
return 0; | |
outptr = outbuf; | |
inptr = inbuf; | |
if (inlen + ((unsigned char *)save)[0] > 2) { | |
const unsigned char *inend = inbuf + inlen - 2; | |
register int c1 = 0, c2 = 0, c3 = 0; | |
register int already; | |
already = *state; | |
switch (((char *)save)[0]) { | |
case 1: c1 = ((unsigned char *)save)[1]; goto skip1; | |
case 2: c1 = ((unsigned char *)save)[1]; | |
c2 = ((unsigned char *)save)[2]; goto skip2; | |
} | |
/* yes, we jump into the loop, no i'm not going to change it, its beautiful! */ | |
while (inptr < inend) { | |
c1 = *inptr++; | |
skip1: | |
c2 = *inptr++; | |
skip2: | |
c3 = *inptr++; | |
*outptr++ = base64_alphabet [c1 >> 2]; | |
*outptr++ = base64_alphabet [(c2 >> 4) | ((c1 & 0x3) << 4)]; | |
*outptr++ = base64_alphabet [((c2 & 0x0f) << 2) | (c3 >> 6)]; | |
*outptr++ = base64_alphabet [c3 & 0x3f]; | |
/* this is a bit ugly ... */ | |
if ((++already) >= 19) { | |
*outptr++ = '\n'; | |
already = 0; | |
} | |
} | |
((unsigned char *)save)[0] = 0; | |
inlen = 2 - (inptr - inend); | |
*state = already; | |
} | |
d(printf ("state = %d, inlen = %d\n", (int)((char *)save)[0], inlen)); | |
if (inlen > 0) { | |
register char *saveout; | |
/* points to the slot for the next char to save */ | |
saveout = & (((char *)save)[1]) + ((char *)save)[0]; | |
/* inlen can only be 0, 1 or 2 */ | |
switch (inlen) { | |
case 2: *saveout++ = *inptr++; | |
case 1: *saveout++ = *inptr++; | |
} | |
((char *)save)[0] += (char) inlen; | |
} | |
d(printf ("mode = %d\nc1 = %c\nc2 = %c\n", | |
(int)((char *)save)[0], | |
(int)((char *)save)[1], | |
(int)((char *)save)[2])); | |
return (outptr - outbuf); | |
} | |
/** | |
* base64_decode_step: | |
* @inbuf: input buffer | |
* @inlen: input buffer length | |
* @outbuf: output buffer | |
* @state: holds the number of bits that are stored in @save | |
* @save: leftover bits that have not yet been decoded | |
* | |
* Decodes a chunk of base64 encoded data. | |
* | |
* Returns: the number of bytes decoded (which have been dumped in | |
* @outbuf). | |
**/ | |
size_t | |
base64_decode_step (const unsigned char *inbuf, size_t inlen, unsigned char *outbuf, int *state, unsigned int *save) | |
{ | |
register const unsigned char *inptr; | |
register unsigned char *outptr; | |
const unsigned char *inend; | |
register unsigned int saved; | |
unsigned char last[2]; | |
unsigned char c, rank; | |
int n; | |
inend = inbuf + inlen; | |
outptr = outbuf; | |
inptr = inbuf; | |
saved = *save; | |
n = *state; | |
if (n < 0) { | |
last[0] = '='; | |
n = -n; | |
} else { | |
last[0] = '\0'; | |
} | |
last[1] = '\0'; | |
/* convert 4 base64 bytes to 3 normal bytes */ | |
while (inptr < inend) { | |
rank = base64_rank[(c = *inptr++)]; | |
if (rank != 0xff) { | |
saved = (saved << 6) | rank; | |
last[1] = last[0]; | |
last[0] = c; | |
n++; | |
if (n == 4) { | |
*outptr++ = saved >> 16; | |
if (last[1] != '=') | |
*outptr++ = saved >> 8; | |
if (last[0] != '=') | |
*outptr++ = saved; | |
n = 0; | |
} | |
} | |
} | |
*state = last[0] == '=' ? -n : n; | |
*save = saved; | |
return (outptr - outbuf); | |
} | |
size_t | |
mk_base64_encode_step (const unsigned char *inbuf, size_t inlen, unsigned char *outbuf, int *state, unsigned int *save) | |
{ | |
register const unsigned char *inptr; | |
register unsigned char *outptr; | |
register int quartets; | |
unsigned char *saved; | |
size_t remaining; | |
if (inlen == 0) | |
return 0; | |
saved = (unsigned char *) save; | |
quartets = *state; | |
outptr = outbuf; | |
inptr = inbuf; | |
if (inlen + *saved > 2) { | |
const unsigned char *inend = inbuf + inlen - 2; | |
register int c1, c2, c3; | |
c1 = *saved < 1 ? *inptr++ : saved[1]; | |
c2 = *saved < 2 ? *inptr++ : saved[2]; | |
c3 = *inptr++; | |
loop: | |
// encode our triplet into a quartet | |
*outptr++ = base64_alphabet[c1 >> 2]; | |
*outptr++ = base64_alphabet[(c2 >> 4) | ((c1 & 0x3) << 4)]; | |
*outptr++ = base64_alphabet[((c2 & 0x0f) << 2) | (c3 >> 6)]; | |
*outptr++ = base64_alphabet[c3 & 0x3f]; | |
// encode 19 quartets per line | |
if ((++quartets) >= 19) { | |
*outptr++ = '\n'; | |
quartets = 0; | |
} | |
if (inptr >= inend) | |
goto loop_exit; | |
c1 = *inptr++; | |
c2 = *inptr++; | |
c3 = *inptr++; | |
goto loop; | |
loop_exit: | |
remaining = 2 - (size_t) (inptr - inend); | |
*save = 0; | |
} else { | |
remaining = inlen; | |
} | |
if (remaining > 0) { | |
// At this point, saved can only be 0 or 1. | |
if (*saved == 0) { | |
// We can have up to 2 remaining input bytes. | |
saved[0] = (unsigned char) remaining; | |
saved[1] = *inptr++; | |
if (remaining == 2) | |
saved[2] = *inptr; | |
else | |
saved[2] = 0; | |
} else { | |
// We have 1 remaining input byte. | |
saved[2] = *inptr; | |
saved[0] = 2; | |
} | |
} | |
*state = quartets; | |
return (size_t) (outptr - outbuf); | |
} | |
size_t | |
mk_base64_encode_close (const unsigned char *inbuf, size_t inlen, unsigned char *outbuf, int *state, unsigned int *save) | |
{ | |
register unsigned char *outptr = outbuf; | |
register int quartets; | |
unsigned char *saved; | |
if (inlen > 0) | |
outptr += mk_base64_encode_step (inbuf, inlen, outbuf, state, save); | |
saved = (unsigned char *) save; | |
quartets = *state; | |
if (*saved > 0) { | |
int c1 = saved[1]; | |
int c2 = saved[2]; | |
*outptr++ = base64_alphabet[c1 >> 2]; | |
*outptr++ = base64_alphabet[c2 >> 4 | ((c1 & 0x3) << 4)]; | |
if (saved[0] == 2) | |
*outptr++ = base64_alphabet[(c2 & 0x0f) << 2]; | |
else | |
*outptr++ = '='; | |
*outptr++ = '='; | |
quartets++; | |
} | |
if (quartets > 0) | |
*outptr++ = '\n'; | |
*state = 0; | |
*save = 0; | |
return (size_t) (outptr - outbuf); | |
} | |
size_t | |
mk_base64_decode_step (const unsigned char *inbuf, size_t inlen, unsigned char *outbuf, int *state, unsigned int *save) | |
{ | |
register const unsigned char *inptr = inbuf; | |
const unsigned char *inend = inptr + inlen; | |
unsigned char *outptr = outbuf; | |
unsigned int saved = *save; | |
unsigned char c, rank; | |
int n, eq, eof = 0; | |
n = *state; | |
if (n == -1) | |
return 0; | |
/* decode every quartet into a triplet */ | |
while (inptr < inend) { | |
rank = base64_rank[(c = *inptr++)]; | |
if (rank != 0xFF) { | |
saved = (saved << 6) | rank; | |
n++; | |
if (n == 4) { | |
*outptr++ = saved >> 16; | |
*outptr++ = saved >> 8; | |
*outptr++ = saved; | |
saved = 0; | |
n = 0; | |
} | |
} else if (c == '=') { | |
eof = 1; | |
break; | |
} | |
} | |
if (eof) { | |
if (n > 1) { | |
eq = 4 - n; | |
//printf ("eq = %d, outlen = %ld\n", eq, (outptr - outbuf)); | |
while (n < 4) { | |
saved <<= 6; | |
n++; | |
} | |
*outptr++ = saved >> 16; | |
if (eq < 2) | |
*outptr++ = saved >> 8; | |
} | |
n = -1; | |
} | |
/* save state */ | |
*save = saved; | |
*state = n; | |
return (size_t) (outptr - outbuf); | |
} | |
int main (int argc, char **argv) | |
{ | |
uint64_t oldenc = 0, newenc = 0, olddec = 0, newdec = 0; | |
unsigned char *input, *output; | |
struct stat info; | |
size_t sz, nread = 0; | |
ssize_t n; | |
int i, fd; | |
if (argc <= 1) | |
return 0; | |
if ((fd = open (argv[1], O_RDONLY, 0644)) == -1) | |
return -1; | |
if (fstat (fd, &info) == -1) { | |
close (fd); | |
return -1; | |
} | |
input = malloc (info.st_size); | |
do { | |
if ((n = read (fd, input + nread, 4096)) <= 0) | |
break; | |
nread += n; | |
} while (nread < info.st_size); | |
close (fd); | |
output = malloc (GMIME_BASE64_ENCODE_LEN(nread)); | |
printf ("Pre-warming the cache for GMime's old base64 encoder...\n"); | |
for (i = 0; i < 3; i++) { | |
unsigned int save = 0; | |
int state = 0; | |
base64_encode_close (input, nread, output, &state, &save); | |
} | |
printf ("\nBenchmarking GMime's old base64 encoder...\n"); | |
for (i = 0; i < 10; i++) { | |
ztimer_t timer = ZTIMER_INITIALIZER; | |
unsigned int save = 0; | |
double seconds; | |
int state = 0; | |
uint64_t usec; | |
ZenTimerStart (&timer); | |
base64_encode_close (input, nread, output, &state, &save); | |
ZenTimerStop (&timer); | |
seconds = ZenTimerElapsed (&timer, &usec); | |
oldenc += usec; | |
printf ("GMime's old base64 encoder took %.6f seconds\n", seconds); | |
} | |
printf ("\nPre-warming the cache for GMime's new base64 encoder...\n"); | |
for (i = 0; i < 3; i++) { | |
unsigned int save = 0; | |
int state = 0; | |
sz = mk_base64_encode_close (input, nread, output, &state, &save); | |
} | |
printf ("\nBenchmarking GMime's new base64 encoder...\n"); | |
for (i = 0; i < 10; i++) { | |
ztimer_t timer = ZTIMER_INITIALIZER; | |
unsigned int save = 0; | |
double seconds; | |
int state = 0; | |
uint64_t usec; | |
ZenTimerStart (&timer); | |
mk_base64_encode_close (input, nread, output, &state, &save); | |
ZenTimerStop (&timer); | |
seconds = ZenTimerElapsed (&timer, &usec); | |
newenc += usec; | |
printf ("GMime's new base64 encoder took %.6f seconds\n", seconds); | |
} | |
printf ("\nPre-warming the cache for GMime's old base64 decoder...\n"); | |
for (i = 0; i < 3; i++) { | |
unsigned int save = 0; | |
int state = 0; | |
base64_decode_step (output, sz, input, &state, &save); | |
} | |
printf ("\nBenchmarking GMime's old base64 decoder...\n"); | |
for (i = 0; i < 10; i++) { | |
ztimer_t timer = ZTIMER_INITIALIZER; | |
unsigned int save = 0; | |
double seconds; | |
int state = 0; | |
uint64_t usec; | |
ZenTimerStart (&timer); | |
base64_decode_step (output, sz, input, &state, &save); | |
ZenTimerStop (&timer); | |
seconds = ZenTimerElapsed (&timer, &usec); | |
olddec += usec; | |
printf ("GMime's old base64 decoder took %.6f seconds\n", seconds); | |
} | |
printf ("\nPre-warming the cache for GMime's new base64 decoder...\n"); | |
base64_rank['='] = 0xFF; | |
for (i = 0; i < 3; i++) { | |
unsigned int save = 0; | |
int state = 0; | |
size_t x; | |
x = mk_base64_decode_step (output, sz, input, &state, &save); | |
if (i == 0) { | |
if (x != nread) | |
printf ("uh oh! output not the same length! %ld != %ld", x, nread); | |
fd = open ("base64.out", O_RDWR | O_CREAT | O_TRUNC, 0644); | |
write (fd, input, x); | |
close (fd); | |
} | |
} | |
printf ("\nBenchmarking GMime's new base64 decoder...\n"); | |
for (i = 0; i < 10; i++) { | |
ztimer_t timer = ZTIMER_INITIALIZER; | |
unsigned int save = 0; | |
double seconds; | |
int state = 0; | |
uint64_t usec; | |
ZenTimerStart (&timer); | |
mk_base64_decode_step (output, sz, input, &state, &save); | |
ZenTimerStop (&timer); | |
seconds = ZenTimerElapsed (&timer, &usec); | |
newdec += usec; | |
printf ("GMime's old base64 decoder took %.6f seconds\n", seconds); | |
} | |
free (input); | |
free (output); | |
printf ("\n"); | |
printf ("\n"); | |
printf (" Description | Total seconds | Average runtime |\n"); | |
printf ("-------------|---------------|-----------------|\n"); | |
printf (" old encoder | %.6fs | %.6fs |\n", oldenc / (double) ZTIME_USEC_PER_SEC, (oldenc / (double) ZTIME_USEC_PER_SEC) / 10); | |
printf (" new encoder | %.6fs | %.6fs |\n", newenc / (double) ZTIME_USEC_PER_SEC, (newenc / (double) ZTIME_USEC_PER_SEC) / 10); | |
printf (" old decoder | %.6fs | %.6fs |\n", olddec / (double) ZTIME_USEC_PER_SEC, (olddec / (double) ZTIME_USEC_PER_SEC) / 10); | |
printf (" new decoder | %.6fs | %.6fs |\n", newdec / (double) ZTIME_USEC_PER_SEC, (newdec / (double) ZTIME_USEC_PER_SEC) / 10); | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment