1use ndn_packet::Name;
2use ndn_store::NameTrie;
3use ndn_transport::FaceId;
4use std::sync::Arc;
5
6#[derive(Clone, Debug)]
8pub struct FibNexthop {
9 pub face_id: FaceId,
10 pub cost: u32,
11}
12
13#[derive(Clone, Debug)]
15pub struct FibEntry {
16 pub nexthops: Vec<FibNexthop>,
17}
18
19impl FibEntry {
20 pub fn nexthops_excluding(&self, exclude: FaceId) -> Vec<FibNexthop> {
21 self.nexthops
22 .iter()
23 .filter(|n| n.face_id != exclude)
24 .cloned()
25 .collect()
26 }
27}
28
29pub struct Fib {
34 trie: NameTrie<Arc<FibEntry>>,
35}
36
37impl Fib {
38 pub fn new() -> Self {
39 Self {
40 trie: NameTrie::new(),
41 }
42 }
43
44 pub fn lpm(&self, name: &Name) -> Option<Arc<FibEntry>> {
46 self.trie.lpm(name)
47 }
48
49 pub fn add_nexthop(&self, prefix: &Name, face_id: FaceId, cost: u32) {
51 let existing = self.trie.get(prefix);
52 let mut nexthops = existing.map(|e| e.nexthops.clone()).unwrap_or_default();
53 nexthops.retain(|n| n.face_id != face_id);
55 nexthops.push(FibNexthop { face_id, cost });
56 self.trie.insert(prefix, Arc::new(FibEntry { nexthops }));
57 }
58
59 pub fn dump(&self) -> Vec<(Name, Arc<FibEntry>)> {
61 self.trie.dump()
62 }
63
64 pub fn remove_prefix(&self, prefix: &Name) {
66 self.trie.remove(prefix);
67 }
68
69 pub fn set_nexthops(&self, prefix: &Name, nexthops: Vec<FibNexthop>) {
74 if nexthops.is_empty() {
75 self.trie.remove(prefix);
76 } else {
77 self.trie.insert(prefix, Arc::new(FibEntry { nexthops }));
78 }
79 }
80
81 pub fn remove_face(&self, face_id: FaceId) {
85 let entries = self.trie.dump();
86 for (prefix, entry) in entries {
87 if entry.nexthops.iter().any(|n| n.face_id == face_id) {
88 self.remove_nexthop(&prefix, face_id);
89 }
90 }
91 }
92
93 pub fn remove_nexthop(&self, prefix: &Name, face_id: FaceId) {
95 let Some(existing) = self.trie.get(prefix) else {
96 return;
97 };
98 let nexthops: Vec<_> = existing
99 .nexthops
100 .iter()
101 .filter(|n| n.face_id != face_id)
102 .cloned()
103 .collect();
104 if nexthops.is_empty() {
105 self.trie.remove(prefix);
106 } else {
107 self.trie.insert(prefix, Arc::new(FibEntry { nexthops }));
108 }
109 }
110}
111
112impl Default for Fib {
113 fn default() -> Self {
114 Self::new()
115 }
116}
117
118#[cfg(test)]
119mod tests {
120 use super::*;
121 use bytes::Bytes;
122 use ndn_packet::NameComponent;
123
124 fn name1(s: &'static str) -> Name {
125 Name::from_components([NameComponent::generic(Bytes::from_static(s.as_bytes()))])
126 }
127
128 fn name2(a: &'static str, b: &'static str) -> Name {
129 Name::from_components([
130 NameComponent::generic(Bytes::from_static(a.as_bytes())),
131 NameComponent::generic(Bytes::from_static(b.as_bytes())),
132 ])
133 }
134
135 #[test]
136 fn lpm_empty_returns_none() {
137 let fib = Fib::new();
138 assert!(fib.lpm(&name1("a")).is_none());
139 }
140
141 #[test]
142 fn add_nexthop_and_lpm() {
143 let fib = Fib::new();
144 fib.add_nexthop(&name1("a"), FaceId(1), 10);
145 let entry = fib.lpm(&name1("a")).unwrap();
146 assert_eq!(entry.nexthops.len(), 1);
147 assert_eq!(entry.nexthops[0].face_id, FaceId(1));
148 assert_eq!(entry.nexthops[0].cost, 10);
149 }
150
151 #[test]
152 fn lpm_returns_longest_prefix() {
153 let fib = Fib::new();
154 fib.add_nexthop(&Name::root(), FaceId(1), 10);
155 fib.add_nexthop(&name1("a"), FaceId(2), 10);
156 let entry = fib.lpm(&name2("a", "b")).unwrap();
158 assert_eq!(entry.nexthops[0].face_id, FaceId(2));
159 }
160
161 #[test]
162 fn add_nexthop_updates_cost() {
163 let fib = Fib::new();
164 fib.add_nexthop(&name1("a"), FaceId(1), 10);
165 fib.add_nexthop(&name1("a"), FaceId(1), 20);
166 let entry = fib.lpm(&name1("a")).unwrap();
167 assert_eq!(entry.nexthops.len(), 1);
168 assert_eq!(entry.nexthops[0].cost, 20);
169 }
170
171 #[test]
172 fn add_multiple_nexthops() {
173 let fib = Fib::new();
174 fib.add_nexthop(&name1("a"), FaceId(1), 10);
175 fib.add_nexthop(&name1("a"), FaceId(2), 20);
176 let entry = fib.lpm(&name1("a")).unwrap();
177 assert_eq!(entry.nexthops.len(), 2);
178 }
179
180 #[test]
181 fn remove_nexthop_removes_face() {
182 let fib = Fib::new();
183 fib.add_nexthop(&name1("a"), FaceId(1), 10);
184 fib.add_nexthop(&name1("a"), FaceId(2), 20);
185 fib.remove_nexthop(&name1("a"), FaceId(1));
186 let entry = fib.lpm(&name1("a")).unwrap();
187 assert_eq!(entry.nexthops.len(), 1);
188 assert_eq!(entry.nexthops[0].face_id, FaceId(2));
189 }
190
191 #[test]
192 fn remove_last_nexthop_deletes_entry() {
193 let fib = Fib::new();
194 fib.add_nexthop(&name1("a"), FaceId(1), 10);
195 fib.remove_nexthop(&name1("a"), FaceId(1));
196 assert!(fib.lpm(&name1("a")).is_none());
197 }
198
199 #[test]
200 fn remove_nonexistent_is_noop() {
201 let fib = Fib::new();
202 fib.add_nexthop(&name1("a"), FaceId(1), 10);
203 fib.remove_nexthop(&name1("a"), FaceId(99));
204 let entry = fib.lpm(&name1("a")).unwrap();
205 assert_eq!(entry.nexthops.len(), 1);
206 }
207
208 #[test]
209 fn remove_face_cleans_all_prefixes() {
210 let fib = Fib::new();
211 fib.add_nexthop(&name1("a"), FaceId(1), 10);
212 fib.add_nexthop(&name1("a"), FaceId(2), 20);
213 fib.add_nexthop(&name1("b"), FaceId(1), 5);
214 fib.add_nexthop(&name2("c", "d"), FaceId(1), 0);
215
216 fib.remove_face(FaceId(1));
217
218 let entry = fib.lpm(&name1("a")).unwrap();
220 assert_eq!(entry.nexthops.len(), 1);
221 assert_eq!(entry.nexthops[0].face_id, FaceId(2));
222 assert!(fib.lpm(&name1("b")).is_none());
224 assert!(fib.lpm(&name2("c", "d")).is_none());
226 }
227
228 #[test]
229 fn nexthops_excluding_filters_in_face() {
230 let entry = FibEntry {
231 nexthops: vec![
232 FibNexthop {
233 face_id: FaceId(1),
234 cost: 0,
235 },
236 FibNexthop {
237 face_id: FaceId(2),
238 cost: 0,
239 },
240 ],
241 };
242 let filtered = entry.nexthops_excluding(FaceId(1));
243 assert_eq!(filtered.len(), 1);
244 assert_eq!(filtered[0].face_id, FaceId(2));
245 }
246}