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