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