microfmt
Zero-allocation C++ formatting for constrained environments
Loading...
Searching...
No Matches
cbor.hpp
Go to the documentation of this file.
1// SPDX-FileCopyrightText: 2026 Jarosław Pelczar <jarek@jpelczar.com>
2//
3// SPDX-License-Identifier: BSD-2-Clause
4
5#pragma once
6
7#include "../microfmt.hpp"
8#include "../reloco.hpp"
9#include <cstddef>
10#include <cstdint>
11#include <string_view>
12#include <type_traits>
13#include <utility>
14
23namespace microfmt::cbor {
24
25// ============================================================================
26// CBOR Major Types & Control Constants
27// ============================================================================
28
29namespace detail {
30inline constexpr uint8_t MT_UNSIGNED = 0x00; // Major 0
31inline constexpr uint8_t MT_NEGATIVE = 0x20; // Major 1
32inline constexpr uint8_t MT_BYTES = 0x40; // Major 2
33inline constexpr uint8_t MT_TEXT = 0x60; // Major 3
34inline constexpr uint8_t MT_ARRAY = 0x80; // Major 4
35inline constexpr uint8_t MT_MAP = 0xA0; // Major 5
36inline constexpr uint8_t MT_SIMPLE = 0xE0; // Major 7
37
38inline constexpr uint8_t BREAK_BYTE = 0xFF; // Stop code for indefinite containers
39
40// wire-format encoding: bytes are deliberately converted between char and unsigned byte.
41RELOCO_BEGIN_UNSAFE_CODE
42inline void encode_header(const sink &out, uint8_t major, uint64_t val) noexcept {
43 if (val < 24) {
44 out.put(static_cast<char>(major | static_cast<uint8_t>(val)));
45 } else if (val <= 0xFF) {
46 out.put(static_cast<char>(major | 24));
47 out.put(static_cast<char>(val));
48 } else if (val <= 0xFFFF) {
49 out.put(static_cast<char>(major | 25));
50 out.put(static_cast<char>((val >> 8) & 0xFF));
51 out.put(static_cast<char>(val & 0xFF));
52 } else if (val <= 0xFFFFFFFF) {
53 out.put(static_cast<char>(major | 26));
54 out.put(static_cast<char>((val >> 24) & 0xFF));
55 out.put(static_cast<char>((val >> 16) & 0xFF));
56 out.put(static_cast<char>((val >> 8) & 0xFF));
57 out.put(static_cast<char>(val & 0xFF));
58 } else {
59 out.put(static_cast<char>(major | 27));
60 for (int shift = 56; shift >= 0; shift -= 8) {
61 out.put(static_cast<char>((val >> shift) & 0xFF));
62 }
63 }
64}
66} // namespace detail
67
68// ============================================================================
69// RAII Scoped Map & Array Writers (Indefinite-Length Streaming)
70// ============================================================================
71
72class array_writer;
73
80class MICROFMT_API_CLASS RELOCO_CONSUMABLE(unconsumed) RELOCO_POINTER map_writer {
81public:
82 // wire-format encoding: bytes are deliberately converted between char and unsigned byte.
83 RELOCO_BEGIN_UNSAFE_CODE
84 explicit map_writer(sink out) noexcept RELOCO_RETURN_TYPESTATE(unconsumed) : out_(std::move(out)) {
85 // 0xBF: Indefinite-length map
86 out_.put(static_cast<char>(detail::MT_MAP | 31));
87 }
89
90 ~map_writer() noexcept { end(); }
91
92 map_writer(const map_writer &) = delete;
93 map_writer &operator=(const map_writer &) = delete;
94 map_writer(map_writer &&other) noexcept RELOCO_RETURN_TYPESTATE(unconsumed)
95 : out_(std::move(other.out_)), closed_(other.closed_) {
96 other.closed_ = true;
97 }
98
99 // Helper for Clang if object state is unknown
100 map_writer &as_known() & noexcept RELOCO_CALLABLE_WHEN("unconsumed", "unknown") RELOCO_RETURN_TYPESTATE(unconsumed) {
101 RELOCO_ASSERT(!closed_, "Attempt to reuse consumed state");
102 return *this;
103 }
104
105 // Key emitters (supports both Text keys and Integer/Tag keys for compact
106 // frames)
107 map_writer &key(microfmt::string_view k) & noexcept RELOCO_CALLABLE_WHEN("unconsumed") {
108 detail::encode_header(out_, detail::MT_TEXT, k.size());
109 out_.write(k);
110 return *this;
111 }
112
113 map_writer &key(uint32_t int_key) & noexcept RELOCO_CALLABLE_WHEN("unconsumed") {
114 detail::encode_header(out_, detail::MT_UNSIGNED, int_key);
115 return *this;
116 }
117
118 // Key-Value primitives (String Key)
119 map_writer &kv(microfmt::string_view k, microfmt::string_view val) & noexcept RELOCO_CALLABLE_WHEN("unconsumed") {
120 key(k);
121 detail::encode_header(out_, detail::MT_TEXT, val.size());
122 out_.write(val);
123 return *this;
124 }
125
126 // wire-format encoding: bytes are deliberately converted between char and unsigned byte.
127 RELOCO_BEGIN_UNSAFE_CODE
128 map_writer &kv(microfmt::string_view k, const char *val) & noexcept RELOCO_CALLABLE_WHEN("unconsumed") {
129 key(k);
130 if (val == nullptr) {
131 out_.put(static_cast<char>(detail::MT_SIMPLE | 22));
132 } else {
133 detail::encode_header(out_, detail::MT_TEXT, microfmt::string_view(val).size());
134 out_.write(val);
135 }
136 return *this;
137 }
139
140 template <std::size_t N>
141 map_writer &kv(microfmt::string_view k, const char (&val)[N]) & noexcept RELOCO_CALLABLE_WHEN("unconsumed") {
142 return kv(k, microfmt::string_view(val, N - 1));
143 }
144
145 map_writer &kv(microfmt::string_view k, span<const uint8_t> bytes) & noexcept RELOCO_CALLABLE_WHEN("unconsumed") {
146 key(k);
147 detail::encode_header(out_, detail::MT_BYTES, bytes.size());
148 for (uint8_t b : bytes)
149 out_.put(static_cast<char>(b));
150 return *this;
151 }
152
153 // wire-format encoding: bytes are deliberately converted between char and unsigned byte.
154 RELOCO_BEGIN_UNSAFE_CODE
155 map_writer &kv(microfmt::string_view k, bool val) & noexcept RELOCO_CALLABLE_WHEN("unconsumed") {
156 key(k);
157 out_.put(static_cast<char>(detail::MT_SIMPLE | (val ? 21 : 20))); // 0xF5 (true), 0xF4 (false)
158 return *this;
159 }
161
162 // wire-format encoding: bytes are deliberately converted between char and unsigned byte.
163 RELOCO_BEGIN_UNSAFE_CODE
164 map_writer &kv(microfmt::string_view k, std::nullptr_t) & noexcept RELOCO_CALLABLE_WHEN("unconsumed") {
165 key(k);
166 out_.put(static_cast<char>(detail::MT_SIMPLE | 22)); // 0xF6 (null)
167 return *this;
168 }
170
171 template <typename T,
172 typename std::enable_if<std::is_integral<T>::value && !std::is_same<T, bool>::value, int>::type = 0>
173 map_writer &kv(microfmt::string_view k, T val) & noexcept RELOCO_CALLABLE_WHEN("unconsumed") {
174 key(k);
175 if constexpr (std::is_signed_v<T>) {
176 if (val < 0) {
177 detail::encode_header(out_, detail::MT_NEGATIVE, static_cast<uint64_t>(-1 - val));
178 } else {
179 detail::encode_header(out_, detail::MT_UNSIGNED, static_cast<uint64_t>(val));
180 }
181 } else {
182 detail::encode_header(out_, detail::MT_UNSIGNED, static_cast<uint64_t>(val));
183 }
184 return *this;
185 }
186
187 // Key-Value primitives (Compact Integer Key)
188 template <typename T,
189 typename std::enable_if<std::is_integral<T>::value && !std::is_same<T, bool>::value, int>::type = 0>
190 map_writer &kv(uint32_t k, T val) & noexcept RELOCO_CALLABLE_WHEN("unconsumed") {
191 key(k);
192 if constexpr (std::is_signed_v<T>) {
193 if (val < 0) {
194 detail::encode_header(out_, detail::MT_NEGATIVE, static_cast<uint64_t>(-1 - val));
195 } else {
196 detail::encode_header(out_, detail::MT_UNSIGNED, static_cast<uint64_t>(val));
197 }
198 } else {
199 detail::encode_header(out_, detail::MT_UNSIGNED, static_cast<uint64_t>(val));
200 }
201 return *this;
202 }
203
204 [[nodiscard]] map_writer nested_map(microfmt::string_view k) noexcept RELOCO_CALLABLE_WHEN("unconsumed")
205 RELOCO_RETURN_TYPESTATE(unconsumed) {
206 key(k);
207 return map_writer(out_);
208 }
209
210 [[nodiscard]] array_writer nested_array(microfmt::string_view k) noexcept RELOCO_CALLABLE_WHEN("unconsumed")
211 RELOCO_RETURN_TYPESTATE(unconsumed);
212
213 void end() noexcept RELOCO_CALLABLE_WHEN("unconsumed", "consumed") RELOCO_SET_TYPESTATE(consumed) {
214 if (!closed_) {
215 out_.put(static_cast<char>(detail::BREAK_BYTE)); // 0xFF
216 closed_ = true;
217 }
218 }
219
220private:
221 sink out_;
222 bool closed_{false};
223};
224
229class MICROFMT_API_CLASS RELOCO_CONSUMABLE(unconsumed) array_writer {
230public:
231 // wire-format encoding: bytes are deliberately converted between char and unsigned byte.
232 RELOCO_BEGIN_UNSAFE_CODE
233 explicit array_writer(sink out) noexcept RELOCO_RETURN_TYPESTATE(unconsumed) : out_(std::move(out)) {
234 // 0x9F: Indefinite-length array
235 out_.put(static_cast<char>(detail::MT_ARRAY | 31));
236 }
237 RELOCO_END_UNSAFE_CODE
238
239 ~array_writer() noexcept { end(); }
240
241 array_writer(const array_writer &) = delete;
242 array_writer &operator=(const array_writer &) = delete;
243 array_writer(array_writer &&other) noexcept RELOCO_RETURN_TYPESTATE(unconsumed)
244 : out_(std::move(other.out_)), closed_(other.closed_) {
245 other.closed_ = true;
246 }
247
248 // Helper for Clang if object state is unknown
249 array_writer &as_known() & noexcept RELOCO_CALLABLE_WHEN("unconsumed", "unknown")
250 RELOCO_RETURN_TYPESTATE(unconsumed) {
251 RELOCO_ASSERT(!closed_, "Attempt to reuse consumed state");
252 return *this;
253 }
254
255 array_writer &val(microfmt::string_view v) & noexcept RELOCO_CALLABLE_WHEN("unconsumed") {
256 detail::encode_header(out_, detail::MT_TEXT, v.size());
257 out_.write(v);
258 return *this;
259 }
260
261 // wire-format encoding: bytes are deliberately converted between char and unsigned byte.
262 RELOCO_BEGIN_UNSAFE_CODE
263 array_writer &val(const char *v) & noexcept RELOCO_CALLABLE_WHEN("unconsumed") {
264 if (v == nullptr) {
265 out_.put(static_cast<char>(detail::MT_SIMPLE | 22));
266 } else {
267 const microfmt::string_view text(v);
268 detail::encode_header(out_, detail::MT_TEXT, text.size());
269 out_.write(text);
270 }
271 return *this;
272 }
274
275 template <std::size_t N> array_writer &val(const char (&v)[N]) & noexcept RELOCO_CALLABLE_WHEN("unconsumed") {
276 return val(microfmt::string_view(v, N - 1));
277 }
278
279 array_writer &val(span<const uint8_t> bytes) & noexcept RELOCO_CALLABLE_WHEN("unconsumed") {
280 detail::encode_header(out_, detail::MT_BYTES, bytes.size());
281 for (uint8_t b : bytes)
282 out_.put(static_cast<char>(b));
283 return *this;
284 }
285
286 // wire-format encoding: bytes are deliberately converted between char and unsigned byte.
287 RELOCO_BEGIN_UNSAFE_CODE
288 array_writer &val(bool v) & noexcept RELOCO_CALLABLE_WHEN("unconsumed") {
289 out_.put(static_cast<char>(detail::MT_SIMPLE | (v ? 21 : 20)));
290 return *this;
291 }
293
294 // wire-format encoding: bytes are deliberately converted between char and unsigned byte.
295 RELOCO_BEGIN_UNSAFE_CODE
296 array_writer &val(std::nullptr_t) & noexcept RELOCO_CALLABLE_WHEN("unconsumed") {
297 out_.put(static_cast<char>(detail::MT_SIMPLE | 22));
298 return *this;
299 }
301
302 template <typename T,
303 typename std::enable_if<std::is_integral<T>::value && !std::is_same<T, bool>::value, int>::type = 0>
304 array_writer &val(T v) & noexcept RELOCO_CALLABLE_WHEN("unconsumed") {
305 if constexpr (std::is_signed_v<T>) {
306 if (v < 0) {
307 detail::encode_header(out_, detail::MT_NEGATIVE, static_cast<uint64_t>(-1 - v));
308 } else {
309 detail::encode_header(out_, detail::MT_UNSIGNED, static_cast<uint64_t>(v));
310 }
311 } else {
312 detail::encode_header(out_, detail::MT_UNSIGNED, static_cast<uint64_t>(v));
313 }
314 return *this;
315 }
316
317 [[nodiscard]] map_writer map() & noexcept RELOCO_LIFETIMEBOUND RELOCO_CALLABLE_WHEN("unconsumed")
318 RELOCO_RETURN_TYPESTATE(unconsumed) {
319 return map_writer(out_);
320 }
321
322 void end() noexcept RELOCO_CALLABLE_WHEN("unconsumed", "consumed") RELOCO_SET_TYPESTATE(consumed) {
323 if (!closed_) {
324 out_.put(static_cast<char>(detail::BREAK_BYTE)); // 0xFF
325 closed_ = true;
326 }
327 }
328
329private:
330 sink out_;
331 bool closed_{false};
332};
333
334inline array_writer map_writer::nested_array(microfmt::string_view k) noexcept {
335 key(k);
336 return array_writer(out_);
337}
338
339// ============================================================================
340// Lambda View Adapter for microfmt Integration
341// ============================================================================
342
343template <typename Fn> struct cbor_map_view {
344 Fn fn;
345};
346
348template <typename Fn> [[nodiscard]] constexpr auto cbor_map(Fn &&fn) noexcept {
349 return cbor_map_view<std::decay_t<Fn>>{std::forward<Fn>(fn)};
350}
351
352} // namespace microfmt::cbor
353
354namespace microfmt {
355
356template <typename Fn> struct formatter<cbor::cbor_map_view<Fn>> {
357 constexpr void parse(format_parse_context &) noexcept {}
358
359 void format(const cbor::cbor_map_view<Fn> &v, const sink &out) const noexcept {
360 cbor::map_writer w(out);
361 v.fn(w);
362 }
363};
364
365} // namespace microfmt
Definition microfmt.hpp:881
#define MICROFMT_API_CLASS
Definition compat.hpp:181
constexpr uint8_t MT_ARRAY
Definition cbor.hpp:34
constexpr uint8_t MT_MAP
Definition cbor.hpp:35
RELOCO_BEGIN_UNSAFE_CODE void encode_header(const sink &out, uint8_t major, uint64_t val) noexcept
Definition cbor.hpp:42
constexpr uint8_t MT_SIMPLE
Definition cbor.hpp:36
constexpr uint8_t MT_NEGATIVE
Definition cbor.hpp:31
constexpr uint8_t MT_BYTES
Definition cbor.hpp:32
constexpr uint8_t MT_TEXT
Definition cbor.hpp:33
constexpr uint8_t BREAK_BYTE
Definition cbor.hpp:38
constexpr uint8_t MT_UNSIGNED
Definition cbor.hpp:30
Definition cbor.hpp:23
constexpr auto cbor_map(Fn &&fn) noexcept
Wraps a map-writing callable for use as a format argument.
Definition cbor.hpp:348
class MICROFMT_API_CLASS RELOCO_CONSUMABLE(unconsumed) RELOCO_POINTER map_writer
RAII writer for an indefinite-length streaming CBOR map.
Definition cbor.hpp:80
Definition assert.hpp:19
reloco::sink sink
Type-erased, zero-allocation output sink.
Definition microfmt.hpp:46
constexpr bytes_view bytes(uint64_t count) noexcept
Definition format_helpers.hpp:82
RELOCO_END_UNSAFE_CODE
Definition remote_object.hpp:132
Definition cbor.hpp:343
Fn fn
Definition cbor.hpp:344
void format(const cbor::cbor_map_view< Fn > &v, const sink &out) const noexcept
Definition cbor.hpp:359
constexpr void parse(format_parse_context &) noexcept
Definition cbor.hpp:357
Definition microfmt.hpp:985