ProtoCore v0.0.2
Deterministic, zero-heap network stack for embedded targets
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 PC_ENABLE_MSGPACK
12
14#include <string.h>
15
16// Thin local names over the shared byte verbs (bytes.h) so the call sites
17// below read the same as before; the cursor invariants live in one place.
18static void put(pc_span *w, uint8_t b)
19{
20 pc_bw_put(w, b);
21}
22
23// Write the low @p nbytes of @p val, big-endian (MessagePack is network order).
24static void put_be(pc_span *w, uint64_t val, int32_t nbytes)
25{
26 pc_bw_put_be(w, val, nbytes);
27}
28
29void pc_msgpack_uint(pc_span *w, uint64_t v)
30{
31 if (v <= 0x7f)
32 {
33 put(w, (uint8_t)v); // positive fixint
34 }
35 else if (v <= 0xff)
36 {
37 put(w, 0xcc);
38 put(w, (uint8_t)v);
39 }
40 else if (v <= 0xffff)
41 {
42 put(w, 0xcd);
43 put_be(w, v, 2);
44 }
45 else if (v <= 0xffffffffULL)
46 {
47 put(w, 0xce);
48 put_be(w, v, 4);
49 }
50 else
51 {
52 put(w, 0xcf);
53 put_be(w, v, 8);
54 }
55}
56
57void pc_msgpack_int(pc_span *w, int64_t v)
58{
59 if (v >= 0)
60 {
61 pc_msgpack_uint(w, (uint64_t)v);
62 return;
63 }
64 if (v >= -32)
65 {
66 put(w, (uint8_t)v); // negative fixint (two's-complement byte 0xe0..0xff)
67 }
68 else if (v >= -128)
69 {
70 put(w, 0xd0);
71 put(w, (uint8_t)v);
72 }
73 else if (v >= -32768)
74 {
75 put(w, 0xd1);
76 put_be(w, (uint64_t)(uint16_t)v, 2);
77 }
78 else if (v >= -2147483648LL)
79 {
80 put(w, 0xd2);
81 put_be(w, (uint64_t)(uint32_t)v, 4);
82 }
83 else
84 {
85 put(w, 0xd3);
86 put_be(w, (uint64_t)v, 8);
87 }
88}
89
90void pc_msgpack_str_n(pc_span *w, const char *s, size_t len)
91{
92 if (len <= 31)
93 {
94 put(w, (uint8_t)(0xa0 | len)); // fixstr
95 }
96 else if (len <= 0xff)
97 {
98 put(w, 0xd9);
99 put(w, (uint8_t)len);
100 }
101 else if (len <= 0xffff)
102 {
103 put(w, 0xda);
104 put_be(w, len, 2);
105 }
106 else
107 {
108 put(w, 0xdb);
109 put_be(w, len, 4);
110 }
111 for (size_t i = 0; i < len; i++)
112 {
113 put(w, (uint8_t)s[i]);
114 }
115}
116
117void pc_msgpack_str(pc_span *w, const char *s)
118{
119 pc_msgpack_str_n(w, s, s ? strnlen(s, w->cap + 1) : 0);
120}
121
122void pc_msgpack_bytes(pc_span *w, const uint8_t *data, size_t len)
123{
124 if (len <= 0xff)
125 {
126 put(w, 0xc4);
127 put(w, (uint8_t)len);
128 }
129 else if (len <= 0xffff)
130 {
131 put(w, 0xc5);
132 put_be(w, len, 2);
133 }
134 else
135 {
136 put(w, 0xc6);
137 put_be(w, len, 4);
138 }
139 for (size_t i = 0; i < len; i++)
140 {
141 put(w, data[i]);
142 }
143}
144
145void pc_msgpack_bool(pc_span *w, bool b)
146{
147 put(w, b ? 0xc3 : 0xc2);
148}
149
150void pc_msgpack_null(pc_span *w)
151{
152 put(w, 0xc0);
153}
154
155void pc_msgpack_float(pc_span *w, float f)
156{
157 uint32_t bits;
158 memcpy(&bits, &f, sizeof(bits));
159 put(w, 0xca); // float32
160 put_be(w, bits, 4);
161}
162
163void pc_msgpack_array(pc_span *w, size_t count)
164{
165 if (count <= 15)
166 {
167 put(w, (uint8_t)(0x90 | count)); // fixarray
168 }
169 else if (count <= 0xffff)
170 {
171 put(w, 0xdc);
172 put_be(w, count, 2);
173 }
174 else
175 {
176 put(w, 0xdd);
177 put_be(w, count, 4);
178 }
179}
180
181void pc_msgpack_map(pc_span *w, size_t count)
182{
183 if (count <= 15)
184 {
185 put(w, (uint8_t)(0x80 | count)); // fixmap
186 }
187 else if (count <= 0xffff)
188 {
189 put(w, 0xde);
190 put_be(w, count, 2);
191 }
192 else
193 {
194 put(w, 0xdf);
195 put_be(w, count, 4);
196 }
197}
198
199void pc_msgpack_label(pc_span *w, const char *name, int64_t num)
200{
201 (void)name; // the binary packs carry the integer label form, as CBOR does
202 pc_msgpack_int(w, num);
203}
204
205// ---------------------------------------------------------------------------
206// Decoder
207// ---------------------------------------------------------------------------
208
209// Read @p nbytes big-endian immediately after the format byte at r->pos, advancing
210// past the format byte and the argument (shared byte cursor, bytes.h).
211static bool take_be(pc_cspan *r, size_t nbytes, uint64_t *out)
212{
213 return pc_br_take_be(r, nbytes, out);
214}
215
216pc_codec_type pc_msgpack_peek(pc_cspan *r)
217{
218 if (r->err || r->pos >= r->len)
219 {
221 }
222 uint8_t b = r->buf[r->pos];
223 if (b <= 0x7f)
224 {
225 return pc_codec_type::PC_CODEC_UINT; // positive fixint
226 }
227 if (b >= 0xe0)
228 {
229 return pc_codec_type::PC_CODEC_INT; // negative fixint
230 }
231 // b is now in [0x80, 0xdf]; each fix* range's lower bound is already
232 // established by the preceding checks, so test only the ascending upper bound.
233 if (b <= 0x8f)
234 {
235 return pc_codec_type::PC_CODEC_MAP; // fixmap (0x80-0x8f)
236 }
237 if (b <= 0x9f)
238 {
239 return pc_codec_type::PC_CODEC_ARRAY; // fixarray (0x90-0x9f)
240 }
241 if (b <= 0xbf)
242 {
243 return pc_codec_type::PC_CODEC_STR; // fixstr (0xa0-0xbf)
244 }
245 switch (b)
246 {
247 case 0xc0:
249 case 0xc2:
250 case 0xc3:
252 case 0xc4:
253 case 0xc5:
254 case 0xc6:
256 case 0xca:
257 case 0xcb:
259 case 0xcc:
260 case 0xcd:
261 case 0xce:
262 case 0xcf:
264 case 0xd0:
265 case 0xd1:
266 case 0xd2:
267 case 0xd3:
269 case 0xd9:
270 case 0xda:
271 case 0xdb:
273 case 0xdc:
274 case 0xdd:
276 case 0xde:
277 case 0xdf:
279 default:
280 return pc_codec_type::PC_CODEC_INVALID; // 0xc1, ext (0xc7-0xc9, 0xd4-0xd8)
281 }
282}
283
284bool pc_msgpack_read_uint(pc_cspan *r, uint64_t *out)
285{
286 if (r->err || r->pos >= r->len)
287 {
288 r->err = true;
289 return false;
290 }
291 uint8_t b = r->buf[r->pos];
292 if (b <= 0x7f) // positive fixint
293 {
294 *out = b;
295 r->pos += 1;
296 return true;
297 }
298 uint64_t v;
299 switch (b)
300 {
301 case 0xcc:
302 if (!take_be(r, 1, &v))
303 {
304 return false;
305 }
306 break;
307 case 0xcd:
308 if (!take_be(r, 2, &v))
309 {
310 return false;
311 }
312 break;
313 case 0xce:
314 if (!take_be(r, 4, &v))
315 {
316 return false;
317 }
318 break;
319 case 0xcf:
320 if (!take_be(r, 8, &v))
321 {
322 return false;
323 }
324 break;
325 default:
326 r->err = true;
327 return false;
328 }
329 *out = v;
330 return true;
331}
332
333bool pc_msgpack_read_int(pc_cspan *r, int64_t *out)
334{
335 if (r->err || r->pos >= r->len)
336 {
337 r->err = true;
338 return false;
339 }
340 uint8_t b = r->buf[r->pos];
341 if (b <= 0x7f) // positive fixint
342 {
343 *out = b;
344 r->pos += 1;
345 return true;
346 }
347 if (b >= 0xe0) // negative fixint (two's-complement byte)
348 {
349 *out = (int8_t)b;
350 r->pos += 1;
351 return true;
352 }
353 uint64_t v;
354 switch (b)
355 {
356 case 0xcc: // uint8
357 if (!take_be(r, 1, &v))
358 {
359 return false;
360 }
361 *out = (int64_t)v;
362 return true;
363 case 0xcd: // uint16
364 if (!take_be(r, 2, &v))
365 {
366 return false;
367 }
368 *out = (int64_t)v;
369 return true;
370 case 0xce: // uint32
371 if (!take_be(r, 4, &v))
372 {
373 return false;
374 }
375 *out = (int64_t)v;
376 return true;
377 case 0xcf: // uint64 (may exceed int64 range; wraps as two's-complement)
378 if (!take_be(r, 8, &v))
379 {
380 return false;
381 }
382 *out = (int64_t)v;
383 return true;
384 case 0xd0: // int8
385 if (!take_be(r, 1, &v))
386 {
387 return false;
388 }
389 *out = (int8_t)(uint8_t)v;
390 return true;
391 case 0xd1: // int16
392 if (!take_be(r, 2, &v))
393 {
394 return false;
395 }
396 *out = (int16_t)(uint16_t)v;
397 return true;
398 case 0xd2: // int32
399 if (!take_be(r, 4, &v))
400 {
401 return false;
402 }
403 *out = (int32_t)(uint32_t)v;
404 return true;
405 case 0xd3: // int64
406 if (!take_be(r, 8, &v))
407 {
408 return false;
409 }
410 *out = (int64_t)v;
411 return true;
412 default:
413 r->err = true;
414 return false;
415 }
416}
417
418bool pc_msgpack_read_bool(pc_cspan *r, bool *out)
419{
420 if (r->err || r->pos >= r->len)
421 {
422 r->err = true;
423 return false;
424 }
425 uint8_t b = r->buf[r->pos];
426 if (b == 0xc2)
427 {
428 *out = false;
429 }
430 else if (b == 0xc3)
431 {
432 *out = true;
433 }
434 else
435 {
436 r->err = true;
437 return false;
438 }
439 r->pos += 1;
440 return true;
441}
442
443bool pc_msgpack_read_null(pc_cspan *r)
444{
445 if (r->err || r->pos >= r->len || r->buf[r->pos] != 0xc0)
446 {
447 r->err = true;
448 return false;
449 }
450 r->pos += 1;
451 return true;
452}
453
454bool pc_msgpack_read_float(pc_cspan *r, float *out)
455{
456 if (r->err || r->pos >= r->len)
457 {
458 r->err = true;
459 return false;
460 }
461 uint8_t b = r->buf[r->pos];
462 uint64_t v;
463 if (b == 0xca) // float32
464 {
465 if (!take_be(r, 4, &v))
466 {
467 return false;
468 }
469 uint32_t bits = (uint32_t)v;
470 memcpy(out, &bits, sizeof(*out));
471 return true;
472 }
473 if (b == 0xcb) // float64 -> narrow to float
474 {
475 if (!take_be(r, 8, &v))
476 {
477 return false;
478 }
479 double d;
480 memcpy(&d, &v, sizeof(d));
481 *out = (float)d;
482 return true;
483 }
484 r->err = true;
485 return false;
486}
487
488// Shared body for the str family (fixstr / str8/16/32) and bin family (bin8/16/32).
489static bool read_blob(pc_cspan *r, bool want_str, const uint8_t **out, size_t *len)
490{
491 if (r->err || r->pos >= r->len)
492 {
493 r->err = true;
494 return false;
495 }
496 uint8_t b = r->buf[r->pos];
497 size_t n;
498 uint64_t v;
499 if (want_str && b >= 0xa0 && b <= 0xbf) // fixstr
500 {
501 n = (size_t)(b & 0x1f);
502 r->pos += 1;
503 }
504 else
505 {
506 const uint8_t f8 = want_str ? 0xd9 : 0xc4;
507 const uint8_t f16 = want_str ? 0xda : 0xc5;
508 const uint8_t f32 = want_str ? 0xdb : 0xc6;
509 if (b == f8)
510 {
511 if (!take_be(r, 1, &v))
512 {
513 return false;
514 }
515 }
516 else if (b == f16)
517 {
518 if (!take_be(r, 2, &v))
519 {
520 return false;
521 }
522 }
523 else if (b == f32)
524 {
525 if (!take_be(r, 4, &v))
526 {
527 return false;
528 }
529 }
530 else
531 {
532 r->err = true;
533 return false;
534 }
535 n = (size_t)v;
536 }
537 if (r->pos + n > r->len) // payload bounds
538 {
539 r->err = true;
540 return false;
541 }
542 *out = &r->buf[r->pos];
543 *len = n;
544 r->pos += n;
545 return true;
546}
547
548bool pc_msgpack_read_str(pc_cspan *r, const char **out, size_t *len)
549{
550 return read_blob(r, true, (const uint8_t **)out, len);
551}
552
553bool pc_msgpack_read_bytes(pc_cspan *r, const uint8_t **out, size_t *len)
554{
555 return read_blob(r, false, out, len);
556}
557
558// Shared body for the array family (fixarray / array16/32) and map family.
559static bool read_count(pc_cspan *r, bool want_map, size_t *count)
560{
561 if (r->err || r->pos >= r->len)
562 {
563 r->err = true;
564 return false;
565 }
566 uint8_t b = r->buf[r->pos];
567 const uint8_t fix_lo = want_map ? 0x80 : 0x90;
568 const uint8_t fix_hi = want_map ? 0x8f : 0x9f;
569 const uint8_t f16 = want_map ? 0xde : 0xdc;
570 const uint8_t f32 = want_map ? 0xdf : 0xdd;
571 if (b >= fix_lo && b <= fix_hi)
572 {
573 *count = (size_t)(b & 0x0f);
574 r->pos += 1;
575 return true;
576 }
577 uint64_t v;
578 if (b == f16)
579 {
580 if (!take_be(r, 2, &v))
581 {
582 return false;
583 }
584 }
585 else if (b == f32)
586 {
587 if (!take_be(r, 4, &v))
588 {
589 return false;
590 }
591 }
592 else
593 {
594 r->err = true;
595 return false;
596 }
597 *count = (size_t)v;
598 return true;
599}
600
601bool pc_msgpack_read_array(pc_cspan *r, size_t *count)
602{
603 return read_count(r, false, count);
604}
605
606bool pc_msgpack_read_map(pc_cspan *r, size_t *count)
607{
608 return read_count(r, true, count);
609}
610
611#endif // PC_ENABLE_MSGPACK
The byte verbs - append into a pc_span, take out of a pc_cspan.
void pc_bw_put_be(pc_span *w, uint64_t val, int32_t nbytes)
Append the low nbytes of val, big-endian (network order).
Definition bytes.h:51
void pc_bw_put(pc_span *w, uint8_t b)
Append one byte; on overflow set the flag but keep counting pos.
Definition bytes.h:37
bool pc_br_take_be(pc_cspan *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:69
pc_codec_type
The next item's type, reported by pc_codec::peek without consuming it.
Definition codec.h:45
@ PC_CODEC_INVALID
end of buffer, a prior error, or an item this format does not carry
Layer 6 (Presentation) - zero-heap MessagePack encoder and decoder.
A read-only byte region.
Definition span.h:79
const uint8_t * buf
first byte, or nullptr when there is nothing to read
Definition span.h:80
size_t len
readable bytes at buf (0 whenever buf is nullptr)
Definition span.h:81
size_t pos
read cursor
Definition span.h:82
bool err
sticky: a read ran past len
Definition span.h:83
A writable byte region: the storage, the capacity that belongs to it, and what has been produced into...
Definition span.h:65
size_t cap
bytes writable at buf (0 whenever buf is nullptr)
Definition span.h:67