Skip to main content

serde_llsd/ser/
notation.rs

1//! # ser/notation -- serialize LLSD, notation form.
2//!
3//!  Library for serializing and de-serializing data in
4//!  Linden Lab Structured Data format.
5//!
6//!  Format documentation is at http://wiki.secondlife.com/wiki/LLSD
7//!
8//!  Notation format, serialization.
9//
10//  Animats
11//  July, 2023.
12//  License: LGPL.
13//
14use crate::LLSDValue;
15use anyhow::Error;
16use chrono::{TimeZone};
17use base64::Engine;
18//
19//  Constants
20//
21/// Notation LLSD prefix
22pub const LLSDNOTATIONPREFIX: &str = "<? llsd/notation ?>\n"; 
23/// Sentinel, must match exactly.
24pub const LLSDNOTATIONSENTINEL: &str = LLSDNOTATIONPREFIX; 
25
26/// Outputs an LLSDValue as a string of bytes, in LLSD "notation" format.
27pub fn to_string(val: &LLSDValue) -> Result<String, Error> {
28    let mut writer = String::new();
29    writer.push_str(LLSDNOTATIONPREFIX); // prefix
30    generate_value(&mut writer, val)?;
31    Ok(writer)
32}
33
34//  There could be a corresponding function to generate LLSD notation as bytes,
35//  but that creates transparency problems best avoided.
36
37/*
38/// Outputs an LLSD value to an output stream
39pub fn to_writer<W: Write>(writer: &mut W, val: &LLSDValue) -> Result<(), Error> {
40    writer.write_all(LLSDNOTATIONPREFIX)?; // prefix
41    generate_value(writer, val)?;
42    writer.flush()?;
43    Ok(())
44}
45*/
46/// Generate one <TYPE> VALUE </TYPE> output. VALUE is recursive.
47fn generate_value(writer: &mut String, val: &LLSDValue) -> Result<(), Error> {
48    //  Emit notation form for all possible types.
49    match val {
50        LLSDValue::Undefined => writer.push('!'),
51        LLSDValue::Boolean(v) => writer.push(if *v { 'T' } else { 'F' }),
52        LLSDValue::String(v) => {
53            writer.push('"');
54            writer.push_str(&escape_quotes(v));
55            writer.push('"');
56        }
57        LLSDValue::URI(v) => {
58            writer.push('l');
59            writer.push('"');
60            writer.push_str(&escape_url(v));
61            writer.push('"');
62        }
63        LLSDValue::Integer(v) => {
64            writer.push('i');
65            writer.push_str(&format!("{}",v));
66        }
67        LLSDValue::Real(v) => {
68            writer.push('r');
69            writer.push_str(&format!("{}",v));
70        }
71        LLSDValue::UUID(v) => {
72            writer.push('u');
73            writer.push_str(&v.to_string());
74        }
75        LLSDValue::Binary(v) => {
76            writer.push('b');
77            writer.push('6');
78            writer.push('4');
79            writer.push('"');
80            writer.push_str(&base64::engine::general_purpose::STANDARD.encode(v));
81            writer.push('"');
82        }
83        LLSDValue::Date(v) => {
84            writer.push('d');
85            writer.push_str(&chrono::Utc
86                .timestamp_opt(*v, 0)
87                .earliest()
88                .unwrap() // may panic for times prior to January 1, 1970.
89                .to_rfc3339_opts(chrono::SecondsFormat::Secs, true))
90        }
91
92        //  Map is {  key : value, key : value ... }
93        LLSDValue::Map(v) => {
94            //  Curly bracketed list
95            writer.push('{');
96            //  Output key/value pairs
97            let mut first: bool = true;
98            for (key, value) in v {
99                if !first {
100                    writer.push(',');
101                    writer.push('\n');
102                }
103                first = false;
104                writer.push('\'');
105                writer.push_str(key);
106                writer.push('\'');
107                writer.push(':');
108                generate_value(writer, value)?;
109            }
110            writer.push('}');
111        }
112        //  Array is [ child, child ... ]
113        LLSDValue::Array(v) => {
114            //  Square bracketed list
115            writer.push('[');
116            //  Output array entries
117            let mut first: bool = true;
118            for value in v {
119                if !first {
120                    writer.push(',');
121                    writer.push('\n');
122                }
123                first = false;
124                generate_value(writer, value)?;           
125            }
126            writer.push(']');
127        }
128    };
129    Ok(())
130}
131
132/// Escape double quote as \", and of course \ as \\.
133fn escape_quotes(s: &str) -> String {
134    let mut writer = String::new();
135    for ch in s.chars() {
136        match ch {
137            '"' | '\\' => { writer.push('\\'); writer.push(ch) }
138            _ => writer.push(ch)
139        }
140    }     
141    writer
142}
143
144/// Escape URL per RFC1738
145fn escape_url(s: &str) -> String {
146    urlencoding::encode(s).to_string()
147}
148
149//  Limited test case. Better tests on decode side.
150#[test]
151fn notationgentest1() {
152    const TESTXMLNAN: &str = r#"
153<?xml version="1.0" encoding="UTF-8"?>
154<llsd>
155<array>
156<real>nan</real>
157<real>0</real>
158<undef />
159</array>
160</llsd>
161"#;
162
163    const TESTXML1: &str = r#"
164<?xml version="1.0" encoding="UTF-8"?>
165<llsd>
166<map>
167  <key>region_id</key>
168    <uuid>67153d5b-3659-afb4-8510-adda2c034649</uuid>
169  <key>scale</key>
170    <string>one minute</string>
171  <key>simulator statistics</key>
172  <map>
173    <key>time dilation</key><real>0.9878624</real>
174    <key>sim fps</key><real>44.38898</real>
175    <key>pysics fps</key><real>44.38906</real>
176    <key>lsl instructions per second</key><real>0</real>
177    <key>total task count</key><real>4</real>
178    <key>active task count</key><real>0</real>
179    <key>active script count</key><real>4</real>
180    <key>main agent count</key><real>0</real>
181    <key>child agent count</key><real>0</real>
182    <key>inbound packets per second</key><real>1.228283</real>
183    <key>outbound packets per second</key><real>1.277508</real>
184    <key>pending downloads</key><real>0</real>
185    <key>pending uploads</key><real>0.0001096525</real>
186    <key>frame ms</key><real>0.7757886</real>
187    <key>net ms</key><real>0.3152919</real>
188    <key>sim other ms</key><real>0.1826937</real>
189    <key>sim physics ms</key><real>0.04323055</real>
190    <key>agent ms</key><real>0.01599029</real>
191    <key>image ms</key><real>0.01865955</real>
192    <key>script ms</key><real>0.1338836</real>
193    <!-- Comment - some additional test values -->
194    <key>hex number</key><binary encoding="base16">0fa1</binary>
195    <key>base64 number</key><binary>SGVsbG8gd29ybGQ=</binary>
196    <key>date</key><date>2006-02-01T14:29:53Z</date>
197    <key>array</key>
198        <array>
199            <boolean>false</boolean>
200            <integer>42</integer>
201            <undef/>
202            <uuid/>
203            <boolean>1</boolean>
204        </array>
205  </map>
206</map>
207</llsd>
208"#;
209
210    fn trytestcase(teststr: &str) {
211        //  Internal utility function.
212        //  Parse canned XML test case into internal format.
213        //  Must not contain NaN, because NaN != Nan and the equal test will fail
214        let parsed1 = crate::de::xml::from_str(teststr).unwrap();
215        println!("Parse of {}: \n{:#?}", teststr, parsed1);
216        //  Generate Notation back from parsed version.
217        let generated = crate::ser::notation::to_string(&parsed1).unwrap();
218        println!("Generated Notation format:\n{}", generated);
219        /*
220        let generated = crate::ser::xml::to_string(&parsed1, true).unwrap();
221        //  Parse that.
222        let parsed2 = from_str(&generated).unwrap();
223        //  Check that parses match.
224        assert_eq!(parsed1, parsed2);
225        */
226    }
227    trytestcase(TESTXML1);
228    //  Test NAN case
229    {
230        let parsed1 =  crate::de::xml::from_str(TESTXMLNAN).unwrap();
231        println!("Parse of {}: \n{:#?}", TESTXMLNAN, parsed1);
232        //  Generate XML back from parsed version.
233        let generated = crate::ser::notation::to_string(&parsed1).unwrap();
234        println!("Generated Notation format:\n{}", generated);
235    }
236}