Skip to main content

rns_core/transport/
tables.rs

1use alloc::vec::Vec;
2
3use super::types::InterfaceId;
4
5/// Entry in the path table, keyed by destination_hash.
6#[derive(Debug, Clone)]
7pub struct PathEntry {
8    pub timestamp: f64,
9    pub next_hop: [u8; 16],
10    pub hops: u8,
11    pub expires: f64,
12    pub random_blobs: Vec<[u8; 10]>,
13    pub receiving_interface: InterfaceId,
14    pub packet_hash: [u8; 32],
15    /// Original announce raw bytes (pre-hop-increment) for cache/retransmission.
16    pub announce_raw: Option<Vec<u8>>,
17}
18
19/// Entry in the announce table, keyed by destination_hash.
20#[derive(Debug, Clone)]
21pub struct AnnounceEntry {
22    pub timestamp: f64,
23    pub retransmit_timeout: f64,
24    pub retries: u8,
25    pub received_from: [u8; 16],
26    pub hops: u8,
27    pub packet_raw: Vec<u8>,
28    pub packet_data: Vec<u8>,
29    pub destination_hash: [u8; 16],
30    pub context_flag: u8,
31    pub local_rebroadcasts: u8,
32    pub block_rebroadcasts: bool,
33    pub attached_interface: Option<InterfaceId>,
34}
35
36/// Entry in the reverse table, keyed by truncated packet hash.
37#[derive(Debug, Clone)]
38pub struct ReverseEntry {
39    pub receiving_interface: InterfaceId,
40    pub outbound_interface: InterfaceId,
41    pub timestamp: f64,
42}
43
44/// Entry in the link table, keyed by link_id.
45#[derive(Debug, Clone)]
46pub struct LinkEntry {
47    pub timestamp: f64,
48    pub next_hop_transport_id: [u8; 16],
49    pub next_hop_interface: InterfaceId,
50    pub remaining_hops: u8,
51    pub received_interface: InterfaceId,
52    pub taken_hops: u8,
53    pub destination_hash: [u8; 16],
54    pub validated: bool,
55    pub proof_timeout: f64,
56}
57
58/// A pending discovery path request — stored when a path request arrives
59/// on a DISCOVER_PATHS_FOR interface for an unknown destination.
60#[derive(Debug, Clone)]
61pub struct DiscoveryPathRequest {
62    pub timestamp: f64,
63    pub requesting_interfaces: Vec<InterfaceId>,
64    pub engaged: bool,
65}
66
67/// Entry in the announce rate table, keyed by destination_hash.
68#[derive(Debug, Clone)]
69pub struct RateEntry {
70    pub last: f64,
71    pub rate_violations: u32,
72    pub blocked_until: f64,
73    pub timestamps: Vec<f64>,
74}
75
76/// A bounded set of alternative paths for a single destination.
77///
78/// `paths[0]` is always the *primary* (best) path.  Ranking: lowest hops
79/// first, then most-recent timestamp.
80#[derive(Debug, Clone)]
81pub struct PathSet {
82    paths: Vec<PathEntry>,
83    capacity: usize,
84}
85
86impl PathSet {
87    /// Create a new PathSet containing a single path.
88    pub fn from_single(entry: PathEntry, capacity: usize) -> Self {
89        PathSet {
90            paths: alloc::vec![entry],
91            capacity: capacity.max(1),
92        }
93    }
94
95    /// Access the primary (best) path, if any.
96    pub fn primary(&self) -> Option<&PathEntry> {
97        self.paths.first()
98    }
99
100    /// Mutable access to the primary path.
101    pub fn primary_mut(&mut self) -> Option<&mut PathEntry> {
102        self.paths.first_mut()
103    }
104
105    /// Update the current primary path's hop metric and restore path ranking.
106    pub fn update_primary_hops(&mut self, hops: u8) -> bool {
107        let Some(primary) = self.paths.first_mut() else {
108            return false;
109        };
110        primary.hops = hops;
111        self.sort();
112        true
113    }
114
115    /// Whether the set contains any paths.
116    pub fn is_empty(&self) -> bool {
117        self.paths.is_empty()
118    }
119
120    /// Number of stored paths.
121    pub fn len(&self) -> usize {
122        self.paths.len()
123    }
124
125    /// Iterator over all paths (primary first).
126    pub fn iter(&self) -> impl Iterator<Item = &PathEntry> {
127        self.paths.iter()
128    }
129
130    /// Insert or update a path entry.
131    ///
132    /// - If a path with the same `next_hop` already exists, it is replaced in-place.
133    /// - Otherwise the entry is added as an alternative.  If at capacity the
134    ///   worst path (highest hops, then oldest) is evicted.
135    ///
136    /// After mutation the vector is re-sorted so `paths[0]` remains the best.
137    pub fn upsert(&mut self, entry: PathEntry) {
138        // Check for existing same-next_hop path
139        if let Some(pos) = self.paths.iter().position(|p| p.next_hop == entry.next_hop) {
140            self.paths[pos] = entry;
141        } else if self.paths.len() < self.capacity {
142            self.paths.push(entry);
143        } else {
144            // At capacity — evict worst (last after sort, but we haven't sorted
145            // the new entry yet).  Replace worst if new entry is better.
146            // We push then sort then truncate, which is simple and correct.
147            self.paths.push(entry);
148        }
149        self.sort();
150        self.paths.truncate(self.capacity);
151    }
152
153    /// Insert or update a path and make it primary among otherwise equal paths.
154    pub fn upsert_primary(&mut self, entry: PathEntry) {
155        if let Some(pos) = self
156            .paths
157            .iter()
158            .position(|path| path.next_hop == entry.next_hop)
159        {
160            self.paths.remove(pos);
161        }
162        self.paths.insert(0, entry);
163        self.sort();
164        self.paths.truncate(self.capacity);
165    }
166
167    /// Promote the next-best path after the current primary becomes
168    /// unresponsive.
169    ///
170    /// If `remove` is true the old primary is discarded; otherwise it is
171    /// moved to the back of the list (it may recover later).
172    pub fn failover(&mut self, remove: bool) {
173        if self.paths.len() <= 1 {
174            return;
175        }
176        if remove {
177            self.paths.remove(0);
178        } else {
179            let old_primary = self.paths.remove(0);
180            self.paths.push(old_primary);
181        }
182    }
183
184    /// Remove expired or orphaned paths.
185    ///
186    /// `interface_exists` is a predicate that checks whether an interface is
187    /// still registered.
188    pub fn cull(&mut self, now: f64, interface_exists: impl Fn(&InterfaceId) -> bool) {
189        self.paths
190            .retain(|p| now <= p.expires && interface_exists(&p.receiving_interface));
191    }
192
193    /// Filter paths by a predicate, keeping only those that match.
194    pub fn retain(&mut self, predicate: impl Fn(&PathEntry) -> bool) {
195        self.paths.retain(predicate);
196    }
197
198    /// Expire all paths in this set (set timestamp/expires to 0).
199    pub fn expire_all(&mut self) {
200        for p in &mut self.paths {
201            p.timestamp = 0.0;
202            p.expires = 0.0;
203        }
204    }
205
206    /// Collect all random_blobs across every path in this set.
207    pub fn all_random_blobs(&self) -> Vec<[u8; 10]> {
208        let mut blobs = Vec::new();
209        for p in &self.paths {
210            blobs.extend_from_slice(&p.random_blobs);
211        }
212        blobs
213    }
214
215    /// Find the path entry that matches a given `next_hop`, if any.
216    pub fn find_by_next_hop(&self, next_hop: &[u8; 16]) -> Option<&PathEntry> {
217        self.paths.iter().find(|p| &p.next_hop == next_hop)
218    }
219
220    /// Sort: lowest hops first, then most-recent timestamp first.
221    fn sort(&mut self) {
222        self.paths.sort_by(|a, b| {
223            a.hops.cmp(&b.hops).then_with(|| {
224                b.timestamp
225                    .partial_cmp(&a.timestamp)
226                    .unwrap_or(core::cmp::Ordering::Equal)
227            })
228        });
229    }
230}
231
232#[cfg(test)]
233mod tests {
234    use super::*;
235
236    #[test]
237    fn test_path_entry_creation() {
238        let entry = PathEntry {
239            timestamp: 1000.0,
240            next_hop: [0xAA; 16],
241            hops: 3,
242            expires: 2000.0,
243            random_blobs: Vec::new(),
244            receiving_interface: InterfaceId(1),
245            packet_hash: [0xBB; 32],
246            announce_raw: None,
247        };
248        assert_eq!(entry.hops, 3);
249        assert_eq!(entry.receiving_interface, InterfaceId(1));
250    }
251
252    #[test]
253    fn upsert_primary_preserves_alternative_and_wins_equal_rank_tie() {
254        let first = PathEntry {
255            timestamp: 1000.0,
256            next_hop: [0xAA; 16],
257            hops: 3,
258            expires: 2000.0,
259            random_blobs: vec![[1; 10]],
260            receiving_interface: InterfaceId(1),
261            packet_hash: [0x11; 32],
262            announce_raw: None,
263        };
264        let preferred = PathEntry {
265            next_hop: [0xBB; 16],
266            receiving_interface: InterfaceId(2),
267            packet_hash: [0x22; 32],
268            ..first.clone()
269        };
270        let mut paths = PathSet::from_single(first, 3);
271
272        paths.upsert_primary(preferred);
273
274        assert_eq!(paths.len(), 2);
275        assert_eq!(paths.primary().unwrap().receiving_interface, InterfaceId(2));
276    }
277
278    #[test]
279    fn test_link_entry_creation() {
280        let entry = LinkEntry {
281            timestamp: 100.0,
282            next_hop_transport_id: [0x11; 16],
283            next_hop_interface: InterfaceId(2),
284            remaining_hops: 5,
285            received_interface: InterfaceId(3),
286            taken_hops: 2,
287            destination_hash: [0x22; 16],
288            validated: false,
289            proof_timeout: 200.0,
290        };
291        assert!(!entry.validated);
292        assert_eq!(entry.remaining_hops, 5);
293    }
294
295    #[test]
296    fn test_rate_entry_creation() {
297        let entry = RateEntry {
298            last: 50.0,
299            rate_violations: 0,
300            blocked_until: 0.0,
301            timestamps: Vec::new(),
302        };
303        assert_eq!(entry.rate_violations, 0);
304    }
305
306    // =========================================================================
307    // PathSet tests
308    // =========================================================================
309
310    fn make_path(next_hop: [u8; 16], hops: u8, timestamp: f64, expires: f64) -> PathEntry {
311        PathEntry {
312            timestamp,
313            next_hop,
314            hops,
315            expires,
316            random_blobs: Vec::new(),
317            receiving_interface: InterfaceId(1),
318            packet_hash: [0; 32],
319            announce_raw: None,
320        }
321    }
322
323    #[test]
324    fn test_pathset_from_single() {
325        let ps = PathSet::from_single(make_path([1; 16], 3, 100.0, 9999.0), 3);
326        assert_eq!(ps.len(), 1);
327        assert_eq!(ps.primary().unwrap().hops, 3);
328    }
329
330    #[test]
331    fn test_pathset_upsert_same_nexthop_replaces() {
332        let mut ps = PathSet::from_single(make_path([1; 16], 3, 100.0, 9999.0), 3);
333        ps.upsert(make_path([1; 16], 2, 200.0, 9999.0));
334        assert_eq!(ps.len(), 1);
335        assert_eq!(ps.primary().unwrap().hops, 2);
336        assert_eq!(ps.primary().unwrap().timestamp, 200.0);
337    }
338
339    #[test]
340    fn test_pathset_upsert_new_nexthop_adds_alternative() {
341        let mut ps = PathSet::from_single(make_path([1; 16], 3, 100.0, 9999.0), 3);
342        ps.upsert(make_path([2; 16], 2, 200.0, 9999.0));
343        assert_eq!(ps.len(), 2);
344        // Best path (fewer hops) should be primary
345        assert_eq!(ps.primary().unwrap().next_hop, [2; 16]);
346    }
347
348    #[test]
349    fn test_pathset_capacity_eviction() {
350        let mut ps = PathSet::from_single(make_path([1; 16], 1, 100.0, 9999.0), 2);
351        ps.upsert(make_path([2; 16], 2, 200.0, 9999.0));
352        ps.upsert(make_path([3; 16], 3, 300.0, 9999.0));
353        // Capacity is 2, worst (3 hops) should be evicted
354        assert_eq!(ps.len(), 2);
355        assert_eq!(ps.primary().unwrap().next_hop, [1; 16]);
356        assert!(ps.find_by_next_hop(&[3; 16]).is_none());
357    }
358
359    #[test]
360    fn test_pathset_failover_promotes_second() {
361        let mut ps = PathSet::from_single(make_path([1; 16], 1, 100.0, 9999.0), 3);
362        ps.upsert(make_path([2; 16], 2, 200.0, 9999.0));
363
364        ps.failover(false); // demote, don't remove
365        assert_eq!(ps.primary().unwrap().next_hop, [2; 16]);
366        assert_eq!(ps.len(), 2); // old primary moved to back
367    }
368
369    #[test]
370    fn test_pathset_failover_with_remove() {
371        let mut ps = PathSet::from_single(make_path([1; 16], 1, 100.0, 9999.0), 3);
372        ps.upsert(make_path([2; 16], 2, 200.0, 9999.0));
373
374        ps.failover(true); // remove old primary
375        assert_eq!(ps.primary().unwrap().next_hop, [2; 16]);
376        assert_eq!(ps.len(), 1);
377    }
378
379    #[test]
380    fn test_pathset_sort_ordering() {
381        let mut ps = PathSet::from_single(make_path([1; 16], 5, 300.0, 9999.0), 4);
382        ps.upsert(make_path([2; 16], 2, 100.0, 9999.0));
383        ps.upsert(make_path([3; 16], 2, 200.0, 9999.0));
384        ps.upsert(make_path([4; 16], 3, 400.0, 9999.0));
385
386        let hops: Vec<u8> = ps.iter().map(|p| p.hops).collect();
387        // Sorted by hops asc, then timestamp desc within same hops
388        assert_eq!(hops, vec![2, 2, 3, 5]);
389        // Among the 2-hop paths, newer timestamp first
390        assert_eq!(ps.paths[0].next_hop, [3; 16]); // timestamp 200
391        assert_eq!(ps.paths[1].next_hop, [2; 16]); // timestamp 100
392    }
393
394    #[test]
395    fn test_pathset_cull_removes_expired() {
396        let mut ps = PathSet::from_single(make_path([1; 16], 1, 100.0, 500.0), 3);
397        ps.upsert(make_path([2; 16], 2, 200.0, 9999.0));
398
399        ps.cull(600.0, |_| true); // now > 500 for first path
400        assert_eq!(ps.len(), 1);
401        assert_eq!(ps.primary().unwrap().next_hop, [2; 16]);
402    }
403
404    #[test]
405    fn test_pathset_cull_removes_orphaned_interface() {
406        let mut ps = PathSet::from_single(make_path([1; 16], 1, 100.0, 9999.0), 3);
407        ps.cull(200.0, |id| id.0 != 1); // interface 1 doesn't exist
408        assert!(ps.is_empty());
409    }
410
411    #[test]
412    fn test_pathset_retain_filters() {
413        let mut ps = PathSet::from_single(make_path([1; 16], 1, 100.0, 9999.0), 3);
414        ps.upsert(make_path([2; 16], 2, 200.0, 9999.0));
415
416        ps.retain(|p| p.next_hop != [1; 16]);
417        assert_eq!(ps.len(), 1);
418        assert_eq!(ps.primary().unwrap().next_hop, [2; 16]);
419    }
420
421    #[test]
422    fn test_pathset_expire_all() {
423        let mut ps = PathSet::from_single(make_path([1; 16], 1, 100.0, 9999.0), 3);
424        ps.upsert(make_path([2; 16], 2, 200.0, 9999.0));
425
426        ps.expire_all();
427        for p in ps.iter() {
428            assert_eq!(p.timestamp, 0.0);
429            assert_eq!(p.expires, 0.0);
430        }
431    }
432
433    #[test]
434    fn test_pathset_all_random_blobs() {
435        let mut e1 = make_path([1; 16], 1, 100.0, 9999.0);
436        e1.random_blobs = alloc::vec![[0xAA; 10]];
437        let mut e2 = make_path([2; 16], 2, 200.0, 9999.0);
438        e2.random_blobs = alloc::vec![[0xBB; 10], [0xCC; 10]];
439
440        let mut ps = PathSet::from_single(e1, 3);
441        ps.upsert(e2);
442
443        let blobs = ps.all_random_blobs();
444        assert_eq!(blobs.len(), 3);
445    }
446
447    #[test]
448    fn test_pathset_failover_single_path_noop() {
449        let mut ps = PathSet::from_single(make_path([1; 16], 1, 100.0, 9999.0), 3);
450        ps.failover(false);
451        assert_eq!(ps.len(), 1);
452        assert_eq!(ps.primary().unwrap().next_hop, [1; 16]);
453    }
454}