OXYGENHUB
◆ main openmsx / src / utils / Base64.cc
4.5 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
1
#include "Base64.hh"
2
3
#include "narrow.hh"
4
#include "ranges.hh"
5
#include "xrange.hh"
6
7
#include <algorithm>
8
#include <array>
9
#include <cassert>
10
11
namespace Base64 {
12
13
using openmsx::MemBuffer;
14
15
[[nodiscard]] static constexpr char encode(uint8_t c)
16
{
17
	constexpr std::array<char, 64> base64_chars = {
18
		'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z',
19
		'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z',
20
		'0','1','2','3','4','5','6','7','8','9','+','/',
21
	};
22
	assert(c < 64);
23
	return base64_chars[c];
24
}
25
26
[[nodiscard]] static constexpr uint8_t decode(uint8_t c)
27
{
28
	if        ('A' <= c && c <= 'Z') {
29
		return c - 'A';
30
	} else if ('a' <= c && c <= 'z') {
31
		return c - 'a' + 26;
32
	} else if ('0' <= c && c <= '9') {
33
		return c - '0' + 52;
34
	} else if (c == '+') {
35
		return 62;
36
	} else if (c == '/') {
37
		return 63;
38
	} else {
39
		return uint8_t(-1);
40
	}
41
}
42
43
std::string encode(std::span<const uint8_t> input)
44
{
45
	constexpr int CHUNKS = 19;
46
	constexpr int IN_CHUNKS  = 3 * CHUNKS;
47
	constexpr int OUT_CHUNKS = 4 * CHUNKS; // 76 chars per line
48
49
	auto outSize = ((input.size() + (IN_CHUNKS - 1)) / IN_CHUNKS) * (OUT_CHUNKS + 1); // overestimation
50
	std::string ret;
51
	ret.resize_and_overwrite(outSize, [&](char* buf, size_t) {
52
		size_t out = 0;
53
		while (!input.empty()) {
54
			if (out) buf[out++] = '\n';
55
			auto n = std::min<size_t>(IN_CHUNKS, input.size());
56
			for (/**/; n >= 3; n -= 3) {
57
				buf[out++] = encode(uint8_t( (input[0] & 0xfc) >> 2));
58
				buf[out++] = encode(uint8_t(((input[0] & 0x03) << 4) +
59
							((input[1] & 0xf0) >> 4)));
60
				buf[out++] = encode(uint8_t(((input[1] & 0x0f) << 2) +
61
							((input[2] & 0xc0) >> 6)));
62
				buf[out++] = encode(uint8_t( (input[2] & 0x3f) >> 0));
63
				input = input.subspan(3);
64
			}
65
			if (n) {
66
				std::array<uint8_t, 3> buf3 = {0, 0, 0};
67
				copy_to_range(input.subspan(0, n), buf3);
68
				input = input.subspan(n);
69
70
				std::array<uint8_t, 4> buf4;
71
				buf4[0] = uint8_t( (buf3[0] & 0xfc) >> 2);
72
				buf4[1] = uint8_t(((buf3[0] & 0x03) << 4) +
73
						((buf3[1] & 0xf0) >> 4));
74
				buf4[2] = uint8_t(((buf3[1] & 0x0f) << 2) +
75
						((buf3[2] & 0xc0) >> 6));
76
				buf4[3] = uint8_t( (buf3[2] & 0x3f) >> 0);
77
				for (auto j : xrange(n + 1)) {
78
					buf[out++] = encode(buf4[j]);
79
				}
80
				for (/**/; n < 3; ++n) {
81
					buf[out++] = '=';
82
				}
83
			}
84
		}
85
86
		assert(outSize >= out);
87
		return out; // shrink to correct size
88
	});
89
	return ret;
90
}
91
92
MemBuffer<uint8_t> decode(std::string_view input)
93
{
94
	auto outSize = (input.size() * 3 + 3) / 4; // overestimation
95
	MemBuffer<uint8_t> ret(outSize); // too big
96
97
	unsigned i = 0;
98
	size_t out = 0;
99
	std::array<uint8_t, 4> buf4;
100
	for (auto c : input) {
101
		uint8_t d = decode(c);
102
		if (d == uint8_t(-1)) continue;
103
		buf4[i++] = d;
104
		if (i == 4) {
105
			i = 0;
106
			ret[out++] = char(((buf4[0] & 0xff) << 2) + ((buf4[1] & 0x30) >> 4));
107
			ret[out++] = char(((buf4[1] & 0x0f) << 4) + ((buf4[2] & 0x3c) >> 2));
108
			ret[out++] = char(((buf4[2] & 0x03) << 6) + ((buf4[3] & 0xff) >> 0));
109
		}
110
	}
111
	if (i) {
112
		for (auto j : xrange(i, 4u)) {
113
			buf4[j] = 0;
114
		}
115
		std::array<uint8_t, 3> buf3;
116
		buf3[0] = narrow_cast<uint8_t>(((buf4[0] & 0xff) << 2) + ((buf4[1] & 0x30) >> 4));
117
		buf3[1] = narrow_cast<uint8_t>(((buf4[1] & 0x0f) << 4) + ((buf4[2] & 0x3c) >> 2));
118
		buf3[2] = narrow_cast<uint8_t>(((buf4[2] & 0x03) << 6) + ((buf4[3] & 0xff) >> 0));
119
		for (auto j : xrange(i - 1)) {
120
			ret[out++] = buf3[j];
121
		}
122
	}
123
124
	assert(outSize >= out);
125
	ret.resize(out); // shrink to correct size
126
	return ret;
127
}
128
129
bool decode_inplace(std::string_view input, std::span<uint8_t> output)
130
{
131
	auto outSize = output.size();
132
	unsigned i = 0;
133
	size_t out = 0;
134
	std::array<uint8_t, 4> buf4;
135
	for (auto c : input) {
136
		uint8_t d = decode(c);
137
		if (d == uint8_t(-1)) continue;
138
		buf4[i++] = d;
139
		if (i == 4) {
140
			i = 0;
141
			if ((out + 3) > outSize) [[unlikely]] return false;
142
			output[out++] = char(((buf4[0] & 0xff) << 2) + ((buf4[1] & 0x30) >> 4));
143
			output[out++] = char(((buf4[1] & 0x0f) << 4) + ((buf4[2] & 0x3c) >> 2));
144
			output[out++] = char(((buf4[2] & 0x03) << 6) + ((buf4[3] & 0xff) >> 0));
145
		}
146
	}
147
	if (i) {
148
		for (auto j : xrange(i, 4u)) {
149
			buf4[j] = 0;
150
		}
151
		std::array<uint8_t, 3> buf3;
152
		buf3[0] = narrow_cast<uint8_t>(((buf4[0] & 0xff) << 2) + ((buf4[1] & 0x30) >> 4));
153
		buf3[1] = narrow_cast<uint8_t>(((buf4[1] & 0x0f) << 4) + ((buf4[2] & 0x3c) >> 2));
154
		buf3[2] = narrow_cast<uint8_t>(((buf4[2] & 0x03) << 6) + ((buf4[3] & 0xff) >> 0));
155
		for (auto j : xrange(i - 1)) {
156
			if (out == outSize) [[unlikely]] return false;
157
			output[out++] = buf3[j];
158
		}
159
	}
160
161
	return out == outSize;
162
}
163
164
} // namespace Base64