snix_castore/directoryservice/
order_validator.rs1use std::collections::HashSet;
2use tracing::warn;
3
4use super::Directory;
5use crate::{B3Digest, Node};
6
7pub trait OrderValidator {
8 fn add_directory(&mut self, directory: &Directory) -> bool;
11}
12
13#[derive(Default)]
14pub struct RootToLeavesValidator {
18 expected_digests: HashSet<B3Digest>,
20}
21
22impl RootToLeavesValidator {
23 pub fn new_with_root_digest(root_digest: B3Digest) -> Self {
26 let mut this = Self::default();
27 this.expected_digests.insert(root_digest);
28 this
29 }
30
31 pub fn digest_allowed(&self, digest: &B3Digest) -> bool {
40 self.expected_digests.is_empty() || self.expected_digests.contains(digest)
42 }
43
44 pub fn add_directory_unchecked(&mut self, directory: &Directory) {
46 if self.expected_digests.is_empty() {
48 self.expected_digests.insert(directory.digest());
49 }
50
51 for (_, node) in directory.nodes() {
53 if let Node::Directory { digest, .. } = node {
54 self.expected_digests.insert(digest.clone());
55 }
56 }
57 }
58}
59
60impl OrderValidator for RootToLeavesValidator {
61 fn add_directory(&mut self, directory: &Directory) -> bool {
62 if !self.digest_allowed(&directory.digest()) {
63 return false;
64 }
65 self.add_directory_unchecked(directory);
66 true
67 }
68}
69
70#[derive(Default)]
71pub struct LeavesToRootValidator {
75 allowed_references: HashSet<B3Digest>,
78}
79
80impl OrderValidator for LeavesToRootValidator {
81 fn add_directory(&mut self, directory: &Directory) -> bool {
82 let digest = directory.digest();
83
84 for (_, node) in directory.nodes() {
85 if let Node::Directory {
86 digest: subdir_node_digest,
87 ..
88 } = node
89 {
90 if !self.allowed_references.contains(subdir_node_digest) {
91 warn!(
92 directory.digest = %digest,
93 subdirectory.digest = %subdir_node_digest,
94 "unexpected directory reference"
95 );
96 return false;
97 }
98 }
99 }
100
101 self.allowed_references.insert(digest.clone());
102
103 true
104 }
105}
106
107#[cfg(test)]
108mod tests {
109 use super::{LeavesToRootValidator, RootToLeavesValidator};
110 use crate::directoryservice::Directory;
111 use crate::directoryservice::order_validator::OrderValidator;
112 use crate::fixtures::{DIRECTORY_A, DIRECTORY_B, DIRECTORY_C};
113 use rstest::rstest;
114
115 #[rstest]
116 #[case::empty_directory(&[&*DIRECTORY_A], false)]
118 #[case::simple_closure(&[&*DIRECTORY_A, &*DIRECTORY_B], false)]
120 #[case::same_child(&[&*DIRECTORY_A, &*DIRECTORY_A, &*DIRECTORY_C], false)]
123 #[case::same_child_dedup(&[&*DIRECTORY_A, &*DIRECTORY_C], false)]
125 #[case::unconnected_node(&[&*DIRECTORY_A, &*DIRECTORY_C, &*DIRECTORY_B], false)]
128 #[case::dangling_pointer(&[&*DIRECTORY_B], true)]
130 fn leaves_to_root(
131 #[case] directories_to_upload: &[&Directory],
132 #[case] exp_fail_upload_last: bool,
133 ) {
134 let mut validator = LeavesToRootValidator::default();
135 let len_directories_to_upload = directories_to_upload.len();
136
137 for (i, d) in directories_to_upload.iter().enumerate() {
138 let resp = validator.add_directory(d);
139 if i == len_directories_to_upload - 1 && exp_fail_upload_last {
140 assert!(!resp, "expect last put to fail");
141
142 return;
145 } else {
146 assert!(resp, "expect put to succeed");
147 }
148 }
149 }
150
151 #[rstest]
152 #[case::empty_directory(&*DIRECTORY_A, &[&*DIRECTORY_A], false)]
154 #[case::simple_closure(&*DIRECTORY_B, &[&*DIRECTORY_B, &*DIRECTORY_A], false)]
156 #[case::same_child_dedup(&*DIRECTORY_C, &[&*DIRECTORY_C, &*DIRECTORY_A], false)]
158 #[case::unconnected_node(&*DIRECTORY_C, &[&*DIRECTORY_C, &*DIRECTORY_B], true)]
160 #[case::dangling_pointer(&*DIRECTORY_B, &[&*DIRECTORY_A], true)]
162 fn root_to_leaves(
163 #[case] root: &Directory,
164 #[case] directories_to_upload: &[&Directory],
165 #[case] exp_fail_upload_last: bool,
166 ) {
167 let mut validator = RootToLeavesValidator::new_with_root_digest(root.digest());
168 let len_directories_to_upload = directories_to_upload.len();
169
170 for (i, d) in directories_to_upload.iter().enumerate() {
171 let resp1 = validator.digest_allowed(&d.digest());
172 let resp = validator.add_directory(d);
173 assert_eq!(
174 resp1, resp,
175 "digest_allowed should return the same value as add_directory"
176 );
177 if i == len_directories_to_upload - 1 && exp_fail_upload_last {
178 assert!(!resp, "expect last put to fail");
179
180 return;
183 } else {
184 assert!(resp, "expect put to succeed");
185 }
186 }
187 }
188}