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).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!(router.register_end_to_end_reliable_tx(&rejected).is_err());
83        {
84            let mut state = router.state.lock();
85            assert_eq!(state.end_to_end_reliable_tx.len(), capacity);
86            let first = state.end_to_end_reliable_tx_order[0];
87            state
88                .end_to_end_reliable_tx
89                .get_mut(&first)
90                .unwrap()
91                .retries = runtime_reliable_max_retries();
92        }
93        now.store(runtime_reliable_retransmit_ms(), Ordering::Relaxed);
94        assert!(router.process_end_to_end_reliable_timeouts().is_err());
95        assert!(router.state.lock().end_to_end_reliable_tx.is_empty());
96    }
97    #[test]
98    fn compact_reachability_survives_incomplete_detailed_topology() {
99        crate::tests::ensure_common_test_schema();
100        let router = Router::new(RouterConfig::new([]).with_sender("GB"));
101        let endpoint = DataEndpoint::try_named("GROUND_STATION")
102            .or_else(|| DataEndpoint::try_named("RADIO"))
103            .unwrap();
104        router.compact_summary_fixture(endpoint);
105        let state = router.state.lock();
106        let now = router.clock.now_ms();
107        let advertised =
108            router.advertised_discovery_endpoints_for_link_locked(&state, now, true, Some(1));
109        assert!(
110            advertised.contains(&endpoint),
111            "compact subscriber route was lost at the bridge"
112        );
113        let reflected =
114            router.advertised_discovery_endpoints_for_link_locked(&state, now, true, Some(0));
115        assert!(
116            !reflected.contains(&endpoint),
117            "split horizon must still exclude ingress"
118        );
119        assert_eq!(
120            router.advertised_discovery_timesync_sources_for_link_locked(&state, now, Some(1)),
121            vec!["clock-behind-bridge".to_string()]
122        );
123        assert!(
124            router
125                .advertised_discovery_timesync_sources_for_link_locked(&state, now, Some(0))
126                .is_empty()
127        );
128        assert!(
129            !router
130                .advertised_discovery_endpoints_for_link_locked(
131                    &state,
132                    now + DISCOVERY_ROUTE_TTL_MS + 1,
133                    true,
134                    Some(1)
135                )
136                .contains(&endpoint)
137        );
138        assert!(
139            router
140                .advertised_discovery_timesync_sources_for_link_locked(
141                    &state,
142                    now + DISCOVERY_ROUTE_TTL_MS + 1,
143                    Some(1)
144                )
145                .is_empty()
146        );
147    }
148}
149
150use crate::config::RuntimeMemoryConfig;
151use crate::diagnostics::{
152    AdaptiveLinkStats, DiscoveryRuntimeStats, QueueRuntimeStats, ReliableRuntimeStats,
153    RouteModeStats, RouteOverrideStats, RoutePriorityStats, RouteWeightStats, RuntimeSideStats,
154    RuntimeStatsSnapshot, RuntimeTypeStats, TypedRouteOverrideStats,
155};
156#[cfg(feature = "discovery")]
157use crate::discovery::{
158    self, ClientStatsSnapshot, DISCOVERY_INCREMENTAL_RETRY_COUNT, DISCOVERY_ROUTE_TTL_MS,
159    DISCOVERY_SLOW_LINK_PING_INTERVAL_MS, DiscoveryCadenceState,
160    TIMESYNC_SLOW_LINK_MIN_INTERVAL_MS, TopologyAnnouncerRoute, TopologyBoardNode,
161    TopologySideRoute, TopologySnapshot,
162};
163use crate::packet::{hash_bytes_u64, sender_address_u32};
164use crate::queue::{BoundedDeque, ByteCost};
165#[cfg(all(not(feature = "std"), target_os = "none"))]
166use crate::seds_error_msg;
167#[cfg(feature = "timesync")]
168use crate::timesync::{
169    INTERNAL_TIMESYNC_SOURCE_ID, LOCAL_TIMESYNC_DATE_SOURCE_ID, LOCAL_TIMESYNC_FULL_SOURCE_ID,
170    LOCAL_TIMESYNC_SUBSEC_SOURCE_ID, LOCAL_TIMESYNC_TOD_SOURCE_ID, NetworkClock,
171    NetworkTimeReading, PartialNetworkTime, SlewedNetworkClock, TimeSyncConfig, TimeSyncLeader,
172    TimeSyncTracker, advance_network_time, compute_network_time_sample, decode_timesync_announce,
173    decode_timesync_request, decode_timesync_response,
174};
175use crate::{
176    E2eEncryptionPolicy, MessageElement, RouteSelectionMode, TelemetryError, TelemetryResult,
177    config::{
178        DataEndpoint, DataType, runtime_device_identifier, runtime_max_handler_retries,
179        runtime_reliable_max_end_to_end_pending, runtime_reliable_max_pending,
180        runtime_reliable_max_retries, runtime_reliable_max_return_routes,
181        runtime_reliable_retransmit_ms,
182    },
183    get_needed_message_size, impl_letype_num, is_reliable_type,
184    lock::{ReentryGate, RouterMutex},
185    message_e2e_encryption_policy, message_meta, message_priority,
186    packet::Packet,
187    reliable_mode, wire_format,
188};
189use alloc::string::{String, ToString};
190use alloc::{
191    borrow::ToOwned,
192    boxed::Box,
193    collections::{BTreeMap, BTreeSet, VecDeque},
194    format,
195    sync::Arc,
196    vec,
197    vec::Vec,
198};
199use core::cell::UnsafeCell;
200use core::fmt;
201use core::fmt::{Debug, Formatter};
202use core::mem::size_of;
203use core::ops::{Deref, DerefMut};
204use core::sync::atomic::{AtomicBool, AtomicU32, Ordering};
205#[cfg(feature = "std")]
206use std::time::Instant;
207
208/// Logical side index (CAN, UART, RADIO, etc.)
209pub type RouterSideId = usize;
210
211const CONTROL_SLOW_LINK_CAPACITY_BPS: u64 = 512;
212pub const IPV4_LIKE_COMPACT_HEADER_TARGET_BYTES: usize = 20;
213pub const IPV6_LIKE_COMPACT_HEADER_TARGET_BYTES: usize = 40;
214pub const DEFAULT_SIDE_TRANSPORT_TEMPLATE_LIMIT: usize = 64;
215static ROUTER_INSTANCE_SEQ: AtomicU32 = AtomicU32::new(1);
216
217#[derive(Clone, Copy, Debug, PartialEq, Eq)]
218pub enum SideTransportProfile {
219    Canonical,
220    Template,
221    Ipv6Like,
222    Ipv4Like,
223}
224
225impl SideTransportProfile {
226    #[inline]
227    pub const fn as_str(self) -> &'static str {
228        match self {
229            Self::Canonical => "canonical",
230            Self::Template => "template",
231            Self::Ipv6Like => "ipv6_like",
232            Self::Ipv4Like => "ipv4_like",
233        }
234    }
235
236    #[cfg(feature = "discovery")]
237    #[inline]
238    pub const fn discovery_code(self) -> u8 {
239        match self {
240            Self::Canonical => discovery::LINK_PROFILE_CANONICAL,
241            Self::Template => discovery::LINK_PROFILE_TEMPLATE,
242            Self::Ipv6Like => discovery::LINK_PROFILE_IPV6_LIKE,
243            Self::Ipv4Like => discovery::LINK_PROFILE_IPV4_LIKE,
244        }
245    }
246}
247
248#[cfg(test)]
249mod chunk_assembly_memory_tests {
250    use super::*;
251    #[test]
252    fn full_chunk_budget_reclaims_other_partial_transfers_before_active_schema() {
253        let mut state = SideTransportState::default();
254        for id in 1..=4 {
255            let mut assembly = SideChunkAssembly {
256                total: 28,
257                last_seen_ms: id as u64,
258                ..Default::default()
259            };
260            for index in 0..23 {
261                assembly.received.insert(
262                    index,
263                    crate::shared_bytes::convert(Arc::<[u8]>::from([id as u8; 112])),
264                );
265            }
266            state.rx_chunks.insert(id, assembly);
267        }
268        state.rx_chunks.get_mut(&1).unwrap().received.insert(
269            23,
270            crate::shared_bytes::convert(Arc::<[u8]>::from([1u8; 56])),
271        );
272        // 16192 + (56 + 64) = 16312 bytes, as in the Gateway capture.
273        SideChunkAssembly::make_room(&mut state.rx_chunks, 4, 23, 28, 112, 16384).unwrap();
274        assert!(!state.rx_chunks.contains_key(&1));
275        assert_eq!(state.rx_chunks[&4].received.len(), 23);
276        for index in 23..28 {
277            SideChunkAssembly::make_room(&mut state.rx_chunks, 4, index, 28, 112, 16384).unwrap();
278            state.rx_chunks.get_mut(&4).unwrap().received.insert(
279                index,
280                crate::shared_bytes::convert(Arc::<[u8]>::from([4u8; 112])),
281            );
282        }
283        assert_eq!(
284            state.rx_chunks.remove(&4).unwrap().assemble().unwrap(),
285            vec![4u8; 28 * 112]
286        );
287        assert!(
288            state
289                .rx_chunks
290                .values()
291                .flat_map(|a| a.received.values())
292                .map(|p| p.len() + 64)
293                .sum::<usize>()
294                <= 16384
295        );
296    }
297
298    #[test]
299    fn duplicate_chunk_keeps_partial_transfers_but_oversize_transfer_is_rejected() {
300        let mut state = SideTransportState::default();
301        let mut assembly = SideChunkAssembly {
302            total: 2,
303            ..Default::default()
304        };
305        assembly.received.insert(
306            0,
307            crate::shared_bytes::convert(Arc::<[u8]>::from([1u8; 112])),
308        );
309        state.rx_chunks.insert(1, assembly.clone());
310        state.rx_chunks.insert(2, assembly);
311        SideChunkAssembly::make_room(&mut state.rx_chunks, 1, 0, 2, 112, 352).unwrap();
312        assert_eq!(state.rx_chunks.len(), 2);
313        assert!(SideChunkAssembly::make_room(&mut state.rx_chunks, 1, 1, 2, 400, 352).is_err());
314        assert!(!state.rx_chunks.contains_key(&1));
315        assert!(state.rx_chunks.contains_key(&2));
316    }
317
318    #[test]
319    fn schema_sized_reassembly_allocates_exactly_and_preserves_order() {
320        let payload: Vec<u8> = (0..3652).map(|i| (i % 251) as u8).collect();
321        let mut assembly = SideChunkAssembly::default();
322        for (index, chunk) in payload.chunks(56).enumerate().rev() {
323            assembly.received.insert(
324                index as u16,
325                crate::shared_bytes::convert(Arc::<[u8]>::from(chunk)),
326            );
327        }
328        assembly.total = assembly.received.len() as u16;
329        let out = assembly.assemble().unwrap();
330        assert_eq!(out, payload);
331        assert_eq!(
332            out.capacity(),
333            payload.len(),
334            "do not double discovery reassembly capacity"
335        );
336    }
337    #[test]
338    fn incomplete_or_noncontiguous_chunks_are_rejected() {
339        let mut assembly = SideChunkAssembly {
340            total: 2,
341            ..Default::default()
342        };
343        assembly
344            .received
345            .insert(0, crate::shared_bytes::convert(Arc::<[u8]>::from([1u8])));
346        assert!(assembly.clone().assemble().is_err());
347        assembly
348            .received
349            .insert(2, crate::shared_bytes::convert(Arc::<[u8]>::from([2u8])));
350        assert!(assembly.assemble().is_err());
351    }
352}
353
354#[derive(Clone, Debug, Default)]
355struct SideTransportState {
356    tx_chunk_progress: Option<Box<crate::side_transport::SideTxProgress>>,
357    tx_template_ids: BTreeMap<u64, u32>,
358    tx_templates: BTreeMap<u64, SideHeaderTemplate>,
359    tx_last_timestamps: BTreeMap<u32, u64>,
360    tx_compact_uses: BTreeMap<u32, u8>,
361    rx_templates: BTreeMap<u64, u32>,
362    rx_templates_by_id: BTreeMap<u32, SideHeaderTemplate>,
363    rx_last_timestamps: BTreeMap<u32, u64>,
364    rx_chunks: BTreeMap<u32, SideChunkAssembly>,
365}
366
367impl SideTransportState {
368    fn refresh_transmit_header_templates(&mut self) {
369        // Refresh on the wire, not by forgetting the eviction history. RX
370        // retains its dictionary; clearing only TX makes bounded caches evict
371        // different entries after the next insertion/capacity negotiation.
372        for uses in self.tx_compact_uses.values_mut() {
373            *uses = u8::MAX;
374        }
375        // Local topology changes do not invalidate dictionaries received from
376        // peers. Dropping RX state here loses already-in-flight compact ACKs.
377    }
378
379    fn tx_template_count(&self) -> usize {
380        self.tx_template_ids.len()
381    }
382
383    fn rx_template_count(&self) -> usize {
384        self.rx_templates_by_id.len()
385    }
386
387    fn trim_tx_templates_to(&mut self, limit: usize) -> usize {
388        let mut removed = 0usize;
389        while self.tx_template_ids.len() > limit {
390            let Some(old_hash) = self.tx_template_ids.keys().next().copied() else {
391                break;
392            };
393            if let Some(old_id) = self.tx_template_ids.remove(&old_hash) {
394                self.tx_last_timestamps.remove(&old_id);
395                self.tx_compact_uses.remove(&old_id);
396            }
397            self.tx_templates.remove(&old_hash);
398            removed += 1;
399        }
400        removed
401    }
402
403    fn insert_tx_template(
404        &mut self,
405        template: SideHeaderTemplate,
406        template_id: u32,
407        max_templates: usize,
408    ) -> bool {
409        if max_templates == 0 {
410            return false;
411        }
412        let evicted = !self.tx_template_ids.contains_key(&template.hash)
413            && self.trim_tx_templates_to(max_templates.saturating_sub(1)) > 0;
414        self.tx_template_ids.insert(template.hash, template_id);
415        self.tx_templates.insert(template.hash, template);
416        self.tx_compact_uses.insert(template_id, 0);
417        evicted
418    }
419
420    fn insert_rx_template(
421        &mut self,
422        template_id: u32,
423        template: SideHeaderTemplate,
424        max_templates: usize,
425    ) -> bool {
426        if max_templates == 0 {
427            return false;
428        }
429        // Keep one owned header. The hash index needs only its wire ID, not
430        // another copy of the header and its shared-buffer owners.
431        if let Some(old_id) = self.rx_templates.get(&template.hash).copied()
432            && old_id != template_id
433        {
434            self.rx_templates_by_id.remove(&old_id);
435            self.rx_last_timestamps.remove(&old_id);
436            self.rx_templates.remove(&template.hash);
437        }
438        if let Some(previous) = self.rx_templates_by_id.get(&template_id)
439            && previous.hash != template.hash
440        {
441            self.rx_templates.remove(&previous.hash);
442            self.rx_last_timestamps.remove(&template_id);
443        }
444        let mut evicted = false;
445        if self.rx_templates_by_id.len() >= max_templates
446            && !self.rx_templates_by_id.contains_key(&template_id)
447            // Match TX's deterministic lowest-hash eviction on both routers
448            // and relays; lowest wire ID can select a different live header.
449            && let Some((&old_hash, &old_id)) = self.rx_templates.first_key_value()
450        {
451            self.rx_templates.remove(&old_hash);
452            self.rx_templates_by_id.remove(&old_id);
453            self.rx_last_timestamps.remove(&old_id);
454            evicted = true;
455        }
456        self.rx_templates.insert(template.hash, template_id);
457        self.rx_templates_by_id.insert(template_id, template);
458        evicted
459    }
460}
461
462#[derive(Clone, Debug, PartialEq, Eq)]
463pub struct CompactTimestampOmissionPolicy {
464    all: bool,
465    types: Option<Arc<[DataType]>>,
466}
467impl CompactTimestampOmissionPolicy {
468    pub const fn none() -> Self {
469        Self {
470            all: false,
471            types: None,
472        }
473    }
474    pub const fn all() -> Self {
475        Self {
476            all: true,
477            types: None,
478        }
479    }
480    pub fn with_data_type(mut self, ty: DataType) -> Self {
481        self.insert(ty);
482        self
483    }
484    pub fn insert(&mut self, ty: DataType) {
485        if self.contains(ty) {
486            return;
487        }
488        let current = self.types.as_deref().unwrap_or(&[]);
489        let mut types = Vec::with_capacity(current.len() + 1);
490        types.extend_from_slice(current);
491        let index = types.binary_search(&ty).unwrap_err();
492        types.insert(index, ty);
493        self.types = Some(Arc::from(types));
494    }
495    pub fn contains(&self, ty: DataType) -> bool {
496        self.all
497            || self
498                .types
499                .as_deref()
500                .is_some_and(|types| types.binary_search(&ty).is_ok())
501    }
502    pub fn is_empty(&self) -> bool {
503        !self.all && self.types.is_none()
504    }
505}
506
507impl Default for CompactTimestampOmissionPolicy {
508    #[inline]
509    fn default() -> Self {
510        Self::none()
511    }
512}
513
514#[derive(Clone, Debug, PartialEq, Eq)]
515pub enum RouterItem {
516    Packet(Packet),
517    Packed(crate::SharedBytes),
518}
519
520#[cfg(feature = "compact-packet-store")]
521impl RouterItem {
522    fn park_for_queue(&mut self) -> TelemetryResult<()> {
523        match self {
524            Self::Packet(packet) => packet.park_for_queue()?,
525            Self::Packed(bytes) => bytes.park_for_queue()?,
526        }
527        Ok(())
528    }
529}
530
531#[derive(Clone, Debug, PartialEq, Eq)]
532struct RouterRxItem {
533    src: Option<RouterSideId>,
534    data: RouterItem,
535    priority: u8,
536}
537
538#[derive(Clone, Debug, PartialEq, Eq)]
539enum RouterTxItem {
540    Broadcast(RouterItem),
541    EndToEndReplay {
542        packet_id: u64,
543    },
544    ToSide {
545        src: Option<RouterSideId>,
546        dst: RouterSideId,
547        data: RouterItem,
548    },
549    ReliableReplay {
550        dst: RouterSideId,
551        bytes: crate::SharedBytes,
552    },
553}
554
555impl ByteCost for RouterRxItem {
556    #[cfg(feature = "compact-packet-store")]
557    fn park_for_queue(&mut self) -> TelemetryResult<()> {
558        self.data.park_for_queue()
559    }
560    #[inline]
561    fn byte_cost(&self) -> usize {
562        match &self.data {
563            RouterItem::Packet(pkt) => pkt.byte_cost(),
564            RouterItem::Packed(bytes) => size_of::<crate::SharedBytes>() + bytes.len(),
565        }
566    }
567}
568
569impl ByteCost for RouterTxItem {
570    #[cfg(feature = "compact-packet-store")]
571    fn park_for_queue(&mut self) -> TelemetryResult<()> {
572        match self {
573            Self::Broadcast(item) | Self::ToSide { data: item, .. } => item.park_for_queue(),
574            Self::ReliableReplay { bytes, .. } => bytes.park_for_queue(),
575            _ => Ok(()),
576        }
577    }
578    #[inline]
579    fn byte_cost(&self) -> usize {
580        match self {
581            RouterTxItem::Broadcast(data) => match data {
582                RouterItem::Packet(pkt) => pkt.byte_cost(),
583                RouterItem::Packed(bytes) => size_of::<crate::SharedBytes>() + bytes.len(),
584            },
585            RouterTxItem::EndToEndReplay { .. } => size_of::<u64>(),
586            RouterTxItem::ToSide { data, .. } => match data {
587                RouterItem::Packet(pkt) => pkt.byte_cost(),
588                RouterItem::Packed(bytes) => size_of::<crate::SharedBytes>() + bytes.len(),
589            },
590            RouterTxItem::ReliableReplay { bytes, .. } => {
591                size_of::<crate::SharedBytes>() + bytes.len()
592            }
593        }
594    }
595}
596
597/// Transmit queue item with flags.
598/// Holds a RouterTxItem and a flag to ignore local dispatch.
599/// Used internally by the Router transmit queue.
600#[derive(Clone, Debug, PartialEq, Eq)]
601struct TxQueued {
602    item: RouterTxItem,
603    ignore_local: bool,
604    priority: u8,
605    retry_after_ms: u64,
606}
607
608/// Do not allow a continuously replenished stream of control traffic to keep
609/// an already-queued application packet from ever reaching a transport.
610/// Priorities still determine normal ordering; this only services the oldest
611/// lowest-priority item after a bounded high-priority burst.
612const MAX_PRIORITY_BURST: u8 = 8;
613
614/// ByteCost implementation for TxQueued.
615impl ByteCost for TxQueued {
616    #[cfg(feature = "compact-packet-store")]
617    fn park_for_queue(&mut self) -> TelemetryResult<()> {
618        self.item.park_for_queue()
619    }
620    /// Byte cost is the cost of the inner item plus one bool.
621    #[inline]
622    fn byte_cost(&self) -> usize {
623        self.item.byte_cost() + size_of::<bool>() + size_of::<u8>() + size_of::<u64>()
624    }
625}
626
627/// ByteCost implementation for `u64` (used by `recent_rx`).
628impl ByteCost for u64 {
629    /// Byte cost is size of u64.
630    #[inline]
631    fn byte_cost(&self) -> usize {
632        size_of::<u64>()
633    }
634}
635
636// -------------------- Reliable delivery state --------------------
637
638#[derive(Debug, Clone)]
639struct ReliableTxState {
640    next_seq: u32,
641    sent_order: VecDeque<u32>,
642    sent: BTreeMap<u32, ReliableSent>,
643}
644
645#[derive(Debug, Clone)]
646struct ReliableSent {
647    bytes: crate::SharedBytes,
648    last_send_ms: u64,
649    retries: u32,
650    queued: bool,
651    partial_acked: bool,
652}
653
654#[derive(Debug, Clone)]
655struct EndToEndReliableSent {
656    ordered_type: Option<u32>,
657    waiting_for_predecessor: bool,
658    data: RouterItem,
659    pending_destinations: BTreeMap<u64, RouterSideId>,
660    tracked_destinations: bool,
661    last_send_ms: u64,
662    retries: u32,
663    queued: bool,
664}
665
666#[derive(Debug, Clone)]
667struct ReliableRxState {
668    expected_seq: u32,
669    buffered: BTreeMap<u32, crate::SharedBytes>,
670}
671
672#[derive(Debug, Clone)]
673struct ReliableReturnRouteState {
674    side: RouterSideId,
675    last_replay_ms: u64,
676    // A legacy contract names this router, not its downstream owner.
677    // Translate the child receipt back to the router identity and original
678    // packet ID that the upstream publisher is awaiting.
679    upstream_contract: Option<(u64, u64)>,
680}
681
682#[cfg(feature = "discovery")]
683#[derive(Debug, Clone, Default, PartialEq, Eq)]
684struct DiscoverySenderState {
685    /// Numeric identity advertised for this hostname. Compact frames may carry
686    /// this address instead of the hostname, so routing must accept either.
687    advertised_address: Option<u32>,
688    reachable: Vec<DataEndpoint>,
689    advertised_reachable: Vec<DataEndpoint>,
690    reachable_network_variables: Vec<DataType>,
691    reachable_timesync_sources: Vec<String>,
692    advertised_reachable_timesync_sources: Vec<String>,
693    link_capabilities: Option<discovery::LinkCapabilities>,
694    topology_boards: Vec<TopologyBoardNode>,
695    has_full_topology: bool,
696    has_schema: bool,
697    last_seen_ms: u64,
698}
699
700#[cfg(feature = "discovery")]
701#[derive(Debug, Clone, Default, PartialEq, Eq)]
702struct DiscoverySideState {
703    reachable: Vec<DataEndpoint>,
704    reachable_network_variables: Vec<DataType>,
705    requested_network_variables: Vec<(DataType, u64)>,
706    reachable_timesync_sources: Vec<String>,
707    last_seen_ms: u64,
708    announcers: BTreeMap<String, DiscoverySenderState>,
709}
710
711#[cfg(feature = "discovery")]
712#[derive(Debug, Clone, Default)]
713struct DiscoverySideThrottleState {
714    next_topology_request_ms: u64,
715    next_schema_request_ms: u64,
716    next_ping_ms: u64,
717    // Scheduled and accepted sends differ when a constrained link is backed up.
718    last_liveness_tx_ms: u64,
719    pending_incremental: bool,
720    incremental_retries_remaining: u8,
721    has_sent_full: bool,
722    last_topology: Vec<TopologyBoardNode>,
723}
724
725#[cfg(feature = "discovery")]
726impl DiscoverySideThrottleState {
727    fn liveness_due(&self, now_ms: u64) -> bool {
728        now_ms >= self.next_ping_ms
729            && now_ms.saturating_sub(self.last_liveness_tx_ms)
730                >= DISCOVERY_SLOW_LINK_PING_INTERVAL_MS
731    }
732}
733
734#[cfg(all(feature = "discovery", feature = "timesync"))]
735#[derive(Debug, Clone, Default)]
736struct TimeSyncSideThrottleState {
737    next_allowed_ms: u64,
738}
739
740#[cfg(feature = "discovery")]
741#[derive(Debug, Clone, Copy, PartialEq, Eq)]
742enum DiscoveryAdvertiseLevel {
743    MinimalPing,
744    Incremental,
745    Full,
746}
747
748#[derive(Debug, Clone, Default)]
749struct AdaptiveRouteStats {
750    estimated_bandwidth_bps: u64,
751    peak_bandwidth_bps: u64,
752    last_observed_ms: u64,
753    last_slow_observed_ms: u64,
754    sample_count: u64,
755    window_started_ms: u64,
756    window_bytes: u64,
757    peak_usage_bps: u64,
758}
759
760impl AdaptiveRouteStats {
761    #[inline]
762    fn observe(&mut self, bytes: usize, sample_bps: u64, now_ms: u64) {
763        self.estimated_bandwidth_bps = if self.estimated_bandwidth_bps == 0 {
764            sample_bps
765        } else if sample_bps >= self.estimated_bandwidth_bps {
766            self.estimated_bandwidth_bps
767                .saturating_mul(3)
768                .saturating_add(sample_bps.saturating_mul(5))
769                / 8
770        } else {
771            self.estimated_bandwidth_bps
772                .saturating_mul(7)
773                .saturating_add(sample_bps)
774                / 8
775        };
776        self.peak_bandwidth_bps = self.peak_bandwidth_bps.max(sample_bps);
777        self.last_observed_ms = now_ms;
778        if sample_bps > 0 && sample_bps <= CONTROL_SLOW_LINK_CAPACITY_BPS {
779            self.last_slow_observed_ms = now_ms;
780        }
781        self.sample_count = self.sample_count.saturating_add(1);
782        if self.window_started_ms == 0 || now_ms.saturating_sub(self.window_started_ms) > 1_000 {
783            self.window_started_ms = now_ms;
784            self.window_bytes = 0;
785        }
786        self.window_bytes = self.window_bytes.saturating_add(bytes as u64);
787        self.peak_usage_bps = self.peak_usage_bps.max(self.current_usage_bps(now_ms));
788    }
789
790    #[inline]
791    fn current_usage_bps(&self, now_ms: u64) -> u64 {
792        if self.window_started_ms == 0 {
793            return 0;
794        }
795        let elapsed_ms = now_ms.saturating_sub(self.window_started_ms).max(1);
796        (u128::from(self.window_bytes).saturating_mul(1000) / u128::from(elapsed_ms))
797            .min(u128::from(u64::MAX)) as u64
798    }
799
800    #[inline]
801    fn available_headroom_bps(&self, now_ms: u64) -> u64 {
802        let capacity = self
803            .estimated_bandwidth_bps
804            .max(self.peak_bandwidth_bps)
805            .max(1);
806        capacity.saturating_sub(self.current_usage_bps(now_ms))
807    }
808
809    #[inline]
810    fn weight(&self, now_ms: u64) -> u64 {
811        self.available_headroom_bps(now_ms).max(1)
812    }
813
814    #[inline]
815    fn snapshot(&self, now_ms: u64, auto_balancing_enabled: bool) -> AdaptiveLinkStats {
816        let current_usage_bps = self.current_usage_bps(now_ms);
817        let estimated_capacity_bps = self.estimated_bandwidth_bps.max(1);
818        let peak_capacity_bps = self.peak_bandwidth_bps.max(estimated_capacity_bps);
819        let available_headroom_bps = peak_capacity_bps.saturating_sub(current_usage_bps);
820        AdaptiveLinkStats {
821            auto_balancing_enabled,
822            estimated_capacity_bps,
823            peak_capacity_bps,
824            current_usage_bps,
825            peak_usage_bps: self.peak_usage_bps.max(current_usage_bps),
826            available_headroom_bps,
827            effective_weight: available_headroom_bps.max(1),
828            last_observed_ms: self.last_observed_ms,
829            sample_count: self.sample_count,
830        }
831    }
832}
833
834#[derive(Debug, Clone, Default)]
835struct TypeRuntimeStatsInner {
836    tx_packets: u64,
837    tx_bytes: u64,
838    rx_packets: u64,
839    rx_bytes: u64,
840    relayed_tx_packets: u64,
841    relayed_tx_bytes: u64,
842    relayed_rx_packets: u64,
843    relayed_rx_bytes: u64,
844    tx_retries: u64,
845    handler_failures: u64,
846}
847
848#[derive(Debug, Clone, Default)]
849struct SideRuntimeStatsInner {
850    tx_packets: u64,
851    tx_bytes: u64,
852    rx_packets: u64,
853    rx_bytes: u64,
854    relayed_tx_packets: u64,
855    relayed_tx_bytes: u64,
856    relayed_rx_packets: u64,
857    relayed_rx_bytes: u64,
858    local_delivery_packets: u64,
859    tx_retries: u64,
860    tx_handler_failures: u64,
861    local_handler_failures: u64,
862    total_handler_retries: u64,
863    side_transport_full_frames: u64,
864    side_transport_compact_frames: u64,
865    side_transport_compact_delta_frames: u64,
866    side_transport_compact_omitted_timestamp_frames: u64,
867    side_transport_chunk_frames: u64,
868    side_transport_raw_bytes: u64,
869    side_transport_wire_bytes: u64,
870    side_transport_bytes_saved: u64,
871    side_transport_min_compact_overhead_bytes: Option<usize>,
872    side_transport_max_compact_overhead_bytes: Option<usize>,
873    side_transport_compact_target_misses: u64,
874    side_transport_template_evictions: u64,
875    data_types: BTreeMap<u32, TypeRuntimeStatsInner>,
876}
877
878impl SideRuntimeStatsInner {
879    fn type_stats_mut(&mut self, ty: DataType) -> &mut TypeRuntimeStatsInner {
880        self.data_types.entry(ty.as_u32()).or_default()
881    }
882
883    fn note_tx(&mut self, ty: DataType, bytes: usize, relayed: bool, retries: usize) {
884        self.tx_packets = self.tx_packets.saturating_add(1);
885        self.tx_bytes = self.tx_bytes.saturating_add(bytes as u64);
886        self.tx_retries = self.tx_retries.saturating_add(retries as u64);
887        self.total_handler_retries = self.total_handler_retries.saturating_add(retries as u64);
888        if relayed {
889            self.relayed_tx_packets = self.relayed_tx_packets.saturating_add(1);
890            self.relayed_tx_bytes = self.relayed_tx_bytes.saturating_add(bytes as u64);
891        }
892        let stats = self.type_stats_mut(ty);
893        stats.tx_packets = stats.tx_packets.saturating_add(1);
894        stats.tx_bytes = stats.tx_bytes.saturating_add(bytes as u64);
895        stats.tx_retries = stats.tx_retries.saturating_add(retries as u64);
896        if relayed {
897            stats.relayed_tx_packets = stats.relayed_tx_packets.saturating_add(1);
898            stats.relayed_tx_bytes = stats.relayed_tx_bytes.saturating_add(bytes as u64);
899        }
900    }
901
902    fn note_rx(&mut self, ty: DataType, bytes: usize, relayed: bool) {
903        self.rx_packets = self.rx_packets.saturating_add(1);
904        self.rx_bytes = self.rx_bytes.saturating_add(bytes as u64);
905        if relayed {
906            self.relayed_rx_packets = self.relayed_rx_packets.saturating_add(1);
907            self.relayed_rx_bytes = self.relayed_rx_bytes.saturating_add(bytes as u64);
908        }
909        let stats = self.type_stats_mut(ty);
910        stats.rx_packets = stats.rx_packets.saturating_add(1);
911        stats.rx_bytes = stats.rx_bytes.saturating_add(bytes as u64);
912        if relayed {
913            stats.relayed_rx_packets = stats.relayed_rx_packets.saturating_add(1);
914            stats.relayed_rx_bytes = stats.relayed_rx_bytes.saturating_add(bytes as u64);
915        }
916    }
917
918    fn note_local_delivery(&mut self, ty: DataType) {
919        self.local_delivery_packets = self.local_delivery_packets.saturating_add(1);
920        let stats = self.type_stats_mut(ty);
921        stats.rx_packets = stats.rx_packets.saturating_add(1);
922    }
923
924    fn note_local_handler_failure(&mut self, ty: DataType, retries: usize) {
925        self.local_handler_failures = self.local_handler_failures.saturating_add(1);
926        self.total_handler_retries = self.total_handler_retries.saturating_add(retries as u64);
927        let stats = self.type_stats_mut(ty);
928        stats.handler_failures = stats.handler_failures.saturating_add(1);
929    }
930
931    fn note_tx_failure(&mut self, ty: DataType, retries: usize) {
932        self.tx_handler_failures = self.tx_handler_failures.saturating_add(1);
933        self.total_handler_retries = self.total_handler_retries.saturating_add(retries as u64);
934        self.tx_retries = self.tx_retries.saturating_add(retries as u64);
935        let stats = self.type_stats_mut(ty);
936        stats.handler_failures = stats.handler_failures.saturating_add(1);
937        stats.tx_retries = stats.tx_retries.saturating_add(retries as u64);
938    }
939
940    fn note_side_transport_full(&mut self, raw_bytes: usize, wire_bytes: usize) {
941        self.side_transport_full_frames = self.side_transport_full_frames.saturating_add(1);
942        self.note_side_transport_bytes(raw_bytes, wire_bytes);
943    }
944
945    fn note_side_transport_compact(
946        &mut self,
947        raw_bytes: usize,
948        wire_bytes: usize,
949        compact_overhead_bytes: usize,
950        used_timestamp_delta: bool,
951        omitted_timestamp: bool,
952    ) {
953        self.side_transport_compact_frames = self.side_transport_compact_frames.saturating_add(1);
954        if used_timestamp_delta {
955            self.side_transport_compact_delta_frames =
956                self.side_transport_compact_delta_frames.saturating_add(1);
957        }
958        if omitted_timestamp {
959            self.side_transport_compact_omitted_timestamp_frames = self
960                .side_transport_compact_omitted_timestamp_frames
961                .saturating_add(1);
962        }
963        self.note_side_transport_bytes(raw_bytes, wire_bytes);
964        self.side_transport_min_compact_overhead_bytes = Some(
965            self.side_transport_min_compact_overhead_bytes
966                .map_or(compact_overhead_bytes, |v| v.min(compact_overhead_bytes)),
967        );
968        self.side_transport_max_compact_overhead_bytes = Some(
969            self.side_transport_max_compact_overhead_bytes
970                .map_or(compact_overhead_bytes, |v| v.max(compact_overhead_bytes)),
971        );
972    }
973
974    fn note_side_transport_chunks(&mut self, chunks: usize) {
975        self.side_transport_chunk_frames = self
976            .side_transport_chunk_frames
977            .saturating_add(chunks as u64);
978    }
979
980    fn note_side_transport_bytes(&mut self, raw_bytes: usize, wire_bytes: usize) {
981        self.side_transport_raw_bytes = self
982            .side_transport_raw_bytes
983            .saturating_add(raw_bytes as u64);
984        self.side_transport_wire_bytes = self
985            .side_transport_wire_bytes
986            .saturating_add(wire_bytes as u64);
987        if raw_bytes > wire_bytes {
988            self.side_transport_bytes_saved = self
989                .side_transport_bytes_saved
990                .saturating_add((raw_bytes - wire_bytes) as u64);
991        }
992    }
993
994    fn note_side_transport_compact_target_miss(&mut self) {
995        self.side_transport_compact_target_misses =
996            self.side_transport_compact_target_misses.saturating_add(1);
997    }
998
999    fn note_side_transport_template_eviction(&mut self) {
1000        self.side_transport_template_evictions =
1001            self.side_transport_template_evictions.saturating_add(1);
1002    }
1003}
1004
1005#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1006enum RouteSelectionOrigin {
1007    Flood,
1008    Discovered,
1009}
1010
1011// -------------------- endpoint + board config --------------------
1012/// Packet Handler function type
1013type PacketHandlerFn = dyn Fn(&Packet) -> TelemetryResult<()> + Send + Sync + 'static;
1014
1015/// Packed Handler function type
1016type PackedHandlerFn = dyn Fn(&[u8]) -> TelemetryResult<()> + Send + Sync + 'static;
1017type PrioritizedPackedHandlerFn = dyn Fn(&[u8], u8) -> TelemetryResult<()> + Send + Sync + 'static;
1018
1019// Make handlers usable across tasks
1020/// Endpoint handler function enum.
1021/// Holds either a `Packet` handler or a packed byte-slice handler.
1022/// /// - Packet handler signature: `Fn(&Packet) -> TelemetryResult<()>`
1023/// /// - Packed handler signature: `Fn(&[u8]) -> TelemetryResult<()>`
1024#[derive(Clone)]
1025pub enum EndpointHandlerFn {
1026    Packet(Arc<PacketHandlerFn>),
1027    Packed(Arc<PackedHandlerFn>),
1028}
1029
1030/// Endpoint handler for a specific data endpoint.
1031pub struct EndpointHandler {
1032    endpoint: DataEndpoint,
1033    handler: EndpointHandlerFn,
1034}
1035
1036/// Debug implementation for EndpointHandlerFn.
1037impl Debug for EndpointHandlerFn {
1038    #[inline]
1039    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1040        match self {
1041            EndpointHandlerFn::Packet(_) => f.write_str("EndpointHandlerFn::Packet(<handler>)"),
1042            EndpointHandlerFn::Packed(_) => f.write_str("EndpointHandlerFn::Packed(<handler>)"),
1043        }
1044    }
1045}
1046
1047/// TX handler for a router side: either packed or packet-based.
1048#[derive(Clone)]
1049pub enum RouterTxHandlerFn {
1050    Packed(Arc<PackedHandlerFn>),
1051    PackedWithPriority(Arc<PrioritizedPackedHandlerFn>),
1052    Packet(Arc<PacketHandlerFn>),
1053}
1054
1055impl Debug for RouterTxHandlerFn {
1056    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1057        match self {
1058            RouterTxHandlerFn::Packed(_) => f.debug_tuple("Packed").field(&"<handler>").finish(),
1059            RouterTxHandlerFn::PackedWithPriority(_) => f
1060                .debug_tuple("PackedWithPriority")
1061                .field(&"<handler>")
1062                .finish(),
1063            RouterTxHandlerFn::Packet(_) => f.debug_tuple("Packet").field(&"<handler>").finish(),
1064        }
1065    }
1066}
1067
1068#[derive(Clone, Debug)]
1069pub struct RouterSideOptions {
1070    /// Enables the router's per-link reliable transport layer on this side.
1071    ///
1072    /// When `true` and the side uses a packed TX handler, reliable schema types gain
1073    /// sequence numbers, ACKs, packet requests, and retransmits on this specific hop.
1074    /// When `false`, the router strips the reliable framing for that side and sends only the
1075    /// application packet payload once.
1076    ///
1077    /// This setting only affects the hop between this router and the side's TX callback. It does
1078    /// not change whether a schema `DataType` is defined as reliable, and it does not disable the
1079    /// router's end-to-end reliable tracking for packets originated elsewhere in the network.
1080    pub reliable_enabled: bool,
1081    /// Marks the side as eligible for link-local-only endpoints and discovery routes.
1082    pub link_local_enabled: bool,
1083    /// Enables a side-local header-template dictionary for packed transport.
1084    ///
1085    /// The first frame for a stable header shape is sent in full. Later frames
1086    /// on the same side can replace the repeated static header bytes with a
1087    /// compact template hash plus only the fields that still vary packet to
1088    /// packet.
1089    pub header_template_enabled: bool,
1090    /// Maximum number of bytes to emit per packed TX callback.
1091    ///
1092    /// When non-zero and a wrapped packed frame would exceed this size, the
1093    /// router splits it into ordered chunks and reassembles those chunks on RX
1094    /// before normal packet processing.
1095    pub max_frame_bytes: usize,
1096    /// Target total side-transport overhead for compact follow-up frames.
1097    ///
1098    /// This is a profiling/negotiation target rather than a hard limit. The
1099    /// canonical packet is still reconstructed before normal router handling,
1100    /// so constrained links should watch runtime stats to confirm compact
1101    /// frames are meeting their IPv4/IPv6-like overhead budget.
1102    pub compact_header_target_bytes: usize,
1103    /// Maximum side-local header templates retained for TX and RX dictionaries.
1104    ///
1105    /// This keeps compact-link state bounded. When the dictionary is full, the
1106    /// oldest deterministic entry is evicted and a later packet with that
1107    /// shape will refresh the receiver with a full template frame.
1108    pub max_side_transport_templates: usize,
1109    /// Omits the timestamp field from compact follow-up frames when it is unchanged.
1110    ///
1111    /// The receiver reconstructs the canonical packet from the previous timestamp for that
1112    /// side-local template. This is only used after a full or compact frame has established
1113    /// timestamp context.
1114    pub omit_unchanged_compact_timestamps: bool,
1115    /// Optional per-data-type timestamp omission policy for compact follow-up frames.
1116    ///
1117    /// This allows a side to omit unchanged timestamps only for selected traffic while keeping
1118    /// absolute/delta timestamps for other data types on the same link.
1119    pub compact_timestamp_omission_types: CompactTimestampOmissionPolicy,
1120    /// Declared compact-link profile for stats and future negotiation.
1121    pub side_transport_profile: SideTransportProfile,
1122    /// Allows packets received from this side to enter router processing.
1123    pub ingress_enabled: bool,
1124    /// Allows the router to transmit packets toward this side.
1125    pub egress_enabled: bool,
1126}
1127
1128impl Default for RouterSideOptions {
1129    fn default() -> Self {
1130        Self {
1131            reliable_enabled: false,
1132            link_local_enabled: false,
1133            header_template_enabled: false,
1134            max_frame_bytes: 0,
1135            compact_header_target_bytes: 0,
1136            max_side_transport_templates: DEFAULT_SIDE_TRANSPORT_TEMPLATE_LIMIT,
1137            omit_unchanged_compact_timestamps: false,
1138            compact_timestamp_omission_types: CompactTimestampOmissionPolicy::none(),
1139            side_transport_profile: SideTransportProfile::Canonical,
1140            ingress_enabled: true,
1141            egress_enabled: true,
1142        }
1143    }
1144}
1145
1146impl RouterSideOptions {
1147    /// Convenience preset for compact packed-side transport.
1148    ///
1149    /// This enables header-template reuse and, when `max_frame_bytes > 0`,
1150    /// router-managed chunking/reassembly for fixed-size transports.
1151    #[inline]
1152    pub fn with_small_packet_transport(mut self, max_frame_bytes: usize) -> Self {
1153        self.header_template_enabled = true;
1154        self.max_frame_bytes = max_frame_bytes;
1155        self.compact_header_target_bytes = IPV6_LIKE_COMPACT_HEADER_TARGET_BYTES;
1156        self.side_transport_profile = SideTransportProfile::Ipv6Like;
1157        self
1158    }
1159
1160    #[inline]
1161    pub fn with_ipv4_like_compact_header_target(mut self) -> Self {
1162        self.header_template_enabled = true;
1163        self.compact_header_target_bytes = IPV4_LIKE_COMPACT_HEADER_TARGET_BYTES;
1164        self.omit_unchanged_compact_timestamps = true;
1165        self.side_transport_profile = SideTransportProfile::Ipv4Like;
1166        self
1167    }
1168
1169    #[inline]
1170    pub fn with_ipv6_like_compact_header_target(mut self) -> Self {
1171        self.header_template_enabled = true;
1172        self.compact_header_target_bytes = IPV6_LIKE_COMPACT_HEADER_TARGET_BYTES;
1173        self.side_transport_profile = SideTransportProfile::Ipv6Like;
1174        self
1175    }
1176
1177    #[inline]
1178    pub fn with_template_transport(mut self) -> Self {
1179        self.header_template_enabled = true;
1180        self.side_transport_profile = SideTransportProfile::Template;
1181        self
1182    }
1183
1184    #[inline]
1185    pub fn with_omitted_unchanged_compact_timestamps(mut self) -> Self {
1186        self.header_template_enabled = true;
1187        self.omit_unchanged_compact_timestamps = true;
1188        self
1189    }
1190
1191    #[inline]
1192    pub fn with_omitted_unchanged_compact_timestamps_for_type(mut self, ty: DataType) -> Self {
1193        self.header_template_enabled = true;
1194        self.compact_timestamp_omission_types.insert(ty);
1195        self
1196    }
1197
1198    #[inline]
1199    pub fn effective_transport_profile(&self) -> SideTransportProfile {
1200        if !self.header_template_enabled && self.max_frame_bytes == 0 {
1201            SideTransportProfile::Canonical
1202        } else if self.side_transport_profile == SideTransportProfile::Canonical {
1203            SideTransportProfile::Template
1204        } else {
1205            self.side_transport_profile
1206        }
1207    }
1208
1209    #[cfg(feature = "discovery")]
1210    #[inline]
1211    pub fn link_capabilities(&self) -> discovery::LinkCapabilities {
1212        let mut flags = discovery::LINK_CAPABILITY_END_TO_END_RELIABILITY;
1213        if self.header_template_enabled {
1214            flags |= discovery::LINK_CAPABILITY_HEADER_TEMPLATES;
1215        }
1216        if self.max_frame_bytes != 0 {
1217            flags |= discovery::LINK_CAPABILITY_CHUNKING;
1218        }
1219        if self.reliable_enabled {
1220            flags |= discovery::LINK_CAPABILITY_RELIABILITY;
1221        }
1222        if self.omit_unchanged_compact_timestamps
1223            || !self.compact_timestamp_omission_types.is_empty()
1224        {
1225            flags |= discovery::LINK_CAPABILITY_OMIT_UNCHANGED_TIMESTAMPS;
1226        }
1227        #[cfg(feature = "cryptography")]
1228        {
1229            flags |= discovery::LINK_CAPABILITY_CRYPTO;
1230        }
1231        discovery::LinkCapabilities {
1232            version: 1,
1233            flags,
1234            profile: self.effective_transport_profile().discovery_code(),
1235            max_frame_bytes: self.max_frame_bytes.min(u32::MAX as usize) as u32,
1236            compact_header_target_bytes: self.compact_header_target_bytes.min(u32::MAX as usize)
1237                as u32,
1238            max_side_transport_templates: self.max_side_transport_templates.min(u32::MAX as usize)
1239                as u32,
1240        }
1241    }
1242}
1243
1244/// One side of the router – a name + TX handler.
1245#[derive(Clone, Debug)]
1246pub struct RouterSide {
1247    pub name: Arc<str>,
1248    pub tx_handler: RouterTxHandlerFn,
1249    pub opts: RouterSideOptions,
1250}
1251
1252/// Debug implementation for EndpointHandler.
1253impl Debug for EndpointHandler {
1254    #[inline]
1255    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1256        f.debug_struct("EndpointHandler")
1257            .field("endpoint", &self.endpoint)
1258            .field("handler", &self.handler)
1259            .finish()
1260    }
1261}
1262
1263#[inline]
1264pub(crate) const fn endpoint_is_router_internal(endpoint: DataEndpoint) -> bool {
1265    #[cfg(feature = "timesync")]
1266    if matches!(endpoint, DataEndpoint::TimeSync) {
1267        return true;
1268    }
1269    #[cfg(feature = "discovery")]
1270    {
1271        discovery::is_discovery_endpoint(endpoint)
1272    }
1273    #[cfg(not(feature = "discovery"))]
1274    {
1275        let _ = endpoint;
1276        false
1277    }
1278}
1279
1280impl EndpointHandler {
1281    /// Create a new endpoint handler for `Packet` callbacks.
1282    ///
1283    /// Handler signature is `Fn(&Packet) -> TelemetryResult<()>`.
1284    #[inline]
1285    pub fn new_packet_handler<F>(endpoint: DataEndpoint, f: F) -> Self
1286    where
1287        F: Fn(&Packet) -> TelemetryResult<()> + Send + Sync + 'static,
1288    {
1289        assert!(
1290            !endpoint_is_router_internal(endpoint),
1291            "reserved internal endpoint handlers must not be user-registered"
1292        );
1293        #[cfg(feature = "std")]
1294        crate::config::ensure_endpoint_id(endpoint, false)
1295            .expect("endpoint handler endpoint registration failed");
1296        Self {
1297            endpoint,
1298            handler: EndpointHandlerFn::Packet(Arc::new(f)),
1299        }
1300    }
1301
1302    /// Create a new packet handler from a runtime endpoint definition.
1303    #[inline]
1304    #[cfg(feature = "std")]
1305    pub fn new_packet_handler_for<F>(endpoint: crate::config::OwnedEndpointDefinition, f: F) -> Self
1306    where
1307        F: Fn(&Packet) -> TelemetryResult<()> + Send + Sync + 'static,
1308    {
1309        Self::new_packet_handler(endpoint.id, f)
1310    }
1311
1312    /// Create a new packet handler from a static endpoint definition.
1313    #[cfg(not(feature = "std"))]
1314    #[inline]
1315    pub fn new_packet_handler_for<F>(endpoint: crate::config::EndpointDefinition, f: F) -> Self
1316    where
1317        F: Fn(&Packet) -> TelemetryResult<()> + Send + Sync + 'static,
1318    {
1319        Self::new_packet_handler(endpoint.id, f)
1320    }
1321
1322    /// Create a new packet handler by endpoint name.
1323    #[cfg(feature = "std")]
1324    #[inline]
1325    pub fn new_packet_handler_by_name<F>(endpoint_name: &str, f: F) -> TelemetryResult<Self>
1326    where
1327        F: Fn(&Packet) -> TelemetryResult<()> + Send + Sync + 'static,
1328    {
1329        let endpoint = crate::config::endpoint_definition_by_name(endpoint_name)
1330            .ok_or(TelemetryError::BadArg)?;
1331        Ok(Self::new_packet_handler(endpoint.id, f))
1332    }
1333
1334    /// Create a new endpoint handler for packed byte-slice callbacks.
1335    ///
1336    /// Handler signature is `Fn(&[u8]) -> TelemetryResult<()>`.
1337    #[inline]
1338    pub fn new_packed_handler<F>(endpoint: DataEndpoint, f: F) -> Self
1339    where
1340        F: Fn(&[u8]) -> TelemetryResult<()> + Send + Sync + 'static,
1341    {
1342        assert!(
1343            !endpoint_is_router_internal(endpoint),
1344            "reserved internal endpoint handlers must not be user-registered"
1345        );
1346        #[cfg(feature = "std")]
1347        crate::config::ensure_endpoint_id(endpoint, false)
1348            .expect("endpoint handler endpoint registration failed");
1349        Self {
1350            endpoint,
1351            handler: EndpointHandlerFn::Packed(Arc::new(f)),
1352        }
1353    }
1354
1355    /// Create a new packed handler from a runtime endpoint definition.
1356    #[inline]
1357    pub fn new_packed_handler_for<F>(endpoint: crate::config::EndpointDefinition, f: F) -> Self
1358    where
1359        F: Fn(&[u8]) -> TelemetryResult<()> + Send + Sync + 'static,
1360    {
1361        Self::new_packed_handler(endpoint.id, f)
1362    }
1363
1364    /// Create a new packed handler by endpoint name.
1365    #[cfg(feature = "std")]
1366    #[inline]
1367    pub fn new_packed_handler_by_name<F>(endpoint_name: &str, f: F) -> TelemetryResult<Self>
1368    where
1369        F: Fn(&[u8]) -> TelemetryResult<()> + Send + Sync + 'static,
1370    {
1371        let endpoint = crate::config::endpoint_definition_by_name(endpoint_name)
1372            .ok_or(TelemetryError::BadArg)?;
1373        Ok(Self::new_packed_handler(endpoint.id, f))
1374    }
1375
1376    /// Return the endpoint that the handler is registered for.
1377    #[inline]
1378    pub fn get_endpoint(&self) -> DataEndpoint {
1379        self.endpoint
1380    }
1381
1382    /// Return a reference to the handler function.
1383    #[inline]
1384    pub fn get_handler(&self) -> &EndpointHandlerFn {
1385        &self.handler
1386    }
1387}
1388
1389pub trait Clock {
1390    /// Return the current time in milliseconds.
1391    fn now_ms(&self) -> u64;
1392
1393    /// Return the current time in nanoseconds.
1394    ///
1395    /// The default implementation derives this from [`Clock::now_ms`].
1396    fn now_ns(&self) -> u64 {
1397        self.now_ms().saturating_mul(1_000_000)
1398    }
1399}
1400
1401impl<T: Fn() -> u64> Clock for T {
1402    #[inline]
1403    fn now_ms(&self) -> u64 {
1404        self()
1405    }
1406}
1407
1408#[cfg(feature = "std")]
1409#[derive(Debug)]
1410struct StdMonotonicClock {
1411    start: Instant,
1412}
1413
1414#[cfg(feature = "std")]
1415impl Default for StdMonotonicClock {
1416    fn default() -> Self {
1417        Self {
1418            start: Instant::now(),
1419        }
1420    }
1421}
1422
1423#[cfg(feature = "std")]
1424impl Clock for StdMonotonicClock {
1425    fn now_ms(&self) -> u64 {
1426        u64::try_from(self.start.elapsed().as_millis()).unwrap_or(u64::MAX)
1427    }
1428
1429    fn now_ns(&self) -> u64 {
1430        u64::try_from(self.start.elapsed().as_nanos()).unwrap_or(u64::MAX)
1431    }
1432}
1433
1434/// Router-level E2E cryptography behavior.
1435#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1436pub enum RouterE2eEncryptionMode {
1437    /// Do not use E2E cryptography. Data types marked `RequireOn` are rejected.
1438    Disabled,
1439    /// Use E2E cryptography only for data types that require it.
1440    RequiredOnly,
1441    /// Use E2E cryptography for required and preferred data types.
1442    Preferred,
1443    /// Require E2E cryptography for every non-control data type.
1444    ForceAll,
1445}
1446
1447pub type NodeAddress = u32;
1448pub type P2pPort = u16;
1449pub type P2pStreamId = u32;
1450
1451const P2P_STREAM_MAGIC: [u8; 4] = *b"SDSP";
1452const P2P_STREAM_VERSION: u8 = 1;
1453const P2P_STREAM_SYN: u8 = 0x01;
1454const P2P_STREAM_ACK: u8 = 0x02;
1455const P2P_STREAM_FIN: u8 = 0x04;
1456const P2P_STREAM_RST: u8 = 0x08;
1457const P2P_STREAM_DATA: u8 = 0x10;
1458
1459#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1460pub enum AddressAssignmentMode {
1461    Dynamic,
1462    Requested(NodeAddress),
1463    Static(NodeAddress),
1464}
1465
1466impl AddressAssignmentMode {
1467    #[inline]
1468    fn mode_code(self) -> u8 {
1469        match self {
1470            Self::Dynamic => 0,
1471            Self::Requested(_) => 1,
1472            Self::Static(_) => 2,
1473        }
1474    }
1475
1476    #[inline]
1477    fn requested_address(self) -> NodeAddress {
1478        match self {
1479            Self::Dynamic => 0,
1480            Self::Requested(addr) | Self::Static(addr) => addr,
1481        }
1482    }
1483}
1484
1485#[derive(Debug, Clone, PartialEq, Eq)]
1486pub enum AddressChangeReason {
1487    Configured,
1488    DynamicConflict,
1489    RequestedConflict,
1490    StaticConflict,
1491    HostnameConflict,
1492}
1493
1494#[derive(Debug, Clone, PartialEq, Eq)]
1495pub struct AddressChange {
1496    pub old_hostname: Arc<str>,
1497    pub new_hostname: Arc<str>,
1498    pub old_address: NodeAddress,
1499    pub new_address: NodeAddress,
1500    pub reason: AddressChangeReason,
1501}
1502
1503#[derive(Debug, Clone, PartialEq, Eq)]
1504pub struct AddressBookEntry {
1505    pub hostname: Arc<str>,
1506    pub address: NodeAddress,
1507    pub requested_address: NodeAddress,
1508    pub mode: AddressAssignmentMode,
1509    pub birth_ms: u64,
1510    pub owner_hash: u64,
1511    pub last_seen_ms: u64,
1512}
1513
1514pub struct P2pMessage<'a> {
1515    pub source_hostname: &'a str,
1516    pub source_address: NodeAddress,
1517    pub source_port: P2pPort,
1518    pub destination_port: P2pPort,
1519    pub payload: &'a [u8],
1520}
1521
1522#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1523pub enum P2pStreamEventKind {
1524    Accepted,
1525    Connected,
1526    Data,
1527    Closed,
1528    Reset,
1529}
1530
1531pub struct P2pStreamEvent<'a> {
1532    pub kind: P2pStreamEventKind,
1533    pub stream_id: P2pStreamId,
1534    pub peer_stream_id: P2pStreamId,
1535    pub sequence: u32,
1536    pub peer_hostname: &'a str,
1537    pub peer_address: NodeAddress,
1538    pub local_port: P2pPort,
1539    pub peer_port: P2pPort,
1540    pub payload: &'a [u8],
1541}
1542
1543struct P2pDecoded<'a> {
1544    source_hostname: &'a str,
1545    source_address: NodeAddress,
1546    source_port: P2pPort,
1547    destination_port: P2pPort,
1548    payload: &'a [u8],
1549}
1550
1551#[derive(Debug, Clone)]
1552struct P2pStreamDecoded<'a> {
1553    flags: u8,
1554    source_stream_id: P2pStreamId,
1555    destination_stream_id: P2pStreamId,
1556    sequence: u32,
1557    payload: &'a [u8],
1558}
1559
1560type AddressChangeFn = dyn Fn(AddressChange) -> TelemetryResult<()> + Send + Sync + 'static;
1561
1562#[derive(Clone)]
1563struct AddressChangeHandler {
1564    handler: Arc<AddressChangeFn>,
1565}
1566
1567impl Debug for AddressChangeHandler {
1568    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1569        f.write_str("AddressChangeHandler(<handler>)")
1570    }
1571}
1572
1573type P2pPortHandlerFn = dyn Fn(P2pMessage<'_>) -> TelemetryResult<()> + Send + Sync + 'static;
1574
1575#[derive(Clone)]
1576struct P2pPortHandler {
1577    handler: Arc<P2pPortHandlerFn>,
1578}
1579
1580impl Debug for P2pPortHandler {
1581    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1582        f.write_str("P2pPortHandler(<handler>)")
1583    }
1584}
1585
1586type P2pStreamHandlerFn = dyn Fn(P2pStreamEvent<'_>) -> TelemetryResult<()> + Send + Sync + 'static;
1587
1588#[derive(Clone)]
1589struct P2pStreamHandler {
1590    handler: Arc<P2pStreamHandlerFn>,
1591}
1592
1593impl Debug for P2pStreamHandler {
1594    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1595        f.write_str("P2pStreamHandler(<handler>)")
1596    }
1597}
1598
1599#[derive(Debug, Clone)]
1600struct P2pStreamSession {
1601    peer_hostname: Arc<str>,
1602    peer_address: NodeAddress,
1603    local_port: P2pPort,
1604    peer_port: P2pPort,
1605    peer_stream_id: P2pStreamId,
1606    next_sequence: u32,
1607    connected: bool,
1608}
1609
1610#[derive(Debug, Clone)]
1611struct PendingP2pStreamEvent {
1612    handlers: Vec<P2pStreamHandler>,
1613    kind: P2pStreamEventKind,
1614    stream_id: P2pStreamId,
1615    peer_stream_id: P2pStreamId,
1616    peer_hostname: Arc<str>,
1617    peer_address: NodeAddress,
1618    local_port: P2pPort,
1619    peer_port: P2pPort,
1620}
1621
1622#[derive(Debug, Clone)]
1623pub struct RouterConfig {
1624    /// Handlers for local endpoints.
1625    handlers: Arc<[EndpointHandler]>,
1626    /// Whether to enable reliable ordering/ACKs for reliable data types.
1627    reliable_enabled: bool,
1628    /// Optional per-router sender override.
1629    sender: Option<Arc<str>>,
1630    /// Address assignment policy used for P2P routing.
1631    address_mode: AddressAssignmentMode,
1632    /// Callbacks invoked when conflict resolution changes this router's identity.
1633    address_change_handlers: Arc<[AddressChangeHandler]>,
1634    /// End-to-end cryptography behavior for user data.
1635    e2e_encryption: RouterE2eEncryptionMode,
1636    /// Application-defined key id passed to the cryptography provider.
1637    #[cfg_attr(not(feature = "cryptography"), allow(dead_code))]
1638    e2e_key_id: u32,
1639    memory: RuntimeMemoryConfig,
1640    #[cfg(feature = "discovery")]
1641    preferred_discovery_master: Option<Arc<str>>,
1642    #[cfg(feature = "timesync")]
1643    timesync: Option<TimeSyncConfig>,
1644}
1645
1646impl RouterConfig {
1647    /// Default router E2E mode for this build.
1648    ///
1649    /// Builds with cryptography prefer encrypted payloads automatically for data types that request
1650    /// it. Minimal builds without cryptography stay disabled and reject `RequireOn` traffic.
1651    pub fn default_e2e_encryption_mode() -> RouterE2eEncryptionMode {
1652        #[cfg(feature = "cryptography")]
1653        {
1654            RouterE2eEncryptionMode::Preferred
1655        }
1656        #[cfg(not(feature = "cryptography"))]
1657        {
1658            RouterE2eEncryptionMode::Disabled
1659        }
1660    }
1661
1662    /// Create a new router configuration with the specified local endpoint handlers.
1663    pub fn new<H>(handlers: H) -> Self
1664    where
1665        H: Into<Arc<[EndpointHandler]>>,
1666    {
1667        let handlers: Arc<[EndpointHandler]> = handlers.into();
1668        assert!(
1669            handlers
1670                .iter()
1671                .all(|handler| !endpoint_is_router_internal(handler.endpoint)),
1672            "reserved internal endpoint handlers must not be user-registered"
1673        );
1674        Self {
1675            handlers,
1676            reliable_enabled: true,
1677            sender: None,
1678            address_mode: AddressAssignmentMode::Dynamic,
1679            address_change_handlers: Arc::from([]),
1680            e2e_encryption: Self::default_e2e_encryption_mode(),
1681            e2e_key_id: 0,
1682            memory: RuntimeMemoryConfig::default(),
1683            #[cfg(feature = "discovery")]
1684            preferred_discovery_master: None,
1685            #[cfg(feature = "timesync")]
1686            timesync: None,
1687        }
1688    }
1689
1690    /// Enable or disable reliable delivery for this router instance.
1691    pub fn with_reliable_enabled(mut self, enabled: bool) -> Self {
1692        self.reliable_enabled = enabled;
1693        self
1694    }
1695
1696    /// Override the sender identifier for this router instance.
1697    pub fn with_sender<S: AsRef<str>>(mut self, sender: S) -> Self {
1698        self.sender = Some(Arc::from(sender.as_ref()));
1699        self
1700    }
1701
1702    /// Alias for `with_sender`; discovery uses this name for P2P service resolution.
1703    pub fn with_hostname<S: AsRef<str>>(self, hostname: S) -> Self {
1704        self.with_sender(hostname)
1705    }
1706
1707    /// Request a dynamic address assigned by the discovered network authority.
1708    pub fn with_dynamic_address(mut self) -> Self {
1709        self.address_mode = AddressAssignmentMode::Dynamic;
1710        self
1711    }
1712
1713    /// Request a preferred address. It is kept when unique and reassigned on conflict.
1714    pub fn with_requested_address(mut self, address: NodeAddress) -> Self {
1715        self.address_mode = AddressAssignmentMode::Requested(address);
1716        self
1717    }
1718
1719    /// Require a static address. If two static nodes conflict, the oldest keeps it.
1720    pub fn with_static_address(mut self, address: NodeAddress) -> Self {
1721        self.address_mode = AddressAssignmentMode::Static(address);
1722        self
1723    }
1724
1725    /// Register a callback for local address or hostname changes after conflict resolution.
1726    pub fn on_address_change<F>(mut self, f: F) -> Self
1727    where
1728        F: Fn(AddressChange) -> TelemetryResult<()> + Send + Sync + 'static,
1729    {
1730        let mut handlers = self.address_change_handlers.to_vec();
1731        handlers.push(AddressChangeHandler {
1732            handler: Arc::new(f),
1733        });
1734        self.address_change_handlers = Arc::from(handlers);
1735        self
1736    }
1737
1738    /// Configure this router's end-to-end cryptography policy.
1739    pub fn with_e2e_encryption(mut self, mode: RouterE2eEncryptionMode) -> Self {
1740        self.e2e_encryption = mode;
1741        self
1742    }
1743
1744    /// Configure the application-defined key id used for E2E encrypted payloads.
1745    pub fn with_e2e_key_id(mut self, key_id: u32) -> Self {
1746        self.e2e_key_id = key_id;
1747        self
1748    }
1749
1750    /// Override per-router queue and dedupe memory limits.
1751    pub fn with_memory_config(mut self, memory: RuntimeMemoryConfig) -> TelemetryResult<Self> {
1752        memory.validate()?;
1753        self.memory = memory;
1754        Ok(self)
1755    }
1756
1757    /// Prefer a named router as discovery master whenever it is visible.
1758    ///
1759    /// All routers in a deployment should use the same preference. If that
1760    /// router is absent, normal topology-based election is used. Discovery
1761    /// leadership remains independent from time-sync source priority.
1762    #[cfg(feature = "discovery")]
1763    pub fn with_preferred_discovery_master<S: AsRef<str>>(mut self, hostname: S) -> Self {
1764        let hostname = hostname.as_ref();
1765        self.preferred_discovery_master = if hostname.is_empty() {
1766            None
1767        } else {
1768            Some(Arc::from(hostname))
1769        };
1770        self
1771    }
1772
1773    #[cfg(feature = "timesync")]
1774    /// Enables and configures built-in time synchronization for this router.
1775    pub fn with_timesync(mut self, cfg: TimeSyncConfig) -> Self {
1776        self.timesync = Some(cfg);
1777        self
1778    }
1779
1780    #[inline]
1781    /// Check if the specified endpoint is local to this router.
1782    fn is_local_endpoint(&self, ep: DataEndpoint) -> bool {
1783        if endpoint_is_router_internal(ep) {
1784            return false;
1785        }
1786        self.handlers.iter().any(|h| h.endpoint == ep)
1787    }
1788
1789    #[inline]
1790    fn reliable_enabled(&self) -> bool {
1791        self.reliable_enabled
1792    }
1793
1794    #[inline]
1795    fn sender(&self) -> String {
1796        self.sender
1797            .as_deref()
1798            .map(ToString::to_string)
1799            .unwrap_or_else(runtime_device_identifier)
1800    }
1801
1802    #[inline]
1803    fn address_mode(&self) -> AddressAssignmentMode {
1804        self.address_mode
1805    }
1806
1807    #[inline]
1808    fn e2e_encryption(&self) -> RouterE2eEncryptionMode {
1809        self.e2e_encryption
1810    }
1811
1812    #[cfg(feature = "cryptography")]
1813    #[inline]
1814    fn e2e_key_id(&self) -> u32 {
1815        self.e2e_key_id
1816    }
1817
1818    #[cfg(feature = "timesync")]
1819    #[inline]
1820    fn timesync_config(&self) -> Option<TimeSyncConfig> {
1821        self.timesync
1822    }
1823
1824    #[inline]
1825    fn memory_config(&self) -> RuntimeMemoryConfig {
1826        self.memory
1827    }
1828}
1829
1830impl Default for RouterConfig {
1831    fn default() -> Self {
1832        Self {
1833            handlers: Arc::from([]),
1834            reliable_enabled: true,
1835            sender: None,
1836            address_mode: AddressAssignmentMode::Dynamic,
1837            address_change_handlers: Arc::from([]),
1838            e2e_encryption: Self::default_e2e_encryption_mode(),
1839            e2e_key_id: 0,
1840            memory: RuntimeMemoryConfig::default(),
1841            #[cfg(feature = "discovery")]
1842            preferred_discovery_master: None,
1843            #[cfg(feature = "timesync")]
1844            timesync: None,
1845        }
1846    }
1847}
1848
1849// -------------------- generic little-endian serialization --------------------
1850
1851pub trait LeBytes: Copy + Sized {
1852    const WIDTH: usize;
1853    fn write_le(self, out: &mut [u8]);
1854    fn from_le_slice(bytes: &[u8]) -> Self;
1855}
1856
1857impl_letype_num!(u8, 1);
1858impl_letype_num!(u16, 2);
1859impl_letype_num!(u32, 4);
1860impl_letype_num!(u64, 8);
1861impl_letype_num!(u128, 16);
1862impl_letype_num!(i8, 1);
1863impl_letype_num!(i16, 2);
1864impl_letype_num!(i32, 4);
1865impl_letype_num!(i64, 8);
1866impl_letype_num!(i128, 16);
1867impl_letype_num!(f32, 4);
1868impl_letype_num!(f64, 8);
1869
1870/// Encode a slice of `LeBytes` into a contiguous little-endian byte array.
1871pub(crate) fn encode_slice_le<T: LeBytes>(data: &[T]) -> Arc<[u8]> {
1872    let total = data.len() * T::WIDTH;
1873    let mut buf = vec![0u8; total];
1874
1875    for (i, v) in data.iter().copied().enumerate() {
1876        let start = i * T::WIDTH;
1877        v.write_le(&mut buf[start..start + T::WIDTH]);
1878    }
1879
1880    Arc::from(buf)
1881}
1882
1883/// Build an error payload for `TelemetryError` packets, respecting the
1884/// static/dynamic sizing rules from `message_meta`.
1885fn make_error_payload(msg: &str) -> crate::SharedBytes {
1886    let meta = message_meta(DataType::TelemetryError);
1887    match meta.element {
1888        MessageElement::Static(_, _, _) => {
1889            let max = get_needed_message_size(DataType::TelemetryError);
1890            let bytes = msg.as_bytes();
1891            let n = core::cmp::min(max, bytes.len());
1892            let mut buf = vec![0u8; max];
1893            if n > 0 {
1894                buf[..n].copy_from_slice(&bytes[..n]);
1895            }
1896            crate::shared_bytes::convert(Arc::<[u8]>::from(buf))
1897        }
1898        MessageElement::Dynamic(_, _) => {
1899            crate::shared_bytes::convert(Arc::<[u8]>::from(msg.as_bytes()))
1900        }
1901    }
1902}
1903
1904/// Generic raw logger function used by Router::log and Router::log_queue.
1905/// Builds a Packet from the provided data slice and passes it to the
1906/// provided transmission function.
1907fn log_raw<T, F>(
1908    sender: &str,
1909    ty: DataType,
1910    data: &[T],
1911    timestamp: u64,
1912    mut tx_function: F,
1913) -> TelemetryResult<()>
1914where
1915    T: LeBytes,
1916    F: FnMut(Packet) -> TelemetryResult<()>,
1917{
1918    let meta = message_meta(ty);
1919    let got = data.len() * T::WIDTH;
1920
1921    match meta.element {
1922        MessageElement::Static(_, _, _) => {
1923            if got != get_needed_message_size(ty) {
1924                return Err(TelemetryError::SizeMismatch {
1925                    expected: get_needed_message_size(ty),
1926                    got,
1927                });
1928            }
1929        }
1930        MessageElement::Dynamic(_, _) => {
1931            // For dynamic numeric payloads, require total byte length to be a multiple of element width.
1932            if !got.is_multiple_of(T::WIDTH) {
1933                return Err(TelemetryError::SizeMismatch {
1934                    expected: T::WIDTH,
1935                    got,
1936                });
1937            }
1938        }
1939    }
1940
1941    let payload = encode_slice_le(data);
1942    let pkt = Packet::new(ty, meta.endpoints_ref(), sender, timestamp, payload)?;
1943    tx_function(pkt)
1944}
1945
1946// Hosted diagnostics only: no trace branches, formatting or storage on MCUs.
1947#[cfg(feature = "std")]
1948fn reliable_trace_enabled() -> bool {
1949    static ENABLED: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
1950    *ENABLED.get_or_init(|| {
1951        matches!(
1952            std::env::var("SEDSNET_TRACE_RELIABLE").as_deref(),
1953            Ok("1" | "true")
1954        )
1955    })
1956}
1957
1958/// Fallback printing for error messages when no local endpoints exist.
1959/// - With `std`: prints to stderr.
1960/// - Without `std`: forwards to `seds_error_msg` (platform-provided).
1961fn fallback_stdout(_msg: &str) {
1962    #[cfg(feature = "std")]
1963    {
1964        eprintln!("{}", _msg);
1965    }
1966    #[cfg(all(not(feature = "std"), target_os = "none"))]
1967    {
1968        let message = format!("{}\n", _msg);
1969        unsafe {
1970            seds_error_msg(message.as_ptr(), message.len());
1971        }
1972    }
1973}
1974
1975// -------------------- Router --------------------
1976
1977/// Internal mutable state of the Router, protected by `RouterMutex`.
1978/// Holds the RX/TX queues and the recent-RX de-duplication set.
1979#[derive(Debug, Clone)]
1980struct RouterInner {
1981    memory: RuntimeMemoryConfig,
1982    sides: Vec<Option<RouterSide>>,
1983    route_overrides: BTreeMap<(Option<RouterSideId>, RouterSideId), bool>,
1984    typed_route_overrides: BTreeMap<(Option<RouterSideId>, u32, RouterSideId), bool>,
1985    route_weights: BTreeMap<(Option<RouterSideId>, RouterSideId), u32>,
1986    route_priorities: BTreeMap<(Option<RouterSideId>, RouterSideId), u32>,
1987    source_route_modes: BTreeMap<Option<RouterSideId>, RouteSelectionMode>,
1988    route_selection_cursors: BTreeMap<Option<RouterSideId>, u64>,
1989    adaptive_route_stats: BTreeMap<RouterSideId, AdaptiveRouteStats>,
1990    side_runtime_stats: BTreeMap<RouterSideId, SideRuntimeStatsInner>,
1991    side_transport: BTreeMap<RouterSideId, SideTransportState>,
1992    managed_variable_types: BTreeSet<u32>,
1993    managed_variable_permissions: BTreeMap<u32, NetworkVariablePermissions>,
1994    managed_variable_latest: BTreeMap<u32, ManagedVariableCacheEntry>,
1995    network_variable_update_handlers: BTreeMap<u32, Vec<NetworkVariableUpdateHandler>>,
1996    local_address: AddressBookEntry,
1997    address_book: BTreeMap<String, AddressBookEntry>,
1998    address_by_value: BTreeMap<NodeAddress, String>,
1999    p2p_port_handlers: BTreeMap<P2pPort, Vec<P2pPortHandler>>,
2000    p2p_stream_handlers: BTreeMap<P2pPort, Vec<P2pStreamHandler>>,
2001    p2p_stream_sessions: BTreeMap<P2pStreamId, P2pStreamSession>,
2002    next_p2p_stream_id: P2pStreamId,
2003    received_queue: BoundedDeque<RouterRxItem>,
2004    transmit_queue: BoundedDeque<TxQueued>,
2005    tx_priority_burst: u8,
2006    recent_rx: BoundedDeque<u64>,
2007    reliable_tx: BTreeMap<(RouterSideId, u32), ReliableTxState>,
2008    reliable_rx: BTreeMap<(RouterSideId, u32), ReliableRxState>,
2009    reliable_return_routes: BTreeMap<u64, ReliableReturnRouteState>,
2010    reliable_return_route_order: VecDeque<u64>,
2011    end_to_end_reliable_tx: BTreeMap<u64, EndToEndReliableSent>,
2012    end_to_end_reliable_tx_order: VecDeque<u64>,
2013    total_handler_failures: u64,
2014    total_handler_retries: u64,
2015    #[cfg(feature = "discovery")]
2016    discovery_routes: BTreeMap<RouterSideId, DiscoverySideState>,
2017    #[cfg(feature = "discovery")]
2018    discovery_cadence: DiscoveryCadenceState,
2019    #[cfg(feature = "discovery")]
2020    discovery_side_throttle: BTreeMap<RouterSideId, DiscoverySideThrottleState>,
2021    #[cfg(feature = "discovery")]
2022    preferred_discovery_master: Option<Arc<str>>,
2023    #[cfg(all(feature = "discovery", feature = "timesync"))]
2024    timesync_side_throttle: BTreeMap<RouterSideId, TimeSyncSideThrottleState>,
2025}
2026
2027#[derive(Debug, Clone)]
2028struct ManagedVariableCacheEntry {
2029    packet: Packet,
2030    sender_address: u32,
2031    cached_at_ms: u64,
2032}
2033
2034type NetworkVariableUpdateFn = dyn Fn(&Packet) -> TelemetryResult<()> + Send + Sync + 'static;
2035
2036#[derive(Clone)]
2037struct NetworkVariableUpdateHandler {
2038    handler: Arc<NetworkVariableUpdateFn>,
2039}
2040
2041impl Debug for NetworkVariableUpdateHandler {
2042    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
2043        f.write_str("NetworkVariableUpdateHandler(<handler>)")
2044    }
2045}
2046
2047/// Local permission policy for a network-managed variable.
2048#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2049pub struct NetworkVariablePermissions {
2050    pub read: bool,
2051    pub write: bool,
2052}
2053
2054impl NetworkVariablePermissions {
2055    pub const NONE: Self = Self {
2056        read: false,
2057        write: false,
2058    };
2059    pub const READ_ONLY: Self = Self {
2060        read: true,
2061        write: false,
2062    };
2063    pub const WRITE_ONLY: Self = Self {
2064        read: false,
2065        write: true,
2066    };
2067    pub const READ_WRITE: Self = Self {
2068        read: true,
2069        write: true,
2070    };
2071}
2072
2073#[derive(Clone, Copy, Debug, PartialEq, Eq)]
2074enum RouterQueueKind {
2075    Received,
2076    Transmit,
2077}
2078
2079impl RouterInner {
2080    #[cfg(feature = "discovery")]
2081    fn topology_board_byte_cost(board: &TopologyBoardNode) -> usize {
2082        board
2083            .sender_id
2084            .len()
2085            .saturating_add(board.reachable_endpoints.len() * size_of::<DataEndpoint>())
2086            .saturating_add(
2087                board
2088                    .reachable_timesync_sources
2089                    .iter()
2090                    .map(|s| s.len())
2091                    .sum::<usize>(),
2092            )
2093            .saturating_add(board.connections.iter().map(|s| s.len()).sum::<usize>())
2094    }
2095
2096    #[cfg(feature = "discovery")]
2097    fn discovery_sender_byte_cost(sender: &str, state: &DiscoverySenderState) -> usize {
2098        sender
2099            .len()
2100            .saturating_add(state.reachable.len() * size_of::<DataEndpoint>())
2101            .saturating_add(state.advertised_reachable.len() * size_of::<DataEndpoint>())
2102            .saturating_add(state.reachable_network_variables.len() * size_of::<DataType>())
2103            .saturating_add(
2104                state
2105                    .reachable_timesync_sources
2106                    .iter()
2107                    .map(|s| s.len())
2108                    .sum::<usize>(),
2109            )
2110            .saturating_add(
2111                state
2112                    .advertised_reachable_timesync_sources
2113                    .iter()
2114                    .map(|s| s.len())
2115                    .sum::<usize>(),
2116            )
2117            .saturating_add(
2118                state
2119                    .topology_boards
2120                    .iter()
2121                    .map(Self::topology_board_byte_cost)
2122                    .sum::<usize>(),
2123            )
2124            .saturating_add(size_of::<DiscoverySenderState>())
2125    }
2126
2127    #[cfg(feature = "discovery")]
2128    fn discovery_route_byte_cost(side: RouterSideId, route: &DiscoverySideState) -> usize {
2129        size_of::<RouterSideId>()
2130            .saturating_add(size_of::<DiscoverySideState>())
2131            .saturating_add(route.reachable.len() * size_of::<DataEndpoint>())
2132            .saturating_add(route.reachable_network_variables.len() * size_of::<DataType>())
2133            .saturating_add(
2134                route.requested_network_variables.len()
2135                    * (size_of::<DataType>() + size_of::<u64>()),
2136            )
2137            .saturating_add(
2138                route
2139                    .reachable_timesync_sources
2140                    .iter()
2141                    .map(|s| s.len())
2142                    .sum::<usize>(),
2143            )
2144            .saturating_add(
2145                route
2146                    .announcers
2147                    .iter()
2148                    .map(|(sender, state)| Self::discovery_sender_byte_cost(sender, state))
2149                    .sum::<usize>(),
2150            )
2151            .saturating_add(side.saturating_sub(side))
2152    }
2153
2154    #[cfg(feature = "discovery")]
2155    fn discovery_bytes_used(&self) -> usize {
2156        self.discovery_routes
2157            .iter()
2158            .map(|(side, route)| Self::discovery_route_byte_cost(*side, route))
2159            .sum()
2160    }
2161
2162    #[inline]
2163    fn reliable_rx_buffered_bytes(&self) -> usize {
2164        self.reliable_rx
2165            .values()
2166            .flat_map(|state| state.buffered.values())
2167            .map(|bytes| size_of::<crate::SharedBytes>() + bytes.len())
2168            .sum()
2169    }
2170
2171    fn reliable_rx_buffer_len(&self) -> usize {
2172        self.reliable_rx
2173            .values()
2174            .map(|state| state.buffered.len())
2175            .sum()
2176    }
2177
2178    #[inline]
2179    fn shared_queue_bytes_used(&self) -> usize {
2180        self.received_queue
2181            .bytes_used()
2182            .saturating_add(self.transmit_queue.bytes_used())
2183            .saturating_add(self.recent_rx.max_bytes())
2184            .saturating_add(self.reliable_rx_buffered_bytes())
2185            .saturating_add(crate::config::schema_bytes_used())
2186            .saturating_add({
2187                #[cfg(feature = "discovery")]
2188                {
2189                    self.discovery_bytes_used()
2190                }
2191                #[cfg(not(feature = "discovery"))]
2192                {
2193                    0
2194                }
2195            })
2196    }
2197
2198    fn pop_shared_queue_item(&mut self, preferred: RouterQueueKind) -> bool {
2199        match preferred {
2200            // RX and TX queues are stored in descending priority order.  When
2201            // the shared byte budget is exhausted, evict from the tail so a
2202            // low-priority telemetry packet cannot displace discovery or a
2203            // managed-variable update at the head of the queue.
2204            RouterQueueKind::Received => self.received_queue.pop_lowest_priority().is_some(),
2205            RouterQueueKind::Transmit => self.transmit_queue.pop_lowest_priority().is_some(),
2206        }
2207    }
2208
2209    fn lower_priority_shared_queue(&self, incoming_priority: u8) -> Option<RouterQueueKind> {
2210        let received = self
2211            .received_queue
2212            .lowest_priority(|queued| queued.priority)
2213            .map(|priority| (RouterQueueKind::Received, priority));
2214        let transmit = self
2215            .transmit_queue
2216            .lowest_priority(|queued| queued.priority)
2217            .map(|priority| (RouterQueueKind::Transmit, priority));
2218        [received, transmit]
2219            .into_iter()
2220            .flatten()
2221            .filter(|(_, priority)| {
2222                *priority < incoming_priority
2223                    || (incoming_priority != 254 && *priority == incoming_priority)
2224            })
2225            .min_by_key(|(kind, priority)| {
2226                (
2227                    *priority,
2228                    if *kind == RouterQueueKind::Received {
2229                        0
2230                    } else {
2231                        1
2232                    },
2233                )
2234            })
2235            .map(|(kind, _)| kind)
2236    }
2237
2238    fn make_shared_queue_room(
2239        &mut self,
2240        incoming_cost: usize,
2241        incoming_priority: u8,
2242    ) -> TelemetryResult<()> {
2243        if incoming_cost > self.memory.max_queue_budget {
2244            return Err(TelemetryError::PacketTooLarge(
2245                "Item exceeds maximum shared queue budget",
2246            ));
2247        }
2248
2249        while self.shared_queue_bytes_used().saturating_add(incoming_cost)
2250            > self.memory.max_queue_budget
2251        {
2252            let Some(victim) = self.lower_priority_shared_queue(incoming_priority) else {
2253                return Err(TelemetryError::Io("shared priority queue saturated"));
2254            };
2255            if !self.pop_shared_queue_item(victim) {
2256                return Err(TelemetryError::PacketTooLarge(
2257                    "Item exceeds maximum shared queue budget",
2258                ));
2259            }
2260        }
2261
2262        Ok(())
2263    }
2264
2265    #[cfg(feature = "discovery")]
2266    fn schema_growth_budget(&self) -> usize {
2267        // Queued packets are reclaimable. Existing reachability and active
2268        // reliable reassembly remain protected when admitting a new schema.
2269        let protected = self
2270            .recent_rx
2271            .max_bytes()
2272            .saturating_add(self.discovery_bytes_used())
2273            .saturating_add(self.reliable_rx_buffered_bytes());
2274        self.memory.max_queue_budget.saturating_sub(protected)
2275    }
2276
2277    #[cfg(feature = "discovery")]
2278    fn commit_discovery_route(
2279        &mut self,
2280        side: RouterSideId,
2281        route: DiscoverySideState,
2282    ) -> TelemetryResult<()> {
2283        let previous = self
2284            .discovery_routes
2285            .get(&side)
2286            .map_or(0, |route| Self::discovery_route_byte_cost(side, route));
2287        let incoming = Self::discovery_route_byte_cost(side, &route);
2288        let growth = incoming.saturating_sub(previous);
2289        self.make_shared_queue_room(growth, 255)?;
2290        self.discovery_routes.insert(side, route);
2291        Ok(())
2292    }
2293
2294    #[cfg(feature = "discovery")]
2295    fn fit_discovery_budget(&mut self) {
2296        while self.shared_queue_bytes_used() > self.memory.max_queue_budget {
2297            // Discovery growth must discard queued work before reachability.
2298            // Evicting a whole side here forgets every downstream command route.
2299            if let Some(victim) = self.lower_priority_shared_queue(255)
2300                && self.pop_shared_queue_item(victim)
2301            {
2302                continue;
2303            }
2304            // Live routes are protected. Growth is admitted transactionally;
2305            // external registry/config changes cannot justify erasing routes.
2306            break;
2307        }
2308    }
2309
2310    fn push_received(&mut self, item: RouterRxItem) -> TelemetryResult<()> {
2311        self.make_shared_queue_room(item.byte_cost(), item.priority)?;
2312        self.received_queue
2313            .push_back_prioritized(item, |queued| queued.priority)
2314    }
2315
2316    fn push_transmit(&mut self, item: TxQueued) -> TelemetryResult<()> {
2317        #[cfg(feature = "discovery")]
2318        if let RouterTxItem::ToSide { dst, data, .. } = &item.item {
2319            // A new timestamp for the same catalog must not replace the
2320            // queued packet: a chunked side may already have sent its prefix.
2321            if let RouterItem::Packet(incoming) = data
2322                && self.transmit_queue.iter().any(|queued| {
2323                    matches!(&queued.item,
2324                        RouterTxItem::ToSide { dst: queued_dst, data: RouterItem::Packet(existing), .. }
2325                        if queued_dst == dst && discovery::same_schema_snapshot(existing, incoming))
2326                })
2327            {
2328                return Ok(());
2329            }
2330            let incoming = match data {
2331                RouterItem::Packet(pkt) if discovery::is_discovery_type(pkt.data_type()) => {
2332                    Some((pkt.data_type(), pkt.sender().to_owned()))
2333                }
2334                RouterItem::Packet(_) | RouterItem::Packed(_) => None,
2335            };
2336            if let Some((incoming_ty, incoming_sender)) = incoming {
2337                self.transmit_queue.retain(|queued| {
2338                    let RouterTxItem::ToSide {
2339                        dst: queued_dst,
2340                        data: queued_data,
2341                        ..
2342                    } = &queued.item
2343                    else {
2344                        return true;
2345                    };
2346                    let same_snapshot = match queued_data {
2347                        RouterItem::Packet(pkt) => {
2348                            pkt.data_type() == incoming_ty && pkt.sender() == incoming_sender
2349                        }
2350                        RouterItem::Packed(_) => false,
2351                    };
2352                    *queued_dst != *dst || !same_snapshot
2353                });
2354            }
2355        }
2356        self.make_shared_queue_room(item.byte_cost(), item.priority)?;
2357        self.transmit_queue
2358            .push_back_prioritized(item, |queued| queued.priority)
2359    }
2360
2361    fn push_recent_rx(&mut self, id: u64) -> TelemetryResult<()> {
2362        while self.recent_rx.len() >= self.memory.max_recent_rx_ids {
2363            let _ = self.recent_rx.pop_front();
2364        }
2365        self.make_shared_queue_room(0, 0)?;
2366        self.recent_rx.push_back(id)
2367    }
2368
2369    #[allow(unused_mut)]
2370    fn buffer_reliable_rx(
2371        &mut self,
2372        side: RouterSideId,
2373        ty: DataType,
2374        seq: u32,
2375        mut bytes: crate::SharedBytes,
2376    ) -> TelemetryResult<()> {
2377        let key = Router::reliable_key(side, ty);
2378        if self
2379            .reliable_rx
2380            .get(&key)
2381            .is_some_and(|state| state.buffered.contains_key(&seq))
2382        {
2383            return Ok(());
2384        }
2385        let cost = size_of::<crate::SharedBytes>() + bytes.len();
2386        self.make_shared_queue_room(cost, crate::transport_priority(ty))?;
2387        #[cfg(feature = "compact-packet-store")]
2388        bytes.park_for_queue()?;
2389        let rx_state = self
2390            .reliable_rx
2391            .entry(key)
2392            .or_insert_with(|| ReliableRxState {
2393                expected_seq: 1,
2394                buffered: BTreeMap::new(),
2395            });
2396        if rx_state.buffered.len() >= runtime_reliable_max_pending() {
2397            let _ = rx_state.buffered.pop_first();
2398        }
2399        rx_state.buffered.insert(seq, bytes);
2400        Ok(())
2401    }
2402}
2403
2404/// Non-blocking RX queue used by ISR-safe `rx_queue*` APIs.
2405///
2406/// Uses a tiny atomic try-lock so enqueue never blocks. If contended, push/pop
2407/// operations return `TelemetryError::Io("rx queue busy")`.
2408struct IsrRxQueue {
2409    busy: AtomicBool,
2410    q: UnsafeCell<BoundedDeque<RouterRxItem>>,
2411}
2412
2413unsafe impl Send for IsrRxQueue {}
2414unsafe impl Sync for IsrRxQueue {}
2415
2416struct IsrRxQueueGuard<'a> {
2417    owner: &'a IsrRxQueue,
2418}
2419
2420impl Deref for IsrRxQueueGuard<'_> {
2421    type Target = BoundedDeque<RouterRxItem>;
2422
2423    #[inline]
2424    fn deref(&self) -> &Self::Target {
2425        unsafe { &*self.owner.q.get() }
2426    }
2427}
2428
2429impl DerefMut for IsrRxQueueGuard<'_> {
2430    #[inline]
2431    fn deref_mut(&mut self) -> &mut Self::Target {
2432        unsafe { &mut *self.owner.q.get() }
2433    }
2434}
2435
2436impl Drop for IsrRxQueueGuard<'_> {
2437    #[inline]
2438    fn drop(&mut self) {
2439        self.owner.busy.store(false, Ordering::Release);
2440    }
2441}
2442
2443impl IsrRxQueue {
2444    #[inline]
2445    fn new(max_bytes: usize, starting_bytes: usize, grow_mult: f64) -> Self {
2446        Self {
2447            busy: AtomicBool::new(false),
2448            q: UnsafeCell::new(BoundedDeque::new(max_bytes, starting_bytes, grow_mult)),
2449        }
2450    }
2451
2452    #[inline]
2453    fn try_lock(&self) -> TelemetryResult<IsrRxQueueGuard<'_>> {
2454        match self
2455            .busy
2456            .compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed)
2457        {
2458            Ok(_) => Ok(IsrRxQueueGuard { owner: self }),
2459            Err(_) => Err(TelemetryError::Io("rx queue busy")),
2460        }
2461    }
2462
2463    #[allow(dead_code)]
2464    #[inline]
2465    fn push_back(&self, item: RouterRxItem) -> TelemetryResult<()> {
2466        let mut g = self.try_lock()?;
2467        g.push_back(item)
2468    }
2469
2470    #[inline]
2471    fn push_back_prioritized(&self, item: RouterRxItem) -> TelemetryResult<()> {
2472        let mut g = self.try_lock()?;
2473        g.push_back_prioritized(item, |queued| queued.priority)
2474    }
2475
2476    #[inline]
2477    fn pop_front(&self) -> TelemetryResult<Option<RouterRxItem>> {
2478        let mut g = self.try_lock()?;
2479        Ok(g.pop_front())
2480    }
2481
2482    #[inline]
2483    fn clear(&self) -> TelemetryResult<()> {
2484        let mut g = self.try_lock()?;
2485        g.clear();
2486        Ok(())
2487    }
2488
2489    #[inline]
2490    fn snapshot(&self) -> Option<(usize, usize)> {
2491        let g = self.try_lock().ok()?;
2492        Some((g.len(), g.bytes_used()))
2493    }
2494}
2495
2496/// Telemetry Router for handling incoming and outgoing telemetry packets.
2497/// Supports queuing, processing, and dispatching to local endpoint handlers.
2498/// Thread-safe via internal locking.
2499pub struct Router {
2500    sender: RouterMutex<Arc<str>>,
2501    cfg: RouterConfig,
2502    state: RouterMutex<RouterInner>,
2503    isr_rx_queue: IsrRxQueue,
2504    side_tx_gate: ReentryGate,
2505    clock: Box<dyn Clock + Send + Sync>,
2506    #[cfg(feature = "timesync")]
2507    timesync: RouterMutex<TimeSyncRuntime>,
2508}
2509
2510#[cfg(feature = "timesync")]
2511#[derive(Debug, Clone)]
2512struct PendingTimeSyncRequest {
2513    seq: u64,
2514    t1_mono_ms: u64,
2515    source: String,
2516}
2517
2518#[cfg(feature = "timesync")]
2519#[derive(Debug, Clone)]
2520struct RemoteTimeSyncSource {
2521    priority: u64,
2522    last_sample_mono_ms: u64,
2523    sample_unix_ms: u64,
2524}
2525
2526#[cfg(feature = "timesync")]
2527#[derive(Debug, Clone)]
2528struct TimeSyncRuntime {
2529    cfg: Option<TimeSyncConfig>,
2530    tracker: Option<TimeSyncTracker>,
2531    clock: NetworkClock,
2532    disciplined_clock: SlewedNetworkClock,
2533    remote_sources: BTreeMap<String, RemoteTimeSyncSource>,
2534    next_seq: u64,
2535    next_announce_mono_ms: u64,
2536    next_request_mono_ms: u64,
2537    pending_request: Option<PendingTimeSyncRequest>,
2538}
2539
2540#[cfg(feature = "timesync")]
2541impl TimeSyncRuntime {
2542    fn new(cfg: Option<TimeSyncConfig>) -> Self {
2543        Self {
2544            tracker: cfg.map(TimeSyncTracker::new),
2545            cfg,
2546            clock: NetworkClock::default(),
2547            disciplined_clock: SlewedNetworkClock::new(
2548                cfg.map(|c| c.max_slew_ppm)
2549                    .unwrap_or(TimeSyncConfig::default().max_slew_ppm),
2550            ),
2551            remote_sources: BTreeMap::new(),
2552            next_seq: 1,
2553            next_announce_mono_ms: 0,
2554            next_request_mono_ms: 0,
2555            pending_request: None,
2556        }
2557    }
2558}
2559
2560enum RemoteSidePlan {
2561    Target(Vec<RouterSideId>),
2562}
2563
2564#[cfg(feature = "discovery")]
2565#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
2566struct DiscoveryCandidateMatch {
2567    side: RouterSideId,
2568    overlap: usize,
2569}
2570
2571#[cfg(feature = "discovery")]
2572#[inline]
2573const fn discovery_is_hop_local_advertisement(ty: DataType) -> bool {
2574    matches!(
2575        ty,
2576        DataType::DiscoveryAnnounce
2577            | DataType::DiscoveryTimeSyncSources
2578            | DataType::DiscoveryTopology
2579            | DataType::DiscoveryAddress
2580            | DataType::DiscoveryLinkCapabilities
2581    )
2582}
2583
2584#[cfg(not(feature = "discovery"))]
2585const fn discovery_is_hop_local_advertisement(_ty: DataType) -> bool {
2586    false
2587}
2588
2589impl Debug for Router {
2590    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
2591        let sender = self.sender();
2592        f.debug_struct("Router")
2593            .field("sender", &sender)
2594            .field("cfg", &self.cfg)
2595            .field("state", &"<mutex>")
2596            .field("clock", &"Clock")
2597            .finish()
2598    }
2599}
2600
2601/// Check if any of the provided endpoints require remote forwarding when
2602/// discovery is unavailable and routing falls back to local-vs-remote schema.
2603#[inline]
2604fn has_nonlocal_endpoint(eps: &[DataEndpoint], cfg: &RouterConfig) -> bool {
2605    eps.iter().copied().any(|ep| !cfg.is_local_endpoint(ep))
2606}
2607
2608#[inline]
2609fn force_remote_for_type(ty: DataType) -> bool {
2610    matches!(
2611        ty,
2612        DataType::ReliableAck
2613            | DataType::ReliablePartialAck
2614            | DataType::ReliablePacketRequest
2615            | DataType::P2pMessage
2616    ) || {
2617        #[cfg(feature = "timesync")]
2618        {
2619            matches!(
2620                ty,
2621                DataType::TimeSyncAnnounce | DataType::TimeSyncRequest | DataType::TimeSyncResponse
2622            )
2623        }
2624        #[cfg(not(feature = "timesync"))]
2625        {
2626            false
2627        }
2628    }
2629}
2630
2631#[inline]
2632fn is_internal_control_type(ty: DataType) -> bool {
2633    if matches!(
2634        ty,
2635        DataType::ReliableAck
2636            | DataType::ReliablePartialAck
2637            | DataType::ReliablePacketRequest
2638            | DataType::P2pMessage
2639    ) {
2640        return true;
2641    }
2642
2643    #[cfg(feature = "timesync")]
2644    if matches!(
2645        ty,
2646        DataType::TimeSyncAnnounce | DataType::TimeSyncRequest | DataType::TimeSyncResponse
2647    ) {
2648        return true;
2649    }
2650
2651    #[cfg(feature = "discovery")]
2652    if discovery::is_discovery_type(ty) {
2653        return true;
2654    }
2655
2656    let _ = ty;
2657    false
2658}
2659
2660/// Helper function to call a handler with retries and error handling.
2661fn with_retries<F>(
2662    this: &Router,
2663    dest: DataEndpoint,
2664    data: &RouterItem,
2665    pkt_for_ctx: Option<&Packet>,
2666    env_for_ctx: Option<&wire_format::TelemetryEnvelope>,
2667    called_from_queue: bool,
2668    run: F,
2669) -> TelemetryResult<()>
2670where
2671    F: Fn() -> TelemetryResult<()>,
2672{
2673    match this.retry_with_attempts(runtime_max_handler_retries(), run) {
2674        Ok(((), attempts)) => {
2675            if attempts > 1 {
2676                let mut st = this.state.lock();
2677                st.total_handler_retries = st
2678                    .total_handler_retries
2679                    .saturating_add(attempts.saturating_sub(1) as u64);
2680            }
2681            Ok(())
2682        }
2683        Err((e, attempts)) => {
2684            {
2685                let mut st = this.state.lock();
2686                st.total_handler_failures = st.total_handler_failures.saturating_add(1);
2687                st.total_handler_retries = st.total_handler_retries.saturating_add(attempts as u64);
2688            }
2689
2690            // If handler fails, remove from dedupe so it can be retried later if resent.
2691            this.remove_pkt_id(data);
2692
2693            // Emit error packet (to local endpoints).
2694            if let Some(pkt) = pkt_for_ctx {
2695                let _ = this.handle_callback_error(pkt, Some(dest), e, called_from_queue);
2696            } else if let Some(env) = env_for_ctx {
2697                let _ = this.handle_callback_error_from_env(env, Some(dest), e, called_from_queue);
2698            }
2699
2700            Err(TelemetryError::HandlerError("local handler failed"))
2701        }
2702    }
2703}
2704/// Router implementation
2705impl Router {
2706    const END_TO_END_ACK_SENDER: &'static str = "E2EACK";
2707    const END_TO_END_ACK_PREFIX: &'static str = "E2EACK:";
2708
2709    #[inline]
2710    fn side_ref(st: &RouterInner, side: RouterSideId) -> TelemetryResult<&RouterSide> {
2711        st.sides
2712            .get(side)
2713            .and_then(|side| side.as_ref())
2714            .ok_or(TelemetryError::HandlerError("router: invalid side id"))
2715    }
2716
2717    fn note_side_tx_success(
2718        &self,
2719        side: RouterSideId,
2720        ty: DataType,
2721        bytes: usize,
2722        relayed: bool,
2723        attempts: usize,
2724    ) {
2725        let mut st = self.state.lock();
2726        #[cfg(feature = "discovery")]
2727        if ty == crate::DataType::DiscoveryAnnounce
2728            && !relayed
2729            && let Some(throttle) = st.discovery_side_throttle.get_mut(&side)
2730        {
2731            throttle.last_liveness_tx_ms = self.clock.now_ms();
2732        }
2733        let entry = st.side_runtime_stats.entry(side).or_default();
2734        entry.note_tx(ty, bytes, relayed, attempts.saturating_sub(1));
2735    }
2736
2737    fn note_side_tx_failure(&self, side: RouterSideId, ty: DataType, attempts: usize) {
2738        let mut st = self.state.lock();
2739        st.total_handler_failures = st.total_handler_failures.saturating_add(1);
2740        st.total_handler_retries = st.total_handler_retries.saturating_add(attempts as u64);
2741        let entry = st.side_runtime_stats.entry(side).or_default();
2742        entry.note_tx_failure(ty, attempts);
2743    }
2744
2745    fn note_side_rx(&self, side: RouterSideId, ty: DataType, bytes: usize, relayed: bool) {
2746        let mut st = self.state.lock();
2747        let entry = st.side_runtime_stats.entry(side).or_default();
2748        entry.note_rx(ty, bytes, relayed);
2749    }
2750
2751    fn note_side_local_delivery(&self, side: RouterSideId, ty: DataType) {
2752        let mut st = self.state.lock();
2753        let entry = st.side_runtime_stats.entry(side).or_default();
2754        entry.note_local_delivery(ty);
2755    }
2756
2757    fn note_side_local_handler_failure(&self, side: RouterSideId, ty: DataType, retries: usize) {
2758        let mut st = self.state.lock();
2759        let entry = st.side_runtime_stats.entry(side).or_default();
2760        entry.note_local_handler_failure(ty, retries);
2761    }
2762
2763    fn cache_managed_variable_packet(
2764        &self,
2765        pkt: &Packet,
2766        notify_handlers: bool,
2767    ) -> TelemetryResult<()> {
2768        let sender_address = pkt
2769            .sender()
2770            .strip_prefix("@addr:")
2771            .and_then(|value| value.parse::<u32>().ok())
2772            .unwrap_or_else(|| sender_address_u32(pkt.sender()));
2773        let handlers = {
2774            let mut st = self.state.lock();
2775            if !st
2776                .managed_variable_types
2777                .contains(&pkt.data_type().as_u32())
2778            {
2779                return Ok(());
2780            }
2781            let existing = st.managed_variable_latest.get(&pkt.data_type().as_u32());
2782            // Sender-local timestamps can order updates from one authoritative
2783            // writer even before network time is available. They cannot order
2784            // a restored local seed against a remote writer, and packet nonces
2785            // are deliberately pseudo-random deduplication keys rather than
2786            // revisions. Accept equal-timestamp updates in arrival order so
2787            // rapid toggles made during one clock tick are not discarded.
2788            if existing.is_some_and(|entry| {
2789                let current = &entry.packet;
2790                sender_address == entry.sender_address && pkt.timestamp() < current.timestamp()
2791            }) {
2792                return Ok(());
2793            }
2794            let changed = existing.is_none_or(|entry| entry.packet != *pkt);
2795            st.managed_variable_latest.insert(
2796                pkt.data_type().as_u32(),
2797                ManagedVariableCacheEntry {
2798                    packet: pkt.clone(),
2799                    sender_address,
2800                    cached_at_ms: self.clock.now_ms(),
2801                },
2802            );
2803            if notify_handlers && changed {
2804                st.network_variable_update_handlers
2805                    .get(&pkt.data_type().as_u32())
2806                    .cloned()
2807                    .unwrap_or_default()
2808            } else {
2809                Vec::new()
2810            }
2811        };
2812        for handler in handlers {
2813            (handler.handler)(pkt)?;
2814        }
2815        Ok(())
2816    }
2817
2818    fn remember_managed_variable_packet(&self, pkt: &Packet) -> TelemetryResult<()> {
2819        self.cache_managed_variable_packet(pkt, true)
2820    }
2821
2822    fn managed_variable_latest(&self, ty: DataType) -> Option<Packet> {
2823        let st = self.state.lock();
2824        st.managed_variable_latest
2825            .get(&ty.as_u32())
2826            .map(|entry| entry.packet.clone())
2827    }
2828
2829    fn managed_variable_latest_with_age(&self, ty: DataType) -> Option<(Packet, u64)> {
2830        let now_ms = self.clock.now_ms();
2831        let st = self.state.lock();
2832        st.managed_variable_latest.get(&ty.as_u32()).map(|entry| {
2833            (
2834                entry.packet.clone(),
2835                now_ms.saturating_sub(entry.cached_at_ms),
2836            )
2837        })
2838    }
2839
2840    fn is_managed_variable_type(&self, ty: DataType) -> bool {
2841        let st = self.state.lock();
2842        st.managed_variable_types.contains(&ty.as_u32())
2843    }
2844
2845    fn managed_variable_permissions_locked(
2846        st: &RouterInner,
2847        ty: DataType,
2848    ) -> NetworkVariablePermissions {
2849        st.managed_variable_permissions
2850            .get(&ty.as_u32())
2851            .copied()
2852            .unwrap_or(NetworkVariablePermissions::READ_WRITE)
2853    }
2854
2855    fn can_read_managed_variable(&self, ty: DataType) -> bool {
2856        let st = self.state.lock();
2857        Self::managed_variable_permissions_locked(&st, ty).read
2858    }
2859
2860    fn can_write_managed_variable(&self, ty: DataType) -> bool {
2861        let st = self.state.lock();
2862        Self::managed_variable_permissions_locked(&st, ty).write
2863    }
2864
2865    #[inline]
2866    fn ensure_side_ingress_enabled(&self, side: RouterSideId) -> TelemetryResult<()> {
2867        let st = self.state.lock();
2868        let side_ref = Self::side_ref(&st, side)?;
2869        if side_ref.opts.ingress_enabled {
2870            Ok(())
2871        } else {
2872            Err(TelemetryError::HandlerError(
2873                "router: ingress disabled for side id",
2874            ))
2875        }
2876    }
2877
2878    #[inline]
2879    fn default_route_enabled(&self, src: Option<RouterSideId>, dst: RouterSideId) -> bool {
2880        src != Some(dst)
2881    }
2882
2883    #[inline]
2884    fn route_allowed_locked(
2885        &self,
2886        st: &RouterInner,
2887        src: Option<RouterSideId>,
2888        ty: Option<DataType>,
2889        dst: RouterSideId,
2890    ) -> bool {
2891        let Some(dst_side) = st.sides.get(dst).and_then(|side| side.as_ref()) else {
2892            return false;
2893        };
2894        if !dst_side.opts.egress_enabled {
2895            return false;
2896        }
2897        if let Some(src_id) = src {
2898            let Some(src_side) = st.sides.get(src_id).and_then(|side| side.as_ref()) else {
2899                return false;
2900            };
2901            if !src_side.opts.ingress_enabled {
2902                return false;
2903            }
2904        }
2905        let base_allowed = st
2906            .route_overrides
2907            .get(&(src, dst))
2908            .copied()
2909            .unwrap_or_else(|| self.default_route_enabled(src, dst));
2910        if !base_allowed {
2911            return false;
2912        }
2913
2914        let Some(ty) = ty else {
2915            return true;
2916        };
2917        if st
2918            .typed_route_overrides
2919            .keys()
2920            .any(|(typed_src, typed_ty, _)| *typed_src == src && *typed_ty == ty.as_u32())
2921        {
2922            return st
2923                .typed_route_overrides
2924                .get(&(src, ty.as_u32(), dst))
2925                .copied()
2926                .unwrap_or(false);
2927        }
2928        true
2929    }
2930
2931    fn has_typed_route_overrides_locked(
2932        st: &RouterInner,
2933        src: Option<RouterSideId>,
2934        ty: DataType,
2935    ) -> bool {
2936        st.typed_route_overrides
2937            .keys()
2938            .any(|(typed_src, typed_ty, _)| *typed_src == src && *typed_ty == ty.as_u32())
2939    }
2940
2941    #[cfg(feature = "discovery")]
2942    fn endpoint_overlap_count<I>(reachable: I, eps: &[DataEndpoint]) -> usize
2943    where
2944        I: IntoIterator<Item = DataEndpoint>,
2945    {
2946        let mut overlap = 0usize;
2947        for ep in reachable {
2948            if eps.contains(&ep) {
2949                overlap = overlap.saturating_add(1);
2950            }
2951        }
2952        overlap
2953    }
2954
2955    #[inline]
2956    fn preferred_scoring_endpoints<'a>(
2957        &self,
2958        eps: &'a [DataEndpoint],
2959        prefer_nonlocal: bool,
2960    ) -> alloc::borrow::Cow<'a, [DataEndpoint]> {
2961        if !prefer_nonlocal || eps.iter().all(|&ep| !self.cfg.is_local_endpoint(ep)) {
2962            return alloc::borrow::Cow::Borrowed(eps);
2963        }
2964        let nonlocal: Vec<DataEndpoint> = eps
2965            .iter()
2966            .copied()
2967            .filter(|&ep| !self.cfg.is_local_endpoint(ep))
2968            .collect();
2969        if nonlocal.is_empty() {
2970            alloc::borrow::Cow::Borrowed(eps)
2971        } else {
2972            alloc::borrow::Cow::Owned(nonlocal)
2973        }
2974    }
2975
2976    fn pop_next_queued_control_rx_item(&self) -> TelemetryResult<Option<RouterRxItem>> {
2977        {
2978            let mut isr_rx = self.isr_rx_queue.try_lock()?;
2979            let idx = isr_rx.iter().position(Self::queued_rx_item_is_control);
2980            if let Some(idx) = idx {
2981                return Ok(isr_rx.remove_pos(idx));
2982            }
2983        }
2984
2985        let mut st = self.state.lock();
2986        let idx = st
2987            .received_queue
2988            .iter()
2989            .position(Self::queued_rx_item_is_control);
2990        if let Some(idx) = idx {
2991            return Ok(st.received_queue.remove_pos(idx));
2992        }
2993        Ok(None)
2994    }
2995
2996    fn queued_rx_item_is_control(item: &RouterRxItem) -> bool {
2997        let ty = match &item.data {
2998            RouterItem::Packet(pkt) => Some(pkt.data_type()),
2999            RouterItem::Packed(bytes) => wire_format::scheduling_type(bytes.as_ref()),
3000        };
3001        match ty {
3002            Some(
3003                DataType::ReliableAck
3004                | DataType::ReliablePartialAck
3005                | DataType::ReliablePacketRequest,
3006            ) => true,
3007            #[cfg(feature = "discovery")]
3008            Some(ty) => discovery::is_discovery_type(ty),
3009            _ => false,
3010        }
3011    }
3012
3013    fn drain_queued_control_rx_before_tx(
3014        &self,
3015        start: u64,
3016        timeout_ms: u32,
3017    ) -> TelemetryResult<bool> {
3018        let mut did_any = false;
3019        while let Some(item) = self.pop_next_queued_control_rx_item()? {
3020            self.process_rx_queue_item(item)?;
3021            did_any = true;
3022            // Receipts precede retry exhaustion; discovery precedes routing.
3023            // A control burst still shares the caller's bounded deadline.
3024            if timeout_ms != 0 && self.clock.now_ms().wrapping_sub(start) >= u64::from(timeout_ms) {
3025                break;
3026            }
3027        }
3028        Ok(did_any)
3029    }
3030
3031    fn eligible_side_ids_locked(
3032        &self,
3033        st: &RouterInner,
3034        src: Option<RouterSideId>,
3035        ty: Option<DataType>,
3036        restrict_link_local: bool,
3037    ) -> Vec<RouterSideId> {
3038        st.sides
3039            .iter()
3040            .enumerate()
3041            .filter_map(|(side_id, side)| {
3042                let side = side.as_ref()?;
3043                if restrict_link_local && !side.opts.link_local_enabled {
3044                    return None;
3045                }
3046                if self.route_allowed_locked(st, src, ty, side_id) {
3047                    Some(side_id)
3048                } else {
3049                    None
3050                }
3051            })
3052            .collect()
3053    }
3054
3055    fn apply_route_selection_locked(
3056        &self,
3057        st: &mut RouterInner,
3058        src: Option<RouterSideId>,
3059        mut sides: Vec<RouterSideId>,
3060        origin: RouteSelectionOrigin,
3061    ) -> Vec<RouterSideId> {
3062        if sides.len() <= 1 {
3063            return sides;
3064        }
3065
3066        let selection_mode = st.source_route_modes.get(&src).copied();
3067        if selection_mode.is_none() && origin == RouteSelectionOrigin::Discovered {
3068            return self.apply_adaptive_discovery_selection_locked(st, src, sides);
3069        }
3070
3071        match selection_mode.unwrap_or(RouteSelectionMode::Fanout) {
3072            RouteSelectionMode::Fanout => sides,
3073            RouteSelectionMode::Weighted => {
3074                crate::small_sort::sort(&mut sides);
3075                let total_weight = sides.iter().fold(0_u64, |acc, side| {
3076                    acc + u64::from(st.route_weights.get(&(src, *side)).copied().unwrap_or(1))
3077                });
3078                if total_weight == 0 {
3079                    return Vec::new();
3080                }
3081                let cursor = st.route_selection_cursors.entry(src).or_insert(0);
3082                let pick = *cursor % total_weight;
3083                *cursor = cursor.wrapping_add(1);
3084                let mut remaining = pick;
3085                for side in sides {
3086                    let weight =
3087                        u64::from(st.route_weights.get(&(src, side)).copied().unwrap_or(1));
3088                    if remaining < weight {
3089                        return vec![side];
3090                    }
3091                    remaining -= weight;
3092                }
3093                Vec::new()
3094            }
3095            RouteSelectionMode::Failover => {
3096                sides.sort_by_key(|side| {
3097                    (
3098                        st.route_priorities.get(&(src, *side)).copied().unwrap_or(0),
3099                        *side,
3100                    )
3101                });
3102                sides.truncate(1);
3103                sides
3104            }
3105        }
3106    }
3107
3108    fn apply_adaptive_discovery_selection_locked(
3109        &self,
3110        st: &mut RouterInner,
3111        src: Option<RouterSideId>,
3112        mut sides: Vec<RouterSideId>,
3113    ) -> Vec<RouterSideId> {
3114        crate::small_sort::sort(&mut sides);
3115        let mut unmeasured: Vec<_> = sides
3116            .iter()
3117            .copied()
3118            .filter(|side| !st.adaptive_route_stats.contains_key(side))
3119            .collect();
3120        if !unmeasured.is_empty() {
3121            let cursor = st.route_selection_cursors.entry(src).or_insert(0);
3122            let pick = (*cursor as usize) % unmeasured.len();
3123            *cursor = cursor.wrapping_add(1);
3124            return vec![unmeasured.swap_remove(pick)];
3125        }
3126
3127        let now_ms = self.clock.now_ms();
3128        let total_weight = sides.iter().fold(0_u64, |acc, side| {
3129            acc + st
3130                .adaptive_route_stats
3131                .get(side)
3132                .map(|stats| stats.weight(now_ms))
3133                .unwrap_or(1)
3134        });
3135        if total_weight == 0 {
3136            sides.truncate(1);
3137            return sides;
3138        }
3139
3140        let cursor = st.route_selection_cursors.entry(src).or_insert(0);
3141        let pick = *cursor % total_weight;
3142        *cursor = cursor.wrapping_add(1);
3143        let mut remaining = pick;
3144        for side in sides {
3145            let weight = st
3146                .adaptive_route_stats
3147                .get(&side)
3148                .map(|stats| stats.weight(now_ms))
3149                .unwrap_or(1);
3150            if remaining < weight {
3151                return vec![side];
3152            }
3153            remaining -= weight;
3154        }
3155        Vec::new()
3156    }
3157
3158    fn record_side_tx_sample(
3159        &self,
3160        side: RouterSideId,
3161        bytes: usize,
3162        started_ms: u64,
3163        ended_ms: u64,
3164    ) {
3165        let sample_ms = ended_ms.saturating_sub(started_ms).max(1);
3166        let sample_bps = ((bytes as u128).saturating_mul(1000) / u128::from(sample_ms))
3167            .min(u128::from(u64::MAX)) as u64;
3168        let mut st = self.state.lock();
3169        st.adaptive_route_stats
3170            .entry(side)
3171            .or_default()
3172            .observe(bytes, sample_bps, ended_ms);
3173    }
3174
3175    /// Seed adaptive route selection with a transport-measured link probe.
3176    ///
3177    /// Call this after a side-specific bring-up probe, or whenever the transport already knows the
3178    /// duration for a frame. The router does not emit synthetic probe frames by itself.
3179    pub fn note_side_link_probe_sample(
3180        &self,
3181        side: RouterSideId,
3182        bytes: usize,
3183        duration_ms: u64,
3184    ) -> TelemetryResult<()> {
3185        {
3186            let st = self.state.lock();
3187            let _ = Self::side_ref(&st, side).map_err(|_| TelemetryError::BadArg)?;
3188        }
3189        let ended_ms = self.clock.now_ms();
3190        self.record_side_tx_sample(side, bytes, ended_ms.saturating_sub(duration_ms), ended_ms);
3191        Ok(())
3192    }
3193
3194    fn router_item_wire_len(data: &RouterItem) -> TelemetryResult<usize> {
3195        match data {
3196            RouterItem::Packet(pkt) => Ok(wire_format::packet_wire_size(pkt)),
3197            RouterItem::Packed(bytes) => Ok(bytes.len()),
3198        }
3199    }
3200
3201    /// Extract the logical packet ID targeted by an end-to-end reliable ACK item.
3202    ///
3203    /// Router TX and replay queues can carry either decoded `Packet` values or
3204    /// packed frames. This helper normalizes both forms so ACK-routing code
3205    /// can reason about them uniformly.
3206    ///
3207    /// Only router-generated end-to-end `ReliableAck` packets qualify here.
3208    /// Ordinary application traffic and hop-level reliable control frames return
3209    /// `Ok(None)`.
3210    #[inline]
3211    fn reliable_control_target_packet_id(data: &RouterItem) -> TelemetryResult<Option<u64>> {
3212        match data {
3213            RouterItem::Packet(pkt) => {
3214                if !Self::is_end_to_end_ack_packet(pkt) {
3215                    return Ok(None);
3216                }
3217                Self::decode_end_to_end_reliable_ack(pkt.payload()).map(Some)
3218            }
3219            RouterItem::Packed(bytes) => {
3220                if wire_format::peek_routing_frame_info(bytes.as_ref())
3221                    .ok()
3222                    .is_some_and(|frame| {
3223                        frame.envelope.ty != DataType::ReliableAck || frame.ack_only()
3224                    })
3225                {
3226                    return Ok(None);
3227                }
3228                let pkt = wire_format::unpack_packet(bytes.as_ref())?;
3229                if !Self::is_end_to_end_ack_packet(&pkt) {
3230                    return Ok(None);
3231                }
3232                Self::decode_end_to_end_reliable_ack(pkt.payload()).map(Some)
3233            }
3234        }
3235    }
3236
3237    fn decode_end_to_end_reliable_ack(payload: &[u8]) -> TelemetryResult<u64> {
3238        if payload.len() != 8 {
3239            return Err(TelemetryError::Unpack("bad reliable e2e ack payload"));
3240        }
3241        Ok(u64::from_le_bytes(payload[0..8].try_into().unwrap()))
3242    }
3243
3244    #[inline]
3245    fn sender_hash(sender: &str) -> u64 {
3246        if let Some(address) = sender
3247            .strip_prefix("@addr:")
3248            .and_then(|v| v.parse::<u32>().ok())
3249        {
3250            return u64::from(address);
3251        }
3252        hash_bytes_u64(0x517C_C1B7_2722_0A95, sender.as_bytes())
3253    }
3254
3255    #[inline]
3256    fn fallback_address_for_hostname(hostname: &str) -> NodeAddress {
3257        let hash = Self::sender_hash(hostname);
3258        let mut address = (hash as u32) ^ ((hash >> 32) as u32);
3259        if address == 0 {
3260            address = 1;
3261        }
3262        address
3263    }
3264
3265    /// Resolve the compact wire header's numeric source back to the stable
3266    /// hostname learned by address discovery. The DHCP-style assigned address
3267    /// and the wire address intentionally occupy separate namespaces, so both
3268    /// forms must be considered here.
3269    fn canonical_sender_locked(st: &RouterInner, sender: &str) -> String {
3270        let Some(address) = sender
3271            .strip_prefix("@addr:")
3272            .and_then(|value| value.parse::<u32>().ok())
3273        else {
3274            return sender.to_string();
3275        };
3276        Self::hostname_for_address_locked(st, address).unwrap_or_else(|| sender.to_string())
3277    }
3278
3279    /// Resolve both assigned node addresses and stable compact wire hashes.
3280    ///
3281    /// Remote boards behind a router appear in incremental topology summaries,
3282    /// but do not send their link-local address advertisement across that
3283    /// router. Include those retained topology names so application packets
3284    /// can still be attributed at the far edge of a multi-hop network.
3285    fn hostname_for_address_locked(st: &RouterInner, address: NodeAddress) -> Option<String> {
3286        if let Some(hostname) = st.address_by_value.get(&address) {
3287            return Some(hostname.clone());
3288        }
3289
3290        let mut found: Option<String> = None;
3291        let mut collision = false;
3292        let mut consider = |candidate: &str| {
3293            if sender_address_u32(candidate) != address {
3294                return;
3295            }
3296            match found.as_deref() {
3297                None => found = Some(candidate.to_string()),
3298                Some(existing) if existing == candidate => {}
3299                // Do not guess if two discovered names have the same compact
3300                // 32-bit wire address. Address discovery must resolve that
3301                // conflict before callers can safely attribute the packet.
3302                Some(_) => collision = true,
3303            }
3304        };
3305
3306        for entry in st.address_book.values() {
3307            consider(entry.hostname.as_ref());
3308        }
3309        #[cfg(feature = "discovery")]
3310        for route in st.discovery_routes.values() {
3311            for (announcer, state) in &route.announcers {
3312                consider(announcer);
3313                for board in &state.topology_boards {
3314                    consider(&board.sender_id);
3315                    // A constrained bridge can retain a remote only as a
3316                    // named connection on its own topology node.  That name
3317                    // is still authoritative discovery identity and is
3318                    // sufficient to resolve the deterministic compact sender
3319                    // address used by application and ACK packets.
3320                    for peer in &board.connections {
3321                        consider(peer);
3322                    }
3323                }
3324            }
3325        }
3326        if collision { None } else { found }
3327    }
3328
3329    fn address_mode_from_code(mode: u8, requested: NodeAddress) -> AddressAssignmentMode {
3330        match mode {
3331            2 => AddressAssignmentMode::Static(if requested == 0 { 1 } else { requested }),
3332            1 => AddressAssignmentMode::Requested(if requested == 0 { 1 } else { requested }),
3333            _ => AddressAssignmentMode::Dynamic,
3334        }
3335    }
3336
3337    fn address_winner_pref(entry: &AddressBookEntry) -> (u8, core::cmp::Reverse<u64>, u64) {
3338        let rank = match entry.mode {
3339            AddressAssignmentMode::Static(_) => 2,
3340            AddressAssignmentMode::Requested(_) => 1,
3341            AddressAssignmentMode::Dynamic => 0,
3342        };
3343        (
3344            rank,
3345            core::cmp::Reverse(entry.birth_ms),
3346            u64::MAX - entry.owner_hash,
3347        )
3348    }
3349
3350    fn identity_winner_pref(entry: &AddressBookEntry) -> (core::cmp::Reverse<u64>, u64) {
3351        (
3352            core::cmp::Reverse(entry.birth_ms),
3353            u64::MAX - entry.owner_hash,
3354        )
3355    }
3356
3357    fn allocate_free_address_locked(st: &RouterInner, seed: NodeAddress) -> NodeAddress {
3358        let mut candidate = if seed == 0 { 1 } else { seed };
3359        for _ in 0..u32::MAX {
3360            if candidate != 0 && !st.address_by_value.contains_key(&candidate) {
3361                return candidate;
3362            }
3363            candidate = candidate.wrapping_add(1);
3364            if candidate == 0 {
3365                candidate = 1;
3366            }
3367        }
3368        1
3369    }
3370
3371    fn unique_hostname_locked(st: &RouterInner, base: &str, owner_hash: u64) -> String {
3372        if !st.address_book.contains_key(base) {
3373            return base.to_string();
3374        }
3375        let stem = if base.is_empty() { "node" } else { base };
3376        let mut candidate = format!("{stem}-{owner_hash:08x}");
3377        let mut suffix = 1u32;
3378        while st.address_book.contains_key(&candidate) {
3379            candidate = format!("{stem}-{owner_hash:08x}-{suffix}");
3380            suffix = suffix.saturating_add(1);
3381        }
3382        candidate
3383    }
3384
3385    fn update_local_identity_locked(
3386        &self,
3387        st: &mut RouterInner,
3388        mut local: AddressBookEntry,
3389        reason: AddressChangeReason,
3390    ) -> AddressChange {
3391        let old = st.local_address.clone();
3392        st.address_book.remove(old.hostname.as_ref());
3393        st.address_by_value.remove(&old.address);
3394        local.last_seen_ms = self.clock.now_ms();
3395        st.address_by_value
3396            .insert(local.address, local.hostname.to_string());
3397        st.address_book
3398            .insert(local.hostname.to_string(), local.clone());
3399        st.local_address = local.clone();
3400        *self.sender.lock() = local.hostname.clone();
3401        #[cfg(feature = "discovery")]
3402        Self::note_discovery_topology_change_locked(st, self.clock.now_ms());
3403        AddressChange {
3404            old_hostname: old.hostname,
3405            new_hostname: local.hostname,
3406            old_address: old.address,
3407            new_address: local.address,
3408            reason,
3409        }
3410    }
3411
3412    fn notify_address_change(&self, change: AddressChange) -> TelemetryResult<()> {
3413        for handler in self.cfg.address_change_handlers.iter() {
3414            (handler.handler)(change.clone())?;
3415        }
3416        Ok(())
3417    }
3418
3419    #[cfg(feature = "discovery")]
3420    fn local_address_advertisement(
3421        &self,
3422        reachable_endpoints: Vec<DataEndpoint>,
3423        reachable_network_variables: Vec<DataType>,
3424        reachable_timesync_sources: Vec<String>,
3425        link_capabilities: discovery::LinkCapabilities,
3426        state: u8,
3427    ) -> discovery::AddressAdvertisement {
3428        let st = self.state.lock();
3429        discovery::AddressAdvertisement {
3430            hostname: st.local_address.hostname.to_string(),
3431            address: st.local_address.address,
3432            requested_address: st.local_address.requested_address,
3433            mode: st.local_address.mode.mode_code(),
3434            state,
3435            birth_ms: st.local_address.birth_ms,
3436            owner_hash: st.local_address.owner_hash,
3437            reachable_endpoints,
3438            reachable_network_variables,
3439            reachable_timesync_sources,
3440            link_capabilities,
3441        }
3442    }
3443
3444    #[cfg(feature = "discovery")]
3445    fn ingest_address_advertisement(
3446        &self,
3447        ad: discovery::AddressAdvertisement,
3448    ) -> TelemetryResult<bool> {
3449        let now_ms = self.clock.now_ms();
3450        let mut remote = AddressBookEntry {
3451            hostname: Arc::from(ad.hostname.as_str()),
3452            address: ad.address,
3453            requested_address: ad.requested_address,
3454            mode: Self::address_mode_from_code(ad.mode, ad.requested_address),
3455            birth_ms: ad.birth_ms,
3456            owner_hash: ad.owner_hash,
3457            last_seen_ms: now_ms,
3458        };
3459        let mut change = None;
3460        let mut changed = false;
3461        {
3462            let mut st = self.state.lock();
3463            let local = st.local_address.clone();
3464            let hostname_conflict =
3465                remote.hostname == local.hostname && remote.owner_hash != local.owner_hash;
3466            let address_conflict =
3467                remote.address == local.address && remote.owner_hash != local.owner_hash;
3468
3469            if hostname_conflict
3470                && Self::identity_winner_pref(&remote) > Self::identity_winner_pref(&local)
3471            {
3472                let mut next = local.clone();
3473                next.hostname = Arc::from(Self::unique_hostname_locked(
3474                    &st,
3475                    local.hostname.as_ref(),
3476                    local.owner_hash,
3477                ));
3478                change = Some(self.update_local_identity_locked(
3479                    &mut st,
3480                    next,
3481                    AddressChangeReason::HostnameConflict,
3482                ));
3483                changed = true;
3484            } else if address_conflict
3485                && Self::address_winner_pref(&remote) > Self::address_winner_pref(&local)
3486            {
3487                let mut next = local.clone();
3488                let seed = next
3489                    .requested_address
3490                    .max(Self::fallback_address_for_hostname(next.hostname.as_ref()));
3491                next.address = Self::allocate_free_address_locked(&st, seed);
3492                let reason = match next.mode {
3493                    AddressAssignmentMode::Static(_) => AddressChangeReason::StaticConflict,
3494                    AddressAssignmentMode::Requested(_) => AddressChangeReason::RequestedConflict,
3495                    AddressAssignmentMode::Dynamic => AddressChangeReason::DynamicConflict,
3496                };
3497                change = Some(self.update_local_identity_locked(&mut st, next, reason));
3498                changed = true;
3499            } else if hostname_conflict {
3500                remote.hostname = Arc::from(Self::unique_hostname_locked(
3501                    &st,
3502                    remote.hostname.as_ref(),
3503                    remote.owner_hash,
3504                ));
3505                changed = true;
3506            } else if address_conflict {
3507                let seed = remote
3508                    .requested_address
3509                    .max(Self::fallback_address_for_hostname(
3510                        remote.hostname.as_ref(),
3511                    ));
3512                remote.address = Self::allocate_free_address_locked(&st, seed);
3513                changed = true;
3514            }
3515
3516            st.address_book.remove(remote.hostname.as_ref());
3517            st.address_by_value.remove(&remote.address);
3518            st.address_by_value
3519                .insert(remote.address, remote.hostname.to_string());
3520            st.address_book
3521                .insert(remote.hostname.to_string(), remote.clone());
3522        }
3523        if let Some(change) = change {
3524            self.notify_address_change(change)?;
3525        }
3526        Ok(changed)
3527    }
3528
3529    #[inline]
3530    fn is_end_to_end_ack_sender(sender: &str) -> bool {
3531        sender == Self::END_TO_END_ACK_SENDER || sender.starts_with(Self::END_TO_END_ACK_PREFIX)
3532    }
3533
3534    fn decode_end_to_end_ack_sender_hash(sender: &str) -> Option<u64> {
3535        if let Some(ack_sender) = sender.strip_prefix(Self::END_TO_END_ACK_PREFIX)
3536            && !ack_sender.is_empty()
3537        {
3538            return Some(Self::sender_hash(ack_sender));
3539        }
3540        None
3541    }
3542
3543    /// Return the stable destination key carried by an end-to-end ACK.
3544    ///
3545    /// Compact embedded headers encode senders as numeric addresses, so the legacy
3546    /// `E2EACK:<hostname>` marker cannot survive a firmware hop. New ACKs carry the acknowledging
3547    /// owner's hash in their frozen target contract; marker decoding remains for older peers.
3548    fn end_to_end_ack_sender_hash(pkt: &Packet) -> Option<u64> {
3549        if pkt.data_type() != DataType::ReliableAck {
3550            return None;
3551        }
3552        pkt.wire_target_senders()
3553            .first()
3554            .copied()
3555            .or_else(|| Self::decode_end_to_end_ack_sender_hash(pkt.sender()))
3556    }
3557
3558    fn is_end_to_end_ack_packet(pkt: &Packet) -> bool {
3559        Self::end_to_end_ack_sender_hash(pkt).is_some()
3560            && Self::decode_end_to_end_reliable_ack(pkt.payload()).is_ok()
3561    }
3562
3563    fn encode_end_to_end_ack_sender(&self) -> String {
3564        let sender = self.sender_arc();
3565        format!("{}{}", Self::END_TO_END_ACK_PREFIX, sender)
3566    }
3567
3568    #[cfg(feature = "discovery")]
3569    fn is_end_to_end_destination_sender(sender: &str) -> bool {
3570        sender != "RELAY" && !Self::is_end_to_end_ack_sender(sender)
3571    }
3572
3573    fn encode_end_to_end_reliable_ack(packet_id: u64) -> crate::SharedBytes {
3574        let mut payload = Vec::with_capacity(8);
3575        payload.extend_from_slice(&packet_id.to_le_bytes());
3576        crate::shared_bytes::convert(Arc::<[u8]>::from(payload))
3577    }
3578
3579    fn encode_p2p_payload(
3580        src_hostname: &str,
3581        src_address: NodeAddress,
3582        src_port: P2pPort,
3583        dst_port: P2pPort,
3584        payload: &[u8],
3585    ) -> TelemetryResult<crate::SharedBytes> {
3586        let host_len = u16::try_from(src_hostname.len())
3587            .map_err(|_| TelemetryError::Pack("p2p hostname too long"))?;
3588        let payload_len = u32::try_from(payload.len())
3589            .map_err(|_| TelemetryError::Pack("p2p payload too long"))?;
3590        let mut out = Vec::with_capacity(
3591            15usize
3592                .saturating_add(src_hostname.len())
3593                .saturating_add(payload.len()),
3594        );
3595        out.push(1);
3596        out.extend_from_slice(&dst_port.to_le_bytes());
3597        out.extend_from_slice(&src_port.to_le_bytes());
3598        out.extend_from_slice(&src_address.to_le_bytes());
3599        out.extend_from_slice(&host_len.to_le_bytes());
3600        out.extend_from_slice(&payload_len.to_le_bytes());
3601        out.extend_from_slice(src_hostname.as_bytes());
3602        out.extend_from_slice(payload);
3603        Ok(out.into())
3604    }
3605
3606    fn decode_p2p_payload(payload: &[u8]) -> TelemetryResult<P2pDecoded<'_>> {
3607        if payload.len() < 15 {
3608            return Err(TelemetryError::Unpack("p2p frame short"));
3609        }
3610        if payload[0] != 1 {
3611            return Err(TelemetryError::Unpack("p2p frame version"));
3612        }
3613        let destination_port =
3614            u16::from_le_bytes(payload[1..3].try_into().expect("2-byte dst port"));
3615        let source_port = u16::from_le_bytes(payload[3..5].try_into().expect("2-byte src port"));
3616        let source_address = u32::from_le_bytes(payload[5..9].try_into().expect("4-byte address"));
3617        let host_len =
3618            u16::from_le_bytes(payload[9..11].try_into().expect("2-byte host len")) as usize;
3619        let body_len =
3620            u32::from_le_bytes(payload[11..15].try_into().expect("4-byte body len")) as usize;
3621        let host_start = 15usize;
3622        let host_end = host_start.saturating_add(host_len);
3623        let body_end = host_end.saturating_add(body_len);
3624        if host_end > payload.len() || body_end != payload.len() {
3625            return Err(TelemetryError::Unpack("p2p frame length"));
3626        }
3627        let source_hostname = core::str::from_utf8(&payload[host_start..host_end])
3628            .map_err(|_| TelemetryError::Unpack("p2p hostname utf8"))?;
3629        Ok(P2pDecoded {
3630            source_hostname,
3631            source_address,
3632            source_port,
3633            destination_port,
3634            payload: &payload[host_end..body_end],
3635        })
3636    }
3637
3638    fn encode_p2p_stream_payload(
3639        flags: u8,
3640        source_stream_id: P2pStreamId,
3641        destination_stream_id: P2pStreamId,
3642        sequence: u32,
3643        payload: &[u8],
3644    ) -> TelemetryResult<crate::SharedBytes> {
3645        let payload_len = u32::try_from(payload.len())
3646            .map_err(|_| TelemetryError::Pack("p2p stream payload too long"))?;
3647        let mut out = Vec::with_capacity(22usize.saturating_add(payload.len()));
3648        out.extend_from_slice(&P2P_STREAM_MAGIC);
3649        out.push(P2P_STREAM_VERSION);
3650        out.push(flags);
3651        out.extend_from_slice(&source_stream_id.to_le_bytes());
3652        out.extend_from_slice(&destination_stream_id.to_le_bytes());
3653        out.extend_from_slice(&sequence.to_le_bytes());
3654        out.extend_from_slice(&payload_len.to_le_bytes());
3655        out.extend_from_slice(payload);
3656        Ok(out.into())
3657    }
3658
3659    fn decode_p2p_stream_payload(payload: &[u8]) -> TelemetryResult<Option<P2pStreamDecoded<'_>>> {
3660        if !payload.starts_with(&P2P_STREAM_MAGIC) {
3661            return Ok(None);
3662        }
3663        if payload.len() < 22 {
3664            return Err(TelemetryError::Unpack("p2p stream frame short"));
3665        }
3666        if payload[4] != P2P_STREAM_VERSION {
3667            return Err(TelemetryError::Unpack("p2p stream frame version"));
3668        }
3669        let flags = payload[5];
3670        let source_stream_id =
3671            u32::from_le_bytes(payload[6..10].try_into().expect("stream source id"));
3672        let destination_stream_id =
3673            u32::from_le_bytes(payload[10..14].try_into().expect("stream destination id"));
3674        let sequence = u32::from_le_bytes(payload[14..18].try_into().expect("stream sequence"));
3675        let body_len =
3676            u32::from_le_bytes(payload[18..22].try_into().expect("stream body len")) as usize;
3677        let body_end = 22usize.saturating_add(body_len);
3678        if body_end != payload.len() {
3679            return Err(TelemetryError::Unpack("p2p stream frame length"));
3680        }
3681        Ok(Some(P2pStreamDecoded {
3682            flags,
3683            source_stream_id,
3684            destination_stream_id,
3685            sequence,
3686            payload: &payload[22..],
3687        }))
3688    }
3689
3690    fn allocate_p2p_stream_id_locked(st: &mut RouterInner) -> P2pStreamId {
3691        for _ in 0..u32::MAX {
3692            let id = st.next_p2p_stream_id.max(1);
3693            st.next_p2p_stream_id = st.next_p2p_stream_id.wrapping_add(1).max(1);
3694            if !st.p2p_stream_sessions.contains_key(&id) {
3695                return id;
3696            }
3697        }
3698        0
3699    }
3700
3701    fn dispatch_p2p_packet(&self, pkt: &Packet) -> TelemetryResult<()> {
3702        if pkt.data_type() != DataType::P2pMessage {
3703            return Ok(());
3704        }
3705        let decoded = Self::decode_p2p_payload(pkt.payload())?;
3706        if let Some(stream) = Self::decode_p2p_stream_payload(decoded.payload)? {
3707            return self.dispatch_p2p_stream_frame(&decoded, &stream);
3708        }
3709        let handlers = {
3710            let st = self.state.lock();
3711            st.p2p_port_handlers
3712                .get(&decoded.destination_port)
3713                .cloned()
3714                .unwrap_or_default()
3715        };
3716        for handler in handlers {
3717            (handler.handler)(P2pMessage {
3718                source_hostname: decoded.source_hostname,
3719                source_address: decoded.source_address,
3720                source_port: decoded.source_port,
3721                destination_port: decoded.destination_port,
3722                payload: decoded.payload,
3723            })?;
3724        }
3725        Ok(())
3726    }
3727
3728    fn dispatch_p2p_stream_frame(
3729        &self,
3730        msg: &P2pDecoded<'_>,
3731        frame: &P2pStreamDecoded<'_>,
3732    ) -> TelemetryResult<()> {
3733        let mut events: Vec<PendingP2pStreamEvent> = Vec::new();
3734        let mut reply: Option<(AddressBookEntry, P2pPort, P2pPort, crate::SharedBytes)> = None;
3735        {
3736            let mut st = self.state.lock();
3737            if frame.flags & P2P_STREAM_SYN != 0 && frame.flags & P2P_STREAM_ACK == 0 {
3738                let peer_hostname: Arc<str> = Arc::from(msg.source_hostname);
3739                let existing_id = st.p2p_stream_sessions.iter().find_map(|(id, session)| {
3740                    (session.peer_stream_id == frame.source_stream_id
3741                        && session.local_port == msg.destination_port
3742                        && session.peer_port == msg.source_port
3743                        && session.peer_address == msg.source_address
3744                        && session.peer_hostname.as_ref() == msg.source_hostname)
3745                        .then_some(*id)
3746                });
3747                let local_id = if let Some(local_id) = existing_id {
3748                    local_id
3749                } else {
3750                    let local_id = Self::allocate_p2p_stream_id_locked(&mut st);
3751                    if local_id == 0 {
3752                        return Err(TelemetryError::Io("p2p stream id exhausted"));
3753                    }
3754                    st.p2p_stream_sessions.insert(
3755                        local_id,
3756                        P2pStreamSession {
3757                            peer_hostname: peer_hostname.clone(),
3758                            peer_address: msg.source_address,
3759                            local_port: msg.destination_port,
3760                            peer_port: msg.source_port,
3761                            peer_stream_id: frame.source_stream_id,
3762                            next_sequence: 1,
3763                            connected: true,
3764                        },
3765                    );
3766                    let handlers = st
3767                        .p2p_stream_handlers
3768                        .get(&msg.destination_port)
3769                        .cloned()
3770                        .unwrap_or_default();
3771                    events.push(PendingP2pStreamEvent {
3772                        handlers,
3773                        kind: P2pStreamEventKind::Accepted,
3774                        stream_id: local_id,
3775                        peer_stream_id: frame.source_stream_id,
3776                        peer_hostname: peer_hostname.clone(),
3777                        peer_address: msg.source_address,
3778                        local_port: msg.destination_port,
3779                        peer_port: msg.source_port,
3780                    });
3781                    local_id
3782                };
3783                let dst =
3784                    st.address_book
3785                        .get(msg.source_hostname)
3786                        .cloned()
3787                        .unwrap_or(AddressBookEntry {
3788                            hostname: peer_hostname,
3789                            address: msg.source_address,
3790                            requested_address: msg.source_address,
3791                            mode: AddressAssignmentMode::Dynamic,
3792                            birth_ms: self.clock.now_ms(),
3793                            owner_hash: Self::sender_hash(msg.source_hostname),
3794                            last_seen_ms: self.clock.now_ms(),
3795                        });
3796                let payload = Self::encode_p2p_stream_payload(
3797                    P2P_STREAM_SYN | P2P_STREAM_ACK,
3798                    local_id,
3799                    frame.source_stream_id,
3800                    0,
3801                    &[],
3802                )?;
3803                reply = Some((dst, msg.source_port, msg.destination_port, payload));
3804            } else if frame.flags & P2P_STREAM_SYN != 0 && frame.flags & P2P_STREAM_ACK != 0 {
3805                if let Some(session) = st.p2p_stream_sessions.get_mut(&frame.destination_stream_id)
3806                {
3807                    session.peer_stream_id = frame.source_stream_id;
3808                    session.connected = true;
3809                    let peer_hostname = session.peer_hostname.clone();
3810                    let peer_address = session.peer_address;
3811                    let local_port = session.local_port;
3812                    let peer_port = session.peer_port;
3813                    let handlers = st
3814                        .p2p_stream_handlers
3815                        .get(&local_port)
3816                        .cloned()
3817                        .unwrap_or_default();
3818                    events.push(PendingP2pStreamEvent {
3819                        handlers,
3820                        kind: P2pStreamEventKind::Connected,
3821                        stream_id: frame.destination_stream_id,
3822                        peer_stream_id: frame.source_stream_id,
3823                        peer_hostname,
3824                        peer_address,
3825                        local_port,
3826                        peer_port,
3827                    });
3828                }
3829            } else if frame.flags & (P2P_STREAM_FIN | P2P_STREAM_RST | P2P_STREAM_DATA) != 0 {
3830                let kind = if frame.flags & P2P_STREAM_RST != 0 {
3831                    P2pStreamEventKind::Reset
3832                } else if frame.flags & P2P_STREAM_FIN != 0 {
3833                    P2pStreamEventKind::Closed
3834                } else {
3835                    P2pStreamEventKind::Data
3836                };
3837                let session_id = if frame.destination_stream_id != 0 {
3838                    Some(frame.destination_stream_id)
3839                } else {
3840                    st.p2p_stream_sessions.iter().find_map(|(id, session)| {
3841                        (session.peer_stream_id == frame.source_stream_id
3842                            && session.local_port == msg.destination_port
3843                            && session.peer_port == msg.source_port
3844                            && session.peer_address == msg.source_address
3845                            && session.peer_hostname.as_ref() == msg.source_hostname)
3846                            .then_some(*id)
3847                    })
3848                };
3849                if let Some(session_id) = session_id
3850                    && let Some(session) = st.p2p_stream_sessions.get(&session_id)
3851                {
3852                    let handlers = st
3853                        .p2p_stream_handlers
3854                        .get(&session.local_port)
3855                        .cloned()
3856                        .unwrap_or_default();
3857                    events.push(PendingP2pStreamEvent {
3858                        handlers,
3859                        kind,
3860                        stream_id: session_id,
3861                        peer_stream_id: frame.source_stream_id,
3862                        peer_hostname: session.peer_hostname.clone(),
3863                        peer_address: session.peer_address,
3864                        local_port: session.local_port,
3865                        peer_port: session.peer_port,
3866                    });
3867                    if matches!(kind, P2pStreamEventKind::Closed | P2pStreamEventKind::Reset) {
3868                        st.p2p_stream_sessions.remove(&session_id);
3869                    }
3870                }
3871            }
3872        }
3873        if let Some((dst, dst_port, src_port, payload)) = reply {
3874            self.send_p2p_to_entry(dst, dst_port, src_port, &payload)?;
3875        }
3876        for pending in events {
3877            for handler in pending.handlers {
3878                (handler.handler)(P2pStreamEvent {
3879                    kind: pending.kind,
3880                    stream_id: pending.stream_id,
3881                    peer_stream_id: pending.peer_stream_id,
3882                    sequence: frame.sequence,
3883                    peer_hostname: pending.peer_hostname.as_ref(),
3884                    peer_address: pending.peer_address,
3885                    local_port: pending.local_port,
3886                    peer_port: pending.peer_port,
3887                    payload: frame.payload,
3888                })?;
3889            }
3890        }
3891        Ok(())
3892    }
3893
3894    /// Record the side that most recently led toward `packet_id`.
3895    ///
3896    /// End-to-end reliable acknowledgements should return only toward the
3897    /// source-side that originated the data flow, not be flooded everywhere.
3898    /// This helper stores that learned return path in a bounded LRU-like cache.
3899    fn note_reliable_return_route(&self, side: RouterSideId, packet_id: u64) {
3900        let mut st = self.state.lock();
3901        // A retry can remain in RX dedupe after its smaller return-path cache
3902        // entry was evicted. Relearning that path must not restart the replay
3903        // delay on every retry, or sustained cache churn suppresses it forever.
3904        let last_replay_ms = if st.recent_rx.contains(&packet_id) {
3905            self.clock
3906                .now_ms()
3907                .saturating_sub(runtime_reliable_retransmit_ms())
3908        } else {
3909            self.clock.now_ms()
3910        };
3911        Self::remember_reliable_return_route_locked(&mut st, packet_id);
3912        st.reliable_return_routes
3913            .entry(packet_id)
3914            .and_modify(|route| route.side = side)
3915            .or_insert(ReliableReturnRouteState {
3916                side,
3917                last_replay_ms,
3918                upstream_contract: None,
3919            });
3920    }
3921
3922    /// Ensure `packet_id` is retained in the bounded reliable return-route cache.
3923    ///
3924    /// Existing entries are refreshed to the back of the order list. When the
3925    /// cache is full, the oldest learned route is evicted before inserting the
3926    /// new one.
3927    fn remember_reliable_return_route_locked(st: &mut RouterInner, packet_id: u64) {
3928        let cap = runtime_reliable_max_return_routes().max(1);
3929        st.reliable_return_route_order
3930            .retain(|id| st.reliable_return_routes.contains_key(id) && *id != packet_id);
3931        while st.reliable_return_route_order.len() >= cap {
3932            if let Some(oldest) = st.reliable_return_route_order.pop_front() {
3933                st.reliable_return_routes.remove(&oldest);
3934            } else {
3935                break;
3936            }
3937        }
3938        st.reliable_return_route_order.push_back(packet_id);
3939    }
3940
3941    fn remember_end_to_end_reliable_tx_locked(st: &mut RouterInner, packet_id: u64) {
3942        let cap = runtime_reliable_max_end_to_end_pending().max(1);
3943        st.end_to_end_reliable_tx_order
3944            .retain(|id| st.end_to_end_reliable_tx.contains_key(id) && *id != packet_id);
3945        while st.end_to_end_reliable_tx_order.len() >= cap {
3946            if let Some(oldest) = st.end_to_end_reliable_tx_order.pop_front() {
3947                st.end_to_end_reliable_tx.remove(&oldest);
3948            } else {
3949                break;
3950            }
3951        }
3952        st.end_to_end_reliable_tx_order.push_back(packet_id);
3953    }
3954
3955    #[cfg(feature = "discovery")]
3956    fn expected_end_to_end_destinations_locked(
3957        &self,
3958        st: &RouterInner,
3959        data: &RouterItem,
3960    ) -> TelemetryResult<BTreeMap<u64, RouterSideId>> {
3961        let (eps, ty) = self.item_route_info(data)?;
3962        let now_ms = self.clock.now_ms();
3963        let restrict_link_local = Self::endpoints_are_link_local_only(&eps);
3964        /* Managed-variable ownership is advertised independently from normal
3965         * endpoint handlers.  Using topology-board endpoint overlap here can
3966         * silently narrow a variable update to one segment once detailed
3967         * topology arrives (for example FLIGHT_STATE is a conventional
3968         * endpoint on the fill side but only a managed variable on avionics).
3969         * Freeze one destination contract per advertising segment instead;
3970         * its announcer is the segment router that can ACK and replicate the
3971         * value to the owners behind it. */
3972        if st.managed_variable_types.contains(&ty.as_u32()) {
3973            let mut out = BTreeMap::new();
3974            for (&side, route) in st.discovery_routes.iter() {
3975                if now_ms.saturating_sub(route.last_seen_ms) > DISCOVERY_ROUTE_TTL_MS {
3976                    continue;
3977                }
3978                let Some(side_ref) = st.sides.get(side).and_then(Option::as_ref) else {
3979                    continue;
3980                };
3981                if restrict_link_local && !side_ref.opts.link_local_enabled {
3982                    continue;
3983                }
3984                if !self.route_allowed_locked(st, None, Some(ty), side) {
3985                    continue;
3986                }
3987                for (sender_id, sender_state) in route.announcers.iter() {
3988                    if now_ms.saturating_sub(sender_state.last_seen_ms) > DISCOVERY_ROUTE_TTL_MS
3989                        || !sender_state.reachable_network_variables.contains(&ty)
3990                        || !Self::is_end_to_end_destination_sender(sender_id)
3991                    {
3992                        continue;
3993                    }
3994                    out.insert(Self::sender_hash(sender_id), side);
3995                    if out.len() >= runtime_reliable_max_end_to_end_pending().max(1) {
3996                        return Ok(out);
3997                    }
3998                }
3999            }
4000            return Ok(out);
4001        }
4002        let prefer_best_overlap =
4003            is_reliable_type(ty) && Self::reliable_control_target_packet_id(data)?.is_none();
4004        let scoring_eps = self.preferred_scoring_endpoints(&eps, prefer_best_overlap);
4005        let mut candidates: Vec<(u64, RouterSideId, usize)> = Vec::new();
4006        let mut best_overlap = 0usize;
4007        let mut out = BTreeMap::new();
4008        for (&side, route) in st.discovery_routes.iter() {
4009            if now_ms.saturating_sub(route.last_seen_ms) > DISCOVERY_ROUTE_TTL_MS {
4010                continue;
4011            }
4012            let Some(side_ref) = st.sides.get(side).and_then(Option::as_ref) else {
4013                continue;
4014            };
4015            if restrict_link_local && !side_ref.opts.link_local_enabled {
4016                continue;
4017            }
4018            if !self.route_allowed_locked(st, None, Some(ty), side) {
4019                continue;
4020            }
4021            for sender_state in route.announcers.values() {
4022                if now_ms.saturating_sub(sender_state.last_seen_ms) > DISCOVERY_ROUTE_TTL_MS {
4023                    continue;
4024                }
4025                for board in sender_state.topology_boards.iter() {
4026                    if !Self::is_end_to_end_destination_sender(&board.sender_id) {
4027                        continue;
4028                    }
4029                    let overlap = Self::endpoint_overlap_count(
4030                        board.reachable_endpoints.iter().copied(),
4031                        &scoring_eps,
4032                    );
4033                    if overlap > 0 {
4034                        if prefer_best_overlap {
4035                            best_overlap = best_overlap.max(overlap);
4036                            candidates.push((Self::sender_hash(&board.sender_id), side, overlap));
4037                        } else {
4038                            out.insert(Self::sender_hash(&board.sender_id), side);
4039                            if out.len() >= runtime_reliable_max_end_to_end_pending().max(1) {
4040                                return Ok(out);
4041                            }
4042                        }
4043                    }
4044                }
4045            }
4046        }
4047
4048        if prefer_best_overlap {
4049            for (sender_hash, side, overlap) in candidates {
4050                if overlap == best_overlap {
4051                    out.insert(sender_hash, side);
4052                    if out.len() >= runtime_reliable_max_end_to_end_pending().max(1) {
4053                        return Ok(out);
4054                    }
4055                }
4056            }
4057        }
4058        Ok(out)
4059    }
4060
4061    #[cfg(feature = "discovery")]
4062    #[allow(clippy::too_many_arguments)]
4063    fn discovered_route_candidates_locked(
4064        &self,
4065        st: &RouterInner,
4066        exclude: Option<RouterSideId>,
4067        ty: DataType,
4068        eps: &[DataEndpoint],
4069        target_senders: &[u64],
4070        prefer_nonlocal: bool,
4071        preferred_timesync_source: Option<&str>,
4072    ) -> Vec<DiscoveryCandidateMatch> {
4073        let restrict_link_local = Self::endpoints_are_link_local_only(eps);
4074        let now_ms = self.clock.now_ms();
4075        let scoring_eps = self.preferred_scoring_endpoints(eps, prefer_nonlocal);
4076        let has_variable_owner = st.discovery_routes.iter().any(|(&side, route)| {
4077            exclude != Some(side)
4078                && now_ms.saturating_sub(route.last_seen_ms) <= DISCOVERY_ROUTE_TTL_MS
4079                && route.reachable_network_variables.contains(&ty)
4080        });
4081        let mut out = Vec::new();
4082        let mut unique_endpoint_fallbacks = Vec::new();
4083
4084        for (&side, route) in st.discovery_routes.iter() {
4085            if exclude == Some(side)
4086                || now_ms.saturating_sub(route.last_seen_ms) > DISCOVERY_ROUTE_TTL_MS
4087            {
4088                continue;
4089            }
4090            if restrict_link_local
4091                && st
4092                    .sides
4093                    .get(side)
4094                    .and_then(|s| s.as_ref())
4095                    .map(|s| !s.opts.link_local_enabled)
4096                    .unwrap_or(true)
4097            {
4098                continue;
4099            }
4100            if !self.route_allowed_locked(st, exclude, Some(ty), side) {
4101                continue;
4102            }
4103            if !target_senders.is_empty()
4104                && !Self::side_matches_target_senders_locked(st, side, target_senders, now_ms)
4105            {
4106                if scoring_eps
4107                    .iter()
4108                    .copied()
4109                    .any(|ep| route.reachable.contains(&ep))
4110                {
4111                    unique_endpoint_fallbacks.push(side);
4112                }
4113                continue;
4114            }
4115            if !target_senders.is_empty() {
4116                out.push(DiscoveryCandidateMatch {
4117                    side,
4118                    overlap: usize::MAX,
4119                });
4120                continue;
4121            }
4122            if has_variable_owner {
4123                if route.reachable_network_variables.contains(&ty) {
4124                    out.push(DiscoveryCandidateMatch {
4125                        side,
4126                        overlap: usize::MAX,
4127                    });
4128                }
4129                continue;
4130            }
4131            if preferred_timesync_source
4132                .is_some_and(|source| route.reachable_timesync_sources.iter().any(|s| s == source))
4133            {
4134                out.push(DiscoveryCandidateMatch {
4135                    side,
4136                    overlap: usize::MAX,
4137                });
4138                continue;
4139            }
4140            let overlap =
4141                Self::endpoint_overlap_count(route.reachable.iter().copied(), &scoring_eps);
4142            if overlap > 0 {
4143                out.push(DiscoveryCandidateMatch { side, overlap });
4144            }
4145        }
4146        if !target_senders.is_empty() && out.is_empty() && unique_endpoint_fallbacks.len() == 1 {
4147            out.push(DiscoveryCandidateMatch {
4148                side: unique_endpoint_fallbacks[0],
4149                overlap: usize::MAX,
4150            });
4151        }
4152        out
4153    }
4154
4155    /// Remove reflected discovery routes when another side advertises a
4156    /// shorter topology path to the destination endpoint. A multi-side Router
4157    /// must make this choice before adaptive weighting; otherwise an endpoint
4158    /// learned directly and through a remote bridge can be sent down either
4159    /// side with equal probability.
4160    #[cfg(feature = "discovery")]
4161    fn retain_shortest_discovery_candidates_locked(
4162        st: &RouterInner,
4163        matches: &mut Vec<DiscoveryCandidateMatch>,
4164        endpoints: &[DataEndpoint],
4165        target_senders: &[u64],
4166        now_ms: u64,
4167    ) {
4168        if matches.len() <= 1 || endpoints.is_empty() {
4169            return;
4170        }
4171
4172        let route_distance = |side: RouterSideId, target_sender: Option<u64>| -> Option<usize> {
4173            let route = st.discovery_routes.get(&side)?;
4174            route
4175                .announcers
4176                .iter()
4177                .filter(|(_, state)| {
4178                    now_ms.saturating_sub(state.last_seen_ms) <= DISCOVERY_ROUTE_TTL_MS
4179                })
4180                .filter_map(|(announcer, state)| {
4181                    let targets: BTreeSet<&str> = state
4182                        .topology_boards
4183                        .iter()
4184                        .filter(|board| {
4185                            target_sender
4186                                .map(|target| Self::sender_hash(&board.sender_id) == target)
4187                                .unwrap_or_else(|| {
4188                                    endpoints.iter().any(|endpoint| {
4189                                        board.reachable_endpoints.contains(endpoint)
4190                                    })
4191                                })
4192                        })
4193                        .map(|board| board.sender_id.as_str())
4194                        .collect();
4195                    if targets.is_empty() {
4196                        return if let Some(target) = target_sender {
4197                            state
4198                                .topology_boards
4199                                .iter()
4200                                .any(|board| Self::sender_hash(&board.sender_id) == target)
4201                                .then_some(usize::MAX / 2)
4202                        } else {
4203                            state
4204                                .reachable
4205                                .iter()
4206                                .any(|endpoint| endpoints.contains(endpoint))
4207                                .then_some(usize::MAX / 2)
4208                        };
4209                    }
4210
4211                    let mut distances: BTreeMap<&str, usize> = BTreeMap::new();
4212                    let mut pending = VecDeque::from([(announcer.as_str(), 0usize)]);
4213                    while let Some((sender, distance)) = pending.pop_front() {
4214                        if distances.contains_key(sender) {
4215                            continue;
4216                        }
4217                        distances.insert(sender, distance);
4218                        if targets.contains(sender) {
4219                            return Some(distance);
4220                        }
4221                        for board in &state.topology_boards {
4222                            if board.sender_id == sender {
4223                                for peer in &board.connections {
4224                                    if !distances.contains_key(peer.as_str()) {
4225                                        pending.push_back((peer.as_str(), distance + 1));
4226                                    }
4227                                }
4228                            } else if board.connections.iter().any(|peer| peer == sender)
4229                                && !distances.contains_key(board.sender_id.as_str())
4230                            {
4231                                pending.push_back((board.sender_id.as_str(), distance + 1));
4232                            }
4233                        }
4234                    }
4235                    None
4236                })
4237                .min()
4238        };
4239
4240        let route_targets: Vec<Option<u64>> = if target_senders.is_empty() {
4241            vec![None]
4242        } else {
4243            target_senders.iter().copied().map(Some).collect()
4244        };
4245        let mut selected = BTreeSet::new();
4246        for target in route_targets {
4247            let distances: Vec<(RouterSideId, usize)> = matches
4248                .iter()
4249                .filter_map(|candidate| {
4250                    route_distance(candidate.side, target)
4251                        .map(|distance| (candidate.side, distance))
4252                })
4253                .collect();
4254            let Some(best) = distances.iter().map(|(_, distance)| *distance).min() else {
4255                continue;
4256            };
4257            selected.extend(
4258                distances
4259                    .into_iter()
4260                    .filter(|(_, distance)| *distance == best)
4261                    .map(|(side, _)| side),
4262            );
4263        }
4264        if !selected.is_empty() {
4265            matches.retain(|candidate| selected.contains(&candidate.side));
4266        }
4267    }
4268
4269    #[cfg(feature = "discovery")]
4270    fn select_discovered_candidate_sides_locked(
4271        &self,
4272        st: &mut RouterInner,
4273        exclude: Option<RouterSideId>,
4274        ty: DataType,
4275        target_senders: &[u64],
4276        prefer_best_overlap: bool,
4277        matches: Vec<DiscoveryCandidateMatch>,
4278    ) -> Vec<RouterSideId> {
4279        let discovered_origin = if Self::has_typed_route_overrides_locked(st, exclude, ty)
4280            || !target_senders.is_empty()
4281        {
4282            RouteSelectionOrigin::Flood
4283        } else {
4284            RouteSelectionOrigin::Discovered
4285        };
4286
4287        let mut matches = matches;
4288        if matches.iter().any(|m| m.overlap == usize::MAX) {
4289            matches.retain(|m| m.overlap == usize::MAX);
4290        }
4291
4292        let selected: Vec<RouterSideId> = if prefer_best_overlap {
4293            let best_overlap = matches.iter().map(|m| m.overlap).max().unwrap_or(0);
4294            matches
4295                .into_iter()
4296                .filter(|m| m.overlap == best_overlap)
4297                .map(|m| m.side)
4298                .collect()
4299        } else {
4300            matches.into_iter().map(|m| m.side).collect()
4301        };
4302
4303        /* Heartbeats represent network-wide liveness rather than a request for
4304         * one interchangeable endpoint owner.  When discovery finds the
4305         * heartbeat endpoint on multiple independent shortest-path sides,
4306         * every segment must receive it.  Ordinary data continues through the
4307         * adaptive single-path selector so a multi-homed router does not
4308         * duplicate application traffic. */
4309        let has_distributed_variable_owners = selected.iter().any(|side| {
4310            st.discovery_routes
4311                .get(side)
4312                .is_some_and(|route| route.reachable_network_variables.contains(&ty))
4313        });
4314        if DataType::try_named("HEARTBEAT") == Some(ty) || has_distributed_variable_owners {
4315            selected
4316        } else {
4317            self.apply_route_selection_locked(st, exclude, selected, discovered_origin)
4318        }
4319    }
4320
4321    #[allow(unused_mut)]
4322    fn register_end_to_end_reliable_tx(&self, data: &RouterItem) -> TelemetryResult<bool> {
4323        let packet_id = Self::get_hash(data);
4324        let now_ms = self.clock.now_ms();
4325        let ty = match data {
4326            RouterItem::Packet(pkt) => pkt.data_type(),
4327            RouterItem::Packed(bytes) => wire_format::peek_routing_envelope(bytes.as_ref())?.ty,
4328        };
4329        let mut st = self.state.lock();
4330        #[cfg(feature = "discovery")]
4331        let mut pending_destinations = self.expected_end_to_end_destinations_locked(&st, data)?;
4332        #[cfg(not(feature = "discovery"))]
4333        let mut pending_destinations = BTreeMap::new();
4334        self.filter_trackable_end_to_end_destinations_locked(&st, ty, &mut pending_destinations);
4335        let tracked_destinations = !pending_destinations.is_empty();
4336        if let Some(existing) = st.end_to_end_reliable_tx.get(&packet_id) {
4337            return Ok(!existing.waiting_for_predecessor);
4338        }
4339        // Never evict an unacknowledged predecessor to admit a newer message.
4340        if st.end_to_end_reliable_tx.len() >= runtime_reliable_max_end_to_end_pending().max(1) {
4341            return Err(TelemetryError::HandlerError(
4342                "end-to-end reliable window full",
4343            ));
4344        }
4345        let ordered_type =
4346            matches!(reliable_mode(ty), crate::ReliableMode::Ordered).then_some(ty.as_u32());
4347        let waiting_for_predecessor = ordered_type.is_some()
4348            && st
4349                .end_to_end_reliable_tx
4350                .values()
4351                .any(|sent| sent.ordered_type == ordered_type);
4352        #[cfg(feature = "std")]
4353        if reliable_trace_enabled() {
4354            eprintln!(
4355                "[sedsnet-reliable] register time={now_ms} type={} id={packet_id:#x} waiting={waiting_for_predecessor} targets={pending_destinations:?}",
4356                ty.as_u32()
4357            );
4358        }
4359        let mut retained = data.clone();
4360        #[cfg(feature = "compact-packet-store")]
4361        retained.park_for_queue()?;
4362        Self::remember_end_to_end_reliable_tx_locked(&mut st, packet_id);
4363        st.end_to_end_reliable_tx.insert(
4364            packet_id,
4365            EndToEndReliableSent {
4366                ordered_type,
4367                waiting_for_predecessor,
4368                data: retained,
4369                pending_destinations,
4370                tracked_destinations,
4371                last_send_ms: now_ms,
4372                retries: 0,
4373                queued: false,
4374            },
4375        );
4376        Ok(!waiting_for_predecessor)
4377    }
4378
4379    // Select at most one in-flight packet per ordered type. Waiting messages
4380    // share the existing bounded pending window, not an unbounded extra queue.
4381    fn release_ordered_successors(&self) -> TelemetryResult<()> {
4382        let ready = {
4383            let mut st = self.state.lock();
4384            let RouterInner {
4385                end_to_end_reliable_tx_order: order,
4386                end_to_end_reliable_tx: pending,
4387                ..
4388            } = &mut *st;
4389            let mut ready = Vec::new();
4390            for (index, id) in order.iter().copied().enumerate() {
4391                let Some(sent) = pending.get(&id) else {
4392                    continue;
4393                };
4394                if !sent.waiting_for_predecessor {
4395                    continue;
4396                }
4397                let Some(ty) = sent.ordered_type else {
4398                    continue;
4399                };
4400                // The retained window is bounded. Inspect earlier entries
4401                // instead of rebuilding an allocated active-type set on every
4402                // idle service pass. Only an actual release needs ready storage.
4403                if order.iter().take(index).any(|earlier| {
4404                    pending
4405                        .get(earlier)
4406                        .is_some_and(|sent| sent.ordered_type == Some(ty))
4407                }) {
4408                    continue;
4409                }
4410                let sent = pending.get_mut(&id).expect("retained ordered entry");
4411                sent.waiting_for_predecessor = false;
4412                sent.last_send_ms = self.clock.now_ms();
4413                ready.push(id);
4414            }
4415            ready
4416        };
4417        for id in ready {
4418            self.queue_end_to_end_reliable_retransmit(id)?;
4419        }
4420        Ok(())
4421    }
4422
4423    #[cfg(feature = "discovery")]
4424    fn reconcile_end_to_end_reliable_destinations_locked(
4425        &self,
4426        st: &mut RouterInner,
4427    ) -> TelemetryResult<()> {
4428        if st.end_to_end_reliable_tx.is_empty() {
4429            return Ok(());
4430        }
4431        let active_destinations = self.active_end_to_end_destinations_locked(st);
4432        let packet_ids: Vec<u64> = st.end_to_end_reliable_tx.keys().copied().collect();
4433        let mut completed = Vec::new();
4434
4435        for packet_id in packet_ids {
4436            let Some(data) = st
4437                .end_to_end_reliable_tx
4438                .get(&packet_id)
4439                .map(|sent| sent.data.clone())
4440            else {
4441                continue;
4442            };
4443            let expected = self.expected_end_to_end_destinations_locked(st, &data)?;
4444            let Some(sent) = st.end_to_end_reliable_tx.get_mut(&packet_id) else {
4445                continue;
4446            };
4447            if !sent.tracked_destinations {
4448                continue;
4449            }
4450            // Route churn is not a delivery acknowledgement for ordered data.
4451            if sent.ordered_type.is_some() {
4452                for (sender, side) in &mut sent.pending_destinations {
4453                    if let Some(next_side) = expected
4454                        .get(sender)
4455                        .or_else(|| active_destinations.get(sender))
4456                    {
4457                        *side = *next_side;
4458                    }
4459                }
4460                continue;
4461            }
4462            sent.pending_destinations.retain(|sender_hash, side| {
4463                match (
4464                    expected.get(sender_hash),
4465                    active_destinations.get(sender_hash),
4466                ) {
4467                    (Some(next_side), _) | (None, Some(next_side)) => {
4468                        *side = *next_side;
4469                        true
4470                    }
4471                    (None, None) => false,
4472                }
4473            });
4474            if sent.pending_destinations.is_empty() {
4475                completed.push(packet_id);
4476            }
4477        }
4478
4479        for packet_id in completed {
4480            st.end_to_end_reliable_tx.remove(&packet_id);
4481        }
4482
4483        Ok(())
4484    }
4485
4486    #[cfg(feature = "discovery")]
4487    fn active_end_to_end_destinations_locked(
4488        &self,
4489        st: &RouterInner,
4490    ) -> BTreeMap<u64, RouterSideId> {
4491        let now_ms = self.clock.now_ms();
4492        let mut out = BTreeMap::new();
4493        for (&side, route) in st.discovery_routes.iter() {
4494            if now_ms.saturating_sub(route.last_seen_ms) > DISCOVERY_ROUTE_TTL_MS {
4495                continue;
4496            }
4497            for sender_state in route.announcers.values() {
4498                if now_ms.saturating_sub(sender_state.last_seen_ms) > DISCOVERY_ROUTE_TTL_MS {
4499                    continue;
4500                }
4501                for board in sender_state.topology_boards.iter() {
4502                    if !Self::is_end_to_end_destination_sender(&board.sender_id) {
4503                        continue;
4504                    }
4505                    out.insert(Self::sender_hash(&board.sender_id), side);
4506                    if out.len() >= runtime_reliable_max_end_to_end_pending().max(1) {
4507                        return out;
4508                    }
4509                }
4510            }
4511        }
4512        out
4513    }
4514
4515    fn side_supports_end_to_end_tracking_locked(st: &RouterInner, side: RouterSideId) -> bool {
4516        matches!(
4517            st.sides
4518                .get(side)
4519                .and_then(Option::as_ref)
4520                .map(|side| &side.tx_handler),
4521            Some(RouterTxHandlerFn::Packed(_) | RouterTxHandlerFn::PackedWithPriority(_))
4522        )
4523    }
4524
4525    fn filter_trackable_end_to_end_destinations_locked(
4526        &self,
4527        st: &RouterInner,
4528        ty: DataType,
4529        pending: &mut BTreeMap<u64, RouterSideId>,
4530    ) {
4531        // A heartbeat describes current liveness. Retrying an old heartbeat
4532        // until every discovered endpoint acknowledges it turns temporary
4533        // congestion into an ACK/retransmit storm and can starve fresh
4534        // heartbeats. Per-link reliability still applies where configured;
4535        // the next periodic heartbeat is the end-to-end freshness retry.
4536        if DataType::try_named("HEARTBEAT") == Some(ty) {
4537            pending.clear();
4538            return;
4539        }
4540        let now_ms = self.clock.now_ms();
4541        pending.retain(|_, side| {
4542            Self::side_supports_end_to_end_tracking_locked(st, *side)
4543                && (is_reliable_type(ty)
4544                    || !self.side_has_multiple_announcers_locked(st, *side, now_ms))
4545        });
4546    }
4547
4548    #[cfg(feature = "discovery")]
4549    fn side_has_multiple_announcers_locked(
4550        &self,
4551        st: &RouterInner,
4552        side: RouterSideId,
4553        now_ms: u64,
4554    ) -> bool {
4555        st.discovery_routes
4556            .get(&side)
4557            .map(|route| {
4558                route
4559                    .announcers
4560                    .values()
4561                    .filter(|sender| {
4562                        now_ms.saturating_sub(sender.last_seen_ms) <= DISCOVERY_ROUTE_TTL_MS
4563                    })
4564                    .take(2)
4565                    .count()
4566                    > 1
4567            })
4568            .unwrap_or(false)
4569    }
4570
4571    #[cfg(not(feature = "discovery"))]
4572    fn side_has_multiple_announcers_locked(
4573        &self,
4574        _st: &RouterInner,
4575        _side: RouterSideId,
4576        _now_ms: u64,
4577    ) -> bool {
4578        false
4579    }
4580
4581    fn queue_end_to_end_reliable_ack(
4582        &self,
4583        pkt: &Packet,
4584        called_from_queue: bool,
4585    ) -> TelemetryResult<()> {
4586        self.queue_end_to_end_reliable_ack_for_packet_id(
4587            pkt.packet_id(),
4588            pkt.sender(),
4589            called_from_queue,
4590        )
4591    }
4592
4593    fn queue_end_to_end_reliable_ack_for_packet_id(
4594        &self,
4595        packet_id: u64,
4596        original_sender: &str,
4597        called_from_queue: bool,
4598    ) -> TelemetryResult<()> {
4599        if !self.cfg.reliable_enabled() {
4600            return Ok(());
4601        }
4602        let original_sender_hash = {
4603            let st = self.state.lock();
4604            Self::sender_hash(&Self::canonical_sender_locked(&st, original_sender))
4605        };
4606        self.queue_end_to_end_reliable_ack_for_sender_hash(
4607            packet_id,
4608            original_sender_hash,
4609            called_from_queue,
4610        )
4611    }
4612
4613    fn queue_end_to_end_reliable_ack_for_sender_hash(
4614        &self,
4615        packet_id: u64,
4616        original_sender_hash: u64,
4617        called_from_queue: bool,
4618    ) -> TelemetryResult<()> {
4619        if !self.cfg.reliable_enabled() {
4620            return Ok(());
4621        }
4622        let ack_sender = self.encode_end_to_end_ack_sender();
4623        let ack = Packet::new(
4624            DataType::ReliableAck,
4625            message_meta(DataType::ReliableAck).endpoints_ref(),
4626            ack_sender.as_str(),
4627            self.packet_timestamp_ms(),
4628            crate::shared_bytes::convert(Self::encode_end_to_end_reliable_ack(packet_id)),
4629        )?;
4630        let local_sender = self.sender_arc();
4631        // Keep the acknowledging owner first for compatibility. The second
4632        // identity is the original publisher: it lets a busy relay recover the
4633        // ACK route from discovery after its bounded packet-id cache expires.
4634        let ack = self.attach_wire_contract_to_item(
4635            RouterItem::Packet(ack),
4636            &[
4637                Self::sender_hash(local_sender.as_ref()),
4638                original_sender_hash,
4639            ],
4640        )?;
4641        self.emit_internal_tx(RouterTxItem::Broadcast(ack), true, called_from_queue)
4642    }
4643
4644    fn emit_internal_tx(
4645        &self,
4646        item: RouterTxItem,
4647        ignore_local: bool,
4648        called_from_queue: bool,
4649    ) -> TelemetryResult<()> {
4650        if called_from_queue {
4651            self.tx_queue_item_with_flags(item, ignore_local)
4652        } else {
4653            self.tx_item_impl(item, ignore_local, false)
4654        }
4655    }
4656
4657    fn emit_internal_tx_with_priority(
4658        &self,
4659        item: RouterTxItem,
4660        ignore_local: bool,
4661        priority: u8,
4662        called_from_queue: bool,
4663    ) -> TelemetryResult<()> {
4664        if called_from_queue {
4665            self.tx_queue_item_with_priority(item, ignore_local, priority)
4666        } else {
4667            self.tx_item_impl(item, ignore_local, false)
4668        }
4669    }
4670
4671    fn queue_end_to_end_reliable_retransmit(&self, packet_id: u64) -> TelemetryResult<()> {
4672        {
4673            let mut st = self.state.lock();
4674            let Some(sent) = st.end_to_end_reliable_tx.get_mut(&packet_id) else {
4675                return Ok(());
4676            };
4677            if sent.queued {
4678                return Ok(());
4679            }
4680            sent.queued = true;
4681        }
4682        let result = self.tx_queue_item_with_priority(
4683            RouterTxItem::EndToEndReplay { packet_id },
4684            true,
4685            Self::router_item_priority_bumped(DataType::ReliableAck),
4686        );
4687        if result.is_err()
4688            && let Some(sent) = self.state.lock().end_to_end_reliable_tx.get_mut(&packet_id)
4689        {
4690            sent.queued = false;
4691        }
4692        result
4693    }
4694
4695    fn end_to_end_retransmit_sides(
4696        &self,
4697        packet_id: u64,
4698    ) -> Option<(RouterItem, Vec<RouterSideId>)> {
4699        let mut st = self.state.lock();
4700        let (data, tracked_destinations, mut sides) = {
4701            let sent = st.end_to_end_reliable_tx.get_mut(&packet_id)?;
4702            #[cfg(feature = "std")]
4703            if reliable_trace_enabled() {
4704                eprintln!(
4705                    "[sedsnet-reliable] replay time={} id={packet_id:#x} retries={} targets={:?}",
4706                    self.clock.now_ms(),
4707                    sent.retries,
4708                    sent.pending_destinations
4709                );
4710            }
4711            sent.queued = false;
4712            sent.last_send_ms = self.clock.now_ms();
4713            let data = sent.data.clone();
4714            let tracked_destinations = sent.tracked_destinations;
4715            let sides: Vec<RouterSideId> = sent.pending_destinations.values().copied().collect();
4716            (data, tracked_destinations, sides)
4717        };
4718        if tracked_destinations && sides.is_empty() {
4719            st.end_to_end_reliable_tx.remove(&packet_id);
4720            return None;
4721        }
4722        crate::small_sort::sort(&mut sides);
4723        sides.dedup();
4724        Some((data, sides))
4725    }
4726
4727    fn router_item_priority(data: &RouterItem) -> TelemetryResult<u8> {
4728        let ty = match data {
4729            RouterItem::Packet(pkt) => pkt.data_type(),
4730            RouterItem::Packed(bytes) => wire_format::peek_routing_envelope(bytes.as_ref())?.ty,
4731        };
4732        Ok(crate::scheduler_priority(ty))
4733    }
4734
4735    #[inline]
4736    fn router_item_priority_bumped(ty: DataType) -> u8 {
4737        crate::scheduler_priority(ty).saturating_add(16)
4738    }
4739
4740    #[inline]
4741    fn is_side_tx_busy(err: &TelemetryError) -> bool {
4742        matches!(err, TelemetryError::Io("side tx busy"))
4743    }
4744
4745    #[cfg(feature = "timesync")]
4746    fn timesync_has_usable_time_locked(st: &TimeSyncRuntime, now_mono_ns: u64) -> bool {
4747        st.disciplined_clock.read_unix_ms(now_mono_ns).is_some()
4748            || st
4749                .clock
4750                .current_time(now_mono_ns)
4751                .and_then(|reading| reading.unix_time_ms)
4752                .is_some()
4753    }
4754
4755    #[cfg(feature = "timesync")]
4756    fn reconcile_pending_timesync_request_locked(
4757        st: &mut TimeSyncRuntime,
4758        leader: &Option<TimeSyncLeader>,
4759        now_ms: u64,
4760    ) {
4761        let active_remote = match leader {
4762            Some(TimeSyncLeader::Remote(remote)) => Some(remote.sender.as_str()),
4763            _ => None,
4764        };
4765        let should_clear = st
4766            .pending_request
4767            .as_ref()
4768            .is_some_and(|pending| Some(pending.source.as_str()) != active_remote);
4769        if should_clear {
4770            st.pending_request = None;
4771            st.next_request_mono_ms = now_ms;
4772        }
4773    }
4774
4775    ///Helper function for relay_send
4776    #[inline]
4777    fn enqueue_to_sides(
4778        &self,
4779        data: RouterItem,
4780        exclude: Option<RouterSideId>,
4781        ignore_local: bool,
4782    ) -> TelemetryResult<()> {
4783        let data = self.clear_intermediate_hop_targets(data)?;
4784        let plan = self.remote_side_plan(&data, exclude)?;
4785        let mut st = self.state.lock();
4786        let ty = Self::item_data_type(&data)?;
4787        let priority = if st.managed_variable_types.contains(&ty.as_u32()) {
4788            crate::transport_priority(DataType::ManagedVariableValue)
4789        } else {
4790            Self::router_item_priority(&data)?
4791        };
4792
4793        let RemoteSidePlan::Target(sides) = plan;
4794        for idx in sides {
4795            st.push_transmit(TxQueued {
4796                item: RouterTxItem::ToSide {
4797                    src: exclude,
4798                    dst: idx,
4799                    data: data.clone(),
4800                },
4801                ignore_local,
4802                priority,
4803                retry_after_ms: 0,
4804            })?;
4805        }
4806
4807        Ok(())
4808    }
4809
4810    fn relay_send(
4811        &self,
4812        data: RouterItem,
4813        src: Option<RouterSideId>,
4814        called_from_queue: bool,
4815    ) -> TelemetryResult<()> {
4816        if called_from_queue {
4817            return self.enqueue_to_sides(data, src, true);
4818        }
4819
4820        let data = self.clear_intermediate_hop_targets(data)?;
4821        let RemoteSidePlan::Target(sides) = self.remote_side_plan(&data, src)?;
4822        for side in sides {
4823            self.tx_item_impl(
4824                RouterTxItem::ToSide {
4825                    src,
4826                    dst: side,
4827                    data: data.clone(),
4828                },
4829                true,
4830                false,
4831            )?;
4832        }
4833
4834        Ok(())
4835    }
4836
4837    fn item_route_info(&self, data: &RouterItem) -> TelemetryResult<(Vec<DataEndpoint>, DataType)> {
4838        match data {
4839            RouterItem::Packet(pkt) => {
4840                pkt.validate()?;
4841                let mut eps = pkt.endpoints().to_vec();
4842                crate::small_sort::sort(&mut eps);
4843                eps.dedup();
4844                Ok((eps, pkt.data_type()))
4845            }
4846            RouterItem::Packed(bytes) => {
4847                let env = wire_format::peek_routing_envelope(bytes.as_ref())?;
4848                let mut eps: Vec<DataEndpoint> = env.endpoints().collect();
4849                crate::small_sort::sort(&mut eps);
4850                eps.dedup();
4851                Ok((eps, env.ty))
4852            }
4853        }
4854    }
4855
4856    fn item_data_type(data: &RouterItem) -> TelemetryResult<DataType> {
4857        match data {
4858            RouterItem::Packet(pkt) => Ok(pkt.data_type()),
4859            RouterItem::Packed(bytes) => Ok(wire_format::peek_routing_envelope(bytes.as_ref())?.ty),
4860        }
4861    }
4862
4863    fn e2e_crypto_supported(&self) -> bool {
4864        #[cfg(feature = "cryptography")]
4865        {
4866            self.cfg.e2e_encryption() != RouterE2eEncryptionMode::Disabled
4867                && crate::crypto::registered_crypto_available()
4868        }
4869        #[cfg(not(feature = "cryptography"))]
4870        {
4871            false
4872        }
4873    }
4874
4875    fn should_require_e2e_for_type(&self, ty: DataType) -> bool {
4876        if is_internal_control_type(ty) {
4877            return false;
4878        }
4879        match self.cfg.e2e_encryption() {
4880            RouterE2eEncryptionMode::Disabled => {
4881                message_e2e_encryption_policy(ty) == E2eEncryptionPolicy::RequireOn
4882            }
4883            RouterE2eEncryptionMode::RequiredOnly => {
4884                message_e2e_encryption_policy(ty) == E2eEncryptionPolicy::RequireOn
4885            }
4886            RouterE2eEncryptionMode::Preferred => matches!(
4887                message_e2e_encryption_policy(ty),
4888                E2eEncryptionPolicy::PreferOn | E2eEncryptionPolicy::RequireOn
4889            ),
4890            RouterE2eEncryptionMode::ForceAll => true,
4891        }
4892    }
4893
4894    fn ensure_e2e_policy_supported_for_type(&self, ty: DataType) -> TelemetryResult<()> {
4895        if self.should_require_e2e_for_type(ty) && !self.e2e_crypto_supported() {
4896            return Err(TelemetryError::BadArg);
4897        }
4898        Ok(())
4899    }
4900
4901    #[cfg(feature = "cryptography")]
4902    fn e2e_seal_config_for_type(&self, ty: DataType) -> Option<wire_format::E2eSealConfig> {
4903        if self.should_require_e2e_for_type(ty) && self.e2e_crypto_supported() {
4904            Some(wire_format::E2eSealConfig {
4905                key_id: self.cfg.e2e_key_id(),
4906            })
4907        } else {
4908            None
4909        }
4910    }
4911
4912    #[inline]
4913    fn pack_packet_for_router(
4914        &self,
4915        pkt: &Packet,
4916        reliable: Option<wire_format::ReliableHeader>,
4917    ) -> TelemetryResult<crate::SharedBytes> {
4918        #[cfg(feature = "cryptography")]
4919        if let Some(e2e) = self.e2e_seal_config_for_type(pkt.data_type()) {
4920            return (wire_format::pack_packet_with_wire_contract_e2e(
4921                pkt,
4922                reliable,
4923                pkt.wire_shape(),
4924                pkt.wire_target_senders(),
4925                e2e,
4926            ))
4927            .map(crate::shared_bytes::convert);
4928        }
4929        Ok(match reliable {
4930            Some(hdr) => {
4931                crate::shared_bytes::convert(wire_format::pack_packet_with_reliable(pkt, hdr))
4932            }
4933            None => crate::shared_bytes::convert(wire_format::pack_packet(pkt)),
4934        })
4935    }
4936
4937    #[inline]
4938    fn pack_packet_for_contract(
4939        &self,
4940        pkt: &Packet,
4941        reliable: Option<wire_format::ReliableHeader>,
4942        shape: Option<MessageElement>,
4943        target_senders: &[u64],
4944    ) -> TelemetryResult<crate::SharedBytes> {
4945        #[cfg(feature = "cryptography")]
4946        if let Some(e2e) = self.e2e_seal_config_for_type(pkt.data_type()) {
4947            return (wire_format::pack_packet_with_wire_contract_e2e(
4948                pkt,
4949                reliable,
4950                shape,
4951                target_senders,
4952                e2e,
4953            ))
4954            .map(crate::shared_bytes::convert);
4955        }
4956        wire_format::pack_packet_with_wire_contract(pkt, reliable, shape, target_senders)
4957            .map(crate::shared_bytes::convert)
4958    }
4959
4960    #[cfg(feature = "cryptography")]
4961    #[inline]
4962    fn prepare_packed_for_remote(
4963        &self,
4964        bytes: crate::SharedBytes,
4965        reliable_override: Option<Option<wire_format::ReliableHeader>>,
4966    ) -> TelemetryResult<crate::SharedBytes> {
4967        let frame = wire_format::peek_frame_info(bytes.as_ref())?;
4968        if frame.ack_only() || self.e2e_seal_config_for_type(frame.envelope.ty).is_none() {
4969            return Ok(bytes);
4970        }
4971        let reliable = reliable_override.unwrap_or(frame.reliable);
4972        let pkt = wire_format::unpack_packet(bytes.as_ref())?;
4973        self.pack_packet_for_contract(
4974            &pkt,
4975            reliable,
4976            frame.envelope.wire_shape,
4977            &frame.envelope.target_senders,
4978        )
4979    }
4980
4981    // Best-effort telemetry does not become reliable merely because discovery
4982    // currently sees one peer. Preserve explicit frozen receipt contracts and
4983    // managed-variable delivery even when the current schema is best effort.
4984    fn item_requires_end_to_end_tracking(
4985        &self,
4986        data: &RouterItem,
4987        ty: DataType,
4988    ) -> TelemetryResult<bool> {
4989        if is_reliable_type(ty) || self.is_managed_variable_type(ty) {
4990            return Ok(true);
4991        }
4992        match data {
4993            RouterItem::Packet(packet) => Ok(!packet.wire_target_senders().is_empty()),
4994            RouterItem::Packed(bytes) => {
4995                let frame = wire_format::peek_routing_frame_info(bytes.as_ref())?;
4996                Ok(frame.reliable.is_some() || frame.envelope.target_senders().len() != 0)
4997            }
4998        }
4999    }
5000
5001    fn item_target_senders<'a>(
5002        &self,
5003        data: &'a RouterItem,
5004    ) -> TelemetryResult<alloc::borrow::Cow<'a, [u64]>> {
5005        match data {
5006            RouterItem::Packet(pkt) => Ok(alloc::borrow::Cow::Borrowed(pkt.wire_target_senders())),
5007            RouterItem::Packed(bytes) => {
5008                let env = wire_format::peek_routing_envelope(bytes.as_ref())?;
5009                if env.target_senders().len() == 0 {
5010                    Ok(alloc::borrow::Cow::Borrowed(&[]))
5011                } else {
5012                    Ok(alloc::borrow::Cow::Owned(env.target_senders().collect()))
5013                }
5014            }
5015        }
5016    }
5017
5018    fn item_targets_local_sender(&self, data: &RouterItem) -> TelemetryResult<bool> {
5019        let local_hash = Self::sender_hash(self.sender_arc().as_ref());
5020        match data {
5021            RouterItem::Packet(pkt) => Ok(pkt.wire_target_senders().is_empty()
5022                || pkt.wire_target_senders().contains(&local_hash)),
5023            RouterItem::Packed(bytes) => {
5024                let env = wire_format::peek_routing_envelope(bytes.as_ref())?;
5025                Ok(env.target_senders().len() == 0
5026                    || env.target_senders().any(|target| target == local_hash))
5027            }
5028        }
5029    }
5030
5031    fn item_targets_router_as_intermediate_hop(
5032        &self,
5033        data: &RouterItem,
5034        endpoints: &[DataEndpoint],
5035    ) -> TelemetryResult<bool> {
5036        let targets = self.item_target_senders(data)?;
5037        if targets.is_empty()
5038            // A contract that already names downstream owners must remain byte-identical while
5039            // crossing this router. Clearing it changes the packet ID and makes those owners'
5040            // end-to-end ACKs impossible for the source to match. Re-resolve only the legacy
5041            // single-next-hop form where this router is the sole frozen target.
5042            || targets.len() != 1
5043            || endpoints.iter().copied().any(|endpoint| {
5044                self.endpoint_has_packet_handler(endpoint)
5045                    || self.endpoint_has_packed_handler(endpoint)
5046            })
5047        {
5048            return Ok(false);
5049        }
5050        let local_sender = self.sender_arc();
5051        Ok(targets.contains(&Self::sender_hash(local_sender.as_ref())))
5052    }
5053
5054    fn clear_intermediate_hop_targets(&self, data: RouterItem) -> TelemetryResult<RouterItem> {
5055        // Only a sole frozen next-hop target can require contract rewriting.
5056        // Ordinary broadcasts need no owned endpoint or target scratch.
5057        if self.item_target_senders(&data)?.len() != 1 {
5058            return Ok(data);
5059        }
5060        let (endpoints, _) = self.item_route_info(&data)?;
5061        if !self.item_targets_router_as_intermediate_hop(&data, &endpoints)? {
5062            return Ok(data);
5063        }
5064        let pkt = match &data {
5065            RouterItem::Packet(pkt) => pkt.clone(),
5066            RouterItem::Packed(bytes) => wire_format::unpack_packet(bytes.as_ref())?,
5067        };
5068        let reliable = match &data {
5069            RouterItem::Packet(_) => None,
5070            RouterItem::Packed(bytes) => wire_format::peek_frame_info(bytes.as_ref())?.reliable,
5071        };
5072        let shape = match &data {
5073            RouterItem::Packet(pkt) => pkt.wire_shape(),
5074            RouterItem::Packed(bytes) => {
5075                wire_format::peek_routing_envelope(bytes.as_ref())?.wire_shape
5076            }
5077        };
5078        let forwarded = self.pack_packet_for_contract(&pkt, reliable, shape, &[])?;
5079        let upstream_id = match &data {
5080            RouterItem::Packet(pkt) => pkt.packet_id(),
5081            RouterItem::Packed(bytes) => wire_format::packet_id_from_wire(bytes.as_ref())?,
5082        };
5083        let downstream_id = wire_format::packet_id_from_wire(forwarded.as_ref())?;
5084        {
5085            let mut st = self.state.lock();
5086            if let Some(mut route) = st.reliable_return_routes.get(&upstream_id).cloned() {
5087                route.upstream_contract = Some((
5088                    upstream_id,
5089                    Self::sender_hash(&Self::canonical_sender_locked(&st, pkt.sender())),
5090                ));
5091                Self::remember_reliable_return_route_locked(&mut st, downstream_id);
5092                st.reliable_return_routes.insert(downstream_id, route);
5093            }
5094        }
5095        Ok(RouterItem::Packed(forwarded))
5096    }
5097
5098    #[cfg(feature = "discovery")]
5099    fn side_matches_target_senders_locked(
5100        st: &RouterInner,
5101        side: RouterSideId,
5102        target_senders: &[u64],
5103        now_ms: u64,
5104    ) -> bool {
5105        st.discovery_routes
5106            .get(&side)
5107            .map(|route| {
5108                if now_ms.saturating_sub(route.last_seen_ms) > DISCOVERY_ROUTE_TTL_MS {
5109                    return false;
5110                }
5111                route.announcers.iter().any(|(announcer, sender_state)| {
5112                    if now_ms.saturating_sub(sender_state.last_seen_ms) > DISCOVERY_ROUTE_TTL_MS {
5113                        return false;
5114                    }
5115                    target_senders.contains(&Self::sender_hash(announcer))
5116                        || sender_state
5117                            .advertised_address
5118                            .is_some_and(|address| target_senders.contains(&u64::from(address)))
5119                        || sender_state.topology_boards.iter().any(|board| {
5120                            target_senders.contains(&Self::sender_hash(&board.sender_id))
5121                        })
5122                })
5123            })
5124            .unwrap_or(false)
5125    }
5126
5127    fn attach_wire_contract_to_item(
5128        &self,
5129        data: RouterItem,
5130        target_senders: &[u64],
5131    ) -> TelemetryResult<RouterItem> {
5132        match data {
5133            RouterItem::Packet(pkt) => {
5134                let reliable = if is_reliable_type(pkt.data_type()) {
5135                    Some(wire_format::ReliableHeader {
5136                        flags: wire_format::RELIABLE_FLAG_UNSEQUENCED,
5137                        seq: 0,
5138                        ack: 0,
5139                    })
5140                } else {
5141                    None
5142                };
5143                let bytes = self.pack_packet_for_contract(
5144                    &pkt,
5145                    reliable,
5146                    Some(message_meta(pkt.data_type()).element),
5147                    target_senders,
5148                )?;
5149                Ok(RouterItem::Packed(bytes))
5150            }
5151            RouterItem::Packed(bytes) => Ok(RouterItem::Packed(bytes)),
5152        }
5153    }
5154
5155    fn endpoints_are_link_local_only(eps: &[DataEndpoint]) -> bool {
5156        !eps.is_empty() && eps.iter().all(|ep| ep.is_link_local_only())
5157    }
5158
5159    fn should_route_remote(
5160        &self,
5161        data: &RouterItem,
5162        exclude: Option<RouterSideId>,
5163    ) -> TelemetryResult<bool> {
5164        #[cfg(feature = "discovery")]
5165        {
5166            let RemoteSidePlan::Target(sides) = self.remote_side_plan(data, exclude)?;
5167            Ok(!sides.is_empty())
5168        }
5169
5170        #[cfg(not(feature = "discovery"))]
5171        {
5172            let (eps, ty) = self.item_route_info(data)?;
5173            if !(has_nonlocal_endpoint(&eps, &self.cfg) || force_remote_for_type(ty)) {
5174                return Ok(false);
5175            }
5176            let st = self.state.lock();
5177            Ok(!self
5178                .eligible_side_ids_locked(
5179                    &st,
5180                    exclude,
5181                    Some(ty),
5182                    Self::endpoints_are_link_local_only(&eps),
5183                )
5184                .is_empty())
5185        }
5186    }
5187
5188    #[cfg(feature = "discovery")]
5189    fn has_explicit_route_policy_locked(
5190        st: &RouterInner,
5191        src: Option<RouterSideId>,
5192        ty: DataType,
5193    ) -> bool {
5194        st.route_overrides
5195            .keys()
5196            .any(|(route_src, _)| *route_src == src)
5197            || Self::has_typed_route_overrides_locked(st, src, ty)
5198    }
5199
5200    fn remote_side_plan(
5201        &self,
5202        data: &RouterItem,
5203        exclude: Option<RouterSideId>,
5204    ) -> TelemetryResult<RemoteSidePlan> {
5205        #[cfg(feature = "discovery")]
5206        {
5207            let (eps, ty) = self.item_route_info(data)?;
5208            let target_senders = self.item_target_senders(data)?;
5209            let resolve_beyond_local_hop =
5210                self.item_targets_router_as_intermediate_hop(data, &eps)?;
5211            let preferred_packet_id = Self::reliable_control_target_packet_id(data)?;
5212            let routing_target_senders: &[u64] = if preferred_packet_id.is_some() {
5213                // ACK target[0] identifies the acknowledging owner, not the
5214                // destination. Never route an ACK back toward that owner.
5215                target_senders.get(1..).unwrap_or(&[])
5216            } else if resolve_beyond_local_hop {
5217                &[]
5218            } else {
5219                target_senders.as_ref()
5220            };
5221            if discovery::is_discovery_type(ty) {
5222                let mut st = self.state.lock();
5223                let sides = self.eligible_side_ids_locked(&st, exclude, Some(ty), false);
5224                return Ok(RemoteSidePlan::Target(self.apply_route_selection_locked(
5225                    &mut st,
5226                    exclude,
5227                    sides,
5228                    RouteSelectionOrigin::Flood,
5229                )));
5230            }
5231            #[cfg(feature = "timesync")]
5232            if ty == DataType::TimeSyncAnnounce {
5233                // A source announcement describes the source carried in the
5234                // packet; routing it *toward* that discovered source sends it
5235                // back where it came from and leaves downstream clocks blind.
5236                // Propagate it once away from the ingress side, like topology
5237                // discovery. Dedupe and split-horizon exclusion prevent echoes,
5238                // while requests still use the exact discovered source route.
5239                let mut st = self.state.lock();
5240                let sides = self.eligible_side_ids_locked(&st, exclude, Some(ty), false);
5241                let sides =
5242                    Self::filter_timesync_sides_locked(&mut st, ty, self.clock.now_ms(), sides);
5243                return Ok(RemoteSidePlan::Target(self.apply_route_selection_locked(
5244                    &mut st,
5245                    exclude,
5246                    sides,
5247                    RouteSelectionOrigin::Flood,
5248                )));
5249            }
5250            let mut st = self.state.lock();
5251            // Preserve the local-only traffic optimization unless discovery
5252            // has positively identified a remote owner of this network
5253            // variable. A router may own the same schema endpoint locally and
5254            // still need to replicate the value across an IPC/network boundary.
5255            let now_ms = self.clock.now_ms();
5256            let has_remote_variable_owner = st.managed_variable_types.contains(&ty.as_u32())
5257                && st.discovery_routes.iter().any(|(&side, route)| {
5258                    exclude != Some(side)
5259                        && now_ms.saturating_sub(route.last_seen_ms) <= DISCOVERY_ROUTE_TTL_MS
5260                        && route.reachable_network_variables.contains(&ty)
5261                });
5262            if !(has_nonlocal_endpoint(&eps, &self.cfg)
5263                || force_remote_for_type(ty)
5264                || !target_senders.is_empty()
5265                || has_remote_variable_owner)
5266            {
5267                return Ok(RemoteSidePlan::Target(Vec::new()));
5268            }
5269
5270            #[cfg(feature = "timesync")]
5271            let preferred_timesync_source = self.preferred_timesync_route_source(data, ty)?;
5272            #[cfg(not(feature = "timesync"))]
5273            let preferred_timesync_source: Option<String> = None;
5274
5275            if let Some(packet_id) = preferred_packet_id {
5276                let target_side = st
5277                    .reliable_return_routes
5278                    .get(&packet_id)
5279                    .map(|route| route.side);
5280                if let Some(side) = target_side
5281                    .filter(|side| self.route_allowed_locked(&st, exclude, Some(ty), *side))
5282                {
5283                    #[cfg(feature = "timesync")]
5284                    if !Self::timesync_allowed_for_side_locked(
5285                        &mut st,
5286                        side,
5287                        ty,
5288                        self.clock.now_ms(),
5289                    ) {
5290                        return Ok(RemoteSidePlan::Target(Vec::new()));
5291                    }
5292                    return Ok(RemoteSidePlan::Target(vec![side]));
5293                }
5294                // A busy multi-source relay can age a packet-id return entry
5295                // out of its bounded cache before the end-to-end ACK arrives.
5296                // The ACK still carries the original sender hash, so fall
5297                // through to the discovered named-sender route. This remains
5298                // selective and avoids either dropping the ACK or flooding it.
5299            }
5300            let restrict_link_local = Self::endpoints_are_link_local_only(&eps);
5301            let prefer_best_overlap = is_reliable_type(ty)
5302                && routing_target_senders.is_empty()
5303                && preferred_packet_id.is_none();
5304            if st.discovery_routes.is_empty() {
5305                let mut fallback =
5306                    self.eligible_side_ids_locked(&st, exclude, Some(ty), restrict_link_local);
5307                #[cfg(feature = "timesync")]
5308                {
5309                    fallback = Self::filter_timesync_sides_locked(
5310                        &mut st,
5311                        ty,
5312                        self.clock.now_ms(),
5313                        fallback,
5314                    );
5315                }
5316                return Ok(RemoteSidePlan::Target(if fallback.len() == 1 {
5317                    fallback
5318                } else {
5319                    Vec::new()
5320                }));
5321            }
5322            let mut matches = self.discovered_route_candidates_locked(
5323                &st,
5324                exclude,
5325                ty,
5326                &eps,
5327                routing_target_senders,
5328                prefer_best_overlap,
5329                preferred_timesync_source.as_deref(),
5330            );
5331            #[cfg(feature = "timesync")]
5332            {
5333                matches =
5334                    Self::filter_timesync_matches_locked(&mut st, ty, self.clock.now_ms(), matches);
5335            }
5336            /* Heartbeats and replicated network variables are network-segment
5337             * state, so a nearer owner on one side must not hide a farther
5338             * independent segment. Discovery's split horizon prevents the
5339             * advertisement from being reflected back onto its ingress side.
5340             * Ordinary application data retains shortest-path suppression. */
5341            let has_distributed_variable_owners = matches.iter().any(|candidate| {
5342                st.discovery_routes
5343                    .get(&candidate.side)
5344                    .is_some_and(|route| route.reachable_network_variables.contains(&ty))
5345            });
5346            if DataType::try_named("HEARTBEAT") != Some(ty) && !has_distributed_variable_owners {
5347                Self::retain_shortest_discovery_candidates_locked(
5348                    &st,
5349                    &mut matches,
5350                    &eps,
5351                    routing_target_senders,
5352                    self.clock.now_ms(),
5353                );
5354            }
5355
5356            if !matches.is_empty() {
5357                Ok(RemoteSidePlan::Target(
5358                    self.select_discovered_candidate_sides_locked(
5359                        &mut st,
5360                        exclude,
5361                        ty,
5362                        routing_target_senders,
5363                        prefer_best_overlap,
5364                        matches,
5365                    ),
5366                ))
5367            } else if preferred_packet_id.is_some() {
5368                // Older ACKs carry only the acknowledging owner, and an
5369                // unresolved compact sender may also lack a discoverable
5370                // publisher identity. After the bounded return cache churns,
5371                // a two-sided bridge still has one unambiguous return path.
5372                // Respect route/side policy and never flood a multi-way relay.
5373                let sides = self.eligible_side_ids_locked(&st, exclude, Some(ty), false);
5374                Ok(RemoteSidePlan::Target(
5375                    if exclude.is_some() && sides.len() == 1 {
5376                        sides
5377                    } else {
5378                        Vec::new()
5379                    },
5380                ))
5381            } else if prefer_best_overlap {
5382                Ok(RemoteSidePlan::Target(Vec::new()))
5383            } else {
5384                if Self::has_explicit_route_policy_locked(&st, exclude, ty) {
5385                    let mut sides =
5386                        self.eligible_side_ids_locked(&st, exclude, Some(ty), restrict_link_local);
5387                    #[cfg(feature = "timesync")]
5388                    {
5389                        sides = Self::filter_timesync_sides_locked(
5390                            &mut st,
5391                            ty,
5392                            self.clock.now_ms(),
5393                            sides,
5394                        );
5395                    }
5396                    Ok(RemoteSidePlan::Target(self.apply_route_selection_locked(
5397                        &mut st,
5398                        exclude,
5399                        sides,
5400                        RouteSelectionOrigin::Flood,
5401                    )))
5402                } else {
5403                    Ok(RemoteSidePlan::Target(Vec::new()))
5404                }
5405            }
5406        }
5407        #[cfg(not(feature = "discovery"))]
5408        {
5409            let (_, ty) = self.item_route_info(data)?;
5410            let mut st = self.state.lock();
5411            if let Some(packet_id) = Self::reliable_control_target_packet_id(data)? {
5412                let target_side = st
5413                    .reliable_return_routes
5414                    .get(&packet_id)
5415                    .map(|route| route.side);
5416                if let Some(side) = target_side
5417                    .filter(|side| self.route_allowed_locked(&st, exclude, Some(ty), *side))
5418                {
5419                    return Ok(RemoteSidePlan::Target(vec![side]));
5420                }
5421                let sides = self.eligible_side_ids_locked(&st, exclude, Some(ty), false);
5422                return Ok(RemoteSidePlan::Target(
5423                    if exclude.is_some() && sides.len() == 1 {
5424                        sides
5425                    } else {
5426                        Vec::new()
5427                    },
5428                ));
5429            }
5430            let sides = self.eligible_side_ids_locked(&st, exclude, Some(ty), false);
5431            Ok(RemoteSidePlan::Target(self.apply_route_selection_locked(
5432                &mut st,
5433                exclude,
5434                sides,
5435                RouteSelectionOrigin::Flood,
5436            )))
5437        }
5438    }
5439
5440    #[cfg(feature = "discovery")]
5441    fn local_discovery_endpoints(&self, st: &RouterInner) -> Vec<DataEndpoint> {
5442        let mut eps: Vec<DataEndpoint> = self.cfg.handlers.iter().map(|h| h.endpoint).collect();
5443        // Managed-variable ownership has its own discovery field. Its message
5444        // endpoints describe delivery sinks, not ownership: treating them as
5445        // local endpoints makes every flight-state replica claim that it is
5446        // GroundStation and prevents ordinary telemetry from reaching the
5447        // real GroundStation route.
5448        let _ = st;
5449        #[cfg(feature = "timesync")]
5450        if self.cfg.timesync_config().is_some() {
5451            eps.push(DataEndpoint::TimeSync);
5452        }
5453        eps.retain(|ep| !discovery::is_discovery_endpoint(*ep));
5454        crate::small_sort::sort(&mut eps);
5455        eps.dedup();
5456        eps
5457    }
5458
5459    #[cfg(feature = "discovery")]
5460    fn local_discovery_timesync_sources(&self, now_ms: u64) -> Vec<String> {
5461        #[cfg(feature = "timesync")]
5462        {
5463            let st = self.timesync.lock();
5464            if let Some(tracker) = st.tracker.as_ref()
5465                && tracker.should_serve(
5466                    now_ms,
5467                    Self::timesync_has_usable_time_locked(&st, self.monotonic_now_ns()),
5468                )
5469            {
5470                return vec![self.sender_arc().to_string()];
5471            }
5472        }
5473        Vec::new()
5474    }
5475
5476    #[cfg(all(feature = "discovery", feature = "timesync"))]
5477    fn preferred_timesync_route_source(
5478        &self,
5479        data: &RouterItem,
5480        ty: DataType,
5481    ) -> TelemetryResult<Option<String>> {
5482        if !matches!(
5483            ty,
5484            DataType::TimeSyncAnnounce | DataType::TimeSyncRequest | DataType::TimeSyncResponse
5485        ) {
5486            return Ok(None);
5487        }
5488
5489        match data {
5490            RouterItem::Packet(pkt) => match ty {
5491                DataType::TimeSyncRequest => {
5492                    let local_sender = self.sender_arc();
5493                    if pkt.sender() == local_sender.as_ref() {
5494                        Ok(self.timesync.lock().tracker.as_ref().and_then(|tracker| {
5495                            tracker.current_source().map(|src| src.sender.clone())
5496                        }))
5497                    } else {
5498                        Ok(None)
5499                    }
5500                }
5501                DataType::TimeSyncAnnounce | DataType::TimeSyncResponse => {
5502                    Ok(Some(pkt.sender().to_owned()))
5503                }
5504                _ => Ok(None),
5505            },
5506            RouterItem::Packed(bytes) => {
5507                let pkt = wire_format::unpack_packet(bytes.as_ref())?;
5508                self.preferred_timesync_route_source(&RouterItem::Packet(pkt), ty)
5509            }
5510        }
5511    }
5512
5513    #[cfg(feature = "discovery")]
5514    fn note_discovery_topology_change_locked(st: &mut RouterInner, now_ms: u64) {
5515        st.discovery_cadence.on_topology_change(now_ms);
5516        // Propagate route changes immediately without restarting discovery or
5517        // resending the complete topology graph. DiscoveryAddress replaces the
5518        // prior compact route summary, so additions and removals converge while
5519        // explicit baseline requests remain available as loss recovery.
5520        for side_id in st
5521            .sides
5522            .iter()
5523            .enumerate()
5524            .filter_map(|(side_id, side)| side.as_ref().map(|_| side_id))
5525        {
5526            let throttle = st.discovery_side_throttle.entry(side_id).or_default();
5527            throttle.pending_incremental = true;
5528            throttle.incremental_retries_remaining = DISCOVERY_INCREMENTAL_RETRY_COUNT;
5529        }
5530        // Make the first incremental control frame self-describing. This is a
5531        // small header refresh, not a topology reset, and prevents a restarted
5532        // peer from missing the route delta because it lost its RX dictionary.
5533        for transport in st.side_transport.values_mut() {
5534            transport.refresh_transmit_header_templates();
5535        }
5536    }
5537
5538    #[cfg(feature = "discovery")]
5539    fn sender_topology_board_mut<'a>(
5540        sender_state: &'a mut DiscoverySenderState,
5541        sender_id: &str,
5542    ) -> &'a mut TopologyBoardNode {
5543        if let Some(idx) = sender_state
5544            .topology_boards
5545            .iter()
5546            .position(|board| board.sender_id == sender_id)
5547        {
5548            return &mut sender_state.topology_boards[idx];
5549        }
5550        sender_state.topology_boards.push(TopologyBoardNode {
5551            sender_id: sender_id.to_string(),
5552            reachable_endpoints: Vec::new(),
5553            reachable_timesync_sources: Vec::new(),
5554            connections: Vec::new(),
5555        });
5556        sender_state
5557            .topology_boards
5558            .last_mut()
5559            .expect("board inserted above")
5560    }
5561
5562    #[cfg(feature = "discovery")]
5563    fn refresh_sender_topology_state(sender_state: &mut DiscoverySenderState) {
5564        discovery::normalize_topology_boards(&mut sender_state.topology_boards);
5565        let (mut reachable, mut reachable_timesync_sources) =
5566            discovery::summarize_topology_boards(&sender_state.topology_boards);
5567        reachable.extend(sender_state.advertised_reachable.iter().copied());
5568        crate::small_sort::sort(&mut reachable);
5569        reachable.dedup();
5570        reachable_timesync_sources.extend(
5571            sender_state
5572                .advertised_reachable_timesync_sources
5573                .iter()
5574                .cloned(),
5575        );
5576        reachable_timesync_sources.sort_unstable();
5577        reachable_timesync_sources.dedup();
5578        sender_state.reachable = reachable;
5579        sender_state.reachable_timesync_sources = reachable_timesync_sources;
5580    }
5581
5582    #[cfg(feature = "discovery")]
5583    fn recompute_discovery_side_state(route: &mut DiscoverySideState) {
5584        let mut reachable = Vec::new();
5585        let mut reachable_network_variables = Vec::new();
5586        let mut reachable_timesync_sources = Vec::new();
5587        let mut last_seen_ms = 0u64;
5588        for sender in route.announcers.values() {
5589            reachable.extend(sender.reachable.iter().copied());
5590            reachable_network_variables.extend(sender.reachable_network_variables.iter().copied());
5591            reachable_timesync_sources.extend(sender.reachable_timesync_sources.iter().cloned());
5592            last_seen_ms = last_seen_ms.max(sender.last_seen_ms);
5593        }
5594        reachable_network_variables
5595            .extend(route.requested_network_variables.iter().map(|(ty, _)| *ty));
5596        last_seen_ms = last_seen_ms.max(
5597            route
5598                .requested_network_variables
5599                .iter()
5600                .map(|(_, last_seen_ms)| *last_seen_ms)
5601                .max()
5602                .unwrap_or(0),
5603        );
5604        crate::small_sort::sort(&mut reachable);
5605        reachable.dedup();
5606        crate::small_sort::sort(&mut reachable_network_variables);
5607        reachable_network_variables.dedup();
5608        reachable_timesync_sources.sort_unstable();
5609        reachable_timesync_sources.dedup();
5610        route.reachable = reachable;
5611        route.reachable_network_variables = reachable_network_variables;
5612        route.reachable_timesync_sources = reachable_timesync_sources;
5613        route.last_seen_ms = last_seen_ms;
5614    }
5615
5616    #[cfg(feature = "discovery")]
5617    fn local_discovery_topology_board(
5618        &self,
5619        st: &RouterInner,
5620        now_ms: u64,
5621        link_local_enabled: bool,
5622    ) -> TopologyBoardNode {
5623        let mut reachable_endpoints = self.local_discovery_endpoints(st);
5624        if !link_local_enabled {
5625            reachable_endpoints.retain(|ep| !ep.is_link_local_only());
5626        }
5627        let mut connections = Vec::new();
5628        for route in st.discovery_routes.values() {
5629            if now_ms.saturating_sub(route.last_seen_ms) > DISCOVERY_ROUTE_TTL_MS {
5630                continue;
5631            }
5632            for (sender, sender_state) in route.announcers.iter() {
5633                if now_ms.saturating_sub(sender_state.last_seen_ms) <= DISCOVERY_ROUTE_TTL_MS {
5634                    connections.push(sender.clone());
5635                }
5636            }
5637        }
5638        connections.sort_unstable();
5639        connections.dedup();
5640        let sender = self.sender_arc();
5641        TopologyBoardNode {
5642            sender_id: sender.to_string(),
5643            reachable_endpoints,
5644            reachable_timesync_sources: self.local_discovery_timesync_sources(now_ms),
5645            connections,
5646        }
5647    }
5648
5649    #[cfg(feature = "discovery")]
5650    fn advertised_discovery_topology_for_link_locked(
5651        &self,
5652        st: &RouterInner,
5653        now_ms: u64,
5654        link_local_enabled: bool,
5655        exclude_side: Option<RouterSideId>,
5656    ) -> Vec<TopologyBoardNode> {
5657        let mut boards = vec![self.local_discovery_topology_board(st, now_ms, link_local_enabled)];
5658        for (&route_side, route) in st.discovery_routes.iter() {
5659            if exclude_side == Some(route_side) {
5660                continue;
5661            }
5662            if now_ms.saturating_sub(route.last_seen_ms) > DISCOVERY_ROUTE_TTL_MS {
5663                continue;
5664            }
5665            for (announcer, sender_state) in route.announcers.iter() {
5666                if now_ms.saturating_sub(sender_state.last_seen_ms) > DISCOVERY_ROUTE_TTL_MS {
5667                    continue;
5668                }
5669                if sender_state.topology_boards.is_empty() {
5670                    let sender = self.sender_arc();
5671                    let fallback = TopologyBoardNode {
5672                        sender_id: announcer.clone(),
5673                        reachable_endpoints: sender_state.reachable.clone(),
5674                        reachable_timesync_sources: sender_state.reachable_timesync_sources.clone(),
5675                        connections: vec![sender.to_string()],
5676                    };
5677                    discovery::merge_topology_boards(&mut boards, core::slice::from_ref(&fallback));
5678                } else {
5679                    // Merge the retained view directly. Cloning it first and
5680                    // cloning again into the output doubled temporary strings.
5681                    discovery::merge_topology_boards(&mut boards, &sender_state.topology_boards);
5682                    if let Some(board) = boards
5683                        .iter_mut()
5684                        .find(|board| board.sender_id == *announcer)
5685                    {
5686                        let sender = self.sender_arc();
5687                        if !board.connections.iter().any(|peer| peer == sender.as_ref()) {
5688                            board.connections.push(sender.to_string());
5689                        }
5690                    }
5691                }
5692            }
5693        }
5694        if !link_local_enabled {
5695            for board in &mut boards {
5696                board
5697                    .reachable_endpoints
5698                    .retain(|ep| !ep.is_link_local_only());
5699            }
5700        }
5701        discovery::normalize_topology_boards(&mut boards);
5702        boards
5703    }
5704
5705    #[cfg(feature = "discovery")]
5706    fn prune_discovery_routes_locked(st: &mut RouterInner, now_ms: u64) -> bool {
5707        // Polling runs at the firmware service rate, not the advertisement
5708        // rate. Never clone the topology just to discover nothing expired.
5709        let mut changed = false;
5710        st.discovery_routes.retain(|_, route| {
5711            let before_peers = route.announcers.len();
5712            let before_requests = route.requested_network_variables.len();
5713            route.announcers.retain(|_, sender| {
5714                now_ms.saturating_sub(sender.last_seen_ms) <= DISCOVERY_ROUTE_TTL_MS
5715            });
5716            route
5717                .requested_network_variables
5718                .retain(|(_, last_seen_ms)| {
5719                    now_ms.saturating_sub(*last_seen_ms) <= DISCOVERY_ROUTE_TTL_MS
5720                });
5721            if before_peers != route.announcers.len()
5722                || before_requests != route.requested_network_variables.len()
5723            {
5724                Self::recompute_discovery_side_state(route);
5725                changed = true;
5726            }
5727            let keep =
5728                !route.announcers.is_empty() || !route.requested_network_variables.is_empty();
5729            changed |= !keep;
5730            keep
5731        });
5732        changed
5733    }
5734
5735    #[cfg(feature = "discovery")]
5736    fn advertised_summaries_from_topology_locked(
5737        &self,
5738        st: &RouterInner,
5739        now_ms: u64,
5740        link_local_enabled: bool,
5741        exclude_side: Option<RouterSideId>,
5742        topology: &[TopologyBoardNode],
5743    ) -> (Vec<crate::DataEndpoint>, Vec<String>) {
5744        let (mut endpoints, mut sources) = discovery::summarize_topology_boards(topology);
5745        for (&side, route) in &st.discovery_routes {
5746            if exclude_side == Some(side)
5747                || now_ms.saturating_sub(route.last_seen_ms) > DISCOVERY_ROUTE_TTL_MS
5748            {
5749                continue;
5750            }
5751            for peer in route.announcers.values() {
5752                if now_ms.saturating_sub(peer.last_seen_ms) > DISCOVERY_ROUTE_TTL_MS {
5753                    continue;
5754                }
5755                for endpoint in &peer.advertised_reachable {
5756                    if !discovery::is_discovery_endpoint(*endpoint)
5757                        && (link_local_enabled || !endpoint.is_link_local_only())
5758                        && !endpoints.contains(endpoint)
5759                    {
5760                        endpoints.push(*endpoint);
5761                    }
5762                }
5763                for source in &peer.advertised_reachable_timesync_sources {
5764                    if !sources.contains(source) {
5765                        sources.push(source.clone());
5766                    }
5767                }
5768            }
5769        }
5770        endpoints.retain(|endpoint| {
5771            !discovery::is_discovery_endpoint(*endpoint)
5772                && (link_local_enabled || !endpoint.is_link_local_only())
5773        });
5774        crate::small_sort::sort(&mut endpoints);
5775        sources.sort_unstable();
5776        (endpoints, sources)
5777    }
5778
5779    #[cfg(feature = "discovery")]
5780    fn advertised_discovery_endpoints_for_link_locked(
5781        &self,
5782        st: &RouterInner,
5783        now_ms: u64,
5784        link_local_enabled: bool,
5785        exclude_side: Option<RouterSideId>,
5786    ) -> Vec<DataEndpoint> {
5787        let (mut reachable_endpoints, _) = discovery::summarize_topology_boards(
5788            &self.advertised_discovery_topology_for_link_locked(
5789                st,
5790                now_ms,
5791                link_local_enabled,
5792                exclude_side,
5793            ),
5794        );
5795        // Compact advertisements can know routes before detailed topology arrives.
5796        // Preserve that reachability without assigning downstream endpoints to the bridge.
5797        for (&side, route) in &st.discovery_routes {
5798            if exclude_side == Some(side)
5799                || now_ms.saturating_sub(route.last_seen_ms) > DISCOVERY_ROUTE_TTL_MS
5800            {
5801                continue;
5802            }
5803            for sender in route.announcers.values() {
5804                if now_ms.saturating_sub(sender.last_seen_ms) <= DISCOVERY_ROUTE_TTL_MS {
5805                    reachable_endpoints.extend(sender.advertised_reachable.iter().copied());
5806                }
5807            }
5808        }
5809        crate::small_sort::sort(&mut reachable_endpoints);
5810        reachable_endpoints.dedup();
5811        reachable_endpoints
5812            .into_iter()
5813            .filter(|ep| {
5814                !discovery::is_discovery_endpoint(*ep)
5815                    && (link_local_enabled || !ep.is_link_local_only())
5816            })
5817            .collect()
5818    }
5819
5820    #[cfg(all(test, feature = "discovery"))]
5821    fn compact_summary_fixture(&self, endpoint: DataEndpoint) {
5822        let mut sender = DiscoverySenderState {
5823            advertised_reachable: vec![endpoint],
5824            advertised_reachable_timesync_sources: vec!["clock-behind-bridge".to_string()],
5825            last_seen_ms: self.clock.now_ms(),
5826            topology_boards: vec![TopologyBoardNode {
5827                sender_id: "bridge".to_string(),
5828                reachable_endpoints: Vec::new(),
5829                reachable_timesync_sources: Vec::new(),
5830                connections: Vec::new(),
5831            }],
5832            ..Default::default()
5833        };
5834        Self::refresh_sender_topology_state(&mut sender);
5835        let mut route = DiscoverySideState::default();
5836        route.announcers.insert("bridge".to_string(), sender);
5837        Self::recompute_discovery_side_state(&mut route);
5838        self.state.lock().discovery_routes.insert(0, route);
5839    }
5840
5841    #[cfg(feature = "discovery")]
5842    fn advertised_discovery_timesync_sources_for_link_locked(
5843        &self,
5844        st: &RouterInner,
5845        now_ms: u64,
5846        exclude_side: Option<RouterSideId>,
5847    ) -> Vec<String> {
5848        let (_, mut sources) = discovery::summarize_topology_boards(
5849            &self.advertised_discovery_topology_for_link_locked(st, now_ms, true, exclude_side),
5850        );
5851        for (&side, route) in &st.discovery_routes {
5852            if exclude_side == Some(side)
5853                || now_ms.saturating_sub(route.last_seen_ms) > DISCOVERY_ROUTE_TTL_MS
5854            {
5855                continue;
5856            }
5857            for sender in route.announcers.values() {
5858                if now_ms.saturating_sub(sender.last_seen_ms) <= DISCOVERY_ROUTE_TTL_MS {
5859                    sources.extend(sender.advertised_reachable_timesync_sources.iter().cloned());
5860                }
5861            }
5862        }
5863        sources.sort_unstable();
5864        sources.dedup();
5865        sources
5866    }
5867
5868    #[cfg(feature = "discovery")]
5869    fn advertised_network_variables_for_link_locked(
5870        &self,
5871        st: &RouterInner,
5872        now_ms: u64,
5873        exclude_side: Option<RouterSideId>,
5874    ) -> Vec<DataType> {
5875        let mut types: Vec<DataType> = st
5876            .managed_variable_types
5877            .iter()
5878            .copied()
5879            .map(DataType)
5880            .collect();
5881        for (&route_side, route) in st.discovery_routes.iter() {
5882            if exclude_side == Some(route_side) {
5883                continue;
5884            }
5885            if now_ms.saturating_sub(route.last_seen_ms) <= DISCOVERY_ROUTE_TTL_MS {
5886                types.extend(route.reachable_network_variables.iter().copied());
5887            }
5888        }
5889        crate::small_sort::sort(&mut types);
5890        types.dedup();
5891        types
5892    }
5893
5894    #[cfg(feature = "discovery")]
5895    fn preferred_discovery_master_present_locked<'a>(
5896        &self,
5897        st: &'a RouterInner,
5898        now_ms: u64,
5899    ) -> Option<&'a str> {
5900        if let Some(preferred) = st.preferred_discovery_master.as_deref()
5901            && (preferred == self.sender_arc().as_ref()
5902                || st
5903                    .discovery_routes
5904                    .values()
5905                    .filter(|route| {
5906                        now_ms.saturating_sub(route.last_seen_ms) <= DISCOVERY_ROUTE_TTL_MS
5907                    })
5908                    .any(|route| {
5909                        route
5910                            .announcers
5911                            .iter()
5912                            .filter(|(_, peer)| {
5913                                now_ms.saturating_sub(peer.last_seen_ms) <= DISCOVERY_ROUTE_TTL_MS
5914                            })
5915                            .any(|(name, peer)| {
5916                                name == preferred
5917                                    || peer.topology_boards.iter().any(|board| {
5918                                        board.sender_id == preferred
5919                                            || board
5920                                                .connections
5921                                                .iter()
5922                                                .any(|name| name == preferred)
5923                                    })
5924                            })
5925                    }))
5926        {
5927            // Presence of the preferred master needs a name scan, not a
5928            // cloned topology and a graph election on each advertisement.
5929            return Some(preferred);
5930        }
5931        None
5932    }
5933
5934    #[cfg(feature = "discovery")]
5935    fn discovery_master_sender_locked(&self, st: &RouterInner, now_ms: u64) -> String {
5936        if let Some(preferred) = self.preferred_discovery_master_present_locked(st, now_ms) {
5937            return preferred.to_string();
5938        }
5939        let boards = self.advertised_discovery_topology_for_link_locked(st, now_ms, true, None);
5940        discovery::elect_discovery_master(self.sender_arc().as_ref(), &boards)
5941    }
5942
5943    #[cfg(all(feature = "discovery", feature = "timesync"))]
5944    #[inline]
5945    fn side_is_slow_control_link_locked(
5946        st: &RouterInner,
5947        side_id: RouterSideId,
5948        now_ms: u64,
5949    ) -> bool {
5950        st.adaptive_route_stats.get(&side_id).is_some_and(|stats| {
5951            let recent_slow = stats.last_slow_observed_ms > 0
5952                && now_ms.saturating_sub(stats.last_slow_observed_ms)
5953                    <= discovery::DISCOVERY_SLOW_LINK_FULL_INTERVAL_MS;
5954            stats.sample_count > 0
5955                && ((stats.estimated_bandwidth_bps > 0
5956                    && stats.estimated_bandwidth_bps <= CONTROL_SLOW_LINK_CAPACITY_BPS)
5957                    || recent_slow)
5958        })
5959    }
5960
5961    #[cfg(feature = "discovery")]
5962    fn discovery_level_for_side_locked(
5963        st: &mut RouterInner,
5964        side_id: RouterSideId,
5965        now_ms: u64,
5966    ) -> Option<DiscoveryAdvertiseLevel> {
5967        let throttle = st.discovery_side_throttle.entry(side_id).or_default();
5968        if !throttle.has_sent_full {
5969            throttle.has_sent_full = true;
5970            throttle.pending_incremental = false;
5971            throttle.incremental_retries_remaining = 0;
5972            throttle.next_ping_ms = now_ms.saturating_add(DISCOVERY_SLOW_LINK_PING_INTERVAL_MS);
5973            return Some(DiscoveryAdvertiseLevel::Full);
5974        }
5975        if throttle.pending_incremental {
5976            throttle.incremental_retries_remaining =
5977                throttle.incremental_retries_remaining.saturating_sub(1);
5978            throttle.pending_incremental = throttle.incremental_retries_remaining > 0;
5979            return Some(DiscoveryAdvertiseLevel::Incremental);
5980        }
5981        // Stable links carry only a small liveness announcement. Route and
5982        // schema snapshots are emitted on changes or explicit recovery requests.
5983        if throttle.liveness_due(now_ms) {
5984            throttle.next_ping_ms = now_ms.saturating_add(DISCOVERY_SLOW_LINK_PING_INTERVAL_MS);
5985            return Some(DiscoveryAdvertiseLevel::MinimalPing);
5986        }
5987        None
5988    }
5989
5990    #[cfg(all(feature = "discovery", feature = "timesync"))]
5991    #[inline]
5992    fn is_timesync_type(ty: DataType) -> bool {
5993        matches!(
5994            ty,
5995            DataType::TimeSyncAnnounce | DataType::TimeSyncRequest | DataType::TimeSyncResponse
5996        )
5997    }
5998
5999    #[cfg(all(feature = "discovery", feature = "timesync"))]
6000    fn timesync_allowed_for_side_locked(
6001        st: &mut RouterInner,
6002        side_id: RouterSideId,
6003        ty: DataType,
6004        now_ms: u64,
6005    ) -> bool {
6006        if !Self::is_timesync_type(ty) {
6007            return true;
6008        }
6009        if !Self::side_is_slow_control_link_locked(st, side_id, now_ms) {
6010            st.timesync_side_throttle.remove(&side_id);
6011            return true;
6012        }
6013
6014        let throttle = st.timesync_side_throttle.entry(side_id).or_default();
6015        if now_ms >= throttle.next_allowed_ms {
6016            throttle.next_allowed_ms = now_ms.saturating_add(TIMESYNC_SLOW_LINK_MIN_INTERVAL_MS);
6017            return true;
6018        }
6019        false
6020    }
6021
6022    #[cfg(all(feature = "discovery", feature = "timesync"))]
6023    fn filter_timesync_sides_locked(
6024        st: &mut RouterInner,
6025        ty: DataType,
6026        now_ms: u64,
6027        sides: Vec<RouterSideId>,
6028    ) -> Vec<RouterSideId> {
6029        sides
6030            .into_iter()
6031            .filter(|side| Self::timesync_allowed_for_side_locked(st, *side, ty, now_ms))
6032            .collect()
6033    }
6034
6035    #[cfg(all(feature = "discovery", feature = "timesync"))]
6036    fn filter_timesync_matches_locked(
6037        st: &mut RouterInner,
6038        ty: DataType,
6039        now_ms: u64,
6040        matches: Vec<DiscoveryCandidateMatch>,
6041    ) -> Vec<DiscoveryCandidateMatch> {
6042        matches
6043            .into_iter()
6044            .filter(|m| Self::timesync_allowed_for_side_locked(st, m.side, ty, now_ms))
6045            .collect()
6046    }
6047
6048    #[cfg(feature = "discovery")]
6049    fn refresh_known_discovery_liveness(
6050        &self,
6051        pkt: &Packet,
6052        side: RouterSideId,
6053    ) -> TelemetryResult<bool> {
6054        let empty_ping =
6055            pkt.data_type() == crate::DataType::DiscoveryAnnounce && pkt.payload().is_empty();
6056        if !empty_ping && pkt.data_type() != crate::DataType::DiscoveryAddress {
6057            return Ok(false);
6058        }
6059        crate::memory_admission::check(
6060            if empty_ping {
6061                512
6062            } else {
6063                1024 + pkt.payload().len().saturating_mul(4)
6064            },
6065            pkt.payload().len().saturating_mul(2).max(128),
6066        )?;
6067        let mut address = if empty_ping {
6068            None
6069        } else {
6070            Some(discovery::decode_discovery_address(pkt)?)
6071        };
6072        let now = self.clock.now_ms();
6073        let mut st = self.state.lock();
6074        if let Some(ad) = &mut address {
6075            let link_local = st
6076                .sides
6077                .get(side)
6078                .and_then(|entry| entry.as_ref())
6079                .is_some_and(|entry| entry.opts.link_local_enabled);
6080            if !link_local {
6081                ad.reachable_endpoints.retain(|ep| !ep.is_link_local_only());
6082            }
6083            let Some(entry) = st.address_book.get(ad.hostname.as_str()) else {
6084                return Ok(false);
6085            };
6086            if entry.address != ad.address
6087                || entry.requested_address != ad.requested_address
6088                || entry.mode != Self::address_mode_from_code(ad.mode, ad.requested_address)
6089                || entry.owner_hash != ad.owner_hash
6090            {
6091                return Ok(false);
6092            }
6093            if entry.birth_ms != ad.birth_ms
6094                && st.address_book.iter().any(|(hostname, other)| {
6095                    hostname != &ad.hostname
6096                        && [ad.address, ad.requested_address].iter().any(|candidate| {
6097                            *candidate != 0
6098                                && (other.address == *candidate
6099                                    || other.requested_address == *candidate)
6100                        })
6101                })
6102            {
6103                // Birth time participates in collision arbitration. Reuse an
6104                // incarnation only when the unchanged owner has no contender;
6105                // contested addresses retain the transactional admission path.
6106                return Ok(false);
6107            }
6108        }
6109        let canonical;
6110        let sender = if let Some(ad) = &address {
6111            ad.hostname.as_str()
6112        } else {
6113            canonical = Self::canonical_sender_locked(&st, pkt.sender());
6114            canonical.as_str()
6115        };
6116        let Some(route) = st.discovery_routes.get_mut(&side) else {
6117            return Ok(false);
6118        };
6119        let Some(peer) = route.announcers.get_mut(sender) else {
6120            return Ok(false);
6121        };
6122        if let Some(ad) = &address
6123            && (peer.advertised_address != Some(ad.address)
6124                || peer.advertised_reachable != ad.reachable_endpoints
6125                || peer.advertised_reachable_timesync_sources != ad.reachable_timesync_sources
6126                || peer.reachable_network_variables != ad.reachable_network_variables
6127                || peer.link_capabilities != Some(ad.link_capabilities))
6128        {
6129            return Ok(false);
6130        }
6131        // A liveness update cannot add ownership, change identity, or enlarge
6132        // discovery state. Leave new/changed summaries behind the full guard.
6133        peer.last_seen_ms = now;
6134        route.last_seen_ms = route.last_seen_ms.max(now);
6135        if let Some(ad) = &address
6136            && let Some(entry) = st.address_book.get_mut(ad.hostname.as_str())
6137        {
6138            // A known, uncontested owner can restart without cloning its
6139            // unchanged route/schema snapshot. Record the new incarnation.
6140            entry.birth_ms = ad.birth_ms;
6141            entry.last_seen_ms = now;
6142        }
6143        Ok(true)
6144    }
6145
6146    #[cfg(feature = "discovery")]
6147    fn queue_pressure_keepalives(
6148        &self,
6149        requested_side: Option<RouterSideId>,
6150    ) -> TelemetryResult<()> {
6151        let now = self.clock.now_ms();
6152        let sender = self.sender_arc();
6153        crate::memory_admission::check(
6154            256 + sender.len().saturating_mul(4),
6155            sender.len().max(256),
6156        )?;
6157        let side_count = self.state.lock().sides.len();
6158        for side in 0..side_count {
6159            let due = {
6160                let st = self.state.lock();
6161                requested_side.is_none_or(|wanted| wanted == side)
6162                    && st.sides[side].is_some()
6163                    && self.route_allowed_locked(
6164                        &st,
6165                        None,
6166                        Some(crate::DataType::DiscoveryAnnounce),
6167                        side,
6168                    )
6169                    && st
6170                        .discovery_side_throttle
6171                        .get(&side)
6172                        .is_none_or(|throttle| now >= throttle.next_ping_ms)
6173            };
6174            if !due {
6175                continue;
6176            }
6177            // Propagate newly learned reachability even when the full topology
6178            // cannot fit. Build only a split-horizon summary, never a graph.
6179            let summary = {
6180                let st = self.state.lock();
6181                let extra = st
6182                    .discovery_routes
6183                    .iter()
6184                    .filter(|(id, _)| **id != side)
6185                    .fold(0usize, |total, (_, route)| {
6186                        total
6187                            .saturating_add(route.reachable.len().saturating_mul(32))
6188                            .saturating_add(
6189                                route.reachable_network_variables.len().saturating_mul(32),
6190                            )
6191                            .saturating_add(
6192                                route
6193                                    .reachable_timesync_sources
6194                                    .iter()
6195                                    .map(|source| source.len().saturating_add(32).saturating_mul(4))
6196                                    .sum::<usize>(),
6197                            )
6198                    });
6199                if crate::memory_admission::check(4096usize.saturating_add(extra), 2048).is_ok() {
6200                    let opts = st.sides[side].as_ref().expect("checked above").opts.clone();
6201                    let mut endpoints = self.local_discovery_endpoints(&st);
6202                    let mut variables =
6203                        self.advertised_network_variables_for_link_locked(&st, now, Some(side));
6204                    let mut sources = self.local_discovery_timesync_sources(now);
6205                    for (&route_side, route) in &st.discovery_routes {
6206                        if route_side == side {
6207                            continue;
6208                        }
6209                        for peer in route.announcers.values() {
6210                            if now.saturating_sub(peer.last_seen_ms) <= DISCOVERY_ROUTE_TTL_MS {
6211                                endpoints.extend(peer.reachable.iter().copied());
6212                                sources.extend(peer.reachable_timesync_sources.iter().cloned());
6213                            }
6214                        }
6215                    }
6216                    endpoints.retain(|ep| {
6217                        !discovery::is_discovery_endpoint(*ep)
6218                            && (opts.link_local_enabled || !ep.is_link_local_only())
6219                    });
6220                    crate::small_sort::sort(&mut endpoints);
6221                    endpoints.dedup();
6222                    crate::small_sort::sort(&mut variables);
6223                    variables.dedup();
6224                    sources.sort_unstable();
6225                    sources.dedup();
6226                    Some((endpoints, variables, sources, opts.link_capabilities()))
6227                } else {
6228                    None
6229                }
6230            };
6231            let pkt = if let Some((endpoints, variables, sources, capabilities)) = summary {
6232                let address = self.local_address_advertisement(
6233                    endpoints,
6234                    variables,
6235                    sources,
6236                    capabilities,
6237                    discovery::ADDRESS_STATE_REQUEST,
6238                );
6239                discovery::build_discovery_address(sender.as_ref(), now, &address)?
6240            } else {
6241                discovery::build_discovery_announce(sender.as_ref(), now, &[])?
6242            };
6243            self.emit_internal_tx(
6244                RouterTxItem::ToSide {
6245                    src: None,
6246                    dst: side,
6247                    data: RouterItem::Packet(pkt),
6248                },
6249                true,
6250                true,
6251            )?;
6252            self.state
6253                .lock()
6254                .discovery_side_throttle
6255                .entry(side)
6256                .or_default()
6257                .next_ping_ms = now.saturating_add(discovery::DISCOVERY_SLOW_INTERVAL_MS);
6258        }
6259        Ok(())
6260    }
6261
6262    #[cfg(feature = "discovery")]
6263    fn emit_discovery_snapshot(
6264        &self,
6265        called_from_queue: bool,
6266        include_schema: bool,
6267        include_topology: bool,
6268        skip_in_flight_sides: bool,
6269    ) -> TelemetryResult<()> {
6270        self.emit_discovery_snapshot_on_side(
6271            called_from_queue,
6272            include_schema,
6273            include_topology,
6274            skip_in_flight_sides,
6275            None,
6276        )
6277    }
6278
6279    #[cfg(feature = "discovery")]
6280    fn emit_discovery_snapshot_on_side(
6281        &self,
6282        called_from_queue: bool,
6283        include_schema: bool,
6284        include_topology: bool,
6285        skip_in_flight_sides: bool,
6286        requested_side: Option<RouterSideId>,
6287    ) -> TelemetryResult<()> {
6288        if requested_side.is_none() {
6289            let count = self.state.lock().sides.len();
6290            for side in 0..count {
6291                if self.state.lock().sides[side].is_some() {
6292                    self.emit_discovery_snapshot_on_side(
6293                        called_from_queue,
6294                        include_schema,
6295                        include_topology,
6296                        skip_in_flight_sides,
6297                        Some(side),
6298                    )?;
6299                }
6300            }
6301            return Ok(());
6302        }
6303        let (scratch, largest) = if include_schema {
6304            (8192, 2048)
6305        } else {
6306            let now = self.clock.now_ms();
6307            let sender = self.sender_arc();
6308            let st = self.state.lock();
6309            let election_required = self
6310                .preferred_discovery_master_present_locked(&st, now)
6311                .is_none();
6312            let snapshot = discovery::topology_snapshot_scratch(
6313                sender.as_ref(),
6314                self.cfg.handlers.len(),
6315                st.managed_variable_types.len().saturating_add(
6316                    st.discovery_routes
6317                        .iter()
6318                        .filter(|(side, _)| Some(**side) != requested_side)
6319                        .map(|(_, r)| r.reachable_network_variables.len())
6320                        .sum::<usize>(),
6321                ),
6322                4,
6323                true,
6324                st.discovery_routes
6325                    .iter()
6326                    .filter(|(side, route)| {
6327                        (Some(**side) != requested_side)
6328                            && now.saturating_sub(route.last_seen_ms) <= DISCOVERY_ROUTE_TTL_MS
6329                    })
6330                    .flat_map(|(_, route)| route.announcers.iter())
6331                    .filter(|(_, peer)| {
6332                        now.saturating_sub(peer.last_seen_ms) <= DISCOVERY_ROUTE_TTL_MS
6333                    })
6334                    .map(|(name, peer)| {
6335                        (
6336                            name.as_str(),
6337                            peer.reachable.as_slice(),
6338                            peer.reachable_timesync_sources.as_slice(),
6339                            peer.topology_boards.as_slice(),
6340                        )
6341                    }),
6342            );
6343            if !election_required {
6344                snapshot
6345            } else {
6346                let election = discovery::topology_snapshot_scratch(
6347                    sender.as_ref(),
6348                    self.cfg.handlers.len(),
6349                    0,
6350                    2,
6351                    false,
6352                    st.discovery_routes
6353                        .values()
6354                        .filter(|route| {
6355                            now.saturating_sub(route.last_seen_ms) <= DISCOVERY_ROUTE_TTL_MS
6356                        })
6357                        .flat_map(|route| route.announcers.iter())
6358                        .filter(|(_, peer)| {
6359                            now.saturating_sub(peer.last_seen_ms) <= DISCOVERY_ROUTE_TTL_MS
6360                        })
6361                        .map(|(name, peer)| {
6362                            (
6363                                name.as_str(),
6364                                peer.reachable.as_slice(),
6365                                peer.reachable_timesync_sources.as_slice(),
6366                                peer.topology_boards.as_slice(),
6367                            )
6368                        }),
6369                );
6370                (snapshot.0.max(election.0), snapshot.1.max(election.1))
6371            }
6372        };
6373        // Snapshot construction may clone several route/schema summaries.
6374        // Reclaim ordinary queued telemetry before refusing discovery scratch.
6375        // Priority >= 200 commands, ACKs and metadata stay protected; do not
6376        // evict routes or reliable receive/reassembly state.
6377        while let Err(err) = crate::memory_admission::check(scratch, largest) {
6378            if matches!(err, TelemetryError::Io("memory pressure"))
6379                && crate::memory_admission::check(512, 256).is_ok()
6380            {
6381                let mut st = self.state.lock();
6382                if let Some(victim) = st.lower_priority_shared_queue(199)
6383                    && st.pop_shared_queue_item(victim)
6384                {
6385                    continue;
6386                }
6387            }
6388            if !include_schema && matches!(err, TelemetryError::Io("memory pressure")) {
6389                return self.queue_pressure_keepalives(requested_side);
6390            }
6391            return Err(err);
6392        }
6393        let now_ms = self.clock.now_ms();
6394        let per_side = {
6395            let mut st = self.state.lock();
6396            if Self::prune_discovery_routes_locked(&mut st, now_ms) {
6397                self.reconcile_end_to_end_reliable_destinations_locked(&mut st)?;
6398                Self::note_discovery_topology_change_locked(&mut st, now_ms);
6399            }
6400            st.fit_discovery_budget();
6401            let local_is_master =
6402                self.discovery_master_sender_locked(&st, now_ms) == self.sender_arc().as_ref();
6403            let mut per_side = Vec::new();
6404            for side_id in 0..st.sides.len() {
6405                let Some(side) = st.sides[side_id].as_ref() else {
6406                    continue;
6407                };
6408                let opts = side.opts.clone();
6409                let link_local_enabled = opts.link_local_enabled;
6410                if requested_side.is_some_and(|requested| requested != side_id) {
6411                    continue;
6412                }
6413                let discovery_in_flight = skip_in_flight_sides
6414                    && st.reliable_tx.iter().any(|((pending_side, ty), tx_state)| {
6415                        *pending_side == side_id
6416                            && !tx_state.sent.is_empty()
6417                            && DataType::try_from_u32(*ty).is_some_and(discovery::is_discovery_type)
6418                    });
6419                if !self.route_allowed_locked(&st, None, Some(DataType::DiscoveryAnnounce), side_id)
6420                {
6421                    continue;
6422                }
6423                // A lost hop ACK must not make a live bridge disappear from
6424                // its neighbors. Keep the baseline pending, but permit a
6425                // bounded, unreliable keepalive independently of its ACK.
6426                let level = if discovery_in_flight {
6427                    let throttle = st.discovery_side_throttle.entry(side_id).or_default();
6428                    if !throttle.liveness_due(now_ms) {
6429                        None
6430                    } else {
6431                        throttle.next_ping_ms =
6432                            now_ms.saturating_add(DISCOVERY_SLOW_LINK_PING_INTERVAL_MS);
6433                        Some(DiscoveryAdvertiseLevel::MinimalPing)
6434                    }
6435                } else {
6436                    Self::discovery_level_for_side_locked(&mut st, side_id, now_ms)
6437                };
6438                let Some(level) = level else {
6439                    continue;
6440                };
6441                let include_side_topology = include_topology
6442                    && self.route_allowed_locked(
6443                        &st,
6444                        None,
6445                        Some(DataType::DiscoveryTopology),
6446                        side_id,
6447                    );
6448                let capabilities = opts.link_capabilities();
6449                if level == DiscoveryAdvertiseLevel::MinimalPing {
6450                    per_side.push((
6451                        side_id,
6452                        level,
6453                        Vec::new(),
6454                        Vec::new(),
6455                        Vec::new(),
6456                        Vec::new(),
6457                        Vec::new(),
6458                        capabilities,
6459                        local_is_master,
6460                    ));
6461                    continue;
6462                }
6463                let current_topology = if include_side_topology {
6464                    self.advertised_discovery_topology_for_link_locked(
6465                        &st,
6466                        now_ms,
6467                        link_local_enabled,
6468                        Some(side_id),
6469                    )
6470                } else {
6471                    Vec::new()
6472                };
6473                let (endpoints, timesync_sources) = if include_side_topology {
6474                    self.advertised_summaries_from_topology_locked(
6475                        &st,
6476                        now_ms,
6477                        link_local_enabled,
6478                        Some(side_id),
6479                        &current_topology,
6480                    )
6481                } else {
6482                    (
6483                        self.advertised_discovery_endpoints_for_link_locked(
6484                            &st,
6485                            now_ms,
6486                            link_local_enabled,
6487                            Some(side_id),
6488                        ),
6489                        self.advertised_discovery_timesync_sources_for_link_locked(
6490                            &st,
6491                            now_ms,
6492                            Some(side_id),
6493                        ),
6494                    )
6495                };
6496                let (topology, removed_topology) = match level {
6497                    DiscoveryAdvertiseLevel::Full => (current_topology.clone(), Vec::new()),
6498                    DiscoveryAdvertiseLevel::Incremental => {
6499                        let previous = &st
6500                            .discovery_side_throttle
6501                            .get(&side_id)
6502                            .expect("discovery throttle exists")
6503                            .last_topology;
6504                        discovery::topology_delta(previous, &current_topology)
6505                    }
6506                    DiscoveryAdvertiseLevel::MinimalPing => (Vec::new(), Vec::new()),
6507                };
6508                let throttle = st
6509                    .discovery_side_throttle
6510                    .get_mut(&side_id)
6511                    .expect("discovery throttle exists");
6512                if level == DiscoveryAdvertiseLevel::Full
6513                    || (level == DiscoveryAdvertiseLevel::Incremental
6514                        && throttle.incremental_retries_remaining == 0)
6515                {
6516                    throttle.last_topology = current_topology;
6517                }
6518                per_side.push((
6519                    side_id,
6520                    level,
6521                    endpoints,
6522                    self.advertised_network_variables_for_link_locked(&st, now_ms, Some(side_id)),
6523                    timesync_sources,
6524                    topology,
6525                    removed_topology,
6526                    capabilities,
6527                    local_is_master,
6528                ));
6529            }
6530            per_side
6531        };
6532        for (
6533            side_id,
6534            level,
6535            endpoints,
6536            network_variables,
6537            timesync_sources,
6538            topology,
6539            removed_topology,
6540            capabilities,
6541            local_is_master,
6542        ) in per_side
6543        {
6544            let sender = self.sender_arc();
6545            if matches!(
6546                level,
6547                DiscoveryAdvertiseLevel::Full | DiscoveryAdvertiseLevel::Incremental
6548            ) {
6549                let address = self.local_address_advertisement(
6550                    endpoints.clone(),
6551                    network_variables.clone(),
6552                    timesync_sources.clone(),
6553                    capabilities,
6554                    if local_is_master {
6555                        discovery::ADDRESS_STATE_APPROVED
6556                    } else {
6557                        discovery::ADDRESS_STATE_REQUEST
6558                    },
6559                );
6560                let pkt = discovery::build_discovery_address(sender.as_ref(), now_ms, &address)?;
6561                self.emit_internal_tx(
6562                    RouterTxItem::ToSide {
6563                        src: None,
6564                        dst: side_id,
6565                        data: RouterItem::Packet(pkt),
6566                    },
6567                    true,
6568                    called_from_queue,
6569                )?;
6570            }
6571            if level == DiscoveryAdvertiseLevel::MinimalPing {
6572                let pkt = discovery::build_discovery_announce(
6573                    sender.as_ref(),
6574                    now_ms,
6575                    endpoints.as_slice(),
6576                )?;
6577                self.emit_internal_tx(
6578                    RouterTxItem::ToSide {
6579                        src: None,
6580                        dst: side_id,
6581                        data: RouterItem::Packet(pkt),
6582                    },
6583                    true,
6584                    called_from_queue,
6585                )?;
6586            }
6587            if include_topology
6588                && (!topology.is_empty() || !removed_topology.is_empty())
6589                && level != DiscoveryAdvertiseLevel::MinimalPing
6590            {
6591                let pkt = if level == DiscoveryAdvertiseLevel::Full {
6592                    discovery::build_discovery_topology(sender.as_ref(), now_ms, &topology)?
6593                } else {
6594                    discovery::build_discovery_topology_delta(
6595                        sender.as_ref(),
6596                        now_ms,
6597                        &topology,
6598                        &removed_topology,
6599                    )?
6600                };
6601                self.emit_internal_tx(
6602                    RouterTxItem::ToSide {
6603                        src: None,
6604                        dst: side_id,
6605                        data: RouterItem::Packet(pkt),
6606                    },
6607                    true,
6608                    called_from_queue,
6609                )?;
6610            }
6611            // Release the temporary topology snapshot before constructing and
6612            // packing the larger schema. These buffers are no longer needed.
6613            drop((
6614                endpoints,
6615                network_variables,
6616                timesync_sources,
6617                topology,
6618                removed_topology,
6619            ));
6620            // Schema snapshots can be substantially larger than address and
6621            // topology advertisements. Send them after the route has been
6622            // established so a slow link can discover the network before it
6623            // begins transferring the merged metadata catalog.
6624            if include_schema && level == DiscoveryAdvertiseLevel::Full {
6625                let pkt = discovery::build_discovery_schema(sender.as_ref(), now_ms)?;
6626                self.emit_internal_tx(
6627                    RouterTxItem::ToSide {
6628                        src: None,
6629                        dst: side_id,
6630                        data: RouterItem::Packet(pkt),
6631                    },
6632                    true,
6633                    called_from_queue,
6634                )?;
6635            }
6636        }
6637        Ok(())
6638    }
6639
6640    #[cfg(feature = "discovery")]
6641    fn queue_discovery_announce(
6642        &self,
6643        include_schema: bool,
6644        skip_in_flight_sides: bool,
6645    ) -> TelemetryResult<()> {
6646        let now_ms = self.clock.now_ms();
6647        {
6648            let mut st = self.state.lock();
6649            if Self::prune_discovery_routes_locked(&mut st, now_ms) {
6650                self.reconcile_end_to_end_reliable_destinations_locked(&mut st)?;
6651                Self::note_discovery_topology_change_locked(&mut st, now_ms);
6652            }
6653            st.fit_discovery_budget();
6654            if st.sides.iter().all(|side| side.is_none()) {
6655                return Ok(());
6656            }
6657            // An explicit announce is the bootstrap/recovery operation. Keep
6658            // it full; cadence-triggered topology changes use Incremental.
6659            if include_schema {
6660                for throttle in st.discovery_side_throttle.values_mut() {
6661                    throttle.has_sent_full = false;
6662                }
6663            }
6664            st.discovery_cadence.on_announce_sent(now_ms);
6665        }
6666        self.emit_discovery_snapshot(true, include_schema, true, skip_in_flight_sides)
6667    }
6668
6669    #[cfg(feature = "discovery")]
6670    fn note_schema_received(&self, side: RouterSideId, sender: &str) {
6671        let mut st = self.state.lock();
6672        // Schema snapshots can be forwarded. They must never introduce a new
6673        // link-local announcer or change shared-bus reliability semantics.
6674        // If schema precedes topology, bounded recovery will request it again.
6675        let address = sender
6676            .strip_prefix("@addr:")
6677            .and_then(|value| value.parse::<u32>().ok());
6678        if let Some(route) = st.discovery_routes.get_mut(&side) {
6679            for (name, peer) in &mut route.announcers {
6680                if name == sender
6681                    || address.is_some_and(|address| {
6682                        peer.advertised_address == Some(address)
6683                            || sender_address_u32(name) == address
6684                    })
6685                {
6686                    peer.has_schema = true;
6687                    break;
6688                }
6689            }
6690        }
6691    }
6692
6693    #[cfg(feature = "discovery")]
6694    fn poll_discovery_announce(&self) -> TelemetryResult<bool> {
6695        let now_ms = self.clock.now_ms();
6696        // An address/keepalive proves liveness, not receipt of the detailed
6697        // baseline. Recover a lost bootstrap without waiting 120 seconds or
6698        // discarding existing routes. Coalesce all missing peers on one side.
6699        let recovery_sides = {
6700            let mut st = self.state.lock();
6701            let mut due = Vec::new();
6702            for side in 0..st.sides.len() {
6703                let missing = st.discovery_routes.get(&side).is_some_and(|route| {
6704                    route.announcers.iter().any(|(name, peer)| {
6705                        !peer.has_full_topology
6706                            && now_ms.saturating_sub(peer.last_seen_ms) < DISCOVERY_ROUTE_TTL_MS
6707                            && !route.announcers.values().any(|bridge| {
6708                                bridge.has_full_topology
6709                                    && now_ms.saturating_sub(bridge.last_seen_ms)
6710                                        < DISCOVERY_ROUTE_TTL_MS
6711                                    && bridge
6712                                        .topology_boards
6713                                        .iter()
6714                                        .any(|board| board.sender_id == *name)
6715                            })
6716                    })
6717                });
6718                if !missing {
6719                    continue;
6720                }
6721                let throttle = st.discovery_side_throttle.entry(side).or_default();
6722                if throttle.next_topology_request_ms == 0 {
6723                    throttle.next_topology_request_ms =
6724                        now_ms.saturating_add(discovery::DISCOVERY_BASELINE_GRACE_MS);
6725                } else if now_ms >= throttle.next_topology_request_ms {
6726                    throttle.next_topology_request_ms =
6727                        now_ms.saturating_add(discovery::DISCOVERY_BASELINE_RETRY_MS);
6728                    due.push(side);
6729                }
6730            }
6731            due
6732        };
6733        let recovering = !recovery_sides.is_empty();
6734        for side in recovery_sides {
6735            let pkt =
6736                discovery::build_discovery_topology_request(self.sender_arc().as_ref(), now_ms)?;
6737            self.emit_internal_tx(
6738                RouterTxItem::ToSide {
6739                    src: None,
6740                    dst: side,
6741                    data: RouterItem::Packet(pkt),
6742                },
6743                true,
6744                true,
6745            )?;
6746        }
6747        let schema_recovery_sides = {
6748            let mut st = self.state.lock();
6749            let mut due = Vec::new();
6750            for side in 0..st.sides.len() {
6751                let missing = st.discovery_routes.get(&side).is_some_and(|route| {
6752                    route.announcers.values().any(|peer| {
6753                        peer.has_full_topology
6754                            && !peer.has_schema
6755                            && now_ms.saturating_sub(peer.last_seen_ms) < DISCOVERY_ROUTE_TTL_MS
6756                    })
6757                });
6758                if !missing {
6759                    continue;
6760                }
6761                let throttle = st.discovery_side_throttle.entry(side).or_default();
6762                if throttle.next_schema_request_ms == 0 {
6763                    throttle.next_schema_request_ms =
6764                        now_ms.saturating_add(discovery::DISCOVERY_BASELINE_GRACE_MS);
6765                } else if now_ms >= throttle.next_schema_request_ms {
6766                    throttle.next_schema_request_ms =
6767                        now_ms.saturating_add(discovery::DISCOVERY_BASELINE_RETRY_MS);
6768                    due.push(side);
6769                }
6770            }
6771            due
6772        };
6773        let recovering = recovering || !schema_recovery_sides.is_empty();
6774        for side in schema_recovery_sides {
6775            let pkt =
6776                discovery::build_discovery_schema_request(self.sender_arc().as_ref(), now_ms)?;
6777            self.emit_internal_tx(
6778                RouterTxItem::ToSide {
6779                    src: None,
6780                    dst: side,
6781                    data: RouterItem::Packet(pkt),
6782                },
6783                true,
6784                true,
6785            )?;
6786        }
6787        let due = {
6788            let mut st = self.state.lock();
6789            let removed = Self::prune_discovery_routes_locked(&mut st, now_ms);
6790            if removed {
6791                self.reconcile_end_to_end_reliable_destinations_locked(&mut st)?;
6792                Self::note_discovery_topology_change_locked(&mut st, now_ms);
6793            }
6794            st.fit_discovery_budget();
6795            let has_any = st.sides.iter().enumerate().any(|(side_id, side)| {
6796                let Some(side) = side.as_ref() else {
6797                    return false;
6798                };
6799                if !self.route_allowed_locked(&st, None, Some(DataType::DiscoveryAnnounce), side_id)
6800                {
6801                    return false;
6802                }
6803                let _ = side;
6804                let in_flight = st.reliable_tx.iter().any(|((pending_side, ty), tx_state)| {
6805                    *pending_side == side_id
6806                        && !tx_state.sent.is_empty()
6807                        && DataType::try_from_u32(*ty).is_some_and(discovery::is_discovery_type)
6808                });
6809                st.discovery_side_throttle
6810                    .get(&side_id)
6811                    .is_none_or(|throttle| {
6812                        (!in_flight && (!throttle.has_sent_full || throttle.pending_incremental))
6813                            || throttle.liveness_due(now_ms)
6814                    })
6815            });
6816            if st.sides.is_empty() || !has_any {
6817                return Ok(recovering);
6818            }
6819            st.discovery_cadence.due(now_ms)
6820        };
6821        if !due {
6822            return Ok(recovering);
6823        }
6824        // Runtime liveness/topology refreshes must stay small. A full schema
6825        // is sent by the initial explicit announce and in response to
6826        // DiscoverySchemaRequest; repeating it at every cadence can starve
6827        // commands on constrained serial and radio links.
6828        // A stalled reliable link must not prevent freshly learned routes
6829        // from propagating across another healthy side. Only omit the side
6830        // that already has discovery awaiting its hop ACK, except for bounded
6831        // liveness pings that must not depend on that ACK arriving.
6832        self.queue_discovery_announce(false, true)?;
6833        Ok(true)
6834    }
6835
6836    #[cfg(feature = "discovery")]
6837    fn learn_discovery_packet(
6838        &self,
6839        pkt: &Packet,
6840        src: Option<RouterSideId>,
6841        called_from_queue: bool,
6842    ) -> TelemetryResult<bool> {
6843        if !discovery::is_discovery_type(pkt.data_type()) {
6844            return Ok(false);
6845        }
6846        let Some(side) = src else {
6847            return Ok(true);
6848        };
6849        if self.refresh_known_discovery_liveness(pkt, side)? {
6850            return Ok(true);
6851        }
6852        #[cfg(not(feature = "std"))]
6853        if pkt.data_type() == DataType::DiscoverySchema
6854            && discovery::discovery_schema_payload_is_fully_known(pkt.payload())?
6855        {
6856            self.note_schema_received(side, pkt.sender());
6857            return Ok(true);
6858        }
6859        // A compact address summary does not clone the complete topology or
6860        // schema. Account for its ingress-side aggregates instead; charging
6861        // every new peer for a full snapshot can prevent restart recovery.
6862        if matches!(
6863            pkt.data_type(),
6864            DataType::DiscoveryTopologyRequest | DataType::DiscoverySchemaRequest
6865        ) {
6866            crate::memory_admission::check(1024, 256)?;
6867            // A requesting peer may have restarted with an empty RX dictionary.
6868            // Its identity and routes need not change. Refresh only this link
6869            // so the next data/state report is immediately self-describing;
6870            // retain RX dictionaries and all learned topology.
6871            if let Some(transport) = self.state.lock().side_transport.get_mut(&side) {
6872                transport.refresh_transmit_header_templates();
6873            }
6874        }
6875        if matches!(
6876            pkt.data_type(),
6877            DataType::DiscoveryTopologyRequest | DataType::DiscoverySchemaRequest
6878        ) {
6879            let now_ms = self.clock.now_ms();
6880            {
6881                let mut st = self.state.lock();
6882                if Self::prune_discovery_routes_locked(&mut st, now_ms) {
6883                    self.reconcile_end_to_end_reliable_destinations_locked(&mut st)?;
6884                    Self::note_discovery_topology_change_locked(&mut st, now_ms);
6885                }
6886                st.discovery_side_throttle
6887                    .entry(side)
6888                    .or_default()
6889                    .has_sent_full = false;
6890            }
6891            // Do not depend on the elected master answering a forwarded request.
6892            // A recovering neighbor must get our retained downstream baseline
6893            // even before its identity is known. Reply only on its ingress link;
6894            // do not reset other links or broadcast application traffic.
6895            self.emit_discovery_snapshot_on_side(
6896                called_from_queue,
6897                pkt.data_type() == DataType::DiscoverySchemaRequest,
6898                true,
6899                false,
6900                Some(side),
6901            )?;
6902            return Ok(true);
6903        }
6904        let retained_summary = if matches!(
6905            pkt.data_type(),
6906            DataType::DiscoveryAddress
6907                | DataType::DiscoveryTopology
6908                | DataType::DiscoveryAnnounce
6909                | DataType::DiscoveryTimeSyncSources
6910                | DataType::ManagedVariableRequest
6911        ) {
6912            self.state
6913                .lock()
6914                .discovery_routes
6915                .get(&side)
6916                .map_or(0, |route| {
6917                    RouterInner::discovery_route_byte_cost(side, route)
6918                })
6919        } else {
6920            0
6921        };
6922        let (decode_bytes, largest_block) = if pkt.data_type() == DataType::ManagedVariableRequest {
6923            let ty = discovery::decode_managed_variable_request(pkt)?;
6924            let existing = self
6925                .state
6926                .lock()
6927                .discovery_routes
6928                .get(&side)
6929                .is_some_and(|route| {
6930                    route
6931                        .requested_network_variables
6932                        .iter()
6933                        .any(|(id, _)| *id == ty)
6934                });
6935            if existing {
6936                (1024, 256)
6937            } else {
6938                // New subscriptions clone only this ingress summary; refreshing
6939                // an existing lease constructs no graph or schema definitions.
6940                (
6941                    2048usize.saturating_add(retained_summary.saturating_mul(2)),
6942                    retained_summary
6943                        .checked_next_power_of_two()
6944                        .unwrap_or(usize::MAX)
6945                        .max(1024),
6946                )
6947            }
6948        } else {
6949            let scratch = if pkt.data_type() == DataType::DiscoveryAddress {
6950                4096usize.saturating_add(retained_summary)
6951            } else {
6952                8192usize.saturating_add(retained_summary)
6953            };
6954            let decode_bytes = pkt
6955                .payload()
6956                .len()
6957                .saturating_mul(4)
6958                .saturating_add(scratch);
6959            let largest_block = pkt.payload().len().saturating_mul(2).max(2048);
6960            (decode_bytes, largest_block)
6961        };
6962        while let Err(err) = crate::memory_admission::check(decode_bytes, largest_block) {
6963            if !matches!(err, TelemetryError::Io("memory pressure")) {
6964                return Err(err);
6965            }
6966            let mut st = self.state.lock();
6967            let Some(victim) = st.lower_priority_shared_queue(255) else {
6968                return Err(err);
6969            };
6970            if !st.pop_shared_queue_item(victim) {
6971                return Err(err);
6972            }
6973        }
6974        if pkt.data_type() == DataType::DiscoveryAddress {
6975            let mut ad = discovery::decode_discovery_address(pkt)?;
6976            let identity = ad.clone();
6977            let mut changed = false;
6978            /* Packed no_std frames represent their source as @addr:N until
6979             * discovery resolves the address. DiscoveryAddress carries the
6980             * authoritative hostname in its payload, so key reachability by
6981             * that name immediately instead of permanently treating the
6982             * placeholder as a distinct router. */
6983            let sender_id = if ad.hostname.is_empty() {
6984                pkt.sender()
6985            } else {
6986                ad.hostname.as_str()
6987            };
6988            let now_ms = self.clock.now_ms();
6989            let mut st = self.state.lock();
6990            let side_link_local_enabled = st
6991                .sides
6992                .get(side)
6993                .and_then(|entry| entry.as_ref())
6994                .map(|side_ref| side_ref.opts.link_local_enabled)
6995                .unwrap_or(false);
6996            if !side_link_local_enabled {
6997                ad.reachable_endpoints.retain(|ep| !ep.is_link_local_only());
6998            }
6999            let mut route = st.discovery_routes.get(&side).cloned().unwrap_or_default();
7000            let previously_reachable_network_variables = route.reachable_network_variables.clone();
7001            if pkt.sender() != sender_id {
7002                route.announcers.remove(pkt.sender());
7003            }
7004            let mut sender_state =
7005                core::mem::take(route.announcers.entry(sender_id.to_string()).or_default());
7006            // DiscoveryAddress is a compact route summary. A multi-sided
7007            // router advertises endpoints reachable *through* it, so these
7008            // entries must not be attached to the announcer's topology node:
7009            // that would falsely claim ownership and freeze reliable packets
7010            // to the bridge before detailed topology arrives.
7011            if sender_state.advertised_address != Some(ad.address) {
7012                sender_state.advertised_address = Some(ad.address);
7013                changed = true;
7014            }
7015            if sender_state.advertised_reachable != ad.reachable_endpoints {
7016                sender_state.advertised_reachable = ad.reachable_endpoints;
7017                changed = true;
7018            }
7019            if sender_state.advertised_reachable_timesync_sources != ad.reachable_timesync_sources {
7020                sender_state.advertised_reachable_timesync_sources = ad.reachable_timesync_sources;
7021                changed = true;
7022            }
7023            if sender_state.reachable_network_variables != ad.reachable_network_variables {
7024                sender_state.reachable_network_variables = ad.reachable_network_variables;
7025                changed = true;
7026            }
7027            if sender_state.link_capabilities != Some(ad.link_capabilities) {
7028                sender_state.link_capabilities = Some(ad.link_capabilities);
7029                changed = true;
7030            }
7031            Self::refresh_sender_topology_state(&mut sender_state);
7032            sender_state.last_seen_ms = now_ms;
7033            route.announcers.insert(sender_id.to_string(), sender_state);
7034            Self::recompute_discovery_side_state(&mut route);
7035            let newly_reachable_network_variables: Vec<DataType> = route
7036                .reachable_network_variables
7037                .iter()
7038                .copied()
7039                .filter(|ty| !previously_reachable_network_variables.contains(ty))
7040                .collect();
7041            st.commit_discovery_route(side, route)?;
7042            self.reconcile_end_to_end_reliable_destinations_locked(&mut st)?;
7043            if changed {
7044                Self::note_discovery_topology_change_locked(&mut st, self.clock.now_ms());
7045            }
7046            // A writer may have changed a managed variable while no remote
7047            // owner route was present. Local-only suppression correctly kept
7048            // that update off unrelated links, but the new owner still needs
7049            // the latest value. Replay only newly reachable variable types to
7050            // this advertising side; do not fan out or restart discovery.
7051            let replay: Vec<Packet> = newly_reachable_network_variables
7052                .iter()
7053                .filter(|ty| Self::managed_variable_permissions_locked(&st, **ty).write)
7054                .filter_map(|ty| {
7055                    st.managed_variable_latest
7056                        .get(&ty.as_u32())
7057                        .map(|entry| entry.packet.clone())
7058                })
7059                .collect();
7060            drop(st);
7061            self.ingest_address_advertisement(identity)?;
7062            for value in replay {
7063                self.emit_internal_tx_with_priority(
7064                    RouterTxItem::ToSide {
7065                        src: None,
7066                        dst: side,
7067                        data: RouterItem::Packet(value),
7068                    },
7069                    true,
7070                    crate::transport_priority(DataType::ManagedVariableValue),
7071                    called_from_queue,
7072                )?;
7073            }
7074            return Ok(true);
7075        }
7076        let mut decoded_topology = if pkt.data_type() == DataType::DiscoveryTopology {
7077            Some(discovery::decode_discovery_topology_update(pkt)?)
7078        } else {
7079            None
7080        };
7081        let packet_sender = {
7082            let st = self.state.lock();
7083            let canonical = Self::canonical_sender_locked(&st, pkt.sender());
7084            if canonical != pkt.sender() {
7085                canonical
7086            } else if let Some(address) = pkt
7087                .sender()
7088                .strip_prefix("@addr:")
7089                .and_then(|value| value.parse::<u32>().ok())
7090            {
7091                // A compact topology frame may arrive before its reliable
7092                // DiscoveryAddress frame. Topology still carries stable board
7093                // names, so use the entry whose wire hash matches the compact
7094                // source address. This keeps autonomous routers keyed by name
7095                // even when the header omits that hostname.
7096                decoded_topology
7097                    .as_ref()
7098                    .and_then(|update| {
7099                        update
7100                            .boards
7101                            .iter()
7102                            .find(|board| sender_address_u32(&board.sender_id) == address)
7103                    })
7104                    .map(|board| board.sender_id.clone())
7105                    .unwrap_or(canonical)
7106            } else {
7107                canonical
7108            }
7109        };
7110        let local_sender = self.sender_arc();
7111        if packet_sender == local_sender.as_ref() {
7112            return Ok(true);
7113        }
7114
7115        if pkt.data_type() == DataType::ManagedVariableRequest {
7116            let ty = discovery::decode_managed_variable_request(pkt)?;
7117            // A refresh request is direct evidence that this ingress segment
7118            // contains a subscriber for the variable. Track one bounded entry
7119            // per type and side rather than manufacturing a full discovery
7120            // announcer for every requester. This keeps later writes selective
7121            // without growing the topology graph or fragmenting embedded pools.
7122            {
7123                let now_ms = self.clock.now_ms();
7124                let mut st = self.state.lock();
7125                // Refreshing an existing subscription changes only its lease.
7126                // Do not clone the complete learned topology or rebuild route
7127                // summaries on every retry from a slow/disconnected subscriber.
7128                let existing = st.discovery_routes.get_mut(&side).and_then(|route| {
7129                    let (_, last_seen_ms) = route
7130                        .requested_network_variables
7131                        .iter_mut()
7132                        .find(|(requested, _)| *requested == ty)?;
7133                    *last_seen_ms = now_ms;
7134                    route.last_seen_ms = route.last_seen_ms.max(now_ms);
7135                    Some(())
7136                });
7137                let newly_reachable = existing.is_none();
7138                if newly_reachable {
7139                    let mut route = st.discovery_routes.get(&side).cloned().unwrap_or_default();
7140                    route.requested_network_variables.push((ty, now_ms));
7141                    Self::recompute_discovery_side_state(&mut route);
7142                    st.commit_discovery_route(side, route)?;
7143                }
7144                if newly_reachable {
7145                    self.reconcile_end_to_end_reliable_destinations_locked(&mut st)?;
7146                    Self::note_discovery_topology_change_locked(&mut st, now_ms);
7147                }
7148            }
7149            if !self.can_write_managed_variable(ty) {
7150                // Read-only caches are replicas, not authoritative owners.
7151                // Leave the request unhandled so normal discovery routing can
7152                // carry it toward a writer on another segment even when this
7153                // subscriber restored an older persisted value at boot.
7154                return Ok(false);
7155            }
7156            if let Some(value) = self.managed_variable_latest(ty) {
7157                self.emit_internal_tx(
7158                    RouterTxItem::ToSide {
7159                        src: None,
7160                        dst: side,
7161                        data: RouterItem::Packet(value),
7162                    },
7163                    true,
7164                    called_from_queue,
7165                )?;
7166                return Ok(true);
7167            }
7168            // A writer may not have produced its first value yet. Keep the
7169            // request moving in case another authoritative writer has one.
7170            return Ok(false);
7171        }
7172        if pkt.data_type() == DataType::DiscoverySchema {
7173            let snapshot = discovery::decode_discovery_schema(pkt)?;
7174            // The decoded snapshot contains the sender's complete schema, but
7175            // embedded targets retain only definitions that are not already
7176            // compiled into their local schema. Charging the complete wire
7177            // snapshot here made heterogeneous boards reject small schema
7178            // deltas whenever the sender's full snapshot exceeded the shared
7179            // queue budget. The merge performs an atomic projected-retained-
7180            // bytes check; afterwards, reclaim queue/topology space using the
7181            // actual retained schema size included by shared_queue_bytes_used.
7182            let mut st = self.state.lock();
7183            let budget = st.schema_growth_budget();
7184            let report = crate::config::merge_owned_schema_snapshot_with_budget(snapshot, budget)?;
7185            if report.changed() {
7186                st.fit_discovery_budget();
7187            }
7188            drop(st);
7189            self.note_schema_received(side, &packet_sender);
7190            return Ok(true);
7191        }
7192        if pkt.data_type() == DataType::DiscoveryLinkCapabilities {
7193            let _ = discovery::decode_discovery_link_capabilities(pkt)?;
7194            return Ok(true);
7195        }
7196        let mut announce = if pkt.data_type() == DataType::DiscoveryAnnounce {
7197            Some(discovery::decode_discovery_announce(pkt)?)
7198        } else {
7199            None
7200        };
7201        let mut time_sources = if pkt.data_type() == DataType::DiscoveryTimeSyncSources {
7202            Some(discovery::decode_discovery_timesync_sources(pkt)?)
7203        } else {
7204            None
7205        };
7206        let mut st = self.state.lock();
7207        let now_ms = self.clock.now_ms();
7208        if pkt.data_type() == DataType::DiscoveryLeave {
7209            let leaving = packet_sender.as_str();
7210            let before = st.discovery_routes.clone();
7211            for route in st.discovery_routes.values_mut() {
7212                route.announcers.remove(leaving);
7213                for sender_state in route.announcers.values_mut() {
7214                    sender_state
7215                        .topology_boards
7216                        .retain(|board| board.sender_id != leaving);
7217                    for board in sender_state.topology_boards.iter_mut() {
7218                        board.connections.retain(|peer| peer != leaving);
7219                    }
7220                    Self::refresh_sender_topology_state(sender_state);
7221                }
7222                Self::recompute_discovery_side_state(route);
7223            }
7224            st.discovery_routes
7225                .retain(|_, route| !route.announcers.is_empty());
7226            if st.discovery_routes != before {
7227                Self::note_discovery_topology_change_locked(&mut st, now_ms);
7228                self.reconcile_end_to_end_reliable_destinations_locked(&mut st)?;
7229            }
7230            return Ok(true);
7231        }
7232        // Resolve aliases while every retained route is still visible.
7233        if let Some(update) = decoded_topology.as_mut() {
7234            for board in update.boards.iter_mut() {
7235                board.sender_id = Self::canonical_sender_locked(&st, &board.sender_id);
7236                for peer in board.connections.iter_mut() {
7237                    *peer = Self::canonical_sender_locked(&st, peer);
7238                }
7239            }
7240        }
7241        let mut route = st.discovery_routes.get(&side).cloned().unwrap_or_default();
7242        let alias_state = if packet_sender != pkt.sender() {
7243            route.announcers.remove(pkt.sender())
7244        } else {
7245            None
7246        };
7247        let side_link_local_enabled = st
7248            .sides
7249            .get(side)
7250            .and_then(|entry| entry.as_ref())
7251            .map(|side_ref| side_ref.opts.link_local_enabled)
7252            .unwrap_or(false);
7253        let mut sender_state = route
7254            .announcers
7255            .get_mut(&packet_sender)
7256            .map(core::mem::take)
7257            .or(alias_state)
7258            .unwrap_or_default();
7259        let changed = match pkt.data_type() {
7260            DataType::DiscoveryAnnounce => {
7261                let mut reachable = announce.take().expect("decoded above");
7262                // Empty announcements are slow-link keepalives, not endpoint
7263                // withdrawals. Preserve ownership until an explicit topology
7264                // update, leave, or expiry removes it. The common receive tail
7265                // still refreshes last_seen_ms without scheduling rediscovery.
7266                if reachable.is_empty() {
7267                    false
7268                } else {
7269                    if !side_link_local_enabled {
7270                        reachable.retain(|ep| !ep.is_link_local_only());
7271                    }
7272                    let board = Self::sender_topology_board_mut(&mut sender_state, &packet_sender);
7273                    let changed = board.reachable_endpoints != reachable;
7274                    board.reachable_endpoints = reachable;
7275                    Self::refresh_sender_topology_state(&mut sender_state);
7276                    changed
7277                }
7278            }
7279            DataType::DiscoveryTimeSyncSources => {
7280                let sources = time_sources.take().expect("decoded above");
7281                let board = Self::sender_topology_board_mut(&mut sender_state, &packet_sender);
7282                let changed = board.reachable_timesync_sources != sources;
7283                board.reachable_timesync_sources = sources;
7284                Self::refresh_sender_topology_state(&mut sender_state);
7285                changed
7286            }
7287            DataType::DiscoveryTopology => {
7288                let mut update = decoded_topology
7289                    .take()
7290                    .expect("topology packet was decoded before route selection");
7291                if !side_link_local_enabled {
7292                    for board in update.boards.iter_mut() {
7293                        board
7294                            .reachable_endpoints
7295                            .retain(|ep| !ep.is_link_local_only());
7296                    }
7297                }
7298                if !update.incremental {
7299                    sender_state.has_full_topology = true;
7300                }
7301                let changed =
7302                    discovery::apply_topology_update(&mut sender_state.topology_boards, update);
7303                Self::refresh_sender_topology_state(&mut sender_state);
7304                changed
7305            }
7306            DataType::DiscoverySchema => false,
7307            _ => false,
7308        };
7309        sender_state.last_seen_ms = now_ms;
7310        route.announcers.insert(packet_sender, sender_state);
7311        Self::recompute_discovery_side_state(&mut route);
7312        st.commit_discovery_route(side, route)?;
7313        self.reconcile_end_to_end_reliable_destinations_locked(&mut st)?;
7314        if changed {
7315            Self::note_discovery_topology_change_locked(&mut st, now_ms);
7316        }
7317        Ok(true)
7318    }
7319
7320    #[cfg(not(feature = "discovery"))]
7321    fn queue_discovery_announce(&self) -> TelemetryResult<()> {
7322        Ok(())
7323    }
7324
7325    #[cfg(not(feature = "discovery"))]
7326    fn poll_discovery_announce(&self) -> TelemetryResult<bool> {
7327        Ok(false)
7328    }
7329
7330    #[cfg(not(feature = "discovery"))]
7331    fn learn_discovery_packet(
7332        &self,
7333        _pkt: &Packet,
7334        _src: Option<RouterSideId>,
7335        _called_from_queue: bool,
7336    ) -> TelemetryResult<bool> {
7337        Ok(false)
7338    }
7339
7340    #[inline]
7341    fn reliable_key(side: RouterSideId, ty: DataType) -> (RouterSideId, u32) {
7342        (side, ty.as_u32())
7343    }
7344
7345    fn reliable_tx_state_mut<'a>(
7346        &'a self,
7347        st: &'a mut RouterInner,
7348        side: RouterSideId,
7349        ty: DataType,
7350    ) -> &'a mut ReliableTxState {
7351        let key = Self::reliable_key(side, ty);
7352        st.reliable_tx
7353            .entry(key)
7354            .or_insert_with(|| ReliableTxState {
7355                next_seq: 1,
7356                sent_order: VecDeque::new(),
7357                sent: BTreeMap::new(),
7358            })
7359    }
7360
7361    fn reliable_rx_state_mut<'a>(
7362        &'a self,
7363        st: &'a mut RouterInner,
7364        side: RouterSideId,
7365        ty: DataType,
7366    ) -> &'a mut ReliableRxState {
7367        let key = Self::reliable_key(side, ty);
7368        st.reliable_rx
7369            .entry(key)
7370            .or_insert_with(|| ReliableRxState {
7371                expected_seq: 1,
7372                buffered: BTreeMap::new(),
7373            })
7374    }
7375
7376    fn reliable_control_packet(
7377        &self,
7378        control_ty: DataType,
7379        ty: DataType,
7380        seq: u32,
7381    ) -> TelemetryResult<Packet> {
7382        let sender = self.sender_arc();
7383        Packet::new(
7384            control_ty,
7385            message_meta(control_ty).endpoints_ref(),
7386            sender.as_ref(),
7387            self.packet_timestamp_ms(),
7388            encode_slice_le(&[ty.as_u32(), seq]),
7389        )
7390    }
7391
7392    fn queue_reliable_ack(
7393        &self,
7394        side: RouterSideId,
7395        ty: DataType,
7396        seq: u32,
7397        called_from_queue: bool,
7398    ) -> TelemetryResult<()> {
7399        let pkt = self.reliable_control_packet(DataType::ReliableAck, ty, seq)?;
7400        self.emit_internal_tx_with_priority(
7401            RouterTxItem::ToSide {
7402                src: None,
7403                dst: side,
7404                data: RouterItem::Packet(pkt),
7405            },
7406            true,
7407            message_priority(DataType::ReliableAck),
7408            called_from_queue,
7409        )
7410    }
7411
7412    fn queue_reliable_packet_request(
7413        &self,
7414        side: RouterSideId,
7415        ty: DataType,
7416        seq: u32,
7417        called_from_queue: bool,
7418    ) -> TelemetryResult<()> {
7419        let pkt = self.reliable_control_packet(DataType::ReliablePacketRequest, ty, seq)?;
7420        self.emit_internal_tx_with_priority(
7421            RouterTxItem::ToSide {
7422                src: None,
7423                dst: side,
7424                data: RouterItem::Packet(pkt),
7425            },
7426            true,
7427            message_priority(DataType::ReliablePacketRequest),
7428            called_from_queue,
7429        )
7430    }
7431
7432    fn queue_reliable_partial_ack(
7433        &self,
7434        side: RouterSideId,
7435        ty: DataType,
7436        seq: u32,
7437        called_from_queue: bool,
7438    ) -> TelemetryResult<()> {
7439        let pkt = self.reliable_control_packet(DataType::ReliablePartialAck, ty, seq)?;
7440        self.emit_internal_tx_with_priority(
7441            RouterTxItem::ToSide {
7442                src: None,
7443                dst: side,
7444                data: RouterItem::Packet(pkt),
7445            },
7446            true,
7447            message_priority(DataType::ReliablePartialAck),
7448            called_from_queue,
7449        )
7450    }
7451
7452    fn handle_reliable_ack(&self, side: RouterSideId, ty: DataType, ack: u32) {
7453        let mut st = self.state.lock();
7454        let tx_state = self.reliable_tx_state_mut(&mut st, side, ty);
7455        if matches!(reliable_mode(ty), crate::ReliableMode::Unordered) {
7456            tx_state.sent.remove(&ack);
7457            tx_state.sent_order.retain(|seq| *seq != ack);
7458            return;
7459        }
7460
7461        while let Some(seq) = tx_state.sent_order.front().copied() {
7462            if seq > ack {
7463                break;
7464            }
7465            tx_state.sent_order.pop_front();
7466            tx_state.sent.remove(&seq);
7467        }
7468    }
7469
7470    fn handle_reliable_partial_ack(&self, side: RouterSideId, ty: DataType, seq: u32) {
7471        let mut st = self.state.lock();
7472        let tx_state = self.reliable_tx_state_mut(&mut st, side, ty);
7473        if let Some(sent) = tx_state.sent.get_mut(&seq) {
7474            sent.partial_acked = true;
7475        }
7476    }
7477
7478    fn queue_reliable_retransmit(
7479        &self,
7480        side: RouterSideId,
7481        ty: DataType,
7482        seq: u32,
7483        called_from_queue: bool,
7484    ) -> TelemetryResult<()> {
7485        let mut queued = None;
7486        {
7487            let mut st = self.state.lock();
7488            let tx_state = self.reliable_tx_state_mut(&mut st, side, ty);
7489            if let Some(sent) = tx_state.sent.get_mut(&seq)
7490                && !sent.queued
7491            {
7492                sent.queued = true;
7493                sent.partial_acked = false;
7494                queued = Some(sent.bytes.clone());
7495            }
7496        }
7497
7498        if let Some(bytes) = queued {
7499            if called_from_queue {
7500                self.tx_queue_item_with_priority(
7501                    RouterTxItem::ReliableReplay { dst: side, bytes },
7502                    true,
7503                    Self::router_item_priority_bumped(ty),
7504                )?;
7505            } else {
7506                self.tx_item_impl(
7507                    RouterTxItem::ReliableReplay { dst: side, bytes },
7508                    true,
7509                    false,
7510                )?;
7511            }
7512        }
7513
7514        Ok(())
7515    }
7516
7517    fn send_reliable_raw_to_side(
7518        &self,
7519        side: RouterSideId,
7520        bytes: crate::SharedBytes,
7521        relayed: bool,
7522    ) -> TelemetryResult<()> {
7523        let handler = {
7524            let st = self.state.lock();
7525            let side_ref = Self::side_ref(&st, side)?;
7526            if !side_ref.opts.egress_enabled {
7527                return Ok(());
7528            }
7529            (side_ref.tx_handler.clone(), side_ref.opts.clone())
7530        };
7531
7532        let (handler, opts) = handler;
7533
7534        let Some(_side_tx_guard) = self.try_enter_side_tx() else {
7535            return Err(TelemetryError::Io("side tx busy"));
7536        };
7537        let started_ms = self.clock.now_ms();
7538        let ty = wire_format::peek_routing_envelope(bytes.as_ref())
7539            .map(|env| env.ty)
7540            .unwrap_or(DataType::ReliableAck);
7541        let priority = self.effective_transport_priority(ty);
7542        let result = match handler {
7543            packed @ (RouterTxHandlerFn::Packed(_) | RouterTxHandlerFn::PackedWithPriority(_)) => {
7544                let frames = self.encode_side_transport_frames(side, opts, bytes.clone())?;
7545                let mut attempts_total = 0usize;
7546                let mut sent_bytes = 0usize;
7547                self.send_side_frames_resumable(side, frames, |frame| {
7548                    match self.retry_with_attempts(runtime_max_handler_retries(), || {
7549                        Self::packed_handler_call(&packed, frame, priority)
7550                    }) {
7551                        Ok((_, attempts)) => {
7552                            attempts_total = attempts_total.saturating_add(attempts);
7553                            sent_bytes = sent_bytes.saturating_add(frame.len());
7554                        }
7555                        Err((err, attempts)) => {
7556                            self.note_side_tx_failure(
7557                                side,
7558                                ty,
7559                                attempts_total.saturating_add(attempts),
7560                            );
7561                            return Err(err);
7562                        }
7563                    }
7564
7565                    Ok(())
7566                })?;
7567                self.record_side_tx_sample(side, sent_bytes, started_ms, self.clock.now_ms());
7568                self.note_side_tx_success(side, ty, sent_bytes, relayed, attempts_total);
7569                return Ok(());
7570            }
7571            RouterTxHandlerFn::Packet(f) => {
7572                let pkt = wire_format::unpack_packet(bytes.as_ref())?;
7573                self.retry_with_attempts(runtime_max_handler_retries(), || f(&pkt))
7574            }
7575        };
7576        match result {
7577            Ok((_, attempts)) => {
7578                self.record_side_tx_sample(side, bytes.len(), started_ms, self.clock.now_ms());
7579                self.note_side_tx_success(side, ty, bytes.len(), relayed, attempts);
7580                Ok(())
7581            }
7582            Err((err, attempts)) => {
7583                self.note_side_tx_failure(side, ty, attempts);
7584                Err(err)
7585            }
7586        }
7587    }
7588
7589    #[allow(unused_mut)]
7590    fn send_reliable_to_side(
7591        &self,
7592        side: RouterSideId,
7593        data: RouterItem,
7594        relayed: bool,
7595    ) -> TelemetryResult<()> {
7596        let end_to_end_ordered = !self.item_target_senders(&data)?.is_empty()
7597            && matches!(
7598                reliable_mode(self.item_route_info(&data)?.1),
7599                crate::ReliableMode::Ordered
7600            );
7601        let (handler, opts, hop_reliable_enabled) = {
7602            let st = self.state.lock();
7603            let side_ref = Self::side_ref(&st, side)?;
7604            let opts = side_ref.opts.clone();
7605            let hop_reliable_enabled = opts.reliable_enabled
7606                && !end_to_end_ordered
7607                && self.cfg.reliable_enabled()
7608                && !self.side_has_multiple_announcers_locked(&st, side, self.clock.now_ms());
7609            (side_ref.tx_handler.clone(), opts, hop_reliable_enabled)
7610        };
7611
7612        if !matches!(
7613            &handler,
7614            RouterTxHandlerFn::Packed(_) | RouterTxHandlerFn::PackedWithPriority(_)
7615        ) {
7616            return self.call_side_tx_handler(side, &handler, &data, relayed);
7617        }
7618
7619        if !hop_reliable_enabled {
7620            let mut adjusted_opts = opts;
7621            adjusted_opts.reliable_enabled = false;
7622            // End-to-end ACKs are ordinary routed control packets and must cross best-effort
7623            // segments. `reliable_enabled` controls only this side's hop sequencing/replay; using
7624            // it to discard an ACK prevents a reliable origin from ever confirming destinations
7625            // behind Pico-Fi, UDP, radio, or another transport with its own reliability layer.
7626            let preserve_end_to_end_ack = true;
7627            if let Some(adjusted) =
7628                self.adjust_reliable_for_side(adjusted_opts, data, preserve_end_to_end_ack)?
7629            {
7630                return self.call_side_tx_handler(side, &handler, &adjusted, relayed);
7631            }
7632            return Ok(());
7633        }
7634
7635        let ty = match &data {
7636            RouterItem::Packet(pkt) => pkt.data_type(),
7637            RouterItem::Packed(bytes) => {
7638                wire_format::peek_routing_frame_info(bytes.as_ref())?
7639                    .envelope
7640                    .ty
7641            }
7642        };
7643        let priority = self.effective_transport_priority(ty);
7644
7645        if !is_reliable_type(ty) {
7646            if let Some(adjusted) = self.adjust_reliable_for_side(opts, data, true)? {
7647                self.call_side_tx_handler(side, &handler, &adjusted, relayed)?;
7648            }
7649            return Ok(());
7650        }
7651
7652        let (seq, flags) = {
7653            let mut st = self.state.lock();
7654            let tx_state = self.reliable_tx_state_mut(&mut st, side, ty);
7655            if tx_state.sent.len() >= runtime_reliable_max_pending() {
7656                return Err(TelemetryError::PacketTooLarge(
7657                    "router reliable history full",
7658                ));
7659            }
7660            let seq = tx_state.next_seq;
7661            let next = tx_state.next_seq.wrapping_add(1);
7662            tx_state.next_seq = if next == 0 { 1 } else { next };
7663            let flags = match reliable_mode(ty) {
7664                crate::ReliableMode::Unordered => wire_format::RELIABLE_FLAG_UNORDERED,
7665                _ => 0,
7666            };
7667            (seq, flags)
7668        };
7669
7670        let mut bytes: crate::SharedBytes = match data {
7671            RouterItem::Packet(pkt) => self.pack_packet_for_router(
7672                &pkt,
7673                Some(wire_format::ReliableHeader { flags, seq, ack: 0 }),
7674            )?,
7675            RouterItem::Packed(bytes) => {
7676                #[cfg(feature = "cryptography")]
7677                if self.e2e_seal_config_for_type(ty).is_some() {
7678                    self.prepare_packed_for_remote(
7679                        bytes,
7680                        Some(Some(wire_format::ReliableHeader { flags, seq, ack: 0 })),
7681                    )?
7682                } else {
7683                    let Some(rewritten) =
7684                        wire_format::rewrite_reliable_header_owned(bytes.as_ref(), flags, seq, 0)?
7685                    else {
7686                        let Some(_side_tx_guard) = self.try_enter_side_tx() else {
7687                            return Err(TelemetryError::Io("side tx busy"));
7688                        };
7689                        let started_ms = self.clock.now_ms();
7690                        let frames =
7691                            self.encode_side_transport_frames(side, opts, bytes.clone())?;
7692                        let mut attempts_total = 0usize;
7693                        let mut sent_bytes = 0usize;
7694                        self.send_side_frames_resumable(side, frames, |frame| {
7695                            match self.retry_with_attempts(runtime_max_handler_retries(), || {
7696                                Self::packed_handler_call(&handler, frame, priority)
7697                            }) {
7698                                Ok((_, attempts)) => {
7699                                    attempts_total = attempts_total.saturating_add(attempts);
7700                                    sent_bytes = sent_bytes.saturating_add(frame.len());
7701                                }
7702                                Err((err, attempts)) => {
7703                                    self.note_side_tx_failure(
7704                                        side,
7705                                        ty,
7706                                        attempts_total.saturating_add(attempts),
7707                                    );
7708                                    return Err(err);
7709                                }
7710                            }
7711
7712                            Ok(())
7713                        })?;
7714                        self.record_side_tx_sample(
7715                            side,
7716                            sent_bytes,
7717                            started_ms,
7718                            self.clock.now_ms(),
7719                        );
7720                        self.note_side_tx_success(side, ty, sent_bytes, relayed, attempts_total);
7721                        return Ok(());
7722                    };
7723                    crate::shared_bytes::convert(rewritten)
7724                }
7725                #[cfg(not(feature = "cryptography"))]
7726                {
7727                    let Some(rewritten) =
7728                        wire_format::rewrite_reliable_header_owned(bytes.as_ref(), flags, seq, 0)?
7729                    else {
7730                        let Some(_side_tx_guard) = self.try_enter_side_tx() else {
7731                            return Err(TelemetryError::Io("side tx busy"));
7732                        };
7733                        let started_ms = self.clock.now_ms();
7734                        let frames =
7735                            self.encode_side_transport_frames(side, opts, bytes.clone())?;
7736                        let mut attempts_total = 0usize;
7737                        let mut sent_bytes = 0usize;
7738                        self.send_side_frames_resumable(side, frames, |frame| {
7739                            match self.retry_with_attempts(runtime_max_handler_retries(), || {
7740                                Self::packed_handler_call(&handler, frame, priority)
7741                            }) {
7742                                Ok((_, attempts)) => {
7743                                    attempts_total = attempts_total.saturating_add(attempts);
7744                                    sent_bytes = sent_bytes.saturating_add(frame.len());
7745                                }
7746                                Err((err, attempts)) => {
7747                                    self.note_side_tx_failure(
7748                                        side,
7749                                        ty,
7750                                        attempts_total.saturating_add(attempts),
7751                                    );
7752                                    return Err(err);
7753                                }
7754                            }
7755
7756                            Ok(())
7757                        })?;
7758                        self.record_side_tx_sample(
7759                            side,
7760                            sent_bytes,
7761                            started_ms,
7762                            self.clock.now_ms(),
7763                        );
7764                        self.note_side_tx_success(side, ty, sent_bytes, relayed, attempts_total);
7765                        return Ok(());
7766                    };
7767                    rewritten.into()
7768                }
7769            }
7770        };
7771
7772        let Some(_side_tx_guard) = self.try_enter_side_tx() else {
7773            return Err(TelemetryError::Io("side tx busy"));
7774        };
7775        let started_ms = self.clock.now_ms();
7776        let frames = self.encode_side_transport_frames(side, opts, bytes.clone())?;
7777        let mut attempts_total = 0usize;
7778        let mut sent_bytes = 0usize;
7779        self.send_side_frames_resumable(side, frames, |frame| {
7780            match self.retry_with_attempts(runtime_max_handler_retries(), || {
7781                Self::packed_handler_call(&handler, frame, priority)
7782            }) {
7783                Ok((_, attempts)) => {
7784                    attempts_total = attempts_total.saturating_add(attempts);
7785                    sent_bytes = sent_bytes.saturating_add(frame.len());
7786                }
7787                Err((err, attempts)) => {
7788                    self.note_side_tx_failure(side, ty, attempts_total.saturating_add(attempts));
7789                    return Err(err);
7790                }
7791            }
7792
7793            Ok(())
7794        })?;
7795        self.record_side_tx_sample(side, sent_bytes, started_ms, self.clock.now_ms());
7796        self.note_side_tx_success(side, ty, sent_bytes, relayed, attempts_total);
7797
7798        #[cfg(feature = "compact-packet-store")]
7799        {
7800            let _ = bytes.park_for_queue();
7801        }
7802
7803        {
7804            let mut st = self.state.lock();
7805            let tx_state = self.reliable_tx_state_mut(&mut st, side, ty);
7806            tx_state.sent_order.push_back(seq);
7807            tx_state.sent.insert(
7808                seq,
7809                ReliableSent {
7810                    bytes: bytes.clone(),
7811                    last_send_ms: self.clock.now_ms(),
7812                    retries: 0,
7813                    queued: false,
7814                    partial_acked: false,
7815                },
7816            );
7817        }
7818
7819        Ok(())
7820    }
7821
7822    fn run_side_chunk_transfer<T>(
7823        &self,
7824        side: RouterSideId,
7825        key: u64,
7826        send: impl FnOnce(&mut usize) -> TelemetryResult<T>,
7827    ) -> TelemetryResult<T> {
7828        let mut next = {
7829            let mut st = self.state.lock();
7830            let transport = st
7831                .side_transport
7832                .get_mut(&side)
7833                .ok_or(TelemetryError::BadArg)?;
7834            if transport.tx_chunk_progress.is_none() {
7835                crate::memory_admission::check(
7836                    512,
7837                    core::mem::size_of::<crate::side_transport::SideTxProgress>(),
7838                )?;
7839                transport.tx_chunk_progress =
7840                    Some(Box::new(crate::side_transport::SideTxProgress::default()));
7841            }
7842            transport
7843                .tx_chunk_progress
7844                .as_mut()
7845                .expect("initialized")
7846                .begin(key, self.clock.now_ms())?
7847        };
7848        let result = send(&mut next);
7849        if let Some(progress) = self
7850            .state
7851            .lock()
7852            .side_transport
7853            .get_mut(&side)
7854            .and_then(|transport| transport.tx_chunk_progress.as_mut())
7855        {
7856            progress.finish(key, next, self.clock.now_ms(), &result);
7857        }
7858        result
7859    }
7860
7861    fn send_side_frames_resumable(
7862        &self,
7863        side: RouterSideId,
7864        mut frames: SideTransportFrames,
7865        mut send: impl FnMut(&[u8]) -> TelemetryResult<()>,
7866    ) -> TelemetryResult<()> {
7867        let bootstraps_template = frames.bootstraps_template();
7868        let result = if frames.len() == 1 {
7869            frames.send_resuming(&mut 0, send)
7870        } else {
7871            let key = frames.progress_key();
7872            self.run_side_chunk_transfer(side, key, |next| frames.send_resuming(next, &mut send))
7873        };
7874        if result.is_err() && bootstraps_template {
7875            // Encoding has already inserted the TX template, but a busy or
7876            // failed handler did not accept its bootstrap. Keep dictionary
7877            // eviction history and force a full refresh on the next attempt.
7878            // Never clear RX templates or allocate during this recovery.
7879            if let Some(state) = self.state.lock().side_transport.get_mut(&side) {
7880                state.refresh_transmit_header_templates();
7881            }
7882        }
7883        result
7884    }
7885
7886    fn split_side_transport_frame(
7887        &self,
7888        _side: RouterSideId,
7889        frame: crate::SharedBytes,
7890        max_frame_bytes: usize,
7891    ) -> TelemetryResult<SideTransportFrames> {
7892        SideTransportFrames::split(frame, max_frame_bytes, self.sender_arc().as_bytes())
7893    }
7894
7895    fn encode_side_transport_frames(
7896        &self,
7897        side: RouterSideId,
7898        opts: RouterSideOptions,
7899        raw: crate::SharedBytes,
7900    ) -> TelemetryResult<SideTransportFrames> {
7901        if !opts.header_template_enabled && opts.max_frame_bytes == 0 {
7902            return Ok(SideTransportFrames::single(raw));
7903        }
7904
7905        let raw_len = raw.len();
7906        if opts.max_frame_bytes != 0 && raw_len > opts.max_frame_bytes {
7907            // Canonical bytes keep a partially accepted transfer independent
7908            // of dictionary churn while other traffic is serviced.
7909            let frames = self.split_side_transport_frame(side, raw, opts.max_frame_bytes)?;
7910            let mut st = self.state.lock();
7911            let stats = st.side_runtime_stats.entry(side).or_default();
7912            stats.note_side_transport_full(raw_len, frames.wire_len());
7913            stats.note_side_transport_chunks(frames.len());
7914            return Ok(frames);
7915        }
7916
7917        if opts.header_template_enabled {
7918            // Shared buses cannot retain one coherent dictionary across
7919            // independent producers. Do not build owned templates or wrap
7920            // canonical bytes when no dictionary can be used.
7921            let frame = wire_format::peek_routing_frame_info(raw.as_ref())?;
7922            let without_dictionary = opts.max_side_transport_templates == 0 || {
7923                let st = self.state.lock();
7924                self.side_has_multiple_announcers_locked(&st, side, self.clock.now_ms())
7925            };
7926            let reliable = is_reliable_type(frame.envelope.ty)
7927                || frame.reliable.is_some()
7928                || matches!(
7929                    frame.envelope.ty,
7930                    crate::DataType::ReliableAck
7931                        | crate::DataType::ReliablePartialAck
7932                        | crate::DataType::ReliablePacketRequest
7933                );
7934            if without_dictionary || reliable {
7935                let mut st = self.state.lock();
7936                if without_dictionary {
7937                    let removed = st
7938                        .side_transport
7939                        .get_mut(&side)
7940                        .ok_or(TelemetryError::BadArg)?
7941                        .trim_tx_templates_to(0);
7942                    for _ in 0..removed {
7943                        st.side_runtime_stats
7944                            .entry(side)
7945                            .or_default()
7946                            .note_side_transport_template_eviction();
7947                    }
7948                }
7949                st.side_runtime_stats
7950                    .entry(side)
7951                    .or_default()
7952                    .note_side_transport_full(raw_len, raw_len);
7953                return Ok(SideTransportFrames::single(raw));
7954            }
7955        }
7956
7957        let mut compact_payload_len = None;
7958        let mut used_compact = false;
7959        let used_timestamp_delta = false;
7960        let omitted_timestamp = false;
7961        let wrapped = if opts.header_template_enabled {
7962            let (template, _ty, flags, data_size, timestamp, nonce, reliable_seq_ack, payload) =
7963                extract_side_header_template_view(raw.as_ref())?;
7964            // Reliable frames never use dictionary compression. Send the native,
7965            // self-describing wire packet without allocating/retaining an unused
7966            // template on either peer. Raw packets are accepted by both decoders.
7967            if is_reliable_type(_ty)
7968                || reliable_seq_ack.is_some()
7969                || matches!(
7970                    _ty,
7971                    DataType::ReliableAck
7972                        | DataType::ReliablePartialAck
7973                        | DataType::ReliablePacketRequest
7974                )
7975            {
7976                let frames = if opts.max_frame_bytes != 0 && raw_len > opts.max_frame_bytes {
7977                    self.split_side_transport_frame(side, raw, opts.max_frame_bytes)?
7978                } else {
7979                    SideTransportFrames::single(raw)
7980                };
7981                let wire_len = frames.wire_len();
7982                let mut st = self.state.lock();
7983                let stats = st.side_runtime_stats.entry(side).or_default();
7984                stats.note_side_transport_full(raw_len, wire_len);
7985                if frames.len() > 1 {
7986                    stats.note_side_transport_chunks(frames.len());
7987                }
7988                return Ok(frames);
7989            }
7990            let (template_id, use_compact, _previous_timestamp) = {
7991                let mut st = self.state.lock();
7992                /* Keep the sender's compact dictionary within the smallest
7993                 * capacity advertised by a live peer on this side. Template
7994                 * IDs otherwise outlive entries on a smaller receiver and
7995                 * later compact values are silently undecodable. */
7996                let mut effective_template_limit = opts.max_side_transport_templates;
7997                #[cfg(feature = "discovery")]
7998                {
7999                    if let Some(route) = st.discovery_routes.get(&side) {
8000                        let active_peers = route
8001                            .announcers
8002                            .values()
8003                            .filter(|sender| {
8004                                self.clock.now_ms().saturating_sub(sender.last_seen_ms)
8005                                    <= DISCOVERY_ROUTE_TTL_MS
8006                            })
8007                            .count();
8008                        /* Compact template state is intentionally side-local.
8009                         * On a shared bus, independent producers cannot divide
8010                         * a receiver's bounded dictionary between themselves,
8011                         * so their compact frames can evict one another. Keep
8012                         * compression for point-to-point links and
8013                         * automatically use self-describing full frames once
8014                         * discovery proves a side is multi-producer. */
8015                        if active_peers > 1 {
8016                            effective_template_limit = 0;
8017                        } else {
8018                            effective_template_limit = route
8019                                .announcers
8020                                .values()
8021                                .filter_map(|sender| sender.link_capabilities)
8022                                .filter(|caps| {
8023                                    caps.flags & discovery::LINK_CAPABILITY_HEADER_TEMPLATES != 0
8024                                        && caps.max_side_transport_templates > 0
8025                                })
8026                                .map(|caps| caps.max_side_transport_templates as usize)
8027                                .fold(effective_template_limit, core::cmp::min);
8028                        }
8029                    }
8030                }
8031                let trimmed = st
8032                    .side_transport
8033                    .get_mut(&side)
8034                    .ok_or(TelemetryError::BadArg)?
8035                    .trim_tx_templates_to(effective_template_limit);
8036                for _ in 0..trimmed {
8037                    st.side_runtime_stats
8038                        .entry(side)
8039                        .or_default()
8040                        .note_side_transport_template_eviction();
8041                }
8042                let side_state = st
8043                    .side_transport
8044                    .get_mut(&side)
8045                    .ok_or(TelemetryError::BadArg)?;
8046                if let Some(id) = side_state.tx_template_ids.get(&template.hash).copied() {
8047                    let previous = side_state.tx_last_timestamps.get(&id).copied();
8048                    /* A full refresh makes compact transport self-healing when
8049                     * the receiver missed the original template frame. */
8050                    const FULL_REFRESH_AFTER_COMPACT_FRAMES: u8 = 8;
8051                    let compact_uses = side_state.tx_compact_uses.entry(id).or_default();
8052                    // Discovery must bootstrap even when a receiver lost its dictionary.
8053                    #[cfg(feature = "discovery")]
8054                    let discovery_refresh = discovery::is_discovery_type(_ty);
8055                    #[cfg(not(feature = "discovery"))]
8056                    let discovery_refresh = false;
8057                    let use_compact =
8058                        *compact_uses < FULL_REFRESH_AFTER_COMPACT_FRAMES && !discovery_refresh;
8059                    if use_compact {
8060                        *compact_uses = compact_uses.saturating_add(1);
8061                    } else {
8062                        *compact_uses = 0;
8063                    }
8064                    (id, use_compact, previous)
8065                } else {
8066                    /* Sequential template IDs collide when multiple routers
8067                     * transmit on one physical bus. The template hash covers
8068                     * the source address and immutable header fields, making
8069                     * this ID producer-stable without adding bytes to compact
8070                     * frames. */
8071                    let template_id = ((template.hash >> 32) as u32 ^ template.hash as u32).max(1);
8072                    let evicted = side_state.insert_tx_template(
8073                        template.into_owned(),
8074                        template_id,
8075                        effective_template_limit,
8076                    );
8077                    if evicted {
8078                        st.side_runtime_stats
8079                            .entry(side)
8080                            .or_default()
8081                            .note_side_transport_template_eviction();
8082                    }
8083                    if let Some(side_state) = st.side_transport.get_mut(&side)
8084                        && effective_template_limit > 0
8085                    {
8086                        side_state.tx_last_timestamps.insert(template_id, timestamp);
8087                    }
8088                    (template_id, false, None)
8089                }
8090            };
8091            if use_compact {
8092                used_compact = true;
8093                compact_payload_len = Some(payload.len());
8094                // A previous transmitted frame is not a confirmed receive base.
8095                // Absolute timestamps preserve identity after loss or reordering.
8096                let timestamp_field = Some(timestamp);
8097                let mut body = Vec::with_capacity(payload.len() + 32);
8098                body.push(flags);
8099                write_uleb128_local(u64::from(template_id), &mut body);
8100                write_uleb128_local(data_size, &mut body);
8101                if let Some(timestamp_field) = timestamp_field {
8102                    write_uleb128_local(timestamp_field, &mut body);
8103                }
8104                if (flags & SIDE_TRANSPORT_FLAG_PACKET_NONCE) != 0 {
8105                    write_uleb128_local(u64::from(nonce), &mut body);
8106                }
8107                if let Some((seq, ack)) = reliable_seq_ack {
8108                    write_uleb128_local(u64::from(seq), &mut body);
8109                    write_uleb128_local(u64::from(ack), &mut body);
8110                }
8111                body.extend_from_slice(payload);
8112                {
8113                    let mut st = self.state.lock();
8114                    if let Some(side_state) = st.side_transport.get_mut(&side) {
8115                        side_state.tx_last_timestamps.insert(template_id, timestamp);
8116                    }
8117                }
8118                let kind = if omitted_timestamp {
8119                    SIDE_TRANSPORT_KIND_COMPACT_SAME_TIMESTAMP
8120                } else if used_timestamp_delta {
8121                    SIDE_TRANSPORT_KIND_COMPACT_DELTA
8122                } else {
8123                    SIDE_TRANSPORT_KIND_COMPACT
8124                };
8125                wrap_side_transport_frame(kind, &body)
8126            } else {
8127                let mut body = Vec::with_capacity(raw.len() + 4);
8128                write_uleb128_local(u64::from(template_id), &mut body);
8129                body.extend_from_slice(raw.as_ref());
8130                wrap_side_transport_frame(SIDE_TRANSPORT_KIND_FULL, &body)
8131            }
8132        } else {
8133            wrap_side_transport_frame(SIDE_TRANSPORT_KIND_FULL, raw.as_ref())
8134        };
8135
8136        let frames = if opts.max_frame_bytes != 0 && wrapped.len() > opts.max_frame_bytes {
8137            self.split_side_transport_frame(side, wrapped, opts.max_frame_bytes)
8138        } else {
8139            Ok(SideTransportFrames::single(wrapped))
8140        }?;
8141        let wire_len = frames.wire_len();
8142        let mut st = self.state.lock();
8143        let stats = st.side_runtime_stats.entry(side).or_default();
8144        if used_compact {
8145            let overhead = compact_payload_len
8146                .map(|payload_len| wire_len.saturating_sub(payload_len))
8147                .unwrap_or(wire_len);
8148            stats.note_side_transport_compact(
8149                raw_len,
8150                wire_len,
8151                overhead,
8152                used_timestamp_delta,
8153                omitted_timestamp,
8154            );
8155            if opts.compact_header_target_bytes != 0 && overhead > opts.compact_header_target_bytes
8156            {
8157                stats.note_side_transport_compact_target_miss();
8158            }
8159        } else {
8160            stats.note_side_transport_full(raw_len, wire_len);
8161        }
8162        if frames.len() > 1 {
8163            stats.note_side_transport_chunks(frames.len());
8164        }
8165        Ok(frames)
8166    }
8167
8168    fn expire_side_chunks(&self) {
8169        let now = self.clock.now_ms();
8170        let mut st = self.state.lock();
8171        for side in st.side_transport.values_mut() {
8172            side.rx_chunks
8173                .retain(|_, assembly| now.saturating_sub(assembly.last_seen_ms) <= 2000);
8174        }
8175    }
8176
8177    // A validated frame from a previously discovered direct peer proves that
8178    // its link is alive, even when allocation admission refuses its payload.
8179    // Update existing timestamps only: no allocation, schema merge or new route.
8180    #[cfg(feature = "discovery")]
8181    fn refresh_ingress_peer_liveness(&self, side: RouterSideId, bytes: &[u8]) {
8182        let Ok(frame) = wire_format::peek_routing_frame_info(bytes) else {
8183            return;
8184        };
8185        let address = frame.envelope.source_address;
8186        self.refresh_peer_liveness(side, address);
8187    }
8188
8189    #[cfg(feature = "discovery")]
8190    #[cfg_attr(not(feature = "std"), inline(never))]
8191    fn refresh_peer_liveness(&self, side: RouterSideId, address: u32) {
8192        let now = self.clock.now_ms();
8193        let mut st = self.state.lock();
8194        let Some(route) = st.discovery_routes.get_mut(&side) else {
8195            return;
8196        };
8197        let mut found = None;
8198        for (name, peer) in &mut route.announcers {
8199            if peer.advertised_address == Some(address) || sender_address_u32(name) == address {
8200                // Ambiguous numeric identities must be resolved by discovery.
8201                if found.is_some() {
8202                    return;
8203                }
8204                found = Some(peer);
8205            }
8206        }
8207        let Some(peer) = found else {
8208            return;
8209        };
8210        peer.last_seen_ms = now;
8211        route.last_seen_ms = route.last_seen_ms.max(now);
8212    }
8213
8214    #[cfg(all(feature = "discovery", not(feature = "std")))]
8215    fn consume_known_packed_schema(
8216        &self,
8217        side: RouterSideId,
8218        bytes: &[u8],
8219    ) -> TelemetryResult<bool> {
8220        let Some((address, payload)) = wire_format::peek_plain_schema_payload(bytes)? else {
8221            return Ok(false);
8222        };
8223        if !discovery::discovery_schema_payload_is_fully_known(payload)? {
8224            return Ok(false);
8225        }
8226        // Discovery schemas are always sent as full refreshes. No compact
8227        // dictionary entry or second owned payload is needed for this no-op.
8228        let mut st = self.state.lock();
8229        if let Some(route) = st.discovery_routes.get_mut(&side) {
8230            for (name, peer) in &mut route.announcers {
8231                if peer.advertised_address == Some(address) || sender_address_u32(name) == address {
8232                    peer.has_schema = true;
8233                    break;
8234                }
8235            }
8236        }
8237        Ok(true)
8238    }
8239
8240    // A two-sided, fully warmed packed bridge needs no new large metadata.
8241    // Re-check before dispatch: schemas/templates/routes can change while queued.
8242    #[cfg(feature = "discovery")]
8243    fn cached_transit_ready(&self, side: RouterSideId, bytes: &[u8]) -> TelemetryResult<bool> {
8244        if bytes.len() > 128 || bytes.first().is_none_or(|flags| flags & 0x15 != 0) {
8245            return Ok(false);
8246        }
8247        let frame = wire_format::peek_routing_frame_info(bytes)?;
8248        let ty = frame.envelope.ty;
8249        if is_internal_control_type(ty)
8250            || frame.envelope.is_reliable
8251            || frame.reliable.is_some()
8252            || frame.envelope.target_senders().len() != 0
8253            || !(1..=3).contains(&frame.envelope.endpoints().take(4).count())
8254        {
8255            return Ok(false);
8256        }
8257        if self.is_managed_variable_type(ty)
8258            || frame.envelope.endpoints().any(|ep| {
8259                self.endpoint_has_packet_handler(ep) || self.endpoint_has_packed_handler(ep)
8260            })
8261        {
8262            return Ok(false);
8263        }
8264
8265        let (template, ..) = extract_side_header_template_view(bytes)?;
8266        let st = self.state.lock();
8267        let active: usize = st.sides.iter().filter(|entry| entry.is_some()).count();
8268        if active != 2 {
8269            return Ok(false);
8270        }
8271        for (id, entry) in st
8272            .sides
8273            .iter()
8274            .enumerate()
8275            .filter_map(|(id, entry)| entry.as_ref().map(|e| (id, e)))
8276        {
8277            if entry.opts.reliable_enabled
8278                || !matches!(
8279                    entry.tx_handler,
8280                    RouterTxHandlerFn::Packed(_) | RouterTxHandlerFn::PackedWithPriority(_)
8281                )
8282                || !st
8283                    .side_runtime_stats
8284                    .get(&id)
8285                    .is_some_and(|stats| stats.data_types.contains_key(&ty.as_u32()))
8286            {
8287                return Ok(false);
8288            }
8289            if id == side {
8290                continue;
8291            }
8292            #[cfg(feature = "discovery")]
8293            if !st.discovery_routes.get(&id).is_some_and(|route| {
8294                self.clock.now_ms().saturating_sub(route.last_seen_ms) <= DISCOVERY_ROUTE_TTL_MS
8295                    && frame
8296                        .envelope
8297                        .endpoints()
8298                        .any(|ep| route.reachable.contains(&ep))
8299            }) {
8300                return Ok(false);
8301            }
8302            if entry.opts.header_template_enabled
8303                && entry.opts.max_frame_bytes != 0
8304                && entry.opts.max_side_transport_templates != 0
8305                && !self.side_has_multiple_announcers_locked(&st, id, self.clock.now_ms())
8306                && !st.side_transport.get(&id).is_some_and(|transport| {
8307                    transport.tx_template_ids.contains_key(&template.hash)
8308                        && transport.tx_templates.contains_key(&template.hash)
8309                })
8310            {
8311                return Ok(false);
8312            }
8313        }
8314        Ok(true)
8315    }
8316    #[cfg(not(feature = "discovery"))]
8317    fn cached_transit_ready(&self, _side: RouterSideId, _bytes: &[u8]) -> TelemetryResult<bool> {
8318        Ok(false)
8319    }
8320    fn admit_received_frame(&self, side: RouterSideId, bytes: &[u8]) -> TelemetryResult<()> {
8321        if crate::memory_admission::supports_work_kinds()
8322            && self.cached_transit_ready(side, bytes)?
8323        {
8324            crate::memory_admission::check_cached_transit(bytes.len())
8325        } else {
8326            let mut protected = None;
8327            loop {
8328                match crate::memory_admission::check_receive(bytes) {
8329                    Err(TelemetryError::Io("memory pressure")) => {
8330                        let protected = *protected.get_or_insert_with(|| {
8331                            wire_format::peek_routing_frame_info(bytes)
8332                                .ok()
8333                                .is_some_and(|frame| message_priority(frame.envelope.ty) >= 200)
8334                        });
8335                        if !protected {
8336                            return Err(TelemetryError::Io("memory pressure"));
8337                        }
8338                        // Queue priority cannot help before the wire copy is
8339                        // admitted. Reclaim bulk payloads, preserving control
8340                        // traffic and every learned route/schema.
8341                        let mut st = self.state.lock();
8342                        let reclaimed = st
8343                            .lower_priority_shared_queue(199)
8344                            .is_some_and(|queue| st.pop_shared_queue_item(queue));
8345                        if !reclaimed {
8346                            return Err(TelemetryError::Io("memory pressure"));
8347                        }
8348                    }
8349                    result => return result,
8350                }
8351            }
8352        }
8353    }
8354
8355    fn decode_side_transport_frame(
8356        &self,
8357        side: RouterSideId,
8358        bytes: &[u8],
8359    ) -> TelemetryResult<Option<crate::SharedBytes>> {
8360        // Reclaim abandoned transfers before admission, even when new work is refused.
8361        self.expire_side_chunks();
8362        let Some((kind, body)) = parse_side_transport_wrapper(bytes)? else {
8363            #[cfg(feature = "discovery")]
8364            self.refresh_ingress_peer_liveness(side, bytes);
8365            #[cfg(all(feature = "discovery", not(feature = "std")))]
8366            if self.consume_known_packed_schema(side, bytes)? {
8367                return Ok(None);
8368            }
8369            self.admit_received_frame(side, bytes)?;
8370            return Ok(Some(crate::shared_bytes::convert(Arc::<[u8]>::from(bytes))));
8371        };
8372        match kind {
8373            SIDE_TRANSPORT_KIND_FULL => {
8374                let header_templates_enabled = self
8375                    .state
8376                    .lock()
8377                    .sides
8378                    .get(side)
8379                    .and_then(|entry| entry.as_ref())
8380                    .map(|side| side.opts.header_template_enabled)
8381                    .unwrap_or(false);
8382                let mut off = 0usize;
8383                let template_id = if header_templates_enabled {
8384                    Some(
8385                        u32::try_from(read_uleb128_local(body, &mut off)?)
8386                            .map_err(|_| TelemetryError::Unpack("side template id too large"))?,
8387                    )
8388                } else {
8389                    None
8390                };
8391                #[cfg(feature = "discovery")]
8392                self.refresh_ingress_peer_liveness(side, &body[off..]);
8393                #[cfg(all(feature = "discovery", not(feature = "std")))]
8394                if self.consume_known_packed_schema(side, &body[off..])? {
8395                    return Ok(None);
8396                }
8397                self.admit_received_frame(side, &body[off..])?;
8398                let raw = Arc::<[u8]>::from(&body[off..]);
8399                if let (Some(template_id), Ok((template, _, _, _, timestamp, _, _, _))) =
8400                    (template_id, extract_side_header_template(raw.as_ref()))
8401                {
8402                    let mut st = self.state.lock();
8403                    let max_templates = st
8404                        .sides
8405                        .get(side)
8406                        .and_then(|side| side.as_ref())
8407                        .map(|side| side.opts.max_side_transport_templates)
8408                        .unwrap_or(DEFAULT_SIDE_TRANSPORT_TEMPLATE_LIMIT);
8409                    let evicted = st.side_transport.get_mut(&side).is_some_and(|side_state| {
8410                        let evicted =
8411                            side_state.insert_rx_template(template_id, template, max_templates);
8412                        if max_templates > 0 {
8413                            side_state.rx_last_timestamps.insert(template_id, timestamp);
8414                        }
8415                        evicted
8416                    });
8417                    if evicted {
8418                        st.side_runtime_stats
8419                            .entry(side)
8420                            .or_default()
8421                            .note_side_transport_template_eviction();
8422                    }
8423                }
8424                Ok(Some(crate::shared_bytes::convert(raw)))
8425            }
8426            SIDE_TRANSPORT_KIND_COMPACT
8427            | SIDE_TRANSPORT_KIND_COMPACT_DELTA
8428            | SIDE_TRANSPORT_KIND_COMPACT_SAME_TIMESTAMP => {
8429                if body.is_empty() {
8430                    return Err(TelemetryError::Unpack("short side compact frame"));
8431                }
8432                let mut off = 1usize;
8433                let template_id = u32::try_from(read_uleb128_local(body, &mut off)?)
8434                    .map_err(|_| TelemetryError::Unpack("side template id too large"))?;
8435                let (template, timestamp_base) = {
8436                    let st = self.state.lock();
8437                    let state = st.side_transport.get(&side);
8438                    let template = state
8439                        .and_then(|state| state.rx_templates_by_id.get(&template_id))
8440                        .cloned();
8441                    let timestamp_base = if matches!(
8442                        kind,
8443                        SIDE_TRANSPORT_KIND_COMPACT_DELTA
8444                            | SIDE_TRANSPORT_KIND_COMPACT_SAME_TIMESTAMP
8445                    ) {
8446                        state
8447                            .and_then(|state| state.rx_last_timestamps.get(&template_id))
8448                            .copied()
8449                    } else {
8450                        None
8451                    };
8452                    (template, timestamp_base)
8453                };
8454                /* A full template frame can be lost on a datagram/radio side.
8455                 * Drop this compact frame without tearing down the transport;
8456                 * the sender periodically emits a full refresh. */
8457                let Some(template) = template else {
8458                    return Ok(None);
8459                };
8460                // The wrapper CRC and known template identify this peer even
8461                // if payload allocation is refused. No payload copy is needed.
8462                #[cfg(feature = "discovery")]
8463                if let Some(address) = template.source_address() {
8464                    self.refresh_peer_liveness(side, address);
8465                }
8466                let reconstructed_bound = body
8467                    .len()
8468                    .saturating_add(template.prefix.len())
8469                    .saturating_add(template.between.len())
8470                    .saturating_add(32);
8471                // Wrapper CRC and the retained full template are validated
8472                // before compact reconstruction. Preserve the same control
8473                // priority as canonical ingress while reclaiming only bulk.
8474                let protected = template
8475                    .data_type()
8476                    .is_some_and(|ty| message_priority(ty) >= 200);
8477                let ack = template.is_ack();
8478                loop {
8479                    match crate::memory_admission::check_frame(reconstructed_bound, ack) {
8480                        Err(TelemetryError::Io("memory pressure")) if protected => {
8481                            let mut st = self.state.lock();
8482                            let reclaimed = st
8483                                .lower_priority_shared_queue(199)
8484                                .is_some_and(|queue| st.pop_shared_queue_item(queue));
8485                            if !reclaimed {
8486                                return Err(TelemetryError::Io("memory pressure"));
8487                            }
8488                        }
8489                        result => {
8490                            result?;
8491                            break;
8492                        }
8493                    }
8494                }
8495                let timestamp_mode = match kind {
8496                    SIDE_TRANSPORT_KIND_COMPACT_DELTA => SideCompactTimestampMode::Delta,
8497                    SIDE_TRANSPORT_KIND_COMPACT_SAME_TIMESTAMP => SideCompactTimestampMode::Omitted,
8498                    _ => SideCompactTimestampMode::Absolute,
8499                };
8500                let (frame, timestamp) = reconstruct_side_compact_frame(
8501                    &template,
8502                    body[0],
8503                    &body[off..],
8504                    timestamp_mode,
8505                    timestamp_base,
8506                )?;
8507                let mut st = self.state.lock();
8508                if let Some(side_state) = st.side_transport.get_mut(&side) {
8509                    side_state.rx_last_timestamps.insert(template_id, timestamp);
8510                }
8511                Ok(Some(frame))
8512            }
8513            SIDE_TRANSPORT_KIND_CHUNK => {
8514                crate::memory_admission::check_receive(bytes)?;
8515                if body.len() < 8 {
8516                    return Err(TelemetryError::Unpack("short side chunk frame"));
8517                }
8518                let transfer_id = u32::from_le_bytes([body[0], body[1], body[2], body[3]]);
8519                let index = u16::from_le_bytes([body[4], body[5]]);
8520                let total = u16::from_le_bytes([body[6], body[7]]);
8521                if total == 0 || index >= total {
8522                    return Err(TelemetryError::Unpack("invalid side chunk index"));
8523                }
8524                let assembled = {
8525                    let mut st = self.state.lock();
8526                    let budget = st.memory.max_queue_budget;
8527                    let now_ms = self.clock.now_ms();
8528                    let side_state = st
8529                        .side_transport
8530                        .get_mut(&side)
8531                        .ok_or(TelemetryError::BadArg)?;
8532                    side_state
8533                        .rx_chunks
8534                        .retain(|_, assembly| now_ms.saturating_sub(assembly.last_seen_ms) <= 2000);
8535                    if !side_state.rx_chunks.contains_key(&transfer_id)
8536                        && side_state.rx_chunks.len() >= 4
8537                        && let Some(oldest) = side_state
8538                            .rx_chunks
8539                            .iter()
8540                            .min_by_key(|(_, a)| a.last_seen_ms)
8541                            .map(|(id, _)| *id)
8542                    {
8543                        side_state.rx_chunks.remove(&oldest);
8544                    }
8545                    SideChunkAssembly::make_room(
8546                        &mut side_state.rx_chunks,
8547                        transfer_id,
8548                        index,
8549                        total,
8550                        body.len() - 8,
8551                        budget,
8552                    )?;
8553                    let entry = side_state.rx_chunks.entry(transfer_id).or_default();
8554                    entry.last_seen_ms = now_ms;
8555                    if entry.total == 0 {
8556                        entry.total = total;
8557                    } else if entry.total != total {
8558                        side_state.rx_chunks.remove(&transfer_id);
8559                        return Err(TelemetryError::Unpack("side chunk total mismatch"));
8560                    }
8561                    entry.received.entry(index).or_insert_with(|| {
8562                        crate::shared_bytes::convert(Arc::<[u8]>::from(&body[8..]))
8563                    });
8564                    if entry.received.len() == usize::from(total) {
8565                        let entry = side_state
8566                            .rx_chunks
8567                            .remove(&transfer_id)
8568                            .ok_or(TelemetryError::Unpack("side chunk missing"))?;
8569                        Some(entry.assemble()?)
8570                    } else {
8571                        None
8572                    }
8573                };
8574                match assembled {
8575                    Some(frame) => self.decode_side_transport_frame(side, frame.as_ref()),
8576                    None => Ok(None),
8577                }
8578            }
8579            _ => Err(TelemetryError::Unpack("unknown side transport frame")),
8580        }
8581    }
8582
8583    fn packed_handler_call(
8584        handler: &RouterTxHandlerFn,
8585        bytes: &[u8],
8586        priority: u8,
8587    ) -> TelemetryResult<()> {
8588        match handler {
8589            RouterTxHandlerFn::Packed(handler) => handler(bytes),
8590            RouterTxHandlerFn::PackedWithPriority(handler) => handler(bytes, priority),
8591            RouterTxHandlerFn::Packet(_) => Err(TelemetryError::BadArg),
8592        }
8593    }
8594
8595    fn effective_transport_priority(&self, ty: DataType) -> u8 {
8596        if self.is_managed_variable_type(ty) {
8597            crate::transport_priority(DataType::ManagedVariableValue)
8598        } else {
8599            crate::transport_priority(ty)
8600        }
8601    }
8602
8603    fn call_side_tx_handler(
8604        &self,
8605        side: RouterSideId,
8606        handler: &RouterTxHandlerFn,
8607        data: &RouterItem,
8608        relayed: bool,
8609    ) -> TelemetryResult<()> {
8610        let opts = {
8611            let st = self.state.lock();
8612            Self::side_ref(&st, side)?.opts.clone()
8613        };
8614        let Some(_side_tx_guard) = self.try_enter_side_tx() else {
8615            return Err(TelemetryError::Io("side tx busy"));
8616        };
8617        let started_ms = self.clock.now_ms();
8618        let ty = match data {
8619            RouterItem::Packet(pkt) => pkt.data_type(),
8620            RouterItem::Packed(bytes) => wire_format::peek_routing_envelope(bytes.as_ref())?.ty,
8621        };
8622        let priority = self.effective_transport_priority(ty);
8623        let result = match (handler, data) {
8624            (
8625                packed @ (RouterTxHandlerFn::Packed(_) | RouterTxHandlerFn::PackedWithPriority(_)),
8626                RouterItem::Packed(bytes),
8627            ) => {
8628                #[cfg(feature = "cryptography")]
8629                let send_bytes = self.prepare_packed_for_remote(bytes.clone(), None)?;
8630                #[cfg(not(feature = "cryptography"))]
8631                let send_bytes = bytes.clone();
8632                let frames = self.encode_side_transport_frames(side, opts, send_bytes)?;
8633                let mut attempts_total = 0usize;
8634                let mut sent_bytes = 0usize;
8635                self.send_side_frames_resumable(side, frames, |frame| {
8636                    match self.retry_with_attempts(runtime_max_handler_retries(), || {
8637                        Self::packed_handler_call(packed, frame, priority)
8638                    }) {
8639                        Ok((_, attempts)) => {
8640                            attempts_total = attempts_total.saturating_add(attempts);
8641                            sent_bytes = sent_bytes.saturating_add(frame.len());
8642                        }
8643                        Err((err, attempts)) => {
8644                            self.note_side_tx_failure(
8645                                side,
8646                                ty,
8647                                attempts_total.saturating_add(attempts),
8648                            );
8649                            return Err(err);
8650                        }
8651                    }
8652
8653                    Ok(())
8654                })?;
8655                self.record_side_tx_sample(side, sent_bytes, started_ms, self.clock.now_ms());
8656                self.note_side_tx_success(side, ty, sent_bytes, relayed, attempts_total);
8657                return Ok(());
8658            }
8659            (RouterTxHandlerFn::Packet(f), RouterItem::Packet(pkt)) => {
8660                self.retry_with_attempts(runtime_max_handler_retries(), || f(pkt))
8661            }
8662            (
8663                packed @ (RouterTxHandlerFn::Packed(_) | RouterTxHandlerFn::PackedWithPriority(_)),
8664                RouterItem::Packet(pkt),
8665            ) => {
8666                // Internal schemas are plaintext and use self-describing frames.
8667                // This path is reached after hop-reliable handling; no replay
8668                // buffer is needed here. Stream fragments from the payload.
8669                if ty == DataType::DiscoverySchema
8670                    && opts.max_frame_bytes != 0
8671                    && pkt.payload().len() > opts.max_frame_bytes
8672                {
8673                    let mut attempts_total = 0usize;
8674                    let mut sent_bytes = 0usize;
8675                    let key = crate::packet::hash_bytes_u64(
8676                        pkt.packet_id(),
8677                        &opts.max_frame_bytes.to_le_bytes(),
8678                    );
8679                    let result = self.run_side_chunk_transfer(side, key, |next| {
8680                        crate::side_transport::send_packet_chunks(
8681                            pkt,
8682                            opts.max_frame_bytes,
8683                            self.sender_arc().as_bytes(),
8684                            next,
8685                            |frame| match self
8686                                .retry_with_attempts(runtime_max_handler_retries(), || {
8687                                    Self::packed_handler_call(packed, frame, priority)
8688                                }) {
8689                                Ok((_, attempts)) => {
8690                                    attempts_total += attempts;
8691                                    sent_bytes += frame.len();
8692                                    Ok(())
8693                                }
8694                                Err((err, attempts)) => {
8695                                    attempts_total += attempts;
8696                                    Err(err)
8697                                }
8698                            },
8699                        )
8700                    });
8701                    let (raw_len, wire_len, chunks) = match result {
8702                        Ok(stats) => stats,
8703                        Err(err) => {
8704                            self.note_side_tx_failure(side, ty, attempts_total);
8705                            return Err(err);
8706                        }
8707                    };
8708                    {
8709                        let mut st = self.state.lock();
8710                        let stats = st.side_runtime_stats.entry(side).or_default();
8711                        stats.note_side_transport_full(raw_len, wire_len);
8712                        if chunks > 1 {
8713                            stats.note_side_transport_chunks(chunks);
8714                        }
8715                    }
8716                    self.record_side_tx_sample(side, sent_bytes, started_ms, self.clock.now_ms());
8717                    self.note_side_tx_success(side, ty, sent_bytes, relayed, attempts_total);
8718                    return Ok(());
8719                }
8720                let owned = self.pack_packet_for_router(pkt, None)?;
8721                let frames = self.encode_side_transport_frames(side, opts, owned)?;
8722                let mut attempts_total = 0usize;
8723                let mut sent_bytes = 0usize;
8724                self.send_side_frames_resumable(side, frames, |frame| {
8725                    match self.retry_with_attempts(runtime_max_handler_retries(), || {
8726                        Self::packed_handler_call(packed, frame, priority)
8727                    }) {
8728                        Ok((_, attempts)) => {
8729                            attempts_total = attempts_total.saturating_add(attempts);
8730                            sent_bytes = sent_bytes.saturating_add(frame.len());
8731                        }
8732                        Err((err, attempts)) => {
8733                            self.note_side_tx_failure(
8734                                side,
8735                                ty,
8736                                attempts_total.saturating_add(attempts),
8737                            );
8738                            return Err(err);
8739                        }
8740                    }
8741
8742                    Ok(())
8743                })?;
8744                self.record_side_tx_sample(side, sent_bytes, started_ms, self.clock.now_ms());
8745                self.note_side_tx_success(side, ty, sent_bytes, relayed, attempts_total);
8746                return Ok(());
8747            }
8748            (RouterTxHandlerFn::Packet(f), RouterItem::Packed(bytes)) => {
8749                let pkt = wire_format::unpack_packet(bytes.as_ref())?;
8750                self.retry_with_attempts(runtime_max_handler_retries(), || f(&pkt))
8751            }
8752        };
8753        match result {
8754            Ok((_, attempts)) => {
8755                if let Ok(bytes) = Self::router_item_wire_len(data) {
8756                    self.record_side_tx_sample(side, bytes, started_ms, self.clock.now_ms());
8757                    self.note_side_tx_success(side, ty, bytes, relayed, attempts);
8758                }
8759                Ok(())
8760            }
8761            Err((err, attempts)) => {
8762                self.note_side_tx_failure(side, ty, attempts);
8763                Err(err)
8764            }
8765        }
8766    }
8767
8768    fn adjust_reliable_for_side(
8769        &self,
8770        opts: RouterSideOptions,
8771        data: RouterItem,
8772        preserve_end_to_end_ack: bool,
8773    ) -> TelemetryResult<Option<RouterItem>> {
8774        if opts.reliable_enabled {
8775            return Ok(Some(data));
8776        }
8777
8778        match data {
8779            RouterItem::Packed(bytes) => {
8780                let frame = wire_format::peek_routing_frame_info(bytes.as_ref())?;
8781                if is_reliable_type(frame.envelope.ty)
8782                    && let Some(hdr) = frame.reliable
8783                {
8784                    if (hdr.flags & wire_format::RELIABLE_FLAG_ACK_ONLY) != 0 {
8785                        return Ok(None);
8786                    }
8787                    if (hdr.flags & wire_format::RELIABLE_FLAG_UNSEQUENCED) == 0 {
8788                        let Some(rewritten) = wire_format::rewrite_reliable_header_owned(
8789                            bytes.as_ref(),
8790                            wire_format::RELIABLE_FLAG_UNSEQUENCED,
8791                            hdr.seq,
8792                            0,
8793                        )?
8794                        else {
8795                            return Ok(Some(RouterItem::Packed(bytes)));
8796                        };
8797                        return Ok(Some(RouterItem::Packed(crate::shared_bytes::convert(
8798                            rewritten,
8799                        ))));
8800                    }
8801                }
8802                Ok(Some(RouterItem::Packed(bytes)))
8803            }
8804            RouterItem::Packet(pkt) => {
8805                if matches!(
8806                    pkt.data_type(),
8807                    DataType::ReliableAck
8808                        | DataType::ReliablePartialAck
8809                        | DataType::ReliablePacketRequest
8810                ) {
8811                    if preserve_end_to_end_ack && Self::is_end_to_end_ack_packet(&pkt) {
8812                        return Ok(Some(RouterItem::Packet(pkt)));
8813                    }
8814                    return Ok(None);
8815                }
8816                Ok(Some(RouterItem::Packet(pkt)))
8817            }
8818        }
8819    }
8820
8821    fn process_reliable_timeouts(&self) -> TelemetryResult<()> {
8822        let now = self.clock.now_ms();
8823        let mut requeue: Vec<(RouterSideId, DataType, u32)> = Vec::new();
8824
8825        {
8826            let mut st = self.state.lock();
8827            if st.reliable_tx.is_empty() {
8828                return Ok(());
8829            }
8830
8831            for ((side, ty_u32), tx_state) in st.reliable_tx.iter_mut() {
8832                let Some(ty) = DataType::try_from_u32(*ty_u32) else {
8833                    continue;
8834                };
8835                let sent_order: Vec<u32> = tx_state.sent_order.iter().copied().collect();
8836                for seq in sent_order {
8837                    let Some(sent) = tx_state.sent.get_mut(&seq) else {
8838                        continue;
8839                    };
8840                    if sent.queued
8841                        || now.wrapping_sub(sent.last_send_ms) < runtime_reliable_retransmit_ms()
8842                    {
8843                        continue;
8844                    }
8845                    if sent.partial_acked {
8846                        // A partial ACK normally arrives with a request for
8847                        // the missing sequence. If that request is itself
8848                        // lost, retaining this flag forever pins the history
8849                        // entry and eventually rejects unrelated traffic with
8850                        // `reliable history full`. Give the request one retry
8851                        // interval, then resume normal bounded retransmission.
8852                        sent.partial_acked = false;
8853                        sent.last_send_ms = now;
8854                        continue;
8855                    }
8856                    if sent.retries >= runtime_reliable_max_retries() {
8857                        tx_state.sent.remove(&seq);
8858                        tx_state.sent_order.retain(|existing| *existing != seq);
8859                        continue;
8860                    }
8861                    sent.retries += 1;
8862                    requeue.push((*side, ty, seq));
8863                }
8864            }
8865        }
8866
8867        for (side, ty, seq) in requeue {
8868            self.queue_reliable_retransmit(side, ty, seq, true)?;
8869        }
8870
8871        Ok(())
8872    }
8873
8874    fn process_end_to_end_reliable_timeouts(&self) -> TelemetryResult<()> {
8875        let now = self.clock.now_ms();
8876        let mut requeue = Vec::new();
8877        let mut failed_ordered_types = BTreeSet::new();
8878
8879        {
8880            let mut st = self.state.lock();
8881            #[cfg(feature = "discovery")]
8882            {
8883                if Self::prune_discovery_routes_locked(&mut st, now) {
8884                    Self::note_discovery_topology_change_locked(&mut st, now);
8885                    self.reconcile_end_to_end_reliable_destinations_locked(&mut st)?;
8886                }
8887                // Route updates reconcile their retained deliveries when the
8888                // topology changes. Rebuilding the destination map on every
8889                // queue item adds allocation and graph scans without changing
8890                // the frozen receipt contract.
8891            }
8892            // Retain walks the bounded pending map in place. A queue pass
8893            // before any retry deadline must not allocate a list of keys.
8894            st.end_to_end_reliable_tx.retain(|&packet_id, sent| {
8895                if sent.waiting_for_predecessor
8896                    || sent.queued
8897                    || now.wrapping_sub(sent.last_send_ms) < runtime_reliable_retransmit_ms()
8898                {
8899                    return true;
8900                }
8901                if sent.retries >= runtime_reliable_max_retries() {
8902                    if let Some(ty) = sent.ordered_type {
8903                        failed_ordered_types.insert(ty);
8904                    }
8905                    return false;
8906                }
8907                sent.retries += 1;
8908                requeue.push(packet_id);
8909                true
8910            });
8911            // Fail the queued tail too; never deliver it past an unconfirmed gap.
8912            st.end_to_end_reliable_tx.retain(|_, sent| {
8913                !sent
8914                    .ordered_type
8915                    .is_some_and(|ty| failed_ordered_types.contains(&ty))
8916            });
8917        }
8918
8919        for packet_id in requeue {
8920            self.queue_end_to_end_reliable_retransmit(packet_id)?;
8921        }
8922        self.release_ordered_successors()?;
8923        if !failed_ordered_types.is_empty() {
8924            return Err(TelemetryError::HandlerError(
8925                "ordered end-to-end delivery exhausted retries",
8926            ));
8927        }
8928        Ok(())
8929    }
8930
8931    #[cfg(feature = "timesync")]
8932    #[inline]
8933    fn monotonic_now_ns(&self) -> u64 {
8934        self.clock.now_ns()
8935    }
8936
8937    #[cfg(feature = "timesync")]
8938    #[inline]
8939    fn monotonic_now_ms(&self) -> u64 {
8940        self.clock.now_ms()
8941    }
8942
8943    #[cfg(feature = "timesync")]
8944    fn refresh_timesync_state(&self, now_mono_ms: u64) {
8945        let now_mono_ns = self.monotonic_now_ns();
8946        let mut st = self.timesync.lock();
8947        st.clock.prune_expired(now_mono_ms);
8948        let timeout_ms = st.cfg.map(|cfg| cfg.source_timeout_ms).unwrap_or(0);
8949        st.remote_sources
8950            .retain(|_, src| now_mono_ms.saturating_sub(src.last_sample_mono_ms) <= timeout_ms);
8951        let has_usable_time = Self::timesync_has_usable_time_locked(&st, now_mono_ns);
8952        let leader = if let Some(tracker) = st.tracker.as_mut() {
8953            let _ = tracker.refresh(now_mono_ms);
8954            tracker.leader(now_mono_ms, has_usable_time)
8955        } else {
8956            None
8957        };
8958        Self::reconcile_pending_timesync_request_locked(&mut st, &leader, now_mono_ms);
8959        if let Some(TimeSyncLeader::Remote(remote)) = leader.as_ref() {
8960            let target_ms = st
8961                .remote_sources
8962                .get(remote.sender.as_str())
8963                .map(|src| src.sample_unix_ms);
8964            if let Some(target_ms) = target_ms {
8965                st.disciplined_clock.steer_unix_ms(now_mono_ns, target_ms);
8966            }
8967        }
8968    }
8969
8970    #[cfg(feature = "timesync")]
8971    /// Inserts or updates a named network-time source with an optional expiration TTL.
8972    pub fn update_network_time_source(
8973        &self,
8974        source: &str,
8975        priority: u64,
8976        time: PartialNetworkTime,
8977        ttl_ms: Option<u64>,
8978    ) {
8979        let now_ms = self.monotonic_now_ms();
8980        let now_ns = self.monotonic_now_ns();
8981        let mut st = self.timesync.lock();
8982        st.clock
8983            .update_source(source, priority, time, now_ms, now_ns, ttl_ms);
8984        if let Some(unix_ms) = time.to_network_time().and_then(|t| t.as_unix_ms()) {
8985            st.disciplined_clock.steer_unix_ms(now_ns, unix_ms);
8986        }
8987    }
8988
8989    #[cfg(feature = "timesync")]
8990    fn set_network_time_source_impl(
8991        &self,
8992        source: &str,
8993        priority: u64,
8994        time: PartialNetworkTime,
8995        ttl_ms: Option<u64>,
8996    ) {
8997        let observed_mono_ms = self.monotonic_now_ms();
8998        let observed_mono_ns = self.monotonic_now_ns();
8999        let mut st = self.timesync.lock();
9000        let commit_mono_ms = self.monotonic_now_ms();
9001        let commit_mono_ns = self.monotonic_now_ns();
9002        let adjusted = if let Some(base) = time.to_network_time() {
9003            let elapsed_ns = commit_mono_ns.saturating_sub(observed_mono_ns);
9004            advance_network_time(base, elapsed_ns)
9005                .map(PartialNetworkTime::from)
9006                .unwrap_or(time)
9007        } else {
9008            time
9009        };
9010        let adjusted_mono_ms =
9011            observed_mono_ms.saturating_add(commit_mono_ms.saturating_sub(observed_mono_ms));
9012        st.clock.update_source(
9013            source,
9014            priority,
9015            adjusted,
9016            commit_mono_ms.max(adjusted_mono_ms),
9017            commit_mono_ns,
9018            ttl_ms,
9019        );
9020        if let Some(unix_ms) = adjusted.to_network_time().and_then(|t| t.as_unix_ms()) {
9021            st.disciplined_clock.steer_unix_ms(commit_mono_ns, unix_ms);
9022        }
9023    }
9024
9025    #[cfg(feature = "timesync")]
9026    fn local_network_time_priority(&self) -> u64 {
9027        let st = self.timesync.lock();
9028        st.cfg.map(|cfg| cfg.priority).unwrap_or(0)
9029    }
9030
9031    #[cfg(feature = "timesync")]
9032    /// Sets the local node's network time using any combination of date, time, and sub-second fields.
9033    pub fn set_local_network_time(&self, time: PartialNetworkTime) {
9034        let priority = self.local_network_time_priority();
9035        if time.is_complete_date() && time.is_complete_time() {
9036            self.set_network_time_source_impl(LOCAL_TIMESYNC_FULL_SOURCE_ID, priority, time, None);
9037            let mut st = self.timesync.lock();
9038            st.clock.remove_source(LOCAL_TIMESYNC_DATE_SOURCE_ID);
9039            st.clock.remove_source(LOCAL_TIMESYNC_TOD_SOURCE_ID);
9040            st.clock.remove_source(LOCAL_TIMESYNC_SUBSEC_SOURCE_ID);
9041            return;
9042        }
9043
9044        {
9045            let mut st = self.timesync.lock();
9046            st.clock.remove_source(LOCAL_TIMESYNC_FULL_SOURCE_ID);
9047        }
9048
9049        if time.year.is_some() || time.month.is_some() || time.day.is_some() {
9050            self.set_network_time_source_impl(
9051                LOCAL_TIMESYNC_DATE_SOURCE_ID,
9052                priority,
9053                PartialNetworkTime {
9054                    year: time.year,
9055                    month: time.month,
9056                    day: time.day,
9057                    ..Default::default()
9058                },
9059                None,
9060            );
9061        }
9062
9063        if time.hour.is_some() || time.minute.is_some() || time.second.is_some() {
9064            self.set_network_time_source_impl(
9065                LOCAL_TIMESYNC_TOD_SOURCE_ID,
9066                priority,
9067                PartialNetworkTime {
9068                    hour: time.hour,
9069                    minute: time.minute,
9070                    second: time.second,
9071                    nanosecond: time.nanosecond,
9072                    ..Default::default()
9073                },
9074                None,
9075            );
9076        }
9077
9078        if time.nanosecond.is_some() {
9079            self.set_network_time_source_impl(
9080                LOCAL_TIMESYNC_SUBSEC_SOURCE_ID,
9081                priority,
9082                PartialNetworkTime {
9083                    nanosecond: time.nanosecond,
9084                    ..Default::default()
9085                },
9086                None,
9087            );
9088        }
9089    }
9090
9091    #[cfg(feature = "timesync")]
9092    /// Removes all locally supplied network-time fragments from the assembled clock.
9093    pub fn clear_local_network_time(&self) {
9094        let mut st = self.timesync.lock();
9095        st.clock.remove_source(LOCAL_TIMESYNC_FULL_SOURCE_ID);
9096        st.clock.remove_source(LOCAL_TIMESYNC_DATE_SOURCE_ID);
9097        st.clock.remove_source(LOCAL_TIMESYNC_TOD_SOURCE_ID);
9098        st.clock.remove_source(LOCAL_TIMESYNC_SUBSEC_SOURCE_ID);
9099    }
9100
9101    #[cfg(feature = "timesync")]
9102    /// Sets only the local calendar date portion of network time.
9103    pub fn set_local_network_date(&self, year: i32, month: u8, day: u8) {
9104        self.set_local_network_time(PartialNetworkTime {
9105            year: Some(year),
9106            month: Some(month),
9107            day: Some(day),
9108            ..Default::default()
9109        });
9110    }
9111
9112    #[cfg(feature = "timesync")]
9113    /// Sets the local time of day to hour and minute precision.
9114    pub fn set_local_network_time_hm(&self, hour: u8, minute: u8) {
9115        self.set_local_network_time(PartialNetworkTime {
9116            hour: Some(hour),
9117            minute: Some(minute),
9118            ..Default::default()
9119        });
9120    }
9121
9122    #[cfg(feature = "timesync")]
9123    /// Sets the local time of day to second precision.
9124    pub fn set_local_network_time_hms(&self, hour: u8, minute: u8, second: u8) {
9125        self.set_local_network_time(PartialNetworkTime {
9126            hour: Some(hour),
9127            minute: Some(minute),
9128            second: Some(second),
9129            ..Default::default()
9130        });
9131    }
9132
9133    #[cfg(feature = "timesync")]
9134    /// Sets the local time of day with millisecond precision.
9135    pub fn set_local_network_time_hms_millis(
9136        &self,
9137        hour: u8,
9138        minute: u8,
9139        second: u8,
9140        millisecond: u16,
9141    ) {
9142        self.set_local_network_time(PartialNetworkTime {
9143            hour: Some(hour),
9144            minute: Some(minute),
9145            second: Some(second),
9146            nanosecond: Some((millisecond as u32).saturating_mul(1_000_000)),
9147            ..Default::default()
9148        });
9149    }
9150
9151    #[cfg(feature = "timesync")]
9152    /// Sets the local time of day with nanosecond precision.
9153    pub fn set_local_network_time_hms_nanos(
9154        &self,
9155        hour: u8,
9156        minute: u8,
9157        second: u8,
9158        nanosecond: u32,
9159    ) {
9160        self.set_local_network_time(PartialNetworkTime {
9161            hour: Some(hour),
9162            minute: Some(minute),
9163            second: Some(second),
9164            nanosecond: Some(nanosecond),
9165            ..Default::default()
9166        });
9167    }
9168
9169    #[cfg(feature = "timesync")]
9170    /// Sets a complete local date and time with second precision.
9171    pub fn set_local_network_datetime(
9172        &self,
9173        year: i32,
9174        month: u8,
9175        day: u8,
9176        hour: u8,
9177        minute: u8,
9178        second: u8,
9179    ) {
9180        self.set_local_network_time(PartialNetworkTime {
9181            year: Some(year),
9182            month: Some(month),
9183            day: Some(day),
9184            hour: Some(hour),
9185            minute: Some(minute),
9186            second: Some(second),
9187            ..Default::default()
9188        });
9189    }
9190
9191    #[cfg(feature = "timesync")]
9192    #[allow(clippy::too_many_arguments)]
9193    /// Sets a complete local date and time with millisecond precision.
9194    pub fn set_local_network_datetime_millis(
9195        &self,
9196        year: i32,
9197        month: u8,
9198        day: u8,
9199        hour: u8,
9200        minute: u8,
9201        second: u8,
9202        millisecond: u16,
9203    ) {
9204        self.set_local_network_time(PartialNetworkTime {
9205            year: Some(year),
9206            month: Some(month),
9207            day: Some(day),
9208            hour: Some(hour),
9209            minute: Some(minute),
9210            second: Some(second),
9211            nanosecond: Some((millisecond as u32).saturating_mul(1_000_000)),
9212        });
9213    }
9214
9215    #[cfg(feature = "timesync")]
9216    #[allow(clippy::too_many_arguments)]
9217    /// Sets a complete local date and time with nanosecond precision.
9218    pub fn set_local_network_datetime_nanos(
9219        &self,
9220        year: i32,
9221        month: u8,
9222        day: u8,
9223        hour: u8,
9224        minute: u8,
9225        second: u8,
9226        nanosecond: u32,
9227    ) {
9228        self.set_local_network_time(PartialNetworkTime {
9229            year: Some(year),
9230            month: Some(month),
9231            day: Some(day),
9232            hour: Some(hour),
9233            minute: Some(minute),
9234            second: Some(second),
9235            nanosecond: Some(nanosecond),
9236        });
9237    }
9238
9239    #[cfg(feature = "timesync")]
9240    /// Removes a previously registered named network-time source.
9241    pub fn clear_network_time_source(&self, source: &str) {
9242        let mut st = self.timesync.lock();
9243        st.clock.remove_source(source);
9244    }
9245
9246    #[cfg(feature = "timesync")]
9247    /// Replaces the active time sync configuration and resets runtime state derived from it.
9248    pub fn set_timesync_config(&self, cfg: Option<TimeSyncConfig>) {
9249        let mut st = self.timesync.lock();
9250        let stale_remote_sources: Vec<String> = st.remote_sources.keys().cloned().collect();
9251        st.cfg = cfg;
9252        st.tracker = cfg.map(TimeSyncTracker::new);
9253        st.disciplined_clock = SlewedNetworkClock::new(
9254            cfg.map(|c| c.max_slew_ppm)
9255                .unwrap_or(TimeSyncConfig::default().max_slew_ppm),
9256        );
9257        st.remote_sources.clear();
9258        st.next_seq = 1;
9259        st.next_announce_mono_ms = 0;
9260        st.next_request_mono_ms = 0;
9261        st.pending_request = None;
9262        st.clock.remove_source(INTERNAL_TIMESYNC_SOURCE_ID);
9263        for source in stale_remote_sources {
9264            st.clock.remove_source(&source);
9265        }
9266        st.clock.remove_source(LOCAL_TIMESYNC_FULL_SOURCE_ID);
9267        st.clock.remove_source(LOCAL_TIMESYNC_DATE_SOURCE_ID);
9268        st.clock.remove_source(LOCAL_TIMESYNC_TOD_SOURCE_ID);
9269        st.clock.remove_source(LOCAL_TIMESYNC_SUBSEC_SOURCE_ID);
9270    }
9271
9272    #[cfg(feature = "timesync")]
9273    /// Returns the best currently known network-time reading, if any.
9274    pub fn network_time(&self) -> Option<NetworkTimeReading> {
9275        let now_ms = self.monotonic_now_ms();
9276        let now_ns = self.monotonic_now_ns();
9277        self.refresh_timesync_state(now_ms);
9278        let st = self.timesync.lock();
9279        if let Some(unix_ms) = st.disciplined_clock.read_unix_ms(now_ns) {
9280            return Some(NetworkTimeReading {
9281                time: PartialNetworkTime::from_unix_ms(unix_ms),
9282                unix_time_ms: Some(unix_ms),
9283            });
9284        }
9285        st.clock.current_time(now_ns)
9286    }
9287
9288    #[cfg(feature = "timesync")]
9289    /// Returns the current network time as Unix milliseconds when available.
9290    pub fn network_time_ms(&self) -> Option<u64> {
9291        self.network_time().and_then(|t| t.unix_time_ms)
9292    }
9293
9294    #[cfg(feature = "timesync")]
9295    fn packet_timestamp_ms(&self) -> u64 {
9296        self.network_time_ms()
9297            .unwrap_or_else(|| self.monotonic_now_ms())
9298    }
9299
9300    #[cfg(not(feature = "timesync"))]
9301    fn packet_timestamp_ms(&self) -> u64 {
9302        self.clock.now_ms()
9303    }
9304
9305    #[cfg(feature = "timesync")]
9306    fn queue_internal_timesync_request(
9307        &self,
9308        seq: u64,
9309        t1_mono_ms: u64,
9310        called_from_queue: bool,
9311    ) -> TelemetryResult<()> {
9312        let pkt_ts = self.packet_timestamp_ms();
9313        if called_from_queue {
9314            self.log_queue_ts(DataType::TimeSyncRequest, pkt_ts, &[seq, t1_mono_ms])
9315        } else {
9316            self.log_ts(DataType::TimeSyncRequest, pkt_ts, &[seq, t1_mono_ms])
9317        }
9318    }
9319
9320    #[cfg(feature = "timesync")]
9321    fn queue_internal_timesync_response(
9322        &self,
9323        seq: u64,
9324        t1_mono_ms: u64,
9325        t2_network_ms: u64,
9326        t3_network_ms: u64,
9327        dst: Option<RouterSideId>,
9328        called_from_queue: bool,
9329    ) -> TelemetryResult<()> {
9330        let pkt_ts = self.packet_timestamp_ms();
9331        let payload = encode_slice_le(&[seq, t1_mono_ms, t2_network_ms, t3_network_ms]);
9332        let sender = self.sender_arc();
9333        let pkt = Packet::new(
9334            DataType::TimeSyncResponse,
9335            &[DataEndpoint::TimeSync],
9336            sender.as_ref(),
9337            pkt_ts,
9338            payload,
9339        )?;
9340        match dst {
9341            Some(dst) => self.emit_internal_tx(
9342                RouterTxItem::ToSide {
9343                    src: None,
9344                    dst,
9345                    data: RouterItem::Packet(pkt),
9346                },
9347                true,
9348                called_from_queue,
9349            ),
9350            None => self.emit_internal_tx(
9351                RouterTxItem::Broadcast(RouterItem::Packet(pkt)),
9352                true,
9353                called_from_queue,
9354            ),
9355        }
9356    }
9357
9358    #[cfg(feature = "timesync")]
9359    /// Runs one time sync maintenance cycle and queues any required announce or request packets.
9360    pub fn poll_timesync(&self) -> TelemetryResult<bool> {
9361        let now_ms = self.monotonic_now_ms();
9362        let now_ns = self.monotonic_now_ns();
9363        let mut queued_any = false;
9364        let mut announce_priority = None;
9365        let mut request = None;
9366
9367        {
9368            let mut st = self.timesync.lock();
9369            st.clock.prune_expired(now_ms);
9370            let timeout_ms = st.cfg.map(|cfg| cfg.source_timeout_ms).unwrap_or(0);
9371            st.remote_sources
9372                .retain(|_, src| now_ms.saturating_sub(src.last_sample_mono_ms) <= timeout_ms);
9373            let Some(cfg) = st.cfg else {
9374                return Ok(false);
9375            };
9376
9377            let has_usable_time = Self::timesync_has_usable_time_locked(&st, now_ns);
9378            let (leader, announce_prio) = if let Some(tracker) = st.tracker.as_mut() {
9379                let _ = tracker.refresh(now_ms);
9380                (
9381                    tracker.leader(now_ms, has_usable_time),
9382                    tracker.local_announce_priority(now_ms, has_usable_time),
9383                )
9384            } else {
9385                (None, None)
9386            };
9387            Self::reconcile_pending_timesync_request_locked(&mut st, &leader, now_ms);
9388
9389            if let Some(TimeSyncLeader::Remote(remote)) = leader.as_ref() {
9390                let target_ms = st
9391                    .remote_sources
9392                    .get(&remote.sender)
9393                    .map(|src| src.sample_unix_ms);
9394                if let Some(target_ms) = target_ms {
9395                    st.disciplined_clock.steer_unix_ms(now_ns, target_ms);
9396                }
9397            }
9398
9399            if let Some(priority) = announce_prio
9400                && now_ms >= st.next_announce_mono_ms
9401            {
9402                announce_priority = Some(priority);
9403                st.next_announce_mono_ms = now_ms.saturating_add(cfg.announce_interval_ms);
9404            }
9405
9406            if let Some(TimeSyncLeader::Remote(remote)) = leader
9407                && now_ms >= st.next_request_mono_ms
9408                && st.pending_request.is_none()
9409            {
9410                let seq = st.next_seq;
9411                let next = st.next_seq.wrapping_add(1);
9412                st.next_seq = if next == 0 { 1 } else { next };
9413                st.next_request_mono_ms = now_ms.saturating_add(cfg.request_interval_ms);
9414                st.pending_request = Some(PendingTimeSyncRequest {
9415                    seq,
9416                    t1_mono_ms: now_ms,
9417                    source: remote.sender,
9418                });
9419                request = Some((seq, now_ms));
9420            }
9421        }
9422
9423        if let Some(priority) = announce_priority {
9424            let time_ms = self.packet_timestamp_ms();
9425            self.log_queue_ts(DataType::TimeSyncAnnounce, time_ms, &[priority, time_ms])?;
9426            queued_any = true;
9427        }
9428        if let Some((seq, t1_mono_ms)) = request {
9429            self.queue_internal_timesync_request(seq, t1_mono_ms, true)?;
9430            queued_any = true;
9431        }
9432
9433        Ok(queued_any)
9434    }
9435
9436    #[cfg(feature = "timesync")]
9437    fn handle_internal_timesync_packet(
9438        &self,
9439        pkt: &Packet,
9440        src: Option<RouterSideId>,
9441        called_from_queue: bool,
9442    ) -> TelemetryResult<bool> {
9443        let Some(cfg) = self.cfg.timesync_config() else {
9444            if self.should_route_remote(&RouterItem::Packet(pkt.clone()), src)? {
9445                self.relay_send(RouterItem::Packet(pkt.clone()), src, called_from_queue)?;
9446            }
9447            return Ok(true);
9448        };
9449
9450        let now_mono_ms = self.monotonic_now_ms();
9451        let now_mono_ns = self.monotonic_now_ns();
9452        let mut response = None;
9453        let mut poll_after = false;
9454
9455        {
9456            let mut st = self.timesync.lock();
9457            st.clock.prune_expired(now_mono_ms);
9458            let timeout_ms = st.cfg.map(|cfg| cfg.source_timeout_ms).unwrap_or(0);
9459            st.remote_sources
9460                .retain(|_, src| now_mono_ms.saturating_sub(src.last_sample_mono_ms) <= timeout_ms);
9461            let has_usable_time = Self::timesync_has_usable_time_locked(&st, now_mono_ns);
9462            if st.tracker.is_none() {
9463                return Ok(true);
9464            }
9465
9466            match pkt.data_type() {
9467                DataType::TimeSyncAnnounce => {
9468                    let ann = decode_timesync_announce(pkt)?;
9469                    let should_steer = {
9470                        let tracker = st.tracker.as_mut().expect("tracker checked above");
9471                        let _ = tracker.handle_announce(pkt, now_mono_ms)?;
9472                        matches!(
9473                            tracker.leader(now_mono_ms, has_usable_time),
9474                            Some(TimeSyncLeader::Remote(ref remote)) if remote.sender == pkt.sender()
9475                        )
9476                    };
9477                    st.remote_sources.insert(
9478                        pkt.sender().to_owned(),
9479                        RemoteTimeSyncSource {
9480                            priority: ann.priority,
9481                            last_sample_mono_ms: now_mono_ms,
9482                            sample_unix_ms: ann.time_ms,
9483                        },
9484                    );
9485                    st.clock.update_source(
9486                        pkt.sender(),
9487                        ann.priority,
9488                        PartialNetworkTime::from_unix_ms(ann.time_ms),
9489                        now_mono_ms,
9490                        now_mono_ns,
9491                        Some(cfg.source_timeout_ms),
9492                    );
9493                    if should_steer {
9494                        st.disciplined_clock.steer_unix_ms(now_mono_ns, ann.time_ms);
9495                    }
9496                    poll_after = true;
9497                }
9498                DataType::TimeSyncRequest => {
9499                    let should_serve = {
9500                        let tracker = st.tracker.as_ref().expect("tracker checked above");
9501                        tracker.should_serve(now_mono_ms, has_usable_time)
9502                    };
9503                    if should_serve {
9504                        let req = decode_timesync_request(pkt)?;
9505                        let network_now = st
9506                            .disciplined_clock
9507                            .read_unix_ms(now_mono_ns)
9508                            .or_else(|| {
9509                                st.clock
9510                                    .current_time(now_mono_ns)
9511                                    .and_then(|t| t.unix_time_ms)
9512                            })
9513                            .unwrap_or(now_mono_ms);
9514                        let t2 = network_now;
9515                        let t3 = network_now;
9516                        response = Some((req.seq, req.t1_ms, t2, t3, src));
9517                    }
9518                }
9519                DataType::TimeSyncResponse => {
9520                    let resp = decode_timesync_response(pkt)?;
9521                    let pending = st.pending_request.clone();
9522                    if let Some(pending) = pending
9523                        && pending.seq == resp.seq
9524                        && pending.source == pkt.sender()
9525                    {
9526                        let source_priority = {
9527                            let tracker = st.tracker.as_ref().expect("tracker checked above");
9528                            tracker
9529                                .best_active_source(now_mono_ms)
9530                                .map(|s| s.priority)
9531                                .or_else(|| st.remote_sources.get(pkt.sender()).map(|s| s.priority))
9532                                .unwrap_or(cfg.priority)
9533                        };
9534                        let (estimate_ms, _delay_ms) = compute_network_time_sample(
9535                            pending.t1_mono_ms,
9536                            resp.t2_ms,
9537                            resp.t3_ms,
9538                            now_mono_ms,
9539                        );
9540                        st.remote_sources.insert(
9541                            pkt.sender().to_owned(),
9542                            RemoteTimeSyncSource {
9543                                priority: source_priority,
9544                                last_sample_mono_ms: now_mono_ms,
9545                                sample_unix_ms: estimate_ms,
9546                            },
9547                        );
9548                        st.clock.update_source(
9549                            pkt.sender(),
9550                            source_priority,
9551                            PartialNetworkTime::from_unix_ms(estimate_ms),
9552                            now_mono_ms,
9553                            now_mono_ns,
9554                            Some(cfg.source_timeout_ms),
9555                        );
9556                        st.disciplined_clock.steer_unix_ms(now_mono_ns, estimate_ms);
9557                        st.pending_request = None;
9558                    }
9559                }
9560                _ => {}
9561            }
9562        }
9563
9564        if let Some((seq, t1, t2, t3, dst)) = response {
9565            self.queue_internal_timesync_response(seq, t1, t2, t3, dst, called_from_queue)?;
9566        }
9567        if poll_after {
9568            let _ = crate::memory_admission::allow_drain(self.poll_timesync())?;
9569        }
9570
9571        if self.should_route_remote(&RouterItem::Packet(pkt.clone()), src)? {
9572            self.relay_send(RouterItem::Packet(pkt.clone()), src, called_from_queue)?;
9573        }
9574
9575        Ok(true)
9576    }
9577
9578    /// Create a new Router with an internal monotonic clock.
9579    #[cfg(feature = "std")]
9580    pub fn new(cfg: RouterConfig) -> Self {
9581        Self::new_with_clock(cfg, Box::new(StdMonotonicClock::default()))
9582    }
9583
9584    /// Create a new Router with the specified router configuration and clock.
9585    pub fn new_with_clock(cfg: RouterConfig, clock: Box<dyn Clock + Send + Sync>) -> Self {
9586        #[cfg(feature = "timesync")]
9587        let timesync_cfg = cfg.timesync_config();
9588        #[cfg(feature = "discovery")]
9589        let preferred_discovery_master = cfg.preferred_discovery_master.clone();
9590        let memory = cfg.memory_config();
9591        let hostname: Arc<str> = Arc::from(cfg.sender());
9592        let address_mode = cfg.address_mode();
9593        let instance_seq = u64::from(ROUTER_INSTANCE_SEQ.fetch_add(1, Ordering::Relaxed));
9594        let owner_hash = Self::sender_hash(hostname.as_ref()) ^ instance_seq;
9595        let fallback = Self::fallback_address_for_hostname(hostname.as_ref());
9596        let requested = address_mode.requested_address();
9597        let address = if requested == 0 { fallback } else { requested };
9598        let local_address = AddressBookEntry {
9599            hostname: hostname.clone(),
9600            address,
9601            requested_address: requested,
9602            mode: address_mode,
9603            birth_ms: clock
9604                .now_ms()
9605                .saturating_mul(1_000_000)
9606                .saturating_add(instance_seq),
9607            owner_hash,
9608            last_seen_ms: clock.now_ms(),
9609        };
9610        let mut address_book = BTreeMap::new();
9611        address_book.insert(hostname.to_string(), local_address.clone());
9612        let mut address_by_value = BTreeMap::new();
9613        address_by_value.insert(address, hostname.to_string());
9614        Self {
9615            sender: RouterMutex::new(hostname),
9616            cfg,
9617            state: RouterMutex::new(RouterInner {
9618                memory,
9619                sides: Vec::new(),
9620                route_overrides: BTreeMap::new(),
9621                typed_route_overrides: BTreeMap::new(),
9622                route_weights: BTreeMap::new(),
9623                route_priorities: BTreeMap::new(),
9624                source_route_modes: BTreeMap::new(),
9625                route_selection_cursors: BTreeMap::new(),
9626                adaptive_route_stats: BTreeMap::new(),
9627                side_runtime_stats: BTreeMap::new(),
9628                side_transport: BTreeMap::new(),
9629                managed_variable_types: BTreeSet::new(),
9630                managed_variable_permissions: BTreeMap::new(),
9631                managed_variable_latest: BTreeMap::new(),
9632                network_variable_update_handlers: BTreeMap::new(),
9633                local_address,
9634                address_book,
9635                address_by_value,
9636                p2p_port_handlers: BTreeMap::new(),
9637                p2p_stream_handlers: BTreeMap::new(),
9638                p2p_stream_sessions: BTreeMap::new(),
9639                next_p2p_stream_id: 1,
9640                received_queue: BoundedDeque::new(
9641                    memory.max_queue_budget,
9642                    memory.starting_queue_size,
9643                    memory.queue_grow_step,
9644                ),
9645                transmit_queue: BoundedDeque::new(
9646                    memory.max_queue_budget,
9647                    memory.starting_queue_size,
9648                    memory.queue_grow_step,
9649                ),
9650                tx_priority_burst: 0,
9651                recent_rx: BoundedDeque::new(
9652                    memory.recent_rx_queue_bytes(),
9653                    memory.recent_rx_queue_bytes(),
9654                    memory.queue_grow_step,
9655                ),
9656                reliable_tx: BTreeMap::new(),
9657                reliable_rx: BTreeMap::new(),
9658                reliable_return_routes: BTreeMap::new(),
9659                reliable_return_route_order: VecDeque::new(),
9660                end_to_end_reliable_tx: BTreeMap::new(),
9661                end_to_end_reliable_tx_order: VecDeque::new(),
9662                total_handler_failures: 0,
9663                total_handler_retries: 0,
9664                #[cfg(feature = "discovery")]
9665                discovery_routes: BTreeMap::new(),
9666                #[cfg(feature = "discovery")]
9667                discovery_cadence: DiscoveryCadenceState::default(),
9668                #[cfg(feature = "discovery")]
9669                discovery_side_throttle: BTreeMap::new(),
9670                #[cfg(feature = "discovery")]
9671                preferred_discovery_master,
9672                #[cfg(all(feature = "discovery", feature = "timesync"))]
9673                timesync_side_throttle: BTreeMap::new(),
9674            }),
9675            isr_rx_queue: IsrRxQueue::new(
9676                memory.max_queue_budget,
9677                memory.starting_queue_size,
9678                memory.queue_grow_step,
9679            ),
9680            side_tx_gate: ReentryGate::new(),
9681            clock,
9682            #[cfg(feature = "timesync")]
9683            timesync: RouterMutex::new(TimeSyncRuntime::new(timesync_cfg)),
9684        }
9685    }
9686
9687    #[inline]
9688    fn sender_arc(&self) -> Arc<str> {
9689        self.sender.lock().clone()
9690    }
9691
9692    #[inline]
9693    pub fn sender(&self) -> Arc<str> {
9694        self.sender_arc()
9695    }
9696
9697    pub fn current_address(&self) -> NodeAddress {
9698        self.state.lock().local_address.address
9699    }
9700
9701    pub fn hostname(&self) -> Arc<str> {
9702        self.sender_arc()
9703    }
9704
9705    /// Change the preferred discovery master at runtime.
9706    ///
9707    /// Passing `None` or an empty hostname restores topology-based election.
9708    /// This does not alter time-sync leadership or source priority.
9709    pub fn set_preferred_discovery_master(&self, hostname: Option<&str>) {
9710        #[cfg(feature = "discovery")]
9711        {
9712            let preferred = hostname
9713                .filter(|value| !value.is_empty())
9714                .map(Arc::<str>::from);
9715            let mut st = self.state.lock();
9716            if st.preferred_discovery_master != preferred {
9717                st.preferred_discovery_master = preferred;
9718                Self::note_discovery_topology_change_locked(&mut st, self.clock.now_ms());
9719            }
9720        }
9721        #[cfg(not(feature = "discovery"))]
9722        let _ = hostname;
9723    }
9724
9725    /// Return the currently configured preferred discovery master.
9726    pub fn preferred_discovery_master(&self) -> Option<Arc<str>> {
9727        #[cfg(feature = "discovery")]
9728        {
9729            return self.state.lock().preferred_discovery_master.clone();
9730        }
9731        #[cfg(not(feature = "discovery"))]
9732        None
9733    }
9734
9735    pub fn address_book(&self) -> Vec<AddressBookEntry> {
9736        self.state.lock().address_book.values().cloned().collect()
9737    }
9738
9739    pub fn resolve_hostname(&self, hostname: &str) -> Option<AddressBookEntry> {
9740        let now_ms = self.clock.now_ms();
9741        #[allow(unused_mut)]
9742        let mut st = self.state.lock();
9743        #[cfg(feature = "discovery")]
9744        if Self::prune_discovery_routes_locked(&mut st, now_ms) {
9745            Self::note_discovery_topology_change_locked(&mut st, now_ms);
9746        }
9747        if let Some(entry) = st.address_book.get(hostname) {
9748            return Some(entry.clone());
9749        }
9750        // Address advertisements are link-local, but topology names cross
9751        // routers. Mirror resolve_address's topology-only identity support so
9752        // named P2P messages/OTA streams can reach those downstream devices.
9753        // Never invent a target merely because an application requested it;
9754        // the retained topology must resolve its compact identity uniquely.
9755        let address = sender_address_u32(hostname);
9756        if Self::hostname_for_address_locked(&st, address).as_deref() != Some(hostname) {
9757            return None;
9758        }
9759        Some(AddressBookEntry {
9760            hostname: Arc::from(hostname),
9761            address,
9762            requested_address: 0,
9763            mode: AddressAssignmentMode::Dynamic,
9764            birth_ms: 0,
9765            owner_hash: Self::sender_hash(hostname),
9766            last_seen_ms: now_ms,
9767        })
9768    }
9769
9770    pub fn resolve_address(&self, address: NodeAddress) -> Option<AddressBookEntry> {
9771        let st = self.state.lock();
9772        let direct = st
9773            .address_by_value
9774            .get(&address)
9775            .and_then(|hostname| st.address_book.get(hostname))
9776            .cloned();
9777        if direct.is_some() {
9778            return direct;
9779        }
9780
9781        let hostname = Self::hostname_for_address_locked(&st, address)?;
9782        if let Some(entry) = st.address_book.get(&hostname) {
9783            return Some(entry.clone());
9784        }
9785
9786        // A topology-only remote has no DHCP-style address record on this
9787        // link. Return a compact-address identity entry; callers that need to
9788        // route to it still use discovery, while telemetry consumers can use
9789        // the stable hostname immediately.
9790        let hostname: Arc<str> = Arc::from(hostname);
9791        Some(AddressBookEntry {
9792            hostname: hostname.clone(),
9793            address,
9794            requested_address: 0,
9795            mode: AddressAssignmentMode::Dynamic,
9796            birth_ms: 0,
9797            owner_hash: Self::sender_hash(hostname.as_ref()),
9798            last_seen_ms: self.clock.now_ms(),
9799        })
9800    }
9801
9802    pub fn bind_p2p_port<F>(&self, port: P2pPort, f: F) -> TelemetryResult<()>
9803    where
9804        F: Fn(P2pMessage<'_>) -> TelemetryResult<()> + Send + Sync + 'static,
9805    {
9806        if port == 0 {
9807            return Err(TelemetryError::BadArg);
9808        }
9809        let mut st = self.state.lock();
9810        st.p2p_port_handlers
9811            .entry(port)
9812            .or_default()
9813            .push(P2pPortHandler {
9814                handler: Arc::new(f),
9815            });
9816        Ok(())
9817    }
9818
9819    pub fn clear_p2p_port(&self, port: P2pPort) {
9820        self.state.lock().p2p_port_handlers.remove(&port);
9821    }
9822
9823    pub fn bind_p2p_stream_port<F>(&self, port: P2pPort, f: F) -> TelemetryResult<()>
9824    where
9825        F: Fn(P2pStreamEvent<'_>) -> TelemetryResult<()> + Send + Sync + 'static,
9826    {
9827        if port == 0 {
9828            return Err(TelemetryError::BadArg);
9829        }
9830        let mut st = self.state.lock();
9831        st.p2p_stream_handlers
9832            .entry(port)
9833            .or_default()
9834            .push(P2pStreamHandler {
9835                handler: Arc::new(f),
9836            });
9837        Ok(())
9838    }
9839
9840    pub fn clear_p2p_stream_port(&self, port: P2pPort) {
9841        self.state.lock().p2p_stream_handlers.remove(&port);
9842    }
9843
9844    fn send_p2p_to_entry(
9845        &self,
9846        dst: AddressBookEntry,
9847        dst_port: P2pPort,
9848        src_port: P2pPort,
9849        payload: &[u8],
9850    ) -> TelemetryResult<()> {
9851        if dst_port == 0 {
9852            return Err(TelemetryError::BadArg);
9853        }
9854        let local = self.state.lock().local_address.clone();
9855        let payload = Self::encode_p2p_payload(
9856            local.hostname.as_ref(),
9857            local.address,
9858            src_port,
9859            dst_port,
9860            payload,
9861        )?;
9862        let pkt = Packet::new(
9863            DataType::P2pMessage,
9864            &[DataEndpoint::Discovery],
9865            local.hostname.as_ref(),
9866            self.packet_timestamp_ms(),
9867            crate::shared_bytes::convert(payload),
9868        )?;
9869        let target = Self::sender_hash(dst.hostname.as_ref());
9870        let item = self.attach_wire_contract_to_item(RouterItem::Packet(pkt.clone()), &[target])?;
9871        if target == Self::sender_hash(self.sender_arc().as_ref()) {
9872            self.dispatch_p2p_packet(&pkt)?;
9873            return Ok(());
9874        }
9875        self.tx_item_impl(RouterTxItem::Broadcast(item), true, false)
9876    }
9877
9878    pub fn send_p2p_to_hostname(
9879        &self,
9880        hostname: &str,
9881        dst_port: P2pPort,
9882        src_port: P2pPort,
9883        payload: &[u8],
9884    ) -> TelemetryResult<()> {
9885        let dst = self
9886            .resolve_hostname(hostname)
9887            .ok_or(TelemetryError::BadArg)?;
9888        self.send_p2p_to_entry(dst, dst_port, src_port, payload)
9889    }
9890
9891    pub fn send_p2p_to_address(
9892        &self,
9893        address: NodeAddress,
9894        dst_port: P2pPort,
9895        src_port: P2pPort,
9896        payload: &[u8],
9897    ) -> TelemetryResult<()> {
9898        let dst = self
9899            .resolve_address(address)
9900            .ok_or(TelemetryError::BadArg)?;
9901        self.send_p2p_to_entry(dst, dst_port, src_port, payload)
9902    }
9903
9904    fn open_p2p_stream_to_entry(
9905        &self,
9906        dst: AddressBookEntry,
9907        dst_port: P2pPort,
9908        src_port: P2pPort,
9909    ) -> TelemetryResult<P2pStreamId> {
9910        if dst_port == 0 || src_port == 0 {
9911            return Err(TelemetryError::BadArg);
9912        }
9913        let stream_id = {
9914            let mut st = self.state.lock();
9915            let stream_id = Self::allocate_p2p_stream_id_locked(&mut st);
9916            if stream_id == 0 {
9917                return Err(TelemetryError::Io("p2p stream id exhausted"));
9918            }
9919            st.p2p_stream_sessions.insert(
9920                stream_id,
9921                P2pStreamSession {
9922                    peer_hostname: dst.hostname.clone(),
9923                    peer_address: dst.address,
9924                    local_port: src_port,
9925                    peer_port: dst_port,
9926                    peer_stream_id: 0,
9927                    next_sequence: 1,
9928                    connected: false,
9929                },
9930            );
9931            stream_id
9932        };
9933        let payload = Self::encode_p2p_stream_payload(P2P_STREAM_SYN, stream_id, 0, 0, &[])?;
9934        self.send_p2p_to_entry(dst, dst_port, src_port, &payload)?;
9935        Ok(stream_id)
9936    }
9937
9938    pub fn open_p2p_stream_to_hostname(
9939        &self,
9940        hostname: &str,
9941        dst_port: P2pPort,
9942        src_port: P2pPort,
9943    ) -> TelemetryResult<P2pStreamId> {
9944        let dst = self
9945            .resolve_hostname(hostname)
9946            .ok_or(TelemetryError::BadArg)?;
9947        self.open_p2p_stream_to_entry(dst, dst_port, src_port)
9948    }
9949
9950    pub fn open_p2p_stream_to_address(
9951        &self,
9952        address: NodeAddress,
9953        dst_port: P2pPort,
9954        src_port: P2pPort,
9955    ) -> TelemetryResult<P2pStreamId> {
9956        let dst = self
9957            .resolve_address(address)
9958            .ok_or(TelemetryError::BadArg)?;
9959        self.open_p2p_stream_to_entry(dst, dst_port, src_port)
9960    }
9961
9962    fn send_p2p_stream_control(
9963        &self,
9964        stream_id: P2pStreamId,
9965        flags: u8,
9966        payload: &[u8],
9967    ) -> TelemetryResult<()> {
9968        let (dst, dst_port, src_port, peer_stream_id, seq, refresh_syn) = {
9969            let mut st = self.state.lock();
9970            let Some(session) = st.p2p_stream_sessions.get_mut(&stream_id) else {
9971                return Err(TelemetryError::BadArg);
9972            };
9973            let peer_hostname = session.peer_hostname.clone();
9974            let peer_address = session.peer_address;
9975            let peer_port = session.peer_port;
9976            let local_port = session.local_port;
9977            let peer_stream_id = session.peer_stream_id;
9978            let refresh_syn = peer_stream_id == 0 && flags & P2P_STREAM_SYN == 0;
9979            let seq = session.next_sequence;
9980            session.next_sequence = session.next_sequence.wrapping_add(1).max(1);
9981            let dst = st
9982                .address_book
9983                .get(peer_hostname.as_ref())
9984                .cloned()
9985                .unwrap_or(AddressBookEntry {
9986                    hostname: peer_hostname.clone(),
9987                    address: peer_address,
9988                    requested_address: peer_address,
9989                    mode: AddressAssignmentMode::Dynamic,
9990                    birth_ms: self.clock.now_ms(),
9991                    owner_hash: Self::sender_hash(peer_hostname.as_ref()),
9992                    last_seen_ms: self.clock.now_ms(),
9993                });
9994            (dst, peer_port, local_port, peer_stream_id, seq, refresh_syn)
9995        };
9996        if refresh_syn {
9997            let syn_payload =
9998                Self::encode_p2p_stream_payload(P2P_STREAM_SYN, stream_id, 0, 0, &[])?;
9999            self.send_p2p_to_entry(dst.clone(), dst_port, src_port, &syn_payload)?;
10000        }
10001        let stream_payload =
10002            Self::encode_p2p_stream_payload(flags, stream_id, peer_stream_id, seq, payload)?;
10003        self.send_p2p_to_entry(dst, dst_port, src_port, &stream_payload)?;
10004        if flags & (P2P_STREAM_FIN | P2P_STREAM_RST) != 0 {
10005            self.state.lock().p2p_stream_sessions.remove(&stream_id);
10006        }
10007        Ok(())
10008    }
10009
10010    pub fn send_p2p_stream(&self, stream_id: P2pStreamId, payload: &[u8]) -> TelemetryResult<()> {
10011        self.send_p2p_stream_control(stream_id, P2P_STREAM_DATA, payload)
10012    }
10013
10014    pub fn close_p2p_stream(&self, stream_id: P2pStreamId) -> TelemetryResult<()> {
10015        self.send_p2p_stream_control(stream_id, P2P_STREAM_FIN, &[])
10016    }
10017
10018    pub fn reset_p2p_stream(&self, stream_id: P2pStreamId) -> TelemetryResult<()> {
10019        self.send_p2p_stream_control(stream_id, P2P_STREAM_RST, &[])
10020    }
10021
10022    pub fn set_sender<S: AsRef<str>>(&self, sender: S) {
10023        let hostname: Arc<str> = Arc::from(sender.as_ref());
10024        let mut st = self.state.lock();
10025        let mut local = st.local_address.clone();
10026        local.hostname = hostname.clone();
10027        local.owner_hash = Self::sender_hash(hostname.as_ref());
10028        if matches!(local.mode, AddressAssignmentMode::Dynamic) {
10029            local.address = Self::fallback_address_for_hostname(hostname.as_ref());
10030            local.requested_address = 0;
10031        }
10032        let change =
10033            self.update_local_identity_locked(&mut st, local, AddressChangeReason::Configured);
10034        drop(st);
10035        let _ = self.notify_address_change(change);
10036    }
10037
10038    pub fn set_address_assignment(
10039        &self,
10040        mode: AddressAssignmentMode,
10041    ) -> TelemetryResult<AddressChange> {
10042        let mut st = self.state.lock();
10043        let mut local = st.local_address.clone();
10044        local.mode = mode;
10045        local.requested_address = mode.requested_address();
10046        local.address = match mode {
10047            AddressAssignmentMode::Dynamic => {
10048                Self::fallback_address_for_hostname(local.hostname.as_ref())
10049            }
10050            AddressAssignmentMode::Requested(address) | AddressAssignmentMode::Static(address) => {
10051                if address == 0 {
10052                    1
10053                } else {
10054                    address
10055                }
10056            }
10057        };
10058        let change =
10059            self.update_local_identity_locked(&mut st, local, AddressChangeReason::Configured);
10060        drop(st);
10061        self.notify_address_change(change.clone())?;
10062        Ok(change)
10063    }
10064
10065    /// Register a side whose TX callback consumes packed packet bytes.
10066    ///
10067    /// `name` is exported in topology/debug views and does not affect routing semantics.
10068    /// `tx` is called whenever the router decides to send a packet toward this side.
10069    ///
10070    /// The default options disable the router's per-link reliable framing on this side. Use
10071    /// [`Router::add_side_packed_with_options`] when this hop should participate in router
10072    /// reliable ACK/retransmit behavior.
10073    pub fn add_side_packed<N, F>(&self, name: N, tx: F) -> RouterSideId
10074    where
10075        N: AsRef<str>,
10076        F: Fn(&[u8]) -> TelemetryResult<()> + Send + Sync + 'static,
10077    {
10078        self.add_side_packed_with_options(name, tx, RouterSideOptions::default())
10079    }
10080
10081    /// Register a packed side with the compact small-packet transport preset enabled.
10082    ///
10083    /// `max_frame_bytes == 0` keeps header-template reuse enabled without chunking.
10084    pub fn add_side_packed_small_packets<N, F>(
10085        &self,
10086        name: N,
10087        tx: F,
10088        max_frame_bytes: usize,
10089    ) -> RouterSideId
10090    where
10091        N: AsRef<str>,
10092        F: Fn(&[u8]) -> TelemetryResult<()> + Send + Sync + 'static,
10093    {
10094        self.add_side_packed_with_options(
10095            name,
10096            tx,
10097            RouterSideOptions::default().with_small_packet_transport(max_frame_bytes),
10098        )
10099    }
10100
10101    /// Register a packed-output side with explicit side options.
10102    ///
10103    /// `opts.reliable_enabled` enables the router's per-hop reliable framing on this side only.
10104    /// That means reliable schema traffic on this side uses router-managed ACKs, packet requests,
10105    /// and retransmits before the bytes reach `tx`.
10106    ///
10107    /// `opts.link_local_enabled` allows link-local-only endpoints and discovery routes to use this
10108    /// side. `ingress_enabled` and `egress_enabled` set the initial directional policy.
10109    pub fn add_side_packed_with_options<N, F>(
10110        &self,
10111        name: N,
10112        tx: F,
10113        opts: RouterSideOptions,
10114    ) -> RouterSideId
10115    where
10116        N: AsRef<str>,
10117        F: Fn(&[u8]) -> TelemetryResult<()> + Send + Sync + 'static,
10118    {
10119        let mut st = self.state.lock();
10120        let side = Some(RouterSide {
10121            name: Arc::from(name.as_ref()),
10122            tx_handler: RouterTxHandlerFn::Packed(Arc::new(tx)),
10123            opts,
10124        });
10125        let id = if let Some(id) = st.sides.iter().position(Option::is_none) {
10126            st.sides[id] = side;
10127            id
10128        } else {
10129            let id = st.sides.len();
10130            st.sides.push(side);
10131            id
10132        };
10133        st.side_runtime_stats
10134            .insert(id, SideRuntimeStatsInner::default());
10135        st.side_transport.insert(id, SideTransportState::default());
10136        #[cfg(feature = "discovery")]
10137        Self::note_discovery_topology_change_locked(&mut st, self.clock.now_ms());
10138        id
10139    }
10140
10141    /// Register a packed-output side whose callback also receives SEDSNet's
10142    /// logical scheduler priority for the frame.
10143    ///
10144    /// This is intended for asynchronous host transports that must queue the
10145    /// emitted bytes after this callback returns. Every compact/chunked frame
10146    /// belonging to a logical packet receives the same priority, so transport
10147    /// backpressure cannot erase discovery, shared-state, time-sync, or schema
10148    /// ordering.
10149    pub fn add_side_packed_with_priority_and_options<N, F>(
10150        &self,
10151        name: N,
10152        tx: F,
10153        opts: RouterSideOptions,
10154    ) -> RouterSideId
10155    where
10156        N: AsRef<str>,
10157        F: Fn(&[u8], u8) -> TelemetryResult<()> + Send + Sync + 'static,
10158    {
10159        let mut st = self.state.lock();
10160        let side = Some(RouterSide {
10161            name: Arc::from(name.as_ref()),
10162            tx_handler: RouterTxHandlerFn::PackedWithPriority(Arc::new(tx)),
10163            opts,
10164        });
10165        let id = if let Some(id) = st.sides.iter().position(Option::is_none) {
10166            st.sides[id] = side;
10167            id
10168        } else {
10169            let id = st.sides.len();
10170            st.sides.push(side);
10171            id
10172        };
10173        st.side_runtime_stats
10174            .insert(id, SideRuntimeStatsInner::default());
10175        st.side_transport.insert(id, SideTransportState::default());
10176        #[cfg(feature = "discovery")]
10177        Self::note_discovery_topology_change_locked(&mut st, self.clock.now_ms());
10178        id
10179    }
10180
10181    /// Register a side whose TX callback receives decoded [`Packet`] values.
10182    ///
10183    /// Packet-output sides do not preserve the packed reliable hop framing, so
10184    /// `RouterSideOptions::reliable_enabled` only has effect for packed sides.
10185    pub fn add_side_packet<N, F>(&self, name: N, tx: F) -> RouterSideId
10186    where
10187        N: AsRef<str>,
10188        F: Fn(&Packet) -> TelemetryResult<()> + Send + Sync + 'static,
10189    {
10190        self.add_side_packet_with_options(name, tx, RouterSideOptions::default())
10191    }
10192
10193    /// Register a packet-output side with explicit side options.
10194    ///
10195    /// `opts.reliable_enabled` still records the operator's intent for this side, but packet-based
10196    /// callbacks receive decoded packets rather than the router's packed reliable hop framing.
10197    /// For router-managed per-link reliable sequencing and ACKs, use a packed side instead.
10198    pub fn add_side_packet_with_options<N, F>(
10199        &self,
10200        name: N,
10201        tx: F,
10202        opts: RouterSideOptions,
10203    ) -> RouterSideId
10204    where
10205        N: AsRef<str>,
10206        F: Fn(&Packet) -> TelemetryResult<()> + Send + Sync + 'static,
10207    {
10208        let mut st = self.state.lock();
10209        let side = Some(RouterSide {
10210            name: Arc::from(name.as_ref()),
10211            tx_handler: RouterTxHandlerFn::Packet(Arc::new(tx)),
10212            opts,
10213        });
10214        let id = if let Some(id) = st.sides.iter().position(Option::is_none) {
10215            st.sides[id] = side;
10216            id
10217        } else {
10218            let id = st.sides.len();
10219            st.sides.push(side);
10220            id
10221        };
10222        st.side_runtime_stats
10223            .insert(id, SideRuntimeStatsInner::default());
10224        st.side_transport.insert(id, SideTransportState::default());
10225        #[cfg(feature = "discovery")]
10226        Self::note_discovery_topology_change_locked(&mut st, self.clock.now_ms());
10227        id
10228    }
10229
10230    /// Remove a side while keeping existing side IDs stable.
10231    ///
10232    /// `side` must be an id returned earlier by one of the `add_side_*` calls. Removed side IDs
10233    /// are tombstoned rather than renumbered so cached IDs for the remaining sides stay valid.
10234    pub fn remove_side(&self, side: RouterSideId) -> TelemetryResult<()> {
10235        let now_ms = self.clock.now_ms();
10236        {
10237            let mut st = self.state.lock();
10238            let slot = st.sides.get_mut(side).ok_or(TelemetryError::BadArg)?;
10239            if slot.is_none() {
10240                return Err(TelemetryError::BadArg);
10241            }
10242            *slot = None;
10243            while st.sides.last().is_some_and(Option::is_none) {
10244                st.sides.pop();
10245            }
10246            // Keep the small side table allocation for reuse. Embedded links can
10247            // disappear and return frequently; shrinking here turned every tail
10248            // remove/add cycle into a free/allocation pair. The retained capacity
10249            // remains owned by the router and is released when the router drops.
10250            st.route_overrides
10251                .retain(|(src_side, dst_side), _| *src_side != Some(side) && *dst_side != side);
10252            st.typed_route_overrides
10253                .retain(|(src_side, _, dst_side), _| *src_side != Some(side) && *dst_side != side);
10254            st.route_weights
10255                .retain(|(src_side, dst_side), _| *src_side != Some(side) && *dst_side != side);
10256            st.route_priorities
10257                .retain(|(src_side, dst_side), _| *src_side != Some(side) && *dst_side != side);
10258            st.source_route_modes.remove(&Some(side));
10259            st.route_selection_cursors.remove(&Some(side));
10260            st.adaptive_route_stats.remove(&side);
10261            #[cfg(feature = "discovery")]
10262            st.discovery_side_throttle.remove(&side);
10263            #[cfg(all(feature = "discovery", feature = "timesync"))]
10264            st.timesync_side_throttle.remove(&side);
10265            st.side_runtime_stats.remove(&side);
10266            st.side_transport.remove(&side);
10267            st.reliable_return_routes
10268                .retain(|_, route| route.side != side);
10269            st.transmit_queue.retain(
10270                |queued| {
10271                    !matches!(&queued.item, RouterTxItem::ToSide { dst, .. } if *dst == side)
10272                        && !matches!(&queued.item, RouterTxItem::ReliableReplay { dst, .. } if *dst == side)
10273                },
10274            );
10275            st.received_queue.retain(|queued| queued.src != Some(side));
10276            st.reliable_tx.retain(|(side_id, _), _| *side_id != side);
10277            st.reliable_rx.retain(|(side_id, _), _| *side_id != side);
10278            #[cfg(feature = "discovery")]
10279            {
10280                st.discovery_routes.remove(&side);
10281                Self::note_discovery_topology_change_locked(&mut st, now_ms);
10282            }
10283        }
10284        let mut isr_rx = self.isr_rx_queue.try_lock()?;
10285        isr_rx.retain(|queued| queued.src != Some(side));
10286        Ok(())
10287    }
10288
10289    /// Enable or disable ingress processing for a registered side.
10290    ///
10291    /// When `enabled` is `false`, packets tagged as arriving from `side` are rejected before local
10292    /// delivery, discovery learning, or forwarding.
10293    pub fn set_side_ingress_enabled(
10294        &self,
10295        side: RouterSideId,
10296        enabled: bool,
10297    ) -> TelemetryResult<()> {
10298        let now_ms = self.clock.now_ms();
10299        let mut st = self.state.lock();
10300        let side_ref = st
10301            .sides
10302            .get_mut(side)
10303            .and_then(|side| side.as_mut())
10304            .ok_or(TelemetryError::BadArg)?;
10305        side_ref.opts.ingress_enabled = enabled;
10306        #[cfg(feature = "discovery")]
10307        Self::note_discovery_topology_change_locked(&mut st, now_ms);
10308        Ok(())
10309    }
10310
10311    /// Enable or disable egress toward a registered side.
10312    ///
10313    /// When `enabled` is `false`, the router keeps the side but stops routing packets toward it.
10314    pub fn set_side_egress_enabled(
10315        &self,
10316        side: RouterSideId,
10317        enabled: bool,
10318    ) -> TelemetryResult<()> {
10319        let now_ms = self.clock.now_ms();
10320        let mut st = self.state.lock();
10321        let side_ref = st
10322            .sides
10323            .get_mut(side)
10324            .and_then(|side| side.as_mut())
10325            .ok_or(TelemetryError::BadArg)?;
10326        side_ref.opts.egress_enabled = enabled;
10327        if !enabled {
10328            st.transmit_queue.retain(
10329                |queued| {
10330                    !matches!(&queued.item, RouterTxItem::ToSide { dst, .. } if *dst == side)
10331                        && !matches!(&queued.item, RouterTxItem::ReliableReplay { dst, .. } if *dst == side)
10332                },
10333            );
10334        }
10335        #[cfg(feature = "discovery")]
10336        Self::note_discovery_topology_change_locked(&mut st, now_ms);
10337        Ok(())
10338    }
10339
10340    /// Set the route-selection policy for traffic originating from `src`.
10341    ///
10342    /// `src == None` targets locally-originated router TX. `src == Some(side)` targets traffic
10343    /// that was received from a specific ingress side. `mode` only matters when more than one
10344    /// destination side is currently eligible.
10345    pub fn set_source_route_mode(
10346        &self,
10347        src: Option<RouterSideId>,
10348        mode: RouteSelectionMode,
10349    ) -> TelemetryResult<()> {
10350        let now_ms = self.clock.now_ms();
10351        let mut st = self.state.lock();
10352        if let Some(src) = src {
10353            let _ = Self::side_ref(&st, src).map_err(|_| TelemetryError::BadArg)?;
10354        }
10355        // Keep an explicitly selected Fanout mode distinct from the absence
10356        // of a policy. With discovery enabled, no policy intentionally uses
10357        // adaptive single-path selection; explicit Fanout must override that.
10358        st.source_route_modes.insert(src, mode);
10359        st.route_selection_cursors.remove(&src);
10360        #[cfg(feature = "discovery")]
10361        Self::note_discovery_topology_change_locked(&mut st, now_ms);
10362        Ok(())
10363    }
10364
10365    /// Clear any source-specific route-selection override for `src`.
10366    pub fn clear_source_route_mode(&self, src: Option<RouterSideId>) -> TelemetryResult<()> {
10367        let now_ms = self.clock.now_ms();
10368        let mut st = self.state.lock();
10369        if let Some(src) = src {
10370            let _ = Self::side_ref(&st, src).map_err(|_| TelemetryError::BadArg)?;
10371        }
10372        st.source_route_modes.remove(&src);
10373        st.route_selection_cursors.remove(&src);
10374        #[cfg(feature = "discovery")]
10375        Self::note_discovery_topology_change_locked(&mut st, now_ms);
10376        Ok(())
10377    }
10378
10379    /// Set the weighted-routing weight from `src` toward `dst`.
10380    ///
10381    /// Higher `weight` values make `dst` more likely to be chosen when the source route mode is
10382    /// [`RouteSelectionMode::Weighted`]. `src == None` applies to locally-originated TX.
10383    pub fn set_route_weight(
10384        &self,
10385        src: Option<RouterSideId>,
10386        dst: RouterSideId,
10387        weight: u32,
10388    ) -> TelemetryResult<()> {
10389        let now_ms = self.clock.now_ms();
10390        let mut st = self.state.lock();
10391        let _ = Self::side_ref(&st, dst).map_err(|_| TelemetryError::BadArg)?;
10392        if let Some(src) = src {
10393            let _ = Self::side_ref(&st, src).map_err(|_| TelemetryError::BadArg)?;
10394        }
10395        st.route_weights.insert((src, dst), weight);
10396        st.route_selection_cursors.remove(&src);
10397        #[cfg(feature = "discovery")]
10398        Self::note_discovery_topology_change_locked(&mut st, now_ms);
10399        Ok(())
10400    }
10401
10402    /// Clear a previously configured weighted-routing weight override.
10403    pub fn clear_route_weight(
10404        &self,
10405        src: Option<RouterSideId>,
10406        dst: RouterSideId,
10407    ) -> TelemetryResult<()> {
10408        let now_ms = self.clock.now_ms();
10409        let mut st = self.state.lock();
10410        let _ = Self::side_ref(&st, dst).map_err(|_| TelemetryError::BadArg)?;
10411        if let Some(src) = src {
10412            let _ = Self::side_ref(&st, src).map_err(|_| TelemetryError::BadArg)?;
10413        }
10414        st.route_weights.remove(&(src, dst));
10415        st.route_selection_cursors.remove(&src);
10416        #[cfg(feature = "discovery")]
10417        Self::note_discovery_topology_change_locked(&mut st, now_ms);
10418        Ok(())
10419    }
10420
10421    /// Set the failover priority from `src` toward `dst`.
10422    ///
10423    /// Lower numeric `priority` wins when the source route mode is
10424    /// [`RouteSelectionMode::Failover`]. `src == None` applies to locally-originated TX.
10425    pub fn set_route_priority(
10426        &self,
10427        src: Option<RouterSideId>,
10428        dst: RouterSideId,
10429        priority: u32,
10430    ) -> TelemetryResult<()> {
10431        let now_ms = self.clock.now_ms();
10432        let mut st = self.state.lock();
10433        let _ = Self::side_ref(&st, dst).map_err(|_| TelemetryError::BadArg)?;
10434        if let Some(src) = src {
10435            let _ = Self::side_ref(&st, src).map_err(|_| TelemetryError::BadArg)?;
10436        }
10437        st.route_priorities.insert((src, dst), priority);
10438        #[cfg(feature = "discovery")]
10439        Self::note_discovery_topology_change_locked(&mut st, now_ms);
10440        Ok(())
10441    }
10442
10443    /// Clear a previously configured failover priority override.
10444    pub fn clear_route_priority(
10445        &self,
10446        src: Option<RouterSideId>,
10447        dst: RouterSideId,
10448    ) -> TelemetryResult<()> {
10449        let now_ms = self.clock.now_ms();
10450        let mut st = self.state.lock();
10451        let _ = Self::side_ref(&st, dst).map_err(|_| TelemetryError::BadArg)?;
10452        if let Some(src) = src {
10453            let _ = Self::side_ref(&st, src).map_err(|_| TelemetryError::BadArg)?;
10454        }
10455        st.route_priorities.remove(&(src, dst));
10456        #[cfg(feature = "discovery")]
10457        Self::note_discovery_topology_change_locked(&mut st, now_ms);
10458        Ok(())
10459    }
10460
10461    /// Allow or block routing from `src` toward `dst`.
10462    ///
10463    /// `src == None` controls locally-originated router TX. `enabled == false` is the sink-like
10464    /// building block for disabling specific directions without changing router construction mode.
10465    pub fn set_route(
10466        &self,
10467        src: Option<RouterSideId>,
10468        dst: RouterSideId,
10469        enabled: bool,
10470    ) -> TelemetryResult<()> {
10471        let now_ms = self.clock.now_ms();
10472        let mut st = self.state.lock();
10473        let _ = Self::side_ref(&st, dst).map_err(|_| TelemetryError::BadArg)?;
10474        if let Some(src) = src {
10475            let _ = Self::side_ref(&st, src).map_err(|_| TelemetryError::BadArg)?;
10476        }
10477        st.route_overrides.insert((src, dst), enabled);
10478        #[cfg(feature = "discovery")]
10479        Self::note_discovery_topology_change_locked(&mut st, now_ms);
10480        Ok(())
10481    }
10482
10483    /// Allow or block routing for a specific `DataType` from `src` toward `dst`.
10484    ///
10485    /// Typed route rules act as allowlists once any rule exists for the `(src, ty)` pair.
10486    /// `src == None` targets locally-originated router TX.
10487    pub fn set_typed_route(
10488        &self,
10489        src: Option<RouterSideId>,
10490        ty: DataType,
10491        dst: RouterSideId,
10492        enabled: bool,
10493    ) -> TelemetryResult<()> {
10494        let now_ms = self.clock.now_ms();
10495        let mut st = self.state.lock();
10496        let _ = Self::side_ref(&st, dst).map_err(|_| TelemetryError::BadArg)?;
10497        if let Some(src) = src {
10498            let _ = Self::side_ref(&st, src).map_err(|_| TelemetryError::BadArg)?;
10499        }
10500        st.typed_route_overrides
10501            .insert((src, ty.as_u32(), dst), enabled);
10502        #[cfg(feature = "discovery")]
10503        Self::note_discovery_topology_change_locked(&mut st, now_ms);
10504        Ok(())
10505    }
10506
10507    /// Clear a typed route override for the `(src, ty, dst)` triple.
10508    pub fn clear_typed_route(
10509        &self,
10510        src: Option<RouterSideId>,
10511        ty: DataType,
10512        dst: RouterSideId,
10513    ) -> TelemetryResult<()> {
10514        let now_ms = self.clock.now_ms();
10515        let mut st = self.state.lock();
10516        let _ = Self::side_ref(&st, dst).map_err(|_| TelemetryError::BadArg)?;
10517        if let Some(src) = src {
10518            let _ = Self::side_ref(&st, src).map_err(|_| TelemetryError::BadArg)?;
10519        }
10520        st.typed_route_overrides.remove(&(src, ty.as_u32(), dst));
10521        #[cfg(feature = "discovery")]
10522        Self::note_discovery_topology_change_locked(&mut st, now_ms);
10523        Ok(())
10524    }
10525
10526    /// Clear a non-typed route override so the router falls back to default behavior.
10527    pub fn clear_route(&self, src: Option<RouterSideId>, dst: RouterSideId) -> TelemetryResult<()> {
10528        let now_ms = self.clock.now_ms();
10529        let mut st = self.state.lock();
10530        let _ = Self::side_ref(&st, dst).map_err(|_| TelemetryError::BadArg)?;
10531        if let Some(src) = src {
10532            let _ = Self::side_ref(&st, src).map_err(|_| TelemetryError::BadArg)?;
10533        }
10534        st.route_overrides.remove(&(src, dst));
10535        #[cfg(feature = "discovery")]
10536        Self::note_discovery_topology_change_locked(&mut st, now_ms);
10537        Ok(())
10538    }
10539
10540    /// Queue a built-in discovery advertisement describing this router's local endpoints.
10541    #[cfg(feature = "discovery")]
10542    pub fn announce_discovery(&self) -> TelemetryResult<()> {
10543        self.queue_discovery_announce(true, false)
10544    }
10545
10546    /// Broadcast that this router is leaving so peers can prune topology immediately.
10547    #[cfg(feature = "discovery")]
10548    pub fn announce_leave(&self) -> TelemetryResult<()> {
10549        let sender = self.sender_arc();
10550        let pkt = discovery::build_discovery_leave(sender.as_ref(), self.clock.now_ms())?;
10551        self.tx_item(RouterTxItem::Broadcast(RouterItem::Packet(pkt)))
10552    }
10553
10554    /// Queue a discovery advertisement if the adaptive cadence says one is due.
10555    #[cfg(feature = "discovery")]
10556    pub fn poll_discovery(&self) -> TelemetryResult<bool> {
10557        self.poll_discovery_announce()
10558    }
10559
10560    #[cfg(feature = "discovery")]
10561    pub fn request_topology(&self) -> TelemetryResult<()> {
10562        let sender = self.sender_arc();
10563        let pkt =
10564            discovery::build_discovery_topology_request(sender.as_ref(), self.clock.now_ms())?;
10565        self.tx_item(RouterTxItem::Broadcast(RouterItem::Packet(pkt)))
10566    }
10567
10568    #[cfg(feature = "discovery")]
10569    pub fn request_schema(&self) -> TelemetryResult<()> {
10570        let sender = self.sender_arc();
10571        let pkt = discovery::build_discovery_schema_request(sender.as_ref(), self.clock.now_ms())?;
10572        self.tx_item(RouterTxItem::Broadcast(RouterItem::Packet(pkt)))
10573    }
10574
10575    /// Mark a data type as a network-managed variable.
10576    ///
10577    /// The router caches the latest packet for this type when it is locally transmitted or
10578    /// received from the network. Peers can request the latest cached value and the router will
10579    /// replay the original value packet, so user endpoint handlers see the same API shape as a
10580    /// normal update.
10581    pub fn enable_managed_variable(&self, ty: DataType) -> TelemetryResult<()> {
10582        self.enable_network_variable(ty, NetworkVariablePermissions::READ_WRITE)
10583    }
10584
10585    /// Mark a data type as a network variable with local read/write permissions.
10586    ///
10587    /// Users do not register a separate endpoint for network variables. Values are cached by
10588    /// data type and refreshed through SEDSnet's internal managed-variable control packets.
10589    pub fn enable_network_variable(
10590        &self,
10591        ty: DataType,
10592        permissions: NetworkVariablePermissions,
10593    ) -> TelemetryResult<()> {
10594        if is_internal_control_type(ty) {
10595            return Err(TelemetryError::InvalidType);
10596        }
10597        let mut st = self.state.lock();
10598        let newly_advertised = st.managed_variable_types.insert(ty.as_u32());
10599        st.managed_variable_permissions
10600            .insert(ty.as_u32(), permissions);
10601        #[cfg(feature = "discovery")]
10602        if newly_advertised {
10603            Self::note_discovery_topology_change_locked(&mut st, self.clock.now_ms());
10604        }
10605        Ok(())
10606    }
10607
10608    /// Register a callback that runs when an inbound network update changes this variable cache.
10609    ///
10610    /// The callback is invoked after the router state lock is released, so it may call back into
10611    /// the router. Local `set_network_variable` and `seed_managed_variable` calls update the cache
10612    /// without invoking this network-update callback.
10613    pub fn on_network_variable_update<F>(&self, ty: DataType, f: F) -> TelemetryResult<()>
10614    where
10615        F: Fn(&Packet) -> TelemetryResult<()> + Send + Sync + 'static,
10616    {
10617        if is_internal_control_type(ty) {
10618            return Err(TelemetryError::InvalidType);
10619        }
10620        let mut st = self.state.lock();
10621        let newly_advertised = st.managed_variable_types.insert(ty.as_u32());
10622        st.network_variable_update_handlers
10623            .entry(ty.as_u32())
10624            .or_default()
10625            .push(NetworkVariableUpdateHandler {
10626                handler: Arc::new(f),
10627            });
10628        #[cfg(feature = "discovery")]
10629        if newly_advertised {
10630            Self::note_discovery_topology_change_locked(&mut st, self.clock.now_ms());
10631        }
10632        Ok(())
10633    }
10634
10635    pub fn disable_managed_variable(&self, ty: DataType) {
10636        let mut st = self.state.lock();
10637        let was_advertised = st.managed_variable_types.remove(&ty.as_u32());
10638        st.managed_variable_permissions.remove(&ty.as_u32());
10639        st.managed_variable_latest.remove(&ty.as_u32());
10640        st.network_variable_update_handlers.remove(&ty.as_u32());
10641        #[cfg(feature = "discovery")]
10642        if was_advertised {
10643            Self::note_discovery_topology_change_locked(&mut st, self.clock.now_ms());
10644        }
10645    }
10646
10647    pub fn seed_managed_variable(&self, pkt: Packet) -> TelemetryResult<()> {
10648        if is_internal_control_type(pkt.data_type()) {
10649            return Err(TelemetryError::InvalidType);
10650        }
10651        pkt.validate()?;
10652        {
10653            let mut st = self.state.lock();
10654            let newly_advertised = st.managed_variable_types.insert(pkt.data_type().as_u32());
10655            #[cfg(feature = "discovery")]
10656            if newly_advertised {
10657                Self::note_discovery_topology_change_locked(&mut st, self.clock.now_ms());
10658            }
10659        }
10660        self.cache_managed_variable_packet(&pkt, false)
10661    }
10662
10663    pub fn cached_managed_variable(&self, ty: DataType) -> Option<Packet> {
10664        self.managed_variable_latest(ty)
10665    }
10666
10667    /// Set a network variable for the whole network, permission policy allowing.
10668    ///
10669    /// The packet is cached locally and sent as normal user data. Routers that have seen or enabled
10670    /// this variable update their local cache internally; applications do not need to register a
10671    /// managed-variable endpoint.
10672    pub fn set_network_variable(&self, pkt: Packet) -> TelemetryResult<()> {
10673        if is_internal_control_type(pkt.data_type()) {
10674            return Err(TelemetryError::InvalidType);
10675        }
10676        pkt.validate()?;
10677        let ty = pkt.data_type();
10678        {
10679            let mut st = self.state.lock();
10680            st.managed_variable_types.insert(ty.as_u32());
10681            let perms = Self::managed_variable_permissions_locked(&st, ty);
10682            if !perms.write {
10683                return Err(TelemetryError::PermissionDenied);
10684            }
10685        }
10686        self.cache_managed_variable_packet(&pkt, false)?;
10687        #[cfg(feature = "discovery")]
10688        let _ = crate::memory_admission::allow_drain(self.poll_discovery())?;
10689        let item = RouterTxItem::Broadcast(RouterItem::Packet(pkt));
10690        if self.side_tx_active() {
10691            return self.tx_queue_item_with_priority(
10692                item,
10693                false,
10694                crate::transport_priority(DataType::ManagedVariableValue),
10695            );
10696        }
10697        self.tx_item(item)
10698    }
10699
10700    /// Read a cached network variable, requesting a refresh if missing or stale.
10701    ///
10702    /// Returns the cached value when present. If the value is missing, this queues an internal
10703    /// managed-variable request and returns `Ok(None)`. If the value is stale, this queues a refresh
10704    /// request and returns the stale cached value so callers can continue operating while the network
10705    /// catches up.
10706    #[cfg(feature = "discovery")]
10707    pub fn get_network_variable(
10708        &self,
10709        ty: DataType,
10710        stale_after_ms: Option<u64>,
10711    ) -> TelemetryResult<Option<Packet>> {
10712        if is_internal_control_type(ty) {
10713            return Err(TelemetryError::InvalidType);
10714        }
10715        {
10716            let mut st = self.state.lock();
10717            st.managed_variable_types.insert(ty.as_u32());
10718            let perms = Self::managed_variable_permissions_locked(&st, ty);
10719            if !perms.read {
10720                return Err(TelemetryError::PermissionDenied);
10721            }
10722        }
10723        let cached = self.managed_variable_latest_with_age(ty);
10724        let needs_refresh = match (cached.as_ref(), stale_after_ms) {
10725            (None, _) => true,
10726            (Some((_pkt, age_ms)), Some(max_age_ms)) => *age_ms > max_age_ms,
10727            (Some(_), None) => false,
10728        };
10729        if needs_refresh {
10730            self.request_managed_variable(ty)?;
10731        }
10732        Ok(cached.map(|(pkt, _age_ms)| pkt))
10733    }
10734
10735    /// Read a cached network variable without issuing a network refresh.
10736    pub fn get_cached_network_variable(&self, ty: DataType) -> TelemetryResult<Option<Packet>> {
10737        if is_internal_control_type(ty) {
10738            return Err(TelemetryError::InvalidType);
10739        }
10740        if !self.can_read_managed_variable(ty) {
10741            return Err(TelemetryError::PermissionDenied);
10742        }
10743        Ok(self.managed_variable_latest(ty))
10744    }
10745
10746    #[cfg(feature = "discovery")]
10747    pub fn request_managed_variable(&self, ty: DataType) -> TelemetryResult<()> {
10748        if is_internal_control_type(ty) {
10749            return Err(TelemetryError::InvalidType);
10750        }
10751        if !self.can_read_managed_variable(ty) {
10752            return Err(TelemetryError::PermissionDenied);
10753        }
10754        let sender = self.sender_arc();
10755        let pkt =
10756            discovery::build_managed_variable_request(sender.as_ref(), self.clock.now_ms(), ty)?;
10757        self.tx_item(RouterTxItem::Broadcast(RouterItem::Packet(pkt)))
10758    }
10759
10760    #[cfg(feature = "discovery")]
10761    pub fn request_managed_variable_by_name(&self, ty_name: &str) -> TelemetryResult<()> {
10762        let ty = DataType::try_named(ty_name).ok_or(TelemetryError::InvalidType)?;
10763        self.request_managed_variable(ty)
10764    }
10765
10766    /// Export the current discovery-driven network topology view.
10767    #[cfg(feature = "discovery")]
10768    pub fn export_topology(&self) -> TopologySnapshot {
10769        let now_ms = self.clock.now_ms();
10770        let mut st = self.state.lock();
10771        if Self::prune_discovery_routes_locked(&mut st, now_ms) {
10772            Self::note_discovery_topology_change_locked(&mut st, now_ms);
10773        }
10774        let routes = st
10775            .discovery_routes
10776            .iter()
10777            .filter_map(|(&side_id, route)| {
10778                let side = st.sides.get(side_id)?.as_ref()?;
10779                let announcers = route
10780                    .announcers
10781                    .iter()
10782                    .map(|(sender_id, sender_state)| TopologyAnnouncerRoute {
10783                        sender_id: sender_id.clone(),
10784                        reachable_endpoints: sender_state
10785                            .reachable
10786                            .iter()
10787                            .copied()
10788                            .filter(|ep| !discovery::is_router_control_endpoint(*ep))
10789                            .collect(),
10790                        reachable_network_variables: sender_state
10791                            .reachable_network_variables
10792                            .clone(),
10793                        reachable_timesync_sources: sender_state.reachable_timesync_sources.clone(),
10794                        routers: sender_state.topology_boards.clone(),
10795                        last_seen_ms: sender_state.last_seen_ms,
10796                        age_ms: now_ms.saturating_sub(sender_state.last_seen_ms),
10797                    })
10798                    .collect();
10799                Some(TopologySideRoute {
10800                    side_id,
10801                    side_name: side.name.to_string(),
10802                    reachable_endpoints: route
10803                        .reachable
10804                        .iter()
10805                        .copied()
10806                        .filter(|ep| !discovery::is_router_control_endpoint(*ep))
10807                        .collect(),
10808                    reachable_network_variables: route.reachable_network_variables.clone(),
10809                    reachable_timesync_sources: route.reachable_timesync_sources.clone(),
10810                    announcers,
10811                    last_seen_ms: route.last_seen_ms,
10812                    age_ms: now_ms.saturating_sub(route.last_seen_ms),
10813                })
10814            })
10815            .collect();
10816        let routers = self.advertised_discovery_topology_for_link_locked(&st, now_ms, true, None);
10817        let advertised_endpoints =
10818            self.advertised_discovery_endpoints_for_link_locked(&st, now_ms, true, None);
10819        let advertised_timesync_sources =
10820            self.advertised_discovery_timesync_sources_for_link_locked(&st, now_ms, None);
10821        let links = discovery::topology_links_from_boards(&routers);
10822        TopologySnapshot {
10823            advertised_endpoints,
10824            advertised_timesync_sources,
10825            routers,
10826            links,
10827            routes,
10828            current_announce_interval_ms: st.discovery_cadence.current_interval_ms,
10829            next_announce_ms: st.discovery_cadence.next_announce_ms,
10830        }
10831    }
10832
10833    #[cfg(feature = "discovery")]
10834    pub fn client_stats(&self, sender_id: &str) -> Option<ClientStatsSnapshot> {
10835        let now_ms = self.clock.now_ms();
10836        let st = self.state.lock();
10837        let mut side_ids = Vec::new();
10838        let mut side_names = Vec::new();
10839        let mut last_seen_ms = None::<u64>;
10840        let mut reachable_endpoints = Vec::new();
10841        let mut reachable_timesync_sources = Vec::new();
10842        let mut packets_sent = 0u64;
10843        let mut packets_received = 0u64;
10844        let mut bytes_sent = 0u64;
10845        let mut bytes_received = 0u64;
10846
10847        for (side_id, route) in &st.discovery_routes {
10848            let Some(sender_state) = route.announcers.get(sender_id) else {
10849                continue;
10850            };
10851            side_ids.push(*side_id);
10852            if let Some(side_name) = st
10853                .sides
10854                .get(*side_id)
10855                .and_then(|side| side.as_ref())
10856                .map(|side| side.name.clone())
10857            {
10858                side_names.push(side_name.to_string());
10859            }
10860            last_seen_ms = Some(last_seen_ms.unwrap_or(0).max(sender_state.last_seen_ms));
10861            reachable_endpoints.extend(sender_state.reachable.iter().copied());
10862            reachable_timesync_sources
10863                .extend(sender_state.reachable_timesync_sources.iter().cloned());
10864            if let Some(stats) = st.side_runtime_stats.get(side_id) {
10865                packets_sent = packets_sent.saturating_add(stats.tx_packets);
10866                packets_received = packets_received.saturating_add(stats.rx_packets);
10867                bytes_sent = bytes_sent.saturating_add(stats.tx_bytes);
10868                bytes_received = bytes_received.saturating_add(stats.rx_bytes);
10869            }
10870        }
10871
10872        if side_ids.is_empty() {
10873            return None;
10874        }
10875        reachable_endpoints.retain(|ep| !discovery::is_router_control_endpoint(*ep));
10876        crate::small_sort::sort(&mut reachable_endpoints);
10877        reachable_endpoints.dedup();
10878        reachable_timesync_sources.sort_unstable();
10879        reachable_timesync_sources.dedup();
10880        crate::small_sort::sort(&mut side_ids);
10881        side_ids.dedup();
10882        side_names.sort_unstable();
10883        side_names.dedup();
10884        let age_ms = last_seen_ms.map(|seen| now_ms.saturating_sub(seen));
10885        Some(ClientStatsSnapshot {
10886            sender_id: sender_id.to_string(),
10887            connected: age_ms.is_some_and(|age| age <= DISCOVERY_ROUTE_TTL_MS),
10888            side_ids,
10889            side_names,
10890            last_seen_ms,
10891            age_ms,
10892            reachable_endpoints,
10893            reachable_timesync_sources,
10894            packets_sent,
10895            packets_received,
10896            bytes_sent,
10897            bytes_received,
10898        })
10899    }
10900
10901    pub fn export_runtime_stats(&self) -> RuntimeStatsSnapshot {
10902        let now_ms = self.clock.now_ms();
10903        let isr_rx = self.isr_rx_queue.snapshot().unwrap_or((0, 0));
10904        let st = self.state.lock();
10905
10906        let mut sides = Vec::new();
10907        for (side_id, side) in st.sides.iter().enumerate() {
10908            let Some(side) = side.as_ref() else { continue };
10909            let stats = st
10910                .side_runtime_stats
10911                .get(&side_id)
10912                .cloned()
10913                .unwrap_or_default();
10914            let adaptive = st
10915                .adaptive_route_stats
10916                .get(&side_id)
10917                .cloned()
10918                .unwrap_or_default()
10919                .snapshot(now_ms, true);
10920            let (tx_template_count, rx_template_count) = st
10921                .side_transport
10922                .get(&side_id)
10923                .map(|state| (state.tx_template_count(), state.rx_template_count()))
10924                .unwrap_or((0, 0));
10925            let mut data_types: Vec<RuntimeTypeStats> = stats
10926                .data_types
10927                .into_iter()
10928                .map(|(ty, item)| RuntimeTypeStats {
10929                    data_type: DataType(ty),
10930                    tx_packets: item.tx_packets,
10931                    tx_bytes: item.tx_bytes,
10932                    rx_packets: item.rx_packets,
10933                    rx_bytes: item.rx_bytes,
10934                    relayed_tx_packets: item.relayed_tx_packets,
10935                    relayed_tx_bytes: item.relayed_tx_bytes,
10936                    relayed_rx_packets: item.relayed_rx_packets,
10937                    relayed_rx_bytes: item.relayed_rx_bytes,
10938                    tx_retries: item.tx_retries,
10939                    handler_failures: item.handler_failures,
10940                })
10941                .collect();
10942            data_types.sort_unstable_by_key(|item| item.data_type.as_u32());
10943            sides.push(RuntimeSideStats {
10944                side_id,
10945                side_name: side.name.to_string(),
10946                reliable_enabled: side.opts.reliable_enabled,
10947                link_local_enabled: side.opts.link_local_enabled,
10948                header_template_enabled: side.opts.header_template_enabled,
10949                max_frame_bytes: side.opts.max_frame_bytes,
10950                compact_header_target_bytes: side.opts.compact_header_target_bytes,
10951                side_transport_profile: side.opts.effective_transport_profile().as_str(),
10952                ingress_enabled: side.opts.ingress_enabled,
10953                egress_enabled: side.opts.egress_enabled,
10954                tx_packets: stats.tx_packets,
10955                tx_bytes: stats.tx_bytes,
10956                rx_packets: stats.rx_packets,
10957                rx_bytes: stats.rx_bytes,
10958                relayed_tx_packets: stats.relayed_tx_packets,
10959                relayed_tx_bytes: stats.relayed_tx_bytes,
10960                relayed_rx_packets: stats.relayed_rx_packets,
10961                relayed_rx_bytes: stats.relayed_rx_bytes,
10962                local_delivery_packets: stats.local_delivery_packets,
10963                tx_retries: stats.tx_retries,
10964                tx_handler_failures: stats.tx_handler_failures,
10965                local_handler_failures: stats.local_handler_failures,
10966                total_handler_retries: stats.total_handler_retries,
10967                side_transport_full_frames: stats.side_transport_full_frames,
10968                side_transport_compact_frames: stats.side_transport_compact_frames,
10969                side_transport_compact_delta_frames: stats.side_transport_compact_delta_frames,
10970                side_transport_compact_omitted_timestamp_frames: stats
10971                    .side_transport_compact_omitted_timestamp_frames,
10972                side_transport_chunk_frames: stats.side_transport_chunk_frames,
10973                side_transport_raw_bytes: stats.side_transport_raw_bytes,
10974                side_transport_wire_bytes: stats.side_transport_wire_bytes,
10975                side_transport_bytes_saved: stats.side_transport_bytes_saved,
10976                side_transport_min_compact_overhead_bytes: stats
10977                    .side_transport_min_compact_overhead_bytes,
10978                side_transport_max_compact_overhead_bytes: stats
10979                    .side_transport_max_compact_overhead_bytes,
10980                side_transport_compact_target_misses: stats.side_transport_compact_target_misses,
10981                side_transport_template_evictions: stats.side_transport_template_evictions,
10982                side_transport_tx_template_count: tx_template_count,
10983                side_transport_rx_template_count: rx_template_count,
10984                max_side_transport_templates: side.opts.max_side_transport_templates,
10985                adaptive,
10986                data_types,
10987            });
10988        }
10989
10990        let mut route_modes: Vec<RouteModeStats> = st
10991            .route_selection_cursors
10992            .iter()
10993            .map(|(src, cursor)| RouteModeStats {
10994                src_side_id: *src,
10995                selection_mode: st.source_route_modes.get(src).copied(),
10996                cursor: *cursor,
10997            })
10998            .collect();
10999        for src in st.source_route_modes.keys() {
11000            if !route_modes.iter().any(|mode| mode.src_side_id == *src) {
11001                route_modes.push(RouteModeStats {
11002                    src_side_id: *src,
11003                    selection_mode: st.source_route_modes.get(src).copied(),
11004                    cursor: 0,
11005                });
11006            }
11007        }
11008        route_modes.sort_unstable_by_key(|mode| mode.src_side_id.unwrap_or(usize::MAX));
11009
11010        let mut route_overrides: Vec<RouteOverrideStats> = st
11011            .route_overrides
11012            .iter()
11013            .map(|((src, dst), enabled)| RouteOverrideStats {
11014                src_side_id: *src,
11015                dst_side_id: *dst,
11016                enabled: *enabled,
11017            })
11018            .collect();
11019        route_overrides.sort_unstable_by_key(|item| {
11020            (item.src_side_id.unwrap_or(usize::MAX), item.dst_side_id)
11021        });
11022
11023        let mut typed_route_overrides: Vec<TypedRouteOverrideStats> = st
11024            .typed_route_overrides
11025            .iter()
11026            .map(|((src, ty, dst), enabled)| TypedRouteOverrideStats {
11027                src_side_id: *src,
11028                data_type: DataType(*ty),
11029                dst_side_id: *dst,
11030                enabled: *enabled,
11031            })
11032            .collect();
11033        typed_route_overrides.sort_unstable_by_key(|item| {
11034            (
11035                item.src_side_id.unwrap_or(usize::MAX),
11036                item.data_type.as_u32(),
11037                item.dst_side_id,
11038            )
11039        });
11040
11041        let mut route_weights: Vec<RouteWeightStats> = st
11042            .route_weights
11043            .iter()
11044            .map(|((src, dst), weight)| RouteWeightStats {
11045                src_side_id: *src,
11046                dst_side_id: *dst,
11047                weight: *weight,
11048            })
11049            .collect();
11050        route_weights.sort_unstable_by_key(|item| {
11051            (item.src_side_id.unwrap_or(usize::MAX), item.dst_side_id)
11052        });
11053
11054        let mut route_priorities: Vec<RoutePriorityStats> = st
11055            .route_priorities
11056            .iter()
11057            .map(|((src, dst), priority)| RoutePriorityStats {
11058                src_side_id: *src,
11059                dst_side_id: *dst,
11060                priority: *priority,
11061            })
11062            .collect();
11063        route_priorities.sort_unstable_by_key(|item| {
11064            (item.src_side_id.unwrap_or(usize::MAX), item.dst_side_id)
11065        });
11066
11067        #[cfg(feature = "discovery")]
11068        let discovery = DiscoveryRuntimeStats {
11069            route_count: st.discovery_routes.len(),
11070            announcer_count: st
11071                .discovery_routes
11072                .values()
11073                .map(|route| route.announcers.len())
11074                .sum(),
11075            current_announce_interval_ms: Some(st.discovery_cadence.current_interval_ms),
11076            next_announce_ms: Some(st.discovery_cadence.next_announce_ms),
11077        };
11078        #[cfg(not(feature = "discovery"))]
11079        let discovery = DiscoveryRuntimeStats {
11080            route_count: 0,
11081            announcer_count: 0,
11082            current_announce_interval_ms: None,
11083            next_announce_ms: None,
11084        };
11085
11086        RuntimeStatsSnapshot {
11087            sides,
11088            route_modes,
11089            route_overrides,
11090            typed_route_overrides,
11091            route_weights,
11092            route_priorities,
11093            queues: QueueRuntimeStats {
11094                rx_len: isr_rx.0.saturating_add(st.received_queue.len()),
11095                rx_bytes: isr_rx.1.saturating_add(st.received_queue.bytes_used()),
11096                tx_len: st.transmit_queue.len(),
11097                tx_bytes: st.transmit_queue.bytes_used(),
11098                replay_len: 0,
11099                replay_bytes: 0,
11100                recent_rx_len: st.recent_rx.len(),
11101                recent_rx_bytes: st.recent_rx.bytes_used(),
11102                reliable_rx_buffered_len: st.reliable_rx_buffer_len(),
11103                reliable_rx_buffered_bytes: st.reliable_rx_buffered_bytes(),
11104                shared_queue_bytes_used: st.shared_queue_bytes_used(),
11105            },
11106            reliable: ReliableRuntimeStats {
11107                reliable_return_route_count: st.reliable_return_routes.len(),
11108                end_to_end_pending_count: st.end_to_end_reliable_tx.len(),
11109                end_to_end_pending_destination_count: st
11110                    .end_to_end_reliable_tx
11111                    .values()
11112                    .map(|sent| sent.pending_destinations.len())
11113                    .sum(),
11114                end_to_end_acked_cache_count: 0,
11115            },
11116            discovery,
11117            total_handler_failures: st.total_handler_failures,
11118            total_handler_retries: st.total_handler_retries,
11119        }
11120    }
11121
11122    /// Export current router memory usage/layout as JSON for profiling.
11123    pub fn export_memory_layout_json(&self) -> String {
11124        let isr_rx = self.isr_rx_queue.snapshot().unwrap_or((0, 0));
11125        let st = self.state.lock();
11126        #[cfg(feature = "discovery")]
11127        let discovery_bytes = st.discovery_bytes_used();
11128        #[cfg(not(feature = "discovery"))]
11129        let discovery_bytes = 0usize;
11130        let schema_bytes = crate::config::schema_bytes_used();
11131        let network_variable_cache_bytes = st
11132            .managed_variable_latest
11133            .values()
11134            .map(|entry| entry.packet.byte_cost())
11135            .sum::<usize>();
11136        let mut out = String::new();
11137        let memory = st.memory;
11138        let _ = fmt::Write::write_fmt(
11139            &mut out,
11140            format_args!(
11141                "{{\"kind\":\"router\",\
11142                 \"shared_queue_bytes_used\":{},\"shared_queue_bytes_allocated\":{},\
11143                 \"rx_queue_bytes_used\":{},\"rx_queue_bytes_allocated\":{},\"rx_queue_len\":{},\
11144                 \"isr_rx_queue_bytes_used\":{},\"isr_rx_queue_bytes_allocated\":{},\"isr_rx_queue_len\":{},\
11145                 \"tx_queue_bytes_used\":{},\"tx_queue_bytes_allocated\":{},\"tx_queue_len\":{},\
11146                 \"recent_rx_bytes_used\":{},\"recent_rx_bytes_allocated\":{},\"recent_rx_len\":{},\
11147                 \"reliable_rx_buffer_bytes_used\":{},\"reliable_rx_buffer_bytes_allocated\":{},\"reliable_rx_buffer_len\":{},\
11148                 \"discovery_bytes_used\":{},\"discovery_bytes_allocated\":{},\
11149                 \"schema_bytes_used\":{},\"schema_bytes_allocated\":{},\
11150                 \"network_variable_cache_bytes_used\":{},\"network_variable_cache_bytes_allocated\":{},\"network_variable_cache_len\":{}}}",
11151                st.shared_queue_bytes_used(),
11152                memory.max_queue_budget,
11153                st.received_queue.bytes_used(),
11154                st.received_queue.max_bytes(),
11155                st.received_queue.len(),
11156                isr_rx.1,
11157                memory.max_queue_budget,
11158                isr_rx.0,
11159                st.transmit_queue.bytes_used(),
11160                st.transmit_queue.max_bytes(),
11161                st.transmit_queue.len(),
11162                st.recent_rx.bytes_used(),
11163                st.recent_rx.max_bytes(),
11164                st.recent_rx.len(),
11165                st.reliable_rx_buffered_bytes(),
11166                memory.max_queue_budget,
11167                st.reliable_rx_buffer_len(),
11168                discovery_bytes,
11169                memory.max_queue_budget,
11170                schema_bytes,
11171                memory.max_queue_budget,
11172                network_variable_cache_bytes,
11173                memory.max_queue_budget,
11174                st.managed_variable_latest.len(),
11175            ),
11176        );
11177        out
11178    }
11179
11180    #[cfg(test)]
11181    pub(crate) fn debug_end_to_end_pending_destination_count(
11182        &self,
11183        packet_id: u64,
11184    ) -> Option<usize> {
11185        let st = self.state.lock();
11186        st.end_to_end_reliable_tx
11187            .get(&packet_id)
11188            .map(|sent| sent.pending_destinations.len())
11189    }
11190
11191    #[cfg(test)]
11192    pub(crate) fn debug_end_to_end_tracked_count(&self) -> usize {
11193        let st = self.state.lock();
11194        st.end_to_end_reliable_tx.len()
11195    }
11196
11197    #[cfg(test)]
11198    pub(crate) fn debug_reliable_return_route_count(&self) -> usize {
11199        let st = self.state.lock();
11200        st.reliable_return_routes.len()
11201    }
11202
11203    #[cfg(test)]
11204    pub(crate) fn debug_side_storage(&self) -> (usize, usize) {
11205        let st = self.state.lock();
11206        (st.sides.len(), st.sides.capacity())
11207    }
11208
11209    #[cfg(test)]
11210    pub(crate) fn debug_queue_lengths(&self) -> (usize, usize, usize) {
11211        let st = self.state.lock();
11212        (
11213            st.received_queue.len(),
11214            st.transmit_queue.len(),
11215            st.recent_rx.len(),
11216        )
11217    }
11218
11219    #[cfg(test)]
11220    pub(crate) fn debug_shared_queue_bytes_used(&self) -> usize {
11221        let st = self.state.lock();
11222        st.shared_queue_bytes_used()
11223    }
11224
11225    #[cfg(test)]
11226    pub(crate) fn debug_recent_rx_capacity(&self) -> (usize, usize) {
11227        let st = self.state.lock();
11228        (st.recent_rx.capacity(), st.recent_rx.max_bytes())
11229    }
11230
11231    /// Compute a de-dupe hash for a RouterItem.
11232    /// Uses packet ID for Packet items, and attempts to extract packet ID from
11233    /// packed bytes. If extraction fails, hashes raw bytes as a fallback.
11234    fn get_hash(item: &RouterItem) -> u64 {
11235        match item {
11236            RouterItem::Packet(pkt) => pkt.packet_id(),
11237            RouterItem::Packed(bytes) => {
11238                match wire_format::packet_id_from_wire(bytes.as_ref()) {
11239                    Ok(id) => id,
11240                    Err(_e) => {
11241                        // Fallback: if bytes are malformed (or compression feature mismatch),
11242                        // hash raw bytes so we can still dedupe identical network duplicates.
11243                        let h: u64 = 0x9E37_79B9_7F4A_7C15;
11244                        hash_bytes_u64(h, bytes.as_ref())
11245                    }
11246                }
11247            }
11248        }
11249    }
11250
11251    /// Remove a hash from the ring buffer of recent RX IDs.
11252    fn remove_pkt_id(&self, item: &RouterItem) {
11253        let hash = Self::get_hash(item);
11254        let mut st = self.state.lock();
11255        st.recent_rx.remove_value(&hash);
11256    }
11257
11258    /// Compute a de-dupe ID for a RouterItem and record it.
11259    /// Returns true if this item was seen recently (and should be skipped).
11260    fn is_duplicate_pkt(&self, item: &RouterItem) -> TelemetryResult<bool> {
11261        let id = Self::get_hash(item);
11262        let mut st = self.state.lock();
11263        if st.recent_rx.contains(&id) {
11264            Ok(true)
11265        } else {
11266            st.push_recent_rx(id)?;
11267            Ok(false)
11268        }
11269    }
11270
11271    /// Error helper when we have a full Packet.
11272    ///
11273    /// Sends a TelemetryError packet to all local endpoints except the failed one (if any).
11274    /// If no local endpoints remain, falls back to `fallback_stdout`.
11275    fn handle_callback_error(
11276        &self,
11277        pkt: &Packet,
11278        dest: Option<DataEndpoint>,
11279        e: TelemetryError,
11280        called_from_queue: bool,
11281    ) -> TelemetryResult<()> {
11282        let device = self.sender_arc();
11283        let error_msg = match dest {
11284            Some(failed_local) => format!(
11285                "Handler for endpoint {:?} failed on device {:?}: {:?}",
11286                failed_local, device, e
11287            ),
11288            None => format!("TX Handler failed on device {:?}: {:?}", device, e),
11289        };
11290
11291        let mut recipients: Vec<DataEndpoint> = pkt
11292            .endpoints()
11293            .iter()
11294            .copied()
11295            .filter(|&ep| self.cfg.is_local_endpoint(ep))
11296            .collect();
11297        crate::small_sort::sort(&mut recipients);
11298        recipients.dedup();
11299
11300        if let Some(failed_local) = dest {
11301            recipients.retain(|&ep| ep != failed_local);
11302        }
11303
11304        // A side TX failure cannot be reported through the same failed
11305        // transport. Doing so recursively creates TelemetryError packets until
11306        // an embedded caller exhausts its stack.
11307        if recipients.is_empty() && dest.is_none() {
11308            fallback_stdout(&error_msg);
11309            return Ok(());
11310        }
11311
11312        // Local endpoint failures may still be reported to the packet's other
11313        // endpoints when no local error recipient exists.
11314        if recipients.is_empty() {
11315            recipients = pkt.endpoints().to_vec();
11316            crate::small_sort::sort(&mut recipients);
11317            recipients.dedup();
11318            if let Some(failed_local) = dest {
11319                recipients.retain(|&ep| ep != failed_local);
11320            }
11321        }
11322
11323        if recipients.is_empty() {
11324            fallback_stdout(&error_msg);
11325            return Ok(());
11326        }
11327
11328        let payload = make_error_payload(&error_msg);
11329
11330        let sender = self.sender_arc();
11331        let error_pkt = Packet::new(
11332            DataType::TelemetryError,
11333            &recipients,
11334            sender.as_ref(),
11335            self.packet_timestamp_ms(),
11336            crate::shared_bytes::convert(payload),
11337        )?;
11338
11339        self.emit_internal_tx(
11340            RouterTxItem::Broadcast(RouterItem::Packet(error_pkt)),
11341            false,
11342            called_from_queue,
11343        )
11344    }
11345
11346    // ---------- PUBLIC API: queues ----------
11347
11348    /// Drain the transmit queue fully.
11349    #[inline]
11350    pub fn process_tx_queue(&self) -> TelemetryResult<()> {
11351        self.process_tx_queue_with_timeout(0)
11352    }
11353
11354    /// Drain both TX and RX queues fully (same semantics as `*_with_timeout(0)`).
11355    #[inline]
11356    pub fn process_all_queues(&self) -> TelemetryResult<()> {
11357        self.process_all_queues_with_timeout(0)
11358    }
11359
11360    /// Clear both the transmit and receive queues without processing.
11361    #[inline]
11362    pub fn clear_queues(&self) {
11363        let mut st = self.state.lock();
11364        st.transmit_queue.clear();
11365        st.received_queue.clear();
11366        drop(st);
11367        let _ = self.isr_rx_queue.clear();
11368    }
11369
11370    /// Clear only the receive queue without processing.
11371    #[inline]
11372    pub fn clear_rx_queue(&self) {
11373        let mut st = self.state.lock();
11374        st.received_queue.clear();
11375        drop(st);
11376        let _ = self.isr_rx_queue.clear();
11377    }
11378
11379    /// Clear only the transmit queue without processing.
11380    #[inline]
11381    pub fn clear_tx_queue(&self) {
11382        let mut st = self.state.lock();
11383        st.transmit_queue.clear();
11384        st.tx_priority_burst = 0;
11385    }
11386
11387    fn pop_transmit_queue_locked(st: &mut RouterInner, now_ms: u64) -> Option<TxQueued> {
11388        st.transmit_queue.pop_front_fair_if(
11389            &mut st.tx_priority_burst,
11390            MAX_PRIORITY_BURST,
11391            |item| item.priority,
11392            |item| item.retry_after_ms <= now_ms,
11393        )
11394    }
11395
11396    /// Process packets in the transmit queue for up to `timeout_ms` milliseconds.
11397    /// If `timeout_ms == 0`, drains the queue fully.
11398    fn process_tx_queue_with_timeout_impl(&self, timeout_ms: u32) -> TelemetryResult<()> {
11399        self.expire_side_chunks();
11400        let start = self.clock.now_ms();
11401        loop {
11402            let received_control = crate::memory_admission::allow_drain(
11403                self.drain_queued_control_rx_before_tx(start, timeout_ms),
11404            )?;
11405            let control_budget_used = received_control
11406                && timeout_ms != 0
11407                && self.clock.now_ms().wrapping_sub(start) >= u64::from(timeout_ms);
11408            // Complete one RX/TX pair even after a cooperative RX overrun.
11409            // Defer timeout cancellation until queued receipts are serviced.
11410            if !control_budget_used {
11411                crate::memory_admission::allow_drain(self.process_reliable_timeouts())?;
11412                crate::memory_admission::allow_drain(self.process_end_to_end_reliable_timeouts())?;
11413            }
11414
11415            let pkt_opt = {
11416                let mut st = self.state.lock();
11417                Self::pop_transmit_queue_locked(&mut st, self.clock.now_ms())
11418            };
11419            let Some(pkt) = pkt_opt else { break };
11420            self.tx_item_impl(pkt.item, pkt.ignore_local, true)?;
11421            if timeout_ms != 0 && self.clock.now_ms().wrapping_sub(start) >= timeout_ms as u64 {
11422                break;
11423            }
11424        }
11425        Ok(())
11426    }
11427
11428    /// Process packets in the transmit queue for up to `timeout_ms` milliseconds.
11429    /// If `timeout_ms == 0`, drains the queue fully.
11430    pub fn process_tx_queue_with_timeout(&self, timeout_ms: u32) -> TelemetryResult<()> {
11431        #[cfg(feature = "timesync")]
11432        let _ = crate::memory_admission::allow_drain(self.poll_timesync())?;
11433        #[cfg(feature = "discovery")]
11434        let _ = crate::memory_admission::allow_drain(self.poll_discovery())?;
11435        self.process_tx_queue_with_timeout_impl(timeout_ms)
11436    }
11437
11438    /// Drain queued transmit work without generating periodic discovery/time-sync
11439    /// announcements. Reliable retries and queued discovery reception are still
11440    /// serviced. Applications using this between ingress bursts must also call
11441    /// `periodic` or the normal queue methods regularly for maintenance.
11442    /// A zero timeout drains the queue fully.
11443    pub fn dispatch_tx_queue_with_timeout(&self, timeout_ms: u32) -> TelemetryResult<()> {
11444        self.process_tx_queue_with_timeout_impl(timeout_ms)
11445    }
11446
11447    /// Process a single queued receive item.
11448    #[inline]
11449    fn process_rx_queue_item(&self, item: RouterRxItem) -> TelemetryResult<()> {
11450        self.rx_item(&item, true)
11451    }
11452
11453    /// Process packets in the receive queue for up to `timeout_ms` milliseconds.
11454    /// If `timeout_ms == 0`, drains the queue fully.
11455    fn process_rx_queue_with_timeout_impl(&self, timeout_ms: u32) -> TelemetryResult<()> {
11456        self.expire_side_chunks();
11457        let start = self.clock.now_ms();
11458        loop {
11459            let item_opt = self.isr_rx_queue.pop_front().unwrap_or(None).or_else(|| {
11460                let mut st = self.state.lock();
11461                st.received_queue.pop_front()
11462            });
11463            let Some(item) = item_opt else { break };
11464            self.process_rx_queue_item(item)?;
11465            if timeout_ms != 0 && self.clock.now_ms().wrapping_sub(start) >= timeout_ms as u64 {
11466                break;
11467            }
11468        }
11469        Ok(())
11470    }
11471
11472    /// Process packets in the receive queue for up to `timeout_ms` milliseconds.
11473    /// If `timeout_ms == 0`, drains the queue fully.
11474    pub fn process_rx_queue_with_timeout(&self, timeout_ms: u32) -> TelemetryResult<()> {
11475        #[cfg(feature = "timesync")]
11476        let _ = crate::memory_admission::allow_drain(self.poll_timesync())?;
11477        #[cfg(feature = "discovery")]
11478        let _ = crate::memory_admission::allow_drain(self.poll_discovery())?;
11479        self.process_rx_queue_with_timeout_impl(timeout_ms)
11480    }
11481
11482    /// Process both transmit and receive queues for up to `timeout_ms` milliseconds.
11483    /// If `timeout_ms == 0`, drains both queues fully.
11484    fn process_all_queues_with_timeout_impl(&self, timeout_ms: u32) -> TelemetryResult<()> {
11485        self.expire_side_chunks();
11486        // Share the deadline between directions. Splitting a 1 ms budget in
11487        // half rounded TX down to zero and serviced only one transmit item,
11488        // even when the remainder of the pass was idle. One TX/RX pair is
11489        // atomic for fairness; a slow handler may overrun the deadline.
11490        let start = self.clock.now_ms();
11491        loop {
11492            let received_control = crate::memory_admission::allow_drain(
11493                self.drain_queued_control_rx_before_tx(start, timeout_ms),
11494            )?;
11495            let mut did_any = received_control;
11496            let control_budget_used = received_control
11497                && timeout_ms != 0
11498                && self.clock.now_ms().wrapping_sub(start) >= u64::from(timeout_ms);
11499            if !control_budget_used {
11500                crate::memory_admission::allow_drain(self.process_reliable_timeouts())?;
11501                crate::memory_admission::allow_drain(self.process_end_to_end_reliable_timeouts())?;
11502            }
11503
11504            if let Some(pkt) = {
11505                let mut st = self.state.lock();
11506                let now = self.clock.now_ms();
11507                let inner = &mut *st;
11508                inner.transmit_queue.pop_front_fair_if(
11509                    &mut inner.tx_priority_burst,
11510                    MAX_PRIORITY_BURST,
11511                    |item| item.priority,
11512                    |item| {
11513                        item.retry_after_ms <= now && (!control_budget_used || item.priority >= 200)
11514                    },
11515                )
11516            } {
11517                self.tx_item_impl(pkt.item, pkt.ignore_local, true)?;
11518                did_any = true;
11519            }
11520            // Control reception must not starve queued ACKs/transmissions.
11521            if control_budget_used {
11522                return Ok(());
11523            }
11524            if let Some(item) = self.isr_rx_queue.pop_front().unwrap_or(None).or_else(|| {
11525                let mut st = self.state.lock();
11526                st.received_queue.pop_front()
11527            }) {
11528                self.process_rx_queue_item(item)?;
11529                did_any = true;
11530            }
11531            if !did_any
11532                || (timeout_ms != 0
11533                    && self.clock.now_ms().wrapping_sub(start) >= u64::from(timeout_ms))
11534            {
11535                break;
11536            }
11537        }
11538
11539        Ok(())
11540    }
11541
11542    /// Process both transmit and receive queues for up to `timeout_ms` milliseconds.
11543    /// If `timeout_ms == 0`, drains both queues fully.
11544    pub fn process_all_queues_with_timeout(&self, timeout_ms: u32) -> TelemetryResult<()> {
11545        #[cfg(feature = "timesync")]
11546        let _ = crate::memory_admission::allow_drain(self.poll_timesync())?;
11547        #[cfg(feature = "discovery")]
11548        let _ = crate::memory_admission::allow_drain(self.poll_discovery())?;
11549        self.process_all_queues_with_timeout_impl(timeout_ms)
11550    }
11551
11552    /// Drain queued RX/TX work without generating periodic announcements.
11553    /// Reliable retries and received discovery are still processed. Call
11554    /// `periodic` or poll discovery/time sync separately on a regular cadence.
11555    /// The deadline is cooperative: a single callback may exceed it.
11556    pub fn drain_queues_with_timeout(&self, timeout_ms: u32) -> TelemetryResult<()> {
11557        self.process_all_queues_with_timeout_impl(timeout_ms)
11558    }
11559
11560    /// Runs one application-loop maintenance cycle.
11561    ///
11562    /// This polls built-in time sync and discovery when those features are compiled in, then
11563    /// drains queued TX/RX work for up to `timeout_ms` milliseconds.
11564    pub fn periodic(&self, timeout_ms: u32) -> TelemetryResult<()> {
11565        #[cfg(feature = "timesync")]
11566        let _ = crate::memory_admission::allow_drain(self.poll_timesync())?;
11567
11568        #[cfg(feature = "discovery")]
11569        {
11570            let _ = crate::memory_admission::allow_drain(self.poll_discovery())?;
11571        }
11572
11573        self.process_all_queues_with_timeout_impl(timeout_ms)
11574    }
11575
11576    /// Runs one application-loop maintenance cycle without polling built-in time sync.
11577    ///
11578    /// Discovery is still polled when that feature is compiled in, then queued TX/RX work is
11579    /// drained for up to `timeout_ms` milliseconds.
11580    pub fn periodic_no_timesync(&self, timeout_ms: u32) -> TelemetryResult<()> {
11581        #[cfg(feature = "discovery")]
11582        {
11583            let _ = crate::memory_admission::allow_drain(self.poll_discovery())?;
11584        }
11585
11586        self.process_all_queues_with_timeout_impl(timeout_ms)
11587    }
11588
11589    /// Enqueue an item for later transmission with flags.
11590    #[inline]
11591    fn tx_queue_item_with_flags(
11592        &self,
11593        item: RouterTxItem,
11594        ignore_local: bool,
11595    ) -> TelemetryResult<()> {
11596        self.tx_queue_item_with_flags_at(item, ignore_local, 0)
11597    }
11598
11599    fn tx_queue_item_for_retry(
11600        &self,
11601        item: RouterTxItem,
11602        ignore_local: bool,
11603    ) -> TelemetryResult<()> {
11604        self.tx_queue_item_with_flags_at(item, ignore_local, self.clock.now_ms().saturating_add(1))
11605    }
11606
11607    fn tx_queue_item_with_flags_at(
11608        &self,
11609        item: RouterTxItem,
11610        ignore_local: bool,
11611        retry_after_ms: u64,
11612    ) -> TelemetryResult<()> {
11613        let priority = match &item {
11614            RouterTxItem::Broadcast(data) => Self::router_item_priority(data)?,
11615            RouterTxItem::EndToEndReplay { .. } => {
11616                Self::router_item_priority_bumped(DataType::ReliableAck)
11617            }
11618            RouterTxItem::ToSide { data, .. } => Self::router_item_priority(data)?,
11619            RouterTxItem::ReliableReplay { bytes, .. } => {
11620                let ty = wire_format::peek_routing_envelope(bytes.as_ref())?.ty;
11621                Self::router_item_priority_bumped(ty)
11622            }
11623        };
11624        self.tx_queue_item_with_priority_at(item, ignore_local, priority, retry_after_ms)
11625    }
11626
11627    #[inline]
11628    fn tx_queue_item_with_priority(
11629        &self,
11630        item: RouterTxItem,
11631        ignore_local: bool,
11632        priority: u8,
11633    ) -> TelemetryResult<()> {
11634        self.tx_queue_item_with_priority_at(item, ignore_local, priority, 0)
11635    }
11636
11637    fn tx_queue_item_with_priority_at(
11638        &self,
11639        item: RouterTxItem,
11640        ignore_local: bool,
11641        priority: u8,
11642        retry_after_ms: u64,
11643    ) -> TelemetryResult<()> {
11644        let mut st = self.state.lock();
11645        st.push_transmit(TxQueued {
11646            item,
11647            ignore_local,
11648            priority,
11649            retry_after_ms,
11650        })?;
11651        Ok(())
11652    }
11653
11654    /// Enqueue an item for later transmission (default: local dispatch enabled).
11655    #[inline]
11656    fn tx_queue_item(&self, item: RouterTxItem) -> TelemetryResult<()> {
11657        self.tx_queue_item_with_flags(item, false)
11658    }
11659
11660    #[inline]
11661    fn try_enter_side_tx(&self) -> Option<crate::lock::ReentryGuard<'_>> {
11662        self.side_tx_gate.try_enter()
11663    }
11664
11665    #[inline]
11666    fn side_tx_active(&self) -> bool {
11667        self.side_tx_gate.is_active()
11668    }
11669
11670    // ---------- PUBLIC API: RX queue ----------
11671
11672    /// Drain the receive queue fully.
11673    #[inline]
11674    pub fn process_rx_queue(&self) -> TelemetryResult<()> {
11675        self.process_rx_queue_with_timeout(0)
11676    }
11677
11678    /// Enqueue packed bytes for RX processing as locally-originated input.
11679    #[inline]
11680    pub fn rx_packed_queue(&self, bytes: &[u8]) -> TelemetryResult<()> {
11681        crate::memory_admission::check_receive(bytes)?;
11682        let data = RouterItem::Packed(crate::shared_bytes::convert(Arc::<[u8]>::from(bytes)));
11683        let priority = Self::router_item_priority(&data)?;
11684        let mut st = self.state.lock();
11685        st.push_received(RouterRxItem {
11686            src: None,
11687            data,
11688            priority,
11689        })?;
11690        Ok(())
11691    }
11692
11693    /// ISR-safe, non-blocking enqueue of packed bytes for RX processing.
11694    ///
11695    /// Returns `TelemetryError::Io("rx queue busy")` if another context is
11696    /// currently mutating the ISR RX queue.
11697    #[inline]
11698    pub fn rx_packed_queue_isr(&self, bytes: &[u8]) -> TelemetryResult<()> {
11699        crate::memory_admission::check_receive(bytes)?;
11700        let data = RouterItem::Packed(crate::shared_bytes::convert(Arc::<[u8]>::from(bytes)));
11701        let priority = Self::router_item_priority(&data)?;
11702        self.isr_rx_queue.push_back_prioritized(RouterRxItem {
11703            src: None,
11704            data,
11705            priority,
11706        })
11707    }
11708
11709    /// Enqueue a decoded packet for RX processing as locally-originated input.
11710    #[inline]
11711    pub fn rx_queue(&self, pkt: Packet) -> TelemetryResult<()> {
11712        pkt.validate()?;
11713        let data = RouterItem::Packet(pkt);
11714        let priority = Self::router_item_priority(&data)?;
11715        let mut st = self.state.lock();
11716        st.push_received(RouterRxItem {
11717            src: None,
11718            data,
11719            priority,
11720        })?;
11721        Ok(())
11722    }
11723
11724    /// ISR-safe, non-blocking enqueue of a packet for RX processing.
11725    ///
11726    /// Returns `TelemetryError::Io("rx queue busy")` if another context is
11727    /// currently mutating the ISR RX queue.
11728    #[inline]
11729    pub fn rx_queue_isr(&self, pkt: Packet) -> TelemetryResult<()> {
11730        pkt.validate()?;
11731        let data = RouterItem::Packet(pkt);
11732        let priority = Self::router_item_priority(&data)?;
11733        self.isr_rx_queue.push_back_prioritized(RouterRxItem {
11734            src: None,
11735            data,
11736            priority,
11737        })
11738    }
11739
11740    /// Enqueue a decoded packet for RX processing with an explicit ingress side.
11741    #[inline]
11742    pub fn rx_queue_from_side(&self, pkt: Packet, side: RouterSideId) -> TelemetryResult<()> {
11743        self.ensure_side_ingress_enabled(side)?;
11744        pkt.validate()?;
11745        let data = RouterItem::Packet(pkt);
11746        let priority = Self::router_item_priority(&data)?;
11747        let mut st = self.state.lock();
11748        st.push_received(RouterRxItem {
11749            src: Some(side),
11750            data,
11751            priority,
11752        })?;
11753        Ok(())
11754    }
11755
11756    /// ISR-safe, non-blocking enqueue of a packet with explicit source side.
11757    ///
11758    /// Returns `TelemetryError::Io("rx queue busy")` if another context is
11759    /// currently mutating the ISR RX queue.
11760    #[inline]
11761    pub fn rx_queue_from_side_isr(&self, pkt: Packet, side: RouterSideId) -> TelemetryResult<()> {
11762        self.ensure_side_ingress_enabled(side)?;
11763        pkt.validate()?;
11764        let data = RouterItem::Packet(pkt);
11765        let priority = Self::router_item_priority(&data)?;
11766        self.isr_rx_queue.push_back_prioritized(RouterRxItem {
11767            src: Some(side),
11768            data,
11769            priority,
11770        })
11771    }
11772
11773    /// Enqueue packed bytes for RX processing with an explicit ingress side.
11774    #[inline]
11775    pub fn rx_packed_queue_from_side(
11776        &self,
11777        bytes: &[u8],
11778        side: RouterSideId,
11779    ) -> TelemetryResult<()> {
11780        self.ensure_side_ingress_enabled(side)?;
11781        let Some(decoded) = self.decode_side_transport_frame(side, bytes)? else {
11782            return Ok(());
11783        };
11784        let data = RouterItem::Packed(decoded);
11785        let priority = Self::router_item_priority(&data)?;
11786        let mut st = self.state.lock();
11787        st.push_received(RouterRxItem {
11788            src: Some(side),
11789            data,
11790            priority,
11791        })?;
11792        Ok(())
11793    }
11794
11795    /// ISR-safe, non-blocking enqueue of packed bytes with source side.
11796    ///
11797    /// Returns `TelemetryError::Io("rx queue busy")` if another context is
11798    /// currently mutating the ISR RX queue.
11799    #[inline]
11800    pub fn rx_packed_queue_from_side_isr(
11801        &self,
11802        bytes: &[u8],
11803        side: RouterSideId,
11804    ) -> TelemetryResult<()> {
11805        self.ensure_side_ingress_enabled(side)?;
11806        crate::memory_admission::check_receive(bytes)?;
11807        let data = RouterItem::Packed(crate::shared_bytes::convert(Arc::<[u8]>::from(bytes)));
11808        let priority = Self::router_item_priority(&data)?;
11809        self.isr_rx_queue.push_back_prioritized(RouterRxItem {
11810            src: Some(side),
11811            data,
11812            priority,
11813        })
11814    }
11815
11816    fn retry_with_attempts<F, T>(
11817        &self,
11818        times: usize,
11819        f: F,
11820    ) -> Result<(T, usize), (TelemetryError, usize)>
11821    where
11822        F: Fn() -> TelemetryResult<T>,
11823    {
11824        let mut last_err = None;
11825        for attempt in 0..times {
11826            match f() {
11827                Ok(v) => return Ok((v, attempt + 1)),
11828                // Queue backpressure cannot clear while the same callback is
11829                // retried synchronously. Retain it for the next service pass.
11830                Err(e) if Self::is_side_tx_busy(&e) => return Err((e, attempt + 1)),
11831                Err(e) => last_err = Some((e, attempt + 1)),
11832            }
11833        }
11834        Err(last_err.expect("times > 0"))
11835    }
11836
11837    /// Check if the specified endpoint has a packet handler registered.
11838    #[inline]
11839    fn endpoint_has_packet_handler(&self, ep: DataEndpoint) -> bool {
11840        self.cfg
11841            .handlers
11842            .iter()
11843            .any(|h| h.endpoint == ep && matches!(h.handler, EndpointHandlerFn::Packet(_)))
11844    }
11845
11846    /// Check if the specified endpoint has a packed handler registered.
11847    #[inline]
11848    fn endpoint_has_packed_handler(&self, ep: DataEndpoint) -> bool {
11849        self.cfg
11850            .handlers
11851            .iter()
11852            .any(|h| h.endpoint == ep && matches!(h.handler, EndpointHandlerFn::Packed(_)))
11853    }
11854
11855    fn packet_has_local_handler(&self, pkt: &Packet) -> bool {
11856        pkt.endpoints()
11857            .iter()
11858            .copied()
11859            .any(|ep| self.endpoint_has_packet_handler(ep) || self.endpoint_has_packed_handler(ep))
11860    }
11861
11862    /// Whether receiving this packet constitutes delivery at this router.
11863    ///
11864    /// Network variables deliberately use their cache/update callback instead of a conventional
11865    /// endpoint handler. Treat that successful cache delivery as local for end-to-end ACKs;
11866    /// otherwise a reliable writer waits forever for every variable owner that does not also
11867    /// happen to register an unrelated packet handler for the variable's endpoint.
11868    fn packet_has_local_delivery(&self, pkt: &Packet) -> bool {
11869        self.packet_has_local_handler(pkt) || self.is_managed_variable_type(pkt.data_type())
11870    }
11871
11872    /// Call the specified endpoint handler with retries on failure.
11873    ///
11874    /// - `data` is present when called from RX processing (queue or immediate).
11875    /// - `pkt_for_ctx` is required for Packet handlers.
11876    /// - `env_for_ctx` provides header-only context when we haven't unpacked.
11877    fn call_handler_with_retries(
11878        &self,
11879        dest: DataEndpoint,
11880        handler: &EndpointHandler,
11881        data: Option<&[u8]>,
11882        pkt_for_ctx: Option<&Packet>,
11883        env_for_ctx: Option<&wire_format::TelemetryEnvelope>,
11884        called_from_queue: bool,
11885    ) -> TelemetryResult<()> {
11886        let owned_tmp: Option<RouterItem>;
11887
11888        let item_for_ctx: &RouterItem = match (data, pkt_for_ctx) {
11889            (Some(d), _) => {
11890                owned_tmp = Some(RouterItem::Packed(crate::shared_bytes::convert(
11891                    Arc::<[u8]>::from(d),
11892                )));
11893                owned_tmp.as_ref().unwrap()
11894            }
11895            (None, Some(pkt)) => {
11896                owned_tmp = Some(RouterItem::Packet(pkt.clone()));
11897                owned_tmp.as_ref().unwrap()
11898            }
11899            (None, None) => {
11900                debug_assert!(
11901                    false,
11902                    "call_handler_with_retries called without data or packet context"
11903                );
11904                return Ok(());
11905            }
11906        };
11907
11908        match (&handler.handler, data) {
11909            (EndpointHandlerFn::Packet(f), _) => {
11910                let pkt = pkt_for_ctx.expect("Packet handler requires Packet context");
11911                with_retries(
11912                    self,
11913                    dest,
11914                    item_for_ctx,
11915                    pkt_for_ctx,
11916                    env_for_ctx,
11917                    called_from_queue,
11918                    || f(pkt),
11919                )
11920            }
11921
11922            (EndpointHandlerFn::Packed(f), Some(bytes)) => with_retries(
11923                self,
11924                dest,
11925                item_for_ctx,
11926                pkt_for_ctx,
11927                env_for_ctx,
11928                called_from_queue,
11929                || f(bytes),
11930            ),
11931
11932            (EndpointHandlerFn::Packed(_), None) => Ok(()),
11933        }
11934    }
11935
11936    /// Error helper when we only have an envelope (no full packet).
11937    ///
11938    /// Sends a TelemetryError packet to all local endpoints except the failed one (if any).
11939    /// If no local endpoints remain, falls back to `fallback_stdout`.
11940    fn handle_callback_error_from_env(
11941        &self,
11942        env: &wire_format::TelemetryEnvelope,
11943        dest: Option<DataEndpoint>,
11944        e: TelemetryError,
11945        called_from_queue: bool,
11946    ) -> TelemetryResult<()> {
11947        let mut recipients: Vec<DataEndpoint> = env
11948            .endpoints
11949            .iter()
11950            .copied()
11951            .filter(|&ep| self.cfg.is_local_endpoint(ep))
11952            .collect();
11953        crate::small_sort::sort(&mut recipients);
11954        recipients.dedup();
11955        if let Some(failed) = dest {
11956            recipients.retain(|&ep| ep != failed);
11957        }
11958
11959        let device = self.sender_arc();
11960        let error_msg = format!(
11961            "Handler for endpoint {:?} failed on device {:?}: {:?}",
11962            dest, device, e
11963        );
11964
11965        if recipients.is_empty() && dest.is_none() {
11966            fallback_stdout(&error_msg);
11967            return Ok(());
11968        }
11969
11970        if recipients.is_empty() {
11971            recipients = env.endpoints.to_vec();
11972            crate::small_sort::sort(&mut recipients);
11973            recipients.dedup();
11974            if let Some(failed) = dest {
11975                recipients.retain(|&ep| ep != failed);
11976            }
11977        }
11978
11979        if recipients.is_empty() {
11980            fallback_stdout(&error_msg);
11981            return Ok(());
11982        }
11983
11984        let payload = make_error_payload(&error_msg);
11985
11986        let error_pkt = Packet::new(
11987            DataType::TelemetryError,
11988            &recipients,
11989            &env.sender.clone(),
11990            env.timestamp_ms,
11991            crate::shared_bytes::convert(payload),
11992        )?;
11993        self.emit_internal_tx(
11994            RouterTxItem::Broadcast(RouterItem::Packet(error_pkt)),
11995            false,
11996            called_from_queue,
11997        )
11998    }
11999
12000    fn handle_internal_reliable_packet(
12001        &self,
12002        pkt: &Packet,
12003        src: Option<RouterSideId>,
12004        called_from_queue: bool,
12005    ) -> TelemetryResult<bool> {
12006        if !matches!(
12007            pkt.data_type(),
12008            DataType::ReliableAck | DataType::ReliablePartialAck | DataType::ReliablePacketRequest
12009        ) {
12010            return Ok(false);
12011        }
12012
12013        let Some(src) = src else {
12014            return Ok(false);
12015        };
12016
12017        if Self::is_end_to_end_ack_packet(pkt)
12018            && let Ok(packet_id) = Self::decode_end_to_end_reliable_ack(pkt.payload())
12019        {
12020            let upstream_contract = {
12021                let st = self.state.lock();
12022                st.reliable_return_routes
12023                    .get(&packet_id)
12024                    .filter(|route| route.side != src)
12025                    .and_then(|route| route.upstream_contract)
12026            };
12027            if let Some((upstream_id, publisher_hash)) = upstream_contract {
12028                // Receipt follows actual downstream delivery, not admission to
12029                // the relay queue. Retain the bounded alias so duplicate data
12030                // can regenerate this receipt if its upstream ACK was lost.
12031                self.queue_end_to_end_reliable_ack_for_sender_hash(
12032                    upstream_id,
12033                    publisher_hash,
12034                    called_from_queue,
12035                )?;
12036                return Ok(true);
12037            }
12038            // Relays may also keep a retransmission record for the forwarded
12039            // packet. Clearing that local record must not consume an ACK whose
12040            // frozen wire contract targets the original sender beyond this
12041            // router; it still has to travel back along the learned return
12042            // route. This occurs when a router is itself one managed-variable
12043            // owner while additional owners sit behind another side.
12044            let targets_local_sender = pkt.wire_target_senders().is_empty()
12045                || pkt
12046                    .wire_target_senders()
12047                    .contains(&Self::sender_hash(self.sender_arc().as_ref()));
12048            let mut st = self.state.lock();
12049            if let Some(sent) = st.end_to_end_reliable_tx.get_mut(&packet_id) {
12050                if sent.waiting_for_predecessor {
12051                    return Ok(targets_local_sender);
12052                }
12053                if let Some(sender_hash) = Self::end_to_end_ack_sender_hash(pkt) {
12054                    let _removed = sent.pending_destinations.remove(&sender_hash);
12055                    #[cfg(feature = "std")]
12056                    if reliable_trace_enabled() {
12057                        eprintln!(
12058                            "[sedsnet-reliable] receipt time={} id={packet_id:#x} sender={sender_hash:#x} matched={} remaining={:?}",
12059                            self.clock.now_ms(),
12060                            _removed.is_some(),
12061                            sent.pending_destinations
12062                        );
12063                    }
12064                    if sent.pending_destinations.is_empty() {
12065                        st.end_to_end_reliable_tx.remove(&packet_id);
12066                    }
12067                    drop(st);
12068                    self.release_ordered_successors()?;
12069                    return Ok(targets_local_sender);
12070                }
12071                st.end_to_end_reliable_tx.remove(&packet_id);
12072                drop(st);
12073                self.release_ordered_successors()?;
12074                return Ok(targets_local_sender);
12075            }
12076            return Ok(false);
12077        }
12078
12079        let vals = pkt.data_as_u32()?;
12080        if vals.len() != 2 {
12081            return Err(TelemetryError::Unpack("bad reliable control payload"));
12082        }
12083        let ty = DataType::try_from_u32(vals[0]).ok_or(TelemetryError::InvalidType)?;
12084        let seq = vals[1];
12085
12086        match pkt.data_type() {
12087            DataType::ReliableAck => {
12088                self.handle_reliable_ack(src, ty, seq);
12089                Ok(true)
12090            }
12091            DataType::ReliablePartialAck => {
12092                self.handle_reliable_partial_ack(src, ty, seq);
12093                Ok(true)
12094            }
12095            DataType::ReliablePacketRequest => {
12096                self.queue_reliable_retransmit(src, ty, seq, called_from_queue)?;
12097                Ok(true)
12098            }
12099            _ => Ok(false),
12100        }
12101    }
12102
12103    /// Core receive function handling both Packet and Packed items.
12104    ///
12105    /// Relay mode: if a destination endpoint has no matching local handler and the packet has
12106    /// any remotely-forwardable endpoints, the router will rebroadcast the packet ONCE, excluding
12107    /// the ingress side.
12108    fn rx_item(&self, item: &RouterRxItem, called_from_queue: bool) -> TelemetryResult<()> {
12109        if crate::memory_admission::supports_work_kinds()
12110            && let (Some(side), RouterItem::Packed(bytes)) = (item.src, &item.data)
12111        {
12112            self.admit_received_frame(side, bytes)?;
12113        }
12114        if let Some(src) = item.src {
12115            self.ensure_side_ingress_enabled(src)?;
12116            match &item.data {
12117                RouterItem::Packet(pkt) => {
12118                    let bytes = wire_format::packet_wire_size(pkt);
12119                    self.note_side_rx(src, pkt.data_type(), bytes, true);
12120                }
12121                RouterItem::Packed(bytes) => {
12122                    if let Ok(env) = wire_format::peek_routing_envelope(bytes.as_ref()) {
12123                        self.note_side_rx(src, env.ty, bytes.len(), true);
12124                    }
12125                }
12126            }
12127            match &item.data {
12128                RouterItem::Packet(pkt) => {
12129                    if (is_reliable_type(pkt.data_type()) || !pkt.wire_target_senders().is_empty())
12130                        && !is_internal_control_type(pkt.data_type())
12131                    {
12132                        self.note_reliable_return_route(src, pkt.packet_id());
12133                    }
12134                }
12135                RouterItem::Packed(bytes) => {
12136                    if let Ok(env) = wire_format::peek_routing_envelope(bytes.as_ref())
12137                        && (is_reliable_type(env.ty) || env.target_senders().len() != 0)
12138                        && !is_internal_control_type(env.ty)
12139                        && let Ok(packet_id) = wire_format::packet_id_from_wire(bytes.as_ref())
12140                    {
12141                        self.note_reliable_return_route(src, packet_id);
12142                    }
12143                }
12144            }
12145        }
12146        match &item.data {
12147            RouterItem::Packet(pkt) => {
12148                if !is_internal_control_type(pkt.data_type()) {
12149                    self.remember_managed_variable_packet(pkt)?;
12150                }
12151            }
12152            RouterItem::Packed(bytes) => {
12153                if let Ok(env) = wire_format::peek_routing_envelope(bytes.as_ref())
12154                    && !is_internal_control_type(env.ty)
12155                    && self.is_managed_variable_type(env.ty)
12156                {
12157                    let pkt = wire_format::unpack_packet(bytes.as_ref())?;
12158                    pkt.validate()?;
12159                    self.remember_managed_variable_packet(&pkt)?;
12160                }
12161            }
12162        }
12163        let mut released_buffered: Vec<crate::SharedBytes> = Vec::new();
12164        if let (Some(src), RouterItem::Packed(bytes)) = (item.src, &item.data) {
12165            let (_opts, handler_is_packed, hop_reliable_enabled) = {
12166                let st = self.state.lock();
12167                let side_ref = Self::side_ref(&st, src)?;
12168                let opts = side_ref.opts.clone();
12169                (
12170                    opts.clone(),
12171                    matches!(
12172                        side_ref.tx_handler,
12173                        RouterTxHandlerFn::Packed(_) | RouterTxHandlerFn::PackedWithPriority(_)
12174                    ),
12175                    opts.reliable_enabled
12176                        && self.cfg.reliable_enabled()
12177                        && !self.side_has_multiple_announcers_locked(&st, src, self.clock.now_ms()),
12178                )
12179            };
12180
12181            if hop_reliable_enabled && handler_is_packed {
12182                let frame = match wire_format::peek_routing_frame_info(bytes.as_ref()) {
12183                    Ok(frame) => frame,
12184                    Err(e) => {
12185                        if matches!(e, TelemetryError::Unpack(msg) if msg == "crc32 mismatch") {
12186                            if let Ok(frame) =
12187                                wire_format::peek_frame_info_unchecked(bytes.as_ref())
12188                                && is_reliable_type(frame.envelope.ty)
12189                                && let Some(hdr) = frame.reliable
12190                            {
12191                                let unordered =
12192                                    (hdr.flags & wire_format::RELIABLE_FLAG_UNORDERED) != 0;
12193                                let unsequenced =
12194                                    (hdr.flags & wire_format::RELIABLE_FLAG_UNSEQUENCED) != 0;
12195
12196                                if !unsequenced {
12197                                    let requested = if unordered {
12198                                        hdr.seq
12199                                    } else {
12200                                        let mut st = self.state.lock();
12201                                        let rx_state = self.reliable_rx_state_mut(
12202                                            &mut st,
12203                                            src,
12204                                            frame.envelope.ty,
12205                                        );
12206                                        rx_state.expected_seq.min(hdr.seq)
12207                                    };
12208                                    self.queue_reliable_packet_request(
12209                                        src,
12210                                        frame.envelope.ty,
12211                                        requested,
12212                                        called_from_queue,
12213                                    )?;
12214                                }
12215                            }
12216                            return Ok(());
12217                        }
12218                        return Err(e);
12219                    }
12220                };
12221                if is_reliable_type(frame.envelope.ty)
12222                    && let Some(hdr) = frame.reliable
12223                {
12224                    if frame.ack_only() {
12225                        self.handle_reliable_ack(src, frame.envelope.ty, hdr.ack);
12226                        return Ok(());
12227                    }
12228                    let unordered = (hdr.flags & wire_format::RELIABLE_FLAG_UNORDERED) != 0;
12229                    let unsequenced = (hdr.flags & wire_format::RELIABLE_FLAG_UNSEQUENCED) != 0;
12230
12231                    if !unsequenced {
12232                        if unordered {
12233                            self.queue_reliable_ack(
12234                                src,
12235                                frame.envelope.ty,
12236                                hdr.seq,
12237                                called_from_queue,
12238                            )?;
12239                        } else {
12240                            let mut release: Vec<crate::SharedBytes> = Vec::new();
12241                            let mut last_delivered = None;
12242                            let mut ack_old = None;
12243                            let mut request_missing = None;
12244                            let mut partial_ack = None;
12245                            {
12246                                let mut st = self.state.lock();
12247                                let rx_state =
12248                                    self.reliable_rx_state_mut(&mut st, src, frame.envelope.ty);
12249                                let expected_seq = rx_state.expected_seq;
12250                                if hdr.seq < expected_seq {
12251                                    ack_old = Some(expected_seq.saturating_sub(1));
12252                                } else if hdr.seq > expected_seq {
12253                                    request_missing = Some(expected_seq);
12254                                    partial_ack = Some(hdr.seq);
12255                                    st.buffer_reliable_rx(
12256                                        src,
12257                                        frame.envelope.ty,
12258                                        hdr.seq,
12259                                        bytes.clone(),
12260                                    )?;
12261                                } else {
12262                                    release.push(bytes.clone());
12263                                    last_delivered = Some(hdr.seq);
12264                                    let mut next_expected = hdr.seq.wrapping_add(1);
12265                                    while let Some(buf) = rx_state.buffered.remove(&next_expected) {
12266                                        release.push(buf);
12267                                        last_delivered = Some(next_expected);
12268                                        let next = next_expected.wrapping_add(1);
12269                                        next_expected = if next == 0 { 1 } else { next };
12270                                    }
12271                                    rx_state.expected_seq = next_expected;
12272                                }
12273                            }
12274
12275                            if let Some(ack_seq) = ack_old {
12276                                self.queue_reliable_ack(
12277                                    src,
12278                                    frame.envelope.ty,
12279                                    ack_seq,
12280                                    called_from_queue,
12281                                )?;
12282                                return Ok(());
12283                            }
12284                            if let Some(request_seq) = request_missing {
12285                                if let Some(partial_seq) = partial_ack {
12286                                    self.queue_reliable_partial_ack(
12287                                        src,
12288                                        frame.envelope.ty,
12289                                        partial_seq,
12290                                        called_from_queue,
12291                                    )?;
12292                                }
12293                                self.queue_reliable_packet_request(
12294                                    src,
12295                                    frame.envelope.ty,
12296                                    request_seq,
12297                                    called_from_queue,
12298                                )?;
12299                                return Ok(());
12300                            }
12301
12302                            if let Some(ack_seq) = last_delivered {
12303                                self.queue_reliable_ack(
12304                                    src,
12305                                    frame.envelope.ty,
12306                                    ack_seq,
12307                                    called_from_queue,
12308                                )?;
12309                            }
12310
12311                            released_buffered.extend(release.into_iter().skip(1));
12312                        }
12313                    }
12314                }
12315            } else {
12316                match wire_format::peek_routing_frame_info(bytes.as_ref()) {
12317                    Ok(frame) => {
12318                        if frame.ack_only() {
12319                            return Ok(());
12320                        }
12321                    }
12322                    Err(e) => {
12323                        if matches!(e, TelemetryError::Unpack(msg) if msg == "crc32 mismatch") {
12324                            return Ok(());
12325                        }
12326                        return Err(e);
12327                    }
12328                }
12329            }
12330        }
12331
12332        if self.is_duplicate_pkt(&item.data)? {
12333            // A final-ACK retry must cross a router again. Do not redeliver it
12334            // locally, and rate-limit repeated forwarding to avoid echo loops.
12335            if (item.src.is_some()
12336                && self
12337                    .item_target_senders(&item.data)?
12338                    .iter()
12339                    .any(|target| *target != Self::sender_hash(self.sender_arc().as_ref())))
12340                || (item.src.is_some() && {
12341                    let (endpoints, _) = self.item_route_info(&item.data)?;
12342                    self.item_targets_router_as_intermediate_hop(&item.data, &endpoints)?
12343                })
12344            {
12345                let id = Self::get_hash(&item.data);
12346                let replay = {
12347                    let mut st = self.state.lock();
12348                    st.reliable_return_routes.get_mut(&id).is_some_and(|route| {
12349                        let now = self.clock.now_ms();
12350                        if now.saturating_sub(route.last_replay_ms)
12351                            < runtime_reliable_retransmit_ms()
12352                        {
12353                            return false;
12354                        }
12355                        route.last_replay_ms = now;
12356                        true
12357                    })
12358                };
12359                if replay {
12360                    self.relay_send(item.data.clone(), item.src, called_from_queue)?;
12361                }
12362            }
12363            if item.src.is_some() {
12364                let local_sender = self.sender_arc();
12365                match &item.data {
12366                    RouterItem::Packet(pkt)
12367                        if (is_reliable_type(pkt.data_type())
12368                            || !pkt.wire_target_senders().is_empty())
12369                            && pkt.sender() != local_sender.as_ref()
12370                            && self.item_targets_local_sender(&item.data)?
12371                            && self.packet_has_local_delivery(pkt) =>
12372                    {
12373                        self.queue_end_to_end_reliable_ack(pkt, called_from_queue)?;
12374                    }
12375                    RouterItem::Packed(bytes) => {
12376                        if let Ok(pkt) = wire_format::unpack_packet(bytes.as_ref())
12377                            && (is_reliable_type(pkt.data_type())
12378                                || !pkt.wire_target_senders().is_empty())
12379                            && pkt.sender() != local_sender.as_ref()
12380                            && self.item_targets_local_sender(&item.data)?
12381                            && self.packet_has_local_delivery(&pkt)
12382                        {
12383                            let packet_id = wire_format::packet_id_from_wire(bytes.as_ref())
12384                                .unwrap_or_else(|_| pkt.packet_id());
12385                            self.queue_end_to_end_reliable_ack_for_packet_id(
12386                                packet_id,
12387                                pkt.sender(),
12388                                called_from_queue,
12389                            )?;
12390                        }
12391                    }
12392                    _ => {}
12393                }
12394            }
12395            return Ok(());
12396        }
12397
12398        self.dispatch_rx_data(item, called_from_queue)?;
12399
12400        for release_bytes in released_buffered {
12401            let release_data = RouterItem::Packed(release_bytes.clone());
12402            if self.is_duplicate_pkt(&release_data)? {
12403                continue;
12404            }
12405            let release_item = RouterRxItem {
12406                src: item.src,
12407                priority: Self::router_item_priority(&release_data)?,
12408                data: release_data,
12409            };
12410            self.dispatch_rx_data(&release_item, called_from_queue)?;
12411        }
12412
12413        Ok(())
12414    }
12415
12416    fn dispatch_rx_data(
12417        &self,
12418        item: &RouterRxItem,
12419        called_from_queue: bool,
12420    ) -> TelemetryResult<()> {
12421        match &item.data {
12422            RouterItem::Packet(pkt) => {
12423                pkt.validate()?;
12424                #[cfg(feature = "discovery")]
12425                if let Some(side) = item.src {
12426                    self.refresh_peer_liveness(side, sender_address_u32(pkt.sender()));
12427                }
12428
12429                if self.handle_internal_reliable_packet(pkt, item.src, called_from_queue)? {
12430                    return Ok(());
12431                }
12432
12433                if pkt.data_type() == DataType::P2pMessage {
12434                    if self.item_targets_local_sender(&item.data)? {
12435                        self.dispatch_p2p_packet(pkt)?;
12436                        if item.src.is_some() {
12437                            self.queue_end_to_end_reliable_ack(pkt, called_from_queue)?;
12438                        }
12439                    }
12440                    if self.should_route_remote(&item.data, item.src)? {
12441                        self.relay_send(
12442                            RouterItem::Packet(pkt.to_owned()),
12443                            item.src,
12444                            called_from_queue,
12445                        )?;
12446                    }
12447                    return Ok(());
12448                }
12449
12450                #[cfg(feature = "timesync")]
12451                if matches!(
12452                    pkt.data_type(),
12453                    DataType::TimeSyncAnnounce
12454                        | DataType::TimeSyncRequest
12455                        | DataType::TimeSyncResponse
12456                ) {
12457                    self.handle_internal_timesync_packet(pkt, item.src, called_from_queue)?;
12458                    return Ok(());
12459                }
12460
12461                if self.learn_discovery_packet(pkt, item.src, called_from_queue)? {
12462                    // Reachability advertisements are hop-local. Learning one
12463                    // schedules this Router's own aggregated, split-horizon
12464                    // snapshot; transparently relaying the original packet
12465                    // would make a distant sender look physically adjacent.
12466                    if !discovery_is_hop_local_advertisement(pkt.data_type())
12467                        && self.should_route_remote(&item.data, item.src)?
12468                    {
12469                        self.relay_send(
12470                            RouterItem::Packet(pkt.to_owned()),
12471                            item.src,
12472                            called_from_queue,
12473                        )?;
12474                    }
12475                    return Ok(());
12476                }
12477
12478                let mut eps: Vec<DataEndpoint> = pkt.endpoints().to_vec();
12479                crate::small_sort::sort(&mut eps);
12480                eps.dedup();
12481                let had_local_handler = eps.iter().copied().any(|ep| {
12482                    self.endpoint_has_packet_handler(ep) || self.endpoint_has_packed_handler(ep)
12483                });
12484                let had_local_delivery =
12485                    had_local_handler || self.is_managed_variable_type(pkt.data_type());
12486
12487                let has_remote = self.should_route_remote(&item.data, item.src)?;
12488                let targets_local = self.item_targets_local_sender(&item.data)?;
12489
12490                let has_packed_local = eps
12491                    .iter()
12492                    .copied()
12493                    .any(|ep| self.endpoint_has_packed_handler(ep));
12494                let bytes_opt = if has_packed_local {
12495                    Some(wire_format::pack_packet(pkt))
12496                } else {
12497                    None
12498                };
12499
12500                if targets_local {
12501                    for dest in eps {
12502                        for h in self.cfg.handlers.iter().filter(|h| h.endpoint == dest) {
12503                            let result = match (&h.handler, &bytes_opt) {
12504                                (EndpointHandlerFn::Packed(_), Some(bytes)) => self
12505                                    .call_handler_with_retries(
12506                                        dest,
12507                                        h,
12508                                        Some(bytes.as_ref()),
12509                                        Some(pkt),
12510                                        None,
12511                                        called_from_queue,
12512                                    ),
12513                                (EndpointHandlerFn::Packed(_), None) => {
12514                                    let bytes = wire_format::pack_packet(pkt);
12515                                    self.call_handler_with_retries(
12516                                        dest,
12517                                        h,
12518                                        Some(bytes.as_ref()),
12519                                        Some(pkt),
12520                                        None,
12521                                        called_from_queue,
12522                                    )
12523                                }
12524                                (EndpointHandlerFn::Packet(_), _) => self
12525                                    .call_handler_with_retries(
12526                                        dest,
12527                                        h,
12528                                        None,
12529                                        Some(pkt),
12530                                        None,
12531                                        called_from_queue,
12532                                    ),
12533                            };
12534                            if result.is_err()
12535                                && let Some(src) = item.src
12536                            {
12537                                self.note_side_local_handler_failure(
12538                                    src,
12539                                    pkt.data_type(),
12540                                    runtime_max_handler_retries(),
12541                                );
12542                            }
12543                        }
12544                    }
12545                }
12546
12547                if let Some(src) = item.src
12548                    && had_local_delivery
12549                    && targets_local
12550                {
12551                    self.note_side_local_delivery(src, pkt.data_type());
12552                }
12553
12554                if item.src.is_some()
12555                    && had_local_delivery
12556                    && targets_local
12557                    && (is_reliable_type(pkt.data_type()) || !pkt.wire_target_senders().is_empty())
12558                {
12559                    self.queue_end_to_end_reliable_ack(pkt, called_from_queue)?;
12560                }
12561
12562                if has_remote {
12563                    let relay_item = RouterItem::Packet(pkt.to_owned());
12564                    self.relay_send(relay_item, item.src, called_from_queue)?;
12565                }
12566
12567                Ok(())
12568            }
12569            RouterItem::Packed(bytes) => {
12570                // Pure best-effort transit needs routing metadata, not an owned
12571                // sender string or endpoint array. Commands, local handlers,
12572                // managed variables and internal protocols retain their normal path.
12573                let routing = wire_format::peek_routing_envelope(bytes.as_ref())?;
12574                if !is_internal_control_type(routing.ty)
12575                    && !routing.is_reliable
12576                    && routing.target_senders().len() == 0
12577                    && !self.is_managed_variable_type(routing.ty)
12578                    && !routing.endpoints().any(|ep| {
12579                        self.endpoint_has_packet_handler(ep) || self.endpoint_has_packed_handler(ep)
12580                    })
12581                {
12582                    // relay_send already resolves the eligible sides, including
12583                    // an empty plan. A preliminary selection duplicates scratch
12584                    // allocations and advances weighted cursors twice.
12585                    return self.relay_send(item.data.clone(), item.src, called_from_queue);
12586                }
12587
12588                let env = wire_format::peek_envelope(bytes.as_ref())?;
12589
12590                if matches!(
12591                    env.ty,
12592                    DataType::ReliableAck
12593                        | DataType::ReliablePartialAck
12594                        | DataType::ReliablePacketRequest
12595                ) {
12596                    let pkt = wire_format::unpack_packet(bytes.as_ref())?;
12597                    pkt.validate()?;
12598                    let handled =
12599                        self.handle_internal_reliable_packet(&pkt, item.src, called_from_queue)?;
12600                    // Hop-level ACKs terminate here. End-to-end ACKs for a packet originated
12601                    // farther upstream continue along the learned packet return route.
12602                    if !handled && self.should_route_remote(&item.data, item.src)? {
12603                        self.relay_send(RouterItem::Packet(pkt), item.src, called_from_queue)?;
12604                    }
12605                    return Ok(());
12606                }
12607
12608                if env.ty == DataType::P2pMessage {
12609                    let pkt = wire_format::unpack_packet(bytes.as_ref())?;
12610                    pkt.validate()?;
12611                    if self.item_targets_local_sender(&item.data)? {
12612                        self.dispatch_p2p_packet(&pkt)?;
12613                        if item.src.is_some() {
12614                            let packet_id = wire_format::packet_id_from_wire(bytes.as_ref())
12615                                .unwrap_or_else(|_| pkt.packet_id());
12616                            self.queue_end_to_end_reliable_ack_for_packet_id(
12617                                packet_id,
12618                                pkt.sender(),
12619                                called_from_queue,
12620                            )?;
12621                        }
12622                    }
12623                    if self.should_route_remote(&item.data, item.src)? {
12624                        self.relay_send(RouterItem::Packet(pkt), item.src, called_from_queue)?;
12625                    }
12626                    return Ok(());
12627                }
12628
12629                #[cfg(feature = "timesync")]
12630                if matches!(
12631                    env.ty,
12632                    DataType::TimeSyncAnnounce
12633                        | DataType::TimeSyncRequest
12634                        | DataType::TimeSyncResponse
12635                ) {
12636                    let pkt = wire_format::unpack_packet(bytes.as_ref())?;
12637                    pkt.validate()?;
12638                    self.handle_internal_timesync_packet(&pkt, item.src, called_from_queue)?;
12639                    return Ok(());
12640                }
12641
12642                #[cfg(feature = "discovery")]
12643                if discovery::is_discovery_type(env.ty) {
12644                    let pkt = wire_format::unpack_packet(bytes.as_ref())?;
12645                    pkt.validate()?;
12646                    let _ = self.learn_discovery_packet(&pkt, item.src, called_from_queue)?;
12647                    if !discovery_is_hop_local_advertisement(env.ty)
12648                        && self.should_route_remote(&item.data, item.src)?
12649                    {
12650                        self.relay_send(RouterItem::Packet(pkt), item.src, called_from_queue)?;
12651                    }
12652                    return Ok(());
12653                }
12654
12655                let any_packet_needed = env
12656                    .endpoints
12657                    .iter()
12658                    .copied()
12659                    .any(|ep| self.endpoint_has_packet_handler(ep));
12660
12661                let mut pkt_opt = if any_packet_needed {
12662                    let pkt = wire_format::unpack_packet(bytes.as_ref())?;
12663                    pkt.validate()?;
12664                    Some(pkt)
12665                } else {
12666                    None
12667                };
12668
12669                let mut eps: Vec<DataEndpoint> = env.endpoints.iter().copied().collect();
12670                crate::small_sort::sort(&mut eps);
12671                eps.dedup();
12672                let had_local_handler = eps.iter().copied().any(|ep| {
12673                    self.endpoint_has_packet_handler(ep) || self.endpoint_has_packed_handler(ep)
12674                });
12675                let had_local_delivery = had_local_handler || self.is_managed_variable_type(env.ty);
12676
12677                let has_remote = self.should_route_remote(&item.data, item.src)?;
12678                let targets_local = self.item_targets_local_sender(&item.data)?;
12679
12680                if targets_local {
12681                    for dest in eps {
12682                        for h in self.cfg.handlers.iter().filter(|h| h.endpoint == dest) {
12683                            let result = match &h.handler {
12684                                EndpointHandlerFn::Packed(_) => self.call_handler_with_retries(
12685                                    dest,
12686                                    h,
12687                                    Some(bytes.as_ref()),
12688                                    pkt_opt.as_ref(),
12689                                    Some(&env),
12690                                    called_from_queue,
12691                                ),
12692                                EndpointHandlerFn::Packet(_) => {
12693                                    if pkt_opt.is_none() {
12694                                        let pkt = wire_format::unpack_packet(bytes.as_ref())?;
12695                                        pkt.validate()?;
12696                                        pkt_opt = Some(pkt);
12697                                    }
12698                                    let pkt_ref = pkt_opt.as_ref().expect("just set");
12699                                    self.call_handler_with_retries(
12700                                        dest,
12701                                        h,
12702                                        None,
12703                                        Some(pkt_ref),
12704                                        Some(&env),
12705                                        called_from_queue,
12706                                    )
12707                                }
12708                            };
12709                            if result.is_err()
12710                                && let Some(src) = item.src
12711                            {
12712                                self.note_side_local_handler_failure(
12713                                    src,
12714                                    env.ty,
12715                                    runtime_max_handler_retries(),
12716                                );
12717                            }
12718                        }
12719                    }
12720                }
12721
12722                if item.src.is_some()
12723                    && had_local_delivery
12724                    && targets_local
12725                    && (is_reliable_type(env.ty) || !env.target_senders.is_empty())
12726                {
12727                    let packet_id =
12728                        wire_format::packet_id_from_wire(bytes.as_ref()).or_else(|_| {
12729                            pkt_opt
12730                                .as_ref()
12731                                .map(Packet::packet_id)
12732                                .ok_or(TelemetryError::Unpack("missing packet id"))
12733                        })?;
12734                    self.queue_end_to_end_reliable_ack_for_packet_id(
12735                        packet_id,
12736                        &env.sender,
12737                        called_from_queue,
12738                    )?;
12739                }
12740
12741                if has_remote {
12742                    let relay_item = match pkt_opt {
12743                        Some(ref p) => RouterItem::Packet(p.clone()),
12744                        None => RouterItem::Packed(bytes.clone()),
12745                    };
12746                    self.relay_send(relay_item, item.src, called_from_queue)?;
12747                }
12748
12749                Ok(())
12750            }
12751        }
12752    }
12753
12754    fn dispatch_local_for_item(
12755        &self,
12756        item: &RouterItem,
12757        called_from_queue: bool,
12758    ) -> TelemetryResult<()> {
12759        match item {
12760            RouterItem::Packet(pkt) => {
12761                pkt.validate()?;
12762                if is_internal_control_type(pkt.data_type()) {
12763                    return Ok(());
12764                }
12765                self.ensure_e2e_policy_supported_for_type(pkt.data_type())?;
12766                if !self.item_targets_local_sender(item)? {
12767                    return Ok(());
12768                }
12769
12770                let mut eps: Vec<DataEndpoint> = pkt.endpoints().to_vec();
12771                crate::small_sort::sort(&mut eps);
12772                eps.dedup();
12773
12774                let has_packed_local = eps
12775                    .iter()
12776                    .copied()
12777                    .any(|ep| self.endpoint_has_packed_handler(ep));
12778                let bytes_opt = if has_packed_local {
12779                    Some(wire_format::pack_packet(pkt))
12780                } else {
12781                    None
12782                };
12783
12784                for dest in eps {
12785                    for h in self.cfg.handlers.iter().filter(|h| h.endpoint == dest) {
12786                        match (&h.handler, &bytes_opt) {
12787                            (EndpointHandlerFn::Packed(_), Some(bytes)) => {
12788                                self.call_handler_with_retries(
12789                                    dest,
12790                                    h,
12791                                    Some(bytes.as_ref()),
12792                                    Some(pkt),
12793                                    None,
12794                                    called_from_queue,
12795                                )?;
12796                            }
12797                            (EndpointHandlerFn::Packed(_), None) => {
12798                                let bytes = wire_format::pack_packet(pkt);
12799                                self.call_handler_with_retries(
12800                                    dest,
12801                                    h,
12802                                    Some(bytes.as_ref()),
12803                                    Some(pkt),
12804                                    None,
12805                                    called_from_queue,
12806                                )?;
12807                            }
12808                            (EndpointHandlerFn::Packet(_), _) => {
12809                                self.call_handler_with_retries(
12810                                    dest,
12811                                    h,
12812                                    None,
12813                                    Some(pkt),
12814                                    None,
12815                                    called_from_queue,
12816                                )?;
12817                            }
12818                        }
12819                    }
12820                }
12821            }
12822            RouterItem::Packed(bytes) => {
12823                let env = wire_format::peek_envelope(bytes.as_ref())?;
12824                if is_internal_control_type(env.ty) {
12825                    return Ok(());
12826                }
12827                self.ensure_e2e_policy_supported_for_type(env.ty)?;
12828                if !self.item_targets_local_sender(item)? {
12829                    return Ok(());
12830                }
12831
12832                let any_packet_needed = env
12833                    .endpoints
12834                    .iter()
12835                    .copied()
12836                    .any(|ep| self.endpoint_has_packet_handler(ep));
12837
12838                let mut pkt_opt = if any_packet_needed {
12839                    let pkt = wire_format::unpack_packet(bytes.as_ref())?;
12840                    pkt.validate()?;
12841                    Some(pkt)
12842                } else {
12843                    None
12844                };
12845
12846                let mut eps: Vec<DataEndpoint> = env.endpoints.iter().copied().collect();
12847                crate::small_sort::sort(&mut eps);
12848                eps.dedup();
12849
12850                for dest in eps {
12851                    for h in self.cfg.handlers.iter().filter(|h| h.endpoint == dest) {
12852                        match &h.handler {
12853                            EndpointHandlerFn::Packed(_) => {
12854                                self.call_handler_with_retries(
12855                                    dest,
12856                                    h,
12857                                    Some(bytes.as_ref()),
12858                                    pkt_opt.as_ref(),
12859                                    Some(&env),
12860                                    called_from_queue,
12861                                )?;
12862                            }
12863                            EndpointHandlerFn::Packet(_) => {
12864                                if pkt_opt.is_none() {
12865                                    let pkt = wire_format::unpack_packet(bytes.as_ref())?;
12866                                    pkt.validate()?;
12867                                    pkt_opt = Some(pkt);
12868                                }
12869                                let pkt_ref = pkt_opt.as_ref().expect("just set");
12870                                self.call_handler_with_retries(
12871                                    dest,
12872                                    h,
12873                                    None,
12874                                    Some(pkt_ref),
12875                                    Some(&env),
12876                                    called_from_queue,
12877                                )?;
12878                            }
12879                        }
12880                    }
12881                }
12882            }
12883        }
12884
12885        Ok(())
12886    }
12887
12888    /// Internal TX implementation used by `tx*()`, `tx_queue*()`, and relay-mode rebroadcast.
12889    ///
12890    /// - Broadcast items are sent to all sides when remote forwarding is required.
12891    /// - ToSide items are sent only to the specified side.
12892    /// - If `ignore_local` is false, local handlers are invoked once.
12893    fn tx_item_impl(
12894        &self,
12895        item: RouterTxItem,
12896        ignore_local: bool,
12897        called_from_queue: bool,
12898    ) -> TelemetryResult<()> {
12899        match item {
12900            RouterTxItem::Broadcast(data) => {
12901                self.ensure_e2e_policy_supported_for_type(Self::item_data_type(&data)?)?;
12902                if let RouterItem::Packet(pkt) = &data
12903                    && !is_internal_control_type(pkt.data_type())
12904                {
12905                    self.remember_managed_variable_packet(pkt)?;
12906                }
12907                #[cfg(feature = "discovery")]
12908                let is_discovery = matches!(&data, RouterItem::Packet(pkt) if discovery::is_discovery_type(pkt.data_type()))
12909                    || matches!(&data, RouterItem::Packed(bytes)
12910                        if wire_format::peek_envelope(bytes.as_ref())
12911                            .map(|env| discovery::is_discovery_type(env.ty))
12912                            .unwrap_or(false));
12913                if !ignore_local {
12914                    if self.is_duplicate_pkt(&data)? {
12915                        return Ok(());
12916                    }
12917                    #[cfg(feature = "discovery")]
12918                    if !is_discovery
12919                        && !matches!(&data, RouterItem::Packet(pkt) if is_internal_control_type(pkt.data_type()))
12920                        && !matches!(&data, RouterItem::Packed(bytes)
12921                            if wire_format::peek_routing_envelope(bytes.as_ref())
12922                                .map(|env| is_internal_control_type(env.ty))
12923                                .unwrap_or(false))
12924                    {
12925                        self.dispatch_local_for_item(&data, called_from_queue)?;
12926                    }
12927                    #[cfg(not(feature = "discovery"))]
12928                    if !matches!(&data, RouterItem::Packet(pkt) if is_internal_control_type(pkt.data_type()))
12929                        && !matches!(&data, RouterItem::Packed(bytes)
12930                            if wire_format::peek_routing_envelope(bytes.as_ref())
12931                                .map(|env| is_internal_control_type(env.ty))
12932                                .unwrap_or(false))
12933                    {
12934                        self.dispatch_local_for_item(&data, called_from_queue)?;
12935                    }
12936                }
12937
12938                let send_remote = match &data {
12939                    RouterItem::Packet(pkt) => {
12940                        pkt.validate()?;
12941                        self.should_route_remote(&data, None)?
12942                    }
12943                    RouterItem::Packed(bytes) => {
12944                        let _ = wire_format::peek_envelope(bytes.as_ref())?;
12945                        self.should_route_remote(&data, None)?
12946                    }
12947                };
12948
12949                if !send_remote {
12950                    let (endpoints, ty) = self.item_route_info(&data)?;
12951                    if !ignore_local
12952                        && !is_internal_control_type(ty)
12953                        && is_reliable_type(ty)
12954                        && has_nonlocal_endpoint(&endpoints, &self.cfg)
12955                        && !self
12956                            .state
12957                            .lock()
12958                            .managed_variable_types
12959                            .contains(&ty.as_u32())
12960                    {
12961                        // Nothing was queued or transmitted. Reporting success
12962                        // here loses application state during route expiry.
12963                        // Permit retry of the same packet after discovery heals.
12964                        self.remove_pkt_id(&data);
12965                        return Err(TelemetryError::Io("no discovered route"));
12966                    }
12967                    return Ok(());
12968                }
12969                let mut data = data;
12970                let ty = match &data {
12971                    RouterItem::Packet(pkt) => pkt.data_type(),
12972                    RouterItem::Packed(bytes) => {
12973                        wire_format::peek_routing_envelope(bytes.as_ref())?.ty
12974                    }
12975                };
12976                if !ignore_local
12977                    && !is_internal_control_type(ty)
12978                    && self.item_requires_end_to_end_tracking(&data, ty)?
12979                {
12980                    #[cfg(feature = "discovery")]
12981                    {
12982                        let pending = {
12983                            let st = self.state.lock();
12984                            let mut pending =
12985                                self.expected_end_to_end_destinations_locked(&st, &data)?;
12986                            self.filter_trackable_end_to_end_destinations_locked(
12987                                &st,
12988                                ty,
12989                                &mut pending,
12990                            );
12991                            pending
12992                        };
12993                        if !pending.is_empty() {
12994                            let original_data = data.clone();
12995                            let mut targets: Vec<u64> = pending.keys().copied().collect();
12996                            crate::small_sort::sort(&mut targets);
12997                            targets.dedup();
12998                            data = self.attach_wire_contract_to_item(data, &targets)?;
12999                            match self.register_end_to_end_reliable_tx(&data) {
13000                                Ok(false) => return Ok(()),
13001                                Ok(true) => {}
13002                                Err(error) => {
13003                                    self.remove_pkt_id(&original_data);
13004                                    return Err(error);
13005                                }
13006                            }
13007                        }
13008                    }
13009                }
13010                let RemoteSidePlan::Target(sides) = self.remote_side_plan(&data, None)?;
13011                for side in sides.iter().copied() {
13012                    if let Err(e) = self.send_reliable_to_side(side, data.clone(), false) {
13013                        if Self::is_side_tx_busy(&e) {
13014                            self.tx_queue_item_for_retry(
13015                                RouterTxItem::ToSide {
13016                                    src: None,
13017                                    dst: side,
13018                                    data: data.clone(),
13019                                },
13020                                true,
13021                            )?;
13022                            continue;
13023                        }
13024                        match &data {
13025                            RouterItem::Packet(pkt) => {
13026                                let _ = self.handle_callback_error(pkt, None, e, called_from_queue);
13027                            }
13028                            RouterItem::Packed(bytes) => {
13029                                if let Ok(env) = wire_format::peek_envelope(bytes.as_ref()) {
13030                                    let _ = self.handle_callback_error_from_env(
13031                                        &env,
13032                                        None,
13033                                        e,
13034                                        called_from_queue,
13035                                    );
13036                                }
13037                            }
13038                        }
13039                        return Err(TelemetryError::HandlerError("tx handler failed"));
13040                    }
13041                }
13042            }
13043            RouterTxItem::ToSide { src, dst, data } => {
13044                self.ensure_e2e_policy_supported_for_type(Self::item_data_type(&data)?)?;
13045                if let RouterItem::Packet(pkt) = &data
13046                    && !is_internal_control_type(pkt.data_type())
13047                {
13048                    self.remember_managed_variable_packet(pkt)?;
13049                }
13050                if !ignore_local {
13051                    if self.is_duplicate_pkt(&data)? {
13052                        return Ok(());
13053                    }
13054                    let suppress_local = matches!(&data, RouterItem::Packet(pkt) if is_internal_control_type(pkt.data_type()))
13055                        || matches!(&data, RouterItem::Packed(bytes)
13056                            if wire_format::peek_routing_envelope(bytes.as_ref())
13057                                .map(|env| is_internal_control_type(env.ty))
13058                                .unwrap_or(false));
13059                    if !suppress_local {
13060                        self.dispatch_local_for_item(&data, called_from_queue)?;
13061                    }
13062                }
13063                let allowed = {
13064                    let mut st = self.state.lock();
13065                    let ty = match &data {
13066                        RouterItem::Packet(pkt) => Some(pkt.data_type()),
13067                        RouterItem::Packed(bytes) => {
13068                            Some(wire_format::peek_routing_envelope(bytes.as_ref())?.ty)
13069                        }
13070                    };
13071                    let route_allowed = self.route_allowed_locked(&st, src, ty, dst);
13072                    #[cfg(all(feature = "discovery", feature = "timesync"))]
13073                    let timesync_allowed = ty
13074                        .map(|ty| {
13075                            Self::timesync_allowed_for_side_locked(
13076                                &mut st,
13077                                dst,
13078                                ty,
13079                                self.clock.now_ms(),
13080                            )
13081                        })
13082                        .unwrap_or(true);
13083                    #[cfg(not(all(feature = "discovery", feature = "timesync")))]
13084                    let timesync_allowed = true;
13085                    route_allowed && timesync_allowed
13086                };
13087                if !allowed {
13088                    return Ok(());
13089                }
13090                if let Err(e) = self.send_reliable_to_side(dst, data.clone(), src.is_some()) {
13091                    if Self::is_side_tx_busy(&e) {
13092                        self.tx_queue_item_for_retry(
13093                            RouterTxItem::ToSide { src, dst, data },
13094                            true,
13095                        )?;
13096                        return Ok(());
13097                    }
13098                    match &data {
13099                        RouterItem::Packet(pkt) => {
13100                            let _ = self.handle_callback_error(pkt, None, e, called_from_queue);
13101                        }
13102                        RouterItem::Packed(bytes) => {
13103                            if let Ok(env) = wire_format::peek_envelope(bytes.as_ref()) {
13104                                let _ = self.handle_callback_error_from_env(
13105                                    &env,
13106                                    None,
13107                                    e,
13108                                    called_from_queue,
13109                                );
13110                            }
13111                        }
13112                    }
13113                    return Err(TelemetryError::HandlerError("tx handler failed"));
13114                }
13115            }
13116            RouterTxItem::EndToEndReplay { packet_id } => {
13117                let Some((data, mut sides)) = self.end_to_end_retransmit_sides(packet_id) else {
13118                    return Ok(());
13119                };
13120                if sides.is_empty() {
13121                    let RemoteSidePlan::Target(fallback_sides) =
13122                        self.remote_side_plan(&data, None)?;
13123                    sides = fallback_sides;
13124                }
13125                for side in sides.iter().copied() {
13126                    if let Err(e) = self.send_reliable_to_side(side, data.clone(), false) {
13127                        if Self::is_side_tx_busy(&e) {
13128                            self.tx_queue_item_for_retry(
13129                                RouterTxItem::ToSide {
13130                                    src: None,
13131                                    dst: side,
13132                                    data: data.clone(),
13133                                },
13134                                true,
13135                            )?;
13136                            continue;
13137                        }
13138                        match &data {
13139                            RouterItem::Packet(pkt) => {
13140                                let _ = self.handle_callback_error(pkt, None, e, called_from_queue);
13141                            }
13142                            RouterItem::Packed(bytes) => {
13143                                if let Ok(env) = wire_format::peek_envelope(bytes.as_ref()) {
13144                                    let _ = self.handle_callback_error_from_env(
13145                                        &env,
13146                                        None,
13147                                        e,
13148                                        called_from_queue,
13149                                    );
13150                                }
13151                            }
13152                        }
13153                        return Err(TelemetryError::HandlerError("tx handler failed"));
13154                    }
13155                }
13156            }
13157            RouterTxItem::ReliableReplay { dst, bytes } => {
13158                let frame = wire_format::peek_frame_info(bytes.as_ref())?;
13159                let ty = frame.envelope.ty;
13160                let Some(hdr) = frame.reliable else {
13161                    return Ok(());
13162                };
13163                {
13164                    let mut st = self.state.lock();
13165                    let tx_state = self.reliable_tx_state_mut(&mut st, dst, ty);
13166                    if !tx_state.sent.contains_key(&hdr.seq) {
13167                        return Ok(());
13168                    }
13169                }
13170                if let Err(e) = self.send_reliable_raw_to_side(dst, bytes.clone(), false) {
13171                    if Self::is_side_tx_busy(&e) {
13172                        self.tx_queue_item_for_retry(
13173                            RouterTxItem::ReliableReplay { dst, bytes },
13174                            true,
13175                        )?;
13176                        return Ok(());
13177                    }
13178                    return Err(e);
13179                }
13180                let mut st = self.state.lock();
13181                let tx_state = self.reliable_tx_state_mut(&mut st, dst, ty);
13182                if let Some(sent) = tx_state.sent.get_mut(&hdr.seq) {
13183                    sent.last_send_ms = self.clock.now_ms();
13184                    sent.queued = false;
13185                }
13186            }
13187        }
13188
13189        Ok(())
13190    }
13191
13192    /// Transmit a telemetry item immediately (remote + local).
13193    #[inline]
13194    fn tx_item(&self, item: RouterTxItem) -> TelemetryResult<()> {
13195        self.tx_item_impl(item, false, false)
13196    }
13197
13198    // ---------- PUBLIC API: RX immediate ----------
13199
13200    /// Process packed bytes immediately as locally-originated input.
13201    ///
13202    /// If this call occurs while a side TX callback is already on the stack, the bytes are queued
13203    /// instead of being processed re-entrantly.
13204    #[inline]
13205    pub fn rx_packed(&self, bytes: &[u8]) -> TelemetryResult<()> {
13206        if self.side_tx_active() {
13207            return self.rx_packed_queue(bytes);
13208        }
13209        crate::memory_admission::check_receive(bytes)?;
13210        let data = RouterItem::Packed(crate::shared_bytes::convert(Arc::<[u8]>::from(bytes)));
13211        let item = RouterRxItem {
13212            src: None,
13213            priority: Self::router_item_priority(&data)?,
13214            data,
13215        };
13216        self.rx_item(&item, false)
13217    }
13218
13219    /// Process a decoded packet immediately as locally-originated input.
13220    ///
13221    /// If this call occurs while a side TX callback is already on the stack, the packet is queued
13222    /// instead of being processed re-entrantly.
13223    #[inline]
13224    pub fn rx(&self, pkt: &Packet) -> TelemetryResult<()> {
13225        if self.side_tx_active() {
13226            return self.rx_queue(pkt.clone());
13227        }
13228        let data = RouterItem::Packet(pkt.clone());
13229        let item = RouterRxItem {
13230            src: None,
13231            priority: Self::router_item_priority(&data)?,
13232            data,
13233        };
13234        self.rx_item(&item, false)
13235    }
13236
13237    /// Process a decoded packet immediately with an explicit ingress side id.
13238    ///
13239    /// If this call occurs while a side TX callback is already on the stack, the packet is queued
13240    /// instead of being processed re-entrantly.
13241    #[inline]
13242    pub fn rx_from_side(&self, pkt: &Packet, side: RouterSideId) -> TelemetryResult<()> {
13243        if self.side_tx_active() {
13244            return self.rx_queue_from_side(pkt.clone(), side);
13245        }
13246        self.ensure_side_ingress_enabled(side)?;
13247        let data = RouterItem::Packet(pkt.clone());
13248        let item = RouterRxItem {
13249            src: Some(side),
13250            priority: Self::router_item_priority(&data)?,
13251            data,
13252        };
13253        self.rx_item(&item, false)
13254    }
13255
13256    /// Process packed bytes immediately with an explicit ingress side id.
13257    ///
13258    /// If this call occurs while a side TX callback is already on the stack, the bytes are queued
13259    /// instead of being processed re-entrantly.
13260    #[inline]
13261    pub fn rx_packed_from_side(&self, bytes: &[u8], side: RouterSideId) -> TelemetryResult<()> {
13262        if self.side_tx_active() {
13263            return self.rx_packed_queue_from_side(bytes, side);
13264        }
13265        self.ensure_side_ingress_enabled(side)?;
13266        let Some(decoded) = self.decode_side_transport_frame(side, bytes)? else {
13267            return Ok(());
13268        };
13269        let data = RouterItem::Packed(decoded);
13270        let item = RouterRxItem {
13271            src: Some(side),
13272            priority: Self::router_item_priority(&data)?,
13273            data,
13274        };
13275        self.rx_item(&item, false)
13276    }
13277
13278    // ---------- PUBLIC API: TX immediate ----------
13279
13280    /// Transmit a decoded packet immediately.
13281    ///
13282    /// The router delivers locally where appropriate and forwards toward eligible sides. If called
13283    /// from inside a side TX callback, the packet is queued instead of being sent re-entrantly.
13284    #[inline]
13285    pub fn tx(&self, pkt: Packet) -> TelemetryResult<()> {
13286        #[cfg(feature = "discovery")]
13287        let _ = crate::memory_admission::allow_drain(self.poll_discovery())?;
13288        if self.side_tx_active() {
13289            return self.tx_queue_item(RouterTxItem::Broadcast(RouterItem::Packet(pkt)));
13290        }
13291        self.tx_item(RouterTxItem::Broadcast(RouterItem::Packet(pkt)))
13292    }
13293
13294    /// Transmit packed bytes immediately.
13295    ///
13296    /// If called from inside a side TX callback, the bytes are queued instead of being sent
13297    /// re-entrantly.
13298    #[inline]
13299    pub fn tx_packed(&self, pkt: Arc<[u8]>) -> TelemetryResult<()> {
13300        #[cfg(feature = "discovery")]
13301        let _ = crate::memory_admission::allow_drain(self.poll_discovery())?;
13302        if self.side_tx_active() {
13303            return self.tx_queue_item(RouterTxItem::Broadcast(RouterItem::Packed(
13304                crate::shared_bytes::convert(pkt),
13305            )));
13306        }
13307        self.tx_item(RouterTxItem::Broadcast(RouterItem::Packed(
13308            crate::shared_bytes::convert(pkt),
13309        )))
13310    }
13311
13312    // ---------- PUBLIC API: TX queue ----------
13313
13314    /// Queue a decoded packet for later transmission.
13315    /// Discovery/time-sync maintenance stays on the worker calling `periodic`.
13316    /// Enqueueing must not serialize topology on a producer's small stack.
13317    #[inline]
13318    pub fn tx_queue(&self, pkt: Packet) -> TelemetryResult<()> {
13319        self.tx_queue_item(RouterTxItem::Broadcast(RouterItem::Packet(pkt)))
13320    }
13321
13322    /// Queue packed bytes for later transmission.
13323    #[inline]
13324    pub fn tx_packed_queue(&self, data: Arc<[u8]>) -> TelemetryResult<()> {
13325        self.tx_queue_item(RouterTxItem::Broadcast(RouterItem::Packed(
13326            crate::shared_bytes::convert(data),
13327        )))
13328    }
13329
13330    // ---------- PUBLIC API: logging ----------
13331
13332    /// Build a packet from typed elements and send it immediately.
13333    ///
13334    /// `ty` selects the schema message type and `data` must match that type's expected element
13335    /// width and count. If called from inside a side TX callback, the built packet is queued.
13336    #[inline]
13337    pub fn log<T: LeBytes>(&self, ty: DataType, data: &[T]) -> TelemetryResult<()> {
13338        #[cfg(feature = "discovery")]
13339        let _ = crate::memory_admission::allow_drain(self.poll_discovery())?;
13340        if self.side_tx_active() {
13341            return self.log_queue(ty, data);
13342        }
13343        let sender = self.sender_arc();
13344        log_raw(
13345            sender.as_ref(),
13346            ty,
13347            data,
13348            self.packet_timestamp_ms(),
13349            |pkt| self.tx_item(RouterTxItem::Broadcast(RouterItem::Packet(pkt))),
13350        )
13351    }
13352
13353    /// Build a packet from typed elements and queue it for later transmission.
13354    #[inline]
13355    pub fn log_queue<T: LeBytes>(&self, ty: DataType, data: &[T]) -> TelemetryResult<()> {
13356        let sender = self.sender_arc();
13357        log_raw(
13358            sender.as_ref(),
13359            ty,
13360            data,
13361            self.packet_timestamp_ms(),
13362            |pkt| self.tx_queue_item(RouterTxItem::Broadcast(RouterItem::Packet(pkt))),
13363        )
13364    }
13365
13366    /// Build a packet with an explicit timestamp and send it immediately.
13367    #[inline]
13368    pub fn log_ts<T: LeBytes>(
13369        &self,
13370        ty: DataType,
13371        timestamp: u64,
13372        data: &[T],
13373    ) -> TelemetryResult<()> {
13374        #[cfg(feature = "discovery")]
13375        let _ = crate::memory_admission::allow_drain(self.poll_discovery())?;
13376        if self.side_tx_active() {
13377            return self.log_queue_ts(ty, timestamp, data);
13378        }
13379        let sender = self.sender_arc();
13380        log_raw(sender.as_ref(), ty, data, timestamp, |pkt| {
13381            self.tx_item(RouterTxItem::Broadcast(RouterItem::Packet(pkt)))
13382        })
13383    }
13384
13385    /// Build a packet with an explicit timestamp and queue it for later transmission.
13386    #[inline]
13387    pub fn log_queue_ts<T: LeBytes>(
13388        &self,
13389        ty: DataType,
13390        timestamp: u64,
13391        data: &[T],
13392    ) -> TelemetryResult<()> {
13393        let sender = self.sender_arc();
13394        log_raw(sender.as_ref(), ty, data, timestamp, |pkt| {
13395            self.tx_queue_item(RouterTxItem::Broadcast(RouterItem::Packet(pkt)))
13396        })
13397    }
13398}
13399
13400#[cfg(all(test, feature = "std", feature = "discovery"))]
13401mod queued_schema_identity_tests {
13402    use super::*;
13403
13404    fn catalog(timestamp: u64, byte: u8) -> Packet {
13405        Packet::new(
13406            crate::DataType::DiscoverySchema,
13407            &[DataEndpoint::Discovery],
13408            "SCHEMA_PEER",
13409            timestamp,
13410            crate::shared_bytes::convert(Arc::<[u8]>::from(vec![byte; 1024])),
13411        )
13412        .unwrap()
13413    }
13414
13415    #[test]
13416    fn repeated_schema_preserves_partial_transfer_but_updates_and_other_sides_do_not_coalesce() {
13417        let router = Router::new_with_clock(RouterConfig::default(), Box::new(|| 0u64));
13418        let side = router.add_side_packed("A", |_| Ok(()));
13419        let other_side = router.add_side_packed("B", |_| Ok(()));
13420        let original = catalog(1, 42);
13421        let duplicate = catalog(2, 42);
13422        assert_ne!(original.packet_id(), duplicate.packet_id());
13423        let mut st = router.state.lock();
13424        st.transmit_queue.clear();
13425        let queued = |dst, pkt| TxQueued {
13426            item: RouterTxItem::ToSide {
13427                src: None,
13428                dst,
13429                data: RouterItem::Packet(pkt),
13430            },
13431            ignore_local: true,
13432            priority: 255,
13433            retry_after_ms: 123,
13434        };
13435        st.push_transmit(queued(side, original.clone())).unwrap();
13436        st.push_transmit(queued(side, duplicate.clone())).unwrap();
13437        assert_eq!(st.transmit_queue.len(), 1);
13438        let pending = st.transmit_queue.iter().next().unwrap();
13439        assert_eq!(pending.retry_after_ms, 123);
13440        assert!(
13441            matches!(&pending.item, RouterTxItem::ToSide { data: RouterItem::Packet(pkt), .. }
13442            if pkt.packet_id() == original.packet_id())
13443        );
13444        st.push_transmit(queued(other_side, duplicate)).unwrap();
13445        assert_eq!(st.transmit_queue.len(), 2);
13446        let changed = catalog(3, 43);
13447        st.push_transmit(queued(side, changed.clone())).unwrap();
13448        assert_eq!(st.transmit_queue.len(), 2);
13449        assert!(st.transmit_queue.iter().any(|queued| matches!(&queued.item,
13450            RouterTxItem::ToSide { dst, data: RouterItem::Packet(pkt), .. }
13451            if *dst == side && pkt.packet_id() == changed.packet_id())));
13452    }
13453
13454    #[test]
13455    fn repeated_announcements_complete_one_fragment_per_dispatch() {
13456        use core::sync::atomic::{AtomicUsize, Ordering};
13457        let allowance = Arc::new(AtomicUsize::new(0));
13458        let accepted = Arc::new(std::sync::Mutex::new(Vec::<Arc<[u8]>>::new()));
13459        let available = allowance.clone();
13460        let frames = accepted.clone();
13461        let node = Router::new_with_clock(RouterConfig::default(), Box::new(|| 1u64));
13462        let side = node.add_side_packed_with_options(
13463            "bounded CAN",
13464            move |bytes| {
13465                if available
13466                    .try_update(Ordering::Relaxed, Ordering::Relaxed, |count| {
13467                        count.checked_sub(1)
13468                    })
13469                    .is_err()
13470                {
13471                    return Err(TelemetryError::Io("side tx busy"));
13472                }
13473                frames.lock().unwrap().push(Arc::<[u8]>::from(bytes));
13474                Ok(())
13475            },
13476            RouterSideOptions::default().with_small_packet_transport(128),
13477        );
13478        let handler = node.state.lock().sides[side]
13479            .as_ref()
13480            .unwrap()
13481            .tx_handler
13482            .clone();
13483        let mut complete = false;
13484        for timestamp in 1..100 {
13485            // Incompressible bytes ensure this really uses several chunks.
13486            let mut state = 0x517cc1b7u32;
13487            let payload: Vec<u8> = (0..3600)
13488                .map(|_| {
13489                    state ^= state << 13;
13490                    state ^= state >> 17;
13491                    state ^= state << 5;
13492                    state as u8
13493                })
13494                .collect();
13495            let packet = Packet::new(
13496                crate::DataType::DiscoverySchema,
13497                &[crate::DataEndpoint::Discovery],
13498                "SCHEMA_PEER",
13499                timestamp,
13500                crate::shared_bytes::convert(Arc::<[u8]>::from(payload)),
13501            )
13502            .unwrap();
13503            node.state
13504                .lock()
13505                .push_transmit(TxQueued {
13506                    item: RouterTxItem::ToSide {
13507                        src: None,
13508                        dst: side,
13509                        data: RouterItem::Packet(packet),
13510                    },
13511                    ignore_local: true,
13512                    priority: 255,
13513                    retry_after_ms: 0,
13514                })
13515                .unwrap();
13516            let data = match &node.state.lock().transmit_queue.iter().next().unwrap().item {
13517                RouterTxItem::ToSide { data, .. } => data.clone(),
13518                _ => panic!("unexpected item"),
13519            };
13520            allowance.store(1, Ordering::Relaxed);
13521            match node.call_side_tx_handler(side, &handler, &data, false) {
13522                Ok(()) => {
13523                    complete = true;
13524                    break;
13525                }
13526                Err(TelemetryError::Io(_)) => {}
13527                other => panic!("unexpected result: {other:?}"),
13528            }
13529        }
13530        assert!(
13531            complete,
13532            "new announcement timestamps must not replay the prefix forever"
13533        );
13534        let received = accepted.lock().unwrap();
13535        // One transfer ID and consecutive fragment indices prove prefix reuse
13536        // did not create incomplete transfers or duplicate accepted fragments.
13537        assert!(
13538            received.len() > 1,
13539            "fixture must exercise fragmentation, not a compressed single frame"
13540        );
13541        let first = &received[0];
13542        assert_eq!(&first[..4], &[83, 68, 84, 3]);
13543        assert_eq!(
13544            u16::from_le_bytes([first[10], first[11]]) as usize,
13545            received.len()
13546        );
13547        let transfer_id = &first[4..8];
13548        for (index, frame) in received.iter().enumerate() {
13549            assert_eq!(&frame[4..8], transfer_id);
13550            assert_eq!(u16::from_le_bytes([frame[8], frame[9]]) as usize, index);
13551        }
13552    }
13553    #[test]
13554    fn schema_receipt_resolves_assigned_numeric_address_without_adding_peers() {
13555        let router = Router::new_with_clock(RouterConfig::default(), Box::new(|| 0u64));
13556        let side = router.add_side_packed("A", |_| Ok(()));
13557        let assigned = 0x1234u32;
13558        assert_ne!(sender_address_u32("SCHEMA_PEER"), assigned);
13559        {
13560            let mut st = router.state.lock();
13561            st.discovery_routes
13562                .entry(side)
13563                .or_default()
13564                .announcers
13565                .insert(
13566                    "SCHEMA_PEER".into(),
13567                    DiscoverySenderState {
13568                        advertised_address: Some(assigned),
13569                        ..DiscoverySenderState::default()
13570                    },
13571                );
13572        }
13573        router.note_schema_received(side, "@addr:4660");
13574        router.note_schema_received(side, "@addr:4661");
13575        let st = router.state.lock();
13576        let peers = &st.discovery_routes.get(&side).unwrap().announcers;
13577        assert_eq!(peers.len(), 1);
13578        assert!(peers["SCHEMA_PEER"].has_schema);
13579    }
13580    #[test]
13581    fn busy_handler_returns_after_one_attempt_and_other_errors_keep_retry_budget() {
13582        let router = Router::new_with_clock(RouterConfig::default(), Box::new(|| 0u64));
13583        let attempts = core::cell::Cell::new(0);
13584        let result: Result<((), usize), _> = router.retry_with_attempts(8, || {
13585            attempts.set(attempts.get() + 1);
13586            Err(TelemetryError::Io("side tx busy"))
13587        });
13588        assert!(matches!(
13589            result,
13590            Err((TelemetryError::Io("side tx busy"), 1))
13591        ));
13592        assert_eq!(attempts.get(), 1);
13593        attempts.set(0);
13594        let result: Result<((), usize), _> = router.retry_with_attempts(3, || {
13595            attempts.set(attempts.get() + 1);
13596            Err(TelemetryError::Io("permanent handler failure"))
13597        });
13598        assert!(matches!(
13599            result,
13600            Err((TelemetryError::Io("permanent handler failure"), 3))
13601        ));
13602        assert_eq!(attempts.get(), 3);
13603    }
13604    #[test]
13605    fn rejected_first_template_frame_is_retried_self_describing() {
13606        crate::tests::ensure_common_test_schema();
13607        let ty = crate::config::register_data_type_with_description(
13608            "ROUTER_BUSY_FIRST_TEMPLATE",
13609            "",
13610            crate::MessageElement::Static(
13611                1,
13612                crate::MessageDataType::UInt8,
13613                crate::MessageClass::Data,
13614            ),
13615            &[crate::DataEndpoint::named("RADIO")],
13616            crate::ReliableMode::None,
13617            1,
13618        )
13619        .unwrap();
13620        let tx = Router::new_with_clock(RouterConfig::default(), Box::new(|| 0u64));
13621        let rx = Router::new_with_clock(RouterConfig::default(), Box::new(|| 0u64));
13622        let opts = RouterSideOptions::default().with_small_packet_transport(0);
13623        let a = tx.add_side_packed_with_options("wire", |_| Ok(()), opts.clone());
13624        let b = rx.add_side_packed_with_options("wire", |_| Ok(()), opts.clone());
13625        let packet = Packet::new(
13626            ty,
13627            &[crate::DataEndpoint::named("RADIO")],
13628            "BUSY_PEER",
13629            1,
13630            crate::shared_bytes::convert(Arc::<[u8]>::from([42u8])),
13631        )
13632        .unwrap();
13633        let raw = wire_format::pack_packet(&packet);
13634        // No byte of the dictionary bootstrap reaches the receiver.
13635        let first = tx
13636            .encode_side_transport_frames(
13637                a,
13638                opts.clone(),
13639                crate::shared_bytes::convert(raw.clone()),
13640            )
13641            .unwrap();
13642        assert!(
13643            tx.send_side_frames_resumable(a, first, |_| Err(TelemetryError::Io("side tx busy")))
13644                .is_err()
13645        );
13646        let retry = tx
13647            .encode_side_transport_frames(
13648                a,
13649                opts.clone(),
13650                crate::shared_bytes::convert(raw.clone()),
13651            )
13652            .unwrap();
13653        let mut received = None;
13654        tx.send_side_frames_resumable(a, retry, |frame| {
13655            assert_eq!(
13656                frame[3], SIDE_TRANSPORT_KIND_FULL,
13657                "retry requires the unaccepted dictionary bootstrap"
13658            );
13659            received = rx.decode_side_transport_frame(b, frame)?;
13660            Ok(())
13661        })
13662        .unwrap();
13663        assert_eq!(received, Some(crate::shared_bytes::convert(raw.clone())));
13664        // Compression resumes once a full frame has actually been accepted.
13665        let next = tx
13666            .encode_side_transport_frames(a, opts, crate::shared_bytes::convert(raw.clone()))
13667            .unwrap();
13668        tx.send_side_frames_resumable(a, next, |frame| {
13669            assert_eq!(frame[3], SIDE_TRANSPORT_KIND_COMPACT);
13670            assert_eq!(
13671                rx.decode_side_transport_frame(b, frame)?,
13672                Some(crate::shared_bytes::convert(raw.clone()))
13673            );
13674            Ok(())
13675        })
13676        .unwrap();
13677    }
13678    #[test]
13679    fn already_queued_receipt_precedes_ordered_retry_exhaustion() {
13680        crate::tests::ensure_common_test_schema();
13681        let ep = DataEndpoint::named("RADIO");
13682        let ty = crate::config::register_data_type_with_description(
13683            "QUEUED_RECEIPT_DEADLINE",
13684            "",
13685            MessageElement::Static(1, crate::MessageDataType::UInt8, crate::MessageClass::Data),
13686            &[ep],
13687            crate::ReliableMode::Ordered,
13688            1,
13689        )
13690        .unwrap();
13691        for isr in [false, true] {
13692            let now = Arc::new(core::sync::atomic::AtomicU64::new(0));
13693            let clock = now.clone();
13694            let router = Router::new_with_clock(
13695                RouterConfig::new([]).with_hostname("FC"),
13696                Box::new(move || clock.load(Ordering::Relaxed)),
13697            );
13698            let side = router.add_side_packed("CAN", |_| Ok(()));
13699            let packet = Packet::new(ty, &[ep], "FC", 1, Arc::<[u8]>::from([42u8])).unwrap();
13700            let id = packet.packet_id();
13701            let owner = Router::sender_hash("GS");
13702            let mut destinations = BTreeMap::new();
13703            destinations.insert(owner, side);
13704            router.state.lock().end_to_end_reliable_tx.insert(
13705                id,
13706                EndToEndReliableSent {
13707                    ordered_type: Some(ty.as_u32()),
13708                    waiting_for_predecessor: false,
13709                    data: RouterItem::Packet(packet),
13710                    pending_destinations: destinations,
13711                    tracked_destinations: true,
13712                    last_send_ms: 0,
13713                    retries: runtime_reliable_max_retries(),
13714                    queued: false,
13715                },
13716            );
13717            let ack = Packet::new_with_wire_contract(
13718                DataType::ReliableAck,
13719                message_meta(DataType::ReliableAck).endpoints_ref(),
13720                "E2EACK:GS",
13721                1,
13722                1,
13723                Arc::<[u8]>::from(id.to_le_bytes()),
13724                None,
13725                Arc::from([owner, Router::sender_hash("FC")]),
13726            )
13727            .unwrap();
13728            let packed = wire_format::pack_packet(&ack);
13729            if isr {
13730                router.rx_packed_queue_from_side_isr(&packed, side).unwrap();
13731            } else {
13732                router.rx_packed_queue_from_side(&packed, side).unwrap();
13733            }
13734            now.store(runtime_reliable_retransmit_ms(), Ordering::Relaxed);
13735            router
13736                .drain_queues_with_timeout(1)
13737                .expect("a received ACK must be processed before cancelling its ordered delivery");
13738            assert!(!router.state.lock().end_to_end_reliable_tx.contains_key(&id));
13739        }
13740    }
13741    #[test]
13742    fn receipt_budget_does_not_starve_transmit() {
13743        use core::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
13744        crate::tests::ensure_common_test_schema();
13745        let now = Arc::new(AtomicU64::new(0));
13746        let clock = now.clone();
13747        let count = Arc::new(AtomicUsize::new(0));
13748        let accepted = count.clone();
13749        let node = Router::new_with_clock(
13750            RouterConfig::default(),
13751            Box::new(move || clock.fetch_add(1, Ordering::Relaxed)),
13752        );
13753        let side = node.add_side_packed("wire", move |_| {
13754            accepted.fetch_add(1, Ordering::Relaxed);
13755            Ok(())
13756        });
13757        node.clear_queues();
13758        let packet = Packet::new(
13759            crate::DataType::ReliableAck,
13760            &[crate::DataEndpoint::TelemetryError],
13761            "E2EACK:PEER",
13762            1,
13763            Arc::<[u8]>::from([0u8; 8]),
13764        )
13765        .unwrap();
13766        node.state
13767            .lock()
13768            .push_transmit(TxQueued {
13769                item: RouterTxItem::ToSide {
13770                    src: None,
13771                    dst: side,
13772                    data: RouterItem::Packet(packet.clone()),
13773                },
13774                ignore_local: true,
13775                priority: 250,
13776                retry_after_ms: 0,
13777            })
13778            .unwrap();
13779        node.rx_packed_queue_from_side(
13780            &wire_format::pack_packet(
13781                &Packet::new(
13782                    crate::DataType::ReliableAck,
13783                    &[crate::DataEndpoint::TelemetryError],
13784                    "RX_PEER",
13785                    2,
13786                    Arc::<[u8]>::from([0u8; 8]),
13787                )
13788                .unwrap(),
13789            ),
13790            side,
13791        )
13792        .unwrap();
13793        node.drain_queues_with_timeout(1).unwrap();
13794        assert_eq!(
13795            count.load(Ordering::Relaxed),
13796            1,
13797            "control RX exhausting its cooperative budget must still allow one queued TX"
13798        );
13799    }
13800}
13801
13802#[cfg(all(test, feature = "discovery", feature = "std"))]
13803fn untemplated_test_node() -> Router {
13804    Router::new_with_clock(RouterConfig::default(), Box::new(|| 0))
13805}
13806#[cfg(all(test, feature = "discovery", feature = "std"))]
13807fn untemplated_test_options() -> RouterSideOptions {
13808    RouterSideOptions::default().with_small_packet_transport(64)
13809}
13810#[cfg(all(test, feature = "discovery", feature = "std"))]
13811#[allow(clippy::duplicate_mod)]
13812#[path = "tests/untemplated_transport.rs"]
13813mod untemplated_transport_tests;
13814
13815#[cfg(all(test, feature = "std", feature = "discovery"))]
13816mod queued_producer_maintenance_tests {
13817    use super::*;
13818    #[test]
13819    fn queued_publication_defers_discovery_to_worker_maintenance() {
13820        crate::tests::ensure_common_test_schema();
13821        let ty = DataType::named("GPS_DATA");
13822        for variant in 0..4 {
13823            let sent = Arc::new(std::sync::Mutex::new(Vec::new()));
13824            let output = sent.clone();
13825            let router = Router::new_with_clock(RouterConfig::default(), Box::new(|| 0));
13826            router.add_side_packed("wire", move |bytes| {
13827                output
13828                    .lock()
13829                    .unwrap()
13830                    .push(wire_format::unpack_packet(bytes)?.data_type());
13831                Ok(())
13832            });
13833            router.clear_queues();
13834            let packet =
13835                Packet::from_f32_slice(ty, &[1.0, 2.0, 3.0], message_meta(ty).endpoints_ref(), 42)
13836                    .unwrap();
13837            match variant {
13838                0 => router.tx_queue(packet).unwrap(),
13839                1 => router
13840                    .tx_packed_queue(wire_format::pack_packet(&packet))
13841                    .unwrap(),
13842                2 => router.log_queue(ty, &[1.0f32, 2.0, 3.0]).unwrap(),
13843                _ => router.log_queue_ts(ty, 42, &[1.0f32, 2.0, 3.0]).unwrap(),
13844            }
13845            assert!(
13846                sent.lock().unwrap().is_empty(),
13847                "producer invoked a transport"
13848            );
13849            assert_eq!(
13850                router.state.lock().transmit_queue.len(),
13851                1,
13852                "queued API performed discovery on the producer stack (variant {variant})"
13853            );
13854            router.periodic(0).unwrap();
13855            let sent = sent.lock().unwrap();
13856            assert!(sent.contains(&ty));
13857            assert!(
13858                sent.iter().copied().any(discovery::is_discovery_type),
13859                "worker maintenance must still announce the board"
13860            );
13861        }
13862    }
13863}