GridFormat 0.2.1
I/O-Library for grid-like data structures
Loading...
Searching...
No Matches
base64.hpp
Go to the documentation of this file.
1// SPDX-FileCopyrightText: 2022-2023 Dennis Gläser <dennis.glaeser@iws.uni-stuttgart.de>
2// SPDX-License-Identifier: MIT
8#ifndef GRIDFORMAT_COMMON_ENCODING_BASE64_HPP_
9#define GRIDFORMAT_COMMON_ENCODING_BASE64_HPP_
10
11#include <array>
12#include <vector>
13#include <utility>
14#include <cassert>
15#include <algorithm>
16#include <istream>
17
18#include <gridformat/common/exceptions.hpp>
19#include <gridformat/common/serialization.hpp>
20#include <gridformat/common/istream_helper.hpp>
21#include <gridformat/common/output_stream.hpp>
22#include <gridformat/common/concepts.hpp>
23
24namespace GridFormat {
25
26#ifndef DOXYGEN
27namespace Base64Detail {
28
29static constexpr auto alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
30
31static constexpr std::array<unsigned char, 256> letter_to_index = [] {
32 std::array<unsigned char, 256> result;
33 std::ranges::fill(result, 0);
34 for (int i = 0; i < 64; ++i)
35 result[static_cast<unsigned>(alphabet[i])] = i;
36 return result;
37} ();
38
39} // namespace Base64Detail
40#endif // DOXYGEN
41
42namespace Base64 {
43
45std::size_t decoded_size(std::size_t encoded_size) {
46 if (encoded_size%4 != 0)
47 throw SizeError("Given size is not a multiple of 4");
48 return encoded_size*3/4;
49}
50
52std::size_t encoded_size(std::size_t raw_size) {
53 return 4*static_cast<std::size_t>(
54 std::ceil(static_cast<double>(raw_size)/3.0)
55 );
56}
57
58} // namespace Base64
59
62
64 Serialization decode_from(std::istream& stream, std::size_t target_num_decoded_bytes) const {
65 InputStreamHelper helper{stream};
66 const auto encoded_size = Base64::encoded_size(target_num_decoded_bytes);
67 std::string chars = helper.read_until_any_of("=", encoded_size);
68 if (chars.size() != encoded_size)
69 chars += helper.read_until_not_any_of("=");
70
71 Serialization result{chars.size()};
72 auto result_chars = result.template as_span_of<char>();
73 std::ranges::move(std::move(chars), result_chars.begin());
74 result.resize(decode(result_chars));
75 return result;
76 }
77
78 template<std::size_t s>
79 std::size_t decode(std::span<char, s> chars) const {
80 if (chars.size() == 0)
81 return 0;
82 if (chars.size()%4 != 0)
83 throw SizeError("Buffer size is not a multiple of 4");
84
85 std::size_t in_offset = 0;
86 std::size_t out_offset = 0;
87 while (in_offset < chars.size()) {
88 std::ranges::copy(
89 _decode_triplet(chars.data() + in_offset),
90 chars.data() + out_offset
91 );
92 in_offset += 4;
93 out_offset += 3;
94 }
95
96 const auto end_chars = chars.subspan(chars.size() - 3);
97 std::string_view end_str{end_chars.data(), end_chars.size()};
98 const auto num_padding_chars = std::ranges::count(end_str, '=');
99 return out_offset - (num_padding_chars > 0 ? num_padding_chars : 0);
100 }
101
102 private:
103 std::array<char, 3> _decode_triplet(const char* in) const {
104 using Base64Detail::letter_to_index;
105 std::array<char, 3> result;
106 result[0] = ((letter_to_index[in[0]] & 0b0011'1111) << 2) | ((letter_to_index[in[1]] & 0b0011'0000) >> 4);
107 result[1] = ((letter_to_index[in[1]] & 0b0000'1111) << 4) | ((letter_to_index[in[2]] & 0b0011'1100) >> 2);
108 result[2] = ((letter_to_index[in[2]] & 0b0000'0011) << 6) | ((letter_to_index[in[3]] & 0b0011'1111));
109 return result;
110 };
111};
112
115 std::size_t num_cached_buffers = 4000;
116};
117
119template<typename OStream>
120class Base64Stream : public OutputStreamWrapperBase<OStream> {
121 using Byte = char;
122 static_assert(sizeof(std::byte) == sizeof(Byte));
123 static constexpr int buffer_size = 3;
124 static constexpr int encoded_buffer_size = 4;
125
126 // Top 6 bits of byte 0
127 inline Byte _encode_sextet_0(const Byte* buffer) const {
128 return Base64Detail::alphabet[((buffer[0] & 0b1111'1100) >> 2)];
129 }
130 // Bottom 2 bits of byte 0, Top 4 bits of byte 1
131 inline Byte _encode_sextet_1(const Byte* buffer) const {
132 return Base64Detail::alphabet[((buffer[0] & 0b0000'0011) << 4)
133 | ((buffer[1] & 0b1111'0000) >> 4)];
134 }
135 // Bottom 4 bits of byte 1, Top 2 bits of byte 2
136 inline Byte _encode_sextet_2(const Byte* buffer) const {
137 return Base64Detail::alphabet[((buffer[1] & 0b0000'1111) << 2)
138 | ((buffer[2] & 0b1100'0000) >> 6)];
139 }
140 // Bottom 6 bits of byte 2
141 inline Byte _encode_sextet_3(const Byte* buffer) const {
142 return Base64Detail::alphabet[(buffer[2] & 0b0011'1111)];
143 }
144
145 public:
146 explicit Base64Stream(OStream& s, Base64EncoderOptions opts = {})
147 : OutputStreamWrapperBase<OStream>(s)
148 , _opts{std::move(opts)}
149 {}
150
151 template<typename T, std::size_t size>
152 void write(std::span<T, size> data) {
153 auto byte_span = std::as_bytes(data);
154 const Byte* bytes = reinterpret_cast<const Byte*>(byte_span.data());
155 _write(bytes, byte_span.size());
156 }
157
158 private:
159 std::size_t _cache_size_in() const { return _opts.num_cached_buffers*buffer_size; }
160 std::size_t _cache_size_out() const { return _opts.num_cached_buffers*encoded_buffer_size; }
161
162 void _write(const Byte* data, std::size_t size) {
163 const auto num_full_buffers = size/buffer_size;
164 const auto num_full_caches = num_full_buffers/_opts.num_cached_buffers;
165 for (const auto i : std::views::iota(std::size_t{0}, num_full_caches))
166 _flush_full_cache(data + i*_cache_size_in());
167
168 const auto processed_bytes = num_full_caches*_cache_size_in();
169 if (size > processed_bytes)
170 _flush_cache(data + processed_bytes, size - processed_bytes);
171 }
172
173 void _flush_full_cache(const Byte* data) {
174 std::vector<Byte> cache(_cache_size_out());
175 for (std::size_t i = 0; i < _cache_size_out()/encoded_buffer_size; ++i) {
176 const std::size_t in_offset = i*buffer_size;
177 const std::size_t out_offset = i*encoded_buffer_size;
178 cache[out_offset + 0] = _encode_sextet_0(data + in_offset);
179 cache[out_offset + 1] = _encode_sextet_1(data + in_offset);
180 cache[out_offset + 2] = _encode_sextet_2(data + in_offset);
181 cache[out_offset + 3] = _encode_sextet_3(data + in_offset);
182 }
183 this->_stream.write(std::span{cache});
184 }
185
186 void _flush_cache(const Byte* data, std::size_t num_bytes_in) {
187 if (num_bytes_in == 0)
188 return;
189 if (num_bytes_in > _cache_size_in())
190 throw SizeError("Number of bytes cannot be larger than cache size");
191
192 const std::size_t num_full_buffers = num_bytes_in/buffer_size;
193 const std::size_t residual = num_bytes_in%buffer_size;
194
195 std::vector<Byte> cache(_cache_size_out());
196 for (std::size_t i = 0; i < num_full_buffers; ++i) {
197 const std::size_t in_offset = i*buffer_size;
198 const std::size_t out_offset = i*encoded_buffer_size;
199 cache[out_offset + 0] = _encode_sextet_0(data + in_offset);
200 cache[out_offset + 1] = _encode_sextet_1(data + in_offset);
201 cache[out_offset + 2] = _encode_sextet_2(data + in_offset);
202 cache[out_offset + 3] = _encode_sextet_3(data + in_offset);
203 }
204
205 const std::size_t in_offset = num_full_buffers*buffer_size;
206 const std::size_t out_offset = num_full_buffers*encoded_buffer_size;
207 if (residual > 0) {
208 Byte last_buffer[buffer_size] = {
209 *(data + in_offset),
210 residual > 1 ? *(data + in_offset + 1) : Byte{0},
211 residual > 2 ? *(data + in_offset + 2) : Byte{0}
212 };
213 cache[out_offset] = _encode_sextet_0(last_buffer);
214 cache[out_offset + 1] = _encode_sextet_1(last_buffer);
215 cache[out_offset + 2] = residual > 1 ? _encode_sextet_2(last_buffer) : '=';
216 cache[out_offset + 3] = residual > 2 ? _encode_sextet_3(last_buffer) : '=';
217 this->_stream.write(std::span{cache.data(), out_offset + 4});
218 } else {
219 this->_stream.write(std::span{cache.data(), out_offset});
220 }
221 }
222
224};
225
227
228} // namespace GridFormat
229
230namespace GridFormat::Encoding {
231
234
236struct Base64 {
238 template<typename Stream>
239 constexpr auto operator()(Stream& s) const noexcept {
240 return Base64Stream{s, _opts};
241 }
242
244 constexpr auto operator()(Base64EncoderOptions opts) const {
245 Base64 other;
246 other._opts = std::move(opts);
247 return other;
248 }
249
252 Base64 enc;
253 enc._opts = std::move(opts);
254 return enc;
255 }
256
257 private:
258 Base64EncoderOptions _opts = {};
259};
260
262inline constexpr Base64 base64;
263
265
266} // namespace GridFormat::Encoding
267
268#endif // GRIDFORMAT_COMMON_ENCODING_BASE64_HPP_
std::size_t encoded_size(std::size_t raw_size)
Return the number of encoded bytes for the given number of raw bytes.
Definition: base64.hpp:52
std::size_t decoded_size(std::size_t encoded_size)
Return the number of decoded bytes for the given number of encoded bytes.
Definition: base64.hpp:45
Wrapper around a given stream to write output encoded with base64.
Definition: base64.hpp:120
constexpr Base64 base64
Instance of the base64 encoder.
Definition: base64.hpp:262
Definition: base64.hpp:63
Options for formatted output of ranges with base64 encoding.
Definition: base64.hpp:114
std::size_t num_cached_buffers
Number of triplets cached between write operations.
Definition: base64.hpp:115
Base64 encoder.
Definition: base64.hpp:236
constexpr auto operator()(Stream &s) const noexcept
Return a base64 stream.
Definition: base64.hpp:239
constexpr auto operator()(Base64EncoderOptions opts) const
Return an encoder instance with different options.
Definition: base64.hpp:244
static Base64 with(Base64EncoderOptions opts)
Return a base64 encoder with the given options.
Definition: base64.hpp:251