summaryrefslogtreecommitdiff
path: root/util/base64.c
diff options
context:
space:
mode:
Diffstat (limited to 'util/base64.c')
-rw-r--r--util/base64.c194
1 files changed, 194 insertions, 0 deletions
diff --git a/util/base64.c b/util/base64.c
new file mode 100644
index 0000000..7406aa5
--- /dev/null
+++ b/util/base64.c
@@ -0,0 +1,194 @@
+#include <ctype.h>
+#include <math.h> //not part of c stdlib, so compile with -lm to link
+#include <assert.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "base64.h"
+
+//*** encoding ***//
+static size_t base64_encoded_length(size_t bytes_len) {
+ return (size_t)(4 * ceil(bytes_len / 3.0));
+}
+
+static char encode_char(sextet s) {
+ char res;
+ assert(s < 64);
+ if (s < 26)
+ //0..25 -> 'A'...'Z'
+ res = 'A' + s;
+ else if (s < 52)
+ //26...51 -> 'a'...'z'
+ res = 'a' + (s - 26);
+ else if (s < 62)
+ //52...61 -> '0'...'9'
+ res = '0' + (s - 52);
+ else if (s == 62)
+ res = '+';
+ else
+ res = '/';
+ return res;
+}
+
+static void encode_1byte(uint8_t byte1, char *encoded) {
+ sextet s[2];
+ s[0] = byte1 >> 2;
+ s[1] = (byte1 & 0x03) << 4;
+ *encoded++ = encode_char(s[0]);
+ *encoded++ = encode_char(s[1]);
+ *encoded++ = '=';
+ *encoded = '=';
+}
+
+static void encode_2byte(uint8_t byte1, uint8_t byte2, char *encoded) {
+ sextet s[3];
+ s[0] = byte1 >> 2;
+ s[1] = ((byte1 & 0x03) << 4) | (byte2 >> 4);
+ s[2] = (byte2 & 0x0F) << 2;
+ for (int i = 0; i < 3; i++)
+ *encoded++ = encode_char(s[i]);
+ *encoded = '=';
+}
+
+static void encode_3byte(uint8_t byte1, uint8_t byte2, uint8_t byte3, char *encoded) {
+ sextet s[4];
+ s[0] = byte1 >> 2;
+ s[1] = ((byte1 & 0x03) << 4) | (byte2 >> 4);
+ s[2] = ((byte2 & 0x0F) << 2) | (byte3 >> 6);
+ s[3] = byte3 & 0x3F;
+ for (int i = 0; i < 4; i++)
+ *encoded++ = encode_char(s[i]);
+}
+
+char *base64_encode(uint8_t *from, size_t len) {
+ long padded_bytes;
+ size_t to_len;
+ char *to, *curr;
+ //first determine size of required result buffer
+ //then malloc (with 1 extra byte for '\0')
+ to_len = base64_encoded_length(len);
+ to = (char*) malloc(to_len + 1);
+ if (to == NULL)
+ return NULL;
+ //handle 3 bytes at a time,
+ //converting into 4 sextets
+ curr = to;
+ for (unsigned long i = 0; i < len / 3; i++) {
+ encode_3byte(*from, *(from + 1), *(from + 2), curr);
+ from += 3;
+ curr += 4;
+ }
+ //convert leftover bytes (if any)
+ padded_bytes = len - 3 * (len / 3);
+ if (padded_bytes == 1)
+ encode_1byte(*from, curr);
+ else if (padded_bytes == 2)
+ encode_2byte(*from, *(from + 1), curr);
+ //aformentioned zero-terminate
+ to[to_len] = '\0';
+ return to;
+}
+
+//*** decoding ***//
+#define WRONG_CHAR 64
+
+static uint8_t decode_char(char c) {
+ uint8_t res;
+ assert(isalnum(c) || c == '+' || c == '/');
+ if (isupper(c))
+ res = c - 'A';
+ else if (islower(c))
+ res = c - 'a' + 26;
+ else if (isdigit(c))
+ res = c - '0' + 52;
+ else if (c == '+')
+ res = 62;
+ else if (c == '/')
+ res = 63;
+ else {
+ fprintf(stderr, "unsupported char: %c\n", c);
+ res = WRONG_CHAR;
+ }
+ return res;
+}
+
+static void decode_1byte(char *from, uint8_t *to) {
+ //read/decode 2 sextets
+ uint8_t s1 = decode_char(*from);
+ uint8_t s2 = decode_char(*(from + 1));
+ //check for error
+ assert(s1 != WRONG_CHAR);
+ assert(s2 != WRONG_CHAR);
+ //combine 2 sextets (12 bits) into single byte
+ uint8_t byte1 = (s1 << 2) | (s2 >> 4);
+ *to++ = byte1;
+}
+
+static void decode_2byte(char *from, uint8_t *to) {
+ //read/decode 3 sextets
+ uint8_t s1 = decode_char(*from);
+ uint8_t s2 = decode_char(*(from + 1));
+ uint8_t s3 = decode_char(*(from + 2));
+ //check for error
+ assert(s1 != WRONG_CHAR);
+ assert(s2 != WRONG_CHAR);
+ assert(s3 != WRONG_CHAR);
+ //combine 3 sextets (18 bits) into single byte
+ uint8_t byte1 = (s1 << 2) | (s2 >> 4);
+ uint8_t byte2 = (s2 << 4) | (s3 >> 2);
+ *to++ = byte1;
+ *to++ = byte1;
+}
+
+static void decode_3byte(char *from, uint8_t *to) {
+ //read/decode 4 sextets
+ uint8_t s1 = decode_char(*from);
+ uint8_t s2 = decode_char(*(from + 1));
+ uint8_t s3 = decode_char(*(from + 2));
+ uint8_t s4 = decode_char(*(from + 3));
+ //check for error
+ assert(s1 != WRONG_CHAR);
+ assert(s2 != WRONG_CHAR);
+ assert(s3 != WRONG_CHAR);
+ assert(s4 != WRONG_CHAR);
+ //combine 4 sextets (24 bits) into single byte
+ uint8_t byte1 = (s1 << 2) | (s2 >> 4);
+ uint8_t byte2 = (s2 << 4) | (s3 >> 2);
+ uint8_t byte3 = (s3 << 6) | s4;
+ *to++ = byte1;
+ *to++ = byte1;
+ *to++ = byte3;
+}
+
+uint8_t *base64_decode(char *from, size_t *len) {
+ assert(from != NULL && len != NULL);
+ size_t text_len = strlen(from);
+ //risky assumption data is properly padded...
+ //so always use 4-character blocks
+ assert(text_len % 4 == 0);
+ //analyze padding...
+ char last = from[text_len - 1], pre_last = from[text_len - 2];
+ int last_block_len = 3;
+ if (last == '=')
+ last_block_len = (pre_last == '=') ? 1 : 2;
+ //determine size of decoded bytes
+ size_t bytes_len = (text_len / 4 - 1) * 3 + last_block_len;
+ uint8_t *to = malloc(bytes_len);
+ if (to == NULL) {
+ perror("malloc");
+ return NULL;
+ }
+ //read 4-bytes and write 3-bytes on each loop
+ unsigned long i;
+ for (i = 0; i < text_len / 4 - 1; i++)
+ decode_3byte(&from[4 * i], &to[3 * i]);
+ //handle last block (ideally with padding)
+ if (last_block_len == 3)
+ decode_3byte(&from[4 * i], &to[3 * i]);
+ else if (last_block_len == 1)
+ decode_1byte(&from[4 * i], &to[3 * i]);
+ else if (last_block_len == 2)
+ decode_2byte(&from[4 * i], &to[3 * i]);
+ *len = bytes_len;
+ return to;
+}