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