1use core::num::NonZeroU8;
2use core::{future::poll_fn, task::Poll};
3
4use hamaddr::HamAddr;
5use heapless::{LinearMap, Vec};
6use rand::{Rng, RngExt as _};
7use umsh_core::{
8 BuildError, ChannelId, ChannelKey, FloodHops, NodeHint, OptionNumber, PacketBuilder,
9 PacketHeader, PacketType, ParseError, ParsedOptions, PayloadType, PublicKey, RouterHint,
10 SourceAddrRef, UnsealedPacket, feed_aad, options::OptionEncoder,
11};
12use umsh_crypto::{
13 CmacState, CryptoEngine, CryptoError, DerivedChannelKeys, NodeIdentity, PairwiseKeys,
14};
15use umsh_hal::{Clock, CounterStore, Radio, RxInfo, Snr, TxError, TxOptions};
16
17use crate::{
18 AddPeerError, CapacityError, DEFAULT_ACKS, DEFAULT_CHANNEL_HINT_REPLAY, DEFAULT_CHANNEL_REPLAY,
19 DEFAULT_CHANNELS, DEFAULT_DUP, DEFAULT_IDENTITIES, DEFAULT_PEERS, DEFAULT_TX,
20 ESTABLISHED_ROUTE_EXTRA_HOPS, MAX_CAD_ATTEMPTS, MAX_FLOOD_HOPS, MAX_FORWARD_RETRIES,
21 MAX_RESEND_FRAME_LEN, MAX_SOURCE_ROUTE_HOPS, Platform, ReplayVerdict, ReplayWindow,
22 cache::{DupCacheKey, DuplicateCache},
23 peers::CachedRoute,
24 peers::{ChannelTable, PeerCryptoMap, PeerId, PeerRegistry},
25 send::{
26 CompletionSignal, PendingAck, PendingAckError, ResendRecord, SendOptions, SendReceipt,
27 TxPriority, TxQueue,
28 },
29};
30
31pub enum WakeReason {
37 Received(RxInfo),
40 TimerExpired,
43}
44
45const COUNTER_PERSIST_BLOCK_SIZE: u32 = 128;
46const COUNTER_PERSIST_BLOCK_MASK: u32 = COUNTER_PERSIST_BLOCK_SIZE - 1;
47const COUNTER_PERSIST_SCHEDULE_OFFSET: u32 = 100;
48const MAC_COMMAND_ECHO_REQUEST_ID: u8 = 4;
49const MAC_COMMAND_ECHO_RESPONSE_ID: u8 = 5;
50const COUNTER_RESYNC_NONCE_LEN: usize = 4;
51const COUNTER_RESYNC_REQUEST_RETRY_MS: u64 = 5_000;
52const COUNTER_RESYNC_GAP_THRESHOLD: u32 = 1024;
59
60#[derive(Clone, Copy, Debug, PartialEq, Eq)]
61struct PendingCounterResync {
62 nonce: u32,
63 requested_ms: u64,
64}
65
66#[derive(Clone, Debug, PartialEq, Eq)]
67struct DeferredCounterResyncFrame<const FRAME: usize> {
68 local_id: LocalIdentityId,
69 peer_id: PeerId,
70 frame: Vec<u8, FRAME>,
71 rssi: i16,
72 snr: Snr,
73 lqi: Option<NonZeroU8>,
74 received_at_ms: u64,
75}
76
77#[derive(Clone, Copy, Debug, PartialEq, Eq)]
78struct ResolvedMulticastSource {
79 peer_id: Option<PeerId>,
80 public_key: Option<PublicKey>,
81 hint: Option<NodeHint>,
82}
83
84#[derive(Clone, Debug, PartialEq, Eq)]
85struct PostTxListen {
86 identity_id: LocalIdentityId,
87 receipt: SendReceipt,
88 confirm_key: DupCacheKey,
89 deadline_ms: u64,
90}
91
92#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
105pub struct LocalIdentityId(pub u8);
106
107pub enum LocalIdentity<I: NodeIdentity> {
127 LongTerm(I),
129 #[cfg(feature = "software-crypto")]
130 Ephemeral(umsh_crypto::software::SoftwareIdentity),
132}
133
134impl<I: NodeIdentity> LocalIdentity<I> {
135 pub fn public_key(&self) -> &PublicKey {
137 match self {
138 Self::LongTerm(identity) => identity.public_key(),
139 #[cfg(feature = "software-crypto")]
140 Self::Ephemeral(identity) => identity.public_key(),
141 }
142 }
143
144 pub fn hint(&self) -> umsh_core::NodeHint {
146 self.public_key().hint()
147 }
148
149 pub fn is_ephemeral(&self) -> bool {
151 match self {
152 Self::LongTerm(_) => false,
153 #[cfg(feature = "software-crypto")]
154 Self::Ephemeral(_) => true,
155 }
156 }
157}
158
159impl<I: NodeIdentity> From<I> for LocalIdentity<I> {
160 fn from(value: I) -> Self {
161 Self::LongTerm(value)
162 }
163}
164
165pub struct IdentitySlot<
201 I: NodeIdentity,
202 const PEERS: usize,
203 const ACKS: usize,
204 const FRAME: usize = MAX_RESEND_FRAME_LEN,
205> {
206 identity: LocalIdentity<I>,
207 peer_crypto: PeerCryptoMap<PEERS>,
208 frame_counter: u32,
209 persisted_counter: u32,
210 pending_persist_target: Option<u32>,
211 save_scheduled_since_boot: bool,
212 counter_persistence_enabled: bool,
213 pending_acks: LinearMap<SendReceipt, PendingAck<FRAME>, ACKS>,
214 next_receipt: u32,
215 pfs_parent: Option<LocalIdentityId>,
216 pending_counter_resync: LinearMap<PeerId, PendingCounterResync, PEERS>,
217}
218
219impl<I: NodeIdentity, const PEERS: usize, const ACKS: usize, const FRAME: usize>
220 IdentitySlot<I, PEERS, ACKS, FRAME>
221{
222 pub fn new(
224 identity: LocalIdentity<I>,
225 frame_counter: u32,
226 pfs_parent: Option<LocalIdentityId>,
227 ) -> Self {
228 let counter_persistence_enabled = !identity.is_ephemeral();
229 Self {
230 identity,
231 peer_crypto: PeerCryptoMap::new(),
232 frame_counter,
233 persisted_counter: frame_counter,
234 pending_persist_target: None,
235 save_scheduled_since_boot: false,
236 counter_persistence_enabled,
237 pending_acks: LinearMap::new(),
238 next_receipt: 0,
239 pfs_parent,
240 pending_counter_resync: LinearMap::new(),
241 }
242 }
243
244 pub fn identity(&self) -> &LocalIdentity<I> {
246 &self.identity
247 }
248 pub fn peer_crypto(&self) -> &PeerCryptoMap<PEERS> {
250 &self.peer_crypto
251 }
252 pub fn peer_crypto_mut(&mut self) -> &mut PeerCryptoMap<PEERS> {
254 &mut self.peer_crypto
255 }
256 pub fn frame_counter(&self) -> u32 {
258 self.frame_counter
259 }
260 pub fn persisted_counter(&self) -> u32 {
262 self.persisted_counter
263 }
264 #[cfg(test)]
269 pub(crate) fn set_frame_counter(&mut self, value: u32) {
270 self.frame_counter = value;
271 }
272 pub fn pending_persist_target(&self) -> Option<u32> {
274 self.pending_persist_target
275 }
276
277 pub fn counter_persistence_enabled(&self) -> bool {
279 self.counter_persistence_enabled
280 }
281
282 pub(crate) fn advance_frame_counter(&mut self) -> u32 {
284 let current = self.frame_counter;
285 self.frame_counter = self.frame_counter.wrapping_add(1);
286 current
287 }
288
289 pub fn load_persisted_counter(&mut self, value: u32) {
291 let aligned = align_counter_boundary(value);
292 self.frame_counter = aligned;
293 self.persisted_counter = aligned;
294 self.pending_persist_target = None;
295 self.save_scheduled_since_boot = false;
296 }
297
298 fn schedule_counter_persist_if_needed(&mut self) {
299 if !self.counter_persistence_enabled {
300 return;
301 }
302
303 let should_schedule = !self.save_scheduled_since_boot
304 || (self.frame_counter & COUNTER_PERSIST_BLOCK_MASK) == COUNTER_PERSIST_SCHEDULE_OFFSET;
305 if !should_schedule {
306 return;
307 }
308
309 let target = next_counter_persist_target(self.frame_counter);
310 self.pending_persist_target = Some(
311 self.pending_persist_target
312 .map(|existing| existing.max(target))
313 .unwrap_or(target),
314 );
315 self.save_scheduled_since_boot = true;
316 }
317
318 fn mark_counter_persisted(&mut self, value: u32) {
319 let aligned = align_counter_boundary(value);
320 self.persisted_counter = aligned;
321 if self.pending_persist_target == Some(aligned) {
322 self.pending_persist_target = None;
323 }
324 }
325
326 fn counter_window_exhausted(&self) -> bool {
327 if !self.counter_persistence_enabled {
328 return false;
329 }
330
331 let ahead = self.persisted_counter.wrapping_sub(self.frame_counter);
332 if ahead > 0 && ahead <= COUNTER_PERSIST_BLOCK_SIZE {
333 return false;
334 }
335
336 if ahead == 0 {
337 return self.save_scheduled_since_boot;
338 }
339
340 self.frame_counter.wrapping_sub(self.persisted_counter) >= COUNTER_PERSIST_BLOCK_SIZE
341 }
342
343 pub fn next_receipt(&mut self) -> SendReceipt {
345 let receipt = SendReceipt(self.next_receipt);
346 self.next_receipt = self.next_receipt.wrapping_add(1);
347 receipt
348 }
349
350 #[cfg(test)]
352 pub(crate) fn set_next_receipt_for_test(&mut self, value: u32) {
353 self.next_receipt = value;
354 }
355
356 pub fn try_insert_pending_ack(
358 &mut self,
359 receipt: SendReceipt,
360 pending: PendingAck<FRAME>,
361 ) -> Result<Option<PendingAck<FRAME>>, PendingAckError> {
362 self.pending_acks
363 .insert(receipt, pending)
364 .map_err(|_| PendingAckError::TableFull)
365 }
366
367 pub fn pending_ack(&self, receipt: &SendReceipt) -> Option<&PendingAck<FRAME>> {
369 self.pending_acks.get(receipt)
370 }
371
372 pub fn pending_acks(&self) -> impl Iterator<Item = (&SendReceipt, &PendingAck<FRAME>)> {
377 self.pending_acks.iter()
378 }
379
380 pub fn pending_ack_mut(&mut self, receipt: &SendReceipt) -> Option<&mut PendingAck<FRAME>> {
382 self.pending_acks.get_mut(receipt)
383 }
384
385 pub fn remove_pending_ack(&mut self, receipt: &SendReceipt) -> Option<PendingAck<FRAME>> {
387 self.pending_acks.remove(receipt)
388 }
389
390 pub fn pfs_parent(&self) -> Option<LocalIdentityId> {
392 self.pfs_parent
393 }
394
395 fn pending_counter_resync(&self) -> &LinearMap<PeerId, PendingCounterResync, PEERS> {
397 &self.pending_counter_resync
398 }
399
400 fn pending_counter_resync_mut(
402 &mut self,
403 ) -> &mut LinearMap<PeerId, PendingCounterResync, PEERS> {
404 &mut self.pending_counter_resync
405 }
406}
407
408#[derive(Clone, Debug, PartialEq, Eq)]
428pub struct ChannelPolicy {
429 pub channel_id: ChannelId,
431 pub require_unencrypted: bool,
433 pub require_full_source: bool,
435 pub max_flood_hops: Option<u8>,
437}
438
439#[derive(Clone, Copy, Debug, PartialEq, Eq)]
457pub enum AmateurRadioMode {
458 Unlicensed,
465 LicensedOnly,
471 Hybrid,
478}
479
480#[derive(Clone, Copy, Debug, PartialEq, Eq)]
481enum TransmitAuthority {
482 Unlicensed,
483 Amateur,
484}
485
486#[derive(Clone, Copy, Debug, PartialEq, Eq)]
487enum ForwardStationAction {
488 Remove,
489 Replace,
490}
491
492#[derive(Clone, Copy, Debug, PartialEq, Eq)]
493struct ForwardPlan {
494 router_hint: RouterHint,
495 consume_source_route: bool,
496 decrement_flood_hops: bool,
497 insert_region_code: Option<[u8; 2]>,
498 delay_ms: u64,
499 station_action: ForwardStationAction,
500 signal: TraceSignalEntry,
503}
504
505#[derive(Clone, Copy, Debug, PartialEq, Eq)]
510struct TraceSignalEntry([u8; 2]);
511
512impl TraceSignalEntry {
513 fn from_rx(rx: &RxInfo) -> Self {
514 let rssi = rx.rssi.clamp(-255, 0).unsigned_abs().min(255) as u8;
515 let snr = rx.snr.as_centibels().clamp(-128, 127) as i8;
516 Self([rssi, snr as u8])
517 }
518
519 fn as_bytes(&self) -> [u8; 2] {
520 self.0
521 }
522}
523
524#[derive(Clone, Debug, PartialEq, Eq)]
543pub struct OperatingPolicy {
544 pub amateur_radio_mode: AmateurRadioMode,
546 pub operator_callsign: Option<HamAddr>,
548 pub channel_policies: Vec<ChannelPolicy, 4>,
550}
551
552impl Default for OperatingPolicy {
553 fn default() -> Self {
554 Self {
555 amateur_radio_mode: AmateurRadioMode::Unlicensed,
556 operator_callsign: None,
557 channel_policies: Vec::new(),
558 }
559 }
560}
561
562#[derive(Clone, Debug, PartialEq, Eq)]
595pub struct RepeaterConfig {
596 pub enabled: bool,
598 pub regions: Vec<[u8; 2], 8>,
600 pub default_region: Option<[u8; 2]>,
603 pub min_rssi: Option<i16>,
605 pub min_snr: Option<i8>,
607 pub flood_contention_snr_low_db: i8,
609 pub flood_contention_snr_high_db: i8,
611 pub flood_contention_min_window_percent: u8,
613 pub flood_contention_max_window_frames: u8,
615 pub flood_contention_ack_guard_percent: u8,
620 pub flood_contention_max_deferrals: u8,
622 pub amateur_radio_mode: AmateurRadioMode,
624 pub station_callsign: Option<HamAddr>,
626}
627
628impl Default for RepeaterConfig {
629 fn default() -> Self {
630 Self {
631 enabled: false,
632 regions: Vec::new(),
633 default_region: None,
634 min_rssi: None,
635 min_snr: None,
636 flood_contention_snr_low_db: -6,
637 flood_contention_snr_high_db: 15,
638 flood_contention_min_window_percent: 20,
639 flood_contention_max_window_frames: 2,
640 flood_contention_ack_guard_percent: 25,
641 flood_contention_max_deferrals: 3,
642 amateur_radio_mode: AmateurRadioMode::Unlicensed,
643 station_callsign: None,
644 }
645 }
646}
647
648#[derive(Clone, Debug, PartialEq, Eq)]
654pub enum SendError {
655 IdentityMissing,
658 PeerMissing,
661 PairwiseKeysMissing,
665 IdentityAgreementFailed,
667 ChannelMissing,
670 PolicyViolation,
673 Build(BuildError),
676 Parse(ParseError),
679 Crypto(CryptoError),
682 QueueFull,
685 PendingAckFull,
688 CounterPersistenceLag,
692}
693
694impl From<BuildError> for SendError {
695 fn from(value: BuildError) -> Self {
696 Self::Build(value)
697 }
698}
699
700impl From<ParseError> for SendError {
701 fn from(value: ParseError) -> Self {
702 Self::Parse(value)
703 }
704}
705
706impl From<CryptoError> for SendError {
707 fn from(value: CryptoError) -> Self {
708 Self::Crypto(value)
709 }
710}
711
712#[derive(Clone, Debug, PartialEq, Eq)]
718pub enum MacError<RadioError> {
719 Radio(RadioError),
723 Transmit(TxError<RadioError>),
729 QueueFull,
733}
734
735#[derive(Clone, Debug, PartialEq, Eq)]
741pub enum CounterPersistenceError<StoreError> {
742 IdentityMissing,
744 Store(StoreError),
748}
749
750impl<RadioError> From<RadioError> for MacError<RadioError> {
751 fn from(value: RadioError) -> Self {
752 Self::Radio(value)
753 }
754}
755
756impl<RadioError> From<TxError<RadioError>> for MacError<RadioError> {
757 fn from(value: TxError<RadioError>) -> Self {
758 Self::Transmit(value)
759 }
760}
761
762#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
835pub struct MacCounters {
836 pub tx_frames: u32,
838 pub tx_abandoned: u32,
840 pub rx_frames: u32,
842 pub rx_accepted: u32,
846 pub forwarded: u32,
848 pub forward_cancelled: u32,
851}
852
853impl MacCounters {
854 fn bump(slot: &mut u32) {
855 *slot = slot.saturating_add(1);
856 }
857}
858
859pub struct Mac<
860 P: Platform,
861 const IDENTITIES: usize = DEFAULT_IDENTITIES,
862 const PEERS: usize = DEFAULT_PEERS,
863 const CHANNELS: usize = DEFAULT_CHANNELS,
864 const ACKS: usize = DEFAULT_ACKS,
865 const TX: usize = DEFAULT_TX,
866 const FRAME: usize = MAX_RESEND_FRAME_LEN,
867 const DUP: usize = DEFAULT_DUP,
868 const RN: usize = DEFAULT_CHANNEL_REPLAY,
869 const HN: usize = DEFAULT_CHANNEL_HINT_REPLAY,
870> {
871 radio: P::Radio,
872 crypto: CryptoEngine<P::Aes, P::Sha>,
873 clock: P::Clock,
874 rng: P::Rng,
875 counter_store: P::CounterStore,
876 identities: Vec<Option<IdentitySlot<P::Identity, PEERS, ACKS, FRAME>>, IDENTITIES>,
877 peer_registry: PeerRegistry<PEERS>,
878 channels: ChannelTable<CHANNELS, RN, HN>,
879 dup_cache: DuplicateCache<DUP>,
880 multicast_unknown_dup_cache: DuplicateCache<DUP>,
881 tx_queue: TxQueue<TX, FRAME>,
882 post_tx_listen: Option<PostTxListen>,
883 repeater: RepeaterConfig,
884 operating_policy: OperatingPolicy,
885 auto_register_full_key_peers: bool,
886 deferred_counter_resync_frame: Option<DeferredCounterResyncFrame<FRAME>>,
887 counters: MacCounters,
888}
889
890impl<
891 P: Platform,
892 const IDENTITIES: usize,
893 const PEERS: usize,
894 const CHANNELS: usize,
895 const ACKS: usize,
896 const TX: usize,
897 const FRAME: usize,
898 const DUP: usize,
899 const RN: usize,
900 const HN: usize,
901> Mac<P, IDENTITIES, PEERS, CHANNELS, ACKS, TX, FRAME, DUP, RN, HN>
902{
903 pub fn new(
905 radio: P::Radio,
906 crypto: CryptoEngine<P::Aes, P::Sha>,
907 clock: P::Clock,
908 rng: P::Rng,
909 counter_store: P::CounterStore,
910 repeater: RepeaterConfig,
911 operating_policy: OperatingPolicy,
912 ) -> Self {
913 Self {
914 radio,
915 crypto,
916 clock,
917 rng,
918 counter_store,
919 identities: Vec::new(),
920 peer_registry: PeerRegistry::new(),
921 channels: ChannelTable::new(),
922 dup_cache: DuplicateCache::new(),
923 multicast_unknown_dup_cache: DuplicateCache::new(),
924 tx_queue: TxQueue::new(),
925 post_tx_listen: None,
926 repeater,
927 operating_policy,
928 auto_register_full_key_peers: false,
929 deferred_counter_resync_frame: None,
930 counters: MacCounters::default(),
931 }
932 }
933
934 pub const fn counters(&self) -> MacCounters {
936 self.counters
937 }
938
939 pub fn radio(&self) -> &P::Radio {
941 &self.radio
942 }
943
944 pub fn radio_mut(&mut self) -> &mut P::Radio {
946 &mut self.radio
947 }
948
949 pub fn crypto(&self) -> &CryptoEngine<P::Aes, P::Sha> {
951 &self.crypto
952 }
953
954 pub fn clock(&self) -> &P::Clock {
956 &self.clock
957 }
958
959 pub fn rng(&self) -> &P::Rng {
961 &self.rng
962 }
963
964 pub fn rng_mut(&mut self) -> &mut P::Rng {
966 &mut self.rng
967 }
968
969 pub fn counter_store(&self) -> &P::CounterStore {
971 &self.counter_store
972 }
973 pub fn tx_queue(&self) -> &TxQueue<TX, FRAME> {
975 &self.tx_queue
976 }
977 pub fn tx_queue_mut(&mut self) -> &mut TxQueue<TX, FRAME> {
979 &mut self.tx_queue
980 }
981 pub fn dup_cache(&self) -> &DuplicateCache<DUP> {
983 &self.dup_cache
984 }
985 pub fn peer_registry(&self) -> &PeerRegistry<PEERS> {
987 &self.peer_registry
988 }
989 pub fn peer_registry_mut(&mut self) -> &mut PeerRegistry<PEERS> {
991 &mut self.peer_registry
992 }
993 pub fn channels(&self) -> &ChannelTable<CHANNELS, RN, HN> {
995 &self.channels
996 }
997 pub fn channels_mut(&mut self) -> &mut ChannelTable<CHANNELS, RN, HN> {
999 &mut self.channels
1000 }
1001 pub fn repeater_config(&self) -> &RepeaterConfig {
1003 &self.repeater
1004 }
1005 pub fn repeater_config_mut(&mut self) -> &mut RepeaterConfig {
1007 &mut self.repeater
1008 }
1009 pub fn operating_policy(&self) -> &OperatingPolicy {
1011 &self.operating_policy
1012 }
1013 pub fn operating_policy_mut(&mut self) -> &mut OperatingPolicy {
1015 &mut self.operating_policy
1016 }
1017
1018 pub fn auto_register_full_key_peers(&self) -> bool {
1020 self.auto_register_full_key_peers
1021 }
1022
1023 pub fn set_auto_register_full_key_peers(&mut self, enabled: bool) {
1025 self.auto_register_full_key_peers = enabled;
1026 }
1027
1028 pub fn add_identity(
1030 &mut self,
1031 identity: P::Identity,
1032 ) -> Result<LocalIdentityId, CapacityError> {
1033 self.insert_identity(LocalIdentity::LongTerm(identity), None)
1034 }
1035
1036 pub async fn load_persisted_counter(
1046 &mut self,
1047 id: LocalIdentityId,
1048 ) -> Result<u32, CounterPersistenceError<<P::CounterStore as CounterStore>::Error>> {
1049 let context = {
1050 let slot = self
1051 .identity(id)
1052 .ok_or(CounterPersistenceError::IdentityMissing)?;
1053 if !slot.counter_persistence_enabled() {
1054 return Ok(slot.frame_counter());
1055 }
1056 *slot.identity().public_key()
1057 };
1058 let loaded = self
1059 .counter_store
1060 .load(&context.0)
1061 .await
1062 .map_err(CounterPersistenceError::Store)?;
1063 if loaded == 0 {
1068 let slot = self
1069 .identity(id)
1070 .ok_or(CounterPersistenceError::IdentityMissing)?;
1071 return Ok(slot.frame_counter());
1072 }
1073 let aligned = align_counter_boundary(loaded);
1074 let slot = self
1075 .identity_mut(id)
1076 .ok_or(CounterPersistenceError::IdentityMissing)?;
1077 slot.load_persisted_counter(aligned);
1078 Ok(aligned)
1079 }
1080
1081 pub async fn service_counter_persistence(
1083 &mut self,
1084 ) -> Result<usize, <P::CounterStore as CounterStore>::Error> {
1085 let mut pending = Vec::<(LocalIdentityId, [u8; 32], u32), IDENTITIES>::new();
1086 for (index, slot) in self.identities.iter().enumerate() {
1087 let Some(slot) = slot.as_ref() else {
1088 continue;
1089 };
1090 let Some(target) = slot.pending_persist_target() else {
1091 continue;
1092 };
1093 if !slot.counter_persistence_enabled() {
1094 continue;
1095 }
1096 pending
1097 .push((
1098 LocalIdentityId(index as u8),
1099 slot.identity().public_key().0,
1100 target,
1101 ))
1102 .expect("identity enumeration must fit configured identity capacity");
1103 }
1104
1105 let mut wrote = 0usize;
1106 for (_, context, target) in pending.iter() {
1107 self.counter_store
1108 .store(context, align_counter_boundary(*target))
1109 .await?;
1110 wrote += 1;
1111 }
1112 if wrote > 0 {
1113 self.counter_store.flush().await?;
1114 for (id, _, target) in pending {
1115 if let Some(slot) = self.identity_mut(id) {
1116 slot.mark_counter_persisted(target);
1117 }
1118 }
1119 }
1120 Ok(wrote)
1121 }
1122
1123 pub(crate) async fn service_rx_counter_persistence(
1129 &mut self,
1130 ) -> Result<usize, <P::CounterStore as CounterStore>::Error> {
1131 let mut pending: heapless::Vec<(u8, crate::peers::PeerId, [u8; 32], u32), PEERS> =
1135 heapless::Vec::new();
1136
1137 for (identity_index, slot) in self.identities.iter().enumerate() {
1138 let Some(slot) = slot.as_ref() else {
1139 continue;
1140 };
1141 for (peer_id, state) in slot.peer_crypto().iter() {
1142 if !state.needs_rx_persist {
1143 continue;
1144 }
1145 let Some(info) = self.peer_registry.get(*peer_id) else {
1146 continue;
1147 };
1148 let _ = pending.push((
1149 identity_index as u8,
1150 *peer_id,
1151 info.public_key.0,
1152 state.replay_window.last_accepted,
1153 ));
1154 }
1155 }
1156
1157 let mut wrote = 0usize;
1158 for (_, _, pk, last) in pending.iter() {
1159 let key = rx_counter_key_bytes(pk);
1160 self.counter_store.store(&key, *last).await?;
1161 wrote += 1;
1162 }
1163 if wrote > 0 {
1164 self.counter_store.flush().await?;
1165 for (identity_index, peer_id, _, last) in pending.iter() {
1168 if let Some(slot) = self.identities[*identity_index as usize].as_mut() {
1169 if let Some(state) = slot.peer_crypto_mut().get_mut(peer_id) {
1170 state.persisted_rx_counter = *last;
1171 state.needs_rx_persist = false;
1172 }
1173 }
1174 }
1175 }
1176 Ok(wrote)
1177 }
1178
1179 pub async fn load_all_persisted_rx_counters(
1187 &mut self,
1188 ) -> Result<usize, <P::CounterStore as CounterStore>::Error> {
1189 let mut loaded = 0usize;
1190 for peer_index in 0..PEERS {
1192 let peer_id = crate::peers::PeerId(peer_index as u8);
1193 let Some(info) = self.peer_registry.get(peer_id) else {
1194 break;
1195 };
1196 let pk = info.public_key;
1197 let key = rx_counter_key_bytes(&pk.0);
1198 let stored = self.counter_store.load(&key).await?;
1199 if stored > 0 {
1200 if let Some(info) = self.peer_registry.get_mut(peer_id) {
1201 info.initial_rx_counter = stored;
1202 loaded += 1;
1203 }
1204 }
1205 }
1206 Ok(loaded)
1207 }
1208
1209 #[cfg(feature = "software-crypto")]
1210 pub fn register_ephemeral(
1212 &mut self,
1213 parent: LocalIdentityId,
1214 identity: umsh_crypto::software::SoftwareIdentity,
1215 ) -> Result<LocalIdentityId, CapacityError> {
1216 self.insert_identity(LocalIdentity::Ephemeral(identity), Some(parent))
1217 }
1218
1219 #[cfg(feature = "software-crypto")]
1220 pub fn remove_ephemeral(&mut self, id: LocalIdentityId) -> bool {
1222 if let Some(slot) = self.identities.get_mut(id.0 as usize) {
1223 let should_remove = slot
1224 .as_ref()
1225 .map(|identity_slot| identity_slot.identity().is_ephemeral())
1226 .unwrap_or(false);
1227 if should_remove {
1228 *slot = None;
1229 return true;
1230 }
1231 }
1232 false
1233 }
1234
1235 pub fn identity(
1237 &self,
1238 id: LocalIdentityId,
1239 ) -> Option<&IdentitySlot<P::Identity, PEERS, ACKS, FRAME>> {
1240 self.identities.get(id.0 as usize)?.as_ref()
1241 }
1242
1243 pub fn identity_mut(
1245 &mut self,
1246 id: LocalIdentityId,
1247 ) -> Option<&mut IdentitySlot<P::Identity, PEERS, ACKS, FRAME>> {
1248 self.identities.get_mut(id.0 as usize)?.as_mut()
1249 }
1250
1251 pub fn add_peer(&mut self, key: PublicKey) -> Result<PeerId, AddPeerError> {
1258 #[cfg(feature = "software-crypto")]
1259 {
1260 if !umsh_crypto::is_valid_ed25519_public_key(&key) {
1261 return Err(AddPeerError::InvalidPublicKey);
1262 }
1263 }
1264 Ok(self.peer_registry.try_insert_or_update(key)?)
1265 }
1266
1267 pub fn remove_peer(&mut self, key: &PublicKey) -> bool {
1277 let Some((peer_id, _)) = self.peer_registry.lookup_by_key(key) else {
1278 return false;
1279 };
1280 self.clear_peer_slot_state(peer_id);
1281 if self
1282 .deferred_counter_resync_frame
1283 .as_ref()
1284 .is_some_and(|deferred| deferred.peer_id == peer_id)
1285 {
1286 self.deferred_counter_resync_frame = None;
1287 }
1288 let Some(removal) = self.peer_registry.remove(peer_id) else {
1289 return false;
1290 };
1291 if let Some((old_id, new_id)) = removal.moved {
1292 self.rekey_peer_slot_state(old_id, new_id);
1293 }
1294 true
1295 }
1296
1297 pub fn add_channel(&mut self, key: ChannelKey) -> Result<(), CapacityError> {
1299 let derived = self.crypto.derive_channel_keys(&key);
1300 self.channels.try_add(key, derived)
1301 }
1302
1303 pub fn remove_channel(&mut self, key: &ChannelKey) -> bool {
1307 self.channels.remove_by_key(key)
1308 }
1309
1310 pub fn add_named_channel(&mut self, name: &str) -> Result<(), crate::AddChannelError> {
1315 let key = self
1316 .crypto
1317 .derive_named_channel_key(name)
1318 .map_err(crate::AddChannelError::InvalidName)?;
1319 self.add_channel(key)?;
1320 Ok(())
1321 }
1322
1323 pub fn identity_count(&self) -> usize {
1325 self.identities.iter().filter(|slot| slot.is_some()).count()
1326 }
1327
1328 #[cfg(any(feature = "unsafe-advanced", test))]
1335 pub(crate) fn install_pairwise_keys(
1336 &mut self,
1337 identity_id: LocalIdentityId,
1338 peer_id: PeerId,
1339 pairwise_keys: PairwiseKeys,
1340 ) -> Result<Option<crate::peers::PeerCryptoState>, SendError> {
1341 let initial = self
1343 .peer_registry
1344 .get(peer_id)
1345 .map(|info| info.initial_rx_counter)
1346 .unwrap_or(0);
1347 let now_ms = self.clock.now_ms();
1348 let mut replay_window = ReplayWindow::new();
1349 if initial > 0 {
1350 replay_window.reset(initial, now_ms);
1351 }
1352 let slot = self
1353 .identity_mut(identity_id)
1354 .ok_or(SendError::IdentityMissing)?;
1355 slot.peer_crypto_mut()
1356 .insert(
1357 peer_id,
1358 crate::peers::PeerCryptoState {
1359 pairwise_keys,
1360 replay_window,
1361 persisted_rx_counter: initial,
1362 needs_rx_persist: false,
1363 },
1364 )
1365 .map_err(|_| SendError::QueueFull)
1366 }
1367
1368 #[cfg(feature = "unsafe-advanced")]
1375 pub fn install_pairwise_keys_advanced(
1376 &mut self,
1377 identity_id: LocalIdentityId,
1378 peer_id: PeerId,
1379 pairwise_keys: PairwiseKeys,
1380 ) -> Result<Option<crate::peers::PeerCryptoState>, SendError> {
1381 self.install_pairwise_keys(identity_id, peer_id, pairwise_keys)
1382 }
1383
1384 pub fn queue_broadcast(
1396 &mut self,
1397 from: LocalIdentityId,
1398 payload: &[u8],
1399 options: &SendOptions,
1400 ) -> Result<SendReceipt, SendError> {
1401 let mut options = options.clone();
1407 options.encrypted = false;
1408 options.ack_requested = false;
1409 options.salt = false;
1410 let options = &options;
1411 self.enforce_send_policy(None, options, false)?;
1412
1413 let slot = self.identity_mut(from).ok_or(SendError::IdentityMissing)?;
1414 let source_key = *slot.identity().public_key();
1415 let receipt = slot.next_receipt();
1416 let mut buf = [0u8; FRAME];
1417 let builder = PacketBuilder::new(&mut buf).broadcast();
1418 let mut builder = if options.full_source {
1419 builder.source_full(&source_key)
1420 } else {
1421 builder.source_hint(source_key.hint())
1422 };
1423 if let Some(hops) = options.flood_hops {
1424 builder = builder.flood_hops(hops);
1425 }
1426 if options.trace_route {
1427 builder = builder.trace_route();
1428 }
1429 if let Some(route) = options
1432 .source_route
1433 .as_ref()
1434 .filter(|route| !route.is_empty())
1435 {
1436 builder = builder.source_route(route.as_slice());
1437 }
1438 if let Some(callsign) = self.operating_policy.operator_callsign {
1439 builder = builder.option(OptionNumber::OperatorCallsign, callsign.as_trimmed_slice());
1440 }
1441 if options.trace_signal {
1442 builder = builder.trace_signal();
1443 }
1444 if let Some(region_code) = options.region_code {
1445 builder = builder.region_code(region_code);
1446 }
1447 let frame = builder.payload(payload).build()?;
1448 if frame.len() > self.radio.max_frame_size() {
1449 return Err(SendError::Build(BuildError::BufferTooSmall));
1450 }
1451 let not_before_ms = self.tx_not_before_ms(options);
1452 self.tx_queue
1453 .enqueue_with_state(
1454 TxPriority::Application,
1455 frame,
1456 Some(receipt),
1457 Some(from),
1458 not_before_ms,
1459 0,
1460 0,
1461 )
1462 .map_err(|_| SendError::QueueFull)?;
1463 Ok(receipt)
1464 }
1465
1466 pub async fn send_broadcast(
1468 &mut self,
1469 from: LocalIdentityId,
1470 payload: &[u8],
1471 options: &SendOptions,
1472 ) -> Result<SendReceipt, SendError> {
1473 self.queue_broadcast(from, payload, options)
1474 }
1475
1476 pub fn queue_multicast(
1478 &mut self,
1479 from: LocalIdentityId,
1480 channel_id: &ChannelId,
1481 payload: &[u8],
1482 options: &SendOptions,
1483 ) -> Result<SendReceipt, SendError> {
1484 self.enforce_send_policy(Some(*channel_id), options, false)?;
1485 let derived = self
1492 .channels
1493 .lookup_by_id(channel_id)
1494 .next()
1495 .ok_or(SendError::ChannelMissing)?
1496 .derived
1497 .clone();
1498 let keys = PairwiseKeys {
1499 k_enc: derived.k_enc,
1500 k_mic: derived.k_mic,
1501 };
1502 let receipt = self
1503 .identity_mut(from)
1504 .ok_or(SendError::IdentityMissing)?
1505 .next_receipt();
1506 let (source_key, frame_counter) = self.identity_and_advance(from)?;
1507 let salt = self.take_salt(options);
1508 let mut buf = [0u8; FRAME];
1509 let builder = PacketBuilder::new(&mut buf).multicast(*channel_id);
1510 let builder = if options.full_source {
1511 builder.source_full(&source_key)
1512 } else {
1513 builder.source_hint(source_key.hint())
1514 };
1515 let mut builder = builder.frame_counter(frame_counter);
1516 if options.encrypted {
1517 builder = builder.encrypted();
1518 }
1519 builder = builder.mic_size(options.mic_size);
1520 if let Some(salt) = salt {
1521 builder = builder.salt(salt);
1522 }
1523 if let Some(hops) = options.flood_hops {
1524 builder = builder.flood_hops(hops);
1525 }
1526 if options.trace_route {
1527 builder = builder.trace_route();
1528 }
1529 if let Some(route) = options
1532 .source_route
1533 .as_ref()
1534 .filter(|route| !route.is_empty())
1535 {
1536 builder = builder.source_route(route.as_slice());
1537 }
1538 if let Some(callsign) = self.operating_policy.operator_callsign {
1539 builder = builder.option(OptionNumber::OperatorCallsign, callsign.as_trimmed_slice());
1540 }
1541 if options.trace_signal {
1542 builder = builder.trace_signal();
1543 }
1544 if let Some(region_code) = options.region_code {
1545 builder = builder.region_code(region_code);
1546 }
1547 let mut packet = builder.payload(payload).build()?;
1548 self.crypto.seal_packet(&mut packet, &keys)?;
1549 let not_before_ms = self.tx_not_before_ms(options);
1550 self.enqueue_packet(packet, Some(receipt), Some(from), not_before_ms)?;
1551 Ok(receipt)
1552 }
1553
1554 pub async fn send_multicast(
1556 &mut self,
1557 from: LocalIdentityId,
1558 channel_id: &ChannelId,
1559 payload: &[u8],
1560 options: &SendOptions,
1561 ) -> Result<SendReceipt, SendError> {
1562 self.queue_multicast(from, channel_id, payload, options)
1563 }
1564
1565 pub fn queue_mac_ack_for_peer(
1567 &mut self,
1568 peer_id: PeerId,
1569 ack_trailer: [u8; 8],
1570 ) -> Result<(), SendError> {
1571 let mut buf = [0u8; FRAME];
1572 let mut builder = PacketBuilder::new(&mut buf).mac_ack(ack_trailer);
1573 if let Some(peer) = self.peer_registry.get(peer_id) {
1574 match peer.route.as_ref() {
1575 Some(CachedRoute::Source(route)) if !route.is_empty() => {
1576 builder = builder.source_route(route.as_slice());
1577 }
1578 Some(CachedRoute::Flood { hops, regions }) => {
1579 builder = builder.flood_hops((*hops).clamp(1, MAX_FLOOD_HOPS));
1580 for region in regions {
1581 builder = builder.region_code(*region);
1582 }
1583 }
1584 _ => {}
1588 }
1589 }
1590 let frame = builder.build()?;
1591 if frame.len() > self.radio.max_frame_size() {
1592 return Err(SendError::Build(BuildError::BufferTooSmall));
1593 }
1594 self.tx_queue
1595 .enqueue(TxPriority::ImmediateAck, frame, None, None)
1596 .map_err(|_| SendError::QueueFull)?;
1597 Ok(())
1598 }
1599
1600 pub fn queue_mac_ack(&mut self, ack_trailer: [u8; 8]) -> Result<(), SendError> {
1602 let mut buf = [0u8; FRAME];
1603 let frame = PacketBuilder::new(&mut buf).mac_ack(ack_trailer).build()?;
1604 if frame.len() > self.radio.max_frame_size() {
1605 return Err(SendError::Build(BuildError::BufferTooSmall));
1606 }
1607 self.tx_queue
1608 .enqueue(TxPriority::ImmediateAck, frame, None, None)
1609 .map_err(|_| SendError::QueueFull)?;
1610 Ok(())
1611 }
1612
1613 pub fn queue_unicast(
1615 &mut self,
1616 from: LocalIdentityId,
1617 peer: &PublicKey,
1618 payload: &[u8],
1619 options: &SendOptions,
1620 ) -> Result<Option<SendReceipt>, SendError> {
1621 self.enforce_send_policy(None, options, false)?;
1622 let (peer_id, _) = self
1623 .peer_registry
1624 .lookup_by_key(peer)
1625 .ok_or(SendError::PeerMissing)?;
1626 let pairwise_keys = self
1627 .identity(from)
1628 .ok_or(SendError::IdentityMissing)?
1629 .peer_crypto()
1630 .get(&peer_id)
1631 .ok_or(SendError::PairwiseKeysMissing)?
1632 .pairwise_keys
1633 .clone();
1634 let effective_source_route = self.effective_source_route(peer_id, options);
1635 let effective_flood_hops =
1636 self.effective_flood_hops(peer_id, options, effective_source_route.as_ref());
1637
1638 let (source_key, frame_counter) = self.identity_and_advance(from)?;
1639 let salt = self.take_salt(options);
1640 let mut buf = [0u8; FRAME];
1641 let builder = PacketBuilder::new(&mut buf).unicast(peer.hint());
1642 let builder = if options.full_source {
1643 builder.source_full(&source_key)
1644 } else {
1645 builder.source_hint(source_key.hint())
1646 };
1647 let mut builder = builder.frame_counter(frame_counter);
1648 if options.ack_requested {
1649 builder = builder.ack_requested();
1650 }
1651 if options.encrypted {
1652 builder = builder.encrypted();
1653 }
1654 builder = builder.mic_size(options.mic_size);
1655 if let Some(salt) = salt {
1656 builder = builder.salt(salt);
1657 }
1658 if let Some(hops) = effective_flood_hops {
1659 builder = builder.flood_hops(hops);
1660 }
1661 if options.trace_route {
1662 builder = builder.trace_route();
1663 }
1664 if let Some(route) = effective_source_route.as_ref() {
1665 builder = builder.source_route(route.as_slice());
1666 }
1667 if let Some(callsign) = self.operating_policy.operator_callsign {
1668 builder = builder.option(OptionNumber::OperatorCallsign, callsign.as_trimmed_slice());
1669 }
1670 if options.trace_signal {
1671 builder = builder.trace_signal();
1672 }
1673 if let Some(region_code) = options.region_code {
1674 builder = builder.region_code(region_code);
1675 }
1676 let mut packet = builder.payload(payload).build()?;
1677
1678 let receipt = if options.ack_requested {
1679 Some(self.prepare_pending_ack(from, *peer, &packet, &pairwise_keys, options)?)
1680 } else {
1681 None
1682 };
1683
1684 self.crypto.seal_packet(&mut packet, &pairwise_keys)?;
1685 if let Some(receipt) = receipt {
1686 self.refresh_pending_resend(
1687 from,
1688 receipt,
1689 packet.as_bytes(),
1690 effective_source_route
1691 .as_ref()
1692 .map(|route| route.as_slice()),
1693 options.flood_hops,
1694 )?;
1695 }
1696 let tracked_receipt = match receipt {
1700 Some(receipt) => Some(receipt),
1701 None if Self::frame_solicits_repeat(packet.as_bytes()) => {
1702 Some(self.prepare_repeat_confirmed_send(
1703 from,
1704 *peer,
1705 packet.as_bytes(),
1706 effective_source_route.as_ref(),
1707 options.flood_hops,
1708 )?)
1709 }
1710 None => None,
1711 };
1712 let not_before_ms = self.tx_not_before_ms(options);
1713 if let Err(err) = self.enqueue_packet(packet, tracked_receipt, Some(from), not_before_ms) {
1714 if let Some(tracked_receipt) = tracked_receipt {
1715 let _ = self
1716 .identity_mut(from)
1717 .and_then(|slot| slot.remove_pending_ack(&tracked_receipt));
1718 }
1719 return Err(err);
1720 }
1721 Ok(receipt)
1722 }
1723
1724 pub async fn send_unicast(
1726 &mut self,
1727 from: LocalIdentityId,
1728 peer: &PublicKey,
1729 payload: &[u8],
1730 options: &SendOptions,
1731 ) -> Result<Option<SendReceipt>, SendError> {
1732 let (peer_id, _) = self
1733 .peer_registry
1734 .lookup_by_key(peer)
1735 .ok_or(SendError::PeerMissing)?;
1736 let _ = self.ensure_peer_crypto(from, peer_id).await?;
1737 self.queue_unicast(from, peer, payload, options)
1738 }
1739
1740 pub fn queue_blind_unicast(
1742 &mut self,
1743 from: LocalIdentityId,
1744 peer: &PublicKey,
1745 channel_id: &ChannelId,
1746 payload: &[u8],
1747 options: &SendOptions,
1748 ) -> Result<Option<SendReceipt>, SendError> {
1749 self.enforce_send_policy(Some(*channel_id), options, true)?;
1750 let (peer_id, _) = self
1751 .peer_registry
1752 .lookup_by_key(peer)
1753 .ok_or(SendError::PeerMissing)?;
1754 let pairwise_keys = self
1755 .identity(from)
1756 .ok_or(SendError::IdentityMissing)?
1757 .peer_crypto()
1758 .get(&peer_id)
1759 .ok_or(SendError::PairwiseKeysMissing)?
1760 .pairwise_keys
1761 .clone();
1762 let channel_keys = self
1763 .channels
1764 .lookup_by_id(channel_id)
1765 .next()
1766 .ok_or(SendError::ChannelMissing)?
1767 .derived
1768 .clone();
1769 let blind_keys = self.crypto.derive_blind_keys(&pairwise_keys, &channel_keys);
1770 let effective_source_route = self.effective_source_route(peer_id, options);
1771 let effective_flood_hops =
1772 self.effective_flood_hops(peer_id, options, effective_source_route.as_ref());
1773
1774 let (source_key, frame_counter) = self.identity_and_advance(from)?;
1775 let salt = self.take_salt(options);
1776 let mut buf = [0u8; FRAME];
1777 let builder = PacketBuilder::new(&mut buf).blind_unicast(*channel_id, peer.hint());
1778 let builder = if options.full_source {
1779 builder.source_full(&source_key)
1780 } else {
1781 builder.source_hint(source_key.hint())
1782 };
1783 let mut builder = builder.frame_counter(frame_counter);
1784 if options.ack_requested {
1785 builder = builder.ack_requested();
1786 }
1787 if !options.encrypted {
1788 builder = builder.unencrypted();
1789 }
1790 builder = builder.mic_size(options.mic_size);
1791 if let Some(salt) = salt {
1792 builder = builder.salt(salt);
1793 }
1794 if let Some(hops) = effective_flood_hops {
1795 builder = builder.flood_hops(hops);
1796 }
1797 if options.trace_route {
1798 builder = builder.trace_route();
1799 }
1800 if let Some(route) = effective_source_route.as_ref() {
1801 builder = builder.source_route(route.as_slice());
1802 }
1803 if let Some(callsign) = self.operating_policy.operator_callsign {
1804 builder = builder.option(OptionNumber::OperatorCallsign, callsign.as_trimmed_slice());
1805 }
1806 if options.trace_signal {
1807 builder = builder.trace_signal();
1808 }
1809 if let Some(region_code) = options.region_code {
1810 builder = builder.region_code(region_code);
1811 }
1812 let mut packet = builder.payload(payload).build()?;
1813
1814 let receipt = if options.ack_requested {
1815 Some(self.prepare_pending_ack(from, *peer, &packet, &blind_keys, options)?)
1816 } else {
1817 None
1818 };
1819
1820 self.crypto
1821 .seal_blind_packet(&mut packet, &blind_keys, &channel_keys)
1822 .map_err(SendError::Crypto)?;
1823 if let Some(receipt) = receipt {
1824 self.refresh_pending_resend(
1825 from,
1826 receipt,
1827 packet.as_bytes(),
1828 effective_source_route
1829 .as_ref()
1830 .map(|route| route.as_slice()),
1831 options.flood_hops,
1832 )?;
1833 }
1834 let tracked_receipt = match receipt {
1838 Some(receipt) => Some(receipt),
1839 None if Self::frame_solicits_repeat(packet.as_bytes()) => {
1840 Some(self.prepare_repeat_confirmed_send(
1841 from,
1842 *peer,
1843 packet.as_bytes(),
1844 effective_source_route.as_ref(),
1845 options.flood_hops,
1846 )?)
1847 }
1848 None => None,
1849 };
1850 let not_before_ms = self.tx_not_before_ms(options);
1851 if let Err(err) = self.enqueue_packet(packet, tracked_receipt, Some(from), not_before_ms) {
1852 if let Some(tracked_receipt) = tracked_receipt {
1853 let _ = self
1854 .identity_mut(from)
1855 .and_then(|slot| slot.remove_pending_ack(&tracked_receipt));
1856 }
1857 return Err(err);
1858 }
1859 Ok(receipt)
1860 }
1861
1862 pub async fn send_blind_unicast(
1864 &mut self,
1865 from: LocalIdentityId,
1866 peer: &PublicKey,
1867 channel_id: &ChannelId,
1868 payload: &[u8],
1869 options: &SendOptions,
1870 ) -> Result<Option<SendReceipt>, SendError> {
1871 let (peer_id, _) = self
1872 .peer_registry
1873 .lookup_by_key(peer)
1874 .ok_or(SendError::PeerMissing)?;
1875 let _ = self.ensure_peer_crypto(from, peer_id).await?;
1876 self.queue_blind_unicast(from, peer, channel_id, payload, options)
1877 }
1878
1879 pub async fn transmit_next(
1887 &mut self,
1888 on_event: &mut impl FnMut(LocalIdentityId, crate::MacEventRef<'_>),
1889 ) -> Result<Option<SendReceipt>, MacError<<P::Radio as Radio>::Error>> {
1890 self.expire_post_tx_listen_if_needed();
1891 let Some(queued) = self.tx_queue.pop_next() else {
1892 return Ok(None);
1893 };
1894 let now_ms = self.clock.now_ms();
1895
1896 if queued.not_before_ms > now_ms {
1897 self.requeue_tx(&queued).map_err(|_| MacError::QueueFull)?;
1898 return Ok(None);
1899 }
1900
1901 if self.post_tx_listen.is_some() && queued.priority != TxPriority::ImmediateAck {
1902 self.requeue_tx(&queued).map_err(|_| MacError::QueueFull)?;
1903 return Ok(None);
1904 }
1905
1906 let receipt = queued.receipt;
1907 let identity_id = queued.identity_id;
1908
1909 if queued.priority == TxPriority::Retry
1918 && let (Some(receipt), Some(identity_id)) = (receipt, identity_id)
1919 && self
1920 .identity(identity_id)
1921 .map(|slot| slot.pending_ack(&receipt).is_none())
1922 .unwrap_or(true)
1923 {
1924 return Ok(None);
1925 }
1926
1927 let tx_options = if queued.priority == TxPriority::ImmediateAck {
1928 TxOptions {
1931 cad: umsh_hal::CadPolicy::Skip,
1932 }
1933 } else {
1934 TxOptions {
1935 cad: umsh_hal::CadPolicy::Gate,
1936 }
1937 };
1938 match self
1939 .radio
1940 .transmit(queued.frame.as_slice(), tx_options)
1941 .await
1942 {
1943 Ok(()) => {}
1944 Err(TxError::CadTimeout) => {
1945 let next_attempt = queued.cad_attempts.saturating_add(1);
1946 if next_attempt >= MAX_CAD_ATTEMPTS {
1947 MacCounters::bump(&mut self.counters.tx_abandoned);
1952 if let Some(identity_id) = identity_id
1962 && queued.priority != TxPriority::Retry
1963 {
1964 if let Some(receipt) = receipt
1968 && let Some(slot) = self.identity_mut(identity_id)
1969 {
1970 let _ = slot.pending_acks.remove(&receipt);
1971 }
1972 on_event(
1973 identity_id,
1974 crate::MacEventRef::TxAbandoned {
1975 identity_id,
1976 receipt,
1977 },
1978 );
1979 }
1980 return Ok(None);
1981 }
1982 let backoff_ms = u64::from(
1983 self.rng
1984 .random_range(..self.radio.t_frame_ms().saturating_add(1)),
1985 );
1986 self.tx_queue
1987 .enqueue_with_state(
1988 queued.priority,
1989 queued.frame.as_slice(),
1990 queued.receipt,
1991 queued.identity_id,
1992 now_ms.saturating_add(backoff_ms),
1993 next_attempt,
1994 queued.forward_deferrals,
1995 )
1996 .map_err(|_| MacError::QueueFull)?;
1997 return Ok(None);
1998 }
1999 Err(error) => return Err(MacError::Transmit(error)),
2000 }
2001 MacCounters::bump(&mut self.counters.tx_frames);
2004 if let Some(identity_id) = identity_id {
2005 on_event(
2006 identity_id,
2007 crate::MacEventRef::Transmitted {
2008 identity_id,
2009 receipt,
2010 wire_bytes: queued.frame.as_slice(),
2011 },
2012 );
2013 }
2014 if let Some(receipt) = receipt {
2015 self.note_transmitted_tracked(receipt, queued.frame.as_slice());
2016 }
2017 Ok(receipt)
2018 }
2019
2020 pub async fn drain_tx_queue(
2025 &mut self,
2026 on_event: &mut impl FnMut(LocalIdentityId, crate::MacEventRef<'_>),
2027 ) -> Result<(), MacError<<P::Radio as Radio>::Error>> {
2028 while !self.tx_queue.is_empty() {
2029 let queue_len = self.tx_queue.len();
2030 let _ = self.transmit_next(on_event).await?;
2031 if self.tx_queue.len() >= queue_len {
2032 break;
2033 }
2034 }
2035 Ok(())
2036 }
2037
2038 pub async fn poll_cycle(
2051 &mut self,
2052 mut on_event: impl FnMut(LocalIdentityId, crate::MacEventRef<'_>),
2053 ) -> Result<(), MacError<<P::Radio as Radio>::Error>> {
2054 self.drain_tx_queue(&mut on_event).await?;
2055 if self.post_tx_listen.is_some() {
2056 self.service_post_tx_listen(&mut on_event).await?;
2057 } else {
2058 let _ = self.receive_one(&mut on_event).await?;
2059 }
2060 self.drain_tx_queue(&mut on_event).await?;
2061 self.service_pending_ack_timeouts(&mut on_event)
2062 .map_err(|_| MacError::QueueFull)?;
2063 Ok(())
2064 }
2065
2066 pub fn earliest_deadline_ms(&self) -> Option<u64> {
2073 let mut earliest: Option<u64> = None;
2074
2075 if let Some(listen) = &self.post_tx_listen {
2076 earliest =
2077 Some(earliest.map_or(listen.deadline_ms, |e: u64| e.min(listen.deadline_ms)));
2078 }
2079
2080 for slot in self.identities.iter().filter_map(|s| s.as_ref()) {
2081 for (_, pending) in slot.pending_acks.iter() {
2082 if !matches!(pending.state, crate::AckState::Queued { .. }) {
2083 earliest = Some(earliest.map_or(pending.ack_deadline_ms, |e: u64| {
2084 e.min(pending.ack_deadline_ms)
2085 }));
2086 }
2087 if let crate::AckState::AwaitingForward {
2092 confirm_deadline_ms,
2093 } = pending.state
2094 && pending.retries < MAX_FORWARD_RETRIES
2095 {
2096 earliest = Some(
2097 earliest.map_or(confirm_deadline_ms, |e: u64| e.min(confirm_deadline_ms)),
2098 );
2099 }
2100 }
2101 }
2102
2103 if let Some(nb) = self.tx_queue.earliest_not_before_ms() {
2104 earliest = Some(earliest.map_or(nb, |e: u64| e.min(nb)));
2105 }
2106
2107 earliest
2108 }
2109
2110 pub async fn next_event(
2129 &mut self,
2130 mut on_event: impl FnMut(LocalIdentityId, crate::MacEventRef<'_>),
2131 ) -> Result<(), MacError<<P::Radio as Radio>::Error>> {
2132 loop {
2133 self.drain_tx_queue(&mut on_event).await?;
2135
2136 let mut buf = [0u8; FRAME];
2138 let reason = poll_fn(|cx| self.poll_wait_for_wake(cx, &mut buf))
2139 .await
2140 .map_err(MacError::Radio)?;
2141
2142 self.process_wake_reason(reason, &mut buf, &mut on_event)
2144 .await?;
2145
2146 let _ = self.service_counter_persistence().await;
2150 let _ = self.service_rx_counter_persistence().await;
2151
2152 if !self.tx_queue.is_empty() {
2155 continue;
2156 }
2157
2158 return Ok(());
2159 }
2160 }
2161
2162 pub fn poll_wait_for_wake(
2170 &mut self,
2171 cx: &mut core::task::Context<'_>,
2172 buf: &mut [u8; FRAME],
2173 ) -> Poll<Result<WakeReason, <P::Radio as Radio>::Error>> {
2174 match self.radio.poll_receive(cx, buf) {
2175 Poll::Ready(Ok(rx)) => return Poll::Ready(Ok(WakeReason::Received(rx))),
2176 Poll::Ready(Err(e)) => return Poll::Ready(Err(e)),
2177 Poll::Pending => {}
2178 }
2179
2180 let now_ms = self.clock.now_ms();
2181 if let Some(deadline) = self.earliest_deadline_ms() {
2182 if now_ms >= deadline {
2183 return Poll::Ready(Ok(WakeReason::TimerExpired));
2184 }
2185 if self.clock.poll_delay_until(cx, deadline).is_ready() {
2191 return Poll::Ready(Ok(WakeReason::TimerExpired));
2192 }
2193 }
2194
2195 if self.tx_queue.has_ready(now_ms)
2203 && (self.post_tx_listen.is_none() || self.tx_queue.has_ready_immediate_ack(now_ms))
2204 {
2205 return Poll::Ready(Ok(WakeReason::TimerExpired));
2206 }
2207
2208 Poll::Pending
2209 }
2210
2211 pub async fn process_wake_reason(
2230 &mut self,
2231 reason: WakeReason,
2232 buf: &mut [u8; FRAME],
2233 mut on_event: impl FnMut(LocalIdentityId, crate::MacEventRef<'_>),
2234 ) -> Result<(), MacError<<P::Radio as Radio>::Error>> {
2235 match reason {
2236 WakeReason::Received(rx) => {
2237 let frame_len = rx.len.min(buf.len());
2238 let _ = self
2239 .process_received_frame(buf, frame_len, &rx, &mut on_event)
2240 .await;
2241 }
2242 WakeReason::TimerExpired => {}
2243 }
2244
2245 self.drain_tx_queue(&mut on_event).await?;
2246
2247 self.service_pending_ack_timeouts(&mut on_event)
2248 .map_err(|_| MacError::QueueFull)?;
2249
2250 Ok(())
2251 }
2252
2253 pub async fn run(
2261 &mut self,
2262 mut on_event: impl FnMut(LocalIdentityId, crate::MacEventRef<'_>),
2263 ) -> Result<(), MacError<<P::Radio as Radio>::Error>> {
2264 loop {
2265 self.next_event(&mut on_event).await?;
2266 }
2267 }
2268
2269 pub async fn run_quiet(&mut self) -> Result<(), MacError<<P::Radio as Radio>::Error>> {
2275 self.run(|_, _| {}).await
2276 }
2277
2278 pub async fn process_received_frame(
2288 &mut self,
2289 buf: &mut [u8; FRAME],
2290 frame_len: usize,
2291 rx: &RxInfo,
2292 on_event: impl FnMut(LocalIdentityId, crate::MacEventRef<'_>),
2293 ) -> bool {
2294 MacCounters::bump(&mut self.counters.rx_frames);
2295 let handled = self
2296 .process_received_frame_inner(buf, frame_len, rx, on_event)
2297 .await;
2298 if handled {
2299 MacCounters::bump(&mut self.counters.rx_accepted);
2300 }
2301 handled
2302 }
2303
2304 async fn process_received_frame_inner(
2305 &mut self,
2306 buf: &mut [u8; FRAME],
2307 frame_len: usize,
2308 rx: &RxInfo,
2309 mut on_event: impl FnMut(LocalIdentityId, crate::MacEventRef<'_>),
2310 ) -> bool {
2311 let received_at_ms = self.clock.now_ms();
2312 let mut current_len = frame_len;
2313 let mut current_rx = RxInfo {
2314 len: frame_len,
2315 rssi: rx.rssi,
2316 snr: rx.snr,
2317 lqi: rx.lqi,
2318 };
2319 let mut current_received_at_ms = received_at_ms;
2320 let mut handled_any = false;
2321
2322 loop {
2323 let Ok(header) = PacketHeader::parse(&buf[..current_len]) else {
2324 return handled_any;
2325 };
2326 self.cancel_forwards_for_ack_mic_option(&buf[..current_len], &header);
2331 let forwarding_confirmed = if let Some((identity_id, receipt)) =
2332 self.observe_forwarding_confirmation(&buf[..current_len])
2333 {
2334 let hint = match header.source {
2335 SourceAddrRef::Hint(h) => Some(RouterHint([h.0[0], h.0[1]])),
2336 SourceAddrRef::FullKeyAt { offset } => {
2337 let mut key_bytes = [0u8; 32];
2338 key_bytes.copy_from_slice(&buf[offset..offset + 32]);
2339 let h = PublicKey(key_bytes).hint();
2340 Some(RouterHint([h.0[0], h.0[1]]))
2341 }
2342 _ => None,
2343 };
2344 on_event(
2345 identity_id,
2346 crate::MacEventRef::Forwarded {
2347 identity_id,
2348 receipt,
2349 hint,
2350 },
2351 );
2352 true
2353 } else {
2354 false
2355 };
2356
2357 let (handled, replay_target) = match header.packet_type() {
2358 PacketType::Broadcast => (
2359 self.process_broadcast(
2360 buf,
2361 current_len,
2362 &header,
2363 ¤t_rx,
2364 current_received_at_ms,
2365 &mut on_event,
2366 ),
2367 None,
2368 ),
2369 PacketType::MacAck => (
2370 self.process_mac_ack(
2371 buf,
2372 current_len,
2373 &header,
2374 ¤t_rx,
2375 forwarding_confirmed,
2376 &mut on_event,
2377 ),
2378 None,
2379 ),
2380 PacketType::Unicast | PacketType::UnicastAckReq => {
2381 self.process_unicast(
2382 buf,
2383 current_len,
2384 &header,
2385 ¤t_rx,
2386 current_received_at_ms,
2387 forwarding_confirmed,
2388 &mut on_event,
2389 )
2390 .await
2391 }
2392 PacketType::Multicast => (
2393 self.process_multicast(
2394 buf,
2395 current_len,
2396 &header,
2397 ¤t_rx,
2398 current_received_at_ms,
2399 forwarding_confirmed,
2400 &mut on_event,
2401 ),
2402 None,
2403 ),
2404 PacketType::BlindUnicast | PacketType::BlindUnicastAckReq => {
2405 self.process_blind_unicast(
2406 buf,
2407 current_len,
2408 &header,
2409 ¤t_rx,
2410 current_received_at_ms,
2411 forwarding_confirmed,
2412 &mut on_event,
2413 )
2414 .await
2415 }
2416 PacketType::Reserved5 => (false, None),
2417 };
2418 handled_any |= handled;
2419
2420 let Some((local_id, peer_id)) = replay_target else {
2421 return handled_any;
2422 };
2423 let Some(deferred) = self.take_deferred_counter_resync_frame(local_id, peer_id) else {
2424 return handled_any;
2425 };
2426 current_len = deferred.frame.len();
2427 buf[..current_len].copy_from_slice(deferred.frame.as_slice());
2428 current_rx = RxInfo {
2429 len: current_len,
2430 rssi: deferred.rssi,
2431 snr: deferred.snr,
2432 lqi: deferred.lqi,
2433 };
2434 current_received_at_ms = deferred.received_at_ms;
2435 }
2436 }
2437
2438 fn process_broadcast(
2439 &mut self,
2440 buf: &[u8; FRAME],
2441 frame_len: usize,
2442 header: &PacketHeader,
2443 rx: &RxInfo,
2444 received_at_ms: u64,
2445 mut on_event: impl FnMut(LocalIdentityId, crate::MacEventRef<'_>),
2446 ) -> bool {
2447 let Some((from_hint, from_key)) = Self::resolve_broadcast_source(&buf[..frame_len], header)
2448 else {
2449 return false;
2450 };
2451 if !Self::payload_is_allowed(header.packet_type(), &buf[header.body_range.clone()]) {
2452 return false;
2453 }
2454 let mut delivered = false;
2455 for (index, slot) in self.identities.iter().enumerate() {
2456 if slot.is_none() {
2457 continue;
2458 }
2459 delivered = true;
2460 on_event(
2461 LocalIdentityId(index as u8),
2462 crate::MacEventRef::Received(crate::ReceivedPacketRef::new(
2463 &buf[..frame_len],
2464 &buf[header.body_range.clone()],
2465 header.clone(),
2466 ParsedOptions::extract(&buf[..frame_len], header.options_range.clone())
2467 .unwrap_or_default(),
2468 from_key,
2469 Some(from_hint),
2470 false,
2471 None,
2472 crate::send::RxMetadata::new(
2473 Some(rx.rssi),
2474 Some(rx.snr),
2475 rx.lqi,
2476 Some(received_at_ms),
2477 ),
2478 )),
2479 );
2480 }
2481 let forwarded = self.maybe_forward_received(&buf[..frame_len], header, rx, false);
2485 delivered || forwarded
2486 }
2487
2488 fn process_mac_ack(
2489 &mut self,
2490 buf: &[u8; FRAME],
2491 frame_len: usize,
2492 header: &PacketHeader,
2493 rx: &RxInfo,
2494 forwarding_confirmed: bool,
2495 mut on_event: impl FnMut(LocalIdentityId, crate::MacEventRef<'_>),
2496 ) -> bool {
2497 if header.mic_range.len() != 8 {
2503 return forwarding_confirmed
2504 || self.maybe_forward_received(&buf[..frame_len], header, rx, false);
2505 }
2506 let mut ack_trailer = [0u8; 8];
2507 ack_trailer.copy_from_slice(&buf[header.mic_range.clone()]);
2508 self.cancel_forwards_for_ack_mic(&ack_trailer[..4]);
2513 if let Some(target_peer) = self.peer_for_ack_trailer(&ack_trailer)
2514 && let Some((identity_id, receipt)) = self.complete_ack(&target_peer, &ack_trailer)
2515 {
2516 on_event(
2517 identity_id,
2518 crate::MacEventRef::AckReceived {
2519 peer: target_peer,
2520 receipt,
2521 },
2522 );
2523 return true;
2524 }
2525 forwarding_confirmed || self.maybe_forward_received(&buf[..frame_len], header, rx, false)
2526 }
2527
2528 async fn process_unicast(
2529 &mut self,
2530 buf: &mut [u8; FRAME],
2531 frame_len: usize,
2532 header: &PacketHeader,
2533 rx: &RxInfo,
2534 received_at_ms: u64,
2535 forwarding_confirmed: bool,
2536 mut on_event: impl FnMut(LocalIdentityId, crate::MacEventRef<'_>),
2537 ) -> (bool, Option<(LocalIdentityId, PeerId)>) {
2538 let mut original = [0u8; FRAME];
2539 original[..frame_len].copy_from_slice(&buf[..frame_len]);
2540 let mut replay_target = None;
2541 let handled = if let Some(local_id) = self.find_local_identity_for_dst(header.dst) {
2542 let mut handled = false;
2543 for (peer_id, peer_key) in
2544 self.resolve_source_peer_candidates(&buf[..frame_len], header)
2545 {
2546 let Ok(keys) = self.ensure_peer_crypto(local_id, peer_id).await else {
2547 continue;
2548 };
2549 let Ok(body_range) = self
2550 .crypto
2551 .open_packet(&mut buf[..frame_len], header, &keys)
2552 else {
2553 continue;
2554 };
2555 let payload = &buf[body_range.clone()];
2556 if !Self::payload_is_allowed(header.packet_type(), payload) {
2557 continue;
2558 }
2559 if header.ack_requested()
2560 && self.should_emit_destination_ack(&buf[..frame_len], header)
2561 && self.is_acknowledgeable_unicast_duplicate(
2562 local_id,
2563 peer_id,
2564 header,
2565 &buf[..frame_len],
2566 )
2567 {
2568 let ack_trailer = self.compute_received_ack_trailer(
2569 &buf[..frame_len],
2570 header,
2571 body_range.clone(),
2572 &keys,
2573 );
2574 self.queue_mac_ack_for_peer(peer_id, ack_trailer).ok();
2575 handled = true;
2576 break;
2577 }
2578 match self.unicast_replay_verdict(local_id, peer_id, header, &buf[..frame_len]) {
2579 Some(ReplayVerdict::Accept) => {
2580 let _ = self.accept_unicast_replay(
2581 local_id,
2582 peer_id,
2583 header,
2584 &buf[..frame_len],
2585 );
2586 }
2587 Some(ReplayVerdict::OutOfWindow | ReplayVerdict::Stale) => {
2588 if self.try_accept_counter_resync_response(
2589 local_id,
2590 peer_id,
2591 header,
2592 &buf[..frame_len],
2593 payload,
2594 ) {
2595 replay_target = Some((local_id, peer_id));
2596 } else {
2597 let counter = Self::replay_metadata(header, &buf[..frame_len])
2604 .map(|(c, _)| c)
2605 .unwrap_or(0);
2606 let gap = self
2607 .identity(local_id)
2608 .and_then(|slot| slot.peer_crypto().get(&peer_id))
2609 .map(|state| {
2610 state.replay_window.last_accepted.wrapping_sub(counter)
2611 })
2612 .unwrap_or(0);
2613 if gap >= COUNTER_RESYNC_GAP_THRESHOLD {
2614 self.store_deferred_counter_resync_frame(
2615 local_id,
2616 peer_id,
2617 &original[..frame_len],
2618 rx,
2619 received_at_ms,
2620 );
2621 self.maybe_request_counter_resync(local_id, peer_id, peer_key)
2622 .await;
2623 }
2624 continue;
2625 }
2626 }
2627 Some(ReplayVerdict::Replay) | None => continue,
2628 }
2629 self.learn_route_for_peer(peer_id, &buf[..frame_len], header);
2630
2631 if header.ack_requested()
2632 && self.should_emit_destination_ack(&buf[..frame_len], header)
2633 {
2634 let ack_trailer = self.compute_received_ack_trailer(
2635 &buf[..frame_len],
2636 header,
2637 body_range.clone(),
2638 &keys,
2639 );
2640 self.queue_mac_ack_for_peer(peer_id, ack_trailer).ok();
2641 }
2642
2643 if let Some(data) = Self::echo_request_data(payload) {
2644 let response =
2645 Self::build_echo_command_payload(MAC_COMMAND_ECHO_RESPONSE_ID, data);
2646 let options = Self::echo_response_options(&original[..frame_len], header);
2647 let _ = self
2648 .send_unicast(local_id, &peer_key, response.as_slice(), &options)
2649 .await;
2650 }
2651
2652 on_event(
2653 local_id,
2654 crate::MacEventRef::Received(crate::ReceivedPacketRef::new(
2655 &original[..frame_len],
2656 &buf[body_range],
2657 header.clone(),
2658 ParsedOptions::extract(
2659 &original[..frame_len],
2660 header.options_range.clone(),
2661 )
2662 .unwrap_or_default(),
2663 Some(peer_key),
2664 Some(peer_key.hint()),
2665 true,
2666 None,
2667 crate::send::RxMetadata::new(
2668 Some(rx.rssi),
2669 Some(rx.snr),
2670 rx.lqi,
2671 Some(received_at_ms),
2672 ),
2673 )),
2674 );
2675 handled = true;
2676 break;
2677 }
2678 handled
2679 } else {
2680 false
2681 };
2682 let forwarded = self.maybe_forward_received(&original[..frame_len], header, rx, handled);
2683 (
2684 handled || forwarding_confirmed || forwarded,
2685 handled.then_some(()).and(replay_target),
2686 )
2687 }
2688
2689 fn process_multicast(
2690 &mut self,
2691 buf: &mut [u8; FRAME],
2692 frame_len: usize,
2693 header: &PacketHeader,
2694 rx: &RxInfo,
2695 received_at_ms: u64,
2696 forwarding_confirmed: bool,
2697 mut on_event: impl FnMut(LocalIdentityId, crate::MacEventRef<'_>),
2698 ) -> bool {
2699 let mut original = [0u8; FRAME];
2700 original[..frame_len].copy_from_slice(&buf[..frame_len]);
2701 let delivered = if let Some(channel_id) = header.channel {
2702 let channel_info = {
2703 self.channels
2704 .lookup_by_id(&channel_id)
2705 .next()
2706 .map(|channel| (channel.channel_key.clone(), channel.derived.clone()))
2707 };
2708 if let Some((channel_key, derived)) = channel_info {
2709 let keys = PairwiseKeys {
2710 k_enc: derived.k_enc,
2711 k_mic: derived.k_mic,
2712 };
2713 if let Ok(body_range) =
2714 self.crypto
2715 .open_packet(&mut buf[..frame_len], header, &keys)
2716 {
2717 if !Self::payload_is_allowed(header.packet_type(), &buf[body_range.clone()]) {
2718 false
2719 } else if let Some(source) =
2720 self.resolve_multicast_source(&buf[..frame_len], header)
2721 {
2722 let accepted = if let Some(peer_id) = source.peer_id {
2723 let accepted = self.accept_multicast_replay(
2724 channel_id,
2725 peer_id,
2726 header,
2727 &buf[..frame_len],
2728 );
2729 if accepted {
2730 self.learn_route_for_peer(peer_id, &buf[..frame_len], header);
2731 }
2732 accepted
2733 } else {
2734 self.accept_unknown_multicast_replay(header, &buf[..frame_len])
2735 };
2736 if accepted {
2737 let mut delivered = false;
2738 for (index, slot) in self.identities.iter().enumerate() {
2739 if slot.is_none() {
2740 continue;
2741 }
2742 delivered = true;
2743 on_event(
2744 LocalIdentityId(index as u8),
2745 crate::MacEventRef::Received(crate::ReceivedPacketRef::new(
2746 &original[..frame_len],
2747 &buf[body_range.clone()],
2748 header.clone(),
2749 ParsedOptions::extract(
2750 &original[..frame_len],
2751 header.options_range.clone(),
2752 )
2753 .unwrap_or_default(),
2754 source.public_key,
2755 source
2756 .hint
2757 .or_else(|| source.public_key.map(|key| key.hint())),
2758 true,
2759 Some(crate::ChannelInfoRef {
2760 id: channel_id,
2761 key: &channel_key,
2762 }),
2763 crate::send::RxMetadata::new(
2764 Some(rx.rssi),
2765 Some(rx.snr),
2766 rx.lqi,
2767 Some(received_at_ms),
2768 ),
2769 )),
2770 );
2771 }
2772 delivered
2773 } else {
2774 false
2775 }
2776 } else {
2777 false
2778 }
2779 } else {
2780 false
2781 }
2782 } else {
2783 false
2784 }
2785 } else {
2786 false
2787 };
2788 let forwarded = self.maybe_forward_received(&original[..frame_len], header, rx, false);
2789 delivered || forwarding_confirmed || forwarded
2790 }
2791
2792 async fn process_blind_unicast(
2793 &mut self,
2794 buf: &mut [u8; FRAME],
2795 frame_len: usize,
2796 header: &PacketHeader,
2797 rx: &RxInfo,
2798 received_at_ms: u64,
2799 forwarding_confirmed: bool,
2800 mut on_event: impl FnMut(LocalIdentityId, crate::MacEventRef<'_>),
2801 ) -> (bool, Option<(LocalIdentityId, PeerId)>) {
2802 let mut original = [0u8; FRAME];
2803 original[..frame_len].copy_from_slice(&buf[..frame_len]);
2804 let mut replay_target = None;
2805 let handled = if let Some(channel_id) = header.channel {
2806 let channel_candidates: Vec<(ChannelKey, DerivedChannelKeys), CHANNELS> = self
2807 .channels
2808 .lookup_by_id(&channel_id)
2809 .map(|channel| (channel.channel_key.clone(), channel.derived.clone()))
2810 .collect();
2811 if channel_candidates.is_empty() {
2812 false
2813 } else {
2814 let mut handled = false;
2815 for (resolved_channel_key, channel_keys) in channel_candidates {
2816 buf[..frame_len].copy_from_slice(&original[..frame_len]);
2817 let Ok((dst, source_addr)) = self.crypto.decrypt_blind_addr(
2818 &mut buf[..frame_len],
2819 header,
2820 &channel_keys,
2821 ) else {
2822 continue;
2823 };
2824 let Some(local_id) = self.find_local_identity_for_dst(Some(dst)) else {
2825 continue;
2826 };
2827 for (peer_id, peer_key) in
2828 self.resolve_blind_source_peer_candidates(&buf[..frame_len], source_addr)
2829 {
2830 let Ok(pairwise_keys) = self.ensure_peer_crypto(local_id, peer_id).await
2831 else {
2832 continue;
2833 };
2834 let blind_keys =
2835 self.crypto.derive_blind_keys(&pairwise_keys, &channel_keys);
2836 let body_range = match self.crypto.open_packet(
2837 &mut buf[..frame_len],
2838 header,
2839 &blind_keys,
2840 ) {
2841 Ok(range) => range,
2842 Err(_) => continue,
2843 };
2844 let payload = &buf[body_range.clone()];
2845 if !Self::payload_is_allowed(header.packet_type(), payload) {
2846 continue;
2847 }
2848 if header.ack_requested()
2849 && self.should_emit_destination_ack(&buf[..frame_len], header)
2850 && self.is_acknowledgeable_unicast_duplicate(
2851 local_id,
2852 peer_id,
2853 header,
2854 &buf[..frame_len],
2855 )
2856 {
2857 let ack_trailer = self.compute_received_ack_trailer(
2858 &buf[..frame_len],
2859 header,
2860 body_range.clone(),
2861 &blind_keys,
2862 );
2863 self.queue_mac_ack_for_peer(peer_id, ack_trailer).ok();
2864 handled = true;
2865 break;
2866 }
2867 match self.unicast_replay_verdict(
2868 local_id,
2869 peer_id,
2870 header,
2871 &buf[..frame_len],
2872 ) {
2873 Some(ReplayVerdict::Accept) => {
2874 let _ = self.accept_unicast_replay(
2875 local_id,
2876 peer_id,
2877 header,
2878 &buf[..frame_len],
2879 );
2880 }
2881 Some(ReplayVerdict::OutOfWindow | ReplayVerdict::Stale) => {
2882 if self.try_accept_counter_resync_response(
2883 local_id,
2884 peer_id,
2885 header,
2886 &buf[..frame_len],
2887 payload,
2888 ) {
2889 replay_target = Some((local_id, peer_id));
2890 } else {
2891 self.store_deferred_counter_resync_frame(
2892 local_id,
2893 peer_id,
2894 &original[..frame_len],
2895 rx,
2896 received_at_ms,
2897 );
2898 self.maybe_request_counter_resync(local_id, peer_id, peer_key)
2899 .await;
2900 continue;
2901 }
2902 }
2903 Some(ReplayVerdict::Replay) | None => continue,
2904 }
2905 self.learn_route_for_peer(peer_id, &buf[..frame_len], header);
2906
2907 if header.ack_requested()
2908 && self.should_emit_destination_ack(&buf[..frame_len], header)
2909 {
2910 let ack_trailer = self.compute_received_ack_trailer(
2911 &buf[..frame_len],
2912 header,
2913 body_range.clone(),
2914 &blind_keys,
2915 );
2916 self.queue_mac_ack_for_peer(peer_id, ack_trailer).ok();
2917 }
2918
2919 if let Some(data) = Self::echo_request_data(payload) {
2920 let response = Self::build_echo_command_payload(
2921 MAC_COMMAND_ECHO_RESPONSE_ID,
2922 data,
2923 );
2924 let options =
2925 Self::echo_response_options(&original[..frame_len], header);
2926 let _ = self
2927 .send_unicast(local_id, &peer_key, response.as_slice(), &options)
2928 .await;
2929 }
2930
2931 on_event(
2932 local_id,
2933 crate::MacEventRef::Received(crate::ReceivedPacketRef::new(
2934 &original[..frame_len],
2935 &buf[body_range],
2936 header.clone(),
2937 ParsedOptions::extract(
2938 &original[..frame_len],
2939 header.options_range.clone(),
2940 )
2941 .unwrap_or_default(),
2942 Some(peer_key),
2943 Some(peer_key.hint()),
2944 true,
2945 Some(crate::ChannelInfoRef {
2946 id: channel_id,
2947 key: &resolved_channel_key,
2948 }),
2949 crate::send::RxMetadata::new(
2950 Some(rx.rssi),
2951 Some(rx.snr),
2952 rx.lqi,
2953 Some(received_at_ms),
2954 ),
2955 )),
2956 );
2957 handled = true;
2958 break;
2959 }
2960 if handled {
2961 break;
2962 }
2963 }
2964 handled
2965 }
2966 } else {
2967 false
2968 };
2969 let forwarded = self.maybe_forward_received(&original[..frame_len], header, rx, handled);
2970 (
2971 handled || forwarding_confirmed || forwarded,
2972 handled.then_some(()).and(replay_target),
2973 )
2974 }
2975
2976 pub async fn receive_one(
2982 &mut self,
2983 mut on_event: impl FnMut(LocalIdentityId, crate::MacEventRef<'_>),
2984 ) -> Result<bool, MacError<<P::Radio as Radio>::Error>> {
2985 let mut buf = [0u8; FRAME];
2986 let Some(rx) = poll_fn(|cx| match self.radio.poll_receive(cx, &mut buf) {
2987 Poll::Ready(Ok(rx)) => Poll::Ready(Ok(Some(rx))),
2988 Poll::Ready(Err(error)) => Poll::Ready(Err(error)),
2989 Poll::Pending => Poll::Ready(Ok(None)),
2990 })
2991 .await
2992 .map_err(MacError::Radio)?
2993 else {
2994 return Ok(false);
2995 };
2996
2997 let frame_len = rx.len.min(buf.len());
2998 Ok(self
2999 .process_received_frame(&mut buf, frame_len, &rx, &mut on_event)
3000 .await)
3001 }
3002
3003 pub fn complete_ack(
3005 &mut self,
3006 peer: &PublicKey,
3007 ack_trailer: &[u8; 8],
3008 ) -> Option<(LocalIdentityId, SendReceipt)> {
3009 for (index, slot) in self.identities.iter_mut().enumerate() {
3010 let Some(slot) = slot.as_mut() else {
3011 continue;
3012 };
3013
3014 let receipt = slot.pending_acks.iter().find_map(|(receipt, pending)| {
3015 (pending.expects_ack()
3016 && pending.peer == *peer
3017 && pending.ack_trailer == *ack_trailer)
3018 .then_some(*receipt)
3019 });
3020
3021 if let Some(receipt) = receipt {
3022 slot.pending_acks.remove(&receipt);
3023 let identity_id = LocalIdentityId(index as u8);
3024 self.tx_queue.remove_all_matching(|entry| {
3028 entry.receipt == Some(receipt) && entry.identity_id == Some(identity_id)
3029 });
3030 if self
3031 .post_tx_listen
3032 .as_ref()
3033 .map(|listen| listen.identity_id == identity_id && listen.receipt == receipt)
3034 .unwrap_or(false)
3035 {
3036 self.post_tx_listen = None;
3037 }
3038 return Some((identity_id, receipt));
3039 }
3040 }
3041
3042 None
3043 }
3044
3045 pub fn service_pending_ack_timeouts(
3047 &mut self,
3048 mut on_event: impl FnMut(LocalIdentityId, crate::MacEventRef<'_>),
3049 ) -> Result<(), CapacityError> {
3050 self.expire_post_tx_listen_if_needed();
3051
3052 #[derive(Clone)]
3053 enum Action<const FRAME: usize> {
3054 Retry {
3055 receipt: SendReceipt,
3056 resend: ResendRecord<FRAME>,
3057 not_before_ms: u64,
3058 },
3059 RouteRetry {
3060 receipt: SendReceipt,
3061 peer: PublicKey,
3062 resend: ResendRecord<FRAME>,
3063 not_before_ms: u64,
3064 },
3065 Timeout {
3066 receipt: SendReceipt,
3067 peer: PublicKey,
3068 },
3069 Abandon { receipt: SendReceipt },
3072 }
3073
3074 let now_ms = self.clock.now_ms();
3075 let t_frame_ms = self.radio.t_frame_ms();
3076
3077 for index in 0..self.identities.len() {
3078 let identity_id = LocalIdentityId(index as u8);
3079 let actions = {
3080 let Some(slot) = self.identities[index].as_mut() else {
3081 continue;
3082 };
3083
3084 let mut actions: Vec<Action<FRAME>, ACKS> = Vec::new();
3085 for (receipt, pending) in slot.pending_acks.iter_mut() {
3086 if !matches!(pending.state, crate::AckState::Queued { .. })
3087 && now_ms >= pending.ack_deadline_ms
3088 {
3089 if !pending.expects_ack() {
3090 actions
3091 .push(Action::Abandon { receipt: *receipt })
3092 .map_err(|_| CapacityError)?;
3093 } else if Self::can_attempt_route_retry(pending) {
3094 let backoff_cap_ms = t_frame_ms;
3095 let backoff_ms = if backoff_cap_ms == 0 {
3096 0
3097 } else {
3098 u64::from(self.rng.random_range(..backoff_cap_ms.saturating_add(1)))
3099 };
3100 actions
3101 .push(Action::RouteRetry {
3102 receipt: *receipt,
3103 peer: pending.peer,
3104 resend: pending.resend.clone(),
3105 not_before_ms: now_ms.saturating_add(backoff_ms),
3106 })
3107 .map_err(|_| CapacityError)?;
3108 } else {
3109 actions
3110 .push(Action::Timeout {
3111 receipt: *receipt,
3112 peer: pending.peer,
3113 })
3114 .map_err(|_| CapacityError)?;
3115 }
3116 continue;
3117 }
3118
3119 if let crate::AckState::AwaitingForward {
3120 confirm_deadline_ms,
3121 } = pending.state
3122 {
3123 if now_ms >= confirm_deadline_ms && pending.retries < MAX_FORWARD_RETRIES {
3124 pending.retries = pending.retries.saturating_add(1);
3125 let backoff_cap_ms = t_frame_ms;
3126 let backoff_ms = if backoff_cap_ms == 0 {
3127 0
3128 } else {
3129 u64::from(self.rng.random_range(..backoff_cap_ms.saturating_add(1)))
3130 };
3131 let not_before_ms = now_ms.saturating_add(backoff_ms);
3132 pending.state = crate::AckState::RetryQueued;
3133 actions
3134 .push(Action::Retry {
3135 receipt: *receipt,
3136 resend: pending.resend.clone(),
3137 not_before_ms,
3138 })
3139 .map_err(|_| CapacityError)?;
3140 }
3141 }
3142 }
3143 actions
3144 };
3145
3146 for action in actions {
3147 match action {
3148 Action::Retry {
3149 receipt,
3150 resend,
3151 not_before_ms,
3152 } => {
3153 self.tx_queue.enqueue_with_state(
3154 TxPriority::Retry,
3155 resend.frame.as_slice(),
3156 Some(receipt),
3157 Some(identity_id),
3158 not_before_ms,
3159 0,
3160 0,
3161 )?;
3162 }
3163 Action::RouteRetry {
3164 receipt,
3165 peer,
3166 resend,
3167 not_before_ms,
3168 } => {
3169 if let Some(rewritten) = self.synthesize_route_retry_resend(&peer, &resend)
3170 {
3171 let rewritten_key = Self::confirmation_key(rewritten.frame.as_slice());
3172 let retry_deadline_ms =
3182 not_before_ms.saturating_add(self.forwarded_ack_timeout_ms());
3183 if let Some(pending) = self
3184 .identity_mut(identity_id)
3185 .and_then(|slot| slot.pending_ack_mut(&receipt))
3186 {
3187 pending.resend = rewritten.clone();
3188 pending.retries = 0;
3189 pending.sent_ms = 0;
3190 pending.ack_deadline_ms = retry_deadline_ms;
3191 pending.state = crate::AckState::RetryQueued;
3192 pending.confirm_key = rewritten_key;
3196 }
3197 self.tx_queue.enqueue_with_state(
3198 TxPriority::Retry,
3199 rewritten.frame.as_slice(),
3200 Some(receipt),
3201 Some(identity_id),
3202 not_before_ms,
3203 0,
3204 0,
3205 )?;
3206 } else {
3207 if let Some(slot) = self.identity_mut(identity_id) {
3208 slot.pending_acks.remove(&receipt);
3209 }
3210 on_event(
3211 identity_id,
3212 crate::MacEventRef::AckTimeout { peer, receipt },
3213 );
3214 }
3215 }
3216 Action::Timeout { receipt, peer } => {
3217 if let Some(slot) = self.identity_mut(identity_id) {
3218 slot.pending_acks.remove(&receipt);
3219 }
3220 self.tx_queue.remove_all_matching(|entry| {
3221 entry.receipt == Some(receipt) && entry.identity_id == Some(identity_id)
3222 });
3223 on_event(
3224 identity_id,
3225 crate::MacEventRef::AckTimeout { peer, receipt },
3226 );
3227 }
3228 Action::Abandon { receipt } => {
3229 if let Some(slot) = self.identity_mut(identity_id) {
3230 slot.pending_acks.remove(&receipt);
3231 }
3232 self.tx_queue.remove_all_matching(|entry| {
3233 entry.receipt == Some(receipt) && entry.identity_id == Some(identity_id)
3234 });
3235 }
3236 }
3237 }
3238 }
3239
3240 Ok(())
3241 }
3242
3243 pub fn cancel_pending_ack(
3249 &mut self,
3250 identity_id: LocalIdentityId,
3251 receipt: SendReceipt,
3252 ) -> bool {
3253 let removed = self
3254 .identity_mut(identity_id)
3255 .and_then(|slot| slot.remove_pending_ack(&receipt))
3256 .is_some();
3257
3258 self.tx_queue.remove_first_matching(|entry| {
3260 entry.receipt == Some(receipt) && entry.identity_id == Some(identity_id)
3261 });
3262
3263 if let Some(listen) = &self.post_tx_listen {
3265 if listen.identity_id == identity_id && listen.receipt == receipt {
3266 self.post_tx_listen = None;
3267 }
3268 }
3269
3270 removed
3271 }
3272
3273 fn identity_and_advance(
3274 &mut self,
3275 from: LocalIdentityId,
3276 ) -> Result<(PublicKey, u32), SendError> {
3277 let slot = self.identity_mut(from).ok_or(SendError::IdentityMissing)?;
3278 if slot.counter_window_exhausted() {
3279 return Err(SendError::CounterPersistenceLag);
3280 }
3281 let source_key = *slot.identity().public_key();
3282 let frame_counter = slot.advance_frame_counter();
3283 slot.schedule_counter_persist_if_needed();
3284 Ok((source_key, frame_counter))
3285 }
3286
3287 fn take_salt(&mut self, options: &SendOptions) -> Option<u16> {
3288 options.salt.then(|| self.rng.next_u32() as u16)
3289 }
3290
3291 fn enforce_send_policy(
3292 &self,
3293 channel_id: Option<ChannelId>,
3294 options: &SendOptions,
3295 blind_unicast: bool,
3296 ) -> Result<(), SendError> {
3297 let _authority = self.classify_send_authority(options, blind_unicast)?;
3298
3299 let Some(channel_id) = channel_id else {
3300 return Ok(());
3301 };
3302 let Some(policy) = self
3303 .operating_policy
3304 .channel_policies
3305 .iter()
3306 .find(|policy| policy.channel_id == channel_id)
3307 else {
3308 return Ok(());
3309 };
3310
3311 if policy.require_unencrypted && options.encrypted {
3312 return Err(SendError::PolicyViolation);
3313 }
3314 if policy.require_full_source && !options.full_source {
3315 return Err(SendError::PolicyViolation);
3316 }
3317 if let Some(max_flood_hops) = policy.max_flood_hops {
3318 if options
3319 .flood_hops
3320 .map(|hops| hops > max_flood_hops)
3321 .unwrap_or(false)
3322 {
3323 return Err(SendError::PolicyViolation);
3324 }
3325 }
3326
3327 Ok(())
3328 }
3329
3330 fn classify_send_authority(
3331 &self,
3332 options: &SendOptions,
3333 _blind_unicast: bool,
3334 ) -> Result<TransmitAuthority, SendError> {
3335 match self.operating_policy.amateur_radio_mode {
3336 AmateurRadioMode::Unlicensed => Ok(TransmitAuthority::Unlicensed),
3337 AmateurRadioMode::LicensedOnly => {
3338 if options.encrypted || self.operating_policy.operator_callsign.is_none() {
3339 return Err(SendError::PolicyViolation);
3340 }
3341 Ok(TransmitAuthority::Amateur)
3342 }
3343 AmateurRadioMode::Hybrid => {
3344 if options.encrypted {
3345 Ok(TransmitAuthority::Unlicensed)
3348 } else if self.operating_policy.operator_callsign.is_some() {
3349 Ok(TransmitAuthority::Amateur)
3350 } else {
3351 Ok(TransmitAuthority::Unlicensed)
3352 }
3353 }
3354 }
3355 }
3356
3357 fn enqueue_packet(
3358 &mut self,
3359 packet: UnsealedPacket<'_>,
3360 receipt: Option<SendReceipt>,
3361 identity_id: Option<LocalIdentityId>,
3362 not_before_ms: u64,
3363 ) -> Result<(), SendError> {
3364 if packet.total_len() > self.radio.max_frame_size() {
3365 return Err(SendError::Build(BuildError::BufferTooSmall));
3366 }
3367 self.tx_queue
3368 .enqueue_with_state(
3369 TxPriority::Application,
3370 packet.as_bytes(),
3371 receipt,
3372 identity_id,
3373 not_before_ms,
3374 0,
3375 0,
3376 )
3377 .map_err(|_| SendError::QueueFull)?;
3378 Ok(())
3379 }
3380
3381 fn tx_not_before_ms(&self, options: &SendOptions) -> u64 {
3384 options
3385 .tx_delay_ms
3386 .map(|delay| self.clock.now_ms() + u64::from(delay))
3387 .unwrap_or(0)
3388 }
3389
3390 fn refresh_pending_resend(
3391 &mut self,
3392 from: LocalIdentityId,
3393 receipt: SendReceipt,
3394 frame: &[u8],
3395 source_route: Option<&[RouterHint]>,
3396 requested_flood_hops: Option<u8>,
3397 ) -> Result<(), SendError> {
3398 let resend = ResendRecord::try_new(frame, source_route)
3399 .map_err(|_| SendError::QueueFull)?
3400 .with_requested_flood_hops(requested_flood_hops);
3401 let pending = self
3402 .identity_mut(from)
3403 .ok_or(SendError::IdentityMissing)?
3404 .pending_ack_mut(&receipt)
3405 .ok_or(SendError::IdentityMissing)?;
3406 pending.resend = resend;
3407 Ok(())
3408 }
3409
3410 fn frame_solicits_repeat(frame: &[u8]) -> bool {
3421 let Ok(header) = PacketHeader::parse(frame) else {
3422 return false;
3423 };
3424 if header
3425 .flood_hops
3426 .map(|hops| hops.remaining() > 0)
3427 .unwrap_or(false)
3428 {
3429 return true;
3430 }
3431 ParsedOptions::extract(frame, header.options_range.clone())
3432 .ok()
3433 .and_then(|options| options.source_route)
3434 .map(|range| !range.is_empty())
3435 .unwrap_or(false)
3436 }
3437
3438 fn prepare_repeat_confirmed_send(
3450 &mut self,
3451 from: LocalIdentityId,
3452 peer: PublicKey,
3453 frame: &[u8],
3454 source_route: Option<&Vec<RouterHint, MAX_SOURCE_ROUTE_HOPS>>,
3455 requested_flood_hops: Option<u8>,
3456 ) -> Result<SendReceipt, SendError> {
3457 let resend = ResendRecord::try_new(frame, source_route.map(|route| route.as_slice()))
3458 .map_err(|_| SendError::QueueFull)?
3459 .with_requested_flood_hops(requested_flood_hops);
3460 let slot = self.identity_mut(from).ok_or(SendError::IdentityMissing)?;
3461 let receipt = slot.next_receipt();
3462 slot.try_insert_pending_ack(receipt, PendingAck::repeat_only(peer, resend))
3463 .map_err(|_| SendError::PendingAckFull)?;
3464 Ok(receipt)
3465 }
3466
3467 fn prepare_pending_ack(
3468 &mut self,
3469 from: LocalIdentityId,
3470 peer: PublicKey,
3471 packet: &UnsealedPacket<'_>,
3472 keys: &PairwiseKeys,
3473 options: &SendOptions,
3474 ) -> Result<SendReceipt, SendError> {
3475 let header = PacketHeader::parse(packet.as_bytes())?;
3476 let mut cmac: CmacState<_> = self.crypto.cmac_state(&keys.k_mic);
3477 feed_aad(&header, packet.as_bytes(), |chunk| cmac.update(chunk));
3478 cmac.update(packet.body());
3479 let full_mac = cmac.finalize();
3480 let ack_trailer = self.crypto.compute_ack_trailer(&full_mac, &keys.k_enc);
3481 let is_forwarded = Self::frame_solicits_repeat(packet.as_bytes());
3485 let resend = ResendRecord::try_new(
3486 packet.as_bytes(),
3487 options.source_route.as_ref().map(|route| route.as_slice()),
3488 )
3489 .map_err(|_| SendError::QueueFull)?
3490 .with_requested_flood_hops(options.flood_hops);
3491
3492 let slot = self.identity_mut(from).ok_or(SendError::IdentityMissing)?;
3493 let receipt = slot.next_receipt();
3494 let pending = if is_forwarded {
3495 PendingAck::forwarded(ack_trailer, peer, resend)
3496 } else {
3497 PendingAck::direct(ack_trailer, peer, resend)
3498 };
3499 slot.try_insert_pending_ack(receipt, pending)
3500 .map_err(|_| SendError::PendingAckFull)?;
3501 Ok(receipt)
3502 }
3503
3504 async fn derive_pairwise_keys_for_peer(
3505 &self,
3506 local_id: LocalIdentityId,
3507 peer_key: &PublicKey,
3508 ) -> Result<PairwiseKeys, SendError> {
3509 let shared_secret = {
3510 let slot = self.identity(local_id).ok_or(SendError::IdentityMissing)?;
3511 match slot.identity() {
3512 LocalIdentity::LongTerm(identity) => identity
3513 .agree(peer_key)
3514 .await
3515 .map_err(|_| SendError::IdentityAgreementFailed)?,
3516 #[cfg(feature = "software-crypto")]
3517 LocalIdentity::Ephemeral(identity) => identity
3518 .agree(peer_key)
3519 .await
3520 .map_err(|_| SendError::IdentityAgreementFailed)?,
3521 }
3522 };
3523
3524 Ok(self.crypto.derive_pairwise_keys(&shared_secret))
3525 }
3526
3527 fn effective_source_route(
3528 &self,
3529 peer_id: PeerId,
3530 options: &SendOptions,
3531 ) -> Option<Vec<RouterHint, MAX_SOURCE_ROUTE_HOPS>> {
3532 if let Some(route) = options.source_route.as_ref() {
3535 return (!route.is_empty()).then(|| route.clone());
3536 }
3537
3538 let Some(peer) = self.peer_registry.get(peer_id) else {
3539 return None;
3540 };
3541 match peer.route.as_ref() {
3542 Some(CachedRoute::Source(route)) => Some(route.clone()),
3543 _ => None,
3544 }
3545 }
3546
3547 fn effective_flood_hops(
3560 &self,
3561 peer_id: PeerId,
3562 options: &SendOptions,
3563 source_route: Option<&Vec<RouterHint, MAX_SOURCE_ROUTE_HOPS>>,
3564 ) -> Option<u8> {
3565 let requested = options.flood_hops?;
3566 let cached = self
3567 .peer_registry
3568 .get(peer_id)
3569 .and_then(|peer| peer.route.as_ref());
3570 let source_route = source_route.filter(|route| !route.is_empty());
3573
3574 let ceiling = match (source_route, cached) {
3575 (Some(_), _) | (None, Some(CachedRoute::Direct)) => ESTABLISHED_ROUTE_EXTRA_HOPS,
3580 (None, Some(CachedRoute::Flood { hops, .. })) => {
3582 hops.saturating_add(ESTABLISHED_ROUTE_EXTRA_HOPS)
3583 }
3584 (None, Some(CachedRoute::Source(route))) => u8::try_from(route.len())
3587 .unwrap_or(u8::MAX)
3588 .saturating_add(ESTABLISHED_ROUTE_EXTRA_HOPS),
3589 (None, None) => MAX_FLOOD_HOPS,
3591 };
3592
3593 Some(requested.min(ceiling).min(MAX_FLOOD_HOPS))
3598 }
3599
3600 fn cache_peer_crypto(
3601 &mut self,
3602 local_id: LocalIdentityId,
3603 peer_id: PeerId,
3604 pairwise_keys: PairwiseKeys,
3605 ) -> Result<(), SendError> {
3606 let slot = self
3607 .identity_mut(local_id)
3608 .ok_or(SendError::IdentityMissing)?;
3609 if slot.peer_crypto().get(&peer_id).is_some() {
3610 return Ok(());
3611 }
3612 let initial = self
3613 .peer_registry
3614 .get(peer_id)
3615 .map(|info| info.initial_rx_counter)
3616 .unwrap_or(0);
3617 let now_ms = self.clock.now_ms();
3618 let mut replay_window = ReplayWindow::new();
3619 if initial > 0 {
3620 replay_window.reset(initial, now_ms);
3621 }
3622 let slot = self
3623 .identity_mut(local_id)
3624 .ok_or(SendError::IdentityMissing)?;
3625 slot.peer_crypto_mut()
3626 .insert(
3627 peer_id,
3628 crate::peers::PeerCryptoState {
3629 pairwise_keys,
3630 replay_window,
3631 persisted_rx_counter: initial,
3632 needs_rx_persist: false,
3633 },
3634 )
3635 .map_err(|_| SendError::QueueFull)?;
3636 Ok(())
3637 }
3638
3639 async fn ensure_peer_crypto(
3640 &mut self,
3641 local_id: LocalIdentityId,
3642 peer_id: PeerId,
3643 ) -> Result<PairwiseKeys, SendError> {
3644 if let Some(keys) = self
3645 .identity(local_id)
3646 .and_then(|slot| slot.peer_crypto().get(&peer_id))
3647 .map(|state| state.pairwise_keys.clone())
3648 {
3649 return Ok(keys);
3650 }
3651
3652 let peer_key = self
3653 .peer_registry
3654 .get(peer_id)
3655 .ok_or(SendError::PeerMissing)?
3656 .public_key;
3657 let pairwise_keys = self
3658 .derive_pairwise_keys_for_peer(local_id, &peer_key)
3659 .await?;
3660 self.cache_peer_crypto(local_id, peer_id, pairwise_keys.clone())?;
3661 Ok(pairwise_keys)
3662 }
3663
3664 fn insert_identity(
3665 &mut self,
3666 identity: LocalIdentity<P::Identity>,
3667 pfs_parent: Option<LocalIdentityId>,
3668 ) -> Result<LocalIdentityId, CapacityError> {
3669 let initial_frame_counter = nonzero_initial_frame_counter(self.rng.next_u32());
3670
3671 if let Some((index, slot)) = self
3672 .identities
3673 .iter_mut()
3674 .enumerate()
3675 .find(|(_, slot)| slot.is_none())
3676 {
3677 *slot = Some(IdentitySlot::new(
3678 identity,
3679 initial_frame_counter,
3680 pfs_parent,
3681 ));
3682 return Ok(LocalIdentityId(index as u8));
3683 }
3684
3685 let next_id = self.identities.len();
3686 self.identities
3687 .push(Some(IdentitySlot::new(
3688 identity,
3689 initial_frame_counter,
3690 pfs_parent,
3691 )))
3692 .map_err(|_| CapacityError)?;
3693 Ok(LocalIdentityId(next_id as u8))
3694 }
3695
3696 fn compute_received_ack_trailer(
3697 &self,
3698 buf: &[u8],
3699 header: &PacketHeader,
3700 body_range: core::ops::Range<usize>,
3701 keys: &PairwiseKeys,
3702 ) -> [u8; 8] {
3703 let mut cmac: CmacState<_> = self.crypto.cmac_state(&keys.k_mic);
3704 feed_aad(header, buf, |chunk| cmac.update(chunk));
3705 cmac.update(&buf[body_range]);
3706 let full_mac = cmac.finalize();
3707 self.crypto.compute_ack_trailer(&full_mac, &keys.k_enc)
3708 }
3709
3710 fn requeue_tx(&mut self, queued: &crate::QueuedTx<FRAME>) -> Result<u32, CapacityError> {
3711 self.tx_queue.enqueue_with_state(
3712 queued.priority,
3713 queued.frame.as_slice(),
3714 queued.receipt,
3715 queued.identity_id,
3716 queued.not_before_ms,
3717 queued.cad_attempts,
3718 queued.forward_deferrals,
3719 )
3720 }
3721
3722 fn accept_unicast_replay(
3723 &mut self,
3724 local_id: LocalIdentityId,
3725 peer_id: PeerId,
3726 header: &PacketHeader,
3727 frame: &[u8],
3728 ) -> bool {
3729 let Some((counter, mic)) = Self::replay_metadata(header, frame) else {
3730 return false;
3731 };
3732 let now_ms = self.clock.now_ms();
3733 let Some(state) = self
3734 .identity_mut(local_id)
3735 .and_then(|slot| slot.peer_crypto_mut().get_mut(&peer_id))
3736 else {
3737 return false;
3738 };
3739
3740 if state.replay_window.check(counter, mic, now_ms) != ReplayVerdict::Accept {
3741 return false;
3742 }
3743
3744 state.replay_window.accept(counter, mic, now_ms);
3745 let last = state.replay_window.last_accepted;
3747 if last.wrapping_sub(state.persisted_rx_counter) >= COUNTER_PERSIST_BLOCK_SIZE {
3748 state.needs_rx_persist = true;
3749 }
3750 true
3751 }
3752
3753 fn unicast_replay_verdict(
3754 &mut self,
3755 local_id: LocalIdentityId,
3756 peer_id: PeerId,
3757 header: &PacketHeader,
3758 frame: &[u8],
3759 ) -> Option<ReplayVerdict> {
3760 let Some((counter, mic)) = Self::replay_metadata(header, frame) else {
3761 return None;
3762 };
3763 let now_ms = self.clock.now_ms();
3764 self.identity_mut(local_id)
3765 .and_then(|slot| slot.peer_crypto_mut().get_mut(&peer_id))
3766 .map(|state| state.replay_window.check(counter, mic, now_ms))
3767 }
3768
3769 fn is_acknowledgeable_unicast_duplicate(
3770 &self,
3771 local_id: LocalIdentityId,
3772 peer_id: PeerId,
3773 header: &PacketHeader,
3774 frame: &[u8],
3775 ) -> bool {
3776 let Some((counter, mic)) = Self::replay_metadata(header, frame) else {
3777 return false;
3778 };
3779 let now_ms = self.clock.now_ms();
3780 self.identity(local_id)
3781 .and_then(|slot| slot.peer_crypto().get(&peer_id))
3782 .is_some_and(|state| {
3783 state
3784 .replay_window
3785 .is_acknowledgeable_duplicate(counter, mic, now_ms)
3786 })
3787 }
3788
3789 fn try_accept_counter_resync_response(
3790 &mut self,
3791 local_id: LocalIdentityId,
3792 peer_id: PeerId,
3793 header: &PacketHeader,
3794 frame: &[u8],
3795 payload: &[u8],
3796 ) -> bool {
3797 let Some(nonce) = Self::echo_response_nonce(payload) else {
3798 return false;
3799 };
3800 let Some((counter, mic)) = Self::replay_metadata(header, frame) else {
3801 return false;
3802 };
3803 let now_ms = self.clock.now_ms();
3804 let Some(slot) = self.identity_mut(local_id) else {
3805 return false;
3806 };
3807 let Some(pending) = slot.pending_counter_resync().get(&peer_id).copied() else {
3808 return false;
3809 };
3810 if pending.nonce != nonce {
3811 return false;
3812 }
3813 let Some(state) = slot.peer_crypto_mut().get_mut(&peer_id) else {
3814 return false;
3815 };
3816 state.replay_window.reset(counter, now_ms);
3817 state.replay_window.accept(counter, mic, now_ms);
3818 state.persisted_rx_counter = 0;
3821 state.needs_rx_persist = true;
3822 let _ = slot.pending_counter_resync_mut().remove(&peer_id);
3823 true
3824 }
3825
3826 async fn maybe_request_counter_resync(
3827 &mut self,
3828 local_id: LocalIdentityId,
3829 peer_id: PeerId,
3830 peer_key: PublicKey,
3831 ) {
3832 let now_ms = self.clock.now_ms();
3833 let should_send = {
3834 let Some(slot) = self.identity(local_id) else {
3835 return;
3836 };
3837 match slot.pending_counter_resync().get(&peer_id).copied() {
3838 Some(pending) => {
3839 now_ms.saturating_sub(pending.requested_ms) >= COUNTER_RESYNC_REQUEST_RETRY_MS
3840 }
3841 None => true,
3842 }
3843 };
3844 if !should_send {
3845 return;
3846 }
3847
3848 let nonce = self.rng.next_u32();
3849 let payload =
3850 Self::build_echo_command_payload(MAC_COMMAND_ECHO_REQUEST_ID, &nonce.to_be_bytes());
3851 let options = SendOptions::default();
3852 if self
3853 .send_unicast(local_id, &peer_key, payload.as_slice(), &options)
3854 .await
3855 .is_ok()
3856 {
3857 if let Some(slot) = self.identity_mut(local_id) {
3858 let _ = slot.pending_counter_resync_mut().insert(
3859 peer_id,
3860 PendingCounterResync {
3861 nonce,
3862 requested_ms: now_ms,
3863 },
3864 );
3865 }
3866 }
3867 }
3868
3869 fn store_deferred_counter_resync_frame(
3870 &mut self,
3871 local_id: LocalIdentityId,
3872 peer_id: PeerId,
3873 frame: &[u8],
3874 rx: &RxInfo,
3875 received_at_ms: u64,
3876 ) {
3877 let mut stored = Vec::new();
3878 stored
3879 .extend_from_slice(frame)
3880 .expect("received frame length must fit configured frame capacity");
3881 self.deferred_counter_resync_frame = Some(DeferredCounterResyncFrame {
3882 local_id,
3883 peer_id,
3884 frame: stored,
3885 rssi: rx.rssi,
3886 snr: rx.snr,
3887 lqi: rx.lqi,
3888 received_at_ms,
3889 });
3890 }
3891
3892 fn take_deferred_counter_resync_frame(
3893 &mut self,
3894 local_id: LocalIdentityId,
3895 peer_id: PeerId,
3896 ) -> Option<DeferredCounterResyncFrame<FRAME>> {
3897 match self.deferred_counter_resync_frame.as_ref() {
3898 Some(deferred) if deferred.local_id == local_id && deferred.peer_id == peer_id => {
3899 self.deferred_counter_resync_frame.take()
3900 }
3901 _ => None,
3902 }
3903 }
3904
3905 fn accept_multicast_replay(
3906 &mut self,
3907 channel_id: ChannelId,
3908 peer_id: PeerId,
3909 header: &PacketHeader,
3910 frame: &[u8],
3911 ) -> bool {
3912 let Some((counter, mic)) = Self::replay_metadata(header, frame) else {
3913 return false;
3914 };
3915 let now_ms = self.clock.now_ms();
3916 let Some(channel) = self.channels.get_mut_by_id(&channel_id) else {
3917 return false;
3918 };
3919
3920 if let Some(window) = channel.replay.get_mut(&peer_id) {
3921 if window.check(counter, mic, now_ms) != ReplayVerdict::Accept {
3922 return false;
3923 }
3924 window.accept(counter, mic, now_ms);
3925 return true;
3926 }
3927
3928 let mut window = ReplayWindow::new();
3929 window.accept(counter, mic, now_ms);
3930 channel.replay.insert(peer_id, window).is_ok()
3931 }
3932
3933 fn clear_peer_slot_state(&mut self, peer_id: PeerId) {
3934 for slot in self.identities.iter_mut().filter_map(|slot| slot.as_mut()) {
3935 let _ = slot.peer_crypto_mut().remove(&peer_id);
3936 let _ = slot.pending_counter_resync_mut().remove(&peer_id);
3937 }
3938 for channel in self.channels.iter_mut() {
3939 let _ = channel.replay.remove(&peer_id);
3940 }
3941 }
3942
3943 fn rekey_peer_slot_state(&mut self, old_id: PeerId, new_id: PeerId) {
3948 for slot in self.identities.iter_mut().filter_map(|slot| slot.as_mut()) {
3949 if let Some(state) = slot.peer_crypto_mut().remove(&old_id) {
3950 let _ = slot.peer_crypto_mut().insert(new_id, state);
3951 }
3952 if let Some(pending) = slot.pending_counter_resync_mut().remove(&old_id) {
3953 let _ = slot.pending_counter_resync_mut().insert(new_id, pending);
3954 }
3955 }
3956 for channel in self.channels.iter_mut() {
3957 if let Some(window) = channel.replay.remove(&old_id) {
3958 let _ = channel.replay.insert(new_id, window);
3959 }
3960 }
3961 if let Some(deferred) = self.deferred_counter_resync_frame.as_mut() {
3962 if deferred.peer_id == old_id {
3963 deferred.peer_id = new_id;
3964 }
3965 }
3966 }
3967
3968 pub fn ensure_transient_peer(&mut self, key: &PublicKey) -> Result<PeerId, AddPeerError> {
3980 if let Some((peer_id, _)) = self.peer_registry.lookup_by_key(key) {
3981 return Ok(peer_id);
3982 }
3983 self.try_auto_register_peer(*key)
3984 }
3985
3986 fn try_auto_register_peer(&mut self, key: PublicKey) -> Result<PeerId, AddPeerError> {
3987 #[cfg(feature = "software-crypto")]
3988 {
3989 if !umsh_crypto::is_valid_ed25519_public_key(&key) {
3990 return Err(AddPeerError::InvalidPublicKey);
3991 }
3992 }
3993 let now_ms = self.clock.now_ms();
3994 let outcome = self.peer_registry.try_insert_or_update_auto(key, now_ms)?;
3995 if outcome.evicted_key.is_some() {
3996 self.clear_peer_slot_state(outcome.peer_id);
3997 }
3998 Ok(outcome.peer_id)
3999 }
4000
4001 fn replay_metadata<'a>(header: &PacketHeader, frame: &'a [u8]) -> Option<(u32, &'a [u8])> {
4002 let counter = header.sec_info?.frame_counter;
4003 let mic = frame.get(header.mic_range.clone())?;
4004 Some((counter, mic))
4005 }
4006
4007 fn build_echo_command_payload(command_id: u8, data: &[u8]) -> Vec<u8, FRAME> {
4008 let mut payload = Vec::new();
4009 let _ = payload.push(PayloadType::MacCommand as u8);
4010 let _ = payload.push(command_id);
4011 let _ = payload.extend_from_slice(data);
4012 payload
4013 }
4014
4015 fn echo_request_data(payload: &[u8]) -> Option<&[u8]> {
4016 let (&payload_type, rest) = payload.split_first()?;
4017 let (&command_id, data) = rest.split_first()?;
4018 if PayloadType::from_byte(payload_type)? != PayloadType::MacCommand
4019 || command_id != MAC_COMMAND_ECHO_REQUEST_ID
4020 {
4021 return None;
4022 }
4023 Some(data)
4024 }
4025
4026 fn echo_response_options(frame: &[u8], header: &PacketHeader) -> SendOptions {
4027 let mut options = SendOptions::default();
4028 let requests_trace = ParsedOptions::extract(frame, header.options_range.clone())
4029 .map(|parsed| parsed.trace_route.is_some())
4030 .unwrap_or(false);
4031 if requests_trace {
4032 options = options.with_trace_route();
4033 }
4034 options
4035 }
4036
4037 fn echo_response_nonce(payload: &[u8]) -> Option<u32> {
4038 let (&payload_type, rest) = payload.split_first()?;
4039 let (&command_id, data) = rest.split_first()?;
4040 if PayloadType::from_byte(payload_type)? != PayloadType::MacCommand
4041 || command_id != MAC_COMMAND_ECHO_RESPONSE_ID
4042 || data.len() != COUNTER_RESYNC_NONCE_LEN
4043 {
4044 return None;
4045 }
4046 Some(u32::from_be_bytes(data.try_into().ok()?))
4047 }
4048
4049 fn full_key_at(frame: &[u8], offset: usize) -> Option<PublicKey> {
4050 let mut key = [0u8; 32];
4051 key.copy_from_slice(frame.get(offset..offset + 32)?);
4052 Some(PublicKey(key))
4053 }
4054
4055 fn find_local_identity_for_dst(
4056 &self,
4057 dst: Option<umsh_core::NodeHint>,
4058 ) -> Option<LocalIdentityId> {
4059 let dst = dst?;
4060 self.identities
4061 .iter()
4062 .enumerate()
4063 .find(|(_, slot)| {
4064 slot.as_ref()
4065 .map(|slot| slot.identity().public_key().hint() == dst)
4066 .unwrap_or(false)
4067 })
4068 .map(|(index, _)| LocalIdentityId(index as u8))
4069 }
4070
4071 fn is_locally_originated(&self, frame: &[u8], header: &PacketHeader) -> bool {
4079 let mut locals = self.identities.iter().filter_map(|slot| slot.as_ref());
4080 match header.source {
4081 SourceAddrRef::Hint(hint) => {
4082 locals.any(|slot| slot.identity().public_key().hint() == hint)
4083 }
4084 SourceAddrRef::FullKeyAt { offset } => match Self::full_key_at(frame, offset) {
4085 Some(key) => locals.any(|slot| *slot.identity().public_key() == key),
4086 None => false,
4087 },
4088 SourceAddrRef::Encrypted { .. } | SourceAddrRef::None => false,
4091 }
4092 }
4093
4094 fn resolve_source_peer_candidates(
4095 &mut self,
4096 frame: &[u8],
4097 header: &PacketHeader,
4098 ) -> Vec<(PeerId, PublicKey), PEERS> {
4099 match header.source {
4100 SourceAddrRef::FullKeyAt { offset } => {
4101 let Some(peer_key) = Self::full_key_at(frame, offset) else {
4102 return Vec::new();
4103 };
4104
4105 if let Some((peer_id, _)) = self.peer_registry.lookup_by_key(&peer_key) {
4106 let mut out = Vec::new();
4107 let _ = out.push((peer_id, peer_key));
4108 return out;
4109 }
4110
4111 if self.auto_register_full_key_peers {
4112 if let Ok(peer_id) = self.try_auto_register_peer(peer_key) {
4113 let mut out = Vec::new();
4114 let _ = out.push((peer_id, peer_key));
4115 return out;
4116 }
4117 }
4118
4119 Vec::new()
4120 }
4121 SourceAddrRef::Hint(hint) => self
4122 .peer_registry
4123 .lookup_by_hint(&hint)
4124 .map(|(peer_id, info)| (peer_id, info.public_key))
4125 .collect(),
4126 SourceAddrRef::Encrypted { .. } | SourceAddrRef::None => Vec::new(),
4127 }
4128 }
4129
4130 fn resolve_multicast_source(
4131 &mut self,
4132 frame: &[u8],
4133 header: &PacketHeader,
4134 ) -> Option<ResolvedMulticastSource> {
4135 match header.source {
4136 SourceAddrRef::FullKeyAt { offset } => {
4137 let mut key = [0u8; 32];
4138 key.copy_from_slice(frame.get(offset..offset + 32)?);
4139 let public_key = PublicKey(key);
4140 let peer_id = self
4141 .peer_registry
4142 .lookup_by_key(&public_key)
4143 .map(|(peer_id, _)| peer_id);
4144 Some(ResolvedMulticastSource {
4145 peer_id,
4146 public_key: Some(public_key),
4147 hint: Some(public_key.hint()),
4148 })
4149 }
4150 SourceAddrRef::Hint(hint) => {
4151 let resolved = self.resolve_unique_hint(hint);
4152 Some(ResolvedMulticastSource {
4153 peer_id: resolved.map(|(peer_id, _)| peer_id),
4154 public_key: resolved.map(|(_, key)| key),
4155 hint: Some(hint),
4156 })
4157 }
4158 SourceAddrRef::Encrypted { offset, len } => match len {
4159 32 => {
4160 let mut key = [0u8; 32];
4161 key.copy_from_slice(frame.get(offset..offset + 32)?);
4162 let public_key = PublicKey(key);
4163 let peer_id = self
4164 .peer_registry
4165 .lookup_by_key(&public_key)
4166 .map(|(peer_id, _)| peer_id);
4167 Some(ResolvedMulticastSource {
4168 peer_id,
4169 public_key: Some(public_key),
4170 hint: Some(public_key.hint()),
4171 })
4172 }
4173 3 => {
4174 let hint = umsh_core::NodeHint([
4175 *frame.get(offset)?,
4176 *frame.get(offset + 1)?,
4177 *frame.get(offset + 2)?,
4178 ]);
4179 let resolved = self.resolve_unique_hint(hint);
4180 Some(ResolvedMulticastSource {
4181 peer_id: resolved.map(|(peer_id, _)| peer_id),
4182 public_key: resolved.map(|(_, key)| key),
4183 hint: Some(hint),
4184 })
4185 }
4186 _ => None,
4187 },
4188 SourceAddrRef::None => None,
4189 }
4190 }
4191
4192 fn accept_unknown_multicast_replay(&mut self, header: &PacketHeader, frame: &[u8]) -> bool {
4193 let Some(cache_key) = Self::forward_dup_key(header, frame) else {
4194 return false;
4195 };
4196 let now_ms = self.clock.now_ms();
4197 if self
4198 .multicast_unknown_dup_cache
4199 .contains(&cache_key, now_ms)
4200 {
4201 return false;
4202 }
4203 self.multicast_unknown_dup_cache.insert(cache_key, now_ms);
4204 true
4205 }
4206
4207 fn resolve_blind_source_peer_candidates(
4208 &mut self,
4209 frame: &[u8],
4210 source: SourceAddrRef,
4211 ) -> Vec<(PeerId, PublicKey), PEERS> {
4212 match source {
4213 SourceAddrRef::FullKeyAt { offset } => {
4214 let Some(peer_key) = Self::full_key_at(frame, offset) else {
4215 return Vec::new();
4216 };
4217
4218 if let Some((peer_id, _)) = self.peer_registry.lookup_by_key(&peer_key) {
4219 let mut out = Vec::new();
4220 let _ = out.push((peer_id, peer_key));
4221 return out;
4222 }
4223
4224 if self.auto_register_full_key_peers {
4225 if let Ok(peer_id) = self.try_auto_register_peer(peer_key) {
4226 let mut out = Vec::new();
4227 let _ = out.push((peer_id, peer_key));
4228 return out;
4229 }
4230 }
4231
4232 Vec::new()
4233 }
4234 SourceAddrRef::Hint(hint) => self
4235 .peer_registry
4236 .lookup_by_hint(&hint)
4237 .map(|(peer_id, info)| (peer_id, info.public_key))
4238 .collect(),
4239 SourceAddrRef::Encrypted { .. } | SourceAddrRef::None => Vec::new(),
4240 }
4241 }
4242
4243 fn resolve_unique_hint(&self, hint: umsh_core::NodeHint) -> Option<(PeerId, PublicKey)> {
4244 let mut matches = self.peer_registry.lookup_by_hint(&hint);
4245 let (peer_id, info) = matches.next()?;
4246 if matches.next().is_some() {
4247 return None;
4248 }
4249 Some((peer_id, info.public_key))
4250 }
4251
4252 fn learn_route_for_peer(&mut self, peer_id: PeerId, frame: &[u8], header: &PacketHeader) {
4253 let now_ms = self.clock.now_ms();
4254 self.peer_registry.touch(peer_id, now_ms);
4255
4256 let Ok(options) = ParsedOptions::extract(frame, header.options_range.clone()) else {
4257 return;
4258 };
4259
4260 if let Some(trace_range) = options.trace_route {
4261 if let Some(route) = self.source_route_from_trace(frame.get(trace_range).unwrap_or(&[]))
4262 {
4263 let learned = if route.is_empty() {
4271 crate::CachedRoute::Direct
4272 } else {
4273 crate::CachedRoute::Source(route)
4274 };
4275 self.peer_registry.update_route(peer_id, learned);
4276 return;
4277 }
4278 }
4279
4280 if options.source_route.is_some() {
4288 return;
4289 }
4290
4291 if let Some(flood_hops) = header.flood_hops {
4292 let regions = Self::region_codes_from_options(frame, header.options_range.clone());
4293 self.peer_registry.update_route(
4294 peer_id,
4295 crate::CachedRoute::Flood {
4296 hops: flood_hops.accumulated(),
4297 regions,
4298 },
4299 );
4300 }
4301 }
4302
4303 fn region_codes_from_options(
4304 frame: &[u8],
4305 options_range: core::ops::Range<usize>,
4306 ) -> Vec<[u8; 2], 8> {
4307 let mut regions = Vec::new();
4308 if options_range.is_empty() {
4309 return regions;
4310 }
4311 for entry in umsh_core::iter_options(frame, options_range) {
4312 let Ok((number, value)) = entry else {
4313 continue;
4314 };
4315 if OptionNumber::from(number) != OptionNumber::RegionCode || value.len() != 2 {
4316 continue;
4317 }
4318 if regions.push([value[0], value[1]]).is_err() {
4319 break;
4320 }
4321 }
4322 regions
4323 }
4324
4325 fn resolve_broadcast_source(
4326 frame: &[u8],
4327 header: &PacketHeader,
4328 ) -> Option<(umsh_core::NodeHint, Option<PublicKey>)> {
4329 match header.source {
4330 SourceAddrRef::Hint(hint) => Some((hint, None)),
4331 SourceAddrRef::FullKeyAt { offset } => {
4332 let mut key = [0u8; 32];
4333 key.copy_from_slice(frame.get(offset..offset + 32)?);
4334 let public_key = PublicKey(key);
4335 Some((public_key.hint(), Some(public_key)))
4336 }
4337 SourceAddrRef::Encrypted { .. } | SourceAddrRef::None => None,
4338 }
4339 }
4340
4341 fn payload_is_allowed(packet_type: PacketType, payload: &[u8]) -> bool {
4342 if payload.is_empty() {
4343 return true;
4344 }
4345
4346 PayloadType::from_byte(payload[0])
4347 .unwrap_or(PayloadType::Empty)
4348 .allowed_for(packet_type)
4349 }
4350
4351 fn source_route_from_trace(
4352 &self,
4353 trace_bytes: &[u8],
4354 ) -> Option<heapless::Vec<RouterHint, { crate::MAX_SOURCE_ROUTE_HOPS }>> {
4355 if trace_bytes.len() % 2 != 0 {
4356 return None;
4357 }
4358
4359 let mut route = heapless::Vec::new();
4360 for chunk in trace_bytes.chunks_exact(2) {
4361 route.push(RouterHint([chunk[0], chunk[1]])).ok()?;
4362 }
4363 Some(route)
4364 }
4365
4366 fn should_emit_destination_ack(&self, frame: &[u8], header: &PacketHeader) -> bool {
4367 let Ok(options) = ParsedOptions::extract(frame, header.options_range.clone()) else {
4368 return false;
4369 };
4370
4371 options
4376 .source_route
4377 .map(|range| range.is_empty())
4378 .unwrap_or(true)
4379 }
4380
4381 fn maybe_forward_received(
4382 &mut self,
4383 frame: &[u8],
4384 header: &PacketHeader,
4385 rx: &RxInfo,
4386 locally_handled_unicast: bool,
4387 ) -> bool {
4388 if !self.repeater.enabled {
4389 return false;
4390 }
4391 if !header.packet_type().is_routable() {
4392 return false;
4393 }
4394 if self.is_locally_originated(frame, header) {
4395 return false;
4396 }
4397 if self.find_local_identity_for_dst(header.dst).is_some() {
4402 return false;
4403 }
4404 if locally_handled_unicast
4410 && matches!(
4411 header.packet_type(),
4412 PacketType::Unicast
4413 | PacketType::UnicastAckReq
4414 | PacketType::BlindUnicast
4415 | PacketType::BlindUnicastAckReq
4416 )
4417 {
4418 return false;
4419 }
4420
4421 let Ok(options) = ParsedOptions::extract(frame, header.options_range.clone()) else {
4422 return false;
4423 };
4424 let Some(cache_key) = Self::forward_dup_key(header, frame) else {
4425 return false;
4426 };
4427 if self.dup_cache.contains(&cache_key, self.clock.now_ms()) {
4428 self.defer_pending_forward(&cache_key, header, rx, &options);
4429 return false;
4430 }
4431
4432 let Some(plan) = self.plan_forwarding(frame, header, &options, rx) else {
4433 return false;
4434 };
4435
4436 let mut rewritten = [0u8; FRAME];
4437 let Ok(total_len) =
4438 self.rewrite_forwarded_frame(frame, header, &options, plan, &mut rewritten)
4439 else {
4440 return false;
4441 };
4442 if total_len > self.radio.max_frame_size() {
4443 return false;
4444 }
4445
4446 let now_ms = self.clock.now_ms();
4447 if self
4448 .tx_queue
4449 .enqueue_with_state(
4450 TxPriority::Forward,
4451 &rewritten[..total_len],
4452 None,
4453 None,
4454 now_ms.saturating_add(plan.delay_ms),
4455 0,
4456 0,
4457 )
4458 .is_err()
4459 {
4460 return false;
4461 }
4462 self.dup_cache.insert(cache_key, now_ms);
4463 MacCounters::bump(&mut self.counters.forwarded);
4469 true
4470 }
4471
4472 fn plan_forwarding(
4473 &mut self,
4474 frame: &[u8],
4475 header: &PacketHeader,
4476 options: &ParsedOptions,
4477 rx: &RxInfo,
4478 ) -> Option<ForwardPlan> {
4479 if options.has_unknown_critical {
4480 return None;
4481 }
4482
4483 let router_hint = self.repeater_router_hint()?;
4484 let station_action = self.classify_forward_station_action(frame, header)?;
4485
4486 let source_route_bytes = options
4487 .source_route
4488 .as_ref()
4489 .and_then(|range| frame.get(range.clone()))
4490 .unwrap_or(&[]);
4491 if source_route_bytes.len() % 2 != 0 {
4492 return None;
4493 }
4494
4495 let mut consume_source_route = false;
4496 let mut decrement_flood_hops = false;
4497 let mut insert_region_code = None;
4498 let mut delay_ms = 0u64;
4499
4500 if !source_route_bytes.is_empty() {
4501 if source_route_bytes[..2] != router_hint.0 {
4510 return None;
4511 }
4512 consume_source_route = true;
4513 } else {
4514 decrement_flood_hops = true;
4515 }
4516
4517 if decrement_flood_hops {
4518 let flood_hops = header.flood_hops?;
4519 if flood_hops.remaining() == 0 {
4520 return None;
4521 }
4522 if let Some(min_rssi) = Self::effective_min_rssi(options, &self.repeater) {
4525 if rx.rssi < min_rssi {
4526 return None;
4527 }
4528 }
4529 if let Some(min_snr) = Self::effective_min_snr(options, &self.repeater) {
4530 if rx.snr < Snr::from_decibels(min_snr) {
4531 return None;
4532 }
4533 }
4534 let mut saw_region_code = false;
4535 let mut matched_region_code = false;
4536 if !header.options_range.is_empty() {
4537 for entry in umsh_core::iter_options(frame, header.options_range.clone()) {
4538 let (number, value) = entry.ok()?;
4539 if OptionNumber::from(number) != OptionNumber::RegionCode || value.len() != 2 {
4540 continue;
4541 }
4542 saw_region_code = true;
4543 let region_code = [value[0], value[1]];
4544 if self
4545 .repeater
4546 .regions
4547 .iter()
4548 .any(|configured| *configured == region_code)
4549 {
4550 matched_region_code = true;
4551 }
4552 }
4553 }
4554 if saw_region_code {
4555 if !self.repeater.regions.is_empty() && !matched_region_code {
4557 return None;
4558 }
4559 } else {
4560 insert_region_code = self.repeater.default_region;
4561 }
4562 delay_ms = self.sample_flood_contention_delay_ms(rx, options);
4563 if header.ack_requested() {
4568 delay_ms = delay_ms.saturating_add(self.ack_guard_delay_ms());
4569 }
4570 }
4571
4572 Some(ForwardPlan {
4573 router_hint,
4574 consume_source_route,
4575 decrement_flood_hops,
4576 insert_region_code,
4577 delay_ms,
4578 station_action,
4579 signal: TraceSignalEntry::from_rx(rx),
4580 })
4581 }
4582
4583 fn classify_forward_station_action(
4584 &self,
4585 frame: &[u8],
4586 header: &PacketHeader,
4587 ) -> Option<ForwardStationAction> {
4588 let has_operator_callsign = if header.options_range.is_empty() {
4589 false
4590 } else {
4591 umsh_core::iter_options(frame, header.options_range.clone())
4592 .filter_map(Result::ok)
4593 .any(|(number, _)| OptionNumber::from(number) == OptionNumber::OperatorCallsign)
4594 };
4595 let encrypted = header
4596 .sec_info
4597 .map(|sec| sec.scf.encrypted())
4598 .unwrap_or(false);
4599
4600 match self.repeater.amateur_radio_mode {
4601 AmateurRadioMode::Unlicensed => Some(ForwardStationAction::Remove),
4602 AmateurRadioMode::LicensedOnly => {
4603 if encrypted || !has_operator_callsign || self.repeater.station_callsign.is_none() {
4604 None
4605 } else {
4606 Some(ForwardStationAction::Replace)
4607 }
4608 }
4609 AmateurRadioMode::Hybrid => {
4610 if !encrypted && has_operator_callsign {
4611 self.repeater
4612 .station_callsign
4613 .as_ref()
4614 .map(|_| ForwardStationAction::Replace)
4615 } else {
4616 Some(ForwardStationAction::Remove)
4617 }
4618 }
4619 }
4620 }
4621
4622 fn rewrite_forwarded_frame(
4623 &self,
4624 src: &[u8],
4625 header: &PacketHeader,
4626 options: &ParsedOptions,
4627 plan: ForwardPlan,
4628 dst: &mut [u8],
4629 ) -> Result<usize, CapacityError> {
4630 if dst.is_empty() {
4631 return Err(CapacityError);
4632 }
4633
4634 dst[0] = umsh_core::Fcf::new(
4636 header.packet_type(),
4637 header.fcf.full_source(),
4638 header.flood_hops.is_some(),
4639 )
4640 .0;
4641 let mut cursor = 1;
4642
4643 if let Some(flood_hops) = header.flood_hops {
4645 let next = if plan.decrement_flood_hops {
4646 flood_hops.decremented().0
4647 } else {
4648 flood_hops.0
4649 };
4650 *dst.get_mut(cursor).ok_or(CapacityError)? = next;
4651 cursor += 1;
4652 }
4653
4654 let fhops_len = usize::from(header.flood_hops.is_some());
4656 let fixed_core = src
4657 .get(1 + fhops_len..header.options_range.start)
4658 .ok_or(CapacityError)?;
4659 let core_end = cursor + fixed_core.len();
4660 dst.get_mut(cursor..core_end)
4661 .ok_or(CapacityError)?
4662 .copy_from_slice(fixed_core);
4663 cursor = core_end;
4664
4665 let options_len =
4667 self.encode_forwarded_options(src, header, options, plan, &mut dst[cursor..])?;
4668 cursor += options_len;
4669
4670 let needs_marker = !matches!(header.packet_type(), PacketType::MacAck)
4672 && header.options_range.end < header.total_len;
4673 if needs_marker {
4674 *dst.get_mut(cursor).ok_or(CapacityError)? = 0xFF;
4675 cursor += 1;
4676 }
4677
4678 let tail = src
4680 .get(header.options_range.end..header.total_len)
4681 .ok_or(CapacityError)?;
4682 let end = cursor + tail.len();
4683 dst.get_mut(cursor..end)
4684 .ok_or(CapacityError)?
4685 .copy_from_slice(tail);
4686 Ok(end)
4687 }
4688
4689 fn encode_forwarded_options(
4690 &self,
4691 src: &[u8],
4692 header: &PacketHeader,
4693 _options: &ParsedOptions,
4694 plan: ForwardPlan,
4695 dst: &mut [u8],
4696 ) -> Result<usize, CapacityError> {
4697 let mut encoder = OptionEncoder::new(dst);
4698 let mut inserted_region = false;
4699 let mut inserted_station = false;
4700 let mut saw_station = false;
4701
4702 if !header.options_range.is_empty() {
4703 for entry in umsh_core::iter_options(src, header.options_range.clone()) {
4704 let (number, value) = entry.map_err(|_| CapacityError)?;
4705 if !inserted_region {
4706 if let Some(region_code) = plan.insert_region_code {
4707 if number > OptionNumber::RegionCode.as_u16() {
4708 encoder
4709 .put(OptionNumber::RegionCode.as_u16(), ®ion_code)
4710 .map_err(|_| CapacityError)?;
4711 inserted_region = true;
4712 }
4713 }
4714 }
4715 if !inserted_station
4716 && matches!(plan.station_action, ForwardStationAction::Replace)
4717 && number > OptionNumber::StationCallsign.as_u16()
4718 {
4719 encoder
4720 .put(
4721 OptionNumber::StationCallsign.as_u16(),
4722 self.repeater
4723 .station_callsign
4724 .as_ref()
4725 .ok_or(CapacityError)?
4726 .as_trimmed_slice(),
4727 )
4728 .map_err(|_| CapacityError)?;
4729 inserted_station = true;
4730 }
4731
4732 match OptionNumber::from(number) {
4733 OptionNumber::RegionCode => {
4734 inserted_region = true;
4735 encoder.put(number, value).map_err(|_| CapacityError)?;
4736 }
4737 OptionNumber::TraceRoute => {
4742 let mut trace = [0u8; crate::MAX_SOURCE_ROUTE_HOPS * 2 + 2];
4743 if value.len() > crate::MAX_SOURCE_ROUTE_HOPS * 2 {
4744 return Err(CapacityError);
4745 }
4746 trace[..2].copy_from_slice(&plan.router_hint.0);
4747 trace[2..2 + value.len()].copy_from_slice(value);
4748 encoder
4749 .put(number, &trace[..2 + value.len()])
4750 .map_err(|_| CapacityError)?;
4751 }
4752 OptionNumber::TraceSignal => {
4756 let mut trace = [0u8; crate::MAX_SOURCE_ROUTE_HOPS * 2 + 2];
4757 if value.len() > crate::MAX_SOURCE_ROUTE_HOPS * 2 {
4758 return Err(CapacityError);
4759 }
4760 trace[..2].copy_from_slice(&plan.signal.as_bytes());
4761 trace[2..2 + value.len()].copy_from_slice(value);
4762 encoder
4763 .put(number, &trace[..2 + value.len()])
4764 .map_err(|_| CapacityError)?;
4765 }
4766 OptionNumber::SourceRoute if plan.consume_source_route => {
4767 if value.len() < 2 || value.len() % 2 != 0 {
4768 return Err(CapacityError);
4769 }
4770 let remaining = if value.len() > 2 { &value[2..] } else { &[] };
4771 encoder.put(number, remaining).map_err(|_| CapacityError)?;
4772 }
4773 OptionNumber::StationCallsign => {
4774 saw_station = true;
4775 match plan.station_action {
4776 ForwardStationAction::Remove => {}
4777 ForwardStationAction::Replace => {
4778 encoder
4779 .put(
4780 number,
4781 self.repeater
4782 .station_callsign
4783 .as_ref()
4784 .ok_or(CapacityError)?
4785 .as_trimmed_slice(),
4786 )
4787 .map_err(|_| CapacityError)?;
4788 inserted_station = true;
4789 }
4790 }
4791 }
4792 _ => {
4793 encoder.put(number, value).map_err(|_| CapacityError)?;
4794 }
4795 }
4796 }
4797 }
4798
4799 if matches!(plan.station_action, ForwardStationAction::Replace)
4800 && !inserted_station
4801 && !saw_station
4802 {
4803 encoder
4804 .put(
4805 OptionNumber::StationCallsign.as_u16(),
4806 self.repeater
4807 .station_callsign
4808 .as_ref()
4809 .ok_or(CapacityError)?
4810 .as_trimmed_slice(),
4811 )
4812 .map_err(|_| CapacityError)?;
4813 }
4814 if let Some(region_code) = plan.insert_region_code {
4815 if !inserted_region {
4816 encoder
4817 .put(OptionNumber::RegionCode.as_u16(), ®ion_code)
4818 .map_err(|_| CapacityError)?;
4819 }
4820 }
4821 Ok(encoder.finish())
4822 }
4823
4824 fn synthesize_route_retry_resend(
4825 &self,
4826 peer: &PublicKey,
4827 resend: &ResendRecord<FRAME>,
4828 ) -> Option<ResendRecord<FRAME>> {
4829 let header = PacketHeader::parse(resend.frame.as_slice()).ok()?;
4830 let options =
4831 ParsedOptions::extract(resend.frame.as_slice(), header.options_range.clone()).ok()?;
4832 if options.route_retry {
4833 return None;
4834 }
4835 let source_route = resend
4836 .source_route
4837 .as_ref()
4838 .filter(|route| !route.is_empty());
4839 if source_route.is_none() && resend.requested_flood_hops.is_none() {
4840 return None;
4842 }
4843
4844 let flood_hops =
4845 self.route_retry_flood_hops(peer, &header, source_route, resend.requested_flood_hops)?;
4846 let has_flood_hops = flood_hops > 0;
4847 let mut rewritten = [0u8; FRAME];
4848
4849 rewritten[0] = umsh_core::Fcf::new(
4851 header.packet_type(),
4852 header.fcf.full_source(),
4853 has_flood_hops,
4854 )
4855 .0;
4856 let mut cursor = 1;
4857
4858 if has_flood_hops {
4860 *rewritten.get_mut(cursor)? = FloodHops::new(flood_hops, 0)?.0;
4861 cursor += 1;
4862 }
4863
4864 let fhops_len = usize::from(header.flood_hops.is_some());
4866 let fixed_core = resend
4867 .frame
4868 .get(1 + fhops_len..header.options_range.start)?;
4869 let core_end = cursor + fixed_core.len();
4870 rewritten
4871 .get_mut(cursor..core_end)?
4872 .copy_from_slice(fixed_core);
4873 cursor = core_end;
4874
4875 let options_len = self
4877 .encode_route_retry_options(
4878 resend.frame.as_slice(),
4879 header.options_range.clone(),
4880 &options,
4881 &mut rewritten[cursor..],
4882 )
4883 .ok()?;
4884 cursor += options_len;
4885
4886 let needs_marker = !matches!(header.packet_type(), PacketType::MacAck)
4888 && header.options_range.end < header.total_len;
4889 if needs_marker {
4890 *rewritten.get_mut(cursor)? = 0xFF;
4891 cursor += 1;
4892 }
4893
4894 let tail = resend
4896 .frame
4897 .get(header.options_range.end..header.total_len)?;
4898 let end = cursor + tail.len();
4899 rewritten.get_mut(cursor..end)?.copy_from_slice(tail);
4900
4901 ResendRecord::try_new(&rewritten[..end], None)
4902 .ok()
4903 .map(|record| record.with_requested_flood_hops(resend.requested_flood_hops))
4904 }
4905
4906 fn encode_route_retry_options(
4907 &self,
4908 src: &[u8],
4909 options_range: core::ops::Range<usize>,
4910 _options: &ParsedOptions,
4911 dst: &mut [u8],
4912 ) -> Result<usize, CapacityError> {
4913 let mut encoder = OptionEncoder::new(dst);
4914 let mut inserted_trace_route = false;
4915 let mut inserted_route_retry = false;
4916
4917 if !options_range.is_empty() {
4918 for entry in umsh_core::iter_options(src, options_range) {
4919 let (number, value) = entry.map_err(|_| CapacityError)?;
4920 if !inserted_trace_route && number > OptionNumber::TraceRoute.as_u16() {
4921 encoder
4922 .put(OptionNumber::TraceRoute.as_u16(), &[])
4923 .map_err(|_| CapacityError)?;
4924 inserted_trace_route = true;
4925 }
4926 if !inserted_route_retry && number > OptionNumber::RouteRetry.as_u16() {
4927 encoder
4928 .put(OptionNumber::RouteRetry.as_u16(), &[])
4929 .map_err(|_| CapacityError)?;
4930 inserted_route_retry = true;
4931 }
4932 match OptionNumber::from(number) {
4933 OptionNumber::SourceRoute => {}
4934 OptionNumber::TraceRoute => {
4935 encoder.put(number, value).map_err(|_| CapacityError)?;
4936 inserted_trace_route = true;
4937 }
4938 OptionNumber::RouteRetry => {}
4939 _ => {
4940 encoder.put(number, value).map_err(|_| CapacityError)?;
4941 }
4942 }
4943 }
4944 }
4945
4946 if !inserted_trace_route {
4947 encoder
4948 .put(OptionNumber::TraceRoute.as_u16(), &[])
4949 .map_err(|_| CapacityError)?;
4950 }
4951 if !inserted_route_retry {
4952 encoder
4953 .put(OptionNumber::RouteRetry.as_u16(), &[])
4954 .map_err(|_| CapacityError)?;
4955 }
4956
4957 Ok(encoder.finish())
4958 }
4959
4960 fn route_retry_flood_hops(
4961 &self,
4962 peer: &PublicKey,
4963 header: &PacketHeader,
4964 source_route: Option<&heapless::Vec<RouterHint, MAX_SOURCE_ROUTE_HOPS>>,
4965 requested: Option<u8>,
4966 ) -> Option<u8> {
4967 let requested = requested
4974 .filter(|hops| *hops > 0)
4975 .map(|hops| hops.clamp(1, MAX_FLOOD_HOPS));
4976 let existing = header
4977 .flood_hops
4978 .map(|hops| hops.remaining())
4979 .filter(|hops| *hops > 0);
4980 let cached = self
4981 .peer_registry
4982 .lookup_by_key(peer)
4983 .and_then(|(_, info)| match info.route.as_ref() {
4984 Some(crate::CachedRoute::Flood { hops, .. }) => {
4985 Some((*hops).clamp(1, MAX_FLOOD_HOPS))
4986 }
4987 _ => None,
4988 });
4989 let route_len = source_route
4990 .and_then(|route| u8::try_from(route.len()).ok())
4991 .map(|hops| hops.clamp(1, MAX_FLOOD_HOPS));
4992
4993 requested.or(existing).or(cached).or(route_len).or(Some(5))
4994 }
4995
4996 fn repeater_router_hint(&self) -> Option<RouterHint> {
4997 self.identities
4998 .iter()
4999 .filter_map(|slot| slot.as_ref())
5000 .next()
5001 .map(|slot| slot.identity().public_key().router_hint())
5002 }
5003
5004 fn effective_min_rssi(options: &ParsedOptions, repeater: &RepeaterConfig) -> Option<i16> {
5005 match (options.min_rssi, repeater.min_rssi) {
5006 (Some(packet), Some(local)) => Some(packet.max(local)),
5007 (Some(packet), None) => Some(packet),
5008 (None, Some(local)) => Some(local),
5009 (None, None) => None,
5010 }
5011 }
5012
5013 fn effective_min_snr(options: &ParsedOptions, repeater: &RepeaterConfig) -> Option<i8> {
5014 match (options.min_snr, repeater.min_snr) {
5015 (Some(packet), Some(local)) => Some(packet.max(local)),
5016 (Some(packet), None) => Some(packet),
5017 (None, Some(local)) => Some(local),
5018 (None, None) => None,
5019 }
5020 }
5021
5022 fn sample_flood_contention_delay_ms(&mut self, rx: &RxInfo, options: &ParsedOptions) -> u64 {
5023 let effective_threshold_db =
5024 Self::effective_min_snr(options, &self.repeater).unwrap_or(i8::MIN);
5025 let low_db = self
5026 .repeater
5027 .flood_contention_snr_low_db
5028 .max(effective_threshold_db);
5029 let high_db = self
5030 .repeater
5031 .flood_contention_snr_high_db
5032 .max(low_db.saturating_add(1));
5033 let low = i32::from(Snr::from_decibels(low_db).as_centibels());
5034 let high = i32::from(Snr::from_decibels(high_db).as_centibels());
5035 let received = i32::from(rx.snr.as_centibels());
5036 let clamped = (received - low).clamp(0, high - low) as u32;
5037 let range = (high - low) as u32;
5038 let t_frame_ms = u64::from(self.radio.t_frame_ms());
5039 let min_window_ms = t_frame_ms
5040 .saturating_mul(u64::from(self.repeater.flood_contention_min_window_percent))
5041 / 100;
5042 let max_window_ms = t_frame_ms
5043 .saturating_mul(u64::from(self.repeater.flood_contention_max_window_frames))
5044 .max(min_window_ms);
5045 let window_span_ms = max_window_ms.saturating_sub(min_window_ms);
5046 let window_ms = if range == 0 {
5047 max_window_ms
5048 } else {
5049 max_window_ms.saturating_sub(
5050 window_span_ms.saturating_mul(u64::from(clamped)) / u64::from(range),
5051 )
5052 };
5053 if window_ms == 0 {
5054 0
5055 } else {
5056 self.rng.random_range(..window_ms.saturating_add(1))
5057 }
5058 }
5059
5060 fn ack_guard_delay_ms(&self) -> u64 {
5063 u64::from(self.radio.t_frame_ms())
5064 .saturating_mul(u64::from(self.repeater.flood_contention_ack_guard_percent))
5065 / 100
5066 }
5067
5068 pub(crate) fn forward_dup_key(header: &PacketHeader, frame: &[u8]) -> Option<DupCacheKey> {
5073 crate::forward_id::forwarding_dup_key_parsed(header, frame)
5074 }
5075
5076 fn defer_pending_forward(
5077 &mut self,
5078 key: &DupCacheKey,
5079 header: &PacketHeader,
5080 rx: &RxInfo,
5081 options: &ParsedOptions,
5082 ) {
5083 let Some(queued) = self.tx_queue.remove_first_matching(|entry| {
5084 entry.priority == TxPriority::Forward
5085 && Self::confirmation_key(entry.frame.as_slice())
5086 .map(|entry_key| &entry_key == key)
5087 .unwrap_or(false)
5088 }) else {
5089 return;
5090 };
5091
5092 if queued.forward_deferrals >= self.repeater.flood_contention_max_deferrals {
5093 return;
5094 }
5095
5096 let now_ms = self.clock.now_ms();
5097 let mut delay_ms = self.sample_flood_contention_delay_ms(rx, options);
5098 let overheard_has_route_hops = options
5101 .source_route
5102 .as_ref()
5103 .map(|range| !range.is_empty())
5104 .unwrap_or(false);
5105 if header.ack_requested() && !overheard_has_route_hops {
5106 delay_ms = delay_ms.saturating_add(self.ack_guard_delay_ms());
5107 }
5108 let _ = self.tx_queue.enqueue_with_state(
5109 queued.priority,
5110 queued.frame.as_slice(),
5111 queued.receipt,
5112 queued.identity_id,
5113 now_ms.saturating_add(delay_ms),
5114 queued.cad_attempts,
5115 queued.forward_deferrals.saturating_add(1),
5116 );
5117 }
5118
5119 fn cancel_forwards_for_ack_mic(&mut self, ack_mic: &[u8]) -> usize {
5144 if ack_mic.len() != 4 {
5145 return 0;
5146 }
5147 let removed = self.tx_queue.remove_all_matching(|entry| {
5148 if entry.priority != TxPriority::Forward || entry.receipt.is_some() {
5149 return false;
5150 }
5151 let frame = entry.frame.as_slice();
5152 let Ok(header) = PacketHeader::parse(frame) else {
5153 return false;
5154 };
5155 if !matches!(
5160 header.packet_type(),
5161 PacketType::UnicastAckReq | PacketType::BlindUnicastAckReq
5162 ) {
5163 return false;
5164 }
5165 frame
5166 .get(header.mic_range.clone())
5167 .and_then(|mic| mic.get(..4))
5168 .map(|prefix| prefix == ack_mic)
5169 .unwrap_or(false)
5170 });
5171 if removed > 0 {
5172 MacCounters::bump(&mut self.counters.forward_cancelled);
5173 }
5174 removed
5175 }
5176
5177 fn cancel_forwards_for_ack_mic_option(&mut self, frame: &[u8], header: &PacketHeader) {
5186 if header.options_range.is_empty() {
5187 return;
5188 }
5189 let mut ack_mic = None;
5190 for entry in umsh_core::iter_options(frame, header.options_range.clone()) {
5191 let Ok((number, value)) = entry else {
5192 continue;
5193 };
5194 if OptionNumber::from(number) == OptionNumber::AckMic && value.len() == 4 {
5195 ack_mic = Some([value[0], value[1], value[2], value[3]]);
5196 break;
5197 }
5198 }
5199 if let Some(ack_mic) = ack_mic {
5200 self.cancel_forwards_for_ack_mic(&ack_mic);
5201 }
5202 }
5203
5204 async fn service_post_tx_listen(
5205 &mut self,
5206 mut on_event: impl FnMut(LocalIdentityId, crate::MacEventRef<'_>),
5207 ) -> Result<(), MacError<<P::Radio as Radio>::Error>> {
5208 loop {
5209 self.expire_post_tx_listen_if_needed();
5210 if self.post_tx_listen.is_none() {
5211 return Ok(());
5212 }
5213
5214 let handled = self.receive_one(&mut on_event).await?;
5215 if !handled {
5216 return Ok(());
5217 }
5218 }
5219 }
5220
5221 fn note_transmitted_tracked(&mut self, receipt: SendReceipt, frame: &[u8]) {
5223 let sent_ms = self.clock.now_ms();
5224 let direct_ack_deadline_ms = sent_ms.saturating_add(self.direct_ack_timeout_ms());
5225 let forwarded_ack_deadline_ms = sent_ms.saturating_add(self.forwarded_ack_timeout_ms());
5226 let repeat_only_deadline_ms = sent_ms.saturating_add(self.repeat_confirm_timeout_ms());
5227 let confirm_timeout_ms = self.forward_confirm_timeout_ms();
5228 let confirm_key = Self::confirmation_key(frame);
5229
5230 let post_tx_listen = {
5231 let Some((identity_id, pending)) = self.pending_ack_mut(receipt) else {
5232 return;
5233 };
5234
5235 let needs_forward_confirmation = match pending.state {
5236 crate::AckState::Queued {
5237 needs_forward_confirmation,
5238 } => needs_forward_confirmation,
5239 crate::AckState::RetryQueued => true,
5240 _ => return,
5241 };
5242
5243 pending.sent_ms = sent_ms;
5244 pending.confirm_key = confirm_key.clone();
5245 if pending.ack_deadline_ms == 0 {
5250 pending.ack_deadline_ms = match (pending.completion, needs_forward_confirmation) {
5251 (CompletionSignal::RepeatOnly, _) => repeat_only_deadline_ms,
5252 (CompletionSignal::Ack, true) => forwarded_ack_deadline_ms,
5253 (CompletionSignal::Ack, false) => direct_ack_deadline_ms,
5254 };
5255 }
5256
5257 if needs_forward_confirmation {
5258 let deadline_ms = sent_ms.saturating_add(confirm_timeout_ms);
5259 pending.state = crate::AckState::AwaitingForward {
5260 confirm_deadline_ms: deadline_ms,
5261 };
5262 if pending.expects_ack() {
5270 confirm_key.map(|confirm_key| PostTxListen {
5271 identity_id,
5272 receipt,
5273 confirm_key,
5274 deadline_ms,
5275 })
5276 } else {
5277 None
5278 }
5279 } else {
5280 pending.state = crate::AckState::AwaitingAck;
5281 None
5282 }
5283 };
5284
5285 self.post_tx_listen = post_tx_listen;
5286 }
5287
5288 fn expire_post_tx_listen_if_needed(&mut self) {
5289 let should_clear = self
5290 .post_tx_listen
5291 .as_ref()
5292 .map(|listen| self.clock.now_ms() >= listen.deadline_ms)
5293 .unwrap_or(false);
5294 if should_clear {
5295 self.post_tx_listen = None;
5296 }
5297 }
5298
5299 fn forward_confirm_timeout_ms(&self) -> u64 {
5300 let t_frame_ms = u64::from(self.radio.t_frame_ms());
5301 t_frame_ms
5302 .saturating_add(self.max_forward_contention_delay_ms())
5303 .saturating_add(self.ack_guard_delay_ms())
5304 .saturating_add(t_frame_ms)
5305 }
5306
5307 fn max_forward_contention_delay_ms(&self) -> u64 {
5308 u64::from(self.radio.t_frame_ms())
5309 .saturating_mul(u64::from(self.repeater.flood_contention_max_window_frames))
5310 }
5311
5312 fn forward_retry_backoff_cap_ms(&self) -> u32 {
5321 self.radio.t_frame_ms()
5322 }
5323
5324 fn can_attempt_route_retry(pending: &PendingAck<FRAME>) -> bool {
5339 if !pending.expects_ack() {
5343 return false;
5344 }
5345 let Ok(header) = PacketHeader::parse(pending.resend.frame.as_slice()) else {
5346 return false;
5347 };
5348 let Ok(options) = ParsedOptions::extract(
5349 pending.resend.frame.as_slice(),
5350 header.options_range.clone(),
5351 ) else {
5352 return false;
5353 };
5354 if options.route_retry {
5355 return false;
5356 }
5357 let has_source_route = pending
5358 .resend
5359 .source_route
5360 .as_ref()
5361 .map(|route| !route.is_empty())
5362 .unwrap_or(false);
5363 if has_source_route {
5364 return true;
5365 }
5366 let sent = header
5368 .flood_hops
5369 .map(|hops| hops.remaining())
5370 .unwrap_or_default();
5371 pending
5372 .resend
5373 .requested_flood_hops
5374 .is_some_and(|requested| requested > sent)
5375 }
5376
5377 fn direct_ack_timeout_ms(&self) -> u64 {
5378 u64::from(self.radio.t_frame_ms()).saturating_mul(10)
5379 }
5380
5381 fn forwarded_ack_timeout_ms(&self) -> u64 {
5382 self.repeat_confirm_timeout_ms()
5383 .saturating_add(u64::from(self.radio.t_frame_ms()))
5384 }
5385
5386 fn repeat_confirm_timeout_ms(&self) -> u64 {
5394 let per_retry_ms = u64::from(self.forward_retry_backoff_cap_ms())
5395 .saturating_add(self.forward_confirm_timeout_ms());
5396 self.forward_confirm_timeout_ms()
5397 .saturating_add(per_retry_ms.saturating_mul(u64::from(MAX_FORWARD_RETRIES)))
5398 }
5399
5400 fn pending_ack_mut(
5401 &mut self,
5402 receipt: SendReceipt,
5403 ) -> Option<(LocalIdentityId, &mut PendingAck<FRAME>)> {
5404 for (index, slot) in self.identities.iter_mut().enumerate() {
5405 let Some(slot) = slot.as_mut() else {
5406 continue;
5407 };
5408 if let Some(pending) = slot.pending_ack_mut(&receipt) {
5409 return Some((LocalIdentityId(index as u8), pending));
5410 }
5411 }
5412 None
5413 }
5414
5415 pub(crate) fn confirmation_key(frame: &[u8]) -> Option<DupCacheKey> {
5416 crate::forward_id::forwarding_dup_key(frame)
5417 }
5418
5419 fn observe_forwarding_confirmation(
5433 &mut self,
5434 frame: &[u8],
5435 ) -> Option<(LocalIdentityId, SendReceipt)> {
5436 self.expire_post_tx_listen_if_needed();
5437 let received_key = Self::confirmation_key(frame)?;
5438
5439 if self
5441 .post_tx_listen
5442 .as_ref()
5443 .map(|listen| listen.confirm_key == received_key)
5444 .unwrap_or(false)
5445 {
5446 self.post_tx_listen = None;
5447 }
5448
5449 let mut found = None;
5450 'search: for (index, slot) in self.identities.iter().enumerate() {
5451 let Some(slot) = slot.as_ref() else {
5452 continue;
5453 };
5454 for (receipt, pending) in slot.pending_acks.iter() {
5455 if !matches!(
5456 pending.state,
5457 crate::AckState::AwaitingForward { .. } | crate::AckState::RetryQueued
5458 ) {
5459 continue;
5460 }
5461 if pending.confirm_key.as_ref() != Some(&received_key) {
5462 continue;
5463 }
5464 found = Some((LocalIdentityId(index as u8), *receipt, pending.completion));
5465 break 'search;
5466 }
5467 }
5468 let (identity_id, receipt, completion) = found?;
5469
5470 self.tx_queue.remove_all_matching(|entry| {
5473 entry.receipt == Some(receipt) && entry.identity_id == Some(identity_id)
5474 });
5475
5476 match completion {
5477 CompletionSignal::Ack => {
5478 if let Some(pending) = self
5479 .identity_mut(identity_id)
5480 .and_then(|slot| slot.pending_ack_mut(&receipt))
5481 {
5482 pending.state = crate::AckState::AwaitingAck;
5483 }
5484 }
5485 CompletionSignal::RepeatOnly => {
5487 if let Some(slot) = self.identity_mut(identity_id) {
5488 slot.pending_acks.remove(&receipt);
5489 }
5490 }
5491 }
5492 Some((identity_id, receipt))
5493 }
5494
5495 fn peer_for_ack_trailer(&self, ack_trailer: &[u8; 8]) -> Option<PublicKey> {
5499 self.identities
5500 .iter()
5501 .filter_map(|slot| slot.as_ref())
5502 .find_map(|slot| self.match_pending_peer_for_ack(slot, ack_trailer))
5503 }
5504
5505 fn match_pending_peer_for_ack(
5506 &self,
5507 slot: &IdentitySlot<P::Identity, PEERS, ACKS, FRAME>,
5508 ack_trailer_bytes: &[u8],
5509 ) -> Option<PublicKey> {
5510 if ack_trailer_bytes.len() != 8 {
5511 return None;
5512 }
5513
5514 slot.pending_acks.iter().find_map(|(_, pending)| {
5515 (pending.expects_ack() && pending.ack_trailer == ack_trailer_bytes)
5516 .then_some(pending.peer)
5517 })
5518 }
5519}
5520
5521fn align_counter_boundary(value: u32) -> u32 {
5522 value & !COUNTER_PERSIST_BLOCK_MASK
5523}
5524
5525pub(crate) const fn nonzero_initial_frame_counter(random: u32) -> u32 {
5526 if random == 0 { 1 } else { random }
5527}
5528
5529fn next_counter_persist_target(next_counter: u32) -> u32 {
5530 next_counter.wrapping_add(COUNTER_PERSIST_BLOCK_SIZE) & !COUNTER_PERSIST_BLOCK_MASK
5531}
5532
5533fn rx_counter_key_bytes(pk: &[u8; 32]) -> [u8; 39] {
5539 let mut key = [0u8; 39];
5540 key[..7].copy_from_slice(b"mac.rx:");
5541 key[7..].copy_from_slice(pk);
5542 key
5543}