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,
11 options::{OptionEncoder, TraceSignalEntry},
12};
13use umsh_crypto::{CryptoEngine, CryptoError, DerivedChannelKeys, NodeIdentity, PairwiseKeys};
14use umsh_hal::{Clock, CounterStore, Radio, RxInfo, RxOrigin, Snr, TxError, TxOptions};
15
16use crate::{
17 AddPeerError, CapacityError, DEFAULT_ACKS, DEFAULT_CHANNEL_HINT_REPLAY, DEFAULT_CHANNEL_REPLAY,
18 DEFAULT_CHANNELS, DEFAULT_DUP, DEFAULT_IDENTITIES, DEFAULT_PEERS, DEFAULT_TX,
19 ESTABLISHED_ROUTE_EXTRA_HOPS, MAX_CAD_ATTEMPTS, MAX_FLOOD_HOPS, MAX_FORWARD_RETRIES,
20 MAX_RESEND_FRAME_LEN, MAX_SOURCE_ROUTE_HOPS, Platform, ReplayVerdict, ReplayWindow,
21 cache::{DupCacheKey, DuplicateCache},
22 peers::CachedRoute,
23 peers::{ChannelTable, PeerCryptoMap, PeerId, PeerRegistry},
24 send::{
25 CompletionSignal, PendingAck, PendingAckError, ResendRecord, SendOptions, SendReceipt,
26 TxPriority, TxQueue,
27 },
28};
29
30pub enum WakeReason {
36 Received(RxInfo),
39 TimerExpired,
42}
43
44pub(crate) const COUNTER_PERSIST_BLOCK_SIZE: u32 = 128;
45const COUNTER_PERSIST_BLOCK_MASK: u32 = COUNTER_PERSIST_BLOCK_SIZE - 1;
46const COUNTER_PERSIST_SCHEDULE_OFFSET: u32 = 100;
47const MAC_COMMAND_ECHO_REQUEST_ID: u8 = 4;
48const MAC_COMMAND_ECHO_RESPONSE_ID: u8 = 5;
49const COUNTER_RESYNC_NONCE_LEN: usize = 4;
50const COUNTER_RESYNC_REQUEST_RETRY_MS: u64 = 5_000;
51const COUNTER_RESYNC_GAP_THRESHOLD: u32 = 1024;
58
59#[derive(Clone, Copy)]
66pub(crate) enum ReplyCarriage<'a> {
67 Unicast,
68 Blind(&'a DerivedChannelKeys),
69}
70
71#[derive(Clone, Copy, Debug, PartialEq, Eq)]
72struct PendingCounterResync {
73 nonce: u32,
74 requested_ms: u64,
75}
76
77#[derive(Clone, Debug, PartialEq, Eq)]
78struct DeferredCounterResyncFrame<const FRAME: usize> {
79 local_id: LocalIdentityId,
80 peer_id: PeerId,
81 frame: Vec<u8, FRAME>,
82 rssi: i16,
83 snr: Snr,
84 lqi: Option<NonZeroU8>,
85 origin: RxOrigin,
86 received_at_ms: u64,
87}
88
89#[derive(Clone, Copy, Debug, PartialEq, Eq)]
90struct ResolvedMulticastSource {
91 peer_id: Option<PeerId>,
92 public_key: Option<PublicKey>,
93 hint: Option<NodeHint>,
94}
95
96#[derive(Clone, Debug, PartialEq, Eq)]
97struct PostTxListen {
98 identity_id: LocalIdentityId,
99 receipt: SendReceipt,
100 confirm_key: DupCacheKey,
101 deadline_ms: u64,
102}
103
104#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
117pub struct LocalIdentityId(pub u8);
118
119pub enum LocalIdentity<I: NodeIdentity> {
139 LongTerm(I),
141 #[cfg(feature = "software-crypto")]
142 Ephemeral(umsh_crypto::software::SoftwareIdentity),
144}
145
146impl<I: NodeIdentity> LocalIdentity<I> {
147 pub fn public_key(&self) -> &PublicKey {
149 match self {
150 Self::LongTerm(identity) => identity.public_key(),
151 #[cfg(feature = "software-crypto")]
152 Self::Ephemeral(identity) => identity.public_key(),
153 }
154 }
155
156 pub fn hint(&self) -> umsh_core::NodeHint {
158 self.public_key().hint()
159 }
160
161 pub fn is_ephemeral(&self) -> bool {
163 match self {
164 Self::LongTerm(_) => false,
165 #[cfg(feature = "software-crypto")]
166 Self::Ephemeral(_) => true,
167 }
168 }
169}
170
171impl<I: NodeIdentity> From<I> for LocalIdentity<I> {
172 fn from(value: I) -> Self {
173 Self::LongTerm(value)
174 }
175}
176
177pub struct IdentitySlot<
213 I: NodeIdentity,
214 const PEERS: usize,
215 const ACKS: usize,
216 const FRAME: usize = MAX_RESEND_FRAME_LEN,
217> {
218 identity: LocalIdentity<I>,
219 peer_crypto: PeerCryptoMap<PEERS>,
220 frame_counter: u32,
221 persisted_counter: u32,
222 pending_persist_target: Option<u32>,
223 save_scheduled_since_boot: bool,
224 counter_persistence_enabled: bool,
225 pending_acks: LinearMap<SendReceipt, PendingAck<FRAME>, ACKS>,
226 next_receipt: u32,
227 pfs_parent: Option<LocalIdentityId>,
228 pending_counter_resync: LinearMap<PeerId, PendingCounterResync, PEERS>,
229}
230
231impl<I: NodeIdentity, const PEERS: usize, const ACKS: usize, const FRAME: usize>
232 IdentitySlot<I, PEERS, ACKS, FRAME>
233{
234 pub fn new(
236 identity: LocalIdentity<I>,
237 frame_counter: u32,
238 pfs_parent: Option<LocalIdentityId>,
239 ) -> Self {
240 let counter_persistence_enabled = !identity.is_ephemeral();
241 Self {
242 identity,
243 peer_crypto: PeerCryptoMap::new(),
244 frame_counter,
245 persisted_counter: frame_counter,
246 pending_persist_target: None,
247 save_scheduled_since_boot: false,
248 counter_persistence_enabled,
249 pending_acks: LinearMap::new(),
250 next_receipt: 0,
251 pfs_parent,
252 pending_counter_resync: LinearMap::new(),
253 }
254 }
255
256 pub fn identity(&self) -> &LocalIdentity<I> {
258 &self.identity
259 }
260 pub fn peer_crypto(&self) -> &PeerCryptoMap<PEERS> {
262 &self.peer_crypto
263 }
264 pub fn peer_crypto_mut(&mut self) -> &mut PeerCryptoMap<PEERS> {
266 &mut self.peer_crypto
267 }
268 pub fn frame_counter(&self) -> u32 {
270 self.frame_counter
271 }
272 pub fn persisted_counter(&self) -> u32 {
274 self.persisted_counter
275 }
276 #[cfg(test)]
281 pub(crate) fn set_frame_counter(&mut self, value: u32) {
282 self.frame_counter = value;
283 }
284 pub fn pending_persist_target(&self) -> Option<u32> {
286 self.pending_persist_target
287 }
288
289 pub fn counter_persistence_enabled(&self) -> bool {
291 self.counter_persistence_enabled
292 }
293
294 pub(crate) fn advance_frame_counter(&mut self) -> u32 {
296 let current = self.frame_counter;
297 self.frame_counter = self.frame_counter.wrapping_add(1);
298 current
299 }
300
301 pub fn load_persisted_counter(&mut self, value: u32) {
303 let aligned = align_counter_boundary(value);
304 self.frame_counter = aligned;
305 self.persisted_counter = aligned;
306 self.pending_persist_target = None;
307 self.save_scheduled_since_boot = false;
308 }
309
310 fn schedule_counter_persist_if_needed(&mut self) {
311 if !self.counter_persistence_enabled {
312 return;
313 }
314
315 let should_schedule = !self.save_scheduled_since_boot
316 || (self.frame_counter & COUNTER_PERSIST_BLOCK_MASK) >= COUNTER_PERSIST_SCHEDULE_OFFSET;
317 if !should_schedule {
318 return;
319 }
320
321 let target = next_counter_persist_target(
330 self.frame_counter
331 .wrapping_add(COUNTER_PERSIST_BLOCK_SIZE - COUNTER_PERSIST_SCHEDULE_OFFSET),
332 );
333 if self.persisted_counter.wrapping_sub(target) <= 2 * COUNTER_PERSIST_BLOCK_SIZE {
337 return;
338 }
339 if self.pending_persist_target == Some(target) {
340 return;
341 }
342 self.pending_persist_target = Some(
343 self.pending_persist_target
344 .map(|existing| existing.max(target))
345 .unwrap_or(target),
346 );
347 self.save_scheduled_since_boot = true;
348 }
349
350 fn mark_counter_persisted(&mut self, value: u32) {
351 let aligned = align_counter_boundary(value);
352 self.persisted_counter = aligned;
353 if self.pending_persist_target == Some(aligned) {
354 self.pending_persist_target = None;
355 }
356 }
357
358 fn counter_window_exhausted(&self) -> bool {
359 if !self.counter_persistence_enabled {
360 return false;
361 }
362
363 let ahead = self.persisted_counter.wrapping_sub(self.frame_counter);
368 if ahead > 0 && ahead <= 2 * COUNTER_PERSIST_BLOCK_SIZE {
369 return false;
370 }
371
372 if ahead == 0 {
373 return self.save_scheduled_since_boot;
374 }
375
376 self.frame_counter.wrapping_sub(self.persisted_counter) >= COUNTER_PERSIST_BLOCK_SIZE
377 }
378
379 pub fn next_receipt(&mut self) -> SendReceipt {
381 let receipt = SendReceipt(self.next_receipt);
382 self.next_receipt = self.next_receipt.wrapping_add(1);
383 receipt
384 }
385
386 #[cfg(test)]
388 pub(crate) fn set_next_receipt_for_test(&mut self, value: u32) {
389 self.next_receipt = value;
390 }
391
392 pub fn try_insert_pending_ack(
394 &mut self,
395 receipt: SendReceipt,
396 pending: PendingAck<FRAME>,
397 ) -> Result<Option<PendingAck<FRAME>>, PendingAckError> {
398 self.pending_acks
399 .insert(receipt, pending)
400 .map_err(|_| PendingAckError::TableFull)
401 }
402
403 pub fn pending_ack(&self, receipt: &SendReceipt) -> Option<&PendingAck<FRAME>> {
405 self.pending_acks.get(receipt)
406 }
407
408 pub fn pending_acks(&self) -> impl Iterator<Item = (&SendReceipt, &PendingAck<FRAME>)> {
413 self.pending_acks.iter()
414 }
415
416 pub fn pending_ack_mut(&mut self, receipt: &SendReceipt) -> Option<&mut PendingAck<FRAME>> {
418 self.pending_acks.get_mut(receipt)
419 }
420
421 pub fn remove_pending_ack(&mut self, receipt: &SendReceipt) -> Option<PendingAck<FRAME>> {
423 self.pending_acks.remove(receipt)
424 }
425
426 pub fn pfs_parent(&self) -> Option<LocalIdentityId> {
428 self.pfs_parent
429 }
430
431 fn pending_counter_resync(&self) -> &LinearMap<PeerId, PendingCounterResync, PEERS> {
433 &self.pending_counter_resync
434 }
435
436 fn pending_counter_resync_mut(
438 &mut self,
439 ) -> &mut LinearMap<PeerId, PendingCounterResync, PEERS> {
440 &mut self.pending_counter_resync
441 }
442}
443
444#[derive(Clone, Debug, PartialEq, Eq)]
464pub struct ChannelPolicy {
465 pub channel_id: ChannelId,
467 pub require_unencrypted: bool,
469 pub require_full_source: bool,
471 pub max_flood_hops: Option<u8>,
473}
474
475#[derive(Clone, Copy, Debug, PartialEq, Eq)]
493pub enum AmateurRadioMode {
494 Unlicensed,
501 LicensedOnly,
507 Hybrid,
514}
515
516#[derive(Clone, Copy, Debug, PartialEq, Eq)]
517enum TransmitAuthority {
518 Unlicensed,
519 Amateur,
520}
521
522#[derive(Clone, Copy, Debug, PartialEq, Eq)]
523enum ForwardStationAction {
524 Remove,
525 Replace,
526}
527
528#[derive(Clone, Copy, Debug, PartialEq, Eq)]
529struct ForwardPlan {
530 router_hint: RouterHint,
531 consume_source_route: bool,
532 decrement_flood_hops: bool,
533 insert_region_code: Option<[u8; 2]>,
534 delay_ms: u64,
535 station_action: ForwardStationAction,
536 signal: TraceSignalEntry,
539}
540
541fn rx_metadata(rx: &RxInfo, received_at_ms: u64) -> crate::send::RxMetadata {
547 if rx.origin.is_measured() {
548 crate::send::RxMetadata::new(Some(rx.rssi), Some(rx.snr), rx.lqi, Some(received_at_ms))
549 } else {
550 crate::send::RxMetadata::new(None, None, None, Some(received_at_ms))
551 }
552}
553
554fn trace_signal_entry(rx: &RxInfo) -> TraceSignalEntry {
555 if rx.origin.is_measured() {
556 TraceSignalEntry::new(rx.rssi, rx.snr.as_centibels())
557 } else {
558 TraceSignalEntry::UNMEASURED
561 }
562}
563
564#[derive(Clone, Debug, PartialEq, Eq)]
583pub struct OperatingPolicy {
584 pub amateur_radio_mode: AmateurRadioMode,
586 pub operator_callsign: Option<HamAddr>,
588 pub channel_policies: Vec<ChannelPolicy, 4>,
590}
591
592impl Default for OperatingPolicy {
593 fn default() -> Self {
594 Self {
595 amateur_radio_mode: AmateurRadioMode::Unlicensed,
596 operator_callsign: None,
597 channel_policies: Vec::new(),
598 }
599 }
600}
601
602#[derive(Clone, Debug, PartialEq, Eq)]
636pub struct RepeaterConfig {
637 pub enabled: bool,
639 pub regions: Vec<[u8; 2], 8>,
641 pub default_region: Option<[u8; 2]>,
644 pub min_rssi: Option<i16>,
646 pub min_snr: Option<i8>,
648 pub flood_contention_snr_low_db: i8,
650 pub flood_contention_snr_high_db: i8,
652 pub flood_contention_rssi_low_dbm: i16,
654 pub flood_contention_rssi_high_dbm: i16,
656 pub flood_contention_min_window_percent: u8,
658 pub flood_contention_max_window_percent: u8,
660 pub flood_contention_jitter_percent: u8,
663 pub flood_contention_ack_guard_percent: u8,
668 pub flood_contention_max_deferrals: u8,
670 pub amateur_radio_mode: AmateurRadioMode,
672 pub station_callsign: Option<HamAddr>,
674}
675
676impl Default for RepeaterConfig {
677 fn default() -> Self {
678 Self {
679 enabled: false,
680 regions: Vec::new(),
681 default_region: None,
682 min_rssi: None,
683 min_snr: None,
684 flood_contention_snr_low_db: -9,
685 flood_contention_snr_high_db: 3,
686 flood_contention_rssi_low_dbm: -100,
687 flood_contention_rssi_high_dbm: -70,
688 flood_contention_min_window_percent: 0,
689 flood_contention_max_window_percent: 50,
690 flood_contention_jitter_percent: 10,
691 flood_contention_ack_guard_percent: 25,
692 flood_contention_max_deferrals: 3,
693 amateur_radio_mode: AmateurRadioMode::Unlicensed,
694 station_callsign: None,
695 }
696 }
697}
698
699#[derive(Clone, Debug, PartialEq, Eq)]
705pub enum SendError {
706 IdentityMissing,
709 PeerMissing,
712 PairwiseKeysMissing,
716 IdentityAgreementFailed,
718 ChannelMissing,
721 PolicyViolation,
724 Build(BuildError),
727 Parse(ParseError),
730 Crypto(CryptoError),
733 QueueFull,
736 PendingAckFull,
739 CounterPersistenceLag,
743}
744
745impl From<BuildError> for SendError {
746 fn from(value: BuildError) -> Self {
747 Self::Build(value)
748 }
749}
750
751impl From<ParseError> for SendError {
752 fn from(value: ParseError) -> Self {
753 Self::Parse(value)
754 }
755}
756
757impl From<CryptoError> for SendError {
758 fn from(value: CryptoError) -> Self {
759 Self::Crypto(value)
760 }
761}
762
763#[derive(Clone, Debug, PartialEq, Eq)]
769pub enum MacError<RadioError> {
770 Radio(RadioError),
774 Transmit(TxError<RadioError>),
780 QueueFull,
784}
785
786#[derive(Clone, Debug, PartialEq, Eq)]
792pub enum CounterPersistenceError<StoreError> {
793 IdentityMissing,
795 Store(StoreError),
799}
800
801impl<RadioError> From<RadioError> for MacError<RadioError> {
802 fn from(value: RadioError) -> Self {
803 Self::Radio(value)
804 }
805}
806
807impl<RadioError> From<TxError<RadioError>> for MacError<RadioError> {
808 fn from(value: TxError<RadioError>) -> Self {
809 Self::Transmit(value)
810 }
811}
812
813#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
886pub struct MacCounters {
887 pub tx_frames: u32,
889 pub tx_abandoned: u32,
891 pub rx_frames: u32,
893 pub rx_accepted: u32,
897 pub forwarded: u32,
899 pub forward_cancelled: u32,
902 pub forward_dropped_policy: u32,
913}
914
915impl MacCounters {
916 fn bump(slot: &mut u32) {
917 *slot = slot.saturating_add(1);
918 }
919}
920
921pub struct Mac<
922 P: Platform,
923 const IDENTITIES: usize = DEFAULT_IDENTITIES,
924 const PEERS: usize = DEFAULT_PEERS,
925 const CHANNELS: usize = DEFAULT_CHANNELS,
926 const ACKS: usize = DEFAULT_ACKS,
927 const TX: usize = DEFAULT_TX,
928 const FRAME: usize = MAX_RESEND_FRAME_LEN,
929 const DUP: usize = DEFAULT_DUP,
930 const RN: usize = DEFAULT_CHANNEL_REPLAY,
931 const HN: usize = DEFAULT_CHANNEL_HINT_REPLAY,
932> {
933 radio: P::Radio,
934 crypto: CryptoEngine<P::Aes, P::Sha>,
935 clock: P::Clock,
936 rng: P::Rng,
937 counter_store: P::CounterStore,
938 identities: Vec<Option<IdentitySlot<P::Identity, PEERS, ACKS, FRAME>>, IDENTITIES>,
939 peer_registry: PeerRegistry<PEERS>,
940 transmitter_observations: crate::TransmitterObservations,
941 channels: ChannelTable<CHANNELS, RN, HN>,
942 dup_cache: DuplicateCache<DUP>,
943 multicast_unknown_dup_cache: DuplicateCache<DUP>,
944 tx_queue: TxQueue<TX, FRAME>,
945 post_tx_listen: Option<PostTxListen>,
946 repeater: RepeaterConfig,
947 operating_policy: OperatingPolicy,
948 auto_register_full_key_peers: bool,
949 deferred_counter_resync_frame: Option<DeferredCounterResyncFrame<FRAME>>,
950 counters: MacCounters,
951}
952
953impl<
954 P: Platform,
955 const IDENTITIES: usize,
956 const PEERS: usize,
957 const CHANNELS: usize,
958 const ACKS: usize,
959 const TX: usize,
960 const FRAME: usize,
961 const DUP: usize,
962 const RN: usize,
963 const HN: usize,
964> Mac<P, IDENTITIES, PEERS, CHANNELS, ACKS, TX, FRAME, DUP, RN, HN>
965{
966 pub fn new(
968 radio: P::Radio,
969 crypto: CryptoEngine<P::Aes, P::Sha>,
970 clock: P::Clock,
971 rng: P::Rng,
972 counter_store: P::CounterStore,
973 repeater: RepeaterConfig,
974 operating_policy: OperatingPolicy,
975 ) -> Self {
976 Self {
977 radio,
978 crypto,
979 clock,
980 rng,
981 counter_store,
982 identities: Vec::new(),
983 peer_registry: PeerRegistry::new(),
984 transmitter_observations: crate::TransmitterObservations::new(),
985 channels: ChannelTable::new(),
986 dup_cache: DuplicateCache::new(),
987 multicast_unknown_dup_cache: DuplicateCache::new(),
988 tx_queue: TxQueue::new(),
989 post_tx_listen: None,
990 repeater,
991 operating_policy,
992 auto_register_full_key_peers: false,
993 deferred_counter_resync_frame: None,
994 counters: MacCounters::default(),
995 }
996 }
997
998 pub const fn counters(&self) -> MacCounters {
1000 self.counters
1001 }
1002
1003 pub fn transmitter_observations(&self) -> &crate::TransmitterObservations {
1006 &self.transmitter_observations
1007 }
1008
1009 pub fn radio(&self) -> &P::Radio {
1011 &self.radio
1012 }
1013
1014 pub fn radio_mut(&mut self) -> &mut P::Radio {
1016 &mut self.radio
1017 }
1018
1019 pub fn into_radio(self) -> P::Radio {
1025 self.radio
1026 }
1027
1028 pub fn crypto(&self) -> &CryptoEngine<P::Aes, P::Sha> {
1030 &self.crypto
1031 }
1032
1033 pub fn clock(&self) -> &P::Clock {
1035 &self.clock
1036 }
1037
1038 pub fn rng(&self) -> &P::Rng {
1040 &self.rng
1041 }
1042
1043 pub fn rng_mut(&mut self) -> &mut P::Rng {
1045 &mut self.rng
1046 }
1047
1048 pub fn counter_store(&self) -> &P::CounterStore {
1050 &self.counter_store
1051 }
1052 pub fn tx_queue(&self) -> &TxQueue<TX, FRAME> {
1054 &self.tx_queue
1055 }
1056 pub fn tx_queue_mut(&mut self) -> &mut TxQueue<TX, FRAME> {
1058 &mut self.tx_queue
1059 }
1060 pub fn dup_cache(&self) -> &DuplicateCache<DUP> {
1062 &self.dup_cache
1063 }
1064 pub fn peer_registry(&self) -> &PeerRegistry<PEERS> {
1066 &self.peer_registry
1067 }
1068 pub fn peer_registry_mut(&mut self) -> &mut PeerRegistry<PEERS> {
1070 &mut self.peer_registry
1071 }
1072 pub fn channels(&self) -> &ChannelTable<CHANNELS, RN, HN> {
1074 &self.channels
1075 }
1076 pub fn channels_mut(&mut self) -> &mut ChannelTable<CHANNELS, RN, HN> {
1078 &mut self.channels
1079 }
1080 pub fn repeater_config(&self) -> &RepeaterConfig {
1082 &self.repeater
1083 }
1084 pub fn repeater_config_mut(&mut self) -> &mut RepeaterConfig {
1086 &mut self.repeater
1087 }
1088 pub fn operating_policy(&self) -> &OperatingPolicy {
1090 &self.operating_policy
1091 }
1092 pub fn operating_policy_mut(&mut self) -> &mut OperatingPolicy {
1094 &mut self.operating_policy
1095 }
1096
1097 pub fn auto_register_full_key_peers(&self) -> bool {
1099 self.auto_register_full_key_peers
1100 }
1101
1102 pub fn set_auto_register_full_key_peers(&mut self, enabled: bool) {
1104 self.auto_register_full_key_peers = enabled;
1105 }
1106
1107 pub fn add_identity(
1109 &mut self,
1110 identity: P::Identity,
1111 ) -> Result<LocalIdentityId, CapacityError> {
1112 self.insert_identity(LocalIdentity::LongTerm(identity), None)
1113 }
1114
1115 pub async fn load_persisted_counter(
1125 &mut self,
1126 id: LocalIdentityId,
1127 ) -> Result<u32, CounterPersistenceError<<P::CounterStore as CounterStore>::Error>> {
1128 let context = {
1129 let slot = self
1130 .identity(id)
1131 .ok_or(CounterPersistenceError::IdentityMissing)?;
1132 if !slot.counter_persistence_enabled() {
1133 return Ok(slot.frame_counter());
1134 }
1135 *slot.identity().public_key()
1136 };
1137 let loaded = self
1138 .counter_store
1139 .load(&context.0)
1140 .await
1141 .map_err(CounterPersistenceError::Store)?;
1142 if loaded == 0 {
1147 let slot = self
1148 .identity(id)
1149 .ok_or(CounterPersistenceError::IdentityMissing)?;
1150 return Ok(slot.frame_counter());
1151 }
1152 let aligned = align_counter_boundary(loaded);
1153 let slot = self
1154 .identity_mut(id)
1155 .ok_or(CounterPersistenceError::IdentityMissing)?;
1156 slot.load_persisted_counter(aligned);
1157 Ok(aligned)
1158 }
1159
1160 pub async fn service_counter_persistence(
1162 &mut self,
1163 ) -> Result<usize, <P::CounterStore as CounterStore>::Error> {
1164 let mut pending = Vec::<(LocalIdentityId, [u8; 32], u32), IDENTITIES>::new();
1165 for (index, slot) in self.identities.iter().enumerate() {
1166 let Some(slot) = slot.as_ref() else {
1167 continue;
1168 };
1169 let Some(target) = slot.pending_persist_target() else {
1170 continue;
1171 };
1172 if !slot.counter_persistence_enabled() {
1173 continue;
1174 }
1175 pending
1176 .push((
1177 LocalIdentityId(index as u8),
1178 slot.identity().public_key().0,
1179 target,
1180 ))
1181 .expect("identity enumeration must fit configured identity capacity");
1182 }
1183
1184 let mut wrote = 0usize;
1185 for (_, context, target) in pending.iter() {
1186 self.counter_store
1187 .store(context, align_counter_boundary(*target))
1188 .await?;
1189 wrote += 1;
1190 }
1191 if wrote > 0 {
1192 self.counter_store.flush().await?;
1193 for (id, _, target) in pending {
1194 if let Some(slot) = self.identity_mut(id) {
1195 slot.mark_counter_persisted(target);
1196 }
1197 }
1198 }
1199 Ok(wrote)
1200 }
1201
1202 pub(crate) async fn service_rx_counter_persistence(
1208 &mut self,
1209 ) -> Result<usize, <P::CounterStore as CounterStore>::Error> {
1210 let mut pending: heapless::Vec<(u8, crate::peers::PeerId, [u8; 32], u32), PEERS> =
1214 heapless::Vec::new();
1215
1216 for (identity_index, slot) in self.identities.iter().enumerate() {
1217 let Some(slot) = slot.as_ref() else {
1218 continue;
1219 };
1220 for (peer_id, state) in slot.peer_crypto().iter() {
1221 if !state.needs_rx_persist {
1222 continue;
1223 }
1224 let Some(info) = self.peer_registry.get(*peer_id) else {
1225 continue;
1226 };
1227 let _ = pending.push((
1228 identity_index as u8,
1229 *peer_id,
1230 info.public_key.0,
1231 state.replay_window.last_accepted,
1232 ));
1233 }
1234 }
1235
1236 let mut wrote = 0usize;
1237 for (_, _, pk, last) in pending.iter() {
1238 let key = rx_counter_key_bytes(pk);
1239 self.counter_store.store(&key, *last).await?;
1240 wrote += 1;
1241 }
1242 if wrote > 0 {
1243 self.counter_store.flush().await?;
1244 for (identity_index, peer_id, _, last) in pending.iter() {
1247 if let Some(slot) = self.identities[*identity_index as usize].as_mut() {
1248 if let Some(state) = slot.peer_crypto_mut().get_mut(peer_id) {
1249 state.persisted_rx_counter = *last;
1250 state.needs_rx_persist = false;
1251 }
1252 }
1253 }
1254 }
1255 Ok(wrote)
1256 }
1257
1258 pub async fn persist_all_rx_counters(
1269 &mut self,
1270 ) -> Result<usize, <P::CounterStore as CounterStore>::Error> {
1271 for slot in self.identities.iter_mut() {
1272 let Some(slot) = slot.as_mut() else {
1273 continue;
1274 };
1275 for (_, state) in slot.peer_crypto_mut().iter_mut() {
1276 if state.replay_window.last_accepted != state.persisted_rx_counter {
1277 state.needs_rx_persist = true;
1278 }
1279 }
1280 }
1281 self.service_rx_counter_persistence().await
1282 }
1283
1284 pub async fn load_all_persisted_rx_counters(
1292 &mut self,
1293 ) -> Result<usize, <P::CounterStore as CounterStore>::Error> {
1294 let mut loaded = 0usize;
1295 for peer_index in 0..PEERS {
1297 let peer_id = crate::peers::PeerId(peer_index as u8);
1298 let Some(info) = self.peer_registry.get(peer_id) else {
1299 break;
1300 };
1301 let pk = info.public_key;
1302 let key = rx_counter_key_bytes(&pk.0);
1303 let stored = self.counter_store.load(&key).await?;
1304 if stored > 0 {
1305 if let Some(info) = self.peer_registry.get_mut(peer_id) {
1306 info.initial_rx_counter = stored;
1307 loaded += 1;
1308 }
1309 }
1310 }
1311 Ok(loaded)
1312 }
1313
1314 #[cfg(feature = "software-crypto")]
1315 pub fn register_ephemeral(
1317 &mut self,
1318 parent: LocalIdentityId,
1319 identity: umsh_crypto::software::SoftwareIdentity,
1320 ) -> Result<LocalIdentityId, CapacityError> {
1321 self.insert_identity(LocalIdentity::Ephemeral(identity), Some(parent))
1322 }
1323
1324 #[cfg(feature = "software-crypto")]
1325 pub fn remove_ephemeral(&mut self, id: LocalIdentityId) -> bool {
1327 if let Some(slot) = self.identities.get_mut(id.0 as usize) {
1328 let should_remove = slot
1329 .as_ref()
1330 .map(|identity_slot| identity_slot.identity().is_ephemeral())
1331 .unwrap_or(false);
1332 if should_remove {
1333 *slot = None;
1334 return true;
1335 }
1336 }
1337 false
1338 }
1339
1340 pub fn identity(
1342 &self,
1343 id: LocalIdentityId,
1344 ) -> Option<&IdentitySlot<P::Identity, PEERS, ACKS, FRAME>> {
1345 self.identities.get(id.0 as usize)?.as_ref()
1346 }
1347
1348 pub fn identity_mut(
1350 &mut self,
1351 id: LocalIdentityId,
1352 ) -> Option<&mut IdentitySlot<P::Identity, PEERS, ACKS, FRAME>> {
1353 self.identities.get_mut(id.0 as usize)?.as_mut()
1354 }
1355
1356 pub fn add_peer(&mut self, key: PublicKey) -> Result<PeerId, AddPeerError> {
1363 #[cfg(feature = "software-crypto")]
1364 {
1365 if !umsh_crypto::is_valid_ed25519_public_key(&key) {
1366 return Err(AddPeerError::InvalidPublicKey);
1367 }
1368 }
1369 Ok(self.peer_registry.try_insert_or_update(key)?)
1370 }
1371
1372 pub fn remove_peer(&mut self, key: &PublicKey) -> bool {
1382 let Some((peer_id, _)) = self.peer_registry.lookup_by_key(key) else {
1383 return false;
1384 };
1385 self.clear_peer_slot_state(peer_id);
1386 if self
1387 .deferred_counter_resync_frame
1388 .as_ref()
1389 .is_some_and(|deferred| deferred.peer_id == peer_id)
1390 {
1391 self.deferred_counter_resync_frame = None;
1392 }
1393 let Some(removal) = self.peer_registry.remove(peer_id) else {
1394 return false;
1395 };
1396 if let Some((old_id, new_id)) = removal.moved {
1397 self.rekey_peer_slot_state(old_id, new_id);
1398 }
1399 true
1400 }
1401
1402 pub fn add_channel(&mut self, key: ChannelKey) -> Result<(), CapacityError> {
1404 let derived = self.crypto.derive_channel_keys(&key);
1405 self.channels.try_add(key, derived)
1406 }
1407
1408 pub fn remove_channel(&mut self, key: &ChannelKey) -> bool {
1412 self.channels.remove_by_key(key)
1413 }
1414
1415 pub fn add_named_channel(&mut self, name: &str) -> Result<(), crate::AddChannelError> {
1420 let key = self
1421 .crypto
1422 .derive_named_channel_key(name)
1423 .map_err(crate::AddChannelError::InvalidName)?;
1424 self.add_channel(key)?;
1425 Ok(())
1426 }
1427
1428 pub fn identity_count(&self) -> usize {
1430 self.identities.iter().filter(|slot| slot.is_some()).count()
1431 }
1432
1433 #[cfg(any(feature = "unsafe-advanced", test))]
1440 pub(crate) fn install_pairwise_keys(
1441 &mut self,
1442 identity_id: LocalIdentityId,
1443 peer_id: PeerId,
1444 pairwise_keys: PairwiseKeys,
1445 ) -> Result<Option<crate::peers::PeerCryptoState>, SendError> {
1446 let initial = self
1448 .peer_registry
1449 .get(peer_id)
1450 .map(|info| info.initial_rx_counter)
1451 .unwrap_or(0);
1452 let now_ms = self.clock.now_ms();
1453 let mut replay_window = ReplayWindow::new();
1454 if initial > 0 {
1455 replay_window.reset(initial, now_ms);
1456 }
1457 let slot = self
1458 .identity_mut(identity_id)
1459 .ok_or(SendError::IdentityMissing)?;
1460 slot.peer_crypto_mut()
1461 .insert(
1462 peer_id,
1463 crate::peers::PeerCryptoState {
1464 pairwise_keys,
1465 replay_window,
1466 persisted_rx_counter: initial,
1467 needs_rx_persist: false,
1468 },
1469 )
1470 .map_err(|_| SendError::QueueFull)
1471 }
1472
1473 #[cfg(feature = "unsafe-advanced")]
1480 pub fn install_pairwise_keys_advanced(
1481 &mut self,
1482 identity_id: LocalIdentityId,
1483 peer_id: PeerId,
1484 pairwise_keys: PairwiseKeys,
1485 ) -> Result<Option<crate::peers::PeerCryptoState>, SendError> {
1486 self.install_pairwise_keys(identity_id, peer_id, pairwise_keys)
1487 }
1488
1489 pub fn queue_broadcast(
1501 &mut self,
1502 from: LocalIdentityId,
1503 payload: &[u8],
1504 options: &SendOptions,
1505 ) -> Result<SendReceipt, SendError> {
1506 let mut options = options.clone();
1512 options.encrypted = false;
1513 options.ack_requested = false;
1514 options.salt = false;
1515 let options = &options;
1516 self.enforce_send_policy(None, options, false)?;
1517
1518 let slot = self.identity_mut(from).ok_or(SendError::IdentityMissing)?;
1519 let source_key = *slot.identity().public_key();
1520 let receipt = slot.next_receipt();
1521 let mut buf = [0u8; FRAME];
1522 let builder = PacketBuilder::new(&mut buf).broadcast();
1523 let mut builder = if options.full_source {
1524 builder.source_full(&source_key)
1525 } else {
1526 builder.source_hint(source_key.hint())
1527 };
1528 let repeatable =
1529 Self::frame_is_repeatable(options.flood_hops, options.source_route.as_deref());
1530 if let Some(hops) = options.flood_hops {
1531 builder = builder.flood_hops(hops);
1532 }
1533 if options.trace_route && repeatable {
1534 builder = builder.trace_route();
1535 }
1536 if let Some(route) = options
1539 .source_route
1540 .as_ref()
1541 .filter(|route| !route.is_empty())
1542 {
1543 builder = builder.source_route(route.as_slice());
1544 }
1545 if let Some(callsign) = self.operating_policy.operator_callsign {
1546 builder = builder.option(OptionNumber::OperatorCallsign, callsign.as_trimmed_slice());
1547 }
1548 if options.trace_signal && repeatable {
1549 builder = builder.trace_signal();
1550 }
1551 if let Some(region_code) = options.region_code {
1552 builder = builder.region_code(region_code);
1553 }
1554 let frame = builder.payload(payload).build()?;
1555 if frame.len() > self.radio.max_frame_size() {
1556 return Err(SendError::Build(BuildError::BufferTooSmall));
1557 }
1558 let not_before_ms = self.tx_not_before_ms(options);
1559 self.tx_queue
1560 .enqueue_with_state(
1561 TxPriority::Application,
1562 frame,
1563 Some(receipt),
1564 Some(from),
1565 not_before_ms,
1566 0,
1567 0,
1568 )
1569 .map_err(|_| SendError::QueueFull)?;
1570 Ok(receipt)
1571 }
1572
1573 pub async fn send_broadcast(
1575 &mut self,
1576 from: LocalIdentityId,
1577 payload: &[u8],
1578 options: &SendOptions,
1579 ) -> Result<SendReceipt, SendError> {
1580 self.queue_broadcast(from, payload, options)
1581 }
1582
1583 pub fn queue_multicast(
1585 &mut self,
1586 from: LocalIdentityId,
1587 channel_id: &ChannelId,
1588 payload: &[u8],
1589 options: &SendOptions,
1590 ) -> Result<SendReceipt, SendError> {
1591 self.enforce_send_policy(Some(*channel_id), options, false)?;
1592 let derived = self
1599 .channels
1600 .lookup_by_id(channel_id)
1601 .next()
1602 .ok_or(SendError::ChannelMissing)?
1603 .derived
1604 .clone();
1605 let keys = PairwiseKeys {
1606 k_enc: derived.k_enc,
1607 k_mic: derived.k_mic,
1608 };
1609 let receipt = self
1610 .identity_mut(from)
1611 .ok_or(SendError::IdentityMissing)?
1612 .next_receipt();
1613 let (source_key, frame_counter) = self.identity_and_advance(from)?;
1614 let salt = self.take_salt(options);
1615 let mut buf = [0u8; FRAME];
1616 let builder = PacketBuilder::new(&mut buf).multicast(*channel_id);
1617 let builder = if options.full_source {
1618 builder.source_full(&source_key)
1619 } else {
1620 builder.source_hint(source_key.hint())
1621 };
1622 let mut builder = builder.frame_counter(frame_counter);
1623 if options.encrypted {
1624 builder = builder.encrypted();
1625 }
1626 builder = builder.mic_size(options.mic_size);
1627 if let Some(salt) = salt {
1628 builder = builder.salt(salt);
1629 }
1630 let repeatable =
1631 Self::frame_is_repeatable(options.flood_hops, options.source_route.as_deref());
1632 if let Some(hops) = options.flood_hops {
1633 builder = builder.flood_hops(hops);
1634 }
1635 if options.trace_route && repeatable {
1636 builder = builder.trace_route();
1637 }
1638 if let Some(route) = options
1641 .source_route
1642 .as_ref()
1643 .filter(|route| !route.is_empty())
1644 {
1645 builder = builder.source_route(route.as_slice());
1646 }
1647 if let Some(callsign) = self.operating_policy.operator_callsign {
1648 builder = builder.option(OptionNumber::OperatorCallsign, callsign.as_trimmed_slice());
1649 }
1650 if options.trace_signal && repeatable {
1651 builder = builder.trace_signal();
1652 }
1653 if let Some(region_code) = options.region_code {
1654 builder = builder.region_code(region_code);
1655 }
1656 let mut packet = builder.payload(payload).build()?;
1657 self.crypto.seal_packet(&mut packet, &keys)?;
1658 let not_before_ms = self.tx_not_before_ms(options);
1659 self.enqueue_packet(packet, Some(receipt), Some(from), not_before_ms)?;
1660 Ok(receipt)
1661 }
1662
1663 pub async fn send_multicast(
1665 &mut self,
1666 from: LocalIdentityId,
1667 channel_id: &ChannelId,
1668 payload: &[u8],
1669 options: &SendOptions,
1670 ) -> Result<SendReceipt, SendError> {
1671 self.queue_multicast(from, channel_id, payload, options)
1672 }
1673
1674 pub fn queue_mac_ack_for_peer(
1695 &mut self,
1696 from: LocalIdentityId,
1697 peer_id: PeerId,
1698 ack_trailer: [u8; 8],
1699 trace_route: bool,
1700 ) -> Result<(), SendError> {
1701 let repeatable = match self
1704 .peer_registry
1705 .get(peer_id)
1706 .and_then(|peer| peer.route.as_ref())
1707 {
1708 Some(CachedRoute::Source(route)) => !route.is_empty(),
1711 Some(CachedRoute::Flood { .. }) => true,
1712 _ => false,
1713 };
1714 let mut buf = [0u8; FRAME];
1715 let mut builder = PacketBuilder::new(&mut buf).mac_ack(ack_trailer);
1716 if trace_route && repeatable {
1719 builder = builder.trace_route();
1720 }
1721 if let Some(peer) = self.peer_registry.get(peer_id) {
1722 match peer.route.as_ref() {
1723 Some(CachedRoute::Source(route)) if !route.is_empty() => {
1724 builder = builder.source_route(route.as_slice());
1725 }
1726 Some(CachedRoute::Flood { hops, regions }) => {
1727 builder = builder.flood_hops((*hops).clamp(1, MAX_FLOOD_HOPS));
1728 for region in regions {
1729 builder = builder.region_code(*region);
1730 }
1731 }
1732 _ => {}
1736 }
1737 }
1738 let frame = builder.build()?;
1739 if frame.len() > self.radio.max_frame_size() {
1740 return Err(SendError::Build(BuildError::BufferTooSmall));
1741 }
1742 let tracked_receipt = if repeatable {
1743 self.prepare_repeat_confirmed_ack(from, peer_id, frame)
1744 } else {
1745 None
1746 };
1747 if self
1748 .tx_queue
1749 .enqueue(
1750 TxPriority::ImmediateAck,
1751 frame,
1752 tracked_receipt,
1753 tracked_receipt.map(|_| from),
1754 )
1755 .is_err()
1756 {
1757 if let Some(receipt) = tracked_receipt {
1758 let _ = self
1759 .identity_mut(from)
1760 .and_then(|slot| slot.remove_pending_ack(&receipt));
1761 }
1762 return Err(SendError::QueueFull);
1763 }
1764 Ok(())
1765 }
1766
1767 fn prepare_repeat_confirmed_ack(
1775 &mut self,
1776 from: LocalIdentityId,
1777 peer_id: PeerId,
1778 frame: &[u8],
1779 ) -> Option<SendReceipt> {
1780 let peer = self.peer_registry.get(peer_id)?.public_key;
1781 let confirm_key = Self::confirmation_key(frame);
1782 let resend = ResendRecord::try_new(frame, None).ok()?;
1783 let slot = self.identity_mut(from)?;
1784 if confirm_key.is_some()
1785 && slot.pending_acks().any(|(_, pending)| {
1786 Self::confirmation_key(pending.resend.frame.as_slice()) == confirm_key
1787 })
1788 {
1789 return None;
1790 }
1791 let receipt = slot.next_receipt();
1792 slot.try_insert_pending_ack(receipt, PendingAck::repeat_only(peer, resend))
1793 .ok()?;
1794 Some(receipt)
1795 }
1796
1797 pub fn queue_mac_ack(&mut self, ack_trailer: [u8; 8]) -> Result<(), SendError> {
1799 let mut buf = [0u8; FRAME];
1800 let frame = PacketBuilder::new(&mut buf).mac_ack(ack_trailer).build()?;
1801 if frame.len() > self.radio.max_frame_size() {
1802 return Err(SendError::Build(BuildError::BufferTooSmall));
1803 }
1804 self.tx_queue
1805 .enqueue(TxPriority::ImmediateAck, frame, None, None)
1806 .map_err(|_| SendError::QueueFull)?;
1807 Ok(())
1808 }
1809
1810 pub fn queue_unicast(
1812 &mut self,
1813 from: LocalIdentityId,
1814 peer: &PublicKey,
1815 payload: &[u8],
1816 options: &SendOptions,
1817 ) -> Result<Option<SendReceipt>, SendError> {
1818 self.enforce_send_policy(None, options, false)?;
1819 let (peer_id, _) = self
1820 .peer_registry
1821 .lookup_by_key(peer)
1822 .ok_or(SendError::PeerMissing)?;
1823 let pairwise_keys = self
1824 .identity(from)
1825 .ok_or(SendError::IdentityMissing)?
1826 .peer_crypto()
1827 .get(&peer_id)
1828 .ok_or(SendError::PairwiseKeysMissing)?
1829 .pairwise_keys
1830 .clone();
1831 let effective_source_route = self.effective_source_route(peer_id, options);
1832 let effective_flood_hops =
1833 self.effective_flood_hops(peer_id, options, effective_source_route.as_ref());
1834
1835 let (source_key, frame_counter) = self.identity_and_advance(from)?;
1836 let salt = self.take_salt(options);
1837 let mut buf = [0u8; FRAME];
1838 let builder = PacketBuilder::new(&mut buf).unicast(peer.hint());
1839 let builder = if options.full_source {
1840 builder.source_full(&source_key)
1841 } else {
1842 builder.source_hint(source_key.hint())
1843 };
1844 let mut builder = builder.frame_counter(frame_counter);
1845 if options.ack_requested {
1846 builder = builder.ack_requested();
1847 }
1848 if options.encrypted {
1849 builder = builder.encrypted();
1850 }
1851 builder = builder.mic_size(options.mic_size);
1852 if let Some(salt) = salt {
1853 builder = builder.salt(salt);
1854 }
1855 let repeatable =
1859 Self::frame_is_repeatable(effective_flood_hops, effective_source_route.as_deref());
1860 if let Some(hops) = effective_flood_hops {
1861 builder = builder.flood_hops(hops);
1862 }
1863 if repeatable
1864 && (options.trace_route
1865 || self.needs_route_discovery(
1866 peer_id,
1867 effective_source_route.as_ref(),
1868 effective_flood_hops,
1869 options.ack_requested,
1870 ))
1871 {
1872 builder = builder.trace_route();
1873 }
1874 if let Some(route) = effective_source_route.as_ref() {
1875 builder = builder.source_route(route.as_slice());
1876 }
1877 if let Some(callsign) = self.operating_policy.operator_callsign {
1878 builder = builder.option(OptionNumber::OperatorCallsign, callsign.as_trimmed_slice());
1879 }
1880 if options.trace_signal && repeatable {
1881 builder = builder.trace_signal();
1882 }
1883 if let Some(region_code) = options.region_code {
1884 builder = builder.region_code(region_code);
1885 }
1886 let mut packet = builder.payload(payload).build()?;
1887
1888 let receipt = if options.ack_requested {
1889 Some(self.prepare_pending_ack(from, *peer, &packet, &pairwise_keys, options)?)
1890 } else {
1891 None
1892 };
1893
1894 self.crypto.seal_packet(&mut packet, &pairwise_keys)?;
1895 if let Some(receipt) = receipt {
1896 self.refresh_pending_resend(
1897 from,
1898 receipt,
1899 packet.as_bytes(),
1900 effective_source_route
1901 .as_ref()
1902 .map(|route| route.as_slice()),
1903 options.flood_hops,
1904 )?;
1905 }
1906 let tracked_receipt = match receipt {
1910 Some(receipt) => Some(receipt),
1911 None if Self::frame_solicits_repeat(packet.as_bytes()) => {
1912 Some(self.prepare_repeat_confirmed_send(
1913 from,
1914 *peer,
1915 packet.as_bytes(),
1916 effective_source_route.as_ref(),
1917 options.flood_hops,
1918 )?)
1919 }
1920 None => None,
1921 };
1922 let not_before_ms = self.tx_not_before_ms(options);
1923 if let Err(err) = self.enqueue_packet(packet, tracked_receipt, Some(from), not_before_ms) {
1924 if let Some(tracked_receipt) = tracked_receipt {
1925 let _ = self
1926 .identity_mut(from)
1927 .and_then(|slot| slot.remove_pending_ack(&tracked_receipt));
1928 }
1929 return Err(err);
1930 }
1931 Ok(receipt)
1932 }
1933
1934 pub async fn send_unicast(
1936 &mut self,
1937 from: LocalIdentityId,
1938 peer: &PublicKey,
1939 payload: &[u8],
1940 options: &SendOptions,
1941 ) -> Result<Option<SendReceipt>, SendError> {
1942 let (peer_id, _) = self
1943 .peer_registry
1944 .lookup_by_key(peer)
1945 .ok_or(SendError::PeerMissing)?;
1946 let _ = self.ensure_peer_crypto(from, peer_id).await?;
1947 self.queue_unicast(from, peer, payload, options)
1948 }
1949
1950 pub fn queue_blind_unicast(
1952 &mut self,
1953 from: LocalIdentityId,
1954 peer: &PublicKey,
1955 channel_id: &ChannelId,
1956 payload: &[u8],
1957 options: &SendOptions,
1958 ) -> Result<Option<SendReceipt>, SendError> {
1959 let channel_keys = self
1960 .channels
1961 .lookup_by_id(channel_id)
1962 .next()
1963 .ok_or(SendError::ChannelMissing)?
1964 .derived
1965 .clone();
1966 self.queue_blind_unicast_with_keys(from, peer, &channel_keys, payload, options)
1967 }
1968
1969 fn queue_blind_unicast_with_keys(
1977 &mut self,
1978 from: LocalIdentityId,
1979 peer: &PublicKey,
1980 channel_keys: &DerivedChannelKeys,
1981 payload: &[u8],
1982 options: &SendOptions,
1983 ) -> Result<Option<SendReceipt>, SendError> {
1984 let channel_id = channel_keys.channel_id;
1985 self.enforce_send_policy(Some(channel_id), options, true)?;
1986 let (peer_id, _) = self
1987 .peer_registry
1988 .lookup_by_key(peer)
1989 .ok_or(SendError::PeerMissing)?;
1990 let pairwise_keys = self
1991 .identity(from)
1992 .ok_or(SendError::IdentityMissing)?
1993 .peer_crypto()
1994 .get(&peer_id)
1995 .ok_or(SendError::PairwiseKeysMissing)?
1996 .pairwise_keys
1997 .clone();
1998 let blind_keys = self.crypto.derive_blind_keys(&pairwise_keys, channel_keys);
1999 let effective_source_route = self.effective_source_route(peer_id, options);
2000 let effective_flood_hops =
2001 self.effective_flood_hops(peer_id, options, effective_source_route.as_ref());
2002
2003 let (source_key, frame_counter) = self.identity_and_advance(from)?;
2004 let salt = self.take_salt(options);
2005 let mut buf = [0u8; FRAME];
2006 let builder = PacketBuilder::new(&mut buf).blind_unicast(channel_id, peer.hint());
2007 let builder = if options.full_source {
2008 builder.source_full(&source_key)
2009 } else {
2010 builder.source_hint(source_key.hint())
2011 };
2012 let mut builder = builder.frame_counter(frame_counter);
2013 if options.ack_requested {
2014 builder = builder.ack_requested();
2015 }
2016 if !options.encrypted {
2017 builder = builder.unencrypted();
2018 }
2019 builder = builder.mic_size(options.mic_size);
2020 if let Some(salt) = salt {
2021 builder = builder.salt(salt);
2022 }
2023 let repeatable =
2026 Self::frame_is_repeatable(effective_flood_hops, effective_source_route.as_deref());
2027 if let Some(hops) = effective_flood_hops {
2028 builder = builder.flood_hops(hops);
2029 }
2030 if repeatable
2031 && (options.trace_route
2032 || self.needs_route_discovery(
2033 peer_id,
2034 effective_source_route.as_ref(),
2035 effective_flood_hops,
2036 options.ack_requested,
2037 ))
2038 {
2039 builder = builder.trace_route();
2040 }
2041 if let Some(route) = effective_source_route.as_ref() {
2042 builder = builder.source_route(route.as_slice());
2043 }
2044 if let Some(callsign) = self.operating_policy.operator_callsign {
2045 builder = builder.option(OptionNumber::OperatorCallsign, callsign.as_trimmed_slice());
2046 }
2047 if options.trace_signal && repeatable {
2048 builder = builder.trace_signal();
2049 }
2050 if let Some(region_code) = options.region_code {
2051 builder = builder.region_code(region_code);
2052 }
2053 let mut packet = builder.payload(payload).build()?;
2054
2055 let receipt = if options.ack_requested {
2056 Some(self.prepare_pending_ack(from, *peer, &packet, &blind_keys, options)?)
2057 } else {
2058 None
2059 };
2060
2061 self.crypto
2062 .seal_blind_packet(&mut packet, &blind_keys, channel_keys)
2063 .map_err(SendError::Crypto)?;
2064 if let Some(receipt) = receipt {
2065 self.refresh_pending_resend(
2066 from,
2067 receipt,
2068 packet.as_bytes(),
2069 effective_source_route
2070 .as_ref()
2071 .map(|route| route.as_slice()),
2072 options.flood_hops,
2073 )?;
2074 }
2075 let tracked_receipt = match receipt {
2079 Some(receipt) => Some(receipt),
2080 None if Self::frame_solicits_repeat(packet.as_bytes()) => {
2081 Some(self.prepare_repeat_confirmed_send(
2082 from,
2083 *peer,
2084 packet.as_bytes(),
2085 effective_source_route.as_ref(),
2086 options.flood_hops,
2087 )?)
2088 }
2089 None => None,
2090 };
2091 let not_before_ms = self.tx_not_before_ms(options);
2092 if let Err(err) = self.enqueue_packet(packet, tracked_receipt, Some(from), not_before_ms) {
2093 if let Some(tracked_receipt) = tracked_receipt {
2094 let _ = self
2095 .identity_mut(from)
2096 .and_then(|slot| slot.remove_pending_ack(&tracked_receipt));
2097 }
2098 return Err(err);
2099 }
2100 Ok(receipt)
2101 }
2102
2103 pub async fn send_blind_unicast(
2105 &mut self,
2106 from: LocalIdentityId,
2107 peer: &PublicKey,
2108 channel_id: &ChannelId,
2109 payload: &[u8],
2110 options: &SendOptions,
2111 ) -> Result<Option<SendReceipt>, SendError> {
2112 let (peer_id, _) = self
2113 .peer_registry
2114 .lookup_by_key(peer)
2115 .ok_or(SendError::PeerMissing)?;
2116 let _ = self.ensure_peer_crypto(from, peer_id).await?;
2117 self.queue_blind_unicast(from, peer, channel_id, payload, options)
2118 }
2119
2120 async fn send_response(
2125 &mut self,
2126 from: LocalIdentityId,
2127 peer: &PublicKey,
2128 carriage: ReplyCarriage<'_>,
2129 payload: &[u8],
2130 options: &SendOptions,
2131 ) -> Result<Option<SendReceipt>, SendError> {
2132 match carriage {
2133 ReplyCarriage::Unicast => self.send_unicast(from, peer, payload, options).await,
2134 ReplyCarriage::Blind(channel_keys) => {
2135 let (peer_id, _) = self
2136 .peer_registry
2137 .lookup_by_key(peer)
2138 .ok_or(SendError::PeerMissing)?;
2139 let _ = self.ensure_peer_crypto(from, peer_id).await?;
2140 self.queue_blind_unicast_with_keys(from, peer, channel_keys, payload, options)
2141 }
2142 }
2143 }
2144
2145 pub async fn transmit_next(
2153 &mut self,
2154 on_event: &mut impl FnMut(LocalIdentityId, crate::MacEventRef<'_>),
2155 ) -> Result<Option<SendReceipt>, MacError<<P::Radio as Radio>::Error>> {
2156 self.expire_post_tx_listen_if_needed();
2157 let Some(queued) = self.tx_queue.pop_next() else {
2158 return Ok(None);
2159 };
2160 let now_ms = self.clock.now_ms();
2161
2162 if queued.not_before_ms > now_ms {
2163 self.requeue_tx(&queued).map_err(|_| MacError::QueueFull)?;
2164 return Ok(None);
2165 }
2166
2167 if self.post_tx_listen.is_some() && queued.priority != TxPriority::ImmediateAck {
2168 self.requeue_tx(&queued).map_err(|_| MacError::QueueFull)?;
2169 return Ok(None);
2170 }
2171
2172 let receipt = queued.receipt;
2173 let identity_id = queued.identity_id;
2174
2175 if queued.priority == TxPriority::Retry
2184 && let (Some(receipt), Some(identity_id)) = (receipt, identity_id)
2185 && self
2186 .identity(identity_id)
2187 .map(|slot| slot.pending_ack(&receipt).is_none())
2188 .unwrap_or(true)
2189 {
2190 return Ok(None);
2191 }
2192
2193 let tx_options = if queued.priority == TxPriority::ImmediateAck {
2194 TxOptions {
2197 cad: umsh_hal::CadPolicy::Skip,
2198 }
2199 } else {
2200 TxOptions {
2201 cad: umsh_hal::CadPolicy::Gate,
2202 }
2203 };
2204 match self
2205 .radio
2206 .transmit(queued.frame.as_slice(), tx_options)
2207 .await
2208 {
2209 Ok(()) => {}
2210 Err(TxError::CadTimeout) => {
2211 let next_attempt = queued.cad_attempts.saturating_add(1);
2212 if next_attempt >= MAX_CAD_ATTEMPTS {
2213 MacCounters::bump(&mut self.counters.tx_abandoned);
2218 if let Some(identity_id) = identity_id
2228 && queued.priority != TxPriority::Retry
2229 {
2230 if let Some(receipt) = receipt
2234 && let Some(slot) = self.identity_mut(identity_id)
2235 {
2236 let _ = slot.pending_acks.remove(&receipt);
2237 }
2238 on_event(
2239 identity_id,
2240 crate::MacEventRef::TxAbandoned {
2241 identity_id,
2242 receipt,
2243 },
2244 );
2245 }
2246 return Ok(None);
2247 }
2248 let t_frame_ms = self.radio.t_frame_ms();
2253 let backoff_lo_ms = t_frame_ms / 40;
2254 let backoff_hi_ms = (t_frame_ms / 4).max(backoff_lo_ms);
2255 let backoff_ms = u64::from(
2256 backoff_lo_ms.saturating_add(
2257 self.rng
2258 .random_range(..(backoff_hi_ms - backoff_lo_ms).saturating_add(1)),
2259 ),
2260 );
2261 self.tx_queue
2262 .enqueue_with_state(
2263 queued.priority,
2264 queued.frame.as_slice(),
2265 queued.receipt,
2266 queued.identity_id,
2267 now_ms.saturating_add(backoff_ms),
2268 next_attempt,
2269 queued.forward_deferrals,
2270 )
2271 .map_err(|_| MacError::QueueFull)?;
2272 return Ok(None);
2273 }
2274 Err(error) => return Err(MacError::Transmit(error)),
2275 }
2276 MacCounters::bump(&mut self.counters.tx_frames);
2279 if let Some(identity_id) = identity_id {
2280 on_event(
2281 identity_id,
2282 crate::MacEventRef::Transmitted {
2283 identity_id,
2284 receipt,
2285 wire_bytes: queued.frame.as_slice(),
2286 },
2287 );
2288 }
2289 if let Some(receipt) = receipt {
2290 self.note_transmitted_tracked(receipt, queued.frame.as_slice());
2291 }
2292 Ok(receipt)
2293 }
2294
2295 pub async fn drain_tx_queue(
2300 &mut self,
2301 on_event: &mut impl FnMut(LocalIdentityId, crate::MacEventRef<'_>),
2302 ) -> Result<(), MacError<<P::Radio as Radio>::Error>> {
2303 while !self.tx_queue.is_empty() {
2304 let queue_len = self.tx_queue.len();
2305 let _ = self.transmit_next(on_event).await?;
2306 if self.tx_queue.len() >= queue_len {
2307 break;
2308 }
2309 }
2310 Ok(())
2311 }
2312
2313 pub async fn poll_cycle(
2326 &mut self,
2327 mut on_event: impl FnMut(LocalIdentityId, crate::MacEventRef<'_>),
2328 ) -> Result<(), MacError<<P::Radio as Radio>::Error>> {
2329 self.drain_tx_queue(&mut on_event).await?;
2330 if self.post_tx_listen.is_some() {
2331 self.service_post_tx_listen(&mut on_event).await?;
2332 } else {
2333 let _ = self.receive_one(&mut on_event).await?;
2334 }
2335 self.drain_tx_queue(&mut on_event).await?;
2336 self.service_pending_ack_timeouts(&mut on_event)
2337 .map_err(|_| MacError::QueueFull)?;
2338 Ok(())
2339 }
2340
2341 pub fn earliest_deadline_ms(&self) -> Option<u64> {
2348 let mut earliest: Option<u64> = None;
2349
2350 if let Some(listen) = &self.post_tx_listen {
2351 earliest =
2352 Some(earliest.map_or(listen.deadline_ms, |e: u64| e.min(listen.deadline_ms)));
2353 }
2354
2355 for slot in self.identities.iter().filter_map(|s| s.as_ref()) {
2356 for (_, pending) in slot.pending_acks.iter() {
2357 if !matches!(pending.state, crate::AckState::Queued { .. }) {
2358 earliest = Some(earliest.map_or(pending.ack_deadline_ms, |e: u64| {
2359 e.min(pending.ack_deadline_ms)
2360 }));
2361 }
2362 if let crate::AckState::AwaitingForward {
2367 confirm_deadline_ms,
2368 } = pending.state
2369 && pending.retries < MAX_FORWARD_RETRIES
2370 {
2371 earliest = Some(
2372 earliest.map_or(confirm_deadline_ms, |e: u64| e.min(confirm_deadline_ms)),
2373 );
2374 }
2375 }
2376 }
2377
2378 if let Some(nb) = self.tx_queue.earliest_not_before_ms() {
2379 earliest = Some(earliest.map_or(nb, |e: u64| e.min(nb)));
2380 }
2381
2382 earliest
2383 }
2384
2385 pub async fn next_event(
2404 &mut self,
2405 mut on_event: impl FnMut(LocalIdentityId, crate::MacEventRef<'_>),
2406 ) -> Result<(), MacError<<P::Radio as Radio>::Error>> {
2407 loop {
2408 self.drain_tx_queue(&mut on_event).await?;
2410
2411 let mut buf = [0u8; FRAME];
2413 let reason = poll_fn(|cx| self.poll_wait_for_wake(cx, &mut buf))
2414 .await
2415 .map_err(MacError::Radio)?;
2416
2417 self.process_wake_reason(reason, &mut buf, &mut on_event)
2419 .await?;
2420
2421 let _ = self.service_counter_persistence().await;
2425 let _ = self.service_rx_counter_persistence().await;
2426
2427 if !self.tx_queue.is_empty() {
2430 continue;
2431 }
2432
2433 return Ok(());
2434 }
2435 }
2436
2437 pub fn poll_wait_for_wake(
2445 &mut self,
2446 cx: &mut core::task::Context<'_>,
2447 buf: &mut [u8; FRAME],
2448 ) -> Poll<Result<WakeReason, <P::Radio as Radio>::Error>> {
2449 match self.radio.poll_receive(cx, buf) {
2450 Poll::Ready(Ok(rx)) => return Poll::Ready(Ok(WakeReason::Received(rx))),
2451 Poll::Ready(Err(e)) => return Poll::Ready(Err(e)),
2452 Poll::Pending => {}
2453 }
2454
2455 let now_ms = self.clock.now_ms();
2456 if let Some(deadline) = self.earliest_deadline_ms() {
2457 if now_ms >= deadline {
2458 return Poll::Ready(Ok(WakeReason::TimerExpired));
2459 }
2460 if self.clock.poll_delay_until(cx, deadline).is_ready() {
2466 return Poll::Ready(Ok(WakeReason::TimerExpired));
2467 }
2468 }
2469
2470 if self.tx_queue.has_ready(now_ms)
2478 && (self.post_tx_listen.is_none() || self.tx_queue.has_ready_immediate_ack(now_ms))
2479 {
2480 return Poll::Ready(Ok(WakeReason::TimerExpired));
2481 }
2482
2483 Poll::Pending
2484 }
2485
2486 pub async fn process_wake_reason(
2505 &mut self,
2506 reason: WakeReason,
2507 buf: &mut [u8; FRAME],
2508 mut on_event: impl FnMut(LocalIdentityId, crate::MacEventRef<'_>),
2509 ) -> Result<(), MacError<<P::Radio as Radio>::Error>> {
2510 match reason {
2511 WakeReason::Received(rx) => {
2512 let frame_len = rx.len.min(buf.len());
2513 let _ = self
2514 .process_received_frame(buf, frame_len, &rx, &mut on_event)
2515 .await;
2516 }
2517 WakeReason::TimerExpired => {}
2518 }
2519
2520 self.drain_tx_queue(&mut on_event).await?;
2521
2522 self.service_pending_ack_timeouts(&mut on_event)
2523 .map_err(|_| MacError::QueueFull)?;
2524
2525 Ok(())
2526 }
2527
2528 pub async fn run(
2536 &mut self,
2537 mut on_event: impl FnMut(LocalIdentityId, crate::MacEventRef<'_>),
2538 ) -> Result<(), MacError<<P::Radio as Radio>::Error>> {
2539 loop {
2540 self.next_event(&mut on_event).await?;
2541 }
2542 }
2543
2544 pub async fn run_quiet(&mut self) -> Result<(), MacError<<P::Radio as Radio>::Error>> {
2550 self.run(|_, _| {}).await
2551 }
2552
2553 pub async fn process_received_frame(
2563 &mut self,
2564 buf: &mut [u8; FRAME],
2565 frame_len: usize,
2566 rx: &RxInfo,
2567 on_event: impl FnMut(LocalIdentityId, crate::MacEventRef<'_>),
2568 ) -> bool {
2569 MacCounters::bump(&mut self.counters.rx_frames);
2570 self.note_transmitter_observation(&buf[..frame_len], rx);
2571 let handled = self
2572 .process_received_frame_inner(buf, frame_len, rx, on_event)
2573 .await;
2574 if handled {
2575 MacCounters::bump(&mut self.counters.rx_accepted);
2576 }
2577 handled
2578 }
2579
2580 async fn process_received_frame_inner(
2581 &mut self,
2582 buf: &mut [u8; FRAME],
2583 frame_len: usize,
2584 rx: &RxInfo,
2585 mut on_event: impl FnMut(LocalIdentityId, crate::MacEventRef<'_>),
2586 ) -> bool {
2587 let received_at_ms = self.clock.now_ms();
2588 let mut current_len = frame_len;
2589 let mut current_rx = RxInfo {
2590 len: frame_len,
2591 rssi: rx.rssi,
2592 snr: rx.snr,
2593 lqi: rx.lqi,
2594 origin: rx.origin,
2595 };
2596 let mut current_received_at_ms = received_at_ms;
2597 let mut handled_any = false;
2598
2599 loop {
2600 let Ok(header) = PacketHeader::parse(&buf[..current_len]) else {
2601 return handled_any;
2602 };
2603 self.cancel_forwards_for_ack_mic_option(&buf[..current_len], &header);
2608 let forwarding_confirmed = if let Some((identity_id, receipt)) =
2609 self.observe_forwarding_confirmation(&buf[..current_len])
2610 {
2611 let hint = match header.source {
2612 SourceAddrRef::Hint(h) => Some(RouterHint([h.0[0], h.0[1]])),
2613 SourceAddrRef::FullKeyAt { offset } => {
2614 let mut key_bytes = [0u8; 32];
2615 key_bytes.copy_from_slice(&buf[offset..offset + 32]);
2616 let h = PublicKey(key_bytes).hint();
2617 Some(RouterHint([h.0[0], h.0[1]]))
2618 }
2619 _ => None,
2620 };
2621 on_event(
2622 identity_id,
2623 crate::MacEventRef::Forwarded {
2624 identity_id,
2625 receipt,
2626 hint,
2627 },
2628 );
2629 true
2630 } else {
2631 false
2632 };
2633
2634 let (handled, replay_target) = match header.packet_type() {
2635 PacketType::Broadcast => (
2636 self.process_broadcast(
2637 buf,
2638 current_len,
2639 &header,
2640 ¤t_rx,
2641 current_received_at_ms,
2642 &mut on_event,
2643 ),
2644 None,
2645 ),
2646 PacketType::MacAck => (
2647 self.process_mac_ack(
2648 buf,
2649 current_len,
2650 &header,
2651 ¤t_rx,
2652 forwarding_confirmed,
2653 &mut on_event,
2654 ),
2655 None,
2656 ),
2657 PacketType::Unicast | PacketType::UnicastAckReq => {
2658 self.process_unicast(
2659 buf,
2660 current_len,
2661 &header,
2662 ¤t_rx,
2663 current_received_at_ms,
2664 forwarding_confirmed,
2665 &mut on_event,
2666 )
2667 .await
2668 }
2669 PacketType::Multicast => (
2670 self.process_multicast(
2671 buf,
2672 current_len,
2673 &header,
2674 ¤t_rx,
2675 current_received_at_ms,
2676 forwarding_confirmed,
2677 &mut on_event,
2678 ),
2679 None,
2680 ),
2681 PacketType::BlindUnicast | PacketType::BlindUnicastAckReq => {
2682 self.process_blind_unicast(
2683 buf,
2684 current_len,
2685 &header,
2686 ¤t_rx,
2687 current_received_at_ms,
2688 forwarding_confirmed,
2689 &mut on_event,
2690 )
2691 .await
2692 }
2693 PacketType::Reserved5 => (false, None),
2694 };
2695 handled_any |= handled;
2696
2697 let Some((local_id, peer_id)) = replay_target else {
2698 return handled_any;
2699 };
2700 let Some(deferred) = self.take_deferred_counter_resync_frame(local_id, peer_id) else {
2701 return handled_any;
2702 };
2703 current_len = deferred.frame.len();
2704 buf[..current_len].copy_from_slice(deferred.frame.as_slice());
2705 current_rx = RxInfo {
2706 len: current_len,
2707 rssi: deferred.rssi,
2708 snr: deferred.snr,
2709 lqi: deferred.lqi,
2710 origin: deferred.origin,
2711 };
2712 current_received_at_ms = deferred.received_at_ms;
2713 }
2714 }
2715
2716 fn process_broadcast(
2717 &mut self,
2718 buf: &[u8; FRAME],
2719 frame_len: usize,
2720 header: &PacketHeader,
2721 rx: &RxInfo,
2722 received_at_ms: u64,
2723 mut on_event: impl FnMut(LocalIdentityId, crate::MacEventRef<'_>),
2724 ) -> bool {
2725 let Some((from_hint, from_key)) = Self::resolve_broadcast_source(&buf[..frame_len], header)
2726 else {
2727 return false;
2728 };
2729 if !Self::payload_is_allowed(header.packet_type(), &buf[header.body_range.clone()]) {
2730 return false;
2731 }
2732 let mut delivered = false;
2733 for (index, slot) in self.identities.iter().enumerate() {
2734 if slot.is_none() {
2735 continue;
2736 }
2737 delivered = true;
2738 on_event(
2739 LocalIdentityId(index as u8),
2740 crate::MacEventRef::Received(crate::ReceivedPacketRef::new(
2741 &buf[..frame_len],
2742 &buf[header.body_range.clone()],
2743 header.clone(),
2744 ParsedOptions::extract(&buf[..frame_len], header.options_range.clone())
2745 .unwrap_or_default(),
2746 from_key,
2747 Some(from_hint),
2748 false,
2749 None,
2750 rx_metadata(rx, received_at_ms),
2751 )),
2752 );
2753 }
2754 let forwarded = self.maybe_forward_received(&buf[..frame_len], header, rx, false);
2758 delivered || forwarded
2759 }
2760
2761 fn process_mac_ack(
2762 &mut self,
2763 buf: &[u8; FRAME],
2764 frame_len: usize,
2765 header: &PacketHeader,
2766 rx: &RxInfo,
2767 forwarding_confirmed: bool,
2768 mut on_event: impl FnMut(LocalIdentityId, crate::MacEventRef<'_>),
2769 ) -> bool {
2770 if header.mic_range.len() != 8 {
2776 return forwarding_confirmed
2777 || self.maybe_forward_received(&buf[..frame_len], header, rx, false);
2778 }
2779 let mut ack_trailer = [0u8; 8];
2780 ack_trailer.copy_from_slice(&buf[header.mic_range.clone()]);
2781 self.cancel_forwards_for_ack_mic(&ack_trailer[..4]);
2786 if let Some(target_peer) = self.peer_for_ack_trailer(&ack_trailer)
2787 && let Some((identity_id, receipt)) = self.complete_ack(&target_peer, &ack_trailer)
2788 {
2789 if let Some((peer_id, _)) = self.peer_registry.lookup_by_key(&target_peer) {
2796 self.learn_route_for_peer(peer_id, &buf[..frame_len], header);
2797 }
2798 on_event(
2799 identity_id,
2800 crate::MacEventRef::AckReceived {
2801 peer: target_peer,
2802 receipt,
2803 },
2804 );
2805 return true;
2806 }
2807 forwarding_confirmed || self.maybe_forward_received(&buf[..frame_len], header, rx, false)
2808 }
2809
2810 async fn process_unicast(
2811 &mut self,
2812 buf: &mut [u8; FRAME],
2813 frame_len: usize,
2814 header: &PacketHeader,
2815 rx: &RxInfo,
2816 received_at_ms: u64,
2817 forwarding_confirmed: bool,
2818 mut on_event: impl FnMut(LocalIdentityId, crate::MacEventRef<'_>),
2819 ) -> (bool, Option<(LocalIdentityId, PeerId)>) {
2820 let mut original = [0u8; FRAME];
2821 original[..frame_len].copy_from_slice(&buf[..frame_len]);
2822 let mut replay_target = None;
2823 let handled = if let Some(local_id) = self.find_local_identity_for_dst(header.dst) {
2824 let mut handled = false;
2825 for (peer_id, peer_key) in
2826 self.resolve_source_peer_candidates(&buf[..frame_len], header)
2827 {
2828 let Ok(keys) = self.ensure_peer_crypto(local_id, peer_id).await else {
2829 continue;
2830 };
2831 let Ok(body_range) = self
2832 .crypto
2833 .open_packet(&mut buf[..frame_len], header, &keys)
2834 else {
2835 continue;
2836 };
2837 let payload = &buf[body_range.clone()];
2838 if !Self::payload_is_allowed(header.packet_type(), payload) {
2839 continue;
2840 }
2841 if header.ack_requested()
2842 && self.should_emit_destination_ack(&buf[..frame_len], header)
2843 && self.note_acknowledgeable_unicast_duplicate(
2844 local_id,
2845 peer_id,
2846 header,
2847 &buf[..frame_len],
2848 )
2849 {
2850 self.learn_route_for_peer(peer_id, &buf[..frame_len], header);
2860 let ack_trailer = self.compute_received_ack_trailer(
2861 &buf[..frame_len],
2862 header,
2863 body_range.clone(),
2864 &keys,
2865 );
2866 let trace_route = Self::frame_requests_trace_route(&buf[..frame_len], header);
2867 self.queue_mac_ack_for_peer(local_id, peer_id, ack_trailer, trace_route)
2868 .ok();
2869 handled = true;
2870 break;
2871 }
2872 match self.unicast_replay_verdict(local_id, peer_id, header, &buf[..frame_len]) {
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 let counter = Self::replay_metadata(header, &buf[..frame_len])
2898 .map(|(c, _)| c)
2899 .unwrap_or(0);
2900 let gap = self
2901 .identity(local_id)
2902 .and_then(|slot| slot.peer_crypto().get(&peer_id))
2903 .map(|state| {
2904 state.replay_window.last_accepted.wrapping_sub(counter)
2905 })
2906 .unwrap_or(0);
2907 if gap >= COUNTER_RESYNC_GAP_THRESHOLD {
2908 self.store_deferred_counter_resync_frame(
2909 local_id,
2910 peer_id,
2911 &original[..frame_len],
2912 rx,
2913 received_at_ms,
2914 );
2915 self.maybe_request_counter_resync(
2916 local_id,
2917 peer_id,
2918 peer_key,
2919 ReplyCarriage::Unicast,
2920 )
2921 .await;
2922 }
2923 continue;
2924 }
2925 }
2926 Some(ReplayVerdict::Replay) | None => continue,
2927 }
2928 self.learn_route_for_peer(peer_id, &buf[..frame_len], header);
2929
2930 if header.ack_requested()
2931 && self.should_emit_destination_ack(&buf[..frame_len], header)
2932 {
2933 let ack_trailer = self.compute_received_ack_trailer(
2934 &buf[..frame_len],
2935 header,
2936 body_range.clone(),
2937 &keys,
2938 );
2939 let trace_route = Self::frame_requests_trace_route(&buf[..frame_len], header);
2940 self.queue_mac_ack_for_peer(local_id, peer_id, ack_trailer, trace_route)
2941 .ok();
2942 }
2943
2944 if let Some(data) = Self::echo_request_data(payload) {
2945 let response =
2946 Self::build_echo_command_payload(MAC_COMMAND_ECHO_RESPONSE_ID, data);
2947 let options = Self::echo_response_options(&original[..frame_len], header);
2948 let _ = self
2949 .send_response(
2950 local_id,
2951 &peer_key,
2952 ReplyCarriage::Unicast,
2953 response.as_slice(),
2954 &options,
2955 )
2956 .await;
2957 }
2958
2959 on_event(
2960 local_id,
2961 crate::MacEventRef::Received(crate::ReceivedPacketRef::new(
2962 &original[..frame_len],
2963 &buf[body_range],
2964 header.clone(),
2965 ParsedOptions::extract(
2966 &original[..frame_len],
2967 header.options_range.clone(),
2968 )
2969 .unwrap_or_default(),
2970 Some(peer_key),
2971 Some(peer_key.hint()),
2972 true,
2973 None,
2974 rx_metadata(rx, received_at_ms),
2975 )),
2976 );
2977 handled = true;
2978 break;
2979 }
2980 handled
2981 } else {
2982 false
2983 };
2984 let forwarded = self.maybe_forward_received(&original[..frame_len], header, rx, handled);
2985 (
2986 handled || forwarding_confirmed || forwarded,
2987 handled.then_some(()).and(replay_target),
2988 )
2989 }
2990
2991 fn process_multicast(
2992 &mut self,
2993 buf: &mut [u8; FRAME],
2994 frame_len: usize,
2995 header: &PacketHeader,
2996 rx: &RxInfo,
2997 received_at_ms: u64,
2998 forwarding_confirmed: bool,
2999 mut on_event: impl FnMut(LocalIdentityId, crate::MacEventRef<'_>),
3000 ) -> bool {
3001 let mut original = [0u8; FRAME];
3002 original[..frame_len].copy_from_slice(&buf[..frame_len]);
3003 let delivered = if let Some(channel_id) = header.channel {
3004 let channel_info = {
3005 self.channels
3006 .lookup_by_id(&channel_id)
3007 .next()
3008 .map(|channel| (channel.channel_key.clone(), channel.derived.clone()))
3009 };
3010 if let Some((channel_key, derived)) = channel_info {
3011 let keys = PairwiseKeys {
3012 k_enc: derived.k_enc,
3013 k_mic: derived.k_mic,
3014 };
3015 if let Ok(body_range) =
3016 self.crypto
3017 .open_packet(&mut buf[..frame_len], header, &keys)
3018 {
3019 if !Self::payload_is_allowed(header.packet_type(), &buf[body_range.clone()]) {
3020 false
3021 } else if let Some(source) =
3022 self.resolve_multicast_source(&buf[..frame_len], header)
3023 {
3024 let accepted = if let Some(peer_id) = source.peer_id {
3025 let accepted = self.accept_multicast_replay(
3026 channel_id,
3027 peer_id,
3028 header,
3029 &buf[..frame_len],
3030 );
3031 if accepted {
3032 self.learn_route_for_peer(peer_id, &buf[..frame_len], header);
3033 }
3034 accepted
3035 } else {
3036 self.accept_unknown_multicast_replay(header, &buf[..frame_len])
3037 };
3038 if accepted {
3039 let mut delivered = false;
3040 for (index, slot) in self.identities.iter().enumerate() {
3041 if slot.is_none() {
3042 continue;
3043 }
3044 delivered = true;
3045 on_event(
3046 LocalIdentityId(index as u8),
3047 crate::MacEventRef::Received(crate::ReceivedPacketRef::new(
3048 &original[..frame_len],
3049 &buf[body_range.clone()],
3050 header.clone(),
3051 ParsedOptions::extract(
3052 &original[..frame_len],
3053 header.options_range.clone(),
3054 )
3055 .unwrap_or_default(),
3056 source.public_key,
3057 source
3058 .hint
3059 .or_else(|| source.public_key.map(|key| key.hint())),
3060 true,
3061 Some(crate::ChannelInfoRef {
3062 id: channel_id,
3063 key: &channel_key,
3064 }),
3065 rx_metadata(rx, received_at_ms),
3066 )),
3067 );
3068 }
3069 delivered
3070 } else {
3071 false
3072 }
3073 } else {
3074 false
3075 }
3076 } else {
3077 false
3078 }
3079 } else {
3080 false
3081 }
3082 } else {
3083 false
3084 };
3085 let forwarded = self.maybe_forward_received(&original[..frame_len], header, rx, false);
3086 delivered || forwarding_confirmed || forwarded
3087 }
3088
3089 async fn process_blind_unicast(
3090 &mut self,
3091 buf: &mut [u8; FRAME],
3092 frame_len: usize,
3093 header: &PacketHeader,
3094 rx: &RxInfo,
3095 received_at_ms: u64,
3096 forwarding_confirmed: bool,
3097 mut on_event: impl FnMut(LocalIdentityId, crate::MacEventRef<'_>),
3098 ) -> (bool, Option<(LocalIdentityId, PeerId)>) {
3099 let mut original = [0u8; FRAME];
3100 original[..frame_len].copy_from_slice(&buf[..frame_len]);
3101 let mut replay_target = None;
3102 let handled = if let Some(channel_id) = header.channel {
3103 let channel_candidates: Vec<(ChannelKey, DerivedChannelKeys), CHANNELS> = self
3104 .channels
3105 .lookup_by_id(&channel_id)
3106 .map(|channel| (channel.channel_key.clone(), channel.derived.clone()))
3107 .collect();
3108 if channel_candidates.is_empty() {
3109 false
3110 } else {
3111 let mut handled = false;
3112 for (resolved_channel_key, channel_keys) in channel_candidates {
3113 buf[..frame_len].copy_from_slice(&original[..frame_len]);
3114 let Ok((dst, source_addr)) = self.crypto.decrypt_blind_addr(
3115 &mut buf[..frame_len],
3116 header,
3117 &channel_keys,
3118 ) else {
3119 continue;
3120 };
3121 let Some(local_id) = self.find_local_identity_for_dst(Some(dst)) else {
3122 continue;
3123 };
3124 for (peer_id, peer_key) in
3125 self.resolve_blind_source_peer_candidates(&buf[..frame_len], source_addr)
3126 {
3127 let Ok(pairwise_keys) = self.ensure_peer_crypto(local_id, peer_id).await
3128 else {
3129 continue;
3130 };
3131 let blind_keys =
3132 self.crypto.derive_blind_keys(&pairwise_keys, &channel_keys);
3133 let body_range = match self.crypto.open_packet(
3134 &mut buf[..frame_len],
3135 header,
3136 &blind_keys,
3137 ) {
3138 Ok(range) => range,
3139 Err(_) => continue,
3140 };
3141 let payload = &buf[body_range.clone()];
3142 if !Self::payload_is_allowed(header.packet_type(), payload) {
3143 continue;
3144 }
3145 if header.ack_requested()
3146 && self.should_emit_destination_ack(&buf[..frame_len], header)
3147 && self.note_acknowledgeable_unicast_duplicate(
3148 local_id,
3149 peer_id,
3150 header,
3151 &buf[..frame_len],
3152 )
3153 {
3154 self.learn_route_for_peer(peer_id, &buf[..frame_len], header);
3158 let ack_trailer = self.compute_received_ack_trailer(
3159 &buf[..frame_len],
3160 header,
3161 body_range.clone(),
3162 &blind_keys,
3163 );
3164 let trace_route =
3165 Self::frame_requests_trace_route(&buf[..frame_len], header);
3166 self.queue_mac_ack_for_peer(
3167 local_id,
3168 peer_id,
3169 ack_trailer,
3170 trace_route,
3171 )
3172 .ok();
3173 handled = true;
3174 break;
3175 }
3176 match self.unicast_replay_verdict(
3177 local_id,
3178 peer_id,
3179 header,
3180 &buf[..frame_len],
3181 ) {
3182 Some(ReplayVerdict::Accept) => {
3183 let _ = self.accept_unicast_replay(
3184 local_id,
3185 peer_id,
3186 header,
3187 &buf[..frame_len],
3188 );
3189 }
3190 Some(ReplayVerdict::OutOfWindow | ReplayVerdict::Stale) => {
3191 if self.try_accept_counter_resync_response(
3192 local_id,
3193 peer_id,
3194 header,
3195 &buf[..frame_len],
3196 payload,
3197 ) {
3198 replay_target = Some((local_id, peer_id));
3199 } else {
3200 self.store_deferred_counter_resync_frame(
3201 local_id,
3202 peer_id,
3203 &original[..frame_len],
3204 rx,
3205 received_at_ms,
3206 );
3207 self.maybe_request_counter_resync(
3208 local_id,
3209 peer_id,
3210 peer_key,
3211 ReplyCarriage::Blind(&channel_keys),
3212 )
3213 .await;
3214 continue;
3215 }
3216 }
3217 Some(ReplayVerdict::Replay) | None => continue,
3218 }
3219 self.learn_route_for_peer(peer_id, &buf[..frame_len], header);
3220
3221 if header.ack_requested()
3222 && self.should_emit_destination_ack(&buf[..frame_len], header)
3223 {
3224 let ack_trailer = self.compute_received_ack_trailer(
3225 &buf[..frame_len],
3226 header,
3227 body_range.clone(),
3228 &blind_keys,
3229 );
3230 let trace_route =
3231 Self::frame_requests_trace_route(&buf[..frame_len], header);
3232 self.queue_mac_ack_for_peer(
3233 local_id,
3234 peer_id,
3235 ack_trailer,
3236 trace_route,
3237 )
3238 .ok();
3239 }
3240
3241 if let Some(data) = Self::echo_request_data(payload) {
3242 let response = Self::build_echo_command_payload(
3243 MAC_COMMAND_ECHO_RESPONSE_ID,
3244 data,
3245 );
3246 let options =
3247 Self::echo_response_options(&original[..frame_len], header);
3248 let _ = self
3249 .send_response(
3250 local_id,
3251 &peer_key,
3252 ReplyCarriage::Blind(&channel_keys),
3253 response.as_slice(),
3254 &options,
3255 )
3256 .await;
3257 }
3258
3259 on_event(
3260 local_id,
3261 crate::MacEventRef::Received(crate::ReceivedPacketRef::new(
3262 &original[..frame_len],
3263 &buf[body_range],
3264 header.clone(),
3265 ParsedOptions::extract(
3266 &original[..frame_len],
3267 header.options_range.clone(),
3268 )
3269 .unwrap_or_default(),
3270 Some(peer_key),
3271 Some(peer_key.hint()),
3272 true,
3273 Some(crate::ChannelInfoRef {
3274 id: channel_id,
3275 key: &resolved_channel_key,
3276 }),
3277 rx_metadata(rx, received_at_ms),
3278 )),
3279 );
3280 handled = true;
3281 break;
3282 }
3283 if handled {
3284 break;
3285 }
3286 }
3287 handled
3288 }
3289 } else {
3290 false
3291 };
3292 let forwarded = self.maybe_forward_received(&original[..frame_len], header, rx, handled);
3293 (
3294 handled || forwarding_confirmed || forwarded,
3295 handled.then_some(()).and(replay_target),
3296 )
3297 }
3298
3299 pub async fn receive_one(
3305 &mut self,
3306 mut on_event: impl FnMut(LocalIdentityId, crate::MacEventRef<'_>),
3307 ) -> Result<bool, MacError<<P::Radio as Radio>::Error>> {
3308 let mut buf = [0u8; FRAME];
3309 let Some(rx) = poll_fn(|cx| match self.radio.poll_receive(cx, &mut buf) {
3310 Poll::Ready(Ok(rx)) => Poll::Ready(Ok(Some(rx))),
3311 Poll::Ready(Err(error)) => Poll::Ready(Err(error)),
3312 Poll::Pending => Poll::Ready(Ok(None)),
3313 })
3314 .await
3315 .map_err(MacError::Radio)?
3316 else {
3317 return Ok(false);
3318 };
3319
3320 let frame_len = rx.len.min(buf.len());
3321 Ok(self
3322 .process_received_frame(&mut buf, frame_len, &rx, &mut on_event)
3323 .await)
3324 }
3325
3326 pub fn complete_ack(
3328 &mut self,
3329 peer: &PublicKey,
3330 ack_trailer: &[u8; 8],
3331 ) -> Option<(LocalIdentityId, SendReceipt)> {
3332 for (index, slot) in self.identities.iter_mut().enumerate() {
3333 let Some(slot) = slot.as_mut() else {
3334 continue;
3335 };
3336
3337 let receipt = slot.pending_acks.iter().find_map(|(receipt, pending)| {
3338 (pending.expects_ack()
3339 && pending.peer == *peer
3340 && pending.ack_trailer == *ack_trailer)
3341 .then_some(*receipt)
3342 });
3343
3344 if let Some(receipt) = receipt {
3345 slot.pending_acks.remove(&receipt);
3346 let identity_id = LocalIdentityId(index as u8);
3347 self.tx_queue.remove_all_matching(|entry| {
3351 entry.receipt == Some(receipt) && entry.identity_id == Some(identity_id)
3352 });
3353 if self
3354 .post_tx_listen
3355 .as_ref()
3356 .map(|listen| listen.identity_id == identity_id && listen.receipt == receipt)
3357 .unwrap_or(false)
3358 {
3359 self.post_tx_listen = None;
3360 }
3361 return Some((identity_id, receipt));
3362 }
3363 }
3364
3365 None
3366 }
3367
3368 pub fn service_pending_ack_timeouts(
3370 &mut self,
3371 mut on_event: impl FnMut(LocalIdentityId, crate::MacEventRef<'_>),
3372 ) -> Result<(), CapacityError> {
3373 self.expire_post_tx_listen_if_needed();
3374
3375 #[derive(Clone)]
3376 enum Action<const FRAME: usize> {
3377 Retry {
3378 receipt: SendReceipt,
3379 resend: ResendRecord<FRAME>,
3380 not_before_ms: u64,
3381 },
3382 RouteRetry {
3383 receipt: SendReceipt,
3384 peer: PublicKey,
3385 resend: ResendRecord<FRAME>,
3386 not_before_ms: u64,
3387 },
3388 Timeout {
3389 receipt: SendReceipt,
3390 peer: PublicKey,
3391 },
3392 Abandon { receipt: SendReceipt },
3395 }
3396
3397 let now_ms = self.clock.now_ms();
3398 let t_frame_ms = self.radio.t_frame_ms();
3399
3400 for index in 0..self.identities.len() {
3401 let identity_id = LocalIdentityId(index as u8);
3402 let actions = {
3403 let Some(slot) = self.identities[index].as_mut() else {
3404 continue;
3405 };
3406
3407 let mut actions: Vec<Action<FRAME>, ACKS> = Vec::new();
3408 for (receipt, pending) in slot.pending_acks.iter_mut() {
3409 if !matches!(pending.state, crate::AckState::Queued { .. })
3410 && now_ms >= pending.ack_deadline_ms
3411 {
3412 if !pending.expects_ack() {
3413 actions
3414 .push(Action::Abandon { receipt: *receipt })
3415 .map_err(|_| CapacityError)?;
3416 } else if Self::can_attempt_route_retry(pending) {
3417 let backoff_cap_ms = t_frame_ms;
3418 let backoff_ms = if backoff_cap_ms == 0 {
3419 0
3420 } else {
3421 u64::from(self.rng.random_range(..backoff_cap_ms.saturating_add(1)))
3422 };
3423 actions
3424 .push(Action::RouteRetry {
3425 receipt: *receipt,
3426 peer: pending.peer,
3427 resend: pending.resend.clone(),
3428 not_before_ms: now_ms.saturating_add(backoff_ms),
3429 })
3430 .map_err(|_| CapacityError)?;
3431 } else {
3432 actions
3433 .push(Action::Timeout {
3434 receipt: *receipt,
3435 peer: pending.peer,
3436 })
3437 .map_err(|_| CapacityError)?;
3438 }
3439 continue;
3440 }
3441
3442 if let crate::AckState::AwaitingForward {
3443 confirm_deadline_ms,
3444 } = pending.state
3445 {
3446 if now_ms >= confirm_deadline_ms && pending.retries < MAX_FORWARD_RETRIES {
3447 pending.retries = pending.retries.saturating_add(1);
3448 let backoff_cap_ms = t_frame_ms;
3449 let backoff_ms = if backoff_cap_ms == 0 {
3450 0
3451 } else {
3452 u64::from(self.rng.random_range(..backoff_cap_ms.saturating_add(1)))
3453 };
3454 let not_before_ms = now_ms.saturating_add(backoff_ms);
3455 pending.state = crate::AckState::RetryQueued;
3456 actions
3457 .push(Action::Retry {
3458 receipt: *receipt,
3459 resend: pending.resend.clone(),
3460 not_before_ms,
3461 })
3462 .map_err(|_| CapacityError)?;
3463 }
3464 }
3465 }
3466 actions
3467 };
3468
3469 for action in actions {
3470 match action {
3471 Action::Retry {
3472 receipt,
3473 resend,
3474 not_before_ms,
3475 } => {
3476 self.tx_queue.enqueue_with_state(
3477 TxPriority::Retry,
3478 resend.frame.as_slice(),
3479 Some(receipt),
3480 Some(identity_id),
3481 not_before_ms,
3482 0,
3483 0,
3484 )?;
3485 }
3486 Action::RouteRetry {
3487 receipt,
3488 peer,
3489 resend,
3490 not_before_ms,
3491 } => {
3492 if let Some(rewritten) = self.synthesize_route_retry_resend(&peer, &resend)
3493 {
3494 let rewritten_key = Self::confirmation_key(rewritten.frame.as_slice());
3495 let retry_deadline_ms =
3505 not_before_ms.saturating_add(self.forwarded_ack_timeout_ms(
3506 Self::allowed_hop_distance(rewritten.frame.as_slice()),
3507 ));
3508 if let Some(pending) = self
3509 .identity_mut(identity_id)
3510 .and_then(|slot| slot.pending_ack_mut(&receipt))
3511 {
3512 pending.resend = rewritten.clone();
3513 pending.retries = 0;
3514 pending.sent_ms = 0;
3515 pending.ack_deadline_ms = retry_deadline_ms;
3516 pending.state = crate::AckState::RetryQueued;
3517 pending.confirm_key = rewritten_key;
3521 }
3522 self.tx_queue.enqueue_with_state(
3523 TxPriority::Retry,
3524 rewritten.frame.as_slice(),
3525 Some(receipt),
3526 Some(identity_id),
3527 not_before_ms,
3528 0,
3529 0,
3530 )?;
3531 } else {
3532 if let Some(slot) = self.identity_mut(identity_id) {
3533 slot.pending_acks.remove(&receipt);
3534 }
3535 on_event(
3536 identity_id,
3537 crate::MacEventRef::AckTimeout { peer, receipt },
3538 );
3539 }
3540 }
3541 Action::Timeout { receipt, peer } => {
3542 if let Some(slot) = self.identity_mut(identity_id) {
3543 slot.pending_acks.remove(&receipt);
3544 }
3545 self.tx_queue.remove_all_matching(|entry| {
3546 entry.receipt == Some(receipt) && entry.identity_id == Some(identity_id)
3547 });
3548 on_event(
3549 identity_id,
3550 crate::MacEventRef::AckTimeout { peer, receipt },
3551 );
3552 }
3553 Action::Abandon { receipt } => {
3554 if let Some(slot) = self.identity_mut(identity_id) {
3555 slot.pending_acks.remove(&receipt);
3556 }
3557 self.tx_queue.remove_all_matching(|entry| {
3558 entry.receipt == Some(receipt) && entry.identity_id == Some(identity_id)
3559 });
3560 }
3561 }
3562 }
3563 }
3564
3565 Ok(())
3566 }
3567
3568 pub fn cancel_pending_ack(
3574 &mut self,
3575 identity_id: LocalIdentityId,
3576 receipt: SendReceipt,
3577 ) -> bool {
3578 let removed = self
3579 .identity_mut(identity_id)
3580 .and_then(|slot| slot.remove_pending_ack(&receipt))
3581 .is_some();
3582
3583 self.tx_queue.remove_first_matching(|entry| {
3585 entry.receipt == Some(receipt) && entry.identity_id == Some(identity_id)
3586 });
3587
3588 if let Some(listen) = &self.post_tx_listen {
3590 if listen.identity_id == identity_id && listen.receipt == receipt {
3591 self.post_tx_listen = None;
3592 }
3593 }
3594
3595 removed
3596 }
3597
3598 fn identity_and_advance(
3599 &mut self,
3600 from: LocalIdentityId,
3601 ) -> Result<(PublicKey, u32), SendError> {
3602 let slot = self.identity_mut(from).ok_or(SendError::IdentityMissing)?;
3603 if slot.counter_window_exhausted() {
3604 return Err(SendError::CounterPersistenceLag);
3605 }
3606 let source_key = *slot.identity().public_key();
3607 let frame_counter = slot.advance_frame_counter();
3608 slot.schedule_counter_persist_if_needed();
3609 Ok((source_key, frame_counter))
3610 }
3611
3612 fn take_salt(&mut self, options: &SendOptions) -> Option<u16> {
3613 options.salt.then(|| self.rng.next_u32() as u16)
3614 }
3615
3616 fn enforce_send_policy(
3617 &self,
3618 channel_id: Option<ChannelId>,
3619 options: &SendOptions,
3620 blind_unicast: bool,
3621 ) -> Result<(), SendError> {
3622 let _authority = self.classify_send_authority(options, blind_unicast)?;
3623
3624 let Some(channel_id) = channel_id else {
3625 return Ok(());
3626 };
3627 let Some(policy) = self
3628 .operating_policy
3629 .channel_policies
3630 .iter()
3631 .find(|policy| policy.channel_id == channel_id)
3632 else {
3633 return Ok(());
3634 };
3635
3636 if policy.require_unencrypted && options.encrypted {
3637 return Err(SendError::PolicyViolation);
3638 }
3639 if policy.require_full_source && !options.full_source {
3640 return Err(SendError::PolicyViolation);
3641 }
3642 if let Some(max_flood_hops) = policy.max_flood_hops {
3643 if options
3644 .flood_hops
3645 .map(|hops| hops > max_flood_hops)
3646 .unwrap_or(false)
3647 {
3648 return Err(SendError::PolicyViolation);
3649 }
3650 }
3651
3652 Ok(())
3653 }
3654
3655 fn classify_send_authority(
3656 &self,
3657 options: &SendOptions,
3658 _blind_unicast: bool,
3659 ) -> Result<TransmitAuthority, SendError> {
3660 match self.operating_policy.amateur_radio_mode {
3661 AmateurRadioMode::Unlicensed => Ok(TransmitAuthority::Unlicensed),
3662 AmateurRadioMode::LicensedOnly => {
3663 if options.encrypted || self.operating_policy.operator_callsign.is_none() {
3664 return Err(SendError::PolicyViolation);
3665 }
3666 Ok(TransmitAuthority::Amateur)
3667 }
3668 AmateurRadioMode::Hybrid => {
3669 if options.encrypted {
3670 Ok(TransmitAuthority::Unlicensed)
3673 } else if self.operating_policy.operator_callsign.is_some() {
3674 Ok(TransmitAuthority::Amateur)
3675 } else {
3676 Ok(TransmitAuthority::Unlicensed)
3677 }
3678 }
3679 }
3680 }
3681
3682 fn enqueue_packet(
3683 &mut self,
3684 packet: UnsealedPacket<'_>,
3685 receipt: Option<SendReceipt>,
3686 identity_id: Option<LocalIdentityId>,
3687 not_before_ms: u64,
3688 ) -> Result<(), SendError> {
3689 if packet.total_len() > self.radio.max_frame_size() {
3690 return Err(SendError::Build(BuildError::BufferTooSmall));
3691 }
3692 self.tx_queue
3693 .enqueue_with_state(
3694 TxPriority::Application,
3695 packet.as_bytes(),
3696 receipt,
3697 identity_id,
3698 not_before_ms,
3699 0,
3700 0,
3701 )
3702 .map_err(|_| SendError::QueueFull)?;
3703 Ok(())
3704 }
3705
3706 fn tx_not_before_ms(&self, options: &SendOptions) -> u64 {
3709 options
3710 .tx_delay_ms
3711 .map(|delay| self.clock.now_ms() + u64::from(delay))
3712 .unwrap_or(0)
3713 }
3714
3715 fn refresh_pending_resend(
3716 &mut self,
3717 from: LocalIdentityId,
3718 receipt: SendReceipt,
3719 frame: &[u8],
3720 source_route: Option<&[RouterHint]>,
3721 requested_flood_hops: Option<u8>,
3722 ) -> Result<(), SendError> {
3723 let resend = ResendRecord::try_new(frame, source_route)
3724 .map_err(|_| SendError::QueueFull)?
3725 .with_requested_flood_hops(requested_flood_hops);
3726 let pending = self
3727 .identity_mut(from)
3728 .ok_or(SendError::IdentityMissing)?
3729 .pending_ack_mut(&receipt)
3730 .ok_or(SendError::IdentityMissing)?;
3731 pending.resend = resend;
3732 Ok(())
3733 }
3734
3735 fn frame_solicits_repeat(frame: &[u8]) -> bool {
3746 let Ok(header) = PacketHeader::parse(frame) else {
3747 return false;
3748 };
3749 if header
3750 .flood_hops
3751 .map(|hops| hops.remaining() > 0)
3752 .unwrap_or(false)
3753 {
3754 return true;
3755 }
3756 ParsedOptions::extract(frame, header.options_range.clone())
3757 .ok()
3758 .and_then(|options| options.source_route)
3759 .map(|range| !range.is_empty())
3760 .unwrap_or(false)
3761 }
3762
3763 fn prepare_repeat_confirmed_send(
3775 &mut self,
3776 from: LocalIdentityId,
3777 peer: PublicKey,
3778 frame: &[u8],
3779 source_route: Option<&Vec<RouterHint, MAX_SOURCE_ROUTE_HOPS>>,
3780 requested_flood_hops: Option<u8>,
3781 ) -> Result<SendReceipt, SendError> {
3782 let resend = ResendRecord::try_new(frame, source_route.map(|route| route.as_slice()))
3783 .map_err(|_| SendError::QueueFull)?
3784 .with_requested_flood_hops(requested_flood_hops);
3785 let slot = self.identity_mut(from).ok_or(SendError::IdentityMissing)?;
3786 let receipt = slot.next_receipt();
3787 slot.try_insert_pending_ack(receipt, PendingAck::repeat_only(peer, resend))
3788 .map_err(|_| SendError::PendingAckFull)?;
3789 Ok(receipt)
3790 }
3791
3792 fn prepare_pending_ack(
3793 &mut self,
3794 from: LocalIdentityId,
3795 peer: PublicKey,
3796 packet: &UnsealedPacket<'_>,
3797 keys: &PairwiseKeys,
3798 options: &SendOptions,
3799 ) -> Result<SendReceipt, SendError> {
3800 let header = PacketHeader::parse(packet.as_bytes())?;
3801 let full_mac = self.crypto.s2v_tag(
3802 &keys.k_mic,
3803 |cmac| feed_aad(&header, packet.as_bytes(), |chunk| cmac.update(chunk)),
3804 packet.body(),
3805 );
3806 let ack_trailer = self.crypto.compute_ack_trailer(&full_mac, &keys.k_enc);
3807 let is_forwarded = Self::frame_solicits_repeat(packet.as_bytes());
3811 let resend = ResendRecord::try_new(
3812 packet.as_bytes(),
3813 options.source_route.as_ref().map(|route| route.as_slice()),
3814 )
3815 .map_err(|_| SendError::QueueFull)?
3816 .with_requested_flood_hops(options.flood_hops);
3817
3818 let slot = self.identity_mut(from).ok_or(SendError::IdentityMissing)?;
3819 let receipt = slot.next_receipt();
3820 let pending = if is_forwarded {
3821 PendingAck::forwarded(ack_trailer, peer, resend)
3822 } else {
3823 PendingAck::direct(ack_trailer, peer, resend)
3824 };
3825 slot.try_insert_pending_ack(receipt, pending)
3826 .map_err(|_| SendError::PendingAckFull)?;
3827 Ok(receipt)
3828 }
3829
3830 async fn derive_pairwise_keys_for_peer(
3831 &self,
3832 local_id: LocalIdentityId,
3833 peer_key: &PublicKey,
3834 ) -> Result<PairwiseKeys, SendError> {
3835 let shared_secret = {
3836 let slot = self.identity(local_id).ok_or(SendError::IdentityMissing)?;
3837 match slot.identity() {
3838 LocalIdentity::LongTerm(identity) => identity
3839 .agree(peer_key)
3840 .await
3841 .map_err(|_| SendError::IdentityAgreementFailed)?,
3842 #[cfg(feature = "software-crypto")]
3843 LocalIdentity::Ephemeral(identity) => identity
3844 .agree(peer_key)
3845 .await
3846 .map_err(|_| SendError::IdentityAgreementFailed)?,
3847 }
3848 };
3849
3850 Ok(self.crypto.derive_pairwise_keys(&shared_secret))
3851 }
3852
3853 fn effective_source_route(
3854 &self,
3855 peer_id: PeerId,
3856 options: &SendOptions,
3857 ) -> Option<Vec<RouterHint, MAX_SOURCE_ROUTE_HOPS>> {
3858 if let Some(route) = options.source_route.as_ref() {
3861 return (!route.is_empty()).then(|| route.clone());
3862 }
3863
3864 let Some(peer) = self.peer_registry.get(peer_id) else {
3865 return None;
3866 };
3867 match peer.route.as_ref() {
3868 Some(CachedRoute::Source(route)) => Some(route.clone()),
3869 _ => None,
3870 }
3871 }
3872
3873 fn needs_route_discovery(
3913 &self,
3914 peer_id: PeerId,
3915 source_route: Option<&Vec<RouterHint, MAX_SOURCE_ROUTE_HOPS>>,
3916 flood_hops: Option<u8>,
3917 ack_requested: bool,
3918 ) -> bool {
3919 if source_route.is_some_and(|route| !route.is_empty()) {
3920 return ack_requested;
3923 }
3924 if flood_hops.unwrap_or(0) == 0 {
3925 return false;
3926 }
3927 !matches!(
3928 self.peer_registry
3929 .get(peer_id)
3930 .and_then(|peer| peer.route.as_ref()),
3931 Some(CachedRoute::Direct)
3932 )
3933 }
3934
3935 fn frame_is_repeatable(flood_hops: Option<u8>, source_route: Option<&[RouterHint]>) -> bool {
3953 flood_hops.is_some_and(|hops| hops > 0)
3954 || source_route.is_some_and(|route| !route.is_empty())
3955 }
3956
3957 fn effective_flood_hops(
3975 &self,
3976 peer_id: PeerId,
3977 options: &SendOptions,
3978 source_route: Option<&Vec<RouterHint, MAX_SOURCE_ROUTE_HOPS>>,
3979 ) -> Option<u8> {
3980 let requested = options.flood_hops?;
3981 let cached = self
3982 .peer_registry
3983 .get(peer_id)
3984 .and_then(|peer| peer.route.as_ref());
3985 let source_route = source_route.filter(|route| !route.is_empty());
3988
3989 let ceiling = match (source_route, cached) {
3990 (Some(_), _) | (None, Some(CachedRoute::Direct)) => ESTABLISHED_ROUTE_EXTRA_HOPS,
3995 (None, Some(CachedRoute::Flood { hops, .. })) => {
3997 hops.saturating_add(ESTABLISHED_ROUTE_EXTRA_HOPS)
3998 }
3999 (None, Some(CachedRoute::Source(route))) => u8::try_from(route.len())
4002 .unwrap_or(u8::MAX)
4003 .saturating_add(ESTABLISHED_ROUTE_EXTRA_HOPS),
4004 (None, None) => MAX_FLOOD_HOPS,
4006 };
4007
4008 let hops = requested.min(ceiling).min(MAX_FLOOD_HOPS);
4013 (hops > 0).then_some(hops)
4014 }
4015
4016 fn cache_peer_crypto(
4017 &mut self,
4018 local_id: LocalIdentityId,
4019 peer_id: PeerId,
4020 pairwise_keys: PairwiseKeys,
4021 ) -> Result<(), SendError> {
4022 let slot = self
4023 .identity_mut(local_id)
4024 .ok_or(SendError::IdentityMissing)?;
4025 if slot.peer_crypto().get(&peer_id).is_some() {
4026 return Ok(());
4027 }
4028 let initial = self
4029 .peer_registry
4030 .get(peer_id)
4031 .map(|info| info.initial_rx_counter)
4032 .unwrap_or(0);
4033 let now_ms = self.clock.now_ms();
4034 let mut replay_window = ReplayWindow::new();
4035 if initial > 0 {
4036 replay_window.reset(initial, now_ms);
4037 }
4038 let slot = self
4039 .identity_mut(local_id)
4040 .ok_or(SendError::IdentityMissing)?;
4041 slot.peer_crypto_mut()
4042 .insert(
4043 peer_id,
4044 crate::peers::PeerCryptoState {
4045 pairwise_keys,
4046 replay_window,
4047 persisted_rx_counter: initial,
4048 needs_rx_persist: false,
4049 },
4050 )
4051 .map_err(|_| SendError::QueueFull)?;
4052 Ok(())
4053 }
4054
4055 async fn ensure_peer_crypto(
4056 &mut self,
4057 local_id: LocalIdentityId,
4058 peer_id: PeerId,
4059 ) -> Result<PairwiseKeys, SendError> {
4060 if let Some(keys) = self
4061 .identity(local_id)
4062 .and_then(|slot| slot.peer_crypto().get(&peer_id))
4063 .map(|state| state.pairwise_keys.clone())
4064 {
4065 return Ok(keys);
4066 }
4067
4068 let peer_key = self
4069 .peer_registry
4070 .get(peer_id)
4071 .ok_or(SendError::PeerMissing)?
4072 .public_key;
4073 let pairwise_keys = self
4074 .derive_pairwise_keys_for_peer(local_id, &peer_key)
4075 .await?;
4076 self.cache_peer_crypto(local_id, peer_id, pairwise_keys.clone())?;
4077 Ok(pairwise_keys)
4078 }
4079
4080 fn insert_identity(
4081 &mut self,
4082 identity: LocalIdentity<P::Identity>,
4083 pfs_parent: Option<LocalIdentityId>,
4084 ) -> Result<LocalIdentityId, CapacityError> {
4085 let initial_frame_counter = nonzero_initial_frame_counter(self.rng.next_u32());
4086
4087 if let Some((index, slot)) = self
4088 .identities
4089 .iter_mut()
4090 .enumerate()
4091 .find(|(_, slot)| slot.is_none())
4092 {
4093 *slot = Some(IdentitySlot::new(
4094 identity,
4095 initial_frame_counter,
4096 pfs_parent,
4097 ));
4098 return Ok(LocalIdentityId(index as u8));
4099 }
4100
4101 let next_id = self.identities.len();
4102 self.identities
4103 .push(Some(IdentitySlot::new(
4104 identity,
4105 initial_frame_counter,
4106 pfs_parent,
4107 )))
4108 .map_err(|_| CapacityError)?;
4109 Ok(LocalIdentityId(next_id as u8))
4110 }
4111
4112 fn compute_received_ack_trailer(
4113 &self,
4114 buf: &[u8],
4115 header: &PacketHeader,
4116 body_range: core::ops::Range<usize>,
4117 keys: &PairwiseKeys,
4118 ) -> [u8; 8] {
4119 let full_mac = self.crypto.s2v_tag(
4120 &keys.k_mic,
4121 |cmac| feed_aad(header, buf, |chunk| cmac.update(chunk)),
4122 &buf[body_range],
4123 );
4124 self.crypto.compute_ack_trailer(&full_mac, &keys.k_enc)
4125 }
4126
4127 fn requeue_tx(&mut self, queued: &crate::QueuedTx<FRAME>) -> Result<u32, CapacityError> {
4128 self.tx_queue.enqueue_with_state(
4129 queued.priority,
4130 queued.frame.as_slice(),
4131 queued.receipt,
4132 queued.identity_id,
4133 queued.not_before_ms,
4134 queued.cad_attempts,
4135 queued.forward_deferrals,
4136 )
4137 }
4138
4139 fn accept_unicast_replay(
4140 &mut self,
4141 local_id: LocalIdentityId,
4142 peer_id: PeerId,
4143 header: &PacketHeader,
4144 frame: &[u8],
4145 ) -> bool {
4146 let Some((counter, mic)) = Self::replay_metadata(header, frame) else {
4147 return false;
4148 };
4149 let now_ms = self.clock.now_ms();
4150 let Some(state) = self
4151 .identity_mut(local_id)
4152 .and_then(|slot| slot.peer_crypto_mut().get_mut(&peer_id))
4153 else {
4154 return false;
4155 };
4156
4157 if state.replay_window.check(counter, mic, now_ms) != ReplayVerdict::Accept {
4158 return false;
4159 }
4160
4161 state.replay_window.accept(counter, mic, now_ms);
4162 let last = state.replay_window.last_accepted;
4164 if last.wrapping_sub(state.persisted_rx_counter) >= COUNTER_PERSIST_BLOCK_SIZE {
4165 state.needs_rx_persist = true;
4166 }
4167 true
4168 }
4169
4170 fn unicast_replay_verdict(
4171 &mut self,
4172 local_id: LocalIdentityId,
4173 peer_id: PeerId,
4174 header: &PacketHeader,
4175 frame: &[u8],
4176 ) -> Option<ReplayVerdict> {
4177 let Some((counter, mic)) = Self::replay_metadata(header, frame) else {
4178 return None;
4179 };
4180 let now_ms = self.clock.now_ms();
4181 self.identity_mut(local_id)
4182 .and_then(|slot| slot.peer_crypto_mut().get_mut(&peer_id))
4183 .map(|state| state.replay_window.check(counter, mic, now_ms))
4184 }
4185
4186 fn note_acknowledgeable_unicast_duplicate(
4193 &mut self,
4194 local_id: LocalIdentityId,
4195 peer_id: PeerId,
4196 header: &PacketHeader,
4197 frame: &[u8],
4198 ) -> bool {
4199 let Some((counter, mic)) = Self::replay_metadata(header, frame) else {
4200 return false;
4201 };
4202 let now_ms = self.clock.now_ms();
4203 let holdoff_ms = self.forward_confirm_timeout_ms();
4204 self.identity_mut(local_id)
4205 .and_then(|slot| slot.peer_crypto_mut().get_mut(&peer_id))
4206 .is_some_and(|state| {
4207 state
4208 .replay_window
4209 .note_acknowledgeable_duplicate(counter, mic, now_ms, holdoff_ms)
4210 })
4211 }
4212
4213 fn try_accept_counter_resync_response(
4214 &mut self,
4215 local_id: LocalIdentityId,
4216 peer_id: PeerId,
4217 header: &PacketHeader,
4218 frame: &[u8],
4219 payload: &[u8],
4220 ) -> bool {
4221 let Some(nonce) = Self::echo_response_nonce(payload) else {
4222 return false;
4223 };
4224 let Some((counter, mic)) = Self::replay_metadata(header, frame) else {
4225 return false;
4226 };
4227 let now_ms = self.clock.now_ms();
4228 let Some(slot) = self.identity_mut(local_id) else {
4229 return false;
4230 };
4231 let Some(pending) = slot.pending_counter_resync().get(&peer_id).copied() else {
4232 return false;
4233 };
4234 if pending.nonce != nonce {
4235 return false;
4236 }
4237 let Some(state) = slot.peer_crypto_mut().get_mut(&peer_id) else {
4238 return false;
4239 };
4240 state.replay_window.reset(counter, now_ms);
4241 state.replay_window.accept(counter, mic, now_ms);
4242 state.persisted_rx_counter = 0;
4245 state.needs_rx_persist = true;
4246 let _ = slot.pending_counter_resync_mut().remove(&peer_id);
4247 true
4248 }
4249
4250 async fn maybe_request_counter_resync(
4255 &mut self,
4256 local_id: LocalIdentityId,
4257 peer_id: PeerId,
4258 peer_key: PublicKey,
4259 carriage: ReplyCarriage<'_>,
4260 ) {
4261 let now_ms = self.clock.now_ms();
4262 let should_send = {
4263 let Some(slot) = self.identity(local_id) else {
4264 return;
4265 };
4266 match slot.pending_counter_resync().get(&peer_id).copied() {
4267 Some(pending) => {
4268 now_ms.saturating_sub(pending.requested_ms) >= COUNTER_RESYNC_REQUEST_RETRY_MS
4269 }
4270 None => true,
4271 }
4272 };
4273 if !should_send {
4274 return;
4275 }
4276
4277 let nonce = self.rng.next_u32();
4278 let payload =
4279 Self::build_echo_command_payload(MAC_COMMAND_ECHO_REQUEST_ID, &nonce.to_be_bytes());
4280 let options = SendOptions::default();
4281 if self
4282 .send_response(local_id, &peer_key, carriage, payload.as_slice(), &options)
4283 .await
4284 .is_ok()
4285 {
4286 if let Some(slot) = self.identity_mut(local_id) {
4287 let _ = slot.pending_counter_resync_mut().insert(
4288 peer_id,
4289 PendingCounterResync {
4290 nonce,
4291 requested_ms: now_ms,
4292 },
4293 );
4294 }
4295 }
4296 }
4297
4298 fn store_deferred_counter_resync_frame(
4299 &mut self,
4300 local_id: LocalIdentityId,
4301 peer_id: PeerId,
4302 frame: &[u8],
4303 rx: &RxInfo,
4304 received_at_ms: u64,
4305 ) {
4306 let mut stored = Vec::new();
4307 stored
4308 .extend_from_slice(frame)
4309 .expect("received frame length must fit configured frame capacity");
4310 self.deferred_counter_resync_frame = Some(DeferredCounterResyncFrame {
4311 local_id,
4312 peer_id,
4313 frame: stored,
4314 rssi: rx.rssi,
4315 snr: rx.snr,
4316 lqi: rx.lqi,
4317 origin: rx.origin,
4318 received_at_ms,
4319 });
4320 }
4321
4322 fn take_deferred_counter_resync_frame(
4323 &mut self,
4324 local_id: LocalIdentityId,
4325 peer_id: PeerId,
4326 ) -> Option<DeferredCounterResyncFrame<FRAME>> {
4327 match self.deferred_counter_resync_frame.as_ref() {
4328 Some(deferred) if deferred.local_id == local_id && deferred.peer_id == peer_id => {
4329 self.deferred_counter_resync_frame.take()
4330 }
4331 _ => None,
4332 }
4333 }
4334
4335 fn accept_multicast_replay(
4336 &mut self,
4337 channel_id: ChannelId,
4338 peer_id: PeerId,
4339 header: &PacketHeader,
4340 frame: &[u8],
4341 ) -> bool {
4342 let Some((counter, mic)) = Self::replay_metadata(header, frame) else {
4343 return false;
4344 };
4345 let now_ms = self.clock.now_ms();
4346 let Some(channel) = self.channels.get_mut_by_id(&channel_id) else {
4347 return false;
4348 };
4349
4350 if let Some(window) = channel.replay.get_mut(&peer_id) {
4351 if window.check(counter, mic, now_ms) != ReplayVerdict::Accept {
4352 return false;
4353 }
4354 window.accept(counter, mic, now_ms);
4355 return true;
4356 }
4357
4358 let mut window = ReplayWindow::new();
4359 window.accept(counter, mic, now_ms);
4360 channel.replay.insert(peer_id, window).is_ok()
4361 }
4362
4363 fn clear_peer_slot_state(&mut self, peer_id: PeerId) {
4364 for slot in self.identities.iter_mut().filter_map(|slot| slot.as_mut()) {
4365 let _ = slot.peer_crypto_mut().remove(&peer_id);
4366 let _ = slot.pending_counter_resync_mut().remove(&peer_id);
4367 }
4368 for channel in self.channels.iter_mut() {
4369 let _ = channel.replay.remove(&peer_id);
4370 }
4371 }
4372
4373 fn rekey_peer_slot_state(&mut self, old_id: PeerId, new_id: PeerId) {
4378 for slot in self.identities.iter_mut().filter_map(|slot| slot.as_mut()) {
4379 if let Some(state) = slot.peer_crypto_mut().remove(&old_id) {
4380 let _ = slot.peer_crypto_mut().insert(new_id, state);
4381 }
4382 if let Some(pending) = slot.pending_counter_resync_mut().remove(&old_id) {
4383 let _ = slot.pending_counter_resync_mut().insert(new_id, pending);
4384 }
4385 }
4386 for channel in self.channels.iter_mut() {
4387 if let Some(window) = channel.replay.remove(&old_id) {
4388 let _ = channel.replay.insert(new_id, window);
4389 }
4390 }
4391 if let Some(deferred) = self.deferred_counter_resync_frame.as_mut() {
4392 if deferred.peer_id == old_id {
4393 deferred.peer_id = new_id;
4394 }
4395 }
4396 }
4397
4398 pub fn ensure_transient_peer(&mut self, key: &PublicKey) -> Result<PeerId, AddPeerError> {
4410 if let Some((peer_id, _)) = self.peer_registry.lookup_by_key(key) {
4411 return Ok(peer_id);
4412 }
4413 self.try_auto_register_peer(*key)
4414 }
4415
4416 fn try_auto_register_peer(&mut self, key: PublicKey) -> Result<PeerId, AddPeerError> {
4417 #[cfg(feature = "software-crypto")]
4418 {
4419 if !umsh_crypto::is_valid_ed25519_public_key(&key) {
4420 return Err(AddPeerError::InvalidPublicKey);
4421 }
4422 }
4423 let now_ms = self.clock.now_ms();
4424 let outcome = self.peer_registry.try_insert_or_update_auto(key, now_ms)?;
4425 if outcome.evicted_key.is_some() {
4426 self.clear_peer_slot_state(outcome.peer_id);
4427 }
4428 Ok(outcome.peer_id)
4429 }
4430
4431 fn replay_metadata<'a>(header: &PacketHeader, frame: &'a [u8]) -> Option<(u32, &'a [u8])> {
4432 let counter = header.sec_info?.frame_counter;
4433 let mic = frame.get(header.mic_range.clone())?;
4434 Some((counter, mic))
4435 }
4436
4437 fn build_echo_command_payload(command_id: u8, data: &[u8]) -> Vec<u8, FRAME> {
4438 let mut payload = Vec::new();
4439 let _ = payload.push(PayloadType::MacCommand as u8);
4440 let _ = payload.push(command_id);
4441 let _ = payload.extend_from_slice(data);
4442 payload
4443 }
4444
4445 fn echo_request_data(payload: &[u8]) -> Option<&[u8]> {
4446 let (&payload_type, rest) = payload.split_first()?;
4447 let (&command_id, data) = rest.split_first()?;
4448 if PayloadType::from_byte(payload_type)? != PayloadType::MacCommand
4449 || command_id != MAC_COMMAND_ECHO_REQUEST_ID
4450 {
4451 return None;
4452 }
4453 Some(data)
4454 }
4455
4456 fn frame_requests_trace_route(frame: &[u8], header: &PacketHeader) -> bool {
4459 ParsedOptions::extract(frame, header.options_range.clone())
4460 .map(|parsed| parsed.trace_route.is_some())
4461 .unwrap_or(false)
4462 }
4463
4464 fn echo_response_options(frame: &[u8], header: &PacketHeader) -> SendOptions {
4477 let mut options = SendOptions::default();
4478 let Ok(parsed) = ParsedOptions::extract(frame, header.options_range.clone()) else {
4479 return options;
4480 };
4481 if parsed.trace_route.is_some() {
4482 options = options.with_trace_route();
4483 }
4484 if parsed.trace_signal.is_some() {
4485 options = options.with_trace_signal();
4486 }
4487 options
4488 }
4489
4490 fn echo_response_nonce(payload: &[u8]) -> Option<u32> {
4491 let (&payload_type, rest) = payload.split_first()?;
4492 let (&command_id, data) = rest.split_first()?;
4493 if PayloadType::from_byte(payload_type)? != PayloadType::MacCommand
4494 || command_id != MAC_COMMAND_ECHO_RESPONSE_ID
4495 || data.len() != COUNTER_RESYNC_NONCE_LEN
4496 {
4497 return None;
4498 }
4499 Some(u32::from_be_bytes(data.try_into().ok()?))
4500 }
4501
4502 fn full_key_at(frame: &[u8], offset: usize) -> Option<PublicKey> {
4503 let mut key = [0u8; 32];
4504 key.copy_from_slice(frame.get(offset..offset + 32)?);
4505 Some(PublicKey(key))
4506 }
4507
4508 fn find_local_identity_for_dst(
4509 &self,
4510 dst: Option<umsh_core::NodeHint>,
4511 ) -> Option<LocalIdentityId> {
4512 let dst = dst?;
4513 self.identities
4514 .iter()
4515 .enumerate()
4516 .find(|(_, slot)| {
4517 slot.as_ref()
4518 .map(|slot| slot.identity().public_key().hint() == dst)
4519 .unwrap_or(false)
4520 })
4521 .map(|(index, _)| LocalIdentityId(index as u8))
4522 }
4523
4524 fn is_locally_originated(&self, frame: &[u8], header: &PacketHeader) -> bool {
4532 let mut locals = self.identities.iter().filter_map(|slot| slot.as_ref());
4533 match header.source {
4534 SourceAddrRef::Hint(hint) => {
4535 locals.any(|slot| slot.identity().public_key().hint() == hint)
4536 }
4537 SourceAddrRef::FullKeyAt { offset } => match Self::full_key_at(frame, offset) {
4538 Some(key) => locals.any(|slot| *slot.identity().public_key() == key),
4539 None => false,
4540 },
4541 SourceAddrRef::Encrypted { .. } | SourceAddrRef::None => false,
4544 }
4545 }
4546
4547 fn resolve_source_peer_candidates(
4548 &mut self,
4549 frame: &[u8],
4550 header: &PacketHeader,
4551 ) -> Vec<(PeerId, PublicKey), PEERS> {
4552 match header.source {
4553 SourceAddrRef::FullKeyAt { offset } => {
4554 let Some(peer_key) = Self::full_key_at(frame, offset) else {
4555 return Vec::new();
4556 };
4557
4558 if let Some((peer_id, _)) = self.peer_registry.lookup_by_key(&peer_key) {
4559 let mut out = Vec::new();
4560 let _ = out.push((peer_id, peer_key));
4561 return out;
4562 }
4563
4564 if self.auto_register_full_key_peers {
4565 if let Ok(peer_id) = self.try_auto_register_peer(peer_key) {
4566 let mut out = Vec::new();
4567 let _ = out.push((peer_id, peer_key));
4568 return out;
4569 }
4570 }
4571
4572 Vec::new()
4573 }
4574 SourceAddrRef::Hint(hint) => self
4575 .peer_registry
4576 .lookup_by_hint(&hint)
4577 .map(|(peer_id, info)| (peer_id, info.public_key))
4578 .collect(),
4579 SourceAddrRef::Encrypted { .. } | SourceAddrRef::None => Vec::new(),
4580 }
4581 }
4582
4583 fn resolve_multicast_source(
4584 &mut self,
4585 frame: &[u8],
4586 header: &PacketHeader,
4587 ) -> Option<ResolvedMulticastSource> {
4588 match header.source {
4589 SourceAddrRef::FullKeyAt { offset } => {
4590 let mut key = [0u8; 32];
4591 key.copy_from_slice(frame.get(offset..offset + 32)?);
4592 let public_key = PublicKey(key);
4593 let peer_id = self
4594 .peer_registry
4595 .lookup_by_key(&public_key)
4596 .map(|(peer_id, _)| peer_id);
4597 Some(ResolvedMulticastSource {
4598 peer_id,
4599 public_key: Some(public_key),
4600 hint: Some(public_key.hint()),
4601 })
4602 }
4603 SourceAddrRef::Hint(hint) => {
4604 let resolved = self.resolve_unique_hint(hint);
4605 Some(ResolvedMulticastSource {
4606 peer_id: resolved.map(|(peer_id, _)| peer_id),
4607 public_key: resolved.map(|(_, key)| key),
4608 hint: Some(hint),
4609 })
4610 }
4611 SourceAddrRef::Encrypted { offset, len } => match len {
4612 32 => {
4613 let mut key = [0u8; 32];
4614 key.copy_from_slice(frame.get(offset..offset + 32)?);
4615 let public_key = PublicKey(key);
4616 let peer_id = self
4617 .peer_registry
4618 .lookup_by_key(&public_key)
4619 .map(|(peer_id, _)| peer_id);
4620 Some(ResolvedMulticastSource {
4621 peer_id,
4622 public_key: Some(public_key),
4623 hint: Some(public_key.hint()),
4624 })
4625 }
4626 3 => {
4627 let hint = umsh_core::NodeHint([
4628 *frame.get(offset)?,
4629 *frame.get(offset + 1)?,
4630 *frame.get(offset + 2)?,
4631 ]);
4632 let resolved = self.resolve_unique_hint(hint);
4633 Some(ResolvedMulticastSource {
4634 peer_id: resolved.map(|(peer_id, _)| peer_id),
4635 public_key: resolved.map(|(_, key)| key),
4636 hint: Some(hint),
4637 })
4638 }
4639 _ => None,
4640 },
4641 SourceAddrRef::None => None,
4642 }
4643 }
4644
4645 fn accept_unknown_multicast_replay(&mut self, header: &PacketHeader, frame: &[u8]) -> bool {
4646 let Some(cache_key) = Self::forward_dup_key(header, frame) else {
4647 return false;
4648 };
4649 let now_ms = self.clock.now_ms();
4650 if self
4651 .multicast_unknown_dup_cache
4652 .contains(&cache_key, now_ms)
4653 {
4654 return false;
4655 }
4656 self.multicast_unknown_dup_cache.insert(cache_key, now_ms);
4657 true
4658 }
4659
4660 fn resolve_blind_source_peer_candidates(
4661 &mut self,
4662 frame: &[u8],
4663 source: SourceAddrRef,
4664 ) -> Vec<(PeerId, PublicKey), PEERS> {
4665 match source {
4666 SourceAddrRef::FullKeyAt { offset } => {
4667 let Some(peer_key) = Self::full_key_at(frame, offset) else {
4668 return Vec::new();
4669 };
4670
4671 if let Some((peer_id, _)) = self.peer_registry.lookup_by_key(&peer_key) {
4672 let mut out = Vec::new();
4673 let _ = out.push((peer_id, peer_key));
4674 return out;
4675 }
4676
4677 if self.auto_register_full_key_peers {
4678 if let Ok(peer_id) = self.try_auto_register_peer(peer_key) {
4679 let mut out = Vec::new();
4680 let _ = out.push((peer_id, peer_key));
4681 return out;
4682 }
4683 }
4684
4685 Vec::new()
4686 }
4687 SourceAddrRef::Hint(hint) => self
4688 .peer_registry
4689 .lookup_by_hint(&hint)
4690 .map(|(peer_id, info)| (peer_id, info.public_key))
4691 .collect(),
4692 SourceAddrRef::Encrypted { .. } | SourceAddrRef::None => Vec::new(),
4693 }
4694 }
4695
4696 fn resolve_unique_hint(&self, hint: umsh_core::NodeHint) -> Option<(PeerId, PublicKey)> {
4697 let mut matches = self.peer_registry.lookup_by_hint(&hint);
4698 let (peer_id, info) = matches.next()?;
4699 if matches.next().is_some() {
4700 return None;
4701 }
4702 Some((peer_id, info.public_key))
4703 }
4704
4705 fn note_transmitter_observation(&mut self, frame: &[u8], rx: &RxInfo) {
4717 if !rx.origin.is_measured() {
4720 return;
4721 }
4722 let Ok(header) = PacketHeader::parse(frame) else {
4723 return;
4724 };
4725 let hint = ParsedOptions::extract(frame, header.options_range.clone())
4726 .ok()
4727 .and_then(|options| options.trace_route)
4728 .and_then(|range| frame.get(range))
4729 .and_then(|trace| trace.get(..2))
4730 .map(|hint| RouterHint([hint[0], hint[1]]))
4731 .or_else(|| match header.source {
4732 SourceAddrRef::Hint(hint) => Some(RouterHint([hint.0[0], hint.0[1]])),
4733 SourceAddrRef::FullKeyAt { offset } => frame
4734 .get(offset..offset + 32)
4735 .and_then(|bytes| <[u8; 32]>::try_from(bytes).ok())
4736 .map(|bytes| {
4737 let node = PublicKey(bytes).hint();
4738 RouterHint([node.0[0], node.0[1]])
4739 }),
4740 SourceAddrRef::None | SourceAddrRef::Encrypted { .. } => None,
4743 });
4744 let Some(hint) = hint else {
4745 return;
4746 };
4747 let now_ms = self.clock.now_ms();
4748 self.transmitter_observations
4749 .observe(hint, rx.rssi, rx.snr, now_ms);
4750 }
4751
4752 fn learn_route_for_peer(&mut self, peer_id: PeerId, frame: &[u8], header: &PacketHeader) {
4753 let now_ms = self.clock.now_ms();
4754 self.peer_registry.touch(peer_id, now_ms);
4755
4756 let Ok(options) = ParsedOptions::extract(frame, header.options_range.clone()) else {
4757 return;
4758 };
4759
4760 if let Some(trace_range) = options.trace_route {
4761 if let Some(route) = self.source_route_from_trace(frame.get(trace_range).unwrap_or(&[]))
4762 {
4763 let learned = if route.is_empty() {
4771 crate::CachedRoute::Direct
4772 } else {
4773 crate::CachedRoute::Source(route)
4774 };
4775 self.peer_registry.update_route(peer_id, learned);
4776 return;
4777 }
4778 }
4779
4780 if options.source_route.is_some() {
4788 return;
4789 }
4790
4791 let Some(flood_hops) = header.flood_hops else {
4792 self.peer_registry
4799 .update_route(peer_id, crate::CachedRoute::Direct);
4800 return;
4801 };
4802
4803 let regions = Self::region_codes_from_options(frame, header.options_range.clone());
4804 self.peer_registry.update_route(
4805 peer_id,
4806 crate::CachedRoute::Flood {
4807 hops: flood_hops.accumulated(),
4808 regions,
4809 },
4810 );
4811 }
4812
4813 fn region_codes_from_options(
4814 frame: &[u8],
4815 options_range: core::ops::Range<usize>,
4816 ) -> Vec<[u8; 2], 8> {
4817 let mut regions = Vec::new();
4818 if options_range.is_empty() {
4819 return regions;
4820 }
4821 for entry in umsh_core::iter_options(frame, options_range) {
4822 let Ok((number, value)) = entry else {
4823 continue;
4824 };
4825 if OptionNumber::from(number) != OptionNumber::RegionCode || value.len() != 2 {
4826 continue;
4827 }
4828 if regions.push([value[0], value[1]]).is_err() {
4829 break;
4830 }
4831 }
4832 regions
4833 }
4834
4835 fn resolve_broadcast_source(
4836 frame: &[u8],
4837 header: &PacketHeader,
4838 ) -> Option<(umsh_core::NodeHint, Option<PublicKey>)> {
4839 match header.source {
4840 SourceAddrRef::Hint(hint) => Some((hint, None)),
4841 SourceAddrRef::FullKeyAt { offset } => {
4842 let mut key = [0u8; 32];
4843 key.copy_from_slice(frame.get(offset..offset + 32)?);
4844 let public_key = PublicKey(key);
4845 Some((public_key.hint(), Some(public_key)))
4846 }
4847 SourceAddrRef::Encrypted { .. } | SourceAddrRef::None => None,
4848 }
4849 }
4850
4851 fn payload_is_allowed(packet_type: PacketType, payload: &[u8]) -> bool {
4852 if payload.is_empty() {
4853 return true;
4854 }
4855
4856 PayloadType::from_byte(payload[0])
4857 .unwrap_or(PayloadType::Empty)
4858 .allowed_for(packet_type)
4859 }
4860
4861 fn source_route_from_trace(
4862 &self,
4863 trace_bytes: &[u8],
4864 ) -> Option<heapless::Vec<RouterHint, { crate::MAX_SOURCE_ROUTE_HOPS }>> {
4865 if trace_bytes.len() % 2 != 0 {
4866 return None;
4867 }
4868
4869 let mut route = heapless::Vec::new();
4870 for chunk in trace_bytes.chunks_exact(2) {
4871 route.push(RouterHint([chunk[0], chunk[1]])).ok()?;
4872 }
4873 Some(route)
4874 }
4875
4876 fn should_emit_destination_ack(&self, frame: &[u8], header: &PacketHeader) -> bool {
4877 let Ok(options) = ParsedOptions::extract(frame, header.options_range.clone()) else {
4878 return false;
4879 };
4880
4881 options
4886 .source_route
4887 .map(|range| range.is_empty())
4888 .unwrap_or(true)
4889 }
4890
4891 fn maybe_forward_received(
4892 &mut self,
4893 frame: &[u8],
4894 header: &PacketHeader,
4895 rx: &RxInfo,
4896 locally_handled_unicast: bool,
4897 ) -> bool {
4898 if !self.repeater.enabled {
4899 return false;
4900 }
4901 if !header.packet_type().is_routable() {
4902 return false;
4903 }
4904 if rx.origin == RxOrigin::LocalTx {
4912 if let Some(cache_key) = Self::forward_dup_key(header, frame) {
4913 self.dup_cache.insert(cache_key, self.clock.now_ms());
4914 }
4915 return false;
4916 }
4917 if self.is_locally_originated(frame, header) {
4918 return false;
4919 }
4920 if self.find_local_identity_for_dst(header.dst).is_some() {
4925 return false;
4926 }
4927 if locally_handled_unicast
4933 && matches!(
4934 header.packet_type(),
4935 PacketType::Unicast
4936 | PacketType::UnicastAckReq
4937 | PacketType::BlindUnicast
4938 | PacketType::BlindUnicastAckReq
4939 )
4940 {
4941 return false;
4942 }
4943
4944 let Ok(options) = ParsedOptions::extract(frame, header.options_range.clone()) else {
4945 return false;
4946 };
4947 let Some(cache_key) = Self::forward_dup_key(header, frame) else {
4948 return false;
4949 };
4950 if self.dup_cache.contains(&cache_key, self.clock.now_ms()) {
4951 self.defer_pending_forward(&cache_key, header, rx, &options);
4952 return false;
4953 }
4954
4955 let Some(plan) = self.plan_forwarding(frame, header, &options, rx) else {
4956 return false;
4957 };
4958
4959 let mut rewritten = [0u8; FRAME];
4960 let Ok(total_len) =
4961 self.rewrite_forwarded_frame(frame, header, &options, plan, &mut rewritten)
4962 else {
4963 return false;
4964 };
4965 if total_len > self.radio.max_frame_size() {
4966 return false;
4967 }
4968
4969 let now_ms = self.clock.now_ms();
4970 if self
4971 .tx_queue
4972 .enqueue_with_state(
4973 TxPriority::Forward,
4974 &rewritten[..total_len],
4975 None,
4976 None,
4977 now_ms.saturating_add(plan.delay_ms),
4978 0,
4979 0,
4980 )
4981 .is_err()
4982 {
4983 return false;
4984 }
4985 self.dup_cache.insert(cache_key, now_ms);
4986 MacCounters::bump(&mut self.counters.forwarded);
4992 true
4993 }
4994
4995 fn plan_forwarding(
4996 &mut self,
4997 frame: &[u8],
4998 header: &PacketHeader,
4999 options: &ParsedOptions,
5000 rx: &RxInfo,
5001 ) -> Option<ForwardPlan> {
5002 if options.has_unknown_critical {
5003 return None;
5004 }
5005
5006 let router_hint = self.repeater_router_hint()?;
5007 let station_action = self.classify_forward_station_action(frame, header)?;
5008
5009 let source_route_bytes = options
5010 .source_route
5011 .as_ref()
5012 .and_then(|range| frame.get(range.clone()))
5013 .unwrap_or(&[]);
5014 if source_route_bytes.len() % 2 != 0 {
5015 return None;
5016 }
5017
5018 let mut consume_source_route = false;
5019 let mut decrement_flood_hops = false;
5020 let mut insert_region_code = None;
5021 let mut delay_ms = 0u64;
5022
5023 if !source_route_bytes.is_empty() {
5024 if source_route_bytes[..2] != router_hint.0 {
5033 return None;
5034 }
5035 consume_source_route = true;
5036 } else {
5037 decrement_flood_hops = true;
5038 }
5039
5040 if decrement_flood_hops {
5041 let flood_hops = header.flood_hops?;
5042 if flood_hops.remaining() == 0 {
5048 MacCounters::bump(&mut self.counters.forward_dropped_policy);
5049 return None;
5050 }
5051 if rx.origin.is_measured() {
5057 if let Some(min_rssi) = Self::effective_min_rssi(options, &self.repeater) {
5058 if rx.rssi < min_rssi {
5059 MacCounters::bump(&mut self.counters.forward_dropped_policy);
5060 return None;
5061 }
5062 }
5063 if let Some(min_snr) = Self::effective_min_snr(options, &self.repeater) {
5064 if rx.snr < Snr::from_decibels(min_snr) {
5065 MacCounters::bump(&mut self.counters.forward_dropped_policy);
5066 return None;
5067 }
5068 }
5069 }
5070 let mut saw_region_code = false;
5071 let mut matched_region_code = false;
5072 if !header.options_range.is_empty() {
5073 for entry in umsh_core::iter_options(frame, header.options_range.clone()) {
5074 let (number, value) = entry.ok()?;
5075 if OptionNumber::from(number) != OptionNumber::RegionCode || value.len() != 2 {
5076 continue;
5077 }
5078 saw_region_code = true;
5079 let region_code = [value[0], value[1]];
5080 if self
5081 .repeater
5082 .regions
5083 .iter()
5084 .any(|configured| *configured == region_code)
5085 {
5086 matched_region_code = true;
5087 }
5088 }
5089 }
5090 if saw_region_code {
5091 if !self.repeater.regions.is_empty() && !matched_region_code {
5093 MacCounters::bump(&mut self.counters.forward_dropped_policy);
5094 return None;
5095 }
5096 } else {
5097 insert_region_code = self.repeater.default_region;
5098 }
5099 delay_ms = self.sample_flood_contention_delay_ms(rx, options);
5100 if header.ack_requested() {
5109 delay_ms = delay_ms.saturating_add(self.ack_guard_delay_ms());
5110 }
5111 }
5112
5113 Some(ForwardPlan {
5114 router_hint,
5115 consume_source_route,
5116 decrement_flood_hops,
5117 insert_region_code,
5118 delay_ms,
5119 station_action,
5120 signal: trace_signal_entry(rx),
5121 })
5122 }
5123
5124 fn classify_forward_station_action(
5125 &self,
5126 frame: &[u8],
5127 header: &PacketHeader,
5128 ) -> Option<ForwardStationAction> {
5129 let has_operator_callsign = if header.options_range.is_empty() {
5130 false
5131 } else {
5132 umsh_core::iter_options(frame, header.options_range.clone())
5133 .filter_map(Result::ok)
5134 .any(|(number, _)| OptionNumber::from(number) == OptionNumber::OperatorCallsign)
5135 };
5136 let encrypted = header
5137 .sec_info
5138 .map(|sec| sec.scf.encrypted())
5139 .unwrap_or(false);
5140
5141 match self.repeater.amateur_radio_mode {
5142 AmateurRadioMode::Unlicensed => Some(ForwardStationAction::Remove),
5143 AmateurRadioMode::LicensedOnly => {
5144 if encrypted || !has_operator_callsign || self.repeater.station_callsign.is_none() {
5145 None
5146 } else {
5147 Some(ForwardStationAction::Replace)
5148 }
5149 }
5150 AmateurRadioMode::Hybrid => {
5151 if !encrypted && has_operator_callsign {
5152 self.repeater
5153 .station_callsign
5154 .as_ref()
5155 .map(|_| ForwardStationAction::Replace)
5156 } else {
5157 Some(ForwardStationAction::Remove)
5158 }
5159 }
5160 }
5161 }
5162
5163 fn rewrite_forwarded_frame(
5164 &self,
5165 src: &[u8],
5166 header: &PacketHeader,
5167 options: &ParsedOptions,
5168 plan: ForwardPlan,
5169 dst: &mut [u8],
5170 ) -> Result<usize, CapacityError> {
5171 if dst.is_empty() {
5172 return Err(CapacityError);
5173 }
5174
5175 dst[0] = umsh_core::Fcf::new(
5177 header.packet_type(),
5178 header.fcf.full_source(),
5179 header.flood_hops.is_some(),
5180 )
5181 .0;
5182 let mut cursor = 1;
5183
5184 if let Some(flood_hops) = header.flood_hops {
5186 let next = if plan.decrement_flood_hops {
5187 flood_hops.decremented().0
5188 } else {
5189 flood_hops.0
5190 };
5191 *dst.get_mut(cursor).ok_or(CapacityError)? = next;
5192 cursor += 1;
5193 }
5194
5195 let fhops_len = usize::from(header.flood_hops.is_some());
5197 let fixed_core = src
5198 .get(1 + fhops_len..header.options_range.start)
5199 .ok_or(CapacityError)?;
5200 let core_end = cursor + fixed_core.len();
5201 dst.get_mut(cursor..core_end)
5202 .ok_or(CapacityError)?
5203 .copy_from_slice(fixed_core);
5204 cursor = core_end;
5205
5206 let options_len =
5208 self.encode_forwarded_options(src, header, options, plan, &mut dst[cursor..])?;
5209 cursor += options_len;
5210
5211 let needs_marker = !matches!(header.packet_type(), PacketType::MacAck)
5213 && header.options_range.end < header.total_len;
5214 if needs_marker {
5215 *dst.get_mut(cursor).ok_or(CapacityError)? = 0xFF;
5216 cursor += 1;
5217 }
5218
5219 let tail = src
5221 .get(header.options_range.end..header.total_len)
5222 .ok_or(CapacityError)?;
5223 let end = cursor + tail.len();
5224 dst.get_mut(cursor..end)
5225 .ok_or(CapacityError)?
5226 .copy_from_slice(tail);
5227 Ok(end)
5228 }
5229
5230 fn encode_forwarded_options(
5231 &self,
5232 src: &[u8],
5233 header: &PacketHeader,
5234 _options: &ParsedOptions,
5235 plan: ForwardPlan,
5236 dst: &mut [u8],
5237 ) -> Result<usize, CapacityError> {
5238 let mut encoder = OptionEncoder::new(dst);
5239 let mut inserted_region = false;
5240 let mut inserted_station = false;
5241 let mut saw_station = false;
5242
5243 if !header.options_range.is_empty() {
5244 for entry in umsh_core::iter_options(src, header.options_range.clone()) {
5245 let (number, value) = entry.map_err(|_| CapacityError)?;
5246 if !inserted_region {
5247 if let Some(region_code) = plan.insert_region_code {
5248 if number > OptionNumber::RegionCode.as_u16() {
5249 encoder
5250 .put(OptionNumber::RegionCode.as_u16(), ®ion_code)
5251 .map_err(|_| CapacityError)?;
5252 inserted_region = true;
5253 }
5254 }
5255 }
5256 if !inserted_station
5257 && matches!(plan.station_action, ForwardStationAction::Replace)
5258 && number > OptionNumber::StationCallsign.as_u16()
5259 {
5260 encoder
5261 .put(
5262 OptionNumber::StationCallsign.as_u16(),
5263 self.repeater
5264 .station_callsign
5265 .as_ref()
5266 .ok_or(CapacityError)?
5267 .as_trimmed_slice(),
5268 )
5269 .map_err(|_| CapacityError)?;
5270 inserted_station = true;
5271 }
5272
5273 match OptionNumber::from(number) {
5274 OptionNumber::RegionCode => {
5275 inserted_region = true;
5276 encoder.put(number, value).map_err(|_| CapacityError)?;
5277 }
5278 OptionNumber::TraceRoute => {
5283 let mut trace = [0u8; crate::MAX_SOURCE_ROUTE_HOPS * 2 + 2];
5284 if value.len() > crate::MAX_SOURCE_ROUTE_HOPS * 2 {
5285 return Err(CapacityError);
5286 }
5287 trace[..2].copy_from_slice(&plan.router_hint.0);
5288 trace[2..2 + value.len()].copy_from_slice(value);
5289 encoder
5290 .put(number, &trace[..2 + value.len()])
5291 .map_err(|_| CapacityError)?;
5292 }
5293 OptionNumber::TraceSignal => {
5297 let mut trace = [0u8; crate::MAX_SOURCE_ROUTE_HOPS * 2 + 2];
5298 if value.len() > crate::MAX_SOURCE_ROUTE_HOPS * 2 {
5299 return Err(CapacityError);
5300 }
5301 trace[..2].copy_from_slice(&plan.signal.as_bytes());
5302 trace[2..2 + value.len()].copy_from_slice(value);
5303 encoder
5304 .put(number, &trace[..2 + value.len()])
5305 .map_err(|_| CapacityError)?;
5306 }
5307 OptionNumber::SourceRoute if plan.consume_source_route => {
5308 if value.len() < 2 || value.len() % 2 != 0 {
5309 return Err(CapacityError);
5310 }
5311 let remaining = if value.len() > 2 { &value[2..] } else { &[] };
5312 encoder.put(number, remaining).map_err(|_| CapacityError)?;
5313 }
5314 OptionNumber::StationCallsign => {
5315 saw_station = true;
5316 match plan.station_action {
5317 ForwardStationAction::Remove => {}
5318 ForwardStationAction::Replace => {
5319 encoder
5320 .put(
5321 number,
5322 self.repeater
5323 .station_callsign
5324 .as_ref()
5325 .ok_or(CapacityError)?
5326 .as_trimmed_slice(),
5327 )
5328 .map_err(|_| CapacityError)?;
5329 inserted_station = true;
5330 }
5331 }
5332 }
5333 _ => {
5334 encoder.put(number, value).map_err(|_| CapacityError)?;
5335 }
5336 }
5337 }
5338 }
5339
5340 if matches!(plan.station_action, ForwardStationAction::Replace)
5341 && !inserted_station
5342 && !saw_station
5343 {
5344 encoder
5345 .put(
5346 OptionNumber::StationCallsign.as_u16(),
5347 self.repeater
5348 .station_callsign
5349 .as_ref()
5350 .ok_or(CapacityError)?
5351 .as_trimmed_slice(),
5352 )
5353 .map_err(|_| CapacityError)?;
5354 }
5355 if let Some(region_code) = plan.insert_region_code {
5356 if !inserted_region {
5357 encoder
5358 .put(OptionNumber::RegionCode.as_u16(), ®ion_code)
5359 .map_err(|_| CapacityError)?;
5360 }
5361 }
5362 Ok(encoder.finish())
5363 }
5364
5365 fn synthesize_route_retry_resend(
5366 &self,
5367 peer: &PublicKey,
5368 resend: &ResendRecord<FRAME>,
5369 ) -> Option<ResendRecord<FRAME>> {
5370 let header = PacketHeader::parse(resend.frame.as_slice()).ok()?;
5371 let options =
5372 ParsedOptions::extract(resend.frame.as_slice(), header.options_range.clone()).ok()?;
5373 if options.route_retry {
5374 return None;
5375 }
5376 let source_route = resend
5377 .source_route
5378 .as_ref()
5379 .filter(|route| !route.is_empty());
5380 if source_route.is_none() && resend.requested_flood_hops.is_none() {
5381 return None;
5383 }
5384
5385 let flood_hops =
5386 self.route_retry_flood_hops(peer, &header, source_route, resend.requested_flood_hops)?;
5387 let has_flood_hops = flood_hops > 0;
5388 let mut rewritten = [0u8; FRAME];
5389
5390 rewritten[0] = umsh_core::Fcf::new(
5392 header.packet_type(),
5393 header.fcf.full_source(),
5394 has_flood_hops,
5395 )
5396 .0;
5397 let mut cursor = 1;
5398
5399 if has_flood_hops {
5401 *rewritten.get_mut(cursor)? = FloodHops::new(flood_hops, 0)?.0;
5402 cursor += 1;
5403 }
5404
5405 let fhops_len = usize::from(header.flood_hops.is_some());
5407 let fixed_core = resend
5408 .frame
5409 .get(1 + fhops_len..header.options_range.start)?;
5410 let core_end = cursor + fixed_core.len();
5411 rewritten
5412 .get_mut(cursor..core_end)?
5413 .copy_from_slice(fixed_core);
5414 cursor = core_end;
5415
5416 let options_len = self
5418 .encode_route_retry_options(
5419 resend.frame.as_slice(),
5420 header.options_range.clone(),
5421 &options,
5422 &mut rewritten[cursor..],
5423 )
5424 .ok()?;
5425 cursor += options_len;
5426
5427 let needs_marker = !matches!(header.packet_type(), PacketType::MacAck)
5429 && header.options_range.end < header.total_len;
5430 if needs_marker {
5431 *rewritten.get_mut(cursor)? = 0xFF;
5432 cursor += 1;
5433 }
5434
5435 let tail = resend
5437 .frame
5438 .get(header.options_range.end..header.total_len)?;
5439 let end = cursor + tail.len();
5440 rewritten.get_mut(cursor..end)?.copy_from_slice(tail);
5441
5442 ResendRecord::try_new(&rewritten[..end], None)
5443 .ok()
5444 .map(|record| record.with_requested_flood_hops(resend.requested_flood_hops))
5445 }
5446
5447 fn encode_route_retry_options(
5448 &self,
5449 src: &[u8],
5450 options_range: core::ops::Range<usize>,
5451 _options: &ParsedOptions,
5452 dst: &mut [u8],
5453 ) -> Result<usize, CapacityError> {
5454 let mut encoder = OptionEncoder::new(dst);
5455 let mut inserted_trace_route = false;
5456 let mut inserted_route_retry = false;
5457
5458 if !options_range.is_empty() {
5459 for entry in umsh_core::iter_options(src, options_range) {
5460 let (number, value) = entry.map_err(|_| CapacityError)?;
5461 if !inserted_trace_route && number > OptionNumber::TraceRoute.as_u16() {
5462 encoder
5463 .put(OptionNumber::TraceRoute.as_u16(), &[])
5464 .map_err(|_| CapacityError)?;
5465 inserted_trace_route = true;
5466 }
5467 if !inserted_route_retry && number > OptionNumber::RouteRetry.as_u16() {
5468 encoder
5469 .put(OptionNumber::RouteRetry.as_u16(), &[])
5470 .map_err(|_| CapacityError)?;
5471 inserted_route_retry = true;
5472 }
5473 match OptionNumber::from(number) {
5474 OptionNumber::SourceRoute => {}
5475 OptionNumber::TraceRoute => {
5476 encoder.put(number, value).map_err(|_| CapacityError)?;
5477 inserted_trace_route = true;
5478 }
5479 OptionNumber::RouteRetry => {}
5480 _ => {
5481 encoder.put(number, value).map_err(|_| CapacityError)?;
5482 }
5483 }
5484 }
5485 }
5486
5487 if !inserted_trace_route {
5488 encoder
5489 .put(OptionNumber::TraceRoute.as_u16(), &[])
5490 .map_err(|_| CapacityError)?;
5491 }
5492 if !inserted_route_retry {
5493 encoder
5494 .put(OptionNumber::RouteRetry.as_u16(), &[])
5495 .map_err(|_| CapacityError)?;
5496 }
5497
5498 Ok(encoder.finish())
5499 }
5500
5501 fn route_retry_flood_hops(
5502 &self,
5503 peer: &PublicKey,
5504 header: &PacketHeader,
5505 source_route: Option<&heapless::Vec<RouterHint, MAX_SOURCE_ROUTE_HOPS>>,
5506 requested: Option<u8>,
5507 ) -> Option<u8> {
5508 let requested = requested
5515 .filter(|hops| *hops > 0)
5516 .map(|hops| hops.clamp(1, MAX_FLOOD_HOPS));
5517 let existing = header
5518 .flood_hops
5519 .map(|hops| hops.remaining())
5520 .filter(|hops| *hops > 0);
5521 let cached = self
5522 .peer_registry
5523 .lookup_by_key(peer)
5524 .and_then(|(_, info)| match info.route.as_ref() {
5525 Some(crate::CachedRoute::Flood { hops, .. }) => {
5526 Some((*hops).clamp(1, MAX_FLOOD_HOPS))
5527 }
5528 _ => None,
5529 });
5530 let route_len = source_route
5531 .and_then(|route| u8::try_from(route.len()).ok())
5532 .map(|hops| hops.clamp(1, MAX_FLOOD_HOPS));
5533
5534 requested.or(existing).or(cached).or(route_len).or(Some(5))
5535 }
5536
5537 fn repeater_router_hint(&self) -> Option<RouterHint> {
5538 self.identities
5539 .iter()
5540 .filter_map(|slot| slot.as_ref())
5541 .next()
5542 .map(|slot| slot.identity().public_key().router_hint())
5543 }
5544
5545 fn effective_min_rssi(options: &ParsedOptions, repeater: &RepeaterConfig) -> Option<i16> {
5546 match (options.min_rssi, repeater.min_rssi) {
5547 (Some(packet), Some(local)) => Some(packet.max(local)),
5548 (Some(packet), None) => Some(packet),
5549 (None, Some(local)) => Some(local),
5550 (None, None) => None,
5551 }
5552 }
5553
5554 fn effective_min_snr(options: &ParsedOptions, repeater: &RepeaterConfig) -> Option<i8> {
5555 match (options.min_snr, repeater.min_snr) {
5556 (Some(packet), Some(local)) => Some(packet.max(local)),
5557 (Some(packet), None) => Some(packet),
5558 (None, Some(local)) => Some(local),
5559 (None, None) => None,
5560 }
5561 }
5562
5563 fn sample_flood_contention_delay_ms(&mut self, rx: &RxInfo, options: &ParsedOptions) -> u64 {
5579 if !rx.origin.is_measured() {
5584 return 0;
5585 }
5586 let effective_threshold_db =
5587 Self::effective_min_snr(options, &self.repeater).unwrap_or(i8::MIN);
5588 let snr_low_db = self
5589 .repeater
5590 .flood_contention_snr_low_db
5591 .max(effective_threshold_db);
5592 let snr_high_db = self
5593 .repeater
5594 .flood_contention_snr_high_db
5595 .max(snr_low_db.saturating_add(1));
5596 let snr_low = i32::from(Snr::from_decibels(snr_low_db).as_centibels());
5597 let snr_high = i32::from(Snr::from_decibels(snr_high_db).as_centibels());
5598 let snr_received = i32::from(rx.snr.as_centibels());
5599 let quality_den = (snr_high - snr_low) as u64;
5600 let quality_num = (snr_received - snr_low).clamp(0, snr_high - snr_low) as u64;
5601
5602 let rssi_low = i32::from(self.repeater.flood_contention_rssi_low_dbm);
5603 let rssi_high =
5604 i32::from(self.repeater.flood_contention_rssi_high_dbm).max(rssi_low.saturating_add(1));
5605 let rssi_received = i32::from(rx.rssi);
5606 let signal_den = (rssi_high - rssi_low) as u64;
5607 let signal_num = (rssi_received - rssi_low).clamp(0, rssi_high - rssi_low) as u64;
5608
5609 let t_frame_ms = u64::from(self.radio.t_frame_ms());
5610 let min_window_ms = t_frame_ms
5611 .saturating_mul(u64::from(self.repeater.flood_contention_min_window_percent))
5612 / 100;
5613 let max_window_ms = t_frame_ms
5614 .saturating_mul(u64::from(self.repeater.flood_contention_max_window_percent))
5615 / 100;
5616 let max_window_ms = max_window_ms.max(min_window_ms);
5617 let span_ms = max_window_ms.saturating_sub(min_window_ms);
5618 let quality_term = span_ms.saturating_mul(quality_den - quality_num) / quality_den;
5620 let signal_term = span_ms.saturating_mul(signal_num) / signal_den;
5621 let window_ms = min_window_ms.saturating_add(quality_term.max(signal_term));
5622
5623 let jitter_ms = t_frame_ms
5624 .saturating_mul(u64::from(self.repeater.flood_contention_jitter_percent))
5625 / 100;
5626 if jitter_ms == 0 {
5627 window_ms
5628 } else {
5629 window_ms.saturating_add(self.rng.random_range(..jitter_ms.saturating_add(1)))
5630 }
5631 }
5632
5633 fn ack_guard_delay_ms(&self) -> u64 {
5636 u64::from(self.radio.t_frame_ms())
5637 .saturating_mul(u64::from(self.repeater.flood_contention_ack_guard_percent))
5638 / 100
5639 }
5640
5641 pub(crate) fn forward_dup_key(header: &PacketHeader, frame: &[u8]) -> Option<DupCacheKey> {
5646 crate::forward_id::forwarding_dup_key_parsed(header, frame)
5647 }
5648
5649 fn defer_pending_forward(
5650 &mut self,
5651 key: &DupCacheKey,
5652 header: &PacketHeader,
5653 rx: &RxInfo,
5654 options: &ParsedOptions,
5655 ) {
5656 let Some(queued) = self.tx_queue.remove_first_matching(|entry| {
5657 entry.priority == TxPriority::Forward
5658 && Self::confirmation_key(entry.frame.as_slice())
5659 .map(|entry_key| &entry_key == key)
5660 .unwrap_or(false)
5661 }) else {
5662 return;
5663 };
5664
5665 if queued.forward_deferrals >= self.repeater.flood_contention_max_deferrals {
5666 return;
5667 }
5668
5669 let now_ms = self.clock.now_ms();
5670 let mut delay_ms = self.sample_flood_contention_delay_ms(rx, options);
5671 let overheard_has_route_hops = options
5674 .source_route
5675 .as_ref()
5676 .map(|range| !range.is_empty())
5677 .unwrap_or(false);
5678 if header.ack_requested() && !overheard_has_route_hops {
5679 delay_ms = delay_ms.saturating_add(self.ack_guard_delay_ms());
5680 }
5681 let _ = self.tx_queue.enqueue_with_state(
5682 queued.priority,
5683 queued.frame.as_slice(),
5684 queued.receipt,
5685 queued.identity_id,
5686 now_ms.saturating_add(delay_ms),
5687 queued.cad_attempts,
5688 queued.forward_deferrals.saturating_add(1),
5689 );
5690 }
5691
5692 fn cancel_forwards_for_ack_mic(&mut self, ack_mic: &[u8]) -> usize {
5717 if ack_mic.len() != 4 {
5718 return 0;
5719 }
5720 let removed = self.tx_queue.remove_all_matching(|entry| {
5721 if entry.priority != TxPriority::Forward || entry.receipt.is_some() {
5722 return false;
5723 }
5724 let frame = entry.frame.as_slice();
5725 let Ok(header) = PacketHeader::parse(frame) else {
5726 return false;
5727 };
5728 if !matches!(
5733 header.packet_type(),
5734 PacketType::UnicastAckReq | PacketType::BlindUnicastAckReq
5735 ) {
5736 return false;
5737 }
5738 frame
5739 .get(header.mic_range.clone())
5740 .and_then(|mic| mic.get(..4))
5741 .map(|prefix| prefix == ack_mic)
5742 .unwrap_or(false)
5743 });
5744 if removed > 0 {
5745 MacCounters::bump(&mut self.counters.forward_cancelled);
5746 }
5747 removed
5748 }
5749
5750 fn cancel_forwards_for_ack_mic_option(&mut self, frame: &[u8], header: &PacketHeader) {
5759 if header.options_range.is_empty() {
5760 return;
5761 }
5762 let mut ack_mic = None;
5763 for entry in umsh_core::iter_options(frame, header.options_range.clone()) {
5764 let Ok((number, value)) = entry else {
5765 continue;
5766 };
5767 if OptionNumber::from(number) == OptionNumber::AckMic && value.len() == 4 {
5768 ack_mic = Some([value[0], value[1], value[2], value[3]]);
5769 break;
5770 }
5771 }
5772 if let Some(ack_mic) = ack_mic {
5773 self.cancel_forwards_for_ack_mic(&ack_mic);
5774 }
5775 }
5776
5777 async fn service_post_tx_listen(
5778 &mut self,
5779 mut on_event: impl FnMut(LocalIdentityId, crate::MacEventRef<'_>),
5780 ) -> Result<(), MacError<<P::Radio as Radio>::Error>> {
5781 loop {
5782 self.expire_post_tx_listen_if_needed();
5783 if self.post_tx_listen.is_none() {
5784 return Ok(());
5785 }
5786
5787 let handled = self.receive_one(&mut on_event).await?;
5788 if !handled {
5789 return Ok(());
5790 }
5791 }
5792 }
5793
5794 fn note_transmitted_tracked(&mut self, receipt: SendReceipt, frame: &[u8]) {
5796 let sent_ms = self.clock.now_ms();
5797 let direct_ack_deadline_ms = sent_ms.saturating_add(self.direct_ack_timeout_ms());
5798 let forwarded_ack_deadline_ms = sent_ms
5799 .saturating_add(self.forwarded_ack_timeout_ms(Self::allowed_hop_distance(frame)));
5800 let repeat_only_deadline_ms = sent_ms.saturating_add(self.repeat_confirm_timeout_ms());
5801 let confirm_timeout_ms = self.forward_confirm_timeout_ms();
5802 let confirm_key = Self::confirmation_key(frame);
5803
5804 let post_tx_listen = {
5805 let Some((identity_id, pending)) = self.pending_ack_mut(receipt) else {
5806 return;
5807 };
5808
5809 let needs_forward_confirmation = match pending.state {
5810 crate::AckState::Queued {
5811 needs_forward_confirmation,
5812 } => needs_forward_confirmation,
5813 crate::AckState::RetryQueued => true,
5814 _ => return,
5815 };
5816
5817 pending.sent_ms = sent_ms;
5818 pending.confirm_key = confirm_key.clone();
5819 if pending.ack_deadline_ms == 0 {
5824 pending.ack_deadline_ms = match (pending.completion, needs_forward_confirmation) {
5825 (CompletionSignal::RepeatOnly, _) => repeat_only_deadline_ms,
5826 (CompletionSignal::Ack, true) => forwarded_ack_deadline_ms,
5827 (CompletionSignal::Ack, false) => direct_ack_deadline_ms,
5828 };
5829 }
5830
5831 if needs_forward_confirmation {
5832 let deadline_ms = sent_ms.saturating_add(confirm_timeout_ms);
5833 pending.state = crate::AckState::AwaitingForward {
5834 confirm_deadline_ms: deadline_ms,
5835 };
5836 if pending.expects_ack() {
5844 confirm_key.map(|confirm_key| PostTxListen {
5845 identity_id,
5846 receipt,
5847 confirm_key,
5848 deadline_ms,
5849 })
5850 } else {
5851 None
5852 }
5853 } else {
5854 pending.state = crate::AckState::AwaitingAck;
5855 None
5856 }
5857 };
5858
5859 self.post_tx_listen = post_tx_listen;
5860 }
5861
5862 fn expire_post_tx_listen_if_needed(&mut self) {
5863 let should_clear = self
5864 .post_tx_listen
5865 .as_ref()
5866 .map(|listen| self.clock.now_ms() >= listen.deadline_ms)
5867 .unwrap_or(false);
5868 if should_clear {
5869 self.post_tx_listen = None;
5870 }
5871 }
5872
5873 fn forward_confirm_timeout_ms(&self) -> u64 {
5880 let t_frame_ms = u64::from(self.radio.t_frame_ms());
5881 t_frame_ms
5882 .saturating_add(self.max_forward_contention_delay_ms())
5883 .saturating_add(self.ack_guard_delay_ms())
5884 .saturating_add(t_frame_ms)
5885 }
5886
5887 fn max_forward_contention_delay_ms(&self) -> u64 {
5890 let t_frame_ms = u64::from(self.radio.t_frame_ms());
5891 let max_window_ms = t_frame_ms
5892 .saturating_mul(u64::from(self.repeater.flood_contention_max_window_percent))
5893 / 100;
5894 let jitter_ms = t_frame_ms
5895 .saturating_mul(u64::from(self.repeater.flood_contention_jitter_percent))
5896 / 100;
5897 max_window_ms.saturating_add(jitter_ms)
5898 }
5899
5900 fn forward_retry_backoff_cap_ms(&self) -> u32 {
5909 self.radio.t_frame_ms()
5910 }
5911
5912 fn can_attempt_route_retry(pending: &PendingAck<FRAME>) -> bool {
5927 if !pending.expects_ack() {
5931 return false;
5932 }
5933 let Ok(header) = PacketHeader::parse(pending.resend.frame.as_slice()) else {
5934 return false;
5935 };
5936 let Ok(options) = ParsedOptions::extract(
5937 pending.resend.frame.as_slice(),
5938 header.options_range.clone(),
5939 ) else {
5940 return false;
5941 };
5942 if options.route_retry {
5943 return false;
5944 }
5945 let has_source_route = pending
5946 .resend
5947 .source_route
5948 .as_ref()
5949 .map(|route| !route.is_empty())
5950 .unwrap_or(false);
5951 if has_source_route {
5952 return true;
5953 }
5954 let sent = header
5956 .flood_hops
5957 .map(|hops| hops.remaining())
5958 .unwrap_or_default();
5959 pending
5960 .resend
5961 .requested_flood_hops
5962 .is_some_and(|requested| requested > sent)
5963 }
5964
5965 fn direct_ack_timeout_ms(&self) -> u64 {
5966 u64::from(self.radio.t_frame_ms()).saturating_mul(10)
5967 }
5968
5969 fn forwarded_ack_timeout_ms(&self, hops: u8) -> u64 {
5981 let t_frame_ms = u64::from(self.radio.t_frame_ms());
5982 let per_hop_ms = self
5983 .forward_confirm_timeout_ms()
5984 .saturating_sub(t_frame_ms)
5985 .max(t_frame_ms);
5986 self.repeat_confirm_timeout_ms().saturating_add(
5987 per_hop_ms
5988 .saturating_mul(2)
5989 .saturating_mul(u64::from(hops.max(1))),
5990 )
5991 }
5992
5993 fn allowed_hop_distance(frame: &[u8]) -> u8 {
6000 let Ok(header) = PacketHeader::parse(frame) else {
6001 return 1;
6002 };
6003 let mut hops = header
6004 .flood_hops
6005 .map(|flood_hops| flood_hops.remaining())
6006 .unwrap_or(0);
6007 if let Ok(options) = ParsedOptions::extract(frame, header.options_range.clone())
6008 && let Some(range) = options.source_route
6009 {
6010 hops = hops.saturating_add(u8::try_from(range.len() / 2).unwrap_or(u8::MAX));
6011 }
6012 hops.max(1)
6013 }
6014
6015 fn repeat_confirm_timeout_ms(&self) -> u64 {
6023 let per_retry_ms = u64::from(self.forward_retry_backoff_cap_ms())
6024 .saturating_add(self.forward_confirm_timeout_ms());
6025 self.forward_confirm_timeout_ms()
6026 .saturating_add(per_retry_ms.saturating_mul(u64::from(MAX_FORWARD_RETRIES)))
6027 }
6028
6029 fn pending_ack_mut(
6030 &mut self,
6031 receipt: SendReceipt,
6032 ) -> Option<(LocalIdentityId, &mut PendingAck<FRAME>)> {
6033 for (index, slot) in self.identities.iter_mut().enumerate() {
6034 let Some(slot) = slot.as_mut() else {
6035 continue;
6036 };
6037 if let Some(pending) = slot.pending_ack_mut(&receipt) {
6038 return Some((LocalIdentityId(index as u8), pending));
6039 }
6040 }
6041 None
6042 }
6043
6044 pub(crate) fn confirmation_key(frame: &[u8]) -> Option<DupCacheKey> {
6045 crate::forward_id::forwarding_dup_key(frame)
6046 }
6047
6048 fn observe_forwarding_confirmation(
6062 &mut self,
6063 frame: &[u8],
6064 ) -> Option<(LocalIdentityId, SendReceipt)> {
6065 self.expire_post_tx_listen_if_needed();
6066 let received_key = Self::confirmation_key(frame)?;
6067
6068 if self
6070 .post_tx_listen
6071 .as_ref()
6072 .map(|listen| listen.confirm_key == received_key)
6073 .unwrap_or(false)
6074 {
6075 self.post_tx_listen = None;
6076 }
6077
6078 let mut found = None;
6079 'search: for (index, slot) in self.identities.iter().enumerate() {
6080 let Some(slot) = slot.as_ref() else {
6081 continue;
6082 };
6083 for (receipt, pending) in slot.pending_acks.iter() {
6084 if !matches!(
6085 pending.state,
6086 crate::AckState::AwaitingForward { .. } | crate::AckState::RetryQueued
6087 ) {
6088 continue;
6089 }
6090 if pending.confirm_key.as_ref() != Some(&received_key) {
6091 continue;
6092 }
6093 found = Some((LocalIdentityId(index as u8), *receipt, pending.completion));
6094 break 'search;
6095 }
6096 }
6097 let (identity_id, receipt, completion) = found?;
6098
6099 self.tx_queue.remove_all_matching(|entry| {
6102 entry.receipt == Some(receipt) && entry.identity_id == Some(identity_id)
6103 });
6104
6105 match completion {
6106 CompletionSignal::Ack => {
6107 if let Some(pending) = self
6108 .identity_mut(identity_id)
6109 .and_then(|slot| slot.pending_ack_mut(&receipt))
6110 {
6111 pending.state = crate::AckState::AwaitingAck;
6112 }
6113 }
6114 CompletionSignal::RepeatOnly => {
6116 if let Some(slot) = self.identity_mut(identity_id) {
6117 slot.pending_acks.remove(&receipt);
6118 }
6119 }
6120 }
6121 Some((identity_id, receipt))
6122 }
6123
6124 fn peer_for_ack_trailer(&self, ack_trailer: &[u8; 8]) -> Option<PublicKey> {
6128 self.identities
6129 .iter()
6130 .filter_map(|slot| slot.as_ref())
6131 .find_map(|slot| self.match_pending_peer_for_ack(slot, ack_trailer))
6132 }
6133
6134 fn match_pending_peer_for_ack(
6135 &self,
6136 slot: &IdentitySlot<P::Identity, PEERS, ACKS, FRAME>,
6137 ack_trailer_bytes: &[u8],
6138 ) -> Option<PublicKey> {
6139 if ack_trailer_bytes.len() != 8 {
6140 return None;
6141 }
6142
6143 slot.pending_acks.iter().find_map(|(_, pending)| {
6144 (pending.expects_ack() && pending.ack_trailer == ack_trailer_bytes)
6145 .then_some(pending.peer)
6146 })
6147 }
6148}
6149
6150fn align_counter_boundary(value: u32) -> u32 {
6151 value & !COUNTER_PERSIST_BLOCK_MASK
6152}
6153
6154pub(crate) const fn nonzero_initial_frame_counter(random: u32) -> u32 {
6155 if random == 0 { 1 } else { random }
6156}
6157
6158fn next_counter_persist_target(next_counter: u32) -> u32 {
6159 next_counter.wrapping_add(COUNTER_PERSIST_BLOCK_SIZE) & !COUNTER_PERSIST_BLOCK_MASK
6160}
6161
6162fn rx_counter_key_bytes(pk: &[u8; 32]) -> [u8; 39] {
6168 let mut key = [0u8; 39];
6169 key[..7].copy_from_slice(b"mac.rx:");
6170 key[7..].copy_from_slice(pk);
6171 key
6172}