Skip to main content

sedsnet/
router.rs

1//! Telemetry Router
2//!
3//! Router with internal named sides (like Relay), plus local processing.
4//!
5//! Design:
6//! - Sides are registered with per-side TX handlers (packed or packet).
7//! - RX is tagged by source side; route rules decide whether it is forwarded.
8//! - Local endpoint handlers process packets as before (no side parameter).
9//! - De-duplication remains packet-id based and side-agnostic.
10
11// Apply the same cache contract independently to router and relay state.
12#[cfg(test)]
13#[allow(clippy::duplicate_mod)]
14#[path = "tests/side_template_index.rs"]
15mod side_template_index_tests;
16use crate::side_transport::{
17    SIDE_TRANSPORT_FLAG_PACKET_NONCE, SIDE_TRANSPORT_KIND_CHUNK, SIDE_TRANSPORT_KIND_COMPACT,
18    SIDE_TRANSPORT_KIND_COMPACT_DELTA, SIDE_TRANSPORT_KIND_COMPACT_SAME_TIMESTAMP,
19    SIDE_TRANSPORT_KIND_FULL, SideChunkAssembly, SideCompactTimestampMode, SideHeaderTemplate,
20    SideTransportFrames, extract_side_header_template, extract_side_header_template_view,
21    parse_side_transport_wrapper, read_uleb128_local, reconstruct_side_compact_frame,
22    wrap_side_transport_frame, write_uleb128_local,
23};
24#[cfg(all(test, feature = "discovery"))]
25#[path = "tests/ack_return_identity.rs"]
26mod ack_return_identity;
27
28#[cfg(all(test, feature = "discovery"))]
29#[path = "tests/discovery_liveness.rs"]
30mod discovery_liveness;
31
32#[cfg(all(test, feature = "discovery"))]
33mod compact_summary_tests {
34    use super::*;
35
36    #[test]
37    fn ordered_pending_window_is_bounded_and_exhaustion_cancels_the_tail() {
38        crate::tests::ensure_common_test_schema();
39        let ep = DataEndpoint::named("RADIO");
40        let ty = crate::config::register_data_type_with_description(
41            "ORDERED_WINDOW_REGRESSION",
42            "bounded ordered window",
43            MessageElement::Static(1, crate::MessageDataType::UInt8, crate::MessageClass::Data),
44            &[ep],
45            crate::ReliableMode::Ordered,
46            200,
47        )
48        .unwrap();
49        let now = Arc::new(core::sync::atomic::AtomicU64::new(0));
50        let clock = now.clone();
51        let router = Router::new_with_clock(
52            RouterConfig::new([]),
53            Box::new(move || clock.load(Ordering::Relaxed)),
54        );
55        let capacity = runtime_reliable_max_end_to_end_pending().max(1);
56        for index in 0..capacity {
57            let item = RouterItem::Packet(
58                Packet::new(
59                    ty,
60                    &[ep],
61                    "SOURCE",
62                    index as u64 + 1,
63                    Arc::<[u8]>::from([index as u8]),
64                )
65                .unwrap(),
66            );
67            assert_eq!(
68                router.register_end_to_end_reliable_tx(&item, None).unwrap(),
69                index == 0
70            );
71        }
72        let rejected = RouterItem::Packet(
73            Packet::new(
74                ty,
75                &[ep],
76                "SOURCE",
77                capacity as u64 + 1,
78                Arc::<[u8]>::from([0]),
79            )
80            .unwrap(),
81        );
82        assert!(
83            router
84                .register_end_to_end_reliable_tx(&rejected, None)
85                .is_err()
86        );
87        {
88            let mut state = router.state.lock();
89            assert_eq!(state.end_to_end_reliable_tx.len(), capacity);
90            let first = state.end_to_end_reliable_tx_order[0];
91            state
92                .end_to_end_reliable_tx
93                .get_mut(&first)
94                .unwrap()
95                .retries = runtime_reliable_max_retries();
96        }
97        now.store(runtime_reliable_retransmit_ms(), Ordering::Relaxed);
98        assert!(router.process_end_to_end_reliable_timeouts().is_err());
99        assert!(router.state.lock().end_to_end_reliable_tx.is_empty());
100    }
101    #[test]
102    fn compact_reachability_survives_incomplete_detailed_topology() {
103        crate::tests::ensure_common_test_schema();
104        let router = Router::new(RouterConfig::new([]).with_sender("GB"));
105        let endpoint = DataEndpoint::try_named("GROUND_STATION")
106            .or_else(|| DataEndpoint::try_named("RADIO"))
107            .unwrap();
108        router.compact_summary_fixture(endpoint);
109        let state = router.state.lock();
110        let now = router.clock.now_ms();
111        let advertised =
112            router.advertised_discovery_endpoints_for_link_locked(&state, now, true, Some(1));
113        assert!(
114            advertised.contains(&endpoint),
115            "compact subscriber route was lost at the bridge"
116        );
117        let reflected =
118            router.advertised_discovery_endpoints_for_link_locked(&state, now, true, Some(0));
119        assert!(
120            !reflected.contains(&endpoint),
121            "split horizon must still exclude ingress"
122        );
123        assert_eq!(
124            router.advertised_discovery_timesync_sources_for_link_locked(&state, now, Some(1)),
125            vec!["clock-behind-bridge".to_string()]
126        );
127        assert!(
128            router
129                .advertised_discovery_timesync_sources_for_link_locked(&state, now, Some(0))
130                .is_empty()
131        );
132        assert!(
133            !router
134                .advertised_discovery_endpoints_for_link_locked(
135                    &state,
136                    now + DISCOVERY_ROUTE_TTL_MS + 1,
137                    true,
138                    Some(1)
139                )
140                .contains(&endpoint)
141        );
142        assert!(
143            router
144                .advertised_discovery_timesync_sources_for_link_locked(
145                    &state,
146                    now + DISCOVERY_ROUTE_TTL_MS + 1,
147                    Some(1)
148                )
149                .is_empty()
150        );
151    }
152}
153
154use crate::config::RuntimeMemoryConfig;
155use crate::diagnostics::{
156    AdaptiveLinkStats, DiscoveryRuntimeStats, QueueRuntimeStats, ReliableRuntimeStats,
157    RouteModeStats, RouteOverrideStats, RoutePriorityStats, RouteWeightStats, RuntimeSideStats,
158    RuntimeStatsSnapshot, RuntimeTypeStats, TypedRouteOverrideStats,
159};
160#[cfg(feature = "discovery")]
161use crate::discovery::{
162    self, ClientStatsSnapshot, DISCOVERY_INCREMENTAL_RETRY_COUNT, DISCOVERY_ROUTE_TTL_MS,
163    DISCOVERY_SLOW_LINK_PING_INTERVAL_MS, DiscoveryCadenceState,
164    TIMESYNC_SLOW_LINK_MIN_INTERVAL_MS, TopologyAnnouncerRoute, TopologyBoardNode,
165    TopologySideRoute, TopologySnapshot,
166};
167use crate::packet::{hash_bytes_u64, sender_address_u32};
168use crate::queue::{BoundedDeque, ByteCost};
169#[cfg(all(not(feature = "std"), target_os = "none"))]
170use crate::seds_error_msg;
171#[cfg(feature = "timesync")]
172use crate::timesync::{
173    INTERNAL_TIMESYNC_SOURCE_ID, LOCAL_TIMESYNC_DATE_SOURCE_ID, LOCAL_TIMESYNC_FULL_SOURCE_ID,
174    LOCAL_TIMESYNC_SUBSEC_SOURCE_ID, LOCAL_TIMESYNC_TOD_SOURCE_ID, NetworkClock,
175    NetworkTimeReading, PartialNetworkTime, SlewedNetworkClock, TimeSyncConfig, TimeSyncLeader,
176    TimeSyncTracker, advance_network_time, compute_network_time_sample, decode_timesync_announce,
177    decode_timesync_request, decode_timesync_response,
178};
179use crate::{
180    E2eEncryptionPolicy, MessageElement, RouteSelectionMode, TelemetryError, TelemetryResult,
181    config::{
182        DataEndpoint, DataType, runtime_device_identifier, runtime_max_handler_retries,
183        runtime_reliable_max_end_to_end_pending, runtime_reliable_max_pending,
184        runtime_reliable_max_retries, runtime_reliable_max_return_routes,
185        runtime_reliable_retransmit_ms,
186    },
187    get_needed_message_size, impl_letype_num, is_reliable_type,
188    lock::{ReentryGate, RouterMutex},
189    message_e2e_encryption_policy, message_meta, message_priority,
190    packet::Packet,
191    reliable_mode, wire_format,
192};
193use alloc::string::{String, ToString};
194use alloc::{
195    borrow::ToOwned,
196    boxed::Box,
197    collections::{BTreeMap, BTreeSet, VecDeque},
198    format,
199    sync::Arc,
200    vec,
201    vec::Vec,
202};
203use core::cell::UnsafeCell;
204use core::fmt;
205use core::fmt::{Debug, Formatter};
206use core::mem::size_of;
207use core::ops::{Deref, DerefMut};
208use core::sync::atomic::{AtomicBool, AtomicU32, Ordering};
209#[cfg(feature = "std")]
210use std::time::Instant;
211
212/// Logical side index (CAN, UART, RADIO, etc.)
213pub type RouterSideId = usize;
214
215const CONTROL_SLOW_LINK_CAPACITY_BPS: u64 = 512;
216pub const IPV4_LIKE_COMPACT_HEADER_TARGET_BYTES: usize = 20;
217pub const IPV6_LIKE_COMPACT_HEADER_TARGET_BYTES: usize = 40;
218pub const DEFAULT_SIDE_TRANSPORT_TEMPLATE_LIMIT: usize = 64;
219static ROUTER_INSTANCE_SEQ: AtomicU32 = AtomicU32::new(1);
220
221#[derive(Clone, Copy, Debug, PartialEq, Eq)]
222pub enum SideTransportProfile {
223    Canonical,
224    Template,
225    Ipv6Like,
226    Ipv4Like,
227}
228
229impl SideTransportProfile {
230    #[inline]
231    pub const fn as_str(self) -> &'static str {
232        match self {
233            Self::Canonical => "canonical",
234            Self::Template => "template",
235            Self::Ipv6Like => "ipv6_like",
236            Self::Ipv4Like => "ipv4_like",
237        }
238    }
239
240    #[cfg(feature = "discovery")]
241    #[inline]
242    pub const fn discovery_code(self) -> u8 {
243        match self {
244            Self::Canonical => discovery::LINK_PROFILE_CANONICAL,
245            Self::Template => discovery::LINK_PROFILE_TEMPLATE,
246            Self::Ipv6Like => discovery::LINK_PROFILE_IPV6_LIKE,
247            Self::Ipv4Like => discovery::LINK_PROFILE_IPV4_LIKE,
248        }
249    }
250}
251
252#[cfg(test)]
253mod chunk_assembly_memory_tests {
254    use super::*;
255    #[test]
256    fn full_chunk_budget_reclaims_other_partial_transfers_before_active_schema() {
257        let mut state = SideTransportState::default();
258        for id in 1..=4 {
259            let mut assembly = SideChunkAssembly {
260                total: 28,
261                last_seen_ms: id as u64,
262                ..Default::default()
263            };
264            for index in 0..23 {
265                assembly.received.insert(
266                    index,
267                    crate::shared_bytes::convert(Arc::<[u8]>::from([id as u8; 112])),
268                );
269            }
270            state.rx_chunks.insert(id, assembly);
271        }
272        state.rx_chunks.get_mut(&1).unwrap().received.insert(
273            23,
274            crate::shared_bytes::convert(Arc::<[u8]>::from([1u8; 56])),
275        );
276        // 16192 + (56 + 64) = 16312 bytes, as in the Gateway capture.
277        SideChunkAssembly::make_room(&mut state.rx_chunks, 4, 23, 28, 112, 16384).unwrap();
278        assert!(!state.rx_chunks.contains_key(&1));
279        assert_eq!(state.rx_chunks[&4].received.len(), 23);
280        for index in 23..28 {
281            SideChunkAssembly::make_room(&mut state.rx_chunks, 4, index, 28, 112, 16384).unwrap();
282            state.rx_chunks.get_mut(&4).unwrap().received.insert(
283                index,
284                crate::shared_bytes::convert(Arc::<[u8]>::from([4u8; 112])),
285            );
286        }
287        assert_eq!(
288            state.rx_chunks.remove(&4).unwrap().assemble().unwrap(),
289            vec![4u8; 28 * 112]
290        );
291        assert!(
292            state
293                .rx_chunks
294                .values()
295                .flat_map(|a| a.received.values())
296                .map(|p| p.len() + 64)
297                .sum::<usize>()
298                <= 16384
299        );
300    }
301
302    #[test]
303    fn duplicate_chunk_keeps_partial_transfers_but_oversize_transfer_is_rejected() {
304        let mut state = SideTransportState::default();
305        let mut assembly = SideChunkAssembly {
306            total: 2,
307            ..Default::default()
308        };
309        assembly.received.insert(
310            0,
311            crate::shared_bytes::convert(Arc::<[u8]>::from([1u8; 112])),
312        );
313        state.rx_chunks.insert(1, assembly.clone());
314        state.rx_chunks.insert(2, assembly);
315        SideChunkAssembly::make_room(&mut state.rx_chunks, 1, 0, 2, 112, 352).unwrap();
316        assert_eq!(state.rx_chunks.len(), 2);
317        assert!(SideChunkAssembly::make_room(&mut state.rx_chunks, 1, 1, 2, 400, 352).is_err());
318        assert!(!state.rx_chunks.contains_key(&1));
319        assert!(state.rx_chunks.contains_key(&2));
320    }
321
322    #[test]
323    fn schema_sized_reassembly_allocates_exactly_and_preserves_order() {
324        let payload: Vec<u8> = (0..3652).map(|i| (i % 251) as u8).collect();
325        let mut assembly = SideChunkAssembly::default();
326        for (index, chunk) in payload.chunks(56).enumerate().rev() {
327            assembly.received.insert(
328                index as u16,
329                crate::shared_bytes::convert(Arc::<[u8]>::from(chunk)),
330            );
331        }
332        assembly.total = assembly.received.len() as u16;
333        let out = assembly.assemble().unwrap();
334        assert_eq!(out, payload);
335        assert_eq!(
336            out.capacity(),
337            payload.len(),
338            "do not double discovery reassembly capacity"
339        );
340    }
341    #[test]
342    fn incomplete_or_noncontiguous_chunks_are_rejected() {
343        let mut assembly = SideChunkAssembly {
344            total: 2,
345            ..Default::default()
346        };
347        assembly
348            .received
349            .insert(0, crate::shared_bytes::convert(Arc::<[u8]>::from([1u8])));
350        assert!(assembly.clone().assemble().is_err());
351        assembly
352            .received
353            .insert(2, crate::shared_bytes::convert(Arc::<[u8]>::from([2u8])));
354        assert!(assembly.assemble().is_err());
355    }
356}
357
358#[derive(Clone, Debug, Default)]
359struct SideTransportState {
360    tx_chunk_progress: Option<Box<crate::side_transport::SideTxProgress>>,
361    tx_template_ids: BTreeMap<u64, u32>,
362    tx_templates: BTreeMap<u64, SideHeaderTemplate>,
363    tx_last_timestamps: BTreeMap<u32, u64>,
364    tx_compact_uses: BTreeMap<u32, u8>,
365    rx_templates: BTreeMap<u64, u32>,
366    rx_templates_by_id: BTreeMap<u32, SideHeaderTemplate>,
367    rx_last_timestamps: BTreeMap<u32, u64>,
368    rx_chunks: BTreeMap<u32, SideChunkAssembly>,
369}
370
371impl SideTransportState {
372    fn refresh_transmit_header_templates(&mut self) {
373        // Refresh on the wire, not by forgetting the eviction history. RX
374        // retains its dictionary; clearing only TX makes bounded caches evict
375        // different entries after the next insertion/capacity negotiation.
376        for uses in self.tx_compact_uses.values_mut() {
377            *uses = u8::MAX;
378        }
379        // Local topology changes do not invalidate dictionaries received from
380        // peers. Dropping RX state here loses already-in-flight compact ACKs.
381    }
382
383    fn tx_template_count(&self) -> usize {
384        self.tx_template_ids.len()
385    }
386
387    fn rx_template_count(&self) -> usize {
388        self.rx_templates_by_id.len()
389    }
390
391    fn trim_tx_templates_to(&mut self, limit: usize) -> usize {
392        let mut removed = 0usize;
393        while self.tx_template_ids.len() > limit {
394            let Some(old_hash) = self.tx_template_ids.keys().next().copied() else {
395                break;
396            };
397            if let Some(old_id) = self.tx_template_ids.remove(&old_hash) {
398                self.tx_last_timestamps.remove(&old_id);
399                self.tx_compact_uses.remove(&old_id);
400            }
401            self.tx_templates.remove(&old_hash);
402            removed += 1;
403        }
404        removed
405    }
406
407    fn insert_tx_template(
408        &mut self,
409        template: SideHeaderTemplate,
410        template_id: u32,
411        max_templates: usize,
412    ) -> bool {
413        if max_templates == 0 {
414            return false;
415        }
416        let evicted = !self.tx_template_ids.contains_key(&template.hash)
417            && self.trim_tx_templates_to(max_templates.saturating_sub(1)) > 0;
418        self.tx_template_ids.insert(template.hash, template_id);
419        self.tx_templates.insert(template.hash, template);
420        self.tx_compact_uses.insert(template_id, 0);
421        evicted
422    }
423
424    fn insert_rx_template(
425        &mut self,
426        template_id: u32,
427        template: SideHeaderTemplate,
428        max_templates: usize,
429    ) -> bool {
430        if max_templates == 0 {
431            return false;
432        }
433        // Keep one owned header. The hash index needs only its wire ID, not
434        // another copy of the header and its shared-buffer owners.
435        if let Some(old_id) = self.rx_templates.get(&template.hash).copied()
436            && old_id != template_id
437        {
438            self.rx_templates_by_id.remove(&old_id);
439            self.rx_last_timestamps.remove(&old_id);
440            self.rx_templates.remove(&template.hash);
441        }
442        if let Some(previous) = self.rx_templates_by_id.get(&template_id)
443            && previous.hash != template.hash
444        {
445            self.rx_templates.remove(&previous.hash);
446            self.rx_last_timestamps.remove(&template_id);
447        }
448        let mut evicted = false;
449        if self.rx_templates_by_id.len() >= max_templates
450            && !self.rx_templates_by_id.contains_key(&template_id)
451            // Match TX's deterministic lowest-hash eviction on both routers
452            // and relays; lowest wire ID can select a different live header.
453            && let Some((&old_hash, &old_id)) = self.rx_templates.first_key_value()
454        {
455            self.rx_templates.remove(&old_hash);
456            self.rx_templates_by_id.remove(&old_id);
457            self.rx_last_timestamps.remove(&old_id);
458            evicted = true;
459        }
460        self.rx_templates.insert(template.hash, template_id);
461        self.rx_templates_by_id.insert(template_id, template);
462        evicted
463    }
464}
465
466#[derive(Clone, Debug, PartialEq, Eq)]
467pub struct CompactTimestampOmissionPolicy {
468    all: bool,
469    types: Option<Arc<[DataType]>>,
470}
471impl CompactTimestampOmissionPolicy {
472    pub const fn none() -> Self {
473        Self {
474            all: false,
475            types: None,
476        }
477    }
478    pub const fn all() -> Self {
479        Self {
480            all: true,
481            types: None,
482        }
483    }
484    pub fn with_data_type(mut self, ty: DataType) -> Self {
485        self.insert(ty);
486        self
487    }
488    pub fn insert(&mut self, ty: DataType) {
489        if self.contains(ty) {
490            return;
491        }
492        let current = self.types.as_deref().unwrap_or(&[]);
493        let mut types = Vec::with_capacity(current.len() + 1);
494        types.extend_from_slice(current);
495        let index = types.binary_search(&ty).unwrap_err();
496        types.insert(index, ty);
497        self.types = Some(Arc::from(types));
498    }
499    pub fn contains(&self, ty: DataType) -> bool {
500        self.all
501            || self
502                .types
503                .as_deref()
504                .is_some_and(|types| types.binary_search(&ty).is_ok())
505    }
506    pub fn is_empty(&self) -> bool {
507        !self.all && self.types.is_none()
508    }
509}
510
511impl Default for CompactTimestampOmissionPolicy {
512    #[inline]
513    fn default() -> Self {
514        Self::none()
515    }
516}
517
518#[derive(Clone, Debug, PartialEq, Eq)]
519pub enum RouterItem {
520    Packet(Packet),
521    Packed(crate::SharedBytes),
522}
523
524#[cfg(feature = "compact-packet-store")]
525impl RouterItem {
526    fn park_for_queue(&mut self) -> TelemetryResult<()> {
527        match self {
528            Self::Packet(packet) => packet.park_for_queue()?,
529            Self::Packed(bytes) => bytes.park_for_queue()?,
530        }
531        Ok(())
532    }
533}
534
535#[derive(Clone, Debug, PartialEq, Eq)]
536struct RouterRxItem {
537    src: Option<RouterSideId>,
538    data: RouterItem,
539    priority: u8,
540}
541
542#[derive(Clone, Debug, PartialEq, Eq)]
543enum RouterTxItem {
544    Broadcast(RouterItem),
545    EndToEndReplay {
546        packet_id: u64,
547    },
548    ToSide {
549        src: Option<RouterSideId>,
550        dst: RouterSideId,
551        data: RouterItem,
552    },
553    ReliableReplay {
554        dst: RouterSideId,
555        bytes: crate::SharedBytes,
556    },
557}
558
559impl ByteCost for RouterRxItem {
560    #[cfg(feature = "compact-packet-store")]
561    fn park_for_queue(&mut self) -> TelemetryResult<()> {
562        self.data.park_for_queue()
563    }
564    #[inline]
565    fn byte_cost(&self) -> usize {
566        match &self.data {
567            RouterItem::Packet(pkt) => pkt.byte_cost(),
568            RouterItem::Packed(bytes) => size_of::<crate::SharedBytes>() + bytes.len(),
569        }
570    }
571}
572
573impl ByteCost for RouterTxItem {
574    #[cfg(feature = "compact-packet-store")]
575    fn park_for_queue(&mut self) -> TelemetryResult<()> {
576        match self {
577            Self::Broadcast(item) | Self::ToSide { data: item, .. } => item.park_for_queue(),
578            Self::ReliableReplay { bytes, .. } => bytes.park_for_queue(),
579            _ => Ok(()),
580        }
581    }
582    #[inline]
583    fn byte_cost(&self) -> usize {
584        match self {
585            RouterTxItem::Broadcast(data) => match data {
586                RouterItem::Packet(pkt) => pkt.byte_cost(),
587                RouterItem::Packed(bytes) => size_of::<crate::SharedBytes>() + bytes.len(),
588            },
589            RouterTxItem::EndToEndReplay { .. } => size_of::<u64>(),
590            RouterTxItem::ToSide { data, .. } => match data {
591                RouterItem::Packet(pkt) => pkt.byte_cost(),
592                RouterItem::Packed(bytes) => size_of::<crate::SharedBytes>() + bytes.len(),
593            },
594            RouterTxItem::ReliableReplay { bytes, .. } => {
595                size_of::<crate::SharedBytes>() + bytes.len()
596            }
597        }
598    }
599}
600
601/// Transmit queue item with flags.
602/// Holds a RouterTxItem and a flag to ignore local dispatch.
603/// Used internally by the Router transmit queue.
604#[derive(Clone, Debug, PartialEq, Eq)]
605struct TxQueued {
606    item: RouterTxItem,
607    ignore_local: bool,
608    priority: u8,
609    retry_after_ms: u64,
610}
611
612/// Do not allow a continuously replenished stream of control traffic to keep
613/// an already-queued application packet from ever reaching a transport.
614/// Priorities still determine normal ordering; this only services the oldest
615/// lowest-priority item after a bounded high-priority burst.
616const MAX_PRIORITY_BURST: u8 = 8;
617
618/// ByteCost implementation for TxQueued.
619impl ByteCost for TxQueued {
620    #[cfg(feature = "compact-packet-store")]
621    fn park_for_queue(&mut self) -> TelemetryResult<()> {
622        self.item.park_for_queue()
623    }
624    /// Byte cost is the cost of the inner item plus one bool.
625    #[inline]
626    fn byte_cost(&self) -> usize {
627        self.item.byte_cost() + size_of::<bool>() + size_of::<u8>() + size_of::<u64>()
628    }
629}
630
631/// ByteCost implementation for `u64` (used by `recent_rx`).
632impl ByteCost for u64 {
633    /// Byte cost is size of u64.
634    #[inline]
635    fn byte_cost(&self) -> usize {
636        size_of::<u64>()
637    }
638}
639
640// -------------------- Reliable delivery state --------------------
641
642#[derive(Debug, Clone)]
643struct ReliableTxState {
644    next_seq: u32,
645    sent_order: VecDeque<u32>,
646    sent: BTreeMap<u32, ReliableSent>,
647}
648
649#[derive(Debug, Clone)]
650struct ReliableSent {
651    bytes: crate::SharedBytes,
652    last_send_ms: u64,
653    retries: u32,
654    queued: bool,
655    partial_acked: bool,
656}
657
658#[derive(Debug, Clone)]
659struct EndToEndReliableSent {
660    ordered_type: Option<u32>,
661    waiting_for_predecessor: bool,
662    data: RouterItem,
663    pending_destinations: BTreeMap<u64, RouterSideId>,
664    tracked_destinations: bool,
665    last_send_ms: u64,
666    retries: u32,
667    queued: bool,
668}
669
670#[derive(Debug, Clone)]
671struct ReliableRxState {
672    expected_seq: u32,
673    buffered: BTreeMap<u32, crate::SharedBytes>,
674}
675
676#[derive(Debug, Clone)]
677struct ReliableReturnRouteState {
678    side: RouterSideId,
679    last_replay_ms: u64,
680    // A legacy contract names this router, not its downstream owner.
681    // Translate the child receipt back to the router identity and original
682    // packet ID that the upstream publisher is awaiting.
683    upstream_contract: Option<(u64, u64)>,
684}
685
686#[cfg(feature = "discovery")]
687#[derive(Debug, Clone, Default, PartialEq, Eq)]
688struct DiscoverySenderState {
689    /// Numeric identity advertised for this hostname. Compact frames may carry
690    /// this address instead of the hostname, so routing must accept either.
691    advertised_address: Option<u32>,
692    reachable: Vec<DataEndpoint>,
693    advertised_reachable: Vec<DataEndpoint>,
694    reachable_network_variables: Vec<DataType>,
695    reachable_timesync_sources: Vec<String>,
696    advertised_reachable_timesync_sources: Vec<String>,
697    link_capabilities: Option<discovery::LinkCapabilities>,
698    topology_boards: Vec<TopologyBoardNode>,
699    has_full_topology: bool,
700    has_schema: bool,
701    last_seen_ms: u64,
702}
703
704#[cfg(feature = "discovery")]
705#[derive(Debug, Clone, Default, PartialEq, Eq)]
706struct DiscoverySideState {
707    reachable: Vec<DataEndpoint>,
708    reachable_network_variables: Vec<DataType>,
709    requested_network_variables: Vec<(DataType, u64)>,
710    reachable_timesync_sources: Vec<String>,
711    last_seen_ms: u64,
712    announcers: BTreeMap<String, DiscoverySenderState>,
713}
714
715#[cfg(feature = "discovery")]
716#[derive(Debug, Clone, Default)]
717struct DiscoverySideThrottleState {
718    next_topology_request_ms: u64,
719    next_schema_request_ms: u64,
720    next_ping_ms: u64,
721    // Scheduled and accepted sends differ when a constrained link is backed up.
722    last_liveness_tx_ms: u64,
723    pending_incremental: bool,
724    incremental_retries_remaining: u8,
725    has_sent_full: bool,
726    last_topology: Vec<TopologyBoardNode>,
727}
728
729#[cfg(feature = "discovery")]
730impl DiscoverySideThrottleState {
731    fn liveness_due(&self, now_ms: u64) -> bool {
732        now_ms >= self.next_ping_ms
733            && now_ms.saturating_sub(self.last_liveness_tx_ms)
734                >= DISCOVERY_SLOW_LINK_PING_INTERVAL_MS
735    }
736}
737
738#[cfg(all(feature = "discovery", feature = "timesync"))]
739#[derive(Debug, Clone, Default)]
740struct TimeSyncSideThrottleState {
741    next_allowed_ms: u64,
742}
743
744#[cfg(feature = "discovery")]
745#[derive(Debug, Clone, Copy, PartialEq, Eq)]
746enum DiscoveryAdvertiseLevel {
747    MinimalPing,
748    Incremental,
749    Full,
750}
751
752#[derive(Debug, Clone, Default)]
753struct AdaptiveRouteStats {
754    estimated_bandwidth_bps: u64,
755    peak_bandwidth_bps: u64,
756    last_observed_ms: u64,
757    last_slow_observed_ms: u64,
758    sample_count: u64,
759    window_started_ms: u64,
760    window_bytes: u64,
761    peak_usage_bps: u64,
762}
763
764impl AdaptiveRouteStats {
765    #[inline]
766    fn observe(&mut self, bytes: usize, sample_bps: u64, now_ms: u64) {
767        self.estimated_bandwidth_bps = if self.estimated_bandwidth_bps == 0 {
768            sample_bps
769        } else if sample_bps >= self.estimated_bandwidth_bps {
770            self.estimated_bandwidth_bps
771                .saturating_mul(3)
772                .saturating_add(sample_bps.saturating_mul(5))
773                / 8
774        } else {
775            self.estimated_bandwidth_bps
776                .saturating_mul(7)
777                .saturating_add(sample_bps)
778                / 8
779        };
780        self.peak_bandwidth_bps = self.peak_bandwidth_bps.max(sample_bps);
781        self.last_observed_ms = now_ms;
782        if sample_bps > 0 && sample_bps <= CONTROL_SLOW_LINK_CAPACITY_BPS {
783            self.last_slow_observed_ms = now_ms;
784        }
785        self.sample_count = self.sample_count.saturating_add(1);
786        if self.window_started_ms == 0 || now_ms.saturating_sub(self.window_started_ms) > 1_000 {
787            self.window_started_ms = now_ms;
788            self.window_bytes = 0;
789        }
790        self.window_bytes = self.window_bytes.saturating_add(bytes as u64);
791        self.peak_usage_bps = self.peak_usage_bps.max(self.current_usage_bps(now_ms));
792    }
793
794    #[inline]
795    fn current_usage_bps(&self, now_ms: u64) -> u64 {
796        if self.window_started_ms == 0 {
797            return 0;
798        }
799        let elapsed_ms = now_ms.saturating_sub(self.window_started_ms).max(1);
800        (u128::from(self.window_bytes).saturating_mul(1000) / u128::from(elapsed_ms))
801            .min(u128::from(u64::MAX)) as u64
802    }
803
804    #[inline]
805    fn available_headroom_bps(&self, now_ms: u64) -> u64 {
806        let capacity = self
807            .estimated_bandwidth_bps
808            .max(self.peak_bandwidth_bps)
809            .max(1);
810        capacity.saturating_sub(self.current_usage_bps(now_ms))
811    }
812
813    #[inline]
814    fn weight(&self, now_ms: u64) -> u64 {
815        self.available_headroom_bps(now_ms).max(1)
816    }
817
818    #[inline]
819    fn snapshot(&self, now_ms: u64, auto_balancing_enabled: bool) -> AdaptiveLinkStats {
820        let current_usage_bps = self.current_usage_bps(now_ms);
821        let estimated_capacity_bps = self.estimated_bandwidth_bps.max(1);
822        let peak_capacity_bps = self.peak_bandwidth_bps.max(estimated_capacity_bps);
823        let available_headroom_bps = peak_capacity_bps.saturating_sub(current_usage_bps);
824        AdaptiveLinkStats {
825            auto_balancing_enabled,
826            estimated_capacity_bps,
827            peak_capacity_bps,
828            current_usage_bps,
829            peak_usage_bps: self.peak_usage_bps.max(current_usage_bps),
830            available_headroom_bps,
831            effective_weight: available_headroom_bps.max(1),
832            last_observed_ms: self.last_observed_ms,
833            sample_count: self.sample_count,
834        }
835    }
836}
837
838#[derive(Debug, Clone, Default)]
839struct TypeRuntimeStatsInner {
840    tx_packets: u64,
841    tx_bytes: u64,
842    rx_packets: u64,
843    rx_bytes: u64,
844    relayed_tx_packets: u64,
845    relayed_tx_bytes: u64,
846    relayed_rx_packets: u64,
847    relayed_rx_bytes: u64,
848    tx_retries: u64,
849    handler_failures: u64,
850}
851
852#[derive(Debug, Clone, Default)]
853struct SideRuntimeStatsInner {
854    tx_packets: u64,
855    tx_bytes: u64,
856    rx_packets: u64,
857    rx_bytes: u64,
858    relayed_tx_packets: u64,
859    relayed_tx_bytes: u64,
860    relayed_rx_packets: u64,
861    relayed_rx_bytes: u64,
862    local_delivery_packets: u64,
863    tx_retries: u64,
864    tx_handler_failures: u64,
865    local_handler_failures: u64,
866    total_handler_retries: u64,
867    side_transport_full_frames: u64,
868    side_transport_compact_frames: u64,
869    side_transport_compact_delta_frames: u64,
870    side_transport_compact_omitted_timestamp_frames: u64,
871    side_transport_chunk_frames: u64,
872    side_transport_raw_bytes: u64,
873    side_transport_wire_bytes: u64,
874    side_transport_bytes_saved: u64,
875    side_transport_min_compact_overhead_bytes: Option<usize>,
876    side_transport_max_compact_overhead_bytes: Option<usize>,
877    side_transport_compact_target_misses: u64,
878    side_transport_template_evictions: u64,
879    data_types: BTreeMap<u32, TypeRuntimeStatsInner>,
880}
881
882impl SideRuntimeStatsInner {
883    fn type_stats_mut(&mut self, ty: DataType) -> &mut TypeRuntimeStatsInner {
884        self.data_types.entry(ty.as_u32()).or_default()
885    }
886
887    fn note_tx(&mut self, ty: DataType, bytes: usize, relayed: bool, retries: usize) {
888        self.tx_packets = self.tx_packets.saturating_add(1);
889        self.tx_bytes = self.tx_bytes.saturating_add(bytes as u64);
890        self.tx_retries = self.tx_retries.saturating_add(retries as u64);
891        self.total_handler_retries = self.total_handler_retries.saturating_add(retries as u64);
892        if relayed {
893            self.relayed_tx_packets = self.relayed_tx_packets.saturating_add(1);
894            self.relayed_tx_bytes = self.relayed_tx_bytes.saturating_add(bytes as u64);
895        }
896        let stats = self.type_stats_mut(ty);
897        stats.tx_packets = stats.tx_packets.saturating_add(1);
898        stats.tx_bytes = stats.tx_bytes.saturating_add(bytes as u64);
899        stats.tx_retries = stats.tx_retries.saturating_add(retries as u64);
900        if relayed {
901            stats.relayed_tx_packets = stats.relayed_tx_packets.saturating_add(1);
902            stats.relayed_tx_bytes = stats.relayed_tx_bytes.saturating_add(bytes as u64);
903        }
904    }
905
906    fn note_rx(&mut self, ty: DataType, bytes: usize, relayed: bool) {
907        self.rx_packets = self.rx_packets.saturating_add(1);
908        self.rx_bytes = self.rx_bytes.saturating_add(bytes as u64);
909        if relayed {
910            self.relayed_rx_packets = self.relayed_rx_packets.saturating_add(1);
911            self.relayed_rx_bytes = self.relayed_rx_bytes.saturating_add(bytes as u64);
912        }
913        let stats = self.type_stats_mut(ty);
914        stats.rx_packets = stats.rx_packets.saturating_add(1);
915        stats.rx_bytes = stats.rx_bytes.saturating_add(bytes as u64);
916        if relayed {
917            stats.relayed_rx_packets = stats.relayed_rx_packets.saturating_add(1);
918            stats.relayed_rx_bytes = stats.relayed_rx_bytes.saturating_add(bytes as u64);
919        }
920    }
921
922    fn note_local_delivery(&mut self, ty: DataType) {
923        self.local_delivery_packets = self.local_delivery_packets.saturating_add(1);
924        let stats = self.type_stats_mut(ty);
925        stats.rx_packets = stats.rx_packets.saturating_add(1);
926    }
927
928    fn note_local_handler_failure(&mut self, ty: DataType, retries: usize) {
929        self.local_handler_failures = self.local_handler_failures.saturating_add(1);
930        self.total_handler_retries = self.total_handler_retries.saturating_add(retries as u64);
931        let stats = self.type_stats_mut(ty);
932        stats.handler_failures = stats.handler_failures.saturating_add(1);
933    }
934
935    fn note_tx_failure(&mut self, ty: DataType, retries: usize) {
936        self.tx_handler_failures = self.tx_handler_failures.saturating_add(1);
937        self.total_handler_retries = self.total_handler_retries.saturating_add(retries as u64);
938        self.tx_retries = self.tx_retries.saturating_add(retries as u64);
939        let stats = self.type_stats_mut(ty);
940        stats.handler_failures = stats.handler_failures.saturating_add(1);
941        stats.tx_retries = stats.tx_retries.saturating_add(retries as u64);
942    }
943
944    fn note_side_transport_full(&mut self, raw_bytes: usize, wire_bytes: usize) {
945        self.side_transport_full_frames = self.side_transport_full_frames.saturating_add(1);
946        self.note_side_transport_bytes(raw_bytes, wire_bytes);
947    }
948
949    fn note_side_transport_compact(
950        &mut self,
951        raw_bytes: usize,
952        wire_bytes: usize,
953        compact_overhead_bytes: usize,
954        used_timestamp_delta: bool,
955        omitted_timestamp: bool,
956    ) {
957        self.side_transport_compact_frames = self.side_transport_compact_frames.saturating_add(1);
958        if used_timestamp_delta {
959            self.side_transport_compact_delta_frames =
960                self.side_transport_compact_delta_frames.saturating_add(1);
961        }
962        if omitted_timestamp {
963            self.side_transport_compact_omitted_timestamp_frames = self
964                .side_transport_compact_omitted_timestamp_frames
965                .saturating_add(1);
966        }
967        self.note_side_transport_bytes(raw_bytes, wire_bytes);
968        self.side_transport_min_compact_overhead_bytes = Some(
969            self.side_transport_min_compact_overhead_bytes
970                .map_or(compact_overhead_bytes, |v| v.min(compact_overhead_bytes)),
971        );
972        self.side_transport_max_compact_overhead_bytes = Some(
973            self.side_transport_max_compact_overhead_bytes
974                .map_or(compact_overhead_bytes, |v| v.max(compact_overhead_bytes)),
975        );
976    }
977
978    fn note_side_transport_chunks(&mut self, chunks: usize) {
979        self.side_transport_chunk_frames = self
980            .side_transport_chunk_frames
981            .saturating_add(chunks as u64);
982    }
983
984    fn note_side_transport_bytes(&mut self, raw_bytes: usize, wire_bytes: usize) {
985        self.side_transport_raw_bytes = self
986            .side_transport_raw_bytes
987            .saturating_add(raw_bytes as u64);
988        self.side_transport_wire_bytes = self
989            .side_transport_wire_bytes
990            .saturating_add(wire_bytes as u64);
991        if raw_bytes > wire_bytes {
992            self.side_transport_bytes_saved = self
993                .side_transport_bytes_saved
994                .saturating_add((raw_bytes - wire_bytes) as u64);
995        }
996    }
997
998    fn note_side_transport_compact_target_miss(&mut self) {
999        self.side_transport_compact_target_misses =
1000            self.side_transport_compact_target_misses.saturating_add(1);
1001    }
1002
1003    fn note_side_transport_template_eviction(&mut self) {
1004        self.side_transport_template_evictions =
1005            self.side_transport_template_evictions.saturating_add(1);
1006    }
1007}
1008
1009#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1010enum RouteSelectionOrigin {
1011    Flood,
1012    Discovered,
1013}
1014
1015// -------------------- endpoint + board config --------------------
1016/// Packet Handler function type
1017type PacketHandlerFn = dyn Fn(&Packet) -> TelemetryResult<()> + Send + Sync + 'static;
1018
1019/// Packed Handler function type
1020type PackedHandlerFn = dyn Fn(&[u8]) -> TelemetryResult<()> + Send + Sync + 'static;
1021type PrioritizedPackedHandlerFn = dyn Fn(&[u8], u8) -> TelemetryResult<()> + Send + Sync + 'static;
1022
1023// Make handlers usable across tasks
1024/// Endpoint handler function enum.
1025/// Holds either a `Packet` handler or a packed byte-slice handler.
1026/// /// - Packet handler signature: `Fn(&Packet) -> TelemetryResult<()>`
1027/// /// - Packed handler signature: `Fn(&[u8]) -> TelemetryResult<()>`
1028#[derive(Clone)]
1029pub enum EndpointHandlerFn {
1030    Packet(Arc<PacketHandlerFn>),
1031    Packed(Arc<PackedHandlerFn>),
1032}
1033
1034/// Endpoint handler for a specific data endpoint.
1035pub struct EndpointHandler {
1036    endpoint: DataEndpoint,
1037    handler: EndpointHandlerFn,
1038}
1039
1040/// Debug implementation for EndpointHandlerFn.
1041impl Debug for EndpointHandlerFn {
1042    #[inline]
1043    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1044        match self {
1045            EndpointHandlerFn::Packet(_) => f.write_str("EndpointHandlerFn::Packet(<handler>)"),
1046            EndpointHandlerFn::Packed(_) => f.write_str("EndpointHandlerFn::Packed(<handler>)"),
1047        }
1048    }
1049}
1050
1051/// TX handler for a router side: either packed or packet-based.
1052#[derive(Clone)]
1053pub enum RouterTxHandlerFn {
1054    Packed(Arc<PackedHandlerFn>),
1055    PackedWithPriority(Arc<PrioritizedPackedHandlerFn>),
1056    Packet(Arc<PacketHandlerFn>),
1057}
1058
1059impl Debug for RouterTxHandlerFn {
1060    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1061        match self {
1062            RouterTxHandlerFn::Packed(_) => f.debug_tuple("Packed").field(&"<handler>").finish(),
1063            RouterTxHandlerFn::PackedWithPriority(_) => f
1064                .debug_tuple("PackedWithPriority")
1065                .field(&"<handler>")
1066                .finish(),
1067            RouterTxHandlerFn::Packet(_) => f.debug_tuple("Packet").field(&"<handler>").finish(),
1068        }
1069    }
1070}
1071
1072#[derive(Clone, Debug)]
1073pub struct RouterSideOptions {
1074    /// Enables the router's per-link reliable transport layer on this side.
1075    ///
1076    /// When `true` and the side uses a packed TX handler, reliable schema types gain
1077    /// sequence numbers, ACKs, packet requests, and retransmits on this specific hop.
1078    /// When `false`, the router strips the reliable framing for that side and sends only the
1079    /// application packet payload once.
1080    ///
1081    /// This setting only affects the hop between this router and the side's TX callback. It does
1082    /// not change whether a schema `DataType` is defined as reliable, and it does not disable the
1083    /// router's end-to-end reliable tracking for packets originated elsewhere in the network.
1084    pub reliable_enabled: bool,
1085    /// Marks the side as eligible for link-local-only endpoints and discovery routes.
1086    pub link_local_enabled: bool,
1087    /// Enables a side-local header-template dictionary for packed transport.
1088    ///
1089    /// The first frame for a stable header shape is sent in full. Later frames
1090    /// on the same side can replace the repeated static header bytes with a
1091    /// compact template hash plus only the fields that still vary packet to
1092    /// packet.
1093    pub header_template_enabled: bool,
1094    /// Maximum number of bytes to emit per packed TX callback.
1095    ///
1096    /// When non-zero and a wrapped packed frame would exceed this size, the
1097    /// router splits it into ordered chunks and reassembles those chunks on RX
1098    /// before normal packet processing.
1099    pub max_frame_bytes: usize,
1100    /// Target total side-transport overhead for compact follow-up frames.
1101    ///
1102    /// This is a profiling/negotiation target rather than a hard limit. The
1103    /// canonical packet is still reconstructed before normal router handling,
1104    /// so constrained links should watch runtime stats to confirm compact
1105    /// frames are meeting their IPv4/IPv6-like overhead budget.
1106    pub compact_header_target_bytes: usize,
1107    /// Maximum side-local header templates retained for TX and RX dictionaries.
1108    ///
1109    /// This keeps compact-link state bounded. When the dictionary is full, the
1110    /// oldest deterministic entry is evicted and a later packet with that
1111    /// shape will refresh the receiver with a full template frame.
1112    pub max_side_transport_templates: usize,
1113    /// Omits the timestamp field from compact follow-up frames when it is unchanged.
1114    ///
1115    /// The receiver reconstructs the canonical packet from the previous timestamp for that
1116    /// side-local template. This is only used after a full or compact frame has established
1117    /// timestamp context.
1118    pub omit_unchanged_compact_timestamps: bool,
1119    /// Optional per-data-type timestamp omission policy for compact follow-up frames.
1120    ///
1121    /// This allows a side to omit unchanged timestamps only for selected traffic while keeping
1122    /// absolute/delta timestamps for other data types on the same link.
1123    pub compact_timestamp_omission_types: CompactTimestampOmissionPolicy,
1124    /// Declared compact-link profile for stats and future negotiation.
1125    pub side_transport_profile: SideTransportProfile,
1126    /// Allows packets received from this side to enter router processing.
1127    pub ingress_enabled: bool,
1128    /// Allows the router to transmit packets toward this side.
1129    pub egress_enabled: bool,
1130}
1131
1132impl Default for RouterSideOptions {
1133    fn default() -> Self {
1134        Self {
1135            reliable_enabled: false,
1136            link_local_enabled: false,
1137            header_template_enabled: false,
1138            max_frame_bytes: 0,
1139            compact_header_target_bytes: 0,
1140            max_side_transport_templates: DEFAULT_SIDE_TRANSPORT_TEMPLATE_LIMIT,
1141            omit_unchanged_compact_timestamps: false,
1142            compact_timestamp_omission_types: CompactTimestampOmissionPolicy::none(),
1143            side_transport_profile: SideTransportProfile::Canonical,
1144            ingress_enabled: true,
1145            egress_enabled: true,
1146        }
1147    }
1148}
1149
1150impl RouterSideOptions {
1151    /// Convenience preset for compact packed-side transport.
1152    ///
1153    /// This enables header-template reuse and, when `max_frame_bytes > 0`,
1154    /// router-managed chunking/reassembly for fixed-size transports.
1155    #[inline]
1156    pub fn with_small_packet_transport(mut self, max_frame_bytes: usize) -> Self {
1157        self.header_template_enabled = true;
1158        self.max_frame_bytes = max_frame_bytes;
1159        self.compact_header_target_bytes = IPV6_LIKE_COMPACT_HEADER_TARGET_BYTES;
1160        self.side_transport_profile = SideTransportProfile::Ipv6Like;
1161        self
1162    }
1163
1164    #[inline]
1165    pub fn with_ipv4_like_compact_header_target(mut self) -> Self {
1166        self.header_template_enabled = true;
1167        self.compact_header_target_bytes = IPV4_LIKE_COMPACT_HEADER_TARGET_BYTES;
1168        self.omit_unchanged_compact_timestamps = true;
1169        self.side_transport_profile = SideTransportProfile::Ipv4Like;
1170        self
1171    }
1172
1173    #[inline]
1174    pub fn with_ipv6_like_compact_header_target(mut self) -> Self {
1175        self.header_template_enabled = true;
1176        self.compact_header_target_bytes = IPV6_LIKE_COMPACT_HEADER_TARGET_BYTES;
1177        self.side_transport_profile = SideTransportProfile::Ipv6Like;
1178        self
1179    }
1180
1181    #[inline]
1182    pub fn with_template_transport(mut self) -> Self {
1183        self.header_template_enabled = true;
1184        self.side_transport_profile = SideTransportProfile::Template;
1185        self
1186    }
1187
1188    #[inline]
1189    pub fn with_omitted_unchanged_compact_timestamps(mut self) -> Self {
1190        self.header_template_enabled = true;
1191        self.omit_unchanged_compact_timestamps = true;
1192        self
1193    }
1194
1195    #[inline]
1196    pub fn with_omitted_unchanged_compact_timestamps_for_type(mut self, ty: DataType) -> Self {
1197        self.header_template_enabled = true;
1198        self.compact_timestamp_omission_types.insert(ty);
1199        self
1200    }
1201
1202    #[inline]
1203    pub fn effective_transport_profile(&self) -> SideTransportProfile {
1204        if !self.header_template_enabled && self.max_frame_bytes == 0 {
1205            SideTransportProfile::Canonical
1206        } else if self.side_transport_profile == SideTransportProfile::Canonical {
1207            SideTransportProfile::Template
1208        } else {
1209            self.side_transport_profile
1210        }
1211    }
1212
1213    #[cfg(feature = "discovery")]
1214    #[inline]
1215    pub fn link_capabilities(&self) -> discovery::LinkCapabilities {
1216        let mut flags = discovery::LINK_CAPABILITY_END_TO_END_RELIABILITY;
1217        if self.header_template_enabled {
1218            flags |= discovery::LINK_CAPABILITY_HEADER_TEMPLATES;
1219        }
1220        if self.max_frame_bytes != 0 {
1221            flags |= discovery::LINK_CAPABILITY_CHUNKING;
1222        }
1223        if self.reliable_enabled {
1224            flags |= discovery::LINK_CAPABILITY_RELIABILITY;
1225        }
1226        if self.omit_unchanged_compact_timestamps
1227            || !self.compact_timestamp_omission_types.is_empty()
1228        {
1229            flags |= discovery::LINK_CAPABILITY_OMIT_UNCHANGED_TIMESTAMPS;
1230        }
1231        #[cfg(feature = "cryptography")]
1232        {
1233            flags |= discovery::LINK_CAPABILITY_CRYPTO;
1234        }
1235        discovery::LinkCapabilities {
1236            version: 1,
1237            flags,
1238            profile: self.effective_transport_profile().discovery_code(),
1239            max_frame_bytes: self.max_frame_bytes.min(u32::MAX as usize) as u32,
1240            compact_header_target_bytes: self.compact_header_target_bytes.min(u32::MAX as usize)
1241                as u32,
1242            max_side_transport_templates: self.max_side_transport_templates.min(u32::MAX as usize)
1243                as u32,
1244        }
1245    }
1246}
1247
1248/// One side of the router – a name + TX handler.
1249#[derive(Clone, Debug)]
1250pub struct RouterSide {
1251    pub name: Arc<str>,
1252    pub tx_handler: RouterTxHandlerFn,
1253    pub opts: RouterSideOptions,
1254}
1255
1256/// Debug implementation for EndpointHandler.
1257impl Debug for EndpointHandler {
1258    #[inline]
1259    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1260        f.debug_struct("EndpointHandler")
1261            .field("endpoint", &self.endpoint)
1262            .field("handler", &self.handler)
1263            .finish()
1264    }
1265}
1266
1267#[inline]
1268pub(crate) const fn endpoint_is_router_internal(endpoint: DataEndpoint) -> bool {
1269    #[cfg(feature = "timesync")]
1270    if matches!(endpoint, DataEndpoint::TimeSync) {
1271        return true;
1272    }
1273    #[cfg(feature = "discovery")]
1274    {
1275        discovery::is_discovery_endpoint(endpoint)
1276    }
1277    #[cfg(not(feature = "discovery"))]
1278    {
1279        let _ = endpoint;
1280        false
1281    }
1282}
1283
1284impl EndpointHandler {
1285    /// Create a new endpoint handler for `Packet` callbacks.
1286    ///
1287    /// Handler signature is `Fn(&Packet) -> TelemetryResult<()>`.
1288    #[inline]
1289    pub fn new_packet_handler<F>(endpoint: DataEndpoint, f: F) -> Self
1290    where
1291        F: Fn(&Packet) -> TelemetryResult<()> + Send + Sync + 'static,
1292    {
1293        assert!(
1294            !endpoint_is_router_internal(endpoint),
1295            "reserved internal endpoint handlers must not be user-registered"
1296        );
1297        #[cfg(feature = "std")]
1298        crate::config::ensure_endpoint_id(endpoint, false)
1299            .expect("endpoint handler endpoint registration failed");
1300        Self {
1301            endpoint,
1302            handler: EndpointHandlerFn::Packet(Arc::new(f)),
1303        }
1304    }
1305
1306    /// Create a new packet handler from a runtime endpoint definition.
1307    #[inline]
1308    #[cfg(feature = "std")]
1309    pub fn new_packet_handler_for<F>(endpoint: crate::config::OwnedEndpointDefinition, f: F) -> Self
1310    where
1311        F: Fn(&Packet) -> TelemetryResult<()> + Send + Sync + 'static,
1312    {
1313        Self::new_packet_handler(endpoint.id, f)
1314    }
1315
1316    /// Create a new packet handler from a static endpoint definition.
1317    #[cfg(not(feature = "std"))]
1318    #[inline]
1319    pub fn new_packet_handler_for<F>(endpoint: crate::config::EndpointDefinition, f: F) -> Self
1320    where
1321        F: Fn(&Packet) -> TelemetryResult<()> + Send + Sync + 'static,
1322    {
1323        Self::new_packet_handler(endpoint.id, f)
1324    }
1325
1326    /// Create a new packet handler by endpoint name.
1327    #[cfg(feature = "std")]
1328    #[inline]
1329    pub fn new_packet_handler_by_name<F>(endpoint_name: &str, f: F) -> TelemetryResult<Self>
1330    where
1331        F: Fn(&Packet) -> TelemetryResult<()> + Send + Sync + 'static,
1332    {
1333        let endpoint = crate::config::endpoint_definition_by_name(endpoint_name)
1334            .ok_or(TelemetryError::BadArg)?;
1335        Ok(Self::new_packet_handler(endpoint.id, f))
1336    }
1337
1338    /// Create a new endpoint handler for packed byte-slice callbacks.
1339    ///
1340    /// Handler signature is `Fn(&[u8]) -> TelemetryResult<()>`.
1341    #[inline]
1342    pub fn new_packed_handler<F>(endpoint: DataEndpoint, f: F) -> Self
1343    where
1344        F: Fn(&[u8]) -> TelemetryResult<()> + Send + Sync + 'static,
1345    {
1346        assert!(
1347            !endpoint_is_router_internal(endpoint),
1348            "reserved internal endpoint handlers must not be user-registered"
1349        );
1350        #[cfg(feature = "std")]
1351        crate::config::ensure_endpoint_id(endpoint, false)
1352            .expect("endpoint handler endpoint registration failed");
1353        Self {
1354            endpoint,
1355            handler: EndpointHandlerFn::Packed(Arc::new(f)),
1356        }
1357    }
1358
1359    /// Create a new packed handler from a runtime endpoint definition.
1360    #[inline]
1361    pub fn new_packed_handler_for<F>(endpoint: crate::config::EndpointDefinition, f: F) -> Self
1362    where
1363        F: Fn(&[u8]) -> TelemetryResult<()> + Send + Sync + 'static,
1364    {
1365        Self::new_packed_handler(endpoint.id, f)
1366    }
1367
1368    /// Create a new packed handler by endpoint name.
1369    #[cfg(feature = "std")]
1370    #[inline]
1371    pub fn new_packed_handler_by_name<F>(endpoint_name: &str, f: F) -> TelemetryResult<Self>
1372    where
1373        F: Fn(&[u8]) -> TelemetryResult<()> + Send + Sync + 'static,
1374    {
1375        let endpoint = crate::config::endpoint_definition_by_name(endpoint_name)
1376            .ok_or(TelemetryError::BadArg)?;
1377        Ok(Self::new_packed_handler(endpoint.id, f))
1378    }
1379
1380    /// Return the endpoint that the handler is registered for.
1381    #[inline]
1382    pub fn get_endpoint(&self) -> DataEndpoint {
1383        self.endpoint
1384    }
1385
1386    /// Return a reference to the handler function.
1387    #[inline]
1388    pub fn get_handler(&self) -> &EndpointHandlerFn {
1389        &self.handler
1390    }
1391}
1392
1393pub trait Clock {
1394    /// Return the current time in milliseconds.
1395    fn now_ms(&self) -> u64;
1396
1397    /// Return the current time in nanoseconds.
1398    ///
1399    /// The default implementation derives this from [`Clock::now_ms`].
1400    fn now_ns(&self) -> u64 {
1401        self.now_ms().saturating_mul(1_000_000)
1402    }
1403}
1404
1405impl<T: Fn() -> u64> Clock for T {
1406    #[inline]
1407    fn now_ms(&self) -> u64 {
1408        self()
1409    }
1410}
1411
1412#[cfg(feature = "std")]
1413#[derive(Debug)]
1414struct StdMonotonicClock {
1415    start: Instant,
1416}
1417
1418#[cfg(feature = "std")]
1419impl Default for StdMonotonicClock {
1420    fn default() -> Self {
1421        Self {
1422            start: Instant::now(),
1423        }
1424    }
1425}
1426
1427#[cfg(feature = "std")]
1428impl Clock for StdMonotonicClock {
1429    fn now_ms(&self) -> u64 {
1430        u64::try_from(self.start.elapsed().as_millis()).unwrap_or(u64::MAX)
1431    }
1432
1433    fn now_ns(&self) -> u64 {
1434        u64::try_from(self.start.elapsed().as_nanos()).unwrap_or(u64::MAX)
1435    }
1436}
1437
1438/// Router-level E2E cryptography behavior.
1439#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1440pub enum RouterE2eEncryptionMode {
1441    /// Do not use E2E cryptography. Data types marked `RequireOn` are rejected.
1442    Disabled,
1443    /// Use E2E cryptography only for data types that require it.
1444    RequiredOnly,
1445    /// Use E2E cryptography for required and preferred data types.
1446    Preferred,
1447    /// Require E2E cryptography for every non-control data type.
1448    ForceAll,
1449}
1450
1451pub type NodeAddress = u32;
1452pub type P2pPort = u16;
1453pub type P2pStreamId = u32;
1454
1455const P2P_STREAM_MAGIC: [u8; 4] = *b"SDSP";
1456const P2P_STREAM_VERSION: u8 = 1;
1457const P2P_STREAM_SYN: u8 = 0x01;
1458const P2P_STREAM_ACK: u8 = 0x02;
1459const P2P_STREAM_FIN: u8 = 0x04;
1460const P2P_STREAM_RST: u8 = 0x08;
1461const P2P_STREAM_DATA: u8 = 0x10;
1462
1463#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1464pub enum AddressAssignmentMode {
1465    Dynamic,
1466    Requested(NodeAddress),
1467    Static(NodeAddress),
1468}
1469
1470impl AddressAssignmentMode {
1471    #[inline]
1472    fn mode_code(self) -> u8 {
1473        match self {
1474            Self::Dynamic => 0,
1475            Self::Requested(_) => 1,
1476            Self::Static(_) => 2,
1477        }
1478    }
1479
1480    #[inline]
1481    fn requested_address(self) -> NodeAddress {
1482        match self {
1483            Self::Dynamic => 0,
1484            Self::Requested(addr) | Self::Static(addr) => addr,
1485        }
1486    }
1487}
1488
1489#[derive(Debug, Clone, PartialEq, Eq)]
1490pub enum AddressChangeReason {
1491    Configured,
1492    DynamicConflict,
1493    RequestedConflict,
1494    StaticConflict,
1495    HostnameConflict,
1496}
1497
1498#[derive(Debug, Clone, PartialEq, Eq)]
1499pub struct AddressChange {
1500    pub old_hostname: Arc<str>,
1501    pub new_hostname: Arc<str>,
1502    pub old_address: NodeAddress,
1503    pub new_address: NodeAddress,
1504    pub reason: AddressChangeReason,
1505}
1506
1507#[derive(Debug, Clone, PartialEq, Eq)]
1508pub struct AddressBookEntry {
1509    pub hostname: Arc<str>,
1510    pub address: NodeAddress,
1511    pub requested_address: NodeAddress,
1512    pub mode: AddressAssignmentMode,
1513    pub birth_ms: u64,
1514    pub owner_hash: u64,
1515    pub last_seen_ms: u64,
1516}
1517
1518pub struct P2pMessage<'a> {
1519    pub source_hostname: &'a str,
1520    pub source_address: NodeAddress,
1521    pub source_port: P2pPort,
1522    pub destination_port: P2pPort,
1523    pub payload: &'a [u8],
1524}
1525
1526#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1527pub enum P2pStreamEventKind {
1528    Accepted,
1529    Connected,
1530    Data,
1531    Closed,
1532    Reset,
1533}
1534
1535pub struct P2pStreamEvent<'a> {
1536    pub kind: P2pStreamEventKind,
1537    pub stream_id: P2pStreamId,
1538    pub peer_stream_id: P2pStreamId,
1539    pub sequence: u32,
1540    pub peer_hostname: &'a str,
1541    pub peer_address: NodeAddress,
1542    pub local_port: P2pPort,
1543    pub peer_port: P2pPort,
1544    pub payload: &'a [u8],
1545}
1546
1547struct P2pDecoded<'a> {
1548    source_hostname: &'a str,
1549    source_address: NodeAddress,
1550    source_port: P2pPort,
1551    destination_port: P2pPort,
1552    payload: &'a [u8],
1553}
1554
1555#[derive(Debug, Clone)]
1556struct P2pStreamDecoded<'a> {
1557    flags: u8,
1558    source_stream_id: P2pStreamId,
1559    destination_stream_id: P2pStreamId,
1560    sequence: u32,
1561    payload: &'a [u8],
1562}
1563
1564type AddressChangeFn = dyn Fn(AddressChange) -> TelemetryResult<()> + Send + Sync + 'static;
1565
1566#[derive(Clone)]
1567struct AddressChangeHandler {
1568    handler: Arc<AddressChangeFn>,
1569}
1570
1571impl Debug for AddressChangeHandler {
1572    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1573        f.write_str("AddressChangeHandler(<handler>)")
1574    }
1575}
1576
1577type P2pPortHandlerFn = dyn Fn(P2pMessage<'_>) -> TelemetryResult<()> + Send + Sync + 'static;
1578
1579#[derive(Clone)]
1580struct P2pPortHandler {
1581    handler: Arc<P2pPortHandlerFn>,
1582}
1583
1584impl Debug for P2pPortHandler {
1585    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1586        f.write_str("P2pPortHandler(<handler>)")
1587    }
1588}
1589
1590type P2pStreamHandlerFn = dyn Fn(P2pStreamEvent<'_>) -> TelemetryResult<()> + Send + Sync + 'static;
1591
1592#[derive(Clone)]
1593struct P2pStreamHandler {
1594    handler: Arc<P2pStreamHandlerFn>,
1595}
1596
1597impl Debug for P2pStreamHandler {
1598    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1599        f.write_str("P2pStreamHandler(<handler>)")
1600    }
1601}
1602
1603#[derive(Debug, Clone)]
1604struct P2pStreamSession {
1605    peer_hostname: Arc<str>,
1606    peer_address: NodeAddress,
1607    local_port: P2pPort,
1608    peer_port: P2pPort,
1609    peer_stream_id: P2pStreamId,
1610    next_sequence: u32,
1611    connected: bool,
1612}
1613
1614#[derive(Debug, Clone)]
1615struct PendingP2pStreamEvent {
1616    handlers: Vec<P2pStreamHandler>,
1617    kind: P2pStreamEventKind,
1618    stream_id: P2pStreamId,
1619    peer_stream_id: P2pStreamId,
1620    peer_hostname: Arc<str>,
1621    peer_address: NodeAddress,
1622    local_port: P2pPort,
1623    peer_port: P2pPort,
1624}
1625
1626#[derive(Debug, Clone)]
1627pub struct RouterConfig {
1628    /// Handlers for local endpoints.
1629    handlers: Arc<[EndpointHandler]>,
1630    /// Whether to enable reliable ordering/ACKs for reliable data types.
1631    reliable_enabled: bool,
1632    /// Optional per-router sender override.
1633    sender: Option<Arc<str>>,
1634    /// Address assignment policy used for P2P routing.
1635    address_mode: AddressAssignmentMode,
1636    /// Callbacks invoked when conflict resolution changes this router's identity.
1637    address_change_handlers: Arc<[AddressChangeHandler]>,
1638    /// End-to-end cryptography behavior for user data.
1639    e2e_encryption: RouterE2eEncryptionMode,
1640    /// Application-defined key id passed to the cryptography provider.
1641    #[cfg_attr(not(feature = "cryptography"), allow(dead_code))]
1642    e2e_key_id: u32,
1643    memory: RuntimeMemoryConfig,
1644    #[cfg(feature = "discovery")]
1645    preferred_discovery_master: Option<Arc<str>>,
1646    #[cfg(feature = "timesync")]
1647    timesync: Option<TimeSyncConfig>,
1648}
1649
1650impl RouterConfig {
1651    /// Default router E2E mode for this build.
1652    ///
1653    /// Builds with cryptography prefer encrypted payloads automatically for data types that request
1654    /// it. Minimal builds without cryptography stay disabled and reject `RequireOn` traffic.
1655    pub fn default_e2e_encryption_mode() -> RouterE2eEncryptionMode {
1656        #[cfg(feature = "cryptography")]
1657        {
1658            RouterE2eEncryptionMode::Preferred
1659        }
1660        #[cfg(not(feature = "cryptography"))]
1661        {
1662            RouterE2eEncryptionMode::Disabled
1663        }
1664    }
1665
1666    /// Create a new router configuration with the specified local endpoint handlers.
1667    pub fn new<H>(handlers: H) -> Self
1668    where
1669        H: Into<Arc<[EndpointHandler]>>,
1670    {
1671        let handlers: Arc<[EndpointHandler]> = handlers.into();
1672        assert!(
1673            handlers
1674                .iter()
1675                .all(|handler| !endpoint_is_router_internal(handler.endpoint)),
1676            "reserved internal endpoint handlers must not be user-registered"
1677        );
1678        Self {
1679            handlers,
1680            reliable_enabled: true,
1681            sender: None,
1682            address_mode: AddressAssignmentMode::Dynamic,
1683            address_change_handlers: Arc::from([]),
1684            e2e_encryption: Self::default_e2e_encryption_mode(),
1685            e2e_key_id: 0,
1686            memory: RuntimeMemoryConfig::default(),
1687            #[cfg(feature = "discovery")]
1688            preferred_discovery_master: None,
1689            #[cfg(feature = "timesync")]
1690            timesync: None,
1691        }
1692    }
1693
1694    /// Enable or disable reliable delivery for this router instance.
1695    pub fn with_reliable_enabled(mut self, enabled: bool) -> Self {
1696        self.reliable_enabled = enabled;
1697        self
1698    }
1699
1700    /// Override the sender identifier for this router instance.
1701    pub fn with_sender<S: AsRef<str>>(mut self, sender: S) -> Self {
1702        self.sender = Some(Arc::from(sender.as_ref()));
1703        self
1704    }
1705
1706    /// Alias for `with_sender`; discovery uses this name for P2P service resolution.
1707    pub fn with_hostname<S: AsRef<str>>(self, hostname: S) -> Self {
1708        self.with_sender(hostname)
1709    }
1710
1711    /// Request a dynamic address assigned by the discovered network authority.
1712    pub fn with_dynamic_address(mut self) -> Self {
1713        self.address_mode = AddressAssignmentMode::Dynamic;
1714        self
1715    }
1716
1717    /// Request a preferred address. It is kept when unique and reassigned on conflict.
1718    pub fn with_requested_address(mut self, address: NodeAddress) -> Self {
1719        self.address_mode = AddressAssignmentMode::Requested(address);
1720        self
1721    }
1722
1723    /// Require a static address. If two static nodes conflict, the oldest keeps it.
1724    pub fn with_static_address(mut self, address: NodeAddress) -> Self {
1725        self.address_mode = AddressAssignmentMode::Static(address);
1726        self
1727    }
1728
1729    /// Register a callback for local address or hostname changes after conflict resolution.
1730    pub fn on_address_change<F>(mut self, f: F) -> Self
1731    where
1732        F: Fn(AddressChange) -> TelemetryResult<()> + Send + Sync + 'static,
1733    {
1734        let mut handlers = self.address_change_handlers.to_vec();
1735        handlers.push(AddressChangeHandler {
1736            handler: Arc::new(f),
1737        });
1738        self.address_change_handlers = Arc::from(handlers);
1739        self
1740    }
1741
1742    /// Configure this router's end-to-end cryptography policy.
1743    pub fn with_e2e_encryption(mut self, mode: RouterE2eEncryptionMode) -> Self {
1744        self.e2e_encryption = mode;
1745        self
1746    }
1747
1748    /// Configure the application-defined key id used for E2E encrypted payloads.
1749    pub fn with_e2e_key_id(mut self, key_id: u32) -> Self {
1750        self.e2e_key_id = key_id;
1751        self
1752    }
1753
1754    /// Override per-router queue and dedupe memory limits.
1755    pub fn with_memory_config(mut self, memory: RuntimeMemoryConfig) -> TelemetryResult<Self> {
1756        memory.validate()?;
1757        self.memory = memory;
1758        Ok(self)
1759    }
1760
1761    /// Prefer a named router as discovery master whenever it is visible.
1762    ///
1763    /// All routers in a deployment should use the same preference. If that
1764    /// router is absent, normal topology-based election is used. Discovery
1765    /// leadership remains independent from time-sync source priority.
1766    #[cfg(feature = "discovery")]
1767    pub fn with_preferred_discovery_master<S: AsRef<str>>(mut self, hostname: S) -> Self {
1768        let hostname = hostname.as_ref();
1769        self.preferred_discovery_master = if hostname.is_empty() {
1770            None
1771        } else {
1772            Some(Arc::from(hostname))
1773        };
1774        self
1775    }
1776
1777    #[cfg(feature = "timesync")]
1778    /// Enables and configures built-in time synchronization for this router.
1779    pub fn with_timesync(mut self, cfg: TimeSyncConfig) -> Self {
1780        self.timesync = Some(cfg);
1781        self
1782    }
1783
1784    #[inline]
1785    /// Check if the specified endpoint is local to this router.
1786    fn is_local_endpoint(&self, ep: DataEndpoint) -> bool {
1787        if endpoint_is_router_internal(ep) {
1788            return false;
1789        }
1790        self.handlers.iter().any(|h| h.endpoint == ep)
1791    }
1792
1793    #[inline]
1794    fn reliable_enabled(&self) -> bool {
1795        self.reliable_enabled
1796    }
1797
1798    #[inline]
1799    fn sender(&self) -> String {
1800        self.sender
1801            .as_deref()
1802            .map(ToString::to_string)
1803            .unwrap_or_else(runtime_device_identifier)
1804    }
1805
1806    #[inline]
1807    fn address_mode(&self) -> AddressAssignmentMode {
1808        self.address_mode
1809    }
1810
1811    #[inline]
1812    fn e2e_encryption(&self) -> RouterE2eEncryptionMode {
1813        self.e2e_encryption
1814    }
1815
1816    #[cfg(feature = "cryptography")]
1817    #[inline]
1818    fn e2e_key_id(&self) -> u32 {
1819        self.e2e_key_id
1820    }
1821
1822    #[cfg(feature = "timesync")]
1823    #[inline]
1824    fn timesync_config(&self) -> Option<TimeSyncConfig> {
1825        self.timesync
1826    }
1827
1828    #[inline]
1829    fn memory_config(&self) -> RuntimeMemoryConfig {
1830        self.memory
1831    }
1832}
1833
1834impl Default for RouterConfig {
1835    fn default() -> Self {
1836        Self {
1837            handlers: Arc::from([]),
1838            reliable_enabled: true,
1839            sender: None,
1840            address_mode: AddressAssignmentMode::Dynamic,
1841            address_change_handlers: Arc::from([]),
1842            e2e_encryption: Self::default_e2e_encryption_mode(),
1843            e2e_key_id: 0,
1844            memory: RuntimeMemoryConfig::default(),
1845            #[cfg(feature = "discovery")]
1846            preferred_discovery_master: None,
1847            #[cfg(feature = "timesync")]
1848            timesync: None,
1849        }
1850    }
1851}
1852
1853// -------------------- generic little-endian serialization --------------------
1854
1855pub trait LeBytes: Copy + Sized {
1856    const WIDTH: usize;
1857    fn write_le(self, out: &mut [u8]);
1858    fn from_le_slice(bytes: &[u8]) -> Self;
1859}
1860
1861impl_letype_num!(u8, 1);
1862impl_letype_num!(u16, 2);
1863impl_letype_num!(u32, 4);
1864impl_letype_num!(u64, 8);
1865impl_letype_num!(u128, 16);
1866impl_letype_num!(i8, 1);
1867impl_letype_num!(i16, 2);
1868impl_letype_num!(i32, 4);
1869impl_letype_num!(i64, 8);
1870impl_letype_num!(i128, 16);
1871impl_letype_num!(f32, 4);
1872impl_letype_num!(f64, 8);
1873
1874/// Encode a slice of `LeBytes` into a contiguous little-endian byte array.
1875pub(crate) fn encode_slice_le<T: LeBytes>(data: &[T]) -> Arc<[u8]> {
1876    let total = data.len() * T::WIDTH;
1877    let mut buf = vec![0u8; total];
1878
1879    for (i, v) in data.iter().copied().enumerate() {
1880        let start = i * T::WIDTH;
1881        v.write_le(&mut buf[start..start + T::WIDTH]);
1882    }
1883
1884    Arc::from(buf)
1885}
1886
1887/// Build an error payload for `TelemetryError` packets, respecting the
1888/// static/dynamic sizing rules from `message_meta`.
1889fn make_error_payload(msg: &str) -> crate::SharedBytes {
1890    let meta = message_meta(DataType::TelemetryError);
1891    match meta.element {
1892        MessageElement::Static(_, _, _) => {
1893            let max = get_needed_message_size(DataType::TelemetryError);
1894            let bytes = msg.as_bytes();
1895            let n = core::cmp::min(max, bytes.len());
1896            let mut buf = vec![0u8; max];
1897            if n > 0 {
1898                buf[..n].copy_from_slice(&bytes[..n]);
1899            }
1900            crate::shared_bytes::convert(Arc::<[u8]>::from(buf))
1901        }
1902        MessageElement::Dynamic(_, _) => {
1903            crate::shared_bytes::convert(Arc::<[u8]>::from(msg.as_bytes()))
1904        }
1905    }
1906}
1907
1908/// Generic raw logger function used by Router::log and Router::log_queue.
1909/// Builds a Packet from the provided data slice and passes it to the
1910/// provided transmission function.
1911fn log_raw<T, F>(
1912    sender: &str,
1913    ty: DataType,
1914    data: &[T],
1915    timestamp: u64,
1916    mut tx_function: F,
1917) -> TelemetryResult<()>
1918where
1919    T: LeBytes,
1920    F: FnMut(Packet) -> TelemetryResult<()>,
1921{
1922    let meta = message_meta(ty);
1923    let got = data.len() * T::WIDTH;
1924
1925    match meta.element {
1926        MessageElement::Static(_, _, _) => {
1927            if got != get_needed_message_size(ty) {
1928                return Err(TelemetryError::SizeMismatch {
1929                    expected: get_needed_message_size(ty),
1930                    got,
1931                });
1932            }
1933        }
1934        MessageElement::Dynamic(_, _) => {
1935            // For dynamic numeric payloads, require total byte length to be a multiple of element width.
1936            if !got.is_multiple_of(T::WIDTH) {
1937                return Err(TelemetryError::SizeMismatch {
1938                    expected: T::WIDTH,
1939                    got,
1940                });
1941            }
1942        }
1943    }
1944
1945    let payload = encode_slice_le(data);
1946    let pkt = Packet::new(ty, meta.endpoints_ref(), sender, timestamp, payload)?;
1947    tx_function(pkt)
1948}
1949
1950// Hosted diagnostics only: no trace branches, formatting or storage on MCUs.
1951#[cfg(feature = "std")]
1952fn reliable_trace_enabled() -> bool {
1953    static ENABLED: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
1954    *ENABLED.get_or_init(|| {
1955        matches!(
1956            std::env::var("SEDSNET_TRACE_RELIABLE").as_deref(),
1957            Ok("1" | "true")
1958        )
1959    })
1960}
1961
1962/// Fallback printing for error messages when no local endpoints exist.
1963/// - With `std`: prints to stderr.
1964/// - Without `std`: forwards to `seds_error_msg` (platform-provided).
1965fn fallback_stdout(_msg: &str) {
1966    #[cfg(feature = "std")]
1967    {
1968        eprintln!("{}", _msg);
1969    }
1970    #[cfg(all(not(feature = "std"), target_os = "none"))]
1971    {
1972        let message = format!("{}\n", _msg);
1973        unsafe {
1974            seds_error_msg(message.as_ptr(), message.len());
1975        }
1976    }
1977}
1978
1979// -------------------- Router --------------------
1980
1981/// Internal mutable state of the Router, protected by `RouterMutex`.
1982/// Holds the RX/TX queues and the recent-RX de-duplication set.
1983#[derive(Debug, Clone)]
1984struct RouterInner {
1985    memory: RuntimeMemoryConfig,
1986    sides: Vec<Option<RouterSide>>,
1987    route_overrides: BTreeMap<(Option<RouterSideId>, RouterSideId), bool>,
1988    typed_route_overrides: BTreeMap<(Option<RouterSideId>, u32, RouterSideId), bool>,
1989    route_weights: BTreeMap<(Option<RouterSideId>, RouterSideId), u32>,
1990    route_priorities: BTreeMap<(Option<RouterSideId>, RouterSideId), u32>,
1991    source_route_modes: BTreeMap<Option<RouterSideId>, RouteSelectionMode>,
1992    route_selection_cursors: BTreeMap<Option<RouterSideId>, u64>,
1993    adaptive_route_stats: BTreeMap<RouterSideId, AdaptiveRouteStats>,
1994    side_runtime_stats: BTreeMap<RouterSideId, SideRuntimeStatsInner>,
1995    side_transport: BTreeMap<RouterSideId, SideTransportState>,
1996    managed_variable_types: BTreeSet<u32>,
1997    managed_variable_permissions: BTreeMap<u32, NetworkVariablePermissions>,
1998    managed_variable_latest: BTreeMap<u32, ManagedVariableCacheEntry>,
1999    network_variable_update_handlers: BTreeMap<u32, Vec<NetworkVariableUpdateHandler>>,
2000    local_address: AddressBookEntry,
2001    address_book: BTreeMap<String, AddressBookEntry>,
2002    address_by_value: BTreeMap<NodeAddress, String>,
2003    p2p_port_handlers: BTreeMap<P2pPort, Vec<P2pPortHandler>>,
2004    p2p_stream_handlers: BTreeMap<P2pPort, Vec<P2pStreamHandler>>,
2005    p2p_stream_sessions: BTreeMap<P2pStreamId, P2pStreamSession>,
2006    next_p2p_stream_id: P2pStreamId,
2007    received_queue: BoundedDeque<RouterRxItem>,
2008    transmit_queue: BoundedDeque<TxQueued>,
2009    tx_priority_burst: u8,
2010    recent_rx: BoundedDeque<u64>,
2011    reliable_tx: BTreeMap<(RouterSideId, u32), ReliableTxState>,
2012    reliable_rx: BTreeMap<(RouterSideId, u32), ReliableRxState>,
2013    reliable_return_routes: BTreeMap<u64, ReliableReturnRouteState>,
2014    reliable_return_route_order: VecDeque<u64>,
2015    end_to_end_reliable_tx: BTreeMap<u64, EndToEndReliableSent>,
2016    end_to_end_reliable_tx_order: VecDeque<u64>,
2017    total_handler_failures: u64,
2018    total_handler_retries: u64,
2019    #[cfg(feature = "discovery")]
2020    discovery_routes: BTreeMap<RouterSideId, DiscoverySideState>,
2021    #[cfg(feature = "discovery")]
2022    discovery_cadence: DiscoveryCadenceState,
2023    #[cfg(feature = "discovery")]
2024    discovery_side_throttle: BTreeMap<RouterSideId, DiscoverySideThrottleState>,
2025    #[cfg(feature = "discovery")]
2026    preferred_discovery_master: Option<Arc<str>>,
2027    #[cfg(all(feature = "discovery", feature = "timesync"))]
2028    timesync_side_throttle: BTreeMap<RouterSideId, TimeSyncSideThrottleState>,
2029}
2030
2031#[derive(Debug, Clone)]
2032struct ManagedVariableCacheEntry {
2033    packet: Packet,
2034    sender_address: u32,
2035    cached_at_ms: u64,
2036}
2037
2038type NetworkVariableUpdateFn = dyn Fn(&Packet) -> TelemetryResult<()> + Send + Sync + 'static;
2039
2040#[derive(Clone)]
2041struct NetworkVariableUpdateHandler {
2042    handler: Arc<NetworkVariableUpdateFn>,
2043}
2044
2045impl Debug for NetworkVariableUpdateHandler {
2046    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
2047        f.write_str("NetworkVariableUpdateHandler(<handler>)")
2048    }
2049}
2050
2051/// Local permission policy for a network-managed variable.
2052#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2053pub struct NetworkVariablePermissions {
2054    pub read: bool,
2055    pub write: bool,
2056}
2057
2058impl NetworkVariablePermissions {
2059    pub const NONE: Self = Self {
2060        read: false,
2061        write: false,
2062    };
2063    pub const READ_ONLY: Self = Self {
2064        read: true,
2065        write: false,
2066    };
2067    pub const WRITE_ONLY: Self = Self {
2068        read: false,
2069        write: true,
2070    };
2071    pub const READ_WRITE: Self = Self {
2072        read: true,
2073        write: true,
2074    };
2075}
2076
2077#[derive(Clone, Copy, Debug, PartialEq, Eq)]
2078enum RouterQueueKind {
2079    Received,
2080    Transmit,
2081}
2082
2083impl RouterInner {
2084    #[cfg(any(feature = "std", feature = "fair-admission"))]
2085    fn fair_bulk_owner(data: &RouterItem, priority: u8) -> Option<u32> {
2086        match data {
2087            RouterItem::Packet(packet) => crate::admission_fairness::packet_owner(packet, priority),
2088            RouterItem::Packed(bytes) => crate::shared_bytes::with_slice(bytes, |view| {
2089                crate::admission_fairness::packed_owner(view, priority)
2090            })
2091            .ok()
2092            .flatten(),
2093        }
2094    }
2095    #[cfg(any(feature = "std", feature = "fair-admission"))]
2096    fn tx_bulk_owner(item: &TxQueued) -> Option<u32> {
2097        match &item.item {
2098            RouterTxItem::Broadcast(data) | RouterTxItem::ToSide { data, .. } => {
2099                Self::fair_bulk_owner(data, item.priority)
2100            }
2101            _ => None,
2102        }
2103    }
2104    #[cfg(any(feature = "std", feature = "fair-admission"))]
2105    fn bulk_stream(data: &RouterItem) -> Option<u32> {
2106        match data {
2107            RouterItem::Packet(packet) => Some(packet.data_type().as_u32()),
2108            RouterItem::Packed(bytes) => crate::shared_bytes::with_slice(bytes, |view| {
2109                wire_format::scheduling_type(view).map(|ty| ty.as_u32())
2110            })
2111            .ok()
2112            .flatten(),
2113        }
2114    }
2115    #[cfg(any(feature = "std", feature = "fair-admission"))]
2116    fn tx_bulk_stream(item: &TxQueued) -> Option<u32> {
2117        match &item.item {
2118            RouterTxItem::Broadcast(data) | RouterTxItem::ToSide { data, .. } => {
2119                Self::bulk_stream(data)
2120            }
2121            _ => None,
2122        }
2123    }
2124    #[cfg(any(feature = "std", feature = "fair-admission"))]
2125    fn bulk_loads(&self, priority: u8) -> crate::admission_fairness::Loads {
2126        let mut loads = crate::admission_fairness::Loads::new();
2127        for item in self.received_queue.iter() {
2128            if item.priority <= priority
2129                && let Some(owner) = Self::fair_bulk_owner(&item.data, item.priority)
2130            {
2131                loads.record(owner, item.byte_cost());
2132            }
2133        }
2134        for item in self.transmit_queue.iter() {
2135            if item.priority <= priority
2136                && let Some(owner) = Self::tx_bulk_owner(item)
2137            {
2138                loads.record(owner, item.byte_cost());
2139            }
2140        }
2141        loads
2142    }
2143    #[cfg(any(feature = "std", feature = "fair-admission"))]
2144    fn competing_bulk_owner(&self, incoming: u32, priority: u8) -> Option<u32> {
2145        self.bulk_loads(priority).dominant_other(incoming)
2146    }
2147    #[cfg(any(feature = "std", feature = "fair-admission"))]
2148    fn reclaim_bulk_from(
2149        &mut self,
2150        owner: u32,
2151        priority: u8,
2152        incoming_stream: Option<u32>,
2153    ) -> bool {
2154        let mut streams = crate::admission_fairness::Loads::new();
2155        for item in self.received_queue.iter() {
2156            if item.priority <= priority
2157                && Self::fair_bulk_owner(&item.data, item.priority) == Some(owner)
2158                && let Some(ty) = Self::bulk_stream(&item.data)
2159            {
2160                streams.record(ty, item.byte_cost());
2161            }
2162        }
2163        for item in self.transmit_queue.iter() {
2164            if item.priority <= priority
2165                && Self::tx_bulk_owner(item) == Some(owner)
2166                && let Some(ty) = Self::tx_bulk_stream(item)
2167            {
2168                streams.record(ty, item.byte_cost());
2169            }
2170        }
2171        let stream = streams.queue_victim(incoming_stream);
2172        let rx = self
2173            .received_queue
2174            .iter()
2175            .enumerate()
2176            .filter(|(_, item)| {
2177                item.priority <= priority
2178                    && Self::fair_bulk_owner(&item.data, item.priority) == Some(owner)
2179                    && stream.is_none_or(|ty| Self::bulk_stream(&item.data) == Some(ty))
2180            })
2181            .min_by_key(|(index, item)| (item.priority, *index))
2182            .map(|(index, item)| (index, item.priority));
2183        let tx = self
2184            .transmit_queue
2185            .iter()
2186            .enumerate()
2187            .filter(|(_, item)| {
2188                item.priority <= priority
2189                    && Self::tx_bulk_owner(item) == Some(owner)
2190                    && stream.is_none_or(|ty| Self::tx_bulk_stream(item) == Some(ty))
2191            })
2192            .min_by_key(|(index, item)| (item.priority, *index))
2193            .map(|(index, item)| (index, item.priority));
2194        match (rx, tx) {
2195            (Some((index, rp)), Some((_, tp))) if rp <= tp => {
2196                self.received_queue.remove_pos(index).is_some()
2197            }
2198            (Some((index, _)), None) => self.received_queue.remove_pos(index).is_some(),
2199            (_, Some((index, _))) => self.transmit_queue.remove_pos(index).is_some(),
2200            _ => false,
2201        }
2202    }
2203
2204    #[cfg(feature = "discovery")]
2205    fn topology_board_byte_cost(board: &TopologyBoardNode) -> usize {
2206        board
2207            .sender_id
2208            .len()
2209            .saturating_add(board.reachable_endpoints.len() * size_of::<DataEndpoint>())
2210            .saturating_add(
2211                board
2212                    .reachable_timesync_sources
2213                    .iter()
2214                    .map(|s| s.len())
2215                    .sum::<usize>(),
2216            )
2217            .saturating_add(board.connections.iter().map(|s| s.len()).sum::<usize>())
2218    }
2219
2220    #[cfg(feature = "discovery")]
2221    fn discovery_sender_byte_cost(sender: &str, state: &DiscoverySenderState) -> usize {
2222        sender
2223            .len()
2224            .saturating_add(state.reachable.len() * size_of::<DataEndpoint>())
2225            .saturating_add(state.advertised_reachable.len() * size_of::<DataEndpoint>())
2226            .saturating_add(state.reachable_network_variables.len() * size_of::<DataType>())
2227            .saturating_add(
2228                state
2229                    .reachable_timesync_sources
2230                    .iter()
2231                    .map(|s| s.len())
2232                    .sum::<usize>(),
2233            )
2234            .saturating_add(
2235                state
2236                    .advertised_reachable_timesync_sources
2237                    .iter()
2238                    .map(|s| s.len())
2239                    .sum::<usize>(),
2240            )
2241            .saturating_add(
2242                state
2243                    .topology_boards
2244                    .iter()
2245                    .map(Self::topology_board_byte_cost)
2246                    .sum::<usize>(),
2247            )
2248            .saturating_add(size_of::<DiscoverySenderState>())
2249    }
2250
2251    #[cfg(feature = "discovery")]
2252    fn discovery_route_byte_cost(side: RouterSideId, route: &DiscoverySideState) -> usize {
2253        size_of::<RouterSideId>()
2254            .saturating_add(size_of::<DiscoverySideState>())
2255            .saturating_add(route.reachable.len() * size_of::<DataEndpoint>())
2256            .saturating_add(route.reachable_network_variables.len() * size_of::<DataType>())
2257            .saturating_add(
2258                route.requested_network_variables.len()
2259                    * (size_of::<DataType>() + size_of::<u64>()),
2260            )
2261            .saturating_add(
2262                route
2263                    .reachable_timesync_sources
2264                    .iter()
2265                    .map(|s| s.len())
2266                    .sum::<usize>(),
2267            )
2268            .saturating_add(
2269                route
2270                    .announcers
2271                    .iter()
2272                    .map(|(sender, state)| Self::discovery_sender_byte_cost(sender, state))
2273                    .sum::<usize>(),
2274            )
2275            .saturating_add(side.saturating_sub(side))
2276    }
2277
2278    #[cfg(feature = "discovery")]
2279    fn discovery_bytes_used(&self) -> usize {
2280        self.discovery_routes
2281            .iter()
2282            .map(|(side, route)| Self::discovery_route_byte_cost(*side, route))
2283            .sum()
2284    }
2285
2286    #[inline]
2287    fn reliable_rx_buffered_bytes(&self) -> usize {
2288        self.reliable_rx
2289            .values()
2290            .flat_map(|state| state.buffered.values())
2291            .map(|bytes| size_of::<crate::SharedBytes>() + bytes.len())
2292            .sum()
2293    }
2294
2295    fn reliable_rx_buffer_len(&self) -> usize {
2296        self.reliable_rx
2297            .values()
2298            .map(|state| state.buffered.len())
2299            .sum()
2300    }
2301
2302    #[inline]
2303    fn shared_queue_bytes_used(&self) -> usize {
2304        self.received_queue
2305            .bytes_used()
2306            .saturating_add(self.transmit_queue.bytes_used())
2307            .saturating_add(self.recent_rx.max_bytes())
2308            .saturating_add(self.reliable_rx_buffered_bytes())
2309            .saturating_add(crate::config::schema_bytes_used())
2310            .saturating_add({
2311                #[cfg(feature = "discovery")]
2312                {
2313                    self.discovery_bytes_used()
2314                }
2315                #[cfg(not(feature = "discovery"))]
2316                {
2317                    0
2318                }
2319            })
2320    }
2321
2322    fn pop_shared_queue_item(&mut self, preferred: RouterQueueKind) -> bool {
2323        match preferred {
2324            // RX and TX queues are stored in descending priority order.  When
2325            // the shared byte budget is exhausted, evict from the tail so a
2326            // low-priority telemetry packet cannot displace discovery or a
2327            // managed-variable update at the head of the queue.
2328            RouterQueueKind::Received => self.received_queue.pop_lowest_priority().is_some(),
2329            RouterQueueKind::Transmit => self.transmit_queue.pop_lowest_priority().is_some(),
2330        }
2331    }
2332
2333    fn lower_priority_shared_queue(&self, incoming_priority: u8) -> Option<RouterQueueKind> {
2334        let received = self
2335            .received_queue
2336            .lowest_priority(|queued| queued.priority)
2337            .map(|priority| (RouterQueueKind::Received, priority));
2338        let transmit = self
2339            .transmit_queue
2340            .lowest_priority(|queued| queued.priority)
2341            .map(|priority| (RouterQueueKind::Transmit, priority));
2342        [received, transmit]
2343            .into_iter()
2344            .flatten()
2345            .filter(|(_, priority)| {
2346                *priority < incoming_priority
2347                    || (incoming_priority != 254 && *priority == incoming_priority)
2348            })
2349            .min_by_key(|(kind, priority)| {
2350                (
2351                    *priority,
2352                    if *kind == RouterQueueKind::Received {
2353                        0
2354                    } else {
2355                        1
2356                    },
2357                )
2358            })
2359            .map(|(kind, _)| kind)
2360    }
2361
2362    fn make_shared_queue_room(
2363        &mut self,
2364        incoming_cost: usize,
2365        incoming_priority: u8,
2366        _incoming: Option<&RouterItem>,
2367    ) -> TelemetryResult<()> {
2368        if incoming_cost > self.memory.max_queue_budget {
2369            return Err(TelemetryError::PacketTooLarge(
2370                "Item exceeds maximum shared queue budget",
2371            ));
2372        }
2373
2374        #[cfg(any(feature = "std", feature = "fair-admission"))]
2375        let mut fair_victim = None;
2376        while self.shared_queue_bytes_used().saturating_add(incoming_cost)
2377            > self.memory.max_queue_budget
2378        {
2379            #[cfg(any(feature = "std", feature = "fair-admission"))]
2380            {
2381                let incoming =
2382                    _incoming.and_then(|data| Self::fair_bulk_owner(data, incoming_priority));
2383                let victim = *fair_victim.get_or_insert_with(|| {
2384                    self.bulk_loads(incoming_priority.min(199))
2385                        .queue_victim(incoming)
2386                });
2387                if let Some(victim) = victim
2388                    && self.reclaim_bulk_from(
2389                        victim,
2390                        incoming_priority.min(199),
2391                        (incoming == Some(victim))
2392                            .then(|| _incoming.and_then(Self::bulk_stream))
2393                            .flatten(),
2394                    )
2395                {
2396                    continue;
2397                }
2398                if incoming.is_some() {
2399                    return Err(TelemetryError::Io("shared fair queue saturated"));
2400                }
2401            }
2402            let Some(victim) = self.lower_priority_shared_queue(incoming_priority) else {
2403                return Err(TelemetryError::Io("shared priority queue saturated"));
2404            };
2405            if !self.pop_shared_queue_item(victim) {
2406                return Err(TelemetryError::PacketTooLarge(
2407                    "Item exceeds maximum shared queue budget",
2408                ));
2409            }
2410        }
2411
2412        Ok(())
2413    }
2414
2415    #[cfg(feature = "discovery")]
2416    fn schema_growth_budget(&self) -> usize {
2417        // Queued packets are reclaimable. Existing reachability and active
2418        // reliable reassembly remain protected when admitting a new schema.
2419        let protected = self
2420            .recent_rx
2421            .max_bytes()
2422            .saturating_add(self.discovery_bytes_used())
2423            .saturating_add(self.reliable_rx_buffered_bytes());
2424        self.memory.max_queue_budget.saturating_sub(protected)
2425    }
2426
2427    #[cfg(feature = "discovery")]
2428    fn commit_discovery_route(
2429        &mut self,
2430        side: RouterSideId,
2431        route: DiscoverySideState,
2432    ) -> TelemetryResult<()> {
2433        let previous = self
2434            .discovery_routes
2435            .get(&side)
2436            .map_or(0, |route| Self::discovery_route_byte_cost(side, route));
2437        let incoming = Self::discovery_route_byte_cost(side, &route);
2438        let growth = incoming.saturating_sub(previous);
2439        self.make_shared_queue_room(growth, 255, None)?;
2440        self.discovery_routes.insert(side, route);
2441        Ok(())
2442    }
2443
2444    #[cfg(feature = "discovery")]
2445    fn fit_discovery_budget(&mut self) {
2446        while self.shared_queue_bytes_used() > self.memory.max_queue_budget {
2447            // Discovery growth must discard queued work before reachability.
2448            // Evicting a whole side here forgets every downstream command route.
2449            if let Some(victim) = self.lower_priority_shared_queue(255)
2450                && self.pop_shared_queue_item(victim)
2451            {
2452                continue;
2453            }
2454            // Live routes are protected. Growth is admitted transactionally;
2455            // external registry/config changes cannot justify erasing routes.
2456            break;
2457        }
2458    }
2459
2460    fn push_received(&mut self, item: RouterRxItem) -> TelemetryResult<()> {
2461        self.make_shared_queue_room(item.byte_cost(), item.priority, Some(&item.data))?;
2462        #[cfg(any(feature = "std", feature = "fair-admission"))]
2463        {
2464            #[cfg(any(feature = "std", feature = "fair-admission"))]
2465            let mut fair_victim = None;
2466            self.received_queue.push_back_prioritized_select(
2467                item,
2468                |queued| queued.priority,
2469                |queue, _incoming| {
2470                    #[cfg(any(feature = "std", feature = "fair-admission"))]
2471                    {
2472                        crate::admission_fairness::queue_victim(
2473                            queue,
2474                            _incoming,
2475                            |item| Self::fair_bulk_owner(&item.data, item.priority),
2476                            |item| item.priority,
2477                            |item| Self::bulk_stream(&item.data),
2478                            &mut fair_victim,
2479                        )
2480                    }
2481                },
2482            )
2483        }
2484        #[cfg(not(any(feature = "std", feature = "fair-admission")))]
2485        {
2486            self.received_queue
2487                .push_back_prioritized(item, |queued| queued.priority)
2488        }
2489    }
2490
2491    fn push_transmit(&mut self, item: TxQueued) -> TelemetryResult<()> {
2492        #[cfg(feature = "discovery")]
2493        if let RouterTxItem::ToSide { dst, data, .. } = &item.item {
2494            // A new timestamp for the same catalog must not replace the
2495            // queued packet: a chunked side may already have sent its prefix.
2496            if let RouterItem::Packet(incoming) = data
2497                && self.transmit_queue.iter().any(|queued| {
2498                    matches!(&queued.item,
2499                        RouterTxItem::ToSide { dst: queued_dst, data: RouterItem::Packet(existing), .. }
2500                        if queued_dst == dst && discovery::same_schema_snapshot(existing, incoming))
2501                })
2502            {
2503                return Ok(());
2504            }
2505            let incoming = match data {
2506                RouterItem::Packet(pkt) if discovery::is_discovery_type(pkt.data_type()) => {
2507                    Some((pkt.data_type(), pkt.sender().to_owned()))
2508                }
2509                RouterItem::Packet(_) | RouterItem::Packed(_) => None,
2510            };
2511            if let Some((incoming_ty, incoming_sender)) = incoming {
2512                self.transmit_queue.retain(|queued| {
2513                    let RouterTxItem::ToSide {
2514                        dst: queued_dst,
2515                        data: queued_data,
2516                        ..
2517                    } = &queued.item
2518                    else {
2519                        return true;
2520                    };
2521                    let same_snapshot = match queued_data {
2522                        RouterItem::Packet(pkt) => {
2523                            pkt.data_type() == incoming_ty && pkt.sender() == incoming_sender
2524                        }
2525                        RouterItem::Packed(_) => false,
2526                    };
2527                    *queued_dst != *dst || !same_snapshot
2528                });
2529            }
2530        }
2531        self.make_shared_queue_room(
2532            item.byte_cost(),
2533            item.priority,
2534            match &item.item {
2535                RouterTxItem::Broadcast(data) | RouterTxItem::ToSide { data, .. } => Some(data),
2536                _ => None,
2537            },
2538        )?;
2539        #[cfg(any(feature = "std", feature = "fair-admission"))]
2540        {
2541            #[cfg(any(feature = "std", feature = "fair-admission"))]
2542            let mut fair_victim = None;
2543            self.transmit_queue.push_back_prioritized_select(
2544                item,
2545                |queued| queued.priority,
2546                |queue, _incoming| {
2547                    #[cfg(any(feature = "std", feature = "fair-admission"))]
2548                    {
2549                        crate::admission_fairness::queue_victim(
2550                            queue,
2551                            _incoming,
2552                            Self::tx_bulk_owner,
2553                            |item| item.priority,
2554                            Self::tx_bulk_stream,
2555                            &mut fair_victim,
2556                        )
2557                    }
2558                },
2559            )
2560        }
2561        #[cfg(not(any(feature = "std", feature = "fair-admission")))]
2562        {
2563            self.transmit_queue
2564                .push_back_prioritized(item, |queued| queued.priority)
2565        }
2566    }
2567
2568    fn push_recent_rx(&mut self, id: u64) -> TelemetryResult<()> {
2569        while self.recent_rx.len() >= self.memory.max_recent_rx_ids {
2570            let _ = self.recent_rx.pop_front();
2571        }
2572        self.make_shared_queue_room(0, 0, None)?;
2573        self.recent_rx.push_back(id)
2574    }
2575
2576    #[allow(unused_mut)]
2577    fn buffer_reliable_rx(
2578        &mut self,
2579        side: RouterSideId,
2580        ty: DataType,
2581        seq: u32,
2582        mut bytes: crate::SharedBytes,
2583    ) -> TelemetryResult<()> {
2584        let key = Router::reliable_key(side, ty);
2585        if self
2586            .reliable_rx
2587            .get(&key)
2588            .is_some_and(|state| state.buffered.contains_key(&seq))
2589        {
2590            return Ok(());
2591        }
2592        let cost = size_of::<crate::SharedBytes>() + bytes.len();
2593        self.make_shared_queue_room(cost, crate::transport_priority(ty), None)?;
2594        #[cfg(feature = "compact-packet-store")]
2595        bytes.park_for_queue()?;
2596        let rx_state = self
2597            .reliable_rx
2598            .entry(key)
2599            .or_insert_with(|| ReliableRxState {
2600                expected_seq: 1,
2601                buffered: BTreeMap::new(),
2602            });
2603        if rx_state.buffered.len() >= runtime_reliable_max_pending() {
2604            let _ = rx_state.buffered.pop_first();
2605        }
2606        rx_state.buffered.insert(seq, bytes);
2607        Ok(())
2608    }
2609}
2610
2611/// Non-blocking RX queue used by ISR-safe `rx_queue*` APIs.
2612///
2613/// Uses a tiny atomic try-lock so enqueue never blocks. If contended, push/pop
2614/// operations return `TelemetryError::Io("rx queue busy")`.
2615struct IsrRxQueue {
2616    busy: AtomicBool,
2617    q: UnsafeCell<BoundedDeque<RouterRxItem>>,
2618}
2619
2620unsafe impl Send for IsrRxQueue {}
2621unsafe impl Sync for IsrRxQueue {}
2622
2623struct IsrRxQueueGuard<'a> {
2624    owner: &'a IsrRxQueue,
2625}
2626
2627impl Deref for IsrRxQueueGuard<'_> {
2628    type Target = BoundedDeque<RouterRxItem>;
2629
2630    #[inline]
2631    fn deref(&self) -> &Self::Target {
2632        unsafe { &*self.owner.q.get() }
2633    }
2634}
2635
2636impl DerefMut for IsrRxQueueGuard<'_> {
2637    #[inline]
2638    fn deref_mut(&mut self) -> &mut Self::Target {
2639        unsafe { &mut *self.owner.q.get() }
2640    }
2641}
2642
2643impl Drop for IsrRxQueueGuard<'_> {
2644    #[inline]
2645    fn drop(&mut self) {
2646        self.owner.busy.store(false, Ordering::Release);
2647    }
2648}
2649
2650impl IsrRxQueue {
2651    #[inline]
2652    fn new(max_bytes: usize, starting_bytes: usize, grow_mult: f64) -> Self {
2653        Self {
2654            busy: AtomicBool::new(false),
2655            q: UnsafeCell::new(BoundedDeque::new(max_bytes, starting_bytes, grow_mult)),
2656        }
2657    }
2658
2659    #[inline]
2660    fn try_lock(&self) -> TelemetryResult<IsrRxQueueGuard<'_>> {
2661        match self
2662            .busy
2663            .compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed)
2664        {
2665            Ok(_) => Ok(IsrRxQueueGuard { owner: self }),
2666            Err(_) => Err(TelemetryError::Io("rx queue busy")),
2667        }
2668    }
2669
2670    #[allow(dead_code)]
2671    #[inline]
2672    fn push_back(&self, item: RouterRxItem) -> TelemetryResult<()> {
2673        let mut g = self.try_lock()?;
2674        g.push_back(item)
2675    }
2676
2677    #[inline]
2678    fn push_back_prioritized(&self, item: RouterRxItem) -> TelemetryResult<()> {
2679        let mut g = self.try_lock()?;
2680        g.push_back_prioritized(item, |queued| queued.priority)
2681    }
2682
2683    #[inline]
2684    fn pop_front(&self) -> TelemetryResult<Option<RouterRxItem>> {
2685        let mut g = self.try_lock()?;
2686        Ok(g.pop_front())
2687    }
2688
2689    #[inline]
2690    fn clear(&self) -> TelemetryResult<()> {
2691        let mut g = self.try_lock()?;
2692        g.clear();
2693        Ok(())
2694    }
2695
2696    #[inline]
2697    fn snapshot(&self) -> Option<(usize, usize)> {
2698        let g = self.try_lock().ok()?;
2699        Some((g.len(), g.bytes_used()))
2700    }
2701}
2702
2703/// Telemetry Router for handling incoming and outgoing telemetry packets.
2704/// Supports queuing, processing, and dispatching to local endpoint handlers.
2705/// Thread-safe via internal locking.
2706pub struct Router {
2707    sender: RouterMutex<Arc<str>>,
2708    cfg: RouterConfig,
2709    state: RouterMutex<RouterInner>,
2710    isr_rx_queue: IsrRxQueue,
2711    side_tx_gate: ReentryGate,
2712    clock: Box<dyn Clock + Send + Sync>,
2713    #[cfg(feature = "timesync")]
2714    timesync: RouterMutex<TimeSyncRuntime>,
2715}
2716
2717#[cfg(feature = "timesync")]
2718#[derive(Debug, Clone)]
2719struct PendingTimeSyncRequest {
2720    seq: u64,
2721    t1_mono_ms: u64,
2722    source: String,
2723}
2724
2725#[cfg(feature = "timesync")]
2726#[derive(Debug, Clone)]
2727struct RemoteTimeSyncSource {
2728    priority: u64,
2729    last_sample_mono_ms: u64,
2730    sample_unix_ms: u64,
2731}
2732
2733#[cfg(feature = "timesync")]
2734#[derive(Debug, Clone)]
2735struct TimeSyncRuntime {
2736    cfg: Option<TimeSyncConfig>,
2737    tracker: Option<TimeSyncTracker>,
2738    clock: NetworkClock,
2739    disciplined_clock: SlewedNetworkClock,
2740    remote_sources: BTreeMap<String, RemoteTimeSyncSource>,
2741    next_seq: u64,
2742    next_announce_mono_ms: u64,
2743    next_request_mono_ms: u64,
2744    pending_request: Option<PendingTimeSyncRequest>,
2745}
2746
2747#[cfg(feature = "timesync")]
2748impl TimeSyncRuntime {
2749    fn new(cfg: Option<TimeSyncConfig>) -> Self {
2750        Self {
2751            tracker: cfg.map(TimeSyncTracker::new),
2752            cfg,
2753            clock: NetworkClock::default(),
2754            disciplined_clock: SlewedNetworkClock::new(
2755                cfg.map(|c| c.max_slew_ppm)
2756                    .unwrap_or(TimeSyncConfig::default().max_slew_ppm),
2757            ),
2758            remote_sources: BTreeMap::new(),
2759            next_seq: 1,
2760            next_announce_mono_ms: 0,
2761            next_request_mono_ms: 0,
2762            pending_request: None,
2763        }
2764    }
2765}
2766
2767enum RemoteSidePlan {
2768    Target(Vec<RouterSideId>),
2769}
2770
2771#[cfg(feature = "discovery")]
2772#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
2773struct DiscoveryCandidateMatch {
2774    side: RouterSideId,
2775    overlap: usize,
2776}
2777
2778#[cfg(feature = "discovery")]
2779#[inline]
2780const fn discovery_is_hop_local_advertisement(ty: DataType) -> bool {
2781    matches!(
2782        ty,
2783        DataType::DiscoveryAnnounce
2784            | DataType::DiscoveryTimeSyncSources
2785            | DataType::DiscoveryTopology
2786            | DataType::DiscoveryAddress
2787            | DataType::DiscoveryLinkCapabilities
2788    )
2789}
2790
2791#[cfg(not(feature = "discovery"))]
2792const fn discovery_is_hop_local_advertisement(_ty: DataType) -> bool {
2793    false
2794}
2795
2796impl Debug for Router {
2797    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
2798        let sender = self.sender();
2799        f.debug_struct("Router")
2800            .field("sender", &sender)
2801            .field("cfg", &self.cfg)
2802            .field("state", &"<mutex>")
2803            .field("clock", &"Clock")
2804            .finish()
2805    }
2806}
2807
2808/// Check if any of the provided endpoints require remote forwarding when
2809/// discovery is unavailable and routing falls back to local-vs-remote schema.
2810#[inline]
2811fn has_nonlocal_endpoint(eps: &[DataEndpoint], cfg: &RouterConfig) -> bool {
2812    eps.iter().copied().any(|ep| !cfg.is_local_endpoint(ep))
2813}
2814
2815#[inline]
2816fn force_remote_for_type(ty: DataType) -> bool {
2817    matches!(
2818        ty,
2819        DataType::ReliableAck
2820            | DataType::ReliablePartialAck
2821            | DataType::ReliablePacketRequest
2822            | DataType::P2pMessage
2823    ) || {
2824        #[cfg(feature = "timesync")]
2825        {
2826            matches!(
2827                ty,
2828                DataType::TimeSyncAnnounce | DataType::TimeSyncRequest | DataType::TimeSyncResponse
2829            )
2830        }
2831        #[cfg(not(feature = "timesync"))]
2832        {
2833            false
2834        }
2835    }
2836}
2837
2838#[inline]
2839fn is_internal_control_type(ty: DataType) -> bool {
2840    if matches!(
2841        ty,
2842        DataType::ReliableAck
2843            | DataType::ReliablePartialAck
2844            | DataType::ReliablePacketRequest
2845            | DataType::P2pMessage
2846    ) {
2847        return true;
2848    }
2849
2850    #[cfg(feature = "timesync")]
2851    if matches!(
2852        ty,
2853        DataType::TimeSyncAnnounce | DataType::TimeSyncRequest | DataType::TimeSyncResponse
2854    ) {
2855        return true;
2856    }
2857
2858    #[cfg(feature = "discovery")]
2859    if discovery::is_discovery_type(ty) {
2860        return true;
2861    }
2862
2863    let _ = ty;
2864    false
2865}
2866
2867/// Helper function to call a handler with retries and error handling.
2868fn with_retries<F>(
2869    this: &Router,
2870    dest: DataEndpoint,
2871    data: &RouterItem,
2872    pkt_for_ctx: Option<&Packet>,
2873    env_for_ctx: Option<&wire_format::TelemetryEnvelope>,
2874    called_from_queue: bool,
2875    run: F,
2876) -> TelemetryResult<()>
2877where
2878    F: Fn() -> TelemetryResult<()>,
2879{
2880    match this.retry_with_attempts(runtime_max_handler_retries(), run) {
2881        Ok(((), attempts)) => {
2882            if attempts > 1 {
2883                let mut st = this.state.lock();
2884                st.total_handler_retries = st
2885                    .total_handler_retries
2886                    .saturating_add(attempts.saturating_sub(1) as u64);
2887            }
2888            Ok(())
2889        }
2890        Err((e, attempts)) => {
2891            {
2892                let mut st = this.state.lock();
2893                st.total_handler_failures = st.total_handler_failures.saturating_add(1);
2894                st.total_handler_retries = st.total_handler_retries.saturating_add(attempts as u64);
2895            }
2896
2897            // If handler fails, remove from dedupe so it can be retried later if resent.
2898            this.remove_pkt_id(data);
2899
2900            // Emit error packet (to local endpoints).
2901            if let Some(pkt) = pkt_for_ctx {
2902                let _ = this.handle_callback_error(pkt, Some(dest), e, called_from_queue);
2903            } else if let Some(env) = env_for_ctx {
2904                let _ = this.handle_callback_error_from_env(env, Some(dest), e, called_from_queue);
2905            }
2906
2907            Err(TelemetryError::HandlerError("local handler failed"))
2908        }
2909    }
2910}
2911/// Router implementation
2912impl Router {
2913    const END_TO_END_ACK_SENDER: &'static str = "E2EACK";
2914    const END_TO_END_ACK_PREFIX: &'static str = "E2EACK:";
2915
2916    #[inline]
2917    fn side_ref(st: &RouterInner, side: RouterSideId) -> TelemetryResult<&RouterSide> {
2918        st.sides
2919            .get(side)
2920            .and_then(|side| side.as_ref())
2921            .ok_or(TelemetryError::HandlerError("router: invalid side id"))
2922    }
2923
2924    fn note_side_tx_success(
2925        &self,
2926        side: RouterSideId,
2927        ty: DataType,
2928        bytes: usize,
2929        relayed: bool,
2930        attempts: usize,
2931    ) {
2932        let mut st = self.state.lock();
2933        #[cfg(feature = "discovery")]
2934        if ty == crate::DataType::DiscoveryAnnounce
2935            && !relayed
2936            && let Some(throttle) = st.discovery_side_throttle.get_mut(&side)
2937        {
2938            throttle.last_liveness_tx_ms = self.clock.now_ms();
2939        }
2940        let entry = st.side_runtime_stats.entry(side).or_default();
2941        entry.note_tx(ty, bytes, relayed, attempts.saturating_sub(1));
2942    }
2943
2944    fn note_side_tx_failure(&self, side: RouterSideId, ty: DataType, attempts: usize) {
2945        let mut st = self.state.lock();
2946        st.total_handler_failures = st.total_handler_failures.saturating_add(1);
2947        st.total_handler_retries = st.total_handler_retries.saturating_add(attempts as u64);
2948        let entry = st.side_runtime_stats.entry(side).or_default();
2949        entry.note_tx_failure(ty, attempts);
2950    }
2951
2952    fn note_side_rx(&self, side: RouterSideId, ty: DataType, bytes: usize, relayed: bool) {
2953        let mut st = self.state.lock();
2954        let entry = st.side_runtime_stats.entry(side).or_default();
2955        entry.note_rx(ty, bytes, relayed);
2956    }
2957
2958    fn note_side_local_delivery(&self, side: RouterSideId, ty: DataType) {
2959        let mut st = self.state.lock();
2960        let entry = st.side_runtime_stats.entry(side).or_default();
2961        entry.note_local_delivery(ty);
2962    }
2963
2964    fn note_side_local_handler_failure(&self, side: RouterSideId, ty: DataType, retries: usize) {
2965        let mut st = self.state.lock();
2966        let entry = st.side_runtime_stats.entry(side).or_default();
2967        entry.note_local_handler_failure(ty, retries);
2968    }
2969
2970    fn cache_managed_variable_packet(
2971        &self,
2972        pkt: &Packet,
2973        notify_handlers: bool,
2974    ) -> TelemetryResult<()> {
2975        let sender_address = pkt
2976            .sender()
2977            .strip_prefix("@addr:")
2978            .and_then(|value| value.parse::<u32>().ok())
2979            .unwrap_or_else(|| sender_address_u32(pkt.sender()));
2980        let handlers = {
2981            let mut st = self.state.lock();
2982            if !st
2983                .managed_variable_types
2984                .contains(&pkt.data_type().as_u32())
2985            {
2986                return Ok(());
2987            }
2988            let existing = st.managed_variable_latest.get(&pkt.data_type().as_u32());
2989            // Sender-local timestamps can order updates from one authoritative
2990            // writer even before network time is available. They cannot order
2991            // a restored local seed against a remote writer, and packet nonces
2992            // are deliberately pseudo-random deduplication keys rather than
2993            // revisions. Accept equal-timestamp updates in arrival order so
2994            // rapid toggles made during one clock tick are not discarded.
2995            if existing.is_some_and(|entry| {
2996                let current = &entry.packet;
2997                sender_address == entry.sender_address && pkt.timestamp() < current.timestamp()
2998            }) {
2999                return Ok(());
3000            }
3001            let changed = existing.is_none_or(|entry| entry.packet != *pkt);
3002            st.managed_variable_latest.insert(
3003                pkt.data_type().as_u32(),
3004                ManagedVariableCacheEntry {
3005                    packet: pkt.clone(),
3006                    sender_address,
3007                    cached_at_ms: self.clock.now_ms(),
3008                },
3009            );
3010            if notify_handlers && changed {
3011                st.network_variable_update_handlers
3012                    .get(&pkt.data_type().as_u32())
3013                    .cloned()
3014                    .unwrap_or_default()
3015            } else {
3016                Vec::new()
3017            }
3018        };
3019        for handler in handlers {
3020            (handler.handler)(pkt)?;
3021        }
3022        Ok(())
3023    }
3024
3025    fn remember_managed_variable_packet(&self, pkt: &Packet) -> TelemetryResult<()> {
3026        self.cache_managed_variable_packet(pkt, true)
3027    }
3028
3029    fn managed_variable_latest(&self, ty: DataType) -> Option<Packet> {
3030        let st = self.state.lock();
3031        st.managed_variable_latest
3032            .get(&ty.as_u32())
3033            .map(|entry| entry.packet.clone())
3034    }
3035
3036    fn managed_variable_latest_with_age(&self, ty: DataType) -> Option<(Packet, u64)> {
3037        let now_ms = self.clock.now_ms();
3038        let st = self.state.lock();
3039        st.managed_variable_latest.get(&ty.as_u32()).map(|entry| {
3040            (
3041                entry.packet.clone(),
3042                now_ms.saturating_sub(entry.cached_at_ms),
3043            )
3044        })
3045    }
3046
3047    fn is_managed_variable_type(&self, ty: DataType) -> bool {
3048        let st = self.state.lock();
3049        st.managed_variable_types.contains(&ty.as_u32())
3050    }
3051
3052    fn managed_variable_permissions_locked(
3053        st: &RouterInner,
3054        ty: DataType,
3055    ) -> NetworkVariablePermissions {
3056        st.managed_variable_permissions
3057            .get(&ty.as_u32())
3058            .copied()
3059            .unwrap_or(NetworkVariablePermissions::READ_WRITE)
3060    }
3061
3062    fn can_read_managed_variable(&self, ty: DataType) -> bool {
3063        let st = self.state.lock();
3064        Self::managed_variable_permissions_locked(&st, ty).read
3065    }
3066
3067    fn can_write_managed_variable(&self, ty: DataType) -> bool {
3068        let st = self.state.lock();
3069        Self::managed_variable_permissions_locked(&st, ty).write
3070    }
3071
3072    #[inline]
3073    fn ensure_side_ingress_enabled(&self, side: RouterSideId) -> TelemetryResult<()> {
3074        let st = self.state.lock();
3075        let side_ref = Self::side_ref(&st, side)?;
3076        if side_ref.opts.ingress_enabled {
3077            Ok(())
3078        } else {
3079            Err(TelemetryError::HandlerError(
3080                "router: ingress disabled for side id",
3081            ))
3082        }
3083    }
3084
3085    #[inline]
3086    fn default_route_enabled(&self, src: Option<RouterSideId>, dst: RouterSideId) -> bool {
3087        src != Some(dst)
3088    }
3089
3090    #[inline]
3091    fn route_allowed_locked(
3092        &self,
3093        st: &RouterInner,
3094        src: Option<RouterSideId>,
3095        ty: Option<DataType>,
3096        dst: RouterSideId,
3097    ) -> bool {
3098        let Some(dst_side) = st.sides.get(dst).and_then(|side| side.as_ref()) else {
3099            return false;
3100        };
3101        if !dst_side.opts.egress_enabled {
3102            return false;
3103        }
3104        if let Some(src_id) = src {
3105            let Some(src_side) = st.sides.get(src_id).and_then(|side| side.as_ref()) else {
3106                return false;
3107            };
3108            if !src_side.opts.ingress_enabled {
3109                return false;
3110            }
3111        }
3112        let base_allowed = st
3113            .route_overrides
3114            .get(&(src, dst))
3115            .copied()
3116            .unwrap_or_else(|| self.default_route_enabled(src, dst));
3117        if !base_allowed {
3118            return false;
3119        }
3120
3121        let Some(ty) = ty else {
3122            return true;
3123        };
3124        if st
3125            .typed_route_overrides
3126            .keys()
3127            .any(|(typed_src, typed_ty, _)| *typed_src == src && *typed_ty == ty.as_u32())
3128        {
3129            return st
3130                .typed_route_overrides
3131                .get(&(src, ty.as_u32(), dst))
3132                .copied()
3133                .unwrap_or(false);
3134        }
3135        true
3136    }
3137
3138    fn has_typed_route_overrides_locked(
3139        st: &RouterInner,
3140        src: Option<RouterSideId>,
3141        ty: DataType,
3142    ) -> bool {
3143        st.typed_route_overrides
3144            .keys()
3145            .any(|(typed_src, typed_ty, _)| *typed_src == src && *typed_ty == ty.as_u32())
3146    }
3147
3148    #[cfg(feature = "discovery")]
3149    fn endpoint_overlap_count<I>(reachable: I, eps: &[DataEndpoint]) -> usize
3150    where
3151        I: IntoIterator<Item = DataEndpoint>,
3152    {
3153        let mut overlap = 0usize;
3154        for ep in reachable {
3155            if eps.contains(&ep) {
3156                overlap = overlap.saturating_add(1);
3157            }
3158        }
3159        overlap
3160    }
3161
3162    #[inline]
3163    fn preferred_scoring_endpoints<'a>(
3164        &self,
3165        eps: &'a [DataEndpoint],
3166        prefer_nonlocal: bool,
3167    ) -> alloc::borrow::Cow<'a, [DataEndpoint]> {
3168        if !prefer_nonlocal || eps.iter().all(|&ep| !self.cfg.is_local_endpoint(ep)) {
3169            return alloc::borrow::Cow::Borrowed(eps);
3170        }
3171        let nonlocal: Vec<DataEndpoint> = eps
3172            .iter()
3173            .copied()
3174            .filter(|&ep| !self.cfg.is_local_endpoint(ep))
3175            .collect();
3176        if nonlocal.is_empty() {
3177            alloc::borrow::Cow::Borrowed(eps)
3178        } else {
3179            alloc::borrow::Cow::Owned(nonlocal)
3180        }
3181    }
3182
3183    fn pop_next_queued_control_rx_item(&self) -> TelemetryResult<Option<RouterRxItem>> {
3184        {
3185            let mut isr_rx = self.isr_rx_queue.try_lock()?;
3186            let idx = isr_rx.iter().position(Self::queued_rx_item_is_control);
3187            if let Some(idx) = idx {
3188                return Ok(isr_rx.remove_pos(idx));
3189            }
3190        }
3191
3192        let mut st = self.state.lock();
3193        let idx = st
3194            .received_queue
3195            .iter()
3196            .position(Self::queued_rx_item_is_control);
3197        if let Some(idx) = idx {
3198            return Ok(st.received_queue.remove_pos(idx));
3199        }
3200        Ok(None)
3201    }
3202
3203    fn queued_rx_item_is_control(item: &RouterRxItem) -> bool {
3204        // Registered managed values keep their application type on the wire,
3205        // but their queued service class must match their transmission class.
3206        if item.priority == crate::transport_priority(DataType::ManagedVariableValue) {
3207            return true;
3208        }
3209        let ty = match &item.data {
3210            RouterItem::Packet(pkt) => Some(pkt.data_type()),
3211            RouterItem::Packed(bytes) => wire_format::scheduling_type(bytes.as_ref()),
3212        };
3213        match ty {
3214            Some(
3215                DataType::ReliableAck
3216                | DataType::ReliablePartialAck
3217                | DataType::ReliablePacketRequest,
3218            ) => true,
3219            #[cfg(feature = "discovery")]
3220            Some(ty) => discovery::is_discovery_type(ty),
3221            _ => false,
3222        }
3223    }
3224
3225    fn drain_queued_control_rx_before_tx(
3226        &self,
3227        start: u64,
3228        timeout_ms: u32,
3229    ) -> TelemetryResult<bool> {
3230        let mut did_any = false;
3231        while let Some(item) = self.pop_next_queued_control_rx_item()? {
3232            self.process_rx_queue_item(item)?;
3233            did_any = true;
3234            // Receipts precede retry exhaustion; discovery precedes routing.
3235            // A control burst still shares the caller's bounded deadline.
3236            if timeout_ms != 0 && self.clock.now_ms().wrapping_sub(start) >= u64::from(timeout_ms) {
3237                break;
3238            }
3239        }
3240        Ok(did_any)
3241    }
3242
3243    fn eligible_side_ids_locked(
3244        &self,
3245        st: &RouterInner,
3246        src: Option<RouterSideId>,
3247        ty: Option<DataType>,
3248        restrict_link_local: bool,
3249    ) -> Vec<RouterSideId> {
3250        st.sides
3251            .iter()
3252            .enumerate()
3253            .filter_map(|(side_id, side)| {
3254                let side = side.as_ref()?;
3255                if restrict_link_local && !side.opts.link_local_enabled {
3256                    return None;
3257                }
3258                if self.route_allowed_locked(st, src, ty, side_id) {
3259                    Some(side_id)
3260                } else {
3261                    None
3262                }
3263            })
3264            .collect()
3265    }
3266
3267    fn apply_route_selection_locked(
3268        &self,
3269        st: &mut RouterInner,
3270        src: Option<RouterSideId>,
3271        mut sides: Vec<RouterSideId>,
3272        origin: RouteSelectionOrigin,
3273    ) -> Vec<RouterSideId> {
3274        if sides.len() <= 1 {
3275            return sides;
3276        }
3277
3278        let selection_mode = st.source_route_modes.get(&src).copied();
3279        if selection_mode.is_none() && origin == RouteSelectionOrigin::Discovered {
3280            return self.apply_adaptive_discovery_selection_locked(st, src, sides);
3281        }
3282
3283        match selection_mode.unwrap_or(RouteSelectionMode::Fanout) {
3284            RouteSelectionMode::Fanout => sides,
3285            RouteSelectionMode::Weighted => {
3286                crate::small_sort::sort(&mut sides);
3287                let total_weight = sides.iter().fold(0_u64, |acc, side| {
3288                    acc + u64::from(st.route_weights.get(&(src, *side)).copied().unwrap_or(1))
3289                });
3290                if total_weight == 0 {
3291                    return Vec::new();
3292                }
3293                let cursor = st.route_selection_cursors.entry(src).or_insert(0);
3294                let pick = *cursor % total_weight;
3295                *cursor = cursor.wrapping_add(1);
3296                let mut remaining = pick;
3297                for side in sides {
3298                    let weight =
3299                        u64::from(st.route_weights.get(&(src, side)).copied().unwrap_or(1));
3300                    if remaining < weight {
3301                        return vec![side];
3302                    }
3303                    remaining -= weight;
3304                }
3305                Vec::new()
3306            }
3307            RouteSelectionMode::Failover => {
3308                sides.sort_by_key(|side| {
3309                    (
3310                        st.route_priorities.get(&(src, *side)).copied().unwrap_or(0),
3311                        *side,
3312                    )
3313                });
3314                sides.truncate(1);
3315                sides
3316            }
3317        }
3318    }
3319
3320    fn apply_adaptive_discovery_selection_locked(
3321        &self,
3322        st: &mut RouterInner,
3323        src: Option<RouterSideId>,
3324        mut sides: Vec<RouterSideId>,
3325    ) -> Vec<RouterSideId> {
3326        crate::small_sort::sort(&mut sides);
3327        let mut unmeasured: Vec<_> = sides
3328            .iter()
3329            .copied()
3330            .filter(|side| !st.adaptive_route_stats.contains_key(side))
3331            .collect();
3332        if !unmeasured.is_empty() {
3333            let cursor = st.route_selection_cursors.entry(src).or_insert(0);
3334            let pick = (*cursor as usize) % unmeasured.len();
3335            *cursor = cursor.wrapping_add(1);
3336            return vec![unmeasured.swap_remove(pick)];
3337        }
3338
3339        let now_ms = self.clock.now_ms();
3340        let total_weight = sides.iter().fold(0_u64, |acc, side| {
3341            acc + st
3342                .adaptive_route_stats
3343                .get(side)
3344                .map(|stats| stats.weight(now_ms))
3345                .unwrap_or(1)
3346        });
3347        if total_weight == 0 {
3348            sides.truncate(1);
3349            return sides;
3350        }
3351
3352        let cursor = st.route_selection_cursors.entry(src).or_insert(0);
3353        let pick = *cursor % total_weight;
3354        *cursor = cursor.wrapping_add(1);
3355        let mut remaining = pick;
3356        for side in sides {
3357            let weight = st
3358                .adaptive_route_stats
3359                .get(&side)
3360                .map(|stats| stats.weight(now_ms))
3361                .unwrap_or(1);
3362            if remaining < weight {
3363                return vec![side];
3364            }
3365            remaining -= weight;
3366        }
3367        Vec::new()
3368    }
3369
3370    fn record_side_tx_sample(
3371        &self,
3372        side: RouterSideId,
3373        bytes: usize,
3374        started_ms: u64,
3375        ended_ms: u64,
3376    ) {
3377        let sample_ms = ended_ms.saturating_sub(started_ms).max(1);
3378        let sample_bps = ((bytes as u128).saturating_mul(1000) / u128::from(sample_ms))
3379            .min(u128::from(u64::MAX)) as u64;
3380        let mut st = self.state.lock();
3381        st.adaptive_route_stats
3382            .entry(side)
3383            .or_default()
3384            .observe(bytes, sample_bps, ended_ms);
3385    }
3386
3387    /// Seed adaptive route selection with a transport-measured link probe.
3388    ///
3389    /// Call this after a side-specific bring-up probe, or whenever the transport already knows the
3390    /// duration for a frame. The router does not emit synthetic probe frames by itself.
3391    pub fn note_side_link_probe_sample(
3392        &self,
3393        side: RouterSideId,
3394        bytes: usize,
3395        duration_ms: u64,
3396    ) -> TelemetryResult<()> {
3397        {
3398            let st = self.state.lock();
3399            let _ = Self::side_ref(&st, side).map_err(|_| TelemetryError::BadArg)?;
3400        }
3401        let ended_ms = self.clock.now_ms();
3402        self.record_side_tx_sample(side, bytes, ended_ms.saturating_sub(duration_ms), ended_ms);
3403        Ok(())
3404    }
3405
3406    fn router_item_wire_len(data: &RouterItem) -> TelemetryResult<usize> {
3407        match data {
3408            RouterItem::Packet(pkt) => Ok(wire_format::packet_wire_size(pkt)),
3409            RouterItem::Packed(bytes) => Ok(bytes.len()),
3410        }
3411    }
3412
3413    /// Extract the logical packet ID targeted by an end-to-end reliable ACK item.
3414    ///
3415    /// Router TX and replay queues can carry either decoded `Packet` values or
3416    /// packed frames. This helper normalizes both forms so ACK-routing code
3417    /// can reason about them uniformly.
3418    ///
3419    /// Only router-generated end-to-end `ReliableAck` packets qualify here.
3420    /// Ordinary application traffic and hop-level reliable control frames return
3421    /// `Ok(None)`.
3422    #[inline]
3423    fn reliable_control_target_packet_id(data: &RouterItem) -> TelemetryResult<Option<u64>> {
3424        match data {
3425            RouterItem::Packet(pkt) => {
3426                if !Self::is_end_to_end_ack_packet(pkt) {
3427                    return Ok(None);
3428                }
3429                Self::decode_end_to_end_reliable_ack(pkt.payload()).map(Some)
3430            }
3431            RouterItem::Packed(bytes) => {
3432                if wire_format::peek_routing_frame_info(bytes.as_ref())
3433                    .ok()
3434                    .is_some_and(|frame| {
3435                        frame.envelope.ty != DataType::ReliableAck || frame.ack_only()
3436                    })
3437                {
3438                    return Ok(None);
3439                }
3440                let pkt = wire_format::unpack_packet(bytes.as_ref())?;
3441                if !Self::is_end_to_end_ack_packet(&pkt) {
3442                    return Ok(None);
3443                }
3444                Self::decode_end_to_end_reliable_ack(pkt.payload()).map(Some)
3445            }
3446        }
3447    }
3448
3449    fn decode_end_to_end_reliable_ack(payload: &[u8]) -> TelemetryResult<u64> {
3450        if payload.len() != 8 {
3451            return Err(TelemetryError::Unpack("bad reliable e2e ack payload"));
3452        }
3453        Ok(u64::from_le_bytes(payload[0..8].try_into().unwrap()))
3454    }
3455
3456    #[inline]
3457    fn sender_hash(sender: &str) -> u64 {
3458        if let Some(address) = sender
3459            .strip_prefix("@addr:")
3460            .and_then(|v| v.parse::<u32>().ok())
3461        {
3462            return u64::from(address);
3463        }
3464        hash_bytes_u64(0x517C_C1B7_2722_0A95, sender.as_bytes())
3465    }
3466
3467    #[inline]
3468    fn fallback_address_for_hostname(hostname: &str) -> NodeAddress {
3469        let hash = Self::sender_hash(hostname);
3470        let mut address = (hash as u32) ^ ((hash >> 32) as u32);
3471        if address == 0 {
3472            address = 1;
3473        }
3474        address
3475    }
3476
3477    /// Resolve the compact wire header's numeric source back to the stable
3478    /// hostname learned by address discovery. The DHCP-style assigned address
3479    /// and the wire address intentionally occupy separate namespaces, so both
3480    /// forms must be considered here.
3481    fn canonical_sender_locked(st: &RouterInner, sender: &str) -> String {
3482        let Some(address) = sender
3483            .strip_prefix("@addr:")
3484            .and_then(|value| value.parse::<u32>().ok())
3485        else {
3486            return sender.to_string();
3487        };
3488        Self::hostname_for_address_locked(st, address).unwrap_or_else(|| sender.to_string())
3489    }
3490
3491    /// Resolve both assigned node addresses and stable compact wire hashes.
3492    ///
3493    /// Remote boards behind a router appear in incremental topology summaries,
3494    /// but do not send their link-local address advertisement across that
3495    /// router. Include those retained topology names so application packets
3496    /// can still be attributed at the far edge of a multi-hop network.
3497    fn hostname_for_address_locked(st: &RouterInner, address: NodeAddress) -> Option<String> {
3498        if let Some(hostname) = st.address_by_value.get(&address) {
3499            return Some(hostname.clone());
3500        }
3501
3502        let mut found: Option<String> = None;
3503        let mut collision = false;
3504        let mut consider = |candidate: &str| {
3505            if sender_address_u32(candidate) != address {
3506                return;
3507            }
3508            match found.as_deref() {
3509                None => found = Some(candidate.to_string()),
3510                Some(existing) if existing == candidate => {}
3511                // Do not guess if two discovered names have the same compact
3512                // 32-bit wire address. Address discovery must resolve that
3513                // conflict before callers can safely attribute the packet.
3514                Some(_) => collision = true,
3515            }
3516        };
3517
3518        for entry in st.address_book.values() {
3519            consider(entry.hostname.as_ref());
3520        }
3521        #[cfg(feature = "discovery")]
3522        for route in st.discovery_routes.values() {
3523            for (announcer, state) in &route.announcers {
3524                consider(announcer);
3525                for board in &state.topology_boards {
3526                    consider(&board.sender_id);
3527                    // A constrained bridge can retain a remote only as a
3528                    // named connection on its own topology node.  That name
3529                    // is still authoritative discovery identity and is
3530                    // sufficient to resolve the deterministic compact sender
3531                    // address used by application and ACK packets.
3532                    for peer in &board.connections {
3533                        consider(peer);
3534                    }
3535                }
3536            }
3537        }
3538        if collision { None } else { found }
3539    }
3540
3541    fn address_mode_from_code(mode: u8, requested: NodeAddress) -> AddressAssignmentMode {
3542        match mode {
3543            2 => AddressAssignmentMode::Static(if requested == 0 { 1 } else { requested }),
3544            1 => AddressAssignmentMode::Requested(if requested == 0 { 1 } else { requested }),
3545            _ => AddressAssignmentMode::Dynamic,
3546        }
3547    }
3548
3549    fn address_winner_pref(entry: &AddressBookEntry) -> (u8, core::cmp::Reverse<u64>, u64) {
3550        let rank = match entry.mode {
3551            AddressAssignmentMode::Static(_) => 2,
3552            AddressAssignmentMode::Requested(_) => 1,
3553            AddressAssignmentMode::Dynamic => 0,
3554        };
3555        (
3556            rank,
3557            core::cmp::Reverse(entry.birth_ms),
3558            u64::MAX - entry.owner_hash,
3559        )
3560    }
3561
3562    fn identity_winner_pref(entry: &AddressBookEntry) -> (core::cmp::Reverse<u64>, u64) {
3563        (
3564            core::cmp::Reverse(entry.birth_ms),
3565            u64::MAX - entry.owner_hash,
3566        )
3567    }
3568
3569    fn allocate_free_address_locked(st: &RouterInner, seed: NodeAddress) -> NodeAddress {
3570        let mut candidate = if seed == 0 { 1 } else { seed };
3571        for _ in 0..u32::MAX {
3572            if candidate != 0 && !st.address_by_value.contains_key(&candidate) {
3573                return candidate;
3574            }
3575            candidate = candidate.wrapping_add(1);
3576            if candidate == 0 {
3577                candidate = 1;
3578            }
3579        }
3580        1
3581    }
3582
3583    fn unique_hostname_locked(st: &RouterInner, base: &str, owner_hash: u64) -> String {
3584        if !st.address_book.contains_key(base) {
3585            return base.to_string();
3586        }
3587        let stem = if base.is_empty() { "node" } else { base };
3588        let mut candidate = format!("{stem}-{owner_hash:08x}");
3589        let mut suffix = 1u32;
3590        while st.address_book.contains_key(&candidate) {
3591            candidate = format!("{stem}-{owner_hash:08x}-{suffix}");
3592            suffix = suffix.saturating_add(1);
3593        }
3594        candidate
3595    }
3596
3597    fn update_local_identity_locked(
3598        &self,
3599        st: &mut RouterInner,
3600        mut local: AddressBookEntry,
3601        reason: AddressChangeReason,
3602    ) -> AddressChange {
3603        let old = st.local_address.clone();
3604        st.address_book.remove(old.hostname.as_ref());
3605        st.address_by_value.remove(&old.address);
3606        local.last_seen_ms = self.clock.now_ms();
3607        st.address_by_value
3608            .insert(local.address, local.hostname.to_string());
3609        st.address_book
3610            .insert(local.hostname.to_string(), local.clone());
3611        st.local_address = local.clone();
3612        *self.sender.lock() = local.hostname.clone();
3613        #[cfg(feature = "discovery")]
3614        Self::note_discovery_topology_change_locked(st, self.clock.now_ms());
3615        AddressChange {
3616            old_hostname: old.hostname,
3617            new_hostname: local.hostname,
3618            old_address: old.address,
3619            new_address: local.address,
3620            reason,
3621        }
3622    }
3623
3624    fn notify_address_change(&self, change: AddressChange) -> TelemetryResult<()> {
3625        for handler in self.cfg.address_change_handlers.iter() {
3626            (handler.handler)(change.clone())?;
3627        }
3628        Ok(())
3629    }
3630
3631    #[cfg(feature = "discovery")]
3632    fn local_address_advertisement(
3633        &self,
3634        reachable_endpoints: Vec<DataEndpoint>,
3635        reachable_network_variables: Vec<DataType>,
3636        reachable_timesync_sources: Vec<String>,
3637        link_capabilities: discovery::LinkCapabilities,
3638        state: u8,
3639    ) -> discovery::AddressAdvertisement {
3640        let st = self.state.lock();
3641        discovery::AddressAdvertisement {
3642            hostname: st.local_address.hostname.to_string(),
3643            address: st.local_address.address,
3644            requested_address: st.local_address.requested_address,
3645            mode: st.local_address.mode.mode_code(),
3646            state,
3647            birth_ms: st.local_address.birth_ms,
3648            owner_hash: st.local_address.owner_hash,
3649            reachable_endpoints,
3650            reachable_network_variables,
3651            reachable_timesync_sources,
3652            link_capabilities,
3653        }
3654    }
3655
3656    #[cfg(feature = "discovery")]
3657    fn ingest_address_advertisement(
3658        &self,
3659        ad: discovery::AddressAdvertisement,
3660    ) -> TelemetryResult<bool> {
3661        let now_ms = self.clock.now_ms();
3662        let mut remote = AddressBookEntry {
3663            hostname: Arc::from(ad.hostname.as_str()),
3664            address: ad.address,
3665            requested_address: ad.requested_address,
3666            mode: Self::address_mode_from_code(ad.mode, ad.requested_address),
3667            birth_ms: ad.birth_ms,
3668            owner_hash: ad.owner_hash,
3669            last_seen_ms: now_ms,
3670        };
3671        let mut change = None;
3672        let mut changed = false;
3673        {
3674            let mut st = self.state.lock();
3675            let local = st.local_address.clone();
3676            let hostname_conflict =
3677                remote.hostname == local.hostname && remote.owner_hash != local.owner_hash;
3678            let address_conflict =
3679                remote.address == local.address && remote.owner_hash != local.owner_hash;
3680
3681            if hostname_conflict
3682                && Self::identity_winner_pref(&remote) > Self::identity_winner_pref(&local)
3683            {
3684                let mut next = local.clone();
3685                next.hostname = Arc::from(Self::unique_hostname_locked(
3686                    &st,
3687                    local.hostname.as_ref(),
3688                    local.owner_hash,
3689                ));
3690                change = Some(self.update_local_identity_locked(
3691                    &mut st,
3692                    next,
3693                    AddressChangeReason::HostnameConflict,
3694                ));
3695                changed = true;
3696            } else if address_conflict
3697                && Self::address_winner_pref(&remote) > Self::address_winner_pref(&local)
3698            {
3699                let mut next = local.clone();
3700                let seed = next
3701                    .requested_address
3702                    .max(Self::fallback_address_for_hostname(next.hostname.as_ref()));
3703                next.address = Self::allocate_free_address_locked(&st, seed);
3704                let reason = match next.mode {
3705                    AddressAssignmentMode::Static(_) => AddressChangeReason::StaticConflict,
3706                    AddressAssignmentMode::Requested(_) => AddressChangeReason::RequestedConflict,
3707                    AddressAssignmentMode::Dynamic => AddressChangeReason::DynamicConflict,
3708                };
3709                change = Some(self.update_local_identity_locked(&mut st, next, reason));
3710                changed = true;
3711            } else if hostname_conflict {
3712                remote.hostname = Arc::from(Self::unique_hostname_locked(
3713                    &st,
3714                    remote.hostname.as_ref(),
3715                    remote.owner_hash,
3716                ));
3717                changed = true;
3718            } else if address_conflict {
3719                let seed = remote
3720                    .requested_address
3721                    .max(Self::fallback_address_for_hostname(
3722                        remote.hostname.as_ref(),
3723                    ));
3724                remote.address = Self::allocate_free_address_locked(&st, seed);
3725                changed = true;
3726            }
3727
3728            st.address_book.remove(remote.hostname.as_ref());
3729            st.address_by_value.remove(&remote.address);
3730            st.address_by_value
3731                .insert(remote.address, remote.hostname.to_string());
3732            st.address_book
3733                .insert(remote.hostname.to_string(), remote.clone());
3734        }
3735        if let Some(change) = change {
3736            self.notify_address_change(change)?;
3737        }
3738        Ok(changed)
3739    }
3740
3741    #[inline]
3742    fn is_end_to_end_ack_sender(sender: &str) -> bool {
3743        sender == Self::END_TO_END_ACK_SENDER || sender.starts_with(Self::END_TO_END_ACK_PREFIX)
3744    }
3745
3746    fn decode_end_to_end_ack_sender_hash(sender: &str) -> Option<u64> {
3747        if let Some(ack_sender) = sender.strip_prefix(Self::END_TO_END_ACK_PREFIX)
3748            && !ack_sender.is_empty()
3749        {
3750            return Some(Self::sender_hash(ack_sender));
3751        }
3752        None
3753    }
3754
3755    /// Return the stable destination key carried by an end-to-end ACK.
3756    ///
3757    /// Compact embedded headers encode senders as numeric addresses, so the legacy
3758    /// `E2EACK:<hostname>` marker cannot survive a firmware hop. New ACKs carry the acknowledging
3759    /// owner's hash in their frozen target contract; marker decoding remains for older peers.
3760    fn end_to_end_ack_sender_hash(pkt: &Packet) -> Option<u64> {
3761        if pkt.data_type() != DataType::ReliableAck {
3762            return None;
3763        }
3764        pkt.wire_target_senders()
3765            .first()
3766            .copied()
3767            .or_else(|| Self::decode_end_to_end_ack_sender_hash(pkt.sender()))
3768    }
3769
3770    fn is_end_to_end_ack_packet(pkt: &Packet) -> bool {
3771        Self::end_to_end_ack_sender_hash(pkt).is_some()
3772            && Self::decode_end_to_end_reliable_ack(pkt.payload()).is_ok()
3773    }
3774
3775    fn encode_end_to_end_ack_sender(&self) -> String {
3776        let sender = self.sender_arc();
3777        format!("{}{}", Self::END_TO_END_ACK_PREFIX, sender)
3778    }
3779
3780    #[cfg(feature = "discovery")]
3781    fn is_end_to_end_destination_sender(sender: &str) -> bool {
3782        sender != "RELAY" && !Self::is_end_to_end_ack_sender(sender)
3783    }
3784
3785    fn encode_end_to_end_reliable_ack(packet_id: u64) -> crate::SharedBytes {
3786        let mut payload = Vec::with_capacity(8);
3787        payload.extend_from_slice(&packet_id.to_le_bytes());
3788        crate::shared_bytes::convert(Arc::<[u8]>::from(payload))
3789    }
3790
3791    fn encode_p2p_payload(
3792        src_hostname: &str,
3793        src_address: NodeAddress,
3794        src_port: P2pPort,
3795        dst_port: P2pPort,
3796        payload: &[u8],
3797    ) -> TelemetryResult<crate::SharedBytes> {
3798        let host_len = u16::try_from(src_hostname.len())
3799            .map_err(|_| TelemetryError::Pack("p2p hostname too long"))?;
3800        let payload_len = u32::try_from(payload.len())
3801            .map_err(|_| TelemetryError::Pack("p2p payload too long"))?;
3802        let mut out = Vec::with_capacity(
3803            15usize
3804                .saturating_add(src_hostname.len())
3805                .saturating_add(payload.len()),
3806        );
3807        out.push(1);
3808        out.extend_from_slice(&dst_port.to_le_bytes());
3809        out.extend_from_slice(&src_port.to_le_bytes());
3810        out.extend_from_slice(&src_address.to_le_bytes());
3811        out.extend_from_slice(&host_len.to_le_bytes());
3812        out.extend_from_slice(&payload_len.to_le_bytes());
3813        out.extend_from_slice(src_hostname.as_bytes());
3814        out.extend_from_slice(payload);
3815        Ok(out.into())
3816    }
3817
3818    fn decode_p2p_payload(payload: &[u8]) -> TelemetryResult<P2pDecoded<'_>> {
3819        if payload.len() < 15 {
3820            return Err(TelemetryError::Unpack("p2p frame short"));
3821        }
3822        if payload[0] != 1 {
3823            return Err(TelemetryError::Unpack("p2p frame version"));
3824        }
3825        let destination_port =
3826            u16::from_le_bytes(payload[1..3].try_into().expect("2-byte dst port"));
3827        let source_port = u16::from_le_bytes(payload[3..5].try_into().expect("2-byte src port"));
3828        let source_address = u32::from_le_bytes(payload[5..9].try_into().expect("4-byte address"));
3829        let host_len =
3830            u16::from_le_bytes(payload[9..11].try_into().expect("2-byte host len")) as usize;
3831        let body_len =
3832            u32::from_le_bytes(payload[11..15].try_into().expect("4-byte body len")) as usize;
3833        let host_start = 15usize;
3834        let host_end = host_start.saturating_add(host_len);
3835        let body_end = host_end.saturating_add(body_len);
3836        if host_end > payload.len() || body_end != payload.len() {
3837            return Err(TelemetryError::Unpack("p2p frame length"));
3838        }
3839        let source_hostname = core::str::from_utf8(&payload[host_start..host_end])
3840            .map_err(|_| TelemetryError::Unpack("p2p hostname utf8"))?;
3841        Ok(P2pDecoded {
3842            source_hostname,
3843            source_address,
3844            source_port,
3845            destination_port,
3846            payload: &payload[host_end..body_end],
3847        })
3848    }
3849
3850    fn encode_p2p_stream_payload(
3851        flags: u8,
3852        source_stream_id: P2pStreamId,
3853        destination_stream_id: P2pStreamId,
3854        sequence: u32,
3855        payload: &[u8],
3856    ) -> TelemetryResult<crate::SharedBytes> {
3857        let payload_len = u32::try_from(payload.len())
3858            .map_err(|_| TelemetryError::Pack("p2p stream payload too long"))?;
3859        let mut out = Vec::with_capacity(22usize.saturating_add(payload.len()));
3860        out.extend_from_slice(&P2P_STREAM_MAGIC);
3861        out.push(P2P_STREAM_VERSION);
3862        out.push(flags);
3863        out.extend_from_slice(&source_stream_id.to_le_bytes());
3864        out.extend_from_slice(&destination_stream_id.to_le_bytes());
3865        out.extend_from_slice(&sequence.to_le_bytes());
3866        out.extend_from_slice(&payload_len.to_le_bytes());
3867        out.extend_from_slice(payload);
3868        Ok(out.into())
3869    }
3870
3871    fn decode_p2p_stream_payload(payload: &[u8]) -> TelemetryResult<Option<P2pStreamDecoded<'_>>> {
3872        if !payload.starts_with(&P2P_STREAM_MAGIC) {
3873            return Ok(None);
3874        }
3875        if payload.len() < 22 {
3876            return Err(TelemetryError::Unpack("p2p stream frame short"));
3877        }
3878        if payload[4] != P2P_STREAM_VERSION {
3879            return Err(TelemetryError::Unpack("p2p stream frame version"));
3880        }
3881        let flags = payload[5];
3882        let source_stream_id =
3883            u32::from_le_bytes(payload[6..10].try_into().expect("stream source id"));
3884        let destination_stream_id =
3885            u32::from_le_bytes(payload[10..14].try_into().expect("stream destination id"));
3886        let sequence = u32::from_le_bytes(payload[14..18].try_into().expect("stream sequence"));
3887        let body_len =
3888            u32::from_le_bytes(payload[18..22].try_into().expect("stream body len")) as usize;
3889        let body_end = 22usize.saturating_add(body_len);
3890        if body_end != payload.len() {
3891            return Err(TelemetryError::Unpack("p2p stream frame length"));
3892        }
3893        Ok(Some(P2pStreamDecoded {
3894            flags,
3895            source_stream_id,
3896            destination_stream_id,
3897            sequence,
3898            payload: &payload[22..],
3899        }))
3900    }
3901
3902    fn allocate_p2p_stream_id_locked(st: &mut RouterInner) -> P2pStreamId {
3903        for _ in 0..u32::MAX {
3904            let id = st.next_p2p_stream_id.max(1);
3905            st.next_p2p_stream_id = st.next_p2p_stream_id.wrapping_add(1).max(1);
3906            if !st.p2p_stream_sessions.contains_key(&id) {
3907                return id;
3908            }
3909        }
3910        0
3911    }
3912
3913    fn dispatch_p2p_packet(&self, pkt: &Packet) -> TelemetryResult<()> {
3914        if pkt.data_type() != DataType::P2pMessage {
3915            return Ok(());
3916        }
3917        let decoded = Self::decode_p2p_payload(pkt.payload())?;
3918        if let Some(stream) = Self::decode_p2p_stream_payload(decoded.payload)? {
3919            return self.dispatch_p2p_stream_frame(&decoded, &stream);
3920        }
3921        let handlers = {
3922            let st = self.state.lock();
3923            st.p2p_port_handlers
3924                .get(&decoded.destination_port)
3925                .cloned()
3926                .unwrap_or_default()
3927        };
3928        for handler in handlers {
3929            (handler.handler)(P2pMessage {
3930                source_hostname: decoded.source_hostname,
3931                source_address: decoded.source_address,
3932                source_port: decoded.source_port,
3933                destination_port: decoded.destination_port,
3934                payload: decoded.payload,
3935            })?;
3936        }
3937        Ok(())
3938    }
3939
3940    fn dispatch_p2p_stream_frame(
3941        &self,
3942        msg: &P2pDecoded<'_>,
3943        frame: &P2pStreamDecoded<'_>,
3944    ) -> TelemetryResult<()> {
3945        let mut events: Vec<PendingP2pStreamEvent> = Vec::new();
3946        let mut reply: Option<(AddressBookEntry, P2pPort, P2pPort, crate::SharedBytes)> = None;
3947        {
3948            let mut st = self.state.lock();
3949            if frame.flags & P2P_STREAM_SYN != 0 && frame.flags & P2P_STREAM_ACK == 0 {
3950                let peer_hostname: Arc<str> = Arc::from(msg.source_hostname);
3951                let existing_id = st.p2p_stream_sessions.iter().find_map(|(id, session)| {
3952                    (session.peer_stream_id == frame.source_stream_id
3953                        && session.local_port == msg.destination_port
3954                        && session.peer_port == msg.source_port
3955                        && session.peer_address == msg.source_address
3956                        && session.peer_hostname.as_ref() == msg.source_hostname)
3957                        .then_some(*id)
3958                });
3959                let local_id = if let Some(local_id) = existing_id {
3960                    local_id
3961                } else {
3962                    let local_id = Self::allocate_p2p_stream_id_locked(&mut st);
3963                    if local_id == 0 {
3964                        return Err(TelemetryError::Io("p2p stream id exhausted"));
3965                    }
3966                    st.p2p_stream_sessions.insert(
3967                        local_id,
3968                        P2pStreamSession {
3969                            peer_hostname: peer_hostname.clone(),
3970                            peer_address: msg.source_address,
3971                            local_port: msg.destination_port,
3972                            peer_port: msg.source_port,
3973                            peer_stream_id: frame.source_stream_id,
3974                            next_sequence: 1,
3975                            connected: true,
3976                        },
3977                    );
3978                    let handlers = st
3979                        .p2p_stream_handlers
3980                        .get(&msg.destination_port)
3981                        .cloned()
3982                        .unwrap_or_default();
3983                    events.push(PendingP2pStreamEvent {
3984                        handlers,
3985                        kind: P2pStreamEventKind::Accepted,
3986                        stream_id: local_id,
3987                        peer_stream_id: frame.source_stream_id,
3988                        peer_hostname: peer_hostname.clone(),
3989                        peer_address: msg.source_address,
3990                        local_port: msg.destination_port,
3991                        peer_port: msg.source_port,
3992                    });
3993                    local_id
3994                };
3995                let dst =
3996                    st.address_book
3997                        .get(msg.source_hostname)
3998                        .cloned()
3999                        .unwrap_or(AddressBookEntry {
4000                            hostname: peer_hostname,
4001                            address: msg.source_address,
4002                            requested_address: msg.source_address,
4003                            mode: AddressAssignmentMode::Dynamic,
4004                            birth_ms: self.clock.now_ms(),
4005                            owner_hash: Self::sender_hash(msg.source_hostname),
4006                            last_seen_ms: self.clock.now_ms(),
4007                        });
4008                let payload = Self::encode_p2p_stream_payload(
4009                    P2P_STREAM_SYN | P2P_STREAM_ACK,
4010                    local_id,
4011                    frame.source_stream_id,
4012                    0,
4013                    &[],
4014                )?;
4015                reply = Some((dst, msg.source_port, msg.destination_port, payload));
4016            } else if frame.flags & P2P_STREAM_SYN != 0 && frame.flags & P2P_STREAM_ACK != 0 {
4017                if let Some(session) = st.p2p_stream_sessions.get_mut(&frame.destination_stream_id)
4018                {
4019                    session.peer_stream_id = frame.source_stream_id;
4020                    session.connected = true;
4021                    let peer_hostname = session.peer_hostname.clone();
4022                    let peer_address = session.peer_address;
4023                    let local_port = session.local_port;
4024                    let peer_port = session.peer_port;
4025                    let handlers = st
4026                        .p2p_stream_handlers
4027                        .get(&local_port)
4028                        .cloned()
4029                        .unwrap_or_default();
4030                    events.push(PendingP2pStreamEvent {
4031                        handlers,
4032                        kind: P2pStreamEventKind::Connected,
4033                        stream_id: frame.destination_stream_id,
4034                        peer_stream_id: frame.source_stream_id,
4035                        peer_hostname,
4036                        peer_address,
4037                        local_port,
4038                        peer_port,
4039                    });
4040                }
4041            } else if frame.flags & (P2P_STREAM_FIN | P2P_STREAM_RST | P2P_STREAM_DATA) != 0 {
4042                let kind = if frame.flags & P2P_STREAM_RST != 0 {
4043                    P2pStreamEventKind::Reset
4044                } else if frame.flags & P2P_STREAM_FIN != 0 {
4045                    P2pStreamEventKind::Closed
4046                } else {
4047                    P2pStreamEventKind::Data
4048                };
4049                let session_id = if frame.destination_stream_id != 0 {
4050                    Some(frame.destination_stream_id)
4051                } else {
4052                    st.p2p_stream_sessions.iter().find_map(|(id, session)| {
4053                        (session.peer_stream_id == frame.source_stream_id
4054                            && session.local_port == msg.destination_port
4055                            && session.peer_port == msg.source_port
4056                            && session.peer_address == msg.source_address
4057                            && session.peer_hostname.as_ref() == msg.source_hostname)
4058                            .then_some(*id)
4059                    })
4060                };
4061                if let Some(session_id) = session_id
4062                    && let Some(session) = st.p2p_stream_sessions.get(&session_id)
4063                {
4064                    let handlers = st
4065                        .p2p_stream_handlers
4066                        .get(&session.local_port)
4067                        .cloned()
4068                        .unwrap_or_default();
4069                    events.push(PendingP2pStreamEvent {
4070                        handlers,
4071                        kind,
4072                        stream_id: session_id,
4073                        peer_stream_id: frame.source_stream_id,
4074                        peer_hostname: session.peer_hostname.clone(),
4075                        peer_address: session.peer_address,
4076                        local_port: session.local_port,
4077                        peer_port: session.peer_port,
4078                    });
4079                    if matches!(kind, P2pStreamEventKind::Closed | P2pStreamEventKind::Reset) {
4080                        st.p2p_stream_sessions.remove(&session_id);
4081                    }
4082                }
4083            }
4084        }
4085        if let Some((dst, dst_port, src_port, payload)) = reply {
4086            self.send_p2p_to_entry(dst, dst_port, src_port, &payload)?;
4087        }
4088        for pending in events {
4089            for handler in pending.handlers {
4090                (handler.handler)(P2pStreamEvent {
4091                    kind: pending.kind,
4092                    stream_id: pending.stream_id,
4093                    peer_stream_id: pending.peer_stream_id,
4094                    sequence: frame.sequence,
4095                    peer_hostname: pending.peer_hostname.as_ref(),
4096                    peer_address: pending.peer_address,
4097                    local_port: pending.local_port,
4098                    peer_port: pending.peer_port,
4099                    payload: frame.payload,
4100                })?;
4101            }
4102        }
4103        Ok(())
4104    }
4105
4106    /// Record the original live ingress for `packet_id`, preserving it across echoes.
4107    ///
4108    /// End-to-end reliable acknowledgements should return only toward the
4109    /// source-side that originated the data flow, not be flooded everywhere.
4110    /// This helper stores that learned return path in a bounded LRU-like cache.
4111    fn note_reliable_return_route(&self, side: RouterSideId, packet_id: u64) -> bool {
4112        let mut st = self.state.lock();
4113        // The retained proxy record owns retries until its child cohort
4114        // completes. Neither CAN reflections nor repeated upstream copies may
4115        // replace its return route or reapply the managed value meanwhile.
4116        if st.end_to_end_reliable_tx.contains_key(&packet_id)
4117            && st
4118                .reliable_return_routes
4119                .get(&packet_id)
4120                .is_some_and(|route| route.upstream_contract.is_some())
4121        {
4122            return false;
4123        }
4124        // A retry can remain in RX dedupe after its smaller return-path cache
4125        // entry was evicted. Relearning that path must not restart the replay
4126        // delay on every retry, or sustained cache churn suppresses it forever.
4127        let now = self.clock.now_ms();
4128        let retry_ms = runtime_reliable_retransmit_ms();
4129        let duplicate = st.recent_rx.contains(&packet_id);
4130        let last_replay_ms = if duplicate {
4131            now.saturating_sub(retry_ms)
4132        } else {
4133            now
4134        };
4135        // A reflected copy is not evidence that the original publisher
4136        // moved. Keep the first live ingress during the current retry
4137        // window, otherwise an echo can redirect an already queued ACK into
4138        // the downstream bus. A retry-window expiry or disabled/removed side
4139        // still permits alternate-path recovery without explicit operator action.
4140        let replace_side = !duplicate
4141            || st
4142                .reliable_return_routes
4143                .get(&packet_id)
4144                .is_none_or(|route| {
4145                    now.saturating_sub(route.last_replay_ms) >= retry_ms
4146                        || !Self::side_ref(&st, route.side)
4147                            .is_ok_and(|side| side.opts.egress_enabled)
4148                });
4149        if !Self::remember_reliable_return_route_locked(&mut st, packet_id) {
4150            return true;
4151        }
4152        st.reliable_return_routes
4153            .entry(packet_id)
4154            .and_modify(|route| {
4155                if replace_side {
4156                    route.side = side;
4157                }
4158            })
4159            .or_insert(ReliableReturnRouteState {
4160                side,
4161                last_replay_ms,
4162                upstream_contract: None,
4163            });
4164        true
4165    }
4166
4167    /// Ensure `packet_id` is retained in the bounded reliable return-route cache.
4168    ///
4169    /// Existing entries are refreshed to the back of the order list. When the
4170    /// cache is full, evict the oldest route not pinned by active delivery.
4171    /// Refuse a new entry if every retained route is still active.
4172    fn remember_reliable_return_route_locked(st: &mut RouterInner, packet_id: u64) -> bool {
4173        let cap = runtime_reliable_max_return_routes().max(1);
4174        st.reliable_return_route_order
4175            .retain(|id| st.reliable_return_routes.contains_key(id) && *id != packet_id);
4176        while st.reliable_return_route_order.len() >= cap {
4177            let Some(index) = st
4178                .reliable_return_route_order
4179                .iter()
4180                .position(|id| !st.end_to_end_reliable_tx.contains_key(id))
4181            else {
4182                return false;
4183            };
4184            if let Some(oldest) = st.reliable_return_route_order.remove(index) {
4185                st.reliable_return_routes.remove(&oldest);
4186            }
4187        }
4188        st.reliable_return_route_order.push_back(packet_id);
4189        true
4190    }
4191
4192    fn remember_end_to_end_reliable_tx_locked(st: &mut RouterInner, packet_id: u64) {
4193        let cap = runtime_reliable_max_end_to_end_pending().max(1);
4194        st.end_to_end_reliable_tx_order
4195            .retain(|id| st.end_to_end_reliable_tx.contains_key(id) && *id != packet_id);
4196        while st.end_to_end_reliable_tx_order.len() >= cap {
4197            if let Some(oldest) = st.end_to_end_reliable_tx_order.pop_front() {
4198                st.end_to_end_reliable_tx.remove(&oldest);
4199            } else {
4200                break;
4201            }
4202        }
4203        st.end_to_end_reliable_tx_order.push_back(packet_id);
4204    }
4205
4206    #[cfg(feature = "discovery")]
4207    fn expected_end_to_end_destinations_locked(
4208        &self,
4209        st: &RouterInner,
4210        data: &RouterItem,
4211        exclude: Option<RouterSideId>,
4212        managed: bool,
4213    ) -> TelemetryResult<BTreeMap<u64, RouterSideId>> {
4214        let (eps, ty) = self.item_route_info(data)?;
4215        let now_ms = self.clock.now_ms();
4216        let restrict_link_local = Self::endpoints_are_link_local_only(&eps);
4217        /* Managed-variable ownership is advertised independently from normal
4218         * endpoint handlers.  Using topology-board endpoint overlap here can
4219         * silently narrow a variable update to one segment once detailed
4220         * topology arrives (for example FLIGHT_STATE is a conventional
4221         * endpoint on the fill side but only a managed variable on avionics).
4222         * Freeze one destination contract per advertising segment instead;
4223         * its announcer is the segment router that can ACK and replicate the
4224         * value to the owners behind it. */
4225        if managed || st.managed_variable_types.contains(&ty.as_u32()) {
4226            let mut out = BTreeMap::new();
4227            for (&side, route) in st.discovery_routes.iter() {
4228                if Some(side) == exclude {
4229                    continue;
4230                }
4231                if now_ms.saturating_sub(route.last_seen_ms) > DISCOVERY_ROUTE_TTL_MS {
4232                    continue;
4233                }
4234                let Some(side_ref) = st.sides.get(side).and_then(Option::as_ref) else {
4235                    continue;
4236                };
4237                if restrict_link_local && !side_ref.opts.link_local_enabled {
4238                    continue;
4239                }
4240                if !self.route_allowed_locked(st, exclude, Some(ty), side) {
4241                    continue;
4242                }
4243                for (sender_id, sender_state) in route.announcers.iter() {
4244                    if now_ms.saturating_sub(sender_state.last_seen_ms) > DISCOVERY_ROUTE_TTL_MS
4245                        || !sender_state.reachable_network_variables.contains(&ty)
4246                        || !Self::is_end_to_end_destination_sender(sender_id)
4247                    {
4248                        continue;
4249                    }
4250                    out.insert(Self::sender_hash(sender_id), side);
4251                    if out.len() >= runtime_reliable_max_end_to_end_pending().max(1) {
4252                        return Ok(out);
4253                    }
4254                }
4255            }
4256            return Ok(out);
4257        }
4258        let prefer_best_overlap =
4259            is_reliable_type(ty) && Self::reliable_control_target_packet_id(data)?.is_none();
4260        let scoring_eps = self.preferred_scoring_endpoints(&eps, prefer_best_overlap);
4261        let mut best_overlap = 0usize;
4262        let limit = runtime_reliable_max_end_to_end_pending().max(1);
4263        let mut out = BTreeMap::new();
4264        for (&side, route) in st.discovery_routes.iter() {
4265            if Some(side) == exclude {
4266                continue;
4267            }
4268            if now_ms.saturating_sub(route.last_seen_ms) > DISCOVERY_ROUTE_TTL_MS {
4269                continue;
4270            }
4271            let Some(side_ref) = st.sides.get(side).and_then(Option::as_ref) else {
4272                continue;
4273            };
4274            if restrict_link_local && !side_ref.opts.link_local_enabled {
4275                continue;
4276            }
4277            if !self.route_allowed_locked(st, exclude, Some(ty), side) {
4278                continue;
4279            }
4280            for sender_state in route.announcers.values() {
4281                if now_ms.saturating_sub(sender_state.last_seen_ms) > DISCOVERY_ROUTE_TTL_MS {
4282                    continue;
4283                }
4284                for board in sender_state.topology_boards.iter() {
4285                    if !Self::is_end_to_end_destination_sender(&board.sender_id) {
4286                        continue;
4287                    }
4288                    let overlap = Self::endpoint_overlap_count(
4289                        board.reachable_endpoints.iter().copied(),
4290                        &scoring_eps,
4291                    );
4292                    if overlap > 0 {
4293                        if prefer_best_overlap {
4294                            if overlap < best_overlap {
4295                                continue;
4296                            }
4297                            if overlap > best_overlap {
4298                                out.clear();
4299                                best_overlap = overlap;
4300                            }
4301                            // Keep only the first bounded cohort at the best
4302                            // score. Later higher scores replace it; no second
4303                            // candidate vector or allocation pass is needed.
4304                            if out.len() >= limit {
4305                                continue;
4306                            }
4307                        }
4308                        out.insert(Self::sender_hash(&board.sender_id), side);
4309                        if !prefer_best_overlap && out.len() >= limit {
4310                            return Ok(out);
4311                        }
4312                    }
4313                }
4314            }
4315        }
4316
4317        Ok(out)
4318    }
4319
4320    #[cfg(feature = "discovery")]
4321    #[allow(clippy::too_many_arguments)]
4322    fn discovered_route_candidates_locked(
4323        &self,
4324        st: &RouterInner,
4325        exclude: Option<RouterSideId>,
4326        ty: DataType,
4327        eps: &[DataEndpoint],
4328        target_senders: &[u64],
4329        prefer_nonlocal: bool,
4330        preferred_timesync_source: Option<&str>,
4331    ) -> Vec<DiscoveryCandidateMatch> {
4332        let restrict_link_local = Self::endpoints_are_link_local_only(eps);
4333        let now_ms = self.clock.now_ms();
4334        let scoring_eps = self.preferred_scoring_endpoints(eps, prefer_nonlocal);
4335        let has_variable_owner = st.discovery_routes.iter().any(|(&side, route)| {
4336            exclude != Some(side)
4337                && now_ms.saturating_sub(route.last_seen_ms) <= DISCOVERY_ROUTE_TTL_MS
4338                && route.reachable_network_variables.contains(&ty)
4339        });
4340        let mut out = Vec::new();
4341        let mut unique_endpoint_fallbacks = Vec::new();
4342
4343        for (&side, route) in st.discovery_routes.iter() {
4344            if exclude == Some(side)
4345                || now_ms.saturating_sub(route.last_seen_ms) > DISCOVERY_ROUTE_TTL_MS
4346            {
4347                continue;
4348            }
4349            if restrict_link_local
4350                && st
4351                    .sides
4352                    .get(side)
4353                    .and_then(|s| s.as_ref())
4354                    .map(|s| !s.opts.link_local_enabled)
4355                    .unwrap_or(true)
4356            {
4357                continue;
4358            }
4359            if !self.route_allowed_locked(st, exclude, Some(ty), side) {
4360                continue;
4361            }
4362            if !target_senders.is_empty()
4363                && !Self::side_matches_target_senders_locked(st, side, target_senders, now_ms)
4364            {
4365                if scoring_eps
4366                    .iter()
4367                    .copied()
4368                    .any(|ep| route.reachable.contains(&ep))
4369                {
4370                    unique_endpoint_fallbacks.push(side);
4371                }
4372                continue;
4373            }
4374            if !target_senders.is_empty() {
4375                out.push(DiscoveryCandidateMatch {
4376                    side,
4377                    overlap: usize::MAX,
4378                });
4379                continue;
4380            }
4381            if has_variable_owner {
4382                if route.reachable_network_variables.contains(&ty) {
4383                    out.push(DiscoveryCandidateMatch {
4384                        side,
4385                        overlap: usize::MAX,
4386                    });
4387                }
4388                continue;
4389            }
4390            if preferred_timesync_source
4391                .is_some_and(|source| route.reachable_timesync_sources.iter().any(|s| s == source))
4392            {
4393                out.push(DiscoveryCandidateMatch {
4394                    side,
4395                    overlap: usize::MAX,
4396                });
4397                continue;
4398            }
4399            let overlap =
4400                Self::endpoint_overlap_count(route.reachable.iter().copied(), &scoring_eps);
4401            if overlap > 0 {
4402                out.push(DiscoveryCandidateMatch { side, overlap });
4403            }
4404        }
4405        if !target_senders.is_empty() && out.is_empty() && unique_endpoint_fallbacks.len() == 1 {
4406            out.push(DiscoveryCandidateMatch {
4407                side: unique_endpoint_fallbacks[0],
4408                overlap: usize::MAX,
4409            });
4410        }
4411        out
4412    }
4413
4414    /// Remove reflected discovery routes when another side advertises a
4415    /// shorter topology path to the destination endpoint. A multi-side Router
4416    /// must make this choice before adaptive weighting; otherwise an endpoint
4417    /// learned directly and through a remote bridge can be sent down either
4418    /// side with equal probability.
4419    #[cfg(feature = "discovery")]
4420    fn retain_shortest_discovery_candidates_locked(
4421        st: &RouterInner,
4422        matches: &mut Vec<DiscoveryCandidateMatch>,
4423        endpoints: &[DataEndpoint],
4424        target_senders: &[u64],
4425        now_ms: u64,
4426    ) {
4427        if matches.len() <= 1 || endpoints.is_empty() {
4428            return;
4429        }
4430
4431        let route_distance = |side: RouterSideId, target_sender: Option<u64>| -> Option<usize> {
4432            let route = st.discovery_routes.get(&side)?;
4433            route
4434                .announcers
4435                .iter()
4436                .filter(|(_, state)| {
4437                    now_ms.saturating_sub(state.last_seen_ms) <= DISCOVERY_ROUTE_TTL_MS
4438                })
4439                .filter_map(|(announcer, state)| {
4440                    let targets: BTreeSet<&str> = state
4441                        .topology_boards
4442                        .iter()
4443                        .filter(|board| {
4444                            target_sender
4445                                .map(|target| Self::sender_hash(&board.sender_id) == target)
4446                                .unwrap_or_else(|| {
4447                                    endpoints.iter().any(|endpoint| {
4448                                        board.reachable_endpoints.contains(endpoint)
4449                                    })
4450                                })
4451                        })
4452                        .map(|board| board.sender_id.as_str())
4453                        .collect();
4454                    if targets.is_empty() {
4455                        return if let Some(target) = target_sender {
4456                            state
4457                                .topology_boards
4458                                .iter()
4459                                .any(|board| Self::sender_hash(&board.sender_id) == target)
4460                                .then_some(usize::MAX / 2)
4461                        } else {
4462                            state
4463                                .reachable
4464                                .iter()
4465                                .any(|endpoint| endpoints.contains(endpoint))
4466                                .then_some(usize::MAX / 2)
4467                        };
4468                    }
4469
4470                    let mut distances: BTreeMap<&str, usize> = BTreeMap::new();
4471                    let mut pending = VecDeque::from([(announcer.as_str(), 0usize)]);
4472                    while let Some((sender, distance)) = pending.pop_front() {
4473                        if distances.contains_key(sender) {
4474                            continue;
4475                        }
4476                        distances.insert(sender, distance);
4477                        if targets.contains(sender) {
4478                            return Some(distance);
4479                        }
4480                        for board in &state.topology_boards {
4481                            if board.sender_id == sender {
4482                                for peer in &board.connections {
4483                                    if !distances.contains_key(peer.as_str()) {
4484                                        pending.push_back((peer.as_str(), distance + 1));
4485                                    }
4486                                }
4487                            } else if board.connections.iter().any(|peer| peer == sender)
4488                                && !distances.contains_key(board.sender_id.as_str())
4489                            {
4490                                pending.push_back((board.sender_id.as_str(), distance + 1));
4491                            }
4492                        }
4493                    }
4494                    None
4495                })
4496                .min()
4497        };
4498
4499        let route_targets: Vec<Option<u64>> = if target_senders.is_empty() {
4500            vec![None]
4501        } else {
4502            target_senders.iter().copied().map(Some).collect()
4503        };
4504        let mut selected = BTreeSet::new();
4505        for target in route_targets {
4506            let distances: Vec<(RouterSideId, usize)> = matches
4507                .iter()
4508                .filter_map(|candidate| {
4509                    route_distance(candidate.side, target)
4510                        .map(|distance| (candidate.side, distance))
4511                })
4512                .collect();
4513            let Some(best) = distances.iter().map(|(_, distance)| *distance).min() else {
4514                continue;
4515            };
4516            selected.extend(
4517                distances
4518                    .into_iter()
4519                    .filter(|(_, distance)| *distance == best)
4520                    .map(|(side, _)| side),
4521            );
4522        }
4523        if !selected.is_empty() {
4524            matches.retain(|candidate| selected.contains(&candidate.side));
4525        }
4526    }
4527
4528    #[cfg(feature = "discovery")]
4529    fn select_discovered_candidate_sides_locked(
4530        &self,
4531        st: &mut RouterInner,
4532        exclude: Option<RouterSideId>,
4533        ty: DataType,
4534        target_senders: &[u64],
4535        prefer_best_overlap: bool,
4536        matches: Vec<DiscoveryCandidateMatch>,
4537    ) -> Vec<RouterSideId> {
4538        let discovered_origin = if Self::has_typed_route_overrides_locked(st, exclude, ty)
4539            || !target_senders.is_empty()
4540        {
4541            RouteSelectionOrigin::Flood
4542        } else {
4543            RouteSelectionOrigin::Discovered
4544        };
4545
4546        let mut matches = matches;
4547        if matches.iter().any(|m| m.overlap == usize::MAX) {
4548            matches.retain(|m| m.overlap == usize::MAX);
4549        }
4550
4551        let selected: Vec<RouterSideId> = if prefer_best_overlap {
4552            let best_overlap = matches.iter().map(|m| m.overlap).max().unwrap_or(0);
4553            matches
4554                .into_iter()
4555                .filter(|m| m.overlap == best_overlap)
4556                .map(|m| m.side)
4557                .collect()
4558        } else {
4559            matches.into_iter().map(|m| m.side).collect()
4560        };
4561
4562        /* Heartbeats represent network-wide liveness rather than a request for
4563         * one interchangeable endpoint owner.  When discovery finds the
4564         * heartbeat endpoint on multiple independent shortest-path sides,
4565         * every segment must receive it.  Ordinary data continues through the
4566         * adaptive single-path selector so a multi-homed router does not
4567         * duplicate application traffic. */
4568        let has_distributed_variable_owners = selected.iter().any(|side| {
4569            st.discovery_routes
4570                .get(side)
4571                .is_some_and(|route| route.reachable_network_variables.contains(&ty))
4572        });
4573        if DataType::try_named("HEARTBEAT") == Some(ty) || has_distributed_variable_owners {
4574            selected
4575        } else {
4576            self.apply_route_selection_locked(st, exclude, selected, discovered_origin)
4577        }
4578    }
4579
4580    #[allow(unused_mut)]
4581    fn register_end_to_end_reliable_tx(
4582        &self,
4583        data: &RouterItem,
4584        destinations: Option<BTreeMap<u64, RouterSideId>>,
4585    ) -> TelemetryResult<bool> {
4586        let packet_id = Self::get_hash(data);
4587        let now_ms = self.clock.now_ms();
4588        let ty = match data {
4589            RouterItem::Packet(pkt) => pkt.data_type(),
4590            RouterItem::Packed(bytes) => wire_format::peek_routing_envelope(bytes.as_ref())?.ty,
4591        };
4592        let mut st = self.state.lock();
4593        #[cfg(feature = "discovery")]
4594        let mut pending_destinations = match destinations {
4595            Some(destinations) => destinations,
4596            None => self.expected_end_to_end_destinations_locked(&st, data, None, false)?,
4597        };
4598        #[cfg(not(feature = "discovery"))]
4599        let mut pending_destinations = destinations.unwrap_or_default();
4600        // Callers pass the destination map used to construct this frozen
4601        // contract. Re-filtering the same map allocates owner scratch and
4602        // instantiates a second tree-extraction path on small MCUs.
4603        let frozen = match data {
4604            RouterItem::Packet(pkt) => !pkt.wire_target_senders().is_empty(),
4605            RouterItem::Packed(bytes) => {
4606                wire_format::peek_routing_envelope(bytes)?
4607                    .target_senders()
4608                    .len()
4609                    != 0
4610            }
4611        };
4612        self.filter_trackable_end_to_end_destinations_locked(
4613            &st,
4614            ty,
4615            &mut pending_destinations,
4616            frozen,
4617        );
4618        let tracked_destinations = !pending_destinations.is_empty();
4619        if let Some(existing) = st.end_to_end_reliable_tx.get(&packet_id) {
4620            return Ok(!existing.waiting_for_predecessor);
4621        }
4622        // Never evict an unacknowledged predecessor to admit a newer message.
4623        if st.end_to_end_reliable_tx.len() >= runtime_reliable_max_end_to_end_pending().max(1) {
4624            return Err(TelemetryError::HandlerError(
4625                "end-to-end reliable window full",
4626            ));
4627        }
4628        let ordered_type =
4629            matches!(reliable_mode(ty), crate::ReliableMode::Ordered).then_some(ty.as_u32());
4630        let waiting_for_predecessor = ordered_type.is_some()
4631            && st
4632                .end_to_end_reliable_tx
4633                .values()
4634                .any(|sent| sent.ordered_type == ordered_type);
4635        #[cfg(feature = "std")]
4636        if reliable_trace_enabled() {
4637            eprintln!(
4638                "[sedsnet-reliable] register time={now_ms} type={} id={packet_id:#x} waiting={waiting_for_predecessor} targets={pending_destinations:?}",
4639                ty.as_u32()
4640            );
4641        }
4642        let mut retained = data.clone();
4643        #[cfg(feature = "compact-packet-store")]
4644        retained.park_for_queue()?;
4645        Self::remember_end_to_end_reliable_tx_locked(&mut st, packet_id);
4646        st.end_to_end_reliable_tx.insert(
4647            packet_id,
4648            EndToEndReliableSent {
4649                ordered_type,
4650                waiting_for_predecessor,
4651                data: retained,
4652                pending_destinations,
4653                tracked_destinations,
4654                last_send_ms: now_ms,
4655                retries: 0,
4656                queued: false,
4657            },
4658        );
4659        Ok(!waiting_for_predecessor)
4660    }
4661
4662    // Select at most one in-flight packet per ordered type. Waiting messages
4663    // share the existing bounded pending window, not an unbounded extra queue.
4664    fn release_ordered_successors(&self) -> TelemetryResult<()> {
4665        let ready = {
4666            let mut st = self.state.lock();
4667            let RouterInner {
4668                end_to_end_reliable_tx_order: order,
4669                end_to_end_reliable_tx: pending,
4670                ..
4671            } = &mut *st;
4672            let mut ready = Vec::new();
4673            for (index, id) in order.iter().copied().enumerate() {
4674                let Some(sent) = pending.get(&id) else {
4675                    continue;
4676                };
4677                if !sent.waiting_for_predecessor {
4678                    continue;
4679                }
4680                let Some(ty) = sent.ordered_type else {
4681                    continue;
4682                };
4683                // The retained window is bounded. Inspect earlier entries
4684                // instead of rebuilding an allocated active-type set on every
4685                // idle service pass. Only an actual release needs ready storage.
4686                if order.iter().take(index).any(|earlier| {
4687                    pending
4688                        .get(earlier)
4689                        .is_some_and(|sent| sent.ordered_type == Some(ty))
4690                }) {
4691                    continue;
4692                }
4693                let sent = pending.get_mut(&id).expect("retained ordered entry");
4694                sent.waiting_for_predecessor = false;
4695                sent.last_send_ms = self.clock.now_ms();
4696                ready.push(id);
4697            }
4698            ready
4699        };
4700        for id in ready {
4701            self.queue_end_to_end_reliable_retransmit(id)?;
4702        }
4703        Ok(())
4704    }
4705
4706    #[cfg(feature = "discovery")]
4707    fn reconcile_end_to_end_reliable_destinations_locked(
4708        &self,
4709        st: &mut RouterInner,
4710    ) -> TelemetryResult<()> {
4711        if st.end_to_end_reliable_tx.is_empty() {
4712            return Ok(());
4713        }
4714        let active_destinations = self.active_end_to_end_destinations_locked(st);
4715        let packet_ids: Vec<u64> = st.end_to_end_reliable_tx.keys().copied().collect();
4716        let mut completed = Vec::new();
4717
4718        for packet_id in packet_ids {
4719            let Some(data) = st
4720                .end_to_end_reliable_tx
4721                .get(&packet_id)
4722                .map(|sent| sent.data.clone())
4723            else {
4724                continue;
4725            };
4726            let excluded = st
4727                .reliable_return_routes
4728                .get(&packet_id)
4729                .filter(|route| route.upstream_contract.is_some())
4730                .map(|route| route.side);
4731            let expected =
4732                self.expected_end_to_end_destinations_locked(st, &data, excluded, false)?;
4733            let Some(sent) = st.end_to_end_reliable_tx.get_mut(&packet_id) else {
4734                continue;
4735            };
4736            if !sent.tracked_destinations {
4737                continue;
4738            }
4739            // Route churn is not a delivery acknowledgement for ordered data.
4740            if sent.ordered_type.is_some() {
4741                for (sender, side) in &mut sent.pending_destinations {
4742                    if let Some(next_side) = expected.get(sender).or_else(|| {
4743                        active_destinations
4744                            .get(sender)
4745                            .filter(|side| Some(**side) != excluded)
4746                    }) {
4747                        *side = *next_side;
4748                    }
4749                }
4750                continue;
4751            }
4752            sent.pending_destinations.retain(|sender_hash, side| {
4753                match (
4754                    expected.get(sender_hash),
4755                    active_destinations
4756                        .get(sender_hash)
4757                        .filter(|side| Some(**side) != excluded),
4758                ) {
4759                    (Some(next_side), _) | (None, Some(next_side)) => {
4760                        *side = *next_side;
4761                        true
4762                    }
4763                    (None, None) => false,
4764                }
4765            });
4766            if sent.pending_destinations.is_empty() {
4767                completed.push(packet_id);
4768            }
4769        }
4770
4771        for packet_id in completed {
4772            st.end_to_end_reliable_tx.remove(&packet_id);
4773        }
4774
4775        Ok(())
4776    }
4777
4778    #[cfg(feature = "discovery")]
4779    fn active_end_to_end_destinations_locked(
4780        &self,
4781        st: &RouterInner,
4782    ) -> BTreeMap<u64, RouterSideId> {
4783        let now_ms = self.clock.now_ms();
4784        let mut out = BTreeMap::new();
4785        for (&side, route) in st.discovery_routes.iter() {
4786            if now_ms.saturating_sub(route.last_seen_ms) > DISCOVERY_ROUTE_TTL_MS {
4787                continue;
4788            }
4789            for (sender_name, sender_state) in &route.announcers {
4790                if now_ms.saturating_sub(sender_state.last_seen_ms) > DISCOVERY_ROUTE_TTL_MS {
4791                    continue;
4792                }
4793                if Self::is_end_to_end_destination_sender(sender_name) {
4794                    out.insert(Self::sender_hash(sender_name), side);
4795                }
4796                for board in sender_state.topology_boards.iter() {
4797                    if !Self::is_end_to_end_destination_sender(&board.sender_id) {
4798                        continue;
4799                    }
4800                    out.insert(Self::sender_hash(&board.sender_id), side);
4801                    if out.len() >= runtime_reliable_max_end_to_end_pending().max(1) {
4802                        return out;
4803                    }
4804                }
4805            }
4806        }
4807        out
4808    }
4809
4810    fn side_supports_end_to_end_tracking_locked(st: &RouterInner, side: RouterSideId) -> bool {
4811        matches!(
4812            st.sides
4813                .get(side)
4814                .and_then(Option::as_ref)
4815                .map(|side| &side.tx_handler),
4816            Some(RouterTxHandlerFn::Packed(_) | RouterTxHandlerFn::PackedWithPriority(_))
4817        )
4818    }
4819
4820    fn filter_trackable_end_to_end_destinations_locked(
4821        &self,
4822        st: &RouterInner,
4823        ty: DataType,
4824        pending: &mut BTreeMap<u64, RouterSideId>,
4825        frozen: bool,
4826    ) {
4827        // A heartbeat describes current liveness. Retrying an old heartbeat
4828        // until every discovered endpoint acknowledges it turns temporary
4829        // congestion into an ACK/retransmit storm and can starve fresh
4830        // heartbeats. Per-link reliability still applies where configured;
4831        // the next periodic heartbeat is the end-to-end freshness retry.
4832        if DataType::try_named("HEARTBEAT") == Some(ty) {
4833            pending.clear();
4834            return;
4835        }
4836        if frozen {
4837            return;
4838        }
4839        let now_ms = self.clock.now_ms();
4840        pending.retain(|_, side| {
4841            Self::side_supports_end_to_end_tracking_locked(st, *side)
4842                && (st.managed_variable_types.contains(&ty.as_u32())
4843                    || is_reliable_type(ty)
4844                    || !self.side_has_multiple_announcers_locked(st, *side, now_ms))
4845        });
4846    }
4847
4848    #[cfg(feature = "discovery")]
4849    fn side_has_multiple_announcers_locked(
4850        &self,
4851        st: &RouterInner,
4852        side: RouterSideId,
4853        now_ms: u64,
4854    ) -> bool {
4855        st.discovery_routes
4856            .get(&side)
4857            .map(|route| {
4858                route
4859                    .announcers
4860                    .values()
4861                    .filter(|sender| {
4862                        now_ms.saturating_sub(sender.last_seen_ms) <= DISCOVERY_ROUTE_TTL_MS
4863                    })
4864                    .take(2)
4865                    .count()
4866                    > 1
4867            })
4868            .unwrap_or(false)
4869    }
4870
4871    #[cfg(not(feature = "discovery"))]
4872    fn side_has_multiple_announcers_locked(
4873        &self,
4874        _st: &RouterInner,
4875        _side: RouterSideId,
4876        _now_ms: u64,
4877    ) -> bool {
4878        false
4879    }
4880
4881    fn queue_end_to_end_reliable_ack(
4882        &self,
4883        pkt: &Packet,
4884        called_from_queue: bool,
4885    ) -> TelemetryResult<()> {
4886        self.queue_end_to_end_reliable_ack_for_packet_id(
4887            pkt.packet_id(),
4888            pkt.sender(),
4889            called_from_queue,
4890        )
4891    }
4892
4893    fn queue_end_to_end_reliable_ack_for_packet_id(
4894        &self,
4895        packet_id: u64,
4896        original_sender: &str,
4897        called_from_queue: bool,
4898    ) -> TelemetryResult<()> {
4899        if !self.cfg.reliable_enabled() {
4900            return Ok(());
4901        }
4902        let original_sender_hash = {
4903            let st = self.state.lock();
4904            Self::sender_hash(&Self::canonical_sender_locked(&st, original_sender))
4905        };
4906        self.queue_end_to_end_reliable_ack_for_sender_hash(
4907            packet_id,
4908            original_sender_hash,
4909            called_from_queue,
4910        )
4911    }
4912
4913    fn queue_end_to_end_reliable_ack_for_sender_hash(
4914        &self,
4915        packet_id: u64,
4916        original_sender_hash: u64,
4917        called_from_queue: bool,
4918    ) -> TelemetryResult<()> {
4919        if !self.cfg.reliable_enabled() {
4920            return Ok(());
4921        }
4922        let ack_sender = self.encode_end_to_end_ack_sender();
4923        let ack = Packet::new(
4924            DataType::ReliableAck,
4925            message_meta(DataType::ReliableAck).endpoints_ref(),
4926            ack_sender.as_str(),
4927            self.packet_timestamp_ms(),
4928            crate::shared_bytes::convert(Self::encode_end_to_end_reliable_ack(packet_id)),
4929        )?;
4930        let local_sender = self.sender_arc();
4931        // Keep the acknowledging owner first for compatibility. The second
4932        // identity is the original publisher: it lets a busy relay recover the
4933        // ACK route from discovery after its bounded packet-id cache expires.
4934        let ack = self.attach_wire_contract_to_item(
4935            RouterItem::Packet(ack),
4936            &[
4937                Self::sender_hash(local_sender.as_ref()),
4938                original_sender_hash,
4939            ],
4940        )?;
4941        // Freeze a known return side in the queued receipt itself. Its bounded
4942        // packet-id cache can churn before dispatch; an echoed data frame must
4943        // not redirect an ACK which already has a valid destination.
4944        let return_side = {
4945            let st = self.state.lock();
4946            st.reliable_return_routes
4947                .get(&packet_id)
4948                .map(|route| route.side)
4949        };
4950        let item = match return_side {
4951            Some(dst) => RouterTxItem::ToSide {
4952                src: None,
4953                dst,
4954                data: ack,
4955            },
4956            None => RouterTxItem::Broadcast(ack),
4957        };
4958        self.emit_internal_tx(item, true, called_from_queue)
4959    }
4960
4961    fn emit_internal_tx(
4962        &self,
4963        item: RouterTxItem,
4964        ignore_local: bool,
4965        called_from_queue: bool,
4966    ) -> TelemetryResult<()> {
4967        if called_from_queue {
4968            self.tx_queue_item_with_flags(item, ignore_local)
4969        } else {
4970            self.tx_item_impl(item, ignore_local, false)
4971        }
4972    }
4973
4974    fn emit_internal_tx_with_priority(
4975        &self,
4976        item: RouterTxItem,
4977        ignore_local: bool,
4978        priority: u8,
4979        called_from_queue: bool,
4980    ) -> TelemetryResult<()> {
4981        if called_from_queue {
4982            self.tx_queue_item_with_priority(item, ignore_local, priority)
4983        } else {
4984            self.tx_item_impl(item, ignore_local, false)
4985        }
4986    }
4987
4988    fn queue_end_to_end_reliable_retransmit(&self, packet_id: u64) -> TelemetryResult<()> {
4989        {
4990            let mut st = self.state.lock();
4991            let Some(sent) = st.end_to_end_reliable_tx.get_mut(&packet_id) else {
4992                return Ok(());
4993            };
4994            if sent.queued {
4995                return Ok(());
4996            }
4997            sent.queued = true;
4998        }
4999        let result = self.tx_queue_item_with_priority(
5000            RouterTxItem::EndToEndReplay { packet_id },
5001            true,
5002            Self::router_item_priority_bumped(DataType::ReliableAck),
5003        );
5004        if result.is_err()
5005            && let Some(sent) = self.state.lock().end_to_end_reliable_tx.get_mut(&packet_id)
5006        {
5007            sent.queued = false;
5008        }
5009        result
5010    }
5011
5012    fn end_to_end_retransmit_sides(
5013        &self,
5014        packet_id: u64,
5015    ) -> Option<(RouterItem, Vec<RouterSideId>)> {
5016        let mut st = self.state.lock();
5017        let (data, tracked_destinations, mut sides) = {
5018            let sent = st.end_to_end_reliable_tx.get_mut(&packet_id)?;
5019            #[cfg(feature = "std")]
5020            if reliable_trace_enabled() {
5021                eprintln!(
5022                    "[sedsnet-reliable] replay time={} id={packet_id:#x} retries={} targets={:?}",
5023                    self.clock.now_ms(),
5024                    sent.retries,
5025                    sent.pending_destinations
5026                );
5027            }
5028            sent.queued = false;
5029            sent.last_send_ms = self.clock.now_ms();
5030            let data = sent.data.clone();
5031            let tracked_destinations = sent.tracked_destinations;
5032            let sides: Vec<RouterSideId> = sent.pending_destinations.values().copied().collect();
5033            (data, tracked_destinations, sides)
5034        };
5035        if tracked_destinations && sides.is_empty() {
5036            st.end_to_end_reliable_tx.remove(&packet_id);
5037            return None;
5038        }
5039        crate::small_sort::sort(&mut sides);
5040        sides.dedup();
5041        Some((data, sides))
5042    }
5043
5044    fn item_type_priority(data: &RouterItem) -> TelemetryResult<(DataType, u8)> {
5045        let (ty, control) = match data {
5046            RouterItem::Packet(pkt) => (
5047                pkt.data_type(),
5048                pkt.payload().len() <= 16
5049                    && pkt.endpoints().len() <= 3
5050                    && crate::small_frozen_control(
5051                        pkt.data_type(),
5052                        pkt.wire_target_senders().len(),
5053                        pkt.wire_shape(),
5054                    ),
5055            ),
5056            RouterItem::Packed(bytes) => {
5057                let env = wire_format::peek_routing_envelope(bytes.as_ref())?;
5058                let control = bytes.len() <= 128
5059                    && bytes.first().is_some_and(|flags| flags & 0x11 == 0)
5060                    && env.endpoints().take(4).count() <= 3
5061                    && crate::small_frozen_control(
5062                        env.ty,
5063                        env.target_senders().len(),
5064                        env.wire_shape,
5065                    );
5066                (env.ty, control)
5067            }
5068        };
5069        let priority = crate::scheduler_priority(ty);
5070        Ok((ty, if control { priority.max(254) } else { priority }))
5071    }
5072
5073    fn router_item_priority(data: &RouterItem) -> TelemetryResult<u8> {
5074        Self::item_type_priority(data).map(|(_, priority)| priority)
5075    }
5076
5077    fn item_service_priority(&self, data: &RouterItem) -> TelemetryResult<u8> {
5078        let (ty, priority) = Self::item_type_priority(data)?;
5079        if self.is_managed_variable_type(ty) {
5080            Ok(crate::transport_priority(DataType::ManagedVariableValue))
5081        } else {
5082            Ok(priority)
5083        }
5084    }
5085
5086    #[inline]
5087    fn router_item_priority_bumped(ty: DataType) -> u8 {
5088        crate::scheduler_priority(ty).saturating_add(16)
5089    }
5090
5091    #[inline]
5092    fn is_side_tx_busy(err: &TelemetryError) -> bool {
5093        matches!(err, TelemetryError::Io("side tx busy"))
5094    }
5095
5096    #[cfg(feature = "timesync")]
5097    fn timesync_has_usable_time_locked(st: &TimeSyncRuntime, now_mono_ns: u64) -> bool {
5098        st.disciplined_clock.read_unix_ms(now_mono_ns).is_some()
5099            || st
5100                .clock
5101                .current_time(now_mono_ns)
5102                .and_then(|reading| reading.unix_time_ms)
5103                .is_some()
5104    }
5105
5106    #[cfg(feature = "timesync")]
5107    fn reconcile_pending_timesync_request_locked(
5108        st: &mut TimeSyncRuntime,
5109        leader: &Option<TimeSyncLeader>,
5110        now_ms: u64,
5111    ) {
5112        let active_remote = match leader {
5113            Some(TimeSyncLeader::Remote(remote)) => Some(remote.sender.as_str()),
5114            _ => None,
5115        };
5116        let should_clear = st
5117            .pending_request
5118            .as_ref()
5119            .is_some_and(|pending| Some(pending.source.as_str()) != active_remote);
5120        if should_clear {
5121            st.pending_request = None;
5122            st.next_request_mono_ms = now_ms;
5123        }
5124    }
5125
5126    ///Helper function for relay_send
5127    #[inline]
5128    fn enqueue_to_sides(
5129        &self,
5130        data: RouterItem,
5131        exclude: Option<RouterSideId>,
5132        ignore_local: bool,
5133    ) -> TelemetryResult<()> {
5134        let Some(data) = self.clear_intermediate_hop_targets(data, true)? else {
5135            return Ok(());
5136        };
5137        let plan = self.remote_side_plan(&data, exclude)?;
5138        let mut st = self.state.lock();
5139        let ty = Self::item_data_type(&data)?;
5140        let priority = if st.managed_variable_types.contains(&ty.as_u32()) {
5141            crate::transport_priority(DataType::ManagedVariableValue)
5142        } else {
5143            Self::router_item_priority(&data)?
5144        };
5145
5146        let RemoteSidePlan::Target(sides) = plan;
5147        for idx in sides {
5148            st.push_transmit(TxQueued {
5149                item: RouterTxItem::ToSide {
5150                    src: exclude,
5151                    dst: idx,
5152                    data: data.clone(),
5153                },
5154                ignore_local,
5155                priority,
5156                retry_after_ms: 0,
5157            })?;
5158        }
5159
5160        Ok(())
5161    }
5162
5163    fn relay_send(
5164        &self,
5165        data: RouterItem,
5166        src: Option<RouterSideId>,
5167        called_from_queue: bool,
5168    ) -> TelemetryResult<()> {
5169        if called_from_queue {
5170            return self.enqueue_to_sides(data, src, true);
5171        }
5172
5173        let Some(data) = self.clear_intermediate_hop_targets(data, false)? else {
5174            return Ok(());
5175        };
5176        let RemoteSidePlan::Target(sides) = self.remote_side_plan(&data, src)?;
5177        for side in sides {
5178            self.tx_item_impl(
5179                RouterTxItem::ToSide {
5180                    src,
5181                    dst: side,
5182                    data: data.clone(),
5183                },
5184                true,
5185                false,
5186            )?;
5187        }
5188
5189        Ok(())
5190    }
5191
5192    fn item_route_info(&self, data: &RouterItem) -> TelemetryResult<(Vec<DataEndpoint>, DataType)> {
5193        match data {
5194            RouterItem::Packet(pkt) => {
5195                pkt.validate()?;
5196                let mut eps = pkt.endpoints().to_vec();
5197                crate::small_sort::sort(&mut eps);
5198                eps.dedup();
5199                Ok((eps, pkt.data_type()))
5200            }
5201            RouterItem::Packed(bytes) => {
5202                let env = wire_format::peek_routing_envelope(bytes.as_ref())?;
5203                let mut eps: Vec<DataEndpoint> = env.endpoints().collect();
5204                crate::small_sort::sort(&mut eps);
5205                eps.dedup();
5206                Ok((eps, env.ty))
5207            }
5208        }
5209    }
5210
5211    fn unpack_packet_for_role(&self, bytes: &[u8]) -> TelemetryResult<Packet> {
5212        let managed = crate::memory_admission::supports_work_kinds()
5213            && wire_format::scheduling_type(bytes).is_some_and(|ty| {
5214                !is_internal_control_type(ty) && self.is_managed_variable_type(ty)
5215            });
5216        wire_format::unpack_packet_with_role(bytes, managed)
5217    }
5218
5219    fn item_data_type(data: &RouterItem) -> TelemetryResult<DataType> {
5220        match data {
5221            RouterItem::Packet(pkt) => Ok(pkt.data_type()),
5222            RouterItem::Packed(bytes) => Ok(wire_format::peek_routing_envelope(bytes.as_ref())?.ty),
5223        }
5224    }
5225
5226    fn e2e_crypto_supported(&self) -> bool {
5227        #[cfg(feature = "cryptography")]
5228        {
5229            self.cfg.e2e_encryption() != RouterE2eEncryptionMode::Disabled
5230                && crate::crypto::registered_crypto_available()
5231        }
5232        #[cfg(not(feature = "cryptography"))]
5233        {
5234            false
5235        }
5236    }
5237
5238    fn should_require_e2e_for_type(&self, ty: DataType) -> bool {
5239        if is_internal_control_type(ty) {
5240            return false;
5241        }
5242        match self.cfg.e2e_encryption() {
5243            RouterE2eEncryptionMode::Disabled => {
5244                message_e2e_encryption_policy(ty) == E2eEncryptionPolicy::RequireOn
5245            }
5246            RouterE2eEncryptionMode::RequiredOnly => {
5247                message_e2e_encryption_policy(ty) == E2eEncryptionPolicy::RequireOn
5248            }
5249            RouterE2eEncryptionMode::Preferred => matches!(
5250                message_e2e_encryption_policy(ty),
5251                E2eEncryptionPolicy::PreferOn | E2eEncryptionPolicy::RequireOn
5252            ),
5253            RouterE2eEncryptionMode::ForceAll => true,
5254        }
5255    }
5256
5257    fn ensure_e2e_policy_supported_for_type(&self, ty: DataType) -> TelemetryResult<()> {
5258        if self.should_require_e2e_for_type(ty) && !self.e2e_crypto_supported() {
5259            return Err(TelemetryError::BadArg);
5260        }
5261        Ok(())
5262    }
5263
5264    #[cfg(feature = "cryptography")]
5265    fn e2e_seal_config_for_type(&self, ty: DataType) -> Option<wire_format::E2eSealConfig> {
5266        if self.should_require_e2e_for_type(ty) && self.e2e_crypto_supported() {
5267            Some(wire_format::E2eSealConfig {
5268                key_id: self.cfg.e2e_key_id(),
5269            })
5270        } else {
5271            None
5272        }
5273    }
5274
5275    #[inline]
5276    fn pack_packet_for_router(
5277        &self,
5278        pkt: &Packet,
5279        reliable: Option<wire_format::ReliableHeader>,
5280    ) -> TelemetryResult<crate::SharedBytes> {
5281        #[cfg(feature = "cryptography")]
5282        if let Some(e2e) = self.e2e_seal_config_for_type(pkt.data_type()) {
5283            return (wire_format::pack_packet_with_wire_contract_e2e(
5284                pkt,
5285                reliable,
5286                pkt.wire_shape(),
5287                pkt.wire_target_senders(),
5288                e2e,
5289            ))
5290            .map(crate::shared_bytes::convert);
5291        }
5292        Ok(match reliable {
5293            Some(hdr) => {
5294                crate::shared_bytes::convert(wire_format::pack_packet_with_reliable(pkt, hdr))
5295            }
5296            None => crate::shared_bytes::convert(wire_format::pack_packet(pkt)),
5297        })
5298    }
5299
5300    #[inline]
5301    fn pack_packet_for_contract(
5302        &self,
5303        pkt: &Packet,
5304        reliable: Option<wire_format::ReliableHeader>,
5305        shape: Option<MessageElement>,
5306        target_senders: &[u64],
5307    ) -> TelemetryResult<crate::SharedBytes> {
5308        #[cfg(feature = "cryptography")]
5309        if let Some(e2e) = self.e2e_seal_config_for_type(pkt.data_type()) {
5310            return (wire_format::pack_packet_with_wire_contract_e2e(
5311                pkt,
5312                reliable,
5313                shape,
5314                target_senders,
5315                e2e,
5316            ))
5317            .map(crate::shared_bytes::convert);
5318        }
5319        wire_format::pack_packet_with_wire_contract(pkt, reliable, shape, target_senders)
5320            .map(crate::shared_bytes::convert)
5321    }
5322
5323    #[cfg(feature = "cryptography")]
5324    #[inline]
5325    fn prepare_packed_for_remote(
5326        &self,
5327        bytes: crate::SharedBytes,
5328        reliable_override: Option<Option<wire_format::ReliableHeader>>,
5329    ) -> TelemetryResult<crate::SharedBytes> {
5330        let frame = wire_format::peek_frame_info(bytes.as_ref())?;
5331        if frame.ack_only() || self.e2e_seal_config_for_type(frame.envelope.ty).is_none() {
5332            return Ok(bytes);
5333        }
5334        let reliable = reliable_override.unwrap_or(frame.reliable);
5335        let pkt = self.unpack_packet_for_role(bytes.as_ref())?;
5336        self.pack_packet_for_contract(
5337            &pkt,
5338            reliable,
5339            frame.envelope.wire_shape,
5340            &frame.envelope.target_senders,
5341        )
5342    }
5343
5344    // Best-effort telemetry does not become reliable merely because discovery
5345    // currently sees one peer. Preserve explicit frozen receipt contracts and
5346    // managed-variable delivery even when the current schema is best effort.
5347    fn item_requires_end_to_end_tracking(
5348        &self,
5349        data: &RouterItem,
5350        ty: DataType,
5351    ) -> TelemetryResult<bool> {
5352        if is_reliable_type(ty) || self.is_managed_variable_type(ty) {
5353            return Ok(true);
5354        }
5355        match data {
5356            RouterItem::Packet(packet) => Ok(!packet.wire_target_senders().is_empty()),
5357            RouterItem::Packed(bytes) => {
5358                let frame = wire_format::peek_routing_frame_info(bytes.as_ref())?;
5359                Ok(frame.reliable.is_some() || frame.envelope.target_senders().len() != 0)
5360            }
5361        }
5362    }
5363
5364    fn item_target_senders<'a>(
5365        &self,
5366        data: &'a RouterItem,
5367    ) -> TelemetryResult<alloc::borrow::Cow<'a, [u64]>> {
5368        match data {
5369            RouterItem::Packet(pkt) => Ok(alloc::borrow::Cow::Borrowed(pkt.wire_target_senders())),
5370            RouterItem::Packed(bytes) => {
5371                let env = wire_format::peek_routing_envelope(bytes.as_ref())?;
5372                if env.target_senders().len() == 0 {
5373                    Ok(alloc::borrow::Cow::Borrowed(&[]))
5374                } else {
5375                    Ok(alloc::borrow::Cow::Owned(env.target_senders().collect()))
5376                }
5377            }
5378        }
5379    }
5380
5381    fn item_targets_local_sender(&self, data: &RouterItem) -> TelemetryResult<bool> {
5382        let local_hash = Self::sender_hash(self.sender_arc().as_ref());
5383        match data {
5384            RouterItem::Packet(pkt) => Ok(pkt.wire_target_senders().is_empty()
5385                || pkt.wire_target_senders().contains(&local_hash)),
5386            RouterItem::Packed(bytes) => {
5387                let env = wire_format::peek_routing_envelope(bytes.as_ref())?;
5388                Ok(env.target_senders().len() == 0
5389                    || env.target_senders().any(|target| target == local_hash))
5390            }
5391        }
5392    }
5393
5394    fn item_targets_router_as_intermediate_hop(
5395        &self,
5396        data: &RouterItem,
5397        endpoints: &[DataEndpoint],
5398    ) -> TelemetryResult<bool> {
5399        // Inspect borrowed owners once. ACK contracts identify receipt owners,
5400        // not managed-value proxies; unaddressed transit remains byte-identical.
5401        let local_target = match data {
5402            RouterItem::Packet(pkt) => {
5403                if pkt.wire_target_senders().is_empty() || is_internal_control_type(pkt.data_type())
5404                {
5405                    return Ok(false);
5406                }
5407                pkt.wire_target_senders()
5408                    .contains(&Self::sender_hash(self.sender_arc().as_ref()))
5409            }
5410            RouterItem::Packed(bytes) => {
5411                let env = wire_format::peek_routing_envelope(bytes)?;
5412                if env.target_senders().len() == 0 || is_internal_control_type(env.ty) {
5413                    return Ok(false);
5414                }
5415                let local = Self::sender_hash(self.sender_arc().as_ref());
5416                env.target_senders().any(|owner| owner == local)
5417            }
5418        };
5419        Ok(local_target
5420            && !endpoints.iter().copied().any(|endpoint| {
5421                self.endpoint_has_packet_handler(endpoint)
5422                    || self.endpoint_has_packed_handler(endpoint)
5423            }))
5424    }
5425
5426    fn clear_intermediate_hop_targets(
5427        &self,
5428        data: RouterItem,
5429        called_from_queue: bool,
5430    ) -> TelemetryResult<Option<RouterItem>> {
5431        // Every explicitly targeted proxy resolves its own downstream segment,
5432        // including a publisher contract naming several independent bridges.
5433        // Ordinary broadcasts need no owned endpoint or target scratch.
5434        let has_targets = match &data {
5435            RouterItem::Packet(pkt) => !pkt.wire_target_senders().is_empty(),
5436            RouterItem::Packed(bytes) => {
5437                wire_format::peek_routing_envelope(bytes)?
5438                    .target_senders()
5439                    .len()
5440                    != 0
5441            }
5442        };
5443        if !has_targets {
5444            return Ok(Some(data));
5445        }
5446        let (endpoints, _) = self.item_route_info(&data)?;
5447        if !self.item_targets_router_as_intermediate_hop(&data, &endpoints)? {
5448            return Ok(Some(data));
5449        }
5450        let pkt = match &data {
5451            RouterItem::Packet(pkt) => pkt.clone(),
5452            RouterItem::Packed(bytes) => self.unpack_packet_for_role(bytes.as_ref())?,
5453        };
5454        let reliable = match &data {
5455            RouterItem::Packet(_) => None,
5456            RouterItem::Packed(bytes) => wire_format::peek_frame_info(bytes.as_ref())?.reliable,
5457        };
5458        let shape = match &data {
5459            RouterItem::Packet(pkt) => pkt.wire_shape(),
5460            RouterItem::Packed(bytes) => {
5461                wire_format::peek_routing_envelope(bytes.as_ref())?.wire_shape
5462            }
5463        };
5464        let upstream_id = Self::get_hash(&data);
5465        #[cfg(feature = "discovery")]
5466        let destinations = {
5467            let st = self.state.lock();
5468            let upstream = st
5469                .reliable_return_routes
5470                .get(&upstream_id)
5471                .map(|route| route.side);
5472            let mut pending =
5473                self.expected_end_to_end_destinations_locked(&st, &data, upstream, true)?;
5474            if pending.is_empty() {
5475                pending =
5476                    self.expected_end_to_end_destinations_locked(&st, &data, upstream, false)?;
5477            }
5478            pending
5479        };
5480        #[cfg(feature = "discovery")]
5481        let targets: Vec<u64> = destinations.keys().copied().collect();
5482        #[cfg(not(feature = "discovery"))]
5483        let targets: Vec<u64> = Vec::new();
5484        #[cfg(feature = "discovery")]
5485        if targets.is_empty() {
5486            if self.packet_has_local_delivery(&pkt) {
5487                self.queue_end_to_end_reliable_ack_for_packet_id(
5488                    upstream_id,
5489                    pkt.sender(),
5490                    called_from_queue,
5491                )?;
5492                return Ok(Some(data));
5493            }
5494            if !is_reliable_type(pkt.data_type()) && reliable.is_none() {
5495                return Err(TelemetryError::Io("no discovered route"));
5496            }
5497        }
5498        // Preserve the publisher's packet identity across proxy hops. The
5499        // alias and frozen reader cohort distinguish downstream receipts;
5500        // changing the nonce here would defeat deduplication around cycles.
5501        let forwarded = self.pack_packet_for_contract(&pkt, reliable, shape, &targets)?;
5502        let downstream_id = wire_format::packet_id_from_wire(forwarded.as_ref())?;
5503        let forwarded = RouterItem::Packed(forwarded);
5504        #[cfg(feature = "discovery")]
5505        let already_pending = self
5506            .state
5507            .lock()
5508            .end_to_end_reliable_tx
5509            .contains_key(&downstream_id);
5510        #[cfg(feature = "discovery")]
5511        let ready = self.register_end_to_end_reliable_tx(&forwarded, Some(destinations))?;
5512        #[cfg(not(feature = "discovery"))]
5513        let (ready, already_pending) = {
5514            let _ = called_from_queue;
5515            (true, false)
5516        };
5517        #[cfg(feature = "std")]
5518        if reliable_trace_enabled() {
5519            eprintln!(
5520                "[sedsnet-reliable] proxy retain upstream={upstream_id:#x} downstream={downstream_id:#x} existing={already_pending} ready={ready}"
5521            );
5522        }
5523        {
5524            let mut st = self.state.lock();
5525            let Some(mut route) = st.reliable_return_routes.get(&upstream_id).cloned() else {
5526                st.end_to_end_reliable_tx.remove(&downstream_id);
5527                return Err(TelemetryError::Io("no reliable return route"));
5528            };
5529            route.upstream_contract = Some((
5530                upstream_id,
5531                Self::sender_hash(&Self::canonical_sender_locked(&st, pkt.sender())),
5532            ));
5533            if !Self::remember_reliable_return_route_locked(&mut st, downstream_id) {
5534                st.end_to_end_reliable_tx.remove(&downstream_id);
5535                return Err(TelemetryError::Io("reliable return route window full"));
5536            }
5537            st.reliable_return_routes.insert(downstream_id, route);
5538        }
5539        Ok((ready && !already_pending).then_some(forwarded))
5540    }
5541
5542    #[cfg(feature = "discovery")]
5543    fn side_matches_target_senders_locked(
5544        st: &RouterInner,
5545        side: RouterSideId,
5546        target_senders: &[u64],
5547        now_ms: u64,
5548    ) -> bool {
5549        st.discovery_routes
5550            .get(&side)
5551            .map(|route| {
5552                if now_ms.saturating_sub(route.last_seen_ms) > DISCOVERY_ROUTE_TTL_MS {
5553                    return false;
5554                }
5555                route.announcers.iter().any(|(announcer, sender_state)| {
5556                    if now_ms.saturating_sub(sender_state.last_seen_ms) > DISCOVERY_ROUTE_TTL_MS {
5557                        return false;
5558                    }
5559                    target_senders.contains(&Self::sender_hash(announcer))
5560                        || sender_state
5561                            .advertised_address
5562                            .is_some_and(|address| target_senders.contains(&u64::from(address)))
5563                        || sender_state.topology_boards.iter().any(|board| {
5564                            target_senders.contains(&Self::sender_hash(&board.sender_id))
5565                        })
5566                })
5567            })
5568            .unwrap_or(false)
5569    }
5570
5571    fn attach_wire_contract_to_item(
5572        &self,
5573        data: RouterItem,
5574        target_senders: &[u64],
5575    ) -> TelemetryResult<RouterItem> {
5576        match data {
5577            RouterItem::Packet(pkt) => {
5578                let reliable = if is_reliable_type(pkt.data_type()) {
5579                    Some(wire_format::ReliableHeader {
5580                        flags: wire_format::RELIABLE_FLAG_UNSEQUENCED,
5581                        seq: 0,
5582                        ack: 0,
5583                    })
5584                } else {
5585                    None
5586                };
5587                let bytes = self.pack_packet_for_contract(
5588                    &pkt,
5589                    reliable,
5590                    Some(message_meta(pkt.data_type()).element),
5591                    target_senders,
5592                )?;
5593                Ok(RouterItem::Packed(bytes))
5594            }
5595            RouterItem::Packed(bytes) => Ok(RouterItem::Packed(bytes)),
5596        }
5597    }
5598
5599    fn endpoints_are_link_local_only(eps: &[DataEndpoint]) -> bool {
5600        !eps.is_empty() && eps.iter().all(|ep| ep.is_link_local_only())
5601    }
5602
5603    fn should_route_remote(
5604        &self,
5605        data: &RouterItem,
5606        exclude: Option<RouterSideId>,
5607    ) -> TelemetryResult<bool> {
5608        #[cfg(feature = "discovery")]
5609        {
5610            let RemoteSidePlan::Target(sides) = self.remote_side_plan(data, exclude)?;
5611            Ok(!sides.is_empty())
5612        }
5613
5614        #[cfg(not(feature = "discovery"))]
5615        {
5616            let (eps, ty) = self.item_route_info(data)?;
5617            if !(has_nonlocal_endpoint(&eps, &self.cfg) || force_remote_for_type(ty)) {
5618                return Ok(false);
5619            }
5620            let st = self.state.lock();
5621            Ok(!self
5622                .eligible_side_ids_locked(
5623                    &st,
5624                    exclude,
5625                    Some(ty),
5626                    Self::endpoints_are_link_local_only(&eps),
5627                )
5628                .is_empty())
5629        }
5630    }
5631
5632    #[cfg(feature = "discovery")]
5633    fn has_explicit_route_policy_locked(
5634        st: &RouterInner,
5635        src: Option<RouterSideId>,
5636        ty: DataType,
5637    ) -> bool {
5638        st.route_overrides
5639            .keys()
5640            .any(|(route_src, _)| *route_src == src)
5641            || Self::has_typed_route_overrides_locked(st, src, ty)
5642    }
5643
5644    fn remote_side_plan(
5645        &self,
5646        data: &RouterItem,
5647        exclude: Option<RouterSideId>,
5648    ) -> TelemetryResult<RemoteSidePlan> {
5649        #[cfg(feature = "discovery")]
5650        {
5651            let (eps, ty) = self.item_route_info(data)?;
5652            let target_senders = self.item_target_senders(data)?;
5653            let resolve_beyond_local_hop =
5654                self.item_targets_router_as_intermediate_hop(data, &eps)?;
5655            let preferred_packet_id = Self::reliable_control_target_packet_id(data)?;
5656            let routing_target_senders: &[u64] = if preferred_packet_id.is_some() {
5657                // ACK target[0] identifies the acknowledging owner, not the
5658                // destination. Never route an ACK back toward that owner.
5659                target_senders.get(1..).unwrap_or(&[])
5660            } else if resolve_beyond_local_hop {
5661                &[]
5662            } else {
5663                target_senders.as_ref()
5664            };
5665            if discovery::is_discovery_type(ty) {
5666                let mut st = self.state.lock();
5667                let sides = self.eligible_side_ids_locked(&st, exclude, Some(ty), false);
5668                return Ok(RemoteSidePlan::Target(self.apply_route_selection_locked(
5669                    &mut st,
5670                    exclude,
5671                    sides,
5672                    RouteSelectionOrigin::Flood,
5673                )));
5674            }
5675            #[cfg(feature = "timesync")]
5676            if ty == DataType::TimeSyncAnnounce {
5677                // A source announcement describes the source carried in the
5678                // packet; routing it *toward* that discovered source sends it
5679                // back where it came from and leaves downstream clocks blind.
5680                // Propagate it once away from the ingress side, like topology
5681                // discovery. Dedupe and split-horizon exclusion prevent echoes,
5682                // while requests still use the exact discovered source route.
5683                let mut st = self.state.lock();
5684                let sides = self.eligible_side_ids_locked(&st, exclude, Some(ty), false);
5685                let sides =
5686                    Self::filter_timesync_sides_locked(&mut st, ty, self.clock.now_ms(), sides);
5687                return Ok(RemoteSidePlan::Target(self.apply_route_selection_locked(
5688                    &mut st,
5689                    exclude,
5690                    sides,
5691                    RouteSelectionOrigin::Flood,
5692                )));
5693            }
5694            let mut st = self.state.lock();
5695            // Preserve the local-only traffic optimization unless discovery
5696            // has positively identified a remote owner of this network
5697            // variable. A router may own the same schema endpoint locally and
5698            // still need to replicate the value across an IPC/network boundary.
5699            let now_ms = self.clock.now_ms();
5700            let has_remote_variable_owner = st.managed_variable_types.contains(&ty.as_u32())
5701                && st.discovery_routes.iter().any(|(&side, route)| {
5702                    exclude != Some(side)
5703                        && now_ms.saturating_sub(route.last_seen_ms) <= DISCOVERY_ROUTE_TTL_MS
5704                        && route.reachable_network_variables.contains(&ty)
5705                });
5706            if !(has_nonlocal_endpoint(&eps, &self.cfg)
5707                || force_remote_for_type(ty)
5708                || !target_senders.is_empty()
5709                || has_remote_variable_owner)
5710            {
5711                return Ok(RemoteSidePlan::Target(Vec::new()));
5712            }
5713
5714            #[cfg(feature = "timesync")]
5715            let preferred_timesync_source = self.preferred_timesync_route_source(data, ty)?;
5716            #[cfg(not(feature = "timesync"))]
5717            let preferred_timesync_source: Option<String> = None;
5718
5719            if let Some(packet_id) = preferred_packet_id {
5720                let target_side = st
5721                    .reliable_return_routes
5722                    .get(&packet_id)
5723                    .map(|route| route.side);
5724                if let Some(side) = target_side
5725                    .filter(|side| self.route_allowed_locked(&st, exclude, Some(ty), *side))
5726                {
5727                    #[cfg(feature = "timesync")]
5728                    if !Self::timesync_allowed_for_side_locked(
5729                        &mut st,
5730                        side,
5731                        ty,
5732                        self.clock.now_ms(),
5733                    ) {
5734                        return Ok(RemoteSidePlan::Target(Vec::new()));
5735                    }
5736                    return Ok(RemoteSidePlan::Target(vec![side]));
5737                }
5738                // A busy multi-source relay can age a packet-id return entry
5739                // out of its bounded cache before the end-to-end ACK arrives.
5740                // The ACK still carries the original sender hash, so fall
5741                // through to the discovered named-sender route. This remains
5742                // selective and avoids either dropping the ACK or flooding it.
5743            }
5744            let restrict_link_local = Self::endpoints_are_link_local_only(&eps);
5745            let prefer_best_overlap = is_reliable_type(ty)
5746                && routing_target_senders.is_empty()
5747                && preferred_packet_id.is_none();
5748            if st.discovery_routes.is_empty() {
5749                let mut fallback =
5750                    self.eligible_side_ids_locked(&st, exclude, Some(ty), restrict_link_local);
5751                #[cfg(feature = "timesync")]
5752                {
5753                    fallback = Self::filter_timesync_sides_locked(
5754                        &mut st,
5755                        ty,
5756                        self.clock.now_ms(),
5757                        fallback,
5758                    );
5759                }
5760                return Ok(RemoteSidePlan::Target(if fallback.len() == 1 {
5761                    fallback
5762                } else {
5763                    Vec::new()
5764                }));
5765            }
5766            let mut matches = self.discovered_route_candidates_locked(
5767                &st,
5768                exclude,
5769                ty,
5770                &eps,
5771                routing_target_senders,
5772                prefer_best_overlap,
5773                preferred_timesync_source.as_deref(),
5774            );
5775            #[cfg(feature = "timesync")]
5776            {
5777                matches =
5778                    Self::filter_timesync_matches_locked(&mut st, ty, self.clock.now_ms(), matches);
5779            }
5780            /* Heartbeats and replicated network variables are network-segment
5781             * state, so a nearer owner on one side must not hide a farther
5782             * independent segment. Discovery's split horizon prevents the
5783             * advertisement from being reflected back onto its ingress side.
5784             * Ordinary application data retains shortest-path suppression. */
5785            let has_distributed_variable_owners = matches.iter().any(|candidate| {
5786                st.discovery_routes
5787                    .get(&candidate.side)
5788                    .is_some_and(|route| route.reachable_network_variables.contains(&ty))
5789            });
5790            if DataType::try_named("HEARTBEAT") != Some(ty) && !has_distributed_variable_owners {
5791                Self::retain_shortest_discovery_candidates_locked(
5792                    &st,
5793                    &mut matches,
5794                    &eps,
5795                    routing_target_senders,
5796                    self.clock.now_ms(),
5797                );
5798            }
5799
5800            if !matches.is_empty() {
5801                Ok(RemoteSidePlan::Target(
5802                    self.select_discovered_candidate_sides_locked(
5803                        &mut st,
5804                        exclude,
5805                        ty,
5806                        routing_target_senders,
5807                        prefer_best_overlap,
5808                        matches,
5809                    ),
5810                ))
5811            } else if preferred_packet_id.is_some() {
5812                // Older ACKs carry only the acknowledging owner, and an
5813                // unresolved compact sender may also lack a discoverable
5814                // publisher identity. After the bounded return cache churns,
5815                // a two-sided bridge still has one unambiguous return path.
5816                // Respect route/side policy and never flood a multi-way relay.
5817                let sides = self.eligible_side_ids_locked(&st, exclude, Some(ty), false);
5818                Ok(RemoteSidePlan::Target(
5819                    if exclude.is_some() && sides.len() == 1 {
5820                        sides
5821                    } else {
5822                        Vec::new()
5823                    },
5824                ))
5825            } else if prefer_best_overlap {
5826                Ok(RemoteSidePlan::Target(Vec::new()))
5827            } else {
5828                if Self::has_explicit_route_policy_locked(&st, exclude, ty) {
5829                    let mut sides =
5830                        self.eligible_side_ids_locked(&st, exclude, Some(ty), restrict_link_local);
5831                    #[cfg(feature = "timesync")]
5832                    {
5833                        sides = Self::filter_timesync_sides_locked(
5834                            &mut st,
5835                            ty,
5836                            self.clock.now_ms(),
5837                            sides,
5838                        );
5839                    }
5840                    Ok(RemoteSidePlan::Target(self.apply_route_selection_locked(
5841                        &mut st,
5842                        exclude,
5843                        sides,
5844                        RouteSelectionOrigin::Flood,
5845                    )))
5846                } else {
5847                    Ok(RemoteSidePlan::Target(Vec::new()))
5848                }
5849            }
5850        }
5851        #[cfg(not(feature = "discovery"))]
5852        {
5853            let (_, ty) = self.item_route_info(data)?;
5854            let mut st = self.state.lock();
5855            if let Some(packet_id) = Self::reliable_control_target_packet_id(data)? {
5856                let target_side = st
5857                    .reliable_return_routes
5858                    .get(&packet_id)
5859                    .map(|route| route.side);
5860                if let Some(side) = target_side
5861                    .filter(|side| self.route_allowed_locked(&st, exclude, Some(ty), *side))
5862                {
5863                    return Ok(RemoteSidePlan::Target(vec![side]));
5864                }
5865                let sides = self.eligible_side_ids_locked(&st, exclude, Some(ty), false);
5866                return Ok(RemoteSidePlan::Target(
5867                    if exclude.is_some() && sides.len() == 1 {
5868                        sides
5869                    } else {
5870                        Vec::new()
5871                    },
5872                ));
5873            }
5874            let sides = self.eligible_side_ids_locked(&st, exclude, Some(ty), false);
5875            Ok(RemoteSidePlan::Target(self.apply_route_selection_locked(
5876                &mut st,
5877                exclude,
5878                sides,
5879                RouteSelectionOrigin::Flood,
5880            )))
5881        }
5882    }
5883
5884    #[cfg(feature = "discovery")]
5885    fn local_discovery_endpoints(&self, st: &RouterInner) -> Vec<DataEndpoint> {
5886        let mut eps: Vec<DataEndpoint> = self.cfg.handlers.iter().map(|h| h.endpoint).collect();
5887        // Managed-variable ownership has its own discovery field. Its message
5888        // endpoints describe delivery sinks, not ownership: treating them as
5889        // local endpoints makes every flight-state replica claim that it is
5890        // GroundStation and prevents ordinary telemetry from reaching the
5891        // real GroundStation route.
5892        let _ = st;
5893        #[cfg(feature = "timesync")]
5894        if self.cfg.timesync_config().is_some() {
5895            eps.push(DataEndpoint::TimeSync);
5896        }
5897        eps.retain(|ep| !discovery::is_discovery_endpoint(*ep));
5898        crate::small_sort::sort(&mut eps);
5899        eps.dedup();
5900        eps
5901    }
5902
5903    #[cfg(feature = "discovery")]
5904    fn local_discovery_timesync_sources(&self, now_ms: u64) -> Vec<String> {
5905        #[cfg(feature = "timesync")]
5906        {
5907            let st = self.timesync.lock();
5908            if let Some(tracker) = st.tracker.as_ref()
5909                && tracker.should_serve(
5910                    now_ms,
5911                    Self::timesync_has_usable_time_locked(&st, self.monotonic_now_ns()),
5912                )
5913            {
5914                return vec![self.sender_arc().to_string()];
5915            }
5916        }
5917        Vec::new()
5918    }
5919
5920    #[cfg(all(feature = "discovery", feature = "timesync"))]
5921    fn preferred_timesync_route_source(
5922        &self,
5923        data: &RouterItem,
5924        ty: DataType,
5925    ) -> TelemetryResult<Option<String>> {
5926        if !matches!(
5927            ty,
5928            DataType::TimeSyncAnnounce | DataType::TimeSyncRequest | DataType::TimeSyncResponse
5929        ) {
5930            return Ok(None);
5931        }
5932
5933        match data {
5934            RouterItem::Packet(pkt) => match ty {
5935                DataType::TimeSyncRequest => {
5936                    let local_sender = self.sender_arc();
5937                    if pkt.sender() == local_sender.as_ref() {
5938                        Ok(self.timesync.lock().tracker.as_ref().and_then(|tracker| {
5939                            tracker.current_source().map(|src| src.sender.clone())
5940                        }))
5941                    } else {
5942                        Ok(None)
5943                    }
5944                }
5945                DataType::TimeSyncAnnounce | DataType::TimeSyncResponse => {
5946                    Ok(Some(pkt.sender().to_owned()))
5947                }
5948                _ => Ok(None),
5949            },
5950            RouterItem::Packed(bytes) => {
5951                let pkt = self.unpack_packet_for_role(bytes.as_ref())?;
5952                self.preferred_timesync_route_source(&RouterItem::Packet(pkt), ty)
5953            }
5954        }
5955    }
5956
5957    #[cfg(feature = "discovery")]
5958    fn note_discovery_topology_change_locked(st: &mut RouterInner, now_ms: u64) {
5959        st.discovery_cadence.on_topology_change(now_ms);
5960        // Propagate route changes immediately without restarting discovery or
5961        // resending the complete topology graph. DiscoveryAddress replaces the
5962        // prior compact route summary, so additions and removals converge while
5963        // explicit baseline requests remain available as loss recovery.
5964        for side_id in st
5965            .sides
5966            .iter()
5967            .enumerate()
5968            .filter_map(|(side_id, side)| side.as_ref().map(|_| side_id))
5969        {
5970            let throttle = st.discovery_side_throttle.entry(side_id).or_default();
5971            throttle.pending_incremental = true;
5972            throttle.incremental_retries_remaining = DISCOVERY_INCREMENTAL_RETRY_COUNT;
5973        }
5974        // Make the first incremental control frame self-describing. This is a
5975        // small header refresh, not a topology reset, and prevents a restarted
5976        // peer from missing the route delta because it lost its RX dictionary.
5977        for transport in st.side_transport.values_mut() {
5978            transport.refresh_transmit_header_templates();
5979        }
5980    }
5981
5982    #[cfg(feature = "discovery")]
5983    fn sender_topology_board_mut<'a>(
5984        sender_state: &'a mut DiscoverySenderState,
5985        sender_id: &str,
5986    ) -> &'a mut TopologyBoardNode {
5987        if let Some(idx) = sender_state
5988            .topology_boards
5989            .iter()
5990            .position(|board| board.sender_id == sender_id)
5991        {
5992            return &mut sender_state.topology_boards[idx];
5993        }
5994        sender_state.topology_boards.push(TopologyBoardNode {
5995            sender_id: sender_id.to_string(),
5996            reachable_endpoints: Vec::new(),
5997            reachable_timesync_sources: Vec::new(),
5998            connections: Vec::new(),
5999        });
6000        sender_state
6001            .topology_boards
6002            .last_mut()
6003            .expect("board inserted above")
6004    }
6005
6006    #[cfg(feature = "discovery")]
6007    fn refresh_sender_topology_state(sender_state: &mut DiscoverySenderState) {
6008        discovery::normalize_topology_boards(&mut sender_state.topology_boards);
6009        let (mut reachable, mut reachable_timesync_sources) =
6010            discovery::summarize_topology_boards(&sender_state.topology_boards);
6011        reachable.extend(sender_state.advertised_reachable.iter().copied());
6012        crate::small_sort::sort(&mut reachable);
6013        reachable.dedup();
6014        reachable_timesync_sources.extend(
6015            sender_state
6016                .advertised_reachable_timesync_sources
6017                .iter()
6018                .cloned(),
6019        );
6020        reachable_timesync_sources.sort_unstable();
6021        reachable_timesync_sources.dedup();
6022        sender_state.reachable = reachable;
6023        sender_state.reachable_timesync_sources = reachable_timesync_sources;
6024    }
6025
6026    #[cfg(feature = "discovery")]
6027    fn recompute_discovery_side_state(route: &mut DiscoverySideState) {
6028        let mut reachable = Vec::new();
6029        let mut reachable_network_variables = Vec::new();
6030        let mut reachable_timesync_sources = Vec::new();
6031        let mut last_seen_ms = 0u64;
6032        for sender in route.announcers.values() {
6033            reachable.extend(sender.reachable.iter().copied());
6034            reachable_network_variables.extend(sender.reachable_network_variables.iter().copied());
6035            reachable_timesync_sources.extend(sender.reachable_timesync_sources.iter().cloned());
6036            last_seen_ms = last_seen_ms.max(sender.last_seen_ms);
6037        }
6038        reachable_network_variables
6039            .extend(route.requested_network_variables.iter().map(|(ty, _)| *ty));
6040        last_seen_ms = last_seen_ms.max(
6041            route
6042                .requested_network_variables
6043                .iter()
6044                .map(|(_, last_seen_ms)| *last_seen_ms)
6045                .max()
6046                .unwrap_or(0),
6047        );
6048        crate::small_sort::sort(&mut reachable);
6049        reachable.dedup();
6050        crate::small_sort::sort(&mut reachable_network_variables);
6051        reachable_network_variables.dedup();
6052        reachable_timesync_sources.sort_unstable();
6053        reachable_timesync_sources.dedup();
6054        route.reachable = reachable;
6055        route.reachable_network_variables = reachable_network_variables;
6056        route.reachable_timesync_sources = reachable_timesync_sources;
6057        route.last_seen_ms = last_seen_ms;
6058    }
6059
6060    #[cfg(feature = "discovery")]
6061    fn local_discovery_topology_board(
6062        &self,
6063        st: &RouterInner,
6064        now_ms: u64,
6065        link_local_enabled: bool,
6066    ) -> TopologyBoardNode {
6067        let mut reachable_endpoints = self.local_discovery_endpoints(st);
6068        if !link_local_enabled {
6069            reachable_endpoints.retain(|ep| !ep.is_link_local_only());
6070        }
6071        let mut connections = Vec::new();
6072        for route in st.discovery_routes.values() {
6073            if now_ms.saturating_sub(route.last_seen_ms) > DISCOVERY_ROUTE_TTL_MS {
6074                continue;
6075            }
6076            for (sender, sender_state) in route.announcers.iter() {
6077                if now_ms.saturating_sub(sender_state.last_seen_ms) <= DISCOVERY_ROUTE_TTL_MS {
6078                    connections.push(sender.clone());
6079                }
6080            }
6081        }
6082        connections.sort_unstable();
6083        connections.dedup();
6084        let sender = self.sender_arc();
6085        TopologyBoardNode {
6086            sender_id: sender.to_string(),
6087            reachable_endpoints,
6088            reachable_timesync_sources: self.local_discovery_timesync_sources(now_ms),
6089            connections,
6090        }
6091    }
6092
6093    #[cfg(feature = "discovery")]
6094    fn advertised_discovery_topology_for_link_locked(
6095        &self,
6096        st: &RouterInner,
6097        now_ms: u64,
6098        link_local_enabled: bool,
6099        exclude_side: Option<RouterSideId>,
6100    ) -> Vec<TopologyBoardNode> {
6101        let mut boards = vec![self.local_discovery_topology_board(st, now_ms, link_local_enabled)];
6102        for (&route_side, route) in st.discovery_routes.iter() {
6103            if exclude_side == Some(route_side) {
6104                continue;
6105            }
6106            if now_ms.saturating_sub(route.last_seen_ms) > DISCOVERY_ROUTE_TTL_MS {
6107                continue;
6108            }
6109            for (announcer, sender_state) in route.announcers.iter() {
6110                if now_ms.saturating_sub(sender_state.last_seen_ms) > DISCOVERY_ROUTE_TTL_MS {
6111                    continue;
6112                }
6113                if sender_state.topology_boards.is_empty() {
6114                    let sender = self.sender_arc();
6115                    let fallback = TopologyBoardNode {
6116                        sender_id: announcer.clone(),
6117                        reachable_endpoints: sender_state.reachable.clone(),
6118                        reachable_timesync_sources: sender_state.reachable_timesync_sources.clone(),
6119                        connections: vec![sender.to_string()],
6120                    };
6121                    discovery::merge_topology_boards(&mut boards, core::slice::from_ref(&fallback));
6122                } else {
6123                    // Merge the retained view directly. Cloning it first and
6124                    // cloning again into the output doubled temporary strings.
6125                    discovery::merge_topology_boards(&mut boards, &sender_state.topology_boards);
6126                    if let Some(board) = boards
6127                        .iter_mut()
6128                        .find(|board| board.sender_id == *announcer)
6129                    {
6130                        let sender = self.sender_arc();
6131                        if !board.connections.iter().any(|peer| peer == sender.as_ref()) {
6132                            board.connections.push(sender.to_string());
6133                        }
6134                    }
6135                }
6136            }
6137        }
6138        if !link_local_enabled {
6139            for board in &mut boards {
6140                board
6141                    .reachable_endpoints
6142                    .retain(|ep| !ep.is_link_local_only());
6143            }
6144        }
6145        discovery::normalize_topology_boards(&mut boards);
6146        boards
6147    }
6148
6149    #[cfg(feature = "discovery")]
6150    fn prune_discovery_routes_locked(st: &mut RouterInner, now_ms: u64) -> bool {
6151        // Polling runs at the firmware service rate, not the advertisement
6152        // rate. Never clone the topology just to discover nothing expired.
6153        let mut changed = false;
6154        st.discovery_routes.retain(|_, route| {
6155            let before_peers = route.announcers.len();
6156            let before_requests = route.requested_network_variables.len();
6157            route.announcers.retain(|_, sender| {
6158                now_ms.saturating_sub(sender.last_seen_ms) <= DISCOVERY_ROUTE_TTL_MS
6159            });
6160            route
6161                .requested_network_variables
6162                .retain(|(_, last_seen_ms)| {
6163                    now_ms.saturating_sub(*last_seen_ms) <= DISCOVERY_ROUTE_TTL_MS
6164                });
6165            if before_peers != route.announcers.len()
6166                || before_requests != route.requested_network_variables.len()
6167            {
6168                Self::recompute_discovery_side_state(route);
6169                changed = true;
6170            }
6171            let keep =
6172                !route.announcers.is_empty() || !route.requested_network_variables.is_empty();
6173            changed |= !keep;
6174            keep
6175        });
6176        changed
6177    }
6178
6179    #[cfg(feature = "discovery")]
6180    fn advertised_summaries_from_topology_locked(
6181        &self,
6182        st: &RouterInner,
6183        now_ms: u64,
6184        link_local_enabled: bool,
6185        exclude_side: Option<RouterSideId>,
6186        topology: &[TopologyBoardNode],
6187    ) -> (Vec<crate::DataEndpoint>, Vec<String>) {
6188        let (mut endpoints, mut sources) = discovery::summarize_topology_boards(topology);
6189        for (&side, route) in &st.discovery_routes {
6190            if exclude_side == Some(side)
6191                || now_ms.saturating_sub(route.last_seen_ms) > DISCOVERY_ROUTE_TTL_MS
6192            {
6193                continue;
6194            }
6195            for peer in route.announcers.values() {
6196                if now_ms.saturating_sub(peer.last_seen_ms) > DISCOVERY_ROUTE_TTL_MS {
6197                    continue;
6198                }
6199                for endpoint in &peer.advertised_reachable {
6200                    if !discovery::is_discovery_endpoint(*endpoint)
6201                        && (link_local_enabled || !endpoint.is_link_local_only())
6202                        && !endpoints.contains(endpoint)
6203                    {
6204                        endpoints.push(*endpoint);
6205                    }
6206                }
6207                for source in &peer.advertised_reachable_timesync_sources {
6208                    if !sources.contains(source) {
6209                        sources.push(source.clone());
6210                    }
6211                }
6212            }
6213        }
6214        endpoints.retain(|endpoint| {
6215            !discovery::is_discovery_endpoint(*endpoint)
6216                && (link_local_enabled || !endpoint.is_link_local_only())
6217        });
6218        crate::small_sort::sort(&mut endpoints);
6219        sources.sort_unstable();
6220        (endpoints, sources)
6221    }
6222
6223    #[cfg(feature = "discovery")]
6224    fn advertised_discovery_endpoints_for_link_locked(
6225        &self,
6226        st: &RouterInner,
6227        now_ms: u64,
6228        link_local_enabled: bool,
6229        exclude_side: Option<RouterSideId>,
6230    ) -> Vec<DataEndpoint> {
6231        let (mut reachable_endpoints, _) = discovery::summarize_topology_boards(
6232            &self.advertised_discovery_topology_for_link_locked(
6233                st,
6234                now_ms,
6235                link_local_enabled,
6236                exclude_side,
6237            ),
6238        );
6239        // Compact advertisements can know routes before detailed topology arrives.
6240        // Preserve that reachability without assigning downstream endpoints to the bridge.
6241        for (&side, route) in &st.discovery_routes {
6242            if exclude_side == Some(side)
6243                || now_ms.saturating_sub(route.last_seen_ms) > DISCOVERY_ROUTE_TTL_MS
6244            {
6245                continue;
6246            }
6247            for sender in route.announcers.values() {
6248                if now_ms.saturating_sub(sender.last_seen_ms) <= DISCOVERY_ROUTE_TTL_MS {
6249                    reachable_endpoints.extend(sender.advertised_reachable.iter().copied());
6250                }
6251            }
6252        }
6253        crate::small_sort::sort(&mut reachable_endpoints);
6254        reachable_endpoints.dedup();
6255        reachable_endpoints
6256            .into_iter()
6257            .filter(|ep| {
6258                !discovery::is_discovery_endpoint(*ep)
6259                    && (link_local_enabled || !ep.is_link_local_only())
6260            })
6261            .collect()
6262    }
6263
6264    #[cfg(all(test, feature = "discovery"))]
6265    fn compact_summary_fixture(&self, endpoint: DataEndpoint) {
6266        let mut sender = DiscoverySenderState {
6267            advertised_reachable: vec![endpoint],
6268            advertised_reachable_timesync_sources: vec!["clock-behind-bridge".to_string()],
6269            last_seen_ms: self.clock.now_ms(),
6270            topology_boards: vec![TopologyBoardNode {
6271                sender_id: "bridge".to_string(),
6272                reachable_endpoints: Vec::new(),
6273                reachable_timesync_sources: Vec::new(),
6274                connections: Vec::new(),
6275            }],
6276            ..Default::default()
6277        };
6278        Self::refresh_sender_topology_state(&mut sender);
6279        let mut route = DiscoverySideState::default();
6280        route.announcers.insert("bridge".to_string(), sender);
6281        Self::recompute_discovery_side_state(&mut route);
6282        self.state.lock().discovery_routes.insert(0, route);
6283    }
6284
6285    #[cfg(feature = "discovery")]
6286    fn advertised_discovery_timesync_sources_for_link_locked(
6287        &self,
6288        st: &RouterInner,
6289        now_ms: u64,
6290        exclude_side: Option<RouterSideId>,
6291    ) -> Vec<String> {
6292        let (_, mut sources) = discovery::summarize_topology_boards(
6293            &self.advertised_discovery_topology_for_link_locked(st, now_ms, true, exclude_side),
6294        );
6295        for (&side, route) in &st.discovery_routes {
6296            if exclude_side == Some(side)
6297                || now_ms.saturating_sub(route.last_seen_ms) > DISCOVERY_ROUTE_TTL_MS
6298            {
6299                continue;
6300            }
6301            for sender in route.announcers.values() {
6302                if now_ms.saturating_sub(sender.last_seen_ms) <= DISCOVERY_ROUTE_TTL_MS {
6303                    sources.extend(sender.advertised_reachable_timesync_sources.iter().cloned());
6304                }
6305            }
6306        }
6307        sources.sort_unstable();
6308        sources.dedup();
6309        sources
6310    }
6311
6312    #[cfg(feature = "discovery")]
6313    fn advertised_network_variables_for_link_locked(
6314        &self,
6315        st: &RouterInner,
6316        now_ms: u64,
6317        exclude_side: Option<RouterSideId>,
6318    ) -> Vec<DataType> {
6319        let mut types: Vec<DataType> = st
6320            .managed_variable_types
6321            .iter()
6322            .copied()
6323            .map(DataType)
6324            .collect();
6325        for (&route_side, route) in st.discovery_routes.iter() {
6326            if exclude_side == Some(route_side) {
6327                continue;
6328            }
6329            if now_ms.saturating_sub(route.last_seen_ms) <= DISCOVERY_ROUTE_TTL_MS {
6330                types.extend(route.reachable_network_variables.iter().copied());
6331            }
6332        }
6333        crate::small_sort::sort(&mut types);
6334        types.dedup();
6335        types
6336    }
6337
6338    #[cfg(feature = "discovery")]
6339    fn preferred_discovery_master_present_locked<'a>(
6340        &self,
6341        st: &'a RouterInner,
6342        now_ms: u64,
6343    ) -> Option<&'a str> {
6344        if let Some(preferred) = st.preferred_discovery_master.as_deref()
6345            && (preferred == self.sender_arc().as_ref()
6346                || st
6347                    .discovery_routes
6348                    .values()
6349                    .filter(|route| {
6350                        now_ms.saturating_sub(route.last_seen_ms) <= DISCOVERY_ROUTE_TTL_MS
6351                    })
6352                    .any(|route| {
6353                        route
6354                            .announcers
6355                            .iter()
6356                            .filter(|(_, peer)| {
6357                                now_ms.saturating_sub(peer.last_seen_ms) <= DISCOVERY_ROUTE_TTL_MS
6358                            })
6359                            .any(|(name, peer)| {
6360                                name == preferred
6361                                    || peer.topology_boards.iter().any(|board| {
6362                                        board.sender_id == preferred
6363                                            || board
6364                                                .connections
6365                                                .iter()
6366                                                .any(|name| name == preferred)
6367                                    })
6368                            })
6369                    }))
6370        {
6371            // Presence of the preferred master needs a name scan, not a
6372            // cloned topology and a graph election on each advertisement.
6373            return Some(preferred);
6374        }
6375        None
6376    }
6377
6378    #[cfg(feature = "discovery")]
6379    fn discovery_master_sender_locked(&self, st: &RouterInner, now_ms: u64) -> String {
6380        if let Some(preferred) = self.preferred_discovery_master_present_locked(st, now_ms) {
6381            return preferred.to_string();
6382        }
6383        let boards = self.advertised_discovery_topology_for_link_locked(st, now_ms, true, None);
6384        discovery::elect_discovery_master(self.sender_arc().as_ref(), &boards)
6385    }
6386
6387    #[cfg(all(feature = "discovery", feature = "timesync"))]
6388    #[inline]
6389    fn side_is_slow_control_link_locked(
6390        st: &RouterInner,
6391        side_id: RouterSideId,
6392        now_ms: u64,
6393    ) -> bool {
6394        st.adaptive_route_stats.get(&side_id).is_some_and(|stats| {
6395            let recent_slow = stats.last_slow_observed_ms > 0
6396                && now_ms.saturating_sub(stats.last_slow_observed_ms)
6397                    <= discovery::DISCOVERY_SLOW_LINK_FULL_INTERVAL_MS;
6398            stats.sample_count > 0
6399                && ((stats.estimated_bandwidth_bps > 0
6400                    && stats.estimated_bandwidth_bps <= CONTROL_SLOW_LINK_CAPACITY_BPS)
6401                    || recent_slow)
6402        })
6403    }
6404
6405    #[cfg(feature = "discovery")]
6406    fn discovery_level_for_side_locked(
6407        st: &mut RouterInner,
6408        side_id: RouterSideId,
6409        now_ms: u64,
6410    ) -> Option<DiscoveryAdvertiseLevel> {
6411        let throttle = st.discovery_side_throttle.entry(side_id).or_default();
6412        if !throttle.has_sent_full {
6413            throttle.has_sent_full = true;
6414            throttle.pending_incremental = false;
6415            throttle.incremental_retries_remaining = 0;
6416            throttle.next_ping_ms = now_ms.saturating_add(DISCOVERY_SLOW_LINK_PING_INTERVAL_MS);
6417            return Some(DiscoveryAdvertiseLevel::Full);
6418        }
6419        if throttle.pending_incremental {
6420            throttle.incremental_retries_remaining =
6421                throttle.incremental_retries_remaining.saturating_sub(1);
6422            throttle.pending_incremental = throttle.incremental_retries_remaining > 0;
6423            return Some(DiscoveryAdvertiseLevel::Incremental);
6424        }
6425        // Stable links carry only a small liveness announcement. Route and
6426        // schema snapshots are emitted on changes or explicit recovery requests.
6427        if throttle.liveness_due(now_ms) {
6428            throttle.next_ping_ms = now_ms.saturating_add(DISCOVERY_SLOW_LINK_PING_INTERVAL_MS);
6429            return Some(DiscoveryAdvertiseLevel::MinimalPing);
6430        }
6431        None
6432    }
6433
6434    #[cfg(all(feature = "discovery", feature = "timesync"))]
6435    #[inline]
6436    fn is_timesync_type(ty: DataType) -> bool {
6437        matches!(
6438            ty,
6439            DataType::TimeSyncAnnounce | DataType::TimeSyncRequest | DataType::TimeSyncResponse
6440        )
6441    }
6442
6443    #[cfg(all(feature = "discovery", feature = "timesync"))]
6444    fn timesync_allowed_for_side_locked(
6445        st: &mut RouterInner,
6446        side_id: RouterSideId,
6447        ty: DataType,
6448        now_ms: u64,
6449    ) -> bool {
6450        if !Self::is_timesync_type(ty) {
6451            return true;
6452        }
6453        if !Self::side_is_slow_control_link_locked(st, side_id, now_ms) {
6454            st.timesync_side_throttle.remove(&side_id);
6455            return true;
6456        }
6457
6458        let throttle = st.timesync_side_throttle.entry(side_id).or_default();
6459        if now_ms >= throttle.next_allowed_ms {
6460            throttle.next_allowed_ms = now_ms.saturating_add(TIMESYNC_SLOW_LINK_MIN_INTERVAL_MS);
6461            return true;
6462        }
6463        false
6464    }
6465
6466    #[cfg(all(feature = "discovery", feature = "timesync"))]
6467    fn filter_timesync_sides_locked(
6468        st: &mut RouterInner,
6469        ty: DataType,
6470        now_ms: u64,
6471        sides: Vec<RouterSideId>,
6472    ) -> Vec<RouterSideId> {
6473        sides
6474            .into_iter()
6475            .filter(|side| Self::timesync_allowed_for_side_locked(st, *side, ty, now_ms))
6476            .collect()
6477    }
6478
6479    #[cfg(all(feature = "discovery", feature = "timesync"))]
6480    fn filter_timesync_matches_locked(
6481        st: &mut RouterInner,
6482        ty: DataType,
6483        now_ms: u64,
6484        matches: Vec<DiscoveryCandidateMatch>,
6485    ) -> Vec<DiscoveryCandidateMatch> {
6486        matches
6487            .into_iter()
6488            .filter(|m| Self::timesync_allowed_for_side_locked(st, m.side, ty, now_ms))
6489            .collect()
6490    }
6491
6492    #[cfg(feature = "discovery")]
6493    fn refresh_known_discovery_liveness(
6494        &self,
6495        pkt: &Packet,
6496        side: RouterSideId,
6497    ) -> TelemetryResult<bool> {
6498        let empty_ping =
6499            pkt.data_type() == crate::DataType::DiscoveryAnnounce && pkt.payload().is_empty();
6500        if !empty_ping && pkt.data_type() != crate::DataType::DiscoveryAddress {
6501            return Ok(false);
6502        }
6503        crate::memory_admission::check(
6504            if empty_ping {
6505                512
6506            } else {
6507                1024 + pkt.payload().len().saturating_mul(4)
6508            },
6509            pkt.payload().len().saturating_mul(2).max(128),
6510        )?;
6511        let mut address = if empty_ping {
6512            None
6513        } else {
6514            Some(discovery::decode_discovery_address(pkt)?)
6515        };
6516        let now = self.clock.now_ms();
6517        let mut st = self.state.lock();
6518        if let Some(ad) = &mut address {
6519            let link_local = st
6520                .sides
6521                .get(side)
6522                .and_then(|entry| entry.as_ref())
6523                .is_some_and(|entry| entry.opts.link_local_enabled);
6524            if !link_local {
6525                ad.reachable_endpoints.retain(|ep| !ep.is_link_local_only());
6526            }
6527            let Some(entry) = st.address_book.get(ad.hostname.as_str()) else {
6528                return Ok(false);
6529            };
6530            if entry.address != ad.address
6531                || entry.requested_address != ad.requested_address
6532                || entry.mode != Self::address_mode_from_code(ad.mode, ad.requested_address)
6533                || entry.owner_hash != ad.owner_hash
6534            {
6535                return Ok(false);
6536            }
6537            if entry.birth_ms != ad.birth_ms
6538                && st.address_book.iter().any(|(hostname, other)| {
6539                    hostname != &ad.hostname
6540                        && [ad.address, ad.requested_address].iter().any(|candidate| {
6541                            *candidate != 0
6542                                && (other.address == *candidate
6543                                    || other.requested_address == *candidate)
6544                        })
6545                })
6546            {
6547                // Birth time participates in collision arbitration. Reuse an
6548                // incarnation only when the unchanged owner has no contender;
6549                // contested addresses retain the transactional admission path.
6550                return Ok(false);
6551            }
6552        }
6553        let canonical;
6554        let sender = if let Some(ad) = &address {
6555            ad.hostname.as_str()
6556        } else {
6557            canonical = Self::canonical_sender_locked(&st, pkt.sender());
6558            canonical.as_str()
6559        };
6560        let Some(route) = st.discovery_routes.get_mut(&side) else {
6561            return Ok(false);
6562        };
6563        let Some(peer) = route.announcers.get_mut(sender) else {
6564            return Ok(false);
6565        };
6566        if let Some(ad) = &address
6567            && (peer.advertised_address != Some(ad.address)
6568                || peer.advertised_reachable != ad.reachable_endpoints
6569                || peer.advertised_reachable_timesync_sources != ad.reachable_timesync_sources
6570                || peer.reachable_network_variables != ad.reachable_network_variables
6571                || peer.link_capabilities != Some(ad.link_capabilities))
6572        {
6573            return Ok(false);
6574        }
6575        // A liveness update cannot add ownership, change identity, or enlarge
6576        // discovery state. Leave new/changed summaries behind the full guard.
6577        peer.last_seen_ms = now;
6578        route.last_seen_ms = route.last_seen_ms.max(now);
6579        if let Some(ad) = &address
6580            && let Some(entry) = st.address_book.get_mut(ad.hostname.as_str())
6581        {
6582            // A known, uncontested owner can restart without cloning its
6583            // unchanged route/schema snapshot. Record the new incarnation.
6584            entry.birth_ms = ad.birth_ms;
6585            entry.last_seen_ms = now;
6586        }
6587        Ok(true)
6588    }
6589
6590    #[cfg(feature = "discovery")]
6591    fn queue_pressure_keepalives(
6592        &self,
6593        requested_side: Option<RouterSideId>,
6594    ) -> TelemetryResult<()> {
6595        let now = self.clock.now_ms();
6596        let sender = self.sender_arc();
6597        crate::memory_admission::check(
6598            256 + sender.len().saturating_mul(4),
6599            sender.len().max(256),
6600        )?;
6601        let side_count = self.state.lock().sides.len();
6602        for side in 0..side_count {
6603            let due = {
6604                let st = self.state.lock();
6605                requested_side.is_none_or(|wanted| wanted == side)
6606                    && st.sides[side].is_some()
6607                    && self.route_allowed_locked(
6608                        &st,
6609                        None,
6610                        Some(crate::DataType::DiscoveryAnnounce),
6611                        side,
6612                    )
6613                    && st
6614                        .discovery_side_throttle
6615                        .get(&side)
6616                        .is_none_or(|throttle| now >= throttle.next_ping_ms)
6617            };
6618            if !due {
6619                continue;
6620            }
6621            // Propagate newly learned reachability even when the full topology
6622            // cannot fit. Build only a split-horizon summary, never a graph.
6623            let summary = {
6624                let st = self.state.lock();
6625                let extra = st
6626                    .discovery_routes
6627                    .iter()
6628                    .filter(|(id, _)| **id != side)
6629                    .fold(0usize, |total, (_, route)| {
6630                        total
6631                            .saturating_add(route.reachable.len().saturating_mul(32))
6632                            .saturating_add(
6633                                route.reachable_network_variables.len().saturating_mul(32),
6634                            )
6635                            .saturating_add(
6636                                route
6637                                    .reachable_timesync_sources
6638                                    .iter()
6639                                    .map(|source| source.len().saturating_add(32).saturating_mul(4))
6640                                    .sum::<usize>(),
6641                            )
6642                    });
6643                if crate::memory_admission::check(4096usize.saturating_add(extra), 2048).is_ok() {
6644                    let opts = st.sides[side].as_ref().expect("checked above").opts.clone();
6645                    let mut endpoints = self.local_discovery_endpoints(&st);
6646                    let mut variables =
6647                        self.advertised_network_variables_for_link_locked(&st, now, Some(side));
6648                    let mut sources = self.local_discovery_timesync_sources(now);
6649                    for (&route_side, route) in &st.discovery_routes {
6650                        if route_side == side {
6651                            continue;
6652                        }
6653                        for peer in route.announcers.values() {
6654                            if now.saturating_sub(peer.last_seen_ms) <= DISCOVERY_ROUTE_TTL_MS {
6655                                endpoints.extend(peer.reachable.iter().copied());
6656                                sources.extend(peer.reachable_timesync_sources.iter().cloned());
6657                            }
6658                        }
6659                    }
6660                    endpoints.retain(|ep| {
6661                        !discovery::is_discovery_endpoint(*ep)
6662                            && (opts.link_local_enabled || !ep.is_link_local_only())
6663                    });
6664                    crate::small_sort::sort(&mut endpoints);
6665                    endpoints.dedup();
6666                    crate::small_sort::sort(&mut variables);
6667                    variables.dedup();
6668                    sources.sort_unstable();
6669                    sources.dedup();
6670                    Some((endpoints, variables, sources, opts.link_capabilities()))
6671                } else {
6672                    None
6673                }
6674            };
6675            let pkt = if let Some((endpoints, variables, sources, capabilities)) = summary {
6676                let address = self.local_address_advertisement(
6677                    endpoints,
6678                    variables,
6679                    sources,
6680                    capabilities,
6681                    discovery::ADDRESS_STATE_REQUEST,
6682                );
6683                discovery::build_discovery_address(sender.as_ref(), now, &address)?
6684            } else {
6685                discovery::build_discovery_announce(sender.as_ref(), now, &[])?
6686            };
6687            self.emit_internal_tx(
6688                RouterTxItem::ToSide {
6689                    src: None,
6690                    dst: side,
6691                    data: RouterItem::Packet(pkt),
6692                },
6693                true,
6694                true,
6695            )?;
6696            self.state
6697                .lock()
6698                .discovery_side_throttle
6699                .entry(side)
6700                .or_default()
6701                .next_ping_ms = now.saturating_add(discovery::DISCOVERY_SLOW_INTERVAL_MS);
6702        }
6703        Ok(())
6704    }
6705
6706    #[cfg(feature = "discovery")]
6707    fn emit_discovery_snapshot(
6708        &self,
6709        called_from_queue: bool,
6710        include_schema: bool,
6711        include_topology: bool,
6712        skip_in_flight_sides: bool,
6713    ) -> TelemetryResult<()> {
6714        self.emit_discovery_snapshot_on_side(
6715            called_from_queue,
6716            include_schema,
6717            include_topology,
6718            skip_in_flight_sides,
6719            None,
6720        )
6721    }
6722
6723    #[cfg(feature = "discovery")]
6724    fn emit_discovery_snapshot_on_side(
6725        &self,
6726        called_from_queue: bool,
6727        include_schema: bool,
6728        include_topology: bool,
6729        skip_in_flight_sides: bool,
6730        requested_side: Option<RouterSideId>,
6731    ) -> TelemetryResult<()> {
6732        if requested_side.is_none() {
6733            let count = self.state.lock().sides.len();
6734            for side in 0..count {
6735                if self.state.lock().sides[side].is_some() {
6736                    self.emit_discovery_snapshot_on_side(
6737                        called_from_queue,
6738                        include_schema,
6739                        include_topology,
6740                        skip_in_flight_sides,
6741                        Some(side),
6742                    )?;
6743                }
6744            }
6745            return Ok(());
6746        }
6747        let (scratch, largest) = if include_schema {
6748            (8192, 2048)
6749        } else {
6750            let now = self.clock.now_ms();
6751            let sender = self.sender_arc();
6752            let st = self.state.lock();
6753            let election_required = self
6754                .preferred_discovery_master_present_locked(&st, now)
6755                .is_none();
6756            let snapshot = discovery::topology_snapshot_scratch(
6757                sender.as_ref(),
6758                self.cfg.handlers.len(),
6759                st.managed_variable_types.len().saturating_add(
6760                    st.discovery_routes
6761                        .iter()
6762                        .filter(|(side, _)| Some(**side) != requested_side)
6763                        .map(|(_, r)| r.reachable_network_variables.len())
6764                        .sum::<usize>(),
6765                ),
6766                4,
6767                true,
6768                st.discovery_routes
6769                    .iter()
6770                    .filter(|(side, route)| {
6771                        (Some(**side) != requested_side)
6772                            && now.saturating_sub(route.last_seen_ms) <= DISCOVERY_ROUTE_TTL_MS
6773                    })
6774                    .flat_map(|(_, route)| route.announcers.iter())
6775                    .filter(|(_, peer)| {
6776                        now.saturating_sub(peer.last_seen_ms) <= DISCOVERY_ROUTE_TTL_MS
6777                    })
6778                    .map(|(name, peer)| {
6779                        (
6780                            name.as_str(),
6781                            peer.reachable.as_slice(),
6782                            peer.reachable_timesync_sources.as_slice(),
6783                            peer.topology_boards.as_slice(),
6784                        )
6785                    }),
6786            );
6787            if !election_required {
6788                snapshot
6789            } else {
6790                let election = discovery::topology_snapshot_scratch(
6791                    sender.as_ref(),
6792                    self.cfg.handlers.len(),
6793                    0,
6794                    2,
6795                    false,
6796                    st.discovery_routes
6797                        .values()
6798                        .filter(|route| {
6799                            now.saturating_sub(route.last_seen_ms) <= DISCOVERY_ROUTE_TTL_MS
6800                        })
6801                        .flat_map(|route| route.announcers.iter())
6802                        .filter(|(_, peer)| {
6803                            now.saturating_sub(peer.last_seen_ms) <= DISCOVERY_ROUTE_TTL_MS
6804                        })
6805                        .map(|(name, peer)| {
6806                            (
6807                                name.as_str(),
6808                                peer.reachable.as_slice(),
6809                                peer.reachable_timesync_sources.as_slice(),
6810                                peer.topology_boards.as_slice(),
6811                            )
6812                        }),
6813                );
6814                (snapshot.0.max(election.0), snapshot.1.max(election.1))
6815            }
6816        };
6817        // Snapshot construction may clone several route/schema summaries.
6818        // Reclaim ordinary queued telemetry before refusing discovery scratch.
6819        // Priority >= 200 commands, ACKs and metadata stay protected; do not
6820        // evict routes or reliable receive/reassembly state.
6821        while let Err(err) = crate::memory_admission::check(scratch, largest) {
6822            if matches!(err, TelemetryError::Io("memory pressure"))
6823                && crate::memory_admission::check(512, 256).is_ok()
6824            {
6825                let mut st = self.state.lock();
6826                if let Some(victim) = st.lower_priority_shared_queue(199)
6827                    && st.pop_shared_queue_item(victim)
6828                {
6829                    continue;
6830                }
6831            }
6832            if !include_schema && matches!(err, TelemetryError::Io("memory pressure")) {
6833                return self.queue_pressure_keepalives(requested_side);
6834            }
6835            return Err(err);
6836        }
6837        let now_ms = self.clock.now_ms();
6838        let per_side = {
6839            let mut st = self.state.lock();
6840            if Self::prune_discovery_routes_locked(&mut st, now_ms) {
6841                self.reconcile_end_to_end_reliable_destinations_locked(&mut st)?;
6842                Self::note_discovery_topology_change_locked(&mut st, now_ms);
6843            }
6844            st.fit_discovery_budget();
6845            let local_is_master =
6846                self.discovery_master_sender_locked(&st, now_ms) == self.sender_arc().as_ref();
6847            let mut per_side = Vec::new();
6848            for side_id in 0..st.sides.len() {
6849                let Some(side) = st.sides[side_id].as_ref() else {
6850                    continue;
6851                };
6852                let opts = side.opts.clone();
6853                let link_local_enabled = opts.link_local_enabled;
6854                if requested_side.is_some_and(|requested| requested != side_id) {
6855                    continue;
6856                }
6857                let discovery_in_flight = skip_in_flight_sides
6858                    && st.reliable_tx.iter().any(|((pending_side, ty), tx_state)| {
6859                        *pending_side == side_id
6860                            && !tx_state.sent.is_empty()
6861                            && DataType::try_from_u32(*ty).is_some_and(discovery::is_discovery_type)
6862                    });
6863                if !self.route_allowed_locked(&st, None, Some(DataType::DiscoveryAnnounce), side_id)
6864                {
6865                    continue;
6866                }
6867                // A lost hop ACK must not make a live bridge disappear from
6868                // its neighbors. Keep the baseline pending, but permit a
6869                // bounded, unreliable keepalive independently of its ACK.
6870                let level = if discovery_in_flight {
6871                    let throttle = st.discovery_side_throttle.entry(side_id).or_default();
6872                    if !throttle.liveness_due(now_ms) {
6873                        None
6874                    } else {
6875                        throttle.next_ping_ms =
6876                            now_ms.saturating_add(DISCOVERY_SLOW_LINK_PING_INTERVAL_MS);
6877                        Some(DiscoveryAdvertiseLevel::MinimalPing)
6878                    }
6879                } else {
6880                    Self::discovery_level_for_side_locked(&mut st, side_id, now_ms)
6881                };
6882                let Some(level) = level else {
6883                    continue;
6884                };
6885                let include_side_topology = include_topology
6886                    && self.route_allowed_locked(
6887                        &st,
6888                        None,
6889                        Some(DataType::DiscoveryTopology),
6890                        side_id,
6891                    );
6892                let capabilities = opts.link_capabilities();
6893                if level == DiscoveryAdvertiseLevel::MinimalPing {
6894                    per_side.push((
6895                        side_id,
6896                        level,
6897                        Vec::new(),
6898                        Vec::new(),
6899                        Vec::new(),
6900                        Vec::new(),
6901                        Vec::new(),
6902                        capabilities,
6903                        local_is_master,
6904                    ));
6905                    continue;
6906                }
6907                let current_topology = if include_side_topology {
6908                    self.advertised_discovery_topology_for_link_locked(
6909                        &st,
6910                        now_ms,
6911                        link_local_enabled,
6912                        Some(side_id),
6913                    )
6914                } else {
6915                    Vec::new()
6916                };
6917                let (endpoints, timesync_sources) = if include_side_topology {
6918                    self.advertised_summaries_from_topology_locked(
6919                        &st,
6920                        now_ms,
6921                        link_local_enabled,
6922                        Some(side_id),
6923                        &current_topology,
6924                    )
6925                } else {
6926                    (
6927                        self.advertised_discovery_endpoints_for_link_locked(
6928                            &st,
6929                            now_ms,
6930                            link_local_enabled,
6931                            Some(side_id),
6932                        ),
6933                        self.advertised_discovery_timesync_sources_for_link_locked(
6934                            &st,
6935                            now_ms,
6936                            Some(side_id),
6937                        ),
6938                    )
6939                };
6940                let (topology, removed_topology) = match level {
6941                    DiscoveryAdvertiseLevel::Full => (current_topology.clone(), Vec::new()),
6942                    DiscoveryAdvertiseLevel::Incremental => {
6943                        let previous = &st
6944                            .discovery_side_throttle
6945                            .get(&side_id)
6946                            .expect("discovery throttle exists")
6947                            .last_topology;
6948                        discovery::topology_delta(previous, &current_topology)
6949                    }
6950                    DiscoveryAdvertiseLevel::MinimalPing => (Vec::new(), Vec::new()),
6951                };
6952                let throttle = st
6953                    .discovery_side_throttle
6954                    .get_mut(&side_id)
6955                    .expect("discovery throttle exists");
6956                if level == DiscoveryAdvertiseLevel::Full
6957                    || (level == DiscoveryAdvertiseLevel::Incremental
6958                        && throttle.incremental_retries_remaining == 0)
6959                {
6960                    throttle.last_topology = current_topology;
6961                }
6962                per_side.push((
6963                    side_id,
6964                    level,
6965                    endpoints,
6966                    self.advertised_network_variables_for_link_locked(&st, now_ms, Some(side_id)),
6967                    timesync_sources,
6968                    topology,
6969                    removed_topology,
6970                    capabilities,
6971                    local_is_master,
6972                ));
6973            }
6974            per_side
6975        };
6976        for (
6977            side_id,
6978            level,
6979            endpoints,
6980            network_variables,
6981            timesync_sources,
6982            topology,
6983            removed_topology,
6984            capabilities,
6985            local_is_master,
6986        ) in per_side
6987        {
6988            let sender = self.sender_arc();
6989            if matches!(
6990                level,
6991                DiscoveryAdvertiseLevel::Full | DiscoveryAdvertiseLevel::Incremental
6992            ) {
6993                let address = self.local_address_advertisement(
6994                    endpoints.clone(),
6995                    network_variables.clone(),
6996                    timesync_sources.clone(),
6997                    capabilities,
6998                    if local_is_master {
6999                        discovery::ADDRESS_STATE_APPROVED
7000                    } else {
7001                        discovery::ADDRESS_STATE_REQUEST
7002                    },
7003                );
7004                let pkt = discovery::build_discovery_address(sender.as_ref(), now_ms, &address)?;
7005                self.emit_internal_tx(
7006                    RouterTxItem::ToSide {
7007                        src: None,
7008                        dst: side_id,
7009                        data: RouterItem::Packet(pkt),
7010                    },
7011                    true,
7012                    called_from_queue,
7013                )?;
7014            }
7015            if level == DiscoveryAdvertiseLevel::MinimalPing {
7016                let pkt = discovery::build_discovery_announce(
7017                    sender.as_ref(),
7018                    now_ms,
7019                    endpoints.as_slice(),
7020                )?;
7021                self.emit_internal_tx(
7022                    RouterTxItem::ToSide {
7023                        src: None,
7024                        dst: side_id,
7025                        data: RouterItem::Packet(pkt),
7026                    },
7027                    true,
7028                    called_from_queue,
7029                )?;
7030            }
7031            if include_topology
7032                && (!topology.is_empty() || !removed_topology.is_empty())
7033                && level != DiscoveryAdvertiseLevel::MinimalPing
7034            {
7035                let pkt = if level == DiscoveryAdvertiseLevel::Full {
7036                    discovery::build_discovery_topology(sender.as_ref(), now_ms, &topology)?
7037                } else {
7038                    discovery::build_discovery_topology_delta(
7039                        sender.as_ref(),
7040                        now_ms,
7041                        &topology,
7042                        &removed_topology,
7043                    )?
7044                };
7045                self.emit_internal_tx(
7046                    RouterTxItem::ToSide {
7047                        src: None,
7048                        dst: side_id,
7049                        data: RouterItem::Packet(pkt),
7050                    },
7051                    true,
7052                    called_from_queue,
7053                )?;
7054            }
7055            // Release the temporary topology snapshot before constructing and
7056            // packing the larger schema. These buffers are no longer needed.
7057            drop((
7058                endpoints,
7059                network_variables,
7060                timesync_sources,
7061                topology,
7062                removed_topology,
7063            ));
7064            // Schema snapshots can be substantially larger than address and
7065            // topology advertisements. Send them after the route has been
7066            // established so a slow link can discover the network before it
7067            // begins transferring the merged metadata catalog.
7068            if include_schema && level == DiscoveryAdvertiseLevel::Full {
7069                let pkt = discovery::build_discovery_schema(sender.as_ref(), now_ms)?;
7070                self.emit_internal_tx(
7071                    RouterTxItem::ToSide {
7072                        src: None,
7073                        dst: side_id,
7074                        data: RouterItem::Packet(pkt),
7075                    },
7076                    true,
7077                    called_from_queue,
7078                )?;
7079            }
7080        }
7081        Ok(())
7082    }
7083
7084    #[cfg(feature = "discovery")]
7085    fn queue_discovery_announce(
7086        &self,
7087        include_schema: bool,
7088        skip_in_flight_sides: bool,
7089    ) -> TelemetryResult<()> {
7090        let now_ms = self.clock.now_ms();
7091        {
7092            let mut st = self.state.lock();
7093            if Self::prune_discovery_routes_locked(&mut st, now_ms) {
7094                self.reconcile_end_to_end_reliable_destinations_locked(&mut st)?;
7095                Self::note_discovery_topology_change_locked(&mut st, now_ms);
7096            }
7097            st.fit_discovery_budget();
7098            if st.sides.iter().all(|side| side.is_none()) {
7099                return Ok(());
7100            }
7101            // An explicit announce is the bootstrap/recovery operation. Keep
7102            // it full; cadence-triggered topology changes use Incremental.
7103            if include_schema {
7104                for throttle in st.discovery_side_throttle.values_mut() {
7105                    throttle.has_sent_full = false;
7106                }
7107            }
7108            st.discovery_cadence.on_announce_sent(now_ms);
7109        }
7110        self.emit_discovery_snapshot(true, include_schema, true, skip_in_flight_sides)
7111    }
7112
7113    #[cfg(feature = "discovery")]
7114    fn note_schema_received(&self, side: RouterSideId, sender: &str) {
7115        let mut st = self.state.lock();
7116        // Schema snapshots can be forwarded. They must never introduce a new
7117        // link-local announcer or change shared-bus reliability semantics.
7118        // If schema precedes topology, bounded recovery will request it again.
7119        let address = sender
7120            .strip_prefix("@addr:")
7121            .and_then(|value| value.parse::<u32>().ok());
7122        if let Some(route) = st.discovery_routes.get_mut(&side) {
7123            for (name, peer) in &mut route.announcers {
7124                if name == sender
7125                    || address.is_some_and(|address| {
7126                        peer.advertised_address == Some(address)
7127                            || sender_address_u32(name) == address
7128                    })
7129                {
7130                    peer.has_schema = true;
7131                    break;
7132                }
7133            }
7134        }
7135    }
7136
7137    #[cfg(feature = "discovery")]
7138    fn poll_discovery_announce(&self) -> TelemetryResult<bool> {
7139        let now_ms = self.clock.now_ms();
7140        // An address/keepalive proves liveness, not receipt of the detailed
7141        // baseline. Recover a lost bootstrap without waiting 120 seconds or
7142        // discarding existing routes. Coalesce all missing peers on one side.
7143        let recovery_sides = {
7144            let mut st = self.state.lock();
7145            let mut due = Vec::new();
7146            for side in 0..st.sides.len() {
7147                let missing = st.discovery_routes.get(&side).is_some_and(|route| {
7148                    route.announcers.iter().any(|(name, peer)| {
7149                        !peer.has_full_topology
7150                            && now_ms.saturating_sub(peer.last_seen_ms) < DISCOVERY_ROUTE_TTL_MS
7151                            && !route.announcers.values().any(|bridge| {
7152                                bridge.has_full_topology
7153                                    && now_ms.saturating_sub(bridge.last_seen_ms)
7154                                        < DISCOVERY_ROUTE_TTL_MS
7155                                    && bridge
7156                                        .topology_boards
7157                                        .iter()
7158                                        .any(|board| board.sender_id == *name)
7159                            })
7160                    })
7161                });
7162                if !missing {
7163                    continue;
7164                }
7165                let throttle = st.discovery_side_throttle.entry(side).or_default();
7166                if throttle.next_topology_request_ms == 0 {
7167                    throttle.next_topology_request_ms =
7168                        now_ms.saturating_add(discovery::DISCOVERY_BASELINE_GRACE_MS);
7169                } else if now_ms >= throttle.next_topology_request_ms {
7170                    throttle.next_topology_request_ms =
7171                        now_ms.saturating_add(discovery::DISCOVERY_BASELINE_RETRY_MS);
7172                    due.push(side);
7173                }
7174            }
7175            due
7176        };
7177        let recovering = !recovery_sides.is_empty();
7178        for side in recovery_sides {
7179            let pkt =
7180                discovery::build_discovery_topology_request(self.sender_arc().as_ref(), now_ms)?;
7181            self.emit_internal_tx(
7182                RouterTxItem::ToSide {
7183                    src: None,
7184                    dst: side,
7185                    data: RouterItem::Packet(pkt),
7186                },
7187                true,
7188                true,
7189            )?;
7190        }
7191        let schema_recovery_sides = {
7192            let mut st = self.state.lock();
7193            let mut due = Vec::new();
7194            for side in 0..st.sides.len() {
7195                let missing = st.discovery_routes.get(&side).is_some_and(|route| {
7196                    route.announcers.values().any(|peer| {
7197                        peer.has_full_topology
7198                            && !peer.has_schema
7199                            && now_ms.saturating_sub(peer.last_seen_ms) < DISCOVERY_ROUTE_TTL_MS
7200                    })
7201                });
7202                if !missing {
7203                    continue;
7204                }
7205                let throttle = st.discovery_side_throttle.entry(side).or_default();
7206                if throttle.next_schema_request_ms == 0 {
7207                    throttle.next_schema_request_ms =
7208                        now_ms.saturating_add(discovery::DISCOVERY_BASELINE_GRACE_MS);
7209                } else if now_ms >= throttle.next_schema_request_ms {
7210                    throttle.next_schema_request_ms =
7211                        now_ms.saturating_add(discovery::DISCOVERY_BASELINE_RETRY_MS);
7212                    due.push(side);
7213                }
7214            }
7215            due
7216        };
7217        let recovering = recovering || !schema_recovery_sides.is_empty();
7218        for side in schema_recovery_sides {
7219            let pkt =
7220                discovery::build_discovery_schema_request(self.sender_arc().as_ref(), now_ms)?;
7221            self.emit_internal_tx(
7222                RouterTxItem::ToSide {
7223                    src: None,
7224                    dst: side,
7225                    data: RouterItem::Packet(pkt),
7226                },
7227                true,
7228                true,
7229            )?;
7230        }
7231        let due = {
7232            let mut st = self.state.lock();
7233            let removed = Self::prune_discovery_routes_locked(&mut st, now_ms);
7234            if removed {
7235                self.reconcile_end_to_end_reliable_destinations_locked(&mut st)?;
7236                Self::note_discovery_topology_change_locked(&mut st, now_ms);
7237            }
7238            st.fit_discovery_budget();
7239            let has_any = st.sides.iter().enumerate().any(|(side_id, side)| {
7240                let Some(side) = side.as_ref() else {
7241                    return false;
7242                };
7243                if !self.route_allowed_locked(&st, None, Some(DataType::DiscoveryAnnounce), side_id)
7244                {
7245                    return false;
7246                }
7247                let _ = side;
7248                let in_flight = st.reliable_tx.iter().any(|((pending_side, ty), tx_state)| {
7249                    *pending_side == side_id
7250                        && !tx_state.sent.is_empty()
7251                        && DataType::try_from_u32(*ty).is_some_and(discovery::is_discovery_type)
7252                });
7253                st.discovery_side_throttle
7254                    .get(&side_id)
7255                    .is_none_or(|throttle| {
7256                        (!in_flight && (!throttle.has_sent_full || throttle.pending_incremental))
7257                            || throttle.liveness_due(now_ms)
7258                    })
7259            });
7260            if st.sides.is_empty() || !has_any {
7261                return Ok(recovering);
7262            }
7263            st.discovery_cadence.due(now_ms)
7264        };
7265        if !due {
7266            return Ok(recovering);
7267        }
7268        // Runtime liveness/topology refreshes must stay small. A full schema
7269        // is sent by the initial explicit announce and in response to
7270        // DiscoverySchemaRequest; repeating it at every cadence can starve
7271        // commands on constrained serial and radio links.
7272        // A stalled reliable link must not prevent freshly learned routes
7273        // from propagating across another healthy side. Only omit the side
7274        // that already has discovery awaiting its hop ACK, except for bounded
7275        // liveness pings that must not depend on that ACK arriving.
7276        self.queue_discovery_announce(false, true)?;
7277        Ok(true)
7278    }
7279
7280    #[cfg(feature = "discovery")]
7281    fn learn_discovery_packet(
7282        &self,
7283        pkt: &Packet,
7284        src: Option<RouterSideId>,
7285        called_from_queue: bool,
7286    ) -> TelemetryResult<bool> {
7287        if !discovery::is_discovery_type(pkt.data_type()) {
7288            return Ok(false);
7289        }
7290        let Some(side) = src else {
7291            return Ok(true);
7292        };
7293        if self.refresh_known_discovery_liveness(pkt, side)? {
7294            return Ok(true);
7295        }
7296        #[cfg(not(feature = "std"))]
7297        if pkt.data_type() == DataType::DiscoverySchema
7298            && discovery::discovery_schema_payload_is_fully_known(pkt.payload())?
7299        {
7300            self.note_schema_received(side, pkt.sender());
7301            return Ok(true);
7302        }
7303        // A compact address summary does not clone the complete topology or
7304        // schema. Account for its ingress-side aggregates instead; charging
7305        // every new peer for a full snapshot can prevent restart recovery.
7306        if matches!(
7307            pkt.data_type(),
7308            DataType::DiscoveryTopologyRequest | DataType::DiscoverySchemaRequest
7309        ) {
7310            crate::memory_admission::check(1024, 256)?;
7311            // A requesting peer may have restarted with an empty RX dictionary.
7312            // Its identity and routes need not change. Refresh only this link
7313            // so the next data/state report is immediately self-describing;
7314            // retain RX dictionaries and all learned topology.
7315            if let Some(transport) = self.state.lock().side_transport.get_mut(&side) {
7316                transport.refresh_transmit_header_templates();
7317            }
7318        }
7319        if matches!(
7320            pkt.data_type(),
7321            DataType::DiscoveryTopologyRequest | DataType::DiscoverySchemaRequest
7322        ) {
7323            let now_ms = self.clock.now_ms();
7324            {
7325                let mut st = self.state.lock();
7326                if Self::prune_discovery_routes_locked(&mut st, now_ms) {
7327                    self.reconcile_end_to_end_reliable_destinations_locked(&mut st)?;
7328                    Self::note_discovery_topology_change_locked(&mut st, now_ms);
7329                }
7330                let throttle = st.discovery_side_throttle.entry(side).or_default();
7331                throttle.has_sent_full = false;
7332                // A full reply cannot use the old incremental baseline. Drop
7333                // that derived copy before admitting its replacement scratch;
7334                // keep all learned routes, schemas and other links intact.
7335                throttle.last_topology = Vec::new();
7336            }
7337            // Do not depend on the elected master answering a forwarded request.
7338            // A recovering neighbor must get our retained downstream baseline
7339            // even before its identity is known. Reply only on its ingress link;
7340            // do not reset other links or broadcast application traffic.
7341            self.emit_discovery_snapshot_on_side(
7342                called_from_queue,
7343                pkt.data_type() == DataType::DiscoverySchemaRequest,
7344                true,
7345                false,
7346                Some(side),
7347            )?;
7348            return Ok(true);
7349        }
7350        let retained_summary = if matches!(
7351            pkt.data_type(),
7352            DataType::DiscoveryAddress
7353                | DataType::DiscoveryTopology
7354                | DataType::DiscoveryAnnounce
7355                | DataType::DiscoveryTimeSyncSources
7356                | DataType::ManagedVariableRequest
7357        ) {
7358            self.state
7359                .lock()
7360                .discovery_routes
7361                .get(&side)
7362                .map_or(0, |route| {
7363                    RouterInner::discovery_route_byte_cost(side, route)
7364                })
7365        } else {
7366            0
7367        };
7368        let (decode_bytes, largest_block) = if pkt.data_type() == DataType::ManagedVariableRequest {
7369            let ty = discovery::decode_managed_variable_request(pkt)?;
7370            let existing = self
7371                .state
7372                .lock()
7373                .discovery_routes
7374                .get(&side)
7375                .is_some_and(|route| {
7376                    route
7377                        .requested_network_variables
7378                        .iter()
7379                        .any(|(id, _)| *id == ty)
7380                });
7381            if existing {
7382                (1024, 256)
7383            } else {
7384                // New subscriptions clone only this ingress summary; refreshing
7385                // an existing lease constructs no graph or schema definitions.
7386                (
7387                    2048usize.saturating_add(retained_summary.saturating_mul(2)),
7388                    retained_summary
7389                        .checked_next_power_of_two()
7390                        .unwrap_or(usize::MAX)
7391                        .max(1024),
7392                )
7393            }
7394        } else {
7395            let scratch = if pkt.data_type() == DataType::DiscoveryAddress {
7396                4096usize.saturating_add(retained_summary)
7397            } else {
7398                8192usize.saturating_add(retained_summary)
7399            };
7400            let decode_bytes = pkt
7401                .payload()
7402                .len()
7403                .saturating_mul(4)
7404                .saturating_add(scratch);
7405            let largest_block = pkt.payload().len().saturating_mul(2).max(2048);
7406            (decode_bytes, largest_block)
7407        };
7408        while let Err(err) = crate::memory_admission::check(decode_bytes, largest_block) {
7409            if !matches!(err, TelemetryError::Io("memory pressure")) {
7410                return Err(err);
7411            }
7412            let mut st = self.state.lock();
7413            let Some(victim) = st.lower_priority_shared_queue(255) else {
7414                return Err(err);
7415            };
7416            if !st.pop_shared_queue_item(victim) {
7417                return Err(err);
7418            }
7419        }
7420        if pkt.data_type() == DataType::DiscoveryAddress {
7421            let mut ad = discovery::decode_discovery_address(pkt)?;
7422            let identity = ad.clone();
7423            let mut changed = false;
7424            /* Packed no_std frames represent their source as @addr:N until
7425             * discovery resolves the address. DiscoveryAddress carries the
7426             * authoritative hostname in its payload, so key reachability by
7427             * that name immediately instead of permanently treating the
7428             * placeholder as a distinct router. */
7429            let sender_id = if ad.hostname.is_empty() {
7430                pkt.sender()
7431            } else {
7432                ad.hostname.as_str()
7433            };
7434            let now_ms = self.clock.now_ms();
7435            let mut st = self.state.lock();
7436            let side_link_local_enabled = st
7437                .sides
7438                .get(side)
7439                .and_then(|entry| entry.as_ref())
7440                .map(|side_ref| side_ref.opts.link_local_enabled)
7441                .unwrap_or(false);
7442            if !side_link_local_enabled {
7443                ad.reachable_endpoints.retain(|ep| !ep.is_link_local_only());
7444            }
7445            let mut route = st.discovery_routes.get(&side).cloned().unwrap_or_default();
7446            let previously_reachable_network_variables = route.reachable_network_variables.clone();
7447            if pkt.sender() != sender_id {
7448                route.announcers.remove(pkt.sender());
7449            }
7450            let mut sender_state =
7451                core::mem::take(route.announcers.entry(sender_id.to_string()).or_default());
7452            // DiscoveryAddress is a compact route summary. A multi-sided
7453            // router advertises endpoints reachable *through* it, so these
7454            // entries must not be attached to the announcer's topology node:
7455            // that would falsely claim ownership and freeze reliable packets
7456            // to the bridge before detailed topology arrives.
7457            if sender_state.advertised_address != Some(ad.address) {
7458                sender_state.advertised_address = Some(ad.address);
7459                changed = true;
7460            }
7461            if sender_state.advertised_reachable != ad.reachable_endpoints {
7462                sender_state.advertised_reachable = ad.reachable_endpoints;
7463                changed = true;
7464            }
7465            if sender_state.advertised_reachable_timesync_sources != ad.reachable_timesync_sources {
7466                sender_state.advertised_reachable_timesync_sources = ad.reachable_timesync_sources;
7467                changed = true;
7468            }
7469            if sender_state.reachable_network_variables != ad.reachable_network_variables {
7470                sender_state.reachable_network_variables = ad.reachable_network_variables;
7471                changed = true;
7472            }
7473            if sender_state.link_capabilities != Some(ad.link_capabilities) {
7474                sender_state.link_capabilities = Some(ad.link_capabilities);
7475                changed = true;
7476            }
7477            Self::refresh_sender_topology_state(&mut sender_state);
7478            sender_state.last_seen_ms = now_ms;
7479            route.announcers.insert(sender_id.to_string(), sender_state);
7480            Self::recompute_discovery_side_state(&mut route);
7481            let newly_reachable_network_variables: Vec<DataType> = route
7482                .reachable_network_variables
7483                .iter()
7484                .copied()
7485                .filter(|ty| !previously_reachable_network_variables.contains(ty))
7486                .collect();
7487            st.commit_discovery_route(side, route)?;
7488            self.reconcile_end_to_end_reliable_destinations_locked(&mut st)?;
7489            if changed {
7490                Self::note_discovery_topology_change_locked(&mut st, self.clock.now_ms());
7491            }
7492            // A writer may have changed a managed variable while no remote
7493            // owner route was present. Local-only suppression correctly kept
7494            // that update off unrelated links, but the new owner still needs
7495            // the latest value. Replay only newly reachable variable types to
7496            // this advertising side; do not fan out or restart discovery.
7497            let replay: Vec<Packet> = newly_reachable_network_variables
7498                .iter()
7499                .filter(|ty| Self::managed_variable_permissions_locked(&st, **ty).write)
7500                .filter_map(|ty| {
7501                    st.managed_variable_latest
7502                        .get(&ty.as_u32())
7503                        .map(|entry| entry.packet.clone())
7504                })
7505                .collect();
7506            drop(st);
7507            self.ingest_address_advertisement(identity)?;
7508            for value in replay {
7509                self.emit_internal_tx_with_priority(
7510                    RouterTxItem::ToSide {
7511                        src: None,
7512                        dst: side,
7513                        data: RouterItem::Packet(value),
7514                    },
7515                    true,
7516                    crate::transport_priority(DataType::ManagedVariableValue),
7517                    called_from_queue,
7518                )?;
7519            }
7520            return Ok(true);
7521        }
7522        let mut decoded_topology = if pkt.data_type() == DataType::DiscoveryTopology {
7523            Some(discovery::decode_discovery_topology_update(pkt)?)
7524        } else {
7525            None
7526        };
7527        let packet_sender = {
7528            let st = self.state.lock();
7529            let canonical = Self::canonical_sender_locked(&st, pkt.sender());
7530            if canonical != pkt.sender() {
7531                canonical
7532            } else if let Some(address) = pkt
7533                .sender()
7534                .strip_prefix("@addr:")
7535                .and_then(|value| value.parse::<u32>().ok())
7536            {
7537                // A compact topology frame may arrive before its reliable
7538                // DiscoveryAddress frame. Topology still carries stable board
7539                // names, so use the entry whose wire hash matches the compact
7540                // source address. This keeps autonomous routers keyed by name
7541                // even when the header omits that hostname.
7542                decoded_topology
7543                    .as_ref()
7544                    .and_then(|update| {
7545                        update
7546                            .boards
7547                            .iter()
7548                            .find(|board| sender_address_u32(&board.sender_id) == address)
7549                    })
7550                    .map(|board| board.sender_id.clone())
7551                    .unwrap_or(canonical)
7552            } else {
7553                canonical
7554            }
7555        };
7556        let local_sender = self.sender_arc();
7557        if packet_sender == local_sender.as_ref() {
7558            return Ok(true);
7559        }
7560
7561        if pkt.data_type() == DataType::ManagedVariableRequest {
7562            let ty = discovery::decode_managed_variable_request(pkt)?;
7563            // A refresh request is direct evidence that this ingress segment
7564            // contains a subscriber for the variable. Track one bounded entry
7565            // per type and side rather than manufacturing a full discovery
7566            // announcer for every requester. This keeps later writes selective
7567            // without growing the topology graph or fragmenting embedded pools.
7568            {
7569                let now_ms = self.clock.now_ms();
7570                let mut st = self.state.lock();
7571                // Refreshing an existing subscription changes only its lease.
7572                // Do not clone the complete learned topology or rebuild route
7573                // summaries on every retry from a slow/disconnected subscriber.
7574                let existing = st.discovery_routes.get_mut(&side).and_then(|route| {
7575                    let (_, last_seen_ms) = route
7576                        .requested_network_variables
7577                        .iter_mut()
7578                        .find(|(requested, _)| *requested == ty)?;
7579                    *last_seen_ms = now_ms;
7580                    route.last_seen_ms = route.last_seen_ms.max(now_ms);
7581                    Some(())
7582                });
7583                let newly_reachable = existing.is_none();
7584                if newly_reachable {
7585                    let mut route = st.discovery_routes.get(&side).cloned().unwrap_or_default();
7586                    route.requested_network_variables.push((ty, now_ms));
7587                    Self::recompute_discovery_side_state(&mut route);
7588                    st.commit_discovery_route(side, route)?;
7589                }
7590                if newly_reachable {
7591                    self.reconcile_end_to_end_reliable_destinations_locked(&mut st)?;
7592                    Self::note_discovery_topology_change_locked(&mut st, now_ms);
7593                }
7594            }
7595            if !self.can_write_managed_variable(ty) {
7596                // Read-only caches are replicas, not authoritative owners.
7597                // Leave the request unhandled so normal discovery routing can
7598                // carry it toward a writer on another segment even when this
7599                // subscriber restored an older persisted value at boot.
7600                return Ok(false);
7601            }
7602            if let Some(value) = self.managed_variable_latest(ty) {
7603                self.emit_internal_tx(
7604                    RouterTxItem::ToSide {
7605                        src: None,
7606                        dst: side,
7607                        data: RouterItem::Packet(value),
7608                    },
7609                    true,
7610                    called_from_queue,
7611                )?;
7612                return Ok(true);
7613            }
7614            // A writer may not have produced its first value yet. Keep the
7615            // request moving in case another authoritative writer has one.
7616            return Ok(false);
7617        }
7618        if pkt.data_type() == DataType::DiscoverySchema {
7619            let snapshot = discovery::decode_discovery_schema(pkt)?;
7620            // The decoded snapshot contains the sender's complete schema, but
7621            // embedded targets retain only definitions that are not already
7622            // compiled into their local schema. Charging the complete wire
7623            // snapshot here made heterogeneous boards reject small schema
7624            // deltas whenever the sender's full snapshot exceeded the shared
7625            // queue budget. The merge performs an atomic projected-retained-
7626            // bytes check; afterwards, reclaim queue/topology space using the
7627            // actual retained schema size included by shared_queue_bytes_used.
7628            let mut st = self.state.lock();
7629            let budget = st.schema_growth_budget();
7630            let report = crate::config::merge_owned_schema_snapshot_with_budget(snapshot, budget)?;
7631            if report.changed() {
7632                st.fit_discovery_budget();
7633            }
7634            drop(st);
7635            self.note_schema_received(side, &packet_sender);
7636            return Ok(true);
7637        }
7638        if pkt.data_type() == DataType::DiscoveryLinkCapabilities {
7639            let _ = discovery::decode_discovery_link_capabilities(pkt)?;
7640            return Ok(true);
7641        }
7642        let mut announce = if pkt.data_type() == DataType::DiscoveryAnnounce {
7643            Some(discovery::decode_discovery_announce(pkt)?)
7644        } else {
7645            None
7646        };
7647        let mut time_sources = if pkt.data_type() == DataType::DiscoveryTimeSyncSources {
7648            Some(discovery::decode_discovery_timesync_sources(pkt)?)
7649        } else {
7650            None
7651        };
7652        let mut st = self.state.lock();
7653        let now_ms = self.clock.now_ms();
7654        if pkt.data_type() == DataType::DiscoveryLeave {
7655            let leaving = packet_sender.as_str();
7656            let before = st.discovery_routes.clone();
7657            for route in st.discovery_routes.values_mut() {
7658                route.announcers.remove(leaving);
7659                for sender_state in route.announcers.values_mut() {
7660                    sender_state
7661                        .topology_boards
7662                        .retain(|board| board.sender_id != leaving);
7663                    for board in sender_state.topology_boards.iter_mut() {
7664                        board.connections.retain(|peer| peer != leaving);
7665                    }
7666                    Self::refresh_sender_topology_state(sender_state);
7667                }
7668                Self::recompute_discovery_side_state(route);
7669            }
7670            st.discovery_routes
7671                .retain(|_, route| !route.announcers.is_empty());
7672            if st.discovery_routes != before {
7673                Self::note_discovery_topology_change_locked(&mut st, now_ms);
7674                self.reconcile_end_to_end_reliable_destinations_locked(&mut st)?;
7675            }
7676            return Ok(true);
7677        }
7678        // Resolve aliases while every retained route is still visible.
7679        if let Some(update) = decoded_topology.as_mut() {
7680            for board in update.boards.iter_mut() {
7681                board.sender_id = Self::canonical_sender_locked(&st, &board.sender_id);
7682                for peer in board.connections.iter_mut() {
7683                    *peer = Self::canonical_sender_locked(&st, peer);
7684                }
7685            }
7686        }
7687        let mut route = st.discovery_routes.get(&side).cloned().unwrap_or_default();
7688        let alias_state = if packet_sender != pkt.sender() {
7689            route.announcers.remove(pkt.sender())
7690        } else {
7691            None
7692        };
7693        let side_link_local_enabled = st
7694            .sides
7695            .get(side)
7696            .and_then(|entry| entry.as_ref())
7697            .map(|side_ref| side_ref.opts.link_local_enabled)
7698            .unwrap_or(false);
7699        let mut sender_state = route
7700            .announcers
7701            .get_mut(&packet_sender)
7702            .map(core::mem::take)
7703            .or(alias_state)
7704            .unwrap_or_default();
7705        let changed = match pkt.data_type() {
7706            DataType::DiscoveryAnnounce => {
7707                let mut reachable = announce.take().expect("decoded above");
7708                // Empty announcements are slow-link keepalives, not endpoint
7709                // withdrawals. Preserve ownership until an explicit topology
7710                // update, leave, or expiry removes it. The common receive tail
7711                // still refreshes last_seen_ms without scheduling rediscovery.
7712                if reachable.is_empty() {
7713                    false
7714                } else {
7715                    if !side_link_local_enabled {
7716                        reachable.retain(|ep| !ep.is_link_local_only());
7717                    }
7718                    let board = Self::sender_topology_board_mut(&mut sender_state, &packet_sender);
7719                    let changed = board.reachable_endpoints != reachable;
7720                    board.reachable_endpoints = reachable;
7721                    Self::refresh_sender_topology_state(&mut sender_state);
7722                    changed
7723                }
7724            }
7725            DataType::DiscoveryTimeSyncSources => {
7726                let sources = time_sources.take().expect("decoded above");
7727                let board = Self::sender_topology_board_mut(&mut sender_state, &packet_sender);
7728                let changed = board.reachable_timesync_sources != sources;
7729                board.reachable_timesync_sources = sources;
7730                Self::refresh_sender_topology_state(&mut sender_state);
7731                changed
7732            }
7733            DataType::DiscoveryTopology => {
7734                let mut update = decoded_topology
7735                    .take()
7736                    .expect("topology packet was decoded before route selection");
7737                if !side_link_local_enabled {
7738                    for board in update.boards.iter_mut() {
7739                        board
7740                            .reachable_endpoints
7741                            .retain(|ep| !ep.is_link_local_only());
7742                    }
7743                }
7744                if !update.incremental {
7745                    sender_state.has_full_topology = true;
7746                }
7747                let changed =
7748                    discovery::apply_topology_update(&mut sender_state.topology_boards, update);
7749                Self::refresh_sender_topology_state(&mut sender_state);
7750                changed
7751            }
7752            DataType::DiscoverySchema => false,
7753            _ => false,
7754        };
7755        sender_state.last_seen_ms = now_ms;
7756        route.announcers.insert(packet_sender, sender_state);
7757        Self::recompute_discovery_side_state(&mut route);
7758        st.commit_discovery_route(side, route)?;
7759        self.reconcile_end_to_end_reliable_destinations_locked(&mut st)?;
7760        if changed {
7761            Self::note_discovery_topology_change_locked(&mut st, now_ms);
7762        }
7763        Ok(true)
7764    }
7765
7766    #[cfg(not(feature = "discovery"))]
7767    fn queue_discovery_announce(&self) -> TelemetryResult<()> {
7768        Ok(())
7769    }
7770
7771    #[cfg(not(feature = "discovery"))]
7772    fn poll_discovery_announce(&self) -> TelemetryResult<bool> {
7773        Ok(false)
7774    }
7775
7776    #[cfg(not(feature = "discovery"))]
7777    fn learn_discovery_packet(
7778        &self,
7779        _pkt: &Packet,
7780        _src: Option<RouterSideId>,
7781        _called_from_queue: bool,
7782    ) -> TelemetryResult<bool> {
7783        Ok(false)
7784    }
7785
7786    #[inline]
7787    fn reliable_key(side: RouterSideId, ty: DataType) -> (RouterSideId, u32) {
7788        (side, ty.as_u32())
7789    }
7790
7791    fn reliable_tx_state_mut<'a>(
7792        &'a self,
7793        st: &'a mut RouterInner,
7794        side: RouterSideId,
7795        ty: DataType,
7796    ) -> &'a mut ReliableTxState {
7797        let key = Self::reliable_key(side, ty);
7798        st.reliable_tx
7799            .entry(key)
7800            .or_insert_with(|| ReliableTxState {
7801                next_seq: 1,
7802                sent_order: VecDeque::new(),
7803                sent: BTreeMap::new(),
7804            })
7805    }
7806
7807    fn reliable_rx_state_mut<'a>(
7808        &'a self,
7809        st: &'a mut RouterInner,
7810        side: RouterSideId,
7811        ty: DataType,
7812    ) -> &'a mut ReliableRxState {
7813        let key = Self::reliable_key(side, ty);
7814        st.reliable_rx
7815            .entry(key)
7816            .or_insert_with(|| ReliableRxState {
7817                expected_seq: 1,
7818                buffered: BTreeMap::new(),
7819            })
7820    }
7821
7822    fn reliable_control_packet(
7823        &self,
7824        control_ty: DataType,
7825        ty: DataType,
7826        seq: u32,
7827    ) -> TelemetryResult<Packet> {
7828        let sender = self.sender_arc();
7829        Packet::new(
7830            control_ty,
7831            message_meta(control_ty).endpoints_ref(),
7832            sender.as_ref(),
7833            self.packet_timestamp_ms(),
7834            encode_slice_le(&[ty.as_u32(), seq]),
7835        )
7836    }
7837
7838    fn queue_reliable_ack(
7839        &self,
7840        side: RouterSideId,
7841        ty: DataType,
7842        seq: u32,
7843        called_from_queue: bool,
7844    ) -> TelemetryResult<()> {
7845        let pkt = self.reliable_control_packet(DataType::ReliableAck, ty, seq)?;
7846        self.emit_internal_tx_with_priority(
7847            RouterTxItem::ToSide {
7848                src: None,
7849                dst: side,
7850                data: RouterItem::Packet(pkt),
7851            },
7852            true,
7853            message_priority(DataType::ReliableAck),
7854            called_from_queue,
7855        )
7856    }
7857
7858    fn queue_reliable_packet_request(
7859        &self,
7860        side: RouterSideId,
7861        ty: DataType,
7862        seq: u32,
7863        called_from_queue: bool,
7864    ) -> TelemetryResult<()> {
7865        let pkt = self.reliable_control_packet(DataType::ReliablePacketRequest, ty, seq)?;
7866        self.emit_internal_tx_with_priority(
7867            RouterTxItem::ToSide {
7868                src: None,
7869                dst: side,
7870                data: RouterItem::Packet(pkt),
7871            },
7872            true,
7873            message_priority(DataType::ReliablePacketRequest),
7874            called_from_queue,
7875        )
7876    }
7877
7878    fn queue_reliable_partial_ack(
7879        &self,
7880        side: RouterSideId,
7881        ty: DataType,
7882        seq: u32,
7883        called_from_queue: bool,
7884    ) -> TelemetryResult<()> {
7885        let pkt = self.reliable_control_packet(DataType::ReliablePartialAck, ty, seq)?;
7886        self.emit_internal_tx_with_priority(
7887            RouterTxItem::ToSide {
7888                src: None,
7889                dst: side,
7890                data: RouterItem::Packet(pkt),
7891            },
7892            true,
7893            message_priority(DataType::ReliablePartialAck),
7894            called_from_queue,
7895        )
7896    }
7897
7898    fn handle_reliable_ack(&self, side: RouterSideId, ty: DataType, ack: u32) {
7899        let mut st = self.state.lock();
7900        let tx_state = self.reliable_tx_state_mut(&mut st, side, ty);
7901        if matches!(reliable_mode(ty), crate::ReliableMode::Unordered) {
7902            tx_state.sent.remove(&ack);
7903            tx_state.sent_order.retain(|seq| *seq != ack);
7904            return;
7905        }
7906
7907        while let Some(seq) = tx_state.sent_order.front().copied() {
7908            if seq > ack {
7909                break;
7910            }
7911            tx_state.sent_order.pop_front();
7912            tx_state.sent.remove(&seq);
7913        }
7914    }
7915
7916    fn handle_reliable_partial_ack(&self, side: RouterSideId, ty: DataType, seq: u32) {
7917        let mut st = self.state.lock();
7918        let tx_state = self.reliable_tx_state_mut(&mut st, side, ty);
7919        if let Some(sent) = tx_state.sent.get_mut(&seq) {
7920            sent.partial_acked = true;
7921        }
7922    }
7923
7924    fn queue_reliable_retransmit(
7925        &self,
7926        side: RouterSideId,
7927        ty: DataType,
7928        seq: u32,
7929        called_from_queue: bool,
7930    ) -> TelemetryResult<()> {
7931        let mut queued = None;
7932        {
7933            let mut st = self.state.lock();
7934            let tx_state = self.reliable_tx_state_mut(&mut st, side, ty);
7935            if let Some(sent) = tx_state.sent.get_mut(&seq)
7936                && !sent.queued
7937            {
7938                sent.queued = true;
7939                sent.partial_acked = false;
7940                queued = Some(sent.bytes.clone());
7941            }
7942        }
7943
7944        if let Some(bytes) = queued {
7945            if called_from_queue {
7946                self.tx_queue_item_with_priority(
7947                    RouterTxItem::ReliableReplay { dst: side, bytes },
7948                    true,
7949                    Self::router_item_priority_bumped(ty),
7950                )?;
7951            } else {
7952                self.tx_item_impl(
7953                    RouterTxItem::ReliableReplay { dst: side, bytes },
7954                    true,
7955                    false,
7956                )?;
7957            }
7958        }
7959
7960        Ok(())
7961    }
7962
7963    fn send_reliable_raw_to_side(
7964        &self,
7965        side: RouterSideId,
7966        bytes: crate::SharedBytes,
7967        relayed: bool,
7968    ) -> TelemetryResult<()> {
7969        let handler = {
7970            let st = self.state.lock();
7971            let side_ref = Self::side_ref(&st, side)?;
7972            if !side_ref.opts.egress_enabled {
7973                return Ok(());
7974            }
7975            (side_ref.tx_handler.clone(), side_ref.opts.clone())
7976        };
7977
7978        let (handler, opts) = handler;
7979
7980        let Some(_side_tx_guard) = self.try_enter_side_tx() else {
7981            return Err(TelemetryError::Io("side tx busy"));
7982        };
7983        let started_ms = self.clock.now_ms();
7984        let (ty, priority) = Self::item_type_priority(&RouterItem::Packed(bytes.clone()))?;
7985        let priority = priority.max(self.effective_transport_priority(ty));
7986        let result = match handler {
7987            packed @ (RouterTxHandlerFn::Packed(_) | RouterTxHandlerFn::PackedWithPriority(_)) => {
7988                let frames = self.encode_side_transport_frames(side, opts, bytes.clone())?;
7989                let mut attempts_total = 0usize;
7990                let mut sent_bytes = 0usize;
7991                self.send_side_frames_resumable(side, frames, |frame| {
7992                    match self.retry_with_attempts(runtime_max_handler_retries(), || {
7993                        Self::packed_handler_call(&packed, frame, priority)
7994                    }) {
7995                        Ok((_, attempts)) => {
7996                            attempts_total = attempts_total.saturating_add(attempts);
7997                            sent_bytes = sent_bytes.saturating_add(frame.len());
7998                        }
7999                        Err((err, attempts)) => {
8000                            self.note_side_tx_failure(
8001                                side,
8002                                ty,
8003                                attempts_total.saturating_add(attempts),
8004                            );
8005                            return Err(err);
8006                        }
8007                    }
8008
8009                    Ok(())
8010                })?;
8011                self.record_side_tx_sample(side, sent_bytes, started_ms, self.clock.now_ms());
8012                self.note_side_tx_success(side, ty, sent_bytes, relayed, attempts_total);
8013                return Ok(());
8014            }
8015            RouterTxHandlerFn::Packet(f) => {
8016                let pkt = self.unpack_packet_for_role(bytes.as_ref())?;
8017                self.retry_with_attempts(runtime_max_handler_retries(), || f(&pkt))
8018            }
8019        };
8020        match result {
8021            Ok((_, attempts)) => {
8022                self.record_side_tx_sample(side, bytes.len(), started_ms, self.clock.now_ms());
8023                self.note_side_tx_success(side, ty, bytes.len(), relayed, attempts);
8024                Ok(())
8025            }
8026            Err((err, attempts)) => {
8027                self.note_side_tx_failure(side, ty, attempts);
8028                Err(err)
8029            }
8030        }
8031    }
8032
8033    #[allow(unused_mut)]
8034    fn send_reliable_to_side(
8035        &self,
8036        side: RouterSideId,
8037        data: RouterItem,
8038        relayed: bool,
8039    ) -> TelemetryResult<()> {
8040        let end_to_end_ordered = !self.item_target_senders(&data)?.is_empty()
8041            && matches!(
8042                reliable_mode(self.item_route_info(&data)?.1),
8043                crate::ReliableMode::Ordered
8044            );
8045        let (handler, opts, hop_reliable_enabled) = {
8046            let st = self.state.lock();
8047            let side_ref = Self::side_ref(&st, side)?;
8048            let opts = side_ref.opts.clone();
8049            let hop_reliable_enabled = opts.reliable_enabled
8050                && !end_to_end_ordered
8051                && self.cfg.reliable_enabled()
8052                && !self.side_has_multiple_announcers_locked(&st, side, self.clock.now_ms());
8053            (side_ref.tx_handler.clone(), opts, hop_reliable_enabled)
8054        };
8055
8056        if !matches!(
8057            &handler,
8058            RouterTxHandlerFn::Packed(_) | RouterTxHandlerFn::PackedWithPriority(_)
8059        ) {
8060            return self.call_side_tx_handler(side, &handler, &data, relayed);
8061        }
8062
8063        if !hop_reliable_enabled {
8064            let mut adjusted_opts = opts;
8065            adjusted_opts.reliable_enabled = false;
8066            // End-to-end ACKs are ordinary routed control packets and must cross best-effort
8067            // segments. `reliable_enabled` controls only this side's hop sequencing/replay; using
8068            // it to discard an ACK prevents a reliable origin from ever confirming destinations
8069            // behind Pico-Fi, UDP, radio, or another transport with its own reliability layer.
8070            let preserve_end_to_end_ack = true;
8071            if let Some(adjusted) =
8072                self.adjust_reliable_for_side(adjusted_opts, data, preserve_end_to_end_ack)?
8073            {
8074                return self.call_side_tx_handler(side, &handler, &adjusted, relayed);
8075            }
8076            return Ok(());
8077        }
8078
8079        let ty = match &data {
8080            RouterItem::Packet(pkt) => pkt.data_type(),
8081            RouterItem::Packed(bytes) => {
8082                wire_format::peek_routing_frame_info(bytes.as_ref())?
8083                    .envelope
8084                    .ty
8085            }
8086        };
8087        let priority = self.item_service_priority(&data)?;
8088
8089        if !is_reliable_type(ty) {
8090            if let Some(adjusted) = self.adjust_reliable_for_side(opts, data, true)? {
8091                self.call_side_tx_handler(side, &handler, &adjusted, relayed)?;
8092            }
8093            return Ok(());
8094        }
8095
8096        let (seq, flags) = {
8097            let mut st = self.state.lock();
8098            let tx_state = self.reliable_tx_state_mut(&mut st, side, ty);
8099            if tx_state.sent.len() >= runtime_reliable_max_pending() {
8100                return Err(TelemetryError::PacketTooLarge(
8101                    "router reliable history full",
8102                ));
8103            }
8104            let seq = tx_state.next_seq;
8105            let next = tx_state.next_seq.wrapping_add(1);
8106            tx_state.next_seq = if next == 0 { 1 } else { next };
8107            let flags = match reliable_mode(ty) {
8108                crate::ReliableMode::Unordered => wire_format::RELIABLE_FLAG_UNORDERED,
8109                _ => 0,
8110            };
8111            (seq, flags)
8112        };
8113
8114        let mut bytes: crate::SharedBytes = match data {
8115            RouterItem::Packet(pkt) => self.pack_packet_for_router(
8116                &pkt,
8117                Some(wire_format::ReliableHeader { flags, seq, ack: 0 }),
8118            )?,
8119            RouterItem::Packed(bytes) => {
8120                #[cfg(feature = "cryptography")]
8121                if self.e2e_seal_config_for_type(ty).is_some() {
8122                    self.prepare_packed_for_remote(
8123                        bytes,
8124                        Some(Some(wire_format::ReliableHeader { flags, seq, ack: 0 })),
8125                    )?
8126                } else {
8127                    let Some(rewritten) =
8128                        wire_format::rewrite_reliable_header_owned(bytes.as_ref(), flags, seq, 0)?
8129                    else {
8130                        let Some(_side_tx_guard) = self.try_enter_side_tx() else {
8131                            return Err(TelemetryError::Io("side tx busy"));
8132                        };
8133                        let started_ms = self.clock.now_ms();
8134                        let frames =
8135                            self.encode_side_transport_frames(side, opts, bytes.clone())?;
8136                        let mut attempts_total = 0usize;
8137                        let mut sent_bytes = 0usize;
8138                        self.send_side_frames_resumable(side, frames, |frame| {
8139                            match self.retry_with_attempts(runtime_max_handler_retries(), || {
8140                                Self::packed_handler_call(&handler, frame, priority)
8141                            }) {
8142                                Ok((_, attempts)) => {
8143                                    attempts_total = attempts_total.saturating_add(attempts);
8144                                    sent_bytes = sent_bytes.saturating_add(frame.len());
8145                                }
8146                                Err((err, attempts)) => {
8147                                    self.note_side_tx_failure(
8148                                        side,
8149                                        ty,
8150                                        attempts_total.saturating_add(attempts),
8151                                    );
8152                                    return Err(err);
8153                                }
8154                            }
8155
8156                            Ok(())
8157                        })?;
8158                        self.record_side_tx_sample(
8159                            side,
8160                            sent_bytes,
8161                            started_ms,
8162                            self.clock.now_ms(),
8163                        );
8164                        self.note_side_tx_success(side, ty, sent_bytes, relayed, attempts_total);
8165                        return Ok(());
8166                    };
8167                    crate::shared_bytes::convert(rewritten)
8168                }
8169                #[cfg(not(feature = "cryptography"))]
8170                {
8171                    let Some(rewritten) =
8172                        wire_format::rewrite_reliable_header_owned(bytes.as_ref(), flags, seq, 0)?
8173                    else {
8174                        let Some(_side_tx_guard) = self.try_enter_side_tx() else {
8175                            return Err(TelemetryError::Io("side tx busy"));
8176                        };
8177                        let started_ms = self.clock.now_ms();
8178                        let frames =
8179                            self.encode_side_transport_frames(side, opts, bytes.clone())?;
8180                        let mut attempts_total = 0usize;
8181                        let mut sent_bytes = 0usize;
8182                        self.send_side_frames_resumable(side, frames, |frame| {
8183                            match self.retry_with_attempts(runtime_max_handler_retries(), || {
8184                                Self::packed_handler_call(&handler, frame, priority)
8185                            }) {
8186                                Ok((_, attempts)) => {
8187                                    attempts_total = attempts_total.saturating_add(attempts);
8188                                    sent_bytes = sent_bytes.saturating_add(frame.len());
8189                                }
8190                                Err((err, attempts)) => {
8191                                    self.note_side_tx_failure(
8192                                        side,
8193                                        ty,
8194                                        attempts_total.saturating_add(attempts),
8195                                    );
8196                                    return Err(err);
8197                                }
8198                            }
8199
8200                            Ok(())
8201                        })?;
8202                        self.record_side_tx_sample(
8203                            side,
8204                            sent_bytes,
8205                            started_ms,
8206                            self.clock.now_ms(),
8207                        );
8208                        self.note_side_tx_success(side, ty, sent_bytes, relayed, attempts_total);
8209                        return Ok(());
8210                    };
8211                    rewritten.into()
8212                }
8213            }
8214        };
8215
8216        let Some(_side_tx_guard) = self.try_enter_side_tx() else {
8217            return Err(TelemetryError::Io("side tx busy"));
8218        };
8219        let started_ms = self.clock.now_ms();
8220        let frames = self.encode_side_transport_frames(side, opts, bytes.clone())?;
8221        let mut attempts_total = 0usize;
8222        let mut sent_bytes = 0usize;
8223        self.send_side_frames_resumable(side, frames, |frame| {
8224            match self.retry_with_attempts(runtime_max_handler_retries(), || {
8225                Self::packed_handler_call(&handler, frame, priority)
8226            }) {
8227                Ok((_, attempts)) => {
8228                    attempts_total = attempts_total.saturating_add(attempts);
8229                    sent_bytes = sent_bytes.saturating_add(frame.len());
8230                }
8231                Err((err, attempts)) => {
8232                    self.note_side_tx_failure(side, ty, attempts_total.saturating_add(attempts));
8233                    return Err(err);
8234                }
8235            }
8236
8237            Ok(())
8238        })?;
8239        self.record_side_tx_sample(side, sent_bytes, started_ms, self.clock.now_ms());
8240        self.note_side_tx_success(side, ty, sent_bytes, relayed, attempts_total);
8241
8242        #[cfg(feature = "compact-packet-store")]
8243        {
8244            let _ = bytes.park_for_queue();
8245        }
8246
8247        {
8248            let mut st = self.state.lock();
8249            let tx_state = self.reliable_tx_state_mut(&mut st, side, ty);
8250            tx_state.sent_order.push_back(seq);
8251            tx_state.sent.insert(
8252                seq,
8253                ReliableSent {
8254                    bytes: bytes.clone(),
8255                    last_send_ms: self.clock.now_ms(),
8256                    retries: 0,
8257                    queued: false,
8258                    partial_acked: false,
8259                },
8260            );
8261        }
8262
8263        Ok(())
8264    }
8265
8266    fn run_side_chunk_transfer<T>(
8267        &self,
8268        side: RouterSideId,
8269        key: u64,
8270        send: impl FnOnce(&mut usize) -> TelemetryResult<T>,
8271    ) -> TelemetryResult<T> {
8272        let mut next = {
8273            let mut st = self.state.lock();
8274            let transport = st
8275                .side_transport
8276                .get_mut(&side)
8277                .ok_or(TelemetryError::BadArg)?;
8278            if transport.tx_chunk_progress.is_none() {
8279                crate::memory_admission::check(
8280                    512,
8281                    core::mem::size_of::<crate::side_transport::SideTxProgress>(),
8282                )?;
8283                transport.tx_chunk_progress =
8284                    Some(Box::new(crate::side_transport::SideTxProgress::default()));
8285            }
8286            transport
8287                .tx_chunk_progress
8288                .as_mut()
8289                .expect("initialized")
8290                .begin(key, self.clock.now_ms())?
8291        };
8292        let result = send(&mut next);
8293        if let Some(progress) = self
8294            .state
8295            .lock()
8296            .side_transport
8297            .get_mut(&side)
8298            .and_then(|transport| transport.tx_chunk_progress.as_mut())
8299        {
8300            progress.finish(key, next, self.clock.now_ms(), &result);
8301        }
8302        result
8303    }
8304
8305    fn send_side_frames_resumable(
8306        &self,
8307        side: RouterSideId,
8308        mut frames: SideTransportFrames,
8309        mut send: impl FnMut(&[u8]) -> TelemetryResult<()>,
8310    ) -> TelemetryResult<()> {
8311        let bootstraps_template = frames.bootstraps_template();
8312        let result = if frames.len() == 1 {
8313            frames.send_resuming(&mut 0, send)
8314        } else {
8315            let key = frames.progress_key();
8316            self.run_side_chunk_transfer(side, key, |next| frames.send_resuming(next, &mut send))
8317        };
8318        if result.is_err() && bootstraps_template {
8319            // Encoding has already inserted the TX template, but a busy or
8320            // failed handler did not accept its bootstrap. Keep dictionary
8321            // eviction history and force a full refresh on the next attempt.
8322            // Never clear RX templates or allocate during this recovery.
8323            if let Some(state) = self.state.lock().side_transport.get_mut(&side) {
8324                state.refresh_transmit_header_templates();
8325            }
8326        }
8327        result
8328    }
8329
8330    fn split_side_transport_frame(
8331        &self,
8332        _side: RouterSideId,
8333        frame: crate::SharedBytes,
8334        max_frame_bytes: usize,
8335    ) -> TelemetryResult<SideTransportFrames> {
8336        SideTransportFrames::split(frame, max_frame_bytes, self.sender_arc().as_bytes())
8337    }
8338
8339    fn encode_side_transport_frames(
8340        &self,
8341        side: RouterSideId,
8342        opts: RouterSideOptions,
8343        raw: crate::SharedBytes,
8344    ) -> TelemetryResult<SideTransportFrames> {
8345        if !opts.header_template_enabled && opts.max_frame_bytes == 0 {
8346            return Ok(SideTransportFrames::single(raw));
8347        }
8348
8349        let raw_len = raw.len();
8350        if opts.max_frame_bytes != 0 && raw_len > opts.max_frame_bytes {
8351            // Canonical bytes keep a partially accepted transfer independent
8352            // of dictionary churn while other traffic is serviced.
8353            let frames = self.split_side_transport_frame(side, raw, opts.max_frame_bytes)?;
8354            let mut st = self.state.lock();
8355            let stats = st.side_runtime_stats.entry(side).or_default();
8356            stats.note_side_transport_full(raw_len, frames.wire_len());
8357            stats.note_side_transport_chunks(frames.len());
8358            return Ok(frames);
8359        }
8360
8361        if opts.header_template_enabled {
8362            // Shared buses cannot retain one coherent dictionary across
8363            // independent producers. Do not build owned templates or wrap
8364            // canonical bytes when no dictionary can be used.
8365            let frame = wire_format::peek_routing_frame_info(raw.as_ref())?;
8366            let without_dictionary = opts.max_side_transport_templates == 0 || {
8367                let st = self.state.lock();
8368                self.side_has_multiple_announcers_locked(&st, side, self.clock.now_ms())
8369            };
8370            let reliable = is_reliable_type(frame.envelope.ty)
8371                || frame.reliable.is_some()
8372                // Managed-variable delivery freezes destination owners even
8373                // when the application schema itself is best effort. Its
8374                // end-to-end receipt must not depend on a lossy dictionary
8375                // bootstrap, and transports must be able to prioritize it
8376                // independently of queued schema/template frames.
8377                || frame.envelope.target_senders().len() != 0
8378                || matches!(
8379                    frame.envelope.ty,
8380                    crate::DataType::ReliableAck
8381                        | crate::DataType::ReliablePartialAck
8382                        | crate::DataType::ReliablePacketRequest
8383                );
8384            if without_dictionary || reliable {
8385                let mut st = self.state.lock();
8386                if without_dictionary {
8387                    let removed = st
8388                        .side_transport
8389                        .get_mut(&side)
8390                        .ok_or(TelemetryError::BadArg)?
8391                        .trim_tx_templates_to(0);
8392                    for _ in 0..removed {
8393                        st.side_runtime_stats
8394                            .entry(side)
8395                            .or_default()
8396                            .note_side_transport_template_eviction();
8397                    }
8398                }
8399                st.side_runtime_stats
8400                    .entry(side)
8401                    .or_default()
8402                    .note_side_transport_full(raw_len, raw_len);
8403                return Ok(SideTransportFrames::single(raw));
8404            }
8405        }
8406
8407        #[cfg(any(feature = "std", feature = "fair-admission"))]
8408        if opts.header_template_enabled && crate::memory_admission::supports_work_kinds() {
8409            let (template, ..) = extract_side_header_template_view(raw.as_ref())?;
8410            let cached = self
8411                .state
8412                .lock()
8413                .side_transport
8414                .get(&side)
8415                .is_some_and(|transport| {
8416                    transport.tx_template_ids.contains_key(&template.hash)
8417                        && transport.tx_templates.contains_key(&template.hash)
8418                });
8419            if !cached
8420                && matches!(
8421                    crate::memory_admission::check_receive(raw.as_ref()),
8422                    Err(TelemetryError::Io("memory pressure"))
8423                )
8424            {
8425                // Compression is optional. Retain the canonical shared frame
8426                // instead of allocating a dictionary entry under pressure.
8427                // Oversized frames returned through the normal chunker above.
8428                self.state
8429                    .lock()
8430                    .side_runtime_stats
8431                    .entry(side)
8432                    .or_default()
8433                    .note_side_transport_full(raw_len, raw_len);
8434                return Ok(SideTransportFrames::single(raw));
8435            }
8436        }
8437
8438        let mut compact_payload_len = None;
8439        let mut used_compact = false;
8440        let used_timestamp_delta = false;
8441        let omitted_timestamp = false;
8442        let wrapped = if opts.header_template_enabled {
8443            let (template, _ty, flags, data_size, timestamp, nonce, reliable_seq_ack, payload) =
8444                extract_side_header_template_view(raw.as_ref())?;
8445            // Reliable and frozen-owner packets already returned above.
8446            // Classify once per encode rather than repeating a schema lookup.
8447            let (template_id, use_compact, _previous_timestamp) = {
8448                let mut st = self.state.lock();
8449                /* Keep the sender's compact dictionary within the smallest
8450                 * capacity advertised by a live peer on this side. Template
8451                 * IDs otherwise outlive entries on a smaller receiver and
8452                 * later compact values are silently undecodable. */
8453                let mut effective_template_limit = opts.max_side_transport_templates;
8454                #[cfg(feature = "discovery")]
8455                {
8456                    if let Some(route) = st.discovery_routes.get(&side) {
8457                        let active_peers = route
8458                            .announcers
8459                            .values()
8460                            .filter(|sender| {
8461                                self.clock.now_ms().saturating_sub(sender.last_seen_ms)
8462                                    <= DISCOVERY_ROUTE_TTL_MS
8463                            })
8464                            .count();
8465                        /* Compact template state is intentionally side-local.
8466                         * On a shared bus, independent producers cannot divide
8467                         * a receiver's bounded dictionary between themselves,
8468                         * so their compact frames can evict one another. Keep
8469                         * compression for point-to-point links and
8470                         * automatically use self-describing full frames once
8471                         * discovery proves a side is multi-producer. */
8472                        if active_peers > 1 {
8473                            effective_template_limit = 0;
8474                        } else {
8475                            effective_template_limit = route
8476                                .announcers
8477                                .values()
8478                                .filter_map(|sender| sender.link_capabilities)
8479                                .filter(|caps| {
8480                                    caps.flags & discovery::LINK_CAPABILITY_HEADER_TEMPLATES != 0
8481                                        && caps.max_side_transport_templates > 0
8482                                })
8483                                .map(|caps| caps.max_side_transport_templates as usize)
8484                                .fold(effective_template_limit, core::cmp::min);
8485                        }
8486                    }
8487                }
8488                let trimmed = st
8489                    .side_transport
8490                    .get_mut(&side)
8491                    .ok_or(TelemetryError::BadArg)?
8492                    .trim_tx_templates_to(effective_template_limit);
8493                for _ in 0..trimmed {
8494                    st.side_runtime_stats
8495                        .entry(side)
8496                        .or_default()
8497                        .note_side_transport_template_eviction();
8498                }
8499                let side_state = st
8500                    .side_transport
8501                    .get_mut(&side)
8502                    .ok_or(TelemetryError::BadArg)?;
8503                if let Some(id) = side_state.tx_template_ids.get(&template.hash).copied() {
8504                    let previous = side_state.tx_last_timestamps.get(&id).copied();
8505                    /* A full refresh makes compact transport self-healing when
8506                     * the receiver missed the original template frame. */
8507                    const FULL_REFRESH_AFTER_COMPACT_FRAMES: u8 = 8;
8508                    let compact_uses = side_state.tx_compact_uses.entry(id).or_default();
8509                    // Discovery must bootstrap even when a receiver lost its dictionary.
8510                    #[cfg(feature = "discovery")]
8511                    let discovery_refresh = discovery::is_discovery_type(_ty);
8512                    #[cfg(not(feature = "discovery"))]
8513                    let discovery_refresh = false;
8514                    let use_compact =
8515                        *compact_uses < FULL_REFRESH_AFTER_COMPACT_FRAMES && !discovery_refresh;
8516                    if use_compact {
8517                        *compact_uses = compact_uses.saturating_add(1);
8518                    } else {
8519                        *compact_uses = 0;
8520                    }
8521                    (id, use_compact, previous)
8522                } else {
8523                    /* Sequential template IDs collide when multiple routers
8524                     * transmit on one physical bus. The template hash covers
8525                     * the source address and immutable header fields, making
8526                     * this ID producer-stable without adding bytes to compact
8527                     * frames. */
8528                    let template_id = ((template.hash >> 32) as u32 ^ template.hash as u32).max(1);
8529                    let evicted = side_state.insert_tx_template(
8530                        template.into_owned(),
8531                        template_id,
8532                        effective_template_limit,
8533                    );
8534                    if evicted {
8535                        st.side_runtime_stats
8536                            .entry(side)
8537                            .or_default()
8538                            .note_side_transport_template_eviction();
8539                    }
8540                    if let Some(side_state) = st.side_transport.get_mut(&side)
8541                        && effective_template_limit > 0
8542                    {
8543                        side_state.tx_last_timestamps.insert(template_id, timestamp);
8544                    }
8545                    (template_id, false, None)
8546                }
8547            };
8548            if use_compact {
8549                used_compact = true;
8550                compact_payload_len = Some(payload.len());
8551                // A previous transmitted frame is not a confirmed receive base.
8552                // Absolute timestamps preserve identity after loss or reordering.
8553                let timestamp_field = Some(timestamp);
8554                let mut body = Vec::with_capacity(payload.len() + 32);
8555                body.push(flags);
8556                write_uleb128_local(u64::from(template_id), &mut body);
8557                write_uleb128_local(data_size, &mut body);
8558                if let Some(timestamp_field) = timestamp_field {
8559                    write_uleb128_local(timestamp_field, &mut body);
8560                }
8561                if (flags & SIDE_TRANSPORT_FLAG_PACKET_NONCE) != 0 {
8562                    write_uleb128_local(u64::from(nonce), &mut body);
8563                }
8564                if let Some((seq, ack)) = reliable_seq_ack {
8565                    write_uleb128_local(u64::from(seq), &mut body);
8566                    write_uleb128_local(u64::from(ack), &mut body);
8567                }
8568                body.extend_from_slice(payload);
8569                {
8570                    let mut st = self.state.lock();
8571                    if let Some(side_state) = st.side_transport.get_mut(&side) {
8572                        side_state.tx_last_timestamps.insert(template_id, timestamp);
8573                    }
8574                }
8575                let kind = if omitted_timestamp {
8576                    SIDE_TRANSPORT_KIND_COMPACT_SAME_TIMESTAMP
8577                } else if used_timestamp_delta {
8578                    SIDE_TRANSPORT_KIND_COMPACT_DELTA
8579                } else {
8580                    SIDE_TRANSPORT_KIND_COMPACT
8581                };
8582                wrap_side_transport_frame(kind, &body)
8583            } else {
8584                let mut body = Vec::with_capacity(raw.len() + 4);
8585                write_uleb128_local(u64::from(template_id), &mut body);
8586                body.extend_from_slice(raw.as_ref());
8587                wrap_side_transport_frame(SIDE_TRANSPORT_KIND_FULL, &body)
8588            }
8589        } else {
8590            wrap_side_transport_frame(SIDE_TRANSPORT_KIND_FULL, raw.as_ref())
8591        };
8592
8593        let frames = if opts.max_frame_bytes != 0 && wrapped.len() > opts.max_frame_bytes {
8594            self.split_side_transport_frame(side, wrapped, opts.max_frame_bytes)
8595        } else {
8596            Ok(SideTransportFrames::single(wrapped))
8597        }?;
8598        let wire_len = frames.wire_len();
8599        let mut st = self.state.lock();
8600        let stats = st.side_runtime_stats.entry(side).or_default();
8601        if used_compact {
8602            let overhead = compact_payload_len
8603                .map(|payload_len| wire_len.saturating_sub(payload_len))
8604                .unwrap_or(wire_len);
8605            stats.note_side_transport_compact(
8606                raw_len,
8607                wire_len,
8608                overhead,
8609                used_timestamp_delta,
8610                omitted_timestamp,
8611            );
8612            if opts.compact_header_target_bytes != 0 && overhead > opts.compact_header_target_bytes
8613            {
8614                stats.note_side_transport_compact_target_miss();
8615            }
8616        } else {
8617            stats.note_side_transport_full(raw_len, wire_len);
8618        }
8619        if frames.len() > 1 {
8620            stats.note_side_transport_chunks(frames.len());
8621        }
8622        Ok(frames)
8623    }
8624
8625    fn expire_side_chunks(&self) {
8626        let now = self.clock.now_ms();
8627        let mut st = self.state.lock();
8628        for side in st.side_transport.values_mut() {
8629            side.rx_chunks
8630                .retain(|_, assembly| now.saturating_sub(assembly.last_seen_ms) <= 2000);
8631        }
8632    }
8633
8634    // A validated frame from a previously discovered direct peer proves that
8635    // its link is alive, even when allocation admission refuses its payload.
8636    // Update existing timestamps only: no allocation, schema merge or new route.
8637    #[cfg(feature = "discovery")]
8638    fn refresh_ingress_peer_liveness(&self, side: RouterSideId, bytes: &[u8]) {
8639        let Ok(frame) = wire_format::peek_routing_frame_info(bytes) else {
8640            return;
8641        };
8642        let address = frame.envelope.source_address;
8643        self.refresh_peer_liveness(side, address);
8644    }
8645
8646    #[cfg(feature = "discovery")]
8647    #[cfg_attr(not(feature = "std"), inline(never))]
8648    fn refresh_peer_liveness(&self, side: RouterSideId, address: u32) {
8649        let now = self.clock.now_ms();
8650        let mut st = self.state.lock();
8651        let Some(route) = st.discovery_routes.get_mut(&side) else {
8652            return;
8653        };
8654        let mut found = None;
8655        for (name, peer) in &mut route.announcers {
8656            if peer.advertised_address == Some(address) || sender_address_u32(name) == address {
8657                // Ambiguous numeric identities must be resolved by discovery.
8658                if found.is_some() {
8659                    return;
8660                }
8661                found = Some(peer);
8662            }
8663        }
8664        let Some(peer) = found else {
8665            return;
8666        };
8667        peer.last_seen_ms = now;
8668        route.last_seen_ms = route.last_seen_ms.max(now);
8669    }
8670
8671    #[cfg(all(feature = "discovery", not(feature = "std")))]
8672    fn consume_known_packed_schema(
8673        &self,
8674        side: RouterSideId,
8675        bytes: &[u8],
8676    ) -> TelemetryResult<bool> {
8677        let Some((address, payload)) = wire_format::peek_plain_schema_payload(bytes)? else {
8678            return Ok(false);
8679        };
8680        if !discovery::discovery_schema_payload_is_fully_known(payload)? {
8681            return Ok(false);
8682        }
8683        // Discovery schemas are always sent as full refreshes. No compact
8684        // dictionary entry or second owned payload is needed for this no-op.
8685        let mut st = self.state.lock();
8686        if let Some(route) = st.discovery_routes.get_mut(&side) {
8687            for (name, peer) in &mut route.announcers {
8688                if peer.advertised_address == Some(address) || sender_address_u32(name) == address {
8689                    peer.has_schema = true;
8690                    break;
8691                }
8692            }
8693        }
8694        Ok(true)
8695    }
8696
8697    // A two-sided, fully warmed packed bridge needs no new large metadata.
8698    // Re-check before dispatch: schemas/templates/routes can change while queued.
8699    #[cfg(feature = "discovery")]
8700    fn cached_transit_ready(&self, side: RouterSideId, bytes: &[u8]) -> TelemetryResult<bool> {
8701        if bytes.len() > 128 || bytes.first().is_none_or(|flags| flags & 0x15 != 0) {
8702            return Ok(false);
8703        }
8704        let frame = wire_format::peek_routing_frame_info(bytes)?;
8705        let ty = frame.envelope.ty;
8706        if is_internal_control_type(ty)
8707            || frame.envelope.is_reliable
8708            || frame.reliable.is_some()
8709            || frame.envelope.target_senders().len() != 0
8710            || !(1..=3).contains(&frame.envelope.endpoints().take(4).count())
8711        {
8712            return Ok(false);
8713        }
8714        if self.is_managed_variable_type(ty)
8715            || frame.envelope.endpoints().any(|ep| {
8716                self.endpoint_has_packet_handler(ep) || self.endpoint_has_packed_handler(ep)
8717            })
8718        {
8719            return Ok(false);
8720        }
8721
8722        let (template, ..) = extract_side_header_template_view(bytes)?;
8723        let st = self.state.lock();
8724        let active: usize = st.sides.iter().filter(|entry| entry.is_some()).count();
8725        if active != 2 {
8726            return Ok(false);
8727        }
8728        for (id, entry) in st
8729            .sides
8730            .iter()
8731            .enumerate()
8732            .filter_map(|(id, entry)| entry.as_ref().map(|e| (id, e)))
8733        {
8734            if entry.opts.reliable_enabled
8735                || !matches!(
8736                    entry.tx_handler,
8737                    RouterTxHandlerFn::Packed(_) | RouterTxHandlerFn::PackedWithPriority(_)
8738                )
8739                || !st
8740                    .side_runtime_stats
8741                    .get(&id)
8742                    .is_some_and(|stats| stats.data_types.contains_key(&ty.as_u32()))
8743            {
8744                return Ok(false);
8745            }
8746            if id == side {
8747                continue;
8748            }
8749            #[cfg(feature = "discovery")]
8750            if !st.discovery_routes.get(&id).is_some_and(|route| {
8751                self.clock.now_ms().saturating_sub(route.last_seen_ms) <= DISCOVERY_ROUTE_TTL_MS
8752                    && frame
8753                        .envelope
8754                        .endpoints()
8755                        .any(|ep| route.reachable.contains(&ep))
8756            }) {
8757                return Ok(false);
8758            }
8759            if entry.opts.header_template_enabled
8760                && entry.opts.max_frame_bytes != 0
8761                && entry.opts.max_side_transport_templates != 0
8762                && !self.side_has_multiple_announcers_locked(&st, id, self.clock.now_ms())
8763                && !st.side_transport.get(&id).is_some_and(|transport| {
8764                    transport.tx_template_ids.contains_key(&template.hash)
8765                        && transport.tx_templates.contains_key(&template.hash)
8766                })
8767            {
8768                // An optional compression entry may have been evicted while
8769                // routing/statistics remain warm. Canonical forwarding needs
8770                // no replacement entry, but must fit the transport and belong
8771                // to an already discovered ingress peer.
8772                #[cfg(any(feature = "std", feature = "fair-admission"))]
8773                if (entry.opts.max_frame_bytes == 0 || bytes.len() <= entry.opts.max_frame_bytes)
8774                    && st.discovery_routes.get(&side).is_some_and(|route| {
8775                        route.announcers.iter().any(|(name, peer)| {
8776                            peer.advertised_address == Some(frame.envelope.source_address)
8777                                || sender_address_u32(name) == frame.envelope.source_address
8778                        })
8779                    })
8780                {
8781                    continue;
8782                }
8783                return Ok(false);
8784            }
8785        }
8786        Ok(true)
8787    }
8788    #[cfg(not(feature = "discovery"))]
8789    fn cached_transit_ready(&self, _side: RouterSideId, _bytes: &[u8]) -> TelemetryResult<bool> {
8790        Ok(false)
8791    }
8792    #[cfg(any(feature = "std", feature = "fair-admission"))]
8793    fn reclaim_for_receive(&self, bytes: &[u8], victim: &mut Option<(u32, u8)>) -> bool {
8794        let Some((incoming, priority)) = crate::admission_fairness::incoming(bytes) else {
8795            return false;
8796        };
8797        self.reclaim_for_incoming(incoming, priority, victim)
8798    }
8799    #[cfg(any(feature = "std", feature = "fair-admission"))]
8800    fn reclaim_for_incoming(
8801        &self,
8802        incoming: u32,
8803        priority: u8,
8804        victim: &mut Option<(u32, u8)>,
8805    ) -> bool {
8806        let mut st = self.state.lock();
8807        let (owner, priority) = if let Some(selected) = *victim {
8808            selected
8809        } else {
8810            let Some(owner) = st.competing_bulk_owner(incoming, priority) else {
8811                return false;
8812            };
8813            *victim = Some((owner, priority));
8814            (owner, priority)
8815        };
8816        st.reclaim_bulk_from(owner, priority, None)
8817    }
8818    fn admit_received_frame(&self, side: RouterSideId, bytes: &[u8]) -> TelemetryResult<()> {
8819        if !crate::memory_admission::has_probe() {
8820            return Ok(());
8821        }
8822        #[cfg(any(feature = "std", feature = "fair-admission"))]
8823        let mut fair_victim = None;
8824        if crate::memory_admission::supports_work_kinds()
8825            && self.cached_transit_ready(side, bytes)?
8826        {
8827            let result = crate::memory_admission::check_cached_transit(bytes.len());
8828            #[cfg(any(feature = "std", feature = "fair-admission"))]
8829            let result = {
8830                let mut result = result;
8831                while matches!(result, Err(TelemetryError::Io("memory pressure"))) {
8832                    if !self.reclaim_for_receive(bytes, &mut fair_victim) {
8833                        break;
8834                    }
8835                    result = crate::memory_admission::check_cached_transit(bytes.len());
8836                }
8837                result
8838            };
8839            result
8840        } else {
8841            let managed = wire_format::scheduling_type(bytes)
8842                .is_some_and(|ty| self.is_managed_variable_type(ty));
8843            let mut protected = None;
8844            loop {
8845                match crate::memory_admission::check_receive_with_role(bytes, managed) {
8846                    Err(TelemetryError::Io("memory pressure")) => {
8847                        let protected = *protected.get_or_insert_with(|| {
8848                            wire_format::peek_routing_frame_info(bytes)
8849                                .ok()
8850                                .is_some_and(|frame| {
8851                                    managed || message_priority(frame.envelope.ty) >= 200
8852                                })
8853                        });
8854                        if !protected {
8855                            #[cfg(any(feature = "std", feature = "fair-admission"))]
8856                            if self.reclaim_for_receive(bytes, &mut fair_victim) {
8857                                continue;
8858                            }
8859                            return Err(TelemetryError::Io("memory pressure"));
8860                        }
8861                        // Queue priority cannot help before the wire copy is
8862                        // admitted. Reclaim bulk payloads, preserving control
8863                        // traffic and every learned route/schema.
8864                        let mut st = self.state.lock();
8865                        let reclaimed = st
8866                            .lower_priority_shared_queue(199)
8867                            .is_some_and(|queue| st.pop_shared_queue_item(queue));
8868                        if !reclaimed {
8869                            return Err(TelemetryError::Io("memory pressure"));
8870                        }
8871                    }
8872                    result => return result,
8873                }
8874            }
8875        }
8876    }
8877
8878    fn decode_side_transport_frame(
8879        &self,
8880        side: RouterSideId,
8881        bytes: &[u8],
8882    ) -> TelemetryResult<Option<crate::SharedBytes>> {
8883        // Reclaim abandoned transfers before admission, even when new work is refused.
8884        self.expire_side_chunks();
8885        let Some((kind, body)) = parse_side_transport_wrapper(bytes)? else {
8886            #[cfg(feature = "discovery")]
8887            self.refresh_ingress_peer_liveness(side, bytes);
8888            #[cfg(all(feature = "discovery", not(feature = "std")))]
8889            if self.consume_known_packed_schema(side, bytes)? {
8890                return Ok(None);
8891            }
8892            self.admit_received_frame(side, bytes)?;
8893            return Ok(Some(crate::shared_bytes::convert(Arc::<[u8]>::from(bytes))));
8894        };
8895        match kind {
8896            SIDE_TRANSPORT_KIND_FULL => {
8897                let header_templates_enabled = self
8898                    .state
8899                    .lock()
8900                    .sides
8901                    .get(side)
8902                    .and_then(|entry| entry.as_ref())
8903                    .map(|side| side.opts.header_template_enabled)
8904                    .unwrap_or(false);
8905                let mut off = 0usize;
8906                let template_id = if header_templates_enabled {
8907                    Some(
8908                        u32::try_from(read_uleb128_local(body, &mut off)?)
8909                            .map_err(|_| TelemetryError::Unpack("side template id too large"))?,
8910                    )
8911                } else {
8912                    None
8913                };
8914                #[cfg(feature = "discovery")]
8915                self.refresh_ingress_peer_liveness(side, &body[off..]);
8916                #[cfg(all(feature = "discovery", not(feature = "std")))]
8917                if self.consume_known_packed_schema(side, &body[off..])? {
8918                    return Ok(None);
8919                }
8920                self.admit_received_frame(side, &body[off..])?;
8921                let raw = Arc::<[u8]>::from(&body[off..]);
8922                if let (Some(template_id), Ok((template, _, _, _, timestamp, _, _, _))) =
8923                    (template_id, extract_side_header_template(raw.as_ref()))
8924                {
8925                    let mut st = self.state.lock();
8926                    let max_templates = st
8927                        .sides
8928                        .get(side)
8929                        .and_then(|side| side.as_ref())
8930                        .map(|side| side.opts.max_side_transport_templates)
8931                        .unwrap_or(DEFAULT_SIDE_TRANSPORT_TEMPLATE_LIMIT);
8932                    let evicted = st.side_transport.get_mut(&side).is_some_and(|side_state| {
8933                        let evicted =
8934                            side_state.insert_rx_template(template_id, template, max_templates);
8935                        if max_templates > 0 {
8936                            side_state.rx_last_timestamps.insert(template_id, timestamp);
8937                        }
8938                        evicted
8939                    });
8940                    if evicted {
8941                        st.side_runtime_stats
8942                            .entry(side)
8943                            .or_default()
8944                            .note_side_transport_template_eviction();
8945                    }
8946                }
8947                Ok(Some(crate::shared_bytes::convert(raw)))
8948            }
8949            SIDE_TRANSPORT_KIND_COMPACT
8950            | SIDE_TRANSPORT_KIND_COMPACT_DELTA
8951            | SIDE_TRANSPORT_KIND_COMPACT_SAME_TIMESTAMP => {
8952                if body.is_empty() {
8953                    return Err(TelemetryError::Unpack("short side compact frame"));
8954                }
8955                let mut off = 1usize;
8956                let template_id = u32::try_from(read_uleb128_local(body, &mut off)?)
8957                    .map_err(|_| TelemetryError::Unpack("side template id too large"))?;
8958                let (template, timestamp_base) = {
8959                    let st = self.state.lock();
8960                    let state = st.side_transport.get(&side);
8961                    let template = state
8962                        .and_then(|state| state.rx_templates_by_id.get(&template_id))
8963                        .cloned();
8964                    let timestamp_base = if matches!(
8965                        kind,
8966                        SIDE_TRANSPORT_KIND_COMPACT_DELTA
8967                            | SIDE_TRANSPORT_KIND_COMPACT_SAME_TIMESTAMP
8968                    ) {
8969                        state
8970                            .and_then(|state| state.rx_last_timestamps.get(&template_id))
8971                            .copied()
8972                    } else {
8973                        None
8974                    };
8975                    (template, timestamp_base)
8976                };
8977                /* A full template frame can be lost on a datagram/radio side.
8978                 * Drop this compact frame without tearing down the transport;
8979                 * the sender periodically emits a full refresh. */
8980                let Some(template) = template else {
8981                    return Ok(None);
8982                };
8983                // The wrapper CRC and known template identify this peer even
8984                // if payload allocation is refused. No payload copy is needed.
8985                #[cfg(feature = "discovery")]
8986                if let Some(address) = template.source_address() {
8987                    self.refresh_peer_liveness(side, address);
8988                }
8989                let reconstructed_bound = body
8990                    .len()
8991                    .saturating_add(template.prefix.len())
8992                    .saturating_add(template.between.len())
8993                    .saturating_add(32);
8994                // Wrapper CRC and the retained full template are validated
8995                // before compact reconstruction. Preserve the same control
8996                // priority as canonical ingress while reclaiming only bulk.
8997                let protected = template.data_type().is_some_and(|ty| {
8998                    message_priority(ty) >= 200 || self.is_managed_variable_type(ty)
8999                });
9000                let ack = template.is_ack();
9001                #[cfg(any(feature = "std", feature = "fair-admission"))]
9002                let mut fair_incoming = None;
9003                #[cfg(any(feature = "std", feature = "fair-admission"))]
9004                let mut fair_victim = None;
9005                loop {
9006                    match crate::memory_admission::check_frame(reconstructed_bound, ack) {
9007                        Err(TelemetryError::Io("memory pressure")) if protected => {
9008                            let mut st = self.state.lock();
9009                            let reclaimed = st
9010                                .lower_priority_shared_queue(199)
9011                                .is_some_and(|queue| st.pop_shared_queue_item(queue));
9012                            if !reclaimed {
9013                                return Err(TelemetryError::Io("memory pressure"));
9014                            }
9015                        }
9016                        Err(TelemetryError::Io("memory pressure")) => {
9017                            // Compact reports need the same fairness before
9018                            // allocation as canonical ingress. Otherwise they
9019                            // never reach the canonical admission/reclaim path.
9020                            #[cfg(any(feature = "std", feature = "fair-admission"))]
9021                            if let Some((incoming, priority)) =
9022                                *fair_incoming.get_or_insert_with(|| {
9023                                    crate::admission_fairness::compact_incoming(&template)
9024                                })
9025                                && self.reclaim_for_incoming(incoming, priority, &mut fair_victim)
9026                            {
9027                                continue;
9028                            }
9029                            return Err(TelemetryError::Io("memory pressure"));
9030                        }
9031                        result => {
9032                            result?;
9033                            break;
9034                        }
9035                    }
9036                }
9037                let timestamp_mode = match kind {
9038                    SIDE_TRANSPORT_KIND_COMPACT_DELTA => SideCompactTimestampMode::Delta,
9039                    SIDE_TRANSPORT_KIND_COMPACT_SAME_TIMESTAMP => SideCompactTimestampMode::Omitted,
9040                    _ => SideCompactTimestampMode::Absolute,
9041                };
9042                let (frame, timestamp) = reconstruct_side_compact_frame(
9043                    &template,
9044                    body[0],
9045                    &body[off..],
9046                    timestamp_mode,
9047                    timestamp_base,
9048                )?;
9049                let mut st = self.state.lock();
9050                if let Some(side_state) = st.side_transport.get_mut(&side) {
9051                    side_state.rx_last_timestamps.insert(template_id, timestamp);
9052                }
9053                Ok(Some(frame))
9054            }
9055            SIDE_TRANSPORT_KIND_CHUNK => {
9056                crate::memory_admission::check_receive(bytes)?;
9057                if body.len() < 8 {
9058                    return Err(TelemetryError::Unpack("short side chunk frame"));
9059                }
9060                let transfer_id = u32::from_le_bytes([body[0], body[1], body[2], body[3]]);
9061                let index = u16::from_le_bytes([body[4], body[5]]);
9062                let total = u16::from_le_bytes([body[6], body[7]]);
9063                if total == 0 || index >= total {
9064                    return Err(TelemetryError::Unpack("invalid side chunk index"));
9065                }
9066                let assembled = {
9067                    let mut st = self.state.lock();
9068                    let budget = st.memory.max_queue_budget;
9069                    let now_ms = self.clock.now_ms();
9070                    let side_state = st
9071                        .side_transport
9072                        .get_mut(&side)
9073                        .ok_or(TelemetryError::BadArg)?;
9074                    side_state
9075                        .rx_chunks
9076                        .retain(|_, assembly| now_ms.saturating_sub(assembly.last_seen_ms) <= 2000);
9077                    if !side_state.rx_chunks.contains_key(&transfer_id)
9078                        && side_state.rx_chunks.len() >= 4
9079                        && let Some(oldest) = side_state
9080                            .rx_chunks
9081                            .iter()
9082                            .min_by_key(|(_, a)| a.last_seen_ms)
9083                            .map(|(id, _)| *id)
9084                    {
9085                        side_state.rx_chunks.remove(&oldest);
9086                    }
9087                    SideChunkAssembly::make_room(
9088                        &mut side_state.rx_chunks,
9089                        transfer_id,
9090                        index,
9091                        total,
9092                        body.len() - 8,
9093                        budget,
9094                    )?;
9095                    let entry = side_state.rx_chunks.entry(transfer_id).or_default();
9096                    entry.last_seen_ms = now_ms;
9097                    if entry.total == 0 {
9098                        entry.total = total;
9099                    } else if entry.total != total {
9100                        side_state.rx_chunks.remove(&transfer_id);
9101                        return Err(TelemetryError::Unpack("side chunk total mismatch"));
9102                    }
9103                    entry.received.entry(index).or_insert_with(|| {
9104                        crate::shared_bytes::convert(Arc::<[u8]>::from(&body[8..]))
9105                    });
9106                    if entry.received.len() == usize::from(total) {
9107                        let entry = side_state
9108                            .rx_chunks
9109                            .remove(&transfer_id)
9110                            .ok_or(TelemetryError::Unpack("side chunk missing"))?;
9111                        Some(entry.assemble()?)
9112                    } else {
9113                        None
9114                    }
9115                };
9116                match assembled {
9117                    Some(frame) => self.decode_side_transport_frame(side, frame.as_ref()),
9118                    None => Ok(None),
9119                }
9120            }
9121            _ => Err(TelemetryError::Unpack("unknown side transport frame")),
9122        }
9123    }
9124
9125    fn packed_handler_call(
9126        handler: &RouterTxHandlerFn,
9127        bytes: &[u8],
9128        priority: u8,
9129    ) -> TelemetryResult<()> {
9130        match handler {
9131            RouterTxHandlerFn::Packed(handler) => handler(bytes),
9132            RouterTxHandlerFn::PackedWithPriority(handler) => handler(bytes, priority),
9133            RouterTxHandlerFn::Packet(_) => Err(TelemetryError::BadArg),
9134        }
9135    }
9136
9137    fn effective_transport_priority(&self, ty: DataType) -> u8 {
9138        if self.is_managed_variable_type(ty) {
9139            crate::transport_priority(DataType::ManagedVariableValue)
9140        } else {
9141            crate::transport_priority(ty)
9142        }
9143    }
9144
9145    fn call_side_tx_handler(
9146        &self,
9147        side: RouterSideId,
9148        handler: &RouterTxHandlerFn,
9149        data: &RouterItem,
9150        relayed: bool,
9151    ) -> TelemetryResult<()> {
9152        let opts = {
9153            let st = self.state.lock();
9154            Self::side_ref(&st, side)?.opts.clone()
9155        };
9156        let Some(_side_tx_guard) = self.try_enter_side_tx() else {
9157            return Err(TelemetryError::Io("side tx busy"));
9158        };
9159        let started_ms = self.clock.now_ms();
9160        let ty = match data {
9161            RouterItem::Packet(pkt) => pkt.data_type(),
9162            RouterItem::Packed(bytes) => wire_format::peek_routing_envelope(bytes.as_ref())?.ty,
9163        };
9164        let priority = self.item_service_priority(data)?;
9165        let result = match (handler, data) {
9166            (
9167                packed @ (RouterTxHandlerFn::Packed(_) | RouterTxHandlerFn::PackedWithPriority(_)),
9168                RouterItem::Packed(bytes),
9169            ) => {
9170                #[cfg(feature = "cryptography")]
9171                let send_bytes = self.prepare_packed_for_remote(bytes.clone(), None)?;
9172                #[cfg(not(feature = "cryptography"))]
9173                let send_bytes = bytes.clone();
9174                let frames = self.encode_side_transport_frames(side, opts, send_bytes)?;
9175                let mut attempts_total = 0usize;
9176                let mut sent_bytes = 0usize;
9177                self.send_side_frames_resumable(side, frames, |frame| {
9178                    match self.retry_with_attempts(runtime_max_handler_retries(), || {
9179                        Self::packed_handler_call(packed, frame, priority)
9180                    }) {
9181                        Ok((_, attempts)) => {
9182                            attempts_total = attempts_total.saturating_add(attempts);
9183                            sent_bytes = sent_bytes.saturating_add(frame.len());
9184                        }
9185                        Err((err, attempts)) => {
9186                            self.note_side_tx_failure(
9187                                side,
9188                                ty,
9189                                attempts_total.saturating_add(attempts),
9190                            );
9191                            return Err(err);
9192                        }
9193                    }
9194
9195                    Ok(())
9196                })?;
9197                self.record_side_tx_sample(side, sent_bytes, started_ms, self.clock.now_ms());
9198                self.note_side_tx_success(side, ty, sent_bytes, relayed, attempts_total);
9199                return Ok(());
9200            }
9201            (RouterTxHandlerFn::Packet(f), RouterItem::Packet(pkt)) => {
9202                self.retry_with_attempts(runtime_max_handler_retries(), || f(pkt))
9203            }
9204            (
9205                packed @ (RouterTxHandlerFn::Packed(_) | RouterTxHandlerFn::PackedWithPriority(_)),
9206                RouterItem::Packet(pkt),
9207            ) => {
9208                // Internal schemas are plaintext and use self-describing frames.
9209                // This path is reached after hop-reliable handling; no replay
9210                // buffer is needed here. Stream fragments from the payload.
9211                if ty == DataType::DiscoverySchema
9212                    && opts.max_frame_bytes != 0
9213                    && pkt.payload().len() > opts.max_frame_bytes
9214                {
9215                    let mut attempts_total = 0usize;
9216                    let mut sent_bytes = 0usize;
9217                    let key = crate::packet::hash_bytes_u64(
9218                        pkt.packet_id(),
9219                        &opts.max_frame_bytes.to_le_bytes(),
9220                    );
9221                    let result = self.run_side_chunk_transfer(side, key, |next| {
9222                        crate::side_transport::send_packet_chunks(
9223                            pkt,
9224                            opts.max_frame_bytes,
9225                            self.sender_arc().as_bytes(),
9226                            next,
9227                            |frame| match self
9228                                .retry_with_attempts(runtime_max_handler_retries(), || {
9229                                    Self::packed_handler_call(packed, frame, priority)
9230                                }) {
9231                                Ok((_, attempts)) => {
9232                                    attempts_total += attempts;
9233                                    sent_bytes += frame.len();
9234                                    Ok(())
9235                                }
9236                                Err((err, attempts)) => {
9237                                    attempts_total += attempts;
9238                                    Err(err)
9239                                }
9240                            },
9241                        )
9242                    });
9243                    let (raw_len, wire_len, chunks) = match result {
9244                        Ok(stats) => stats,
9245                        Err(err) => {
9246                            self.note_side_tx_failure(side, ty, attempts_total);
9247                            return Err(err);
9248                        }
9249                    };
9250                    {
9251                        let mut st = self.state.lock();
9252                        let stats = st.side_runtime_stats.entry(side).or_default();
9253                        stats.note_side_transport_full(raw_len, wire_len);
9254                        if chunks > 1 {
9255                            stats.note_side_transport_chunks(chunks);
9256                        }
9257                    }
9258                    self.record_side_tx_sample(side, sent_bytes, started_ms, self.clock.now_ms());
9259                    self.note_side_tx_success(side, ty, sent_bytes, relayed, attempts_total);
9260                    return Ok(());
9261                }
9262                let owned = self.pack_packet_for_router(pkt, None)?;
9263                let frames = self.encode_side_transport_frames(side, opts, owned)?;
9264                let mut attempts_total = 0usize;
9265                let mut sent_bytes = 0usize;
9266                self.send_side_frames_resumable(side, frames, |frame| {
9267                    match self.retry_with_attempts(runtime_max_handler_retries(), || {
9268                        Self::packed_handler_call(packed, frame, priority)
9269                    }) {
9270                        Ok((_, attempts)) => {
9271                            attempts_total = attempts_total.saturating_add(attempts);
9272                            sent_bytes = sent_bytes.saturating_add(frame.len());
9273                        }
9274                        Err((err, attempts)) => {
9275                            self.note_side_tx_failure(
9276                                side,
9277                                ty,
9278                                attempts_total.saturating_add(attempts),
9279                            );
9280                            return Err(err);
9281                        }
9282                    }
9283
9284                    Ok(())
9285                })?;
9286                self.record_side_tx_sample(side, sent_bytes, started_ms, self.clock.now_ms());
9287                self.note_side_tx_success(side, ty, sent_bytes, relayed, attempts_total);
9288                return Ok(());
9289            }
9290            (RouterTxHandlerFn::Packet(f), RouterItem::Packed(bytes)) => {
9291                let pkt = self.unpack_packet_for_role(bytes.as_ref())?;
9292                self.retry_with_attempts(runtime_max_handler_retries(), || f(&pkt))
9293            }
9294        };
9295        match result {
9296            Ok((_, attempts)) => {
9297                if let Ok(bytes) = Self::router_item_wire_len(data) {
9298                    self.record_side_tx_sample(side, bytes, started_ms, self.clock.now_ms());
9299                    self.note_side_tx_success(side, ty, bytes, relayed, attempts);
9300                }
9301                Ok(())
9302            }
9303            Err((err, attempts)) => {
9304                self.note_side_tx_failure(side, ty, attempts);
9305                Err(err)
9306            }
9307        }
9308    }
9309
9310    fn adjust_reliable_for_side(
9311        &self,
9312        opts: RouterSideOptions,
9313        data: RouterItem,
9314        preserve_end_to_end_ack: bool,
9315    ) -> TelemetryResult<Option<RouterItem>> {
9316        if opts.reliable_enabled {
9317            return Ok(Some(data));
9318        }
9319
9320        match data {
9321            RouterItem::Packed(bytes) => {
9322                let frame = wire_format::peek_routing_frame_info(bytes.as_ref())?;
9323                if is_reliable_type(frame.envelope.ty)
9324                    && let Some(hdr) = frame.reliable
9325                {
9326                    if (hdr.flags & wire_format::RELIABLE_FLAG_ACK_ONLY) != 0 {
9327                        return Ok(None);
9328                    }
9329                    if (hdr.flags & wire_format::RELIABLE_FLAG_UNSEQUENCED) == 0 {
9330                        let Some(rewritten) = wire_format::rewrite_reliable_header_owned(
9331                            bytes.as_ref(),
9332                            wire_format::RELIABLE_FLAG_UNSEQUENCED,
9333                            hdr.seq,
9334                            0,
9335                        )?
9336                        else {
9337                            return Ok(Some(RouterItem::Packed(bytes)));
9338                        };
9339                        return Ok(Some(RouterItem::Packed(crate::shared_bytes::convert(
9340                            rewritten,
9341                        ))));
9342                    }
9343                }
9344                Ok(Some(RouterItem::Packed(bytes)))
9345            }
9346            RouterItem::Packet(pkt) => {
9347                if matches!(
9348                    pkt.data_type(),
9349                    DataType::ReliableAck
9350                        | DataType::ReliablePartialAck
9351                        | DataType::ReliablePacketRequest
9352                ) {
9353                    if preserve_end_to_end_ack && Self::is_end_to_end_ack_packet(&pkt) {
9354                        return Ok(Some(RouterItem::Packet(pkt)));
9355                    }
9356                    return Ok(None);
9357                }
9358                Ok(Some(RouterItem::Packet(pkt)))
9359            }
9360        }
9361    }
9362
9363    fn process_reliable_timeouts(&self) -> TelemetryResult<()> {
9364        let now = self.clock.now_ms();
9365        let mut requeue: Vec<(RouterSideId, DataType, u32)> = Vec::new();
9366
9367        {
9368            let mut st = self.state.lock();
9369            if st.reliable_tx.is_empty() {
9370                return Ok(());
9371            }
9372
9373            for ((side, ty_u32), tx_state) in st.reliable_tx.iter_mut() {
9374                let Some(ty) = DataType::try_from_u32(*ty_u32) else {
9375                    continue;
9376                };
9377                let sent_order: Vec<u32> = tx_state.sent_order.iter().copied().collect();
9378                for seq in sent_order {
9379                    let Some(sent) = tx_state.sent.get_mut(&seq) else {
9380                        continue;
9381                    };
9382                    if sent.queued
9383                        || now.wrapping_sub(sent.last_send_ms) < runtime_reliable_retransmit_ms()
9384                    {
9385                        continue;
9386                    }
9387                    if sent.partial_acked {
9388                        // A partial ACK normally arrives with a request for
9389                        // the missing sequence. If that request is itself
9390                        // lost, retaining this flag forever pins the history
9391                        // entry and eventually rejects unrelated traffic with
9392                        // `reliable history full`. Give the request one retry
9393                        // interval, then resume normal bounded retransmission.
9394                        sent.partial_acked = false;
9395                        sent.last_send_ms = now;
9396                        continue;
9397                    }
9398                    if sent.retries >= runtime_reliable_max_retries() {
9399                        tx_state.sent.remove(&seq);
9400                        tx_state.sent_order.retain(|existing| *existing != seq);
9401                        continue;
9402                    }
9403                    sent.retries += 1;
9404                    requeue.push((*side, ty, seq));
9405                }
9406            }
9407        }
9408
9409        for (side, ty, seq) in requeue {
9410            self.queue_reliable_retransmit(side, ty, seq, true)?;
9411        }
9412
9413        Ok(())
9414    }
9415
9416    fn process_end_to_end_reliable_timeouts(&self) -> TelemetryResult<()> {
9417        let now = self.clock.now_ms();
9418        let mut requeue = Vec::new();
9419        let mut failed_ordered_types = BTreeSet::new();
9420
9421        {
9422            let mut st = self.state.lock();
9423            #[cfg(feature = "discovery")]
9424            {
9425                if Self::prune_discovery_routes_locked(&mut st, now) {
9426                    Self::note_discovery_topology_change_locked(&mut st, now);
9427                    self.reconcile_end_to_end_reliable_destinations_locked(&mut st)?;
9428                }
9429                // Route updates reconcile their retained deliveries when the
9430                // topology changes. Rebuilding the destination map on every
9431                // queue item adds allocation and graph scans without changing
9432                // the frozen receipt contract.
9433            }
9434            // Retain walks the bounded pending map in place. A queue pass
9435            // before any retry deadline must not allocate a list of keys.
9436            st.end_to_end_reliable_tx.retain(|&packet_id, sent| {
9437                if sent.waiting_for_predecessor
9438                    || sent.queued
9439                    || now.wrapping_sub(sent.last_send_ms) < runtime_reliable_retransmit_ms()
9440                {
9441                    return true;
9442                }
9443                if sent.retries >= runtime_reliable_max_retries() {
9444                    if let Some(ty) = sent.ordered_type {
9445                        failed_ordered_types.insert(ty);
9446                    }
9447                    return false;
9448                }
9449                sent.retries += 1;
9450                requeue.push(packet_id);
9451                true
9452            });
9453            // Fail the queued tail too; never deliver it past an unconfirmed gap.
9454            st.end_to_end_reliable_tx.retain(|_, sent| {
9455                !sent
9456                    .ordered_type
9457                    .is_some_and(|ty| failed_ordered_types.contains(&ty))
9458            });
9459        }
9460
9461        for packet_id in requeue {
9462            self.queue_end_to_end_reliable_retransmit(packet_id)?;
9463        }
9464        self.release_ordered_successors()?;
9465        if !failed_ordered_types.is_empty() {
9466            return Err(TelemetryError::HandlerError(
9467                "ordered end-to-end delivery exhausted retries",
9468            ));
9469        }
9470        Ok(())
9471    }
9472
9473    #[cfg(feature = "timesync")]
9474    #[inline]
9475    fn monotonic_now_ns(&self) -> u64 {
9476        self.clock.now_ns()
9477    }
9478
9479    #[cfg(feature = "timesync")]
9480    #[inline]
9481    fn monotonic_now_ms(&self) -> u64 {
9482        self.clock.now_ms()
9483    }
9484
9485    #[cfg(feature = "timesync")]
9486    fn refresh_timesync_state(&self, now_mono_ms: u64) {
9487        let now_mono_ns = self.monotonic_now_ns();
9488        let mut st = self.timesync.lock();
9489        st.clock.prune_expired(now_mono_ms);
9490        let timeout_ms = st.cfg.map(|cfg| cfg.source_timeout_ms).unwrap_or(0);
9491        st.remote_sources
9492            .retain(|_, src| now_mono_ms.saturating_sub(src.last_sample_mono_ms) <= timeout_ms);
9493        let has_usable_time = Self::timesync_has_usable_time_locked(&st, now_mono_ns);
9494        let leader = if let Some(tracker) = st.tracker.as_mut() {
9495            let _ = tracker.refresh(now_mono_ms);
9496            tracker.leader(now_mono_ms, has_usable_time)
9497        } else {
9498            None
9499        };
9500        Self::reconcile_pending_timesync_request_locked(&mut st, &leader, now_mono_ms);
9501        if let Some(TimeSyncLeader::Remote(remote)) = leader.as_ref() {
9502            let target_ms = st
9503                .remote_sources
9504                .get(remote.sender.as_str())
9505                .map(|src| src.sample_unix_ms);
9506            if let Some(target_ms) = target_ms {
9507                st.disciplined_clock.steer_unix_ms(now_mono_ns, target_ms);
9508            }
9509        }
9510    }
9511
9512    #[cfg(feature = "timesync")]
9513    /// Inserts or updates a named network-time source with an optional expiration TTL.
9514    pub fn update_network_time_source(
9515        &self,
9516        source: &str,
9517        priority: u64,
9518        time: PartialNetworkTime,
9519        ttl_ms: Option<u64>,
9520    ) {
9521        let now_ms = self.monotonic_now_ms();
9522        let now_ns = self.monotonic_now_ns();
9523        let mut st = self.timesync.lock();
9524        st.clock
9525            .update_source(source, priority, time, now_ms, now_ns, ttl_ms);
9526        if let Some(unix_ms) = time.to_network_time().and_then(|t| t.as_unix_ms()) {
9527            st.disciplined_clock.steer_unix_ms(now_ns, unix_ms);
9528        }
9529    }
9530
9531    #[cfg(feature = "timesync")]
9532    fn set_network_time_source_impl(
9533        &self,
9534        source: &str,
9535        priority: u64,
9536        time: PartialNetworkTime,
9537        ttl_ms: Option<u64>,
9538    ) {
9539        let observed_mono_ms = self.monotonic_now_ms();
9540        let observed_mono_ns = self.monotonic_now_ns();
9541        let mut st = self.timesync.lock();
9542        let commit_mono_ms = self.monotonic_now_ms();
9543        let commit_mono_ns = self.monotonic_now_ns();
9544        let adjusted = if let Some(base) = time.to_network_time() {
9545            let elapsed_ns = commit_mono_ns.saturating_sub(observed_mono_ns);
9546            advance_network_time(base, elapsed_ns)
9547                .map(PartialNetworkTime::from)
9548                .unwrap_or(time)
9549        } else {
9550            time
9551        };
9552        let adjusted_mono_ms =
9553            observed_mono_ms.saturating_add(commit_mono_ms.saturating_sub(observed_mono_ms));
9554        st.clock.update_source(
9555            source,
9556            priority,
9557            adjusted,
9558            commit_mono_ms.max(adjusted_mono_ms),
9559            commit_mono_ns,
9560            ttl_ms,
9561        );
9562        if let Some(unix_ms) = adjusted.to_network_time().and_then(|t| t.as_unix_ms()) {
9563            st.disciplined_clock.steer_unix_ms(commit_mono_ns, unix_ms);
9564        }
9565    }
9566
9567    #[cfg(feature = "timesync")]
9568    fn local_network_time_priority(&self) -> u64 {
9569        let st = self.timesync.lock();
9570        st.cfg.map(|cfg| cfg.priority).unwrap_or(0)
9571    }
9572
9573    #[cfg(feature = "timesync")]
9574    /// Sets the local node's network time using any combination of date, time, and sub-second fields.
9575    pub fn set_local_network_time(&self, time: PartialNetworkTime) {
9576        let priority = self.local_network_time_priority();
9577        if time.is_complete_date() && time.is_complete_time() {
9578            self.set_network_time_source_impl(LOCAL_TIMESYNC_FULL_SOURCE_ID, priority, time, None);
9579            let mut st = self.timesync.lock();
9580            st.clock.remove_source(LOCAL_TIMESYNC_DATE_SOURCE_ID);
9581            st.clock.remove_source(LOCAL_TIMESYNC_TOD_SOURCE_ID);
9582            st.clock.remove_source(LOCAL_TIMESYNC_SUBSEC_SOURCE_ID);
9583            return;
9584        }
9585
9586        {
9587            let mut st = self.timesync.lock();
9588            st.clock.remove_source(LOCAL_TIMESYNC_FULL_SOURCE_ID);
9589        }
9590
9591        if time.year.is_some() || time.month.is_some() || time.day.is_some() {
9592            self.set_network_time_source_impl(
9593                LOCAL_TIMESYNC_DATE_SOURCE_ID,
9594                priority,
9595                PartialNetworkTime {
9596                    year: time.year,
9597                    month: time.month,
9598                    day: time.day,
9599                    ..Default::default()
9600                },
9601                None,
9602            );
9603        }
9604
9605        if time.hour.is_some() || time.minute.is_some() || time.second.is_some() {
9606            self.set_network_time_source_impl(
9607                LOCAL_TIMESYNC_TOD_SOURCE_ID,
9608                priority,
9609                PartialNetworkTime {
9610                    hour: time.hour,
9611                    minute: time.minute,
9612                    second: time.second,
9613                    nanosecond: time.nanosecond,
9614                    ..Default::default()
9615                },
9616                None,
9617            );
9618        }
9619
9620        if time.nanosecond.is_some() {
9621            self.set_network_time_source_impl(
9622                LOCAL_TIMESYNC_SUBSEC_SOURCE_ID,
9623                priority,
9624                PartialNetworkTime {
9625                    nanosecond: time.nanosecond,
9626                    ..Default::default()
9627                },
9628                None,
9629            );
9630        }
9631    }
9632
9633    #[cfg(feature = "timesync")]
9634    /// Removes all locally supplied network-time fragments from the assembled clock.
9635    pub fn clear_local_network_time(&self) {
9636        let mut st = self.timesync.lock();
9637        st.clock.remove_source(LOCAL_TIMESYNC_FULL_SOURCE_ID);
9638        st.clock.remove_source(LOCAL_TIMESYNC_DATE_SOURCE_ID);
9639        st.clock.remove_source(LOCAL_TIMESYNC_TOD_SOURCE_ID);
9640        st.clock.remove_source(LOCAL_TIMESYNC_SUBSEC_SOURCE_ID);
9641    }
9642
9643    #[cfg(feature = "timesync")]
9644    /// Sets only the local calendar date portion of network time.
9645    pub fn set_local_network_date(&self, year: i32, month: u8, day: u8) {
9646        self.set_local_network_time(PartialNetworkTime {
9647            year: Some(year),
9648            month: Some(month),
9649            day: Some(day),
9650            ..Default::default()
9651        });
9652    }
9653
9654    #[cfg(feature = "timesync")]
9655    /// Sets the local time of day to hour and minute precision.
9656    pub fn set_local_network_time_hm(&self, hour: u8, minute: u8) {
9657        self.set_local_network_time(PartialNetworkTime {
9658            hour: Some(hour),
9659            minute: Some(minute),
9660            ..Default::default()
9661        });
9662    }
9663
9664    #[cfg(feature = "timesync")]
9665    /// Sets the local time of day to second precision.
9666    pub fn set_local_network_time_hms(&self, hour: u8, minute: u8, second: u8) {
9667        self.set_local_network_time(PartialNetworkTime {
9668            hour: Some(hour),
9669            minute: Some(minute),
9670            second: Some(second),
9671            ..Default::default()
9672        });
9673    }
9674
9675    #[cfg(feature = "timesync")]
9676    /// Sets the local time of day with millisecond precision.
9677    pub fn set_local_network_time_hms_millis(
9678        &self,
9679        hour: u8,
9680        minute: u8,
9681        second: u8,
9682        millisecond: u16,
9683    ) {
9684        self.set_local_network_time(PartialNetworkTime {
9685            hour: Some(hour),
9686            minute: Some(minute),
9687            second: Some(second),
9688            nanosecond: Some((millisecond as u32).saturating_mul(1_000_000)),
9689            ..Default::default()
9690        });
9691    }
9692
9693    #[cfg(feature = "timesync")]
9694    /// Sets the local time of day with nanosecond precision.
9695    pub fn set_local_network_time_hms_nanos(
9696        &self,
9697        hour: u8,
9698        minute: u8,
9699        second: u8,
9700        nanosecond: u32,
9701    ) {
9702        self.set_local_network_time(PartialNetworkTime {
9703            hour: Some(hour),
9704            minute: Some(minute),
9705            second: Some(second),
9706            nanosecond: Some(nanosecond),
9707            ..Default::default()
9708        });
9709    }
9710
9711    #[cfg(feature = "timesync")]
9712    /// Sets a complete local date and time with second precision.
9713    pub fn set_local_network_datetime(
9714        &self,
9715        year: i32,
9716        month: u8,
9717        day: u8,
9718        hour: u8,
9719        minute: u8,
9720        second: u8,
9721    ) {
9722        self.set_local_network_time(PartialNetworkTime {
9723            year: Some(year),
9724            month: Some(month),
9725            day: Some(day),
9726            hour: Some(hour),
9727            minute: Some(minute),
9728            second: Some(second),
9729            ..Default::default()
9730        });
9731    }
9732
9733    #[cfg(feature = "timesync")]
9734    #[allow(clippy::too_many_arguments)]
9735    /// Sets a complete local date and time with millisecond precision.
9736    pub fn set_local_network_datetime_millis(
9737        &self,
9738        year: i32,
9739        month: u8,
9740        day: u8,
9741        hour: u8,
9742        minute: u8,
9743        second: u8,
9744        millisecond: u16,
9745    ) {
9746        self.set_local_network_time(PartialNetworkTime {
9747            year: Some(year),
9748            month: Some(month),
9749            day: Some(day),
9750            hour: Some(hour),
9751            minute: Some(minute),
9752            second: Some(second),
9753            nanosecond: Some((millisecond as u32).saturating_mul(1_000_000)),
9754        });
9755    }
9756
9757    #[cfg(feature = "timesync")]
9758    #[allow(clippy::too_many_arguments)]
9759    /// Sets a complete local date and time with nanosecond precision.
9760    pub fn set_local_network_datetime_nanos(
9761        &self,
9762        year: i32,
9763        month: u8,
9764        day: u8,
9765        hour: u8,
9766        minute: u8,
9767        second: u8,
9768        nanosecond: u32,
9769    ) {
9770        self.set_local_network_time(PartialNetworkTime {
9771            year: Some(year),
9772            month: Some(month),
9773            day: Some(day),
9774            hour: Some(hour),
9775            minute: Some(minute),
9776            second: Some(second),
9777            nanosecond: Some(nanosecond),
9778        });
9779    }
9780
9781    #[cfg(feature = "timesync")]
9782    /// Removes a previously registered named network-time source.
9783    pub fn clear_network_time_source(&self, source: &str) {
9784        let mut st = self.timesync.lock();
9785        st.clock.remove_source(source);
9786    }
9787
9788    #[cfg(feature = "timesync")]
9789    /// Replaces the active time sync configuration and resets runtime state derived from it.
9790    pub fn set_timesync_config(&self, cfg: Option<TimeSyncConfig>) {
9791        let mut st = self.timesync.lock();
9792        let stale_remote_sources: Vec<String> = st.remote_sources.keys().cloned().collect();
9793        st.cfg = cfg;
9794        st.tracker = cfg.map(TimeSyncTracker::new);
9795        st.disciplined_clock = SlewedNetworkClock::new(
9796            cfg.map(|c| c.max_slew_ppm)
9797                .unwrap_or(TimeSyncConfig::default().max_slew_ppm),
9798        );
9799        st.remote_sources.clear();
9800        st.next_seq = 1;
9801        st.next_announce_mono_ms = 0;
9802        st.next_request_mono_ms = 0;
9803        st.pending_request = None;
9804        st.clock.remove_source(INTERNAL_TIMESYNC_SOURCE_ID);
9805        for source in stale_remote_sources {
9806            st.clock.remove_source(&source);
9807        }
9808        st.clock.remove_source(LOCAL_TIMESYNC_FULL_SOURCE_ID);
9809        st.clock.remove_source(LOCAL_TIMESYNC_DATE_SOURCE_ID);
9810        st.clock.remove_source(LOCAL_TIMESYNC_TOD_SOURCE_ID);
9811        st.clock.remove_source(LOCAL_TIMESYNC_SUBSEC_SOURCE_ID);
9812    }
9813
9814    #[cfg(feature = "timesync")]
9815    /// Returns the best currently known network-time reading, if any.
9816    pub fn network_time(&self) -> Option<NetworkTimeReading> {
9817        let now_ms = self.monotonic_now_ms();
9818        let now_ns = self.monotonic_now_ns();
9819        self.refresh_timesync_state(now_ms);
9820        let st = self.timesync.lock();
9821        if let Some(unix_ms) = st.disciplined_clock.read_unix_ms(now_ns) {
9822            return Some(NetworkTimeReading {
9823                time: PartialNetworkTime::from_unix_ms(unix_ms),
9824                unix_time_ms: Some(unix_ms),
9825            });
9826        }
9827        st.clock.current_time(now_ns)
9828    }
9829
9830    #[cfg(feature = "timesync")]
9831    /// Returns the current network time as Unix milliseconds when available.
9832    pub fn network_time_ms(&self) -> Option<u64> {
9833        self.network_time().and_then(|t| t.unix_time_ms)
9834    }
9835
9836    #[cfg(feature = "timesync")]
9837    fn packet_timestamp_ms(&self) -> u64 {
9838        self.network_time_ms()
9839            .unwrap_or_else(|| self.monotonic_now_ms())
9840    }
9841
9842    #[cfg(not(feature = "timesync"))]
9843    fn packet_timestamp_ms(&self) -> u64 {
9844        self.clock.now_ms()
9845    }
9846
9847    #[cfg(feature = "timesync")]
9848    fn queue_internal_timesync_request(
9849        &self,
9850        seq: u64,
9851        t1_mono_ms: u64,
9852        called_from_queue: bool,
9853    ) -> TelemetryResult<()> {
9854        let pkt_ts = self.packet_timestamp_ms();
9855        if called_from_queue {
9856            self.log_queue_ts(DataType::TimeSyncRequest, pkt_ts, &[seq, t1_mono_ms])
9857        } else {
9858            self.log_ts(DataType::TimeSyncRequest, pkt_ts, &[seq, t1_mono_ms])
9859        }
9860    }
9861
9862    #[cfg(feature = "timesync")]
9863    fn queue_internal_timesync_response(
9864        &self,
9865        seq: u64,
9866        t1_mono_ms: u64,
9867        t2_network_ms: u64,
9868        t3_network_ms: u64,
9869        dst: Option<RouterSideId>,
9870        called_from_queue: bool,
9871    ) -> TelemetryResult<()> {
9872        let pkt_ts = self.packet_timestamp_ms();
9873        let payload = encode_slice_le(&[seq, t1_mono_ms, t2_network_ms, t3_network_ms]);
9874        let sender = self.sender_arc();
9875        let pkt = Packet::new(
9876            DataType::TimeSyncResponse,
9877            &[DataEndpoint::TimeSync],
9878            sender.as_ref(),
9879            pkt_ts,
9880            payload,
9881        )?;
9882        match dst {
9883            Some(dst) => self.emit_internal_tx(
9884                RouterTxItem::ToSide {
9885                    src: None,
9886                    dst,
9887                    data: RouterItem::Packet(pkt),
9888                },
9889                true,
9890                called_from_queue,
9891            ),
9892            None => self.emit_internal_tx(
9893                RouterTxItem::Broadcast(RouterItem::Packet(pkt)),
9894                true,
9895                called_from_queue,
9896            ),
9897        }
9898    }
9899
9900    #[cfg(feature = "timesync")]
9901    /// Runs one time sync maintenance cycle and queues any required announce or request packets.
9902    pub fn poll_timesync(&self) -> TelemetryResult<bool> {
9903        let now_ms = self.monotonic_now_ms();
9904        let now_ns = self.monotonic_now_ns();
9905        let mut queued_any = false;
9906        let mut announce_priority = None;
9907        let mut request = None;
9908
9909        {
9910            let mut st = self.timesync.lock();
9911            st.clock.prune_expired(now_ms);
9912            let timeout_ms = st.cfg.map(|cfg| cfg.source_timeout_ms).unwrap_or(0);
9913            st.remote_sources
9914                .retain(|_, src| now_ms.saturating_sub(src.last_sample_mono_ms) <= timeout_ms);
9915            let Some(cfg) = st.cfg else {
9916                return Ok(false);
9917            };
9918
9919            let has_usable_time = Self::timesync_has_usable_time_locked(&st, now_ns);
9920            let (leader, announce_prio) = if let Some(tracker) = st.tracker.as_mut() {
9921                let _ = tracker.refresh(now_ms);
9922                (
9923                    tracker.leader(now_ms, has_usable_time),
9924                    tracker.local_announce_priority(now_ms, has_usable_time),
9925                )
9926            } else {
9927                (None, None)
9928            };
9929            Self::reconcile_pending_timesync_request_locked(&mut st, &leader, now_ms);
9930
9931            if let Some(TimeSyncLeader::Remote(remote)) = leader.as_ref() {
9932                let target_ms = st
9933                    .remote_sources
9934                    .get(&remote.sender)
9935                    .map(|src| src.sample_unix_ms);
9936                if let Some(target_ms) = target_ms {
9937                    st.disciplined_clock.steer_unix_ms(now_ns, target_ms);
9938                }
9939            }
9940
9941            if let Some(priority) = announce_prio
9942                && now_ms >= st.next_announce_mono_ms
9943            {
9944                announce_priority = Some(priority);
9945                st.next_announce_mono_ms = now_ms.saturating_add(cfg.announce_interval_ms);
9946            }
9947
9948            if let Some(TimeSyncLeader::Remote(remote)) = leader
9949                && now_ms >= st.next_request_mono_ms
9950                && st.pending_request.is_none()
9951            {
9952                let seq = st.next_seq;
9953                let next = st.next_seq.wrapping_add(1);
9954                st.next_seq = if next == 0 { 1 } else { next };
9955                st.next_request_mono_ms = now_ms.saturating_add(cfg.request_interval_ms);
9956                st.pending_request = Some(PendingTimeSyncRequest {
9957                    seq,
9958                    t1_mono_ms: now_ms,
9959                    source: remote.sender,
9960                });
9961                request = Some((seq, now_ms));
9962            }
9963        }
9964
9965        if let Some(priority) = announce_priority {
9966            let time_ms = self.packet_timestamp_ms();
9967            self.log_queue_ts(DataType::TimeSyncAnnounce, time_ms, &[priority, time_ms])?;
9968            queued_any = true;
9969        }
9970        if let Some((seq, t1_mono_ms)) = request {
9971            self.queue_internal_timesync_request(seq, t1_mono_ms, true)?;
9972            queued_any = true;
9973        }
9974
9975        Ok(queued_any)
9976    }
9977
9978    #[cfg(feature = "timesync")]
9979    fn handle_internal_timesync_packet(
9980        &self,
9981        pkt: &Packet,
9982        src: Option<RouterSideId>,
9983        called_from_queue: bool,
9984    ) -> TelemetryResult<bool> {
9985        let Some(cfg) = self.cfg.timesync_config() else {
9986            if self.should_route_remote(&RouterItem::Packet(pkt.clone()), src)? {
9987                self.relay_send(RouterItem::Packet(pkt.clone()), src, called_from_queue)?;
9988            }
9989            return Ok(true);
9990        };
9991
9992        let now_mono_ms = self.monotonic_now_ms();
9993        let now_mono_ns = self.monotonic_now_ns();
9994        let mut response = None;
9995        let mut poll_after = false;
9996
9997        {
9998            let mut st = self.timesync.lock();
9999            st.clock.prune_expired(now_mono_ms);
10000            let timeout_ms = st.cfg.map(|cfg| cfg.source_timeout_ms).unwrap_or(0);
10001            st.remote_sources
10002                .retain(|_, src| now_mono_ms.saturating_sub(src.last_sample_mono_ms) <= timeout_ms);
10003            let has_usable_time = Self::timesync_has_usable_time_locked(&st, now_mono_ns);
10004            if st.tracker.is_none() {
10005                return Ok(true);
10006            }
10007
10008            match pkt.data_type() {
10009                DataType::TimeSyncAnnounce => {
10010                    let ann = decode_timesync_announce(pkt)?;
10011                    let should_steer = {
10012                        let tracker = st.tracker.as_mut().expect("tracker checked above");
10013                        let _ = tracker.handle_announce(pkt, now_mono_ms)?;
10014                        matches!(
10015                            tracker.leader(now_mono_ms, has_usable_time),
10016                            Some(TimeSyncLeader::Remote(ref remote)) if remote.sender == pkt.sender()
10017                        )
10018                    };
10019                    st.remote_sources.insert(
10020                        pkt.sender().to_owned(),
10021                        RemoteTimeSyncSource {
10022                            priority: ann.priority,
10023                            last_sample_mono_ms: now_mono_ms,
10024                            sample_unix_ms: ann.time_ms,
10025                        },
10026                    );
10027                    st.clock.update_source(
10028                        pkt.sender(),
10029                        ann.priority,
10030                        PartialNetworkTime::from_unix_ms(ann.time_ms),
10031                        now_mono_ms,
10032                        now_mono_ns,
10033                        Some(cfg.source_timeout_ms),
10034                    );
10035                    if should_steer {
10036                        st.disciplined_clock.steer_unix_ms(now_mono_ns, ann.time_ms);
10037                    }
10038                    poll_after = true;
10039                }
10040                DataType::TimeSyncRequest => {
10041                    let should_serve = {
10042                        let tracker = st.tracker.as_ref().expect("tracker checked above");
10043                        tracker.should_serve(now_mono_ms, has_usable_time)
10044                    };
10045                    if should_serve {
10046                        let req = decode_timesync_request(pkt)?;
10047                        let network_now = st
10048                            .disciplined_clock
10049                            .read_unix_ms(now_mono_ns)
10050                            .or_else(|| {
10051                                st.clock
10052                                    .current_time(now_mono_ns)
10053                                    .and_then(|t| t.unix_time_ms)
10054                            })
10055                            .unwrap_or(now_mono_ms);
10056                        let t2 = network_now;
10057                        let t3 = network_now;
10058                        response = Some((req.seq, req.t1_ms, t2, t3, src));
10059                    }
10060                }
10061                DataType::TimeSyncResponse => {
10062                    let resp = decode_timesync_response(pkt)?;
10063                    let pending = st.pending_request.clone();
10064                    if let Some(pending) = pending
10065                        && pending.seq == resp.seq
10066                        && pending.source == pkt.sender()
10067                    {
10068                        let source_priority = {
10069                            let tracker = st.tracker.as_ref().expect("tracker checked above");
10070                            tracker
10071                                .best_active_source(now_mono_ms)
10072                                .map(|s| s.priority)
10073                                .or_else(|| st.remote_sources.get(pkt.sender()).map(|s| s.priority))
10074                                .unwrap_or(cfg.priority)
10075                        };
10076                        let (estimate_ms, _delay_ms) = compute_network_time_sample(
10077                            pending.t1_mono_ms,
10078                            resp.t2_ms,
10079                            resp.t3_ms,
10080                            now_mono_ms,
10081                        );
10082                        st.remote_sources.insert(
10083                            pkt.sender().to_owned(),
10084                            RemoteTimeSyncSource {
10085                                priority: source_priority,
10086                                last_sample_mono_ms: now_mono_ms,
10087                                sample_unix_ms: estimate_ms,
10088                            },
10089                        );
10090                        st.clock.update_source(
10091                            pkt.sender(),
10092                            source_priority,
10093                            PartialNetworkTime::from_unix_ms(estimate_ms),
10094                            now_mono_ms,
10095                            now_mono_ns,
10096                            Some(cfg.source_timeout_ms),
10097                        );
10098                        st.disciplined_clock.steer_unix_ms(now_mono_ns, estimate_ms);
10099                        st.pending_request = None;
10100                    }
10101                }
10102                _ => {}
10103            }
10104        }
10105
10106        if let Some((seq, t1, t2, t3, dst)) = response {
10107            self.queue_internal_timesync_response(seq, t1, t2, t3, dst, called_from_queue)?;
10108        }
10109        if poll_after {
10110            let _ = crate::memory_admission::allow_drain(self.poll_timesync())?;
10111        }
10112
10113        if self.should_route_remote(&RouterItem::Packet(pkt.clone()), src)? {
10114            self.relay_send(RouterItem::Packet(pkt.clone()), src, called_from_queue)?;
10115        }
10116
10117        Ok(true)
10118    }
10119
10120    /// Create a new Router with an internal monotonic clock.
10121    #[cfg(feature = "std")]
10122    pub fn new(cfg: RouterConfig) -> Self {
10123        Self::new_with_clock(cfg, Box::new(StdMonotonicClock::default()))
10124    }
10125
10126    /// Create a new Router with the specified router configuration and clock.
10127    pub fn new_with_clock(cfg: RouterConfig, clock: Box<dyn Clock + Send + Sync>) -> Self {
10128        #[cfg(feature = "timesync")]
10129        let timesync_cfg = cfg.timesync_config();
10130        #[cfg(feature = "discovery")]
10131        let preferred_discovery_master = cfg.preferred_discovery_master.clone();
10132        let memory = cfg.memory_config();
10133        let hostname: Arc<str> = Arc::from(cfg.sender());
10134        let address_mode = cfg.address_mode();
10135        let instance_seq = u64::from(ROUTER_INSTANCE_SEQ.fetch_add(1, Ordering::Relaxed));
10136        let owner_hash = Self::sender_hash(hostname.as_ref()) ^ instance_seq;
10137        let fallback = Self::fallback_address_for_hostname(hostname.as_ref());
10138        let requested = address_mode.requested_address();
10139        let address = if requested == 0 { fallback } else { requested };
10140        let local_address = AddressBookEntry {
10141            hostname: hostname.clone(),
10142            address,
10143            requested_address: requested,
10144            mode: address_mode,
10145            birth_ms: clock
10146                .now_ms()
10147                .saturating_mul(1_000_000)
10148                .saturating_add(instance_seq),
10149            owner_hash,
10150            last_seen_ms: clock.now_ms(),
10151        };
10152        let mut address_book = BTreeMap::new();
10153        address_book.insert(hostname.to_string(), local_address.clone());
10154        let mut address_by_value = BTreeMap::new();
10155        address_by_value.insert(address, hostname.to_string());
10156        Self {
10157            sender: RouterMutex::new(hostname),
10158            cfg,
10159            state: RouterMutex::new(RouterInner {
10160                memory,
10161                sides: Vec::new(),
10162                route_overrides: BTreeMap::new(),
10163                typed_route_overrides: BTreeMap::new(),
10164                route_weights: BTreeMap::new(),
10165                route_priorities: BTreeMap::new(),
10166                source_route_modes: BTreeMap::new(),
10167                route_selection_cursors: BTreeMap::new(),
10168                adaptive_route_stats: BTreeMap::new(),
10169                side_runtime_stats: BTreeMap::new(),
10170                side_transport: BTreeMap::new(),
10171                managed_variable_types: BTreeSet::new(),
10172                managed_variable_permissions: BTreeMap::new(),
10173                managed_variable_latest: BTreeMap::new(),
10174                network_variable_update_handlers: BTreeMap::new(),
10175                local_address,
10176                address_book,
10177                address_by_value,
10178                p2p_port_handlers: BTreeMap::new(),
10179                p2p_stream_handlers: BTreeMap::new(),
10180                p2p_stream_sessions: BTreeMap::new(),
10181                next_p2p_stream_id: 1,
10182                received_queue: BoundedDeque::new(
10183                    memory.max_queue_budget,
10184                    memory.starting_queue_size,
10185                    memory.queue_grow_step,
10186                ),
10187                transmit_queue: BoundedDeque::new(
10188                    memory.max_queue_budget,
10189                    memory.starting_queue_size,
10190                    memory.queue_grow_step,
10191                ),
10192                tx_priority_burst: 0,
10193                recent_rx: BoundedDeque::new(
10194                    memory.recent_rx_queue_bytes(),
10195                    memory.recent_rx_queue_bytes(),
10196                    memory.queue_grow_step,
10197                ),
10198                reliable_tx: BTreeMap::new(),
10199                reliable_rx: BTreeMap::new(),
10200                reliable_return_routes: BTreeMap::new(),
10201                reliable_return_route_order: VecDeque::new(),
10202                end_to_end_reliable_tx: BTreeMap::new(),
10203                end_to_end_reliable_tx_order: VecDeque::new(),
10204                total_handler_failures: 0,
10205                total_handler_retries: 0,
10206                #[cfg(feature = "discovery")]
10207                discovery_routes: BTreeMap::new(),
10208                #[cfg(feature = "discovery")]
10209                discovery_cadence: DiscoveryCadenceState::default(),
10210                #[cfg(feature = "discovery")]
10211                discovery_side_throttle: BTreeMap::new(),
10212                #[cfg(feature = "discovery")]
10213                preferred_discovery_master,
10214                #[cfg(all(feature = "discovery", feature = "timesync"))]
10215                timesync_side_throttle: BTreeMap::new(),
10216            }),
10217            isr_rx_queue: IsrRxQueue::new(
10218                memory.max_queue_budget,
10219                memory.starting_queue_size,
10220                memory.queue_grow_step,
10221            ),
10222            side_tx_gate: ReentryGate::new(),
10223            clock,
10224            #[cfg(feature = "timesync")]
10225            timesync: RouterMutex::new(TimeSyncRuntime::new(timesync_cfg)),
10226        }
10227    }
10228
10229    #[inline]
10230    fn sender_arc(&self) -> Arc<str> {
10231        self.sender.lock().clone()
10232    }
10233
10234    #[inline]
10235    pub fn sender(&self) -> Arc<str> {
10236        self.sender_arc()
10237    }
10238
10239    pub fn current_address(&self) -> NodeAddress {
10240        self.state.lock().local_address.address
10241    }
10242
10243    pub fn hostname(&self) -> Arc<str> {
10244        self.sender_arc()
10245    }
10246
10247    /// Change the preferred discovery master at runtime.
10248    ///
10249    /// Passing `None` or an empty hostname restores topology-based election.
10250    /// This does not alter time-sync leadership or source priority.
10251    pub fn set_preferred_discovery_master(&self, hostname: Option<&str>) {
10252        #[cfg(feature = "discovery")]
10253        {
10254            let preferred = hostname
10255                .filter(|value| !value.is_empty())
10256                .map(Arc::<str>::from);
10257            let mut st = self.state.lock();
10258            if st.preferred_discovery_master != preferred {
10259                st.preferred_discovery_master = preferred;
10260                Self::note_discovery_topology_change_locked(&mut st, self.clock.now_ms());
10261            }
10262        }
10263        #[cfg(not(feature = "discovery"))]
10264        let _ = hostname;
10265    }
10266
10267    /// Return the currently configured preferred discovery master.
10268    pub fn preferred_discovery_master(&self) -> Option<Arc<str>> {
10269        #[cfg(feature = "discovery")]
10270        {
10271            return self.state.lock().preferred_discovery_master.clone();
10272        }
10273        #[cfg(not(feature = "discovery"))]
10274        None
10275    }
10276
10277    pub fn address_book(&self) -> Vec<AddressBookEntry> {
10278        self.state.lock().address_book.values().cloned().collect()
10279    }
10280
10281    pub fn resolve_hostname(&self, hostname: &str) -> Option<AddressBookEntry> {
10282        let now_ms = self.clock.now_ms();
10283        #[allow(unused_mut)]
10284        let mut st = self.state.lock();
10285        #[cfg(feature = "discovery")]
10286        if Self::prune_discovery_routes_locked(&mut st, now_ms) {
10287            Self::note_discovery_topology_change_locked(&mut st, now_ms);
10288        }
10289        if let Some(entry) = st.address_book.get(hostname) {
10290            return Some(entry.clone());
10291        }
10292        // Address advertisements are link-local, but topology names cross
10293        // routers. Mirror resolve_address's topology-only identity support so
10294        // named P2P messages/OTA streams can reach those downstream devices.
10295        // Never invent a target merely because an application requested it;
10296        // the retained topology must resolve its compact identity uniquely.
10297        let address = sender_address_u32(hostname);
10298        if Self::hostname_for_address_locked(&st, address).as_deref() != Some(hostname) {
10299            return None;
10300        }
10301        Some(AddressBookEntry {
10302            hostname: Arc::from(hostname),
10303            address,
10304            requested_address: 0,
10305            mode: AddressAssignmentMode::Dynamic,
10306            birth_ms: 0,
10307            owner_hash: Self::sender_hash(hostname),
10308            last_seen_ms: now_ms,
10309        })
10310    }
10311
10312    pub fn resolve_address(&self, address: NodeAddress) -> Option<AddressBookEntry> {
10313        let st = self.state.lock();
10314        let direct = st
10315            .address_by_value
10316            .get(&address)
10317            .and_then(|hostname| st.address_book.get(hostname))
10318            .cloned();
10319        if direct.is_some() {
10320            return direct;
10321        }
10322
10323        let hostname = Self::hostname_for_address_locked(&st, address)?;
10324        if let Some(entry) = st.address_book.get(&hostname) {
10325            return Some(entry.clone());
10326        }
10327
10328        // A topology-only remote has no DHCP-style address record on this
10329        // link. Return a compact-address identity entry; callers that need to
10330        // route to it still use discovery, while telemetry consumers can use
10331        // the stable hostname immediately.
10332        let hostname: Arc<str> = Arc::from(hostname);
10333        Some(AddressBookEntry {
10334            hostname: hostname.clone(),
10335            address,
10336            requested_address: 0,
10337            mode: AddressAssignmentMode::Dynamic,
10338            birth_ms: 0,
10339            owner_hash: Self::sender_hash(hostname.as_ref()),
10340            last_seen_ms: self.clock.now_ms(),
10341        })
10342    }
10343
10344    pub fn bind_p2p_port<F>(&self, port: P2pPort, f: F) -> TelemetryResult<()>
10345    where
10346        F: Fn(P2pMessage<'_>) -> TelemetryResult<()> + Send + Sync + 'static,
10347    {
10348        if port == 0 {
10349            return Err(TelemetryError::BadArg);
10350        }
10351        let mut st = self.state.lock();
10352        st.p2p_port_handlers
10353            .entry(port)
10354            .or_default()
10355            .push(P2pPortHandler {
10356                handler: Arc::new(f),
10357            });
10358        Ok(())
10359    }
10360
10361    pub fn clear_p2p_port(&self, port: P2pPort) {
10362        self.state.lock().p2p_port_handlers.remove(&port);
10363    }
10364
10365    pub fn bind_p2p_stream_port<F>(&self, port: P2pPort, f: F) -> TelemetryResult<()>
10366    where
10367        F: Fn(P2pStreamEvent<'_>) -> TelemetryResult<()> + Send + Sync + 'static,
10368    {
10369        if port == 0 {
10370            return Err(TelemetryError::BadArg);
10371        }
10372        let mut st = self.state.lock();
10373        st.p2p_stream_handlers
10374            .entry(port)
10375            .or_default()
10376            .push(P2pStreamHandler {
10377                handler: Arc::new(f),
10378            });
10379        Ok(())
10380    }
10381
10382    pub fn clear_p2p_stream_port(&self, port: P2pPort) {
10383        self.state.lock().p2p_stream_handlers.remove(&port);
10384    }
10385
10386    fn send_p2p_to_entry(
10387        &self,
10388        dst: AddressBookEntry,
10389        dst_port: P2pPort,
10390        src_port: P2pPort,
10391        payload: &[u8],
10392    ) -> TelemetryResult<()> {
10393        if dst_port == 0 {
10394            return Err(TelemetryError::BadArg);
10395        }
10396        let local = self.state.lock().local_address.clone();
10397        let payload = Self::encode_p2p_payload(
10398            local.hostname.as_ref(),
10399            local.address,
10400            src_port,
10401            dst_port,
10402            payload,
10403        )?;
10404        let pkt = Packet::new(
10405            DataType::P2pMessage,
10406            &[DataEndpoint::Discovery],
10407            local.hostname.as_ref(),
10408            self.packet_timestamp_ms(),
10409            crate::shared_bytes::convert(payload),
10410        )?;
10411        let target = Self::sender_hash(dst.hostname.as_ref());
10412        let item = self.attach_wire_contract_to_item(RouterItem::Packet(pkt.clone()), &[target])?;
10413        if target == Self::sender_hash(self.sender_arc().as_ref()) {
10414            self.dispatch_p2p_packet(&pkt)?;
10415            return Ok(());
10416        }
10417        self.tx_item_impl(RouterTxItem::Broadcast(item), true, false)
10418    }
10419
10420    pub fn send_p2p_to_hostname(
10421        &self,
10422        hostname: &str,
10423        dst_port: P2pPort,
10424        src_port: P2pPort,
10425        payload: &[u8],
10426    ) -> TelemetryResult<()> {
10427        let dst = self
10428            .resolve_hostname(hostname)
10429            .ok_or(TelemetryError::BadArg)?;
10430        self.send_p2p_to_entry(dst, dst_port, src_port, payload)
10431    }
10432
10433    pub fn send_p2p_to_address(
10434        &self,
10435        address: NodeAddress,
10436        dst_port: P2pPort,
10437        src_port: P2pPort,
10438        payload: &[u8],
10439    ) -> TelemetryResult<()> {
10440        let dst = self
10441            .resolve_address(address)
10442            .ok_or(TelemetryError::BadArg)?;
10443        self.send_p2p_to_entry(dst, dst_port, src_port, payload)
10444    }
10445
10446    fn open_p2p_stream_to_entry(
10447        &self,
10448        dst: AddressBookEntry,
10449        dst_port: P2pPort,
10450        src_port: P2pPort,
10451    ) -> TelemetryResult<P2pStreamId> {
10452        if dst_port == 0 || src_port == 0 {
10453            return Err(TelemetryError::BadArg);
10454        }
10455        let stream_id = {
10456            let mut st = self.state.lock();
10457            let stream_id = Self::allocate_p2p_stream_id_locked(&mut st);
10458            if stream_id == 0 {
10459                return Err(TelemetryError::Io("p2p stream id exhausted"));
10460            }
10461            st.p2p_stream_sessions.insert(
10462                stream_id,
10463                P2pStreamSession {
10464                    peer_hostname: dst.hostname.clone(),
10465                    peer_address: dst.address,
10466                    local_port: src_port,
10467                    peer_port: dst_port,
10468                    peer_stream_id: 0,
10469                    next_sequence: 1,
10470                    connected: false,
10471                },
10472            );
10473            stream_id
10474        };
10475        let payload = Self::encode_p2p_stream_payload(P2P_STREAM_SYN, stream_id, 0, 0, &[])?;
10476        self.send_p2p_to_entry(dst, dst_port, src_port, &payload)?;
10477        Ok(stream_id)
10478    }
10479
10480    pub fn open_p2p_stream_to_hostname(
10481        &self,
10482        hostname: &str,
10483        dst_port: P2pPort,
10484        src_port: P2pPort,
10485    ) -> TelemetryResult<P2pStreamId> {
10486        let dst = self
10487            .resolve_hostname(hostname)
10488            .ok_or(TelemetryError::BadArg)?;
10489        self.open_p2p_stream_to_entry(dst, dst_port, src_port)
10490    }
10491
10492    pub fn open_p2p_stream_to_address(
10493        &self,
10494        address: NodeAddress,
10495        dst_port: P2pPort,
10496        src_port: P2pPort,
10497    ) -> TelemetryResult<P2pStreamId> {
10498        let dst = self
10499            .resolve_address(address)
10500            .ok_or(TelemetryError::BadArg)?;
10501        self.open_p2p_stream_to_entry(dst, dst_port, src_port)
10502    }
10503
10504    fn send_p2p_stream_control(
10505        &self,
10506        stream_id: P2pStreamId,
10507        flags: u8,
10508        payload: &[u8],
10509    ) -> TelemetryResult<()> {
10510        let (dst, dst_port, src_port, peer_stream_id, seq, refresh_syn) = {
10511            let mut st = self.state.lock();
10512            let Some(session) = st.p2p_stream_sessions.get_mut(&stream_id) else {
10513                return Err(TelemetryError::BadArg);
10514            };
10515            let peer_hostname = session.peer_hostname.clone();
10516            let peer_address = session.peer_address;
10517            let peer_port = session.peer_port;
10518            let local_port = session.local_port;
10519            let peer_stream_id = session.peer_stream_id;
10520            let refresh_syn = peer_stream_id == 0 && flags & P2P_STREAM_SYN == 0;
10521            let seq = session.next_sequence;
10522            session.next_sequence = session.next_sequence.wrapping_add(1).max(1);
10523            let dst = st
10524                .address_book
10525                .get(peer_hostname.as_ref())
10526                .cloned()
10527                .unwrap_or(AddressBookEntry {
10528                    hostname: peer_hostname.clone(),
10529                    address: peer_address,
10530                    requested_address: peer_address,
10531                    mode: AddressAssignmentMode::Dynamic,
10532                    birth_ms: self.clock.now_ms(),
10533                    owner_hash: Self::sender_hash(peer_hostname.as_ref()),
10534                    last_seen_ms: self.clock.now_ms(),
10535                });
10536            (dst, peer_port, local_port, peer_stream_id, seq, refresh_syn)
10537        };
10538        if refresh_syn {
10539            let syn_payload =
10540                Self::encode_p2p_stream_payload(P2P_STREAM_SYN, stream_id, 0, 0, &[])?;
10541            self.send_p2p_to_entry(dst.clone(), dst_port, src_port, &syn_payload)?;
10542        }
10543        let stream_payload =
10544            Self::encode_p2p_stream_payload(flags, stream_id, peer_stream_id, seq, payload)?;
10545        self.send_p2p_to_entry(dst, dst_port, src_port, &stream_payload)?;
10546        if flags & (P2P_STREAM_FIN | P2P_STREAM_RST) != 0 {
10547            self.state.lock().p2p_stream_sessions.remove(&stream_id);
10548        }
10549        Ok(())
10550    }
10551
10552    pub fn send_p2p_stream(&self, stream_id: P2pStreamId, payload: &[u8]) -> TelemetryResult<()> {
10553        self.send_p2p_stream_control(stream_id, P2P_STREAM_DATA, payload)
10554    }
10555
10556    pub fn close_p2p_stream(&self, stream_id: P2pStreamId) -> TelemetryResult<()> {
10557        self.send_p2p_stream_control(stream_id, P2P_STREAM_FIN, &[])
10558    }
10559
10560    pub fn reset_p2p_stream(&self, stream_id: P2pStreamId) -> TelemetryResult<()> {
10561        self.send_p2p_stream_control(stream_id, P2P_STREAM_RST, &[])
10562    }
10563
10564    pub fn set_sender<S: AsRef<str>>(&self, sender: S) {
10565        let hostname: Arc<str> = Arc::from(sender.as_ref());
10566        let mut st = self.state.lock();
10567        let mut local = st.local_address.clone();
10568        local.hostname = hostname.clone();
10569        local.owner_hash = Self::sender_hash(hostname.as_ref());
10570        if matches!(local.mode, AddressAssignmentMode::Dynamic) {
10571            local.address = Self::fallback_address_for_hostname(hostname.as_ref());
10572            local.requested_address = 0;
10573        }
10574        let change =
10575            self.update_local_identity_locked(&mut st, local, AddressChangeReason::Configured);
10576        drop(st);
10577        let _ = self.notify_address_change(change);
10578    }
10579
10580    pub fn set_address_assignment(
10581        &self,
10582        mode: AddressAssignmentMode,
10583    ) -> TelemetryResult<AddressChange> {
10584        let mut st = self.state.lock();
10585        let mut local = st.local_address.clone();
10586        local.mode = mode;
10587        local.requested_address = mode.requested_address();
10588        local.address = match mode {
10589            AddressAssignmentMode::Dynamic => {
10590                Self::fallback_address_for_hostname(local.hostname.as_ref())
10591            }
10592            AddressAssignmentMode::Requested(address) | AddressAssignmentMode::Static(address) => {
10593                if address == 0 {
10594                    1
10595                } else {
10596                    address
10597                }
10598            }
10599        };
10600        let change =
10601            self.update_local_identity_locked(&mut st, local, AddressChangeReason::Configured);
10602        drop(st);
10603        self.notify_address_change(change.clone())?;
10604        Ok(change)
10605    }
10606
10607    /// Register a side whose TX callback consumes packed packet bytes.
10608    ///
10609    /// `name` is exported in topology/debug views and does not affect routing semantics.
10610    /// `tx` is called whenever the router decides to send a packet toward this side.
10611    ///
10612    /// The default options disable the router's per-link reliable framing on this side. Use
10613    /// [`Router::add_side_packed_with_options`] when this hop should participate in router
10614    /// reliable ACK/retransmit behavior.
10615    pub fn add_side_packed<N, F>(&self, name: N, tx: F) -> RouterSideId
10616    where
10617        N: AsRef<str>,
10618        F: Fn(&[u8]) -> TelemetryResult<()> + Send + Sync + 'static,
10619    {
10620        self.add_side_packed_with_options(name, tx, RouterSideOptions::default())
10621    }
10622
10623    /// Register a packed side with the compact small-packet transport preset enabled.
10624    ///
10625    /// `max_frame_bytes == 0` keeps header-template reuse enabled without chunking.
10626    pub fn add_side_packed_small_packets<N, F>(
10627        &self,
10628        name: N,
10629        tx: F,
10630        max_frame_bytes: usize,
10631    ) -> RouterSideId
10632    where
10633        N: AsRef<str>,
10634        F: Fn(&[u8]) -> TelemetryResult<()> + Send + Sync + 'static,
10635    {
10636        self.add_side_packed_with_options(
10637            name,
10638            tx,
10639            RouterSideOptions::default().with_small_packet_transport(max_frame_bytes),
10640        )
10641    }
10642
10643    /// Register a packed-output side with explicit side options.
10644    ///
10645    /// `opts.reliable_enabled` enables the router's per-hop reliable framing on this side only.
10646    /// That means reliable schema traffic on this side uses router-managed ACKs, packet requests,
10647    /// and retransmits before the bytes reach `tx`.
10648    ///
10649    /// `opts.link_local_enabled` allows link-local-only endpoints and discovery routes to use this
10650    /// side. `ingress_enabled` and `egress_enabled` set the initial directional policy.
10651    pub fn add_side_packed_with_options<N, F>(
10652        &self,
10653        name: N,
10654        tx: F,
10655        opts: RouterSideOptions,
10656    ) -> RouterSideId
10657    where
10658        N: AsRef<str>,
10659        F: Fn(&[u8]) -> TelemetryResult<()> + Send + Sync + 'static,
10660    {
10661        let mut st = self.state.lock();
10662        let side = Some(RouterSide {
10663            name: Arc::from(name.as_ref()),
10664            tx_handler: RouterTxHandlerFn::Packed(Arc::new(tx)),
10665            opts,
10666        });
10667        let id = if let Some(id) = st.sides.iter().position(Option::is_none) {
10668            st.sides[id] = side;
10669            id
10670        } else {
10671            let id = st.sides.len();
10672            st.sides.push(side);
10673            id
10674        };
10675        st.side_runtime_stats
10676            .insert(id, SideRuntimeStatsInner::default());
10677        st.side_transport.insert(id, SideTransportState::default());
10678        #[cfg(feature = "discovery")]
10679        Self::note_discovery_topology_change_locked(&mut st, self.clock.now_ms());
10680        id
10681    }
10682
10683    /// Register a packed-output side whose callback also receives SEDSNet's
10684    /// logical scheduler priority for the frame.
10685    ///
10686    /// This is intended for asynchronous host transports that must queue the
10687    /// emitted bytes after this callback returns. Every compact/chunked frame
10688    /// belonging to a logical packet receives the same priority, so transport
10689    /// backpressure cannot erase discovery, shared-state, time-sync, or schema
10690    /// ordering.
10691    pub fn add_side_packed_with_priority_and_options<N, F>(
10692        &self,
10693        name: N,
10694        tx: F,
10695        opts: RouterSideOptions,
10696    ) -> RouterSideId
10697    where
10698        N: AsRef<str>,
10699        F: Fn(&[u8], u8) -> TelemetryResult<()> + Send + Sync + 'static,
10700    {
10701        let mut st = self.state.lock();
10702        let side = Some(RouterSide {
10703            name: Arc::from(name.as_ref()),
10704            tx_handler: RouterTxHandlerFn::PackedWithPriority(Arc::new(tx)),
10705            opts,
10706        });
10707        let id = if let Some(id) = st.sides.iter().position(Option::is_none) {
10708            st.sides[id] = side;
10709            id
10710        } else {
10711            let id = st.sides.len();
10712            st.sides.push(side);
10713            id
10714        };
10715        st.side_runtime_stats
10716            .insert(id, SideRuntimeStatsInner::default());
10717        st.side_transport.insert(id, SideTransportState::default());
10718        #[cfg(feature = "discovery")]
10719        Self::note_discovery_topology_change_locked(&mut st, self.clock.now_ms());
10720        id
10721    }
10722
10723    /// Register a side whose TX callback receives decoded [`Packet`] values.
10724    ///
10725    /// Packet-output sides do not preserve the packed reliable hop framing, so
10726    /// `RouterSideOptions::reliable_enabled` only has effect for packed sides.
10727    pub fn add_side_packet<N, F>(&self, name: N, tx: F) -> RouterSideId
10728    where
10729        N: AsRef<str>,
10730        F: Fn(&Packet) -> TelemetryResult<()> + Send + Sync + 'static,
10731    {
10732        self.add_side_packet_with_options(name, tx, RouterSideOptions::default())
10733    }
10734
10735    /// Register a packet-output side with explicit side options.
10736    ///
10737    /// `opts.reliable_enabled` still records the operator's intent for this side, but packet-based
10738    /// callbacks receive decoded packets rather than the router's packed reliable hop framing.
10739    /// For router-managed per-link reliable sequencing and ACKs, use a packed side instead.
10740    pub fn add_side_packet_with_options<N, F>(
10741        &self,
10742        name: N,
10743        tx: F,
10744        opts: RouterSideOptions,
10745    ) -> RouterSideId
10746    where
10747        N: AsRef<str>,
10748        F: Fn(&Packet) -> TelemetryResult<()> + Send + Sync + 'static,
10749    {
10750        let mut st = self.state.lock();
10751        let side = Some(RouterSide {
10752            name: Arc::from(name.as_ref()),
10753            tx_handler: RouterTxHandlerFn::Packet(Arc::new(tx)),
10754            opts,
10755        });
10756        let id = if let Some(id) = st.sides.iter().position(Option::is_none) {
10757            st.sides[id] = side;
10758            id
10759        } else {
10760            let id = st.sides.len();
10761            st.sides.push(side);
10762            id
10763        };
10764        st.side_runtime_stats
10765            .insert(id, SideRuntimeStatsInner::default());
10766        st.side_transport.insert(id, SideTransportState::default());
10767        #[cfg(feature = "discovery")]
10768        Self::note_discovery_topology_change_locked(&mut st, self.clock.now_ms());
10769        id
10770    }
10771
10772    /// Remove a side while keeping existing side IDs stable.
10773    ///
10774    /// `side` must be an id returned earlier by one of the `add_side_*` calls. Removed side IDs
10775    /// are tombstoned rather than renumbered so cached IDs for the remaining sides stay valid.
10776    pub fn remove_side(&self, side: RouterSideId) -> TelemetryResult<()> {
10777        let now_ms = self.clock.now_ms();
10778        {
10779            let mut st = self.state.lock();
10780            let slot = st.sides.get_mut(side).ok_or(TelemetryError::BadArg)?;
10781            if slot.is_none() {
10782                return Err(TelemetryError::BadArg);
10783            }
10784            *slot = None;
10785            while st.sides.last().is_some_and(Option::is_none) {
10786                st.sides.pop();
10787            }
10788            // Keep the small side table allocation for reuse. Embedded links can
10789            // disappear and return frequently; shrinking here turned every tail
10790            // remove/add cycle into a free/allocation pair. The retained capacity
10791            // remains owned by the router and is released when the router drops.
10792            st.route_overrides
10793                .retain(|(src_side, dst_side), _| *src_side != Some(side) && *dst_side != side);
10794            st.typed_route_overrides
10795                .retain(|(src_side, _, dst_side), _| *src_side != Some(side) && *dst_side != side);
10796            st.route_weights
10797                .retain(|(src_side, dst_side), _| *src_side != Some(side) && *dst_side != side);
10798            st.route_priorities
10799                .retain(|(src_side, dst_side), _| *src_side != Some(side) && *dst_side != side);
10800            st.source_route_modes.remove(&Some(side));
10801            st.route_selection_cursors.remove(&Some(side));
10802            st.adaptive_route_stats.remove(&side);
10803            #[cfg(feature = "discovery")]
10804            st.discovery_side_throttle.remove(&side);
10805            #[cfg(all(feature = "discovery", feature = "timesync"))]
10806            st.timesync_side_throttle.remove(&side);
10807            st.side_runtime_stats.remove(&side);
10808            st.side_transport.remove(&side);
10809            st.reliable_return_routes
10810                .retain(|_, route| route.side != side);
10811            st.transmit_queue.retain(
10812                |queued| {
10813                    !matches!(&queued.item, RouterTxItem::ToSide { dst, .. } if *dst == side)
10814                        && !matches!(&queued.item, RouterTxItem::ReliableReplay { dst, .. } if *dst == side)
10815                },
10816            );
10817            st.received_queue.retain(|queued| queued.src != Some(side));
10818            st.reliable_tx.retain(|(side_id, _), _| *side_id != side);
10819            st.reliable_rx.retain(|(side_id, _), _| *side_id != side);
10820            #[cfg(feature = "discovery")]
10821            {
10822                st.discovery_routes.remove(&side);
10823                Self::note_discovery_topology_change_locked(&mut st, now_ms);
10824            }
10825        }
10826        let mut isr_rx = self.isr_rx_queue.try_lock()?;
10827        isr_rx.retain(|queued| queued.src != Some(side));
10828        Ok(())
10829    }
10830
10831    /// Enable or disable ingress processing for a registered side.
10832    ///
10833    /// When `enabled` is `false`, packets tagged as arriving from `side` are rejected before local
10834    /// delivery, discovery learning, or forwarding.
10835    pub fn set_side_ingress_enabled(
10836        &self,
10837        side: RouterSideId,
10838        enabled: bool,
10839    ) -> TelemetryResult<()> {
10840        let now_ms = self.clock.now_ms();
10841        let mut st = self.state.lock();
10842        let side_ref = st
10843            .sides
10844            .get_mut(side)
10845            .and_then(|side| side.as_mut())
10846            .ok_or(TelemetryError::BadArg)?;
10847        side_ref.opts.ingress_enabled = enabled;
10848        #[cfg(feature = "discovery")]
10849        Self::note_discovery_topology_change_locked(&mut st, now_ms);
10850        Ok(())
10851    }
10852
10853    /// Enable or disable egress toward a registered side.
10854    ///
10855    /// When `enabled` is `false`, the router keeps the side but stops routing packets toward it.
10856    pub fn set_side_egress_enabled(
10857        &self,
10858        side: RouterSideId,
10859        enabled: bool,
10860    ) -> TelemetryResult<()> {
10861        let now_ms = self.clock.now_ms();
10862        let mut st = self.state.lock();
10863        let side_ref = st
10864            .sides
10865            .get_mut(side)
10866            .and_then(|side| side.as_mut())
10867            .ok_or(TelemetryError::BadArg)?;
10868        side_ref.opts.egress_enabled = enabled;
10869        if !enabled {
10870            st.transmit_queue.retain(
10871                |queued| {
10872                    !matches!(&queued.item, RouterTxItem::ToSide { dst, .. } if *dst == side)
10873                        && !matches!(&queued.item, RouterTxItem::ReliableReplay { dst, .. } if *dst == side)
10874                },
10875            );
10876        }
10877        #[cfg(feature = "discovery")]
10878        Self::note_discovery_topology_change_locked(&mut st, now_ms);
10879        Ok(())
10880    }
10881
10882    /// Set the route-selection policy for traffic originating from `src`.
10883    ///
10884    /// `src == None` targets locally-originated router TX. `src == Some(side)` targets traffic
10885    /// that was received from a specific ingress side. `mode` only matters when more than one
10886    /// destination side is currently eligible.
10887    pub fn set_source_route_mode(
10888        &self,
10889        src: Option<RouterSideId>,
10890        mode: RouteSelectionMode,
10891    ) -> TelemetryResult<()> {
10892        let now_ms = self.clock.now_ms();
10893        let mut st = self.state.lock();
10894        if let Some(src) = src {
10895            let _ = Self::side_ref(&st, src).map_err(|_| TelemetryError::BadArg)?;
10896        }
10897        // Keep an explicitly selected Fanout mode distinct from the absence
10898        // of a policy. With discovery enabled, no policy intentionally uses
10899        // adaptive single-path selection; explicit Fanout must override that.
10900        st.source_route_modes.insert(src, mode);
10901        st.route_selection_cursors.remove(&src);
10902        #[cfg(feature = "discovery")]
10903        Self::note_discovery_topology_change_locked(&mut st, now_ms);
10904        Ok(())
10905    }
10906
10907    /// Clear any source-specific route-selection override for `src`.
10908    pub fn clear_source_route_mode(&self, src: Option<RouterSideId>) -> TelemetryResult<()> {
10909        let now_ms = self.clock.now_ms();
10910        let mut st = self.state.lock();
10911        if let Some(src) = src {
10912            let _ = Self::side_ref(&st, src).map_err(|_| TelemetryError::BadArg)?;
10913        }
10914        st.source_route_modes.remove(&src);
10915        st.route_selection_cursors.remove(&src);
10916        #[cfg(feature = "discovery")]
10917        Self::note_discovery_topology_change_locked(&mut st, now_ms);
10918        Ok(())
10919    }
10920
10921    /// Set the weighted-routing weight from `src` toward `dst`.
10922    ///
10923    /// Higher `weight` values make `dst` more likely to be chosen when the source route mode is
10924    /// [`RouteSelectionMode::Weighted`]. `src == None` applies to locally-originated TX.
10925    pub fn set_route_weight(
10926        &self,
10927        src: Option<RouterSideId>,
10928        dst: RouterSideId,
10929        weight: u32,
10930    ) -> TelemetryResult<()> {
10931        let now_ms = self.clock.now_ms();
10932        let mut st = self.state.lock();
10933        let _ = Self::side_ref(&st, dst).map_err(|_| TelemetryError::BadArg)?;
10934        if let Some(src) = src {
10935            let _ = Self::side_ref(&st, src).map_err(|_| TelemetryError::BadArg)?;
10936        }
10937        st.route_weights.insert((src, dst), weight);
10938        st.route_selection_cursors.remove(&src);
10939        #[cfg(feature = "discovery")]
10940        Self::note_discovery_topology_change_locked(&mut st, now_ms);
10941        Ok(())
10942    }
10943
10944    /// Clear a previously configured weighted-routing weight override.
10945    pub fn clear_route_weight(
10946        &self,
10947        src: Option<RouterSideId>,
10948        dst: RouterSideId,
10949    ) -> TelemetryResult<()> {
10950        let now_ms = self.clock.now_ms();
10951        let mut st = self.state.lock();
10952        let _ = Self::side_ref(&st, dst).map_err(|_| TelemetryError::BadArg)?;
10953        if let Some(src) = src {
10954            let _ = Self::side_ref(&st, src).map_err(|_| TelemetryError::BadArg)?;
10955        }
10956        st.route_weights.remove(&(src, dst));
10957        st.route_selection_cursors.remove(&src);
10958        #[cfg(feature = "discovery")]
10959        Self::note_discovery_topology_change_locked(&mut st, now_ms);
10960        Ok(())
10961    }
10962
10963    /// Set the failover priority from `src` toward `dst`.
10964    ///
10965    /// Lower numeric `priority` wins when the source route mode is
10966    /// [`RouteSelectionMode::Failover`]. `src == None` applies to locally-originated TX.
10967    pub fn set_route_priority(
10968        &self,
10969        src: Option<RouterSideId>,
10970        dst: RouterSideId,
10971        priority: u32,
10972    ) -> TelemetryResult<()> {
10973        let now_ms = self.clock.now_ms();
10974        let mut st = self.state.lock();
10975        let _ = Self::side_ref(&st, dst).map_err(|_| TelemetryError::BadArg)?;
10976        if let Some(src) = src {
10977            let _ = Self::side_ref(&st, src).map_err(|_| TelemetryError::BadArg)?;
10978        }
10979        st.route_priorities.insert((src, dst), priority);
10980        #[cfg(feature = "discovery")]
10981        Self::note_discovery_topology_change_locked(&mut st, now_ms);
10982        Ok(())
10983    }
10984
10985    /// Clear a previously configured failover priority override.
10986    pub fn clear_route_priority(
10987        &self,
10988        src: Option<RouterSideId>,
10989        dst: RouterSideId,
10990    ) -> TelemetryResult<()> {
10991        let now_ms = self.clock.now_ms();
10992        let mut st = self.state.lock();
10993        let _ = Self::side_ref(&st, dst).map_err(|_| TelemetryError::BadArg)?;
10994        if let Some(src) = src {
10995            let _ = Self::side_ref(&st, src).map_err(|_| TelemetryError::BadArg)?;
10996        }
10997        st.route_priorities.remove(&(src, dst));
10998        #[cfg(feature = "discovery")]
10999        Self::note_discovery_topology_change_locked(&mut st, now_ms);
11000        Ok(())
11001    }
11002
11003    /// Allow or block routing from `src` toward `dst`.
11004    ///
11005    /// `src == None` controls locally-originated router TX. `enabled == false` is the sink-like
11006    /// building block for disabling specific directions without changing router construction mode.
11007    pub fn set_route(
11008        &self,
11009        src: Option<RouterSideId>,
11010        dst: RouterSideId,
11011        enabled: bool,
11012    ) -> TelemetryResult<()> {
11013        let now_ms = self.clock.now_ms();
11014        let mut st = self.state.lock();
11015        let _ = Self::side_ref(&st, dst).map_err(|_| TelemetryError::BadArg)?;
11016        if let Some(src) = src {
11017            let _ = Self::side_ref(&st, src).map_err(|_| TelemetryError::BadArg)?;
11018        }
11019        st.route_overrides.insert((src, dst), enabled);
11020        #[cfg(feature = "discovery")]
11021        Self::note_discovery_topology_change_locked(&mut st, now_ms);
11022        Ok(())
11023    }
11024
11025    /// Allow or block routing for a specific `DataType` from `src` toward `dst`.
11026    ///
11027    /// Typed route rules act as allowlists once any rule exists for the `(src, ty)` pair.
11028    /// `src == None` targets locally-originated router TX.
11029    pub fn set_typed_route(
11030        &self,
11031        src: Option<RouterSideId>,
11032        ty: DataType,
11033        dst: RouterSideId,
11034        enabled: bool,
11035    ) -> TelemetryResult<()> {
11036        let now_ms = self.clock.now_ms();
11037        let mut st = self.state.lock();
11038        let _ = Self::side_ref(&st, dst).map_err(|_| TelemetryError::BadArg)?;
11039        if let Some(src) = src {
11040            let _ = Self::side_ref(&st, src).map_err(|_| TelemetryError::BadArg)?;
11041        }
11042        st.typed_route_overrides
11043            .insert((src, ty.as_u32(), dst), enabled);
11044        #[cfg(feature = "discovery")]
11045        Self::note_discovery_topology_change_locked(&mut st, now_ms);
11046        Ok(())
11047    }
11048
11049    /// Clear a typed route override for the `(src, ty, dst)` triple.
11050    pub fn clear_typed_route(
11051        &self,
11052        src: Option<RouterSideId>,
11053        ty: DataType,
11054        dst: RouterSideId,
11055    ) -> TelemetryResult<()> {
11056        let now_ms = self.clock.now_ms();
11057        let mut st = self.state.lock();
11058        let _ = Self::side_ref(&st, dst).map_err(|_| TelemetryError::BadArg)?;
11059        if let Some(src) = src {
11060            let _ = Self::side_ref(&st, src).map_err(|_| TelemetryError::BadArg)?;
11061        }
11062        st.typed_route_overrides.remove(&(src, ty.as_u32(), dst));
11063        #[cfg(feature = "discovery")]
11064        Self::note_discovery_topology_change_locked(&mut st, now_ms);
11065        Ok(())
11066    }
11067
11068    /// Clear a non-typed route override so the router falls back to default behavior.
11069    pub fn clear_route(&self, src: Option<RouterSideId>, dst: RouterSideId) -> TelemetryResult<()> {
11070        let now_ms = self.clock.now_ms();
11071        let mut st = self.state.lock();
11072        let _ = Self::side_ref(&st, dst).map_err(|_| TelemetryError::BadArg)?;
11073        if let Some(src) = src {
11074            let _ = Self::side_ref(&st, src).map_err(|_| TelemetryError::BadArg)?;
11075        }
11076        st.route_overrides.remove(&(src, dst));
11077        #[cfg(feature = "discovery")]
11078        Self::note_discovery_topology_change_locked(&mut st, now_ms);
11079        Ok(())
11080    }
11081
11082    /// Queue a built-in discovery advertisement describing this router's local endpoints.
11083    #[cfg(feature = "discovery")]
11084    pub fn announce_discovery(&self) -> TelemetryResult<()> {
11085        self.queue_discovery_announce(true, false)
11086    }
11087
11088    /// Broadcast that this router is leaving so peers can prune topology immediately.
11089    #[cfg(feature = "discovery")]
11090    pub fn announce_leave(&self) -> TelemetryResult<()> {
11091        let sender = self.sender_arc();
11092        let pkt = discovery::build_discovery_leave(sender.as_ref(), self.clock.now_ms())?;
11093        self.tx_item(RouterTxItem::Broadcast(RouterItem::Packet(pkt)))
11094    }
11095
11096    /// Queue a discovery advertisement if the adaptive cadence says one is due.
11097    #[cfg(feature = "discovery")]
11098    pub fn poll_discovery(&self) -> TelemetryResult<bool> {
11099        self.poll_discovery_announce()
11100    }
11101
11102    #[cfg(feature = "discovery")]
11103    pub fn request_topology(&self) -> TelemetryResult<()> {
11104        let sender = self.sender_arc();
11105        let pkt =
11106            discovery::build_discovery_topology_request(sender.as_ref(), self.clock.now_ms())?;
11107        self.tx_item(RouterTxItem::Broadcast(RouterItem::Packet(pkt)))
11108    }
11109
11110    #[cfg(feature = "discovery")]
11111    pub fn request_schema(&self) -> TelemetryResult<()> {
11112        let sender = self.sender_arc();
11113        let pkt = discovery::build_discovery_schema_request(sender.as_ref(), self.clock.now_ms())?;
11114        self.tx_item(RouterTxItem::Broadcast(RouterItem::Packet(pkt)))
11115    }
11116
11117    /// Mark a data type as a network-managed variable.
11118    ///
11119    /// The router caches the latest packet for this type when it is locally transmitted or
11120    /// received from the network. Peers can request the latest cached value and the router will
11121    /// replay the original value packet, so user endpoint handlers see the same API shape as a
11122    /// normal update.
11123    pub fn enable_managed_variable(&self, ty: DataType) -> TelemetryResult<()> {
11124        self.enable_network_variable(ty, NetworkVariablePermissions::READ_WRITE)
11125    }
11126
11127    /// Mark a data type as a network variable with local read/write permissions.
11128    ///
11129    /// Users do not register a separate endpoint for network variables. Values are cached by
11130    /// data type and refreshed through SEDSnet's internal managed-variable control packets.
11131    pub fn enable_network_variable(
11132        &self,
11133        ty: DataType,
11134        permissions: NetworkVariablePermissions,
11135    ) -> TelemetryResult<()> {
11136        if is_internal_control_type(ty) {
11137            return Err(TelemetryError::InvalidType);
11138        }
11139        let mut st = self.state.lock();
11140        let newly_advertised = st.managed_variable_types.insert(ty.as_u32());
11141        st.managed_variable_permissions
11142            .insert(ty.as_u32(), permissions);
11143        #[cfg(feature = "discovery")]
11144        if newly_advertised {
11145            Self::note_discovery_topology_change_locked(&mut st, self.clock.now_ms());
11146        }
11147        Ok(())
11148    }
11149
11150    /// Register a callback that runs when an inbound network update changes this variable cache.
11151    ///
11152    /// The callback is invoked after the router state lock is released, so it may call back into
11153    /// the router. Local `set_network_variable` and `seed_managed_variable` calls update the cache
11154    /// without invoking this network-update callback.
11155    pub fn on_network_variable_update<F>(&self, ty: DataType, f: F) -> TelemetryResult<()>
11156    where
11157        F: Fn(&Packet) -> TelemetryResult<()> + Send + Sync + 'static,
11158    {
11159        if is_internal_control_type(ty) {
11160            return Err(TelemetryError::InvalidType);
11161        }
11162        let mut st = self.state.lock();
11163        let newly_advertised = st.managed_variable_types.insert(ty.as_u32());
11164        st.network_variable_update_handlers
11165            .entry(ty.as_u32())
11166            .or_default()
11167            .push(NetworkVariableUpdateHandler {
11168                handler: Arc::new(f),
11169            });
11170        #[cfg(feature = "discovery")]
11171        if newly_advertised {
11172            Self::note_discovery_topology_change_locked(&mut st, self.clock.now_ms());
11173        }
11174        Ok(())
11175    }
11176
11177    pub fn disable_managed_variable(&self, ty: DataType) {
11178        let mut st = self.state.lock();
11179        let was_advertised = st.managed_variable_types.remove(&ty.as_u32());
11180        st.managed_variable_permissions.remove(&ty.as_u32());
11181        st.managed_variable_latest.remove(&ty.as_u32());
11182        st.network_variable_update_handlers.remove(&ty.as_u32());
11183        #[cfg(feature = "discovery")]
11184        if was_advertised {
11185            Self::note_discovery_topology_change_locked(&mut st, self.clock.now_ms());
11186        }
11187    }
11188
11189    pub fn seed_managed_variable(&self, pkt: Packet) -> TelemetryResult<()> {
11190        if is_internal_control_type(pkt.data_type()) {
11191            return Err(TelemetryError::InvalidType);
11192        }
11193        pkt.validate()?;
11194        {
11195            let mut st = self.state.lock();
11196            let newly_advertised = st.managed_variable_types.insert(pkt.data_type().as_u32());
11197            #[cfg(feature = "discovery")]
11198            if newly_advertised {
11199                Self::note_discovery_topology_change_locked(&mut st, self.clock.now_ms());
11200            }
11201        }
11202        self.cache_managed_variable_packet(&pkt, false)
11203    }
11204
11205    pub fn cached_managed_variable(&self, ty: DataType) -> Option<Packet> {
11206        self.managed_variable_latest(ty)
11207    }
11208
11209    /// Set a network variable for the whole network, permission policy allowing.
11210    ///
11211    /// The packet is cached locally and sent as normal user data. Routers that have seen or enabled
11212    /// this variable update their local cache internally; applications do not need to register a
11213    /// managed-variable endpoint.
11214    pub fn set_network_variable(&self, pkt: Packet) -> TelemetryResult<()> {
11215        if is_internal_control_type(pkt.data_type()) {
11216            return Err(TelemetryError::InvalidType);
11217        }
11218        pkt.validate()?;
11219        let ty = pkt.data_type();
11220        {
11221            let mut st = self.state.lock();
11222            st.managed_variable_types.insert(ty.as_u32());
11223            let perms = Self::managed_variable_permissions_locked(&st, ty);
11224            if !perms.write {
11225                return Err(TelemetryError::PermissionDenied);
11226            }
11227        }
11228        self.cache_managed_variable_packet(&pkt, false)?;
11229        #[cfg(feature = "discovery")]
11230        let _ = crate::memory_admission::allow_drain(self.poll_discovery())?;
11231        let item = RouterTxItem::Broadcast(RouterItem::Packet(pkt));
11232        if self.side_tx_active() {
11233            return self.tx_queue_item_with_priority(
11234                item,
11235                false,
11236                crate::transport_priority(DataType::ManagedVariableValue),
11237            );
11238        }
11239        self.tx_item(item)
11240    }
11241
11242    /// Read a cached network variable, requesting a refresh if missing or stale.
11243    ///
11244    /// Returns the cached value when present. If the value is missing, this queues an internal
11245    /// managed-variable request and returns `Ok(None)`. If the value is stale, this queues a refresh
11246    /// request and returns the stale cached value so callers can continue operating while the network
11247    /// catches up.
11248    #[cfg(feature = "discovery")]
11249    pub fn get_network_variable(
11250        &self,
11251        ty: DataType,
11252        stale_after_ms: Option<u64>,
11253    ) -> TelemetryResult<Option<Packet>> {
11254        if is_internal_control_type(ty) {
11255            return Err(TelemetryError::InvalidType);
11256        }
11257        {
11258            let mut st = self.state.lock();
11259            st.managed_variable_types.insert(ty.as_u32());
11260            let perms = Self::managed_variable_permissions_locked(&st, ty);
11261            if !perms.read {
11262                return Err(TelemetryError::PermissionDenied);
11263            }
11264        }
11265        let cached = self.managed_variable_latest_with_age(ty);
11266        let needs_refresh = match (cached.as_ref(), stale_after_ms) {
11267            (None, _) => true,
11268            (Some((_pkt, age_ms)), Some(max_age_ms)) => *age_ms > max_age_ms,
11269            (Some(_), None) => false,
11270        };
11271        if needs_refresh {
11272            self.request_managed_variable(ty)?;
11273        }
11274        Ok(cached.map(|(pkt, _age_ms)| pkt))
11275    }
11276
11277    /// Read a cached network variable without issuing a network refresh.
11278    pub fn get_cached_network_variable(&self, ty: DataType) -> TelemetryResult<Option<Packet>> {
11279        if is_internal_control_type(ty) {
11280            return Err(TelemetryError::InvalidType);
11281        }
11282        if !self.can_read_managed_variable(ty) {
11283            return Err(TelemetryError::PermissionDenied);
11284        }
11285        Ok(self.managed_variable_latest(ty))
11286    }
11287
11288    #[cfg(feature = "discovery")]
11289    pub fn request_managed_variable(&self, ty: DataType) -> TelemetryResult<()> {
11290        if is_internal_control_type(ty) {
11291            return Err(TelemetryError::InvalidType);
11292        }
11293        if !self.can_read_managed_variable(ty) {
11294            return Err(TelemetryError::PermissionDenied);
11295        }
11296        let sender = self.sender_arc();
11297        let pkt =
11298            discovery::build_managed_variable_request(sender.as_ref(), self.clock.now_ms(), ty)?;
11299        self.tx_item(RouterTxItem::Broadcast(RouterItem::Packet(pkt)))
11300    }
11301
11302    #[cfg(feature = "discovery")]
11303    pub fn request_managed_variable_by_name(&self, ty_name: &str) -> TelemetryResult<()> {
11304        let ty = DataType::try_named(ty_name).ok_or(TelemetryError::InvalidType)?;
11305        self.request_managed_variable(ty)
11306    }
11307
11308    /// Export the current discovery-driven network topology view.
11309    #[cfg(feature = "discovery")]
11310    pub fn export_topology(&self) -> TopologySnapshot {
11311        let now_ms = self.clock.now_ms();
11312        let mut st = self.state.lock();
11313        if Self::prune_discovery_routes_locked(&mut st, now_ms) {
11314            Self::note_discovery_topology_change_locked(&mut st, now_ms);
11315        }
11316        let routes = st
11317            .discovery_routes
11318            .iter()
11319            .filter_map(|(&side_id, route)| {
11320                let side = st.sides.get(side_id)?.as_ref()?;
11321                let announcers = route
11322                    .announcers
11323                    .iter()
11324                    .map(|(sender_id, sender_state)| TopologyAnnouncerRoute {
11325                        sender_id: sender_id.clone(),
11326                        reachable_endpoints: sender_state
11327                            .reachable
11328                            .iter()
11329                            .copied()
11330                            .filter(|ep| !discovery::is_router_control_endpoint(*ep))
11331                            .collect(),
11332                        reachable_network_variables: sender_state
11333                            .reachable_network_variables
11334                            .clone(),
11335                        reachable_timesync_sources: sender_state.reachable_timesync_sources.clone(),
11336                        routers: sender_state.topology_boards.clone(),
11337                        last_seen_ms: sender_state.last_seen_ms,
11338                        age_ms: now_ms.saturating_sub(sender_state.last_seen_ms),
11339                    })
11340                    .collect();
11341                Some(TopologySideRoute {
11342                    side_id,
11343                    side_name: side.name.to_string(),
11344                    reachable_endpoints: route
11345                        .reachable
11346                        .iter()
11347                        .copied()
11348                        .filter(|ep| !discovery::is_router_control_endpoint(*ep))
11349                        .collect(),
11350                    reachable_network_variables: route.reachable_network_variables.clone(),
11351                    reachable_timesync_sources: route.reachable_timesync_sources.clone(),
11352                    announcers,
11353                    last_seen_ms: route.last_seen_ms,
11354                    age_ms: now_ms.saturating_sub(route.last_seen_ms),
11355                })
11356            })
11357            .collect();
11358        let routers = self.advertised_discovery_topology_for_link_locked(&st, now_ms, true, None);
11359        let advertised_endpoints =
11360            self.advertised_discovery_endpoints_for_link_locked(&st, now_ms, true, None);
11361        let advertised_timesync_sources =
11362            self.advertised_discovery_timesync_sources_for_link_locked(&st, now_ms, None);
11363        let links = discovery::topology_links_from_boards(&routers);
11364        TopologySnapshot {
11365            advertised_endpoints,
11366            advertised_timesync_sources,
11367            routers,
11368            links,
11369            routes,
11370            current_announce_interval_ms: st.discovery_cadence.current_interval_ms,
11371            next_announce_ms: st.discovery_cadence.next_announce_ms,
11372        }
11373    }
11374
11375    #[cfg(feature = "discovery")]
11376    pub fn client_stats(&self, sender_id: &str) -> Option<ClientStatsSnapshot> {
11377        let now_ms = self.clock.now_ms();
11378        let st = self.state.lock();
11379        let mut side_ids = Vec::new();
11380        let mut side_names = Vec::new();
11381        let mut last_seen_ms = None::<u64>;
11382        let mut reachable_endpoints = Vec::new();
11383        let mut reachable_timesync_sources = Vec::new();
11384        let mut packets_sent = 0u64;
11385        let mut packets_received = 0u64;
11386        let mut bytes_sent = 0u64;
11387        let mut bytes_received = 0u64;
11388
11389        for (side_id, route) in &st.discovery_routes {
11390            let Some(sender_state) = route.announcers.get(sender_id) else {
11391                continue;
11392            };
11393            side_ids.push(*side_id);
11394            if let Some(side_name) = st
11395                .sides
11396                .get(*side_id)
11397                .and_then(|side| side.as_ref())
11398                .map(|side| side.name.clone())
11399            {
11400                side_names.push(side_name.to_string());
11401            }
11402            last_seen_ms = Some(last_seen_ms.unwrap_or(0).max(sender_state.last_seen_ms));
11403            reachable_endpoints.extend(sender_state.reachable.iter().copied());
11404            reachable_timesync_sources
11405                .extend(sender_state.reachable_timesync_sources.iter().cloned());
11406            if let Some(stats) = st.side_runtime_stats.get(side_id) {
11407                packets_sent = packets_sent.saturating_add(stats.tx_packets);
11408                packets_received = packets_received.saturating_add(stats.rx_packets);
11409                bytes_sent = bytes_sent.saturating_add(stats.tx_bytes);
11410                bytes_received = bytes_received.saturating_add(stats.rx_bytes);
11411            }
11412        }
11413
11414        if side_ids.is_empty() {
11415            return None;
11416        }
11417        reachable_endpoints.retain(|ep| !discovery::is_router_control_endpoint(*ep));
11418        crate::small_sort::sort(&mut reachable_endpoints);
11419        reachable_endpoints.dedup();
11420        reachable_timesync_sources.sort_unstable();
11421        reachable_timesync_sources.dedup();
11422        crate::small_sort::sort(&mut side_ids);
11423        side_ids.dedup();
11424        side_names.sort_unstable();
11425        side_names.dedup();
11426        let age_ms = last_seen_ms.map(|seen| now_ms.saturating_sub(seen));
11427        Some(ClientStatsSnapshot {
11428            sender_id: sender_id.to_string(),
11429            connected: age_ms.is_some_and(|age| age <= DISCOVERY_ROUTE_TTL_MS),
11430            side_ids,
11431            side_names,
11432            last_seen_ms,
11433            age_ms,
11434            reachable_endpoints,
11435            reachable_timesync_sources,
11436            packets_sent,
11437            packets_received,
11438            bytes_sent,
11439            bytes_received,
11440        })
11441    }
11442
11443    /// Copy bounded metadata into caller storage without allocating. Returns
11444    /// the total pending count across all pages. Copy begins at `first`; rows
11445    /// outside the pending range and unused owner slots are zeroed.
11446    pub fn copy_reliable_pending_stats(
11447        &self,
11448        first: usize,
11449        rows: &mut [crate::diagnostics::ReliablePendingRow],
11450    ) -> usize {
11451        rows.fill(crate::diagnostics::ReliablePendingRow::default());
11452        let st = self.state.lock();
11453        for (row, (&id, sent)) in rows
11454            .iter_mut()
11455            .zip(st.end_to_end_reliable_tx.iter().skip(first))
11456        {
11457            row.packet_id = id;
11458            row.last_send_ms = sent.last_send_ms;
11459            row.data_type = Self::item_data_type(&sent.data).map_or(0, DataType::as_u32);
11460            row.pending_count = sent.pending_destinations.len().min(u32::MAX as usize) as u32;
11461            row.retries = sent.retries;
11462            row.flags = u32::from(sent.queued)
11463                | (u32::from(sent.waiting_for_predecessor) << 1)
11464                | (u32::from(
11465                    st.reliable_return_routes
11466                        .get(&id)
11467                        .is_some_and(|route| route.upstream_contract.is_some()),
11468                ) << 2)
11469                | (u32::from(sent.tracked_destinations) << 3);
11470            for (out, owner) in row
11471                .pending_owners
11472                .iter_mut()
11473                .zip(sent.pending_destinations.keys())
11474            {
11475                *out = *owner;
11476            }
11477        }
11478        st.end_to_end_reliable_tx.len()
11479    }
11480
11481    /// Snapshot outstanding reliable deliveries for diagnostics. Allocates
11482    /// metadata bounded by the existing pending window; never clones payloads
11483    /// or changes retry timing. Not needed in the normal dispatch hot path.
11484    pub fn export_reliable_pending_stats(&self) -> Vec<crate::diagnostics::ReliablePendingStats> {
11485        let st = self.state.lock();
11486        st.end_to_end_reliable_tx
11487            .iter()
11488            .filter_map(|(&packet_id, sent)| {
11489                Some(crate::diagnostics::ReliablePendingStats {
11490                    packet_id,
11491                    data_type: Self::item_data_type(&sent.data).ok()?,
11492                    pending_destinations: sent.pending_destinations.keys().copied().collect(),
11493                    retries: sent.retries,
11494                    queued: sent.queued,
11495                    waiting_for_predecessor: sent.waiting_for_predecessor,
11496                    last_send_ms: sent.last_send_ms,
11497                })
11498            })
11499            .collect()
11500    }
11501
11502    pub fn export_runtime_stats(&self) -> RuntimeStatsSnapshot {
11503        let now_ms = self.clock.now_ms();
11504        let isr_rx = self.isr_rx_queue.snapshot().unwrap_or((0, 0));
11505        let st = self.state.lock();
11506
11507        let mut sides = Vec::new();
11508        for (side_id, side) in st.sides.iter().enumerate() {
11509            let Some(side) = side.as_ref() else { continue };
11510            let stats = st
11511                .side_runtime_stats
11512                .get(&side_id)
11513                .cloned()
11514                .unwrap_or_default();
11515            let adaptive = st
11516                .adaptive_route_stats
11517                .get(&side_id)
11518                .cloned()
11519                .unwrap_or_default()
11520                .snapshot(now_ms, true);
11521            let (tx_template_count, rx_template_count) = st
11522                .side_transport
11523                .get(&side_id)
11524                .map(|state| (state.tx_template_count(), state.rx_template_count()))
11525                .unwrap_or((0, 0));
11526            let mut data_types: Vec<RuntimeTypeStats> = stats
11527                .data_types
11528                .into_iter()
11529                .map(|(ty, item)| RuntimeTypeStats {
11530                    data_type: DataType(ty),
11531                    tx_packets: item.tx_packets,
11532                    tx_bytes: item.tx_bytes,
11533                    rx_packets: item.rx_packets,
11534                    rx_bytes: item.rx_bytes,
11535                    relayed_tx_packets: item.relayed_tx_packets,
11536                    relayed_tx_bytes: item.relayed_tx_bytes,
11537                    relayed_rx_packets: item.relayed_rx_packets,
11538                    relayed_rx_bytes: item.relayed_rx_bytes,
11539                    tx_retries: item.tx_retries,
11540                    handler_failures: item.handler_failures,
11541                })
11542                .collect();
11543            data_types.sort_unstable_by_key(|item| item.data_type.as_u32());
11544            sides.push(RuntimeSideStats {
11545                side_id,
11546                side_name: side.name.to_string(),
11547                reliable_enabled: side.opts.reliable_enabled,
11548                link_local_enabled: side.opts.link_local_enabled,
11549                header_template_enabled: side.opts.header_template_enabled,
11550                max_frame_bytes: side.opts.max_frame_bytes,
11551                compact_header_target_bytes: side.opts.compact_header_target_bytes,
11552                side_transport_profile: side.opts.effective_transport_profile().as_str(),
11553                ingress_enabled: side.opts.ingress_enabled,
11554                egress_enabled: side.opts.egress_enabled,
11555                tx_packets: stats.tx_packets,
11556                tx_bytes: stats.tx_bytes,
11557                rx_packets: stats.rx_packets,
11558                rx_bytes: stats.rx_bytes,
11559                relayed_tx_packets: stats.relayed_tx_packets,
11560                relayed_tx_bytes: stats.relayed_tx_bytes,
11561                relayed_rx_packets: stats.relayed_rx_packets,
11562                relayed_rx_bytes: stats.relayed_rx_bytes,
11563                local_delivery_packets: stats.local_delivery_packets,
11564                tx_retries: stats.tx_retries,
11565                tx_handler_failures: stats.tx_handler_failures,
11566                local_handler_failures: stats.local_handler_failures,
11567                total_handler_retries: stats.total_handler_retries,
11568                side_transport_full_frames: stats.side_transport_full_frames,
11569                side_transport_compact_frames: stats.side_transport_compact_frames,
11570                side_transport_compact_delta_frames: stats.side_transport_compact_delta_frames,
11571                side_transport_compact_omitted_timestamp_frames: stats
11572                    .side_transport_compact_omitted_timestamp_frames,
11573                side_transport_chunk_frames: stats.side_transport_chunk_frames,
11574                side_transport_raw_bytes: stats.side_transport_raw_bytes,
11575                side_transport_wire_bytes: stats.side_transport_wire_bytes,
11576                side_transport_bytes_saved: stats.side_transport_bytes_saved,
11577                side_transport_min_compact_overhead_bytes: stats
11578                    .side_transport_min_compact_overhead_bytes,
11579                side_transport_max_compact_overhead_bytes: stats
11580                    .side_transport_max_compact_overhead_bytes,
11581                side_transport_compact_target_misses: stats.side_transport_compact_target_misses,
11582                side_transport_template_evictions: stats.side_transport_template_evictions,
11583                side_transport_tx_template_count: tx_template_count,
11584                side_transport_rx_template_count: rx_template_count,
11585                max_side_transport_templates: side.opts.max_side_transport_templates,
11586                adaptive,
11587                data_types,
11588            });
11589        }
11590
11591        let mut route_modes: Vec<RouteModeStats> = st
11592            .route_selection_cursors
11593            .iter()
11594            .map(|(src, cursor)| RouteModeStats {
11595                src_side_id: *src,
11596                selection_mode: st.source_route_modes.get(src).copied(),
11597                cursor: *cursor,
11598            })
11599            .collect();
11600        for src in st.source_route_modes.keys() {
11601            if !route_modes.iter().any(|mode| mode.src_side_id == *src) {
11602                route_modes.push(RouteModeStats {
11603                    src_side_id: *src,
11604                    selection_mode: st.source_route_modes.get(src).copied(),
11605                    cursor: 0,
11606                });
11607            }
11608        }
11609        route_modes.sort_unstable_by_key(|mode| mode.src_side_id.unwrap_or(usize::MAX));
11610
11611        let mut route_overrides: Vec<RouteOverrideStats> = st
11612            .route_overrides
11613            .iter()
11614            .map(|((src, dst), enabled)| RouteOverrideStats {
11615                src_side_id: *src,
11616                dst_side_id: *dst,
11617                enabled: *enabled,
11618            })
11619            .collect();
11620        route_overrides.sort_unstable_by_key(|item| {
11621            (item.src_side_id.unwrap_or(usize::MAX), item.dst_side_id)
11622        });
11623
11624        let mut typed_route_overrides: Vec<TypedRouteOverrideStats> = st
11625            .typed_route_overrides
11626            .iter()
11627            .map(|((src, ty, dst), enabled)| TypedRouteOverrideStats {
11628                src_side_id: *src,
11629                data_type: DataType(*ty),
11630                dst_side_id: *dst,
11631                enabled: *enabled,
11632            })
11633            .collect();
11634        typed_route_overrides.sort_unstable_by_key(|item| {
11635            (
11636                item.src_side_id.unwrap_or(usize::MAX),
11637                item.data_type.as_u32(),
11638                item.dst_side_id,
11639            )
11640        });
11641
11642        let mut route_weights: Vec<RouteWeightStats> = st
11643            .route_weights
11644            .iter()
11645            .map(|((src, dst), weight)| RouteWeightStats {
11646                src_side_id: *src,
11647                dst_side_id: *dst,
11648                weight: *weight,
11649            })
11650            .collect();
11651        route_weights.sort_unstable_by_key(|item| {
11652            (item.src_side_id.unwrap_or(usize::MAX), item.dst_side_id)
11653        });
11654
11655        let mut route_priorities: Vec<RoutePriorityStats> = st
11656            .route_priorities
11657            .iter()
11658            .map(|((src, dst), priority)| RoutePriorityStats {
11659                src_side_id: *src,
11660                dst_side_id: *dst,
11661                priority: *priority,
11662            })
11663            .collect();
11664        route_priorities.sort_unstable_by_key(|item| {
11665            (item.src_side_id.unwrap_or(usize::MAX), item.dst_side_id)
11666        });
11667
11668        #[cfg(feature = "discovery")]
11669        let discovery = DiscoveryRuntimeStats {
11670            route_count: st.discovery_routes.len(),
11671            announcer_count: st
11672                .discovery_routes
11673                .values()
11674                .map(|route| route.announcers.len())
11675                .sum(),
11676            current_announce_interval_ms: Some(st.discovery_cadence.current_interval_ms),
11677            next_announce_ms: Some(st.discovery_cadence.next_announce_ms),
11678        };
11679        #[cfg(not(feature = "discovery"))]
11680        let discovery = DiscoveryRuntimeStats {
11681            route_count: 0,
11682            announcer_count: 0,
11683            current_announce_interval_ms: None,
11684            next_announce_ms: None,
11685        };
11686
11687        RuntimeStatsSnapshot {
11688            sides,
11689            route_modes,
11690            route_overrides,
11691            typed_route_overrides,
11692            route_weights,
11693            route_priorities,
11694            queues: QueueRuntimeStats {
11695                rx_len: isr_rx.0.saturating_add(st.received_queue.len()),
11696                rx_bytes: isr_rx.1.saturating_add(st.received_queue.bytes_used()),
11697                tx_len: st.transmit_queue.len(),
11698                tx_bytes: st.transmit_queue.bytes_used(),
11699                replay_len: 0,
11700                replay_bytes: 0,
11701                recent_rx_len: st.recent_rx.len(),
11702                recent_rx_bytes: st.recent_rx.bytes_used(),
11703                reliable_rx_buffered_len: st.reliable_rx_buffer_len(),
11704                reliable_rx_buffered_bytes: st.reliable_rx_buffered_bytes(),
11705                shared_queue_bytes_used: st.shared_queue_bytes_used(),
11706            },
11707            reliable: ReliableRuntimeStats {
11708                reliable_return_route_count: st.reliable_return_routes.len(),
11709                end_to_end_pending_count: st.end_to_end_reliable_tx.len(),
11710                end_to_end_pending_destination_count: st
11711                    .end_to_end_reliable_tx
11712                    .values()
11713                    .map(|sent| sent.pending_destinations.len())
11714                    .sum(),
11715                end_to_end_acked_cache_count: 0,
11716            },
11717            discovery,
11718            total_handler_failures: st.total_handler_failures,
11719            total_handler_retries: st.total_handler_retries,
11720        }
11721    }
11722
11723    /// Export current router memory usage/layout as JSON for profiling.
11724    pub fn export_memory_layout_json(&self) -> String {
11725        let isr_rx = self.isr_rx_queue.snapshot().unwrap_or((0, 0));
11726        let st = self.state.lock();
11727        #[cfg(feature = "discovery")]
11728        let discovery_bytes = st.discovery_bytes_used();
11729        #[cfg(not(feature = "discovery"))]
11730        let discovery_bytes = 0usize;
11731        let schema_bytes = crate::config::schema_bytes_used();
11732        let network_variable_cache_bytes = st
11733            .managed_variable_latest
11734            .values()
11735            .map(|entry| entry.packet.byte_cost())
11736            .sum::<usize>();
11737        let mut out = String::new();
11738        let memory = st.memory;
11739        let _ = fmt::Write::write_fmt(
11740            &mut out,
11741            format_args!(
11742                "{{\"kind\":\"router\",\
11743                 \"shared_queue_bytes_used\":{},\"shared_queue_bytes_allocated\":{},\
11744                 \"rx_queue_bytes_used\":{},\"rx_queue_bytes_allocated\":{},\"rx_queue_len\":{},\
11745                 \"isr_rx_queue_bytes_used\":{},\"isr_rx_queue_bytes_allocated\":{},\"isr_rx_queue_len\":{},\
11746                 \"tx_queue_bytes_used\":{},\"tx_queue_bytes_allocated\":{},\"tx_queue_len\":{},\
11747                 \"recent_rx_bytes_used\":{},\"recent_rx_bytes_allocated\":{},\"recent_rx_len\":{},\
11748                 \"reliable_rx_buffer_bytes_used\":{},\"reliable_rx_buffer_bytes_allocated\":{},\"reliable_rx_buffer_len\":{},\
11749                 \"discovery_bytes_used\":{},\"discovery_bytes_allocated\":{},\
11750                 \"schema_bytes_used\":{},\"schema_bytes_allocated\":{},\
11751                 \"network_variable_cache_bytes_used\":{},\"network_variable_cache_bytes_allocated\":{},\"network_variable_cache_len\":{}}}",
11752                st.shared_queue_bytes_used(),
11753                memory.max_queue_budget,
11754                st.received_queue.bytes_used(),
11755                st.received_queue.max_bytes(),
11756                st.received_queue.len(),
11757                isr_rx.1,
11758                memory.max_queue_budget,
11759                isr_rx.0,
11760                st.transmit_queue.bytes_used(),
11761                st.transmit_queue.max_bytes(),
11762                st.transmit_queue.len(),
11763                st.recent_rx.bytes_used(),
11764                st.recent_rx.max_bytes(),
11765                st.recent_rx.len(),
11766                st.reliable_rx_buffered_bytes(),
11767                memory.max_queue_budget,
11768                st.reliable_rx_buffer_len(),
11769                discovery_bytes,
11770                memory.max_queue_budget,
11771                schema_bytes,
11772                memory.max_queue_budget,
11773                network_variable_cache_bytes,
11774                memory.max_queue_budget,
11775                st.managed_variable_latest.len(),
11776            ),
11777        );
11778        out
11779    }
11780
11781    #[cfg(test)]
11782    pub(crate) fn debug_end_to_end_pending_destination_count(
11783        &self,
11784        packet_id: u64,
11785    ) -> Option<usize> {
11786        let st = self.state.lock();
11787        st.end_to_end_reliable_tx
11788            .get(&packet_id)
11789            .map(|sent| sent.pending_destinations.len())
11790    }
11791
11792    #[cfg(test)]
11793    pub(crate) fn debug_end_to_end_tracked_count(&self) -> usize {
11794        let st = self.state.lock();
11795        st.end_to_end_reliable_tx.len()
11796    }
11797
11798    #[cfg(test)]
11799    pub(crate) fn debug_reliable_return_route_count(&self) -> usize {
11800        let st = self.state.lock();
11801        st.reliable_return_routes.len()
11802    }
11803
11804    #[cfg(test)]
11805    pub(crate) fn debug_side_storage(&self) -> (usize, usize) {
11806        let st = self.state.lock();
11807        (st.sides.len(), st.sides.capacity())
11808    }
11809
11810    #[cfg(test)]
11811    pub(crate) fn debug_queue_lengths(&self) -> (usize, usize, usize) {
11812        let st = self.state.lock();
11813        (
11814            st.received_queue.len(),
11815            st.transmit_queue.len(),
11816            st.recent_rx.len(),
11817        )
11818    }
11819
11820    #[cfg(test)]
11821    pub(crate) fn debug_shared_queue_bytes_used(&self) -> usize {
11822        let st = self.state.lock();
11823        st.shared_queue_bytes_used()
11824    }
11825
11826    #[cfg(test)]
11827    pub(crate) fn debug_recent_rx_capacity(&self) -> (usize, usize) {
11828        let st = self.state.lock();
11829        (st.recent_rx.capacity(), st.recent_rx.max_bytes())
11830    }
11831
11832    /// Compute a de-dupe hash for a RouterItem.
11833    /// Uses packet ID for Packet items, and attempts to extract packet ID from
11834    /// packed bytes. If extraction fails, hashes raw bytes as a fallback.
11835    fn get_hash(item: &RouterItem) -> u64 {
11836        match item {
11837            RouterItem::Packet(pkt) => pkt.packet_id(),
11838            RouterItem::Packed(bytes) => {
11839                match wire_format::packet_id_from_wire(bytes.as_ref()) {
11840                    Ok(id) => id,
11841                    Err(_e) => {
11842                        // Fallback: if bytes are malformed (or compression feature mismatch),
11843                        // hash raw bytes so we can still dedupe identical network duplicates.
11844                        let h: u64 = 0x9E37_79B9_7F4A_7C15;
11845                        hash_bytes_u64(h, bytes.as_ref())
11846                    }
11847                }
11848            }
11849        }
11850    }
11851
11852    /// Remove a hash from the ring buffer of recent RX IDs.
11853    fn remove_pkt_id(&self, item: &RouterItem) {
11854        let hash = Self::get_hash(item);
11855        let mut st = self.state.lock();
11856        st.recent_rx.remove_value(&hash);
11857    }
11858
11859    /// Compute a de-dupe ID for a RouterItem and record it.
11860    /// Returns true if this item was seen recently (and should be skipped).
11861    fn is_duplicate_pkt(&self, item: &RouterItem) -> TelemetryResult<bool> {
11862        let id = Self::get_hash(item);
11863        let mut st = self.state.lock();
11864        if st.recent_rx.contains(&id) {
11865            Ok(true)
11866        } else {
11867            st.push_recent_rx(id)?;
11868            Ok(false)
11869        }
11870    }
11871
11872    /// Error helper when we have a full Packet.
11873    ///
11874    /// Sends a TelemetryError packet to all local endpoints except the failed one (if any).
11875    /// If no local endpoints remain, falls back to `fallback_stdout`.
11876    fn handle_callback_error(
11877        &self,
11878        pkt: &Packet,
11879        dest: Option<DataEndpoint>,
11880        e: TelemetryError,
11881        called_from_queue: bool,
11882    ) -> TelemetryResult<()> {
11883        let device = self.sender_arc();
11884        let error_msg = match dest {
11885            Some(failed_local) => format!(
11886                "Handler for endpoint {:?} failed on device {:?}: {:?}",
11887                failed_local, device, e
11888            ),
11889            None => format!("TX Handler failed on device {:?}: {:?}", device, e),
11890        };
11891
11892        let mut recipients: Vec<DataEndpoint> = pkt
11893            .endpoints()
11894            .iter()
11895            .copied()
11896            .filter(|&ep| self.cfg.is_local_endpoint(ep))
11897            .collect();
11898        crate::small_sort::sort(&mut recipients);
11899        recipients.dedup();
11900
11901        if let Some(failed_local) = dest {
11902            recipients.retain(|&ep| ep != failed_local);
11903        }
11904
11905        // A side TX failure cannot be reported through the same failed
11906        // transport. Doing so recursively creates TelemetryError packets until
11907        // an embedded caller exhausts its stack.
11908        if recipients.is_empty() && dest.is_none() {
11909            fallback_stdout(&error_msg);
11910            return Ok(());
11911        }
11912
11913        // Local endpoint failures may still be reported to the packet's other
11914        // endpoints when no local error recipient exists.
11915        if recipients.is_empty() {
11916            recipients = pkt.endpoints().to_vec();
11917            crate::small_sort::sort(&mut recipients);
11918            recipients.dedup();
11919            if let Some(failed_local) = dest {
11920                recipients.retain(|&ep| ep != failed_local);
11921            }
11922        }
11923
11924        if recipients.is_empty() {
11925            fallback_stdout(&error_msg);
11926            return Ok(());
11927        }
11928
11929        let payload = make_error_payload(&error_msg);
11930
11931        let sender = self.sender_arc();
11932        let error_pkt = Packet::new(
11933            DataType::TelemetryError,
11934            &recipients,
11935            sender.as_ref(),
11936            self.packet_timestamp_ms(),
11937            crate::shared_bytes::convert(payload),
11938        )?;
11939
11940        self.emit_internal_tx(
11941            RouterTxItem::Broadcast(RouterItem::Packet(error_pkt)),
11942            false,
11943            called_from_queue,
11944        )
11945    }
11946
11947    // ---------- PUBLIC API: queues ----------
11948
11949    /// Drain the transmit queue fully.
11950    #[inline]
11951    pub fn process_tx_queue(&self) -> TelemetryResult<()> {
11952        self.process_tx_queue_with_timeout(0)
11953    }
11954
11955    /// Drain both TX and RX queues fully (same semantics as `*_with_timeout(0)`).
11956    #[inline]
11957    pub fn process_all_queues(&self) -> TelemetryResult<()> {
11958        self.process_all_queues_with_timeout(0)
11959    }
11960
11961    /// Clear both the transmit and receive queues without processing.
11962    #[inline]
11963    pub fn clear_queues(&self) {
11964        let mut st = self.state.lock();
11965        st.transmit_queue.clear();
11966        st.received_queue.clear();
11967        drop(st);
11968        let _ = self.isr_rx_queue.clear();
11969    }
11970
11971    /// Clear only the receive queue without processing.
11972    #[inline]
11973    pub fn clear_rx_queue(&self) {
11974        let mut st = self.state.lock();
11975        st.received_queue.clear();
11976        drop(st);
11977        let _ = self.isr_rx_queue.clear();
11978    }
11979
11980    /// Clear only the transmit queue without processing.
11981    #[inline]
11982    pub fn clear_tx_queue(&self) {
11983        let mut st = self.state.lock();
11984        st.transmit_queue.clear();
11985        st.tx_priority_burst = 0;
11986    }
11987
11988    fn pop_transmit_queue_locked(st: &mut RouterInner, now_ms: u64) -> Option<TxQueued> {
11989        st.transmit_queue.pop_front_fair_if(
11990            &mut st.tx_priority_burst,
11991            MAX_PRIORITY_BURST,
11992            |item| item.priority,
11993            |item| item.retry_after_ms <= now_ms,
11994        )
11995    }
11996
11997    /// Process packets in the transmit queue for up to `timeout_ms` milliseconds.
11998    /// If `timeout_ms == 0`, drains the queue fully.
11999    fn process_tx_queue_with_timeout_impl(&self, timeout_ms: u32) -> TelemetryResult<()> {
12000        self.expire_side_chunks();
12001        let start = self.clock.now_ms();
12002        loop {
12003            let received_control = crate::memory_admission::allow_drain(
12004                self.drain_queued_control_rx_before_tx(start, timeout_ms),
12005            )?;
12006            let control_budget_used = received_control
12007                && timeout_ms != 0
12008                && self.clock.now_ms().wrapping_sub(start) >= u64::from(timeout_ms);
12009            // Complete one RX/TX pair even after a cooperative RX overrun.
12010            // Defer timeout cancellation until queued receipts are serviced.
12011            if !control_budget_used {
12012                crate::memory_admission::allow_drain(self.process_reliable_timeouts())?;
12013                crate::memory_admission::allow_drain(self.process_end_to_end_reliable_timeouts())?;
12014            }
12015
12016            let pkt_opt = {
12017                let mut st = self.state.lock();
12018                Self::pop_transmit_queue_locked(&mut st, self.clock.now_ms())
12019            };
12020            let Some(pkt) = pkt_opt else { break };
12021            self.tx_item_impl(pkt.item, pkt.ignore_local, true)?;
12022            if timeout_ms != 0 && self.clock.now_ms().wrapping_sub(start) >= timeout_ms as u64 {
12023                break;
12024            }
12025        }
12026        Ok(())
12027    }
12028
12029    /// Process packets in the transmit queue for up to `timeout_ms` milliseconds.
12030    /// If `timeout_ms == 0`, drains the queue fully.
12031    pub fn process_tx_queue_with_timeout(&self, timeout_ms: u32) -> TelemetryResult<()> {
12032        #[cfg(feature = "timesync")]
12033        let _ = crate::memory_admission::allow_drain(self.poll_timesync())?;
12034        #[cfg(feature = "discovery")]
12035        let _ = crate::memory_admission::allow_drain(self.poll_discovery())?;
12036        self.process_tx_queue_with_timeout_impl(timeout_ms)
12037    }
12038
12039    /// Drain queued transmit work without generating periodic discovery/time-sync
12040    /// announcements. Reliable retries and queued discovery reception are still
12041    /// serviced. Applications using this between ingress bursts must also call
12042    /// `periodic` or the normal queue methods regularly for maintenance.
12043    /// A zero timeout drains the queue fully.
12044    pub fn dispatch_tx_queue_with_timeout(&self, timeout_ms: u32) -> TelemetryResult<()> {
12045        self.process_tx_queue_with_timeout_impl(timeout_ms)
12046    }
12047
12048    /// Process a single queued receive item.
12049    #[inline]
12050    fn process_rx_queue_item(&self, item: RouterRxItem) -> TelemetryResult<()> {
12051        self.rx_item(&item, true)
12052    }
12053
12054    /// Process packets in the receive queue for up to `timeout_ms` milliseconds.
12055    /// If `timeout_ms == 0`, drains the queue fully.
12056    fn process_rx_queue_with_timeout_impl(&self, timeout_ms: u32) -> TelemetryResult<()> {
12057        self.expire_side_chunks();
12058        let start = self.clock.now_ms();
12059        loop {
12060            let item_opt = self.isr_rx_queue.pop_front().unwrap_or(None).or_else(|| {
12061                let mut st = self.state.lock();
12062                st.received_queue.pop_front()
12063            });
12064            let Some(item) = item_opt else { break };
12065            self.process_rx_queue_item(item)?;
12066            if timeout_ms != 0 && self.clock.now_ms().wrapping_sub(start) >= timeout_ms as u64 {
12067                break;
12068            }
12069        }
12070        Ok(())
12071    }
12072
12073    /// Process packets in the receive queue for up to `timeout_ms` milliseconds.
12074    /// If `timeout_ms == 0`, drains the queue fully.
12075    pub fn process_rx_queue_with_timeout(&self, timeout_ms: u32) -> TelemetryResult<()> {
12076        #[cfg(feature = "timesync")]
12077        let _ = crate::memory_admission::allow_drain(self.poll_timesync())?;
12078        #[cfg(feature = "discovery")]
12079        let _ = crate::memory_admission::allow_drain(self.poll_discovery())?;
12080        self.process_rx_queue_with_timeout_impl(timeout_ms)
12081    }
12082
12083    /// Process both transmit and receive queues for up to `timeout_ms` milliseconds.
12084    /// If `timeout_ms == 0`, drains both queues fully.
12085    fn process_all_queues_with_timeout_impl(&self, timeout_ms: u32) -> TelemetryResult<()> {
12086        self.expire_side_chunks();
12087        // Share the deadline between directions. Splitting a 1 ms budget in
12088        // half rounded TX down to zero and serviced only one transmit item,
12089        // even when the remainder of the pass was idle. One TX/RX pair is
12090        // atomic for fairness; a slow handler may overrun the deadline.
12091        let start = self.clock.now_ms();
12092        loop {
12093            let received_control = crate::memory_admission::allow_drain(
12094                self.drain_queued_control_rx_before_tx(start, timeout_ms),
12095            )?;
12096            let mut did_any = received_control;
12097            let control_budget_used = received_control
12098                && timeout_ms != 0
12099                && self.clock.now_ms().wrapping_sub(start) >= u64::from(timeout_ms);
12100            if !control_budget_used {
12101                crate::memory_admission::allow_drain(self.process_reliable_timeouts())?;
12102                crate::memory_admission::allow_drain(self.process_end_to_end_reliable_timeouts())?;
12103            }
12104
12105            if let Some(pkt) = {
12106                let mut st = self.state.lock();
12107                let now = self.clock.now_ms();
12108                let inner = &mut *st;
12109                inner.transmit_queue.pop_front_fair_if(
12110                    &mut inner.tx_priority_burst,
12111                    MAX_PRIORITY_BURST,
12112                    |item| item.priority,
12113                    |item| {
12114                        item.retry_after_ms <= now && (!control_budget_used || item.priority >= 200)
12115                    },
12116                )
12117            } {
12118                self.tx_item_impl(pkt.item, pkt.ignore_local, true)?;
12119                did_any = true;
12120            }
12121            // Control reception must not starve queued ACKs/transmissions.
12122            if control_budget_used {
12123                return Ok(());
12124            }
12125            if let Some(item) = self.isr_rx_queue.pop_front().unwrap_or(None).or_else(|| {
12126                let mut st = self.state.lock();
12127                st.received_queue.pop_front()
12128            }) {
12129                self.process_rx_queue_item(item)?;
12130                did_any = true;
12131            }
12132            if !did_any
12133                || (timeout_ms != 0
12134                    && self.clock.now_ms().wrapping_sub(start) >= u64::from(timeout_ms))
12135            {
12136                break;
12137            }
12138        }
12139
12140        Ok(())
12141    }
12142
12143    /// Process both transmit and receive queues for up to `timeout_ms` milliseconds.
12144    /// If `timeout_ms == 0`, drains both queues fully.
12145    pub fn process_all_queues_with_timeout(&self, timeout_ms: u32) -> TelemetryResult<()> {
12146        #[cfg(feature = "timesync")]
12147        let _ = crate::memory_admission::allow_drain(self.poll_timesync())?;
12148        #[cfg(feature = "discovery")]
12149        let _ = crate::memory_admission::allow_drain(self.poll_discovery())?;
12150        self.process_all_queues_with_timeout_impl(timeout_ms)
12151    }
12152
12153    /// Drain queued RX/TX work without generating periodic announcements.
12154    /// Reliable retries and received discovery are still processed. Call
12155    /// `periodic` or poll discovery/time sync separately on a regular cadence.
12156    /// The deadline is cooperative: a single callback may exceed it.
12157    pub fn drain_queues_with_timeout(&self, timeout_ms: u32) -> TelemetryResult<()> {
12158        self.process_all_queues_with_timeout_impl(timeout_ms)
12159    }
12160
12161    /// Runs one application-loop maintenance cycle.
12162    ///
12163    /// This polls built-in time sync and discovery when those features are compiled in, then
12164    /// drains queued TX/RX work for up to `timeout_ms` milliseconds.
12165    pub fn periodic(&self, timeout_ms: u32) -> TelemetryResult<()> {
12166        #[cfg(feature = "timesync")]
12167        let _ = crate::memory_admission::allow_drain(self.poll_timesync())?;
12168
12169        #[cfg(feature = "discovery")]
12170        {
12171            let _ = crate::memory_admission::allow_drain(self.poll_discovery())?;
12172        }
12173
12174        self.process_all_queues_with_timeout_impl(timeout_ms)
12175    }
12176
12177    /// Runs one application-loop maintenance cycle without polling built-in time sync.
12178    ///
12179    /// Discovery is still polled when that feature is compiled in, then queued TX/RX work is
12180    /// drained for up to `timeout_ms` milliseconds.
12181    pub fn periodic_no_timesync(&self, timeout_ms: u32) -> TelemetryResult<()> {
12182        #[cfg(feature = "discovery")]
12183        {
12184            let _ = crate::memory_admission::allow_drain(self.poll_discovery())?;
12185        }
12186
12187        self.process_all_queues_with_timeout_impl(timeout_ms)
12188    }
12189
12190    /// Enqueue an item for later transmission with flags.
12191    #[inline]
12192    fn tx_queue_item_with_flags(
12193        &self,
12194        item: RouterTxItem,
12195        ignore_local: bool,
12196    ) -> TelemetryResult<()> {
12197        self.tx_queue_item_with_flags_at(item, ignore_local, 0)
12198    }
12199
12200    fn tx_queue_item_for_retry(
12201        &self,
12202        item: RouterTxItem,
12203        ignore_local: bool,
12204    ) -> TelemetryResult<()> {
12205        self.tx_queue_item_with_flags_at(item, ignore_local, self.clock.now_ms().saturating_add(1))
12206    }
12207
12208    fn tx_queue_item_with_flags_at(
12209        &self,
12210        item: RouterTxItem,
12211        ignore_local: bool,
12212        retry_after_ms: u64,
12213    ) -> TelemetryResult<()> {
12214        let priority = match &item {
12215            RouterTxItem::Broadcast(data) => self.item_service_priority(data)?,
12216            RouterTxItem::EndToEndReplay { .. } => {
12217                Self::router_item_priority_bumped(DataType::ReliableAck)
12218            }
12219            RouterTxItem::ToSide { data, .. } => self.item_service_priority(data)?,
12220            RouterTxItem::ReliableReplay { bytes, .. } => {
12221                let ty = wire_format::peek_routing_envelope(bytes.as_ref())?.ty;
12222                Self::router_item_priority_bumped(ty)
12223            }
12224        };
12225        self.tx_queue_item_with_priority_at(item, ignore_local, priority, retry_after_ms)
12226    }
12227
12228    #[inline]
12229    fn tx_queue_item_with_priority(
12230        &self,
12231        item: RouterTxItem,
12232        ignore_local: bool,
12233        priority: u8,
12234    ) -> TelemetryResult<()> {
12235        self.tx_queue_item_with_priority_at(item, ignore_local, priority, 0)
12236    }
12237
12238    fn tx_queue_item_with_priority_at(
12239        &self,
12240        item: RouterTxItem,
12241        ignore_local: bool,
12242        priority: u8,
12243        retry_after_ms: u64,
12244    ) -> TelemetryResult<()> {
12245        let mut st = self.state.lock();
12246        st.push_transmit(TxQueued {
12247            item,
12248            ignore_local,
12249            priority,
12250            retry_after_ms,
12251        })?;
12252        Ok(())
12253    }
12254
12255    /// Enqueue an item for later transmission (default: local dispatch enabled).
12256    #[inline]
12257    fn tx_queue_item(&self, item: RouterTxItem) -> TelemetryResult<()> {
12258        self.tx_queue_item_with_flags(item, false)
12259    }
12260
12261    #[inline]
12262    fn try_enter_side_tx(&self) -> Option<crate::lock::ReentryGuard<'_>> {
12263        self.side_tx_gate.try_enter()
12264    }
12265
12266    #[inline]
12267    fn side_tx_active(&self) -> bool {
12268        self.side_tx_gate.is_active()
12269    }
12270
12271    // ---------- PUBLIC API: RX queue ----------
12272
12273    /// Drain the receive queue fully.
12274    #[inline]
12275    pub fn process_rx_queue(&self) -> TelemetryResult<()> {
12276        self.process_rx_queue_with_timeout(0)
12277    }
12278
12279    /// Enqueue packed bytes for RX processing as locally-originated input.
12280    #[inline]
12281    pub fn rx_packed_queue(&self, bytes: &[u8]) -> TelemetryResult<()> {
12282        crate::memory_admission::check_receive(bytes)?;
12283        let data = RouterItem::Packed(crate::shared_bytes::convert(Arc::<[u8]>::from(bytes)));
12284        let priority = self.item_service_priority(&data)?;
12285        let mut st = self.state.lock();
12286        st.push_received(RouterRxItem {
12287            src: None,
12288            data,
12289            priority,
12290        })?;
12291        Ok(())
12292    }
12293
12294    /// ISR-safe, non-blocking enqueue of packed bytes for RX processing.
12295    ///
12296    /// Returns `TelemetryError::Io("rx queue busy")` if another context is
12297    /// currently mutating the ISR RX queue.
12298    #[inline]
12299    pub fn rx_packed_queue_isr(&self, bytes: &[u8]) -> TelemetryResult<()> {
12300        crate::memory_admission::check_receive(bytes)?;
12301        let data = RouterItem::Packed(crate::shared_bytes::convert(Arc::<[u8]>::from(bytes)));
12302        let priority = Self::router_item_priority(&data)?;
12303        self.isr_rx_queue.push_back_prioritized(RouterRxItem {
12304            src: None,
12305            data,
12306            priority,
12307        })
12308    }
12309
12310    /// Enqueue a decoded packet for RX processing as locally-originated input.
12311    #[inline]
12312    pub fn rx_queue(&self, pkt: Packet) -> TelemetryResult<()> {
12313        pkt.validate()?;
12314        let data = RouterItem::Packet(pkt);
12315        let priority = self.item_service_priority(&data)?;
12316        let mut st = self.state.lock();
12317        st.push_received(RouterRxItem {
12318            src: None,
12319            data,
12320            priority,
12321        })?;
12322        Ok(())
12323    }
12324
12325    /// ISR-safe, non-blocking enqueue of a packet for RX processing.
12326    ///
12327    /// Returns `TelemetryError::Io("rx queue busy")` if another context is
12328    /// currently mutating the ISR RX queue.
12329    #[inline]
12330    pub fn rx_queue_isr(&self, pkt: Packet) -> TelemetryResult<()> {
12331        pkt.validate()?;
12332        let data = RouterItem::Packet(pkt);
12333        let priority = Self::router_item_priority(&data)?;
12334        self.isr_rx_queue.push_back_prioritized(RouterRxItem {
12335            src: None,
12336            data,
12337            priority,
12338        })
12339    }
12340
12341    /// Enqueue a decoded packet for RX processing with an explicit ingress side.
12342    #[inline]
12343    pub fn rx_queue_from_side(&self, pkt: Packet, side: RouterSideId) -> TelemetryResult<()> {
12344        self.ensure_side_ingress_enabled(side)?;
12345        pkt.validate()?;
12346        let data = RouterItem::Packet(pkt);
12347        let priority = self.item_service_priority(&data)?;
12348        let mut st = self.state.lock();
12349        st.push_received(RouterRxItem {
12350            src: Some(side),
12351            data,
12352            priority,
12353        })?;
12354        Ok(())
12355    }
12356
12357    /// ISR-safe, non-blocking enqueue of a packet with explicit source side.
12358    ///
12359    /// Returns `TelemetryError::Io("rx queue busy")` if another context is
12360    /// currently mutating the ISR RX queue.
12361    #[inline]
12362    pub fn rx_queue_from_side_isr(&self, pkt: Packet, side: RouterSideId) -> TelemetryResult<()> {
12363        self.ensure_side_ingress_enabled(side)?;
12364        pkt.validate()?;
12365        let data = RouterItem::Packet(pkt);
12366        let priority = Self::router_item_priority(&data)?;
12367        self.isr_rx_queue.push_back_prioritized(RouterRxItem {
12368            src: Some(side),
12369            data,
12370            priority,
12371        })
12372    }
12373
12374    /// Enqueue packed bytes for RX processing with an explicit ingress side.
12375    #[inline]
12376    pub fn rx_packed_queue_from_side(
12377        &self,
12378        bytes: &[u8],
12379        side: RouterSideId,
12380    ) -> TelemetryResult<()> {
12381        self.ensure_side_ingress_enabled(side)?;
12382        let Some(decoded) = self.decode_side_transport_frame(side, bytes)? else {
12383            return Ok(());
12384        };
12385        let data = RouterItem::Packed(decoded);
12386        let priority = self.item_service_priority(&data)?;
12387        let mut st = self.state.lock();
12388        st.push_received(RouterRxItem {
12389            src: Some(side),
12390            data,
12391            priority,
12392        })?;
12393        Ok(())
12394    }
12395
12396    /// ISR-safe, non-blocking enqueue of packed bytes with source side.
12397    ///
12398    /// Returns `TelemetryError::Io("rx queue busy")` if another context is
12399    /// currently mutating the ISR RX queue.
12400    #[inline]
12401    pub fn rx_packed_queue_from_side_isr(
12402        &self,
12403        bytes: &[u8],
12404        side: RouterSideId,
12405    ) -> TelemetryResult<()> {
12406        self.ensure_side_ingress_enabled(side)?;
12407        crate::memory_admission::check_receive(bytes)?;
12408        let data = RouterItem::Packed(crate::shared_bytes::convert(Arc::<[u8]>::from(bytes)));
12409        let priority = Self::router_item_priority(&data)?;
12410        self.isr_rx_queue.push_back_prioritized(RouterRxItem {
12411            src: Some(side),
12412            data,
12413            priority,
12414        })
12415    }
12416
12417    fn retry_with_attempts<F, T>(
12418        &self,
12419        times: usize,
12420        f: F,
12421    ) -> Result<(T, usize), (TelemetryError, usize)>
12422    where
12423        F: Fn() -> TelemetryResult<T>,
12424    {
12425        let mut last_err = None;
12426        for attempt in 0..times {
12427            match f() {
12428                Ok(v) => return Ok((v, attempt + 1)),
12429                // Queue backpressure cannot clear while the same callback is
12430                // retried synchronously. Retain it for the next service pass.
12431                Err(e) if Self::is_side_tx_busy(&e) => return Err((e, attempt + 1)),
12432                Err(e) => last_err = Some((e, attempt + 1)),
12433            }
12434        }
12435        Err(last_err.expect("times > 0"))
12436    }
12437
12438    /// Check if the specified endpoint has a packet handler registered.
12439    #[inline]
12440    fn endpoint_has_packet_handler(&self, ep: DataEndpoint) -> bool {
12441        self.cfg
12442            .handlers
12443            .iter()
12444            .any(|h| h.endpoint == ep && matches!(h.handler, EndpointHandlerFn::Packet(_)))
12445    }
12446
12447    /// Check if the specified endpoint has a packed handler registered.
12448    #[inline]
12449    fn endpoint_has_packed_handler(&self, ep: DataEndpoint) -> bool {
12450        self.cfg
12451            .handlers
12452            .iter()
12453            .any(|h| h.endpoint == ep && matches!(h.handler, EndpointHandlerFn::Packed(_)))
12454    }
12455
12456    fn packet_has_local_handler(&self, pkt: &Packet) -> bool {
12457        pkt.endpoints()
12458            .iter()
12459            .copied()
12460            .any(|ep| self.endpoint_has_packet_handler(ep) || self.endpoint_has_packed_handler(ep))
12461    }
12462
12463    /// Whether receiving this packet constitutes delivery at this router.
12464    ///
12465    /// Network variables deliberately use their cache/update callback instead of a conventional
12466    /// endpoint handler. Treat that successful cache delivery as local for end-to-end ACKs;
12467    /// otherwise a reliable writer waits forever for every variable owner that does not also
12468    /// happen to register an unrelated packet handler for the variable's endpoint.
12469    fn packet_has_local_delivery(&self, pkt: &Packet) -> bool {
12470        self.packet_has_local_handler(pkt) || self.is_managed_variable_type(pkt.data_type())
12471    }
12472
12473    /// Call the specified endpoint handler with retries on failure.
12474    ///
12475    /// - `data` is present when called from RX processing (queue or immediate).
12476    /// - `pkt_for_ctx` is required for Packet handlers.
12477    /// - `env_for_ctx` provides header-only context when we haven't unpacked.
12478    fn call_handler_with_retries(
12479        &self,
12480        dest: DataEndpoint,
12481        handler: &EndpointHandler,
12482        data: Option<&[u8]>,
12483        pkt_for_ctx: Option<&Packet>,
12484        env_for_ctx: Option<&wire_format::TelemetryEnvelope>,
12485        called_from_queue: bool,
12486    ) -> TelemetryResult<()> {
12487        let owned_tmp: Option<RouterItem>;
12488
12489        let item_for_ctx: &RouterItem = match (data, pkt_for_ctx) {
12490            (Some(d), _) => {
12491                owned_tmp = Some(RouterItem::Packed(crate::shared_bytes::convert(
12492                    Arc::<[u8]>::from(d),
12493                )));
12494                owned_tmp.as_ref().unwrap()
12495            }
12496            (None, Some(pkt)) => {
12497                owned_tmp = Some(RouterItem::Packet(pkt.clone()));
12498                owned_tmp.as_ref().unwrap()
12499            }
12500            (None, None) => {
12501                debug_assert!(
12502                    false,
12503                    "call_handler_with_retries called without data or packet context"
12504                );
12505                return Ok(());
12506            }
12507        };
12508
12509        match (&handler.handler, data) {
12510            (EndpointHandlerFn::Packet(f), _) => {
12511                let pkt = pkt_for_ctx.expect("Packet handler requires Packet context");
12512                with_retries(
12513                    self,
12514                    dest,
12515                    item_for_ctx,
12516                    pkt_for_ctx,
12517                    env_for_ctx,
12518                    called_from_queue,
12519                    || f(pkt),
12520                )
12521            }
12522
12523            (EndpointHandlerFn::Packed(f), Some(bytes)) => with_retries(
12524                self,
12525                dest,
12526                item_for_ctx,
12527                pkt_for_ctx,
12528                env_for_ctx,
12529                called_from_queue,
12530                || f(bytes),
12531            ),
12532
12533            (EndpointHandlerFn::Packed(_), None) => Ok(()),
12534        }
12535    }
12536
12537    /// Error helper when we only have an envelope (no full packet).
12538    ///
12539    /// Sends a TelemetryError packet to all local endpoints except the failed one (if any).
12540    /// If no local endpoints remain, falls back to `fallback_stdout`.
12541    fn handle_callback_error_from_env(
12542        &self,
12543        env: &wire_format::TelemetryEnvelope,
12544        dest: Option<DataEndpoint>,
12545        e: TelemetryError,
12546        called_from_queue: bool,
12547    ) -> TelemetryResult<()> {
12548        let mut recipients: Vec<DataEndpoint> = env
12549            .endpoints
12550            .iter()
12551            .copied()
12552            .filter(|&ep| self.cfg.is_local_endpoint(ep))
12553            .collect();
12554        crate::small_sort::sort(&mut recipients);
12555        recipients.dedup();
12556        if let Some(failed) = dest {
12557            recipients.retain(|&ep| ep != failed);
12558        }
12559
12560        let device = self.sender_arc();
12561        let error_msg = format!(
12562            "Handler for endpoint {:?} failed on device {:?}: {:?}",
12563            dest, device, e
12564        );
12565
12566        if recipients.is_empty() && dest.is_none() {
12567            fallback_stdout(&error_msg);
12568            return Ok(());
12569        }
12570
12571        if recipients.is_empty() {
12572            recipients = env.endpoints.to_vec();
12573            crate::small_sort::sort(&mut recipients);
12574            recipients.dedup();
12575            if let Some(failed) = dest {
12576                recipients.retain(|&ep| ep != failed);
12577            }
12578        }
12579
12580        if recipients.is_empty() {
12581            fallback_stdout(&error_msg);
12582            return Ok(());
12583        }
12584
12585        let payload = make_error_payload(&error_msg);
12586
12587        let error_pkt = Packet::new(
12588            DataType::TelemetryError,
12589            &recipients,
12590            &env.sender.clone(),
12591            env.timestamp_ms,
12592            crate::shared_bytes::convert(payload),
12593        )?;
12594        self.emit_internal_tx(
12595            RouterTxItem::Broadcast(RouterItem::Packet(error_pkt)),
12596            false,
12597            called_from_queue,
12598        )
12599    }
12600
12601    fn handle_internal_reliable_packet(
12602        &self,
12603        pkt: &Packet,
12604        src: Option<RouterSideId>,
12605        called_from_queue: bool,
12606    ) -> TelemetryResult<bool> {
12607        if !matches!(
12608            pkt.data_type(),
12609            DataType::ReliableAck | DataType::ReliablePartialAck | DataType::ReliablePacketRequest
12610        ) {
12611            return Ok(false);
12612        }
12613
12614        let Some(src) = src else {
12615            return Ok(false);
12616        };
12617
12618        if Self::is_end_to_end_ack_packet(pkt)
12619            && let Ok(packet_id) = Self::decode_end_to_end_reliable_ack(pkt.payload())
12620        {
12621            let alias = {
12622                let st = self.state.lock();
12623                st.reliable_return_routes
12624                    .get(&packet_id)
12625                    .filter(|route| route.upstream_contract.is_some())
12626                    .cloned()
12627            };
12628            if let Some(alias) = alias {
12629                let (upstream_id, publisher_hash) = alias.upstream_contract.expect("checked alias");
12630                #[cfg(feature = "std")]
12631                if reliable_trace_enabled() {
12632                    eprintln!(
12633                        "[sedsnet-reliable] proxy receipt id={packet_id:#x} upstream={upstream_id:#x} owner={:?}",
12634                        Self::end_to_end_ack_sender_hash(pkt)
12635                    );
12636                }
12637                let completed = {
12638                    let mut st = self.state.lock();
12639                    if let Some(sent) = st.end_to_end_reliable_tx.get_mut(&packet_id) {
12640                        if let Some(owner) = Self::end_to_end_ack_sender_hash(pkt) {
12641                            sent.pending_destinations.remove(&owner);
12642                        }
12643                        if !sent.pending_destinations.is_empty() {
12644                            return Ok(true);
12645                        }
12646                        st.end_to_end_reliable_tx.remove(&packet_id);
12647                        true
12648                    } else {
12649                        // An expired replay record is not evidence that the
12650                        // whole child cohort completed. Let the parent retry.
12651                        return Ok(true);
12652                    }
12653                };
12654                if completed {
12655                    let mut st = self.state.lock();
12656                    if Self::remember_reliable_return_route_locked(&mut st, upstream_id) {
12657                        st.reliable_return_routes.insert(
12658                            upstream_id,
12659                            ReliableReturnRouteState {
12660                                side: alias.side,
12661                                last_replay_ms: alias.last_replay_ms,
12662                                upstream_contract: None,
12663                            },
12664                        );
12665                    }
12666                }
12667                self.queue_end_to_end_reliable_ack_for_sender_hash(
12668                    upstream_id,
12669                    publisher_hash,
12670                    called_from_queue,
12671                )?;
12672                self.release_ordered_successors()?;
12673                return Ok(true);
12674            }
12675            // Relays may also keep a retransmission record for the forwarded
12676            // packet. Clearing that local record must not consume an ACK whose
12677            // frozen wire contract targets the original sender beyond this
12678            // router; it still has to travel back along the learned return
12679            // route. This occurs when a router is itself one managed-variable
12680            // owner while additional owners sit behind another side.
12681            let targets_local_sender = pkt.wire_target_senders().is_empty()
12682                || pkt
12683                    .wire_target_senders()
12684                    .contains(&Self::sender_hash(self.sender_arc().as_ref()));
12685            let mut st = self.state.lock();
12686            if let Some(sent) = st.end_to_end_reliable_tx.get_mut(&packet_id) {
12687                if sent.waiting_for_predecessor {
12688                    return Ok(targets_local_sender);
12689                }
12690                if let Some(sender_hash) = Self::end_to_end_ack_sender_hash(pkt) {
12691                    let _removed = sent.pending_destinations.remove(&sender_hash);
12692                    #[cfg(feature = "std")]
12693                    if reliable_trace_enabled() {
12694                        eprintln!(
12695                            "[sedsnet-reliable] receipt time={} id={packet_id:#x} sender={sender_hash:#x} matched={} remaining={:?}",
12696                            self.clock.now_ms(),
12697                            _removed.is_some(),
12698                            sent.pending_destinations
12699                        );
12700                    }
12701                    if sent.pending_destinations.is_empty() {
12702                        st.end_to_end_reliable_tx.remove(&packet_id);
12703                    }
12704                    drop(st);
12705                    self.release_ordered_successors()?;
12706                    return Ok(targets_local_sender);
12707                }
12708                st.end_to_end_reliable_tx.remove(&packet_id);
12709                drop(st);
12710                self.release_ordered_successors()?;
12711                return Ok(targets_local_sender);
12712            }
12713            // A late or duplicate receipt addressed to this publisher is
12714            // complete locally; it must not leak onto another network side.
12715            return Ok(targets_local_sender);
12716        }
12717
12718        let vals = pkt.data_as_u32()?;
12719        if vals.len() != 2 {
12720            return Err(TelemetryError::Unpack("bad reliable control payload"));
12721        }
12722        let ty = DataType::try_from_u32(vals[0]).ok_or(TelemetryError::InvalidType)?;
12723        let seq = vals[1];
12724
12725        match pkt.data_type() {
12726            DataType::ReliableAck => {
12727                self.handle_reliable_ack(src, ty, seq);
12728                Ok(true)
12729            }
12730            DataType::ReliablePartialAck => {
12731                self.handle_reliable_partial_ack(src, ty, seq);
12732                Ok(true)
12733            }
12734            DataType::ReliablePacketRequest => {
12735                self.queue_reliable_retransmit(src, ty, seq, called_from_queue)?;
12736                Ok(true)
12737            }
12738            _ => Ok(false),
12739        }
12740    }
12741
12742    /// Core receive function handling both Packet and Packed items.
12743    ///
12744    /// Relay mode: if a destination endpoint has no matching local handler and the packet has
12745    /// any remotely-forwardable endpoints, the router will rebroadcast the packet ONCE, excluding
12746    /// the ingress side.
12747    fn rx_item(&self, item: &RouterRxItem, called_from_queue: bool) -> TelemetryResult<()> {
12748        if crate::memory_admission::supports_work_kinds()
12749            && let (Some(side), RouterItem::Packed(bytes)) = (item.src, &item.data)
12750        {
12751            self.admit_received_frame(side, bytes)?;
12752        }
12753        if let Some(src) = item.src {
12754            self.ensure_side_ingress_enabled(src)?;
12755            match &item.data {
12756                RouterItem::Packet(pkt) => {
12757                    let bytes = wire_format::packet_wire_size(pkt);
12758                    self.note_side_rx(src, pkt.data_type(), bytes, true);
12759                }
12760                RouterItem::Packed(bytes) => {
12761                    if let Ok(env) = wire_format::peek_routing_envelope(bytes.as_ref()) {
12762                        self.note_side_rx(src, env.ty, bytes.len(), true);
12763                    }
12764                }
12765            }
12766            match &item.data {
12767                RouterItem::Packet(pkt) => {
12768                    if (is_reliable_type(pkt.data_type()) || !pkt.wire_target_senders().is_empty())
12769                        && !is_internal_control_type(pkt.data_type())
12770                        && !self.note_reliable_return_route(src, pkt.packet_id())
12771                    {
12772                        return Ok(());
12773                    }
12774                }
12775                RouterItem::Packed(bytes) => {
12776                    if let Ok(env) = wire_format::peek_routing_envelope(bytes.as_ref())
12777                        && (is_reliable_type(env.ty) || env.target_senders().len() != 0)
12778                        && !is_internal_control_type(env.ty)
12779                        && let Ok(packet_id) = wire_format::packet_id_from_wire(bytes.as_ref())
12780                        && !self.note_reliable_return_route(src, packet_id)
12781                    {
12782                        return Ok(());
12783                    }
12784                }
12785            }
12786        }
12787        match &item.data {
12788            RouterItem::Packet(pkt) => {
12789                if !is_internal_control_type(pkt.data_type()) {
12790                    self.remember_managed_variable_packet(pkt)?;
12791                }
12792            }
12793            RouterItem::Packed(bytes) => {
12794                if let Ok(env) = wire_format::peek_routing_envelope(bytes.as_ref())
12795                    && !is_internal_control_type(env.ty)
12796                    && self.is_managed_variable_type(env.ty)
12797                {
12798                    let pkt = self.unpack_packet_for_role(bytes.as_ref())?;
12799                    pkt.validate()?;
12800                    self.remember_managed_variable_packet(&pkt)?;
12801                }
12802            }
12803        }
12804        let mut released_buffered: Vec<crate::SharedBytes> = Vec::new();
12805        if let (Some(src), RouterItem::Packed(bytes)) = (item.src, &item.data) {
12806            let (_opts, handler_is_packed, hop_reliable_enabled) = {
12807                let st = self.state.lock();
12808                let side_ref = Self::side_ref(&st, src)?;
12809                let opts = side_ref.opts.clone();
12810                (
12811                    opts.clone(),
12812                    matches!(
12813                        side_ref.tx_handler,
12814                        RouterTxHandlerFn::Packed(_) | RouterTxHandlerFn::PackedWithPriority(_)
12815                    ),
12816                    opts.reliable_enabled
12817                        && self.cfg.reliable_enabled()
12818                        && !self.side_has_multiple_announcers_locked(&st, src, self.clock.now_ms()),
12819                )
12820            };
12821
12822            if hop_reliable_enabled && handler_is_packed {
12823                let frame = match wire_format::peek_routing_frame_info(bytes.as_ref()) {
12824                    Ok(frame) => frame,
12825                    Err(e) => {
12826                        if matches!(e, TelemetryError::Unpack(msg) if msg == "crc32 mismatch") {
12827                            if let Ok(frame) =
12828                                wire_format::peek_frame_info_unchecked(bytes.as_ref())
12829                                && is_reliable_type(frame.envelope.ty)
12830                                && let Some(hdr) = frame.reliable
12831                            {
12832                                let unordered =
12833                                    (hdr.flags & wire_format::RELIABLE_FLAG_UNORDERED) != 0;
12834                                let unsequenced =
12835                                    (hdr.flags & wire_format::RELIABLE_FLAG_UNSEQUENCED) != 0;
12836
12837                                if !unsequenced {
12838                                    let requested = if unordered {
12839                                        hdr.seq
12840                                    } else {
12841                                        let mut st = self.state.lock();
12842                                        let rx_state = self.reliable_rx_state_mut(
12843                                            &mut st,
12844                                            src,
12845                                            frame.envelope.ty,
12846                                        );
12847                                        rx_state.expected_seq.min(hdr.seq)
12848                                    };
12849                                    self.queue_reliable_packet_request(
12850                                        src,
12851                                        frame.envelope.ty,
12852                                        requested,
12853                                        called_from_queue,
12854                                    )?;
12855                                }
12856                            }
12857                            return Ok(());
12858                        }
12859                        return Err(e);
12860                    }
12861                };
12862                if is_reliable_type(frame.envelope.ty)
12863                    && let Some(hdr) = frame.reliable
12864                {
12865                    if frame.ack_only() {
12866                        self.handle_reliable_ack(src, frame.envelope.ty, hdr.ack);
12867                        return Ok(());
12868                    }
12869                    let unordered = (hdr.flags & wire_format::RELIABLE_FLAG_UNORDERED) != 0;
12870                    let unsequenced = (hdr.flags & wire_format::RELIABLE_FLAG_UNSEQUENCED) != 0;
12871
12872                    if !unsequenced {
12873                        if unordered {
12874                            self.queue_reliable_ack(
12875                                src,
12876                                frame.envelope.ty,
12877                                hdr.seq,
12878                                called_from_queue,
12879                            )?;
12880                        } else {
12881                            let mut release: Vec<crate::SharedBytes> = Vec::new();
12882                            let mut last_delivered = None;
12883                            let mut ack_old = None;
12884                            let mut request_missing = None;
12885                            let mut partial_ack = None;
12886                            {
12887                                let mut st = self.state.lock();
12888                                let rx_state =
12889                                    self.reliable_rx_state_mut(&mut st, src, frame.envelope.ty);
12890                                let expected_seq = rx_state.expected_seq;
12891                                if hdr.seq < expected_seq {
12892                                    ack_old = Some(expected_seq.saturating_sub(1));
12893                                } else if hdr.seq > expected_seq {
12894                                    request_missing = Some(expected_seq);
12895                                    partial_ack = Some(hdr.seq);
12896                                    st.buffer_reliable_rx(
12897                                        src,
12898                                        frame.envelope.ty,
12899                                        hdr.seq,
12900                                        bytes.clone(),
12901                                    )?;
12902                                } else {
12903                                    release.push(bytes.clone());
12904                                    last_delivered = Some(hdr.seq);
12905                                    let mut next_expected = hdr.seq.wrapping_add(1);
12906                                    while let Some(buf) = rx_state.buffered.remove(&next_expected) {
12907                                        release.push(buf);
12908                                        last_delivered = Some(next_expected);
12909                                        let next = next_expected.wrapping_add(1);
12910                                        next_expected = if next == 0 { 1 } else { next };
12911                                    }
12912                                    rx_state.expected_seq = next_expected;
12913                                }
12914                            }
12915
12916                            if let Some(ack_seq) = ack_old {
12917                                self.queue_reliable_ack(
12918                                    src,
12919                                    frame.envelope.ty,
12920                                    ack_seq,
12921                                    called_from_queue,
12922                                )?;
12923                                return Ok(());
12924                            }
12925                            if let Some(request_seq) = request_missing {
12926                                if let Some(partial_seq) = partial_ack {
12927                                    self.queue_reliable_partial_ack(
12928                                        src,
12929                                        frame.envelope.ty,
12930                                        partial_seq,
12931                                        called_from_queue,
12932                                    )?;
12933                                }
12934                                self.queue_reliable_packet_request(
12935                                    src,
12936                                    frame.envelope.ty,
12937                                    request_seq,
12938                                    called_from_queue,
12939                                )?;
12940                                return Ok(());
12941                            }
12942
12943                            if let Some(ack_seq) = last_delivered {
12944                                self.queue_reliable_ack(
12945                                    src,
12946                                    frame.envelope.ty,
12947                                    ack_seq,
12948                                    called_from_queue,
12949                                )?;
12950                            }
12951
12952                            released_buffered.extend(release.into_iter().skip(1));
12953                        }
12954                    }
12955                }
12956            } else {
12957                match wire_format::peek_routing_frame_info(bytes.as_ref()) {
12958                    Ok(frame) => {
12959                        if frame.ack_only() {
12960                            return Ok(());
12961                        }
12962                    }
12963                    Err(e) => {
12964                        if matches!(e, TelemetryError::Unpack(msg) if msg == "crc32 mismatch") {
12965                            return Ok(());
12966                        }
12967                        return Err(e);
12968                    }
12969                }
12970            }
12971        }
12972
12973        if self.is_duplicate_pkt(&item.data)? {
12974            // A final-ACK retry must cross a router again. Do not redeliver it
12975            // locally, and rate-limit repeated forwarding to avoid echo loops.
12976            if (item.src.is_some()
12977                && self
12978                    .item_target_senders(&item.data)?
12979                    .iter()
12980                    .any(|target| *target != Self::sender_hash(self.sender_arc().as_ref())))
12981                || (item.src.is_some() && self.should_route_remote(&item.data, item.src)? && {
12982                    let (endpoints, _) = self.item_route_info(&item.data)?;
12983                    self.item_targets_router_as_intermediate_hop(&item.data, &endpoints)?
12984                })
12985            {
12986                let id = Self::get_hash(&item.data);
12987                let replay = {
12988                    let mut st = self.state.lock();
12989                    st.reliable_return_routes.get_mut(&id).is_some_and(|route| {
12990                        let now = self.clock.now_ms();
12991                        if now.saturating_sub(route.last_replay_ms)
12992                            < runtime_reliable_retransmit_ms()
12993                        {
12994                            return false;
12995                        }
12996                        route.last_replay_ms = now;
12997                        true
12998                    })
12999                };
13000                if replay {
13001                    self.relay_send(item.data.clone(), item.src, called_from_queue)?;
13002                }
13003            }
13004            if item.src.is_some() {
13005                let local_sender = self.sender_arc();
13006                match &item.data {
13007                    RouterItem::Packet(pkt)
13008                        if (is_reliable_type(pkt.data_type())
13009                            || !pkt.wire_target_senders().is_empty())
13010                            && pkt.sender() != local_sender.as_ref()
13011                            && self.item_targets_local_sender(&item.data)?
13012                            && self.packet_has_local_delivery(pkt)
13013                            && !(self.should_route_remote(&item.data, item.src)?
13014                                && self.item_targets_router_as_intermediate_hop(
13015                                    &item.data,
13016                                    pkt.endpoints(),
13017                                )?) =>
13018                    {
13019                        self.queue_end_to_end_reliable_ack(pkt, called_from_queue)?;
13020                    }
13021                    RouterItem::Packed(bytes) => {
13022                        if let Ok(pkt) = self.unpack_packet_for_role(bytes.as_ref())
13023                            && (is_reliable_type(pkt.data_type())
13024                                || !pkt.wire_target_senders().is_empty())
13025                            && pkt.sender() != local_sender.as_ref()
13026                            && self.item_targets_local_sender(&item.data)?
13027                            && self.packet_has_local_delivery(&pkt)
13028                            && !(self.should_route_remote(&item.data, item.src)?
13029                                && self.item_targets_router_as_intermediate_hop(
13030                                    &item.data,
13031                                    pkt.endpoints(),
13032                                )?)
13033                        {
13034                            let packet_id = wire_format::packet_id_from_wire(bytes.as_ref())
13035                                .unwrap_or_else(|_| pkt.packet_id());
13036                            self.queue_end_to_end_reliable_ack_for_packet_id(
13037                                packet_id,
13038                                pkt.sender(),
13039                                called_from_queue,
13040                            )?;
13041                        }
13042                    }
13043                    _ => {}
13044                }
13045            }
13046            return Ok(());
13047        }
13048
13049        self.dispatch_rx_data(item, called_from_queue)?;
13050
13051        for release_bytes in released_buffered {
13052            let release_data = RouterItem::Packed(release_bytes.clone());
13053            if self.is_duplicate_pkt(&release_data)? {
13054                continue;
13055            }
13056            let release_item = RouterRxItem {
13057                src: item.src,
13058                priority: Self::router_item_priority(&release_data)?,
13059                data: release_data,
13060            };
13061            self.dispatch_rx_data(&release_item, called_from_queue)?;
13062        }
13063
13064        Ok(())
13065    }
13066
13067    fn dispatch_rx_data(
13068        &self,
13069        item: &RouterRxItem,
13070        called_from_queue: bool,
13071    ) -> TelemetryResult<()> {
13072        match &item.data {
13073            RouterItem::Packet(pkt) => {
13074                pkt.validate()?;
13075                #[cfg(feature = "discovery")]
13076                if let Some(side) = item.src {
13077                    self.refresh_peer_liveness(side, sender_address_u32(pkt.sender()));
13078                }
13079
13080                if self.handle_internal_reliable_packet(pkt, item.src, called_from_queue)? {
13081                    return Ok(());
13082                }
13083
13084                if pkt.data_type() == DataType::P2pMessage {
13085                    if self.item_targets_local_sender(&item.data)? {
13086                        self.dispatch_p2p_packet(pkt)?;
13087                        if item.src.is_some() {
13088                            self.queue_end_to_end_reliable_ack(pkt, called_from_queue)?;
13089                        }
13090                    }
13091                    if self.should_route_remote(&item.data, item.src)? {
13092                        self.relay_send(
13093                            RouterItem::Packet(pkt.to_owned()),
13094                            item.src,
13095                            called_from_queue,
13096                        )?;
13097                    }
13098                    return Ok(());
13099                }
13100
13101                #[cfg(feature = "timesync")]
13102                if matches!(
13103                    pkt.data_type(),
13104                    DataType::TimeSyncAnnounce
13105                        | DataType::TimeSyncRequest
13106                        | DataType::TimeSyncResponse
13107                ) {
13108                    self.handle_internal_timesync_packet(pkt, item.src, called_from_queue)?;
13109                    return Ok(());
13110                }
13111
13112                if self.learn_discovery_packet(pkt, item.src, called_from_queue)? {
13113                    // Reachability advertisements are hop-local. Learning one
13114                    // schedules this Router's own aggregated, split-horizon
13115                    // snapshot; transparently relaying the original packet
13116                    // would make a distant sender look physically adjacent.
13117                    if !discovery_is_hop_local_advertisement(pkt.data_type())
13118                        && self.should_route_remote(&item.data, item.src)?
13119                    {
13120                        self.relay_send(
13121                            RouterItem::Packet(pkt.to_owned()),
13122                            item.src,
13123                            called_from_queue,
13124                        )?;
13125                    }
13126                    return Ok(());
13127                }
13128
13129                let mut eps: Vec<DataEndpoint> = pkt.endpoints().to_vec();
13130                crate::small_sort::sort(&mut eps);
13131                eps.dedup();
13132                let had_local_handler = eps.iter().copied().any(|ep| {
13133                    self.endpoint_has_packet_handler(ep) || self.endpoint_has_packed_handler(ep)
13134                });
13135                let had_local_delivery =
13136                    had_local_handler || self.is_managed_variable_type(pkt.data_type());
13137
13138                let has_remote = self.should_route_remote(&item.data, item.src)?;
13139                let targets_local = self.item_targets_local_sender(&item.data)?;
13140
13141                let has_packed_local = eps
13142                    .iter()
13143                    .copied()
13144                    .any(|ep| self.endpoint_has_packed_handler(ep));
13145                let bytes_opt = if has_packed_local {
13146                    Some(wire_format::pack_packet(pkt))
13147                } else {
13148                    None
13149                };
13150
13151                if targets_local {
13152                    for dest in eps {
13153                        for h in self.cfg.handlers.iter().filter(|h| h.endpoint == dest) {
13154                            let result = match (&h.handler, &bytes_opt) {
13155                                (EndpointHandlerFn::Packed(_), Some(bytes)) => self
13156                                    .call_handler_with_retries(
13157                                        dest,
13158                                        h,
13159                                        Some(bytes.as_ref()),
13160                                        Some(pkt),
13161                                        None,
13162                                        called_from_queue,
13163                                    ),
13164                                (EndpointHandlerFn::Packed(_), None) => {
13165                                    let bytes = wire_format::pack_packet(pkt);
13166                                    self.call_handler_with_retries(
13167                                        dest,
13168                                        h,
13169                                        Some(bytes.as_ref()),
13170                                        Some(pkt),
13171                                        None,
13172                                        called_from_queue,
13173                                    )
13174                                }
13175                                (EndpointHandlerFn::Packet(_), _) => self
13176                                    .call_handler_with_retries(
13177                                        dest,
13178                                        h,
13179                                        None,
13180                                        Some(pkt),
13181                                        None,
13182                                        called_from_queue,
13183                                    ),
13184                            };
13185                            if result.is_err()
13186                                && let Some(src) = item.src
13187                            {
13188                                self.note_side_local_handler_failure(
13189                                    src,
13190                                    pkt.data_type(),
13191                                    runtime_max_handler_retries(),
13192                                );
13193                            }
13194                        }
13195                    }
13196                }
13197
13198                if let Some(src) = item.src
13199                    && had_local_delivery
13200                    && targets_local
13201                {
13202                    self.note_side_local_delivery(src, pkt.data_type());
13203                }
13204
13205                if item.src.is_some()
13206                    && had_local_delivery
13207                    && targets_local
13208                    && (is_reliable_type(pkt.data_type()) || !pkt.wire_target_senders().is_empty())
13209                    && !(has_remote
13210                        && self
13211                            .item_targets_router_as_intermediate_hop(&item.data, pkt.endpoints())?)
13212                {
13213                    self.queue_end_to_end_reliable_ack(pkt, called_from_queue)?;
13214                }
13215
13216                if has_remote {
13217                    let relay_item = RouterItem::Packet(pkt.to_owned());
13218                    self.relay_send(relay_item, item.src, called_from_queue)?;
13219                }
13220
13221                Ok(())
13222            }
13223            RouterItem::Packed(bytes) => {
13224                // Pure best-effort transit needs routing metadata, not an owned
13225                // sender string or endpoint array. Commands, local handlers,
13226                // managed variables and internal protocols retain their normal path.
13227                let routing = wire_format::peek_routing_envelope(bytes.as_ref())?;
13228                if !is_internal_control_type(routing.ty)
13229                    && !routing.is_reliable
13230                    && routing.target_senders().len() == 0
13231                    && !self.is_managed_variable_type(routing.ty)
13232                    && !routing.endpoints().any(|ep| {
13233                        self.endpoint_has_packet_handler(ep) || self.endpoint_has_packed_handler(ep)
13234                    })
13235                {
13236                    // relay_send already resolves the eligible sides, including
13237                    // an empty plan. A preliminary selection duplicates scratch
13238                    // allocations and advances weighted cursors twice.
13239                    return self.relay_send(item.data.clone(), item.src, called_from_queue);
13240                }
13241
13242                let env = wire_format::peek_envelope(bytes.as_ref())?;
13243
13244                if matches!(
13245                    env.ty,
13246                    DataType::ReliableAck
13247                        | DataType::ReliablePartialAck
13248                        | DataType::ReliablePacketRequest
13249                ) {
13250                    let pkt = self.unpack_packet_for_role(bytes.as_ref())?;
13251                    pkt.validate()?;
13252                    let handled =
13253                        self.handle_internal_reliable_packet(&pkt, item.src, called_from_queue)?;
13254                    // Hop-level ACKs terminate here. End-to-end ACKs for a packet originated
13255                    // farther upstream continue along the learned packet return route.
13256                    if !handled && self.should_route_remote(&item.data, item.src)? {
13257                        self.relay_send(RouterItem::Packet(pkt), item.src, called_from_queue)?;
13258                    }
13259                    return Ok(());
13260                }
13261
13262                if env.ty == DataType::P2pMessage {
13263                    let pkt = self.unpack_packet_for_role(bytes.as_ref())?;
13264                    pkt.validate()?;
13265                    if self.item_targets_local_sender(&item.data)? {
13266                        self.dispatch_p2p_packet(&pkt)?;
13267                        if item.src.is_some() {
13268                            let packet_id = wire_format::packet_id_from_wire(bytes.as_ref())
13269                                .unwrap_or_else(|_| pkt.packet_id());
13270                            self.queue_end_to_end_reliable_ack_for_packet_id(
13271                                packet_id,
13272                                pkt.sender(),
13273                                called_from_queue,
13274                            )?;
13275                        }
13276                    }
13277                    if self.should_route_remote(&item.data, item.src)? {
13278                        self.relay_send(RouterItem::Packet(pkt), item.src, called_from_queue)?;
13279                    }
13280                    return Ok(());
13281                }
13282
13283                #[cfg(feature = "timesync")]
13284                if matches!(
13285                    env.ty,
13286                    DataType::TimeSyncAnnounce
13287                        | DataType::TimeSyncRequest
13288                        | DataType::TimeSyncResponse
13289                ) {
13290                    let pkt = self.unpack_packet_for_role(bytes.as_ref())?;
13291                    pkt.validate()?;
13292                    self.handle_internal_timesync_packet(&pkt, item.src, called_from_queue)?;
13293                    return Ok(());
13294                }
13295
13296                #[cfg(feature = "discovery")]
13297                if discovery::is_discovery_type(env.ty) {
13298                    let pkt = self.unpack_packet_for_role(bytes.as_ref())?;
13299                    pkt.validate()?;
13300                    let _ = self.learn_discovery_packet(&pkt, item.src, called_from_queue)?;
13301                    if !discovery_is_hop_local_advertisement(env.ty)
13302                        && self.should_route_remote(&item.data, item.src)?
13303                    {
13304                        self.relay_send(RouterItem::Packet(pkt), item.src, called_from_queue)?;
13305                    }
13306                    return Ok(());
13307                }
13308
13309                let any_packet_needed = env
13310                    .endpoints
13311                    .iter()
13312                    .copied()
13313                    .any(|ep| self.endpoint_has_packet_handler(ep));
13314
13315                let mut pkt_opt = if any_packet_needed {
13316                    let pkt = self.unpack_packet_for_role(bytes.as_ref())?;
13317                    pkt.validate()?;
13318                    Some(pkt)
13319                } else {
13320                    None
13321                };
13322
13323                let mut eps: Vec<DataEndpoint> = env.endpoints.iter().copied().collect();
13324                crate::small_sort::sort(&mut eps);
13325                eps.dedup();
13326                let had_local_handler = eps.iter().copied().any(|ep| {
13327                    self.endpoint_has_packet_handler(ep) || self.endpoint_has_packed_handler(ep)
13328                });
13329                let had_local_delivery = had_local_handler || self.is_managed_variable_type(env.ty);
13330
13331                let has_remote = self.should_route_remote(&item.data, item.src)?;
13332                let targets_local = self.item_targets_local_sender(&item.data)?;
13333
13334                if targets_local {
13335                    for dest in eps {
13336                        for h in self.cfg.handlers.iter().filter(|h| h.endpoint == dest) {
13337                            let result = match &h.handler {
13338                                EndpointHandlerFn::Packed(_) => self.call_handler_with_retries(
13339                                    dest,
13340                                    h,
13341                                    Some(bytes.as_ref()),
13342                                    pkt_opt.as_ref(),
13343                                    Some(&env),
13344                                    called_from_queue,
13345                                ),
13346                                EndpointHandlerFn::Packet(_) => {
13347                                    if pkt_opt.is_none() {
13348                                        let pkt = self.unpack_packet_for_role(bytes.as_ref())?;
13349                                        pkt.validate()?;
13350                                        pkt_opt = Some(pkt);
13351                                    }
13352                                    let pkt_ref = pkt_opt.as_ref().expect("just set");
13353                                    self.call_handler_with_retries(
13354                                        dest,
13355                                        h,
13356                                        None,
13357                                        Some(pkt_ref),
13358                                        Some(&env),
13359                                        called_from_queue,
13360                                    )
13361                                }
13362                            };
13363                            if result.is_err()
13364                                && let Some(src) = item.src
13365                            {
13366                                self.note_side_local_handler_failure(
13367                                    src,
13368                                    env.ty,
13369                                    runtime_max_handler_retries(),
13370                                );
13371                            }
13372                        }
13373                    }
13374                }
13375
13376                if item.src.is_some()
13377                    && had_local_delivery
13378                    && targets_local
13379                    && (is_reliable_type(env.ty) || !env.target_senders.is_empty())
13380                    && !(has_remote
13381                        && self
13382                            .item_targets_router_as_intermediate_hop(&item.data, &env.endpoints)?)
13383                {
13384                    let packet_id =
13385                        wire_format::packet_id_from_wire(bytes.as_ref()).or_else(|_| {
13386                            pkt_opt
13387                                .as_ref()
13388                                .map(Packet::packet_id)
13389                                .ok_or(TelemetryError::Unpack("missing packet id"))
13390                        })?;
13391                    self.queue_end_to_end_reliable_ack_for_packet_id(
13392                        packet_id,
13393                        &env.sender,
13394                        called_from_queue,
13395                    )?;
13396                }
13397
13398                if has_remote {
13399                    let relay_item = match pkt_opt {
13400                        Some(ref p) => RouterItem::Packet(p.clone()),
13401                        None => RouterItem::Packed(bytes.clone()),
13402                    };
13403                    self.relay_send(relay_item, item.src, called_from_queue)?;
13404                }
13405
13406                Ok(())
13407            }
13408        }
13409    }
13410
13411    fn dispatch_local_for_item(
13412        &self,
13413        item: &RouterItem,
13414        called_from_queue: bool,
13415    ) -> TelemetryResult<()> {
13416        match item {
13417            RouterItem::Packet(pkt) => {
13418                pkt.validate()?;
13419                if is_internal_control_type(pkt.data_type()) {
13420                    return Ok(());
13421                }
13422                self.ensure_e2e_policy_supported_for_type(pkt.data_type())?;
13423                if !self.item_targets_local_sender(item)? {
13424                    return Ok(());
13425                }
13426
13427                let mut eps: Vec<DataEndpoint> = pkt.endpoints().to_vec();
13428                crate::small_sort::sort(&mut eps);
13429                eps.dedup();
13430
13431                let has_packed_local = eps
13432                    .iter()
13433                    .copied()
13434                    .any(|ep| self.endpoint_has_packed_handler(ep));
13435                let bytes_opt = if has_packed_local {
13436                    Some(wire_format::pack_packet(pkt))
13437                } else {
13438                    None
13439                };
13440
13441                for dest in eps {
13442                    for h in self.cfg.handlers.iter().filter(|h| h.endpoint == dest) {
13443                        match (&h.handler, &bytes_opt) {
13444                            (EndpointHandlerFn::Packed(_), Some(bytes)) => {
13445                                self.call_handler_with_retries(
13446                                    dest,
13447                                    h,
13448                                    Some(bytes.as_ref()),
13449                                    Some(pkt),
13450                                    None,
13451                                    called_from_queue,
13452                                )?;
13453                            }
13454                            (EndpointHandlerFn::Packed(_), None) => {
13455                                let bytes = wire_format::pack_packet(pkt);
13456                                self.call_handler_with_retries(
13457                                    dest,
13458                                    h,
13459                                    Some(bytes.as_ref()),
13460                                    Some(pkt),
13461                                    None,
13462                                    called_from_queue,
13463                                )?;
13464                            }
13465                            (EndpointHandlerFn::Packet(_), _) => {
13466                                self.call_handler_with_retries(
13467                                    dest,
13468                                    h,
13469                                    None,
13470                                    Some(pkt),
13471                                    None,
13472                                    called_from_queue,
13473                                )?;
13474                            }
13475                        }
13476                    }
13477                }
13478            }
13479            RouterItem::Packed(bytes) => {
13480                let env = wire_format::peek_envelope(bytes.as_ref())?;
13481                if is_internal_control_type(env.ty) {
13482                    return Ok(());
13483                }
13484                self.ensure_e2e_policy_supported_for_type(env.ty)?;
13485                if !self.item_targets_local_sender(item)? {
13486                    return Ok(());
13487                }
13488
13489                let any_packet_needed = env
13490                    .endpoints
13491                    .iter()
13492                    .copied()
13493                    .any(|ep| self.endpoint_has_packet_handler(ep));
13494
13495                let mut pkt_opt = if any_packet_needed {
13496                    let pkt = self.unpack_packet_for_role(bytes.as_ref())?;
13497                    pkt.validate()?;
13498                    Some(pkt)
13499                } else {
13500                    None
13501                };
13502
13503                let mut eps: Vec<DataEndpoint> = env.endpoints.iter().copied().collect();
13504                crate::small_sort::sort(&mut eps);
13505                eps.dedup();
13506
13507                for dest in eps {
13508                    for h in self.cfg.handlers.iter().filter(|h| h.endpoint == dest) {
13509                        match &h.handler {
13510                            EndpointHandlerFn::Packed(_) => {
13511                                self.call_handler_with_retries(
13512                                    dest,
13513                                    h,
13514                                    Some(bytes.as_ref()),
13515                                    pkt_opt.as_ref(),
13516                                    Some(&env),
13517                                    called_from_queue,
13518                                )?;
13519                            }
13520                            EndpointHandlerFn::Packet(_) => {
13521                                if pkt_opt.is_none() {
13522                                    let pkt = self.unpack_packet_for_role(bytes.as_ref())?;
13523                                    pkt.validate()?;
13524                                    pkt_opt = Some(pkt);
13525                                }
13526                                let pkt_ref = pkt_opt.as_ref().expect("just set");
13527                                self.call_handler_with_retries(
13528                                    dest,
13529                                    h,
13530                                    None,
13531                                    Some(pkt_ref),
13532                                    Some(&env),
13533                                    called_from_queue,
13534                                )?;
13535                            }
13536                        }
13537                    }
13538                }
13539            }
13540        }
13541
13542        Ok(())
13543    }
13544
13545    /// Internal TX implementation used by `tx*()`, `tx_queue*()`, and relay-mode rebroadcast.
13546    ///
13547    /// - Broadcast items are sent to all sides when remote forwarding is required.
13548    /// - ToSide items are sent only to the specified side.
13549    /// - If `ignore_local` is false, local handlers are invoked once.
13550    fn tx_item_impl(
13551        &self,
13552        item: RouterTxItem,
13553        ignore_local: bool,
13554        called_from_queue: bool,
13555    ) -> TelemetryResult<()> {
13556        match item {
13557            RouterTxItem::Broadcast(data) => {
13558                self.ensure_e2e_policy_supported_for_type(Self::item_data_type(&data)?)?;
13559                if let RouterItem::Packet(pkt) = &data
13560                    && !is_internal_control_type(pkt.data_type())
13561                {
13562                    self.remember_managed_variable_packet(pkt)?;
13563                }
13564                #[cfg(feature = "discovery")]
13565                let is_discovery = matches!(&data, RouterItem::Packet(pkt) if discovery::is_discovery_type(pkt.data_type()))
13566                    || matches!(&data, RouterItem::Packed(bytes)
13567                        if wire_format::peek_envelope(bytes.as_ref())
13568                            .map(|env| discovery::is_discovery_type(env.ty))
13569                            .unwrap_or(false));
13570                if !ignore_local {
13571                    if self.is_duplicate_pkt(&data)? {
13572                        return Ok(());
13573                    }
13574                    #[cfg(feature = "discovery")]
13575                    if !is_discovery
13576                        && !matches!(&data, RouterItem::Packet(pkt) if is_internal_control_type(pkt.data_type()))
13577                        && !matches!(&data, RouterItem::Packed(bytes)
13578                            if wire_format::peek_routing_envelope(bytes.as_ref())
13579                                .map(|env| is_internal_control_type(env.ty))
13580                                .unwrap_or(false))
13581                    {
13582                        self.dispatch_local_for_item(&data, called_from_queue)?;
13583                    }
13584                    #[cfg(not(feature = "discovery"))]
13585                    if !matches!(&data, RouterItem::Packet(pkt) if is_internal_control_type(pkt.data_type()))
13586                        && !matches!(&data, RouterItem::Packed(bytes)
13587                            if wire_format::peek_routing_envelope(bytes.as_ref())
13588                                .map(|env| is_internal_control_type(env.ty))
13589                                .unwrap_or(false))
13590                    {
13591                        self.dispatch_local_for_item(&data, called_from_queue)?;
13592                    }
13593                }
13594
13595                let send_remote = match &data {
13596                    RouterItem::Packet(pkt) => {
13597                        pkt.validate()?;
13598                        self.should_route_remote(&data, None)?
13599                    }
13600                    RouterItem::Packed(bytes) => {
13601                        let _ = wire_format::peek_envelope(bytes.as_ref())?;
13602                        self.should_route_remote(&data, None)?
13603                    }
13604                };
13605
13606                if !send_remote {
13607                    let (endpoints, ty) = self.item_route_info(&data)?;
13608                    if !ignore_local
13609                        && !is_internal_control_type(ty)
13610                        && is_reliable_type(ty)
13611                        && has_nonlocal_endpoint(&endpoints, &self.cfg)
13612                        && !self
13613                            .state
13614                            .lock()
13615                            .managed_variable_types
13616                            .contains(&ty.as_u32())
13617                    {
13618                        // Nothing was queued or transmitted. Reporting success
13619                        // here loses application state during route expiry.
13620                        // Permit retry of the same packet after discovery heals.
13621                        self.remove_pkt_id(&data);
13622                        return Err(TelemetryError::Io("no discovered route"));
13623                    }
13624                    return Ok(());
13625                }
13626                let mut data = data;
13627                let ty = match &data {
13628                    RouterItem::Packet(pkt) => pkt.data_type(),
13629                    RouterItem::Packed(bytes) => {
13630                        wire_format::peek_routing_envelope(bytes.as_ref())?.ty
13631                    }
13632                };
13633                if !ignore_local
13634                    && !is_internal_control_type(ty)
13635                    && self.item_requires_end_to_end_tracking(&data, ty)?
13636                {
13637                    #[cfg(feature = "discovery")]
13638                    {
13639                        let pending = {
13640                            let st = self.state.lock();
13641                            let mut pending = self
13642                                .expected_end_to_end_destinations_locked(&st, &data, None, false)?;
13643                            self.filter_trackable_end_to_end_destinations_locked(
13644                                &st,
13645                                ty,
13646                                &mut pending,
13647                                false,
13648                            );
13649                            pending
13650                        };
13651                        if !pending.is_empty() {
13652                            let original_data = data.clone();
13653                            let mut targets: Vec<u64> = pending.keys().copied().collect();
13654                            crate::small_sort::sort(&mut targets);
13655                            targets.dedup();
13656                            data = self.attach_wire_contract_to_item(data, &targets)?;
13657                            match self.register_end_to_end_reliable_tx(&data, Some(pending)) {
13658                                Ok(false) => return Ok(()),
13659                                Ok(true) => {}
13660                                Err(error) => {
13661                                    self.remove_pkt_id(&original_data);
13662                                    return Err(error);
13663                                }
13664                            }
13665                        }
13666                    }
13667                }
13668                let RemoteSidePlan::Target(sides) = self.remote_side_plan(&data, None)?;
13669                for side in sides.iter().copied() {
13670                    if let Err(e) = self.send_reliable_to_side(side, data.clone(), false) {
13671                        if Self::is_side_tx_busy(&e) {
13672                            self.tx_queue_item_for_retry(
13673                                RouterTxItem::ToSide {
13674                                    src: None,
13675                                    dst: side,
13676                                    data: data.clone(),
13677                                },
13678                                true,
13679                            )?;
13680                            continue;
13681                        }
13682                        match &data {
13683                            RouterItem::Packet(pkt) => {
13684                                let _ = self.handle_callback_error(pkt, None, e, called_from_queue);
13685                            }
13686                            RouterItem::Packed(bytes) => {
13687                                if let Ok(env) = wire_format::peek_envelope(bytes.as_ref()) {
13688                                    let _ = self.handle_callback_error_from_env(
13689                                        &env,
13690                                        None,
13691                                        e,
13692                                        called_from_queue,
13693                                    );
13694                                }
13695                            }
13696                        }
13697                        return Err(TelemetryError::HandlerError("tx handler failed"));
13698                    }
13699                }
13700            }
13701            RouterTxItem::ToSide { src, dst, data } => {
13702                self.ensure_e2e_policy_supported_for_type(Self::item_data_type(&data)?)?;
13703                if let RouterItem::Packet(pkt) = &data
13704                    && !is_internal_control_type(pkt.data_type())
13705                {
13706                    self.remember_managed_variable_packet(pkt)?;
13707                }
13708                if !ignore_local {
13709                    if self.is_duplicate_pkt(&data)? {
13710                        return Ok(());
13711                    }
13712                    let suppress_local = matches!(&data, RouterItem::Packet(pkt) if is_internal_control_type(pkt.data_type()))
13713                        || matches!(&data, RouterItem::Packed(bytes)
13714                            if wire_format::peek_routing_envelope(bytes.as_ref())
13715                                .map(|env| is_internal_control_type(env.ty))
13716                                .unwrap_or(false));
13717                    if !suppress_local {
13718                        self.dispatch_local_for_item(&data, called_from_queue)?;
13719                    }
13720                }
13721                let allowed = {
13722                    let mut st = self.state.lock();
13723                    let ty = match &data {
13724                        RouterItem::Packet(pkt) => Some(pkt.data_type()),
13725                        RouterItem::Packed(bytes) => {
13726                            Some(wire_format::peek_routing_envelope(bytes.as_ref())?.ty)
13727                        }
13728                    };
13729                    let route_allowed = self.route_allowed_locked(&st, src, ty, dst);
13730                    #[cfg(all(feature = "discovery", feature = "timesync"))]
13731                    let timesync_allowed = ty
13732                        .map(|ty| {
13733                            Self::timesync_allowed_for_side_locked(
13734                                &mut st,
13735                                dst,
13736                                ty,
13737                                self.clock.now_ms(),
13738                            )
13739                        })
13740                        .unwrap_or(true);
13741                    #[cfg(not(all(feature = "discovery", feature = "timesync")))]
13742                    let timesync_allowed = true;
13743                    route_allowed && timesync_allowed
13744                };
13745                if !allowed {
13746                    return Ok(());
13747                }
13748                if let Err(e) = self.send_reliable_to_side(dst, data.clone(), src.is_some()) {
13749                    if Self::is_side_tx_busy(&e) {
13750                        self.tx_queue_item_for_retry(
13751                            RouterTxItem::ToSide { src, dst, data },
13752                            true,
13753                        )?;
13754                        return Ok(());
13755                    }
13756                    match &data {
13757                        RouterItem::Packet(pkt) => {
13758                            let _ = self.handle_callback_error(pkt, None, e, called_from_queue);
13759                        }
13760                        RouterItem::Packed(bytes) => {
13761                            if let Ok(env) = wire_format::peek_envelope(bytes.as_ref()) {
13762                                let _ = self.handle_callback_error_from_env(
13763                                    &env,
13764                                    None,
13765                                    e,
13766                                    called_from_queue,
13767                                );
13768                            }
13769                        }
13770                    }
13771                    return Err(TelemetryError::HandlerError("tx handler failed"));
13772                }
13773            }
13774            RouterTxItem::EndToEndReplay { packet_id } => {
13775                let Some((data, mut sides)) = self.end_to_end_retransmit_sides(packet_id) else {
13776                    return Ok(());
13777                };
13778                if sides.is_empty() {
13779                    let RemoteSidePlan::Target(fallback_sides) =
13780                        self.remote_side_plan(&data, None)?;
13781                    sides = fallback_sides;
13782                }
13783                for side in sides.iter().copied() {
13784                    if let Err(e) = self.send_reliable_to_side(side, data.clone(), false) {
13785                        if Self::is_side_tx_busy(&e) {
13786                            self.tx_queue_item_for_retry(
13787                                RouterTxItem::ToSide {
13788                                    src: None,
13789                                    dst: side,
13790                                    data: data.clone(),
13791                                },
13792                                true,
13793                            )?;
13794                            continue;
13795                        }
13796                        match &data {
13797                            RouterItem::Packet(pkt) => {
13798                                let _ = self.handle_callback_error(pkt, None, e, called_from_queue);
13799                            }
13800                            RouterItem::Packed(bytes) => {
13801                                if let Ok(env) = wire_format::peek_envelope(bytes.as_ref()) {
13802                                    let _ = self.handle_callback_error_from_env(
13803                                        &env,
13804                                        None,
13805                                        e,
13806                                        called_from_queue,
13807                                    );
13808                                }
13809                            }
13810                        }
13811                        return Err(TelemetryError::HandlerError("tx handler failed"));
13812                    }
13813                }
13814            }
13815            RouterTxItem::ReliableReplay { dst, bytes } => {
13816                let frame = wire_format::peek_frame_info(bytes.as_ref())?;
13817                let ty = frame.envelope.ty;
13818                let Some(hdr) = frame.reliable else {
13819                    return Ok(());
13820                };
13821                {
13822                    let mut st = self.state.lock();
13823                    let tx_state = self.reliable_tx_state_mut(&mut st, dst, ty);
13824                    if !tx_state.sent.contains_key(&hdr.seq) {
13825                        return Ok(());
13826                    }
13827                }
13828                if let Err(e) = self.send_reliable_raw_to_side(dst, bytes.clone(), false) {
13829                    if Self::is_side_tx_busy(&e) {
13830                        self.tx_queue_item_for_retry(
13831                            RouterTxItem::ReliableReplay { dst, bytes },
13832                            true,
13833                        )?;
13834                        return Ok(());
13835                    }
13836                    return Err(e);
13837                }
13838                let mut st = self.state.lock();
13839                let tx_state = self.reliable_tx_state_mut(&mut st, dst, ty);
13840                if let Some(sent) = tx_state.sent.get_mut(&hdr.seq) {
13841                    sent.last_send_ms = self.clock.now_ms();
13842                    sent.queued = false;
13843                }
13844            }
13845        }
13846
13847        Ok(())
13848    }
13849
13850    /// Transmit a telemetry item immediately (remote + local).
13851    #[inline]
13852    fn tx_item(&self, item: RouterTxItem) -> TelemetryResult<()> {
13853        self.tx_item_impl(item, false, false)
13854    }
13855
13856    // ---------- PUBLIC API: RX immediate ----------
13857
13858    /// Process packed bytes immediately as locally-originated input.
13859    ///
13860    /// If this call occurs while a side TX callback is already on the stack, the bytes are queued
13861    /// instead of being processed re-entrantly.
13862    #[inline]
13863    pub fn rx_packed(&self, bytes: &[u8]) -> TelemetryResult<()> {
13864        if self.side_tx_active() {
13865            return self.rx_packed_queue(bytes);
13866        }
13867        crate::memory_admission::check_receive(bytes)?;
13868        let data = RouterItem::Packed(crate::shared_bytes::convert(Arc::<[u8]>::from(bytes)));
13869        let item = RouterRxItem {
13870            src: None,
13871            priority: Self::router_item_priority(&data)?,
13872            data,
13873        };
13874        self.rx_item(&item, false)
13875    }
13876
13877    /// Process a decoded packet immediately as locally-originated input.
13878    ///
13879    /// If this call occurs while a side TX callback is already on the stack, the packet is queued
13880    /// instead of being processed re-entrantly.
13881    #[inline]
13882    pub fn rx(&self, pkt: &Packet) -> TelemetryResult<()> {
13883        if self.side_tx_active() {
13884            return self.rx_queue(pkt.clone());
13885        }
13886        let data = RouterItem::Packet(pkt.clone());
13887        let item = RouterRxItem {
13888            src: None,
13889            priority: Self::router_item_priority(&data)?,
13890            data,
13891        };
13892        self.rx_item(&item, false)
13893    }
13894
13895    /// Process a decoded packet immediately with an explicit ingress side id.
13896    ///
13897    /// If this call occurs while a side TX callback is already on the stack, the packet is queued
13898    /// instead of being processed re-entrantly.
13899    #[inline]
13900    pub fn rx_from_side(&self, pkt: &Packet, side: RouterSideId) -> TelemetryResult<()> {
13901        if self.side_tx_active() {
13902            return self.rx_queue_from_side(pkt.clone(), side);
13903        }
13904        self.ensure_side_ingress_enabled(side)?;
13905        let data = RouterItem::Packet(pkt.clone());
13906        let item = RouterRxItem {
13907            src: Some(side),
13908            priority: Self::router_item_priority(&data)?,
13909            data,
13910        };
13911        self.rx_item(&item, false)
13912    }
13913
13914    /// Process packed bytes immediately with an explicit ingress side id.
13915    ///
13916    /// If this call occurs while a side TX callback is already on the stack, the bytes are queued
13917    /// instead of being processed re-entrantly.
13918    #[inline]
13919    pub fn rx_packed_from_side(&self, bytes: &[u8], side: RouterSideId) -> TelemetryResult<()> {
13920        if self.side_tx_active() {
13921            return self.rx_packed_queue_from_side(bytes, side);
13922        }
13923        self.ensure_side_ingress_enabled(side)?;
13924        let Some(decoded) = self.decode_side_transport_frame(side, bytes)? else {
13925            return Ok(());
13926        };
13927        let data = RouterItem::Packed(decoded);
13928        let item = RouterRxItem {
13929            src: Some(side),
13930            priority: Self::router_item_priority(&data)?,
13931            data,
13932        };
13933        self.rx_item(&item, false)
13934    }
13935
13936    // ---------- PUBLIC API: TX immediate ----------
13937
13938    /// Transmit a decoded packet immediately.
13939    ///
13940    /// The router delivers locally where appropriate and forwards toward eligible sides. If called
13941    /// from inside a side TX callback, the packet is queued instead of being sent re-entrantly.
13942    #[inline]
13943    pub fn tx(&self, pkt: Packet) -> TelemetryResult<()> {
13944        #[cfg(feature = "discovery")]
13945        let _ = crate::memory_admission::allow_drain(self.poll_discovery())?;
13946        if self.side_tx_active() {
13947            return self.tx_queue_item(RouterTxItem::Broadcast(RouterItem::Packet(pkt)));
13948        }
13949        self.tx_item(RouterTxItem::Broadcast(RouterItem::Packet(pkt)))
13950    }
13951
13952    /// Transmit packed bytes immediately.
13953    ///
13954    /// If called from inside a side TX callback, the bytes are queued instead of being sent
13955    /// re-entrantly.
13956    #[inline]
13957    pub fn tx_packed(&self, pkt: Arc<[u8]>) -> TelemetryResult<()> {
13958        #[cfg(feature = "discovery")]
13959        let _ = crate::memory_admission::allow_drain(self.poll_discovery())?;
13960        if self.side_tx_active() {
13961            return self.tx_queue_item(RouterTxItem::Broadcast(RouterItem::Packed(
13962                crate::shared_bytes::convert(pkt),
13963            )));
13964        }
13965        self.tx_item(RouterTxItem::Broadcast(RouterItem::Packed(
13966            crate::shared_bytes::convert(pkt),
13967        )))
13968    }
13969
13970    // ---------- PUBLIC API: TX queue ----------
13971
13972    /// Queue a decoded packet for later transmission.
13973    /// Discovery/time-sync maintenance stays on the worker calling `periodic`.
13974    /// Enqueueing must not serialize topology on a producer's small stack.
13975    #[inline]
13976    pub fn tx_queue(&self, pkt: Packet) -> TelemetryResult<()> {
13977        self.tx_queue_item(RouterTxItem::Broadcast(RouterItem::Packet(pkt)))
13978    }
13979
13980    /// Queue packed bytes for later transmission.
13981    #[inline]
13982    pub fn tx_packed_queue(&self, data: Arc<[u8]>) -> TelemetryResult<()> {
13983        self.tx_queue_item(RouterTxItem::Broadcast(RouterItem::Packed(
13984            crate::shared_bytes::convert(data),
13985        )))
13986    }
13987
13988    // ---------- PUBLIC API: logging ----------
13989
13990    /// Build a packet from typed elements and send it immediately.
13991    ///
13992    /// `ty` selects the schema message type and `data` must match that type's expected element
13993    /// width and count. If called from inside a side TX callback, the built packet is queued.
13994    #[inline]
13995    pub fn log<T: LeBytes>(&self, ty: DataType, data: &[T]) -> TelemetryResult<()> {
13996        #[cfg(feature = "discovery")]
13997        let _ = crate::memory_admission::allow_drain(self.poll_discovery())?;
13998        if self.side_tx_active() {
13999            return self.log_queue(ty, data);
14000        }
14001        let sender = self.sender_arc();
14002        log_raw(
14003            sender.as_ref(),
14004            ty,
14005            data,
14006            self.packet_timestamp_ms(),
14007            |pkt| self.tx_item(RouterTxItem::Broadcast(RouterItem::Packet(pkt))),
14008        )
14009    }
14010
14011    /// Build a packet from typed elements and queue it for later transmission.
14012    #[inline]
14013    pub fn log_queue<T: LeBytes>(&self, ty: DataType, data: &[T]) -> TelemetryResult<()> {
14014        let sender = self.sender_arc();
14015        log_raw(
14016            sender.as_ref(),
14017            ty,
14018            data,
14019            self.packet_timestamp_ms(),
14020            |pkt| self.tx_queue_item(RouterTxItem::Broadcast(RouterItem::Packet(pkt))),
14021        )
14022    }
14023
14024    /// Build a packet with an explicit timestamp and send it immediately.
14025    #[inline]
14026    pub fn log_ts<T: LeBytes>(
14027        &self,
14028        ty: DataType,
14029        timestamp: u64,
14030        data: &[T],
14031    ) -> TelemetryResult<()> {
14032        #[cfg(feature = "discovery")]
14033        let _ = crate::memory_admission::allow_drain(self.poll_discovery())?;
14034        if self.side_tx_active() {
14035            return self.log_queue_ts(ty, timestamp, data);
14036        }
14037        let sender = self.sender_arc();
14038        log_raw(sender.as_ref(), ty, data, timestamp, |pkt| {
14039            self.tx_item(RouterTxItem::Broadcast(RouterItem::Packet(pkt)))
14040        })
14041    }
14042
14043    /// Build a packet with an explicit timestamp and queue it for later transmission.
14044    #[inline]
14045    pub fn log_queue_ts<T: LeBytes>(
14046        &self,
14047        ty: DataType,
14048        timestamp: u64,
14049        data: &[T],
14050    ) -> TelemetryResult<()> {
14051        let sender = self.sender_arc();
14052        log_raw(sender.as_ref(), ty, data, timestamp, |pkt| {
14053            self.tx_queue_item(RouterTxItem::Broadcast(RouterItem::Packet(pkt)))
14054        })
14055    }
14056}
14057
14058#[cfg(all(test, feature = "std", feature = "discovery"))]
14059mod queued_schema_identity_tests {
14060    use super::*;
14061
14062    fn catalog(timestamp: u64, byte: u8) -> Packet {
14063        Packet::new(
14064            crate::DataType::DiscoverySchema,
14065            &[DataEndpoint::Discovery],
14066            "SCHEMA_PEER",
14067            timestamp,
14068            crate::shared_bytes::convert(Arc::<[u8]>::from(vec![byte; 1024])),
14069        )
14070        .unwrap()
14071    }
14072
14073    #[test]
14074    fn repeated_schema_preserves_partial_transfer_but_updates_and_other_sides_do_not_coalesce() {
14075        let router = Router::new_with_clock(RouterConfig::default(), Box::new(|| 0u64));
14076        let side = router.add_side_packed("A", |_| Ok(()));
14077        let other_side = router.add_side_packed("B", |_| Ok(()));
14078        let original = catalog(1, 42);
14079        let duplicate = catalog(2, 42);
14080        assert_ne!(original.packet_id(), duplicate.packet_id());
14081        let mut st = router.state.lock();
14082        st.transmit_queue.clear();
14083        let queued = |dst, pkt| TxQueued {
14084            item: RouterTxItem::ToSide {
14085                src: None,
14086                dst,
14087                data: RouterItem::Packet(pkt),
14088            },
14089            ignore_local: true,
14090            priority: 255,
14091            retry_after_ms: 123,
14092        };
14093        st.push_transmit(queued(side, original.clone())).unwrap();
14094        st.push_transmit(queued(side, duplicate.clone())).unwrap();
14095        assert_eq!(st.transmit_queue.len(), 1);
14096        let pending = st.transmit_queue.iter().next().unwrap();
14097        assert_eq!(pending.retry_after_ms, 123);
14098        assert!(
14099            matches!(&pending.item, RouterTxItem::ToSide { data: RouterItem::Packet(pkt), .. }
14100            if pkt.packet_id() == original.packet_id())
14101        );
14102        st.push_transmit(queued(other_side, duplicate)).unwrap();
14103        assert_eq!(st.transmit_queue.len(), 2);
14104        let changed = catalog(3, 43);
14105        st.push_transmit(queued(side, changed.clone())).unwrap();
14106        assert_eq!(st.transmit_queue.len(), 2);
14107        assert!(st.transmit_queue.iter().any(|queued| matches!(&queued.item,
14108            RouterTxItem::ToSide { dst, data: RouterItem::Packet(pkt), .. }
14109            if *dst == side && pkt.packet_id() == changed.packet_id())));
14110    }
14111
14112    #[test]
14113    fn repeated_announcements_complete_one_fragment_per_dispatch() {
14114        use core::sync::atomic::{AtomicUsize, Ordering};
14115        let allowance = Arc::new(AtomicUsize::new(0));
14116        let accepted = Arc::new(std::sync::Mutex::new(Vec::<Arc<[u8]>>::new()));
14117        let available = allowance.clone();
14118        let frames = accepted.clone();
14119        let node = Router::new_with_clock(RouterConfig::default(), Box::new(|| 1u64));
14120        let side = node.add_side_packed_with_options(
14121            "bounded CAN",
14122            move |bytes| {
14123                if available
14124                    .try_update(Ordering::Relaxed, Ordering::Relaxed, |count| {
14125                        count.checked_sub(1)
14126                    })
14127                    .is_err()
14128                {
14129                    return Err(TelemetryError::Io("side tx busy"));
14130                }
14131                frames.lock().unwrap().push(Arc::<[u8]>::from(bytes));
14132                Ok(())
14133            },
14134            RouterSideOptions::default().with_small_packet_transport(128),
14135        );
14136        let handler = node.state.lock().sides[side]
14137            .as_ref()
14138            .unwrap()
14139            .tx_handler
14140            .clone();
14141        let mut complete = false;
14142        for timestamp in 1..100 {
14143            // Incompressible bytes ensure this really uses several chunks.
14144            let mut state = 0x517cc1b7u32;
14145            let payload: Vec<u8> = (0..3600)
14146                .map(|_| {
14147                    state ^= state << 13;
14148                    state ^= state >> 17;
14149                    state ^= state << 5;
14150                    state as u8
14151                })
14152                .collect();
14153            let packet = Packet::new(
14154                crate::DataType::DiscoverySchema,
14155                &[crate::DataEndpoint::Discovery],
14156                "SCHEMA_PEER",
14157                timestamp,
14158                crate::shared_bytes::convert(Arc::<[u8]>::from(payload)),
14159            )
14160            .unwrap();
14161            node.state
14162                .lock()
14163                .push_transmit(TxQueued {
14164                    item: RouterTxItem::ToSide {
14165                        src: None,
14166                        dst: side,
14167                        data: RouterItem::Packet(packet),
14168                    },
14169                    ignore_local: true,
14170                    priority: 255,
14171                    retry_after_ms: 0,
14172                })
14173                .unwrap();
14174            let data = match &node.state.lock().transmit_queue.iter().next().unwrap().item {
14175                RouterTxItem::ToSide { data, .. } => data.clone(),
14176                _ => panic!("unexpected item"),
14177            };
14178            allowance.store(1, Ordering::Relaxed);
14179            match node.call_side_tx_handler(side, &handler, &data, false) {
14180                Ok(()) => {
14181                    complete = true;
14182                    break;
14183                }
14184                Err(TelemetryError::Io(_)) => {}
14185                other => panic!("unexpected result: {other:?}"),
14186            }
14187        }
14188        assert!(
14189            complete,
14190            "new announcement timestamps must not replay the prefix forever"
14191        );
14192        let received = accepted.lock().unwrap();
14193        // One transfer ID and consecutive fragment indices prove prefix reuse
14194        // did not create incomplete transfers or duplicate accepted fragments.
14195        assert!(
14196            received.len() > 1,
14197            "fixture must exercise fragmentation, not a compressed single frame"
14198        );
14199        let first = &received[0];
14200        assert_eq!(&first[..4], &[83, 68, 84, 3]);
14201        assert_eq!(
14202            u16::from_le_bytes([first[10], first[11]]) as usize,
14203            received.len()
14204        );
14205        let transfer_id = &first[4..8];
14206        for (index, frame) in received.iter().enumerate() {
14207            assert_eq!(&frame[4..8], transfer_id);
14208            assert_eq!(u16::from_le_bytes([frame[8], frame[9]]) as usize, index);
14209        }
14210    }
14211    #[test]
14212    fn schema_receipt_resolves_assigned_numeric_address_without_adding_peers() {
14213        let router = Router::new_with_clock(RouterConfig::default(), Box::new(|| 0u64));
14214        let side = router.add_side_packed("A", |_| Ok(()));
14215        let assigned = 0x1234u32;
14216        assert_ne!(sender_address_u32("SCHEMA_PEER"), assigned);
14217        {
14218            let mut st = router.state.lock();
14219            st.discovery_routes
14220                .entry(side)
14221                .or_default()
14222                .announcers
14223                .insert(
14224                    "SCHEMA_PEER".into(),
14225                    DiscoverySenderState {
14226                        advertised_address: Some(assigned),
14227                        ..DiscoverySenderState::default()
14228                    },
14229                );
14230        }
14231        router.note_schema_received(side, "@addr:4660");
14232        router.note_schema_received(side, "@addr:4661");
14233        let st = router.state.lock();
14234        let peers = &st.discovery_routes.get(&side).unwrap().announcers;
14235        assert_eq!(peers.len(), 1);
14236        assert!(peers["SCHEMA_PEER"].has_schema);
14237    }
14238    #[test]
14239    fn busy_handler_returns_after_one_attempt_and_other_errors_keep_retry_budget() {
14240        let router = Router::new_with_clock(RouterConfig::default(), Box::new(|| 0u64));
14241        let attempts = core::cell::Cell::new(0);
14242        let result: Result<((), usize), _> = router.retry_with_attempts(8, || {
14243            attempts.set(attempts.get() + 1);
14244            Err(TelemetryError::Io("side tx busy"))
14245        });
14246        assert!(matches!(
14247            result,
14248            Err((TelemetryError::Io("side tx busy"), 1))
14249        ));
14250        assert_eq!(attempts.get(), 1);
14251        attempts.set(0);
14252        let result: Result<((), usize), _> = router.retry_with_attempts(3, || {
14253            attempts.set(attempts.get() + 1);
14254            Err(TelemetryError::Io("permanent handler failure"))
14255        });
14256        assert!(matches!(
14257            result,
14258            Err((TelemetryError::Io("permanent handler failure"), 3))
14259        ));
14260        assert_eq!(attempts.get(), 3);
14261    }
14262    #[test]
14263    fn rejected_first_template_frame_is_retried_self_describing() {
14264        crate::tests::ensure_common_test_schema();
14265        let ty = crate::config::register_data_type_with_description(
14266            "ROUTER_BUSY_FIRST_TEMPLATE",
14267            "",
14268            crate::MessageElement::Static(
14269                1,
14270                crate::MessageDataType::UInt8,
14271                crate::MessageClass::Data,
14272            ),
14273            &[crate::DataEndpoint::named("RADIO")],
14274            crate::ReliableMode::None,
14275            1,
14276        )
14277        .unwrap();
14278        let tx = Router::new_with_clock(RouterConfig::default(), Box::new(|| 0u64));
14279        let rx = Router::new_with_clock(RouterConfig::default(), Box::new(|| 0u64));
14280        let opts = RouterSideOptions::default().with_small_packet_transport(0);
14281        let a = tx.add_side_packed_with_options("wire", |_| Ok(()), opts.clone());
14282        let b = rx.add_side_packed_with_options("wire", |_| Ok(()), opts.clone());
14283        let packet = Packet::new(
14284            ty,
14285            &[crate::DataEndpoint::named("RADIO")],
14286            "BUSY_PEER",
14287            1,
14288            crate::shared_bytes::convert(Arc::<[u8]>::from([42u8])),
14289        )
14290        .unwrap();
14291        let raw = wire_format::pack_packet(&packet);
14292        // No byte of the dictionary bootstrap reaches the receiver.
14293        let first = tx
14294            .encode_side_transport_frames(
14295                a,
14296                opts.clone(),
14297                crate::shared_bytes::convert(raw.clone()),
14298            )
14299            .unwrap();
14300        assert!(
14301            tx.send_side_frames_resumable(a, first, |_| Err(TelemetryError::Io("side tx busy")))
14302                .is_err()
14303        );
14304        let retry = tx
14305            .encode_side_transport_frames(
14306                a,
14307                opts.clone(),
14308                crate::shared_bytes::convert(raw.clone()),
14309            )
14310            .unwrap();
14311        let mut received = None;
14312        tx.send_side_frames_resumable(a, retry, |frame| {
14313            assert_eq!(
14314                frame[3], SIDE_TRANSPORT_KIND_FULL,
14315                "retry requires the unaccepted dictionary bootstrap"
14316            );
14317            received = rx.decode_side_transport_frame(b, frame)?;
14318            Ok(())
14319        })
14320        .unwrap();
14321        assert_eq!(received, Some(crate::shared_bytes::convert(raw.clone())));
14322        // Compression resumes once a full frame has actually been accepted.
14323        let next = tx
14324            .encode_side_transport_frames(a, opts, crate::shared_bytes::convert(raw.clone()))
14325            .unwrap();
14326        tx.send_side_frames_resumable(a, next, |frame| {
14327            assert_eq!(frame[3], SIDE_TRANSPORT_KIND_COMPACT);
14328            assert_eq!(
14329                rx.decode_side_transport_frame(b, frame)?,
14330                Some(crate::shared_bytes::convert(raw.clone()))
14331            );
14332            Ok(())
14333        })
14334        .unwrap();
14335    }
14336    #[test]
14337    fn already_queued_receipt_precedes_ordered_retry_exhaustion() {
14338        crate::tests::ensure_common_test_schema();
14339        let ep = DataEndpoint::named("RADIO");
14340        let ty = crate::config::register_data_type_with_description(
14341            "QUEUED_RECEIPT_DEADLINE",
14342            "",
14343            MessageElement::Static(1, crate::MessageDataType::UInt8, crate::MessageClass::Data),
14344            &[ep],
14345            crate::ReliableMode::Ordered,
14346            1,
14347        )
14348        .unwrap();
14349        for isr in [false, true] {
14350            let now = Arc::new(core::sync::atomic::AtomicU64::new(0));
14351            let clock = now.clone();
14352            let router = Router::new_with_clock(
14353                RouterConfig::new([]).with_hostname("FC"),
14354                Box::new(move || clock.load(Ordering::Relaxed)),
14355            );
14356            let side = router.add_side_packed("CAN", |_| Ok(()));
14357            let packet = Packet::new(ty, &[ep], "FC", 1, Arc::<[u8]>::from([42u8])).unwrap();
14358            let id = packet.packet_id();
14359            let owner = Router::sender_hash("GS");
14360            let mut destinations = BTreeMap::new();
14361            destinations.insert(owner, side);
14362            router.state.lock().end_to_end_reliable_tx.insert(
14363                id,
14364                EndToEndReliableSent {
14365                    ordered_type: Some(ty.as_u32()),
14366                    waiting_for_predecessor: false,
14367                    data: RouterItem::Packet(packet),
14368                    pending_destinations: destinations,
14369                    tracked_destinations: true,
14370                    last_send_ms: 0,
14371                    retries: runtime_reliable_max_retries(),
14372                    queued: false,
14373                },
14374            );
14375            let snapshot = router.export_reliable_pending_stats();
14376            assert_eq!(snapshot.len(), 1);
14377            assert_eq!(snapshot[0].packet_id, id);
14378            assert_eq!(snapshot[0].data_type, ty);
14379            assert_eq!(snapshot[0].pending_destinations, vec![owner]);
14380            assert_eq!(snapshot[0].retries, runtime_reliable_max_retries());
14381            assert!(!snapshot[0].queued);
14382            assert!(!snapshot[0].waiting_for_predecessor);
14383            let ack = Packet::new_with_wire_contract(
14384                DataType::ReliableAck,
14385                message_meta(DataType::ReliableAck).endpoints_ref(),
14386                "E2EACK:GS",
14387                1,
14388                1,
14389                Arc::<[u8]>::from(id.to_le_bytes()),
14390                None,
14391                Arc::from([owner, Router::sender_hash("FC")]),
14392            )
14393            .unwrap();
14394            let packed = wire_format::pack_packet(&ack);
14395            if isr {
14396                router.rx_packed_queue_from_side_isr(&packed, side).unwrap();
14397            } else {
14398                router.rx_packed_queue_from_side(&packed, side).unwrap();
14399            }
14400            now.store(runtime_reliable_retransmit_ms(), Ordering::Relaxed);
14401            router
14402                .drain_queues_with_timeout(1)
14403                .expect("a received ACK must be processed before cancelling its ordered delivery");
14404            assert!(!router.state.lock().end_to_end_reliable_tx.contains_key(&id));
14405            assert!(router.export_reliable_pending_stats().is_empty());
14406        }
14407    }
14408    #[test]
14409    fn embedded_gateway_receipt_with_numeric_sender_clears_host_pending_owner() {
14410        crate::tests::ensure_common_test_schema();
14411        // Captured after the actual gateway UART and Pico mailbox. The owner
14412        // hash is carried in the contract, because the ACK sender is numeric.
14413        let bytes = [
14414            0x0c, 0x01, 0x01, 0x08, 0xd4, 0xe5, 0xea, 0xb6, 0xb7, 0x33, 0xff, 0x90, 0x02, 0xb4,
14415            0xe6, 0xdd, 0x94, 0x07, 0x14, 0x03, 0x44, 0x02, 0x02, 0x06, 0x25, 0xd2, 0x59, 0x1b,
14416            0xbb, 0xfc, 0xa7, 0x5f, 0x43, 0x92, 0x9e, 0x12, 0xbb, 0xfc, 0xa7, 0x26, 0xa2, 0xa0,
14417            0xbe, 0x39, 0x65, 0xe2, 0xd8, 0x61, 0x68, 0x22, 0xfd,
14418        ];
14419        let ack = wire_format::unpack_packet(&bytes).unwrap();
14420        let owner = Router::sender_hash("GB");
14421        assert_eq!(Router::end_to_end_ack_sender_hash(&ack), Some(owner));
14422        let id = Router::decode_end_to_end_reliable_ack(ack.payload()).unwrap();
14423        let router = Router::new(RouterConfig::new([]).with_hostname("GS"));
14424        let side = router.add_side_packed("Pico", |_| Ok(()));
14425        let data = ack.clone();
14426        router.state.lock().end_to_end_reliable_tx.insert(
14427            id,
14428            EndToEndReliableSent {
14429                ordered_type: None,
14430                waiting_for_predecessor: false,
14431                data: RouterItem::Packet(data),
14432                pending_destinations: BTreeMap::from([(owner, side)]),
14433                tracked_destinations: true,
14434                last_send_ms: 0,
14435                retries: 0,
14436                queued: false,
14437            },
14438        );
14439        router.rx_packed_queue_from_side(&bytes, side).unwrap();
14440        router.drain_queues_with_timeout(1).unwrap();
14441        assert!(router.export_reliable_pending_stats().is_empty());
14442    }
14443
14444    #[test]
14445    fn frozen_delivery_contract_bypasses_dictionary_even_for_best_effort_schema() {
14446        crate::tests::ensure_common_test_schema();
14447        let ep = DataEndpoint::named("RADIO");
14448        let ty = crate::config::register_data_type_with_description(
14449            "MANAGED_CONTROL_WITH_FROZEN_OWNERS",
14450            "",
14451            MessageElement::Static(1, crate::MessageDataType::UInt8, crate::MessageClass::Data),
14452            &[ep],
14453            crate::ReliableMode::None,
14454            16,
14455        )
14456        .unwrap();
14457        let router = Router::new(RouterConfig::new([]).with_hostname("GS"));
14458        let opts = RouterSideOptions::default().with_template_transport();
14459        let side = router.add_side_packed_with_options("radio", |_| Ok(()), opts.clone());
14460        let packet = Packet::new_with_wire_contract(
14461            ty,
14462            &[ep],
14463            "GS",
14464            1,
14465            1,
14466            Arc::<[u8]>::from([1u8]),
14467            None,
14468            Arc::from([Router::sender_hash("RF")]),
14469        )
14470        .unwrap();
14471        let raw = wire_format::pack_packet(&packet);
14472        assert_eq!(
14473            router
14474                .item_service_priority(&RouterItem::Packet(packet.clone()))
14475                .unwrap(),
14476            254
14477        );
14478        assert_eq!(
14479            router
14480                .item_service_priority(&RouterItem::Packed(crate::shared_bytes::convert(
14481                    raw.clone()
14482                )))
14483                .unwrap(),
14484            254
14485        );
14486        assert!(
14487            !wire_format::peek_routing_frame_info(&raw)
14488                .unwrap()
14489                .envelope
14490                .is_reliable
14491        );
14492        for _ in 0..3 {
14493            let frames = router
14494                .encode_side_transport_frames(
14495                    side,
14496                    opts.clone(),
14497                    crate::shared_bytes::convert(raw.clone()),
14498                )
14499                .unwrap();
14500            let mut count = 0;
14501            router
14502                .send_side_frames_resumable(side, frames, |bytes| {
14503                    assert_eq!(
14504                        bytes,
14505                        raw.as_ref(),
14506                        "delivery contract must remain independently decodable"
14507                    );
14508                    count += 1;
14509                    Ok(())
14510                })
14511                .unwrap();
14512            assert_eq!(count, 1);
14513        }
14514    }
14515
14516    #[test]
14517    fn managed_receive_queues_keep_service_priority_with_low_schema_priority() {
14518        crate::tests::ensure_common_test_schema();
14519        let ep = DataEndpoint::named("RADIO");
14520        let ty = crate::config::register_data_type_with_description(
14521            "MANAGED_RX_SERVICE_PRIORITY",
14522            "",
14523            MessageElement::Static(1, crate::MessageDataType::UInt8, crate::MessageClass::Data),
14524            &[ep],
14525            crate::ReliableMode::None,
14526            16,
14527        )
14528        .unwrap();
14529        let router = Router::new(RouterConfig::new([]).with_hostname("RF"));
14530        router
14531            .enable_network_variable(ty, NetworkVariablePermissions::READ_ONLY)
14532            .unwrap();
14533        let side = router.add_side_packed("radio", |_| Ok(()));
14534        router.clear_queues();
14535        let packet = Packet::new(ty, &[ep], "GS", 1, Arc::<[u8]>::from([1])).unwrap();
14536        let wire = wire_format::pack_packet(&packet);
14537        router.rx_queue(packet.clone()).unwrap();
14538        router.rx_queue_from_side(packet.clone(), side).unwrap();
14539        router.rx_packed_queue(&wire).unwrap();
14540        router.rx_packed_queue_from_side(&wire, side).unwrap();
14541        for _ in 0..4 {
14542            let queued = router
14543                .pop_next_queued_control_rx_item()
14544                .unwrap()
14545                .expect("registered managed controls must precede queued bulk work");
14546            assert_eq!(
14547                queued.priority,
14548                crate::transport_priority(DataType::ManagedVariableValue)
14549            );
14550        }
14551        assert!(router.pop_next_queued_control_rx_item().unwrap().is_none());
14552    }
14553
14554    #[test]
14555    fn managed_value_keeps_service_priority_after_side_backpressure() {
14556        crate::tests::ensure_common_test_schema();
14557        let ep = DataEndpoint::named("RADIO");
14558        let ty = crate::config::register_data_type_with_description(
14559            "MANAGED_RETRY_SERVICE_PRIORITY",
14560            "",
14561            MessageElement::Static(1, crate::MessageDataType::UInt8, crate::MessageClass::Data),
14562            &[ep],
14563            crate::ReliableMode::None,
14564            16,
14565        )
14566        .unwrap();
14567        let router = Router::new(RouterConfig::new([]).with_hostname("GS"));
14568        router
14569            .enable_network_variable(ty, NetworkVariablePermissions::READ_WRITE)
14570            .unwrap();
14571        let side = router.add_side_packed("radio", |_| Err(TelemetryError::Io("side tx busy")));
14572        let packet = Packet::new(ty, &[ep], "GS", 1, Arc::<[u8]>::from([1])).unwrap();
14573        let item = RouterItem::Packet(packet);
14574        for queued in [
14575            RouterTxItem::Broadcast(item.clone()),
14576            RouterTxItem::ToSide {
14577                src: None,
14578                dst: side,
14579                data: item.clone(),
14580            },
14581        ] {
14582            router.tx_queue_item_for_retry(queued, true).unwrap();
14583            let queued = router.state.lock().transmit_queue.pop_front().unwrap();
14584            assert_eq!(
14585                queued.priority,
14586                crate::transport_priority(DataType::ManagedVariableValue),
14587                "a busy side must not demote managed controls to bulk application priority"
14588            );
14589            assert!(queued.retry_after_ms > 0);
14590        }
14591    }
14592    #[test]
14593    fn receipt_budget_does_not_starve_transmit() {
14594        use core::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
14595        crate::tests::ensure_common_test_schema();
14596        let now = Arc::new(AtomicU64::new(0));
14597        let clock = now.clone();
14598        let count = Arc::new(AtomicUsize::new(0));
14599        let accepted = count.clone();
14600        let node = Router::new_with_clock(
14601            RouterConfig::default(),
14602            Box::new(move || clock.fetch_add(1, Ordering::Relaxed)),
14603        );
14604        let side = node.add_side_packed("wire", move |_| {
14605            accepted.fetch_add(1, Ordering::Relaxed);
14606            Ok(())
14607        });
14608        node.clear_queues();
14609        let packet = Packet::new(
14610            crate::DataType::ReliableAck,
14611            &[crate::DataEndpoint::TelemetryError],
14612            "E2EACK:PEER",
14613            1,
14614            Arc::<[u8]>::from([0u8; 8]),
14615        )
14616        .unwrap();
14617        node.state
14618            .lock()
14619            .push_transmit(TxQueued {
14620                item: RouterTxItem::ToSide {
14621                    src: None,
14622                    dst: side,
14623                    data: RouterItem::Packet(packet.clone()),
14624                },
14625                ignore_local: true,
14626                priority: 250,
14627                retry_after_ms: 0,
14628            })
14629            .unwrap();
14630        node.rx_packed_queue_from_side(
14631            &wire_format::pack_packet(
14632                &Packet::new(
14633                    crate::DataType::ReliableAck,
14634                    &[crate::DataEndpoint::TelemetryError],
14635                    "RX_PEER",
14636                    2,
14637                    Arc::<[u8]>::from([0u8; 8]),
14638                )
14639                .unwrap(),
14640            ),
14641            side,
14642        )
14643        .unwrap();
14644        node.drain_queues_with_timeout(1).unwrap();
14645        assert_eq!(
14646            count.load(Ordering::Relaxed),
14647            1,
14648            "control RX exhausting its cooperative budget must still allow one queued TX"
14649        );
14650    }
14651}
14652
14653#[cfg(all(test, feature = "discovery", feature = "std"))]
14654fn untemplated_test_node() -> Router {
14655    Router::new_with_clock(RouterConfig::default(), Box::new(|| 0))
14656}
14657#[cfg(all(test, feature = "discovery", feature = "std"))]
14658fn untemplated_test_options() -> RouterSideOptions {
14659    RouterSideOptions::default().with_small_packet_transport(64)
14660}
14661#[cfg(all(test, feature = "discovery", feature = "std"))]
14662#[allow(clippy::duplicate_mod)]
14663#[path = "tests/untemplated_transport.rs"]
14664mod untemplated_transport_tests;
14665
14666#[cfg(all(test, feature = "std", feature = "discovery"))]
14667mod queued_producer_maintenance_tests {
14668    use super::*;
14669    #[test]
14670    fn queued_publication_defers_discovery_to_worker_maintenance() {
14671        crate::tests::ensure_common_test_schema();
14672        let ty = DataType::named("GPS_DATA");
14673        for variant in 0..4 {
14674            let sent = Arc::new(std::sync::Mutex::new(Vec::new()));
14675            let output = sent.clone();
14676            let router = Router::new_with_clock(RouterConfig::default(), Box::new(|| 0));
14677            router.add_side_packed("wire", move |bytes| {
14678                output
14679                    .lock()
14680                    .unwrap()
14681                    .push(wire_format::unpack_packet(bytes)?.data_type());
14682                Ok(())
14683            });
14684            router.clear_queues();
14685            let packet =
14686                Packet::from_f32_slice(ty, &[1.0, 2.0, 3.0], message_meta(ty).endpoints_ref(), 42)
14687                    .unwrap();
14688            match variant {
14689                0 => router.tx_queue(packet).unwrap(),
14690                1 => router
14691                    .tx_packed_queue(wire_format::pack_packet(&packet))
14692                    .unwrap(),
14693                2 => router.log_queue(ty, &[1.0f32, 2.0, 3.0]).unwrap(),
14694                _ => router.log_queue_ts(ty, 42, &[1.0f32, 2.0, 3.0]).unwrap(),
14695            }
14696            assert!(
14697                sent.lock().unwrap().is_empty(),
14698                "producer invoked a transport"
14699            );
14700            assert_eq!(
14701                router.state.lock().transmit_queue.len(),
14702                1,
14703                "queued API performed discovery on the producer stack (variant {variant})"
14704            );
14705            router.periodic(0).unwrap();
14706            let sent = sent.lock().unwrap();
14707            assert!(sent.contains(&ty));
14708            assert!(
14709                sent.iter().copied().any(discovery::is_discovery_type),
14710                "worker maintenance must still announce the board"
14711            );
14712        }
14713    }
14714}
14715
14716#[cfg(all(test, feature = "std"))]
14717mod bounded_fair_queue_tests {
14718    use super::*;
14719    #[test]
14720    fn small_canonical_reports_survive_fast_senders_fixed_ring_bursts() {
14721        let ep = crate::config::register_endpoint("ROUTER_SMALL_QUEUE_DEST", false).unwrap();
14722        let ty = crate::config::register_data_type_with_description(
14723            "ROUTER_SMALL_QUEUE_REPORT",
14724            "",
14725            crate::MessageElement::Static(
14726                4,
14727                crate::MessageDataType::UInt8,
14728                crate::MessageClass::Data,
14729            ),
14730            &[ep],
14731            crate::ReliableMode::None,
14732            0,
14733        )
14734        .unwrap();
14735        for tx in [false, true] {
14736            for slow_last in [false, true] {
14737                let node = Router::new_with_clock(RouterConfig::default(), Box::new(|| 0));
14738                let make = |sender| {
14739                    RouterItem::Packed(crate::shared_bytes::convert(wire_format::pack_packet(
14740                        &Packet::new(ty, &[ep], sender, 1, Arc::<[u8]>::from([1; 4])).unwrap(),
14741                    )))
14742                };
14743                let rx = |data| RouterRxItem {
14744                    src: None,
14745                    data,
14746                    priority: 0,
14747                };
14748                let tx_item = |data| TxQueued {
14749                    item: RouterTxItem::Broadcast(data),
14750                    ignore_local: true,
14751                    priority: 0,
14752                    retry_after_ms: 0,
14753                };
14754                let mut st = node.state.lock();
14755                st.memory.max_queue_budget = usize::MAX;
14756                if tx {
14757                    st.transmit_queue = BoundedDeque::new(8192, 3 * size_of::<TxQueued>(), 1.0);
14758                } else {
14759                    st.received_queue = BoundedDeque::new(8192, 3 * size_of::<RouterRxItem>(), 1.0);
14760                }
14761                let order = if slow_last {
14762                    ["FAST", "FAST", "SLOW"]
14763                } else {
14764                    ["SLOW", "FAST", "FAST"]
14765                };
14766                for sender in order.into_iter().chain(core::iter::repeat_n("FAST", 32)) {
14767                    if tx {
14768                        st.push_transmit(tx_item(make(sender))).unwrap();
14769                    } else {
14770                        st.push_received(rx(make(sender))).unwrap();
14771                    }
14772                }
14773                let slow = wire_format::source_address_for_sender("SLOW");
14774                let retained = if tx {
14775                    st.transmit_queue
14776                        .iter()
14777                        .any(|item| RouterInner::tx_bulk_owner(item) == Some(slow))
14778                } else {
14779                    st.received_queue.iter().any(|item| {
14780                        RouterInner::fair_bulk_owner(&item.data, item.priority) == Some(slow)
14781                    })
14782                };
14783                assert!(
14784                    retained,
14785                    "small report was displaced: tx={tx}, slow_last={slow_last}"
14786                );
14787            }
14788        }
14789    }
14790
14791    #[test]
14792    fn slow_stream_survives_same_board_fast_stream_bursts() {
14793        let ep = crate::config::register_endpoint("ROUTER_STREAM_QUEUE_DEST", false).unwrap();
14794        let ty = crate::config::register_data_type_with_description(
14795            "ROUTER_STREAM_QUEUE_FAST",
14796            "",
14797            crate::MessageElement::Static(
14798                64,
14799                crate::MessageDataType::UInt8,
14800                crate::MessageClass::Data,
14801            ),
14802            &[ep],
14803            crate::ReliableMode::None,
14804            0,
14805        )
14806        .unwrap();
14807        let slow_ty = crate::config::register_data_type_with_description(
14808            "ROUTER_STREAM_QUEUE_SLOW",
14809            "",
14810            crate::MessageElement::Static(
14811                64,
14812                crate::MessageDataType::UInt8,
14813                crate::MessageClass::Data,
14814            ),
14815            &[ep],
14816            crate::ReliableMode::None,
14817            0,
14818        )
14819        .unwrap();
14820        for mode in 0..3 {
14821            for tx in [false, true] {
14822                for slow_last in [false, true] {
14823                    let node = Router::new_with_clock(RouterConfig::default(), Box::new(|| 0));
14824                    let make = |sender| {
14825                        let packet = Packet::new(
14826                            if sender == "SLOW" { slow_ty } else { ty },
14827                            &[ep],
14828                            "SAME_BOARD",
14829                            1,
14830                            Arc::<[u8]>::from([1; 64]),
14831                        )
14832                        .unwrap();
14833                        if mode == 1 {
14834                            RouterItem::Packet(packet)
14835                        } else {
14836                            RouterItem::Packed(crate::shared_bytes::convert(
14837                                wire_format::pack_packet(&packet),
14838                            ))
14839                        }
14840                    };
14841                    let rx = |data| RouterRxItem {
14842                        src: None,
14843                        data,
14844                        priority: 0,
14845                    };
14846                    let tx_item = |data| TxQueued {
14847                        item: RouterTxItem::Broadcast(data),
14848                        ignore_local: true,
14849                        priority: 0,
14850                        retry_after_ms: 0,
14851                    };
14852                    let mut st = node.state.lock();
14853                    st.memory.max_queue_budget = usize::MAX;
14854                    if mode == 0 {
14855                        if tx {
14856                            st.transmit_queue =
14857                                BoundedDeque::new(8192, 3 * size_of::<TxQueued>(), 1.0);
14858                        } else {
14859                            st.received_queue =
14860                                BoundedDeque::new(8192, 3 * size_of::<RouterRxItem>(), 1.0);
14861                        }
14862                    } else if mode == 1 {
14863                        let cost = if tx {
14864                            tx_item(make("SLOW")).byte_cost()
14865                                + 2 * tx_item(make("FAST")).byte_cost()
14866                        } else {
14867                            rx(make("SLOW")).byte_cost() + 2 * rx(make("FAST")).byte_cost()
14868                        };
14869                        if tx {
14870                            st.transmit_queue = BoundedDeque::new(cost, cost, 2.0);
14871                        } else {
14872                            st.received_queue = BoundedDeque::new(cost, cost, 2.0);
14873                        }
14874                    }
14875                    let order = if slow_last {
14876                        ["FAST", "FAST", "SLOW"]
14877                    } else {
14878                        ["SLOW", "FAST", "FAST"]
14879                    };
14880                    for (index, sender) in order
14881                        .into_iter()
14882                        .chain(core::iter::repeat_n("FAST", 32))
14883                        .enumerate()
14884                    {
14885                        if mode == 2 && index == 3 {
14886                            st.memory.max_queue_budget = st.shared_queue_bytes_used();
14887                        }
14888                        if tx {
14889                            st.push_transmit(tx_item(make(sender))).unwrap();
14890                        } else {
14891                            st.push_received(rx(make(sender))).unwrap();
14892                        }
14893                    }
14894                    let is_slow = |data: &RouterItem| match data {
14895                        RouterItem::Packet(p) => p.data_type() == slow_ty,
14896                        RouterItem::Packed(b) => {
14897                            wire_format::scheduling_type(b.as_ref()) == Some(slow_ty)
14898                        }
14899                    };
14900                    let retained = if tx {
14901                        st.transmit_queue.iter().any(|item| {
14902                            let data = match &item.item {
14903                                RouterTxItem::Broadcast(data)
14904                                | RouterTxItem::ToSide { data, .. } => data,
14905                                _ => return false,
14906                            };
14907                            is_slow(data)
14908                        })
14909                    } else {
14910                        st.received_queue.iter().any(|item| is_slow(&item.data))
14911                    };
14912                    assert!(
14913                        retained,
14914                        "slow stream displaced: mode={mode}, tx={tx}, slow_last={slow_last}"
14915                    );
14916                }
14917            }
14918        }
14919    }
14920
14921    #[test]
14922    fn fast_producer_preserves_slow_reports_at_each_queue_boundary() {
14923        let ep = crate::config::register_endpoint("QUEUE_FAIR_DEST", false).unwrap();
14924        let ty = crate::config::register_data_type_with_description(
14925            "QUEUE_FAIR_REPORT",
14926            "",
14927            MessageElement::Static(64, crate::MessageDataType::UInt8, crate::MessageClass::Data),
14928            &[ep],
14929            crate::ReliableMode::None,
14930            0,
14931        )
14932        .unwrap();
14933        for mode in 0..3 {
14934            for tx in [false, true] {
14935                for slow_last in [false, true] {
14936                    for next_sender in ["FAST", "NEW_SLOW"] {
14937                        let router =
14938                            Router::new_with_clock(RouterConfig::default(), Box::new(|| 0));
14939                        let make = |sender| {
14940                            RouterItem::Packet(
14941                                Packet::new(ty, &[ep], sender, 1, Arc::<[u8]>::from([1; 64]))
14942                                    .unwrap(),
14943                            )
14944                        };
14945                        let rx = |data| RouterRxItem {
14946                            src: None,
14947                            data,
14948                            priority: 0,
14949                        };
14950                        let tx_item = |data| TxQueued {
14951                            item: RouterTxItem::Broadcast(data),
14952                            ignore_local: true,
14953                            priority: 0,
14954                            retry_after_ms: 0,
14955                        };
14956                        let order = if slow_last {
14957                            ["FAST", "FAST", "SLOW"]
14958                        } else {
14959                            ["SLOW", "FAST", "FAST"]
14960                        };
14961                        let mut st = router.state.lock();
14962                        st.memory.max_queue_budget = usize::MAX;
14963                        let cost = if tx {
14964                            order.iter().map(|s| tx_item(make(s)).byte_cost()).sum()
14965                        } else {
14966                            order.iter().map(|s| rx(make(s)).byte_cost()).sum()
14967                        };
14968                        if mode == 0 {
14969                            if tx {
14970                                st.transmit_queue = BoundedDeque::new(cost, cost, 2.0);
14971                            } else {
14972                                st.received_queue = BoundedDeque::new(cost, cost, 2.0);
14973                            }
14974                        } else if mode == 1 {
14975                            if tx {
14976                                st.transmit_queue =
14977                                    BoundedDeque::new(cost * 10, 3 * size_of::<TxQueued>(), 1.0);
14978                            } else {
14979                                st.received_queue = BoundedDeque::new(
14980                                    cost * 10,
14981                                    3 * size_of::<RouterRxItem>(),
14982                                    1.0,
14983                                );
14984                            }
14985                        }
14986                        for sender in order {
14987                            if tx {
14988                                st.push_transmit(tx_item(make(sender))).unwrap();
14989                            } else {
14990                                st.push_received(rx(make(sender))).unwrap();
14991                            }
14992                        }
14993                        if mode == 2 {
14994                            st.memory.max_queue_budget = st.shared_queue_bytes_used();
14995                        }
14996                        if tx {
14997                            st.push_transmit(tx_item(make(next_sender))).unwrap_or_else(|error| panic!("admit error {error:?}: mode={mode} tx={tx} slow_last={slow_last} incoming={next_sender}"));
14998                        } else {
14999                            st.push_received(rx(make(next_sender))).unwrap_or_else(|error| panic!("admit error {error:?}: mode={mode} tx={tx} slow_last={slow_last} incoming={next_sender}"));
15000                        }
15001                        let slow = wire_format::source_address_for_sender("SLOW");
15002                        let retained = if tx {
15003                            st.transmit_queue
15004                                .iter()
15005                                .any(|item| RouterInner::tx_bulk_owner(item) == Some(slow))
15006                        } else {
15007                            st.received_queue.iter().any(|item| {
15008                                RouterInner::fair_bulk_owner(&item.data, item.priority)
15009                                    == Some(slow)
15010                            })
15011                        };
15012                        assert!(
15013                            retained,
15014                            "slow report evicted: mode={mode} tx={tx} slow_last={slow_last} incoming={next_sender}"
15015                        );
15016                    }
15017                }
15018            }
15019        }
15020    }
15021}