seeed_erpc/
codec.rs

1use super::ids::*;
2use nom::{
3    error::ParseError, lib::std::ops::RangeFrom, number::streaming, IResult, InputIter,
4    InputLength, Slice,
5};
6
7const BASIC_CODEC_VERSION: u8 = 1;
8
9/// Data precluding an RPC payload, describing which RPC it is and other metadata
10#[derive(Clone, Debug)]
11pub struct Header {
12    pub service: Service,
13    pub request: u8,
14    pub msg_type: MsgType,
15    pub sequence: u32, // incrementing number.
16}
17
18impl Header {
19    /// Encodes the RPC into its wire format
20    pub fn as_bytes(&self) -> [u8; 8] {
21        let header: u32 = (BASIC_CODEC_VERSION as u32) << 24
22            | ((self.service as u32) << 16)
23            | ((self.request as u32) << 8)
24            | (self.msg_type as u32);
25        let header = header.to_le_bytes();
26
27        let seq = self.sequence.to_le_bytes();
28
29        [
30            header[0], header[1], header[2], header[3], seq[0], seq[1], seq[2], seq[3],
31        ]
32    }
33
34    /// Decodes an RPC header from a byte slice or other compatible type
35    pub fn parse<I, E: ParseError<I>>(i: I) -> IResult<I, Self, E>
36    where
37        I: Slice<RangeFrom<usize>> + InputIter<Item = u8> + InputLength,
38    {
39        let (i, header) = streaming::le_u32(i)?;
40        let (i, sequence) = streaming::le_u32(i)?;
41        Ok((
42            i,
43            Self {
44                service: (((header >> 16) & 0xff) as u8).into(),
45                request: ((header >> 8) & 0xff) as u8,
46                msg_type: ((header & 0xff) as u8).into(),
47                sequence,
48            },
49        ))
50    }
51}
52
53/// Wraps a complete RPC (Header + data) on stream transports, like a UART.
54#[derive(Clone, Debug)]
55pub struct FrameHeader {
56    pub msg_length: u16,
57    pub crc16: u16,
58}
59
60impl FrameHeader {
61    /// Builds a frame header which will wrap the provided msg.
62    pub fn new_from_msg(msg: &[u8]) -> Self {
63        Self {
64            msg_length: msg.len() as u16,
65            crc16: crc16(msg),
66        }
67    }
68
69    /// Encodes the frame header in its wire format.
70    pub fn as_bytes(&self) -> [u8; 4] {
71        let (l, c) = (self.msg_length.to_le_bytes(), self.crc16.to_le_bytes());
72        [l[0], l[1], c[0], c[1]]
73    }
74
75    /// Nom parser which decodes a leading FrameHeader from the input. Can be
76    /// chained with other nom parsers.
77    pub fn parse<I, E: ParseError<I>>(i: I) -> IResult<I, Self, E>
78    where
79        I: Slice<RangeFrom<usize>> + InputIter<Item = u8> + InputLength,
80    {
81        let (i, msg_length) = streaming::le_u16(i)?;
82        let (i, crc16) = streaming::le_u16(i)?;
83        Ok((i, Self { msg_length, crc16 }))
84    }
85
86    /// Checks the CRC matches that computed from the provided payload.
87    pub fn check_crc<I, E>(&self, data: I) -> Result<(), super::Err<E>>
88    where
89        I: InputIter<Item = u8>,
90    {
91        if crc16(data) == self.crc16 {
92            Ok(())
93        } else {
94            Err(super::Err::CRCMismatch)
95        }
96    }
97}
98
99/// computes the CRC value used in the Wio Terminal eRPC codec
100pub(crate) fn crc16<I>(data: I) -> u16
101where
102    I: InputIter<Item = u8>,
103{
104    let mut crc: u32 = 0xEF4A;
105
106    for b in data.iter_elements() {
107        crc ^= (b as u32) << 8;
108        for _ in 0..8 {
109            let mut temp: u32 = crc << 1;
110            if (crc & 0x8000) != 0 {
111                temp ^= 0x1021;
112            }
113            crc = temp;
114        }
115    }
116
117    crc as u16
118}