Skip to main content

llsd_rs/
rpc.rs

1use base64::prelude::*;
2use chrono::DateTime;
3use xml::{EventReader, EventWriter};
4
5use super::Llsd;
6
7#[derive(Debug, Clone, PartialEq)]
8pub enum XmlRpc {
9    MethodCall(String, Llsd),
10    MethodResponse(Llsd),
11}
12
13impl XmlRpc {
14    pub fn new_method_call(method: String, llsd: Llsd) -> Self {
15        XmlRpc::MethodCall(method, llsd)
16    }
17
18    pub fn new_method_response(llsd: Llsd) -> Self {
19        XmlRpc::MethodResponse(llsd)
20    }
21
22    pub fn method(&self) -> Option<&str> {
23        match self {
24            XmlRpc::MethodCall(method, _) => Some(method),
25            XmlRpc::MethodResponse(_) => None,
26        }
27    }
28
29    pub fn llsd(&self) -> &Llsd {
30        match self {
31            XmlRpc::MethodCall(_, llsd) => llsd,
32            XmlRpc::MethodResponse(llsd) => llsd,
33        }
34    }
35}
36
37impl AsRef<Llsd> for XmlRpc {
38    fn as_ref(&self) -> &Llsd {
39        self.llsd()
40    }
41}
42
43impl AsMut<Llsd> for XmlRpc {
44    fn as_mut(&mut self) -> &mut Llsd {
45        match self {
46            XmlRpc::MethodCall(_, llsd) => llsd,
47            XmlRpc::MethodResponse(llsd) => llsd,
48        }
49    }
50}
51
52impl From<XmlRpc> for Llsd {
53    fn from(rpc: XmlRpc) -> Self {
54        match rpc {
55            XmlRpc::MethodCall(_, llsd) => llsd,
56            XmlRpc::MethodResponse(llsd) => llsd,
57        }
58    }
59}
60
61impl From<Llsd> for XmlRpc {
62    fn from(llsd: Llsd) -> Self {
63        XmlRpc::MethodResponse(llsd)
64    }
65}
66
67impl From<(String, Llsd)> for XmlRpc {
68    fn from((method, llsd): (String, Llsd)) -> Self {
69        XmlRpc::MethodCall(method, llsd)
70    }
71}
72
73#[derive(Debug, Clone, Copy, PartialEq, Eq)]
74enum Expected {
75    None,
76    Data,
77    Member,
78    Name,
79    Value,
80    XmlRpcHeader,
81    MethodCallName,
82    Parmas,
83    Param,
84}
85
86#[inline]
87#[cfg(feature = "opensim")]
88fn is_xmlrpc_int_tag(tag: &str) -> bool {
89    matches!(tag, "int" | "i4" | "i")
90}
91
92#[inline]
93#[cfg(not(feature = "opensim"))]
94fn is_xmlrpc_int_tag(tag: &str) -> bool {
95    matches!(tag, "int")
96}
97
98pub fn from_parser<R: std::io::Read>(parser: EventReader<R>) -> Result<XmlRpc, anyhow::Error> {
99    use xml::reader::XmlEvent;
100    let mut stack: Vec<Llsd> = Vec::new();
101    let mut name_stack: Vec<String> = Vec::new();
102    let mut key_stack: Vec<String> = Vec::new();
103
104    let mut expect_value = Expected::XmlRpcHeader;
105    let mut method = None;
106
107    for event in parser {
108        match event {
109            Ok(XmlEvent::StartElement { name, .. }) => {
110                name_stack.push(name.local_name.clone());
111                match (expect_value, name.local_name.as_str()) {
112                    (Expected::Data, "data") => expect_value = Expected::Value,
113                    (Expected::Member, "member") => expect_value = Expected::Name,
114                    (Expected::Name, "name") => expect_value = Expected::Value,
115                    (Expected::Value, "value") => expect_value = Expected::None,
116                    (Expected::XmlRpcHeader, "methodResponse") => expect_value = Expected::Parmas,
117                    (Expected::XmlRpcHeader, "methodCall") => {
118                        expect_value = Expected::MethodCallName
119                    }
120                    (Expected::MethodCallName, "methodName") => expect_value = Expected::Parmas,
121                    (Expected::Parmas, "params") => expect_value = Expected::Param,
122                    (Expected::Param, "param") => expect_value = Expected::Value,
123                    (Expected::None, "nil") => stack.push(Llsd::Undefined),
124                    (Expected::None, "boolean") => stack.push(Llsd::Boolean(false)),
125                    (Expected::None, "string") => stack.push(Llsd::String(String::new())),
126                    (Expected::None, tag) if is_xmlrpc_int_tag(tag) => stack.push(Llsd::Integer(0)),
127                    (Expected::None, "double") => stack.push(Llsd::Real(0.0)),
128                    (Expected::None, "dateTime.iso8601") => {
129                        stack.push(Llsd::Date(Default::default()))
130                    }
131                    (Expected::None, "base64") => stack.push(Llsd::Binary(Vec::new())),
132                    (Expected::None, "array") => {
133                        stack.push(Llsd::Array(Vec::new()));
134                        expect_value = Expected::Data;
135                    }
136                    (Expected::None, "struct") => {
137                        stack.push(Llsd::Map(Default::default()));
138                        expect_value = Expected::Member;
139                    }
140                    _ => {
141                        return Err(anyhow::anyhow!(
142                            "Error parsing XML-RPC: unexpected element {}",
143                            name.local_name
144                        ));
145                    }
146                }
147            }
148            Ok(XmlEvent::Characters(data)) => {
149                let data = data.trim();
150                if expect_value == Expected::MethodCallName {
151                    method = Some(data.to_string());
152                } else if name_stack.last().map(|s| s.as_str()) == Some("name") {
153                    key_stack.push(data.to_string());
154                } else if let Some(llsd) = stack.last_mut() {
155                    match llsd {
156                        Llsd::Boolean(_) => match data {
157                            "true" => *llsd = Llsd::Boolean(true),
158                            "false" => *llsd = Llsd::Boolean(false),
159                            "1" => *llsd = Llsd::Boolean(true),
160                            "0" => *llsd = Llsd::Boolean(false),
161                            _ => {
162                                return Err(anyhow::anyhow!(
163                                    "Error parsing XML-RPC: expected boolean, got {}",
164                                    data
165                                ));
166                            }
167                        },
168                        &mut Llsd::String(ref mut s) => s.push_str(data),
169                        &mut Llsd::Date(ref mut d) => {
170                            *d = DateTime::parse_from_rfc3339(data)?.into()
171                        }
172                        &mut Llsd::Binary(ref mut b) => {
173                            *b = BASE64_STANDARD.decode(data.as_bytes())?
174                        }
175                        &mut Llsd::Integer(ref mut i) => *i = data.parse()?,
176                        &mut Llsd::Real(ref mut r) => match data {
177                            "nan" => *r = f64::NAN,
178                            "inf" => *r = f64::INFINITY,
179                            "-inf" => *r = f64::NEG_INFINITY,
180                            _ => *r = data.parse()?,
181                        },
182                        _ => {
183                            return Err(anyhow::anyhow!(
184                                "Error parsing XML-RPC: unexpected characters {}",
185                                data
186                            ));
187                        }
188                    }
189                }
190            }
191            Ok(XmlEvent::EndElement { name }) => {
192                if name_stack.pop().as_ref() != Some(&name.local_name) {
193                    return Err(anyhow::anyhow!(
194                        "Error parsing LLSD: unexpected end element {}",
195                        name.local_name
196                    ));
197                }
198                match name.local_name.as_str() {
199                    "struct" | "array" if stack.len() > 1 => {
200                        if let Some(parent) = stack.get(stack.len() - 2) {
201                            if parent.is_array() {
202                                expect_value = Expected::Value;
203                            } else if parent.is_map() {
204                                expect_value = Expected::Member;
205                            } else {
206                                return Err(anyhow::anyhow!(
207                                    "Error parsing XML-RPC: not a map or array"
208                                ));
209                            }
210                        }
211                    }
212                    "member" => {
213                        let Some(key) = key_stack.pop() else {
214                            return Err(anyhow::anyhow!("Error parsing XML-RPC: missing key"));
215                        };
216                        let Some(value) = stack.pop() else {
217                            return Err(anyhow::anyhow!(
218                                "Error parsing XML-RPC: unexpected end element {}",
219                                name.local_name
220                            ));
221                        };
222                        let Some(Llsd::Map(parent)) = stack.last_mut() else {
223                            return Err(anyhow::anyhow!("Error parsing XML-RPC: not a map"));
224                        };
225                        parent.insert(key.to_string(), value);
226                        expect_value = Expected::Member;
227                    }
228                    "value" if stack.len() > 1 => {
229                        let Some(value) = stack.pop() else {
230                            return Err(anyhow::anyhow!(
231                                "Error parsing XML-RPC: unexpected end element {}",
232                                name.local_name
233                            ));
234                        };
235                        if let Some(Llsd::Array(parent)) = stack.last_mut() {
236                            parent.push(value);
237                            expect_value = Expected::Value;
238                        } else {
239                            stack.push(value);
240                        }
241                    }
242                    _ => {}
243                };
244            }
245            Err(e) => return Err(anyhow::anyhow!("Error parsing XML-RPC: {}", e)),
246            _ => {}
247        }
248    }
249    if let Some(llsd) = stack.pop() {
250        if !stack.is_empty() {
251            return Err(anyhow::anyhow!(
252                "Error parsing XML-RPC: expected 1 value, got {}",
253                stack.len() + 1
254            ));
255        }
256        if let Some(method) = method {
257            Ok(XmlRpc::MethodCall(method, llsd))
258        } else {
259            Ok(XmlRpc::MethodResponse(llsd))
260        }
261    } else {
262        Err(anyhow::anyhow!("Error parsing XML-RPC: missing key"))
263    }
264}
265
266pub fn from_str(data: &str) -> Result<XmlRpc, anyhow::Error> {
267    from_parser(EventReader::from_str(data))
268}
269
270pub fn from_reader<R: std::io::Read>(reader: R) -> Result<XmlRpc, anyhow::Error> {
271    from_parser(EventReader::new(reader))
272}
273
274pub fn from_slice(data: &[u8]) -> Result<XmlRpc, anyhow::Error> {
275    from_parser(EventReader::new(std::io::Cursor::new(data)))
276}
277
278fn write_inner<W: std::io::Write>(
279    llsd: &Llsd,
280    w: &mut EventWriter<W>,
281) -> Result<(), anyhow::Error> {
282    use xml::writer::XmlEvent;
283    let tag = |w: &mut EventWriter<W>, tag, text: &str| -> Result<(), anyhow::Error> {
284        w.write(XmlEvent::start_element(tag))?;
285        if !text.is_empty() {
286            w.write(XmlEvent::characters(text))?;
287        }
288        w.write(XmlEvent::end_element())?;
289        Ok(())
290    };
291    match llsd {
292        Llsd::Undefined => tag(w, "nil", ""),
293        Llsd::Boolean(b) => tag(w, "boolean", if *b { "1" } else { "0" }),
294        Llsd::Integer(i) => tag(w, "int", &i.to_string()),
295        Llsd::Real(r) => tag(w, "double", &r.to_string()),
296        Llsd::String(s) => tag(w, "string", s),
297        Llsd::Uri(u) => tag(w, "string", u.as_str()),
298        Llsd::Uuid(u) => tag(w, "string", &u.to_string()),
299        Llsd::Date(d) => tag(w, "dateTime.iso8601", &d.to_rfc3339()),
300        Llsd::Binary(b) => tag(w, "base64", &BASE64_STANDARD.encode(b)),
301        Llsd::Array(a) => {
302            w.write(XmlEvent::start_element("array"))?;
303            w.write(XmlEvent::start_element("data"))?;
304            for llsd in a {
305                w.write(XmlEvent::start_element("value"))?;
306                write_inner(llsd, w)?;
307                w.write(XmlEvent::end_element())?;
308            }
309            w.write(XmlEvent::end_element())?;
310            w.write(XmlEvent::end_element())?;
311            Ok(())
312        }
313        Llsd::Map(m) => {
314            w.write(XmlEvent::start_element("struct"))?;
315            for (k, v) in m {
316                w.write(XmlEvent::start_element("member"))?;
317                tag(w, "name", k)?;
318                w.write(XmlEvent::start_element("value"))?;
319                write_inner(v, w)?;
320                w.write(XmlEvent::end_element())?;
321                w.write(XmlEvent::end_element())?;
322            }
323            w.write(XmlEvent::end_element())?;
324            Ok(())
325        }
326    }
327}
328
329pub fn write<W: std::io::Write>(rpc: &XmlRpc, w: &mut EventWriter<W>) -> Result<(), anyhow::Error> {
330    use xml::writer::XmlEvent;
331    match rpc {
332        XmlRpc::MethodCall(method, _) => {
333            w.write(XmlEvent::start_element("methodCall"))?;
334            w.write(XmlEvent::start_element("methodName"))?;
335            w.write(XmlEvent::characters(method))?;
336            w.write(XmlEvent::end_element())?;
337        }
338        XmlRpc::MethodResponse(_) => {
339            w.write(XmlEvent::start_element("methodResponse"))?;
340        }
341    }
342    w.write(XmlEvent::start_element("params"))?;
343    w.write(XmlEvent::start_element("param"))?;
344    w.write(XmlEvent::start_element("value"))?;
345    write_inner(rpc.as_ref(), w)?;
346    w.write(XmlEvent::end_element())?;
347    w.write(XmlEvent::end_element())?;
348    w.write(XmlEvent::end_element())?;
349    w.write(XmlEvent::end_element())?;
350    Ok(())
351}
352
353pub fn to_string(rpc: &XmlRpc) -> Result<String, anyhow::Error> {
354    let mut buf = Vec::new();
355    write(rpc, &mut EventWriter::new(&mut buf))?;
356    Ok(String::from_utf8(buf)?)
357}
358
359pub fn to_pretty_string(rpc: &XmlRpc) -> Result<String, anyhow::Error> {
360    let mut buf = Vec::new();
361    write(
362        rpc,
363        &mut EventWriter::new_with_config(
364            &mut buf,
365            xml::writer::EmitterConfig::new().perform_indent(true),
366        ),
367    )?;
368    Ok(String::from_utf8(buf)?)
369}
370
371#[cfg(test)]
372mod tests {
373    use super::*;
374    use chrono::{TimeZone, Utc};
375    use std::collections::HashMap;
376    use url::Url;
377    use uuid::Uuid;
378
379    fn round_trip(llsd: Llsd) {
380        trip(llsd.clone(), llsd);
381    }
382
383    fn trip(input: Llsd, output: Llsd) {
384        let resp = XmlRpc::new_method_response(input);
385        let encoded = to_string(&resp).expect("Failed to encode");
386        let decoded = from_str(&encoded).expect("Failed to decode");
387        assert_eq!(&output, decoded.llsd());
388    }
389
390    #[test]
391    fn undefined() {
392        round_trip(Llsd::Undefined);
393    }
394
395    #[test]
396    fn boolean() {
397        round_trip(Llsd::Boolean(true));
398        round_trip(Llsd::Boolean(false));
399    }
400
401    #[test]
402    fn integer() {
403        round_trip(Llsd::Integer(42));
404    }
405
406    #[cfg(feature = "opensim")]
407    #[test]
408    fn parses_opensim_int_alias_tags() {
409        let xml_i4 = r#"
410<methodResponse><params><param><value><i4>7</i4></value></param></params></methodResponse>
411"#;
412        let xml_i = r#"
413<methodResponse><params><param><value><i>9</i></value></param></params></methodResponse>
414"#;
415        let parsed_i4 = from_str(xml_i4).expect("i4 should parse");
416        let parsed_i = from_str(xml_i).expect("i should parse");
417        assert_eq!(parsed_i4.llsd(), &Llsd::Integer(7));
418        assert_eq!(parsed_i.llsd(), &Llsd::Integer(9));
419    }
420
421    #[cfg(not(feature = "opensim"))]
422    #[test]
423    fn rejects_opensim_int_alias_tags_without_feature() {
424        let xml_i4 = r#"
425<methodResponse><params><param><value><i4>7</i4></value></param></params></methodResponse>
426"#;
427        let xml_i = r#"
428<methodResponse><params><param><value><i>9</i></value></param></params></methodResponse>
429"#;
430        assert!(
431            from_str(xml_i4).is_err(),
432            "i4 should not parse without opensim"
433        );
434        assert!(
435            from_str(xml_i).is_err(),
436            "i should not parse without opensim"
437        );
438    }
439
440    #[test]
441    fn real() {
442        round_trip(Llsd::Real(13.1415));
443    }
444
445    #[test]
446    fn string() {
447        round_trip(Llsd::String("Hello, LLSD!".to_owned()));
448    }
449
450    #[test]
451    fn uri() {
452        let url = Url::parse("https://example.com/").unwrap();
453        trip(Llsd::Uri(url.clone().into()), Llsd::String(url.to_string()));
454    }
455
456    #[test]
457    fn uuid() {
458        let uuid = Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000").unwrap();
459        trip(Llsd::Uuid(uuid), Llsd::String(uuid.to_string()));
460    }
461
462    #[test]
463    fn date() {
464        let dt = Utc.timestamp_opt(1_620_000_000, 0).unwrap();
465        round_trip(Llsd::Date(dt));
466    }
467
468    #[test]
469    fn binary() {
470        round_trip(Llsd::Binary(vec![0xde, 0xad, 0xbe, 0xef]));
471    }
472
473    #[test]
474    fn array() {
475        let arr = vec![
476            Llsd::Integer(1),
477            Llsd::String("two".into()),
478            Llsd::Boolean(false),
479        ];
480        round_trip(Llsd::Array(arr));
481    }
482
483    #[test]
484    fn map() {
485        let mut map = HashMap::new();
486        map.insert("answer".into(), Llsd::Integer(42));
487        map.insert("pi".into(), Llsd::Real(13.14));
488        map.insert("greeting".into(), Llsd::String("hello".into()));
489        round_trip(Llsd::Map(map));
490    }
491}