esp_hosted/
rpc.rs

1//! Details about the RPC part of the protocol; this is how our payload
2//! is packaged into the Payload header and TLV structure. It contains a mix of protobuf-general
3//! concepts, and ones specific to the RPC used by esp-hosted-mcu.
4
5use defmt::{Format, Formatter};
6use heapless::Vec;
7use micropb::{MessageEncode, PbEncoder};
8use num_enum::TryFromPrimitive;
9
10pub use crate::proto_data::RpcId;
11use crate::{
12    EspError, RpcP,
13    header::build_frame,
14    proto_data::EventHeartbeat,
15    transport::{RPC_EP_NAME_EVT, RPC_EP_NAME_RSP},
16    wifi::WifiApRecord,
17};
18
19// todo: A/R, or ideally pass in.
20pub(crate) const MAX_RPC_SIZE: usize = 500;
21pub(crate) const RPC_MIN_SIZE: usize = 10;
22
23// #[derive(Format)]
24pub enum RpcPayload {
25    EventHeartbeat(EventHeartbeat),
26    EventWifiScanGetApRecord(WifiApRecord),
27    // todo: Setting up a static buf here may be trouble.
28    EventWifiScanGetApRecords(Vec<WifiApRecord, 30>),
29}
30
31impl Format for RpcPayload {
32    fn format(&self, fmt: Formatter<'_>) {
33        // todo: Temp  until we sort out the Vec problem.
34        // match self {
35        //     Self::EventHeartbeat(_) => (),
36        // _ => write!()
37        // }
38    }
39}
40
41/// See `esp_hosted_rpc.proto`.
42#[derive(Format)]
43pub struct Rpc {
44    /// E.g. send a request, and receive a response or event.
45    pub msg_type: RpcType,
46    /// Identifies the type of message we're sending.
47    pub msg_id: RpcId,
48    /// This is used for tracking purposes; it can be anything we want. Responses will match it.
49    pub uid: u32,
50    /// Notes: A: this is semi-redundant with msg_id. B: We currently only use it for responses
51    /// and events, and maily for ones that have multiple records, which we've having a
52    /// hard time parsing with micropb.
53    pub payload: Option<RpcPayload>, // This is followed by a (len-determined; sub-struct) field associated with the RPC Id.
54                                     // It has field number = Rpc ID.
55}
56
57impl Rpc {
58    pub fn new_req(msg_id: RpcId, uid: u32) -> Self {
59        Self {
60            msg_type: RpcType::Req,
61            msg_id,
62            uid,
63            payload: None,
64        }
65    }
66
67    /// Writes the Rpc struct and data to buf. Returns the total size used.
68    pub fn to_bytes(&self, buf: &mut [u8], data: &[u8]) -> usize {
69        let mut i = 0;
70        let data_len = data.len();
71
72        write_rpc(buf, 1, WireType::Varint, self.msg_type as u64, &mut i);
73        write_rpc(buf, 2, WireType::Varint, self.msg_id as u64, &mut i);
74        write_rpc(buf, 3, WireType::Varint, self.uid as u64, &mut i);
75
76        // We repeat the message id as the payload's tag field.
77        // Note: When using length-determined, we must follow the tag with a varint len.
78
79        write_rpc(
80            buf,
81            self.msg_id as u16,
82            WireType::Len,
83            data_len as u64,
84            &mut i,
85        );
86
87        buf[i..i + data_len].copy_from_slice(data);
88        i += data_len;
89
90        i
91    }
92
93    /// Returns (Self, data start i, data len expected).
94    pub fn from_bytes(buf: &[u8]) -> Result<(Self, usize, usize), EspError> {
95        // Skipping msg type tag at byte 0.
96        let msg_type = buf[1].try_into().map_err(|e| EspError::InvalidData)?;
97
98        let (rpc_id, rpc_id_size) = decode_varint(&buf[3..])?;
99        let msg_id = (rpc_id as u16)
100            .try_into()
101            .map_err(|e| EspError::InvalidData)?;
102
103        let mut i = 3 + rpc_id_size;
104
105        let mut uid = 0; // Default if the UID is missing. We observe this for events.
106        // This is a bit fragile, but works for now.
107        if buf[3 + rpc_id_size] == 16 {
108            // UID present
109            i += 1; // Skip the tag.
110            let (uid_, uid_size) = decode_varint(&buf[i..])?;
111            uid = uid_;
112            i += uid_size;
113        }
114
115        let result = Self {
116            msg_type,
117            msg_id,
118            uid: uid as u32,
119            payload: None, // todo: Update this
120        };
121
122        let (_data_tag, data_tag_size) = decode_varint(&buf[i..])?;
123        i += data_tag_size;
124
125        let (data_len, data_len_size) = decode_varint(&buf[i..])?;
126        i += data_len_size;
127
128        Ok((result, i, data_len as usize))
129    }
130}
131
132/// https://protobuf.dev/programming-guides/encoding/
133#[derive(Clone, Copy, Default, PartialEq, Format, TryFromPrimitive)]
134#[repr(u8)]
135pub enum WireType {
136    #[default]
137    /// i32, i64, u32, u64, sint64, sint32, sing64, bool, enum
138    Varint = 0,
139    /// fixed64, sfixed64, double
140    I64 = 1,
141    /// Len-determined (string, bytes, embedded messages, packed repeated fields)
142    Len = 2,
143    /// 32-bit fixed (fixed32, sfixed32, float)
144    I32 = 5,
145}
146
147#[derive(Clone, Copy, PartialEq, TryFromPrimitive, Format)]
148#[repr(u8)]
149/// See `esp_hosted_rpc.proto`, enum by this name.
150pub(crate) enum Rpc_WifiBw {
151    BW_Invalid = 0,
152    HT20 = 1,
153    HT40 = 2,
154}
155
156#[derive(Clone, Copy, PartialEq, TryFromPrimitive, Format)]
157#[repr(u8)]
158/// See `esp_hosted_rpc.proto`, enum by this name.
159pub(crate) enum Rpc_WifiPowerSave {
160    PS_Invalid = 0,
161    MIN_MODEM = 1,
162    MAX_MODEM = 2,
163}
164
165#[derive(Clone, Copy, PartialEq, TryFromPrimitive, Format)]
166#[repr(u8)]
167/// See `esp_hosted_rpc.proto`, enum by this name.
168pub(crate) enum Rpc_WifiSecProt {
169    Open = 0,
170    WEP = 1,
171    WPA_PSK = 2,
172    WPA2_PSK = 3,
173    WPA_WPA2_PSK = 4,
174    WPA2_ENTERPRISE = 5,
175    WPA3_PSK = 6,
176    WPA2_WPA3_PSK = 7,
177}
178
179#[derive(Clone, Copy, PartialEq, TryFromPrimitive, Format)]
180#[repr(u8)]
181/// See `esp_hosted_rpc.proto`, enum by this name.
182pub(crate) enum Rpc_Status {
183    Connected = 0,
184    Not_Connected = 1,
185    No_AP_Found = 2,
186    Connection_Fail = 3,
187    Invalid_Argument = 4,
188    Out_Of_Range = 5,
189}
190
191#[derive(Clone, Copy, PartialEq, TryFromPrimitive, Format)]
192#[repr(u8)]
193/// See `esp_hosted_rpc.proto`, enum by this name. And `esp_hosted_rpc.pb-c.h`. (Maybe taken from there?)
194/// We encode this as a varint.
195pub enum RpcType {
196    MsgType_Invalid = 0,
197    Req = 1,
198    Resp = 2,
199    Event = 3,
200    MsgType_Max = 4,
201}
202
203#[derive(Clone, Copy, PartialEq, TryFromPrimitive)]
204#[repr(u8)]
205/// Type-length-value header. See host/drivers/virtual_serial_if/serial_if.c
206pub(crate) enum EndpointType {
207    /// PROTO_PSER_TLV_T_EPNAME
208    EndpointName = 0x01,
209    /// PROTO_PSER_TLV_T_DATA
210    Data = 0x02,
211}
212
213#[derive(Clone, Copy, PartialEq, TryFromPrimitive)]
214#[repr(u8)]
215/// Type-length-value header. See host/drivers/virtual_serial_if/serial_if.c
216/// Note that other sources imply there should also be a CtrlReq, which is the only
217/// one the host sends. (Is that from esp-hosted-nonmcu?)
218pub(crate) enum RpcEndpoint {
219    CtrlResp,
220    CtrlEvent,
221}
222
223impl RpcEndpoint {
224    pub fn as_bytes(&self) -> &'static [u8] {
225        match self {
226            // Host only sends this.
227            Self::CtrlResp => RPC_EP_NAME_RSP,
228            // Slave sends either.
229            Self::CtrlEvent => RPC_EP_NAME_EVT,
230        }
231        .as_bytes()
232    }
233}
234
235/// Sets up an RPC command to write. This makes calls to set up payload header and TLV.
236/// returns the total payload size after setup. (Including PL header, TLV, RPC). This function is mainly
237/// used internally by our higher-level API.
238pub fn setup_rpc(buf: &mut [u8], rpc: &Rpc, data: &[u8]) -> usize {
239    // todo: I don't like how we need to specify another size constraint here, in addition to the frame buf.
240    let mut rpc_buf = [0; MAX_RPC_SIZE];
241
242    let mut i = 0;
243    i += rpc.to_bytes(&mut rpc_buf, data);
244
245    build_frame(buf, &rpc_buf[..i])
246}
247
248/// Sets up an RPC command to write using the automatic protbuf decoding from micropb. This is flexible, and
249/// allows writing arbitrary commands, but its interface isn't as streamlined as our native impl.
250pub fn setup_rpc_proto(buf: &mut [u8], message: RpcP) -> Result<usize, EspError> {
251    let mut rpc_buf = Vec::<u8, MAX_RPC_SIZE>::new();
252
253    let mut encoder = PbEncoder::new(&mut rpc_buf);
254    message.encode(&mut encoder).map_err(|_| EspError::Proto)?;
255
256    Ok(build_frame(buf, &rpc_buf[..rpc_buf.len()]))
257}
258
259/// Write an automatically-decoded protobuf message directly.
260pub fn write_rpc_raw<W>(buf: &mut [u8], mut write: W, msg: RpcP) -> Result<(), EspError>
261where
262    W: FnMut(&[u8]) -> Result<(), EspError>,
263{
264    let frame_len = setup_rpc_proto(buf, msg)?;
265    write(&buf[..frame_len])?;
266
267    Ok(())
268}
269
270/// Handles making tags, and encoding as varints. Increments the index.
271/// todo: Consider here, and `encode_varint`, using u32 vice u64.
272pub(crate) fn write_rpc(buf: &mut [u8], field: u16, wire_type: WireType, val: u64, i: &mut usize) {
273    let tag = encode_tag(field, wire_type);
274    *i += encode_varint(tag as u64, &mut buf[*i..]);
275    *i += encode_varint(val, &mut buf[*i..]);
276}
277
278/// Used in a few places when setting up RPC.
279pub(crate) fn encode_tag(field: u16, wire_type: WireType) -> u16 {
280    (field << 3) | (wire_type as u16)
281}
282
283pub(crate) fn decode_tag(val: u16) -> (u16, WireType) {
284    (
285        val >> 3,
286        ((val & 0b111) as u8).try_into().unwrap_or_default(),
287    )
288}
289
290/// Encodes `v` as little-endian 7-bit var-int.
291/// Returns number of bytes written (1–3 for a `u16`).
292pub(crate) fn encode_varint(mut v: u64, out: &mut [u8]) -> usize {
293    let mut idx = 0;
294    loop {
295        let byte = (v & 0x7F) as u8;
296        v >>= 7;
297        if v == 0 {
298            out[idx] = byte; // last byte – high bit clear
299            idx += 1;
300            break;
301        } else {
302            out[idx] = byte | 0x80; // more bytes follow
303            idx += 1;
304        }
305    }
306    idx
307}
308
309/// Decodes a little-endian 7-bit var-int.
310/// Returns `(value, bytes_consumed)`.
311pub(crate) fn decode_varint(input: &[u8]) -> Result<(u64, usize), EspError> {
312    let mut val = 0u64;
313    let mut shift = 0;
314    for (idx, &byte) in input.iter().enumerate() {
315        val |= ((byte & 0x7F) as u64) << shift;
316        if byte & 0x80 == 0 {
317            return Ok((val, idx + 1));
318        }
319        shift += 7;
320    }
321
322    Err(EspError::InvalidData)
323}