Mercurial > libjeffpc
changeset 829:2abc6dc12302
base64: rewrite to use a init/step/finish API
The original single-shot API is still preserved.
Signed-off-by: Josef 'Jeff' Sipek <jeffpc@josefsipek.net>
author | Josef 'Jeff' Sipek <jeffpc@josefsipek.net> |
---|---|
date | Fri, 01 Jan 2021 14:44:53 -0500 |
parents | 3fa2ebce953c |
children | 3425a53b6499 |
files | base64.c include/jeffpc/base64.h mapfile-vers |
diffstat | 3 files changed, 441 insertions(+), 91 deletions(-) [+] |
line wrap: on
line diff
--- a/base64.c Wed Dec 30 17:28:41 2020 -0500 +++ b/base64.c Fri Jan 01 14:44:53 2021 -0500 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 Josef 'Jeff' Sipek <jeffpc@josefsipek.net> + * Copyright (c) 2019-2021 Josef 'Jeff' Sipek <jeffpc@josefsipek.net> * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,6 +21,7 @@ */ #include <jeffpc/base64.h> +#include <jeffpc/error.h> static const char b64_encode_table[64] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" @@ -113,152 +114,459 @@ INV, INV, INV, INV, INV, INV, INV, INV, /* 0xf8..0xff */ }; +/* + * Encoder + */ + __attribute__((always_inline)) -static inline void __b64_encode(char *out, const void *_in, size_t inlen, - const char *table) +static inline void __b64_encode_init(struct base64_encoder *state, + char *out) +{ + state->out = out; + state->plen = 0; +} + +__attribute__((always_inline)) +static inline void __b64_encode_group(char *out, const uint8_t *in, + const char *table) +{ + uint32_t v; + + v = in[0] << 16; + v |= in[1] << 8; + v |= in[2]; + + out[0] = table[(v >> 18) & 0x3f]; + out[1] = table[(v >> 12) & 0x3f]; + out[2] = table[(v >> 6) & 0x3f]; + out[3] = table[(v >> 0) & 0x3f]; +} + +__attribute__((always_inline)) +static inline void __b64_encode_step_partial(struct base64_encoder *state, + const uint8_t **in, size_t *inlen, + const char *table) { - const uint8_t *in = _in; - const size_t groups = inlen / 3; - uint32_t v; + switch (state->plen) { + case 0: + return; /* no partial - nothing to do */ + case 1: + state->partial[1] = (*in)[0]; + state->plen++; + (*in)++; + (*inlen)--; + if (!*inlen) + return; + /* fallthrough */ + case 2: + state->partial[2] = (*in)[0]; + state->plen++; + (*in)++; + (*inlen)--; + break; + default: + panic("impossible base64 encoder partial length: %u", + state->plen); + } + + ASSERT3U(state->plen, ==, 3); + + __b64_encode_group(state->out, state->partial, table); + + state->out += 4; + state->plen = 0; +} + +__attribute__((always_inline)) +static inline void __b64_encode_step(struct base64_encoder *state, + const uint8_t *in, size_t inlen, + const char *table) +{ size_t i; - for (i = 0; i < groups; i++) { - const size_t iidx = i * 3; - const size_t oidx = i * 4; + if (!inlen) + return; - v = in[iidx] << 16; - v |= in[iidx + 1] << 8; - v |= in[iidx + 2]; + __b64_encode_step_partial(state, &in, &inlen, table); + if (state->plen) { + ASSERT0(inlen); + return; + } - out[oidx + 0] = table[(v >> 18) & 0x3f]; - out[oidx + 1] = table[(v >> 12) & 0x3f]; - out[oidx + 2] = table[(v >> 6) & 0x3f]; - out[oidx + 3] = table[(v >> 0) & 0x3f]; + for (i = 0; i < inlen / 3; i++) { + __b64_encode_group(state->out, in, table); + + state->out += 4; + in += 3; } switch (inlen % 3) { case 0: + /* nothing left */ + break; + case 1: + case 2: + /* 1 or 2 byte left - save them */ + memcpy(state->partial, in, inlen % 3); + state->plen = inlen % 3; + break; + default: + panic("impossible base64 encoder partial length: %zu", + inlen % 3); + } +} + +__attribute__((always_inline)) +static inline void __b64_encode_finish(struct base64_encoder *state, + const char *table) +{ + uint32_t v; + + switch (state->plen) { + case 0: /* nothing left - nul terminate */ - out[i * 4] = '\0'; + *state->out = '\0'; break; case 1: /* 1 byte left - encoded it & pad with == */ - v = in[i * 3]; + v = state->partial[0]; v <<= 4; - out[i * 4] = table[(v >> 6) & 0x3f]; - out[i * 4 + 1] = table[(v >> 0) & 0x3f]; - out[i * 4 + 2] = '='; - out[i * 4 + 3] = '='; - out[i * 4 + 4] = '\0'; + state->out[0] = table[(v >> 6) & 0x3f]; + state->out[1] = table[(v >> 0) & 0x3f]; + state->out[2] = '='; + state->out[3] = '='; + state->out[4] = '\0'; break; case 2: /* 2 bytes left - encoded them & pad with = */ - v = in[i * 3] << 8; - v |= in[i * 3 + 1]; + v = state->partial[0] << 8; + v |= state->partial[1]; v <<= 2; - out[i * 4] = table[(v >> 12) & 0x3f]; - out[i * 4 + 1] = table[(v >> 6) & 0x3f]; - out[i * 4 + 2] = table[(v >> 0) & 0x3f]; - out[i * 4 + 3] = '='; - out[i * 4 + 4] = '\0'; + state->out[0] = table[(v >> 12) & 0x3f]; + state->out[1] = table[(v >> 6) & 0x3f]; + state->out[2] = table[(v >> 0) & 0x3f]; + state->out[3] = '='; + state->out[4] = '\0'; break; + default: + panic("impossible base64 encoder partial length: %u", + state->plen); } } +void base64_encode_init(struct base64_encoder *state, void *out) +{ + __b64_encode_init(state, out); +} + +void base64_encode_step(struct base64_encoder *state, + const void *in, size_t inlen) +{ + __b64_encode_step(state, in, inlen, b64_encode_table); +} + +void base64_encode_finish(struct base64_encoder *state) +{ + __b64_encode_finish(state, b64_encode_table); +} + void base64_encode(char *out, const void *in, size_t inlen) { - __b64_encode(out, in, inlen, b64_encode_table); + struct base64_encoder state; + + __b64_encode_init(&state, out); + __b64_encode_step(&state, in, inlen, b64_encode_table); + __b64_encode_finish(&state, b64_encode_table); +} + +void base64url_encode_init(struct base64_encoder *state, void *out) +{ + __b64_encode_init(state, out); +} + +void base64url_encode_step(struct base64_encoder *state, + const void *in, size_t inlen) +{ + __b64_encode_step(state, in, inlen, b64url_encode_table); +} + +void base64url_encode_finish(struct base64_encoder *state) +{ + __b64_encode_finish(state, b64url_encode_table); } void base64url_encode(char *out, const void *in, size_t inlen) { - __b64_encode(out, in, inlen, b64url_encode_table); + struct base64_encoder state; + + __b64_encode_init(&state, out); + __b64_encode_step(&state, in, inlen, b64url_encode_table); + __b64_encode_finish(&state, b64url_encode_table); +} + +/* + * Decoder + */ + +__attribute__((always_inline)) +static inline void __b64_decode_init(struct base64_decoder *state, + void *out) +{ + state->out = out; + state->outlen = 0; + state->plen = 0; + state->pads = 0; +} + +__attribute__((always_inline)) +static inline bool __b64_decode_group(uint8_t *out, const uint8_t *in, + const uint8_t *table) +{ + const uint8_t a = table[in[0]]; + const uint8_t b = table[in[1]]; + const uint8_t c = table[in[2]]; + const uint8_t d = table[in[3]]; + uint32_t v; + + if (CHECK_INV(a, b, c, d)) + return false; + + v = (a << 18) | (b << 12) | (c << 6) | d; + + out[0] = (v >> 16) & 0xff; + out[1] = (v >> 8) & 0xff; + out[2] = (v >> 0) & 0xff; + + return true; +} + +__attribute__((always_inline)) +static inline bool __b64_decode_step_partial(struct base64_decoder *state, + const uint8_t **in, size_t *inlen, + const uint8_t *table) +{ + switch (state->plen) { + case 0: + return true; /* no partial - nothing to do */ + case 1: + if (table[(*in)[0]] == INV) + return false; /* invalid input */ + + state->partial[1] = (*in)[0]; + state->plen++; + (*in)++; + (*inlen)--; + if (!*inlen) + return true; + /* fallthrough */ + case 2: + if (table[(*in)[0]] == INV) + return false; /* invalid input */ + + state->partial[2] = (*in)[0]; + state->plen++; + (*in)++; + (*inlen)--; + if (!*inlen) + return true; + /* fallthrough */ + case 3: + if (table[(*in)[0]] == INV) + return false; /* invalid input */ + + state->partial[3] = (*in)[0]; + state->plen++; + (*in)++; + (*inlen)--; + break; + default: + panic("impossible base64 decoder partial length: %u", + state->plen); + } + + ASSERT3U(state->plen, ==, 4); + + if (!__b64_decode_group(state->out, state->partial, table)) + return false; + + state->out += 3; + state->outlen += 3; + state->plen = 0; + + return true; } __attribute__((always_inline)) -static inline ssize_t __b64_decode(void *_out, const char *_in, size_t inlen, - const uint8_t *table) +static inline bool __b64_decode_step(struct base64_decoder *state, + const uint8_t *in, size_t inlen, + const uint8_t *table) { - const uint8_t *in = (const uint8_t *) _in; - uint8_t *out = _out; - size_t groups; + size_t pads; size_t i; - /* special case: empty input means empty output */ - if (!inlen) - return 0; + /* count the padding on this input & stash the info into the state */ + for (pads = 0; inlen && (in[inlen - 1] == '='); inlen--) + pads++; + + if (state->pads && inlen) + return false; /* non-padding after padding */ + if ((pads + state->pads) > 3) + return false; /* too much padding */ + + state->pads += pads; - /* must have full groups */ - if (inlen % 4) - return -1; + if (!inlen) + return true; - groups = inlen / 4; - if (in[inlen - 1] == '=') - groups--; /* last group has padding */ + if (!__b64_decode_step_partial(state, &in, &inlen, table)) + return false; + if (state->plen) { + ASSERT0(inlen); + return true; + } + + for (i = 0; i < inlen / 4; i++) { + if (!__b64_decode_group(state->out, in, table)) + return false; - for (i = 0; i < groups; i++) { - const uint8_t a = table[in[(i * 4) + 0]]; - const uint8_t b = table[in[(i * 4) + 1]]; - const uint8_t c = table[in[(i * 4) + 2]]; - const uint8_t d = table[in[(i * 4) + 3]]; - uint32_t v; + state->out += 3; + state->outlen += 3; + in += 4; + } - if (CHECK_INV(a, b, c, d)) - return -1; + switch (inlen % 4) { + case 0: + /* nothing left */ + break; + case 3: + if (table[in[2]] == INV) + return false; + /* fallthrough */ + case 2: + if (table[in[1]] == INV) + return false; + /* fallthrough */ + case 1: + /* 1-3 chars left - save them */ + if (table[in[0]] == INV) + return false; - v = (a << 18) | (b << 12) | (c << 6) | d; - - out[(i * 3) + 0] = (v >> 16) & 0xff; - out[(i * 3) + 1] = (v >> 8) & 0xff; - out[(i * 3) + 2] = (v >> 0) & 0xff; + memcpy(state->partial, in, inlen % 4); + state->plen = inlen % 4; + break; + default: + panic("impossible base64 decoder partial length: %zu", + inlen % 4); } - if ((in[inlen - 2] == '=') && (in[inlen - 1] == '=')) { - /* two pad chars, 1 byte of output left */ - const uint8_t a = table[in[inlen - 4]]; - const uint8_t b = table[in[inlen - 3]]; - uint32_t v; + return true; +} + +__attribute__((always_inline)) +static inline ssize_t __b64_decode_finish(struct base64_decoder *state, + const uint8_t *table) +{ + const uint32_t a = table[state->partial[0]]; + const uint32_t b = table[state->partial[1]]; + const uint32_t c = table[state->partial[2]]; + uint32_t v; - if (CHECK_INV(a, b, 0, 0)) + switch (state->plen) { + case 0: + /* 0 encoded chars left, 0 pad => no output */ + if (state->pads) + return -1; + return state->outlen; + case 1: + /* 1 encoded char left - impossible */ return -1; + case 2: + /* 2 encoded chars left, 2 pads => 1 output byte */ + if (state->pads != 2) + return -1; - v = (a << 6) | b; - v >>= 4; + if ((a == INV) || (b == INV)) + return -1; - out[(groups * 3) + 0] = v & 0xff; + v = (a << 6) | b; + v >>= 4; + + state->out[0] = v & 0xff; - return (groups * 3) + 1; - } else if (in[inlen - 1] == '=') { - /* one pad char, 2 bytes of output left */ - const uint8_t a = table[in[inlen - 4]]; - const uint8_t b = table[in[inlen - 3]]; - const uint8_t c = table[in[inlen - 2]]; - uint32_t v; + return state->outlen + 1; + case 3: + /* 3 encoded chars left, 1 pads => 2 output bytes */ + if (state->pads != 1) + return -1; - if (CHECK_INV(a, b, c, 0)) - return -1; + if ((a == INV) || (b == INV) || (c == INV)) + return -1; + + v = (a << 12) | (b << 6) | c; + v >>= 2; + + state->out[0] = (v >> 8) & 0xff; + state->out[1] = (v >> 0) & 0xff; - v = (a << 12) | (b << 6) | c; - v >>= 2; - - out[(groups * 3) + 0] = (v >> 8) & 0xff; - out[(groups * 3) + 1] = (v >> 0) & 0xff; + return state->outlen + 2; + default: + panic("impossible base64 decoder partial length: %u", + state->plen); + } +} - return (groups * 3) + 2; - } else { - /* zero pad chars, nothing to do */ - return (groups * 3); - } +void base64_decode_init(struct base64_decoder *state, void *out) +{ + __b64_decode_init(state, out); +} + +bool base64_decode_step(struct base64_decoder *state, + const void *in, size_t inlen) +{ + return __b64_decode_step(state, in, inlen, b64_decode_table); +} + +ssize_t base64_decode_finish(struct base64_decoder *state) +{ + return __b64_decode_finish(state, b64_decode_table); } ssize_t base64_decode(void *out, const char *in, size_t inlen) { - return __b64_decode(out, in, inlen, b64_decode_table); + struct base64_decoder state; + + __b64_decode_init(&state, out); + if (!__b64_decode_step(&state, (const uint8_t *) in, inlen, + b64_decode_table)) + return -1; + return __b64_decode_finish(&state, b64_decode_table); +} + +void base64url_decode_init(struct base64_decoder *state, void *out) +{ + __b64_decode_init(state, out); +} + +bool base64url_decode_step(struct base64_decoder *state, + const void *in, size_t inlen) +{ + return __b64_decode_step(state, in, inlen, b64url_decode_table); +} + +ssize_t base64url_decode_finish(struct base64_decoder *state) +{ + return __b64_decode_finish(state, b64url_decode_table); } ssize_t base64url_decode(void *out, const char *in, size_t inlen) { - return __b64_decode(out, in, inlen, b64url_decode_table); + struct base64_decoder state; + + __b64_decode_init(&state, out); + if (!__b64_decode_step(&state, (const uint8_t *) in, inlen, + b64url_decode_table)) + return -1; + return __b64_decode_finish(&state, b64url_decode_table); }
--- a/include/jeffpc/base64.h Wed Dec 30 17:28:41 2020 -0500 +++ b/include/jeffpc/base64.h Fri Jan 01 14:44:53 2021 -0500 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 Josef 'Jeff' Sipek <jeffpc@josefsipek.net> + * Copyright (c) 2019-2021 Josef 'Jeff' Sipek <jeffpc@josefsipek.net> * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -27,11 +27,41 @@ /* RFC 4648 */ +struct base64_encoder { + char *out; + uint8_t partial[3]; + uint8_t plen; +}; + +struct base64_decoder { + uint8_t *out; + size_t outlen; + uint8_t partial[4]; + uint8_t plen; + uint8_t pads; +}; + extern ssize_t base64_decode(void *out, const char *in, size_t inlen); +extern void base64_decode_init(struct base64_decoder *state, void *out); +extern bool base64_decode_step(struct base64_decoder *state, + const void *in, size_t inlen); +extern ssize_t base64_decode_finish(struct base64_decoder *state); extern void base64_encode(char *out, const void *in, size_t inlen); +extern void base64_encode_init(struct base64_encoder *state, void *out); +extern void base64_encode_step(struct base64_encoder *state, + const void *in, size_t inlen); +extern void base64_encode_finish(struct base64_encoder *state); extern ssize_t base64url_decode(void *out, const char *in, size_t inlen); +extern void base64url_decode_init(struct base64_decoder *state, void *out); +extern bool base64url_decode_step(struct base64_decoder *state, + const void *in, size_t inlen); +extern ssize_t base64url_decode_finish(struct base64_decoder *state); extern void base64url_encode(char *out, const void *in, size_t inlen); +extern void base64url_encode_init(struct base64_encoder *state, void *out); +extern void base64url_encode_step(struct base64_encoder *state, + const void *in, size_t inlen); +extern void base64url_encode_finish(struct base64_encoder *state); /* returns number of bytes needed to encode an input of @inlen bytes */ static inline size_t base64_required_length(size_t inlen)
--- a/mapfile-vers Wed Dec 30 17:28:41 2020 -0500 +++ b/mapfile-vers Fri Jan 01 14:44:53 2021 -0500 @@ -1,5 +1,5 @@ # -# Copyright (c) 2016-2020 Josef 'Jeff' Sipek <jeffpc@josefsipek.net> +# Copyright (c) 2016-2021 Josef 'Jeff' Sipek <jeffpc@josefsipek.net> # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -30,9 +30,21 @@ # base64 base64_decode; + base64_decode_init; + base64_decode_step; + base64_decode_finish; base64_encode; + base64_encode_init; + base64_encode_step; + base64_encode_finish; base64url_decode; + base64url_decode_init; + base64url_decode_step; + base64url_decode_finish; base64url_encode; + base64url_encode_init; + base64url_encode_step; + base64url_encode_finish; # bst bst_add;