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
86pub fn from_parser<R: std::io::Read>(parser: EventReader<R>) -> Result<XmlRpc, anyhow::Error> {
87    use xml::reader::XmlEvent;
88    let mut stack: Vec<Llsd> = Vec::new();
89    let mut name_stack: Vec<String> = Vec::new();
90    let mut key_stack: Vec<String> = Vec::new();
91
92    let mut expect_value = Expected::XmlRpcHeader;
93    let mut method = None;
94
95    for event in parser {
96        match event {
97            Ok(XmlEvent::StartElement { name, .. }) => {
98                name_stack.push(name.local_name.clone());
99                match (expect_value, name.local_name.as_str()) {
100                    (Expected::Data, "data") => expect_value = Expected::Value,
101                    (Expected::Member, "member") => expect_value = Expected::Name,
102                    (Expected::Name, "name") => expect_value = Expected::Value,
103                    (Expected::Value, "value") => expect_value = Expected::None,
104                    (Expected::XmlRpcHeader, "methodResponse") => expect_value = Expected::Parmas,
105                    (Expected::XmlRpcHeader, "methodCall") => {
106                        expect_value = Expected::MethodCallName
107                    }
108                    (Expected::MethodCallName, "methodName") => expect_value = Expected::Parmas,
109                    (Expected::Parmas, "params") => expect_value = Expected::Param,
110                    (Expected::Param, "param") => expect_value = Expected::Value,
111                    (Expected::None, "nil") => stack.push(Llsd::Undefined),
112                    (Expected::None, "boolean") => stack.push(Llsd::Boolean(false)),
113                    (Expected::None, "string") => stack.push(Llsd::String(String::new())),
114                    (Expected::None, "int") => stack.push(Llsd::Integer(0)),
115                    (Expected::None, "double") => stack.push(Llsd::Real(0.0)),
116                    (Expected::None, "dateTime.iso8601") => {
117                        stack.push(Llsd::Date(Default::default()))
118                    }
119                    (Expected::None, "base64") => stack.push(Llsd::Binary(Vec::new())),
120                    (Expected::None, "array") => {
121                        stack.push(Llsd::Array(Vec::new()));
122                        expect_value = Expected::Data;
123                    }
124                    (Expected::None, "struct") => {
125                        stack.push(Llsd::Map(Default::default()));
126                        expect_value = Expected::Member;
127                    }
128                    _ => {
129                        return Err(anyhow::anyhow!(
130                            "Error parsing XML-RPC: unexpected element {}",
131                            name.local_name
132                        ));
133                    }
134                }
135            }
136            Ok(XmlEvent::Characters(data)) => {
137                let data = data.trim();
138                if expect_value == Expected::MethodCallName {
139                    method = Some(data.to_string());
140                } else if name_stack.last().map(|s| s.as_str()) == Some("name") {
141                    key_stack.push(data.to_string());
142                } else if let Some(llsd) = stack.last_mut() {
143                    match llsd {
144                        Llsd::Boolean(_) => match data {
145                            "true" => *llsd = Llsd::Boolean(true),
146                            "false" => *llsd = Llsd::Boolean(false),
147                            "1" => *llsd = Llsd::Boolean(true),
148                            "0" => *llsd = Llsd::Boolean(false),
149                            _ => {
150                                return Err(anyhow::anyhow!(
151                                    "Error parsing XML-RPC: expected boolean, got {}",
152                                    data
153                                ));
154                            }
155                        },
156                        &mut Llsd::String(ref mut s) => s.push_str(data),
157                        &mut Llsd::Date(ref mut d) => {
158                            *d = DateTime::parse_from_rfc3339(data)?.into()
159                        }
160                        &mut Llsd::Binary(ref mut b) => {
161                            *b = BASE64_STANDARD.decode(data.as_bytes())?
162                        }
163                        &mut Llsd::Integer(ref mut i) => *i = data.parse()?,
164                        &mut Llsd::Real(ref mut r) => match data {
165                            "nan" => *r = f64::NAN,
166                            "inf" => *r = f64::INFINITY,
167                            "-inf" => *r = f64::NEG_INFINITY,
168                            _ => *r = data.parse()?,
169                        },
170                        _ => {
171                            return Err(anyhow::anyhow!(
172                                "Error parsing XML-RPC: unexpected characters {}",
173                                data
174                            ));
175                        }
176                    }
177                }
178            }
179            Ok(XmlEvent::EndElement { name }) => {
180                if name_stack.pop().as_ref() != Some(&name.local_name) {
181                    return Err(anyhow::anyhow!(
182                        "Error parsing LLSD: unexpected end element {}",
183                        name.local_name
184                    ));
185                }
186                match name.local_name.as_str() {
187                    "struct" | "array" if stack.len() > 1 => {
188                        if let Some(parent) = stack.get(stack.len() - 2) {
189                            if parent.is_array() {
190                                expect_value = Expected::Value;
191                            } else if parent.is_map() {
192                                expect_value = Expected::Member;
193                            } else {
194                                return Err(anyhow::anyhow!(
195                                    "Error parsing XML-RPC: not a map or array"
196                                ));
197                            }
198                        }
199                    }
200                    "member" => {
201                        let Some(key) = key_stack.pop() else {
202                            return Err(anyhow::anyhow!("Error parsing XML-RPC: missing key"));
203                        };
204                        let Some(value) = stack.pop() else {
205                            return Err(anyhow::anyhow!(
206                                "Error parsing XML-RPC: unexpected end element {}",
207                                name.local_name
208                            ));
209                        };
210                        let Some(Llsd::Map(parent)) = stack.last_mut() else {
211                            return Err(anyhow::anyhow!("Error parsing XML-RPC: not a map"));
212                        };
213                        parent.insert(key.to_string(), value);
214                        expect_value = Expected::Member;
215                    }
216                    "value" if stack.len() > 1 => {
217                        let Some(value) = stack.pop() else {
218                            return Err(anyhow::anyhow!(
219                                "Error parsing XML-RPC: unexpected end element {}",
220                                name.local_name
221                            ));
222                        };
223                        if let Some(Llsd::Array(parent)) = stack.last_mut() {
224                            parent.push(value);
225                            expect_value = Expected::Value;
226                        } else {
227                            stack.push(value);
228                        }
229                    }
230                    _ => {}
231                };
232            }
233            Err(e) => return Err(anyhow::anyhow!("Error parsing XML-RPC: {}", e)),
234            _ => {}
235        }
236    }
237    if let Some(llsd) = stack.pop() {
238        if !stack.is_empty() {
239            return Err(anyhow::anyhow!(
240                "Error parsing XML-RPC: expected 1 value, got {}",
241                stack.len() + 1
242            ));
243        }
244        if let Some(method) = method {
245            Ok(XmlRpc::MethodCall(method, llsd))
246        } else {
247            Ok(XmlRpc::MethodResponse(llsd))
248        }
249    } else {
250        Err(anyhow::anyhow!("Error parsing XML-RPC: missing key"))
251    }
252}
253
254pub fn from_str(data: &str) -> Result<XmlRpc, anyhow::Error> {
255    from_parser(EventReader::from_str(data))
256}
257
258pub fn from_reader<R: std::io::Read>(reader: R) -> Result<XmlRpc, anyhow::Error> {
259    from_parser(EventReader::new(reader))
260}
261
262pub fn from_slice(data: &[u8]) -> Result<XmlRpc, anyhow::Error> {
263    from_parser(EventReader::new(std::io::Cursor::new(data)))
264}
265
266fn write_inner<W: std::io::Write>(
267    llsd: &Llsd,
268    w: &mut EventWriter<W>,
269) -> Result<(), anyhow::Error> {
270    use xml::writer::XmlEvent;
271    let tag = |w: &mut EventWriter<W>, tag, text: &str| -> Result<(), anyhow::Error> {
272        w.write(XmlEvent::start_element(tag))?;
273        if !text.is_empty() {
274            w.write(XmlEvent::characters(text))?;
275        }
276        w.write(XmlEvent::end_element())?;
277        Ok(())
278    };
279    match llsd {
280        Llsd::Undefined => tag(w, "nil", ""),
281        Llsd::Boolean(b) => tag(w, "boolean", if *b { "1" } else { "0" }),
282        Llsd::Integer(i) => tag(w, "int", &i.to_string()),
283        Llsd::Real(r) => tag(w, "double", &r.to_string()),
284        Llsd::String(s) => tag(w, "string", s),
285        Llsd::Uri(u) => tag(w, "string", u.as_str()),
286        Llsd::Uuid(u) => tag(w, "string", &u.to_string()),
287        Llsd::Date(d) => tag(w, "dateTime.iso8601", &d.to_rfc3339()),
288        Llsd::Binary(b) => tag(w, "base64", &BASE64_STANDARD.encode(b)),
289        Llsd::Array(a) => {
290            w.write(XmlEvent::start_element("array"))?;
291            w.write(XmlEvent::start_element("data"))?;
292            for llsd in a {
293                w.write(XmlEvent::start_element("value"))?;
294                write_inner(llsd, w)?;
295                w.write(XmlEvent::end_element())?;
296            }
297            w.write(XmlEvent::end_element())?;
298            w.write(XmlEvent::end_element())?;
299            Ok(())
300        }
301        Llsd::Map(m) => {
302            w.write(XmlEvent::start_element("struct"))?;
303            for (k, v) in m {
304                w.write(XmlEvent::start_element("member"))?;
305                tag(w, "name", k)?;
306                w.write(XmlEvent::start_element("value"))?;
307                write_inner(v, w)?;
308                w.write(XmlEvent::end_element())?;
309                w.write(XmlEvent::end_element())?;
310            }
311            w.write(XmlEvent::end_element())?;
312            Ok(())
313        }
314    }
315}
316
317pub fn write<W: std::io::Write>(rpc: &XmlRpc, w: &mut EventWriter<W>) -> Result<(), anyhow::Error> {
318    use xml::writer::XmlEvent;
319    match rpc {
320        XmlRpc::MethodCall(method, _) => {
321            w.write(XmlEvent::start_element("methodCall"))?;
322            w.write(XmlEvent::start_element("methodName"))?;
323            w.write(XmlEvent::characters(method))?;
324            w.write(XmlEvent::end_element())?;
325        }
326        XmlRpc::MethodResponse(_) => {
327            w.write(XmlEvent::start_element("methodResponse"))?;
328        }
329    }
330    w.write(XmlEvent::start_element("params"))?;
331    w.write(XmlEvent::start_element("param"))?;
332    w.write(XmlEvent::start_element("value"))?;
333    write_inner(rpc.as_ref(), w)?;
334    w.write(XmlEvent::end_element())?;
335    w.write(XmlEvent::end_element())?;
336    w.write(XmlEvent::end_element())?;
337    w.write(XmlEvent::end_element())?;
338    Ok(())
339}
340
341pub fn to_string(rpc: &XmlRpc) -> Result<String, anyhow::Error> {
342    let mut buf = Vec::new();
343    write(rpc, &mut EventWriter::new(&mut buf))?;
344    Ok(String::from_utf8(buf)?)
345}
346
347pub fn to_pretty_string(rpc: &XmlRpc) -> Result<String, anyhow::Error> {
348    let mut buf = Vec::new();
349    write(
350        rpc,
351        &mut EventWriter::new_with_config(
352            &mut buf,
353            xml::writer::EmitterConfig::new().perform_indent(true),
354        ),
355    )?;
356    Ok(String::from_utf8(buf)?)
357}
358
359#[cfg(test)]
360mod tests {
361    use super::*;
362    use chrono::{TimeZone, Utc};
363    use std::collections::HashMap;
364    use url::Url;
365    use uuid::Uuid;
366
367    fn round_trip(llsd: Llsd) {
368        trip(llsd.clone(), llsd);
369    }
370
371    fn trip(input: Llsd, output: Llsd) {
372        let resp = XmlRpc::new_method_response(input);
373        let encoded = to_string(&resp).expect("Failed to encode");
374        let decoded = from_str(&encoded).expect("Failed to decode");
375        assert_eq!(&output, decoded.llsd());
376    }
377
378    #[test]
379    fn undefined() {
380        round_trip(Llsd::Undefined);
381    }
382
383    #[test]
384    fn boolean() {
385        round_trip(Llsd::Boolean(true));
386        round_trip(Llsd::Boolean(false));
387    }
388
389    #[test]
390    fn integer() {
391        round_trip(Llsd::Integer(42));
392    }
393
394    #[test]
395    fn real() {
396        round_trip(Llsd::Real(13.1415));
397    }
398
399    #[test]
400    fn string() {
401        round_trip(Llsd::String("Hello, LLSD!".to_owned()));
402    }
403
404    #[test]
405    fn uri() {
406        let url = Url::parse("https://example.com/").unwrap();
407        trip(Llsd::Uri(url.clone().into()), Llsd::String(url.to_string()));
408    }
409
410    #[test]
411    fn uuid() {
412        let uuid = Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000").unwrap();
413        trip(Llsd::Uuid(uuid), Llsd::String(uuid.to_string()));
414    }
415
416    #[test]
417    fn date() {
418        let dt = Utc.timestamp_opt(1_620_000_000, 0).unwrap();
419        round_trip(Llsd::Date(dt));
420    }
421
422    #[test]
423    fn binary() {
424        round_trip(Llsd::Binary(vec![0xde, 0xad, 0xbe, 0xef]));
425    }
426
427    #[test]
428    fn array() {
429        let arr = vec![
430            Llsd::Integer(1),
431            Llsd::String("two".into()),
432            Llsd::Boolean(false),
433        ];
434        round_trip(Llsd::Array(arr));
435    }
436
437    #[test]
438    fn map() {
439        let mut map = HashMap::new();
440        map.insert("answer".into(), Llsd::Integer(42));
441        map.insert("pi".into(), Llsd::Real(13.14));
442        map.insert("greeting".into(), Llsd::String("hello".into()));
443        round_trip(Llsd::Map(map));
444    }
445}