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