aboutsummaryrefslogtreecommitdiffstats
path: root/subprojects/exess/src/base64.c
diff options
context:
space:
mode:
Diffstat (limited to 'subprojects/exess/src/base64.c')
-rw-r--r--subprojects/exess/src/base64.c163
1 files changed, 163 insertions, 0 deletions
diff --git a/subprojects/exess/src/base64.c b/subprojects/exess/src/base64.c
new file mode 100644
index 00000000..de64ee68
--- /dev/null
+++ b/subprojects/exess/src/base64.c
@@ -0,0 +1,163 @@
+/*
+ Copyright 2011-2021 David Robillard <d@drobilla.net>
+
+ Permission to use, copy, modify, and/or distribute this software for any
+ purpose with or without fee is hereby granted, provided that the above
+ copyright notice and this permission notice appear in all copies.
+
+ THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+*/
+
+#include "macros.h"
+#include "read_utils.h"
+#include "string_utils.h"
+#include "write_utils.h"
+
+#include "exess/exess.h"
+
+#include <assert.h>
+#include <stdint.h>
+#include <string.h>
+
+// Map a 6-bit base64 group to a base64 digit
+static inline uint8_t
+map(const unsigned group)
+{
+ assert(group < 64);
+
+ // See <http://tools.ietf.org/html/rfc3548#section-3>.
+ static const uint8_t b64_map[] =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+ return b64_map[group];
+}
+
+// Unmap a base64 digit to the numeric value used for decoding
+static inline uint8_t
+unmap(const uint8_t in)
+{
+ /* Table indexed by encoded characters that contains the numeric value used
+ for decoding, shifted up by 47 to be in the range of printable ASCII. A
+ '$' is a placeholder for characters not in the base64 alphabet. */
+ static const uint8_t b64_unmap[] =
+ "$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$m$$$ncdefghijkl$$$$$$"
+ "$/0123456789:;<=>?@ABCDEFGH$$$$$$IJKLMNOPQRSTUVWXYZ[\\]^_`ab$$$$"
+ "$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$"
+ "$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$";
+
+ return (uint8_t)(b64_unmap[in] - 47u);
+}
+
+static char
+next_char(const char* const str, size_t* const i)
+{
+ *i += skip_whitespace(str + *i);
+
+ return str[*i];
+}
+
+size_t
+exess_base64_decoded_size(const size_t length)
+{
+ return (length * 3) / 4 + 2;
+}
+
+ExessResult
+exess_read_base64(ExessBlob* const out, const char* const str)
+{
+ uint8_t* const uout = (uint8_t*)out->data;
+ const uint8_t* const ustr = (const uint8_t*)str;
+ size_t i = 0u;
+ size_t o = 0u;
+
+ while (str[i]) {
+ // Skip leading whitespace
+ i += skip_whitespace(str + i);
+ if (!str[i]) {
+ break;
+ }
+
+ // Read next chunk of 4 input characters
+ uint8_t in[] = "====";
+ for (size_t j = 0; j < 4; ++j) {
+ const char c = next_char(str, &i);
+ if (!is_base64(c)) {
+ return result(EXESS_EXPECTED_BASE64, i);
+ }
+
+ in[j] = ustr[i++];
+ }
+
+ if (in[0] == '=' || in[1] == '=' || (in[2] == '=' && in[3] != '=')) {
+ return result(EXESS_BAD_VALUE, i);
+ }
+
+ const size_t n_bytes = 1u + (in[2] != '=') + (in[3] != '=');
+ if (o + n_bytes > out->size) {
+ return result(EXESS_NO_SPACE, i);
+ }
+
+ const uint8_t a1 = (uint8_t)(unmap(in[0]) << 2u);
+ const uint8_t a2 = unmap(in[1]) >> 4u;
+
+ uout[o++] = a1 | a2;
+
+ if (in[2] != '=') {
+ const uint8_t b1 = (uint8_t)(unmap(in[1]) << 4u) & 0xF0u;
+ const uint8_t b2 = unmap(in[2]) >> 2u;
+
+ uout[o++] = b1 | b2;
+ }
+
+ if (in[3] != '=') {
+ const uint8_t c1 = (uint8_t)(unmap(in[2]) << 6u) & 0xC0u;
+ const uint8_t c2 = unmap(in[3]);
+
+ uout[o++] = c1 | c2;
+ }
+ }
+
+ out->size = o;
+ return result(EXESS_SUCCESS, i);
+}
+
+ExessResult
+exess_write_base64(const size_t data_size,
+ const void* const data,
+ const size_t buf_size,
+ char* const buf)
+{
+ const size_t length = (data_size + 2) / 3 * 4;
+ if (!buf) {
+ return result(EXESS_SUCCESS, length);
+ }
+
+ if (buf_size < length + 1) {
+ return result(EXESS_NO_SPACE, 0);
+ }
+
+ uint8_t* const out = (uint8_t*)buf;
+
+ size_t o = 0;
+ for (size_t i = 0; i < data_size; i += 3, o += 4) {
+ uint8_t in[4] = {0, 0, 0, 0};
+ const size_t n_in = MIN(3, data_size - i);
+ memcpy(in, (const uint8_t*)data + i, n_in);
+
+ out[o] = map(in[0] >> 2u);
+ out[o + 1] = map(((in[0] & 0x03u) << 4u) | ((in[1] & 0xF0u) >> 4u));
+ out[o + 2] =
+ ((n_in > 1u) ? map(((in[1] & 0x0Fu) << 2u) | ((in[2] & 0xC0u) >> 6u))
+ : '=');
+
+ out[o + 3] = ((n_in > 2u) ? map(in[2] & 0x3Fu) : '=');
+ }
+
+ return end_write(EXESS_SUCCESS, buf_size, buf, o);
+}