1use ndn_packet::{Name, NameComponent};
2use std::collections::HashMap;
3use std::sync::Arc;
4
5use crate::lvs::{LvsError, LvsModel};
6
7#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
9pub enum PatternParseError {
10 #[error("empty pattern string")]
11 Empty,
12 #[error("unclosed capture variable (missing '>')")]
13 UnclosedCapture,
14 #[error("MultiCapture ('**') must be the last component")]
15 MultiCaptureNotLast,
16 #[error("rule must have exactly one '=>' separator")]
17 BadRuleSeparator,
18}
19
20#[derive(Clone, Debug, PartialEq, Eq)]
22pub enum PatternComponent {
23 Literal(NameComponent),
25 Capture(Arc<str>),
27 MultiCapture(Arc<str>),
29}
30
31#[derive(Clone, Debug, PartialEq, Eq)]
48pub struct NamePattern(pub Vec<PatternComponent>);
49
50impl NamePattern {
51 pub fn parse(s: &str) -> Result<Self, PatternParseError> {
65 let s = s.trim();
66 if s.is_empty() {
67 return Err(PatternParseError::Empty);
68 }
69 let s = s.strip_prefix('/').unwrap_or(s);
71 if s.is_empty() {
72 return Ok(Self(vec![]));
74 }
75
76 let mut components = Vec::new();
77 let parts: Vec<&str> = s.split('/').collect();
78 let last_idx = parts.len().saturating_sub(1);
79
80 for (i, part) in parts.iter().enumerate() {
81 if let Some(inner) = part.strip_prefix('<') {
82 let var = inner
83 .strip_suffix('>')
84 .ok_or(PatternParseError::UnclosedCapture)?;
85 if let Some(multi_var) = var.strip_prefix("**") {
86 if i != last_idx {
87 return Err(PatternParseError::MultiCaptureNotLast);
88 }
89 components.push(PatternComponent::MultiCapture(Arc::from(multi_var)));
90 } else {
91 components.push(PatternComponent::Capture(Arc::from(var)));
92 }
93 } else {
94 let comp = NameComponent::generic(bytes::Bytes::copy_from_slice(part.as_bytes()));
95 components.push(PatternComponent::Literal(comp));
96 }
97 }
98
99 Ok(Self(components))
100 }
101
102 pub fn matches(&self, name: &Name, bindings: &mut HashMap<Arc<str>, NameComponent>) -> bool {
105 let components = name.components();
106 let mut name_idx = 0;
107
108 for pat in &self.0 {
109 match pat {
110 PatternComponent::Literal(c) => {
111 if name_idx >= components.len() || &components[name_idx] != c {
112 return false;
113 }
114 name_idx += 1;
115 }
116 PatternComponent::Capture(var) => {
117 if name_idx >= components.len() {
118 return false;
119 }
120 let comp = components[name_idx].clone();
121 if let Some(existing) = bindings.get(var) {
122 if existing != &comp {
123 return false; }
125 } else {
126 bindings.insert(Arc::clone(var), comp);
127 }
128 name_idx += 1;
129 }
130 PatternComponent::MultiCapture(_var) => {
131 name_idx = components.len();
133 }
134 }
135 }
136 name_idx == components.len()
137 }
138}
139
140#[derive(Clone, Debug, PartialEq, Eq)]
152pub struct SchemaRule {
153 pub data_pattern: NamePattern,
154 pub key_pattern: NamePattern,
155}
156
157impl SchemaRule {
158 pub fn parse(s: &str) -> Result<Self, PatternParseError> {
160 let parts: Vec<&str> = s.splitn(2, "=>").collect();
161 if parts.len() != 2 {
162 return Err(PatternParseError::BadRuleSeparator);
163 }
164 let data_pattern = NamePattern::parse(parts[0].trim())?;
165 let key_pattern = NamePattern::parse(parts[1].trim())?;
166 Ok(Self {
167 data_pattern,
168 key_pattern,
169 })
170 }
171
172 pub fn check(&self, data_name: &Name, key_name: &Name) -> bool {
174 let mut bindings = HashMap::new();
175 self.data_pattern.matches(data_name, &mut bindings)
176 && self.key_pattern.matches(key_name, &mut bindings)
177 }
178}
179
180impl std::fmt::Display for NamePattern {
181 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
183 if self.0.is_empty() {
184 return f.write_str("/");
185 }
186 for comp in &self.0 {
187 f.write_str("/")?;
188 match comp {
189 PatternComponent::Literal(nc) => {
190 f.write_str(&String::from_utf8_lossy(&nc.value))?;
191 }
192 PatternComponent::Capture(var) => {
193 write!(f, "<{var}>")?;
194 }
195 PatternComponent::MultiCapture(var) => {
196 write!(f, "<**{var}>")?;
197 }
198 }
199 }
200 Ok(())
201 }
202}
203
204impl std::fmt::Display for SchemaRule {
205 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
207 write!(f, "{} => {}", self.data_pattern, self.key_pattern)
208 }
209}
210
211#[derive(Clone, Debug, Default)]
230pub struct TrustSchema {
231 rules: Vec<SchemaRule>,
232 lvs: Option<Arc<LvsModel>>,
233}
234
235impl TrustSchema {
236 pub fn new() -> Self {
237 Self {
238 rules: Vec::new(),
239 lvs: None,
240 }
241 }
242
243 pub fn add_rule(&mut self, rule: SchemaRule) {
244 self.rules.push(rule);
245 }
246
247 pub fn from_lvs_binary(wire: &[u8]) -> Result<Self, LvsError> {
263 let model = LvsModel::decode(wire)?;
264 Ok(Self {
265 rules: Vec::new(),
266 lvs: Some(Arc::new(model)),
267 })
268 }
269
270 pub fn lvs_model(&self) -> Option<&LvsModel> {
274 self.lvs.as_deref()
275 }
276
277 pub fn allows(&self, data_name: &Name, key_name: &Name) -> bool {
281 if self.rules.iter().any(|r| r.check(data_name, key_name)) {
282 return true;
283 }
284 if let Some(lvs) = self.lvs.as_deref() {
285 return lvs.check(data_name, key_name);
286 }
287 false
288 }
289
290 pub fn rules(&self) -> &[SchemaRule] {
293 &self.rules
294 }
295
296 pub fn remove_rule(&mut self, index: usize) -> SchemaRule {
300 self.rules.remove(index)
301 }
302
303 pub fn clear(&mut self) {
306 self.rules.clear();
307 self.lvs = None;
308 }
309
310 pub fn accept_all() -> Self {
314 let mut schema = Self::new();
315 schema.add_rule(SchemaRule {
316 data_pattern: NamePattern(vec![PatternComponent::MultiCapture("_".into())]),
317 key_pattern: NamePattern(vec![PatternComponent::MultiCapture("_".into())]),
318 });
319 schema
320 }
321
322 pub fn hierarchical() -> Self {
329 let mut schema = Self::new();
330 schema.add_rule(SchemaRule {
331 data_pattern: NamePattern(vec![
332 PatternComponent::Capture("org".into()),
333 PatternComponent::MultiCapture("_data".into()),
334 ]),
335 key_pattern: NamePattern(vec![
336 PatternComponent::Capture("org".into()),
337 PatternComponent::MultiCapture("_key".into()),
338 ]),
339 });
340 schema
341 }
342}
343
344#[cfg(test)]
345mod tests {
346 use super::*;
347 use bytes::Bytes;
348 use ndn_packet::NameComponent;
349
350 fn comp(s: &'static str) -> NameComponent {
351 NameComponent::generic(Bytes::from_static(s.as_bytes()))
352 }
353 fn name(components: &[&'static str]) -> Name {
354 Name::from_components(components.iter().map(|s| comp(s)))
355 }
356
357 #[test]
358 fn literal_matches_exact() {
359 let pat = NamePattern(vec![PatternComponent::Literal(comp("sensor"))]);
360 assert!(pat.matches(&name(&["sensor"]), &mut HashMap::new()));
361 }
362
363 #[test]
364 fn literal_rejects_wrong_component() {
365 let pat = NamePattern(vec![PatternComponent::Literal(comp("sensor"))]);
366 assert!(!pat.matches(&name(&["actuator"]), &mut HashMap::new()));
367 }
368
369 #[test]
370 fn literal_rejects_extra_components() {
371 let pat = NamePattern(vec![PatternComponent::Literal(comp("a"))]);
372 assert!(!pat.matches(&name(&["a", "b"]), &mut HashMap::new()));
373 }
374
375 #[test]
376 fn capture_binds_variable() {
377 let pat = NamePattern(vec![
378 PatternComponent::Literal(comp("sensor")),
379 PatternComponent::Capture(Arc::from("node")),
380 ]);
381 let mut bindings = HashMap::new();
382 assert!(pat.matches(&name(&["sensor", "node1"]), &mut bindings));
383 assert_eq!(bindings[&Arc::from("node")], comp("node1"));
384 }
385
386 #[test]
387 fn capture_enforces_consistency() {
388 let var: Arc<str> = Arc::from("node");
389 let data_pat = NamePattern(vec![PatternComponent::Capture(Arc::clone(&var))]);
390 let key_pat = NamePattern(vec![PatternComponent::Capture(Arc::clone(&var))]);
391 let mut bindings = HashMap::new();
392 assert!(data_pat.matches(&name(&["n1"]), &mut bindings));
394 assert!(key_pat.matches(&name(&["n1"]), &mut bindings.clone()));
396 assert!(!key_pat.matches(&name(&["n2"]), &mut bindings));
398 }
399
400 #[test]
401 fn multi_capture_consumes_remaining() {
402 let pat = NamePattern(vec![
403 PatternComponent::Literal(comp("prefix")),
404 PatternComponent::MultiCapture(Arc::from("rest")),
405 ]);
406 assert!(pat.matches(&name(&["prefix", "a", "b", "c"]), &mut HashMap::new()));
407 }
408
409 #[test]
410 fn schema_rule_allows_matching_pair() {
411 let rule = SchemaRule {
412 data_pattern: NamePattern(vec![PatternComponent::Literal(comp("data"))]),
413 key_pattern: NamePattern(vec![PatternComponent::Literal(comp("key"))]),
414 };
415 assert!(rule.check(&name(&["data"]), &name(&["key"])));
416 assert!(!rule.check(&name(&["data"]), &name(&["wrong"])));
417 }
418
419 #[test]
420 fn trust_schema_allows_via_any_rule() {
421 let mut schema = TrustSchema::new();
422 schema.add_rule(SchemaRule {
423 data_pattern: NamePattern(vec![PatternComponent::Literal(comp("data"))]),
424 key_pattern: NamePattern(vec![PatternComponent::Literal(comp("key"))]),
425 });
426 assert!(schema.allows(&name(&["data"]), &name(&["key"])));
427 assert!(!schema.allows(&name(&["data"]), &name(&["wrong"])));
428 }
429
430 #[test]
431 fn empty_schema_rejects_everything() {
432 let schema = TrustSchema::new();
433 assert!(!schema.allows(&name(&["a"]), &name(&["b"])));
434 }
435
436 #[test]
437 fn accept_all_allows_any_pair() {
438 let schema = TrustSchema::accept_all();
439 assert!(schema.allows(&name(&["a", "b"]), &name(&["x", "y", "z"])));
440 assert!(schema.allows(&name(&["data"]), &name(&["key"])));
441 }
442
443 #[test]
444 fn pattern_parse_literal() {
445 let p = NamePattern::parse("/sensor/temp").unwrap();
446 assert_eq!(p.0.len(), 2);
447 assert!(matches!(&p.0[0], PatternComponent::Literal(nc) if nc.value.as_ref() == b"sensor"));
448 assert!(matches!(&p.0[1], PatternComponent::Literal(nc) if nc.value.as_ref() == b"temp"));
449 }
450
451 #[test]
452 fn pattern_parse_captures() {
453 let p = NamePattern::parse("/sensor/<node>/KEY/<id>").unwrap();
454 assert_eq!(p.0.len(), 4);
455 assert!(matches!(&p.0[0], PatternComponent::Literal(_)));
456 assert!(matches!(&p.0[1], PatternComponent::Capture(v) if v.as_ref() == "node"));
457 assert!(matches!(&p.0[2], PatternComponent::Literal(_)));
458 assert!(matches!(&p.0[3], PatternComponent::Capture(v) if v.as_ref() == "id"));
459 }
460
461 #[test]
462 fn pattern_parse_multi_capture_at_end() {
463 let p = NamePattern::parse("/org/<**rest>").unwrap();
464 assert_eq!(p.0.len(), 2);
465 assert!(matches!(&p.0[1], PatternComponent::MultiCapture(v) if v.as_ref() == "rest"));
466 }
467
468 #[test]
469 fn pattern_parse_multi_capture_not_last_errors() {
470 assert!(matches!(
471 NamePattern::parse("/org/<**rest>/extra"),
472 Err(PatternParseError::MultiCaptureNotLast)
473 ));
474 }
475
476 #[test]
477 fn pattern_parse_unclosed_capture_errors() {
478 assert!(matches!(
479 NamePattern::parse("/sensor/<node"),
480 Err(PatternParseError::UnclosedCapture)
481 ));
482 }
483
484 #[test]
485 fn pattern_roundtrip_text() {
486 let s = "/sensor/<node>/KEY/<id>";
487 let p = NamePattern::parse(s).unwrap();
488 assert_eq!(p.to_string(), s);
489 }
490
491 #[test]
492 fn pattern_roundtrip_multi() {
493 let s = "/org/<**rest>";
494 let p = NamePattern::parse(s).unwrap();
495 assert_eq!(p.to_string(), s);
496 }
497
498 #[test]
499 fn rule_parse_roundtrip() {
500 let s = "/sensor/<node>/<type> => /sensor/<node>/KEY/<id>";
501 let r = SchemaRule::parse(s).unwrap();
502 assert_eq!(r.to_string(), s);
503 }
504
505 #[test]
506 fn rule_parse_bad_separator_errors() {
507 assert!(matches!(
508 SchemaRule::parse("/a /b"),
509 Err(PatternParseError::BadRuleSeparator)
510 ));
511 }
512
513 #[test]
514 fn schema_remove_rule() {
515 let mut schema = TrustSchema::new();
516 schema.add_rule(SchemaRule {
517 data_pattern: NamePattern(vec![PatternComponent::Literal(comp("data"))]),
518 key_pattern: NamePattern(vec![PatternComponent::Literal(comp("key"))]),
519 });
520 assert!(schema.allows(&name(&["data"]), &name(&["key"])));
521 schema.remove_rule(0);
522 assert!(!schema.allows(&name(&["data"]), &name(&["key"])));
523 }
524
525 #[test]
526 fn schema_rules_returns_slice() {
527 let mut schema = TrustSchema::new();
528 schema.add_rule(SchemaRule {
529 data_pattern: NamePattern(vec![PatternComponent::Literal(comp("d"))]),
530 key_pattern: NamePattern(vec![PatternComponent::Literal(comp("k"))]),
531 });
532 assert_eq!(schema.rules().len(), 1);
533 }
534
535 fn lvs_hierarchical_fixture() -> Vec<u8> {
541 use crate::lvs::type_number as tn;
542 use bytes::BytesMut;
543 use ndn_tlv::TlvWriter;
544
545 fn write_tlv(buf: &mut BytesMut, t: u64, v: &[u8]) {
546 let mut w = TlvWriter::new();
547 w.write_tlv(t, v);
548 buf.extend_from_slice(&w.finish());
549 }
550 fn uint_tlv(buf: &mut BytesMut, t: u64, v: u64) {
551 let be = if v <= u8::MAX as u64 {
552 vec![v as u8]
553 } else {
554 (v as u32).to_be_bytes().to_vec()
555 };
556 write_tlv(buf, t, &be);
557 }
558 fn write_cv(buf: &mut BytesMut, bytes: &[u8]) {
560 let mut nc = Vec::with_capacity(2 + bytes.len());
561 nc.push(0x08);
562 nc.push(bytes.len() as u8);
563 nc.extend_from_slice(bytes);
564 write_tlv(buf, tn::COMPONENT_VALUE, &nc);
565 }
566
567 let mut out = BytesMut::new();
568 uint_tlv(&mut out, tn::VERSION, crate::lvs::LVS_VERSION);
569 uint_tlv(&mut out, tn::NODE_ID, 0);
570 uint_tlv(&mut out, tn::NAMED_PATTERN_NUM, 0);
571
572 {
574 let mut node = BytesMut::new();
575 uint_tlv(&mut node, tn::NODE_ID, 0);
576 {
577 let mut ve = BytesMut::new();
578 uint_tlv(&mut ve, tn::NODE_ID, 1);
579 write_cv(&mut ve, b"app");
580 write_tlv(&mut node, tn::VALUE_EDGE, &ve);
581 }
582 {
583 let mut ve = BytesMut::new();
584 uint_tlv(&mut ve, tn::NODE_ID, 2);
585 write_cv(&mut ve, b"key");
586 write_tlv(&mut node, tn::VALUE_EDGE, &ve);
587 }
588 write_tlv(&mut out, tn::NODE, &node);
589 }
590 {
592 let mut node = BytesMut::new();
593 uint_tlv(&mut node, tn::NODE_ID, 1);
594 uint_tlv(&mut node, tn::PARENT_ID, 0);
595 uint_tlv(&mut node, tn::KEY_NODE_ID, 2);
596 write_tlv(&mut out, tn::NODE, &node);
597 }
598 {
600 let mut node = BytesMut::new();
601 uint_tlv(&mut node, tn::NODE_ID, 2);
602 uint_tlv(&mut node, tn::PARENT_ID, 0);
603 write_tlv(&mut out, tn::NODE, &node);
604 }
605 out.to_vec()
606 }
607
608 #[test]
609 fn trust_schema_from_lvs_binary_roundtrip() {
610 let schema = TrustSchema::from_lvs_binary(&lvs_hierarchical_fixture()).expect("LVS import");
611 assert!(schema.lvs_model().is_some());
612 assert!(schema.allows(&name(&["app"]), &name(&["key"])));
613 assert!(!schema.allows(&name(&["app"]), &name(&["wrong"])));
614 assert!(!schema.allows(&name(&["stranger"]), &name(&["key"])));
615 }
616
617 #[test]
618 fn trust_schema_mixes_native_rules_with_lvs_model() {
619 let mut schema = TrustSchema::from_lvs_binary(&lvs_hierarchical_fixture()).unwrap();
620 schema.add_rule(SchemaRule::parse("/native => /native/KEY").unwrap());
623
624 assert!(schema.allows(&name(&["app"]), &name(&["key"])));
626 assert!(schema.allows(&name(&["native"]), &name(&["native", "KEY"])));
628 assert!(!schema.allows(&name(&["foo"]), &name(&["bar"])));
630 }
631
632 #[test]
633 fn trust_schema_lvs_model_accessor_returns_parsed_model() {
634 let schema = TrustSchema::from_lvs_binary(&lvs_hierarchical_fixture()).unwrap();
635 let model = schema.lvs_model().expect("lvs model set");
636 assert_eq!(model.nodes.len(), 3);
637 assert!(!model.uses_user_functions());
638 }
639
640 #[test]
641 fn trust_schema_from_lvs_binary_bad_version_errors() {
642 use crate::lvs::LvsError;
643 let mut bad = lvs_hierarchical_fixture();
644 bad.clear();
648 use crate::lvs::type_number as tn;
649 use bytes::BytesMut;
650 use ndn_tlv::TlvWriter;
651 let mut out = BytesMut::new();
652 {
653 let mut w = TlvWriter::new();
654 w.write_tlv(tn::VERSION, &0xDEADBEEFu32.to_be_bytes());
655 out.extend_from_slice(&w.finish());
656 let mut w = TlvWriter::new();
657 w.write_tlv(tn::NODE_ID, &[0u8]);
658 out.extend_from_slice(&w.finish());
659 let mut w = TlvWriter::new();
660 w.write_tlv(tn::NAMED_PATTERN_NUM, &[0u8]);
661 out.extend_from_slice(&w.finish());
662 }
663 let err = TrustSchema::from_lvs_binary(&out).unwrap_err();
664 assert!(matches!(err, LvsError::UnsupportedVersion { .. }));
665 }
666
667 #[test]
670 fn hierarchical_requires_matching_first_component() {
671 let schema = TrustSchema::hierarchical();
672 assert!(schema.allows(&name(&["org", "data"]), &name(&["org", "KEY", "k1"])));
674 assert!(!schema.allows(&name(&["orgA", "data"]), &name(&["orgB", "KEY", "k1"])));
676 assert!(schema.allows(
678 &name(&["org", "dept", "sensor", "temp"]),
679 &name(&["org", "dept", "KEY", "k1"])
680 ));
681 }
682}