Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
153 changes: 147 additions & 6 deletions crates/netconf-proto/src/yanglib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,19 @@
// Copyright (C) 2026-present The NetCalyx Authors.
// Copyright (C) 2025-present The NetGauze Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
// implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use crate::xml_utils::{ParsingError, XmlDeserialize, XmlParser, XmlSerialize, XmlWriter};
use crate::yangparser::{YangDependencies, extract_yang_dependencies};
use crate::{YANG_DATASTORES_NS_STR, YANG_LIBRARY_AUGMENTED_BY_NS, YANG_LIBRARY_NS};
Expand Down Expand Up @@ -2091,21 +2107,47 @@ impl ModuleSetBuilder {
}

/// Produce a YANG library that contains only one module set
///
/// The `content-id` is a SHA-256 fingerprint computed over the module
/// schemas. To make it a stable, canonical fingerprint of the *content*
/// (independent of the order modules/features/submodules were
/// discovered or inserted, e.g. due to BFS dependency traversal order or
/// device-reported ordering), everything that is hashed is first sorted
/// deterministically (by name/revision). Otherwise, semantically
/// identical module sets could produce different content-ids just
/// because of insertion order, causing spurious cache duplication.
pub fn build_yang_lib(self) -> (YangLibrary, HashMap<Box<str>, Box<str>>) {
let default_name: Box<str> = "ALL".into();
let mut content_id = sha2::Sha256::new();
for module in self.module_set.modules().values() {
for feature in module.features() {

let mut modules: Vec<_> = self.module_set.modules().values().collect();
modules.sort_unstable_by(|a, b| a.name().cmp(b.name()));
for module in modules {
let mut features: Vec<&Box<str>> = module.features().iter().collect();
features.sort_unstable();
for feature in features {
content_id.update(feature.as_ref());
}
Comment on lines +2126 to 2130
for submodule in module.submodules() {
let mut submodules: Vec<_> = module.submodules().iter().collect();
submodules.sort_unstable_by(|a, b| a.name().cmp(b.name()));
for submodule in submodules {
content_id.update(self.yang_schemas.get(submodule.name()).unwrap().as_ref());
}
content_id.update(self.yang_schemas.get(module.name()).unwrap().as_ref());
}
for import_only_versions in self.module_set.import_only_modules().values() {
for module in import_only_versions.values() {
for (_, submodule) in module.submodules() {

let mut import_only_names: Vec<_> = self.module_set.import_only_modules().keys().collect();
import_only_names.sort_unstable();
for name in import_only_names {
let import_only_versions = &self.module_set.import_only_modules()[name];
let mut modules: Vec<_> = import_only_versions.values().collect();
modules.sort_unstable_by(|a, b| {
a.name().cmp(b.name()).then(a.revision().cmp(&b.revision()))
});
for module in modules {
let mut submodules: Vec<_> = module.submodules().iter().collect();
submodules.sort_unstable_by(|a, b| a.0.cmp(b.0));
for (_, submodule) in submodules {
content_id.update(self.yang_schemas.get(submodule.name()).unwrap().as_ref());
}
content_id.update(self.yang_schemas.get(module.name()).unwrap().as_ref());
Expand Down Expand Up @@ -2821,4 +2863,103 @@ mod tests {
None
);
}

/// The `content-id` produced by `ModuleSetBuilder::build_yang_lib` must be
/// a canonical fingerprint of the module set content: it should not
/// depend on the order modules (and their features/submodules) were
/// inserted into the builder. Otherwise, semantically identical module
/// sets discovered/traversed in a different order (e.g. BFS dependency
/// resolution visiting modules in a different sequence) would produce
/// different content-ids, causing spurious cache duplication.
#[test]
fn test_build_yang_lib_content_id_is_insertion_order_independent() {
let module_a = Module::new(
"module-a".into(),
Some("2020-01-01".into()),
Comment on lines +2874 to +2878
"urn:example:module-a".into(),
Box::new(["feature-2".into(), "feature-1".into()]),
Box::new([]),
Box::new([Submodule::new(
"module-a-sub".into(),
Some("2020-01-01".into()),
Box::new([]),
)]),
Box::new([]),
Box::new([]),
);
let module_b = Module::new(
"module-b".into(),
Some("2020-02-02".into()),
"urn:example:module-b".into(),
Box::new([]),
Box::new([]),
Box::new([]),
Box::new([]),
Box::new([]),
);

let mut builder1 = ModuleSetBuilder::new("ALL".into());
builder1
.add_module(
module_a.clone(),
"module-a-schema".into(),
&PermissiveVersionChecker,
)
.unwrap();
builder1
.add_submodule_for_module(
"module-a",
Submodule::new(
"module-a-sub".into(),
Some("2020-01-01".into()),
Box::new([]),
),
"module-a-sub-schema".into(),
&PermissiveVersionChecker,
)
.unwrap();
builder1
.add_module(
module_b.clone(),
"module-b-schema".into(),
&PermissiveVersionChecker,
)
.unwrap();
let (yang_lib1, _) = builder1.build_yang_lib();

// Insert the same modules in reverse order, and with module_a's
// features reordered, to also check feature-order independence.
let mut module_a2 = module_a;
module_a2.feature = Box::new(["feature-1".into(), "feature-2".into()]);
let mut builder2 = ModuleSetBuilder::new("ALL".into());
builder2
.add_module(
module_b,
"module-b-schema".into(),
&PermissiveVersionChecker,
)
.unwrap();
builder2
.add_module(
module_a2,
"module-a-schema".into(),
&PermissiveVersionChecker,
)
.unwrap();
builder2
.add_submodule_for_module(
"module-a",
Submodule::new(
"module-a-sub".into(),
Some("2020-01-01".into()),
Box::new([]),
),
"module-a-sub-schema".into(),
&PermissiveVersionChecker,
)
.unwrap();
let (yang_lib2, _) = builder2.build_yang_lib();

assert_eq!(yang_lib1.content_id(), yang_lib2.content_id());
}
}