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 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 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 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}