rustici/
wire.rs

1//! Message codec (hierarchical sections, lists, key/value pairs).
2//!
3//! Encoding summary (as specified by the VICI protocol):
4//! - Element types are 8-bit values:
5//!   - 1: SECTION_START (has a name)
6//!   - 2: SECTION_END
7//!   - 3: KEY_VALUE (has name + value)
8//!   - 4: LIST_START (has a name)
9//!   - 5: LIST_ITEM (has a value)
10//!   - 6: LIST_END
11//! - Names are ASCII strings preceded by an 8-bit length (not NUL-terminated).
12//! - Values are raw blobs preceded by a 16-bit big-endian length.
13//!
14//! Transport framing (outside of this module) wraps the packet with a 32-bit
15//! big-endian length field, followed by: packet type (u8), optional name, and
16//! the encoded message bytes.
17
18use crate::error::{Error, Result};
19use std::fmt;
20
21/// A single message element.
22#[derive(Debug, Clone, PartialEq, Eq)]
23pub enum Element {
24    /// Begin a named section.
25    SectionStart(String),
26    /// End the most recently opened section.
27    SectionEnd,
28    /// A key/value pair.
29    KeyValue(String, Vec<u8>),
30    /// Begin a named list.
31    ListStart(String),
32    /// A list item value.
33    ListItem(Vec<u8>),
34    /// End the most recently opened list.
35    ListEnd,
36}
37
38impl Element {
39    fn encode_into(&self, out: &mut Vec<u8>) -> Result<()> {
40        match self {
41            Element::SectionStart(name) => {
42                out.push(1);
43                encode_name(out, name)?;
44            }
45            Element::SectionEnd => out.push(2),
46            Element::KeyValue(name, val) => {
47                out.push(3);
48                encode_name(out, name)?;
49                encode_value(out, val)?;
50            }
51            Element::ListStart(name) => {
52                out.push(4);
53                encode_name(out, name)?;
54            }
55            Element::ListItem(val) => {
56                out.push(5);
57                encode_value(out, val)?;
58            }
59            Element::ListEnd => out.push(6),
60        }
61        Ok(())
62    }
63}
64
65/// A full message consisting of a flat sequence of elements.
66/// The sequence must be *balanced* with regards to sections and lists.
67#[derive(Debug, Clone, PartialEq, Eq, Default)]
68pub struct Message {
69    elements: Vec<Element>,
70}
71
72impl Message {
73    /// Create an empty message.
74    pub fn new() -> Self {
75        Self {
76            elements: Vec::new(),
77        }
78    }
79
80    /// Borrow inner elements.
81    pub fn elements(&self) -> &[Element] {
82        &self.elements
83    }
84
85    /// Push a raw element.
86    pub fn push(&mut self, el: Element) {
87        self.elements.push(el);
88    }
89
90    /// Convenience: add key/value where the value is a string.
91    pub fn kv_str(mut self, name: impl Into<String>, value: impl AsRef<str>) -> Self {
92        self.elements.push(Element::KeyValue(
93            name.into(),
94            value.as_ref().as_bytes().to_vec(),
95        ));
96        self
97    }
98
99    /// Convenience: add key/value where the value is raw bytes.
100    pub fn kv_bytes(mut self, name: impl Into<String>, value: impl AsRef<[u8]>) -> Self {
101        self.elements
102            .push(Element::KeyValue(name.into(), value.as_ref().to_vec()));
103        self
104    }
105
106    /// Begin a section.
107    pub fn section_start(mut self, name: impl Into<String>) -> Self {
108        self.elements.push(Element::SectionStart(name.into()));
109        self
110    }
111
112    /// End a section.
113    pub fn section_end(mut self) -> Self {
114        self.elements.push(Element::SectionEnd);
115        self
116    }
117
118    /// Begin a list.
119    pub fn list_start(mut self, name: impl Into<String>) -> Self {
120        self.elements.push(Element::ListStart(name.into()));
121        self
122    }
123
124    /// Add a list item (string value convenience).
125    pub fn list_item_str(mut self, value: impl AsRef<str>) -> Self {
126        self.elements
127            .push(Element::ListItem(value.as_ref().as_bytes().to_vec()));
128        self
129    }
130
131    /// Add a list item (raw bytes).
132    pub fn list_item_bytes(mut self, value: impl AsRef<[u8]>) -> Self {
133        self.elements
134            .push(Element::ListItem(value.as_ref().to_vec()));
135        self
136    }
137
138    /// End a list.
139    pub fn list_end(mut self) -> Self {
140        self.elements.push(Element::ListEnd);
141        self
142    }
143
144    /// Encode this message into bytes.
145    pub fn encode(&self) -> Result<Vec<u8>> {
146        let mut out = Vec::with_capacity(self.elements.len() * 8);
147        for el in &self.elements {
148            el.encode_into(&mut out)?;
149        }
150        Ok(out)
151    }
152
153    /// Decode a message from bytes.
154    pub fn decode(mut bytes: &[u8]) -> Result<Self> {
155        let mut elements = Vec::new();
156        while !bytes.is_empty() {
157            let (el, rest) = decode_element(bytes)?;
158            elements.push(el);
159            bytes = rest;
160        }
161        Ok(Self { elements })
162    }
163}
164
165fn encode_name(out: &mut Vec<u8>, name: &str) -> Result<()> {
166    let bytes = name.as_bytes();
167    if bytes.len() > u8::MAX as usize {
168        return Err(Error::TooLong("element name"));
169    }
170    out.push(bytes.len() as u8);
171    out.extend_from_slice(bytes);
172    Ok(())
173}
174
175fn encode_value(out: &mut Vec<u8>, value: &[u8]) -> Result<()> {
176    if value.len() > u16::MAX as usize {
177        return Err(Error::TooLong("element value"));
178    }
179    out.extend_from_slice(&(value.len() as u16).to_be_bytes());
180    out.extend_from_slice(value);
181    Ok(())
182}
183
184fn decode_u8(input: &[u8]) -> Result<(u8, &[u8])> {
185    if input.is_empty() {
186        return Err(Error::Protocol("unexpected EOF reading u8"));
187    }
188    Ok((input[0], &input[1..]))
189}
190
191fn decode_be_u16(input: &[u8]) -> Result<(u16, &[u8])> {
192    if input.len() < 2 {
193        return Err(Error::Protocol("unexpected EOF reading u16"));
194    }
195    let v = u16::from_be_bytes([input[0], input[1]]);
196    Ok((v, &input[2..]))
197}
198
199fn take(input: &[u8], n: usize) -> Result<(&[u8], &[u8])> {
200    if input.len() < n {
201        return Err(Error::Protocol("unexpected EOF taking slice"));
202    }
203    Ok((&input[..n], &input[n..]))
204}
205
206fn decode_name(input: &[u8]) -> Result<(String, &[u8])> {
207    let (len, input) = decode_u8(input)?;
208    let (name_bytes, rest) = take(input, len as usize)?;
209    Ok((String::from_utf8(name_bytes.to_vec())?, rest))
210}
211
212fn decode_value(input: &[u8]) -> Result<(Vec<u8>, &[u8])> {
213    let (len, input) = decode_be_u16(input)?;
214    let (value, rest) = take(input, len as usize)?;
215    Ok((value.to_vec(), rest))
216}
217
218fn decode_element(input: &[u8]) -> Result<(Element, &[u8])> {
219    let (tag, input) = decode_u8(input)?;
220    match tag {
221        1 => {
222            // SECTION_START
223            let (name, rest) = decode_name(input)?;
224            Ok((Element::SectionStart(name), rest))
225        }
226        2 => Ok((Element::SectionEnd, input)),
227        3 => {
228            let (name, input) = decode_name(input)?;
229            let (value, rest) = decode_value(input)?;
230            Ok((Element::KeyValue(name, value), rest))
231        }
232        4 => {
233            let (name, rest) = decode_name(input)?;
234            Ok((Element::ListStart(name), rest))
235        }
236        5 => {
237            let (value, rest) = decode_value(input)?;
238            Ok((Element::ListItem(value), rest))
239        }
240        6 => Ok((Element::ListEnd, input)),
241        _ => Err(Error::Protocol("unknown message element tag")),
242    }
243}
244
245impl fmt::Display for Message {
246    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
247        for el in &self.elements {
248            match el {
249                Element::SectionStart(n) => writeln!(f, "<section {n}>")?,
250                Element::SectionEnd => writeln!(f, "</section>")?,
251                Element::KeyValue(k, v) => match String::from_utf8(v.clone()) {
252                    Ok(s) => writeln!(f, "{k} = {s}")?,
253                    Err(_) => writeln!(f, "{k} = 0x{}", hex(v))?,
254                },
255                Element::ListStart(n) => writeln!(f, "<list {n}>")?,
256                Element::ListItem(v) => match String::from_utf8(v.clone()) {
257                    Ok(s) => writeln!(f, "- {s}")?,
258                    Err(_) => writeln!(f, "- 0x{}", hex(v))?,
259                },
260                Element::ListEnd => writeln!(f, "</list>")?,
261            }
262        }
263        Ok(())
264    }
265}
266
267fn hex(bytes: &[u8]) -> String {
268    const HEX: &[u8; 16] = b"0123456789abcdef";
269    let mut s = String::with_capacity(bytes.len() * 2);
270    for &b in bytes {
271        s.push(HEX[(b >> 4) as usize] as char);
272        s.push(HEX[(b & 0x0F) as usize] as char);
273    }
274    s
275}
276
277#[cfg(test)]
278mod tests {
279    use super::*;
280
281    #[test]
282    fn roundtrip_simple_message() {
283        let msg = Message::new()
284            .section_start("root")
285            .kv_str("key", "value")
286            .list_start("ids")
287            .list_item_str("a")
288            .list_item_str("b")
289            .list_end()
290            .section_end();
291
292        let encoded = msg.encode().unwrap();
293        let decoded = Message::decode(&encoded).unwrap();
294        assert_eq!(msg, decoded);
295    }
296}