DeterministicESPAsyncWebServer v6.27.1
Zero-allocation, bounded-execution async HTTP server for ESP32
Loading...
Searching...
No Matches
thread.h
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.h
6 * @brief Thread spinel / HDLC-lite framing codec (DETWS_ENABLE_THREAD) - OpenThread RCP.
7 *
8 * The HDLC-lite framing that carries spinel frames to an OpenThread radio co-processor (an
9 * nRF52840 / EFR32 RCP) over UART - an 802.15.4 / Thread mesh bridged to IP and the web.
10 * HDLC-lite wraps each spinel frame by appending an FCS, byte-stuffing the reserved bytes,
11 * and terminating with a Flag:
12 *
13 * [spinel payload | FCS(lo,hi)] --byte-stuffed--> ... | 0x7E
14 *
15 * The FCS is the HDLC frame check sequence, **CRC-16/X-25** (poly 0x1021 reflected, init
16 * 0xFFFF, reflected in/out, final XOR 0xFFFF), transmitted low byte first. The reserved
17 * bytes stuffed (as 0x7D, byte XOR 0x20) are the Flag 0x7E, the Escape 0x7D, XON 0x11, and
18 * XOFF 0x13.
19 *
20 * spinel_frame_encode() wraps a payload; spinel_frame_decode() finds the flag, removes the
21 * stuffing, and verifies the FCS. spinel_fcs() is the shared checksum. The spinel command
22 * inside (a property get/set/insert, an 802.15.4 stream) is the application's. Pure - you
23 * carry the bytes over your UART - so it is fully host-testable.
24 *
25 * @author Douglas Quigg (dstroy0)
26 * @date 2026
27 */
28
29#ifndef DETERMINISTICESPASYNCWEBSERVER_THREAD_H
30#define DETERMINISTICESPASYNCWEBSERVER_THREAD_H
31
32#include "ServerConfig.h"
33
34#if DETWS_ENABLE_THREAD
35
36#include <stddef.h>
37#include <stdint.h>
38
39/** @brief HDLC-lite markers. */
40struct ThreadHdlc
41{
42 static constexpr uint8_t HDLC_FLAG = 0x7E; ///< frame delimiter
43 static constexpr uint8_t HDLC_ESCAPE = 0x7D; ///< byte-stuffing escape
44};
45
46/** @brief Common spinel commands (the property accessors a gateway uses). */
47struct SpinelCmd
48{
49 static constexpr uint8_t SPINEL_CMD_NOOP = 0;
50 static constexpr uint8_t SPINEL_CMD_RESET = 1;
51 static constexpr uint8_t SPINEL_CMD_PROP_VALUE_GET = 2;
52 static constexpr uint8_t SPINEL_CMD_PROP_VALUE_SET = 3;
53 static constexpr uint8_t SPINEL_CMD_PROP_VALUE_INSERT = 4;
54 static constexpr uint8_t SPINEL_CMD_PROP_VALUE_REMOVE = 5;
55 static constexpr uint8_t SPINEL_CMD_PROP_VALUE_IS = 6; ///< an async property update from the NCP
56};
57
58/** @brief HDLC frame check sequence: CRC-16/X-25 over @p buf. */
59uint16_t spinel_fcs(const uint8_t *buf, uint16_t len);
60
61// --- Spinel command layer (rides inside a decoded HDLC frame's payload) ---------------
62
63/**
64 * @brief Encode a spinel packed unsigned integer (7 bits/byte, little-endian, high bit =
65 * continuation) into @p out.
66 * @return the number of bytes written (1..5), or 0 if it would not fit @p cap.
67 */
68uint8_t spinel_pack_uint(uint32_t value, uint8_t *out, uint8_t cap);
69
70/**
71 * @brief Decode a spinel packed unsigned integer from the front of @p raw.
72 * @return the bytes consumed (> 0, value in @p value), 0 if more bytes are needed, or -1 if
73 * the encoding overflows a uint32.
74 */
75int spinel_unpack_uint(const uint8_t *raw, uint8_t len, uint32_t *value);
76
77/**
78 * @brief Build a spinel property-command payload (`header | CMD | PROP | value`) - the
79 * content of an HDLC frame - into @p out. CMD and PROP are packed integers.
80 * @return the payload length, or 0 if it would not fit @p cap.
81 */
82uint16_t spinel_command_build(uint8_t header, uint32_t cmd, uint32_t prop, const uint8_t *value, uint16_t value_len,
83 uint8_t *out, uint16_t cap);
84
85/**
86 * @brief Parse a spinel property-command payload (from a decoded HDLC frame).
87 * @param[out] header the flag/iid/tid header byte.
88 * @param[out] cmd the command (unpacked).
89 * @param[out] prop the property id (unpacked).
90 * @param[out] value the first value byte (points into @p payload).
91 * @param[out] value_len the value length.
92 * @return the value offset (> 0), or -1 if the header / command / property is malformed.
93 */
94int spinel_command_parse(const uint8_t *payload, uint16_t len, uint8_t *header, uint32_t *cmd, uint32_t *prop,
95 const uint8_t **value, uint16_t *value_len);
96
97/**
98 * @brief Encode an HDLC-lite frame: @p payload + FCS, byte-stuffed, flag-terminated.
99 * @return the encoded frame length, or 0 if @p len exceeds DETWS_THREAD_MAX_DATA or the
100 * stuffed frame would not fit @p cap.
101 */
102uint16_t spinel_frame_encode(const uint8_t *payload, uint16_t len, uint8_t *out, uint16_t cap);
103
104/**
105 * @brief Decode one HDLC-lite frame from the front of @p raw: find the flag, remove the
106 * stuffing, verify the FCS, and copy the spinel payload to @p payload.
107 * @param[out] pay_len set to the payload length.
108 * @return the bytes consumed up to and including the flag (> 0), 0 if no flag is present yet
109 * (need more), or -1 if the frame is malformed (too short, bad FCS, dangling escape,
110 * or the payload overflows @p pay_cap) - the caller drops one byte and retries.
111 */
112int spinel_frame_decode(const uint8_t *raw, uint16_t len, uint8_t *payload, uint16_t pay_cap, uint16_t *pay_len);
113
114#endif // DETWS_ENABLE_THREAD
115
116#endif // DETERMINISTICESPASYNCWEBSERVER_THREAD_H
User-facing configuration for DeterministicESPAsyncWebServer.