DeterministicESPAsyncWebServer v6.27.1
Zero-allocation, bounded-execution async HTTP server for ESP32
Loading...
Searching...
No Matches
cbor.cpp
Go to the documentation of this file.
1// Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file cbor.cpp
6 * @brief Zero-heap CBOR (RFC 8949) encoder implementation.
7 */
8
9#include "cbor.h"
10
11#if DETWS_ENABLE_CBOR
12
14#include <string.h>
15
16void cbor_init(CborWriter *w, uint8_t *buf, size_t cap)
17{
18 det_bw_init(w, buf, cap);
19}
20
21size_t cbor_len(const CborWriter *w)
22{
23 return det_bw_len(w);
24}
25
26bool cbor_ok(const CborWriter *w)
27{
28 return det_bw_ok(w);
29}
30
31static void put(CborWriter *w, uint8_t b)
32{
33 det_bw_put(w, b);
34}
35
36// Write a CBOR head: the major type (top 3 bits) plus the argument, choosing the
37// shortest of the 1/2/3/5/9-byte forms (RFC 8949 section 3). The argument after
38// the lead byte is big-endian, the same network order as MessagePack.
39static void head(CborWriter *w, uint8_t major, uint64_t val)
40{
41 uint8_t m = (uint8_t)(major << 5);
42 if (val < 24)
43 {
44 put(w, (uint8_t)(m | val));
45 }
46 else if (val < 0x100ULL)
47 {
48 put(w, (uint8_t)(m | 24));
49 det_bw_put_be(w, val, 1);
50 }
51 else if (val < 0x10000ULL)
52 {
53 put(w, (uint8_t)(m | 25));
54 det_bw_put_be(w, val, 2);
55 }
56 else if (val < 0x100000000ULL)
57 {
58 put(w, (uint8_t)(m | 26));
59 det_bw_put_be(w, val, 4);
60 }
61 else
62 {
63 put(w, (uint8_t)(m | 27));
64 det_bw_put_be(w, val, 8);
65 }
66}
67
68void cbor_uint(CborWriter *w, uint64_t v)
69{
70 head(w, 0, v);
71}
72
73void cbor_int(CborWriter *w, int64_t v)
74{
75 if (v >= 0)
76 head(w, 0, (uint64_t)v);
77 else
78 head(w, 1, (uint64_t)(-1 - v)); // major 1 encodes -1 - n
79}
80
81void cbor_bytes(CborWriter *w, const uint8_t *data, size_t len)
82{
83 head(w, 2, (uint64_t)len);
84 for (size_t i = 0; i < len; i++)
85 put(w, data[i]);
86}
87
88void cbor_text_n(CborWriter *w, const char *s, size_t len)
89{
90 head(w, 3, (uint64_t)len);
91 for (size_t i = 0; i < len; i++)
92 put(w, (uint8_t)s[i]);
93}
94
95void cbor_text(CborWriter *w, const char *s)
96{
97 cbor_text_n(w, s, s ? strnlen(s, w->cap + 1) : 0);
98}
99
100void cbor_bool(CborWriter *w, bool b)
101{
102 put(w, b ? 0xf5 : 0xf4);
103}
104
105void cbor_null(CborWriter *w)
106{
107 put(w, 0xf6);
108}
109
110void cbor_float(CborWriter *w, float f)
111{
112 uint32_t bits;
113 memcpy(&bits, &f, sizeof(bits));
114 put(w, 0xfa); // major 7, single-precision
115 det_bw_put_be(w, bits, 4);
116}
117
118void cbor_array(CborWriter *w, size_t count)
119{
120 head(w, 4, (uint64_t)count);
121}
122
123void cbor_map(CborWriter *w, size_t count)
124{
125 head(w, 5, (uint64_t)count);
126}
127
128// ---------------------------------------------------------------------------
129// Decoder
130// ---------------------------------------------------------------------------
131
132void cbor_reader_init(CborReader *r, const uint8_t *buf, size_t len)
133{
134 det_br_init(r, buf, len);
135}
136
137bool cbor_reader_ok(const CborReader *r)
138{
139 return det_br_ok(r);
140}
141
142// Read a CBOR head at r->pos: major type + argument, advancing pos. Sets err and
143// returns false on out-of-bounds or a reserved/indefinite additional-info value.
144static bool read_head(CborReader *r, uint8_t *major, uint64_t *val)
145{
146 if (r->err || r->pos >= r->len)
147 {
148 r->err = true;
149 return false;
150 }
151 uint8_t b = r->buf[r->pos];
152 uint8_t info = (uint8_t)(b & 0x1f);
153 *major = (uint8_t)(b >> 5);
154 if (info < 24)
155 {
156 *val = info;
157 r->pos += 1;
158 return true;
159 }
160 size_t need;
161 switch (info)
162 {
163 case 24:
164 need = 1;
165 break;
166 case 25:
167 need = 2;
168 break;
169 case 26:
170 need = 4;
171 break;
172 case 27:
173 need = 8;
174 break;
175 default:
176 r->err = true; // 28-31: reserved / indefinite-length, unsupported
177 return false;
178 }
179 // The argument is the `need` big-endian bytes after this head byte.
180 return det_br_take_be(r, need, val);
181}
182
183CborType cbor_peek(CborReader *r)
184{
185 if (r->err || r->pos >= r->len)
186 return CborType::CBOR_TYPE_INVALID;
187 uint8_t b = r->buf[r->pos];
188 switch (b >> 5)
189 {
190 case 0:
191 return CborType::CBOR_TYPE_UINT;
192 case 1:
193 return CborType::CBOR_TYPE_INT;
194 case 2:
195 return CborType::CBOR_TYPE_BYTES;
196 case 3:
197 return CborType::CBOR_TYPE_TEXT;
198 case 4:
199 return CborType::CBOR_TYPE_ARRAY;
200 case 5:
201 return CborType::CBOR_TYPE_MAP;
202 case 7: {
203 uint8_t info = (uint8_t)(b & 0x1f);
204 if (info == 20 || info == 21)
205 return CborType::CBOR_TYPE_BOOL;
206 if (info == 22)
207 return CborType::CBOR_TYPE_NULL;
208 if (info == 26 || info == 27)
209 return CborType::CBOR_TYPE_FLOAT;
210 return CborType::CBOR_TYPE_INVALID;
211 }
212 default:
213 return CborType::CBOR_TYPE_INVALID; // major 6 (tags) unsupported
214 }
215}
216
217bool cbor_read_uint(CborReader *r, uint64_t *out)
218{
219 uint8_t m;
220 uint64_t v;
221 if (!read_head(r, &m, &v))
222 return false;
223 if (m != 0)
224 {
225 r->err = true;
226 return false;
227 }
228 *out = v;
229 return true;
230}
231
232bool cbor_read_int(CborReader *r, int64_t *out)
233{
234 uint8_t m;
235 uint64_t v;
236 if (!read_head(r, &m, &v))
237 return false;
238 if (m == 0)
239 *out = (int64_t)v;
240 else if (m == 1)
241 *out = -1 - (int64_t)v;
242 else
243 {
244 r->err = true;
245 return false;
246 }
247 return true;
248}
249
250bool cbor_read_bool(CborReader *r, bool *out)
251{
252 if (r->err || r->pos >= r->len)
253 {
254 r->err = true;
255 return false;
256 }
257 uint8_t b = r->buf[r->pos];
258 if (b == 0xf4)
259 *out = false;
260 else if (b == 0xf5)
261 *out = true;
262 else
263 {
264 r->err = true;
265 return false;
266 }
267 r->pos += 1;
268 return true;
269}
270
271bool cbor_read_null(CborReader *r)
272{
273 if (r->err || r->pos >= r->len || r->buf[r->pos] != 0xf6)
274 {
275 r->err = true;
276 return false;
277 }
278 r->pos += 1;
279 return true;
280}
281
282bool cbor_read_float(CborReader *r, float *out)
283{
284 if (r->err || r->pos >= r->len)
285 {
286 r->err = true;
287 return false;
288 }
289 uint8_t b = r->buf[r->pos];
290 if (b == 0xfa) // single
291 {
292 uint64_t v;
293 if (!det_br_take_be(r, 4, &v))
294 return false;
295 uint32_t bits = (uint32_t)v;
296 memcpy(out, &bits, sizeof(*out));
297 return true;
298 }
299 if (b == 0xfb) // double -> narrow to float
300 {
301 uint64_t bits;
302 if (!det_br_take_be(r, 8, &bits))
303 return false;
304 double d;
305 memcpy(&d, &bits, sizeof(d));
306 *out = (float)d;
307 return true;
308 }
309 r->err = true;
310 return false;
311}
312
313// Shared body for text (major 3) and byte (major 2) strings.
314static bool read_str(CborReader *r, uint8_t want_major, const uint8_t **out, size_t *len)
315{
316 uint8_t m;
317 uint64_t v;
318 if (!read_head(r, &m, &v))
319 return false;
320 if (m != want_major || r->pos + v > r->len)
321 {
322 r->err = true;
323 return false;
324 }
325 *out = &r->buf[r->pos];
326 *len = (size_t)v;
327 r->pos += (size_t)v;
328 return true;
329}
330
331bool cbor_read_text(CborReader *r, const char **out, size_t *len)
332{
333 return read_str(r, 3, (const uint8_t **)out, len);
334}
335
336bool cbor_read_bytes(CborReader *r, const uint8_t **out, size_t *len)
337{
338 return read_str(r, 2, out, len);
339}
340
341bool cbor_read_array(CborReader *r, size_t *count)
342{
343 uint8_t m;
344 uint64_t v;
345 if (!read_head(r, &m, &v))
346 return false;
347 if (m != 4)
348 {
349 r->err = true;
350 return false;
351 }
352 *count = (size_t)v;
353 return true;
354}
355
356bool cbor_read_map(CborReader *r, size_t *count)
357{
358 uint8_t m;
359 uint64_t v;
360 if (!read_head(r, &m, &v))
361 return false;
362 if (m != 5)
363 {
364 r->err = true;
365 return false;
366 }
367 *count = (size_t)v;
368 return true;
369}
370
371#endif // DETWS_ENABLE_CBOR
Shared byte-cursor mechanics for the binary codecs (one source of truth).
void det_bw_put_be(W *w, uint64_t val, int nbytes)
Append the low nbytes of val, big-endian (network order).
Definition bytes.h:63
bool det_br_take_be(R *r, size_t nbytes, uint64_t *out)
Read nbytes big-endian immediately after the tag byte at pos, advancing past the tag and the argument...
Definition bytes.h:94
void det_br_init(R *r, const uint8_t *buf, size_t len)
Bind a read cursor to buf (length len) at offset 0.
Definition bytes.h:72
void det_bw_put(W *w, uint8_t b)
Append one byte; on overflow set the flag but keep counting pos.
Definition bytes.h:53
bool det_br_ok(const R *r)
True while no malformed / out-of-bounds read has occurred.
Definition bytes.h:81
bool det_bw_ok(const W *w)
True while every write has fit in the buffer.
Definition bytes.h:47
void det_bw_init(W *w, uint8_t *buf, size_t cap)
Bind a write cursor to buf (capacity cap) and reset it.
Definition bytes.h:32
size_t det_bw_len(const W *w)
Bytes the payload needs so far (keeps counting past cap on overflow).
Definition bytes.h:41
Layer 6 (Presentation) - zero-heap CBOR (RFC 8949) encoder.