{{ message }}
Repository navigation
Expand file tree
/
Copy pathencode.h
More file actions
442 lines (414 loc) · 14.3 KB
/
Copy pathencode.h
File metadata and controls
442 lines (414 loc) · 14.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the Apache 2.0 License.
#pragma once
// Header-only msgpack encoder.
//
// Spec: https://github.com/msgpack/msgpack/blob/master/spec.md
//
// Encoder-only. Decoding is out of scope; CCF currently decodes via
// nlohmann::json::from_msgpack. The encoder writes the smallest format
// family that fits each value (the spec's recommended canonical form).
//
// Supported subset:
// - All msgpack scalar types (nil, bool, int, uint, float64,
// str fixstr/str8/str16/str32, bin bin8/16/32).
// - Arrays (fixarray/array16/array32) and maps (fixmap/map16/map32).
// Out of scope:
// - float32 (write_float always emits float64).
//
// Failure modes that may escape ANY write_* function:
// - MsgpackEncodeError on encoder-defined limits (see Error enum).
// - std::bad_alloc from the underlying std::vector if buffer growth
// fails. The encoder offers no special handling - callers that
// might recover from OOM should treat the buffer as undefined-but-
// well-typed.
//
// For repeated records, reserve the expected maximum size once and reuse the
// same vector with clear(). This keeps its allocation while resetting its size.
#include "msgpack/endian.h"
#include <cstdint>
#include <cstring>
#include <functional>
#include <limits>
#include <span>
#include <stdexcept>
#include <string>
#include <string_view>
#include <vector>
namespace ccf::msgpack
{
// ===== Errors as data, throw at the boundary =====
enum class Error : uint8_t
{
STRING_TOO_LARGE = 1, // > 2^32-1 bytes
BIN_TOO_LARGE = 2, // > 2^32-1 bytes
INVALID_EVENT_TIME = 4, // outside Fluentd EventTime's representable range
};
// Every error knows how to describe itself. The returned string_view
// refers to a function-local string literal (static storage duration);
// it is safe to retain indefinitely.
//
// Tests should match on MsgpackEncodeError::error_code(), not what():
// what() messages are not part of the API contract and may be
// reformatted at any time.
[[nodiscard]] inline std::string_view to_string(Error e)
{
switch (e)
{
case Error::STRING_TOO_LARGE:
{
return "STRING_TOO_LARGE";
}
case Error::BIN_TOO_LARGE:
{
return "BIN_TOO_LARGE";
}
case Error::INVALID_EVENT_TIME:
{
return "INVALID_EVENT_TIME";
}
default:
{
return "UNKNOWN_MSGPACK_ERROR";
}
}
}
// Thrown by encoder boundary functions.
//
// API contract:
// - error_code() identifies the failure as a stable enum value.
// - what() returns a human-readable diagnostic that includes the
// offending value where applicable. The exact format is NOT
// part of the API; do not parse it. Tests asserting on a
// specific failure mode must match on error_code().
class MsgpackEncodeError : public std::runtime_error
{
public:
explicit MsgpackEncodeError(Error err, const std::string& what) :
std::runtime_error(what),
error(err)
{}
// Convenience constructor: composes the standard "<NAME>: <detail>"
// shape used at every throw site, ensuring every diagnostic
// includes the error code's name without each call site having to
// remember the convention.
[[nodiscard]] static MsgpackEncodeError make(
Error err, std::string_view detail)
{
return MsgpackEncodeError(
err, std::string{to_string(err)} + ": " + std::string{detail});
}
[[nodiscard]] Error error_code() const
{
return error;
}
private:
Error error;
};
// ===== Format byte constants =====
// Named per the msgpack spec so the write_* bodies read as direct
// transcriptions rather than magic numbers. Fix-family values are
// prefixes that get OR'd with a small N.
namespace fmt_byte
{
// Variable-length families.
constexpr uint8_t NIL = 0xC0;
constexpr uint8_t FALSE_ = 0xC2;
constexpr uint8_t TRUE_ = 0xC3;
constexpr uint8_t BIN_8 = 0xC4;
constexpr uint8_t BIN_16 = 0xC5;
constexpr uint8_t BIN_32 = 0xC6;
constexpr uint8_t FLOAT_64 = 0xCB;
constexpr uint8_t UINT_8 = 0xCC;
constexpr uint8_t UINT_16 = 0xCD;
constexpr uint8_t UINT_32 = 0xCE;
constexpr uint8_t UINT_64 = 0xCF;
constexpr uint8_t INT_8 = 0xD0;
constexpr uint8_t INT_16 = 0xD1;
constexpr uint8_t INT_32 = 0xD2;
constexpr uint8_t INT_64 = 0xD3;
constexpr uint8_t FIXEXT_8 = 0xD7;
constexpr uint8_t STR_8 = 0xD9;
constexpr uint8_t STR_16 = 0xDA;
constexpr uint8_t STR_32 = 0xDB;
constexpr uint8_t ARRAY_16 = 0xDC;
constexpr uint8_t ARRAY_32 = 0xDD;
constexpr uint8_t MAP_16 = 0xDE;
constexpr uint8_t MAP_32 = 0xDF;
// Fix-family prefixes (OR with the 4- or 5-bit count).
constexpr uint8_t FIXSTR_PREFIX = 0xA0; // 0b101XXXXX (0xA0..0xBF)
constexpr uint8_t FIXARRAY_PREFIX = 0x90; // 0b1001XXXX (0x90..0x9F)
constexpr uint8_t FIXMAP_PREFIX = 0x80; // 0b1000XXXX (0x80..0x8F)
// positive fixint: 0b0XXXXXXX (0x00..0x7F) - emitted as the value itself.
// negative fixint: 0b111XXXXX (0xE0..0xFF) - emitted as the int8 bit
// pattern.
} // namespace fmt_byte
// ===== Scalar encoders =====
inline void write_nil(std::vector<uint8_t>& buf)
{
buf.push_back(fmt_byte::NIL);
}
inline void write_bool(std::vector<uint8_t>& buf, bool v)
{
buf.push_back(v ? fmt_byte::TRUE_ : fmt_byte::FALSE_);
}
// Smallest-format-wins:
// [0, 127] -> positive fixint (1 byte)
// [128, 255] -> uint 8 (2 bytes)
// [256, 65535] -> uint 16 (3 bytes)
// [65536, 2^32-1] -> uint 32 (5 bytes)
// [2^32, 2^64-1] -> uint 64 (9 bytes)
inline void write_uint(std::vector<uint8_t>& buf, uint64_t v)
{
if (v <= 0x7FU)
{
buf.push_back(static_cast<uint8_t>(v));
}
else if (v <= 0xFFU)
{
utils::append_tagged_be<uint8_t>(
buf, fmt_byte::UINT_8, static_cast<uint8_t>(v));
}
else if (v <= 0xFFFFU)
{
utils::append_tagged_be<uint16_t>(
buf, fmt_byte::UINT_16, static_cast<uint16_t>(v));
}
else if (v <= 0xFFFFFFFFU)
{
utils::append_tagged_be<uint32_t>(
buf, fmt_byte::UINT_32, static_cast<uint32_t>(v));
}
else
{
utils::append_tagged_be<uint64_t>(buf, fmt_byte::UINT_64, v);
}
}
// Smallest-format-wins for signed values.
// For non-negative inputs we delegate to write_uint, so write_int(5)
// produces one byte 0x05 (positive fixint), not the wider int 8 form
// 0xD0 0x05. This is the spec's canonical form (smallest fitting
// family across the unsigned and signed numeric ranges).
//
// For negative values:
// [-32, -1] -> negative fixint (1 byte)
// [-128, -33] -> int 8 (2 bytes)
// [-32768, -129] -> int 16 (3 bytes)
// [-2^31, -32769] -> int 32 (5 bytes)
// [INT64_MIN, -2^31 - 1] -> int 64 (9 bytes)
inline void write_int(std::vector<uint8_t>& buf, int64_t v)
{
if (v >= 0)
{
write_uint(buf, static_cast<uint64_t>(v));
return;
}
if (v >= -32)
{
// negative fixint: 0b111XXXXX, value is the 5-bit two's-complement.
// Equivalently: byte = 0xE0 | (v & 0x1F), but the cleanest formulation
// is to take the unsigned bit-pattern of the int8.
buf.push_back(static_cast<uint8_t>(static_cast<int8_t>(v)));
}
else if (v >= std::numeric_limits<int8_t>::min())
{
utils::append_tagged_be<uint8_t>(
buf, fmt_byte::INT_8, static_cast<uint8_t>(static_cast<int8_t>(v)));
}
else if (v >= std::numeric_limits<int16_t>::min())
{
utils::append_tagged_be<uint16_t>(
buf, fmt_byte::INT_16, static_cast<uint16_t>(static_cast<int16_t>(v)));
}
else if (v >= std::numeric_limits<int32_t>::min())
{
utils::append_tagged_be<uint32_t>(
buf, fmt_byte::INT_32, static_cast<uint32_t>(static_cast<int32_t>(v)));
}
else
{
utils::append_tagged_be<uint64_t>(
buf, fmt_byte::INT_64, static_cast<uint64_t>(v));
}
}
// Always emits float64 (0xCB ...). float32 narrowing is not
// supported; callers wanting it can add a separate write_float32.
//
// NaN and infinity bit-patterns are passed through unchanged: the
// function performs no canonicalisation. A signalling NaN stays a
// signalling NaN; -inf stays -inf. If the caller needs canonical
// NaN encoding, normalise before calling.
inline void write_float(std::vector<uint8_t>& buf, double v)
{
static_assert(
sizeof(double) == 8, "ccf::msgpack assumes IEEE-754 binary64 doubles");
uint64_t bits = 0;
std::memcpy(&bits, &v, sizeof(bits));
utils::append_tagged_be<uint64_t>(buf, fmt_byte::FLOAT_64, bits);
}
// ===== str =====
//
// Smallest-format-wins:
// [0, 31] -> fixstr (1-byte header)
// [32, 255] -> str 8 (2-byte header)
// [256, 65535] -> str 16 (3-byte header)
// [65536, 2^32-1] -> str 32 (5-byte header)
// Throws MsgpackEncodeError(STRING_TOO_LARGE) for sizes >= 2^32.
//
// The payload is copied verbatim. The msgpack spec defines str as
// UTF-8, but this encoder does not validate it.
inline void write_str(std::vector<uint8_t>& buf, std::string_view s)
{
// The reinterpret_cast below from `const char*` to `const uint8_t*`
// is well-defined only if uint8_t IS unsigned char (so the access
// is "an unsigned char or std::byte" per [basic.lval]). Hold this
// invariant explicitly.
static_assert(
std::is_same_v<uint8_t, unsigned char>,
"ccf::msgpack assumes uint8_t == unsigned char");
std::string aliased_s;
if (!buf.empty() && !s.empty())
{
const auto less = std::less<const uint8_t*>{};
const auto* const buf_begin = buf.data();
const auto* const buf_end = buf_begin + buf.size();
const auto* const s_begin = reinterpret_cast<const uint8_t*>(s.data());
const auto* const s_end = s_begin + s.size();
if (less(s_begin, buf_end) && less(buf_begin, s_end))
{
aliased_s.assign(s);
s = aliased_s;
}
}
const auto n = s.size();
if (n <= 31U)
{
buf.push_back(static_cast<uint8_t>(fmt_byte::FIXSTR_PREFIX | n));
}
else if (n <= 0xFFU)
{
utils::append_tagged_be<uint8_t>(
buf, fmt_byte::STR_8, static_cast<uint8_t>(n));
}
else if (n <= 0xFFFFU)
{
utils::append_tagged_be<uint16_t>(
buf, fmt_byte::STR_16, static_cast<uint16_t>(n));
}
else if (n <= 0xFFFFFFFFULL)
{
utils::append_tagged_be<uint32_t>(
buf, fmt_byte::STR_32, static_cast<uint32_t>(n));
}
else
{
throw MsgpackEncodeError::make(
Error::STRING_TOO_LARGE,
"string length " + std::to_string(n) + " exceeds 2^32 - 1");
}
if (!s.empty())
{
buf.insert(
buf.end(),
reinterpret_cast<const uint8_t*>(s.data()),
reinterpret_cast<const uint8_t*>(s.data()) + n);
}
}
// ===== bin =====
//
// Smallest-format-wins:
// [0, 255] -> bin 8 (2-byte header)
// [256, 65535] -> bin 16 (3-byte header)
// [65536, 2^32-1] -> bin 32 (5-byte header)
// Throws MsgpackEncodeError(BIN_TOO_LARGE) for sizes >= 2^32.
inline void write_bin(
std::vector<uint8_t>& buf, std::span<const uint8_t> data)
{
std::vector<uint8_t> aliased_data;
if (!buf.empty() && !data.empty())
{
const auto less = std::less<const uint8_t*>{};
const auto* const buf_begin = buf.data();
const auto* const buf_end = buf_begin + buf.size();
const auto* const data_begin = data.data();
const auto* const data_end = data_begin + data.size();
if (less(data_begin, buf_end) && less(buf_begin, data_end))
{
aliased_data.assign(data.begin(), data.end());
data = aliased_data;
}
}
const auto n = data.size();
if (n <= 0xFFU)
{
utils::append_tagged_be<uint8_t>(
buf, fmt_byte::BIN_8, static_cast<uint8_t>(n));
}
else if (n <= 0xFFFFU)
{
utils::append_tagged_be<uint16_t>(
buf, fmt_byte::BIN_16, static_cast<uint16_t>(n));
}
else if (n <= 0xFFFFFFFFULL)
{
utils::append_tagged_be<uint32_t>(
buf, fmt_byte::BIN_32, static_cast<uint32_t>(n));
}
else
{
throw MsgpackEncodeError::make(
Error::BIN_TOO_LARGE,
"bin length " + std::to_string(n) + " exceeds 2^32 - 1");
}
buf.insert(buf.end(), data.begin(), data.end());
}
// ===== container headers =====
//
// Coupling: the wire format requires the element count up front, so
// the caller must subsequently emit exactly `n` values (or `n`
// key/value pairs for a map). A wrong `n` produces malformed msgpack
// output silently - the encoder cannot check this at the header
// call site.
// Smallest-format-wins:
// [0, 15] -> fixarray (1-byte header)
// [16, 65535] -> array_16 (3-byte header)
// [65536, 2^32-1] -> array_32 (5-byte header)
// Cannot throw MsgpackEncodeError: the input is uint32_t, so every
// value fits one of the above families.
inline void write_array_header(std::vector<uint8_t>& buf, uint32_t n)
{
if (n <= 15U)
{
buf.push_back(static_cast<uint8_t>(fmt_byte::FIXARRAY_PREFIX | n));
}
else if (n <= 0xFFFFU)
{
utils::append_tagged_be<uint16_t>(
buf, fmt_byte::ARRAY_16, static_cast<uint16_t>(n));
}
else
{
utils::append_tagged_be<uint32_t>(buf, fmt_byte::ARRAY_32, n);
}
}
// Smallest-format-wins:
// [0, 15] -> fixmap (1-byte header)
// [16, 65535] -> map_16 (3-byte header)
// [65536, 2^32-1] -> map_32 (5-byte header)
inline void write_map_header(std::vector<uint8_t>& buf, uint32_t n)
{
if (n <= 15U)
{
buf.push_back(static_cast<uint8_t>(fmt_byte::FIXMAP_PREFIX | n));
}
else if (n <= 0xFFFFU)
{
utils::append_tagged_be<uint16_t>(
buf, fmt_byte::MAP_16, static_cast<uint16_t>(n));
}
else
{
utils::append_tagged_be<uint32_t>(buf, fmt_byte::MAP_32, n);
}
}
} // namespace ccf::msgpack
You can’t perform that action at this time.
