ohmu
·
2026-10-15
1#include "common/base64.h"
2#include "common/endian.h"
3
4// https://github.com/kostya/benchmarks/blob/master/base64/test-nolib.c#L145
5void ffBase64EncodeRaw(uint32_t size, const char* str, uint32_t* out_size, char* output) {
6 static const char chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
7 char* out = output;
8 const char* ends = str + (size - size % 3);
9 while (str != ends) {
10 uint32_t n = *(uint32_t*) str;
11 // The 3 input bytes must be laid out big-endian (str[0] in the most significant position).
12 n = FF_READ_BE(n);
13 *out++ = chars[(n >> 26) & 63];
14 *out++ = chars[(n >> 20) & 63];
15 *out++ = chars[(n >> 14) & 63];
16 *out++ = chars[(n >> 8) & 63];
17 str += 3;
18 }
19
20 // The bytes have to be widened through `uint8_t`: `char` is signed here, so widening a byte
21 // with the high bit set would sign extend it and the `& 63` below would then read the extension
22 // instead of the byte's own bits. The whole groups above are immune because they read through a
23 // `uint32_t` instead.
24 if (size % 3 == 1) {
25 uint64_t n = (uint64_t) (uint8_t) *str << 16;
26 *out++ = chars[(n >> 18) & 63];
27 *out++ = chars[(n >> 12) & 63];
28 *out++ = '=';
29 *out++ = '=';
30 } else if (size % 3 == 2) {
31 uint64_t n = (uint64_t) (uint8_t) *str++ << 16;
32 n |= (uint64_t) (uint8_t) *str << 8;
33 *out++ = chars[(n >> 18) & 63];
34 *out++ = chars[(n >> 12) & 63];
35 *out++ = chars[(n >> 6) & 63];
36 *out++ = '=';
37 }
38 *out = '\0';
39 *out_size = (uint32_t) (out - output);
40}
41
42static uint8_t decode_table[256];
43
44static void init_decode_table() {
45 uint8_t ch = 0;
46 do {
47 int32_t code = -1;
48 if (ch >= 'A' && ch <= 'Z') {
49 code = ch - 0x41;
50 }
51 if (ch >= 'a' && ch <= 'z') {
52 code = ch - 0x47;
53 }
54 if (ch >= '0' && ch <= '9') {
55 code = ch + 0x04;
56 }
57 if (ch == '+' || ch == '-') {
58 code = 0x3E;
59 }
60 if (ch == '/' || ch == '_') {
61 code = 0x3F;
62 }
63 decode_table[ch] = (uint8_t) code;
64 } while (ch++ < 0xFF);
65}
66
67#define next_char(x) uint8_t x = decode_table[(uint8_t) *str++];
68
69void ffBase64DecodeRaw(uint32_t size, const char* str, uint32_t* out_size, char* output) {
70 if (*(uint64_t*) decode_table == 0) {
71 init_decode_table();
72 }
73
74 char* out = output;
75 while (size > 0 && (str[size - 1] == '\n' || str[size - 1] == '\r' || str[size - 1] == '=')) {
76 size--;
77 }
78
79 const char* ends = str + size - 4;
80 while (true) {
81 if (str > ends) {
82 break;
83 }
84 while (*str == '\n' || *str == '\r') {
85 str++;
86 }
87
88 if (str > ends) {
89 break;
90 }
91 next_char(a);
92 next_char(b);
93 next_char(c);
94 next_char(d);
95
96 *out++ = (char) (a << 2 | b >> 4);
97 *out++ = (char) (b << 4 | c >> 2);
98 *out++ = (char) (c << 6 | d >> 0);
99 }
100
101 uint8_t mod = (uint8_t) (ends - str + 4) % 4;
102 if (mod == 2) {
103 next_char(a);
104 next_char(b);
105 *out++ = (char) (a << 2 | b >> 4);
106 } else if (mod == 3) {
107 next_char(a);
108 next_char(b);
109 next_char(c);
110 *out++ = (char) (a << 2 | b >> 4);
111 *out++ = (char) (b << 4 | c >> 2);
112 }
113
114 *out = '\0';
115 *out_size = (uint32_t) (out - output);
116}