esp_hosted/
header.rs

1//! This module contains the payload header and TLV structure which proceeds RPC data,
2//! creating a frame, and support types.
3
4use core::sync::atomic::{AtomicU16, Ordering};
5
6use defmt::Format;
7use num_enum::TryFromPrimitive;
8
9use crate::{
10    EspError,
11    ble::HciPkt,
12    copy_le, parse_le,
13    rpc::{EndpointType, RpcEndpoint},
14    transport::{PacketType, RPC_EP_NAME_RSP, compute_checksum},
15};
16
17pub(crate) const PL_HEADER_SIZE: usize = 12; // Verified from ESP docs
18
19// The 6 static bytes in the TLV header: endpoint type (1), endpoint length (2), data type (1),
20// data length (2).
21const TLV_HEADER_SIZE: usize = 6;
22// RPC_EP_NAME_EVT is the same size as `RPC_EP_NAME_RESP`.
23pub(crate) const TLV_SIZE: usize = TLV_HEADER_SIZE + RPC_EP_NAME_RSP.len();
24
25pub(crate) const CRC_SIZE: usize = 2; // todo: Determine if you need this; for trailing CRC.
26
27pub const HEADER_SIZE: usize = PL_HEADER_SIZE + TLV_SIZE;
28
29static SEQ_NUM: AtomicU16 = AtomicU16::new(0);
30
31#[derive(Clone, Copy, PartialEq, TryFromPrimitive, Format)]
32#[repr(u8)]
33/// See ESP-Hosted-MCU readme, section 7.2
34/// /// [official enum](https://github.com/espressif/esp-hosted-mcu/blob/634e51233af2f8124dfa8118747f97f8615ea4a6/common/esp_hosted_interface.h)
35pub enum InterfaceType {
36    Invalid = 0,
37    Sta = 1,
38    Ap = 2,
39    Serial = 3,
40    Hci = 4,
41    Priv = 5,
42    Test = 6,
43    Eth = 7,
44    Max = 8,
45}
46
47#[derive(Clone, Copy, PartialEq, TryFromPrimitive)]
48#[repr(u8)]
49// todo: Verify the provenance.
50pub(crate) enum Module {
51    /// “system / housekeeping”
52    Ctrl = 0x00,
53    Wifi = 0x01,
54    Ble = 0x02,
55}
56
57/// Adapted from `esp-hosted-mcu/common/esp_hosted_header.h`
58/// This is at the start of the message, and is followed by the RPC header.
59/// See ESP-hosted-MCU readme, section 7.1.
60#[derive(Format)]
61pub struct PayloadHeader {
62    /// Interface type. Serial, AP etc.
63    pub if_type: InterfaceType, // 2 4-bit values
64    /// Interface number. 0 may be a good default?
65    pub if_num: u8, // 2 4-bit values
66    pub flags: u8,
67    /// Payload length. The size, in bytes, of everything in the frame following this
68    /// header
69    pub len: u16,
70    ///  Offset. Always = 12 (This header's size). Indicates the byte index the payload
71    /// starts.
72    pub offset: u16,
73    /// Checksum, calculated over the entire frame.
74    pub checksum: u16,
75    /// Sequence number for tracking packets (Useful in debugging)
76    pub seq_num: u16,
77    /// Flow control
78    pub throttle_cmd: u8, // First two bits of this byte.
79    // u8 reserved; `reserve2:6`; same byte as `throttle_cmd`
80
81    // From Esp doc: First 3 bits may be reserved. The remaining bits for HCI or
82    // Private packet type?
83    pub pkt_type: PacketType,
84}
85
86impl PayloadHeader {
87    pub fn new(
88        if_type: InterfaceType,
89        if_num: u8,
90        pkt_type: PacketType,
91        payload_len: usize,
92    ) -> Self {
93        // Len is the number of bytes following the header. (all)
94        Self {
95            if_type,
96            // todo: should we pass if_num as a param? 0 to start?
97            if_num,
98            flags: 0,
99            len: payload_len as u16,
100            offset: PL_HEADER_SIZE as u16,
101            // Computed after the entire frame is constructed. Must be set to 0 for
102            // now, as this goes into the checksum calculation.
103            checksum: 0,
104            seq_num: SEQ_NUM.fetch_add(1, Ordering::SeqCst),
105            throttle_cmd: 0,
106            pkt_type,
107        }
108    }
109
110    /// Serialize into the 12-byte packed representation
111    pub fn to_bytes(&self) -> [u8; PL_HEADER_SIZE] {
112        let mut buf = [0; PL_HEADER_SIZE];
113
114        // byte 0:   [ if_num:4 | if_type:4 ]
115        buf[0] = (self.if_num << 4) | ((self.if_type as u8) & 0x0F);
116
117        buf[1] = self.flags;
118
119        copy_le!(buf, self.len, 2..4);
120        copy_le!(buf, self.offset, 4..6);
121        copy_le!(buf, self.checksum, 6..8);
122        copy_le!(buf, self.seq_num, 8..10);
123
124        // byte 10:  [ reserved2:6 | throttle_cmd:2 ]
125        buf[10] = self.throttle_cmd; // todo: QC if you need a shift.
126
127        // byte 11: union field
128        buf[11] = self.pkt_type.val();
129
130        buf
131    }
132
133    /// Parse from a 12-byte slice (will panic if `buf.len() < 12` or slice-to-array fails)
134    pub fn from_bytes(buf: &[u8]) -> Result<Self, EspError> {
135        let if_type = (buf[0] & 0x0F)
136            .try_into()
137            .map_err(|_| EspError::InvalidData)?;
138        let if_num = (buf[0] >> 4) & 0x0F;
139        let flags = buf[1];
140
141        let len = parse_le!(buf, u16, 2..4);
142        let offset = parse_le!(buf, u16, 4..6);
143        let checksum = parse_le!(buf, u16, 6..8);
144        let seq_num = parse_le!(buf, u16, 8..10);
145
146        let throttle_cmd = buf[10] & 3;
147        let pkt_type = PacketType::from_byte(buf[11])?;
148
149        Ok(Self {
150            if_type,
151            if_num,
152            flags,
153            len,
154            offset,
155            checksum,
156            seq_num,
157            throttle_cmd,
158            pkt_type,
159        })
160    }
161}
162
163/// Builds the entire frame sent and received over the wire protocol. See `esp_hosted_protocol.md`
164/// for details on how this is constructed.
165/// Outputs total bytes in the frame.
166pub(crate) fn build_frame_wifi(out: &mut [u8], payload: &[u8]) -> usize {
167    // `payload` here is all remaining bytes, including RPC metadata.
168    let payload_len = payload.len();
169
170    // From `serial_if.c`: Always Resp for compose. Either Resp or Event from parse. (host-side)
171    let endpoint_value = RpcEndpoint::CtrlResp.as_bytes();
172    let endpoint_len = endpoint_value.len() as u16;
173
174    let hdr = PayloadHeader::new(
175        InterfaceType::Serial,
176        0,
177        PacketType::None,
178        payload_len + TLV_SIZE,
179    );
180    out[..PL_HEADER_SIZE].copy_from_slice(&hdr.to_bytes());
181
182    // Add the TLV data.
183    let mut i = PL_HEADER_SIZE;
184
185    out[i] = EndpointType::EndpointName as _;
186    i += 1;
187
188    copy_le!(out, endpoint_len, i..i + 2);
189    i += 2;
190
191    out[i..i + endpoint_len as usize].copy_from_slice(endpoint_value);
192    i += endpoint_len as usize;
193
194    out[i] = EndpointType::Data as _;
195    i += 1;
196
197    copy_le!(out, payload_len as u16, i..i + 2);
198    i += 2;
199
200    out[i..i + payload_len].copy_from_slice(payload);
201    i += payload_len;
202
203    // system_design...: "**Checksum Coverage**: The checksum covers the **entire frame** including:
204    // 1. Complete `esp_payload_header` (with checksum field set to 0 during calculation)
205    // 2. Complete payload data"
206    let pl_checksum = compute_checksum(&out[..i]);
207    copy_le!(out, pl_checksum, 6..8);
208
209    i
210}
211
212/// Public, since the BLE interface is more raw, relying on HCI from the host.
213pub fn build_frame_ble(out: &mut [u8], pkt_type: HciPkt, hci_payload: &[u8]) -> usize {
214    // `payload` here is all remaining bytes, including RPC metadata.
215    let payload_len = hci_payload.len();
216
217    let packet_type = PacketType::None;
218
219    let mut hdr = PayloadHeader::new(InterfaceType::Hci, 0, packet_type, payload_len);
220    hdr.pkt_type = PacketType::Hci(pkt_type);
221
222    out[..PL_HEADER_SIZE].copy_from_slice(&hdr.to_bytes());
223
224    let mut i = PL_HEADER_SIZE;
225
226    out[i..i + payload_len].copy_from_slice(hci_payload);
227    i += payload_len;
228
229    // system_design...: "**Checksum Coverage**: The checksum covers the **entire frame** including:
230    // 1. Complete `esp_payload_header` (with checksum field set to 0 during calculation)
231    // 2. Complete payload data"
232    let pl_checksum = compute_checksum(&out[..i]);
233    copy_le!(out, pl_checksum, 6..8);
234    i
235}