1use anyhow::{bail, ensure, Context, Result};
5use quick_xml::escape::escape;
6use quick_xml::events::Event as XmlEvent;
7use serde_json::{json, Map, Value};
8
9pub const MAX_ENVELOPE: usize = 1024 * 1024;
11pub const MAX_DEPTH: usize = 32;
13pub const MAX_ELEMENTS: usize = 50_000;
15pub const MAX_PARAMETERS: usize = 10_000;
17pub const CWMP_NS: &str = "urn:dslforum-org:cwmp-1-0";
18
19#[derive(Debug, Default, Clone)]
20pub struct Element {
21 pub name: String,
22 pub attrs: Vec<(String, String)>,
23 pub children: Vec<Element>,
24 pub text: String,
25}
26
27fn local(name: &[u8]) -> String {
28 let s = String::from_utf8_lossy(name);
29 s.rsplit(':').next().unwrap_or_default().to_string()
30}
31
32impl Element {
33 pub fn child(&self, name: &str) -> Option<&Element> {
34 self.children.iter().find(|c| c.name == name)
35 }
36 pub fn text_of(&self, name: &str) -> Option<String> {
37 self.child(name).map(|c| c.text.trim().to_string())
38 }
39 pub fn attr(&self, name: &str) -> Option<&str> {
40 self.attrs
41 .iter()
42 .find(|(k, _)| k == name)
43 .map(|(_, v)| v.as_str())
44 }
45}
46
47pub fn tree(xml: &[u8]) -> Result<Element> {
49 ensure!(
50 xml.len() <= MAX_ENVELOPE,
51 "envelope larger than {MAX_ENVELOPE} bytes"
52 );
53 let mut reader = quick_xml::Reader::from_reader(xml);
54 reader.config_mut().trim_text(false);
55 let mut stack: Vec<Element> = vec![Element::default()];
56 let mut count = 0usize;
57 let mut buf = Vec::new();
58 loop {
59 let ev = reader
60 .read_event_into(&mut buf)
61 .context("the envelope is not well-formed XML")?;
62 let empty = matches!(ev, XmlEvent::Empty(_));
63 match ev {
64 XmlEvent::Start(e) | XmlEvent::Empty(e) => {
65 count += 1;
66 ensure!(count <= MAX_ELEMENTS, "more than {MAX_ELEMENTS} elements");
67 ensure!(stack.len() <= MAX_DEPTH, "nested deeper than {MAX_DEPTH}");
68 let mut el = Element {
69 name: local(e.name().as_ref()),
70 ..Default::default()
71 };
72 for a in e.attributes().flatten() {
73 let v = a
74 .decode_and_unescape_value(reader.decoder())
75 .map(|v| v.into_owned())
76 .unwrap_or_default();
77 el.attrs.push((local(a.key.as_ref()), v));
78 }
79 if empty {
80 if let Some(parent) = stack.last_mut() {
81 parent.children.push(el);
82 }
83 } else {
84 stack.push(el);
85 }
86 }
87 XmlEvent::End(_) => {
88 ensure!(stack.len() > 1, "an end tag with nothing open");
89 let el = stack.pop().unwrap_or_default();
90 if let Some(parent) = stack.last_mut() {
91 parent.children.push(el);
92 }
93 }
94 XmlEvent::Text(t) => {
95 if let Some(top) = stack.last_mut() {
96 top.text
97 .push_str(&t.unescape().context("bad character reference")?);
98 }
99 }
100 XmlEvent::CData(t) => {
101 if let Some(top) = stack.last_mut() {
102 top.text.push_str(&String::from_utf8_lossy(&t));
103 }
104 }
105 XmlEvent::DocType(_) => bail!("a DOCTYPE is not allowed in a SOAP envelope"),
106 XmlEvent::Eof => break,
107 _ => {}
108 }
109 buf.clear();
110 }
111 ensure!(stack.len() == 1, "the document ends inside an element");
112 stack
113 .pop()
114 .unwrap_or_default()
115 .children
116 .into_iter()
117 .next()
118 .context("an empty document")
119}
120
121#[derive(Debug)]
123pub struct Message {
124 pub id: Option<String>,
125 pub method: String,
126 pub content: Value,
127}
128
129pub fn parse(xml: &[u8]) -> Result<Message> {
130 let env = tree(xml)?;
131 ensure!(env.name == "Envelope", "not a SOAP envelope");
132 let id = env.child("Header").and_then(|h| h.text_of("ID"));
133 let body = env.child("Body").context("the envelope has no Body")?;
134 let el = body.children.first().context("the Body is empty")?;
135 let content = match el.name.as_str() {
136 "Fault" => fault_json(el),
137 m => method_json(m, el)?,
138 };
139 let method = if el.name == "Fault" {
140 "Fault".to_string()
141 } else {
142 el.name.clone()
143 };
144 Ok(Message {
145 id,
146 method,
147 content,
148 })
149}
150
151fn param_values(list: Option<&Element>) -> Result<Vec<Value>> {
152 let items: Vec<&Element> = list
153 .map(|l| l.children.iter().collect())
154 .unwrap_or_default();
155 ensure!(
156 items.len() <= MAX_PARAMETERS,
157 "more than {MAX_PARAMETERS} parameters"
158 );
159 Ok(items
160 .into_iter()
161 .map(|p| {
162 let value = p.child("Value");
163 json!({"name": p.text_of("Name").unwrap_or_default(),
164 "value": value.map(|v| v.text.clone()).unwrap_or_default(),
165 "type": value.and_then(|v| v.attr("type")).unwrap_or("xsd:string")})
166 })
167 .collect())
168}
169
170fn method_json(method: &str, el: &Element) -> Result<Value> {
171 Ok(match method {
172 "Inform" => {
173 let d = el.child("DeviceId");
174 let field = |n: &str| d.and_then(|d| d.text_of(n)).unwrap_or_default();
175 json!({
176 "device_id": {"manufacturer": field("Manufacturer"), "oui": field("OUI"),
177 "product_class": field("ProductClass"), "serial_number": field("SerialNumber")},
178 "events": el.child("Event").map(|e| e.children.iter().map(|s| json!({
179 "code": s.text_of("EventCode").unwrap_or_default(),
180 "command_key": s.text_of("CommandKey").unwrap_or_default()})).collect::<Vec<_>>()).unwrap_or_default(),
181 "max_envelopes": el.text_of("MaxEnvelopes"),
182 "current_time": el.text_of("CurrentTime"),
183 "retry_count": el.text_of("RetryCount").and_then(|r| r.parse::<u64>().ok()).unwrap_or(0),
184 "parameters": param_values(el.child("ParameterList"))?,
185 })
186 }
187 "InformResponse" => json!({"max_envelopes": el.text_of("MaxEnvelopes")}),
188 "GetParameterValuesResponse" => {
189 json!({"parameters": param_values(el.child("ParameterList"))?})
190 }
191 "SetParameterValuesResponse" | "AddObjectResponse" | "DeleteObjectResponse" => {
192 let mut o = json!({"status": el.text_of("Status").and_then(|s| s.parse::<u64>().ok()).unwrap_or(0)});
193 if let Some(n) = el.text_of("InstanceNumber") {
194 o["instance_number"] = json!(n.parse::<u64>().unwrap_or(0));
195 }
196 o
197 }
198 "GetParameterNamesResponse" => {
199 let items: Vec<&Element> = el
200 .child("ParameterList")
201 .map(|l| l.children.iter().collect())
202 .unwrap_or_default();
203 ensure!(
204 items.len() <= MAX_PARAMETERS,
205 "more than {MAX_PARAMETERS} parameters"
206 );
207 json!({"parameters": items.into_iter().map(|p| json!({
208 "name": p.text_of("Name").unwrap_or_default(),
209 "writable": matches!(p.text_of("Writable").as_deref(), Some("1" | "true"))})).collect::<Vec<_>>()})
210 }
211 "GetRPCMethodsResponse" => {
212 json!({"methods": el.child("MethodList").map(|l| l.children.iter().map(|m| m.text.trim().to_string()).collect::<Vec<_>>()).unwrap_or_default()})
213 }
214 "GetParameterValues" => {
215 json!({"names": el.child("ParameterNames").map(|l| l.children.iter().map(|n| n.text.trim().to_string()).collect::<Vec<_>>()).unwrap_or_default()})
216 }
217 "SetParameterValues" => {
218 json!({"parameters": param_values(el.child("ParameterList"))?, "parameter_key": el.text_of("ParameterKey").unwrap_or_default()})
219 }
220 "GetParameterNames" => json!({"path": el.text_of("ParameterPath").unwrap_or_default(),
221 "next_level": matches!(el.text_of("NextLevel").as_deref(), Some("1" | "true"))}),
222 "AddObject" | "DeleteObject" => {
223 json!({"object": el.text_of("ObjectName").unwrap_or_default(), "parameter_key": el.text_of("ParameterKey").unwrap_or_default()})
224 }
225 "Reboot" => json!({"command_key": el.text_of("CommandKey").unwrap_or_default()}),
226 _ => {
228 let mut m = Map::new();
229 for c in &el.children {
230 m.insert(c.name.clone(), json!(c.text.trim()));
231 }
232 Value::Object(m)
233 }
234 })
235}
236
237fn fault_json(el: &Element) -> Value {
238 let f = el.child("detail").and_then(|d| d.child("Fault"));
239 json!({
240 "code": f.and_then(|f| f.text_of("FaultCode")).and_then(|c| c.parse::<u64>().ok()).unwrap_or(0),
241 "message": f.and_then(|f| f.text_of("FaultString")).or_else(|| el.text_of("faultstring")).unwrap_or_default(),
242 })
243}
244
245fn esc(s: &str) -> String {
246 escape(s).into_owned()
247}
248
249pub fn envelope(id: &str, method: &str) -> String {
251 format!(
252 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<soap-env:Envelope xmlns:soap-env=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:soap-enc=\"http://schemas.xmlsoap.org/soap/encoding/\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:cwmp=\"{CWMP_NS}\"><soap-env:Header><cwmp:ID soap-env:mustUnderstand=\"1\">{}</cwmp:ID></soap-env:Header><soap-env:Body>{method}</soap-env:Body></soap-env:Envelope>",
253 esc(id)
254 )
255}
256
257pub fn value_list(params: &[(String, String, String)]) -> String {
259 let mut s = format!(
260 "<ParameterList soap-enc:arrayType=\"cwmp:ParameterValueStruct[{}]\">",
261 params.len()
262 );
263 for (n, v, t) in params {
264 s.push_str(&format!(
265 "<ParameterValueStruct><Name>{}</Name><Value xsi:type=\"{}\">{}</Value></ParameterValueStruct>",
266 esc(n),
267 esc(t),
268 esc(v)
269 ));
270 }
271 s.push_str("</ParameterList>");
272 s
273}
274
275pub struct DeviceId {
276 pub manufacturer: String,
277 pub oui: String,
278 pub product_class: String,
279 pub serial_number: String,
280}
281
282pub fn inform(
283 d: &DeviceId,
284 events: &[(String, String)],
285 params: &[(String, String, String)],
286 retry: u32,
287 now: &str,
288) -> String {
289 let mut ev = format!(
290 "<Event soap-enc:arrayType=\"cwmp:EventStruct[{}]\">",
291 events.len()
292 );
293 for (code, key) in events {
294 ev.push_str(&format!(
295 "<EventStruct><EventCode>{}</EventCode><CommandKey>{}</CommandKey></EventStruct>",
296 esc(code),
297 esc(key)
298 ));
299 }
300 ev.push_str("</Event>");
301 format!(
302 "<cwmp:Inform><DeviceId><Manufacturer>{}</Manufacturer><OUI>{}</OUI><ProductClass>{}</ProductClass><SerialNumber>{}</SerialNumber></DeviceId>{ev}<MaxEnvelopes>1</MaxEnvelopes><CurrentTime>{}</CurrentTime><RetryCount>{retry}</RetryCount>{}</cwmp:Inform>",
303 esc(&d.manufacturer), esc(&d.oui), esc(&d.product_class), esc(&d.serial_number), esc(now), value_list(params)
304 )
305}
306
307pub fn inform_response() -> String {
308 "<cwmp:InformResponse><MaxEnvelopes>1</MaxEnvelopes></cwmp:InformResponse>".into()
309}
310
311pub fn fault(code: u32, message: &str) -> String {
312 format!(
313 "<soap-env:Fault><faultcode>{}</faultcode><faultstring>CWMP fault</faultstring><detail><cwmp:Fault><FaultCode>{code}</FaultCode><FaultString>{}</FaultString></cwmp:Fault></detail></soap-env:Fault>",
314 if (8000..9000).contains(&code) { "Server" } else { "Client" },
315 esc(message)
316 )
317}
318
319fn string_list(tag: &str, kind: &str, items: &[String]) -> String {
320 let mut s = format!("<{tag} soap-enc:arrayType=\"{kind}[{}]\">", items.len());
321 for i in items {
322 s.push_str(&format!("<string>{}</string>", esc(i)));
323 }
324 s.push_str(&format!("</{tag}>"));
325 s
326}
327
328pub fn get_parameter_values(names: &[String]) -> String {
329 format!(
330 "<cwmp:GetParameterValues>{}</cwmp:GetParameterValues>",
331 string_list("ParameterNames", "xsd:string", names)
332 )
333}
334
335pub fn set_parameter_values(params: &[(String, String, String)], key: &str) -> String {
336 format!(
337 "<cwmp:SetParameterValues>{}<ParameterKey>{}</ParameterKey></cwmp:SetParameterValues>",
338 value_list(params),
339 esc(key)
340 )
341}
342
343pub fn get_parameter_names(path: &str, next_level: bool) -> String {
344 format!("<cwmp:GetParameterNames><ParameterPath>{}</ParameterPath><NextLevel>{}</NextLevel></cwmp:GetParameterNames>", esc(path), u8::from(next_level))
345}
346
347pub fn add_object(object: &str, key: &str) -> String {
348 format!("<cwmp:AddObject><ObjectName>{}</ObjectName><ParameterKey>{}</ParameterKey></cwmp:AddObject>", esc(object), esc(key))
349}
350
351pub fn delete_object(object: &str, key: &str) -> String {
352 format!("<cwmp:DeleteObject><ObjectName>{}</ObjectName><ParameterKey>{}</ParameterKey></cwmp:DeleteObject>", esc(object), esc(key))
353}
354
355pub fn reboot(key: &str) -> String {
356 format!(
357 "<cwmp:Reboot><CommandKey>{}</CommandKey></cwmp:Reboot>",
358 esc(key)
359 )
360}
361
362pub fn factory_reset() -> String {
363 "<cwmp:FactoryReset></cwmp:FactoryReset>".into()
364}
365
366pub fn get_parameter_values_response(params: &[(String, String, String)]) -> String {
367 format!(
368 "<cwmp:GetParameterValuesResponse>{}</cwmp:GetParameterValuesResponse>",
369 value_list(params)
370 )
371}
372
373pub fn get_parameter_names_response(params: &[(String, bool)]) -> String {
374 let mut s = format!("<cwmp:GetParameterNamesResponse><ParameterList soap-enc:arrayType=\"cwmp:ParameterInfoStruct[{}]\">", params.len());
375 for (n, w) in params {
376 s.push_str(&format!(
377 "<ParameterInfoStruct><Name>{}</Name><Writable>{}</Writable></ParameterInfoStruct>",
378 esc(n),
379 u8::from(*w)
380 ));
381 }
382 s.push_str("</ParameterList></cwmp:GetParameterNamesResponse>");
383 s
384}
385
386pub fn status_response(method: &str, status: u32, instance: Option<u64>) -> String {
388 match method {
389 "SetParameterValues" => format!("<cwmp:SetParameterValuesResponse><Status>{status}</Status></cwmp:SetParameterValuesResponse>"),
390 "AddObject" => format!(
391 "<cwmp:AddObjectResponse><InstanceNumber>{}</InstanceNumber><Status>{status}</Status></cwmp:AddObjectResponse>",
392 instance.unwrap_or(1)
393 ),
394 "DeleteObject" => format!("<cwmp:DeleteObjectResponse><Status>{status}</Status></cwmp:DeleteObjectResponse>"),
395 other => format!("<cwmp:{other}Response></cwmp:{other}Response>"),
396 }
397}
398
399pub fn get_rpc_methods_response(methods: &[&str]) -> String {
400 let items: Vec<String> = methods.iter().map(|m| m.to_string()).collect();
401 format!(
402 "<cwmp:GetRPCMethodsResponse>{}</cwmp:GetRPCMethodsResponse>",
403 string_list("MethodList", "xsd:string", &items)
404 )
405}
406
407pub fn triples(values: &Value, types: Option<&Value>) -> Result<Vec<(String, String, String)>> {
409 let o = values
410 .as_object()
411 .context("parameters must be an object of name: value")?;
412 ensure!(
413 o.len() <= MAX_PARAMETERS,
414 "more than {MAX_PARAMETERS} parameters"
415 );
416 o.iter()
417 .map(|(k, v)| {
418 ensure!(
419 !k.is_empty() && k.len() <= 256,
420 "parameter names must be 1-256 bytes"
421 );
422 let value = match v {
423 Value::String(s) => s.clone(),
424 Value::Bool(b) => b.to_string(),
425 Value::Number(n) => n.to_string(),
426 _ => bail!("the value of {k} must be a string, number or boolean"),
427 };
428 let kind = types
429 .and_then(|t| t[k].as_str())
430 .map(str::to_string)
431 .unwrap_or_else(|| match v {
432 Value::Bool(_) => "xsd:boolean".into(),
433 Value::Number(n) if n.is_u64() => "xsd:unsignedInt".into(),
434 Value::Number(_) => "xsd:int".into(),
435 _ => "xsd:string".into(),
436 });
437 ensure!(
438 kind.starts_with("xsd:") && kind.len() <= 32,
439 "a type is xsd:string, xsd:boolean, xsd:unsignedInt and the like"
440 );
441 Ok((k.clone(), value, kind))
442 })
443 .collect()
444}