dash7_alp/sub_iot/v0/
mod.rs

1#[cfg(test)]
2use hex_literal::hex;
3
4/// ALP basic Actions used to build Commands
5pub mod action;
6/// Dash7 specific items (most of the ALP protocol could be in theory be used over any
7/// communication link)
8pub mod dash7;
9pub mod operand;
10/// ALP variable int codec implementation
11pub use crate::codec::{Codec, WithOffset, WithSize};
12pub use action::Action;
13
14// ===============================================================================
15// Command
16// ===============================================================================
17/// ALP request that can be sent to an ALP compatible device.
18#[derive(Clone, Debug, PartialEq, Default)]
19pub struct Command {
20    // Does that impact application that don't use the structure?
21    pub actions: Vec<Action>,
22}
23
24impl std::fmt::Display for Command {
25    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
26        write!(f, "[")?;
27        let end = self.actions.len() - 1;
28        for (i, action) in self.actions.iter().enumerate() {
29            write!(f, "{}", action)?;
30            if i != end {
31                write!(f, "; ")?;
32            }
33        }
34        write!(f, "]")
35    }
36}
37
38#[derive(Debug, Clone, PartialEq)]
39pub struct CommandParseFail {
40    pub actions: Vec<Action>,
41    pub error: action::ActionDecodingError,
42}
43
44impl Command {
45    pub fn encoded_size(&self) -> usize {
46        self.actions.iter().map(|act| act.encoded_size()).sum()
47    }
48    /// Encode the item into a given byte array.
49    /// # Safety
50    /// You have to ensure there is enough space in the given array (compared to what
51    /// [encoded_size](#encoded_size) returns) or this method will panic.
52    /// # Panics
53    /// Panics if the given `out` array is too small.
54    pub unsafe fn encode_in(&self, out: &mut [u8]) -> usize {
55        let mut offset = 0;
56        for action in self.actions.iter() {
57            offset += action.encode_in(&mut out[offset..]);
58        }
59        offset
60    }
61    pub fn encode(&self) -> Box<[u8]> {
62        let mut data = vec![0; self.encoded_size()].into_boxed_slice();
63        unsafe { self.encode_in(&mut data) };
64        data
65    }
66    pub fn decode(out: &[u8]) -> Result<Self, WithOffset<CommandParseFail>> {
67        let mut actions = vec![];
68        let mut offset = 0;
69        loop {
70            if offset == out.len() {
71                break;
72            }
73            match Action::decode(&out[offset..]) {
74                Ok(WithSize { value, size }) => {
75                    actions.push(value);
76                    offset += size;
77                }
78                Err(error) => {
79                    let WithOffset { offset: off, value } = error;
80                    return Err(WithOffset {
81                        offset: offset + off,
82                        value: CommandParseFail {
83                            actions,
84                            error: value,
85                        },
86                    });
87                }
88            }
89        }
90        Ok(Self { actions })
91    }
92
93    pub fn request_id(&self) -> Option<u8> {
94        for action in self.actions.iter() {
95            if let Action::RequestTag(action::RequestTag { id, .. }) = action {
96                return Some(*id);
97            }
98        }
99        None
100    }
101
102    pub fn response_id(&self) -> Option<u8> {
103        for action in self.actions.iter() {
104            if let Action::ResponseTag(action::ResponseTag { id, .. }) = action {
105                return Some(*id);
106            }
107        }
108        None
109    }
110
111    pub fn is_last_response(&self) -> bool {
112        for action in self.actions.iter() {
113            if let Action::ResponseTag(action::ResponseTag { eop, .. }) = action {
114                return *eop;
115            }
116        }
117        false
118    }
119}
120#[test]
121fn test_command() {
122    let cmd = Command {
123        actions: vec![
124            Action::RequestTag(action::RequestTag { id: 66, eop: true }),
125            Action::ReadFileData(action::ReadFileData {
126                resp: true,
127                group: false,
128                file_id: 0,
129                offset: 0,
130                size: 8,
131            }),
132            Action::ReadFileData(action::ReadFileData {
133                resp: false,
134                group: true,
135                file_id: 4,
136                offset: 2,
137                size: 3,
138            }),
139            Action::Nop(action::Nop {
140                resp: true,
141                group: true,
142            }),
143        ],
144    };
145    let data = &hex!("B4 42   41 00 00 08   81 04 02 03  C0") as &[u8];
146
147    assert_eq!(&cmd.encode()[..], data);
148    assert_eq!(
149        Command::decode(data).expect("should be parsed without error"),
150        cmd,
151    );
152}
153#[test]
154fn test_command_display() {
155    assert_eq!(
156        Command {
157            actions: vec![
158                Action::RequestTag(action::RequestTag { id: 66, eop: true }),
159                Action::Nop(action::Nop {
160                    resp: true,
161                    group: true,
162                }),
163            ]
164        }
165        .to_string(),
166        "[RTAG[E](66); NOP[GR]]"
167    );
168}
169
170#[test]
171fn test_command_request_id() {
172    assert_eq!(
173        Command {
174            actions: vec![Action::request_tag(true, 66), Action::nop(true, true)]
175        }
176        .request_id(),
177        Some(66)
178    );
179    assert_eq!(
180        Command {
181            actions: vec![Action::nop(true, false), Action::request_tag(true, 44)]
182        }
183        .request_id(),
184        Some(44)
185    );
186    assert_eq!(
187        Command {
188            actions: vec![Action::nop(true, false), Action::nop(true, false)]
189        }
190        .request_id(),
191        None
192    );
193}
194
195#[test]
196fn test_comman_response_id() {
197    assert_eq!(
198        Command {
199            actions: vec![
200                Action::response_tag(true, true, 66),
201                Action::nop(true, true)
202            ]
203        }
204        .response_id(),
205        Some(66)
206    );
207    assert_eq!(
208        Command {
209            actions: vec![
210                Action::nop(true, false),
211                Action::response_tag(true, true, 44)
212            ]
213        }
214        .response_id(),
215        Some(44)
216    );
217    assert_eq!(
218        Command {
219            actions: vec![Action::nop(true, false), Action::nop(true, false)]
220        }
221        .response_id(),
222        None
223    );
224}
225
226#[test]
227fn test_command_is_last_response() {
228    assert!(Command {
229        actions: vec![
230            Action::response_tag(true, true, 66),
231            Action::nop(true, true)
232        ]
233    }
234    .is_last_response());
235    assert!(!Command {
236        actions: vec![
237            Action::response_tag(false, false, 66),
238            Action::nop(true, true)
239        ]
240    }
241    .is_last_response());
242    assert!(!Command {
243        actions: vec![
244            Action::response_tag(false, true, 44),
245            Action::response_tag(true, true, 44)
246        ]
247    }
248    .is_last_response());
249    assert!(!Command {
250        actions: vec![Action::nop(true, false), Action::nop(true, false)]
251    }
252    .is_last_response());
253}