ProtoCore v0.0.2
Deterministic, zero-heap network stack for embedded targets
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 PC_NEED_CBOR
12
14#include <string.h>
15
16static void put(pc_span *w, uint8_t b)
17{
18 pc_bw_put(w, b);
19}
20
21// Write a CBOR head: the major type (top 3 bits) plus the argument, choosing the
22// shortest of the 1/2/3/5/9-byte forms (RFC 8949 section 3). The argument after
23// the lead byte is big-endian, the same network order as MessagePack.
24static void head(pc_span *w, uint8_t major, uint64_t val)
25{
26 uint8_t m = (uint8_t)(major << 5);
27 if (val < 24)
28 {
29 put(w, (uint8_t)(m | val));
30 }
31 else if (val < 0x100ULL)
32 {
33 put(w, (uint8_t)(m | 24));
34 pc_bw_put_be(w, val, 1);
35 }
36 else if (val < 0x10000ULL)
37 {
38 put(w, (uint8_t)(m | 25));
39 pc_bw_put_be(w, val, 2);
40 }
41 else if (val < 0x100000000ULL)
42 {
43 put(w, (uint8_t)(m | 26));
44 pc_bw_put_be(w, val, 4);
45 }
46 else
47 {
48 put(w, (uint8_t)(m | 27));
49 pc_bw_put_be(w, val, 8);
50 }
51}
52
53void pc_cbor_uint(pc_span *w, uint64_t v)
54{
55 head(w, 0, v);
56}
57
58void pc_cbor_int(pc_span *w, int64_t v)
59{
60 if (v >= 0)
61 {
62 head(w, 0, (uint64_t)v);
63 }
64 else
65 {
66 head(w, 1, (uint64_t)(-1 - v)); // major 1 encodes -1 - n
67 }
68}
69
70void pc_cbor_bytes(pc_span *w, const uint8_t *data, size_t len)
71{
72 head(w, 2, (uint64_t)len);
73 for (size_t i = 0; i < len; i++)
74 {
75 put(w, data[i]);
76 }
77}
78
79void pc_cbor_str_n(pc_span *w, const char *s, size_t len)
80{
81 head(w, 3, (uint64_t)len);
82 for (size_t i = 0; i < len; i++)
83 {
84 put(w, (uint8_t)s[i]);
85 }
86}
87
88void pc_cbor_str(pc_span *w, const char *s)
89{
90 pc_cbor_str_n(w, s, s ? strnlen(s, w->cap + 1) : 0);
91}
92
93void pc_cbor_bool(pc_span *w, bool b)
94{
95 put(w, b ? 0xf5 : 0xf4);
96}
97
98void pc_cbor_null(pc_span *w)
99{
100 put(w, 0xf6);
101}
102
103void pc_cbor_float(pc_span *w, float f)
104{
105 uint32_t bits;
106 memcpy(&bits, &f, sizeof(bits));
107 put(w, 0xfa); // major 7, single-precision
108 pc_bw_put_be(w, bits, 4);
109}
110
111void pc_cbor_array(pc_span *w, size_t count)
112{
113 head(w, 4, (uint64_t)count);
114}
115
116void pc_cbor_map(pc_span *w, size_t count)
117{
118 head(w, 5, (uint64_t)count);
119}
120
121void pc_cbor_label(pc_span *w, const char *name, int64_t num)
122{
123 (void)name; // CBOR carries the integer label form (RFC 8428 sec 6)
124 pc_cbor_int(w, num);
125}
126
127// ---------------------------------------------------------------------------
128// Decoder
129// ---------------------------------------------------------------------------
130
131// Read a CBOR head at r->pos: major type + argument, advancing pos. Sets err and
132// returns false on out-of-bounds or a reserved/indefinite additional-info value.
133static bool read_head(pc_cspan *r, uint8_t *major, uint64_t *val)
134{
135 if (r->err || r->pos >= r->len)
136 {
137 r->err = true;
138 return false;
139 }
140 uint8_t b = r->buf[r->pos];
141 uint8_t info = (uint8_t)(b & 0x1f);
142 *major = (uint8_t)(b >> 5);
143 if (info < 24)
144 {
145 *val = info;
146 r->pos += 1;
147 return true;
148 }
149 size_t need;
150 switch (info)
151 {
152 case 24:
153 need = 1;
154 break;
155 case 25:
156 need = 2;
157 break;
158 case 26:
159 need = 4;
160 break;
161 case 27:
162 need = 8;
163 break;
164 default:
165 r->err = true; // 28-31: reserved / indefinite-length, unsupported
166 return false;
167 }
168 // The argument is the `need` big-endian bytes after this head byte.
169 return pc_br_take_be(r, need, val);
170}
171
172pc_codec_type pc_cbor_peek(pc_cspan *r)
173{
174 if (r->err || r->pos >= r->len)
175 {
177 }
178 uint8_t b = r->buf[r->pos];
179 switch (b >> 5)
180 {
181 case 0:
183 case 1:
185 case 2:
187 case 3:
189 case 4:
191 case 5:
193 case 7: {
194 uint8_t info = (uint8_t)(b & 0x1f);
195 if (info == 20 || info == 21)
196 {
198 }
199 if (info == 22)
200 {
202 }
203 if (info == 26 || info == 27)
204 {
206 }
208 }
209 default:
210 return pc_codec_type::PC_CODEC_INVALID; // major 6 (tags) unsupported
211 }
212}
213
214bool pc_cbor_read_uint(pc_cspan *r, uint64_t *out)
215{
216 uint8_t m;
217 uint64_t v;
218 if (!read_head(r, &m, &v))
219 {
220 return false;
221 }
222 if (m != 0)
223 {
224 r->err = true;
225 return false;
226 }
227 *out = v;
228 return true;
229}
230
231bool pc_cbor_read_int(pc_cspan *r, int64_t *out)
232{
233 uint8_t m;
234 uint64_t v;
235 if (!read_head(r, &m, &v))
236 {
237 return false;
238 }
239 if (m == 0)
240 {
241 *out = (int64_t)v;
242 }
243 else if (m == 1)
244 {
245 *out = -1 - (int64_t)v;
246 }
247 else
248 {
249 r->err = true;
250 return false;
251 }
252 return true;
253}
254
255bool pc_cbor_read_bool(pc_cspan *r, bool *out)
256{
257 if (r->err || r->pos >= r->len)
258 {
259 r->err = true;
260 return false;
261 }
262 uint8_t b = r->buf[r->pos];
263 if (b == 0xf4)
264 {
265 *out = false;
266 }
267 else if (b == 0xf5)
268 {
269 *out = true;
270 }
271 else
272 {
273 r->err = true;
274 return false;
275 }
276 r->pos += 1;
277 return true;
278}
279
280bool pc_cbor_read_null(pc_cspan *r)
281{
282 if (r->err || r->pos >= r->len || r->buf[r->pos] != 0xf6)
283 {
284 r->err = true;
285 return false;
286 }
287 r->pos += 1;
288 return true;
289}
290
291bool pc_cbor_read_float(pc_cspan *r, float *out)
292{
293 if (r->err || r->pos >= r->len)
294 {
295 r->err = true;
296 return false;
297 }
298 uint8_t b = r->buf[r->pos];
299 if (b == 0xfa) // single
300 {
301 uint64_t v;
302 if (!pc_br_take_be(r, 4, &v))
303 {
304 return false;
305 }
306 uint32_t bits = (uint32_t)v;
307 memcpy(out, &bits, sizeof(*out));
308 return true;
309 }
310 if (b == 0xfb) // double -> narrow to float
311 {
312 uint64_t bits;
313 if (!pc_br_take_be(r, 8, &bits))
314 {
315 return false;
316 }
317 double d;
318 memcpy(&d, &bits, sizeof(d));
319 *out = (float)d;
320 return true;
321 }
322 r->err = true;
323 return false;
324}
325
326// Shared body for text (major 3) and byte (major 2) strings.
327static bool read_str(pc_cspan *r, uint8_t want_major, const uint8_t **out, size_t *len)
328{
329 uint8_t m;
330 uint64_t v;
331 if (!read_head(r, &m, &v))
332 {
333 return false;
334 }
335 if (m != want_major || r->pos + v > r->len)
336 {
337 r->err = true;
338 return false;
339 }
340 *out = &r->buf[r->pos];
341 *len = (size_t)v;
342 r->pos += (size_t)v;
343 return true;
344}
345
346bool pc_cbor_read_str(pc_cspan *r, const char **out, size_t *len)
347{
348 return read_str(r, 3, (const uint8_t **)out, len);
349}
350
351bool pc_cbor_read_bytes(pc_cspan *r, const uint8_t **out, size_t *len)
352{
353 return read_str(r, 2, out, len);
354}
355
356bool pc_cbor_read_array(pc_cspan *r, size_t *count)
357{
358 uint8_t m;
359 uint64_t v;
360 if (!read_head(r, &m, &v))
361 {
362 return false;
363 }
364 if (m != 4)
365 {
366 r->err = true;
367 return false;
368 }
369 *count = (size_t)v;
370 return true;
371}
372
373bool pc_cbor_read_map(pc_cspan *r, size_t *count)
374{
375 uint8_t m;
376 uint64_t v;
377 if (!read_head(r, &m, &v))
378 {
379 return false;
380 }
381 if (m != 5)
382 {
383 r->err = true;
384 return false;
385 }
386 *count = (size_t)v;
387 return true;
388}
389
390#endif // PC_NEED_CBOR
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
Layer 6 (Presentation) - zero-heap CBOR (RFC 8949) encoder.
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
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