1use std::fmt;
2use thiserror::Error;
3
4#[derive(Default, Clone, Debug)]
7pub struct Dictionary {
8 pub attributes: Vec<DictionaryAttribute>,
10 pub values: Vec<DictionaryValue>,
12 pub vendors: Vec<DictionaryVendor>,
14}
15impl Dictionary {
16 pub fn merge(d1: &Dictionary, d2: &Dictionary) -> Result<Dictionary, DictionaryError> {
27 for attr in &d2.attributes {
28 if d1.attributes.iter().any(|a| a.name == attr.name) {
29 return Err(DictionaryError::Conflict(format!(
30 "duplicate attribute name: {}",
31 attr.name
32 )));
33 }
34 if d1.attributes.iter().any(|a| a.oid == attr.oid) {
35 return Err(DictionaryError::Conflict(format!(
36 "duplicate attribute OID: {}",
37 attr.oid
38 )));
39 }
40 }
41
42 for vendor in &d2.vendors {
43 let existing_by_name = d1.vendors.iter().find(|v| v.name == vendor.name);
44 let existing_by_code = d1.vendors.iter().find(|v| v.code == vendor.code);
45
46 if existing_by_name != existing_by_code {
48 return Err(DictionaryError::Conflict(format!(
49 "conflicting vendor definition: {} ({})",
50 vendor.name, vendor.code
51 )));
52 }
53
54 if let Some(existing) = existing_by_name {
56 for attr in &vendor.attributes {
57 if existing.attributes.iter().any(|a| a.name == attr.name) {
58 return Err(DictionaryError::Conflict(format!(
59 "duplicate vendor attribute name: {}",
60 attr.name
61 )));
62 }
63 if existing.attributes.iter().any(|a| a.oid == attr.oid) {
64 return Err(DictionaryError::Conflict(format!(
65 "duplicate vendor attribute OID: {}",
66 attr.oid
67 )));
68 }
69 }
70 }
71 }
72
73 let mut new_dict = d1.clone();
74
75 new_dict.attributes.extend(d2.attributes.clone());
77 new_dict.values.extend(d2.values.clone());
78
79 for v2 in &d2.vendors {
81 if let Some(v1) = new_dict.vendors.iter_mut().find(|v| v.code == v2.code) {
82 v1.attributes.extend(v2.attributes.clone());
84 v1.values.extend(v2.values.clone());
85 } else {
86 new_dict.vendors.push(v2.clone());
87 }
88 }
89
90 Ok(new_dict)
91 }
92}
93#[derive(Error, Debug)]
94pub enum DictionaryError {
95 #[error("Dictionary conflict: {0}")]
96 Conflict(String),
97}
98#[derive(Debug, PartialEq, Eq, Clone)]
99pub enum AttributeType {
100 String,
101 Integer,
102 IpAddr,
103 Octets,
104 Date,
105 Vsa,
106 Ether,
107 ABinary,
108 Byte,
109 Short,
110 Signed,
111 Tlv,
112 Ipv4Prefix,
113 Ifid,
114 Ipv6Addr,
115 Ipv6Prefix,
116 InterfaceId,
117 Unknown(String),
119}
120#[derive(Debug, PartialEq, Eq, Clone)]
121pub struct DictionaryAttribute {
122 pub name: String,
123 pub oid: Oid,
124 pub attr_type: AttributeType,
125 pub size: SizeFlag,
126 pub encrypt: Option<u8>,
127 pub has_tag: Option<bool>,
128 pub concat: Option<bool>,
129}
130#[derive(Debug, PartialEq, Eq, Clone, Default)]
133pub struct Oid {
134 pub vendor: Option<u32>,
136 pub code: u32,
138}
139
140impl fmt::Display for Oid {
141 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
142 match self.vendor {
143 Some(v) => write!(f, "{}-{}", v, self.code),
144 None => write!(f, "{}", self.code),
145 }
146 }
147}
148#[derive(Debug, PartialEq, Eq, Clone, Default)]
150pub enum SizeFlag {
151 #[default]
153 Any,
154 Exact(u32),
156 Range(u32, u32),
158}
159
160impl SizeFlag {
161 pub fn is_constrained(&self) -> bool {
162 !matches!(self, SizeFlag::Any)
163 }
164}
165#[derive(Debug, PartialEq, Eq, Clone)]
167pub struct DictionaryValue {
168 pub attribute_name: String,
170 pub name: String,
172 pub value: u64,
174}
175#[derive(Debug, PartialEq, Eq, Clone)]
177pub struct DictionaryVendor {
178 pub name: String,
180 pub code: u32,
182 pub attributes: Vec<DictionaryAttribute>,
184 pub values: Vec<DictionaryValue>,
186}
187
188#[cfg(test)]
189mod tests {
190 use super::*;
191
192 fn mock_attr(name: &str, code: u32) -> DictionaryAttribute {
193 DictionaryAttribute {
194 name: name.to_string(),
195 oid: Oid { vendor: None, code },
196 attr_type: AttributeType::Integer,
197 size: SizeFlag::Any,
198 encrypt: None,
199 has_tag: None,
200 concat: None,
201 }
202 }
203
204 #[test]
205 fn test_merge_success() {
206 let mut d1 = Dictionary::default();
207 d1.attributes.push(mock_attr("User-Name", 1));
208
209 let mut d2 = Dictionary::default();
210 d2.attributes.push(mock_attr("Password", 2));
211
212 let merged = Dictionary::merge(&d1, &d2).unwrap();
213 assert_eq!(merged.attributes.len(), 2);
214 }
215
216 #[test]
217 fn test_merge_conflict_name() {
218 let mut d1 = Dictionary::default();
219 d1.attributes.push(mock_attr("User-Name", 1));
220
221 let mut d2 = Dictionary::default();
222 d2.attributes.push(mock_attr("User-Name", 2)); let result = Dictionary::merge(&d1, &d2);
225 assert!(matches!(result, Err(DictionaryError::Conflict(m)) if m.contains("name")));
226 }
227
228 #[test]
229 fn test_merge_conflict_oid() {
230 let mut d1 = Dictionary::default();
231 d1.attributes.push(mock_attr("User-Name", 1));
232
233 let mut d2 = Dictionary::default();
234 d2.attributes.push(mock_attr("Login-Name", 1)); let result = Dictionary::merge(&d1, &d2);
237 assert!(matches!(result, Err(DictionaryError::Conflict(m)) if m.contains("OID")));
238 }
239
240 #[test]
241 fn test_vendor_merge_and_conflict() {
242 let v1 = DictionaryVendor {
243 name: "Cisco".to_string(),
244 code: 9,
245 attributes: vec![mock_attr("Cisco-AVPair", 1)],
246 values: vec![],
247 };
248
249 let mut d1 = Dictionary::default();
250 d1.vendors.push(v1);
251
252 let v2 = DictionaryVendor {
254 name: "Cisco".to_string(),
255 code: 9,
256 attributes: vec![mock_attr("Cisco-Other", 2)],
257 values: vec![],
258 };
259 let mut d2 = Dictionary::default();
260 d2.vendors.push(v2);
261
262 let merged = Dictionary::merge(&d1, &d2).expect("Should merge vendor attributes");
263 assert_eq!(merged.vendors[0].attributes.len(), 2);
264
265 let v3 = DictionaryVendor {
267 name: "Cisco".to_string(),
268 code: 9,
269 attributes: vec![mock_attr("Cisco-Duplicate", 1)], values: vec![],
271 };
272 let mut d3 = Dictionary::default();
273 d3.vendors.push(v3);
274
275 let result = Dictionary::merge(&d1, &d3);
276 assert!(result.is_err());
277 }
278
279 #[test]
280 fn test_vendor_mismatch_definition() {
281 let mut d1 = Dictionary::default();
282 d1.vendors.push(DictionaryVendor {
283 name: "Cisco".to_string(),
284 code: 9,
285 attributes: vec![],
286 values: vec![],
287 });
288
289 let mut d2 = Dictionary::default();
290 d2.vendors.push(DictionaryVendor {
291 name: "Cisco".to_string(),
292 code: 10, attributes: vec![],
294 values: vec![],
295 });
296
297 let result = Dictionary::merge(&d1, &d2);
298 assert!(matches!(result, Err(DictionaryError::Conflict(_))));
299 }
300}