Skip to main content

prns_runtime/runtime/node_introspection/impls/
heap.rs

1use alloc::collections::{BTreeMap, VecDeque};
2use alloc::string::String;
3use alloc::vec::Vec;
4use core::future::Future;
5
6use prns_core::engine::{AnnounceRateState, RouteSnapshot};
7use prns_core::interfaces::PacketPhyStats;
8use prns_core::routing::announce::AnnounceRateAccounting;
9use prns_core::routing::dedup::PacketHash;
10use prns_core::units::InstantMillis;
11use prns_core::wire::{DestinationHash, TRUNCATED_HASH_BYTE_LEN};
12
13use super::super::{fold_logical_interface_inventory, InterfaceInventoryEntry};
14
15#[derive(Debug, Clone, PartialEq, Eq)]
16pub struct AnnounceRateSnapshot {
17    pub destination: DestinationHash,
18    pub last_allowed_announce_at: InstantMillis,
19    pub blocked_until: InstantMillis,
20    pub rate_violations: u16,
21    pub observed_at: Vec<InstantMillis>,
22}
23
24const MAX_ANNOUNCE_RATE_OBSERVATIONS: usize = 16;
25
26#[derive(Default)]
27pub struct HeapAnnounceRateHistory {
28    observed_at: BTreeMap<AnnounceRateHistoryKey, VecDeque<InstantMillis>>,
29}
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
32struct AnnounceRateHistoryKey([u8; TRUNCATED_HASH_BYTE_LEN]);
33
34impl From<DestinationHash> for AnnounceRateHistoryKey {
35    fn from(destination: DestinationHash) -> Self {
36        Self(*destination.as_bytes())
37    }
38}
39
40impl HeapAnnounceRateHistory {
41    pub fn record(
42        &mut self,
43        destination: DestinationHash,
44        observed_at: InstantMillis,
45        accounting: AnnounceRateAccounting,
46    ) {
47        match accounting {
48            AnnounceRateAccounting::NotApplied => return,
49            AnnounceRateAccounting::Started => {
50                self.observed_at.insert(destination.into(), VecDeque::new());
51            }
52            AnnounceRateAccounting::Continued => {}
53        }
54        let history = self.observed_at.entry(destination.into()).or_default();
55        if history.len() == MAX_ANNOUNCE_RATE_OBSERVATIONS {
56            history.pop_front();
57        }
58        history.push_back(observed_at);
59    }
60
61    #[must_use]
62    pub fn snapshot(&self, state: AnnounceRateState) -> AnnounceRateSnapshot {
63        AnnounceRateSnapshot {
64            destination: state.destination,
65            last_allowed_announce_at: state.last_allowed_announce_at,
66            blocked_until: state.blocked_until,
67            rate_violations: state.rate_violations,
68            observed_at: self
69                .observed_at
70                .get(&state.destination.into())
71                .map(|history| history.iter().copied().collect())
72                .unwrap_or_default(),
73        }
74    }
75}
76
77pub trait NodeIntrospection {
78    fn interface_inventory(&self) -> Vec<InterfaceInventoryEntry<String>>;
79
80    fn link_count(&self) -> impl Future<Output = u32> + Send;
81
82    fn packet_phy(&self, packet_hash: PacketHash) -> Option<PacketPhyStats>;
83
84    fn announce_rates(&self) -> impl Future<Output = Vec<AnnounceRateSnapshot>> + Send;
85
86    fn routes(&self) -> impl Future<Output = Vec<RouteSnapshot>> + Send;
87
88    fn route(
89        &self,
90        destination: DestinationHash,
91    ) -> impl Future<Output = Option<RouteSnapshot>> + Send;
92}
93
94#[must_use]
95pub fn logical_interface_inventory<Label: Ord>(
96    mut inventory: Vec<InterfaceInventoryEntry<Label>>,
97) -> Vec<InterfaceInventoryEntry<Label>> {
98    let logical_len = fold_logical_interface_inventory(&mut inventory).len();
99    inventory.truncate(logical_len);
100    inventory
101}
102
103#[cfg(test)]
104mod tests {
105    use super::*;
106
107    #[test]
108    fn announce_rate_history_is_bounded_and_restartable() {
109        let destination = DestinationHash::new([0x42; 16]);
110        let mut history = HeapAnnounceRateHistory::default();
111        history.record(
112            destination,
113            InstantMillis(99),
114            AnnounceRateAccounting::Started,
115        );
116        for observed_at in 0..20 {
117            history.record(
118                destination,
119                InstantMillis(observed_at),
120                AnnounceRateAccounting::Continued,
121            );
122        }
123
124        let snapshot = history.snapshot(AnnounceRateState {
125            destination,
126            last_allowed_announce_at: InstantMillis(19),
127            blocked_until: InstantMillis(0),
128            rate_violations: 0,
129        });
130
131        assert_eq!(
132            snapshot.observed_at,
133            (4..20).map(InstantMillis).collect::<Vec<_>>()
134        );
135
136        history.record(
137            destination,
138            InstantMillis(25),
139            AnnounceRateAccounting::Started,
140        );
141
142        assert_eq!(
143            history
144                .snapshot(AnnounceRateState {
145                    destination,
146                    last_allowed_announce_at: InstantMillis(25),
147                    blocked_until: InstantMillis(0),
148                    rate_violations: 0,
149                })
150                .observed_at,
151            vec![InstantMillis(25)]
152        );
153    }
154}