ProtoCore v0.0.2
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
thread.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 thread.cpp
6 * @brief Thread spinel / HDLC-lite framing codec - implementation.
7 *
8 * HDLC-lite: [payload | FCS(lo,hi)] byte-stuffed and Flag-terminated. The FCS is CRC-16/X-25
9 * (poly 0x1021 reflected = 0x8408, init 0xFFFF, reflected, final XOR 0xFFFF); the reserved
10 * bytes 0x7E / 0x7D / 0x11 / 0x13 are escaped as 0x7D, (byte XOR 0x20).
11 */
12
14#include "shared_primitives/crc.h" // PC_CRC16_X25
15
16#if PC_ENABLE_THREAD
17
18namespace
19{
20bool is_reserved(uint8_t b)
21{
22 return b == 0x7E || b == 0x7D || b == 0x11 || b == 0x13;
23}
24
25// Append a byte with HDLC stuffing; return false if it would overflow cap.
26bool put_stuffed(uint8_t *out, uint16_t *p, uint16_t cap, uint8_t b)
27{
28 if (is_reserved(b))
29 {
30 if (*p + 2 > cap)
31 {
32 return false;
33 }
34 out[(*p)++] = ThreadHdlc::HDLC_ESCAPE;
35 out[(*p)++] = (uint8_t)(b ^ 0x20);
36 }
37 else
38 {
39 if (*p + 1 > cap)
40 {
41 return false;
42 }
43 out[(*p)++] = b;
44 }
45 return true;
46}
47} // namespace
48
49uint8_t pc_spinel_pack_uint(uint32_t value, uint8_t *out, uint8_t cap)
50{
51 if (!out)
52 {
53 return 0;
54 }
55 uint8_t n = 0;
56 do
57 {
58 if (n >= cap)
59 {
60 return 0;
61 }
62 uint8_t byte = (uint8_t)(value & 0x7F);
63 value >>= 7;
64 if (value)
65 {
66 byte |= 0x80; // more bytes follow
67 }
68 out[n++] = byte;
69 } while (value);
70 return n;
71}
72
73int pc_spinel_unpack_uint(const uint8_t *raw, uint8_t len, uint32_t *value)
74{
75 if (!raw)
76 {
77 return 0;
78 }
79 uint32_t v = 0;
80 uint8_t shift = 0;
81 for (uint8_t n = 0; n < len; n++)
82 {
83 uint8_t b = raw[n];
84 v |= (uint32_t)(b & 0x7F) << shift;
85 if (!(b & 0x80))
86 {
87 if (value)
88 {
89 *value = v;
90 }
91 return n + 1;
92 }
93 shift += 7;
94 if (shift >= 32)
95 {
96 return -1; // does not fit a uint32
97 }
98 }
99 return 0; // truncated - need more bytes
100}
101
102uint16_t pc_spinel_command_build(uint8_t header, uint32_t cmd, uint32_t prop, const uint8_t *value, uint16_t value_len,
103 uint8_t *out, uint16_t cap)
104{
105 if (!out || cap < 1 || (value == nullptr && value_len > 0))
106 {
107 return 0;
108 }
109 uint16_t p = 0;
110 out[p++] = header;
111 uint8_t n = pc_spinel_pack_uint(cmd, out + p, (uint8_t)(cap - p));
112 if (n == 0)
113 {
114 return 0;
115 }
116 p += n;
117 n = pc_spinel_pack_uint(prop, out + p, (uint8_t)(cap > p ? cap - p : 0));
118 if (n == 0)
119 {
120 return 0;
121 }
122 p += n;
123 if ((uint32_t)p + value_len > cap)
124 {
125 return 0;
126 }
127 for (uint16_t i = 0; i < value_len; i++)
128 {
129 out[p + i] = value[i];
130 }
131 return (uint16_t)(p + value_len);
132}
133
134int pc_spinel_command_parse(const uint8_t *payload, uint16_t len, uint8_t *header, uint32_t *cmd, uint32_t *prop,
135 const uint8_t **value, uint16_t *value_len)
136{
137 if (!payload || len < 1)
138 {
139 return -1;
140 }
141 uint16_t p = 0;
142 uint8_t h = payload[p++];
143 uint32_t c = 0;
144 uint32_t pr = 0;
145 int n = pc_spinel_unpack_uint(payload + p, (uint8_t)((len - p) > 255 ? 255 : (len - p)), &c);
146 if (n <= 0)
147 {
148 return -1;
149 }
150 p += (uint16_t)n;
151 n = pc_spinel_unpack_uint(payload + p, (uint8_t)((len - p) > 255 ? 255 : (len - p)), &pr);
152 if (n <= 0)
153 {
154 return -1;
155 }
156 p += (uint16_t)n;
157 if (header)
158 {
159 *header = h;
160 }
161 if (cmd)
162 {
163 *cmd = c;
164 }
165 if (prop)
166 {
167 *prop = pr;
168 }
169 if (value)
170 {
171 *value = payload + p;
172 }
173 if (value_len)
174 {
175 *value_len = (uint16_t)(len - p);
176 }
177 return (int)p;
178}
179
180// --- Spinel value semantics -------------------------------------------------------------
181
182void pc_spinel_reader_init(SpinelReader *r, const uint8_t *value, uint16_t len)
183{
184 if (!r)
185 {
186 return;
187 }
188 r->buf = value;
189 r->len = value ? len : 0;
190 r->off = 0;
191 r->err = (value == nullptr && len > 0);
192}
193
194namespace
195{
196// Reserve n bytes at the cursor; return the read pointer or nullptr (latching err) if short.
197const uint8_t *take(SpinelReader *r, uint16_t n)
198{
199 if (!r || r->err || (uint32_t)r->off + n > r->len)
200 {
201 if (r)
202 {
203 r->err = true;
204 }
205 return nullptr;
206 }
207 const uint8_t *at = r->buf + r->off;
208 r->off = (uint16_t)(r->off + n);
209 return at;
210}
211} // namespace
212
213bool pc_spinel_get_bool(SpinelReader *r, bool *out)
214{
215 const uint8_t *b = take(r, 1);
216 if (!b)
217 {
218 return false;
219 }
220 if (out)
221 {
222 *out = (*b != 0);
223 }
224 return true;
225}
226
227bool pc_spinel_get_u8(SpinelReader *r, uint8_t *out)
228{
229 const uint8_t *b = take(r, 1);
230 if (!b)
231 {
232 return false;
233 }
234 if (out)
235 {
236 *out = b[0];
237 }
238 return true;
239}
240
241bool pc_spinel_get_i8(SpinelReader *r, int8_t *out)
242{
243 const uint8_t *b = take(r, 1);
244 if (!b)
245 {
246 return false;
247 }
248 if (out)
249 {
250 *out = (int8_t)b[0];
251 }
252 return true;
253}
254
255bool pc_spinel_get_u16(SpinelReader *r, uint16_t *out)
256{
257 const uint8_t *b = take(r, 2);
258 if (!b)
259 {
260 return false;
261 }
262 if (out)
263 {
264 *out = (uint16_t)(b[0] | (b[1] << 8));
265 }
266 return true;
267}
268
269bool pc_spinel_get_i16(SpinelReader *r, int16_t *out)
270{
271 uint16_t v = 0;
272 if (!pc_spinel_get_u16(r, &v))
273 {
274 return false;
275 }
276 if (out)
277 {
278 *out = (int16_t)v;
279 }
280 return true;
281}
282
283bool pc_spinel_get_u32(SpinelReader *r, uint32_t *out)
284{
285 const uint8_t *b = take(r, 4);
286 if (!b)
287 {
288 return false;
289 }
290 if (out)
291 {
292 *out = (uint32_t)b[0] | ((uint32_t)b[1] << 8) | ((uint32_t)b[2] << 16) | ((uint32_t)b[3] << 24);
293 }
294 return true;
295}
296
297bool pc_spinel_get_i32(SpinelReader *r, int32_t *out)
298{
299 uint32_t v = 0;
300 if (!pc_spinel_get_u32(r, &v))
301 {
302 return false;
303 }
304 if (out)
305 {
306 *out = (int32_t)v;
307 }
308 return true;
309}
310
311bool pc_spinel_get_uint(SpinelReader *r, uint32_t *out)
312{
313 if (!r || r->err)
314 {
315 return false;
316 }
317 uint32_t v = 0;
318 int n = pc_spinel_unpack_uint(r->buf + r->off, (uint8_t)((r->len - r->off) > 255 ? 255 : (r->len - r->off)), &v);
319 if (n <= 0)
320 {
321 r->err = true;
322 return false;
323 }
324 r->off = (uint16_t)(r->off + n);
325 if (out)
326 {
327 *out = v;
328 }
329 return true;
330}
331
332bool pc_spinel_get_eui64(SpinelReader *r, const uint8_t **out8)
333{
334 const uint8_t *b = take(r, 8);
335 if (!b)
336 {
337 return false;
338 }
339 if (out8)
340 {
341 *out8 = b;
342 }
343 return true;
344}
345
346bool pc_spinel_get_ipv6(SpinelReader *r, const uint8_t **out16)
347{
348 const uint8_t *b = take(r, 16);
349 if (!b)
350 {
351 return false;
352 }
353 if (out16)
354 {
355 *out16 = b;
356 }
357 return true;
358}
359
360bool pc_spinel_get_utf8(SpinelReader *r, const char **out, uint16_t *out_len)
361{
362 if (!r || r->err)
363 {
364 return false;
365 }
366 uint16_t i = r->off;
367 while (i < r->len && r->buf[i] != 0)
368 {
369 i++;
370 }
371 if (i >= r->len) // no NUL terminator in the value
372 {
373 r->err = true;
374 return false;
375 }
376 if (out)
377 {
378 *out = (const char *)(r->buf + r->off);
379 }
380 if (out_len)
381 {
382 *out_len = (uint16_t)(i - r->off);
383 }
384 r->off = (uint16_t)(i + 1); // consume the string and its NUL
385 return true;
386}
387
388bool pc_spinel_get_data(SpinelReader *r, const uint8_t **out, uint16_t *out_len)
389{
390 if (!r || r->err)
391 {
392 return false;
393 }
394 if (out)
395 {
396 *out = r->buf + r->off;
397 }
398 if (out_len)
399 {
400 *out_len = (uint16_t)(r->len - r->off);
401 }
402 r->off = r->len;
403 return true;
404}
405
406bool pc_spinel_get_data_wlen(SpinelReader *r, const uint8_t **out, uint16_t *out_len)
407{
408 uint16_t n = 0;
409 if (!pc_spinel_get_u16(r, &n))
410 {
411 return false;
412 }
413 const uint8_t *b = take(r, n);
414 if (!b)
415 {
416 return false;
417 }
418 if (out)
419 {
420 *out = b;
421 }
422 if (out_len)
423 {
424 *out_len = n;
425 }
426 return true;
427}
428
429bool pc_spinel_reader_ok(const SpinelReader *r)
430{
431 return r && !r->err;
432}
433
434void pc_spinel_writer_init(SpinelWriter *w, uint8_t *out, uint16_t cap)
435{
436 if (!w)
437 {
438 return;
439 }
440 w->buf = out;
441 w->cap = out ? cap : 0;
442 w->off = 0;
443 w->err = (out == nullptr && cap > 0);
444}
445
446namespace
447{
448// Reserve n bytes for writing; return the write pointer or nullptr (latching err) if no room.
449uint8_t *room(SpinelWriter *w, uint16_t n)
450{
451 if (!w || w->err || (uint32_t)w->off + n > w->cap)
452 {
453 if (w)
454 {
455 w->err = true;
456 }
457 return nullptr;
458 }
459 uint8_t *at = w->buf + w->off;
460 w->off = (uint16_t)(w->off + n);
461 return at;
462}
463} // namespace
464
465bool pc_spinel_put_bool(SpinelWriter *w, bool v)
466{
467 uint8_t *b = room(w, 1);
468 if (!b)
469 {
470 return false;
471 }
472 b[0] = v ? 1 : 0;
473 return true;
474}
475
476bool pc_spinel_put_u8(SpinelWriter *w, uint8_t v)
477{
478 uint8_t *b = room(w, 1);
479 if (!b)
480 {
481 return false;
482 }
483 b[0] = v;
484 return true;
485}
486
487bool pc_spinel_put_i8(SpinelWriter *w, int8_t v)
488{
489 return pc_spinel_put_u8(w, (uint8_t)v);
490}
491
492bool pc_spinel_put_u16(SpinelWriter *w, uint16_t v)
493{
494 uint8_t *b = room(w, 2);
495 if (!b)
496 {
497 return false;
498 }
499 b[0] = (uint8_t)(v & 0xFF);
500 b[1] = (uint8_t)(v >> 8);
501 return true;
502}
503
504bool pc_spinel_put_i16(SpinelWriter *w, int16_t v)
505{
506 return pc_spinel_put_u16(w, (uint16_t)v);
507}
508
509bool pc_spinel_put_u32(SpinelWriter *w, uint32_t v)
510{
511 uint8_t *b = room(w, 4);
512 if (!b)
513 {
514 return false;
515 }
516 b[0] = (uint8_t)(v & 0xFF);
517 b[1] = (uint8_t)((v >> 8) & 0xFF);
518 b[2] = (uint8_t)((v >> 16) & 0xFF);
519 b[3] = (uint8_t)((v >> 24) & 0xFF);
520 return true;
521}
522
523bool pc_spinel_put_i32(SpinelWriter *w, int32_t v)
524{
525 return pc_spinel_put_u32(w, (uint32_t)v);
526}
527
528bool pc_spinel_put_uint(SpinelWriter *w, uint32_t v)
529{
530 if (!w || w->err)
531 {
532 return false;
533 }
534 uint8_t tmp[5];
535 uint8_t n = pc_spinel_pack_uint(v, tmp, sizeof(tmp));
536 // GCOVR_EXCL_START unreachable: tmp is fixed at 5 bytes, which is always enough to pack any
537 // uint32 (needs at most ceil(32/7) = 5 bytes of 7-bit groups), so pack_uint can never return 0 here.
538 if (n == 0)
539 {
540 w->err = true;
541 return false;
542 }
543 // GCOVR_EXCL_STOP
544 uint8_t *b = room(w, n);
545 if (!b)
546 {
547 return false;
548 }
549 for (uint8_t i = 0; i < n; i++)
550 {
551 b[i] = tmp[i];
552 }
553 return true;
554}
555
556bool pc_spinel_put_eui64(SpinelWriter *w, const uint8_t *v8)
557{
558 if (!v8)
559 {
560 if (w)
561 {
562 w->err = true;
563 }
564 return false;
565 }
566 uint8_t *b = room(w, 8);
567 if (!b)
568 {
569 return false;
570 }
571 for (uint8_t i = 0; i < 8; i++)
572 {
573 b[i] = v8[i];
574 }
575 return true;
576}
577
578bool pc_spinel_put_ipv6(SpinelWriter *w, const uint8_t *v16)
579{
580 if (!v16)
581 {
582 if (w)
583 {
584 w->err = true;
585 }
586 return false;
587 }
588 uint8_t *b = room(w, 16);
589 if (!b)
590 {
591 return false;
592 }
593 for (uint8_t i = 0; i < 16; i++)
594 {
595 b[i] = v16[i];
596 }
597 return true;
598}
599
600bool pc_spinel_put_utf8(SpinelWriter *w, const char *s)
601{
602 if (!s)
603 {
604 if (w)
605 {
606 w->err = true;
607 }
608 return false;
609 }
610 uint16_t n = 0;
611 while (s[n] != 0)
612 {
613 n++;
614 }
615 uint8_t *b = room(w, (uint16_t)(n + 1)); // include the NUL
616 if (!b)
617 {
618 return false;
619 }
620 for (uint16_t i = 0; i <= n; i++)
621 {
622 b[i] = (uint8_t)s[i];
623 }
624 return true;
625}
626
627bool pc_spinel_put_data(SpinelWriter *w, const uint8_t *d, uint16_t n)
628{
629 if (d == nullptr && n > 0)
630 {
631 if (w)
632 {
633 w->err = true;
634 }
635 return false;
636 }
637 uint8_t *b = room(w, n);
638 if (!b)
639 {
640 return false;
641 }
642 for (uint16_t i = 0; i < n; i++)
643 {
644 b[i] = d[i];
645 }
646 return true;
647}
648
649bool pc_spinel_put_data_wlen(SpinelWriter *w, const uint8_t *d, uint16_t n)
650{
651 if (!pc_spinel_put_u16(w, n))
652 {
653 return false;
654 }
655 return pc_spinel_put_data(w, d, n);
656}
657
658uint16_t pc_spinel_writer_len(const SpinelWriter *w)
659{
660 if (!w || w->err)
661 {
662 return 0;
663 }
664 return w->off;
665}
666
667// --- Property registry ------------------------------------------------------------------
668
669namespace
670{
671const SpinelPropInfo k_props[] = {
672 {SpinelProp::SPINEL_PROP_LAST_STATUS, "LAST_STATUS", 'i'},
673 {SpinelProp::SPINEL_PROP_PROTOCOL_VERSION, "PROTOCOL_VERSION", 'i'},
674 {SpinelProp::SPINEL_PROP_NCP_VERSION, "NCP_VERSION", 'U'},
675 {SpinelProp::SPINEL_PROP_INTERFACE_TYPE, "INTERFACE_TYPE", 'i'},
676 {SpinelProp::SPINEL_PROP_VENDOR_ID, "VENDOR_ID", 'i'},
677 {SpinelProp::SPINEL_PROP_CAPS, "CAPS", 'i'},
678 {SpinelProp::SPINEL_PROP_INTERFACE_COUNT, "INTERFACE_COUNT", 'C'},
679 {SpinelProp::SPINEL_PROP_HWADDR, "HWADDR", 'E'},
680 {SpinelProp::SPINEL_PROP_LOCK, "LOCK", 'b'},
681 {SpinelProp::SPINEL_PROP_PHY_ENABLED, "PHY_ENABLED", 'b'},
682 {SpinelProp::SPINEL_PROP_PHY_CHAN, "PHY_CHAN", 'C'},
683 {SpinelProp::SPINEL_PROP_PHY_CHAN_SUPPORTED, "PHY_CHAN_SUPPORTED", 'C'},
684 {SpinelProp::SPINEL_PROP_PHY_FREQ, "PHY_FREQ", 'L'},
685 {SpinelProp::SPINEL_PROP_PHY_TX_POWER, "PHY_TX_POWER", 'c'},
686 {SpinelProp::SPINEL_PROP_PHY_RSSI, "PHY_RSSI", 'c'},
687 {SpinelProp::SPINEL_PROP_MAC_SCAN_STATE, "MAC_SCAN_STATE", 'C'},
688 {SpinelProp::SPINEL_PROP_MAC_SCAN_MASK, "MAC_SCAN_MASK", 'C'},
689 {SpinelProp::SPINEL_PROP_MAC_SCAN_PERIOD, "MAC_SCAN_PERIOD", 'S'},
690 {SpinelProp::SPINEL_PROP_MAC_15_4_LADDR, "MAC_15_4_LADDR", 'E'},
691 {SpinelProp::SPINEL_PROP_MAC_15_4_SADDR, "MAC_15_4_SADDR", 'S'},
692 {SpinelProp::SPINEL_PROP_MAC_15_4_PANID, "MAC_15_4_PANID", 'S'},
693 {SpinelProp::SPINEL_PROP_NET_SAVED, "NET_SAVED", 'b'},
694 {SpinelProp::SPINEL_PROP_NET_IF_UP, "NET_IF_UP", 'b'},
695 {SpinelProp::SPINEL_PROP_NET_STACK_UP, "NET_STACK_UP", 'b'},
696 {SpinelProp::SPINEL_PROP_NET_ROLE, "NET_ROLE", 'C'},
697 {SpinelProp::SPINEL_PROP_NET_NETWORK_NAME, "NET_NETWORK_NAME", 'U'},
698 {SpinelProp::SPINEL_PROP_NET_XPANID, "NET_XPANID", 'D'},
699 {SpinelProp::SPINEL_PROP_NET_NETWORK_KEY, "NET_NETWORK_KEY", 'D'},
700 {SpinelProp::SPINEL_PROP_IPV6_LL_ADDR, "IPV6_LL_ADDR", '6'},
701 {SpinelProp::SPINEL_PROP_IPV6_ML_ADDR, "IPV6_ML_ADDR", '6'},
702 {SpinelProp::SPINEL_PROP_STREAM_DEBUG, "STREAM_DEBUG", 'U'},
703 {SpinelProp::SPINEL_PROP_STREAM_RAW, "STREAM_RAW", 'd'},
704 {SpinelProp::SPINEL_PROP_STREAM_NET, "STREAM_NET", 'd'},
705};
706
707struct StatusName
708{
709 uint32_t code;
710 const char *name;
711};
712const StatusName k_status[] = {
713 {SpinelStatus::SPINEL_STATUS_OK, "OK"},
714 {SpinelStatus::SPINEL_STATUS_FAILURE, "FAILURE"},
715 {SpinelStatus::SPINEL_STATUS_UNIMPLEMENTED, "UNIMPLEMENTED"},
716 {SpinelStatus::SPINEL_STATUS_INVALID_ARGUMENT, "INVALID_ARGUMENT"},
717 {SpinelStatus::SPINEL_STATUS_INVALID_STATE, "INVALID_STATE"},
718 {SpinelStatus::SPINEL_STATUS_INVALID_COMMAND, "INVALID_COMMAND"},
719 {SpinelStatus::SPINEL_STATUS_INVALID_INTERFACE, "INVALID_INTERFACE"},
720 {SpinelStatus::SPINEL_STATUS_INTERNAL_ERROR, "INTERNAL_ERROR"},
721 {SpinelStatus::SPINEL_STATUS_SECURITY_ERROR, "SECURITY_ERROR"},
722 {SpinelStatus::SPINEL_STATUS_PARSE_ERROR, "PARSE_ERROR"},
723 {SpinelStatus::SPINEL_STATUS_IN_PROGRESS, "IN_PROGRESS"},
724 {SpinelStatus::SPINEL_STATUS_NOMEM, "NOMEM"},
725 {SpinelStatus::SPINEL_STATUS_BUSY, "BUSY"},
726 {SpinelStatus::SPINEL_STATUS_PROP_NOT_FOUND, "PROP_NOT_FOUND"},
727 {SpinelStatus::SPINEL_STATUS_DROPPED, "DROPPED"},
728 {SpinelStatus::SPINEL_STATUS_EMPTY, "EMPTY"},
729};
730} // namespace
731
732const SpinelPropInfo *pc_spinel_prop_lookup(uint32_t id)
733{
734 for (uint16_t i = 0; i < sizeof(k_props) / sizeof(k_props[0]); i++)
735 {
736 if (k_props[i].id == id)
737 {
738 return &k_props[i];
739 }
740 }
741 return nullptr;
742}
743
744const char *pc_spinel_prop_name(uint32_t id)
745{
746 const SpinelPropInfo *e = pc_spinel_prop_lookup(id);
747 return e ? e->name : "UNKNOWN";
748}
749
750const char *pc_spinel_status_name(uint32_t status)
751{
752 for (uint16_t i = 0; i < sizeof(k_status) / sizeof(k_status[0]); i++)
753 {
754 if (k_status[i].code == status)
755 {
756 return k_status[i].name;
757 }
758 }
759 if (status >= SpinelStatus::SPINEL_STATUS_RESET_POWER_ON &&
760 status <= SpinelStatus::SPINEL_STATUS_RESET_POWER_ON + 7)
761 {
762 return "RESET";
763 }
764 return "UNKNOWN";
765}
766
767uint16_t pc_spinel_fcs(const uint8_t *buf, uint16_t len)
768{
769 // The HDLC-lite FCS is CRC-16/X-25 (reflected poly 0x8408, init 0xFFFF, xorout 0xFFFF).
770 // test_crc diffs the shared engine against the loop that used to live here over every length
771 // 0..64, and asserts the cataloge check value 0x906E that this suite already pinned.
772 return (uint16_t)pc_crc(&PC_CRC16_X25, buf, len);
773}
774
775uint16_t pc_spinel_frame_encode(const uint8_t *payload, uint16_t len, uint8_t *out, uint16_t cap)
776{
777 if (!out || len > PC_THREAD_MAX_DATA || (payload == nullptr && len > 0))
778 {
779 return 0;
780 }
781 uint16_t fcs = pc_spinel_fcs(payload, len);
782 uint16_t p = 0;
783 for (uint16_t i = 0; i < len; i++)
784 {
785 if (!put_stuffed(out, &p, cap, payload[i]))
786 {
787 return 0;
788 }
789 }
790 if (!put_stuffed(out, &p, cap, (uint8_t)(fcs & 0xFF)) || // FCS low byte first
791 !put_stuffed(out, &p, cap, (uint8_t)(fcs >> 8)))
792 {
793 return 0;
794 }
795 if (p + 1 > cap)
796 {
797 return 0;
798 }
799 out[p++] = ThreadHdlc::HDLC_FLAG;
800 return p;
801}
802
803int pc_spinel_frame_decode(const uint8_t *raw, uint16_t len, uint8_t *payload, uint16_t pay_cap, uint16_t *pay_len)
804{
805 if (!raw)
806 {
807 return 0;
808 }
809 uint16_t flag = 0;
810 while (flag < len && raw[flag] != ThreadHdlc::HDLC_FLAG)
811 {
812 flag++;
813 }
814 if (flag >= len)
815 {
816 return 0; // no complete frame yet
817 }
818
819 // Remove the byte-stuffing from raw[0, flag) into a scratch: payload + FCS(2).
820 uint8_t un[PC_THREAD_MAX_DATA + 2];
821 uint16_t n = 0;
822 for (uint16_t i = 0; i < flag; i++)
823 {
824 uint8_t b = raw[i];
825 if (b == ThreadHdlc::HDLC_ESCAPE)
826 {
827 if (++i >= flag)
828 {
829 return -1; // dangling escape
830 }
831 b = (uint8_t)(raw[i] ^ 0x20);
832 }
833 if (n >= sizeof(un))
834 {
835 return -1;
836 }
837 un[n++] = b;
838 }
839 if (n < 2)
840 {
841 return -1; // need at least the FCS
842 }
843 uint16_t plen = (uint16_t)(n - 2);
844 uint16_t fcs = pc_spinel_fcs(un, plen);
845 if ((uint16_t)(un[plen] | (un[plen + 1] << 8)) != fcs)
846 {
847 return -1; // FCS mismatch (transmitted low byte first)
848 }
849 if (plen > pay_cap)
850 {
851 return -1;
852 }
853 for (uint16_t i = 0; i < plen; i++)
854 {
855 payload[i] = un[i];
856 }
857 if (pay_len)
858 {
859 *pay_len = plen;
860 }
861 return (int)(flag + 1);
862}
863
864#endif // PC_ENABLE_THREAD
Parameterized CRC engine - one source of truth for every cyclic redundancy check.
constexpr pc_crc_params PC_CRC16_X25
CRC-16/X-25 (HDLC FCS). check = 0x906E. Used by services/radio/thread, mbplus, nema_ts2.
Definition crc.h:190
uint32_t pc_crc(const pc_crc_params *p, const uint8_t *data, size_t len)
One-shot CRC of len octets at data.
Definition crc.h:158
#define PC_THREAD_MAX_DATA
Max spinel payload bytes carried in one HDLC-lite frame.
Thread spinel / HDLC-lite framing codec (PC_ENABLE_THREAD) - OpenThread RCP.