prns_runtime_embassy/runtime/
interface_store.rs1use 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}