Skip to main content

lxmf_core/
payload_fields.rs

1use crate::constants::FIELD_COMMANDS;
2use crate::LxmfError;
3use alloc::collections::BTreeMap;
4use alloc::string::ToString;
5use alloc::vec;
6use alloc::vec::Vec;
7
8#[derive(Debug, Clone, PartialEq, Eq)]
9pub struct CommandEntry {
10    pub command_id: u8,
11    pub payload: Vec<u8>,
12}
13
14impl CommandEntry {
15    pub fn from_text(command_id: u8, payload: &str) -> Self {
16        Self { command_id, payload: payload.as_bytes().to_vec() }
17    }
18
19    pub fn from_bytes(command_id: u8, payload: Vec<u8>) -> Self {
20        Self { command_id, payload }
21    }
22}
23
24#[derive(Debug, Clone, Default, PartialEq)]
25pub struct WireFields {
26    entries: BTreeMap<u8, rmpv::Value>,
27}
28
29impl WireFields {
30    pub fn new() -> Self {
31        Self::default()
32    }
33
34    pub fn insert_field(&mut self, field_id: u8, value: rmpv::Value) -> &mut Self {
35        self.entries.insert(field_id, value);
36        self
37    }
38
39    pub fn set_commands<I>(&mut self, commands: I) -> &mut Self
40    where
41        I: IntoIterator<Item = CommandEntry>,
42    {
43        let mut out = Vec::new();
44        for command in commands {
45            let entry = rmpv::Value::Map(vec![(
46                rmpv::Value::Integer((command.command_id as i64).into()),
47                rmpv::Value::Binary(command.payload),
48            )]);
49            out.push(entry);
50        }
51        self.entries.insert(FIELD_COMMANDS, rmpv::Value::Array(out));
52        self
53    }
54
55    pub fn to_rmpv(&self) -> rmpv::Value {
56        let mut entries = Vec::with_capacity(self.entries.len());
57        for (field_id, value) in &self.entries {
58            entries.push((rmpv::Value::Integer((*field_id as i64).into()), value.clone()));
59        }
60        rmpv::Value::Map(entries)
61    }
62
63    pub fn encode_msgpack(&self) -> Result<Vec<u8>, LxmfError> {
64        rmp_serde::to_vec(&self.to_rmpv()).map_err(|err| LxmfError::Encode(err.to_string()))
65    }
66}
67
68#[cfg(test)]
69mod tests {
70    use super::{CommandEntry, WireFields};
71    use crate::constants::FIELD_COMMANDS;
72
73    #[test]
74    fn commands_encode_with_integer_field_ids() {
75        let mut fields = WireFields::new();
76        fields.set_commands(vec![
77            CommandEntry::from_text(0x01, "ping"),
78            CommandEntry::from_bytes(0x02, vec![0xAA, 0xBB]),
79        ]);
80
81        let rmpv::Value::Map(entries) = fields.to_rmpv() else { panic!("expected map") };
82        assert_eq!(entries.len(), 1);
83        assert_eq!(entries[0].0.as_i64(), Some(FIELD_COMMANDS as i64));
84        let Some(cmds) = entries[0].1.as_array().cloned() else {
85            panic!("commands array expected")
86        };
87        assert_eq!(cmds.len(), 2);
88    }
89}