Skip to main content

llsd_rs/
binary.rs

1use std::io::{Read, Write};
2
3use chrono::{DateTime, Utc};
4use uuid::Uuid;
5
6use crate::{Llsd, Uri};
7
8fn write_inner<W: Write>(llsd: &Llsd, w: &mut W) -> Result<(), anyhow::Error> {
9    match llsd {
10        Llsd::Undefined => w.write_all(b"!")?,
11        Llsd::Boolean(v) => w.write_all(if *v { b"1" } else { b"0" })?,
12        Llsd::Integer(v) => {
13            w.write_all(b"i")?;
14            w.write_all(&v.to_be_bytes())?;
15        }
16        Llsd::Real(v) => {
17            w.write_all(b"r")?;
18            w.write_all(&v.to_be_bytes())?;
19        }
20        Llsd::String(v) => {
21            w.write_all(b"s")?;
22            w.write_all(&(v.len() as u32).to_be_bytes())?;
23            w.write_all(v.as_bytes())?;
24        }
25        Llsd::Uri(v) => {
26            w.write_all(b"l")?;
27            let v = v.as_str();
28            w.write_all(&(v.len() as u32).to_be_bytes())?;
29            w.write_all(v.as_bytes())?;
30        }
31        Llsd::Uuid(v) => {
32            w.write_all(b"u")?;
33            w.write_all((*v).as_bytes())?;
34        }
35        Llsd::Date(v) => {
36            w.write_all(b"d")?;
37            let real: f64 =
38                v.timestamp() as f64 + (v.timestamp_subsec_nanos() as f64 / 1_000_000_000.0);
39            // Use little endian
40            w.write_all(&real.to_le_bytes())?;
41        }
42        Llsd::Binary(v) => {
43            w.write_all(b"b")?;
44            w.write_all(&(v.len() as u32).to_be_bytes())?;
45            w.write_all(v)?;
46        }
47        Llsd::Array(v) => {
48            w.write_all(b"[")?;
49            w.write_all(&(v.len() as u32).to_be_bytes())?;
50            for e in v {
51                write_inner(e, w)?;
52            }
53            w.write_all(b"]")?;
54        }
55        Llsd::Map(v) => {
56            w.write_all(b"{")?;
57            w.write_all(&(v.len() as u32).to_be_bytes())?;
58            for (k, e) in v {
59                w.write_all(b"k")?;
60                w.write_all(&(k.len() as u32).to_be_bytes())?;
61                w.write_all(k.as_bytes())?;
62                write_inner(e, w)?;
63            }
64            w.write_all(b"}")?;
65        }
66    }
67    Ok(())
68}
69
70pub fn write<W: Write>(llsd: &Llsd, w: &mut W) -> Result<(), anyhow::Error> {
71    write_inner(llsd, w)
72}
73
74pub fn to_vec(llsd: &Llsd) -> Result<Vec<u8>, anyhow::Error> {
75    let mut buf = Vec::new();
76    write(llsd, &mut buf)?;
77    Ok(buf)
78}
79
80macro_rules! read_be_fn {
81    ($func_name:ident, $type:ty) => {
82        fn $func_name<R: Read>(reader: &mut R) -> Result<$type, anyhow::Error> {
83            let mut buf = [0_u8; std::mem::size_of::<$type>()];
84            reader.read_exact(&mut buf)?;
85            Ok(<$type>::from_be_bytes(buf))
86        }
87    };
88}
89
90read_be_fn!(read_u8, u8);
91read_be_fn!(read_i32_be, i32);
92read_be_fn!(read_f64_be, f64);
93
94fn hex<R: Read>(r: &mut R) -> Result<u8, anyhow::Error> {
95    let c = read_u8(r)?;
96    match c {
97        b'0'..=b'9' => Ok(c - b'0'),
98        b'a'..=b'f' => Ok(c - b'a' + 10),
99        b'A'..=b'F' => Ok(c - b'A' + 10),
100        _ => Ok(0),
101    }
102}
103
104fn unescape<R: Read>(r: &mut R, delim: u8) -> Result<String, anyhow::Error> {
105    let mut buf = Vec::new();
106    loop {
107        match read_u8(r)? {
108            c if c == delim => break,
109            b'\\' => match read_u8(r)? {
110                b'a' => buf.push(0x07),
111                b'b' => buf.push(0x08),
112                b'f' => buf.push(0x0c),
113                b'n' => buf.push(b'\n'),
114                b'r' => buf.push(b'\r'),
115                b't' => buf.push(b'\t'),
116                b'v' => buf.push(0x0b),
117                b'\\' => buf.push(b'\\'),
118                b'\'' => buf.push(b'\''),
119                b'"' => buf.push(b'"'),
120                b'x' => buf.push((hex(r)? << 4) | hex(r)?),
121                other => buf.push(other),
122            },
123            other => buf.push(other),
124        }
125    }
126    Ok(String::from_utf8(buf)?)
127}
128
129fn from_reader_inner_with_tag<R: Read>(r: &mut R, tag: u8) -> Result<Llsd, anyhow::Error> {
130    match tag {
131        b'!' => Ok(Llsd::Undefined),
132        b'1' => Ok(Llsd::Boolean(true)),
133        b'0' => Ok(Llsd::Boolean(false)),
134        b'i' => Ok(Llsd::Integer(read_i32_be(r)?)),
135        b'r' => Ok(Llsd::Real(read_f64_be(r)?)),
136        b's' => {
137            let len = read_i32_be(r)? as usize;
138            let mut buf = vec![0; len];
139            r.read_exact(&mut buf)?;
140            Ok(Llsd::String(String::from_utf8(buf)?))
141        }
142        b'l' => {
143            let len = read_i32_be(r)? as usize;
144            let mut buf = vec![0; len];
145            r.read_exact(&mut buf)?;
146            Ok(Llsd::Uri(Uri::parse(std::str::from_utf8(&buf)?)))
147        }
148        b'u' => {
149            let mut buf = [0_u8; 16];
150            r.read_exact(&mut buf)?;
151            Ok(Llsd::Uuid(Uuid::from_slice(&buf)?))
152        }
153        b'd' => {
154            let mut buf = [0_u8; 8];
155            r.read_exact(&mut buf)?;
156            // Use little endian
157            let real = f64::from_le_bytes(buf);
158            let date = DateTime::<Utc>::from_timestamp(
159                real.trunc() as i64,
160                (real.fract() * 1_000_000_000.0) as u32,
161            );
162            Ok(Llsd::Date(date.unwrap_or_default()))
163        }
164        b'b' => {
165            let len = read_i32_be(r)? as usize;
166            let mut buf = vec![0; len];
167            r.read_exact(&mut buf)?;
168            Ok(Llsd::Binary(buf))
169        }
170        b'[' => {
171            let len = read_i32_be(r)? as usize;
172            let mut buf = Vec::with_capacity(len);
173            for _ in 0..len {
174                buf.push(from_reader_inner(r)?);
175            }
176            if read_u8(r)? != b']' {
177                return Err(anyhow::anyhow!("Expected ']'"));
178            }
179            Ok(Llsd::Array(buf))
180        }
181        b'{' => {
182            let len = read_i32_be(r)? as usize;
183            let mut buf = std::collections::HashMap::with_capacity(len);
184            for _ in 0..len {
185                if read_u8(r)? != b'k' {
186                    return Err(anyhow::anyhow!("Expected 'k'"));
187                }
188                let key_len = read_i32_be(r)? as usize;
189                let mut key_buf = vec![0; key_len];
190                r.read_exact(&mut key_buf)?;
191                let key = String::from_utf8(key_buf)?;
192                let value = from_reader_inner(r)?;
193                buf.insert(key, value);
194            }
195            if read_u8(r)? != b'}' {
196                return Err(anyhow::anyhow!("Expected '}}'"));
197            }
198            Ok(Llsd::Map(buf))
199        }
200        b'"' => Ok(Llsd::String(unescape(r, b'"')?)),
201        b'\'' => Ok(Llsd::String(unescape(r, b'\'')?)),
202        other => Err(anyhow::anyhow!("Unknown LLSD type: {}", other)),
203    }
204}
205
206pub fn from_reader_inner<R: Read>(r: &mut R) -> Result<Llsd, anyhow::Error> {
207    let tag = read_u8(r)?;
208    from_reader_inner_with_tag(r, tag)
209}
210
211fn looks_like_llsd_binary_header(header: &[u8]) -> bool {
212    const NEEDLE: &[u8] = b"LLSD/Binary";
213    header
214        .windows(NEEDLE.len())
215        .any(|w| w.eq_ignore_ascii_case(NEEDLE))
216}
217
218pub fn from_reader<R: Read>(r: &mut R) -> Result<Llsd, anyhow::Error> {
219    let mut first = [0u8; 1];
220    r.read_exact(&mut first)?;
221    if first[0] != b'<' {
222        return from_reader_inner_with_tag(r, first[0]);
223    }
224
225    let mut header = vec![first[0]];
226    let mut buf = [0u8; 1];
227    let mut found_end = false;
228    for _ in 0..128 {
229        r.read_exact(&mut buf)?;
230        header.push(buf[0]);
231        if buf[0] == b'>' {
232            found_end = true;
233            break;
234        }
235    }
236
237    if !found_end || !looks_like_llsd_binary_header(&header) {
238        return Err(anyhow::anyhow!("Unexpected LLSD header"));
239    }
240
241    // consume optional whitespace after header, then parse next tag
242    loop {
243        let mut next = [0u8; 1];
244        match r.read(&mut next) {
245            Ok(0) => return Err(anyhow::anyhow!("Unexpected EOF after LLSD header")),
246            Ok(1) => {
247                if matches!(next[0], b' ' | b'\r' | b'\n' | b'\t') {
248                    continue;
249                }
250                return from_reader_inner_with_tag(r, next[0]);
251            }
252            Ok(_) => unreachable!(),
253            Err(err) => return Err(err.into()),
254        }
255    }
256}
257
258pub fn from_slice(data: &[u8]) -> Result<Llsd, anyhow::Error> {
259    from_reader(&mut std::io::Cursor::new(data))
260}
261
262#[cfg(test)]
263mod tests {
264    use super::*;
265    use chrono::{TimeZone, Utc};
266    use std::collections::HashMap;
267
268    fn round_trip(llsd: Llsd) {
269        let encoded = to_vec(&llsd).expect("Failed to encode");
270        let decoded = from_slice(&encoded).expect("Failed to decode");
271        assert_eq!(llsd, decoded);
272    }
273
274    #[test]
275    fn undefined() {
276        round_trip(Llsd::Undefined);
277    }
278
279    #[test]
280    fn boolean() {
281        round_trip(Llsd::Boolean(true));
282        round_trip(Llsd::Boolean(false));
283    }
284
285    #[test]
286    fn integer() {
287        round_trip(Llsd::Integer(42));
288    }
289
290    #[test]
291    fn real() {
292        round_trip(Llsd::Real(13.1415));
293    }
294
295    #[test]
296    fn string() {
297        round_trip(Llsd::String("Hello, LLSD!".to_owned()));
298    }
299
300    #[test]
301    fn uri() {
302        round_trip(Llsd::Uri(Uri::parse("https://example.com/")));
303    }
304
305    #[test]
306    fn uuid() {
307        let uuid = Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000").unwrap();
308        round_trip(Llsd::Uuid(uuid));
309    }
310
311    #[test]
312    fn date() {
313        let dt = Utc.timestamp_opt(1_620_000_000, 0).unwrap();
314        round_trip(Llsd::Date(dt));
315    }
316
317    #[test]
318    fn binary() {
319        round_trip(Llsd::Binary(vec![0xde, 0xad, 0xbe, 0xef]));
320    }
321
322    #[test]
323    fn array() {
324        let arr = vec![
325            Llsd::Integer(1),
326            Llsd::String("two".into()),
327            Llsd::Boolean(false),
328        ];
329        round_trip(Llsd::Array(arr));
330    }
331
332    #[test]
333    fn array_in_map_parses_closing_bracket() {
334        let mut map = HashMap::new();
335        map.insert(
336            "a".to_string(),
337            Llsd::Array(vec![Llsd::Integer(1), Llsd::Integer(2)]),
338        );
339        map.insert("b".to_string(), Llsd::String("ok".to_string()));
340
341        let encoded = to_vec(&Llsd::Map(map.clone())).expect("encode failed");
342        let decoded = from_slice(&encoded).expect("decode failed");
343        assert_eq!(decoded, Llsd::Map(map));
344    }
345
346    #[test]
347    fn binary_header_prefix_is_skipped() {
348        let value = Llsd::String("hello".to_string());
349        let mut encoded = b"<? LLSD/Binary ?>\n".to_vec();
350        encoded.extend(to_vec(&value).expect("encode failed"));
351
352        let decoded = from_slice(&encoded).expect("decode failed");
353        assert_eq!(decoded, value);
354    }
355
356    #[test]
357    fn binary_header_is_case_insensitive() {
358        let value = Llsd::String("hello".to_string());
359        let mut encoded = b"<? llsd/binary ?>\n".to_vec();
360        encoded.extend(to_vec(&value).expect("encode failed"));
361
362        let decoded = from_slice(&encoded).expect("decode failed");
363        assert_eq!(decoded, value);
364    }
365
366    #[test]
367    fn from_reader_preserves_trailing_bytes() {
368        let mut map = HashMap::new();
369        map.insert("answer".into(), Llsd::Integer(42));
370        let value = Llsd::Map(map);
371        let mut encoded = b"<? LLSD/Binary ?>\n".to_vec();
372        encoded.extend(to_vec(&value).expect("encode failed"));
373        encoded.extend(b"TAIL");
374
375        let mut cursor = std::io::Cursor::new(encoded);
376        let decoded = from_reader(&mut cursor).expect("decode failed");
377        assert_eq!(decoded, value);
378
379        let pos = cursor.position() as usize;
380        let buf = cursor.get_ref();
381        assert_eq!(&buf[pos..], b"TAIL");
382    }
383
384    #[test]
385    fn map() {
386        let mut map = HashMap::new();
387        map.insert("answer".into(), Llsd::Integer(42));
388        map.insert("pi".into(), Llsd::Real(13.14));
389        map.insert("greeting".into(), Llsd::String("hello".into()));
390        round_trip(Llsd::Map(map));
391    }
392}