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;