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