DeterministicESPAsyncWebServer v6.27.1
Zero-allocation, bounded-execution async HTTP server for ESP32
Loading...
Searching...
No Matches
msgpack.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 msgpack.cpp
6 * @brief Zero-heap MessagePack encoder and decoder implementation.
7 */
8
9#include "msgpack.h"
10
11#if DETWS_ENABLE_MSGPACK
12
14#include <string.h>
15
16void msgpack_init(MsgpackWriter *w, uint8_t *buf, size_t cap)
17{
18 det_bw_init(w, buf, cap);
19}
20
21size_t msgpack_len(const MsgpackWriter *w)
22{
23 return det_bw_len(w);
24}
25
26bool msgpack_ok(const MsgpackWriter *w)
27{
28 return det_bw_ok(w);
29}
30
31// Thin local names over the shared byte cursor (bytes.h) so the call sites
32// below read the same as before; the cursor invariants live in one place.
33static void put(MsgpackWriter *w, uint8_t b)
34{
35 det_bw_put(w, b);
36}
37
38// Write the low @p nbytes of @p val, big-endian (MessagePack is network order).
39static void put_be(MsgpackWriter *w, uint64_t val, int nbytes)
40{
41 det_bw_put_be(w, val, nbytes);
42}
43
44void msgpack_uint(MsgpackWriter *w, uint64_t v)
45{
46 if (v <= 0x7f)
47 put(w, (uint8_t)v); // positive fixint
48 else if (v <= 0xff)
49 {
50 put(w, 0xcc);
51 put(w, (uint8_t)v);
52 }
53 else if (v <= 0xffff)
54 {
55 put(w, 0xcd);
56 put_be(w, v, 2);
57 }
58 else if (v <= 0xffffffffULL)
59 {
60 put(w, 0xce);
61 put_be(w, v, 4);
62 }
63 else
64 {
65 put(w, 0xcf);
66 put_be(w, v, 8);
67 }
68}
69
70void msgpack_int(MsgpackWriter *w, int64_t v)
71{
72 if (v >= 0)
73 {
74 msgpack_uint(w, (uint64_t)v);
75 return;
76 }
77 if (v >= -32)
78 put(w, (uint8_t)v); // negative fixint (two's-complement byte 0xe0..0xff)
79 else if (v >= -128)
80 {
81 put(w, 0xd0);
82 put(w, (uint8_t)v);
83 }
84 else if (v >= -32768)
85 {
86 put(w, 0xd1);
87 put_be(w, (uint64_t)(uint16_t)v, 2);
88 }
89 else if (v >= -2147483648LL)
90 {
91 put(w, 0xd2);
92 put_be(w, (uint64_t)(uint32_t)v, 4);
93 }
94 else
95 {
96 put(w, 0xd3);
97 put_be(w, (uint64_t)v, 8);
98 }
99}
100
101void msgpack_str_n(MsgpackWriter *w, const char *s, size_t len)
102{
103 if (len <= 31)
104 put(w, (uint8_t)(0xa0 | len)); // fixstr
105 else if (len <= 0xff)
106 {
107 put(w, 0xd9);
108 put(w, (uint8_t)len);
109 }
110 else if (len <= 0xffff)
111 {
112 put(w, 0xda);
113 put_be(w, len, 2);
114 }
115 else
116 {
117 put(w, 0xdb);
118 put_be(w, len, 4);
119 }
120 for (size_t i = 0; i < len; i++)
121 put(w, (uint8_t)s[i]);
122}
123
124void msgpack_str(MsgpackWriter *w, const char *s)
125{
126 msgpack_str_n(w, s, s ? strnlen(s, w->cap + 1) : 0);
127}
128
129void msgpack_bytes(MsgpackWriter *w, const uint8_t *data, size_t len)
130{
131 if (len <= 0xff)
132 {
133 put(w, 0xc4);
134 put(w, (uint8_t)len);
135 }
136 else if (len <= 0xffff)
137 {
138 put(w, 0xc5);
139 put_be(w, len, 2);
140 }
141 else
142 {
143 put(w, 0xc6);
144 put_be(w, len, 4);
145 }
146 for (size_t i = 0; i < len; i++)
147 put(w, data[i]);
148}
149
150void msgpack_bool(MsgpackWriter *w, bool b)
151{
152 put(w, b ? 0xc3 : 0xc2);
153}
154
155void msgpack_nil(MsgpackWriter *w)
156{
157 put(w, 0xc0);
158}
159
160void msgpack_float(MsgpackWriter *w, float f)
161{
162 uint32_t bits;
163 memcpy(&bits, &f, sizeof(bits));
164 put(w, 0xca); // float32
165 put_be(w, bits, 4);
166}
167
168void msgpack_array(MsgpackWriter *w, size_t count)
169{
170 if (count <= 15)
171 put(w, (uint8_t)(0x90 | count)); // fixarray
172 else if (count <= 0xffff)
173 {
174 put(w, 0xdc);
175 put_be(w, count, 2);
176 }
177 else
178 {
179 put(w, 0xdd);
180 put_be(w, count, 4);
181 }
182}
183
184void msgpack_map(MsgpackWriter *w, size_t count)
185{
186 if (count <= 15)
187 put(w, (uint8_t)(0x80 | count)); // fixmap
188 else if (count <= 0xffff)
189 {
190 put(w, 0xde);
191 put_be(w, count, 2);
192 }
193 else
194 {
195 put(w, 0xdf);
196 put_be(w, count, 4);
197 }
198}
199
200// ---------------------------------------------------------------------------
201// Decoder
202// ---------------------------------------------------------------------------
203
204void msgpack_reader_init(MsgpackReader *r, const uint8_t *buf, size_t len)
205{
206 det_br_init(r, buf, len);
207}
208
209bool msgpack_reader_ok(const MsgpackReader *r)
210{
211 return det_br_ok(r);
212}
213
214// Read @p nbytes big-endian immediately after the format byte at r->pos, advancing
215// past the format byte and the argument (shared byte cursor, bytes.h).
216static bool take_be(MsgpackReader *r, size_t nbytes, uint64_t *out)
217{
218 return det_br_take_be(r, nbytes, out);
219}
220
221MsgpackType msgpack_peek(MsgpackReader *r)
222{
223 if (r->err || r->pos >= r->len)
224 return MsgpackType::MSGPACK_TYPE_INVALID;
225 uint8_t b = r->buf[r->pos];
226 if (b <= 0x7f)
227 return MsgpackType::MSGPACK_TYPE_UINT; // positive fixint
228 if (b >= 0xe0)
229 return MsgpackType::MSGPACK_TYPE_INT; // negative fixint
230 // b is now in [0x80, 0xdf]; each fix* range's lower bound is already
231 // established by the preceding checks, so test only the ascending upper bound.
232 if (b <= 0x8f)
233 return MsgpackType::MSGPACK_TYPE_MAP; // fixmap (0x80-0x8f)
234 if (b <= 0x9f)
235 return MsgpackType::MSGPACK_TYPE_ARRAY; // fixarray (0x90-0x9f)
236 if (b <= 0xbf)
237 return MsgpackType::MSGPACK_TYPE_STR; // fixstr (0xa0-0xbf)
238 switch (b)
239 {
240 case 0xc0:
241 return MsgpackType::MSGPACK_TYPE_NIL;
242 case 0xc2:
243 case 0xc3:
244 return MsgpackType::MSGPACK_TYPE_BOOL;
245 case 0xc4:
246 case 0xc5:
247 case 0xc6:
248 return MsgpackType::MSGPACK_TYPE_BIN;
249 case 0xca:
250 case 0xcb:
251 return MsgpackType::MSGPACK_TYPE_FLOAT;
252 case 0xcc:
253 case 0xcd:
254 case 0xce:
255 case 0xcf:
256 return MsgpackType::MSGPACK_TYPE_UINT;
257 case 0xd0:
258 case 0xd1:
259 case 0xd2:
260 case 0xd3:
261 return MsgpackType::MSGPACK_TYPE_INT;
262 case 0xd9:
263 case 0xda:
264 case 0xdb:
265 return MsgpackType::MSGPACK_TYPE_STR;
266 case 0xdc:
267 case 0xdd:
268 return MsgpackType::MSGPACK_TYPE_ARRAY;
269 case 0xde:
270 case 0xdf:
271 return MsgpackType::MSGPACK_TYPE_MAP;
272 default:
273 return MsgpackType::MSGPACK_TYPE_INVALID; // 0xc1, ext (0xc7-0xc9, 0xd4-0xd8)
274 }
275}
276
277bool msgpack_read_uint(MsgpackReader *r, uint64_t *out)
278{
279 if (r->err || r->pos >= r->len)
280 {
281 r->err = true;
282 return false;
283 }
284 uint8_t b = r->buf[r->pos];
285 if (b <= 0x7f) // positive fixint
286 {
287 *out = b;
288 r->pos += 1;
289 return true;
290 }
291 uint64_t v;
292 switch (b)
293 {
294 case 0xcc:
295 if (!take_be(r, 1, &v))
296 return false;
297 break;
298 case 0xcd:
299 if (!take_be(r, 2, &v))
300 return false;
301 break;
302 case 0xce:
303 if (!take_be(r, 4, &v))
304 return false;
305 break;
306 case 0xcf:
307 if (!take_be(r, 8, &v))
308 return false;
309 break;
310 default:
311 r->err = true;
312 return false;
313 }
314 *out = v;
315 return true;
316}
317
318bool msgpack_read_int(MsgpackReader *r, int64_t *out)
319{
320 if (r->err || r->pos >= r->len)
321 {
322 r->err = true;
323 return false;
324 }
325 uint8_t b = r->buf[r->pos];
326 if (b <= 0x7f) // positive fixint
327 {
328 *out = b;
329 r->pos += 1;
330 return true;
331 }
332 if (b >= 0xe0) // negative fixint (two's-complement byte)
333 {
334 *out = (int8_t)b;
335 r->pos += 1;
336 return true;
337 }
338 uint64_t v;
339 switch (b)
340 {
341 case 0xcc: // uint8
342 if (!take_be(r, 1, &v))
343 return false;
344 *out = (int64_t)v;
345 return true;
346 case 0xcd: // uint16
347 if (!take_be(r, 2, &v))
348 return false;
349 *out = (int64_t)v;
350 return true;
351 case 0xce: // uint32
352 if (!take_be(r, 4, &v))
353 return false;
354 *out = (int64_t)v;
355 return true;
356 case 0xcf: // uint64 (may exceed int64 range; wraps as two's-complement)
357 if (!take_be(r, 8, &v))
358 return false;
359 *out = (int64_t)v;
360 return true;
361 case 0xd0: // int8
362 if (!take_be(r, 1, &v))
363 return false;
364 *out = (int8_t)(uint8_t)v;
365 return true;
366 case 0xd1: // int16
367 if (!take_be(r, 2, &v))
368 return false;
369 *out = (int16_t)(uint16_t)v;
370 return true;
371 case 0xd2: // int32
372 if (!take_be(r, 4, &v))
373 return false;
374 *out = (int32_t)(uint32_t)v;
375 return true;
376 case 0xd3: // int64
377 if (!take_be(r, 8, &v))
378 return false;
379 *out = (int64_t)v;
380 return true;
381 default:
382 r->err = true;
383 return false;
384 }
385}
386
387bool msgpack_read_bool(MsgpackReader *r, bool *out)
388{
389 if (r->err || r->pos >= r->len)
390 {
391 r->err = true;
392 return false;
393 }
394 uint8_t b = r->buf[r->pos];
395 if (b == 0xc2)
396 *out = false;
397 else if (b == 0xc3)
398 *out = true;
399 else
400 {
401 r->err = true;
402 return false;
403 }
404 r->pos += 1;
405 return true;
406}
407
408bool msgpack_read_nil(MsgpackReader *r)
409{
410 if (r->err || r->pos >= r->len || r->buf[r->pos] != 0xc0)
411 {
412 r->err = true;
413 return false;
414 }
415 r->pos += 1;
416 return true;
417}
418
419bool msgpack_read_float(MsgpackReader *r, float *out)
420{
421 if (r->err || r->pos >= r->len)
422 {
423 r->err = true;
424 return false;
425 }
426 uint8_t b = r->buf[r->pos];
427 uint64_t v;
428 if (b == 0xca) // float32
429 {
430 if (!take_be(r, 4, &v))
431 return false;
432 uint32_t bits = (uint32_t)v;
433 memcpy(out, &bits, sizeof(*out));
434 return true;
435 }
436 if (b == 0xcb) // float64 -> narrow to float
437 {
438 if (!take_be(r, 8, &v))
439 return false;
440 double d;
441 memcpy(&d, &v, sizeof(d));
442 *out = (float)d;
443 return true;
444 }
445 r->err = true;
446 return false;
447}
448
449// Shared body for the str family (fixstr / str8/16/32) and bin family (bin8/16/32).
450static bool read_blob(MsgpackReader *r, bool want_str, const uint8_t **out, size_t *len)
451{
452 if (r->err || r->pos >= r->len)
453 {
454 r->err = true;
455 return false;
456 }
457 uint8_t b = r->buf[r->pos];
458 size_t n;
459 uint64_t v;
460 if (want_str && b >= 0xa0 && b <= 0xbf) // fixstr
461 {
462 n = (size_t)(b & 0x1f);
463 r->pos += 1;
464 }
465 else
466 {
467 const uint8_t f8 = want_str ? 0xd9 : 0xc4;
468 const uint8_t f16 = want_str ? 0xda : 0xc5;
469 const uint8_t f32 = want_str ? 0xdb : 0xc6;
470 if (b == f8)
471 {
472 if (!take_be(r, 1, &v))
473 return false;
474 }
475 else if (b == f16)
476 {
477 if (!take_be(r, 2, &v))
478 return false;
479 }
480 else if (b == f32)
481 {
482 if (!take_be(r, 4, &v))
483 return false;
484 }
485 else
486 {
487 r->err = true;
488 return false;
489 }
490 n = (size_t)v;
491 }
492 if (r->pos + n > r->len) // payload bounds
493 {
494 r->err = true;
495 return false;
496 }
497 *out = &r->buf[r->pos];
498 *len = n;
499 r->pos += n;
500 return true;
501}
502
503bool msgpack_read_str(MsgpackReader *r, const char **out, size_t *len)
504{
505 return read_blob(r, true, (const uint8_t **)out, len);
506}
507
508bool msgpack_read_bytes(MsgpackReader *r, const uint8_t **out, size_t *len)
509{
510 return read_blob(r, false, out, len);
511}
512
513// Shared body for the array family (fixarray / array16/32) and map family.
514static bool read_count(MsgpackReader *r, bool want_map, size_t *count)
515{
516 if (r->err || r->pos >= r->len)
517 {
518 r->err = true;
519 return false;
520 }
521 uint8_t b = r->buf[r->pos];
522 const uint8_t fix_lo = want_map ? 0x80 : 0x90;
523 const uint8_t fix_hi = want_map ? 0x8f : 0x9f;
524 const uint8_t f16 = want_map ? 0xde : 0xdc;
525 const uint8_t f32 = want_map ? 0xdf : 0xdd;
526 if (b >= fix_lo && b <= fix_hi)
527 {
528 *count = (size_t)(b & 0x0f);
529 r->pos += 1;
530 return true;
531 }
532 uint64_t v;
533 if (b == f16)
534 {
535 if (!take_be(r, 2, &v))
536 return false;
537 }
538 else if (b == f32)
539 {
540 if (!take_be(r, 4, &v))
541 return false;
542 }
543 else
544 {
545 r->err = true;
546 return false;
547 }
548 *count = (size_t)v;
549 return true;
550}
551
552bool msgpack_read_array(MsgpackReader *r, size_t *count)
553{
554 return read_count(r, false, count);
555}
556
557bool msgpack_read_map(MsgpackReader *r, size_t *count)
558{
559 return read_count(r, true, count);
560}
561
562#endif // DETWS_ENABLE_MSGPACK
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 MessagePack encoder and decoder.