1use std::sync::Arc;
2
3use ndn_packet::Name;
4
5use crate::NameTrie;
6
7#[derive(Clone, Debug, PartialEq, Eq)]
13pub struct FibNexthop {
14 pub face_id: u32,
15 pub cost: u32,
16}
17
18#[derive(Clone, Debug)]
20pub struct FibEntry {
21 pub nexthops: Vec<FibNexthop>,
22}
23
24impl FibEntry {
25 pub fn new(nexthops: Vec<FibNexthop>) -> Self {
26 Self { nexthops }
27 }
28}
29
30pub struct Fib(NameTrie<Arc<FibEntry>>);
36
37impl Fib {
38 pub fn new() -> Self {
39 Self(NameTrie::new())
40 }
41
42 pub fn lpm(&self, name: &Name) -> Option<Arc<FibEntry>> {
44 self.0.lpm(name)
45 }
46
47 pub fn get(&self, prefix: &Name) -> Option<Arc<FibEntry>> {
49 self.0.get(prefix)
50 }
51
52 pub fn insert(&self, prefix: &Name, entry: FibEntry) {
54 self.0.insert(prefix, Arc::new(entry));
55 }
56
57 pub fn add_nexthop(&self, prefix: &Name, nexthop: FibNexthop) {
59 let nexthops = match self.0.get(prefix) {
60 Some(existing) => {
61 let mut v = existing.nexthops.clone();
62 v.push(nexthop);
63 v
64 }
65 None => vec![nexthop],
66 };
67 self.0.insert(prefix, Arc::new(FibEntry { nexthops }));
68 }
69
70 pub fn remove(&self, prefix: &Name) {
72 self.0.remove(prefix);
73 }
74}
75
76impl Default for Fib {
77 fn default() -> Self {
78 Self::new()
79 }
80}
81
82#[cfg(test)]
83mod tests {
84 use super::*;
85 use bytes::Bytes;
86 use ndn_packet::NameComponent;
87
88 fn name(components: &[&str]) -> Name {
89 Name::from_components(
90 components
91 .iter()
92 .map(|s| NameComponent::generic(Bytes::copy_from_slice(s.as_bytes()))),
93 )
94 }
95
96 fn nexthop(face_id: u32, cost: u32) -> FibNexthop {
97 FibNexthop { face_id, cost }
98 }
99
100 #[test]
103 fn lpm_empty_returns_none() {
104 let fib = Fib::new();
105 assert!(fib.lpm(&name(&["edu", "ucla"])).is_none());
106 }
107
108 #[test]
109 fn lpm_exact_match() {
110 let fib = Fib::new();
111 fib.insert(&name(&["edu", "ucla"]), FibEntry::new(vec![nexthop(1, 0)]));
112 let entry = fib.lpm(&name(&["edu", "ucla"])).unwrap();
113 assert_eq!(entry.nexthops[0].face_id, 1);
114 }
115
116 #[test]
117 fn lpm_most_specific_wins() {
118 let fib = Fib::new();
119 fib.insert(&name(&["edu"]), FibEntry::new(vec![nexthop(1, 10)]));
120 fib.insert(&name(&["edu", "ucla"]), FibEntry::new(vec![nexthop(2, 0)]));
121 let entry = fib.lpm(&name(&["edu", "ucla", "data"])).unwrap();
122 assert_eq!(entry.nexthops[0].face_id, 2);
123 }
124
125 #[test]
126 fn lpm_falls_back_to_shorter_prefix() {
127 let fib = Fib::new();
128 fib.insert(&name(&["edu"]), FibEntry::new(vec![nexthop(3, 5)]));
129 let entry = fib.lpm(&name(&["edu", "mit"])).unwrap();
130 assert_eq!(entry.nexthops[0].face_id, 3);
131 }
132
133 #[test]
136 fn add_nexthop_creates_entry() {
137 let fib = Fib::new();
138 fib.add_nexthop(&name(&["a"]), nexthop(7, 1));
139 let entry = fib.get(&name(&["a"])).unwrap();
140 assert_eq!(entry.nexthops.len(), 1);
141 assert_eq!(entry.nexthops[0].face_id, 7);
142 }
143
144 #[test]
145 fn add_nexthop_appends_to_existing() {
146 let fib = Fib::new();
147 fib.add_nexthop(&name(&["a"]), nexthop(1, 0));
148 fib.add_nexthop(&name(&["a"]), nexthop(2, 10));
149 let entry = fib.get(&name(&["a"])).unwrap();
150 assert_eq!(entry.nexthops.len(), 2);
151 assert!(entry.nexthops.iter().any(|n| n.face_id == 1));
152 assert!(entry.nexthops.iter().any(|n| n.face_id == 2));
153 }
154
155 #[test]
158 fn remove_clears_prefix() {
159 let fib = Fib::new();
160 fib.insert(&name(&["a", "b"]), FibEntry::new(vec![nexthop(5, 0)]));
161 fib.remove(&name(&["a", "b"]));
162 assert!(fib.get(&name(&["a", "b"])).is_none());
163 }
164
165 #[test]
166 fn remove_does_not_affect_parent() {
167 let fib = Fib::new();
168 fib.insert(&name(&["a"]), FibEntry::new(vec![nexthop(1, 0)]));
169 fib.insert(&name(&["a", "b"]), FibEntry::new(vec![nexthop(2, 0)]));
170 fib.remove(&name(&["a", "b"]));
171 assert!(fib.get(&name(&["a"])).is_some());
172 }
173}