mcumgr_smp/
smp.rs

1// Author: Sascha Zenglein <zenglein@gessler.de>
2// Copyright (c) 2023 Gessler GmbH.
3
4use thiserror::Error;
5
6#[derive(Error, Debug)]
7pub enum SmpError {
8    #[error("payload decoding error: {0}")]
9    PayloadDecodingError(#[from] Box<dyn std::error::Error>),
10    #[error("smp frame decoding error")]
11    InvalidFrame,
12    #[error("unexpected sequence number")]
13    UnexpectedSeq,
14}
15
16#[derive(Debug, Clone, Copy)]
17pub enum OpCode {
18    ReadRequest = 0,
19    ReadResponse = 1,
20    WriteRequest = 2,
21    WriteResponse = 3,
22}
23
24impl From<u8> for OpCode {
25    fn from(num: u8) -> Self {
26        match num {
27            0 => OpCode::ReadRequest,
28            1 => OpCode::ReadResponse,
29            2 => OpCode::WriteRequest,
30            3 => OpCode::WriteResponse,
31            _ => panic!("unknown opcode"),
32        }
33    }
34}
35
36impl From<OpCode> for u8 {
37    fn from(op: OpCode) -> Self {
38        match op {
39            OpCode::ReadRequest => 0,
40            OpCode::ReadResponse => 1,
41            OpCode::WriteRequest => 2,
42            OpCode::WriteResponse => 3,
43        }
44    }
45}
46
47#[derive(Debug, Clone, Copy)]
48pub enum Group {
49    Default,
50    ApplicationManagement,
51    Statistics,
52    FileManagement,
53    ShellManagement,
54    ZephyrCommand,
55    Custom(u16),
56}
57
58impl From<u16> for Group {
59    fn from(num: u16) -> Self {
60        match num {
61            0 => Self::Default,
62            1 => Self::ApplicationManagement,
63            2 => Self::Statistics,
64            8 => Self::FileManagement,
65            9 => Self::ShellManagement,
66            63 => Self::ZephyrCommand,
67            num => Self::Custom(num),
68        }
69    }
70}
71
72impl From<Group> for u16 {
73    fn from(g: Group) -> Self {
74        match g {
75            Group::Default => 0,
76            Group::ApplicationManagement => 1,
77            Group::Statistics => 2,
78            Group::FileManagement => 8,
79            Group::ShellManagement => 9,
80            Group::ZephyrCommand => 63,
81            Group::Custom(num) => num,
82        }
83    }
84}
85
86pub enum ReturnCode {
87    Ok = 0,
88    Unknown = 1,
89    OutOfMemory = 2,
90    // ...
91    UserDefined = 256,
92}
93
94/// Definitition of a single SMP message.  
95/// SMP Requests and Responses always have this format.
96#[derive(Debug, Clone)]
97pub struct SmpFrame<T> {
98    pub operation: OpCode,
99    pub flags: u8,
100    pub group: Group,
101    pub sequence: u8,
102    pub command: u8,
103    pub data: T,
104}
105
106impl<T> SmpFrame<T> {
107    ///  Create new with default flags
108    pub fn new(operation: OpCode, sequence: u8, group: Group, command: u8, payload: T) -> Self {
109        Self {
110            operation,
111            flags: 0,
112            group,
113            sequence,
114            command,
115            data: payload,
116        }
117    }
118}
119
120impl<T> SmpFrame<T> {
121    /// Encode the frame to bytes using the given encode_payload handler.  
122    /// For the common CBOR serialisation, see [SmpFrame::encode_with_cbor]
123    pub fn encode<E, R: AsRef<[u8]>>(
124        &self,
125        encode_payload: impl FnOnce(&T) -> Result<R, E>,
126    ) -> Result<Vec<u8>, E> {
127        let mut buf: Vec<u8> = Vec::with_capacity(12);
128
129        let encoded = encode_payload(&self.data)?;
130        let data: &[u8] = encoded.as_ref();
131
132        buf.push(self.operation.into());
133        buf.push(self.flags);
134        buf.extend_from_slice(&(data.len() as u16).to_be_bytes());
135        let group: u16 = self.group.into();
136        buf.extend_from_slice(&group.to_be_bytes());
137        buf.push(self.sequence);
138        buf.push(self.command);
139
140        buf.extend(data.iter());
141
142        Ok(buf)
143    }
144
145    /// Decode the frame from bytes using the given decode_payload handler.  
146    /// For the common CBOR serialisation, see [SmpFrame::decode_with_cbor]
147    pub fn decode(
148        buf: &[u8],
149        decode_payload: impl FnOnce(&[u8]) -> Result<T, Box<dyn std::error::Error>>,
150    ) -> Result<SmpFrame<T>, SmpError> {
151        if buf.len() < 8 {
152            return Err(SmpError::InvalidFrame);
153        }
154
155        let operation = OpCode::from(buf[0] & 0x07);
156        let group = Group::from(u16::from_be_bytes([buf[4], buf[5]]));
157        let data_len = u16::from_be_bytes([buf[2], buf[3]]);
158        let _flags = buf[1];
159        let sequence = buf[6];
160        let command = buf[7];
161
162        if buf.len() < (8 + data_len) as usize {
163            return Err(SmpError::InvalidFrame);
164        }
165
166        let data_buf = &buf[8..(8 + data_len as usize)];
167        let data = decode_payload(data_buf)?;
168
169        Ok(SmpFrame::new(operation, sequence, group, command, data))
170    }
171}
172
173#[cfg(feature = "payload-cbor")]
174impl<T: serde::Serialize> SmpFrame<T> {
175    /// Encode the frame to bytes using CBOR serialization.  
176    /// This method requires Serde
177    pub fn encode_with_cbor(&self) -> Vec<u8> {
178        // buf cannot run out of space because it can allocate
179        self.encode(|data| {
180            let mut buf = Vec::new();
181            match ciborium::ser::into_writer(data, &mut buf) {
182                Ok(_) => Ok(buf),
183                Err(e) => Err(e),
184            }
185        })
186        .unwrap()
187    }
188}
189
190#[cfg(feature = "payload-cbor")]
191impl<T: serde::de::DeserializeOwned> SmpFrame<T> {
192    /// Decode the frame to bytes using CBOR deserialization.  
193    /// This method requires Serde
194    pub fn decode_with_cbor(buf: &[u8]) -> Result<SmpFrame<T>, SmpError> {
195        Self::decode(buf, |buf| {
196            let x: T = ciborium::de::from_reader(buf)?;
197            Ok(x)
198        })
199    }
200}