1use std::{fmt::Debug, io::Write, str::from_utf8};
2
3use quick_xml::{
4 events::{attributes::Attribute, BytesStart, Event},
5 name::{Namespace, ResolveResult},
6 NsReader, Writer,
7};
8
9use super::{xmlns, ClientMsg, ReadError, ReadXml, ServerMsg, WriteError, WriteXml, MARKER};
10
11pub mod error;
12pub use self::error::{Error, Errors};
13
14pub mod operation;
15pub(crate) use self::operation::Operation;
16
17#[derive(Debug, Default, Clone, Copy, Hash, PartialEq, Eq)]
18pub struct MessageId(usize);
19
20impl MessageId {
21 pub(crate) fn increment(&mut self) -> Self {
22 self.0 += 1;
23 *self
24 }
25}
26
27impl TryFrom<Attribute<'_>> for MessageId {
28 type Error = ReadError;
29
30 fn try_from(value: Attribute<'_>) -> Result<Self, Self::Error> {
31 Ok(Self(
32 value
33 .unescape_value()?
34 .as_ref()
35 .parse()
36 .map_err(ReadError::MessageIdParse)?,
37 ))
38 }
39}
40
41#[derive(Debug, Clone)]
42pub(crate) struct Request<O: Operation> {
43 message_id: MessageId,
44 operation: O,
45}
46
47impl<O: Operation> Request<O> {
48 pub(crate) const fn new(message_id: MessageId, operation: O) -> Self {
49 Self {
50 message_id,
51 operation,
52 }
53 }
54}
55
56impl<O: Operation> WriteXml for Request<O> {
57 fn write_xml<W: Write>(&self, writer: &mut Writer<W>) -> Result<(), WriteError> {
58 writer
59 .create_element("rpc")
60 .with_attribute(("message-id", self.message_id.0.to_string().as_ref()))
61 .write_inner_content(|writer| self.operation.write_xml(writer))
62 .map(|_| ())
63 }
64}
65
66impl<O: Operation> ClientMsg for Request<O> {}
67
68#[derive(Debug, Clone, PartialEq, Eq)]
69pub(crate) struct PartialReply {
70 message_id: MessageId,
71 buf: Box<str>,
72}
73
74impl PartialReply {
75 pub(crate) const fn message_id(&self) -> MessageId {
76 self.message_id
77 }
78}
79
80impl ServerMsg for PartialReply {
81 const TAG_NS: Namespace<'static> = xmlns::BASE;
82 const TAG_NAME: &'static str = "rpc-reply";
83
84 #[tracing::instrument(skip(input))]
88 fn from_xml<S>(input: S) -> Result<Self, ReadError>
89 where
90 S: AsRef<str> + Debug,
91 {
92 let mut reader = NsReader::from_str(input.as_ref());
93 _ = reader.trim_text(true);
94 Self::read_xml(&mut reader, &BytesStart::new("dummy"))
95 }
96}
97
98impl ReadXml for PartialReply {
99 #[tracing::instrument(skip_all, level = "debug")]
100 fn read_xml(reader: &mut NsReader<&[u8]>, _: &BytesStart<'_>) -> Result<Self, ReadError> {
101 let buf = from_utf8(reader.get_ref())?.into();
102 tracing::debug!("expecting <{}>", Self::TAG_NAME);
103 let mut message_id = None;
104 loop {
105 match reader.read_resolved_event()? {
106 (ResolveResult::Bound(ns), Event::Start(tag))
107 if ns == Self::TAG_NS
108 && tag.local_name().as_ref() == Self::TAG_NAME.as_bytes() =>
109 {
110 let end = tag.to_end();
111 tracing::debug!("trying to parse message-id");
112 message_id = tag
113 .try_get_attribute("message-id")?
114 .map(MessageId::try_from)
115 .transpose()?;
116 _ = reader.read_to_end(end.name());
117 }
118 (_, Event::Comment(_)) => continue,
119 (_, Event::Eof) => break,
120 (_, Event::Text(txt)) if &*txt == MARKER => break,
121 (ns, event) => {
122 tracing::error!(?event, ?ns, "unexpected xml event");
123 return Err(ReadError::UnexpectedXmlEvent(event.into_owned()));
124 }
125 }
126 }
127 Ok(Self {
128 message_id: message_id.ok_or_else(|| ReadError::NoMessageId)?,
129 buf,
130 })
131 }
132}
133
134#[derive(Debug, Clone, PartialEq, Eq)]
135pub(crate) struct Reply<O: Operation> {
136 message_id: MessageId,
137 inner: O::Reply,
138}
139
140impl<O: Operation> Reply<O> {
141 pub(crate) fn into_result(self) -> Result<<O::Reply as IntoResult>::Ok, crate::Error> {
142 self.inner.into_result()
143 }
144}
145
146impl<O: Operation> ReadXml for Reply<O> {
147 #[tracing::instrument(skip_all, fields(tag = ?start.local_name()), level = "debug")]
148 fn read_xml(reader: &mut NsReader<&[u8]>, start: &BytesStart<'_>) -> Result<Self, ReadError> {
149 tracing::debug!("trying to parse message-id");
150 let message_id = start
151 .try_get_attribute("message-id")?
152 .ok_or_else(|| ReadError::NoMessageId)
153 .and_then(MessageId::try_from)?;
154 let inner = O::Reply::read_xml(reader, start)?;
155 Ok(Self { message_id, inner })
156 }
157}
158
159impl<O: Operation> ServerMsg for Reply<O> {
160 const TAG_NS: Namespace<'static> = xmlns::BASE;
161 const TAG_NAME: &'static str = "rpc-reply";
162}
163
164impl<O: Operation> TryFrom<PartialReply> for Reply<O> {
165 type Error = ReadError;
166
167 #[tracing::instrument(skip(value), level = "debug")]
168 fn try_from(value: PartialReply) -> Result<Self, Self::Error> {
169 let this = Self::from_xml(&value.buf)?;
170 if this.message_id != value.message_id {
171 return Err(Self::Error::message_id_mismatch(
172 value.message_id,
173 this.message_id,
174 ));
175 };
176 Ok(this)
177 }
178}
179
180pub trait IntoResult {
181 type Ok;
182 fn into_result(self) -> Result<Self::Ok, crate::Error>;
183}
184
185#[derive(Debug, Clone, PartialEq, Eq)]
186pub enum EmptyReply {
187 Ok,
188 Errs(Errors),
189}
190
191impl ReadXml for EmptyReply {
192 #[tracing::instrument(skip_all, fields(tag = ?start.local_name()), level = "debug")]
193 fn read_xml(reader: &mut NsReader<&[u8]>, start: &BytesStart<'_>) -> Result<Self, ReadError> {
194 let end = start.to_end();
195 let mut errors = Errors::new();
196 let mut this = None;
197 tracing::debug!("expecting <ok> or <rpc-error>");
198 loop {
199 match reader.read_resolved_event()? {
200 (ResolveResult::Bound(ns), Event::Empty(tag))
201 if ns == xmlns::BASE
202 && tag.local_name().as_ref() == b"ok"
203 && this.is_none()
204 && errors.is_empty() =>
205 {
206 tracing::debug!(?tag);
207 this = Some(Self::Ok);
208 }
209 (ResolveResult::Bound(ns), Event::Start(tag))
210 if ns == xmlns::BASE
211 && tag.local_name().as_ref() == b"rpc-error"
212 && this.is_none() =>
213 {
214 tracing::debug!(?tag);
215 errors.push(Error::read_xml(reader, &tag)?);
216 }
217 (_, Event::Comment(_)) => continue,
218 (_, Event::End(tag)) if tag == end => break,
219 (ns, event) => {
220 tracing::error!(?event, ?ns, "unexpected xml event");
221 return Err(ReadError::UnexpectedXmlEvent(event.into_owned()));
222 }
223 }
224 }
225 this.or_else(|| (!errors.is_empty()).then(|| Self::Errs(errors)))
226 .ok_or_else(|| ReadError::missing_element("rpc-reply", "ok/rpc-error"))
227 }
228}
229
230impl IntoResult for EmptyReply {
231 type Ok = ();
232 fn into_result(self) -> Result<<Self as IntoResult>::Ok, crate::Error> {
233 match self {
234 Self::Ok => Ok(()),
235 Self::Errs(errs) => Err(errs.into()),
236 }
237 }
238}
239
240#[derive(Debug, Clone, PartialEq, Eq)]
241pub enum DataReply<D> {
242 Data(D),
243 Errs(Errors),
244}
245
246impl<D: ReadXml> ReadXml for DataReply<D> {
247 #[tracing::instrument(skip_all, fields(tag = ?start.local_name()), level = "debug")]
248 fn read_xml(reader: &mut NsReader<&[u8]>, start: &BytesStart<'_>) -> Result<Self, ReadError> {
249 let end = start.to_end();
250 let mut errors = Errors::new();
251 let mut this = None;
252 tracing::debug!("expecting <data> or <rpc-error>");
253 loop {
254 match reader.read_resolved_event()? {
255 (ResolveResult::Bound(ns), Event::Start(tag))
256 if ns == xmlns::BASE
257 && tag.local_name().as_ref() == b"data"
258 && this.is_none()
259 && errors.is_empty() =>
260 {
261 tracing::debug!(?tag);
262 this = Some(Self::Data(D::read_xml(reader, &tag)?));
263 }
264 (ResolveResult::Bound(ns), Event::Start(tag))
265 if ns == xmlns::BASE
266 && tag.local_name().as_ref() == b"rpc-error"
267 && this.is_none() =>
268 {
269 tracing::debug!(?tag);
270 errors.push(Error::read_xml(reader, &tag)?);
271 }
272 (_, Event::Comment(_)) => continue,
273 (_, Event::End(tag)) if tag == end => break,
274 (ns, event) => {
275 tracing::error!(?event, ?ns, "unexpected xml event");
276 return Err(ReadError::UnexpectedXmlEvent(event.into_owned()));
277 }
278 }
279 }
280 this.or_else(|| (!errors.is_empty()).then(|| Self::Errs(errors)))
281 .ok_or_else(|| ReadError::missing_element("rpc-reply", "data/rpc-error"))
282 }
283}
284
285impl<D> IntoResult for DataReply<D> {
286 type Ok = D;
287 fn into_result(self) -> Result<Self::Ok, crate::Error> {
288 match self {
289 Self::Data(data) => Ok(data),
290 Self::Errs(errs) => Err(errs.into()),
291 }
292 }
293}
294#[cfg(test)]
295mod tests {
296 use quick_xml::events::BytesText;
297
298 use super::*;
299 use crate::capabilities::Requirements;
300
301 #[derive(Debug, Clone, PartialEq, Eq)]
302 struct Foo {
303 foo: &'static str,
304 }
305
306 impl WriteXml for Foo {
307 fn write_xml<W: Write>(&self, writer: &mut Writer<W>) -> Result<(), WriteError> {
308 _ = writer
309 .create_element("foo")
310 .write_text_content(BytesText::new(self.foo))?;
311 Ok(())
312 }
313 }
314
315 impl Operation for Foo {
316 const NAME: &'static str = "foo";
317 const REQUIRED_CAPABILITIES: Requirements = Requirements::None;
318 type Builder<'a> = FooBuilder;
319 type Reply = EmptyReply;
320 }
321
322 #[derive(Debug, Default)]
323 struct FooBuilder {
324 foo: Option<&'static str>,
325 }
326
327 impl operation::Builder<'_, Foo> for FooBuilder {
328 fn new(_: &crate::session::Context) -> Self {
329 Self { foo: None }
330 }
331
332 fn finish(self) -> Result<Foo, crate::Error> {
333 let foo = self
334 .foo
335 .ok_or_else(|| crate::Error::missing_operation_parameter("foo", "foo"))?;
336 Ok(Foo { foo })
337 }
338 }
339
340 #[test]
341 fn serialize_foo_request() {
342 let req: Request<Foo> = Request {
343 message_id: MessageId(101),
344 operation: Foo { foo: "bar" },
345 };
346 let expect = r#"<rpc message-id="101"><foo>bar</foo></rpc>]]>]]>"#;
347 assert_eq!(req.to_xml().unwrap(), expect);
348 }
349
350 #[test]
351 fn deserialize_foo_reply() {
352 let data = r#"
353 <rpc-reply
354 message-id="101"
355 xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
356 <ok/>
357 </rpc-reply>
358 "#;
359 let expect: Reply<Foo> = Reply {
360 message_id: MessageId(101),
361 inner: EmptyReply::Ok,
362 };
363 assert_eq!(
364 expect,
365 PartialReply::from_xml(data)
366 .and_then(Reply::try_from)
367 .unwrap()
368 );
369 }
370
371 #[test]
372 fn deserialize_foo_reply_with_xmlns() {
373 let data = r#"
374 <nc:rpc-reply
375 message-id="101"
376 xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0"
377 xmlns:junos="http://xml.juniper.net/junos/23.1R0/junos">
378 <nc:ok/>
379 </nc:rpc-reply>
380 ]]>]]>
381 "#;
382 let expect: Reply<Foo> = Reply {
383 message_id: MessageId(101),
384 inner: EmptyReply::Ok,
385 };
386 assert_eq!(
387 expect,
388 PartialReply::from_xml(data)
389 .and_then(Reply::try_from)
390 .unwrap()
391 );
392 }
393
394 #[derive(Debug, Clone, PartialEq, Eq)]
395 struct Bar;
396
397 impl WriteXml for Bar {
398 fn write_xml<W: Write>(&self, writer: &mut Writer<W>) -> Result<(), WriteError> {
399 _ = writer.create_element("bar").write_empty()?;
400 Ok(())
401 }
402 }
403
404 impl Operation for Bar {
405 const NAME: &'static str = "bar";
406 const REQUIRED_CAPABILITIES: Requirements = Requirements::None;
407 type Builder<'a> = BarBuilder;
408 type Reply = DataReply<BarReply>;
409 }
410
411 #[derive(Debug, Clone, PartialEq, Eq)]
412 struct BarReply(usize);
413
414 impl ReadXml for BarReply {
415 #[tracing::instrument(skip_all, fields(tag = ?start.local_name()), level = "debug")]
416 fn read_xml(
417 reader: &mut NsReader<&[u8]>,
418 start: &BytesStart<'_>,
419 ) -> Result<Self, ReadError> {
420 let end = start.to_end();
421 let mut result = None;
422 loop {
423 match reader.read_resolved_event()? {
424 (ResolveResult::Bound(ns), Event::Start(tag))
425 if ns == Namespace(b"bar")
426 && tag.local_name().as_ref() == b"result"
427 && result.is_none() =>
428 {
429 result = Some(
430 reader
431 .read_text(tag.to_end().name())?
432 .parse::<usize>()
433 .map_err(|err| ReadError::Other(err.into()))?,
434 );
435 }
436 (_, Event::Comment(_)) => continue,
437 (_, Event::End(tag)) if tag == end => break,
438 (ns, event) => {
439 tracing::error!(?event, ?ns, "unexpected xml event");
440 return Err(ReadError::UnexpectedXmlEvent(event.into_owned()));
441 }
442 }
443 }
444 Ok(Self(result.ok_or_else(|| {
445 ReadError::missing_element("rpc-reply", "result")
446 })?))
447 }
448 }
449
450 #[derive(Debug, Default)]
451 struct BarBuilder;
452
453 impl operation::Builder<'_, Bar> for BarBuilder {
454 fn new(_: &crate::session::Context) -> Self {
455 Self
456 }
457
458 fn finish(self) -> Result<Bar, crate::Error> {
459 Ok(Bar)
460 }
461 }
462
463 #[test]
464 fn serialize_bar_request() {
465 let req = Request {
466 message_id: MessageId(101),
467 operation: Bar,
468 };
469 let expect = r#"<rpc message-id="101"><bar/></rpc>]]>]]>"#;
470 assert_eq!(req.to_xml().unwrap(), expect);
471 }
472
473 #[test]
474 fn deserialize_bar_reply() {
475 let data = r#"
476 <rpc-reply
477 message-id="101"
478 xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
479 <data>
480 <result xmlns="bar">99</result>
481 </data>
482 </rpc-reply>
483 "#;
484 let expect: Reply<Bar> = Reply {
485 message_id: MessageId(101),
486 inner: DataReply::Data(BarReply(99)),
487 };
488 assert_eq!(
489 expect,
490 PartialReply::from_xml(data)
491 .and_then(Reply::try_from)
492 .unwrap()
493 );
494 }
495
496 #[test]
497 fn deserialize_bar_reply_with_xmlns() {
498 let data = r#"
499 <nc:rpc-reply
500 message-id="101"
501 xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0"
502 xmlns:junos="http://xml.juniper.net/junos/23.1R0/junos">
503 <nc:data>
504 <bar:result xmlns:bar="bar">99</bar:result>
505 </nc:data>
506 </nc:rpc-reply>
507 ]]>]]>
508 "#;
509 let expect: Reply<Bar> = Reply {
510 message_id: MessageId(101),
511 inner: DataReply::Data(BarReply(99)),
512 };
513 assert_eq!(
514 expect,
515 PartialReply::from_xml(data)
516 .and_then(Reply::try_from)
517 .unwrap()
518 );
519 }
520
521 #[test]
522 fn deserialize_ok_partial_reply() {
523 let data = r#"
524 <rpc-reply
525 message-id="101"
526 xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
527 <ok/>
528 </rpc-reply>
529 "#;
530 let expect = PartialReply {
531 message_id: MessageId(101),
532 buf: data.into(),
533 };
534 assert_eq!(expect, PartialReply::from_xml(data).unwrap());
535 }
536
537 #[test]
538 fn deserialize_data_partial_reply() {
539 let data = r#"
540 <rpc-reply
541 message-id="101"
542 xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
543 <data><foo/></data>
544 </rpc-reply>
545 "#;
546 let expect = PartialReply {
547 message_id: MessageId(101),
548 buf: data.into(),
549 };
550 assert_eq!(expect, PartialReply::from_xml(data).unwrap());
551 }
552}