Skip to main content

prns_runtime_embassy/runtime/
interface_store.rs

1use core::cell::RefCell;
2
3use embassy_sync::blocking_mutex::raw::RawMutex;
4use embassy_sync::blocking_mutex::Mutex;
5use embassy_sync::signal::Signal;
6use heapless::FnvIndexMap;
7
8use crate::engine::InterfaceCounts;
9use crate::interfaces::{InterfaceId, PacketPhyStats};
10use crate::routing::dedup::PacketHash;
11
12use prns_runtime::runtime::packet_phy_retention::{
13    fixed_packet_phy_retention, FixedPacketPhyRetention,
14};
15
16#[must_use]
17pub const fn minimum_interface_store_capacity(interface_capacity: usize) -> usize {
18    assert!(interface_capacity > 0);
19    interface_capacity.next_power_of_two()
20}
21
22pub(crate) trait InterfaceInspectionStore: Sync {
23    const RETAINS_COUNTS: bool;
24    const RETAINS_PACKET_PHY: bool;
25
26    fn set_interface_counts(&self, interface: InterfaceId, counts: InterfaceCounts);
27    fn forget_interface(&self, interface: InterfaceId);
28    fn signal_interface_counts_changed(&self);
29    fn remember_packet_phy(&self, packet_hash: PacketHash, stats: PacketPhyStats);
30}
31
32pub(crate) struct NoInterfaceInspectionStore;
33
34impl InterfaceInspectionStore for NoInterfaceInspectionStore {
35    const RETAINS_COUNTS: bool = false;
36    const RETAINS_PACKET_PHY: bool = false;
37
38    fn set_interface_counts(&self, _interface: InterfaceId, _counts: InterfaceCounts) {}
39
40    fn forget_interface(&self, _interface: InterfaceId) {}
41
42    fn signal_interface_counts_changed(&self) {}
43
44    fn remember_packet_phy(&self, _packet_hash: PacketHash, _stats: PacketPhyStats) {}
45}
46
47pub struct EmbassyInterfaceStore<
48    M: RawMutex,
49    const INTERFACES: usize,
50    const PACKET_PHY_CAPACITY: usize,
51    const PACKET_PHY_INDEX_BUCKETS: usize,
52> {
53    counts: Mutex<M, RefCell<FnvIndexMap<InterfaceId, InterfaceCounts, INTERFACES>>>,
54    packet_phy:
55        Mutex<M, RefCell<FixedPacketPhyRetention<PACKET_PHY_CAPACITY, PACKET_PHY_INDEX_BUCKETS>>>,
56    signal: Signal<M, ()>,
57}
58
59impl<
60        M: RawMutex,
61        const INTERFACES: usize,
62        const PACKET_PHY_CAPACITY: usize,
63        const PACKET_PHY_INDEX_BUCKETS: usize,
64    > Default
65    for EmbassyInterfaceStore<M, INTERFACES, PACKET_PHY_CAPACITY, PACKET_PHY_INDEX_BUCKETS>
66{
67    fn default() -> Self {
68        Self::new()
69    }
70}
71
72impl<
73        M: RawMutex,
74        const INTERFACES: usize,
75        const PACKET_PHY_CAPACITY: usize,
76        const PACKET_PHY_INDEX_BUCKETS: usize,
77    > EmbassyInterfaceStore<M, INTERFACES, PACKET_PHY_CAPACITY, PACKET_PHY_INDEX_BUCKETS>
78{
79    #[must_use]
80    pub const fn new() -> Self {
81        const {
82            assert!(
83                INTERFACES.is_power_of_two(),
84                "EmbassyInterfaceStore INTERFACES must be a power of two: heapless::FnvIndexMap requires it"
85            )
86        };
87        Self {
88            counts: Mutex::new(RefCell::new(FnvIndexMap::new())),
89            packet_phy: Mutex::new(RefCell::new(fixed_packet_phy_retention())),
90            signal: Signal::new(),
91        }
92    }
93
94    #[must_use]
95    pub fn counts(&self, interface: InterfaceId) -> InterfaceCounts {
96        self.counts
97            .lock(|cell| cell.borrow().get(&interface).copied().unwrap_or_default())
98    }
99
100    #[must_use]
101    pub fn packet_phy(&self, packet_hash: PacketHash) -> Option<PacketPhyStats> {
102        self.packet_phy.lock(|cell| cell.borrow().get(packet_hash))
103    }
104
105    pub async fn changed(&self) {
106        self.signal.wait().await;
107    }
108}
109
110impl<
111        M: RawMutex + Sync,
112        const INTERFACES: usize,
113        const PACKET_PHY_CAPACITY: usize,
114        const PACKET_PHY_INDEX_BUCKETS: usize,
115    > InterfaceInspectionStore
116    for EmbassyInterfaceStore<M, INTERFACES, PACKET_PHY_CAPACITY, PACKET_PHY_INDEX_BUCKETS>
117{
118    const RETAINS_COUNTS: bool = true;
119    const RETAINS_PACKET_PHY: bool = true;
120
121    fn set_interface_counts(&self, interface: InterfaceId, counts: InterfaceCounts) {
122        self.counts.lock(|cell| {
123            let stored = cell.borrow_mut().insert(interface, counts);
124            assert!(
125                stored.is_ok(),
126                "EmbassyInterfaceStore INTERFACES is smaller than the live interface count"
127            );
128        });
129    }
130
131    fn forget_interface(&self, interface: InterfaceId) {
132        self.counts.lock(|cell| {
133            let _ = cell.borrow_mut().remove(&interface);
134        });
135    }
136
137    fn signal_interface_counts_changed(&self) {
138        self.signal.signal(());
139    }
140
141    fn remember_packet_phy(&self, packet_hash: PacketHash, stats: PacketPhyStats) {
142        if stats.is_empty() {
143            return;
144        }
145        self.packet_phy
146            .lock(|cell| cell.borrow_mut().remember(packet_hash, stats));
147    }
148}
149
150#[cfg(test)]
151mod tests {
152    use super::*;
153    use crate::interfaces::{RssiDbm, INTERFACE_ID_LEN};
154    use crate::routing::dedup::dedup_index_buckets;
155    use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
156
157    #[test]
158    fn interface_store_capacity_covers_the_interface_ceiling_with_a_power_of_two() {
159        assert_eq!(minimum_interface_store_capacity(1), 1);
160        assert_eq!(minimum_interface_store_capacity(7), 8);
161        assert_eq!(minimum_interface_store_capacity(24), 32);
162    }
163
164    #[test]
165    fn fixed_store_reads_interface_counts_and_packet_phy() {
166        const PACKET_PHY_CAPACITY: usize = 8;
167        const PACKET_PHY_INDEX_BUCKETS: usize = dedup_index_buckets(PACKET_PHY_CAPACITY);
168
169        let store = EmbassyInterfaceStore::<
170            CriticalSectionRawMutex,
171            8,
172            PACKET_PHY_CAPACITY,
173            PACKET_PHY_INDEX_BUCKETS,
174        >::new();
175        let interface = InterfaceId::new([5; INTERFACE_ID_LEN]);
176        let packet_hash = PacketHash::new([7; 32]);
177        let packet_phy = PacketPhyStats {
178            rssi: Some(RssiDbm::new(-87)),
179            snr: None,
180            quality: None,
181        };
182
183        assert_eq!(store.counts(interface), InterfaceCounts::default());
184        assert_eq!(store.packet_phy(packet_hash), None);
185
186        store.set_interface_counts(
187            interface,
188            InterfaceCounts {
189                destinations: 2,
190                links: 1,
191                transported_links: 4,
192            },
193        );
194        store.remember_packet_phy(packet_hash, packet_phy);
195
196        assert_eq!(store.counts(interface).transported_links, 4);
197        assert_eq!(store.packet_phy(packet_hash), Some(packet_phy));
198    }
199}