Skip to main content

netgauze_netconf_proto/
lib.rs

1// Copyright (C) 2025-present The NetGauze Authors.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//    http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
12// implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16//! Low-level NETCONF protocol types and helpers.
17//!
18//! This crate provides the NETCONF message model, XML parsing/serialization
19//! traits, SSH client wiring, and YANG library utilities. It is designed as a
20//! building block for higher-level NETCONF or telemetry services.
21//!
22//! Start with [client] for SSH connectivity and session helpers, and [protocol]
23//! for the message types, and [xml_utils] for XML parsing and writing
24//! utilities.
25
26use quick_xml::name::Namespace;
27
28pub mod capabilities;
29pub mod client;
30pub mod codec;
31pub mod protocol;
32pub mod xml_utils;
33pub mod yang_push;
34pub mod yanglib;
35pub mod yangparser;
36
37pub(crate) const NETCONF_NS_STR: &str = "urn:ietf:params:xml:ns:netconf:base:1.0";
38pub(crate) const NETCONF_NS: Namespace<'static> = Namespace(NETCONF_NS_STR.as_bytes());
39pub(crate) const NETCONF_MONITORING_NS_STR: &str =
40    "urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring";
41pub(crate) const NETCONF_MONITORING_NS: Namespace<'static> =
42    Namespace(NETCONF_MONITORING_NS_STR.as_bytes());
43pub(crate) const YANG_LIBRARY_NS_STR: &str = "urn:ietf:params:xml:ns:yang:ietf-yang-library";
44pub(crate) const YANG_LIBRARY_NS: Namespace<'static> = Namespace(YANG_LIBRARY_NS_STR.as_bytes());
45pub(crate) const YANG_LIBRARY_AUGMENTED_BY_NS_STR: &str =
46    "urn:ietf:params:xml:ns:yang:ietf-yang-library-augmentedby";
47pub(crate) const YANG_LIBRARY_AUGMENTED_BY_NS: Namespace<'static> =
48    Namespace(YANG_LIBRARY_AUGMENTED_BY_NS_STR.as_bytes());
49pub(crate) const YANG_DATASTORES_NS_STR: &str = "urn:ietf:params:xml:ns:yang:ietf-datastores";
50
51#[cfg(test)]
52mod tests {
53    use crate::xml_utils::{ParsingError, XmlDeserialize, XmlParser, XmlSerialize, XmlWriter};
54    use quick_xml::NsReader;
55    use std::{fmt, io};
56
57    pub(crate) fn test_parse_error<
58        'a,
59        T: XmlDeserialize<'a, T> + XmlSerialize + PartialEq + fmt::Debug,
60    >(
61        input_str: &'_ str,
62    ) -> Result<(), ParsingError> {
63        // Check first we can deserialize value correctly
64        let cursor = io::Cursor::new(input_str);
65        let mut reader = NsReader::from_reader(cursor);
66        reader.config_mut().trim_text(false);
67        let mut xml_parser = XmlParser::new(reader)?;
68        let ret = T::xml_deserialize(&mut xml_parser);
69        assert!(ret.is_err(), "Expected an error but parsed successfully");
70        ret.map(|_| ())
71    }
72
73    /// Test function for types that own all their data (no borrowed references)
74    /// This version can do round-trip testing
75    pub(crate) fn test_xml_value_owned<'a, T>(
76        input_str: &'_ str,
77        expected: T,
78    ) -> Result<(), ParsingError>
79    where
80        T: XmlDeserialize<'a, T> + XmlSerialize + PartialEq + fmt::Debug + Clone,
81    {
82        // Check first we can deserialize value correctly
83        let mut reader = NsReader::from_str(input_str);
84        reader.config_mut().trim_text(false);
85        let mut xml_parser = XmlParser::new(reader)?;
86        let parsed = T::xml_deserialize(&mut xml_parser);
87        assert!(parsed.is_ok(), "{parsed:?}");
88        let parsed = parsed?;
89        assert_eq!(
90            parsed, expected,
91            "Expecting:\n{expected:#?}\nparsed:\n{parsed:#?}"
92        );
93
94        // Now test round-trip: serialize and deserialize again
95        let writer = quick_xml::writer::Writer::new(io::Cursor::new(Vec::new()));
96        let mut writer = XmlWriter::new(writer);
97        expected.clone().xml_serialize(&mut writer)?;
98
99        let serialize_vec = writer.into_inner().into_inner();
100        let serialize_str =
101            String::from_utf8(serialize_vec).expect("Serialized value is not valid UTF-8");
102
103        // Deserialize from the serialized string
104        let mut reader = NsReader::from_reader(serialize_str.as_bytes());
105        reader.config_mut().trim_text(false);
106        let mut xml_parser = XmlParser::new(reader)?;
107        let parsed_again = T::xml_deserialize(&mut xml_parser)?;
108        assert_eq!(parsed_again, expected);
109
110        Ok(())
111    }
112
113    pub(crate) fn test_xml_value<
114        'a,
115        T: XmlDeserialize<'a, T> + XmlSerialize + PartialEq + fmt::Debug + Clone,
116    >(
117        input_str: &'_ str,
118        expected: T,
119    ) -> Result<(), ParsingError> {
120        // Check first we can deserialize value correctly
121        let mut reader = NsReader::from_str(input_str);
122        reader.config_mut().trim_text(false);
123        let mut xml_parser = XmlParser::new(reader)?;
124        let parsed = T::xml_deserialize(&mut xml_parser);
125        assert!(parsed.is_ok(), "{parsed:?}");
126        let parsed = parsed?;
127        assert_eq!(
128            parsed, expected,
129            "Expecting:\n{expected:#?}\nparsed:\n{parsed:#?}"
130        );
131
132        // Check after we serialize the test value we can deserialize back the same
133        // value
134        let writer = quick_xml::writer::Writer::new(io::Cursor::new(Vec::new()));
135        let mut writer = XmlWriter::new(writer);
136        parsed.xml_serialize(&mut writer)?;
137
138        let serialize_str = String::from_utf8(writer.into_inner().into_inner())
139            .expect("Serialized value is not valid UTF-8");
140
141        let reader = NsReader::from_str(&serialize_str);
142        let mut xml_parser = XmlParser::new(reader)?;
143        let parsed = <T as XmlDeserialize<T>>::xml_deserialize(&mut xml_parser)?;
144        assert_eq!(parsed, expected, "In second round of serialized value");
145        Ok(())
146    }
147}