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