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
11use crate::config::{
12    MAX_QUEUE_BUDGET, MAX_RECENT_RX_IDS, QUEUE_GROW_STEP, RECENT_RX_QUEUE_BYTES,
13    STARTING_QUEUE_SIZE,
14};
15use crate::diagnostics::{
16    AdaptiveLinkStats, DiscoveryRuntimeStats, QueueRuntimeStats, ReliableRuntimeStats,
17    RouteModeStats, RouteOverrideStats, RoutePriorityStats, RouteWeightStats, RuntimeSideStats,
18    RuntimeStatsSnapshot, RuntimeTypeStats, TypedRouteOverrideStats,
19};
20#[cfg(feature = "discovery")]
21use crate::discovery::{
22    self, ClientStatsSnapshot, DISCOVERY_ROUTE_TTL_MS, DISCOVERY_SLOW_LINK_FULL_INTERVAL_MS,
23    DISCOVERY_SLOW_LINK_PING_INTERVAL_MS, DiscoveryCadenceState,
24    TIMESYNC_SLOW_LINK_MIN_INTERVAL_MS, TopologyAnnouncerRoute, TopologyBoardNode,
25    TopologySideRoute, TopologySnapshot,
26};
27use crate::packet::hash_bytes_u64;
28use crate::queue::{BoundedDeque, ByteCost};
29#[cfg(all(not(feature = "std"), target_os = "none"))]
30use crate::seds_error_msg;
31#[cfg(feature = "timesync")]
32use crate::timesync::{
33    INTERNAL_TIMESYNC_SOURCE_ID, LOCAL_TIMESYNC_DATE_SOURCE_ID, LOCAL_TIMESYNC_FULL_SOURCE_ID,
34    LOCAL_TIMESYNC_SUBSEC_SOURCE_ID, LOCAL_TIMESYNC_TOD_SOURCE_ID, NetworkClock,
35    NetworkTimeReading, PartialNetworkTime, SlewedNetworkClock, TimeSyncConfig, TimeSyncLeader,
36    TimeSyncTracker, advance_network_time, compute_network_time_sample, decode_timesync_announce,
37    decode_timesync_request, decode_timesync_response,
38};
39use crate::{
40    E2eEncryptionPolicy, MessageElement, RouteSelectionMode, TelemetryError, TelemetryResult,
41    config::{
42        DEVICE_IDENTIFIER, DataEndpoint, DataType, MAX_HANDLER_RETRIES,
43        RELIABLE_MAX_END_TO_END_PENDING, RELIABLE_MAX_PENDING, RELIABLE_MAX_RETRIES,
44        RELIABLE_MAX_RETURN_ROUTES, RELIABLE_RETRANSMIT_MS,
45    },
46    get_needed_message_size, impl_letype_num, is_reliable_type,
47    lock::{ReentryGate, RouterMutex},
48    message_e2e_encryption_policy, message_meta, message_priority,
49    packet::Packet,
50    reliable_mode, wire_format,
51};
52use alloc::string::{String, ToString};
53use alloc::{
54    borrow::ToOwned,
55    boxed::Box,
56    collections::{BTreeMap, BTreeSet, VecDeque},
57    format,
58    sync::Arc,
59    vec,
60    vec::Vec,
61};
62use core::cell::UnsafeCell;
63use core::fmt;
64use core::fmt::{Debug, Formatter};
65use core::mem::size_of;
66use core::ops::{Deref, DerefMut};
67use core::sync::atomic::{AtomicBool, AtomicU32, Ordering};
68use crc32fast::Hasher as Crc32Hasher;
69#[cfg(feature = "std")]
70use std::time::Instant;
71
72/// Logical side index (CAN, UART, RADIO, etc.)
73pub type RouterSideId = usize;
74
75const SIDE_TRANSPORT_MAGIC: &[u8; 3] = b"SDT";
76const SIDE_TRANSPORT_KIND_FULL: u8 = 0x01;
77const SIDE_TRANSPORT_KIND_COMPACT: u8 = 0x02;
78const SIDE_TRANSPORT_KIND_CHUNK: u8 = 0x03;
79const SIDE_TRANSPORT_KIND_COMPACT_DELTA: u8 = 0x04;
80const SIDE_TRANSPORT_KIND_COMPACT_SAME_TIMESTAMP: u8 = 0x05;
81const SIDE_TRANSPORT_FLAG_PAYLOAD_COMPRESSED: u8 = 0x01;
82const SIDE_TRANSPORT_FLAG_WIRE_CONTRACT: u8 = 0x04;
83const SIDE_TRANSPORT_FLAG_PACKET_NONCE: u8 = 0x08;
84const SIDE_TRANSPORT_FLAG_ENDPOINT_BITMAP_PRESENT: u8 = 0x20;
85const SIDE_TRANSPORT_FLAG_COMPACT_RELIABLE_HEADER: u8 = 0x40;
86const CONTROL_SLOW_LINK_CAPACITY_BPS: u64 = 512;
87const SIDE_TRANSPORT_CHUNK_OVERHEAD: usize = 3 + 1 + 4 + 2 + 2 + wire_format::CRC32_BYTES;
88const SIDE_TIMESTAMP_POLICY_WORDS: usize = ((crate::MAX_VALUE_DATA_TYPE as usize) + 1).div_ceil(64);
89const SIDE_TRANSPORT_EP_BITMAP_BITS: usize = (crate::MAX_VALUE_DATA_ENDPOINT as usize) + 1;
90const SIDE_TRANSPORT_EP_BITMAP_BYTES: usize = SIDE_TRANSPORT_EP_BITMAP_BITS.div_ceil(8);
91pub const IPV4_LIKE_COMPACT_HEADER_TARGET_BYTES: usize = 20;
92pub const IPV6_LIKE_COMPACT_HEADER_TARGET_BYTES: usize = 40;
93pub const DEFAULT_SIDE_TRANSPORT_TEMPLATE_LIMIT: usize = 64;
94static ROUTER_INSTANCE_SEQ: AtomicU32 = AtomicU32::new(1);
95
96#[derive(Clone, Copy, Debug, PartialEq, Eq)]
97pub enum SideTransportProfile {
98    Canonical,
99    Template,
100    Ipv6Like,
101    Ipv4Like,
102}
103
104impl SideTransportProfile {
105    #[inline]
106    pub const fn as_str(self) -> &'static str {
107        match self {
108            Self::Canonical => "canonical",
109            Self::Template => "template",
110            Self::Ipv6Like => "ipv6_like",
111            Self::Ipv4Like => "ipv4_like",
112        }
113    }
114
115    #[cfg(feature = "discovery")]
116    #[inline]
117    pub const fn discovery_code(self) -> u8 {
118        match self {
119            Self::Canonical => discovery::LINK_PROFILE_CANONICAL,
120            Self::Template => discovery::LINK_PROFILE_TEMPLATE,
121            Self::Ipv6Like => discovery::LINK_PROFILE_IPV6_LIKE,
122            Self::Ipv4Like => discovery::LINK_PROFILE_IPV4_LIKE,
123        }
124    }
125}
126
127#[derive(Clone, Debug, PartialEq, Eq)]
128struct SideHeaderTemplate {
129    hash: u64,
130    base_flags: u8,
131    prefix: Arc<[u8]>,
132    between: Arc<[u8]>,
133    reliable_flags: Option<u8>,
134    reliable_compact: bool,
135}
136
137#[derive(Clone, Debug, Default, PartialEq, Eq)]
138struct SideChunkAssembly {
139    total: u16,
140    received: BTreeMap<u16, Arc<[u8]>>,
141}
142
143#[derive(Clone, Debug, Default)]
144struct SideTransportState {
145    tx_template_ids: BTreeMap<u64, u32>,
146    tx_templates: BTreeMap<u64, SideHeaderTemplate>,
147    tx_last_timestamps: BTreeMap<u32, u64>,
148    rx_templates: BTreeMap<u64, SideHeaderTemplate>,
149    rx_templates_by_id: BTreeMap<u32, SideHeaderTemplate>,
150    rx_last_timestamps: BTreeMap<u32, u64>,
151    rx_chunks: BTreeMap<u32, SideChunkAssembly>,
152    next_chunk_id: u32,
153    next_template_id: u32,
154}
155
156impl SideTransportState {
157    fn tx_template_count(&self) -> usize {
158        self.tx_template_ids.len()
159    }
160
161    fn rx_template_count(&self) -> usize {
162        self.rx_templates_by_id.len()
163    }
164
165    fn insert_tx_template(
166        &mut self,
167        template: SideHeaderTemplate,
168        template_id: u32,
169        max_templates: usize,
170    ) -> bool {
171        if max_templates == 0 {
172            return false;
173        }
174        let mut evicted = false;
175        if self.tx_template_ids.len() >= max_templates
176            && !self.tx_template_ids.contains_key(&template.hash)
177            && let Some(old_hash) = self.tx_template_ids.keys().next().copied()
178        {
179            if let Some(old_id) = self.tx_template_ids.remove(&old_hash) {
180                self.tx_last_timestamps.remove(&old_id);
181            }
182            self.tx_templates.remove(&old_hash);
183            evicted = true;
184        }
185        self.tx_template_ids.insert(template.hash, template_id);
186        self.tx_templates.insert(template.hash, template);
187        evicted
188    }
189
190    fn insert_rx_template(
191        &mut self,
192        template_id: u32,
193        template: SideHeaderTemplate,
194        max_templates: usize,
195    ) -> bool {
196        if max_templates == 0 {
197            return false;
198        }
199        let mut evicted = false;
200        if self.rx_templates_by_id.len() >= max_templates
201            && !self.rx_templates_by_id.contains_key(&template_id)
202            && let Some(old_id) = self.rx_templates_by_id.keys().next().copied()
203            && let Some(old_template) = self.rx_templates_by_id.remove(&old_id)
204        {
205            self.rx_templates.remove(&old_template.hash);
206            self.rx_last_timestamps.remove(&old_id);
207            evicted = true;
208        }
209        self.rx_templates_by_id
210            .insert(template_id, template.clone());
211        self.rx_templates.insert(template.hash, template);
212        evicted
213    }
214}
215
216type SideTemplateExtract<'a> = (
217    SideHeaderTemplate,
218    DataType,
219    u8,
220    u64,
221    u64,
222    u16,
223    Option<(u32, u32)>,
224    &'a [u8],
225);
226
227#[derive(Clone, Copy, Debug, PartialEq, Eq)]
228pub struct CompactTimestampOmissionPolicy {
229    all: bool,
230    words: [u64; SIDE_TIMESTAMP_POLICY_WORDS],
231}
232
233impl CompactTimestampOmissionPolicy {
234    #[inline]
235    pub const fn none() -> Self {
236        Self {
237            all: false,
238            words: [0; SIDE_TIMESTAMP_POLICY_WORDS],
239        }
240    }
241
242    #[inline]
243    pub const fn all() -> Self {
244        Self {
245            all: true,
246            words: [0; SIDE_TIMESTAMP_POLICY_WORDS],
247        }
248    }
249
250    #[inline]
251    pub fn with_data_type(mut self, ty: DataType) -> Self {
252        self.insert(ty);
253        self
254    }
255
256    #[inline]
257    pub fn insert(&mut self, ty: DataType) {
258        let id = ty.as_u32() as usize;
259        if id <= crate::MAX_VALUE_DATA_TYPE as usize {
260            self.words[id / 64] |= 1u64 << (id % 64);
261        }
262    }
263
264    #[inline]
265    pub fn contains(self, ty: DataType) -> bool {
266        if self.all {
267            return true;
268        }
269        let id = ty.as_u32() as usize;
270        id <= crate::MAX_VALUE_DATA_TYPE as usize
271            && (self.words[id / 64] & (1u64 << (id % 64))) != 0
272    }
273
274    #[inline]
275    pub fn is_empty(self) -> bool {
276        !self.all && self.words.iter().all(|word| *word == 0)
277    }
278}
279
280impl Default for CompactTimestampOmissionPolicy {
281    #[inline]
282    fn default() -> Self {
283        Self::none()
284    }
285}
286
287#[derive(Clone, Copy, Debug, PartialEq, Eq)]
288enum SideCompactTimestampMode {
289    Absolute,
290    Delta,
291    Omitted,
292}
293
294#[derive(Clone, Debug, PartialEq, Eq)]
295pub enum RouterItem {
296    Packet(Packet),
297    Packed(Arc<[u8]>),
298}
299
300#[derive(Clone, Debug, PartialEq, Eq)]
301struct RouterRxItem {
302    src: Option<RouterSideId>,
303    data: RouterItem,
304    priority: u8,
305}
306
307#[derive(Clone, Debug, PartialEq, Eq)]
308enum RouterTxItem {
309    Broadcast(RouterItem),
310    EndToEndReplay {
311        packet_id: u64,
312    },
313    ToSide {
314        src: Option<RouterSideId>,
315        dst: RouterSideId,
316        data: RouterItem,
317    },
318    ReliableReplay {
319        dst: RouterSideId,
320        bytes: Arc<[u8]>,
321    },
322}
323
324impl ByteCost for RouterRxItem {
325    #[inline]
326    fn byte_cost(&self) -> usize {
327        match &self.data {
328            RouterItem::Packet(pkt) => pkt.byte_cost(),
329            RouterItem::Packed(bytes) => size_of::<Arc<[u8]>>() + bytes.len(),
330        }
331    }
332}
333
334impl ByteCost for RouterTxItem {
335    #[inline]
336    fn byte_cost(&self) -> usize {
337        match self {
338            RouterTxItem::Broadcast(data) => match data {
339                RouterItem::Packet(pkt) => pkt.byte_cost(),
340                RouterItem::Packed(bytes) => size_of::<Arc<[u8]>>() + bytes.len(),
341            },
342            RouterTxItem::EndToEndReplay { .. } => size_of::<u64>(),
343            RouterTxItem::ToSide { data, .. } => match data {
344                RouterItem::Packet(pkt) => pkt.byte_cost(),
345                RouterItem::Packed(bytes) => size_of::<Arc<[u8]>>() + bytes.len(),
346            },
347            RouterTxItem::ReliableReplay { bytes, .. } => size_of::<Arc<[u8]>>() + bytes.len(),
348        }
349    }
350}
351
352/// Transmit queue item with flags.
353/// Holds a RouterTxItem and a flag to ignore local dispatch.
354/// Used internally by the Router transmit queue.
355#[derive(Clone, Debug, PartialEq, Eq)]
356struct TxQueued {
357    item: RouterTxItem,
358    ignore_local: bool,
359    priority: u8,
360}
361
362/// ByteCost implementation for TxQueued.
363impl ByteCost for TxQueued {
364    /// Byte cost is the cost of the inner item plus one bool.
365    #[inline]
366    fn byte_cost(&self) -> usize {
367        self.item.byte_cost() + size_of::<bool>() + size_of::<u8>()
368    }
369}
370
371/// ByteCost implementation for `u64` (used by `recent_rx`).
372impl ByteCost for u64 {
373    /// Byte cost is size of u64.
374    #[inline]
375    fn byte_cost(&self) -> usize {
376        size_of::<u64>()
377    }
378}
379
380// -------------------- Reliable delivery state --------------------
381
382#[derive(Debug, Clone)]
383struct ReliableTxState {
384    next_seq: u32,
385    sent_order: VecDeque<u32>,
386    sent: BTreeMap<u32, ReliableSent>,
387}
388
389#[derive(Debug, Clone)]
390struct ReliableSent {
391    bytes: Arc<[u8]>,
392    last_send_ms: u64,
393    retries: u32,
394    queued: bool,
395    partial_acked: bool,
396}
397
398#[derive(Debug, Clone)]
399struct EndToEndReliableSent {
400    data: RouterItem,
401    pending_destinations: BTreeMap<u64, RouterSideId>,
402    tracked_destinations: bool,
403    last_send_ms: u64,
404    retries: u32,
405    queued: bool,
406}
407
408#[derive(Debug, Clone)]
409struct ReliableRxState {
410    expected_seq: u32,
411    buffered: BTreeMap<u32, Arc<[u8]>>,
412}
413
414#[derive(Debug, Clone)]
415struct ReliableReturnRouteState {
416    side: RouterSideId,
417}
418
419#[cfg(feature = "discovery")]
420#[derive(Debug, Clone, Default, PartialEq, Eq)]
421struct DiscoverySenderState {
422    reachable: Vec<DataEndpoint>,
423    reachable_timesync_sources: Vec<String>,
424    topology_boards: Vec<TopologyBoardNode>,
425    last_seen_ms: u64,
426}
427
428#[cfg(feature = "discovery")]
429#[derive(Debug, Clone, Default, PartialEq, Eq)]
430struct DiscoverySideState {
431    reachable: Vec<DataEndpoint>,
432    reachable_timesync_sources: Vec<String>,
433    last_seen_ms: u64,
434    announcers: BTreeMap<String, DiscoverySenderState>,
435}
436
437#[cfg(feature = "discovery")]
438#[derive(Debug, Clone, Default)]
439struct DiscoverySideThrottleState {
440    next_ping_ms: u64,
441    next_full_ms: u64,
442}
443
444#[cfg(all(feature = "discovery", feature = "timesync"))]
445#[derive(Debug, Clone, Default)]
446struct TimeSyncSideThrottleState {
447    next_allowed_ms: u64,
448}
449
450#[cfg(feature = "discovery")]
451#[derive(Debug, Clone, Copy, PartialEq, Eq)]
452enum DiscoveryAdvertiseLevel {
453    MinimalPing,
454    Full,
455}
456
457#[derive(Debug, Clone, Default)]
458struct AdaptiveRouteStats {
459    estimated_bandwidth_bps: u64,
460    peak_bandwidth_bps: u64,
461    last_observed_ms: u64,
462    last_slow_observed_ms: u64,
463    sample_count: u64,
464    window_started_ms: u64,
465    window_bytes: u64,
466    peak_usage_bps: u64,
467}
468
469impl AdaptiveRouteStats {
470    #[inline]
471    fn observe(&mut self, bytes: usize, sample_bps: u64, now_ms: u64) {
472        self.estimated_bandwidth_bps = if self.estimated_bandwidth_bps == 0 {
473            sample_bps
474        } else if sample_bps >= self.estimated_bandwidth_bps {
475            self.estimated_bandwidth_bps
476                .saturating_mul(3)
477                .saturating_add(sample_bps.saturating_mul(5))
478                / 8
479        } else {
480            self.estimated_bandwidth_bps
481                .saturating_mul(7)
482                .saturating_add(sample_bps)
483                / 8
484        };
485        self.peak_bandwidth_bps = self.peak_bandwidth_bps.max(sample_bps);
486        self.last_observed_ms = now_ms;
487        if sample_bps > 0 && sample_bps <= CONTROL_SLOW_LINK_CAPACITY_BPS {
488            self.last_slow_observed_ms = now_ms;
489        }
490        self.sample_count = self.sample_count.saturating_add(1);
491        if self.window_started_ms == 0 || now_ms.saturating_sub(self.window_started_ms) > 1_000 {
492            self.window_started_ms = now_ms;
493            self.window_bytes = 0;
494        }
495        self.window_bytes = self.window_bytes.saturating_add(bytes as u64);
496        self.peak_usage_bps = self.peak_usage_bps.max(self.current_usage_bps(now_ms));
497    }
498
499    #[inline]
500    fn current_usage_bps(&self, now_ms: u64) -> u64 {
501        if self.window_started_ms == 0 {
502            return 0;
503        }
504        let elapsed_ms = now_ms.saturating_sub(self.window_started_ms).max(1);
505        ((u128::from(self.window_bytes)).saturating_mul(1000) / u128::from(elapsed_ms))
506            .min(u128::from(u64::MAX)) as u64
507    }
508
509    #[inline]
510    fn available_headroom_bps(&self, now_ms: u64) -> u64 {
511        let capacity = self
512            .estimated_bandwidth_bps
513            .max(self.peak_bandwidth_bps)
514            .max(1);
515        capacity.saturating_sub(self.current_usage_bps(now_ms))
516    }
517
518    #[inline]
519    fn weight(&self, now_ms: u64) -> u64 {
520        self.available_headroom_bps(now_ms).max(1)
521    }
522
523    #[inline]
524    fn snapshot(&self, now_ms: u64, auto_balancing_enabled: bool) -> AdaptiveLinkStats {
525        let current_usage_bps = self.current_usage_bps(now_ms);
526        let estimated_capacity_bps = self.estimated_bandwidth_bps.max(1);
527        let peak_capacity_bps = self.peak_bandwidth_bps.max(estimated_capacity_bps);
528        let available_headroom_bps = peak_capacity_bps.saturating_sub(current_usage_bps);
529        AdaptiveLinkStats {
530            auto_balancing_enabled,
531            estimated_capacity_bps,
532            peak_capacity_bps,
533            current_usage_bps,
534            peak_usage_bps: self.peak_usage_bps.max(current_usage_bps),
535            available_headroom_bps,
536            effective_weight: available_headroom_bps.max(1),
537            last_observed_ms: self.last_observed_ms,
538            sample_count: self.sample_count,
539        }
540    }
541}
542
543#[derive(Debug, Clone, Default)]
544struct TypeRuntimeStatsInner {
545    tx_packets: u64,
546    tx_bytes: u64,
547    rx_packets: u64,
548    rx_bytes: u64,
549    relayed_tx_packets: u64,
550    relayed_tx_bytes: u64,
551    relayed_rx_packets: u64,
552    relayed_rx_bytes: u64,
553    tx_retries: u64,
554    handler_failures: u64,
555}
556
557#[derive(Debug, Clone, Default)]
558struct SideRuntimeStatsInner {
559    tx_packets: u64,
560    tx_bytes: u64,
561    rx_packets: u64,
562    rx_bytes: u64,
563    relayed_tx_packets: u64,
564    relayed_tx_bytes: u64,
565    relayed_rx_packets: u64,
566    relayed_rx_bytes: u64,
567    local_delivery_packets: u64,
568    tx_retries: u64,
569    tx_handler_failures: u64,
570    local_handler_failures: u64,
571    total_handler_retries: u64,
572    side_transport_full_frames: u64,
573    side_transport_compact_frames: u64,
574    side_transport_compact_delta_frames: u64,
575    side_transport_compact_omitted_timestamp_frames: u64,
576    side_transport_chunk_frames: u64,
577    side_transport_raw_bytes: u64,
578    side_transport_wire_bytes: u64,
579    side_transport_bytes_saved: u64,
580    side_transport_min_compact_overhead_bytes: Option<usize>,
581    side_transport_max_compact_overhead_bytes: Option<usize>,
582    side_transport_compact_target_misses: u64,
583    side_transport_template_evictions: u64,
584    data_types: BTreeMap<u32, TypeRuntimeStatsInner>,
585}
586
587impl SideRuntimeStatsInner {
588    fn type_stats_mut(&mut self, ty: DataType) -> &mut TypeRuntimeStatsInner {
589        self.data_types.entry(ty.as_u32()).or_default()
590    }
591
592    fn note_tx(&mut self, ty: DataType, bytes: usize, relayed: bool, retries: usize) {
593        self.tx_packets = self.tx_packets.saturating_add(1);
594        self.tx_bytes = self.tx_bytes.saturating_add(bytes as u64);
595        self.tx_retries = self.tx_retries.saturating_add(retries as u64);
596        self.total_handler_retries = self.total_handler_retries.saturating_add(retries as u64);
597        if relayed {
598            self.relayed_tx_packets = self.relayed_tx_packets.saturating_add(1);
599            self.relayed_tx_bytes = self.relayed_tx_bytes.saturating_add(bytes as u64);
600        }
601        let stats = self.type_stats_mut(ty);
602        stats.tx_packets = stats.tx_packets.saturating_add(1);
603        stats.tx_bytes = stats.tx_bytes.saturating_add(bytes as u64);
604        stats.tx_retries = stats.tx_retries.saturating_add(retries as u64);
605        if relayed {
606            stats.relayed_tx_packets = stats.relayed_tx_packets.saturating_add(1);
607            stats.relayed_tx_bytes = stats.relayed_tx_bytes.saturating_add(bytes as u64);
608        }
609    }
610
611    fn note_rx(&mut self, ty: DataType, bytes: usize, relayed: bool) {
612        self.rx_packets = self.rx_packets.saturating_add(1);
613        self.rx_bytes = self.rx_bytes.saturating_add(bytes as u64);
614        if relayed {
615            self.relayed_rx_packets = self.relayed_rx_packets.saturating_add(1);
616            self.relayed_rx_bytes = self.relayed_rx_bytes.saturating_add(bytes as u64);
617        }
618        let stats = self.type_stats_mut(ty);
619        stats.rx_packets = stats.rx_packets.saturating_add(1);
620        stats.rx_bytes = stats.rx_bytes.saturating_add(bytes as u64);
621        if relayed {
622            stats.relayed_rx_packets = stats.relayed_rx_packets.saturating_add(1);
623            stats.relayed_rx_bytes = stats.relayed_rx_bytes.saturating_add(bytes as u64);
624        }
625    }
626
627    fn note_local_delivery(&mut self, ty: DataType) {
628        self.local_delivery_packets = self.local_delivery_packets.saturating_add(1);
629        let stats = self.type_stats_mut(ty);
630        stats.rx_packets = stats.rx_packets.saturating_add(1);
631    }
632
633    fn note_local_handler_failure(&mut self, ty: DataType, retries: usize) {
634        self.local_handler_failures = self.local_handler_failures.saturating_add(1);
635        self.total_handler_retries = self.total_handler_retries.saturating_add(retries as u64);
636        let stats = self.type_stats_mut(ty);
637        stats.handler_failures = stats.handler_failures.saturating_add(1);
638    }
639
640    fn note_tx_failure(&mut self, ty: DataType, retries: usize) {
641        self.tx_handler_failures = self.tx_handler_failures.saturating_add(1);
642        self.total_handler_retries = self.total_handler_retries.saturating_add(retries as u64);
643        self.tx_retries = self.tx_retries.saturating_add(retries as u64);
644        let stats = self.type_stats_mut(ty);
645        stats.handler_failures = stats.handler_failures.saturating_add(1);
646        stats.tx_retries = stats.tx_retries.saturating_add(retries as u64);
647    }
648
649    fn note_side_transport_full(&mut self, raw_bytes: usize, wire_bytes: usize) {
650        self.side_transport_full_frames = self.side_transport_full_frames.saturating_add(1);
651        self.note_side_transport_bytes(raw_bytes, wire_bytes);
652    }
653
654    fn note_side_transport_compact(
655        &mut self,
656        raw_bytes: usize,
657        wire_bytes: usize,
658        compact_overhead_bytes: usize,
659        used_timestamp_delta: bool,
660        omitted_timestamp: bool,
661    ) {
662        self.side_transport_compact_frames = self.side_transport_compact_frames.saturating_add(1);
663        if used_timestamp_delta {
664            self.side_transport_compact_delta_frames =
665                self.side_transport_compact_delta_frames.saturating_add(1);
666        }
667        if omitted_timestamp {
668            self.side_transport_compact_omitted_timestamp_frames = self
669                .side_transport_compact_omitted_timestamp_frames
670                .saturating_add(1);
671        }
672        self.note_side_transport_bytes(raw_bytes, wire_bytes);
673        self.side_transport_min_compact_overhead_bytes = Some(
674            self.side_transport_min_compact_overhead_bytes
675                .map_or(compact_overhead_bytes, |v| v.min(compact_overhead_bytes)),
676        );
677        self.side_transport_max_compact_overhead_bytes = Some(
678            self.side_transport_max_compact_overhead_bytes
679                .map_or(compact_overhead_bytes, |v| v.max(compact_overhead_bytes)),
680        );
681    }
682
683    fn note_side_transport_chunks(&mut self, chunks: usize) {
684        self.side_transport_chunk_frames = self
685            .side_transport_chunk_frames
686            .saturating_add(chunks as u64);
687    }
688
689    fn note_side_transport_bytes(&mut self, raw_bytes: usize, wire_bytes: usize) {
690        self.side_transport_raw_bytes = self
691            .side_transport_raw_bytes
692            .saturating_add(raw_bytes as u64);
693        self.side_transport_wire_bytes = self
694            .side_transport_wire_bytes
695            .saturating_add(wire_bytes as u64);
696        if raw_bytes > wire_bytes {
697            self.side_transport_bytes_saved = self
698                .side_transport_bytes_saved
699                .saturating_add((raw_bytes - wire_bytes) as u64);
700        }
701    }
702
703    fn note_side_transport_compact_target_miss(&mut self) {
704        self.side_transport_compact_target_misses =
705            self.side_transport_compact_target_misses.saturating_add(1);
706    }
707
708    fn note_side_transport_template_eviction(&mut self) {
709        self.side_transport_template_evictions =
710            self.side_transport_template_evictions.saturating_add(1);
711    }
712}
713
714#[derive(Clone, Copy, Debug, PartialEq, Eq)]
715enum RouteSelectionOrigin {
716    Flood,
717    Discovered,
718}
719
720// -------------------- endpoint + board config --------------------
721/// Packet Handler function type
722type PacketHandlerFn = dyn Fn(&Packet) -> TelemetryResult<()> + Send + Sync + 'static;
723
724/// Packed Handler function type
725type PackedHandlerFn = dyn Fn(&[u8]) -> TelemetryResult<()> + Send + Sync + 'static;
726
727// Make handlers usable across tasks
728/// Endpoint handler function enum.
729/// Holds either a `Packet` handler or a packed byte-slice handler.
730/// /// - Packet handler signature: `Fn(&Packet) -> TelemetryResult<()>`
731/// /// - Packed handler signature: `Fn(&[u8]) -> TelemetryResult<()>`
732#[derive(Clone)]
733pub enum EndpointHandlerFn {
734    Packet(Arc<PacketHandlerFn>),
735    Packed(Arc<PackedHandlerFn>),
736}
737
738/// Endpoint handler for a specific data endpoint.
739pub struct EndpointHandler {
740    endpoint: DataEndpoint,
741    handler: EndpointHandlerFn,
742}
743
744/// Debug implementation for EndpointHandlerFn.
745impl Debug for EndpointHandlerFn {
746    #[inline]
747    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
748        match self {
749            EndpointHandlerFn::Packet(_) => f.write_str("EndpointHandlerFn::Packet(<handler>)"),
750            EndpointHandlerFn::Packed(_) => f.write_str("EndpointHandlerFn::Packed(<handler>)"),
751        }
752    }
753}
754
755/// TX handler for a router side: either packed or packet-based.
756#[derive(Clone)]
757pub enum RouterTxHandlerFn {
758    Packed(Arc<PackedHandlerFn>),
759    Packet(Arc<PacketHandlerFn>),
760}
761
762impl Debug for RouterTxHandlerFn {
763    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
764        match self {
765            RouterTxHandlerFn::Packed(_) => f.debug_tuple("Packed").field(&"<handler>").finish(),
766            RouterTxHandlerFn::Packet(_) => f.debug_tuple("Packet").field(&"<handler>").finish(),
767        }
768    }
769}
770
771#[derive(Clone, Copy, Debug)]
772pub struct RouterSideOptions {
773    /// Enables the router's per-link reliable transport layer on this side.
774    ///
775    /// When `true` and the side uses a packed TX handler, reliable schema types gain
776    /// sequence numbers, ACKs, packet requests, and retransmits on this specific hop.
777    /// When `false`, the router strips the reliable framing for that side and sends only the
778    /// application packet payload once.
779    ///
780    /// This setting only affects the hop between this router and the side's TX callback. It does
781    /// not change whether a schema `DataType` is defined as reliable, and it does not disable the
782    /// router's end-to-end reliable tracking for packets originated elsewhere in the network.
783    pub reliable_enabled: bool,
784    /// Marks the side as eligible for link-local-only endpoints and discovery routes.
785    pub link_local_enabled: bool,
786    /// Enables a side-local header-template dictionary for packed transport.
787    ///
788    /// The first frame for a stable header shape is sent in full. Later frames
789    /// on the same side can replace the repeated static header bytes with a
790    /// compact template hash plus only the fields that still vary packet to
791    /// packet.
792    pub header_template_enabled: bool,
793    /// Maximum number of bytes to emit per packed TX callback.
794    ///
795    /// When non-zero and a wrapped packed frame would exceed this size, the
796    /// router splits it into ordered chunks and reassembles those chunks on RX
797    /// before normal packet processing.
798    pub max_frame_bytes: usize,
799    /// Target total side-transport overhead for compact follow-up frames.
800    ///
801    /// This is a profiling/negotiation target rather than a hard limit. The
802    /// canonical packet is still reconstructed before normal router handling,
803    /// so constrained links should watch runtime stats to confirm compact
804    /// frames are meeting their IPv4/IPv6-like overhead budget.
805    pub compact_header_target_bytes: usize,
806    /// Maximum side-local header templates retained for TX and RX dictionaries.
807    ///
808    /// This keeps compact-link state bounded. When the dictionary is full, the
809    /// oldest deterministic entry is evicted and a later packet with that
810    /// shape will refresh the receiver with a full template frame.
811    pub max_side_transport_templates: usize,
812    /// Omits the timestamp field from compact follow-up frames when it is unchanged.
813    ///
814    /// The receiver reconstructs the canonical packet from the previous timestamp for that
815    /// side-local template. This is only used after a full or compact frame has established
816    /// timestamp context.
817    pub omit_unchanged_compact_timestamps: bool,
818    /// Optional per-data-type timestamp omission policy for compact follow-up frames.
819    ///
820    /// This allows a side to omit unchanged timestamps only for selected traffic while keeping
821    /// absolute/delta timestamps for other data types on the same link.
822    pub compact_timestamp_omission_types: CompactTimestampOmissionPolicy,
823    /// Declared compact-link profile for stats and future negotiation.
824    pub side_transport_profile: SideTransportProfile,
825    /// Allows packets received from this side to enter router processing.
826    pub ingress_enabled: bool,
827    /// Allows the router to transmit packets toward this side.
828    pub egress_enabled: bool,
829}
830
831impl Default for RouterSideOptions {
832    fn default() -> Self {
833        Self {
834            reliable_enabled: false,
835            link_local_enabled: false,
836            header_template_enabled: false,
837            max_frame_bytes: 0,
838            compact_header_target_bytes: 0,
839            max_side_transport_templates: DEFAULT_SIDE_TRANSPORT_TEMPLATE_LIMIT,
840            omit_unchanged_compact_timestamps: false,
841            compact_timestamp_omission_types: CompactTimestampOmissionPolicy::none(),
842            side_transport_profile: SideTransportProfile::Canonical,
843            ingress_enabled: true,
844            egress_enabled: true,
845        }
846    }
847}
848
849impl RouterSideOptions {
850    /// Convenience preset for compact packed-side transport.
851    ///
852    /// This enables header-template reuse and, when `max_frame_bytes > 0`,
853    /// router-managed chunking/reassembly for fixed-size transports.
854    #[inline]
855    pub fn with_small_packet_transport(mut self, max_frame_bytes: usize) -> Self {
856        self.header_template_enabled = true;
857        self.max_frame_bytes = max_frame_bytes;
858        self.compact_header_target_bytes = IPV6_LIKE_COMPACT_HEADER_TARGET_BYTES;
859        self.side_transport_profile = SideTransportProfile::Ipv6Like;
860        self
861    }
862
863    #[inline]
864    pub fn with_ipv4_like_compact_header_target(mut self) -> Self {
865        self.header_template_enabled = true;
866        self.compact_header_target_bytes = IPV4_LIKE_COMPACT_HEADER_TARGET_BYTES;
867        self.omit_unchanged_compact_timestamps = true;
868        self.side_transport_profile = SideTransportProfile::Ipv4Like;
869        self
870    }
871
872    #[inline]
873    pub fn with_ipv6_like_compact_header_target(mut self) -> Self {
874        self.header_template_enabled = true;
875        self.compact_header_target_bytes = IPV6_LIKE_COMPACT_HEADER_TARGET_BYTES;
876        self.side_transport_profile = SideTransportProfile::Ipv6Like;
877        self
878    }
879
880    #[inline]
881    pub fn with_template_transport(mut self) -> Self {
882        self.header_template_enabled = true;
883        self.side_transport_profile = SideTransportProfile::Template;
884        self
885    }
886
887    #[inline]
888    pub fn with_omitted_unchanged_compact_timestamps(mut self) -> Self {
889        self.header_template_enabled = true;
890        self.omit_unchanged_compact_timestamps = true;
891        self
892    }
893
894    #[inline]
895    pub fn with_omitted_unchanged_compact_timestamps_for_type(mut self, ty: DataType) -> Self {
896        self.header_template_enabled = true;
897        self.compact_timestamp_omission_types.insert(ty);
898        self
899    }
900
901    #[inline]
902    pub fn effective_transport_profile(self) -> SideTransportProfile {
903        if !self.header_template_enabled && self.max_frame_bytes == 0 {
904            SideTransportProfile::Canonical
905        } else if self.side_transport_profile == SideTransportProfile::Canonical {
906            SideTransportProfile::Template
907        } else {
908            self.side_transport_profile
909        }
910    }
911
912    #[cfg(feature = "discovery")]
913    #[inline]
914    pub fn link_capabilities(self) -> discovery::LinkCapabilities {
915        let mut flags = discovery::LINK_CAPABILITY_END_TO_END_RELIABILITY;
916        if self.header_template_enabled {
917            flags |= discovery::LINK_CAPABILITY_HEADER_TEMPLATES;
918        }
919        if self.max_frame_bytes != 0 {
920            flags |= discovery::LINK_CAPABILITY_CHUNKING;
921        }
922        if self.reliable_enabled {
923            flags |= discovery::LINK_CAPABILITY_RELIABILITY;
924        }
925        if self.omit_unchanged_compact_timestamps
926            || !self.compact_timestamp_omission_types.is_empty()
927        {
928            flags |= discovery::LINK_CAPABILITY_OMIT_UNCHANGED_TIMESTAMPS;
929        }
930        #[cfg(feature = "cryptography")]
931        {
932            flags |= discovery::LINK_CAPABILITY_CRYPTO;
933        }
934        discovery::LinkCapabilities {
935            version: 1,
936            flags,
937            profile: self.effective_transport_profile().discovery_code(),
938            max_frame_bytes: self.max_frame_bytes.min(u32::MAX as usize) as u32,
939            compact_header_target_bytes: self.compact_header_target_bytes.min(u32::MAX as usize)
940                as u32,
941            max_side_transport_templates: self.max_side_transport_templates.min(u32::MAX as usize)
942                as u32,
943        }
944    }
945}
946
947/// One side of the router – a name + TX handler.
948#[derive(Clone, Debug)]
949pub struct RouterSide {
950    pub name: &'static str,
951    pub tx_handler: RouterTxHandlerFn,
952    pub opts: RouterSideOptions,
953}
954
955/// Debug implementation for EndpointHandler.
956impl Debug for EndpointHandler {
957    #[inline]
958    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
959        f.debug_struct("EndpointHandler")
960            .field("endpoint", &self.endpoint)
961            .field("handler", &self.handler)
962            .finish()
963    }
964}
965
966#[inline]
967pub(crate) const fn endpoint_is_router_internal(endpoint: DataEndpoint) -> bool {
968    #[cfg(feature = "timesync")]
969    if matches!(endpoint, DataEndpoint::TimeSync) {
970        return true;
971    }
972    discovery::is_discovery_endpoint(endpoint)
973}
974
975impl EndpointHandler {
976    /// Create a new endpoint handler for `Packet` callbacks.
977    ///
978    /// Handler signature is `Fn(&Packet) -> TelemetryResult<()>`.
979    #[inline]
980    pub fn new_packet_handler<F>(endpoint: DataEndpoint, f: F) -> Self
981    where
982        F: Fn(&Packet) -> TelemetryResult<()> + Send + Sync + 'static,
983    {
984        assert!(
985            !endpoint_is_router_internal(endpoint),
986            "reserved internal endpoint handlers must not be user-registered"
987        );
988        #[cfg(feature = "std")]
989        crate::config::ensure_endpoint_id(endpoint, false)
990            .expect("endpoint handler endpoint registration failed");
991        Self {
992            endpoint,
993            handler: EndpointHandlerFn::Packet(Arc::new(f)),
994        }
995    }
996
997    /// Create a new packet handler from a runtime endpoint definition.
998    #[inline]
999    pub fn new_packet_handler_for<F>(endpoint: crate::config::EndpointDefinition, f: F) -> Self
1000    where
1001        F: Fn(&Packet) -> TelemetryResult<()> + Send + Sync + 'static,
1002    {
1003        Self::new_packet_handler(endpoint.id, f)
1004    }
1005
1006    /// Create a new packet handler by endpoint name.
1007    #[cfg(feature = "std")]
1008    #[inline]
1009    pub fn new_packet_handler_by_name<F>(endpoint_name: &str, f: F) -> TelemetryResult<Self>
1010    where
1011        F: Fn(&Packet) -> TelemetryResult<()> + Send + Sync + 'static,
1012    {
1013        let endpoint = crate::config::endpoint_definition_by_name(endpoint_name)
1014            .ok_or(TelemetryError::BadArg)?;
1015        Ok(Self::new_packet_handler(endpoint.id, f))
1016    }
1017
1018    /// Create a new endpoint handler for packed byte-slice callbacks.
1019    ///
1020    /// Handler signature is `Fn(&[u8]) -> TelemetryResult<()>`.
1021    #[inline]
1022    pub fn new_packed_handler<F>(endpoint: DataEndpoint, f: F) -> Self
1023    where
1024        F: Fn(&[u8]) -> TelemetryResult<()> + Send + Sync + 'static,
1025    {
1026        assert!(
1027            !endpoint_is_router_internal(endpoint),
1028            "reserved internal endpoint handlers must not be user-registered"
1029        );
1030        #[cfg(feature = "std")]
1031        crate::config::ensure_endpoint_id(endpoint, false)
1032            .expect("endpoint handler endpoint registration failed");
1033        Self {
1034            endpoint,
1035            handler: EndpointHandlerFn::Packed(Arc::new(f)),
1036        }
1037    }
1038
1039    /// Create a new packed handler from a runtime endpoint definition.
1040    #[inline]
1041    pub fn new_packed_handler_for<F>(endpoint: crate::config::EndpointDefinition, f: F) -> Self
1042    where
1043        F: Fn(&[u8]) -> TelemetryResult<()> + Send + Sync + 'static,
1044    {
1045        Self::new_packed_handler(endpoint.id, f)
1046    }
1047
1048    /// Create a new packed handler by endpoint name.
1049    #[cfg(feature = "std")]
1050    #[inline]
1051    pub fn new_packed_handler_by_name<F>(endpoint_name: &str, f: F) -> TelemetryResult<Self>
1052    where
1053        F: Fn(&[u8]) -> TelemetryResult<()> + Send + Sync + 'static,
1054    {
1055        let endpoint = crate::config::endpoint_definition_by_name(endpoint_name)
1056            .ok_or(TelemetryError::BadArg)?;
1057        Ok(Self::new_packed_handler(endpoint.id, f))
1058    }
1059
1060    /// Return the endpoint that the handler is registered for.
1061    #[inline]
1062    pub fn get_endpoint(&self) -> DataEndpoint {
1063        self.endpoint
1064    }
1065
1066    /// Return a reference to the handler function.
1067    #[inline]
1068    pub fn get_handler(&self) -> &EndpointHandlerFn {
1069        &self.handler
1070    }
1071}
1072
1073pub trait Clock {
1074    /// Return the current time in milliseconds.
1075    fn now_ms(&self) -> u64;
1076
1077    /// Return the current time in nanoseconds.
1078    ///
1079    /// The default implementation derives this from [`Clock::now_ms`].
1080    fn now_ns(&self) -> u64 {
1081        self.now_ms().saturating_mul(1_000_000)
1082    }
1083}
1084
1085impl<T: Fn() -> u64> Clock for T {
1086    #[inline]
1087    fn now_ms(&self) -> u64 {
1088        self()
1089    }
1090}
1091
1092#[cfg(feature = "std")]
1093#[derive(Debug)]
1094struct StdMonotonicClock {
1095    start: Instant,
1096}
1097
1098#[cfg(feature = "std")]
1099impl Default for StdMonotonicClock {
1100    fn default() -> Self {
1101        Self {
1102            start: Instant::now(),
1103        }
1104    }
1105}
1106
1107#[cfg(feature = "std")]
1108impl Clock for StdMonotonicClock {
1109    fn now_ms(&self) -> u64 {
1110        u64::try_from(self.start.elapsed().as_millis()).unwrap_or(u64::MAX)
1111    }
1112
1113    fn now_ns(&self) -> u64 {
1114        u64::try_from(self.start.elapsed().as_nanos()).unwrap_or(u64::MAX)
1115    }
1116}
1117
1118/// Router-level E2E cryptography behavior.
1119#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1120pub enum RouterE2eEncryptionMode {
1121    /// Do not use E2E cryptography. Data types marked `RequireOn` are rejected.
1122    Disabled,
1123    /// Use E2E cryptography only for data types that require it.
1124    RequiredOnly,
1125    /// Use E2E cryptography for required and preferred data types.
1126    Preferred,
1127    /// Require E2E cryptography for every non-control data type.
1128    ForceAll,
1129}
1130
1131pub type NodeAddress = u32;
1132pub type P2pPort = u16;
1133pub type P2pStreamId = u32;
1134
1135const P2P_STREAM_MAGIC: [u8; 4] = *b"SDSP";
1136const P2P_STREAM_VERSION: u8 = 1;
1137const P2P_STREAM_SYN: u8 = 0x01;
1138const P2P_STREAM_ACK: u8 = 0x02;
1139const P2P_STREAM_FIN: u8 = 0x04;
1140const P2P_STREAM_RST: u8 = 0x08;
1141const P2P_STREAM_DATA: u8 = 0x10;
1142
1143#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1144pub enum AddressAssignmentMode {
1145    Dynamic,
1146    Requested(NodeAddress),
1147    Static(NodeAddress),
1148}
1149
1150impl AddressAssignmentMode {
1151    #[inline]
1152    fn mode_code(self) -> u8 {
1153        match self {
1154            Self::Dynamic => 0,
1155            Self::Requested(_) => 1,
1156            Self::Static(_) => 2,
1157        }
1158    }
1159
1160    #[inline]
1161    fn requested_address(self) -> NodeAddress {
1162        match self {
1163            Self::Dynamic => 0,
1164            Self::Requested(addr) | Self::Static(addr) => addr,
1165        }
1166    }
1167}
1168
1169#[derive(Debug, Clone, PartialEq, Eq)]
1170pub enum AddressChangeReason {
1171    DynamicConflict,
1172    RequestedConflict,
1173    StaticConflict,
1174    HostnameConflict,
1175}
1176
1177#[derive(Debug, Clone, PartialEq, Eq)]
1178pub struct AddressChange {
1179    pub old_hostname: Arc<str>,
1180    pub new_hostname: Arc<str>,
1181    pub old_address: NodeAddress,
1182    pub new_address: NodeAddress,
1183    pub reason: AddressChangeReason,
1184}
1185
1186#[derive(Debug, Clone, PartialEq, Eq)]
1187pub struct AddressBookEntry {
1188    pub hostname: Arc<str>,
1189    pub address: NodeAddress,
1190    pub requested_address: NodeAddress,
1191    pub mode: AddressAssignmentMode,
1192    pub birth_ms: u64,
1193    pub owner_hash: u64,
1194    pub last_seen_ms: u64,
1195}
1196
1197pub struct P2pMessage<'a> {
1198    pub source_hostname: &'a str,
1199    pub source_address: NodeAddress,
1200    pub source_port: P2pPort,
1201    pub destination_port: P2pPort,
1202    pub payload: &'a [u8],
1203}
1204
1205#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1206pub enum P2pStreamEventKind {
1207    Accepted,
1208    Connected,
1209    Data,
1210    Closed,
1211    Reset,
1212}
1213
1214pub struct P2pStreamEvent<'a> {
1215    pub kind: P2pStreamEventKind,
1216    pub stream_id: P2pStreamId,
1217    pub peer_stream_id: P2pStreamId,
1218    pub sequence: u32,
1219    pub peer_hostname: &'a str,
1220    pub peer_address: NodeAddress,
1221    pub local_port: P2pPort,
1222    pub peer_port: P2pPort,
1223    pub payload: &'a [u8],
1224}
1225
1226struct P2pDecoded<'a> {
1227    source_hostname: &'a str,
1228    source_address: NodeAddress,
1229    source_port: P2pPort,
1230    destination_port: P2pPort,
1231    payload: &'a [u8],
1232}
1233
1234#[derive(Debug, Clone)]
1235struct P2pStreamDecoded<'a> {
1236    flags: u8,
1237    source_stream_id: P2pStreamId,
1238    destination_stream_id: P2pStreamId,
1239    sequence: u32,
1240    payload: &'a [u8],
1241}
1242
1243type AddressChangeFn = dyn Fn(AddressChange) -> TelemetryResult<()> + Send + Sync + 'static;
1244
1245#[derive(Clone)]
1246struct AddressChangeHandler {
1247    handler: Arc<AddressChangeFn>,
1248}
1249
1250impl Debug for AddressChangeHandler {
1251    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1252        f.write_str("AddressChangeHandler(<handler>)")
1253    }
1254}
1255
1256type P2pPortHandlerFn = dyn Fn(P2pMessage<'_>) -> TelemetryResult<()> + Send + Sync + 'static;
1257
1258#[derive(Clone)]
1259struct P2pPortHandler {
1260    handler: Arc<P2pPortHandlerFn>,
1261}
1262
1263impl Debug for P2pPortHandler {
1264    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1265        f.write_str("P2pPortHandler(<handler>)")
1266    }
1267}
1268
1269type P2pStreamHandlerFn = dyn Fn(P2pStreamEvent<'_>) -> TelemetryResult<()> + Send + Sync + 'static;
1270
1271#[derive(Clone)]
1272struct P2pStreamHandler {
1273    handler: Arc<P2pStreamHandlerFn>,
1274}
1275
1276impl Debug for P2pStreamHandler {
1277    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1278        f.write_str("P2pStreamHandler(<handler>)")
1279    }
1280}
1281
1282#[derive(Debug, Clone)]
1283struct P2pStreamSession {
1284    peer_hostname: Arc<str>,
1285    peer_address: NodeAddress,
1286    local_port: P2pPort,
1287    peer_port: P2pPort,
1288    peer_stream_id: P2pStreamId,
1289    next_sequence: u32,
1290    connected: bool,
1291}
1292
1293#[derive(Debug, Clone)]
1294struct PendingP2pStreamEvent {
1295    handlers: Vec<P2pStreamHandler>,
1296    kind: P2pStreamEventKind,
1297    stream_id: P2pStreamId,
1298    peer_stream_id: P2pStreamId,
1299    peer_hostname: Arc<str>,
1300    peer_address: NodeAddress,
1301    local_port: P2pPort,
1302    peer_port: P2pPort,
1303}
1304
1305#[derive(Debug, Clone)]
1306pub struct RouterConfig {
1307    /// Handlers for local endpoints.
1308    handlers: Arc<[EndpointHandler]>,
1309    /// Whether to enable reliable ordering/ACKs for reliable data types.
1310    reliable_enabled: bool,
1311    /// Optional per-router sender override.
1312    sender: Option<Arc<str>>,
1313    /// Address assignment policy used for P2P routing.
1314    address_mode: AddressAssignmentMode,
1315    /// Callbacks invoked when conflict resolution changes this router's identity.
1316    address_change_handlers: Arc<[AddressChangeHandler]>,
1317    /// End-to-end cryptography behavior for user data.
1318    e2e_encryption: RouterE2eEncryptionMode,
1319    /// Application-defined key id passed to the cryptography provider.
1320    #[cfg_attr(not(feature = "cryptography"), allow(dead_code))]
1321    e2e_key_id: u32,
1322    #[cfg(feature = "timesync")]
1323    timesync: Option<TimeSyncConfig>,
1324}
1325
1326impl RouterConfig {
1327    /// Default router E2E mode for this build.
1328    ///
1329    /// Builds with cryptography prefer encrypted payloads automatically for data types that request
1330    /// it. Minimal builds without cryptography stay disabled and reject `RequireOn` traffic.
1331    pub fn default_e2e_encryption_mode() -> RouterE2eEncryptionMode {
1332        #[cfg(feature = "cryptography")]
1333        {
1334            RouterE2eEncryptionMode::Preferred
1335        }
1336        #[cfg(not(feature = "cryptography"))]
1337        {
1338            RouterE2eEncryptionMode::Disabled
1339        }
1340    }
1341
1342    /// Create a new router configuration with the specified local endpoint handlers.
1343    pub fn new<H>(handlers: H) -> Self
1344    where
1345        H: Into<Arc<[EndpointHandler]>>,
1346    {
1347        let handlers: Arc<[EndpointHandler]> = handlers.into();
1348        assert!(
1349            handlers
1350                .iter()
1351                .all(|handler| !endpoint_is_router_internal(handler.endpoint)),
1352            "reserved internal endpoint handlers must not be user-registered"
1353        );
1354        Self {
1355            handlers,
1356            reliable_enabled: true,
1357            sender: None,
1358            address_mode: AddressAssignmentMode::Dynamic,
1359            address_change_handlers: Arc::from([]),
1360            e2e_encryption: Self::default_e2e_encryption_mode(),
1361            e2e_key_id: 0,
1362            #[cfg(feature = "timesync")]
1363            timesync: None,
1364        }
1365    }
1366
1367    /// Enable or disable reliable delivery for this router instance.
1368    pub fn with_reliable_enabled(mut self, enabled: bool) -> Self {
1369        self.reliable_enabled = enabled;
1370        self
1371    }
1372
1373    /// Override the sender identifier for this router instance.
1374    pub fn with_sender<S: AsRef<str>>(mut self, sender: S) -> Self {
1375        self.sender = Some(Arc::from(sender.as_ref()));
1376        self
1377    }
1378
1379    /// Alias for `with_sender`; discovery uses this name for P2P service resolution.
1380    pub fn with_hostname<S: AsRef<str>>(self, hostname: S) -> Self {
1381        self.with_sender(hostname)
1382    }
1383
1384    /// Request a dynamic address assigned by the discovered network authority.
1385    pub fn with_dynamic_address(mut self) -> Self {
1386        self.address_mode = AddressAssignmentMode::Dynamic;
1387        self
1388    }
1389
1390    /// Request a preferred address. It is kept when unique and reassigned on conflict.
1391    pub fn with_requested_address(mut self, address: NodeAddress) -> Self {
1392        self.address_mode = AddressAssignmentMode::Requested(address);
1393        self
1394    }
1395
1396    /// Require a static address. If two static nodes conflict, the oldest keeps it.
1397    pub fn with_static_address(mut self, address: NodeAddress) -> Self {
1398        self.address_mode = AddressAssignmentMode::Static(address);
1399        self
1400    }
1401
1402    /// Register a callback for local address or hostname changes after conflict resolution.
1403    pub fn on_address_change<F>(mut self, f: F) -> Self
1404    where
1405        F: Fn(AddressChange) -> TelemetryResult<()> + Send + Sync + 'static,
1406    {
1407        let mut handlers = self.address_change_handlers.to_vec();
1408        handlers.push(AddressChangeHandler {
1409            handler: Arc::new(f),
1410        });
1411        self.address_change_handlers = Arc::from(handlers);
1412        self
1413    }
1414
1415    /// Configure this router's end-to-end cryptography policy.
1416    pub fn with_e2e_encryption(mut self, mode: RouterE2eEncryptionMode) -> Self {
1417        self.e2e_encryption = mode;
1418        self
1419    }
1420
1421    /// Configure the application-defined key id used for E2E encrypted payloads.
1422    pub fn with_e2e_key_id(mut self, key_id: u32) -> Self {
1423        self.e2e_key_id = key_id;
1424        self
1425    }
1426
1427    #[cfg(feature = "timesync")]
1428    /// Enables and configures built-in time synchronization for this router.
1429    pub fn with_timesync(mut self, cfg: TimeSyncConfig) -> Self {
1430        self.timesync = Some(cfg);
1431        self
1432    }
1433
1434    #[inline]
1435    /// Check if the specified endpoint is local to this router.
1436    fn is_local_endpoint(&self, ep: DataEndpoint) -> bool {
1437        if endpoint_is_router_internal(ep) {
1438            return false;
1439        }
1440        self.handlers.iter().any(|h| h.endpoint == ep)
1441    }
1442
1443    #[inline]
1444    fn reliable_enabled(&self) -> bool {
1445        self.reliable_enabled
1446    }
1447
1448    #[inline]
1449    fn sender(&self) -> &str {
1450        self.sender.as_deref().unwrap_or(DEVICE_IDENTIFIER)
1451    }
1452
1453    #[inline]
1454    fn address_mode(&self) -> AddressAssignmentMode {
1455        self.address_mode
1456    }
1457
1458    #[inline]
1459    fn e2e_encryption(&self) -> RouterE2eEncryptionMode {
1460        self.e2e_encryption
1461    }
1462
1463    #[cfg(feature = "cryptography")]
1464    #[inline]
1465    fn e2e_key_id(&self) -> u32 {
1466        self.e2e_key_id
1467    }
1468
1469    #[cfg(feature = "timesync")]
1470    #[inline]
1471    fn timesync_config(&self) -> Option<TimeSyncConfig> {
1472        self.timesync
1473    }
1474}
1475
1476impl Default for RouterConfig {
1477    fn default() -> Self {
1478        Self {
1479            handlers: Arc::from([]),
1480            reliable_enabled: true,
1481            sender: None,
1482            address_mode: AddressAssignmentMode::Dynamic,
1483            address_change_handlers: Arc::from([]),
1484            e2e_encryption: Self::default_e2e_encryption_mode(),
1485            e2e_key_id: 0,
1486            #[cfg(feature = "timesync")]
1487            timesync: None,
1488        }
1489    }
1490}
1491
1492// -------------------- generic little-endian serialization --------------------
1493
1494pub trait LeBytes: Copy + Sized {
1495    const WIDTH: usize;
1496    fn write_le(self, out: &mut [u8]);
1497    fn from_le_slice(bytes: &[u8]) -> Self;
1498}
1499
1500impl_letype_num!(u8, 1);
1501impl_letype_num!(u16, 2);
1502impl_letype_num!(u32, 4);
1503impl_letype_num!(u64, 8);
1504impl_letype_num!(u128, 16);
1505impl_letype_num!(i8, 1);
1506impl_letype_num!(i16, 2);
1507impl_letype_num!(i32, 4);
1508impl_letype_num!(i64, 8);
1509impl_letype_num!(i128, 16);
1510impl_letype_num!(f32, 4);
1511impl_letype_num!(f64, 8);
1512
1513/// Encode a slice of `LeBytes` into a contiguous little-endian byte array.
1514pub(crate) fn encode_slice_le<T: LeBytes>(data: &[T]) -> Arc<[u8]> {
1515    let total = data.len() * T::WIDTH;
1516    let mut buf = vec![0u8; total];
1517
1518    for (i, v) in data.iter().copied().enumerate() {
1519        let start = i * T::WIDTH;
1520        v.write_le(&mut buf[start..start + T::WIDTH]);
1521    }
1522
1523    Arc::from(buf)
1524}
1525
1526/// Build an error payload for `TelemetryError` packets, respecting the
1527/// static/dynamic sizing rules from `message_meta`.
1528fn make_error_payload(msg: &str) -> Arc<[u8]> {
1529    let meta = message_meta(DataType::TelemetryError);
1530    match meta.element {
1531        MessageElement::Static(_, _, _) => {
1532            let max = get_needed_message_size(DataType::TelemetryError);
1533            let bytes = msg.as_bytes();
1534            let n = core::cmp::min(max, bytes.len());
1535            let mut buf = vec![0u8; max];
1536            if n > 0 {
1537                buf[..n].copy_from_slice(&bytes[..n]);
1538            }
1539            Arc::from(buf)
1540        }
1541        MessageElement::Dynamic(_, _) => Arc::from(msg.as_bytes()),
1542    }
1543}
1544
1545/// Generic raw logger function used by Router::log and Router::log_queue.
1546/// Builds a Packet from the provided data slice and passes it to the
1547/// provided transmission function.
1548fn log_raw<T, F>(
1549    sender: &str,
1550    ty: DataType,
1551    data: &[T],
1552    timestamp: u64,
1553    mut tx_function: F,
1554) -> TelemetryResult<()>
1555where
1556    T: LeBytes,
1557    F: FnMut(Packet) -> TelemetryResult<()>,
1558{
1559    let meta = message_meta(ty);
1560    let got = data.len() * T::WIDTH;
1561
1562    match meta.element {
1563        MessageElement::Static(_, _, _) => {
1564            if got != get_needed_message_size(ty) {
1565                return Err(TelemetryError::SizeMismatch {
1566                    expected: get_needed_message_size(ty),
1567                    got,
1568                });
1569            }
1570        }
1571        MessageElement::Dynamic(_, _) => {
1572            // For dynamic numeric payloads, require total byte length to be a multiple of element width.
1573            if !got.is_multiple_of(T::WIDTH) {
1574                return Err(TelemetryError::SizeMismatch {
1575                    expected: T::WIDTH,
1576                    got,
1577                });
1578            }
1579        }
1580    }
1581
1582    let payload = encode_slice_le(data);
1583    let pkt = Packet::new(ty, meta.endpoints, sender, timestamp, payload)?;
1584    tx_function(pkt)
1585}
1586
1587/// Fallback printing for error messages when no local endpoints exist.
1588/// - With `std`: prints to stderr.
1589/// - Without `std`: forwards to `seds_error_msg` (platform-provided).
1590fn fallback_stdout(msg: &str) {
1591    #[cfg(feature = "std")]
1592    {
1593        eprintln!("{}", msg);
1594    }
1595    #[cfg(all(not(feature = "std"), target_os = "none"))]
1596    {
1597        let message = format!("{}\n", msg);
1598        unsafe {
1599            seds_error_msg(message.as_ptr(), message.len());
1600        }
1601    }
1602}
1603
1604// -------------------- Router --------------------
1605
1606/// Internal mutable state of the Router, protected by `RouterMutex`.
1607/// Holds the RX/TX queues and the recent-RX de-duplication set.
1608#[derive(Debug, Clone)]
1609struct RouterInner {
1610    sides: Vec<Option<RouterSide>>,
1611    route_overrides: BTreeMap<(Option<RouterSideId>, RouterSideId), bool>,
1612    typed_route_overrides: BTreeMap<(Option<RouterSideId>, u32, RouterSideId), bool>,
1613    route_weights: BTreeMap<(Option<RouterSideId>, RouterSideId), u32>,
1614    route_priorities: BTreeMap<(Option<RouterSideId>, RouterSideId), u32>,
1615    source_route_modes: BTreeMap<Option<RouterSideId>, RouteSelectionMode>,
1616    route_selection_cursors: BTreeMap<Option<RouterSideId>, u64>,
1617    adaptive_route_stats: BTreeMap<RouterSideId, AdaptiveRouteStats>,
1618    side_runtime_stats: BTreeMap<RouterSideId, SideRuntimeStatsInner>,
1619    side_transport: BTreeMap<RouterSideId, SideTransportState>,
1620    managed_variable_types: BTreeSet<u32>,
1621    managed_variable_permissions: BTreeMap<u32, NetworkVariablePermissions>,
1622    managed_variable_latest: BTreeMap<u32, ManagedVariableCacheEntry>,
1623    network_variable_update_handlers: BTreeMap<u32, Vec<NetworkVariableUpdateHandler>>,
1624    local_address: AddressBookEntry,
1625    address_book: BTreeMap<String, AddressBookEntry>,
1626    address_by_value: BTreeMap<NodeAddress, String>,
1627    p2p_port_handlers: BTreeMap<P2pPort, Vec<P2pPortHandler>>,
1628    p2p_stream_handlers: BTreeMap<P2pPort, Vec<P2pStreamHandler>>,
1629    p2p_stream_sessions: BTreeMap<P2pStreamId, P2pStreamSession>,
1630    next_p2p_stream_id: P2pStreamId,
1631    received_queue: BoundedDeque<RouterRxItem>,
1632    transmit_queue: BoundedDeque<TxQueued>,
1633    recent_rx: BoundedDeque<u64>,
1634    reliable_tx: BTreeMap<(RouterSideId, u32), ReliableTxState>,
1635    reliable_rx: BTreeMap<(RouterSideId, u32), ReliableRxState>,
1636    reliable_return_routes: BTreeMap<u64, ReliableReturnRouteState>,
1637    reliable_return_route_order: VecDeque<u64>,
1638    end_to_end_reliable_tx: BTreeMap<u64, EndToEndReliableSent>,
1639    end_to_end_reliable_tx_order: VecDeque<u64>,
1640    total_handler_failures: u64,
1641    total_handler_retries: u64,
1642    #[cfg(feature = "discovery")]
1643    discovery_routes: BTreeMap<RouterSideId, DiscoverySideState>,
1644    #[cfg(feature = "discovery")]
1645    discovery_cadence: DiscoveryCadenceState,
1646    #[cfg(feature = "discovery")]
1647    discovery_side_throttle: BTreeMap<RouterSideId, DiscoverySideThrottleState>,
1648    #[cfg(all(feature = "discovery", feature = "timesync"))]
1649    timesync_side_throttle: BTreeMap<RouterSideId, TimeSyncSideThrottleState>,
1650}
1651
1652#[derive(Debug, Clone)]
1653struct ManagedVariableCacheEntry {
1654    packet: Packet,
1655    cached_at_ms: u64,
1656}
1657
1658type NetworkVariableUpdateFn = dyn Fn(&Packet) -> TelemetryResult<()> + Send + Sync + 'static;
1659
1660#[derive(Clone)]
1661struct NetworkVariableUpdateHandler {
1662    handler: Arc<NetworkVariableUpdateFn>,
1663}
1664
1665impl Debug for NetworkVariableUpdateHandler {
1666    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1667        f.write_str("NetworkVariableUpdateHandler(<handler>)")
1668    }
1669}
1670
1671/// Local permission policy for a network-managed variable.
1672#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1673pub struct NetworkVariablePermissions {
1674    pub read: bool,
1675    pub write: bool,
1676}
1677
1678impl NetworkVariablePermissions {
1679    pub const NONE: Self = Self {
1680        read: false,
1681        write: false,
1682    };
1683    pub const READ_ONLY: Self = Self {
1684        read: true,
1685        write: false,
1686    };
1687    pub const WRITE_ONLY: Self = Self {
1688        read: false,
1689        write: true,
1690    };
1691    pub const READ_WRITE: Self = Self {
1692        read: true,
1693        write: true,
1694    };
1695}
1696
1697#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1698enum RouterQueueKind {
1699    Received,
1700    Transmit,
1701    Recent,
1702    ReliableRxBuffer,
1703    #[cfg(feature = "discovery")]
1704    Discovery,
1705}
1706
1707impl RouterInner {
1708    #[cfg(feature = "discovery")]
1709    fn topology_board_byte_cost(board: &TopologyBoardNode) -> usize {
1710        board
1711            .sender_id
1712            .len()
1713            .saturating_add(board.reachable_endpoints.len() * size_of::<DataEndpoint>())
1714            .saturating_add(
1715                board
1716                    .reachable_timesync_sources
1717                    .iter()
1718                    .map(|s| s.len())
1719                    .sum::<usize>(),
1720            )
1721            .saturating_add(board.connections.iter().map(|s| s.len()).sum::<usize>())
1722    }
1723
1724    #[cfg(feature = "discovery")]
1725    fn discovery_sender_byte_cost(sender: &str, state: &DiscoverySenderState) -> usize {
1726        sender
1727            .len()
1728            .saturating_add(state.reachable.len() * size_of::<DataEndpoint>())
1729            .saturating_add(
1730                state
1731                    .reachable_timesync_sources
1732                    .iter()
1733                    .map(|s| s.len())
1734                    .sum::<usize>(),
1735            )
1736            .saturating_add(
1737                state
1738                    .topology_boards
1739                    .iter()
1740                    .map(Self::topology_board_byte_cost)
1741                    .sum::<usize>(),
1742            )
1743            .saturating_add(size_of::<DiscoverySenderState>())
1744    }
1745
1746    #[cfg(feature = "discovery")]
1747    fn discovery_route_byte_cost(side: RouterSideId, route: &DiscoverySideState) -> usize {
1748        size_of::<RouterSideId>()
1749            .saturating_add(size_of::<DiscoverySideState>())
1750            .saturating_add(route.reachable.len() * size_of::<DataEndpoint>())
1751            .saturating_add(
1752                route
1753                    .reachable_timesync_sources
1754                    .iter()
1755                    .map(|s| s.len())
1756                    .sum::<usize>(),
1757            )
1758            .saturating_add(
1759                route
1760                    .announcers
1761                    .iter()
1762                    .map(|(sender, state)| Self::discovery_sender_byte_cost(sender, state))
1763                    .sum::<usize>(),
1764            )
1765            .saturating_add(side.saturating_sub(side))
1766    }
1767
1768    #[cfg(feature = "discovery")]
1769    fn discovery_bytes_used(&self) -> usize {
1770        self.discovery_routes
1771            .iter()
1772            .map(|(side, route)| Self::discovery_route_byte_cost(*side, route))
1773            .sum()
1774    }
1775
1776    #[inline]
1777    fn reliable_rx_buffered_bytes(&self) -> usize {
1778        self.reliable_rx
1779            .values()
1780            .flat_map(|state| state.buffered.values())
1781            .map(|bytes| size_of::<Arc<[u8]>>() + bytes.len())
1782            .sum()
1783    }
1784
1785    #[inline]
1786    fn shared_queue_bytes_used(&self) -> usize {
1787        self.received_queue
1788            .bytes_used()
1789            .saturating_add(self.transmit_queue.bytes_used())
1790            .saturating_add(self.recent_rx.max_bytes())
1791            .saturating_add(self.reliable_rx_buffered_bytes())
1792            .saturating_add(crate::config::schema_bytes_used())
1793            .saturating_add({
1794                #[cfg(feature = "discovery")]
1795                {
1796                    self.discovery_bytes_used()
1797                }
1798                #[cfg(not(feature = "discovery"))]
1799                {
1800                    0
1801                }
1802            })
1803    }
1804
1805    fn reliable_rx_buffer_len(&self) -> usize {
1806        self.reliable_rx
1807            .values()
1808            .map(|state| state.buffered.len())
1809            .sum()
1810    }
1811
1812    fn pop_reliable_rx_buffered(&mut self) -> Option<Arc<[u8]>> {
1813        let key = self
1814            .reliable_rx
1815            .iter()
1816            .find_map(|(key, state)| (!state.buffered.is_empty()).then_some(*key))?;
1817        self.reliable_rx
1818            .get_mut(&key)?
1819            .buffered
1820            .pop_first()
1821            .map(|(_, v)| v)
1822    }
1823
1824    fn pop_shared_queue_item(&mut self, preferred: RouterQueueKind) -> bool {
1825        match preferred {
1826            RouterQueueKind::Received => self.received_queue.pop_front().is_some(),
1827            RouterQueueKind::Transmit => self.transmit_queue.pop_front().is_some(),
1828            RouterQueueKind::Recent => self.recent_rx.pop_front().is_some(),
1829            RouterQueueKind::ReliableRxBuffer => self.pop_reliable_rx_buffered().is_some(),
1830            #[cfg(feature = "discovery")]
1831            RouterQueueKind::Discovery => self.pop_discovery_route(),
1832        }
1833    }
1834
1835    #[cfg(feature = "discovery")]
1836    fn pop_discovery_route(&mut self) -> bool {
1837        let Some((&side, _)) = self
1838            .discovery_routes
1839            .iter()
1840            .min_by_key(|(_, route)| route.last_seen_ms)
1841        else {
1842            return false;
1843        };
1844        self.discovery_routes.remove(&side);
1845        Self::queue_budget_warning("topology route evicted because MAX_QUEUE_BUDGET is full");
1846        true
1847    }
1848
1849    fn largest_shared_queue(&self) -> Option<RouterQueueKind> {
1850        let candidates = [
1851            (
1852                RouterQueueKind::Received,
1853                self.received_queue.bytes_used(),
1854                self.received_queue.len(),
1855            ),
1856            (
1857                RouterQueueKind::Transmit,
1858                self.transmit_queue.bytes_used(),
1859                self.transmit_queue.len(),
1860            ),
1861            (RouterQueueKind::Recent, 0, 0),
1862            (
1863                RouterQueueKind::ReliableRxBuffer,
1864                self.reliable_rx_buffered_bytes(),
1865                self.reliable_rx_buffer_len(),
1866            ),
1867            #[cfg(feature = "discovery")]
1868            (
1869                RouterQueueKind::Discovery,
1870                self.discovery_bytes_used(),
1871                self.discovery_routes.len(),
1872            ),
1873        ];
1874        candidates
1875            .into_iter()
1876            .filter(|(_, bytes, len)| *bytes > 0 && *len > 0)
1877            .max_by_key(|(kind, bytes, _)| {
1878                (
1879                    *bytes,
1880                    if *kind == RouterQueueKind::ReliableRxBuffer {
1881                        0
1882                    } else {
1883                        1
1884                    },
1885                )
1886            })
1887            .map(|(kind, _, _)| kind)
1888    }
1889
1890    fn make_shared_queue_room(
1891        &mut self,
1892        incoming_cost: usize,
1893        preferred: RouterQueueKind,
1894    ) -> TelemetryResult<()> {
1895        if incoming_cost > MAX_QUEUE_BUDGET {
1896            return Err(TelemetryError::PacketTooLarge(
1897                "Item exceeds maximum shared queue budget",
1898            ));
1899        }
1900
1901        while self.shared_queue_bytes_used().saturating_add(incoming_cost) > MAX_QUEUE_BUDGET {
1902            let victim = self.largest_shared_queue().unwrap_or(preferred);
1903            if victim == RouterQueueKind::Discovery {
1904                Self::queue_budget_warning("topology data is using the largest queue budget share");
1905            }
1906            if !self.pop_shared_queue_item(victim) && !self.pop_shared_queue_item(preferred) {
1907                return Err(TelemetryError::PacketTooLarge(
1908                    "Item exceeds maximum shared queue budget",
1909                ));
1910            }
1911        }
1912
1913        Ok(())
1914    }
1915
1916    #[inline]
1917    fn queue_budget_warning(msg: &str) {
1918        #[cfg(feature = "std")]
1919        eprintln!("sedsnet queue budget warning: {msg}");
1920        let _ = msg;
1921    }
1922
1923    #[cfg(feature = "discovery")]
1924    fn fit_discovery_budget(&mut self) {
1925        while self.shared_queue_bytes_used() > MAX_QUEUE_BUDGET {
1926            if !self.pop_discovery_route() {
1927                break;
1928            }
1929        }
1930    }
1931
1932    fn push_received(&mut self, item: RouterRxItem) -> TelemetryResult<()> {
1933        self.make_shared_queue_room(item.byte_cost(), RouterQueueKind::Received)?;
1934        self.received_queue
1935            .push_back_prioritized(item, |queued| queued.priority)
1936    }
1937
1938    fn push_transmit(&mut self, item: TxQueued) -> TelemetryResult<()> {
1939        self.make_shared_queue_room(item.byte_cost(), RouterQueueKind::Transmit)?;
1940        self.transmit_queue
1941            .push_back_prioritized(item, |queued| queued.priority)
1942    }
1943
1944    fn push_recent_rx(&mut self, id: u64) -> TelemetryResult<()> {
1945        while self.recent_rx.len() >= MAX_RECENT_RX_IDS {
1946            let _ = self.recent_rx.pop_front();
1947        }
1948        self.make_shared_queue_room(0, RouterQueueKind::Recent)?;
1949        self.recent_rx.push_back(id)
1950    }
1951
1952    fn buffer_reliable_rx(
1953        &mut self,
1954        side: RouterSideId,
1955        ty: DataType,
1956        seq: u32,
1957        bytes: Arc<[u8]>,
1958    ) -> TelemetryResult<()> {
1959        let key = Router::reliable_key(side, ty);
1960        if self
1961            .reliable_rx
1962            .get(&key)
1963            .is_some_and(|state| state.buffered.contains_key(&seq))
1964        {
1965            return Ok(());
1966        }
1967        let cost = size_of::<Arc<[u8]>>() + bytes.len();
1968        self.make_shared_queue_room(cost, RouterQueueKind::ReliableRxBuffer)?;
1969        let rx_state = self
1970            .reliable_rx
1971            .entry(key)
1972            .or_insert_with(|| ReliableRxState {
1973                expected_seq: 1,
1974                buffered: BTreeMap::new(),
1975            });
1976        if rx_state.buffered.len() >= RELIABLE_MAX_PENDING {
1977            let _ = rx_state.buffered.pop_first();
1978        }
1979        rx_state.buffered.insert(seq, bytes);
1980        Ok(())
1981    }
1982}
1983
1984/// Non-blocking RX queue used by ISR-safe `rx_queue*` APIs.
1985///
1986/// Uses a tiny atomic try-lock so enqueue never blocks. If contended, push/pop
1987/// operations return `TelemetryError::Io("rx queue busy")`.
1988struct IsrRxQueue {
1989    busy: AtomicBool,
1990    q: UnsafeCell<BoundedDeque<RouterRxItem>>,
1991}
1992
1993unsafe impl Send for IsrRxQueue {}
1994unsafe impl Sync for IsrRxQueue {}
1995
1996struct IsrRxQueueGuard<'a> {
1997    owner: &'a IsrRxQueue,
1998}
1999
2000impl Deref for IsrRxQueueGuard<'_> {
2001    type Target = BoundedDeque<RouterRxItem>;
2002
2003    #[inline]
2004    fn deref(&self) -> &Self::Target {
2005        unsafe { &*self.owner.q.get() }
2006    }
2007}
2008
2009impl DerefMut for IsrRxQueueGuard<'_> {
2010    #[inline]
2011    fn deref_mut(&mut self) -> &mut Self::Target {
2012        unsafe { &mut *self.owner.q.get() }
2013    }
2014}
2015
2016impl Drop for IsrRxQueueGuard<'_> {
2017    #[inline]
2018    fn drop(&mut self) {
2019        self.owner.busy.store(false, Ordering::Release);
2020    }
2021}
2022
2023impl IsrRxQueue {
2024    #[inline]
2025    fn new(max_bytes: usize, starting_bytes: usize, grow_mult: f64) -> Self {
2026        Self {
2027            busy: AtomicBool::new(false),
2028            q: UnsafeCell::new(BoundedDeque::new(max_bytes, starting_bytes, grow_mult)),
2029        }
2030    }
2031
2032    #[inline]
2033    fn try_lock(&self) -> TelemetryResult<IsrRxQueueGuard<'_>> {
2034        match self
2035            .busy
2036            .compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed)
2037        {
2038            Ok(_) => Ok(IsrRxQueueGuard { owner: self }),
2039            Err(_) => Err(TelemetryError::Io("rx queue busy")),
2040        }
2041    }
2042
2043    #[allow(dead_code)]
2044    #[inline]
2045    fn push_back(&self, item: RouterRxItem) -> TelemetryResult<()> {
2046        let mut g = self.try_lock()?;
2047        g.push_back(item)
2048    }
2049
2050    #[inline]
2051    fn push_back_prioritized(&self, item: RouterRxItem) -> TelemetryResult<()> {
2052        let mut g = self.try_lock()?;
2053        g.push_back_prioritized(item, |queued| queued.priority)
2054    }
2055
2056    #[inline]
2057    fn pop_front(&self) -> TelemetryResult<Option<RouterRxItem>> {
2058        let mut g = self.try_lock()?;
2059        Ok(g.pop_front())
2060    }
2061
2062    #[inline]
2063    fn clear(&self) -> TelemetryResult<()> {
2064        let mut g = self.try_lock()?;
2065        g.clear();
2066        Ok(())
2067    }
2068
2069    #[inline]
2070    fn snapshot(&self) -> Option<(usize, usize)> {
2071        let g = self.try_lock().ok()?;
2072        Some((g.len(), g.bytes_used()))
2073    }
2074}
2075
2076/// Telemetry Router for handling incoming and outgoing telemetry packets.
2077/// Supports queuing, processing, and dispatching to local endpoint handlers.
2078/// Thread-safe via internal locking.
2079pub struct Router {
2080    sender: RouterMutex<Arc<str>>,
2081    cfg: RouterConfig,
2082    state: RouterMutex<RouterInner>,
2083    isr_rx_queue: IsrRxQueue,
2084    side_tx_gate: ReentryGate,
2085    clock: Box<dyn Clock + Send + Sync>,
2086    #[cfg(feature = "timesync")]
2087    timesync: RouterMutex<TimeSyncRuntime>,
2088}
2089
2090#[cfg(feature = "timesync")]
2091#[derive(Debug, Clone)]
2092struct PendingTimeSyncRequest {
2093    seq: u64,
2094    t1_mono_ms: u64,
2095    source: String,
2096}
2097
2098#[cfg(feature = "timesync")]
2099#[derive(Debug, Clone)]
2100struct RemoteTimeSyncSource {
2101    priority: u64,
2102    last_sample_mono_ms: u64,
2103    sample_unix_ms: u64,
2104}
2105
2106#[cfg(feature = "timesync")]
2107#[derive(Debug, Clone)]
2108struct TimeSyncRuntime {
2109    cfg: Option<TimeSyncConfig>,
2110    tracker: Option<TimeSyncTracker>,
2111    clock: NetworkClock,
2112    disciplined_clock: SlewedNetworkClock,
2113    remote_sources: BTreeMap<String, RemoteTimeSyncSource>,
2114    next_seq: u64,
2115    next_announce_mono_ms: u64,
2116    next_request_mono_ms: u64,
2117    pending_request: Option<PendingTimeSyncRequest>,
2118}
2119
2120#[cfg(feature = "timesync")]
2121impl TimeSyncRuntime {
2122    fn new(cfg: Option<TimeSyncConfig>) -> Self {
2123        Self {
2124            tracker: cfg.map(TimeSyncTracker::new),
2125            cfg,
2126            clock: NetworkClock::default(),
2127            disciplined_clock: SlewedNetworkClock::new(
2128                cfg.map(|c| c.max_slew_ppm)
2129                    .unwrap_or(TimeSyncConfig::default().max_slew_ppm),
2130            ),
2131            remote_sources: BTreeMap::new(),
2132            next_seq: 1,
2133            next_announce_mono_ms: 0,
2134            next_request_mono_ms: 0,
2135            pending_request: None,
2136        }
2137    }
2138}
2139
2140enum RemoteSidePlan {
2141    Target(Vec<RouterSideId>),
2142}
2143
2144#[cfg(feature = "discovery")]
2145#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
2146struct DiscoveryCandidateMatch {
2147    side: RouterSideId,
2148    overlap: usize,
2149}
2150
2151impl Debug for Router {
2152    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
2153        let sender = self.sender();
2154        f.debug_struct("Router")
2155            .field("sender", &sender)
2156            .field("cfg", &self.cfg)
2157            .field("state", &"<mutex>")
2158            .field("clock", &"Clock")
2159            .finish()
2160    }
2161}
2162
2163/// Check if any of the provided endpoints require remote forwarding when
2164/// discovery is unavailable and routing falls back to local-vs-remote schema.
2165#[inline]
2166fn has_nonlocal_endpoint(eps: &[DataEndpoint], cfg: &RouterConfig) -> bool {
2167    eps.iter().copied().any(|ep| !cfg.is_local_endpoint(ep))
2168}
2169
2170#[inline]
2171fn force_remote_for_type(ty: DataType) -> bool {
2172    matches!(
2173        ty,
2174        DataType::ReliableAck
2175            | DataType::ReliablePartialAck
2176            | DataType::ReliablePacketRequest
2177            | DataType::P2pMessage
2178    ) || {
2179        #[cfg(feature = "timesync")]
2180        {
2181            matches!(
2182                ty,
2183                DataType::TimeSyncAnnounce | DataType::TimeSyncRequest | DataType::TimeSyncResponse
2184            )
2185        }
2186        #[cfg(not(feature = "timesync"))]
2187        {
2188            false
2189        }
2190    }
2191}
2192
2193#[inline]
2194fn is_internal_control_type(ty: DataType) -> bool {
2195    if matches!(
2196        ty,
2197        DataType::ReliableAck
2198            | DataType::ReliablePartialAck
2199            | DataType::ReliablePacketRequest
2200            | DataType::P2pMessage
2201    ) {
2202        return true;
2203    }
2204
2205    #[cfg(feature = "timesync")]
2206    if matches!(
2207        ty,
2208        DataType::TimeSyncAnnounce | DataType::TimeSyncRequest | DataType::TimeSyncResponse
2209    ) {
2210        return true;
2211    }
2212
2213    #[cfg(feature = "discovery")]
2214    if discovery::is_discovery_type(ty) {
2215        return true;
2216    }
2217
2218    let _ = ty;
2219    false
2220}
2221
2222/// Helper function to call a handler with retries and error handling.
2223fn with_retries<F>(
2224    this: &Router,
2225    dest: DataEndpoint,
2226    data: &RouterItem,
2227    pkt_for_ctx: Option<&Packet>,
2228    env_for_ctx: Option<&wire_format::TelemetryEnvelope>,
2229    called_from_queue: bool,
2230    run: F,
2231) -> TelemetryResult<()>
2232where
2233    F: Fn() -> TelemetryResult<()>,
2234{
2235    match this.retry_with_attempts(MAX_HANDLER_RETRIES, run) {
2236        Ok(((), attempts)) => {
2237            if attempts > 1 {
2238                let mut st = this.state.lock();
2239                st.total_handler_retries = st
2240                    .total_handler_retries
2241                    .saturating_add((attempts.saturating_sub(1)) as u64);
2242            }
2243            Ok(())
2244        }
2245        Err((e, attempts)) => {
2246            {
2247                let mut st = this.state.lock();
2248                st.total_handler_failures = st.total_handler_failures.saturating_add(1);
2249                st.total_handler_retries = st.total_handler_retries.saturating_add(attempts as u64);
2250            }
2251
2252            // If handler fails, remove from dedupe so it can be retried later if resent.
2253            this.remove_pkt_id(data);
2254
2255            // Emit error packet (to local endpoints).
2256            if let Some(pkt) = pkt_for_ctx {
2257                let _ = this.handle_callback_error(pkt, Some(dest), e, called_from_queue);
2258            } else if let Some(env) = env_for_ctx {
2259                let _ = this.handle_callback_error_from_env(env, Some(dest), e, called_from_queue);
2260            }
2261
2262            Err(TelemetryError::HandlerError("local handler failed"))
2263        }
2264    }
2265}
2266/// Router implementation
2267impl Router {
2268    const END_TO_END_ACK_SENDER: &'static str = "E2EACK";
2269    const END_TO_END_ACK_PREFIX: &'static str = "E2EACK:";
2270
2271    #[inline]
2272    fn side_ref(st: &RouterInner, side: RouterSideId) -> TelemetryResult<&RouterSide> {
2273        st.sides
2274            .get(side)
2275            .and_then(|side| side.as_ref())
2276            .ok_or(TelemetryError::HandlerError("router: invalid side id"))
2277    }
2278
2279    fn note_side_tx_success(
2280        &self,
2281        side: RouterSideId,
2282        ty: DataType,
2283        bytes: usize,
2284        relayed: bool,
2285        attempts: usize,
2286    ) {
2287        let mut st = self.state.lock();
2288        let entry = st.side_runtime_stats.entry(side).or_default();
2289        entry.note_tx(ty, bytes, relayed, attempts.saturating_sub(1));
2290    }
2291
2292    fn note_side_tx_failure(&self, side: RouterSideId, ty: DataType, attempts: usize) {
2293        let mut st = self.state.lock();
2294        st.total_handler_failures = st.total_handler_failures.saturating_add(1);
2295        st.total_handler_retries = st.total_handler_retries.saturating_add(attempts as u64);
2296        let entry = st.side_runtime_stats.entry(side).or_default();
2297        entry.note_tx_failure(ty, attempts);
2298    }
2299
2300    fn note_side_rx(&self, side: RouterSideId, ty: DataType, bytes: usize, relayed: bool) {
2301        let mut st = self.state.lock();
2302        let entry = st.side_runtime_stats.entry(side).or_default();
2303        entry.note_rx(ty, bytes, relayed);
2304    }
2305
2306    fn note_side_local_delivery(&self, side: RouterSideId, ty: DataType) {
2307        let mut st = self.state.lock();
2308        let entry = st.side_runtime_stats.entry(side).or_default();
2309        entry.note_local_delivery(ty);
2310    }
2311
2312    fn note_side_local_handler_failure(&self, side: RouterSideId, ty: DataType, retries: usize) {
2313        let mut st = self.state.lock();
2314        let entry = st.side_runtime_stats.entry(side).or_default();
2315        entry.note_local_handler_failure(ty, retries);
2316    }
2317
2318    fn cache_managed_variable_packet(
2319        &self,
2320        pkt: &Packet,
2321        notify_handlers: bool,
2322    ) -> TelemetryResult<()> {
2323        let handlers = {
2324            let mut st = self.state.lock();
2325            if !st
2326                .managed_variable_types
2327                .contains(&pkt.data_type().as_u32())
2328            {
2329                return Ok(());
2330            }
2331            let changed = st
2332                .managed_variable_latest
2333                .get(&pkt.data_type().as_u32())
2334                .is_none_or(|entry| entry.packet != *pkt);
2335            st.managed_variable_latest.insert(
2336                pkt.data_type().as_u32(),
2337                ManagedVariableCacheEntry {
2338                    packet: pkt.clone(),
2339                    cached_at_ms: self.clock.now_ms(),
2340                },
2341            );
2342            if notify_handlers && changed {
2343                st.network_variable_update_handlers
2344                    .get(&pkt.data_type().as_u32())
2345                    .cloned()
2346                    .unwrap_or_default()
2347            } else {
2348                Vec::new()
2349            }
2350        };
2351        for handler in handlers {
2352            (handler.handler)(pkt)?;
2353        }
2354        Ok(())
2355    }
2356
2357    fn remember_managed_variable_packet(&self, pkt: &Packet) -> TelemetryResult<()> {
2358        self.cache_managed_variable_packet(pkt, true)
2359    }
2360
2361    fn managed_variable_latest(&self, ty: DataType) -> Option<Packet> {
2362        let st = self.state.lock();
2363        st.managed_variable_latest
2364            .get(&ty.as_u32())
2365            .map(|entry| entry.packet.clone())
2366    }
2367
2368    fn managed_variable_latest_with_age(&self, ty: DataType) -> Option<(Packet, u64)> {
2369        let now_ms = self.clock.now_ms();
2370        let st = self.state.lock();
2371        st.managed_variable_latest.get(&ty.as_u32()).map(|entry| {
2372            (
2373                entry.packet.clone(),
2374                now_ms.saturating_sub(entry.cached_at_ms),
2375            )
2376        })
2377    }
2378
2379    fn is_managed_variable_type(&self, ty: DataType) -> bool {
2380        let st = self.state.lock();
2381        st.managed_variable_types.contains(&ty.as_u32())
2382    }
2383
2384    fn managed_variable_permissions_locked(
2385        st: &RouterInner,
2386        ty: DataType,
2387    ) -> NetworkVariablePermissions {
2388        st.managed_variable_permissions
2389            .get(&ty.as_u32())
2390            .copied()
2391            .unwrap_or(NetworkVariablePermissions::READ_WRITE)
2392    }
2393
2394    fn can_read_managed_variable(&self, ty: DataType) -> bool {
2395        let st = self.state.lock();
2396        Self::managed_variable_permissions_locked(&st, ty).read
2397    }
2398
2399    #[inline]
2400    fn ensure_side_ingress_enabled(&self, side: RouterSideId) -> TelemetryResult<()> {
2401        let st = self.state.lock();
2402        let side_ref = Self::side_ref(&st, side)?;
2403        if side_ref.opts.ingress_enabled {
2404            Ok(())
2405        } else {
2406            Err(TelemetryError::HandlerError(
2407                "router: ingress disabled for side id",
2408            ))
2409        }
2410    }
2411
2412    #[inline]
2413    fn default_route_enabled(&self, src: Option<RouterSideId>, dst: RouterSideId) -> bool {
2414        src != Some(dst)
2415    }
2416
2417    #[inline]
2418    fn route_allowed_locked(
2419        &self,
2420        st: &RouterInner,
2421        src: Option<RouterSideId>,
2422        ty: Option<DataType>,
2423        dst: RouterSideId,
2424    ) -> bool {
2425        let Some(dst_side) = st.sides.get(dst).and_then(|side| side.as_ref()) else {
2426            return false;
2427        };
2428        if !dst_side.opts.egress_enabled {
2429            return false;
2430        }
2431        if let Some(src_id) = src {
2432            let Some(src_side) = st.sides.get(src_id).and_then(|side| side.as_ref()) else {
2433                return false;
2434            };
2435            if !src_side.opts.ingress_enabled {
2436                return false;
2437            }
2438        }
2439        let base_allowed = st
2440            .route_overrides
2441            .get(&(src, dst))
2442            .copied()
2443            .unwrap_or_else(|| self.default_route_enabled(src, dst));
2444        if !base_allowed {
2445            return false;
2446        }
2447
2448        let Some(ty) = ty else {
2449            return true;
2450        };
2451        if st
2452            .typed_route_overrides
2453            .keys()
2454            .any(|(typed_src, typed_ty, _)| *typed_src == src && *typed_ty == ty.as_u32())
2455        {
2456            return st
2457                .typed_route_overrides
2458                .get(&(src, ty.as_u32(), dst))
2459                .copied()
2460                .unwrap_or(false);
2461        }
2462        true
2463    }
2464
2465    fn has_typed_route_overrides_locked(
2466        st: &RouterInner,
2467        src: Option<RouterSideId>,
2468        ty: DataType,
2469    ) -> bool {
2470        st.typed_route_overrides
2471            .keys()
2472            .any(|(typed_src, typed_ty, _)| *typed_src == src && *typed_ty == ty.as_u32())
2473    }
2474
2475    #[cfg(feature = "discovery")]
2476    fn endpoint_overlap_count<I>(reachable: I, eps: &[DataEndpoint]) -> usize
2477    where
2478        I: IntoIterator<Item = DataEndpoint>,
2479    {
2480        let mut overlap = 0usize;
2481        for ep in reachable {
2482            if eps.contains(&ep) {
2483                overlap = overlap.saturating_add(1);
2484            }
2485        }
2486        overlap
2487    }
2488
2489    #[inline]
2490    fn preferred_scoring_endpoints(
2491        &self,
2492        eps: &[DataEndpoint],
2493        prefer_nonlocal: bool,
2494    ) -> Vec<DataEndpoint> {
2495        if !prefer_nonlocal {
2496            return eps.to_vec();
2497        }
2498        let nonlocal: Vec<DataEndpoint> = eps
2499            .iter()
2500            .copied()
2501            .filter(|&ep| !self.cfg.is_local_endpoint(ep))
2502            .collect();
2503        if nonlocal.is_empty() {
2504            eps.to_vec()
2505        } else {
2506            nonlocal
2507        }
2508    }
2509
2510    #[cfg(feature = "discovery")]
2511    fn pop_next_queued_discovery_rx_item(&self) -> TelemetryResult<Option<RouterRxItem>> {
2512        {
2513            let mut isr_rx = self.isr_rx_queue.try_lock()?;
2514            let idx = isr_rx.iter().position(Self::queued_rx_item_is_discovery);
2515            if let Some(idx) = idx {
2516                return Ok(isr_rx.remove_pos(idx));
2517            }
2518        }
2519
2520        let mut st = self.state.lock();
2521        let idx = st
2522            .received_queue
2523            .iter()
2524            .position(Self::queued_rx_item_is_discovery);
2525        if let Some(idx) = idx {
2526            return Ok(st.received_queue.remove_pos(idx));
2527        }
2528        Ok(None)
2529    }
2530
2531    #[cfg(feature = "discovery")]
2532    fn queued_rx_item_is_discovery(item: &RouterRxItem) -> bool {
2533        match &item.data {
2534            RouterItem::Packet(pkt) => discovery::is_discovery_type(pkt.data_type()),
2535            RouterItem::Packed(bytes) => wire_format::peek_envelope(bytes.as_ref())
2536                .map(|env| discovery::is_discovery_type(env.ty))
2537                .unwrap_or(false),
2538        }
2539    }
2540
2541    #[cfg(feature = "discovery")]
2542    fn drain_queued_discovery_rx_before_tx(&self) -> TelemetryResult<bool> {
2543        let mut did_any = false;
2544        while let Some(item) = self.pop_next_queued_discovery_rx_item()? {
2545            self.process_rx_queue_item(item)?;
2546            did_any = true;
2547        }
2548        Ok(did_any)
2549    }
2550
2551    fn eligible_side_ids_locked(
2552        &self,
2553        st: &RouterInner,
2554        src: Option<RouterSideId>,
2555        ty: Option<DataType>,
2556        restrict_link_local: bool,
2557    ) -> Vec<RouterSideId> {
2558        st.sides
2559            .iter()
2560            .enumerate()
2561            .filter_map(|(side_id, side)| {
2562                let side = side.as_ref()?;
2563                if restrict_link_local && !side.opts.link_local_enabled {
2564                    return None;
2565                }
2566                if self.route_allowed_locked(st, src, ty, side_id) {
2567                    Some(side_id)
2568                } else {
2569                    None
2570                }
2571            })
2572            .collect()
2573    }
2574
2575    fn apply_route_selection_locked(
2576        &self,
2577        st: &mut RouterInner,
2578        src: Option<RouterSideId>,
2579        mut sides: Vec<RouterSideId>,
2580        origin: RouteSelectionOrigin,
2581    ) -> Vec<RouterSideId> {
2582        if sides.len() <= 1 {
2583            return sides;
2584        }
2585
2586        let selection_mode = st.source_route_modes.get(&src).copied();
2587        if selection_mode.is_none() && origin == RouteSelectionOrigin::Discovered {
2588            return self.apply_adaptive_discovery_selection_locked(st, src, sides);
2589        }
2590
2591        match selection_mode.unwrap_or(RouteSelectionMode::Fanout) {
2592            RouteSelectionMode::Fanout => sides,
2593            RouteSelectionMode::Weighted => {
2594                sides.sort_unstable();
2595                let total_weight = sides.iter().fold(0_u64, |acc, side| {
2596                    acc + u64::from(st.route_weights.get(&(src, *side)).copied().unwrap_or(1))
2597                });
2598                if total_weight == 0 {
2599                    return Vec::new();
2600                }
2601                let cursor = st.route_selection_cursors.entry(src).or_insert(0);
2602                let pick = *cursor % total_weight;
2603                *cursor = cursor.wrapping_add(1);
2604                let mut remaining = pick;
2605                for side in sides {
2606                    let weight =
2607                        u64::from(st.route_weights.get(&(src, side)).copied().unwrap_or(1));
2608                    if remaining < weight {
2609                        return vec![side];
2610                    }
2611                    remaining -= weight;
2612                }
2613                Vec::new()
2614            }
2615            RouteSelectionMode::Failover => {
2616                sides.sort_by_key(|side| {
2617                    (
2618                        st.route_priorities.get(&(src, *side)).copied().unwrap_or(0),
2619                        *side,
2620                    )
2621                });
2622                sides.truncate(1);
2623                sides
2624            }
2625        }
2626    }
2627
2628    fn apply_adaptive_discovery_selection_locked(
2629        &self,
2630        st: &mut RouterInner,
2631        src: Option<RouterSideId>,
2632        mut sides: Vec<RouterSideId>,
2633    ) -> Vec<RouterSideId> {
2634        sides.sort_unstable();
2635        let mut unmeasured: Vec<_> = sides
2636            .iter()
2637            .copied()
2638            .filter(|side| !st.adaptive_route_stats.contains_key(side))
2639            .collect();
2640        if !unmeasured.is_empty() {
2641            let cursor = st.route_selection_cursors.entry(src).or_insert(0);
2642            let pick = (*cursor as usize) % unmeasured.len();
2643            *cursor = cursor.wrapping_add(1);
2644            return vec![unmeasured.swap_remove(pick)];
2645        }
2646
2647        let now_ms = self.clock.now_ms();
2648        let total_weight = sides.iter().fold(0_u64, |acc, side| {
2649            acc + st
2650                .adaptive_route_stats
2651                .get(side)
2652                .map(|stats| stats.weight(now_ms))
2653                .unwrap_or(1)
2654        });
2655        if total_weight == 0 {
2656            sides.truncate(1);
2657            return sides;
2658        }
2659
2660        let cursor = st.route_selection_cursors.entry(src).or_insert(0);
2661        let pick = *cursor % total_weight;
2662        *cursor = cursor.wrapping_add(1);
2663        let mut remaining = pick;
2664        for side in sides {
2665            let weight = st
2666                .adaptive_route_stats
2667                .get(&side)
2668                .map(|stats| stats.weight(now_ms))
2669                .unwrap_or(1);
2670            if remaining < weight {
2671                return vec![side];
2672            }
2673            remaining -= weight;
2674        }
2675        Vec::new()
2676    }
2677
2678    fn record_side_tx_sample(
2679        &self,
2680        side: RouterSideId,
2681        bytes: usize,
2682        started_ms: u64,
2683        ended_ms: u64,
2684    ) {
2685        let sample_ms = ended_ms.saturating_sub(started_ms).max(1);
2686        let sample_bps = ((bytes as u128).saturating_mul(1000) / u128::from(sample_ms))
2687            .min(u128::from(u64::MAX)) as u64;
2688        let mut st = self.state.lock();
2689        st.adaptive_route_stats
2690            .entry(side)
2691            .or_default()
2692            .observe(bytes, sample_bps, ended_ms);
2693    }
2694
2695    /// Seed adaptive route selection with a transport-measured link probe.
2696    ///
2697    /// Call this after a side-specific bring-up probe, or whenever the transport already knows the
2698    /// duration for a frame. The router does not emit synthetic probe frames by itself.
2699    pub fn note_side_link_probe_sample(
2700        &self,
2701        side: RouterSideId,
2702        bytes: usize,
2703        duration_ms: u64,
2704    ) -> TelemetryResult<()> {
2705        {
2706            let st = self.state.lock();
2707            let _ = Self::side_ref(&st, side).map_err(|_| TelemetryError::BadArg)?;
2708        }
2709        let ended_ms = self.clock.now_ms();
2710        self.record_side_tx_sample(side, bytes, ended_ms.saturating_sub(duration_ms), ended_ms);
2711        Ok(())
2712    }
2713
2714    fn router_item_wire_len(data: &RouterItem) -> TelemetryResult<usize> {
2715        match data {
2716            RouterItem::Packet(pkt) => Ok(wire_format::pack_packet(pkt).len()),
2717            RouterItem::Packed(bytes) => Ok(bytes.len()),
2718        }
2719    }
2720
2721    /// Extract the logical packet ID targeted by an end-to-end reliable ACK item.
2722    ///
2723    /// Router TX and replay queues can carry either decoded `Packet` values or
2724    /// packed frames. This helper normalizes both forms so ACK-routing code
2725    /// can reason about them uniformly.
2726    ///
2727    /// Only router-generated end-to-end `ReliableAck` packets qualify here.
2728    /// Ordinary application traffic and hop-level reliable control frames return
2729    /// `Ok(None)`.
2730    #[inline]
2731    fn reliable_control_target_packet_id(data: &RouterItem) -> TelemetryResult<Option<u64>> {
2732        match data {
2733            RouterItem::Packet(pkt) => {
2734                if pkt.data_type() != DataType::ReliableAck
2735                    || !Self::is_end_to_end_ack_sender(pkt.sender())
2736                {
2737                    return Ok(None);
2738                }
2739                Self::decode_end_to_end_reliable_ack(pkt.payload()).map(Some)
2740            }
2741            RouterItem::Packed(bytes) => {
2742                if wire_format::peek_frame_info(bytes.as_ref())
2743                    .ok()
2744                    .is_some_and(|frame| frame.ack_only())
2745                {
2746                    return Ok(None);
2747                }
2748                let pkt = wire_format::unpack_packet(bytes.as_ref())?;
2749                if pkt.data_type() != DataType::ReliableAck
2750                    || !Self::is_end_to_end_ack_sender(pkt.sender())
2751                {
2752                    return Ok(None);
2753                }
2754                Self::decode_end_to_end_reliable_ack(pkt.payload()).map(Some)
2755            }
2756        }
2757    }
2758
2759    fn decode_end_to_end_reliable_ack(payload: &[u8]) -> TelemetryResult<u64> {
2760        if payload.len() != 8 {
2761            return Err(TelemetryError::Unpack("bad reliable e2e ack payload"));
2762        }
2763        Ok(u64::from_le_bytes(payload[0..8].try_into().unwrap()))
2764    }
2765
2766    #[inline]
2767    fn sender_hash(sender: &str) -> u64 {
2768        hash_bytes_u64(0x517C_C1B7_2722_0A95, sender.as_bytes())
2769    }
2770
2771    #[inline]
2772    fn fallback_address_for_hostname(hostname: &str) -> NodeAddress {
2773        let hash = Self::sender_hash(hostname);
2774        let mut address = (hash as u32) ^ ((hash >> 32) as u32);
2775        if address == 0 {
2776            address = 1;
2777        }
2778        address
2779    }
2780
2781    fn address_mode_from_code(mode: u8, requested: NodeAddress) -> AddressAssignmentMode {
2782        match mode {
2783            2 => AddressAssignmentMode::Static(if requested == 0 { 1 } else { requested }),
2784            1 => AddressAssignmentMode::Requested(if requested == 0 { 1 } else { requested }),
2785            _ => AddressAssignmentMode::Dynamic,
2786        }
2787    }
2788
2789    fn address_winner_pref(entry: &AddressBookEntry) -> (u8, core::cmp::Reverse<u64>, u64) {
2790        let rank = match entry.mode {
2791            AddressAssignmentMode::Static(_) => 2,
2792            AddressAssignmentMode::Requested(_) => 1,
2793            AddressAssignmentMode::Dynamic => 0,
2794        };
2795        (
2796            rank,
2797            core::cmp::Reverse(entry.birth_ms),
2798            u64::MAX - entry.owner_hash,
2799        )
2800    }
2801
2802    fn identity_winner_pref(entry: &AddressBookEntry) -> (core::cmp::Reverse<u64>, u64) {
2803        (
2804            core::cmp::Reverse(entry.birth_ms),
2805            u64::MAX - entry.owner_hash,
2806        )
2807    }
2808
2809    fn allocate_free_address_locked(st: &RouterInner, seed: NodeAddress) -> NodeAddress {
2810        let mut candidate = if seed == 0 { 1 } else { seed };
2811        for _ in 0..u32::MAX {
2812            if candidate != 0 && !st.address_by_value.contains_key(&candidate) {
2813                return candidate;
2814            }
2815            candidate = candidate.wrapping_add(1);
2816            if candidate == 0 {
2817                candidate = 1;
2818            }
2819        }
2820        1
2821    }
2822
2823    fn unique_hostname_locked(st: &RouterInner, base: &str, owner_hash: u64) -> String {
2824        if !st.address_book.contains_key(base) {
2825            return base.to_string();
2826        }
2827        let stem = if base.is_empty() { "node" } else { base };
2828        let mut candidate = format!("{stem}-{owner_hash:08x}");
2829        let mut suffix = 1u32;
2830        while st.address_book.contains_key(&candidate) {
2831            candidate = format!("{stem}-{owner_hash:08x}-{suffix}");
2832            suffix = suffix.saturating_add(1);
2833        }
2834        candidate
2835    }
2836
2837    fn update_local_identity_locked(
2838        &self,
2839        st: &mut RouterInner,
2840        mut local: AddressBookEntry,
2841        reason: AddressChangeReason,
2842    ) -> AddressChange {
2843        let old = st.local_address.clone();
2844        st.address_book.remove(old.hostname.as_ref());
2845        st.address_by_value.remove(&old.address);
2846        local.last_seen_ms = self.clock.now_ms();
2847        st.address_by_value
2848            .insert(local.address, local.hostname.to_string());
2849        st.address_book
2850            .insert(local.hostname.to_string(), local.clone());
2851        st.local_address = local.clone();
2852        *self.sender.lock() = local.hostname.clone();
2853        #[cfg(feature = "discovery")]
2854        Self::note_discovery_topology_change_locked(st, self.clock.now_ms());
2855        AddressChange {
2856            old_hostname: old.hostname,
2857            new_hostname: local.hostname,
2858            old_address: old.address,
2859            new_address: local.address,
2860            reason,
2861        }
2862    }
2863
2864    fn notify_address_change(&self, change: AddressChange) -> TelemetryResult<()> {
2865        for handler in self.cfg.address_change_handlers.iter() {
2866            (handler.handler)(change.clone())?;
2867        }
2868        Ok(())
2869    }
2870
2871    #[cfg(feature = "discovery")]
2872    fn local_address_advertisement(
2873        &self,
2874        reachable_endpoints: Vec<DataEndpoint>,
2875        reachable_timesync_sources: Vec<String>,
2876        link_capabilities: discovery::LinkCapabilities,
2877        state: u8,
2878    ) -> discovery::AddressAdvertisement {
2879        let st = self.state.lock();
2880        discovery::AddressAdvertisement {
2881            hostname: st.local_address.hostname.to_string(),
2882            address: st.local_address.address,
2883            requested_address: st.local_address.requested_address,
2884            mode: st.local_address.mode.mode_code(),
2885            state,
2886            birth_ms: st.local_address.birth_ms,
2887            owner_hash: st.local_address.owner_hash,
2888            reachable_endpoints,
2889            reachable_timesync_sources,
2890            link_capabilities,
2891        }
2892    }
2893
2894    #[cfg(feature = "discovery")]
2895    fn ingest_address_advertisement(
2896        &self,
2897        ad: discovery::AddressAdvertisement,
2898    ) -> TelemetryResult<bool> {
2899        let now_ms = self.clock.now_ms();
2900        let mut remote = AddressBookEntry {
2901            hostname: Arc::from(ad.hostname.as_str()),
2902            address: ad.address,
2903            requested_address: ad.requested_address,
2904            mode: Self::address_mode_from_code(ad.mode, ad.requested_address),
2905            birth_ms: ad.birth_ms,
2906            owner_hash: ad.owner_hash,
2907            last_seen_ms: now_ms,
2908        };
2909        let mut change = None;
2910        let mut changed = false;
2911        {
2912            let mut st = self.state.lock();
2913            let local = st.local_address.clone();
2914            let hostname_conflict =
2915                remote.hostname == local.hostname && remote.owner_hash != local.owner_hash;
2916            let address_conflict =
2917                remote.address == local.address && remote.owner_hash != local.owner_hash;
2918
2919            if hostname_conflict
2920                && Self::identity_winner_pref(&remote) > Self::identity_winner_pref(&local)
2921            {
2922                let mut next = local.clone();
2923                next.hostname = Arc::from(Self::unique_hostname_locked(
2924                    &st,
2925                    local.hostname.as_ref(),
2926                    local.owner_hash,
2927                ));
2928                change = Some(self.update_local_identity_locked(
2929                    &mut st,
2930                    next,
2931                    AddressChangeReason::HostnameConflict,
2932                ));
2933                changed = true;
2934            } else if address_conflict
2935                && Self::address_winner_pref(&remote) > Self::address_winner_pref(&local)
2936            {
2937                let mut next = local.clone();
2938                let seed = next
2939                    .requested_address
2940                    .max(Self::fallback_address_for_hostname(next.hostname.as_ref()));
2941                next.address = Self::allocate_free_address_locked(&st, seed);
2942                let reason = match next.mode {
2943                    AddressAssignmentMode::Static(_) => AddressChangeReason::StaticConflict,
2944                    AddressAssignmentMode::Requested(_) => AddressChangeReason::RequestedConflict,
2945                    AddressAssignmentMode::Dynamic => AddressChangeReason::DynamicConflict,
2946                };
2947                change = Some(self.update_local_identity_locked(&mut st, next, reason));
2948                changed = true;
2949            } else if hostname_conflict {
2950                remote.hostname = Arc::from(Self::unique_hostname_locked(
2951                    &st,
2952                    remote.hostname.as_ref(),
2953                    remote.owner_hash,
2954                ));
2955                changed = true;
2956            } else if address_conflict {
2957                let seed = remote
2958                    .requested_address
2959                    .max(Self::fallback_address_for_hostname(
2960                        remote.hostname.as_ref(),
2961                    ));
2962                remote.address = Self::allocate_free_address_locked(&st, seed);
2963                changed = true;
2964            }
2965
2966            st.address_book.remove(remote.hostname.as_ref());
2967            st.address_by_value.remove(&remote.address);
2968            st.address_by_value
2969                .insert(remote.address, remote.hostname.to_string());
2970            st.address_book
2971                .insert(remote.hostname.to_string(), remote.clone());
2972        }
2973        if let Some(change) = change {
2974            self.notify_address_change(change)?;
2975        }
2976        Ok(changed)
2977    }
2978
2979    #[inline]
2980    fn is_end_to_end_ack_sender(sender: &str) -> bool {
2981        sender == Self::END_TO_END_ACK_SENDER || sender.starts_with(Self::END_TO_END_ACK_PREFIX)
2982    }
2983
2984    fn decode_end_to_end_ack_sender_hash(sender: &str) -> Option<u64> {
2985        if let Some(ack_sender) = sender.strip_prefix(Self::END_TO_END_ACK_PREFIX)
2986            && !ack_sender.is_empty()
2987        {
2988            return Some(Self::sender_hash(ack_sender));
2989        }
2990        None
2991    }
2992
2993    fn encode_end_to_end_ack_sender(&self) -> String {
2994        let sender = self.sender_arc();
2995        format!("{}{}", Self::END_TO_END_ACK_PREFIX, sender)
2996    }
2997
2998    #[cfg(feature = "discovery")]
2999    fn is_end_to_end_destination_sender(sender: &str) -> bool {
3000        sender != "RELAY" && !Self::is_end_to_end_ack_sender(sender)
3001    }
3002
3003    fn encode_end_to_end_reliable_ack(packet_id: u64) -> Arc<[u8]> {
3004        let mut payload = Vec::with_capacity(8);
3005        payload.extend_from_slice(&packet_id.to_le_bytes());
3006        Arc::from(payload)
3007    }
3008
3009    fn encode_p2p_payload(
3010        src_hostname: &str,
3011        src_address: NodeAddress,
3012        src_port: P2pPort,
3013        dst_port: P2pPort,
3014        payload: &[u8],
3015    ) -> TelemetryResult<Arc<[u8]>> {
3016        let host_len = u16::try_from(src_hostname.len())
3017            .map_err(|_| TelemetryError::Pack("p2p hostname too long"))?;
3018        let payload_len = u32::try_from(payload.len())
3019            .map_err(|_| TelemetryError::Pack("p2p payload too long"))?;
3020        let mut out = Vec::with_capacity(
3021            15usize
3022                .saturating_add(src_hostname.len())
3023                .saturating_add(payload.len()),
3024        );
3025        out.push(1);
3026        out.extend_from_slice(&dst_port.to_le_bytes());
3027        out.extend_from_slice(&src_port.to_le_bytes());
3028        out.extend_from_slice(&src_address.to_le_bytes());
3029        out.extend_from_slice(&host_len.to_le_bytes());
3030        out.extend_from_slice(&payload_len.to_le_bytes());
3031        out.extend_from_slice(src_hostname.as_bytes());
3032        out.extend_from_slice(payload);
3033        Ok(out.into())
3034    }
3035
3036    fn decode_p2p_payload<'a>(payload: &'a [u8]) -> TelemetryResult<P2pDecoded<'a>> {
3037        if payload.len() < 15 {
3038            return Err(TelemetryError::Unpack("p2p frame short"));
3039        }
3040        if payload[0] != 1 {
3041            return Err(TelemetryError::Unpack("p2p frame version"));
3042        }
3043        let destination_port =
3044            u16::from_le_bytes(payload[1..3].try_into().expect("2-byte dst port"));
3045        let source_port = u16::from_le_bytes(payload[3..5].try_into().expect("2-byte src port"));
3046        let source_address = u32::from_le_bytes(payload[5..9].try_into().expect("4-byte address"));
3047        let host_len =
3048            u16::from_le_bytes(payload[9..11].try_into().expect("2-byte host len")) as usize;
3049        let body_len =
3050            u32::from_le_bytes(payload[11..15].try_into().expect("4-byte body len")) as usize;
3051        let host_start = 15usize;
3052        let host_end = host_start.saturating_add(host_len);
3053        let body_end = host_end.saturating_add(body_len);
3054        if host_end > payload.len() || body_end != payload.len() {
3055            return Err(TelemetryError::Unpack("p2p frame length"));
3056        }
3057        let source_hostname = core::str::from_utf8(&payload[host_start..host_end])
3058            .map_err(|_| TelemetryError::Unpack("p2p hostname utf8"))?;
3059        Ok(P2pDecoded {
3060            source_hostname,
3061            source_address,
3062            source_port,
3063            destination_port,
3064            payload: &payload[host_end..body_end],
3065        })
3066    }
3067
3068    fn encode_p2p_stream_payload(
3069        flags: u8,
3070        source_stream_id: P2pStreamId,
3071        destination_stream_id: P2pStreamId,
3072        sequence: u32,
3073        payload: &[u8],
3074    ) -> TelemetryResult<Arc<[u8]>> {
3075        let payload_len = u32::try_from(payload.len())
3076            .map_err(|_| TelemetryError::Pack("p2p stream payload too long"))?;
3077        let mut out = Vec::with_capacity(22usize.saturating_add(payload.len()));
3078        out.extend_from_slice(&P2P_STREAM_MAGIC);
3079        out.push(P2P_STREAM_VERSION);
3080        out.push(flags);
3081        out.extend_from_slice(&source_stream_id.to_le_bytes());
3082        out.extend_from_slice(&destination_stream_id.to_le_bytes());
3083        out.extend_from_slice(&sequence.to_le_bytes());
3084        out.extend_from_slice(&payload_len.to_le_bytes());
3085        out.extend_from_slice(payload);
3086        Ok(out.into())
3087    }
3088
3089    fn decode_p2p_stream_payload<'a>(
3090        payload: &'a [u8],
3091    ) -> TelemetryResult<Option<P2pStreamDecoded<'a>>> {
3092        if !payload.starts_with(&P2P_STREAM_MAGIC) {
3093            return Ok(None);
3094        }
3095        if payload.len() < 22 {
3096            return Err(TelemetryError::Unpack("p2p stream frame short"));
3097        }
3098        if payload[4] != P2P_STREAM_VERSION {
3099            return Err(TelemetryError::Unpack("p2p stream frame version"));
3100        }
3101        let flags = payload[5];
3102        let source_stream_id =
3103            u32::from_le_bytes(payload[6..10].try_into().expect("stream source id"));
3104        let destination_stream_id =
3105            u32::from_le_bytes(payload[10..14].try_into().expect("stream destination id"));
3106        let sequence = u32::from_le_bytes(payload[14..18].try_into().expect("stream sequence"));
3107        let body_len =
3108            u32::from_le_bytes(payload[18..22].try_into().expect("stream body len")) as usize;
3109        let body_end = 22usize.saturating_add(body_len);
3110        if body_end != payload.len() {
3111            return Err(TelemetryError::Unpack("p2p stream frame length"));
3112        }
3113        Ok(Some(P2pStreamDecoded {
3114            flags,
3115            source_stream_id,
3116            destination_stream_id,
3117            sequence,
3118            payload: &payload[22..],
3119        }))
3120    }
3121
3122    fn allocate_p2p_stream_id_locked(st: &mut RouterInner) -> P2pStreamId {
3123        for _ in 0..u32::MAX {
3124            let id = st.next_p2p_stream_id.max(1);
3125            st.next_p2p_stream_id = st.next_p2p_stream_id.wrapping_add(1).max(1);
3126            if !st.p2p_stream_sessions.contains_key(&id) {
3127                return id;
3128            }
3129        }
3130        0
3131    }
3132
3133    fn dispatch_p2p_packet(&self, pkt: &Packet) -> TelemetryResult<()> {
3134        if pkt.data_type() != DataType::P2pMessage {
3135            return Ok(());
3136        }
3137        let decoded = Self::decode_p2p_payload(pkt.payload())?;
3138        if let Some(stream) = Self::decode_p2p_stream_payload(decoded.payload)? {
3139            return self.dispatch_p2p_stream_frame(&decoded, &stream);
3140        }
3141        let handlers = {
3142            let st = self.state.lock();
3143            st.p2p_port_handlers
3144                .get(&decoded.destination_port)
3145                .cloned()
3146                .unwrap_or_default()
3147        };
3148        for handler in handlers {
3149            (handler.handler)(P2pMessage {
3150                source_hostname: decoded.source_hostname,
3151                source_address: decoded.source_address,
3152                source_port: decoded.source_port,
3153                destination_port: decoded.destination_port,
3154                payload: decoded.payload,
3155            })?;
3156        }
3157        Ok(())
3158    }
3159
3160    fn dispatch_p2p_stream_frame(
3161        &self,
3162        msg: &P2pDecoded<'_>,
3163        frame: &P2pStreamDecoded<'_>,
3164    ) -> TelemetryResult<()> {
3165        let mut events: Vec<PendingP2pStreamEvent> = Vec::new();
3166        let mut reply: Option<(AddressBookEntry, P2pPort, P2pPort, Arc<[u8]>)> = None;
3167        {
3168            let mut st = self.state.lock();
3169            if frame.flags & P2P_STREAM_SYN != 0 && frame.flags & P2P_STREAM_ACK == 0 {
3170                let peer_hostname: Arc<str> = Arc::from(msg.source_hostname);
3171                let existing_id = st.p2p_stream_sessions.iter().find_map(|(id, session)| {
3172                    (session.peer_stream_id == frame.source_stream_id
3173                        && session.local_port == msg.destination_port
3174                        && session.peer_port == msg.source_port
3175                        && session.peer_address == msg.source_address
3176                        && session.peer_hostname.as_ref() == msg.source_hostname)
3177                        .then_some(*id)
3178                });
3179                let local_id = if let Some(local_id) = existing_id {
3180                    local_id
3181                } else {
3182                    let local_id = Self::allocate_p2p_stream_id_locked(&mut st);
3183                    if local_id == 0 {
3184                        return Err(TelemetryError::Io("p2p stream id exhausted"));
3185                    }
3186                    st.p2p_stream_sessions.insert(
3187                        local_id,
3188                        P2pStreamSession {
3189                            peer_hostname: peer_hostname.clone(),
3190                            peer_address: msg.source_address,
3191                            local_port: msg.destination_port,
3192                            peer_port: msg.source_port,
3193                            peer_stream_id: frame.source_stream_id,
3194                            next_sequence: 1,
3195                            connected: true,
3196                        },
3197                    );
3198                    let handlers = st
3199                        .p2p_stream_handlers
3200                        .get(&msg.destination_port)
3201                        .cloned()
3202                        .unwrap_or_default();
3203                    events.push(PendingP2pStreamEvent {
3204                        handlers,
3205                        kind: P2pStreamEventKind::Accepted,
3206                        stream_id: local_id,
3207                        peer_stream_id: frame.source_stream_id,
3208                        peer_hostname: peer_hostname.clone(),
3209                        peer_address: msg.source_address,
3210                        local_port: msg.destination_port,
3211                        peer_port: msg.source_port,
3212                    });
3213                    local_id
3214                };
3215                let dst =
3216                    st.address_book
3217                        .get(msg.source_hostname)
3218                        .cloned()
3219                        .unwrap_or(AddressBookEntry {
3220                            hostname: peer_hostname,
3221                            address: msg.source_address,
3222                            requested_address: msg.source_address,
3223                            mode: AddressAssignmentMode::Dynamic,
3224                            birth_ms: self.clock.now_ms(),
3225                            owner_hash: Self::sender_hash(msg.source_hostname),
3226                            last_seen_ms: self.clock.now_ms(),
3227                        });
3228                let payload = Self::encode_p2p_stream_payload(
3229                    P2P_STREAM_SYN | P2P_STREAM_ACK,
3230                    local_id,
3231                    frame.source_stream_id,
3232                    0,
3233                    &[],
3234                )?;
3235                reply = Some((dst, msg.source_port, msg.destination_port, payload));
3236            } else if frame.flags & P2P_STREAM_SYN != 0 && frame.flags & P2P_STREAM_ACK != 0 {
3237                if let Some(session) = st.p2p_stream_sessions.get_mut(&frame.destination_stream_id)
3238                {
3239                    session.peer_stream_id = frame.source_stream_id;
3240                    session.connected = true;
3241                    let peer_hostname = session.peer_hostname.clone();
3242                    let peer_address = session.peer_address;
3243                    let local_port = session.local_port;
3244                    let peer_port = session.peer_port;
3245                    let handlers = st
3246                        .p2p_stream_handlers
3247                        .get(&local_port)
3248                        .cloned()
3249                        .unwrap_or_default();
3250                    events.push(PendingP2pStreamEvent {
3251                        handlers,
3252                        kind: P2pStreamEventKind::Connected,
3253                        stream_id: frame.destination_stream_id,
3254                        peer_stream_id: frame.source_stream_id,
3255                        peer_hostname,
3256                        peer_address,
3257                        local_port,
3258                        peer_port,
3259                    });
3260                }
3261            } else if frame.flags & (P2P_STREAM_FIN | P2P_STREAM_RST | P2P_STREAM_DATA) != 0 {
3262                let kind = if frame.flags & P2P_STREAM_RST != 0 {
3263                    P2pStreamEventKind::Reset
3264                } else if frame.flags & P2P_STREAM_FIN != 0 {
3265                    P2pStreamEventKind::Closed
3266                } else {
3267                    P2pStreamEventKind::Data
3268                };
3269                let session_id = if frame.destination_stream_id != 0 {
3270                    Some(frame.destination_stream_id)
3271                } else {
3272                    st.p2p_stream_sessions.iter().find_map(|(id, session)| {
3273                        (session.peer_stream_id == frame.source_stream_id
3274                            && session.local_port == msg.destination_port
3275                            && session.peer_port == msg.source_port
3276                            && session.peer_address == msg.source_address
3277                            && session.peer_hostname.as_ref() == msg.source_hostname)
3278                            .then_some(*id)
3279                    })
3280                };
3281                if let Some(session_id) = session_id
3282                    && let Some(session) = st.p2p_stream_sessions.get(&session_id)
3283                {
3284                    let handlers = st
3285                        .p2p_stream_handlers
3286                        .get(&session.local_port)
3287                        .cloned()
3288                        .unwrap_or_default();
3289                    events.push(PendingP2pStreamEvent {
3290                        handlers,
3291                        kind,
3292                        stream_id: session_id,
3293                        peer_stream_id: frame.source_stream_id,
3294                        peer_hostname: session.peer_hostname.clone(),
3295                        peer_address: session.peer_address,
3296                        local_port: session.local_port,
3297                        peer_port: session.peer_port,
3298                    });
3299                    if matches!(kind, P2pStreamEventKind::Closed | P2pStreamEventKind::Reset) {
3300                        st.p2p_stream_sessions.remove(&session_id);
3301                    }
3302                }
3303            }
3304        }
3305        if let Some((dst, dst_port, src_port, payload)) = reply {
3306            self.send_p2p_to_entry(dst, dst_port, src_port, &payload)?;
3307        }
3308        for pending in events {
3309            for handler in pending.handlers {
3310                (handler.handler)(P2pStreamEvent {
3311                    kind: pending.kind,
3312                    stream_id: pending.stream_id,
3313                    peer_stream_id: pending.peer_stream_id,
3314                    sequence: frame.sequence,
3315                    peer_hostname: pending.peer_hostname.as_ref(),
3316                    peer_address: pending.peer_address,
3317                    local_port: pending.local_port,
3318                    peer_port: pending.peer_port,
3319                    payload: frame.payload,
3320                })?;
3321            }
3322        }
3323        Ok(())
3324    }
3325
3326    /// Record the side that most recently led toward `packet_id`.
3327    ///
3328    /// End-to-end reliable acknowledgements should return only toward the
3329    /// source-side that originated the data flow, not be flooded everywhere.
3330    /// This helper stores that learned return path in a bounded LRU-like cache.
3331    fn note_reliable_return_route(&self, side: RouterSideId, packet_id: u64) {
3332        let mut st = self.state.lock();
3333        Self::remember_reliable_return_route_locked(&mut st, packet_id);
3334        st.reliable_return_routes
3335            .insert(packet_id, ReliableReturnRouteState { side });
3336    }
3337
3338    /// Ensure `packet_id` is retained in the bounded reliable return-route cache.
3339    ///
3340    /// Existing entries are refreshed to the back of the order list. When the
3341    /// cache is full, the oldest learned route is evicted before inserting the
3342    /// new one.
3343    fn remember_reliable_return_route_locked(st: &mut RouterInner, packet_id: u64) {
3344        let cap = RELIABLE_MAX_RETURN_ROUTES.max(1);
3345        st.reliable_return_route_order
3346            .retain(|id| st.reliable_return_routes.contains_key(id) && *id != packet_id);
3347        while st.reliable_return_route_order.len() >= cap {
3348            if let Some(oldest) = st.reliable_return_route_order.pop_front() {
3349                st.reliable_return_routes.remove(&oldest);
3350            } else {
3351                break;
3352            }
3353        }
3354        st.reliable_return_route_order.push_back(packet_id);
3355    }
3356
3357    fn remember_end_to_end_reliable_tx_locked(st: &mut RouterInner, packet_id: u64) {
3358        let cap = RELIABLE_MAX_END_TO_END_PENDING.max(1);
3359        st.end_to_end_reliable_tx_order
3360            .retain(|id| st.end_to_end_reliable_tx.contains_key(id) && *id != packet_id);
3361        while st.end_to_end_reliable_tx_order.len() >= cap {
3362            if let Some(oldest) = st.end_to_end_reliable_tx_order.pop_front() {
3363                st.end_to_end_reliable_tx.remove(&oldest);
3364            } else {
3365                break;
3366            }
3367        }
3368        st.end_to_end_reliable_tx_order.push_back(packet_id);
3369    }
3370
3371    #[cfg(feature = "discovery")]
3372    fn expected_end_to_end_destinations_locked(
3373        &self,
3374        st: &RouterInner,
3375        data: &RouterItem,
3376    ) -> TelemetryResult<BTreeMap<u64, RouterSideId>> {
3377        let (eps, ty) = self.item_route_info(data)?;
3378        let now_ms = self.clock.now_ms();
3379        let restrict_link_local = Self::endpoints_are_link_local_only(&eps);
3380        let prefer_best_overlap =
3381            is_reliable_type(ty) && Self::reliable_control_target_packet_id(data)?.is_none();
3382        let scoring_eps = self.preferred_scoring_endpoints(&eps, prefer_best_overlap);
3383        let mut candidates: Vec<(u64, RouterSideId, usize)> = Vec::new();
3384        let mut best_overlap = 0usize;
3385        let mut out = BTreeMap::new();
3386        for (&side, route) in st.discovery_routes.iter() {
3387            if now_ms.saturating_sub(route.last_seen_ms) > DISCOVERY_ROUTE_TTL_MS {
3388                continue;
3389            }
3390            let Some(side_ref) = st.sides.get(side).and_then(Option::as_ref) else {
3391                continue;
3392            };
3393            if restrict_link_local && !side_ref.opts.link_local_enabled {
3394                continue;
3395            }
3396            if !self.route_allowed_locked(st, None, Some(ty), side) {
3397                continue;
3398            }
3399            for sender_state in route.announcers.values() {
3400                if now_ms.saturating_sub(sender_state.last_seen_ms) > DISCOVERY_ROUTE_TTL_MS {
3401                    continue;
3402                }
3403                for board in sender_state.topology_boards.iter() {
3404                    if !Self::is_end_to_end_destination_sender(&board.sender_id) {
3405                        continue;
3406                    }
3407                    let overlap = Self::endpoint_overlap_count(
3408                        board.reachable_endpoints.iter().copied(),
3409                        &scoring_eps,
3410                    );
3411                    if overlap > 0 {
3412                        if prefer_best_overlap {
3413                            best_overlap = best_overlap.max(overlap);
3414                            candidates.push((Self::sender_hash(&board.sender_id), side, overlap));
3415                        } else {
3416                            out.insert(Self::sender_hash(&board.sender_id), side);
3417                            if out.len() >= RELIABLE_MAX_END_TO_END_PENDING.max(1) {
3418                                return Ok(out);
3419                            }
3420                        }
3421                    }
3422                }
3423            }
3424        }
3425
3426        if prefer_best_overlap {
3427            for (sender_hash, side, overlap) in candidates {
3428                if overlap == best_overlap {
3429                    out.insert(sender_hash, side);
3430                    if out.len() >= RELIABLE_MAX_END_TO_END_PENDING.max(1) {
3431                        return Ok(out);
3432                    }
3433                }
3434            }
3435        }
3436        Ok(out)
3437    }
3438
3439    #[cfg(feature = "discovery")]
3440    #[allow(clippy::too_many_arguments)]
3441    fn discovered_route_candidates_locked(
3442        &self,
3443        st: &RouterInner,
3444        exclude: Option<RouterSideId>,
3445        ty: DataType,
3446        eps: &[DataEndpoint],
3447        target_senders: &[u64],
3448        prefer_nonlocal: bool,
3449        preferred_timesync_source: Option<&str>,
3450    ) -> Vec<DiscoveryCandidateMatch> {
3451        let restrict_link_local = Self::endpoints_are_link_local_only(eps);
3452        let now_ms = self.clock.now_ms();
3453        let scoring_eps = self.preferred_scoring_endpoints(eps, prefer_nonlocal);
3454        let mut out = Vec::new();
3455
3456        for (&side, route) in st.discovery_routes.iter() {
3457            if exclude == Some(side)
3458                || now_ms.saturating_sub(route.last_seen_ms) > DISCOVERY_ROUTE_TTL_MS
3459            {
3460                continue;
3461            }
3462            if restrict_link_local
3463                && st
3464                    .sides
3465                    .get(side)
3466                    .and_then(|s| s.as_ref())
3467                    .map(|s| !s.opts.link_local_enabled)
3468                    .unwrap_or(true)
3469            {
3470                continue;
3471            }
3472            if !self.route_allowed_locked(st, exclude, Some(ty), side) {
3473                continue;
3474            }
3475            if !target_senders.is_empty()
3476                && !Self::side_matches_target_senders_locked(st, side, target_senders, now_ms)
3477            {
3478                continue;
3479            }
3480            if !target_senders.is_empty() {
3481                out.push(DiscoveryCandidateMatch {
3482                    side,
3483                    overlap: usize::MAX,
3484                });
3485                continue;
3486            }
3487            if preferred_timesync_source
3488                .is_some_and(|source| route.reachable_timesync_sources.iter().any(|s| s == source))
3489            {
3490                out.push(DiscoveryCandidateMatch {
3491                    side,
3492                    overlap: usize::MAX,
3493                });
3494                continue;
3495            }
3496            let overlap =
3497                Self::endpoint_overlap_count(route.reachable.iter().copied(), &scoring_eps);
3498            if overlap > 0 {
3499                out.push(DiscoveryCandidateMatch { side, overlap });
3500            }
3501        }
3502        out
3503    }
3504
3505    #[cfg(feature = "discovery")]
3506    fn select_discovered_candidate_sides_locked(
3507        &self,
3508        st: &mut RouterInner,
3509        exclude: Option<RouterSideId>,
3510        ty: DataType,
3511        target_senders: &[u64],
3512        prefer_best_overlap: bool,
3513        matches: Vec<DiscoveryCandidateMatch>,
3514    ) -> Vec<RouterSideId> {
3515        let discovered_origin = if Self::has_typed_route_overrides_locked(st, exclude, ty)
3516            || !target_senders.is_empty()
3517        {
3518            RouteSelectionOrigin::Flood
3519        } else {
3520            RouteSelectionOrigin::Discovered
3521        };
3522
3523        let mut matches = matches;
3524        if matches.iter().any(|m| m.overlap == usize::MAX) {
3525            matches.retain(|m| m.overlap == usize::MAX);
3526        }
3527
3528        let selected: Vec<RouterSideId> = if prefer_best_overlap {
3529            let best_overlap = matches.iter().map(|m| m.overlap).max().unwrap_or(0);
3530            matches
3531                .into_iter()
3532                .filter(|m| m.overlap == best_overlap)
3533                .map(|m| m.side)
3534                .collect()
3535        } else {
3536            matches.into_iter().map(|m| m.side).collect()
3537        };
3538
3539        self.apply_route_selection_locked(st, exclude, selected, discovered_origin)
3540    }
3541
3542    fn register_end_to_end_reliable_tx(&self, data: &RouterItem) -> TelemetryResult<()> {
3543        let packet_id = Self::get_hash(data);
3544        let now_ms = self.clock.now_ms();
3545        let ty = match data {
3546            RouterItem::Packet(pkt) => pkt.data_type(),
3547            RouterItem::Packed(bytes) => wire_format::peek_envelope(bytes.as_ref())?.ty,
3548        };
3549        let mut st = self.state.lock();
3550        #[cfg(feature = "discovery")]
3551        let mut pending_destinations = self.expected_end_to_end_destinations_locked(&st, data)?;
3552        #[cfg(not(feature = "discovery"))]
3553        let mut pending_destinations = BTreeMap::new();
3554        self.filter_trackable_end_to_end_destinations_locked(&st, ty, &mut pending_destinations);
3555        let tracked_destinations = !pending_destinations.is_empty();
3556        Self::remember_end_to_end_reliable_tx_locked(&mut st, packet_id);
3557        st.end_to_end_reliable_tx.insert(
3558            packet_id,
3559            EndToEndReliableSent {
3560                data: data.clone(),
3561                pending_destinations,
3562                tracked_destinations,
3563                last_send_ms: now_ms,
3564                retries: 0,
3565                queued: false,
3566            },
3567        );
3568        Ok(())
3569    }
3570
3571    #[cfg(feature = "discovery")]
3572    fn reconcile_end_to_end_reliable_destinations_locked(
3573        &self,
3574        st: &mut RouterInner,
3575    ) -> TelemetryResult<()> {
3576        let active_destinations = self.active_end_to_end_destinations_locked(st);
3577        let packet_ids: Vec<u64> = st.end_to_end_reliable_tx.keys().copied().collect();
3578        let mut completed = Vec::new();
3579
3580        for packet_id in packet_ids {
3581            let Some(data) = st
3582                .end_to_end_reliable_tx
3583                .get(&packet_id)
3584                .map(|sent| sent.data.clone())
3585            else {
3586                continue;
3587            };
3588            let expected = self.expected_end_to_end_destinations_locked(st, &data)?;
3589            let Some(sent) = st.end_to_end_reliable_tx.get_mut(&packet_id) else {
3590                continue;
3591            };
3592            if !sent.tracked_destinations {
3593                continue;
3594            }
3595            sent.pending_destinations.retain(|sender_hash, side| {
3596                match (
3597                    expected.get(sender_hash),
3598                    active_destinations.get(sender_hash),
3599                ) {
3600                    (Some(next_side), _) | (None, Some(next_side)) => {
3601                        *side = *next_side;
3602                        true
3603                    }
3604                    (None, None) => false,
3605                }
3606            });
3607            if sent.pending_destinations.is_empty() {
3608                completed.push(packet_id);
3609            }
3610        }
3611
3612        for packet_id in completed {
3613            st.end_to_end_reliable_tx.remove(&packet_id);
3614        }
3615
3616        Ok(())
3617    }
3618
3619    #[cfg(feature = "discovery")]
3620    fn active_end_to_end_destinations_locked(
3621        &self,
3622        st: &RouterInner,
3623    ) -> BTreeMap<u64, RouterSideId> {
3624        let now_ms = self.clock.now_ms();
3625        let mut out = BTreeMap::new();
3626        for (&side, route) in st.discovery_routes.iter() {
3627            if now_ms.saturating_sub(route.last_seen_ms) > DISCOVERY_ROUTE_TTL_MS {
3628                continue;
3629            }
3630            for sender_state in route.announcers.values() {
3631                if now_ms.saturating_sub(sender_state.last_seen_ms) > DISCOVERY_ROUTE_TTL_MS {
3632                    continue;
3633                }
3634                for board in sender_state.topology_boards.iter() {
3635                    if !Self::is_end_to_end_destination_sender(&board.sender_id) {
3636                        continue;
3637                    }
3638                    out.insert(Self::sender_hash(&board.sender_id), side);
3639                    if out.len() >= RELIABLE_MAX_END_TO_END_PENDING.max(1) {
3640                        return out;
3641                    }
3642                }
3643            }
3644        }
3645        out
3646    }
3647
3648    fn side_supports_end_to_end_tracking_locked(st: &RouterInner, side: RouterSideId) -> bool {
3649        matches!(
3650            st.sides
3651                .get(side)
3652                .and_then(Option::as_ref)
3653                .map(|side| &side.tx_handler),
3654            Some(RouterTxHandlerFn::Packed(_))
3655        )
3656    }
3657
3658    fn filter_trackable_end_to_end_destinations_locked(
3659        &self,
3660        st: &RouterInner,
3661        ty: DataType,
3662        pending: &mut BTreeMap<u64, RouterSideId>,
3663    ) {
3664        let now_ms = self.clock.now_ms();
3665        pending.retain(|_, side| {
3666            Self::side_supports_end_to_end_tracking_locked(st, *side)
3667                && (is_reliable_type(ty)
3668                    || !self.side_has_multiple_announcers_locked(st, *side, now_ms))
3669        });
3670    }
3671
3672    #[cfg(feature = "discovery")]
3673    fn side_has_multiple_announcers_locked(
3674        &self,
3675        st: &RouterInner,
3676        side: RouterSideId,
3677        now_ms: u64,
3678    ) -> bool {
3679        st.discovery_routes
3680            .get(&side)
3681            .map(|route| {
3682                route
3683                    .announcers
3684                    .values()
3685                    .filter(|sender| {
3686                        now_ms.saturating_sub(sender.last_seen_ms) <= DISCOVERY_ROUTE_TTL_MS
3687                    })
3688                    .take(2)
3689                    .count()
3690                    > 1
3691            })
3692            .unwrap_or(false)
3693    }
3694
3695    #[cfg(not(feature = "discovery"))]
3696    fn side_has_multiple_announcers_locked(
3697        &self,
3698        _st: &RouterInner,
3699        _side: RouterSideId,
3700        _now_ms: u64,
3701    ) -> bool {
3702        false
3703    }
3704
3705    fn queue_end_to_end_reliable_ack(
3706        &self,
3707        pkt: &Packet,
3708        called_from_queue: bool,
3709    ) -> TelemetryResult<()> {
3710        self.queue_end_to_end_reliable_ack_for_packet_id(pkt.packet_id(), called_from_queue)
3711    }
3712
3713    fn queue_end_to_end_reliable_ack_for_packet_id(
3714        &self,
3715        packet_id: u64,
3716        called_from_queue: bool,
3717    ) -> TelemetryResult<()> {
3718        let ack_sender = self.encode_end_to_end_ack_sender();
3719        let ack = Packet::new(
3720            DataType::ReliableAck,
3721            message_meta(DataType::ReliableAck).endpoints,
3722            ack_sender.as_str(),
3723            self.packet_timestamp_ms(),
3724            Self::encode_end_to_end_reliable_ack(packet_id),
3725        )?;
3726        self.emit_internal_tx(
3727            RouterTxItem::Broadcast(RouterItem::Packet(ack)),
3728            true,
3729            called_from_queue,
3730        )
3731    }
3732
3733    fn emit_internal_tx(
3734        &self,
3735        item: RouterTxItem,
3736        ignore_local: bool,
3737        called_from_queue: bool,
3738    ) -> TelemetryResult<()> {
3739        if called_from_queue {
3740            self.tx_queue_item_with_flags(item, ignore_local)
3741        } else {
3742            self.tx_item_impl(item, ignore_local, false)
3743        }
3744    }
3745
3746    fn emit_internal_tx_with_priority(
3747        &self,
3748        item: RouterTxItem,
3749        ignore_local: bool,
3750        priority: u8,
3751        called_from_queue: bool,
3752    ) -> TelemetryResult<()> {
3753        if called_from_queue {
3754            self.tx_queue_item_with_priority(item, ignore_local, priority)
3755        } else {
3756            self.tx_item_impl(item, ignore_local, false)
3757        }
3758    }
3759
3760    fn queue_end_to_end_reliable_retransmit(&self, packet_id: u64) -> TelemetryResult<()> {
3761        {
3762            let mut st = self.state.lock();
3763            let Some(sent) = st.end_to_end_reliable_tx.get_mut(&packet_id) else {
3764                return Ok(());
3765            };
3766            if sent.queued {
3767                return Ok(());
3768            }
3769            sent.queued = true;
3770        }
3771        self.tx_queue_item_with_priority(
3772            RouterTxItem::EndToEndReplay { packet_id },
3773            true,
3774            Self::router_item_priority_bumped(DataType::ReliableAck),
3775        )
3776    }
3777
3778    fn end_to_end_retransmit_sides(
3779        &self,
3780        packet_id: u64,
3781    ) -> Option<(RouterItem, Vec<RouterSideId>)> {
3782        let mut st = self.state.lock();
3783        let (data, tracked_destinations, mut sides) = {
3784            let sent = st.end_to_end_reliable_tx.get_mut(&packet_id)?;
3785            sent.queued = false;
3786            sent.last_send_ms = self.clock.now_ms();
3787            let data = sent.data.clone();
3788            let tracked_destinations = sent.tracked_destinations;
3789            let sides: Vec<RouterSideId> = sent.pending_destinations.values().copied().collect();
3790            (data, tracked_destinations, sides)
3791        };
3792        if tracked_destinations && sides.is_empty() {
3793            st.end_to_end_reliable_tx.remove(&packet_id);
3794            return None;
3795        }
3796        sides.sort_unstable();
3797        sides.dedup();
3798        Some((data, sides))
3799    }
3800
3801    fn router_item_priority(data: &RouterItem) -> TelemetryResult<u8> {
3802        let ty = match data {
3803            RouterItem::Packet(pkt) => pkt.data_type(),
3804            RouterItem::Packed(bytes) => wire_format::peek_envelope(bytes.as_ref())?.ty,
3805        };
3806        Ok(message_priority(ty))
3807    }
3808
3809    #[inline]
3810    fn router_item_priority_bumped(ty: DataType) -> u8 {
3811        message_priority(ty).saturating_add(16)
3812    }
3813
3814    #[inline]
3815    fn is_side_tx_busy(err: &TelemetryError) -> bool {
3816        matches!(err, TelemetryError::Io("side tx busy"))
3817    }
3818
3819    #[cfg(feature = "timesync")]
3820    fn timesync_has_usable_time_locked(st: &TimeSyncRuntime, now_mono_ns: u64) -> bool {
3821        st.disciplined_clock.read_unix_ms(now_mono_ns).is_some()
3822            || st
3823                .clock
3824                .current_time(now_mono_ns)
3825                .and_then(|reading| reading.unix_time_ms)
3826                .is_some()
3827    }
3828
3829    #[cfg(feature = "timesync")]
3830    fn reconcile_pending_timesync_request_locked(
3831        st: &mut TimeSyncRuntime,
3832        leader: &Option<TimeSyncLeader>,
3833        now_ms: u64,
3834    ) {
3835        let active_remote = match leader {
3836            Some(TimeSyncLeader::Remote(remote)) => Some(remote.sender.as_str()),
3837            _ => None,
3838        };
3839        let should_clear = st
3840            .pending_request
3841            .as_ref()
3842            .is_some_and(|pending| Some(pending.source.as_str()) != active_remote);
3843        if should_clear {
3844            st.pending_request = None;
3845            st.next_request_mono_ms = now_ms;
3846        }
3847    }
3848
3849    ///Helper function for relay_send
3850    #[inline]
3851    fn enqueue_to_sides(
3852        &self,
3853        data: RouterItem,
3854        exclude: Option<RouterSideId>,
3855        ignore_local: bool,
3856    ) -> TelemetryResult<()> {
3857        let plan = self.remote_side_plan(&data, exclude)?;
3858        let mut st = self.state.lock();
3859        let priority = Self::router_item_priority(&data)?;
3860
3861        let RemoteSidePlan::Target(sides) = plan;
3862        for idx in sides {
3863            st.push_transmit(TxQueued {
3864                item: RouterTxItem::ToSide {
3865                    src: exclude,
3866                    dst: idx,
3867                    data: data.clone(),
3868                },
3869                ignore_local,
3870                priority,
3871            })?;
3872        }
3873
3874        Ok(())
3875    }
3876
3877    fn relay_send(
3878        &self,
3879        data: RouterItem,
3880        src: Option<RouterSideId>,
3881        called_from_queue: bool,
3882    ) -> TelemetryResult<()> {
3883        if called_from_queue {
3884            return self.enqueue_to_sides(data, src, true);
3885        }
3886
3887        let RemoteSidePlan::Target(sides) = self.remote_side_plan(&data, src)?;
3888        for side in sides {
3889            self.tx_item_impl(
3890                RouterTxItem::ToSide {
3891                    src,
3892                    dst: side,
3893                    data: data.clone(),
3894                },
3895                true,
3896                false,
3897            )?;
3898        }
3899
3900        Ok(())
3901    }
3902
3903    fn item_route_info(&self, data: &RouterItem) -> TelemetryResult<(Vec<DataEndpoint>, DataType)> {
3904        match data {
3905            RouterItem::Packet(pkt) => {
3906                pkt.validate()?;
3907                let mut eps = pkt.endpoints().to_vec();
3908                eps.sort_unstable();
3909                eps.dedup();
3910                Ok((eps, pkt.data_type()))
3911            }
3912            RouterItem::Packed(bytes) => {
3913                let env = wire_format::peek_envelope(bytes.as_ref())?;
3914                let mut eps: Vec<DataEndpoint> = env.endpoints.iter().copied().collect();
3915                eps.sort_unstable();
3916                eps.dedup();
3917                Ok((eps, env.ty))
3918            }
3919        }
3920    }
3921
3922    fn item_data_type(data: &RouterItem) -> TelemetryResult<DataType> {
3923        match data {
3924            RouterItem::Packet(pkt) => Ok(pkt.data_type()),
3925            RouterItem::Packed(bytes) => Ok(wire_format::peek_envelope(bytes.as_ref())?.ty),
3926        }
3927    }
3928
3929    fn e2e_crypto_supported(&self) -> bool {
3930        #[cfg(feature = "cryptography")]
3931        {
3932            self.cfg.e2e_encryption() != RouterE2eEncryptionMode::Disabled
3933                && crate::crypto::registered_crypto_available()
3934        }
3935        #[cfg(not(feature = "cryptography"))]
3936        {
3937            false
3938        }
3939    }
3940
3941    fn should_require_e2e_for_type(&self, ty: DataType) -> bool {
3942        if is_internal_control_type(ty) {
3943            return false;
3944        }
3945        match self.cfg.e2e_encryption() {
3946            RouterE2eEncryptionMode::Disabled => {
3947                message_e2e_encryption_policy(ty) == E2eEncryptionPolicy::RequireOn
3948            }
3949            RouterE2eEncryptionMode::RequiredOnly => {
3950                message_e2e_encryption_policy(ty) == E2eEncryptionPolicy::RequireOn
3951            }
3952            RouterE2eEncryptionMode::Preferred => matches!(
3953                message_e2e_encryption_policy(ty),
3954                E2eEncryptionPolicy::PreferOn | E2eEncryptionPolicy::RequireOn
3955            ),
3956            RouterE2eEncryptionMode::ForceAll => true,
3957        }
3958    }
3959
3960    fn ensure_e2e_policy_supported_for_type(&self, ty: DataType) -> TelemetryResult<()> {
3961        if self.should_require_e2e_for_type(ty) && !self.e2e_crypto_supported() {
3962            return Err(TelemetryError::BadArg);
3963        }
3964        Ok(())
3965    }
3966
3967    #[cfg(feature = "cryptography")]
3968    fn e2e_seal_config_for_type(&self, ty: DataType) -> Option<wire_format::E2eSealConfig> {
3969        if self.should_require_e2e_for_type(ty) && self.e2e_crypto_supported() {
3970            Some(wire_format::E2eSealConfig {
3971                key_id: self.cfg.e2e_key_id(),
3972            })
3973        } else {
3974            None
3975        }
3976    }
3977
3978    #[inline]
3979    fn pack_packet_for_router(
3980        &self,
3981        pkt: &Packet,
3982        reliable: Option<wire_format::ReliableHeader>,
3983    ) -> TelemetryResult<Arc<[u8]>> {
3984        #[cfg(feature = "cryptography")]
3985        if let Some(e2e) = self.e2e_seal_config_for_type(pkt.data_type()) {
3986            return wire_format::pack_packet_with_wire_contract_e2e(
3987                pkt,
3988                reliable,
3989                pkt.wire_shape(),
3990                pkt.wire_target_senders(),
3991                e2e,
3992            );
3993        }
3994        Ok(match reliable {
3995            Some(hdr) => wire_format::pack_packet_with_reliable(pkt, hdr),
3996            None => wire_format::pack_packet(pkt),
3997        })
3998    }
3999
4000    #[inline]
4001    fn pack_packet_for_contract(
4002        &self,
4003        pkt: &Packet,
4004        reliable: Option<wire_format::ReliableHeader>,
4005        shape: Option<MessageElement>,
4006        target_senders: &[u64],
4007    ) -> TelemetryResult<Arc<[u8]>> {
4008        #[cfg(feature = "cryptography")]
4009        if let Some(e2e) = self.e2e_seal_config_for_type(pkt.data_type()) {
4010            return wire_format::pack_packet_with_wire_contract_e2e(
4011                pkt,
4012                reliable,
4013                shape,
4014                target_senders,
4015                e2e,
4016            );
4017        }
4018        wire_format::pack_packet_with_wire_contract(pkt, reliable, shape, target_senders)
4019    }
4020
4021    #[cfg(feature = "cryptography")]
4022    #[inline]
4023    fn prepare_packed_for_remote(
4024        &self,
4025        bytes: Arc<[u8]>,
4026        reliable_override: Option<Option<wire_format::ReliableHeader>>,
4027    ) -> TelemetryResult<Arc<[u8]>> {
4028        let frame = wire_format::peek_frame_info(bytes.as_ref())?;
4029        if frame.ack_only() || self.e2e_seal_config_for_type(frame.envelope.ty).is_none() {
4030            return Ok(bytes);
4031        }
4032        let reliable = reliable_override.unwrap_or(frame.reliable);
4033        let pkt = wire_format::unpack_packet(bytes.as_ref())?;
4034        self.pack_packet_for_contract(
4035            &pkt,
4036            reliable,
4037            frame.envelope.wire_shape,
4038            &frame.envelope.target_senders,
4039        )
4040    }
4041
4042    fn item_target_senders(&self, data: &RouterItem) -> TelemetryResult<Arc<[u64]>> {
4043        match data {
4044            RouterItem::Packet(pkt) => Ok(Arc::from(pkt.wire_target_senders())),
4045            RouterItem::Packed(bytes) => {
4046                Ok(wire_format::peek_envelope(bytes.as_ref())?.target_senders)
4047            }
4048        }
4049    }
4050
4051    fn item_targets_local_sender(&self, data: &RouterItem) -> TelemetryResult<bool> {
4052        let targets = self.item_target_senders(data)?;
4053        if targets.is_empty() {
4054            return Ok(true);
4055        }
4056        let local_sender = self.sender_arc();
4057        let local_hash = Self::sender_hash(local_sender.as_ref());
4058        Ok(targets.contains(&local_hash))
4059    }
4060
4061    #[cfg(feature = "discovery")]
4062    fn side_matches_target_senders_locked(
4063        st: &RouterInner,
4064        side: RouterSideId,
4065        target_senders: &[u64],
4066        now_ms: u64,
4067    ) -> bool {
4068        st.discovery_routes
4069            .get(&side)
4070            .map(|route| {
4071                if now_ms.saturating_sub(route.last_seen_ms) > DISCOVERY_ROUTE_TTL_MS {
4072                    return false;
4073                }
4074                route.announcers.values().any(|sender_state| {
4075                    if now_ms.saturating_sub(sender_state.last_seen_ms) > DISCOVERY_ROUTE_TTL_MS {
4076                        return false;
4077                    }
4078                    sender_state
4079                        .topology_boards
4080                        .iter()
4081                        .any(|board| target_senders.contains(&Self::sender_hash(&board.sender_id)))
4082                })
4083            })
4084            .unwrap_or(false)
4085    }
4086
4087    fn attach_wire_contract_to_item(
4088        &self,
4089        data: RouterItem,
4090        target_senders: &[u64],
4091    ) -> TelemetryResult<RouterItem> {
4092        match data {
4093            RouterItem::Packet(pkt) => {
4094                let reliable = if is_reliable_type(pkt.data_type()) {
4095                    Some(wire_format::ReliableHeader {
4096                        flags: wire_format::RELIABLE_FLAG_UNSEQUENCED,
4097                        seq: 0,
4098                        ack: 0,
4099                    })
4100                } else {
4101                    None
4102                };
4103                let bytes = self.pack_packet_for_contract(
4104                    &pkt,
4105                    reliable,
4106                    Some(message_meta(pkt.data_type()).element),
4107                    target_senders,
4108                )?;
4109                Ok(RouterItem::Packed(bytes))
4110            }
4111            RouterItem::Packed(bytes) => Ok(RouterItem::Packed(bytes)),
4112        }
4113    }
4114
4115    fn endpoints_are_link_local_only(eps: &[DataEndpoint]) -> bool {
4116        !eps.is_empty() && eps.iter().all(|ep| ep.is_link_local_only())
4117    }
4118
4119    fn should_route_remote(
4120        &self,
4121        data: &RouterItem,
4122        exclude: Option<RouterSideId>,
4123    ) -> TelemetryResult<bool> {
4124        #[cfg(feature = "discovery")]
4125        {
4126            let RemoteSidePlan::Target(sides) = self.remote_side_plan(data, exclude)?;
4127            Ok(!sides.is_empty())
4128        }
4129
4130        #[cfg(not(feature = "discovery"))]
4131        {
4132            let (eps, ty) = self.item_route_info(data)?;
4133            if !(has_nonlocal_endpoint(&eps, &self.cfg) || force_remote_for_type(ty)) {
4134                return Ok(false);
4135            }
4136            let st = self.state.lock();
4137            Ok(!self
4138                .eligible_side_ids_locked(
4139                    &st,
4140                    exclude,
4141                    Some(ty),
4142                    Self::endpoints_are_link_local_only(&eps),
4143                )
4144                .is_empty())
4145        }
4146    }
4147
4148    #[cfg(feature = "discovery")]
4149    fn has_explicit_route_policy_locked(
4150        st: &RouterInner,
4151        src: Option<RouterSideId>,
4152        ty: DataType,
4153    ) -> bool {
4154        st.route_overrides
4155            .keys()
4156            .any(|(route_src, _)| *route_src == src)
4157            || Self::has_typed_route_overrides_locked(st, src, ty)
4158    }
4159
4160    fn remote_side_plan(
4161        &self,
4162        data: &RouterItem,
4163        exclude: Option<RouterSideId>,
4164    ) -> TelemetryResult<RemoteSidePlan> {
4165        #[cfg(feature = "discovery")]
4166        {
4167            let (eps, ty) = self.item_route_info(data)?;
4168            let target_senders = self.item_target_senders(data)?;
4169            let preferred_packet_id = Self::reliable_control_target_packet_id(data)?;
4170            if discovery::is_discovery_type(ty) {
4171                let mut st = self.state.lock();
4172                let sides = self.eligible_side_ids_locked(&st, exclude, Some(ty), false);
4173                return Ok(RemoteSidePlan::Target(self.apply_route_selection_locked(
4174                    &mut st,
4175                    exclude,
4176                    sides,
4177                    RouteSelectionOrigin::Flood,
4178                )));
4179            }
4180            if !(has_nonlocal_endpoint(&eps, &self.cfg) || force_remote_for_type(ty)) {
4181                return Ok(RemoteSidePlan::Target(Vec::new()));
4182            }
4183
4184            #[cfg(feature = "timesync")]
4185            let preferred_timesync_source = self.preferred_timesync_route_source(data, ty)?;
4186            #[cfg(not(feature = "timesync"))]
4187            let preferred_timesync_source: Option<String> = None;
4188
4189            let mut st = self.state.lock();
4190            if let Some(packet_id) = preferred_packet_id {
4191                let target_side = st
4192                    .reliable_return_routes
4193                    .get(&packet_id)
4194                    .map(|route| route.side);
4195                if let Some(side) = target_side
4196                    .filter(|side| self.route_allowed_locked(&st, exclude, Some(ty), *side))
4197                {
4198                    #[cfg(feature = "timesync")]
4199                    if !Self::timesync_allowed_for_side_locked(
4200                        &mut st,
4201                        side,
4202                        ty,
4203                        self.clock.now_ms(),
4204                    ) {
4205                        return Ok(RemoteSidePlan::Target(Vec::new()));
4206                    }
4207                    return Ok(RemoteSidePlan::Target(vec![side]));
4208                }
4209                return Ok(RemoteSidePlan::Target(Vec::new()));
4210            }
4211            let restrict_link_local = Self::endpoints_are_link_local_only(&eps);
4212            let prefer_best_overlap =
4213                is_reliable_type(ty) && target_senders.is_empty() && preferred_packet_id.is_none();
4214            if st.discovery_routes.is_empty() {
4215                let mut fallback =
4216                    self.eligible_side_ids_locked(&st, exclude, Some(ty), restrict_link_local);
4217                #[cfg(feature = "timesync")]
4218                {
4219                    fallback = Self::filter_timesync_sides_locked(
4220                        &mut st,
4221                        ty,
4222                        self.clock.now_ms(),
4223                        fallback,
4224                    );
4225                }
4226                return Ok(RemoteSidePlan::Target(if fallback.len() == 1 {
4227                    fallback
4228                } else {
4229                    Vec::new()
4230                }));
4231            }
4232            let mut matches = self.discovered_route_candidates_locked(
4233                &st,
4234                exclude,
4235                ty,
4236                &eps,
4237                &target_senders,
4238                prefer_best_overlap,
4239                preferred_timesync_source.as_deref(),
4240            );
4241            #[cfg(feature = "timesync")]
4242            {
4243                matches =
4244                    Self::filter_timesync_matches_locked(&mut st, ty, self.clock.now_ms(), matches);
4245            }
4246
4247            if !matches.is_empty() {
4248                Ok(RemoteSidePlan::Target(
4249                    self.select_discovered_candidate_sides_locked(
4250                        &mut st,
4251                        exclude,
4252                        ty,
4253                        &target_senders,
4254                        prefer_best_overlap,
4255                        matches,
4256                    ),
4257                ))
4258            } else if prefer_best_overlap {
4259                Ok(RemoteSidePlan::Target(Vec::new()))
4260            } else {
4261                if Self::has_explicit_route_policy_locked(&st, exclude, ty) {
4262                    let mut sides =
4263                        self.eligible_side_ids_locked(&st, exclude, Some(ty), restrict_link_local);
4264                    #[cfg(feature = "timesync")]
4265                    {
4266                        sides = Self::filter_timesync_sides_locked(
4267                            &mut st,
4268                            ty,
4269                            self.clock.now_ms(),
4270                            sides,
4271                        );
4272                    }
4273                    Ok(RemoteSidePlan::Target(self.apply_route_selection_locked(
4274                        &mut st,
4275                        exclude,
4276                        sides,
4277                        RouteSelectionOrigin::Flood,
4278                    )))
4279                } else {
4280                    Ok(RemoteSidePlan::Target(Vec::new()))
4281                }
4282            }
4283        }
4284        #[cfg(not(feature = "discovery"))]
4285        {
4286            let (_, ty) = self.item_route_info(data)?;
4287            let mut st = self.state.lock();
4288            if let Some(packet_id) = Self::reliable_control_target_packet_id(data)? {
4289                let target_side = st
4290                    .reliable_return_routes
4291                    .get(&packet_id)
4292                    .map(|route| route.side);
4293                if let Some(side) = target_side
4294                    .filter(|side| self.route_allowed_locked(&st, exclude, Some(ty), *side))
4295                {
4296                    return Ok(RemoteSidePlan::Target(vec![side]));
4297                }
4298                return Ok(RemoteSidePlan::Target(Vec::new()));
4299            }
4300            let sides = self.eligible_side_ids_locked(&st, exclude, Some(ty), false);
4301            Ok(RemoteSidePlan::Target(self.apply_route_selection_locked(
4302                &mut st,
4303                exclude,
4304                sides,
4305                RouteSelectionOrigin::Flood,
4306            )))
4307        }
4308    }
4309
4310    #[cfg(feature = "discovery")]
4311    fn local_discovery_endpoints(&self) -> Vec<DataEndpoint> {
4312        let mut eps: Vec<DataEndpoint> = self.cfg.handlers.iter().map(|h| h.endpoint).collect();
4313        #[cfg(feature = "timesync")]
4314        if self.cfg.timesync_config().is_some() {
4315            eps.push(DataEndpoint::TimeSync);
4316        }
4317        eps.retain(|ep| !discovery::is_discovery_endpoint(*ep));
4318        eps.sort_unstable();
4319        eps.dedup();
4320        eps
4321    }
4322
4323    #[cfg(feature = "discovery")]
4324    fn local_discovery_timesync_sources(&self, now_ms: u64) -> Vec<String> {
4325        #[cfg(feature = "timesync")]
4326        {
4327            let st = self.timesync.lock();
4328            if let Some(tracker) = st.tracker.as_ref()
4329                && tracker.should_serve(
4330                    now_ms,
4331                    Self::timesync_has_usable_time_locked(&st, self.monotonic_now_ns()),
4332                )
4333            {
4334                return vec![self.sender_arc().to_string()];
4335            }
4336        }
4337        Vec::new()
4338    }
4339
4340    #[cfg(all(feature = "discovery", feature = "timesync"))]
4341    fn preferred_timesync_route_source(
4342        &self,
4343        data: &RouterItem,
4344        ty: DataType,
4345    ) -> TelemetryResult<Option<String>> {
4346        if !matches!(
4347            ty,
4348            DataType::TimeSyncAnnounce | DataType::TimeSyncRequest | DataType::TimeSyncResponse
4349        ) {
4350            return Ok(None);
4351        }
4352
4353        match data {
4354            RouterItem::Packet(pkt) => match ty {
4355                DataType::TimeSyncRequest => {
4356                    let local_sender = self.sender_arc();
4357                    if pkt.sender() == local_sender.as_ref() {
4358                        Ok(self.timesync.lock().tracker.as_ref().and_then(|tracker| {
4359                            tracker.current_source().map(|src| src.sender.clone())
4360                        }))
4361                    } else {
4362                        Ok(None)
4363                    }
4364                }
4365                DataType::TimeSyncAnnounce | DataType::TimeSyncResponse => {
4366                    Ok(Some(pkt.sender().to_owned()))
4367                }
4368                _ => Ok(None),
4369            },
4370            RouterItem::Packed(bytes) => {
4371                let pkt = wire_format::unpack_packet(bytes.as_ref())?;
4372                self.preferred_timesync_route_source(&RouterItem::Packet(pkt), ty)
4373            }
4374        }
4375    }
4376
4377    #[cfg(feature = "discovery")]
4378    fn note_discovery_topology_change_locked(st: &mut RouterInner, now_ms: u64) {
4379        st.discovery_cadence.on_topology_change(now_ms);
4380    }
4381
4382    #[cfg(feature = "discovery")]
4383    fn sender_topology_board_mut<'a>(
4384        sender_state: &'a mut DiscoverySenderState,
4385        sender_id: &str,
4386    ) -> &'a mut TopologyBoardNode {
4387        if let Some(idx) = sender_state
4388            .topology_boards
4389            .iter()
4390            .position(|board| board.sender_id == sender_id)
4391        {
4392            return &mut sender_state.topology_boards[idx];
4393        }
4394        sender_state.topology_boards.push(TopologyBoardNode {
4395            sender_id: sender_id.to_string(),
4396            reachable_endpoints: Vec::new(),
4397            reachable_timesync_sources: Vec::new(),
4398            connections: Vec::new(),
4399        });
4400        sender_state
4401            .topology_boards
4402            .last_mut()
4403            .expect("board inserted above")
4404    }
4405
4406    #[cfg(feature = "discovery")]
4407    fn refresh_sender_topology_state(sender_state: &mut DiscoverySenderState) {
4408        discovery::normalize_topology_boards(&mut sender_state.topology_boards);
4409        let (reachable, reachable_timesync_sources) =
4410            discovery::summarize_topology_boards(&sender_state.topology_boards);
4411        sender_state.reachable = reachable;
4412        sender_state.reachable_timesync_sources = reachable_timesync_sources;
4413    }
4414
4415    #[cfg(feature = "discovery")]
4416    fn recompute_discovery_side_state(route: &mut DiscoverySideState) {
4417        let mut reachable = Vec::new();
4418        let mut reachable_timesync_sources = Vec::new();
4419        let mut last_seen_ms = 0u64;
4420        for sender in route.announcers.values() {
4421            reachable.extend(sender.reachable.iter().copied());
4422            reachable_timesync_sources.extend(sender.reachable_timesync_sources.iter().cloned());
4423            last_seen_ms = last_seen_ms.max(sender.last_seen_ms);
4424        }
4425        reachable.sort_unstable();
4426        reachable.dedup();
4427        reachable_timesync_sources.sort_unstable();
4428        reachable_timesync_sources.dedup();
4429        route.reachable = reachable;
4430        route.reachable_timesync_sources = reachable_timesync_sources;
4431        route.last_seen_ms = last_seen_ms;
4432    }
4433
4434    #[cfg(feature = "discovery")]
4435    fn local_discovery_topology_board(
4436        &self,
4437        st: &RouterInner,
4438        now_ms: u64,
4439        link_local_enabled: bool,
4440    ) -> TopologyBoardNode {
4441        let mut reachable_endpoints = self.local_discovery_endpoints();
4442        if !link_local_enabled {
4443            reachable_endpoints.retain(|ep| !ep.is_link_local_only());
4444        }
4445        let mut connections = Vec::new();
4446        for route in st.discovery_routes.values() {
4447            if now_ms.saturating_sub(route.last_seen_ms) > DISCOVERY_ROUTE_TTL_MS {
4448                continue;
4449            }
4450            for (sender, sender_state) in route.announcers.iter() {
4451                if now_ms.saturating_sub(sender_state.last_seen_ms) <= DISCOVERY_ROUTE_TTL_MS {
4452                    connections.push(sender.clone());
4453                }
4454            }
4455        }
4456        connections.sort_unstable();
4457        connections.dedup();
4458        let sender = self.sender_arc();
4459        TopologyBoardNode {
4460            sender_id: sender.to_string(),
4461            reachable_endpoints,
4462            reachable_timesync_sources: self.local_discovery_timesync_sources(now_ms),
4463            connections,
4464        }
4465    }
4466
4467    #[cfg(feature = "discovery")]
4468    fn advertised_discovery_topology_for_link_locked(
4469        &self,
4470        st: &RouterInner,
4471        now_ms: u64,
4472        link_local_enabled: bool,
4473    ) -> Vec<TopologyBoardNode> {
4474        let mut boards = vec![self.local_discovery_topology_board(st, now_ms, link_local_enabled)];
4475        for route in st.discovery_routes.values() {
4476            if now_ms.saturating_sub(route.last_seen_ms) > DISCOVERY_ROUTE_TTL_MS {
4477                continue;
4478            }
4479            for (announcer, sender_state) in route.announcers.iter() {
4480                if now_ms.saturating_sub(sender_state.last_seen_ms) > DISCOVERY_ROUTE_TTL_MS {
4481                    continue;
4482                }
4483                let mut sender_boards = sender_state.topology_boards.clone();
4484                if sender_boards.is_empty() {
4485                    let sender = self.sender_arc();
4486                    sender_boards.push(TopologyBoardNode {
4487                        sender_id: announcer.clone(),
4488                        reachable_endpoints: sender_state.reachable.clone(),
4489                        reachable_timesync_sources: sender_state.reachable_timesync_sources.clone(),
4490                        connections: vec![sender.to_string()],
4491                    });
4492                } else if let Some(board) = sender_boards
4493                    .iter_mut()
4494                    .find(|board| board.sender_id == *announcer)
4495                {
4496                    board.connections.push(self.sender_arc().to_string());
4497                }
4498                if !link_local_enabled {
4499                    for board in sender_boards.iter_mut() {
4500                        board
4501                            .reachable_endpoints
4502                            .retain(|ep| !ep.is_link_local_only());
4503                    }
4504                }
4505                discovery::merge_topology_boards(&mut boards, &sender_boards);
4506            }
4507        }
4508        discovery::normalize_topology_boards(&mut boards);
4509        boards
4510    }
4511
4512    #[cfg(feature = "discovery")]
4513    fn prune_discovery_routes_locked(st: &mut RouterInner, now_ms: u64) -> bool {
4514        let before = st.discovery_routes.clone();
4515        st.discovery_routes.retain(|_, route| {
4516            route.announcers.retain(|_, sender| {
4517                now_ms.saturating_sub(sender.last_seen_ms) <= DISCOVERY_ROUTE_TTL_MS
4518            });
4519            Self::recompute_discovery_side_state(route);
4520            !route.announcers.is_empty()
4521        });
4522        st.discovery_routes != before
4523    }
4524
4525    #[cfg(feature = "discovery")]
4526    fn advertised_discovery_endpoints_for_link_locked(
4527        &self,
4528        st: &RouterInner,
4529        now_ms: u64,
4530        link_local_enabled: bool,
4531    ) -> Vec<DataEndpoint> {
4532        let (reachable_endpoints, _) = discovery::summarize_topology_boards(
4533            &self.advertised_discovery_topology_for_link_locked(st, now_ms, link_local_enabled),
4534        );
4535        reachable_endpoints
4536            .into_iter()
4537            .filter(|ep| {
4538                !discovery::is_discovery_endpoint(*ep)
4539                    && (link_local_enabled || !ep.is_link_local_only())
4540            })
4541            .collect()
4542    }
4543
4544    #[cfg(feature = "discovery")]
4545    fn advertised_discovery_timesync_sources_for_link_locked(
4546        &self,
4547        st: &RouterInner,
4548        now_ms: u64,
4549    ) -> Vec<String> {
4550        let (_, sources) = discovery::summarize_topology_boards(
4551            &self.advertised_discovery_topology_for_link_locked(st, now_ms, true),
4552        );
4553        sources
4554    }
4555
4556    #[cfg(feature = "discovery")]
4557    fn discovery_master_sender_locked(&self, st: &RouterInner, now_ms: u64) -> String {
4558        let boards = self.advertised_discovery_topology_for_link_locked(st, now_ms, true);
4559        discovery::elect_discovery_master(self.sender_arc().as_ref(), &boards)
4560    }
4561
4562    #[cfg(feature = "discovery")]
4563    fn should_answer_discovery_request_locked(
4564        &self,
4565        st: &RouterInner,
4566        requester: &str,
4567        now_ms: u64,
4568    ) -> bool {
4569        if requester == self.sender_arc().as_ref() {
4570            return false;
4571        }
4572        self.discovery_master_sender_locked(st, now_ms) == self.sender_arc().as_ref()
4573    }
4574
4575    #[cfg(feature = "discovery")]
4576    #[inline]
4577    fn side_is_slow_control_link_locked(
4578        st: &RouterInner,
4579        side_id: RouterSideId,
4580        now_ms: u64,
4581    ) -> bool {
4582        st.adaptive_route_stats.get(&side_id).is_some_and(|stats| {
4583            let recent_slow = stats.last_slow_observed_ms > 0
4584                && now_ms.saturating_sub(stats.last_slow_observed_ms)
4585                    <= DISCOVERY_SLOW_LINK_FULL_INTERVAL_MS;
4586            stats.sample_count > 0
4587                && ((stats.estimated_bandwidth_bps > 0
4588                    && stats.estimated_bandwidth_bps <= CONTROL_SLOW_LINK_CAPACITY_BPS)
4589                    || recent_slow)
4590        })
4591    }
4592
4593    #[cfg(feature = "discovery")]
4594    fn discovery_level_for_side_locked(
4595        st: &mut RouterInner,
4596        side_id: RouterSideId,
4597        now_ms: u64,
4598    ) -> Option<DiscoveryAdvertiseLevel> {
4599        if !Self::side_is_slow_control_link_locked(st, side_id, now_ms) {
4600            st.discovery_side_throttle.remove(&side_id);
4601            return Some(DiscoveryAdvertiseLevel::Full);
4602        }
4603
4604        let throttle = st.discovery_side_throttle.entry(side_id).or_default();
4605        if now_ms >= throttle.next_full_ms {
4606            throttle.next_full_ms = now_ms.saturating_add(DISCOVERY_SLOW_LINK_FULL_INTERVAL_MS);
4607            throttle.next_ping_ms = now_ms.saturating_add(DISCOVERY_SLOW_LINK_PING_INTERVAL_MS);
4608            return Some(DiscoveryAdvertiseLevel::Full);
4609        }
4610        if now_ms >= throttle.next_ping_ms {
4611            throttle.next_ping_ms = now_ms.saturating_add(DISCOVERY_SLOW_LINK_PING_INTERVAL_MS);
4612            return Some(DiscoveryAdvertiseLevel::MinimalPing);
4613        }
4614        None
4615    }
4616
4617    #[cfg(all(feature = "discovery", feature = "timesync"))]
4618    #[inline]
4619    fn is_timesync_type(ty: DataType) -> bool {
4620        matches!(
4621            ty,
4622            DataType::TimeSyncAnnounce | DataType::TimeSyncRequest | DataType::TimeSyncResponse
4623        )
4624    }
4625
4626    #[cfg(all(feature = "discovery", feature = "timesync"))]
4627    fn timesync_allowed_for_side_locked(
4628        st: &mut RouterInner,
4629        side_id: RouterSideId,
4630        ty: DataType,
4631        now_ms: u64,
4632    ) -> bool {
4633        if !Self::is_timesync_type(ty) {
4634            return true;
4635        }
4636        if !Self::side_is_slow_control_link_locked(st, side_id, now_ms) {
4637            st.timesync_side_throttle.remove(&side_id);
4638            return true;
4639        }
4640
4641        let throttle = st.timesync_side_throttle.entry(side_id).or_default();
4642        if now_ms >= throttle.next_allowed_ms {
4643            throttle.next_allowed_ms = now_ms.saturating_add(TIMESYNC_SLOW_LINK_MIN_INTERVAL_MS);
4644            return true;
4645        }
4646        false
4647    }
4648
4649    #[cfg(all(feature = "discovery", feature = "timesync"))]
4650    fn filter_timesync_sides_locked(
4651        st: &mut RouterInner,
4652        ty: DataType,
4653        now_ms: u64,
4654        sides: Vec<RouterSideId>,
4655    ) -> Vec<RouterSideId> {
4656        sides
4657            .into_iter()
4658            .filter(|side| Self::timesync_allowed_for_side_locked(st, *side, ty, now_ms))
4659            .collect()
4660    }
4661
4662    #[cfg(all(feature = "discovery", feature = "timesync"))]
4663    fn filter_timesync_matches_locked(
4664        st: &mut RouterInner,
4665        ty: DataType,
4666        now_ms: u64,
4667        matches: Vec<DiscoveryCandidateMatch>,
4668    ) -> Vec<DiscoveryCandidateMatch> {
4669        matches
4670            .into_iter()
4671            .filter(|m| Self::timesync_allowed_for_side_locked(st, m.side, ty, now_ms))
4672            .collect()
4673    }
4674
4675    #[cfg(feature = "discovery")]
4676    fn emit_discovery_snapshot(
4677        &self,
4678        called_from_queue: bool,
4679        include_schema: bool,
4680        include_topology: bool,
4681    ) -> TelemetryResult<()> {
4682        let now_ms = self.clock.now_ms();
4683        let per_side = {
4684            let mut st = self.state.lock();
4685            if Self::prune_discovery_routes_locked(&mut st, now_ms) {
4686                self.reconcile_end_to_end_reliable_destinations_locked(&mut st)?;
4687                Self::note_discovery_topology_change_locked(&mut st, now_ms);
4688            }
4689            st.fit_discovery_budget();
4690            let side_entries = st
4691                .sides
4692                .iter()
4693                .enumerate()
4694                .filter_map(|(side_id, side)| {
4695                    side.as_ref()
4696                        .map(|side| (side_id, side.opts.link_local_enabled, side.opts))
4697                })
4698                .collect::<Vec<_>>();
4699            let local_is_master =
4700                self.discovery_master_sender_locked(&st, now_ms) == self.sender_arc().as_ref();
4701            let mut per_side = Vec::new();
4702            for (side_id, link_local_enabled, opts) in side_entries {
4703                if !self.route_allowed_locked(&st, None, Some(DataType::DiscoveryAnnounce), side_id)
4704                {
4705                    continue;
4706                }
4707                let Some(level) = Self::discovery_level_for_side_locked(&mut st, side_id, now_ms)
4708                else {
4709                    continue;
4710                };
4711                let capabilities = opts.link_capabilities();
4712                if level == DiscoveryAdvertiseLevel::MinimalPing {
4713                    per_side.push((
4714                        side_id,
4715                        level,
4716                        Vec::new(),
4717                        Vec::new(),
4718                        Vec::new(),
4719                        capabilities,
4720                        local_is_master,
4721                    ));
4722                    continue;
4723                }
4724                per_side.push((
4725                    side_id,
4726                    level,
4727                    self.advertised_discovery_endpoints_for_link_locked(
4728                        &st,
4729                        now_ms,
4730                        link_local_enabled,
4731                    ),
4732                    self.advertised_discovery_timesync_sources_for_link_locked(&st, now_ms),
4733                    self.advertised_discovery_topology_for_link_locked(
4734                        &st,
4735                        now_ms,
4736                        link_local_enabled,
4737                    ),
4738                    capabilities,
4739                    local_is_master,
4740                ));
4741            }
4742            per_side
4743        };
4744        for (
4745            side_id,
4746            level,
4747            endpoints,
4748            timesync_sources,
4749            topology,
4750            capabilities,
4751            local_is_master,
4752        ) in per_side
4753        {
4754            let sender = self.sender_arc();
4755            if include_schema && level == DiscoveryAdvertiseLevel::Full {
4756                let pkt = discovery::build_discovery_schema(sender.as_ref(), now_ms)?;
4757                self.emit_internal_tx(
4758                    RouterTxItem::ToSide {
4759                        src: None,
4760                        dst: side_id,
4761                        data: RouterItem::Packet(pkt),
4762                    },
4763                    true,
4764                    called_from_queue,
4765                )?;
4766            }
4767            if level == DiscoveryAdvertiseLevel::Full {
4768                let address = self.local_address_advertisement(
4769                    endpoints.clone(),
4770                    timesync_sources.clone(),
4771                    capabilities,
4772                    if local_is_master {
4773                        discovery::ADDRESS_STATE_APPROVED
4774                    } else {
4775                        discovery::ADDRESS_STATE_REQUEST
4776                    },
4777                );
4778                let pkt = discovery::build_discovery_address(sender.as_ref(), now_ms, &address)?;
4779                self.emit_internal_tx(
4780                    RouterTxItem::ToSide {
4781                        src: None,
4782                        dst: side_id,
4783                        data: RouterItem::Packet(pkt),
4784                    },
4785                    true,
4786                    called_from_queue,
4787                )?;
4788            }
4789            if level == DiscoveryAdvertiseLevel::MinimalPing {
4790                let pkt = discovery::build_discovery_announce(
4791                    sender.as_ref(),
4792                    now_ms,
4793                    endpoints.as_slice(),
4794                )?;
4795                self.emit_internal_tx(
4796                    RouterTxItem::ToSide {
4797                        src: None,
4798                        dst: side_id,
4799                        data: RouterItem::Packet(pkt),
4800                    },
4801                    true,
4802                    called_from_queue,
4803                )?;
4804            }
4805            if include_topology && level == DiscoveryAdvertiseLevel::Full && !topology.is_empty() {
4806                let pkt = discovery::build_discovery_topology(sender.as_ref(), now_ms, &topology)?;
4807                self.emit_internal_tx(
4808                    RouterTxItem::ToSide {
4809                        src: None,
4810                        dst: side_id,
4811                        data: RouterItem::Packet(pkt),
4812                    },
4813                    true,
4814                    called_from_queue,
4815                )?;
4816            }
4817        }
4818        Ok(())
4819    }
4820
4821    #[cfg(feature = "discovery")]
4822    fn queue_discovery_announce(&self) -> TelemetryResult<()> {
4823        let now_ms = self.clock.now_ms();
4824        {
4825            let mut st = self.state.lock();
4826            if Self::prune_discovery_routes_locked(&mut st, now_ms) {
4827                self.reconcile_end_to_end_reliable_destinations_locked(&mut st)?;
4828                Self::note_discovery_topology_change_locked(&mut st, now_ms);
4829            }
4830            st.fit_discovery_budget();
4831            if st.sides.iter().all(|side| side.is_none()) {
4832                return Ok(());
4833            }
4834            st.discovery_cadence.on_announce_sent(now_ms);
4835        }
4836        self.emit_discovery_snapshot(true, true, true)
4837    }
4838
4839    #[cfg(feature = "discovery")]
4840    fn poll_discovery_announce(&self) -> TelemetryResult<bool> {
4841        let now_ms = self.clock.now_ms();
4842        let due = {
4843            let mut st = self.state.lock();
4844            let removed = Self::prune_discovery_routes_locked(&mut st, now_ms);
4845            if removed {
4846                self.reconcile_end_to_end_reliable_destinations_locked(&mut st)?;
4847                Self::note_discovery_topology_change_locked(&mut st, now_ms);
4848            }
4849            st.fit_discovery_budget();
4850            let has_any = st.sides.iter().enumerate().any(|(side_id, side)| {
4851                let Some(side) = side.as_ref() else {
4852                    return false;
4853                };
4854                if !self.route_allowed_locked(&st, None, Some(DataType::DiscoveryAnnounce), side_id)
4855                {
4856                    return false;
4857                }
4858                let _ = side;
4859                true
4860            });
4861            if st.sides.is_empty() || !has_any {
4862                return Ok(false);
4863            }
4864            st.discovery_cadence.due(now_ms)
4865        };
4866        if !due {
4867            return Ok(false);
4868        }
4869        self.queue_discovery_announce()?;
4870        Ok(true)
4871    }
4872
4873    #[cfg(feature = "discovery")]
4874    fn learn_discovery_packet(
4875        &self,
4876        pkt: &Packet,
4877        src: Option<RouterSideId>,
4878        called_from_queue: bool,
4879    ) -> TelemetryResult<bool> {
4880        if !discovery::is_discovery_type(pkt.data_type()) {
4881            return Ok(false);
4882        }
4883        let Some(side) = src else {
4884            return Ok(true);
4885        };
4886        if pkt.data_type() == DataType::DiscoveryAddress {
4887            let mut ad = discovery::decode_discovery_address(pkt)?;
4888            let mut changed = self.ingest_address_advertisement(ad.clone())?;
4889            let now_ms = self.clock.now_ms();
4890            let mut st = self.state.lock();
4891            let side_link_local_enabled = st
4892                .sides
4893                .get(side)
4894                .and_then(|entry| entry.as_ref())
4895                .map(|side_ref| side_ref.opts.link_local_enabled)
4896                .unwrap_or(false);
4897            if !side_link_local_enabled {
4898                ad.reachable_endpoints.retain(|ep| !ep.is_link_local_only());
4899            }
4900            let mut route = st.discovery_routes.get(&side).cloned().unwrap_or_default();
4901            let mut sender_state = route
4902                .announcers
4903                .get(pkt.sender())
4904                .cloned()
4905                .unwrap_or_default();
4906            let board = Self::sender_topology_board_mut(&mut sender_state, pkt.sender());
4907            if board.reachable_endpoints != ad.reachable_endpoints {
4908                board.reachable_endpoints = ad.reachable_endpoints;
4909                changed = true;
4910            }
4911            if board.reachable_timesync_sources != ad.reachable_timesync_sources {
4912                board.reachable_timesync_sources = ad.reachable_timesync_sources;
4913                changed = true;
4914            }
4915            Self::refresh_sender_topology_state(&mut sender_state);
4916            sender_state.last_seen_ms = now_ms;
4917            route
4918                .announcers
4919                .insert(pkt.sender().to_string(), sender_state);
4920            Self::recompute_discovery_side_state(&mut route);
4921            st.discovery_routes.insert(side, route);
4922            st.fit_discovery_budget();
4923            self.reconcile_end_to_end_reliable_destinations_locked(&mut st)?;
4924            if changed {
4925                Self::note_discovery_topology_change_locked(&mut st, self.clock.now_ms());
4926            }
4927            return Ok(true);
4928        }
4929        let local_sender = self.sender_arc();
4930        if pkt.sender() == local_sender.as_ref() {
4931            return Ok(true);
4932        }
4933        if pkt.data_type() == DataType::DiscoveryTopologyRequest {
4934            let now_ms = self.clock.now_ms();
4935            let should_answer = {
4936                let mut st = self.state.lock();
4937                if Self::prune_discovery_routes_locked(&mut st, now_ms) {
4938                    self.reconcile_end_to_end_reliable_destinations_locked(&mut st)?;
4939                    Self::note_discovery_topology_change_locked(&mut st, now_ms);
4940                }
4941                self.should_answer_discovery_request_locked(&st, pkt.sender(), now_ms)
4942            };
4943            if should_answer {
4944                self.emit_discovery_snapshot(called_from_queue, false, true)?;
4945            }
4946            return Ok(true);
4947        }
4948        if pkt.data_type() == DataType::DiscoverySchemaRequest {
4949            let now_ms = self.clock.now_ms();
4950            let should_answer = {
4951                let mut st = self.state.lock();
4952                if Self::prune_discovery_routes_locked(&mut st, now_ms) {
4953                    self.reconcile_end_to_end_reliable_destinations_locked(&mut st)?;
4954                    Self::note_discovery_topology_change_locked(&mut st, now_ms);
4955                }
4956                self.should_answer_discovery_request_locked(&st, pkt.sender(), now_ms)
4957            };
4958            if should_answer {
4959                self.emit_discovery_snapshot(called_from_queue, true, true)?;
4960            }
4961            return Ok(true);
4962        }
4963        if pkt.data_type() == DataType::ManagedVariableRequest {
4964            let ty = discovery::decode_managed_variable_request(pkt)?;
4965            if !self.can_read_managed_variable(ty) {
4966                let payload = make_error_payload("managed variable permission denied");
4967                let err = Packet::new(
4968                    DataType::TelemetryError,
4969                    message_meta(DataType::TelemetryError).endpoints,
4970                    self.sender_arc().as_ref(),
4971                    self.clock.now_ms(),
4972                    payload,
4973                )?;
4974                self.emit_internal_tx(
4975                    RouterTxItem::ToSide {
4976                        src: None,
4977                        dst: side,
4978                        data: RouterItem::Packet(err),
4979                    },
4980                    true,
4981                    called_from_queue,
4982                )?;
4983                return Ok(true);
4984            }
4985            if let Some(value) = self.managed_variable_latest(ty) {
4986                self.emit_internal_tx(
4987                    RouterTxItem::ToSide {
4988                        src: None,
4989                        dst: side,
4990                        data: RouterItem::Packet(value),
4991                    },
4992                    true,
4993                    called_from_queue,
4994                )?;
4995            }
4996            return Ok(true);
4997        }
4998        if pkt.data_type() == DataType::DiscoverySchema {
4999            let snapshot = discovery::decode_discovery_schema(pkt)?;
5000            let incoming_cost = crate::config::owned_schema_byte_cost(&snapshot);
5001            let mut st = self.state.lock();
5002            st.make_shared_queue_room(incoming_cost, RouterQueueKind::Discovery)?;
5003            drop(st);
5004            let report =
5005                crate::config::merge_owned_schema_snapshot_with_budget(snapshot, MAX_QUEUE_BUDGET)?;
5006            if report.changed() {
5007                let mut st = self.state.lock();
5008                st.fit_discovery_budget();
5009                Self::note_discovery_topology_change_locked(&mut st, self.clock.now_ms());
5010            }
5011            return Ok(true);
5012        }
5013        if pkt.data_type() == DataType::DiscoveryLinkCapabilities {
5014            let _ = discovery::decode_discovery_link_capabilities(pkt)?;
5015            return Ok(true);
5016        }
5017        let mut st = self.state.lock();
5018        let now_ms = self.clock.now_ms();
5019        if pkt.data_type() == DataType::DiscoveryLeave {
5020            let leaving = pkt.sender();
5021            let before = st.discovery_routes.clone();
5022            for route in st.discovery_routes.values_mut() {
5023                route.announcers.remove(leaving);
5024                for sender_state in route.announcers.values_mut() {
5025                    sender_state
5026                        .topology_boards
5027                        .retain(|board| board.sender_id != leaving);
5028                    for board in sender_state.topology_boards.iter_mut() {
5029                        board.connections.retain(|peer| peer != leaving);
5030                    }
5031                    Self::refresh_sender_topology_state(sender_state);
5032                }
5033                Self::recompute_discovery_side_state(route);
5034            }
5035            st.discovery_routes
5036                .retain(|_, route| !route.announcers.is_empty());
5037            if st.discovery_routes != before {
5038                Self::note_discovery_topology_change_locked(&mut st, now_ms);
5039                self.reconcile_end_to_end_reliable_destinations_locked(&mut st)?;
5040            }
5041            return Ok(true);
5042        }
5043        let mut route = st.discovery_routes.get(&side).cloned().unwrap_or_default();
5044        let side_link_local_enabled = st
5045            .sides
5046            .get(side)
5047            .and_then(|entry| entry.as_ref())
5048            .map(|side_ref| side_ref.opts.link_local_enabled)
5049            .unwrap_or(false);
5050        let mut sender_state = route
5051            .announcers
5052            .get(pkt.sender())
5053            .cloned()
5054            .unwrap_or_default();
5055        let changed = match pkt.data_type() {
5056            DataType::DiscoveryAnnounce => {
5057                let mut reachable = discovery::decode_discovery_announce(pkt)?;
5058                if !side_link_local_enabled {
5059                    reachable.retain(|ep| !ep.is_link_local_only());
5060                }
5061                let board = Self::sender_topology_board_mut(&mut sender_state, pkt.sender());
5062                let changed = board.reachable_endpoints != reachable;
5063                board.reachable_endpoints = reachable;
5064                Self::refresh_sender_topology_state(&mut sender_state);
5065                changed
5066            }
5067            DataType::DiscoveryTimeSyncSources => {
5068                let sources = discovery::decode_discovery_timesync_sources(pkt)?;
5069                let board = Self::sender_topology_board_mut(&mut sender_state, pkt.sender());
5070                let changed = board.reachable_timesync_sources != sources;
5071                board.reachable_timesync_sources = sources;
5072                Self::refresh_sender_topology_state(&mut sender_state);
5073                changed
5074            }
5075            DataType::DiscoveryTopology => {
5076                let mut boards = discovery::decode_discovery_topology(pkt)?;
5077                if !side_link_local_enabled {
5078                    for board in boards.iter_mut() {
5079                        board
5080                            .reachable_endpoints
5081                            .retain(|ep| !ep.is_link_local_only());
5082                    }
5083                }
5084                let changed = sender_state.topology_boards != boards;
5085                sender_state.topology_boards = boards;
5086                Self::refresh_sender_topology_state(&mut sender_state);
5087                changed
5088            }
5089            DataType::DiscoverySchema => false,
5090            _ => false,
5091        };
5092        sender_state.last_seen_ms = now_ms;
5093        route
5094            .announcers
5095            .insert(pkt.sender().to_string(), sender_state);
5096        Self::recompute_discovery_side_state(&mut route);
5097        st.discovery_routes.insert(side, route);
5098        st.fit_discovery_budget();
5099        self.reconcile_end_to_end_reliable_destinations_locked(&mut st)?;
5100        if changed {
5101            Self::note_discovery_topology_change_locked(&mut st, now_ms);
5102        }
5103        Ok(true)
5104    }
5105
5106    #[cfg(not(feature = "discovery"))]
5107    fn queue_discovery_announce(&self) -> TelemetryResult<()> {
5108        Ok(())
5109    }
5110
5111    #[cfg(not(feature = "discovery"))]
5112    fn poll_discovery_announce(&self) -> TelemetryResult<bool> {
5113        Ok(false)
5114    }
5115
5116    #[cfg(not(feature = "discovery"))]
5117    fn learn_discovery_packet(
5118        &self,
5119        _pkt: &Packet,
5120        _src: Option<RouterSideId>,
5121        _called_from_queue: bool,
5122    ) -> TelemetryResult<bool> {
5123        Ok(false)
5124    }
5125
5126    #[inline]
5127    fn reliable_key(side: RouterSideId, ty: DataType) -> (RouterSideId, u32) {
5128        (side, ty.as_u32())
5129    }
5130
5131    fn reliable_tx_state_mut<'a>(
5132        &'a self,
5133        st: &'a mut RouterInner,
5134        side: RouterSideId,
5135        ty: DataType,
5136    ) -> &'a mut ReliableTxState {
5137        let key = Self::reliable_key(side, ty);
5138        st.reliable_tx
5139            .entry(key)
5140            .or_insert_with(|| ReliableTxState {
5141                next_seq: 1,
5142                sent_order: VecDeque::new(),
5143                sent: BTreeMap::new(),
5144            })
5145    }
5146
5147    fn reliable_rx_state_mut<'a>(
5148        &'a self,
5149        st: &'a mut RouterInner,
5150        side: RouterSideId,
5151        ty: DataType,
5152    ) -> &'a mut ReliableRxState {
5153        let key = Self::reliable_key(side, ty);
5154        st.reliable_rx
5155            .entry(key)
5156            .or_insert_with(|| ReliableRxState {
5157                expected_seq: 1,
5158                buffered: BTreeMap::new(),
5159            })
5160    }
5161
5162    fn reliable_control_packet(
5163        &self,
5164        control_ty: DataType,
5165        ty: DataType,
5166        seq: u32,
5167    ) -> TelemetryResult<Packet> {
5168        let sender = self.sender_arc();
5169        Packet::new(
5170            control_ty,
5171            message_meta(control_ty).endpoints,
5172            sender.as_ref(),
5173            self.packet_timestamp_ms(),
5174            encode_slice_le(&[ty.as_u32(), seq]),
5175        )
5176    }
5177
5178    fn queue_reliable_ack(
5179        &self,
5180        side: RouterSideId,
5181        ty: DataType,
5182        seq: u32,
5183        called_from_queue: bool,
5184    ) -> TelemetryResult<()> {
5185        let pkt = self.reliable_control_packet(DataType::ReliableAck, ty, seq)?;
5186        self.emit_internal_tx_with_priority(
5187            RouterTxItem::ToSide {
5188                src: None,
5189                dst: side,
5190                data: RouterItem::Packet(pkt),
5191            },
5192            true,
5193            message_priority(DataType::ReliableAck),
5194            called_from_queue,
5195        )
5196    }
5197
5198    fn queue_reliable_packet_request(
5199        &self,
5200        side: RouterSideId,
5201        ty: DataType,
5202        seq: u32,
5203        called_from_queue: bool,
5204    ) -> TelemetryResult<()> {
5205        let pkt = self.reliable_control_packet(DataType::ReliablePacketRequest, ty, seq)?;
5206        self.emit_internal_tx_with_priority(
5207            RouterTxItem::ToSide {
5208                src: None,
5209                dst: side,
5210                data: RouterItem::Packet(pkt),
5211            },
5212            true,
5213            message_priority(DataType::ReliablePacketRequest),
5214            called_from_queue,
5215        )
5216    }
5217
5218    fn queue_reliable_partial_ack(
5219        &self,
5220        side: RouterSideId,
5221        ty: DataType,
5222        seq: u32,
5223        called_from_queue: bool,
5224    ) -> TelemetryResult<()> {
5225        let pkt = self.reliable_control_packet(DataType::ReliablePartialAck, ty, seq)?;
5226        self.emit_internal_tx_with_priority(
5227            RouterTxItem::ToSide {
5228                src: None,
5229                dst: side,
5230                data: RouterItem::Packet(pkt),
5231            },
5232            true,
5233            message_priority(DataType::ReliablePartialAck),
5234            called_from_queue,
5235        )
5236    }
5237
5238    fn handle_reliable_ack(&self, side: RouterSideId, ty: DataType, ack: u32) {
5239        let mut st = self.state.lock();
5240        let tx_state = self.reliable_tx_state_mut(&mut st, side, ty);
5241        if matches!(reliable_mode(ty), crate::ReliableMode::Unordered) {
5242            tx_state.sent.remove(&ack);
5243            tx_state.sent_order.retain(|seq| *seq != ack);
5244            return;
5245        }
5246
5247        while let Some(seq) = tx_state.sent_order.front().copied() {
5248            if seq > ack {
5249                break;
5250            }
5251            tx_state.sent_order.pop_front();
5252            tx_state.sent.remove(&seq);
5253        }
5254    }
5255
5256    fn handle_reliable_partial_ack(&self, side: RouterSideId, ty: DataType, seq: u32) {
5257        let mut st = self.state.lock();
5258        let tx_state = self.reliable_tx_state_mut(&mut st, side, ty);
5259        if let Some(sent) = tx_state.sent.get_mut(&seq) {
5260            sent.partial_acked = true;
5261        }
5262    }
5263
5264    fn queue_reliable_retransmit(
5265        &self,
5266        side: RouterSideId,
5267        ty: DataType,
5268        seq: u32,
5269        called_from_queue: bool,
5270    ) -> TelemetryResult<()> {
5271        let mut queued = None;
5272        {
5273            let mut st = self.state.lock();
5274            let tx_state = self.reliable_tx_state_mut(&mut st, side, ty);
5275            if let Some(sent) = tx_state.sent.get_mut(&seq)
5276                && !sent.queued
5277            {
5278                sent.queued = true;
5279                sent.partial_acked = false;
5280                queued = Some(sent.bytes.clone());
5281            }
5282        }
5283
5284        if let Some(bytes) = queued {
5285            if called_from_queue {
5286                self.tx_queue_item_with_priority(
5287                    RouterTxItem::ReliableReplay { dst: side, bytes },
5288                    true,
5289                    Self::router_item_priority_bumped(ty),
5290                )?;
5291            } else {
5292                self.tx_item_impl(
5293                    RouterTxItem::ReliableReplay { dst: side, bytes },
5294                    true,
5295                    false,
5296                )?;
5297            }
5298        }
5299
5300        Ok(())
5301    }
5302
5303    fn send_reliable_raw_to_side(
5304        &self,
5305        side: RouterSideId,
5306        bytes: Arc<[u8]>,
5307        relayed: bool,
5308    ) -> TelemetryResult<()> {
5309        let handler = {
5310            let st = self.state.lock();
5311            let side_ref = Self::side_ref(&st, side)?;
5312            if !side_ref.opts.egress_enabled {
5313                return Ok(());
5314            }
5315            (side_ref.tx_handler.clone(), side_ref.opts)
5316        };
5317
5318        let (handler, opts) = handler;
5319
5320        let Some(_side_tx_guard) = self.try_enter_side_tx() else {
5321            return Err(TelemetryError::Io("side tx busy"));
5322        };
5323        let started_ms = self.clock.now_ms();
5324        let ty = wire_format::peek_envelope(bytes.as_ref())
5325            .map(|env| env.ty)
5326            .unwrap_or(DataType::ReliableAck);
5327        let result = match handler {
5328            RouterTxHandlerFn::Packed(f) => {
5329                let frames = self.encode_side_transport_frames(side, opts, bytes.clone())?;
5330                let mut attempts_total = 0usize;
5331                let mut sent_bytes = 0usize;
5332                for frame in frames {
5333                    match self.retry_with_attempts(MAX_HANDLER_RETRIES, || f(frame.as_ref())) {
5334                        Ok((_, attempts)) => {
5335                            attempts_total = attempts_total.saturating_add(attempts);
5336                            sent_bytes = sent_bytes.saturating_add(frame.len());
5337                        }
5338                        Err((err, attempts)) => {
5339                            self.note_side_tx_failure(
5340                                side,
5341                                ty,
5342                                attempts_total.saturating_add(attempts),
5343                            );
5344                            return Err(err);
5345                        }
5346                    }
5347                }
5348                self.record_side_tx_sample(side, sent_bytes, started_ms, self.clock.now_ms());
5349                self.note_side_tx_success(side, ty, sent_bytes, relayed, attempts_total);
5350                return Ok(());
5351            }
5352            RouterTxHandlerFn::Packet(f) => {
5353                let pkt = wire_format::unpack_packet(bytes.as_ref())?;
5354                self.retry_with_attempts(MAX_HANDLER_RETRIES, || f(&pkt))
5355            }
5356        };
5357        match result {
5358            Ok((_, attempts)) => {
5359                self.record_side_tx_sample(side, bytes.len(), started_ms, self.clock.now_ms());
5360                self.note_side_tx_success(side, ty, bytes.len(), relayed, attempts);
5361                Ok(())
5362            }
5363            Err((err, attempts)) => {
5364                self.note_side_tx_failure(side, ty, attempts);
5365                Err(err)
5366            }
5367        }
5368    }
5369
5370    fn send_reliable_to_side(
5371        &self,
5372        side: RouterSideId,
5373        data: RouterItem,
5374        relayed: bool,
5375    ) -> TelemetryResult<()> {
5376        let (handler, opts, hop_reliable_enabled) = {
5377            let st = self.state.lock();
5378            let side_ref = Self::side_ref(&st, side)?;
5379            let opts = side_ref.opts;
5380            let hop_reliable_enabled = opts.reliable_enabled
5381                && self.cfg.reliable_enabled()
5382                && !self.side_has_multiple_announcers_locked(&st, side, self.clock.now_ms());
5383            (side_ref.tx_handler.clone(), opts, hop_reliable_enabled)
5384        };
5385
5386        let RouterTxHandlerFn::Packed(f) = &handler else {
5387            return self.call_side_tx_handler(side, &handler, &data, relayed);
5388        };
5389
5390        if !hop_reliable_enabled {
5391            let mut adjusted_opts = opts;
5392            adjusted_opts.reliable_enabled = false;
5393            let preserve_end_to_end_ack = opts.reliable_enabled && self.cfg.reliable_enabled();
5394            if let Some(adjusted) =
5395                self.adjust_reliable_for_side(adjusted_opts, data, preserve_end_to_end_ack)?
5396            {
5397                return self.call_side_tx_handler(side, &handler, &adjusted, relayed);
5398            }
5399            return Ok(());
5400        }
5401
5402        let ty = match &data {
5403            RouterItem::Packet(pkt) => pkt.data_type(),
5404            RouterItem::Packed(bytes) => wire_format::peek_frame_info(bytes.as_ref())?.envelope.ty,
5405        };
5406
5407        if !is_reliable_type(ty) {
5408            if let Some(adjusted) = self.adjust_reliable_for_side(opts, data, true)? {
5409                self.call_side_tx_handler(side, &handler, &adjusted, relayed)?;
5410            }
5411            return Ok(());
5412        }
5413
5414        let (seq, flags) = {
5415            let mut st = self.state.lock();
5416            let tx_state = self.reliable_tx_state_mut(&mut st, side, ty);
5417            if tx_state.sent.len() >= RELIABLE_MAX_PENDING {
5418                return Err(TelemetryError::PacketTooLarge(
5419                    "router reliable history full",
5420                ));
5421            }
5422            let seq = tx_state.next_seq;
5423            let next = tx_state.next_seq.wrapping_add(1);
5424            tx_state.next_seq = if next == 0 { 1 } else { next };
5425            let flags = match reliable_mode(ty) {
5426                crate::ReliableMode::Unordered => wire_format::RELIABLE_FLAG_UNORDERED,
5427                _ => 0,
5428            };
5429            (seq, flags)
5430        };
5431
5432        let bytes: Arc<[u8]> = match data {
5433            RouterItem::Packet(pkt) => self.pack_packet_for_router(
5434                &pkt,
5435                Some(wire_format::ReliableHeader { flags, seq, ack: 0 }),
5436            )?,
5437            RouterItem::Packed(bytes) => {
5438                #[cfg(feature = "cryptography")]
5439                if self.e2e_seal_config_for_type(ty).is_some() {
5440                    self.prepare_packed_for_remote(
5441                        bytes,
5442                        Some(Some(wire_format::ReliableHeader { flags, seq, ack: 0 })),
5443                    )?
5444                } else {
5445                    let Some(rewritten) =
5446                        wire_format::rewrite_reliable_header_owned(bytes.as_ref(), flags, seq, 0)?
5447                    else {
5448                        let Some(_side_tx_guard) = self.try_enter_side_tx() else {
5449                            return Err(TelemetryError::Io("side tx busy"));
5450                        };
5451                        let started_ms = self.clock.now_ms();
5452                        let frames =
5453                            self.encode_side_transport_frames(side, opts, bytes.clone())?;
5454                        let mut attempts_total = 0usize;
5455                        let mut sent_bytes = 0usize;
5456                        for frame in frames {
5457                            match self
5458                                .retry_with_attempts(MAX_HANDLER_RETRIES, || f(frame.as_ref()))
5459                            {
5460                                Ok((_, attempts)) => {
5461                                    attempts_total = attempts_total.saturating_add(attempts);
5462                                    sent_bytes = sent_bytes.saturating_add(frame.len());
5463                                }
5464                                Err((err, attempts)) => {
5465                                    self.note_side_tx_failure(
5466                                        side,
5467                                        ty,
5468                                        attempts_total.saturating_add(attempts),
5469                                    );
5470                                    return Err(err);
5471                                }
5472                            }
5473                        }
5474                        self.record_side_tx_sample(
5475                            side,
5476                            sent_bytes,
5477                            started_ms,
5478                            self.clock.now_ms(),
5479                        );
5480                        self.note_side_tx_success(side, ty, sent_bytes, relayed, attempts_total);
5481                        return Ok(());
5482                    };
5483                    rewritten
5484                }
5485                #[cfg(not(feature = "cryptography"))]
5486                {
5487                    let Some(rewritten) =
5488                        wire_format::rewrite_reliable_header_owned(bytes.as_ref(), flags, seq, 0)?
5489                    else {
5490                        let Some(_side_tx_guard) = self.try_enter_side_tx() else {
5491                            return Err(TelemetryError::Io("side tx busy"));
5492                        };
5493                        let started_ms = self.clock.now_ms();
5494                        let frames =
5495                            self.encode_side_transport_frames(side, opts, bytes.clone())?;
5496                        let mut attempts_total = 0usize;
5497                        let mut sent_bytes = 0usize;
5498                        for frame in frames {
5499                            match self
5500                                .retry_with_attempts(MAX_HANDLER_RETRIES, || f(frame.as_ref()))
5501                            {
5502                                Ok((_, attempts)) => {
5503                                    attempts_total = attempts_total.saturating_add(attempts);
5504                                    sent_bytes = sent_bytes.saturating_add(frame.len());
5505                                }
5506                                Err((err, attempts)) => {
5507                                    self.note_side_tx_failure(
5508                                        side,
5509                                        ty,
5510                                        attempts_total.saturating_add(attempts),
5511                                    );
5512                                    return Err(err);
5513                                }
5514                            }
5515                        }
5516                        self.record_side_tx_sample(
5517                            side,
5518                            sent_bytes,
5519                            started_ms,
5520                            self.clock.now_ms(),
5521                        );
5522                        self.note_side_tx_success(side, ty, sent_bytes, relayed, attempts_total);
5523                        return Ok(());
5524                    };
5525                    rewritten
5526                }
5527            }
5528        };
5529
5530        let Some(_side_tx_guard) = self.try_enter_side_tx() else {
5531            return Err(TelemetryError::Io("side tx busy"));
5532        };
5533        let started_ms = self.clock.now_ms();
5534        let frames = self.encode_side_transport_frames(side, opts, bytes.clone())?;
5535        let mut attempts_total = 0usize;
5536        let mut sent_bytes = 0usize;
5537        for frame in frames {
5538            match self.retry_with_attempts(MAX_HANDLER_RETRIES, || f(frame.as_ref())) {
5539                Ok((_, attempts)) => {
5540                    attempts_total = attempts_total.saturating_add(attempts);
5541                    sent_bytes = sent_bytes.saturating_add(frame.len());
5542                }
5543                Err((err, attempts)) => {
5544                    self.note_side_tx_failure(side, ty, attempts_total.saturating_add(attempts));
5545                    return Err(err);
5546                }
5547            }
5548        }
5549        self.record_side_tx_sample(side, sent_bytes, started_ms, self.clock.now_ms());
5550        self.note_side_tx_success(side, ty, sent_bytes, relayed, attempts_total);
5551
5552        {
5553            let mut st = self.state.lock();
5554            let tx_state = self.reliable_tx_state_mut(&mut st, side, ty);
5555            tx_state.sent_order.push_back(seq);
5556            tx_state.sent.insert(
5557                seq,
5558                ReliableSent {
5559                    bytes: bytes.clone(),
5560                    last_send_ms: self.clock.now_ms(),
5561                    retries: 0,
5562                    queued: false,
5563                    partial_acked: false,
5564                },
5565            );
5566        }
5567
5568        Ok(())
5569    }
5570
5571    #[inline]
5572    fn crc32_bytes(data: &[u8]) -> u32 {
5573        let mut hasher = Crc32Hasher::new();
5574        hasher.update(data);
5575        hasher.finalize()
5576    }
5577
5578    fn read_uleb128_local(buf: &[u8], off: &mut usize) -> TelemetryResult<u64> {
5579        let mut result = 0u64;
5580        let mut shift = 0u32;
5581        for _ in 0..10 {
5582            let byte = *buf.get(*off).ok_or(TelemetryError::Unpack("short read"))?;
5583            *off += 1;
5584            result |= u64::from(byte & 0x7F) << shift;
5585            if (byte & 0x80) == 0 {
5586                return Ok(result);
5587            }
5588            shift += 7;
5589        }
5590        Err(TelemetryError::Unpack("uleb128 too long"))
5591    }
5592
5593    fn write_uleb128_local(mut value: u64, out: &mut Vec<u8>) {
5594        loop {
5595            let mut byte = (value & 0x7F) as u8;
5596            value >>= 7;
5597            if value != 0 {
5598                byte |= 0x80;
5599            }
5600            out.push(byte);
5601            if value == 0 {
5602                break;
5603            }
5604        }
5605    }
5606
5607    fn uleb128_len_local(mut value: u64) -> usize {
5608        let mut len = 1;
5609        while value >= 0x80 {
5610            value >>= 7;
5611            len += 1;
5612        }
5613        len
5614    }
5615
5616    fn wrap_side_transport_frame(kind: u8, body: &[u8]) -> Arc<[u8]> {
5617        let mut out = Vec::with_capacity(
5618            SIDE_TRANSPORT_MAGIC.len() + 1 + body.len() + wire_format::CRC32_BYTES,
5619        );
5620        out.extend_from_slice(SIDE_TRANSPORT_MAGIC);
5621        out.push(kind);
5622        out.extend_from_slice(body);
5623        let crc = Self::crc32_bytes(&out);
5624        out.extend_from_slice(&crc.to_le_bytes());
5625        Arc::from(out)
5626    }
5627
5628    fn parse_side_transport_wrapper(bytes: &[u8]) -> TelemetryResult<Option<(u8, &[u8])>> {
5629        if bytes.len() < SIDE_TRANSPORT_MAGIC.len() + 1 + wire_format::CRC32_BYTES {
5630            return Ok(None);
5631        }
5632        if &bytes[..SIDE_TRANSPORT_MAGIC.len()] != SIDE_TRANSPORT_MAGIC {
5633            return Ok(None);
5634        }
5635        let data_len = bytes.len() - wire_format::CRC32_BYTES;
5636        let expected = u32::from_le_bytes([
5637            bytes[data_len],
5638            bytes[data_len + 1],
5639            bytes[data_len + 2],
5640            bytes[data_len + 3],
5641        ]);
5642        let data = &bytes[..data_len];
5643        if Self::crc32_bytes(data) != expected {
5644            return Err(TelemetryError::Unpack("side transport crc32 mismatch"));
5645        }
5646        let kind = data[SIDE_TRANSPORT_MAGIC.len()];
5647        Ok(Some((kind, &data[SIDE_TRANSPORT_MAGIC.len() + 1..])))
5648    }
5649
5650    fn extract_side_header_template(bytes: &[u8]) -> TelemetryResult<SideTemplateExtract<'_>> {
5651        if bytes.len() < wire_format::CRC32_BYTES + 4 {
5652            return Err(TelemetryError::Unpack("short buffer"));
5653        }
5654        let data_len = bytes.len() - wire_format::CRC32_BYTES;
5655        let data = &bytes[..data_len];
5656        let mut off = 0usize;
5657        let flags = *data
5658            .get(off)
5659            .ok_or(TelemetryError::Unpack("short prelude"))?;
5660        off += 1;
5661        off += 1; // NEP
5662        let ty_end_start = off;
5663        let ty_u64 = Self::read_uleb128_local(data, &mut off)?;
5664        let ty_u32 = u32::try_from(ty_u64).map_err(|_| TelemetryError::Unpack("bad data type"))?;
5665        if ty_u32 > crate::MAX_VALUE_DATA_TYPE {
5666            return Err(TelemetryError::Unpack("bad data type"));
5667        }
5668        let ty = DataType(ty_u32);
5669        let data_size_off = off;
5670        let data_size = Self::read_uleb128_local(data, &mut off)?;
5671        let _timestamp_off = off;
5672        let timestamp = Self::read_uleb128_local(data, &mut off)?;
5673        let nonce = if (flags & SIDE_TRANSPORT_FLAG_PACKET_NONCE) != 0 {
5674            u16::try_from(Self::read_uleb128_local(data, &mut off)?)
5675                .map_err(|_| TelemetryError::Unpack("packet nonce too large"))?
5676        } else {
5677            0
5678        };
5679        let between_start = off;
5680        let _source_address = u32::try_from(Self::read_uleb128_local(data, &mut off)?)
5681            .map_err(|_| TelemetryError::Unpack("source address too large"))?;
5682        let endpoint_bitmap_bytes = if (flags & SIDE_TRANSPORT_FLAG_ENDPOINT_BITMAP_PRESENT) != 0 {
5683            SIDE_TRANSPORT_EP_BITMAP_BYTES
5684        } else {
5685            0
5686        };
5687        if data.len() < off + endpoint_bitmap_bytes {
5688            return Err(TelemetryError::Unpack("short buffer"));
5689        }
5690        off += endpoint_bitmap_bytes;
5691        if (flags & SIDE_TRANSPORT_FLAG_WIRE_CONTRACT) != 0 {
5692            let contract_len = usize::try_from(Self::read_uleb128_local(data, &mut off)?)
5693                .map_err(|_| TelemetryError::Unpack("wire contract length"))?;
5694            if data.len() < off + contract_len {
5695                return Err(TelemetryError::Unpack("short buffer"));
5696            }
5697            off += contract_len;
5698        }
5699        let reliable_span = wire_format::reliable_header_span(bytes)?;
5700        let (reliable_flags, reliable_seq_ack, reliable_compact, payload_off) =
5701            if let Some((rel_off, rel_len, hdr)) = reliable_span {
5702                if data.len() < rel_off + rel_len {
5703                    return Err(TelemetryError::Unpack("short buffer"));
5704                }
5705                (
5706                    Some(hdr.flags),
5707                    Some((hdr.seq, hdr.ack)),
5708                    (flags & SIDE_TRANSPORT_FLAG_COMPACT_RELIABLE_HEADER) != 0,
5709                    rel_off + rel_len,
5710                )
5711            } else {
5712                (None, None, false, off)
5713            };
5714        if payload_off > data.len() {
5715            return Err(TelemetryError::Unpack("short buffer"));
5716        }
5717        let payload = &data[payload_off..];
5718        let prefix = Arc::<[u8]>::from(&data[1..data_size_off]);
5719        let between_end = reliable_span
5720            .map(|(rel_off, _, _)| rel_off)
5721            .unwrap_or(payload_off);
5722        let between = Arc::<[u8]>::from(&data[between_start..between_end]);
5723        let base_flags =
5724            flags & !(SIDE_TRANSPORT_FLAG_PAYLOAD_COMPRESSED | SIDE_TRANSPORT_FLAG_PACKET_NONCE);
5725        let mut hash = 0xD1B5_4A32_9C7E_01F3u64;
5726        hash = hash_bytes_u64(hash, &[base_flags]);
5727        hash = hash_bytes_u64(hash, &prefix);
5728        hash = hash_bytes_u64(hash, &between);
5729        if let Some(rel_flags) = reliable_flags {
5730            hash = hash_bytes_u64(hash, &[rel_flags]);
5731        }
5732        let template = SideHeaderTemplate {
5733            hash,
5734            base_flags,
5735            prefix,
5736            between,
5737            reliable_flags,
5738            reliable_compact,
5739        };
5740        let _ = ty_end_start;
5741        Ok((
5742            template,
5743            ty,
5744            flags,
5745            data_size,
5746            timestamp,
5747            nonce,
5748            reliable_seq_ack,
5749            payload,
5750        ))
5751    }
5752
5753    fn reconstruct_side_compact_frame(
5754        template: &SideHeaderTemplate,
5755        body: &[u8],
5756        timestamp_mode: SideCompactTimestampMode,
5757        timestamp_base: Option<u64>,
5758    ) -> TelemetryResult<(Arc<[u8]>, u64)> {
5759        if body.is_empty() {
5760            return Err(TelemetryError::Unpack("short side compact frame"));
5761        }
5762        let mut off = 0usize;
5763        let flags = body[off];
5764        off += 1;
5765        if (flags & !(SIDE_TRANSPORT_FLAG_PAYLOAD_COMPRESSED | SIDE_TRANSPORT_FLAG_PACKET_NONCE))
5766            != template.base_flags
5767        {
5768            return Err(TelemetryError::Unpack("side compact flags mismatch"));
5769        }
5770        let data_size = Self::read_uleb128_local(body, &mut off)?;
5771        let timestamp = match timestamp_mode {
5772            SideCompactTimestampMode::Absolute => Self::read_uleb128_local(body, &mut off)?,
5773            SideCompactTimestampMode::Delta => {
5774                let timestamp_field = Self::read_uleb128_local(body, &mut off)?;
5775                let base = timestamp_base.ok_or(TelemetryError::Unpack(
5776                    "missing side compact timestamp context",
5777                ))?;
5778                base.checked_add(timestamp_field)
5779                    .ok_or(TelemetryError::Unpack(
5780                        "side compact timestamp delta overflow",
5781                    ))?
5782            }
5783            SideCompactTimestampMode::Omitted => timestamp_base.ok_or(TelemetryError::Unpack(
5784                "missing side compact timestamp context",
5785            ))?,
5786        };
5787        let nonce = if (flags & SIDE_TRANSPORT_FLAG_PACKET_NONCE) != 0 {
5788            Some(Self::read_uleb128_local(body, &mut off)?)
5789        } else {
5790            None
5791        };
5792        let reliable_seq_ack = if template.reliable_flags.is_some() {
5793            let seq = u32::try_from(Self::read_uleb128_local(body, &mut off)?)
5794                .map_err(|_| TelemetryError::Unpack("side compact reliable seq too large"))?;
5795            let ack = u32::try_from(Self::read_uleb128_local(body, &mut off)?)
5796                .map_err(|_| TelemetryError::Unpack("side compact reliable ack too large"))?;
5797            Some((seq, ack))
5798        } else {
5799            None
5800        };
5801        let payload = &body[off..];
5802        let mut raw = Vec::with_capacity(
5803            1 + template.prefix.len() + template.between.len() + payload.len() + 32,
5804        );
5805        raw.push(flags);
5806        raw.extend_from_slice(&template.prefix);
5807        Self::write_uleb128_local(data_size, &mut raw);
5808        Self::write_uleb128_local(timestamp, &mut raw);
5809        if let Some(nonce) = nonce {
5810            Self::write_uleb128_local(nonce, &mut raw);
5811        }
5812        raw.extend_from_slice(&template.between);
5813        if let Some(rel_flags) = template.reliable_flags {
5814            let (seq, ack) =
5815                reliable_seq_ack.ok_or(TelemetryError::Unpack("missing side compact reliable"))?;
5816            wire_format::write_reliable_header_encoded(
5817                wire_format::ReliableHeader {
5818                    flags: rel_flags,
5819                    seq,
5820                    ack,
5821                },
5822                template.reliable_compact,
5823                &mut raw,
5824            );
5825        }
5826        raw.extend_from_slice(payload);
5827        let crc = Self::crc32_bytes(&raw);
5828        raw.extend_from_slice(&crc.to_le_bytes());
5829        Ok((Arc::from(raw), timestamp))
5830    }
5831    fn split_side_transport_frame(
5832        &self,
5833        side: RouterSideId,
5834        frame: Arc<[u8]>,
5835        max_frame_bytes: usize,
5836    ) -> TelemetryResult<Vec<Arc<[u8]>>> {
5837        if max_frame_bytes <= SIDE_TRANSPORT_CHUNK_OVERHEAD {
5838            return Err(TelemetryError::BadArg);
5839        }
5840        let payload_budget = max_frame_bytes - SIDE_TRANSPORT_CHUNK_OVERHEAD;
5841        let mut st = self.state.lock();
5842        let side_state = st
5843            .side_transport
5844            .get_mut(&side)
5845            .ok_or(TelemetryError::BadArg)?;
5846        let transfer_id = side_state.next_chunk_id.wrapping_add(1).max(1);
5847        side_state.next_chunk_id = transfer_id;
5848        drop(st);
5849
5850        let total = frame.len().div_ceil(payload_budget);
5851        let total_u16 =
5852            u16::try_from(total).map_err(|_| TelemetryError::PacketTooLarge("too many chunks"))?;
5853        let mut frames = Vec::with_capacity(total);
5854        for (idx, chunk) in frame.chunks(payload_budget).enumerate() {
5855            let mut body = Vec::with_capacity(8 + chunk.len());
5856            body.extend_from_slice(&transfer_id.to_le_bytes());
5857            body.extend_from_slice(&(idx as u16).to_le_bytes());
5858            body.extend_from_slice(&total_u16.to_le_bytes());
5859            body.extend_from_slice(chunk);
5860            frames.push(Self::wrap_side_transport_frame(
5861                SIDE_TRANSPORT_KIND_CHUNK,
5862                &body,
5863            ));
5864        }
5865        Ok(frames)
5866    }
5867
5868    fn encode_side_transport_frames(
5869        &self,
5870        side: RouterSideId,
5871        opts: RouterSideOptions,
5872        raw: Arc<[u8]>,
5873    ) -> TelemetryResult<Vec<Arc<[u8]>>> {
5874        if !opts.header_template_enabled && opts.max_frame_bytes == 0 {
5875            return Ok(vec![raw]);
5876        }
5877
5878        let raw_len = raw.len();
5879        let mut compact_payload_len = None;
5880        let mut used_compact = false;
5881        let mut used_timestamp_delta = false;
5882        let mut omitted_timestamp = false;
5883        let wrapped = if opts.header_template_enabled {
5884            let (template, ty, flags, data_size, timestamp, nonce, reliable_seq_ack, payload) =
5885                Self::extract_side_header_template(raw.as_ref())?;
5886            let (template_id, use_compact, previous_timestamp) = {
5887                let mut st = self.state.lock();
5888                let side_state = st
5889                    .side_transport
5890                    .get_mut(&side)
5891                    .ok_or(TelemetryError::BadArg)?;
5892                if let Some(id) = side_state.tx_template_ids.get(&template.hash).copied() {
5893                    let previous = side_state.tx_last_timestamps.get(&id).copied();
5894                    (id, true, previous)
5895                } else {
5896                    let next = side_state.next_template_id.wrapping_add(1).max(1);
5897                    side_state.next_template_id = next;
5898                    let evicted = side_state.insert_tx_template(
5899                        template.clone(),
5900                        next,
5901                        opts.max_side_transport_templates,
5902                    );
5903                    if evicted {
5904                        st.side_runtime_stats
5905                            .entry(side)
5906                            .or_default()
5907                            .note_side_transport_template_eviction();
5908                    }
5909                    if let Some(side_state) = st.side_transport.get_mut(&side) {
5910                        side_state.tx_last_timestamps.insert(next, timestamp);
5911                    }
5912                    (next, false, None)
5913                }
5914            };
5915            if use_compact {
5916                used_compact = true;
5917                compact_payload_len = Some(payload.len());
5918                let timestamp_field = if let Some(previous) = previous_timestamp {
5919                    let delta = timestamp.saturating_sub(previous);
5920                    let omit_timestamp = opts.omit_unchanged_compact_timestamps
5921                        || opts.compact_timestamp_omission_types.contains(ty);
5922                    if omit_timestamp && timestamp == previous {
5923                        omitted_timestamp = true;
5924                        None
5925                    } else if timestamp >= previous
5926                        && Self::uleb128_len_local(delta) < Self::uleb128_len_local(timestamp)
5927                    {
5928                        used_timestamp_delta = true;
5929                        Some(delta)
5930                    } else {
5931                        Some(timestamp)
5932                    }
5933                } else {
5934                    Some(timestamp)
5935                };
5936                let mut body = Vec::with_capacity(payload.len() + 32);
5937                body.push(flags);
5938                Self::write_uleb128_local(u64::from(template_id), &mut body);
5939                Self::write_uleb128_local(data_size, &mut body);
5940                if let Some(timestamp_field) = timestamp_field {
5941                    Self::write_uleb128_local(timestamp_field, &mut body);
5942                }
5943                if (flags & SIDE_TRANSPORT_FLAG_PACKET_NONCE) != 0 {
5944                    Self::write_uleb128_local(u64::from(nonce), &mut body);
5945                }
5946                if let Some((seq, ack)) = reliable_seq_ack {
5947                    Self::write_uleb128_local(u64::from(seq), &mut body);
5948                    Self::write_uleb128_local(u64::from(ack), &mut body);
5949                }
5950                body.extend_from_slice(payload);
5951                {
5952                    let mut st = self.state.lock();
5953                    if let Some(side_state) = st.side_transport.get_mut(&side) {
5954                        side_state.tx_last_timestamps.insert(template_id, timestamp);
5955                    }
5956                }
5957                let kind = if omitted_timestamp {
5958                    SIDE_TRANSPORT_KIND_COMPACT_SAME_TIMESTAMP
5959                } else if used_timestamp_delta {
5960                    SIDE_TRANSPORT_KIND_COMPACT_DELTA
5961                } else {
5962                    SIDE_TRANSPORT_KIND_COMPACT
5963                };
5964                Self::wrap_side_transport_frame(kind, &body)
5965            } else {
5966                let mut body = Vec::with_capacity(raw.len() + 4);
5967                Self::write_uleb128_local(u64::from(template_id), &mut body);
5968                body.extend_from_slice(raw.as_ref());
5969                Self::wrap_side_transport_frame(SIDE_TRANSPORT_KIND_FULL, &body)
5970            }
5971        } else {
5972            Self::wrap_side_transport_frame(SIDE_TRANSPORT_KIND_FULL, raw.as_ref())
5973        };
5974
5975        let frames = if opts.max_frame_bytes != 0 && wrapped.len() > opts.max_frame_bytes {
5976            self.split_side_transport_frame(side, wrapped, opts.max_frame_bytes)
5977        } else {
5978            Ok(vec![wrapped])
5979        }?;
5980        let wire_len = frames.iter().map(|frame| frame.len()).sum::<usize>();
5981        let mut st = self.state.lock();
5982        let stats = st.side_runtime_stats.entry(side).or_default();
5983        if used_compact {
5984            let overhead = compact_payload_len
5985                .map(|payload_len| wire_len.saturating_sub(payload_len))
5986                .unwrap_or(wire_len);
5987            stats.note_side_transport_compact(
5988                raw_len,
5989                wire_len,
5990                overhead,
5991                used_timestamp_delta,
5992                omitted_timestamp,
5993            );
5994            if opts.compact_header_target_bytes != 0 && overhead > opts.compact_header_target_bytes
5995            {
5996                stats.note_side_transport_compact_target_miss();
5997            }
5998        } else {
5999            stats.note_side_transport_full(raw_len, wire_len);
6000        }
6001        if frames.len() > 1 {
6002            stats.note_side_transport_chunks(frames.len());
6003        }
6004        Ok(frames)
6005    }
6006
6007    fn decode_side_transport_frame(
6008        &self,
6009        side: RouterSideId,
6010        bytes: &[u8],
6011    ) -> TelemetryResult<Option<Arc<[u8]>>> {
6012        let Some((kind, body)) = Self::parse_side_transport_wrapper(bytes)? else {
6013            return Ok(Some(Arc::from(bytes)));
6014        };
6015        match kind {
6016            SIDE_TRANSPORT_KIND_FULL => {
6017                let mut off = 0usize;
6018                let template_id = u32::try_from(Self::read_uleb128_local(body, &mut off)?)
6019                    .map_err(|_| TelemetryError::Unpack("side template id too large"))?;
6020                let raw = Arc::<[u8]>::from(&body[off..]);
6021                if let Ok((template, _, _, _, timestamp, _, _, _)) =
6022                    Self::extract_side_header_template(raw.as_ref())
6023                {
6024                    let mut st = self.state.lock();
6025                    let max_templates = st
6026                        .sides
6027                        .get(side)
6028                        .and_then(|side| side.as_ref())
6029                        .map(|side| side.opts.max_side_transport_templates)
6030                        .unwrap_or(DEFAULT_SIDE_TRANSPORT_TEMPLATE_LIMIT);
6031                    let evicted = st.side_transport.get_mut(&side).is_some_and(|side_state| {
6032                        let evicted =
6033                            side_state.insert_rx_template(template_id, template, max_templates);
6034                        side_state.rx_last_timestamps.insert(template_id, timestamp);
6035                        evicted
6036                    });
6037                    if evicted {
6038                        st.side_runtime_stats
6039                            .entry(side)
6040                            .or_default()
6041                            .note_side_transport_template_eviction();
6042                    }
6043                }
6044                Ok(Some(raw))
6045            }
6046            SIDE_TRANSPORT_KIND_COMPACT
6047            | SIDE_TRANSPORT_KIND_COMPACT_DELTA
6048            | SIDE_TRANSPORT_KIND_COMPACT_SAME_TIMESTAMP => {
6049                if body.is_empty() {
6050                    return Err(TelemetryError::Unpack("short side compact frame"));
6051                }
6052                let mut off = 1usize;
6053                let template_id = u32::try_from(Self::read_uleb128_local(body, &mut off)?)
6054                    .map_err(|_| TelemetryError::Unpack("side template id too large"))?;
6055                let mut compact_body = Vec::with_capacity(1 + body.len().saturating_sub(off));
6056                compact_body.push(body[0]);
6057                compact_body.extend_from_slice(&body[off..]);
6058                let (template, timestamp_base) = {
6059                    let st = self.state.lock();
6060                    let state = st.side_transport.get(&side);
6061                    let template = state
6062                        .and_then(|state| state.rx_templates_by_id.get(&template_id))
6063                        .cloned();
6064                    let timestamp_base = if matches!(
6065                        kind,
6066                        SIDE_TRANSPORT_KIND_COMPACT_DELTA
6067                            | SIDE_TRANSPORT_KIND_COMPACT_SAME_TIMESTAMP
6068                    ) {
6069                        state
6070                            .and_then(|state| state.rx_last_timestamps.get(&template_id))
6071                            .copied()
6072                    } else {
6073                        None
6074                    };
6075                    (template, timestamp_base)
6076                };
6077                let template =
6078                    template.ok_or(TelemetryError::Unpack("unknown side compact template"))?;
6079                let timestamp_mode = match kind {
6080                    SIDE_TRANSPORT_KIND_COMPACT_DELTA => SideCompactTimestampMode::Delta,
6081                    SIDE_TRANSPORT_KIND_COMPACT_SAME_TIMESTAMP => SideCompactTimestampMode::Omitted,
6082                    _ => SideCompactTimestampMode::Absolute,
6083                };
6084                let (frame, timestamp) = Self::reconstruct_side_compact_frame(
6085                    &template,
6086                    &compact_body,
6087                    timestamp_mode,
6088                    timestamp_base,
6089                )?;
6090                let mut st = self.state.lock();
6091                if let Some(side_state) = st.side_transport.get_mut(&side) {
6092                    side_state.rx_last_timestamps.insert(template_id, timestamp);
6093                }
6094                Ok(Some(frame))
6095            }
6096            SIDE_TRANSPORT_KIND_CHUNK => {
6097                if body.len() < 8 {
6098                    return Err(TelemetryError::Unpack("short side chunk frame"));
6099                }
6100                let transfer_id = u32::from_le_bytes([body[0], body[1], body[2], body[3]]);
6101                let index = u16::from_le_bytes([body[4], body[5]]);
6102                let total = u16::from_le_bytes([body[6], body[7]]);
6103                let payload = Arc::<[u8]>::from(&body[8..]);
6104                let assembled = {
6105                    let mut st = self.state.lock();
6106                    let side_state = st
6107                        .side_transport
6108                        .get_mut(&side)
6109                        .ok_or(TelemetryError::BadArg)?;
6110                    let entry = side_state.rx_chunks.entry(transfer_id).or_default();
6111                    if entry.total == 0 {
6112                        entry.total = total;
6113                    } else if entry.total != total {
6114                        side_state.rx_chunks.remove(&transfer_id);
6115                        return Err(TelemetryError::Unpack("side chunk total mismatch"));
6116                    }
6117                    entry.received.entry(index).or_insert(payload);
6118                    if entry.received.len() == usize::from(total) {
6119                        let entry = side_state
6120                            .rx_chunks
6121                            .remove(&transfer_id)
6122                            .ok_or(TelemetryError::Unpack("side chunk missing"))?;
6123                        let mut out = Vec::new();
6124                        for idx in 0..entry.total {
6125                            let chunk = entry
6126                                .received
6127                                .get(&idx)
6128                                .ok_or(TelemetryError::Unpack("side chunk gap"))?;
6129                            out.extend_from_slice(chunk);
6130                        }
6131                        Some(Arc::<[u8]>::from(out))
6132                    } else {
6133                        None
6134                    }
6135                };
6136                match assembled {
6137                    Some(frame) => self.decode_side_transport_frame(side, frame.as_ref()),
6138                    None => Ok(None),
6139                }
6140            }
6141            _ => Err(TelemetryError::Unpack("unknown side transport frame")),
6142        }
6143    }
6144
6145    fn call_side_tx_handler(
6146        &self,
6147        side: RouterSideId,
6148        handler: &RouterTxHandlerFn,
6149        data: &RouterItem,
6150        relayed: bool,
6151    ) -> TelemetryResult<()> {
6152        let opts = {
6153            let st = self.state.lock();
6154            Self::side_ref(&st, side)?.opts
6155        };
6156        let Some(_side_tx_guard) = self.try_enter_side_tx() else {
6157            return Err(TelemetryError::Io("side tx busy"));
6158        };
6159        let started_ms = self.clock.now_ms();
6160        let ty = match data {
6161            RouterItem::Packet(pkt) => pkt.data_type(),
6162            RouterItem::Packed(bytes) => wire_format::peek_envelope(bytes.as_ref())?.ty,
6163        };
6164        let result = match (handler, data) {
6165            (RouterTxHandlerFn::Packed(f), RouterItem::Packed(bytes)) => {
6166                #[cfg(feature = "cryptography")]
6167                let send_bytes = self.prepare_packed_for_remote(bytes.clone(), None)?;
6168                #[cfg(not(feature = "cryptography"))]
6169                let send_bytes = bytes.clone();
6170                let frames = self.encode_side_transport_frames(side, opts, send_bytes)?;
6171                let mut attempts_total = 0usize;
6172                let mut sent_bytes = 0usize;
6173                for frame in frames {
6174                    match self.retry_with_attempts(MAX_HANDLER_RETRIES, || f(frame.as_ref())) {
6175                        Ok((_, attempts)) => {
6176                            attempts_total = attempts_total.saturating_add(attempts);
6177                            sent_bytes = sent_bytes.saturating_add(frame.len());
6178                        }
6179                        Err((err, attempts)) => {
6180                            self.note_side_tx_failure(
6181                                side,
6182                                ty,
6183                                attempts_total.saturating_add(attempts),
6184                            );
6185                            return Err(err);
6186                        }
6187                    }
6188                }
6189                self.record_side_tx_sample(side, sent_bytes, started_ms, self.clock.now_ms());
6190                self.note_side_tx_success(side, ty, sent_bytes, relayed, attempts_total);
6191                return Ok(());
6192            }
6193            (RouterTxHandlerFn::Packet(f), RouterItem::Packet(pkt)) => {
6194                self.retry_with_attempts(MAX_HANDLER_RETRIES, || f(pkt))
6195            }
6196            (RouterTxHandlerFn::Packed(f), RouterItem::Packet(pkt)) => {
6197                let owned = self.pack_packet_for_router(pkt, None)?;
6198                let frames = self.encode_side_transport_frames(side, opts, owned)?;
6199                let mut attempts_total = 0usize;
6200                let mut sent_bytes = 0usize;
6201                for frame in frames {
6202                    match self.retry_with_attempts(MAX_HANDLER_RETRIES, || f(frame.as_ref())) {
6203                        Ok((_, attempts)) => {
6204                            attempts_total = attempts_total.saturating_add(attempts);
6205                            sent_bytes = sent_bytes.saturating_add(frame.len());
6206                        }
6207                        Err((err, attempts)) => {
6208                            self.note_side_tx_failure(
6209                                side,
6210                                ty,
6211                                attempts_total.saturating_add(attempts),
6212                            );
6213                            return Err(err);
6214                        }
6215                    }
6216                }
6217                self.record_side_tx_sample(side, sent_bytes, started_ms, self.clock.now_ms());
6218                self.note_side_tx_success(side, ty, sent_bytes, relayed, attempts_total);
6219                return Ok(());
6220            }
6221            (RouterTxHandlerFn::Packet(f), RouterItem::Packed(bytes)) => {
6222                let pkt = wire_format::unpack_packet(bytes.as_ref())?;
6223                self.retry_with_attempts(MAX_HANDLER_RETRIES, || f(&pkt))
6224            }
6225        };
6226        match result {
6227            Ok((_, attempts)) => {
6228                if let Ok(bytes) = Self::router_item_wire_len(data) {
6229                    self.record_side_tx_sample(side, bytes, started_ms, self.clock.now_ms());
6230                    self.note_side_tx_success(side, ty, bytes, relayed, attempts);
6231                }
6232                Ok(())
6233            }
6234            Err((err, attempts)) => {
6235                self.note_side_tx_failure(side, ty, attempts);
6236                Err(err)
6237            }
6238        }
6239    }
6240
6241    fn adjust_reliable_for_side(
6242        &self,
6243        opts: RouterSideOptions,
6244        data: RouterItem,
6245        preserve_end_to_end_ack: bool,
6246    ) -> TelemetryResult<Option<RouterItem>> {
6247        if opts.reliable_enabled {
6248            return Ok(Some(data));
6249        }
6250
6251        match data {
6252            RouterItem::Packed(bytes) => {
6253                let frame = wire_format::peek_frame_info(bytes.as_ref())?;
6254                if is_reliable_type(frame.envelope.ty)
6255                    && let Some(hdr) = frame.reliable
6256                {
6257                    if (hdr.flags & wire_format::RELIABLE_FLAG_ACK_ONLY) != 0 {
6258                        return Ok(None);
6259                    }
6260                    if (hdr.flags & wire_format::RELIABLE_FLAG_UNSEQUENCED) == 0 {
6261                        let Some(rewritten) = wire_format::rewrite_reliable_header_owned(
6262                            bytes.as_ref(),
6263                            wire_format::RELIABLE_FLAG_UNSEQUENCED,
6264                            hdr.seq,
6265                            0,
6266                        )?
6267                        else {
6268                            return Ok(Some(RouterItem::Packed(bytes)));
6269                        };
6270                        return Ok(Some(RouterItem::Packed(rewritten)));
6271                    }
6272                }
6273                Ok(Some(RouterItem::Packed(bytes)))
6274            }
6275            RouterItem::Packet(pkt) => {
6276                if matches!(
6277                    pkt.data_type(),
6278                    DataType::ReliableAck
6279                        | DataType::ReliablePartialAck
6280                        | DataType::ReliablePacketRequest
6281                ) {
6282                    if preserve_end_to_end_ack
6283                        && pkt.data_type() == DataType::ReliableAck
6284                        && Self::is_end_to_end_ack_sender(pkt.sender())
6285                    {
6286                        return Ok(Some(RouterItem::Packet(pkt)));
6287                    }
6288                    return Ok(None);
6289                }
6290                Ok(Some(RouterItem::Packet(pkt)))
6291            }
6292        }
6293    }
6294
6295    fn process_reliable_timeouts(&self) -> TelemetryResult<()> {
6296        let now = self.clock.now_ms();
6297        let mut requeue: Vec<(RouterSideId, DataType, u32)> = Vec::new();
6298
6299        {
6300            let mut st = self.state.lock();
6301            if st.reliable_tx.is_empty() {
6302                return Ok(());
6303            }
6304
6305            for ((side, ty_u32), tx_state) in st.reliable_tx.iter_mut() {
6306                let Some(ty) = DataType::try_from_u32(*ty_u32) else {
6307                    continue;
6308                };
6309                let sent_order: Vec<u32> = tx_state.sent_order.iter().copied().collect();
6310                for seq in sent_order {
6311                    let Some(sent) = tx_state.sent.get_mut(&seq) else {
6312                        continue;
6313                    };
6314                    if sent.queued || now.wrapping_sub(sent.last_send_ms) < RELIABLE_RETRANSMIT_MS {
6315                        continue;
6316                    }
6317                    if sent.partial_acked {
6318                        continue;
6319                    }
6320                    if sent.retries >= RELIABLE_MAX_RETRIES {
6321                        tx_state.sent.remove(&seq);
6322                        tx_state.sent_order.retain(|existing| *existing != seq);
6323                        continue;
6324                    }
6325                    sent.retries += 1;
6326                    requeue.push((*side, ty, seq));
6327                }
6328            }
6329        }
6330
6331        for (side, ty, seq) in requeue {
6332            self.queue_reliable_retransmit(side, ty, seq, true)?;
6333        }
6334
6335        Ok(())
6336    }
6337
6338    fn process_end_to_end_reliable_timeouts(&self) -> TelemetryResult<()> {
6339        let now = self.clock.now_ms();
6340        let mut requeue = Vec::new();
6341
6342        {
6343            let mut st = self.state.lock();
6344            #[cfg(feature = "discovery")]
6345            {
6346                if Self::prune_discovery_routes_locked(&mut st, now) {
6347                    Self::note_discovery_topology_change_locked(&mut st, now);
6348                }
6349                self.reconcile_end_to_end_reliable_destinations_locked(&mut st)?;
6350            }
6351            let packet_ids: Vec<u64> = st.end_to_end_reliable_tx.keys().copied().collect();
6352            for packet_id in packet_ids {
6353                let Some(sent) = st.end_to_end_reliable_tx.get_mut(&packet_id) else {
6354                    continue;
6355                };
6356                if sent.queued || now.wrapping_sub(sent.last_send_ms) < RELIABLE_RETRANSMIT_MS {
6357                    continue;
6358                }
6359                if sent.retries >= RELIABLE_MAX_RETRIES {
6360                    st.end_to_end_reliable_tx.remove(&packet_id);
6361                    continue;
6362                }
6363                sent.retries += 1;
6364                requeue.push(packet_id);
6365            }
6366        }
6367
6368        for packet_id in requeue {
6369            self.queue_end_to_end_reliable_retransmit(packet_id)?;
6370        }
6371
6372        Ok(())
6373    }
6374
6375    #[cfg(feature = "timesync")]
6376    #[inline]
6377    fn monotonic_now_ns(&self) -> u64 {
6378        self.clock.now_ns()
6379    }
6380
6381    #[cfg(feature = "timesync")]
6382    #[inline]
6383    fn monotonic_now_ms(&self) -> u64 {
6384        self.clock.now_ms()
6385    }
6386
6387    #[cfg(feature = "timesync")]
6388    fn refresh_timesync_state(&self, now_mono_ms: u64) {
6389        let now_mono_ns = self.monotonic_now_ns();
6390        let mut st = self.timesync.lock();
6391        st.clock.prune_expired(now_mono_ms);
6392        let timeout_ms = st.cfg.map(|cfg| cfg.source_timeout_ms).unwrap_or(0);
6393        st.remote_sources
6394            .retain(|_, src| now_mono_ms.saturating_sub(src.last_sample_mono_ms) <= timeout_ms);
6395        let has_usable_time = Self::timesync_has_usable_time_locked(&st, now_mono_ns);
6396        let leader = if let Some(tracker) = st.tracker.as_mut() {
6397            let _ = tracker.refresh(now_mono_ms);
6398            tracker.leader(now_mono_ms, has_usable_time)
6399        } else {
6400            None
6401        };
6402        Self::reconcile_pending_timesync_request_locked(&mut st, &leader, now_mono_ms);
6403        if let Some(TimeSyncLeader::Remote(remote)) = leader.as_ref() {
6404            let target_ms = st
6405                .remote_sources
6406                .get(remote.sender.as_str())
6407                .map(|src| src.sample_unix_ms);
6408            if let Some(target_ms) = target_ms {
6409                st.disciplined_clock.steer_unix_ms(now_mono_ns, target_ms);
6410            }
6411        }
6412    }
6413
6414    #[cfg(feature = "timesync")]
6415    /// Inserts or updates a named network-time source with an optional expiration TTL.
6416    pub fn update_network_time_source(
6417        &self,
6418        source: &str,
6419        priority: u64,
6420        time: PartialNetworkTime,
6421        ttl_ms: Option<u64>,
6422    ) {
6423        let now_ms = self.monotonic_now_ms();
6424        let now_ns = self.monotonic_now_ns();
6425        let mut st = self.timesync.lock();
6426        st.clock
6427            .update_source(source, priority, time, now_ms, now_ns, ttl_ms);
6428        if let Some(unix_ms) = time.to_network_time().and_then(|t| t.as_unix_ms()) {
6429            st.disciplined_clock.steer_unix_ms(now_ns, unix_ms);
6430        }
6431    }
6432
6433    #[cfg(feature = "timesync")]
6434    fn set_network_time_source_impl(
6435        &self,
6436        source: &str,
6437        priority: u64,
6438        time: PartialNetworkTime,
6439        ttl_ms: Option<u64>,
6440    ) {
6441        let observed_mono_ms = self.monotonic_now_ms();
6442        let observed_mono_ns = self.monotonic_now_ns();
6443        let mut st = self.timesync.lock();
6444        let commit_mono_ms = self.monotonic_now_ms();
6445        let commit_mono_ns = self.monotonic_now_ns();
6446        let adjusted = if let Some(base) = time.to_network_time() {
6447            let elapsed_ns = commit_mono_ns.saturating_sub(observed_mono_ns);
6448            advance_network_time(base, elapsed_ns)
6449                .map(PartialNetworkTime::from)
6450                .unwrap_or(time)
6451        } else {
6452            time
6453        };
6454        let adjusted_mono_ms =
6455            observed_mono_ms.saturating_add(commit_mono_ms.saturating_sub(observed_mono_ms));
6456        st.clock.update_source(
6457            source,
6458            priority,
6459            adjusted,
6460            commit_mono_ms.max(adjusted_mono_ms),
6461            commit_mono_ns,
6462            ttl_ms,
6463        );
6464        if let Some(unix_ms) = adjusted.to_network_time().and_then(|t| t.as_unix_ms()) {
6465            st.disciplined_clock.steer_unix_ms(commit_mono_ns, unix_ms);
6466        }
6467    }
6468
6469    #[cfg(feature = "timesync")]
6470    fn local_network_time_priority(&self) -> u64 {
6471        let st = self.timesync.lock();
6472        st.cfg.map(|cfg| cfg.priority).unwrap_or(0)
6473    }
6474
6475    #[cfg(feature = "timesync")]
6476    /// Sets the local node's network time using any combination of date, time, and sub-second fields.
6477    pub fn set_local_network_time(&self, time: PartialNetworkTime) {
6478        let priority = self.local_network_time_priority();
6479        if time.is_complete_date() && time.is_complete_time() {
6480            self.set_network_time_source_impl(LOCAL_TIMESYNC_FULL_SOURCE_ID, priority, time, None);
6481            let mut st = self.timesync.lock();
6482            st.clock.remove_source(LOCAL_TIMESYNC_DATE_SOURCE_ID);
6483            st.clock.remove_source(LOCAL_TIMESYNC_TOD_SOURCE_ID);
6484            st.clock.remove_source(LOCAL_TIMESYNC_SUBSEC_SOURCE_ID);
6485            return;
6486        }
6487
6488        {
6489            let mut st = self.timesync.lock();
6490            st.clock.remove_source(LOCAL_TIMESYNC_FULL_SOURCE_ID);
6491        }
6492
6493        if time.year.is_some() || time.month.is_some() || time.day.is_some() {
6494            self.set_network_time_source_impl(
6495                LOCAL_TIMESYNC_DATE_SOURCE_ID,
6496                priority,
6497                PartialNetworkTime {
6498                    year: time.year,
6499                    month: time.month,
6500                    day: time.day,
6501                    ..Default::default()
6502                },
6503                None,
6504            );
6505        }
6506
6507        if time.hour.is_some() || time.minute.is_some() || time.second.is_some() {
6508            self.set_network_time_source_impl(
6509                LOCAL_TIMESYNC_TOD_SOURCE_ID,
6510                priority,
6511                PartialNetworkTime {
6512                    hour: time.hour,
6513                    minute: time.minute,
6514                    second: time.second,
6515                    nanosecond: time.nanosecond,
6516                    ..Default::default()
6517                },
6518                None,
6519            );
6520        }
6521
6522        if time.nanosecond.is_some() {
6523            self.set_network_time_source_impl(
6524                LOCAL_TIMESYNC_SUBSEC_SOURCE_ID,
6525                priority,
6526                PartialNetworkTime {
6527                    nanosecond: time.nanosecond,
6528                    ..Default::default()
6529                },
6530                None,
6531            );
6532        }
6533    }
6534
6535    #[cfg(feature = "timesync")]
6536    /// Removes all locally supplied network-time fragments from the assembled clock.
6537    pub fn clear_local_network_time(&self) {
6538        let mut st = self.timesync.lock();
6539        st.clock.remove_source(LOCAL_TIMESYNC_FULL_SOURCE_ID);
6540        st.clock.remove_source(LOCAL_TIMESYNC_DATE_SOURCE_ID);
6541        st.clock.remove_source(LOCAL_TIMESYNC_TOD_SOURCE_ID);
6542        st.clock.remove_source(LOCAL_TIMESYNC_SUBSEC_SOURCE_ID);
6543    }
6544
6545    #[cfg(feature = "timesync")]
6546    /// Sets only the local calendar date portion of network time.
6547    pub fn set_local_network_date(&self, year: i32, month: u8, day: u8) {
6548        self.set_local_network_time(PartialNetworkTime {
6549            year: Some(year),
6550            month: Some(month),
6551            day: Some(day),
6552            ..Default::default()
6553        });
6554    }
6555
6556    #[cfg(feature = "timesync")]
6557    /// Sets the local time of day to hour and minute precision.
6558    pub fn set_local_network_time_hm(&self, hour: u8, minute: u8) {
6559        self.set_local_network_time(PartialNetworkTime {
6560            hour: Some(hour),
6561            minute: Some(minute),
6562            ..Default::default()
6563        });
6564    }
6565
6566    #[cfg(feature = "timesync")]
6567    /// Sets the local time of day to second precision.
6568    pub fn set_local_network_time_hms(&self, hour: u8, minute: u8, second: u8) {
6569        self.set_local_network_time(PartialNetworkTime {
6570            hour: Some(hour),
6571            minute: Some(minute),
6572            second: Some(second),
6573            ..Default::default()
6574        });
6575    }
6576
6577    #[cfg(feature = "timesync")]
6578    /// Sets the local time of day with millisecond precision.
6579    pub fn set_local_network_time_hms_millis(
6580        &self,
6581        hour: u8,
6582        minute: u8,
6583        second: u8,
6584        millisecond: u16,
6585    ) {
6586        self.set_local_network_time(PartialNetworkTime {
6587            hour: Some(hour),
6588            minute: Some(minute),
6589            second: Some(second),
6590            nanosecond: Some((millisecond as u32).saturating_mul(1_000_000)),
6591            ..Default::default()
6592        });
6593    }
6594
6595    #[cfg(feature = "timesync")]
6596    /// Sets the local time of day with nanosecond precision.
6597    pub fn set_local_network_time_hms_nanos(
6598        &self,
6599        hour: u8,
6600        minute: u8,
6601        second: u8,
6602        nanosecond: u32,
6603    ) {
6604        self.set_local_network_time(PartialNetworkTime {
6605            hour: Some(hour),
6606            minute: Some(minute),
6607            second: Some(second),
6608            nanosecond: Some(nanosecond),
6609            ..Default::default()
6610        });
6611    }
6612
6613    #[cfg(feature = "timesync")]
6614    /// Sets a complete local date and time with second precision.
6615    pub fn set_local_network_datetime(
6616        &self,
6617        year: i32,
6618        month: u8,
6619        day: u8,
6620        hour: u8,
6621        minute: u8,
6622        second: u8,
6623    ) {
6624        self.set_local_network_time(PartialNetworkTime {
6625            year: Some(year),
6626            month: Some(month),
6627            day: Some(day),
6628            hour: Some(hour),
6629            minute: Some(minute),
6630            second: Some(second),
6631            ..Default::default()
6632        });
6633    }
6634
6635    #[cfg(feature = "timesync")]
6636    #[allow(clippy::too_many_arguments)]
6637    /// Sets a complete local date and time with millisecond precision.
6638    pub fn set_local_network_datetime_millis(
6639        &self,
6640        year: i32,
6641        month: u8,
6642        day: u8,
6643        hour: u8,
6644        minute: u8,
6645        second: u8,
6646        millisecond: u16,
6647    ) {
6648        self.set_local_network_time(PartialNetworkTime {
6649            year: Some(year),
6650            month: Some(month),
6651            day: Some(day),
6652            hour: Some(hour),
6653            minute: Some(minute),
6654            second: Some(second),
6655            nanosecond: Some((millisecond as u32).saturating_mul(1_000_000)),
6656        });
6657    }
6658
6659    #[cfg(feature = "timesync")]
6660    #[allow(clippy::too_many_arguments)]
6661    /// Sets a complete local date and time with nanosecond precision.
6662    pub fn set_local_network_datetime_nanos(
6663        &self,
6664        year: i32,
6665        month: u8,
6666        day: u8,
6667        hour: u8,
6668        minute: u8,
6669        second: u8,
6670        nanosecond: u32,
6671    ) {
6672        self.set_local_network_time(PartialNetworkTime {
6673            year: Some(year),
6674            month: Some(month),
6675            day: Some(day),
6676            hour: Some(hour),
6677            minute: Some(minute),
6678            second: Some(second),
6679            nanosecond: Some(nanosecond),
6680        });
6681    }
6682
6683    #[cfg(feature = "timesync")]
6684    /// Removes a previously registered named network-time source.
6685    pub fn clear_network_time_source(&self, source: &str) {
6686        let mut st = self.timesync.lock();
6687        st.clock.remove_source(source);
6688    }
6689
6690    #[cfg(feature = "timesync")]
6691    /// Replaces the active time sync configuration and resets runtime state derived from it.
6692    pub fn set_timesync_config(&self, cfg: Option<TimeSyncConfig>) {
6693        let mut st = self.timesync.lock();
6694        let stale_remote_sources: Vec<String> = st.remote_sources.keys().cloned().collect();
6695        st.cfg = cfg;
6696        st.tracker = cfg.map(TimeSyncTracker::new);
6697        st.disciplined_clock = SlewedNetworkClock::new(
6698            cfg.map(|c| c.max_slew_ppm)
6699                .unwrap_or(TimeSyncConfig::default().max_slew_ppm),
6700        );
6701        st.remote_sources.clear();
6702        st.next_seq = 1;
6703        st.next_announce_mono_ms = 0;
6704        st.next_request_mono_ms = 0;
6705        st.pending_request = None;
6706        st.clock.remove_source(INTERNAL_TIMESYNC_SOURCE_ID);
6707        for source in stale_remote_sources {
6708            st.clock.remove_source(&source);
6709        }
6710        st.clock.remove_source(LOCAL_TIMESYNC_FULL_SOURCE_ID);
6711        st.clock.remove_source(LOCAL_TIMESYNC_DATE_SOURCE_ID);
6712        st.clock.remove_source(LOCAL_TIMESYNC_TOD_SOURCE_ID);
6713        st.clock.remove_source(LOCAL_TIMESYNC_SUBSEC_SOURCE_ID);
6714    }
6715
6716    #[cfg(feature = "timesync")]
6717    /// Returns the best currently known network-time reading, if any.
6718    pub fn network_time(&self) -> Option<NetworkTimeReading> {
6719        let now_ms = self.monotonic_now_ms();
6720        let now_ns = self.monotonic_now_ns();
6721        self.refresh_timesync_state(now_ms);
6722        let st = self.timesync.lock();
6723        if let Some(unix_ms) = st.disciplined_clock.read_unix_ms(now_ns) {
6724            return Some(NetworkTimeReading {
6725                time: PartialNetworkTime::from_unix_ms(unix_ms),
6726                unix_time_ms: Some(unix_ms),
6727            });
6728        }
6729        st.clock.current_time(now_ns)
6730    }
6731
6732    #[cfg(feature = "timesync")]
6733    /// Returns the current network time as Unix milliseconds when available.
6734    pub fn network_time_ms(&self) -> Option<u64> {
6735        self.network_time().and_then(|t| t.unix_time_ms)
6736    }
6737
6738    #[cfg(feature = "timesync")]
6739    fn packet_timestamp_ms(&self) -> u64 {
6740        self.network_time_ms()
6741            .unwrap_or_else(|| self.monotonic_now_ms())
6742    }
6743
6744    #[cfg(not(feature = "timesync"))]
6745    fn packet_timestamp_ms(&self) -> u64 {
6746        self.clock.now_ms()
6747    }
6748
6749    #[cfg(feature = "timesync")]
6750    fn queue_internal_timesync_request(
6751        &self,
6752        seq: u64,
6753        t1_mono_ms: u64,
6754        called_from_queue: bool,
6755    ) -> TelemetryResult<()> {
6756        let pkt_ts = self.packet_timestamp_ms();
6757        if called_from_queue {
6758            self.log_queue_ts(DataType::TimeSyncRequest, pkt_ts, &[seq, t1_mono_ms])
6759        } else {
6760            self.log_ts(DataType::TimeSyncRequest, pkt_ts, &[seq, t1_mono_ms])
6761        }
6762    }
6763
6764    #[cfg(feature = "timesync")]
6765    fn queue_internal_timesync_response(
6766        &self,
6767        seq: u64,
6768        t1_mono_ms: u64,
6769        t2_network_ms: u64,
6770        t3_network_ms: u64,
6771        dst: Option<RouterSideId>,
6772        called_from_queue: bool,
6773    ) -> TelemetryResult<()> {
6774        let pkt_ts = self.packet_timestamp_ms();
6775        let payload = encode_slice_le(&[seq, t1_mono_ms, t2_network_ms, t3_network_ms]);
6776        let sender = self.sender_arc();
6777        let pkt = Packet::new(
6778            DataType::TimeSyncResponse,
6779            &[DataEndpoint::TimeSync],
6780            sender.as_ref(),
6781            pkt_ts,
6782            payload,
6783        )?;
6784        match dst {
6785            Some(dst) => self.emit_internal_tx(
6786                RouterTxItem::ToSide {
6787                    src: None,
6788                    dst,
6789                    data: RouterItem::Packet(pkt),
6790                },
6791                true,
6792                called_from_queue,
6793            ),
6794            None => self.emit_internal_tx(
6795                RouterTxItem::Broadcast(RouterItem::Packet(pkt)),
6796                true,
6797                called_from_queue,
6798            ),
6799        }
6800    }
6801
6802    #[cfg(feature = "timesync")]
6803    /// Runs one time sync maintenance cycle and queues any required announce or request packets.
6804    pub fn poll_timesync(&self) -> TelemetryResult<bool> {
6805        let now_ms = self.monotonic_now_ms();
6806        let now_ns = self.monotonic_now_ns();
6807        let mut queued_any = false;
6808        let mut announce_priority = None;
6809        let mut request = None;
6810
6811        {
6812            let mut st = self.timesync.lock();
6813            st.clock.prune_expired(now_ms);
6814            let timeout_ms = st.cfg.map(|cfg| cfg.source_timeout_ms).unwrap_or(0);
6815            st.remote_sources
6816                .retain(|_, src| now_ms.saturating_sub(src.last_sample_mono_ms) <= timeout_ms);
6817            let Some(cfg) = st.cfg else {
6818                return Ok(false);
6819            };
6820
6821            let has_usable_time = Self::timesync_has_usable_time_locked(&st, now_ns);
6822            let (leader, announce_prio) = if let Some(tracker) = st.tracker.as_mut() {
6823                let _ = tracker.refresh(now_ms);
6824                (
6825                    tracker.leader(now_ms, has_usable_time),
6826                    tracker.local_announce_priority(now_ms, has_usable_time),
6827                )
6828            } else {
6829                (None, None)
6830            };
6831            Self::reconcile_pending_timesync_request_locked(&mut st, &leader, now_ms);
6832
6833            if let Some(TimeSyncLeader::Remote(remote)) = leader.as_ref() {
6834                let target_ms = st
6835                    .remote_sources
6836                    .get(&remote.sender)
6837                    .map(|src| src.sample_unix_ms);
6838                if let Some(target_ms) = target_ms {
6839                    st.disciplined_clock.steer_unix_ms(now_ns, target_ms);
6840                }
6841            }
6842
6843            if let Some(priority) = announce_prio
6844                && now_ms >= st.next_announce_mono_ms
6845            {
6846                announce_priority = Some(priority);
6847                st.next_announce_mono_ms = now_ms.saturating_add(cfg.announce_interval_ms);
6848            }
6849
6850            if let Some(TimeSyncLeader::Remote(remote)) = leader
6851                && now_ms >= st.next_request_mono_ms
6852                && st.pending_request.is_none()
6853            {
6854                let seq = st.next_seq;
6855                let next = st.next_seq.wrapping_add(1);
6856                st.next_seq = if next == 0 { 1 } else { next };
6857                st.next_request_mono_ms = now_ms.saturating_add(cfg.request_interval_ms);
6858                st.pending_request = Some(PendingTimeSyncRequest {
6859                    seq,
6860                    t1_mono_ms: now_ms,
6861                    source: remote.sender,
6862                });
6863                request = Some((seq, now_ms));
6864            }
6865        }
6866
6867        if let Some(priority) = announce_priority {
6868            let time_ms = self.packet_timestamp_ms();
6869            self.log_queue_ts(DataType::TimeSyncAnnounce, time_ms, &[priority, time_ms])?;
6870            queued_any = true;
6871        }
6872        if let Some((seq, t1_mono_ms)) = request {
6873            self.queue_internal_timesync_request(seq, t1_mono_ms, true)?;
6874            queued_any = true;
6875        }
6876
6877        Ok(queued_any)
6878    }
6879
6880    #[cfg(feature = "timesync")]
6881    fn handle_internal_timesync_packet(
6882        &self,
6883        pkt: &Packet,
6884        src: Option<RouterSideId>,
6885        called_from_queue: bool,
6886    ) -> TelemetryResult<bool> {
6887        let Some(cfg) = self.cfg.timesync_config() else {
6888            if self.should_route_remote(&RouterItem::Packet(pkt.clone()), src)? {
6889                self.relay_send(RouterItem::Packet(pkt.clone()), src, called_from_queue)?;
6890            }
6891            return Ok(true);
6892        };
6893
6894        let now_mono_ms = self.monotonic_now_ms();
6895        let now_mono_ns = self.monotonic_now_ns();
6896        let mut response = None;
6897        let mut poll_after = false;
6898
6899        {
6900            let mut st = self.timesync.lock();
6901            st.clock.prune_expired(now_mono_ms);
6902            let timeout_ms = st.cfg.map(|cfg| cfg.source_timeout_ms).unwrap_or(0);
6903            st.remote_sources
6904                .retain(|_, src| now_mono_ms.saturating_sub(src.last_sample_mono_ms) <= timeout_ms);
6905            let has_usable_time = Self::timesync_has_usable_time_locked(&st, now_mono_ns);
6906            if st.tracker.is_none() {
6907                return Ok(true);
6908            }
6909
6910            match pkt.data_type() {
6911                DataType::TimeSyncAnnounce => {
6912                    let ann = decode_timesync_announce(pkt)?;
6913                    let should_steer = {
6914                        let tracker = st.tracker.as_mut().expect("tracker checked above");
6915                        let _ = tracker.handle_announce(pkt, now_mono_ms)?;
6916                        matches!(
6917                            tracker.leader(now_mono_ms, has_usable_time),
6918                            Some(TimeSyncLeader::Remote(ref remote)) if remote.sender == pkt.sender()
6919                        )
6920                    };
6921                    st.remote_sources.insert(
6922                        pkt.sender().to_owned(),
6923                        RemoteTimeSyncSource {
6924                            priority: ann.priority,
6925                            last_sample_mono_ms: now_mono_ms,
6926                            sample_unix_ms: ann.time_ms,
6927                        },
6928                    );
6929                    st.clock.update_source(
6930                        pkt.sender(),
6931                        ann.priority,
6932                        PartialNetworkTime::from_unix_ms(ann.time_ms),
6933                        now_mono_ms,
6934                        now_mono_ns,
6935                        Some(cfg.source_timeout_ms),
6936                    );
6937                    if should_steer {
6938                        st.disciplined_clock.steer_unix_ms(now_mono_ns, ann.time_ms);
6939                    }
6940                    poll_after = true;
6941                }
6942                DataType::TimeSyncRequest => {
6943                    let should_serve = {
6944                        let tracker = st.tracker.as_ref().expect("tracker checked above");
6945                        tracker.should_serve(now_mono_ms, has_usable_time)
6946                    };
6947                    if should_serve {
6948                        let req = decode_timesync_request(pkt)?;
6949                        let network_now = st
6950                            .disciplined_clock
6951                            .read_unix_ms(now_mono_ns)
6952                            .or_else(|| {
6953                                st.clock
6954                                    .current_time(now_mono_ns)
6955                                    .and_then(|t| t.unix_time_ms)
6956                            })
6957                            .unwrap_or(now_mono_ms);
6958                        let t2 = network_now;
6959                        let t3 = network_now;
6960                        response = Some((req.seq, req.t1_ms, t2, t3, src));
6961                    }
6962                }
6963                DataType::TimeSyncResponse => {
6964                    let resp = decode_timesync_response(pkt)?;
6965                    let pending = st.pending_request.clone();
6966                    if let Some(pending) = pending
6967                        && pending.seq == resp.seq
6968                        && pending.source == pkt.sender()
6969                    {
6970                        let source_priority = {
6971                            let tracker = st.tracker.as_ref().expect("tracker checked above");
6972                            tracker
6973                                .best_active_source(now_mono_ms)
6974                                .map(|s| s.priority)
6975                                .or_else(|| st.remote_sources.get(pkt.sender()).map(|s| s.priority))
6976                                .unwrap_or(cfg.priority)
6977                        };
6978                        let (estimate_ms, _delay_ms) = compute_network_time_sample(
6979                            pending.t1_mono_ms,
6980                            resp.t2_ms,
6981                            resp.t3_ms,
6982                            now_mono_ms,
6983                        );
6984                        st.remote_sources.insert(
6985                            pkt.sender().to_owned(),
6986                            RemoteTimeSyncSource {
6987                                priority: source_priority,
6988                                last_sample_mono_ms: now_mono_ms,
6989                                sample_unix_ms: estimate_ms,
6990                            },
6991                        );
6992                        st.clock.update_source(
6993                            pkt.sender(),
6994                            source_priority,
6995                            PartialNetworkTime::from_unix_ms(estimate_ms),
6996                            now_mono_ms,
6997                            now_mono_ns,
6998                            Some(cfg.source_timeout_ms),
6999                        );
7000                        st.disciplined_clock.steer_unix_ms(now_mono_ns, estimate_ms);
7001                        st.pending_request = None;
7002                    }
7003                }
7004                _ => {}
7005            }
7006        }
7007
7008        if let Some((seq, t1, t2, t3, dst)) = response {
7009            self.queue_internal_timesync_response(seq, t1, t2, t3, dst, called_from_queue)?;
7010        }
7011        if poll_after {
7012            let _ = self.poll_timesync()?;
7013        }
7014
7015        if self.should_route_remote(&RouterItem::Packet(pkt.clone()), src)? {
7016            self.relay_send(RouterItem::Packet(pkt.clone()), src, called_from_queue)?;
7017        }
7018
7019        Ok(true)
7020    }
7021
7022    /// Create a new Router with an internal monotonic clock.
7023    #[cfg(feature = "std")]
7024    pub fn new(cfg: RouterConfig) -> Self {
7025        Self::new_with_clock(cfg, Box::new(StdMonotonicClock::default()))
7026    }
7027
7028    /// Create a new Router with the specified router configuration and clock.
7029    pub fn new_with_clock(cfg: RouterConfig, clock: Box<dyn Clock + Send + Sync>) -> Self {
7030        #[cfg(feature = "timesync")]
7031        let timesync_cfg = cfg.timesync_config();
7032        let hostname: Arc<str> = Arc::from(cfg.sender());
7033        let address_mode = cfg.address_mode();
7034        let instance_seq = u64::from(ROUTER_INSTANCE_SEQ.fetch_add(1, Ordering::Relaxed));
7035        let owner_hash = Self::sender_hash(hostname.as_ref()) ^ instance_seq;
7036        let fallback = Self::fallback_address_for_hostname(hostname.as_ref());
7037        let requested = address_mode.requested_address();
7038        let address = if requested == 0 { fallback } else { requested };
7039        let local_address = AddressBookEntry {
7040            hostname: hostname.clone(),
7041            address,
7042            requested_address: requested,
7043            mode: address_mode,
7044            birth_ms: clock
7045                .now_ms()
7046                .saturating_mul(1_000_000)
7047                .saturating_add(instance_seq),
7048            owner_hash,
7049            last_seen_ms: clock.now_ms(),
7050        };
7051        let mut address_book = BTreeMap::new();
7052        address_book.insert(hostname.to_string(), local_address.clone());
7053        let mut address_by_value = BTreeMap::new();
7054        address_by_value.insert(address, hostname.to_string());
7055        Self {
7056            sender: RouterMutex::new(hostname),
7057            cfg,
7058            state: RouterMutex::new(RouterInner {
7059                sides: Vec::new(),
7060                route_overrides: BTreeMap::new(),
7061                typed_route_overrides: BTreeMap::new(),
7062                route_weights: BTreeMap::new(),
7063                route_priorities: BTreeMap::new(),
7064                source_route_modes: BTreeMap::new(),
7065                route_selection_cursors: BTreeMap::new(),
7066                adaptive_route_stats: BTreeMap::new(),
7067                side_runtime_stats: BTreeMap::new(),
7068                side_transport: BTreeMap::new(),
7069                managed_variable_types: BTreeSet::new(),
7070                managed_variable_permissions: BTreeMap::new(),
7071                managed_variable_latest: BTreeMap::new(),
7072                network_variable_update_handlers: BTreeMap::new(),
7073                local_address,
7074                address_book,
7075                address_by_value,
7076                p2p_port_handlers: BTreeMap::new(),
7077                p2p_stream_handlers: BTreeMap::new(),
7078                p2p_stream_sessions: BTreeMap::new(),
7079                next_p2p_stream_id: 1,
7080                received_queue: BoundedDeque::new(
7081                    MAX_QUEUE_BUDGET,
7082                    STARTING_QUEUE_SIZE,
7083                    QUEUE_GROW_STEP,
7084                ),
7085                transmit_queue: BoundedDeque::new(
7086                    MAX_QUEUE_BUDGET,
7087                    STARTING_QUEUE_SIZE,
7088                    QUEUE_GROW_STEP,
7089                ),
7090                recent_rx: BoundedDeque::new(
7091                    RECENT_RX_QUEUE_BYTES.max(1),
7092                    RECENT_RX_QUEUE_BYTES.max(1),
7093                    QUEUE_GROW_STEP,
7094                ),
7095                reliable_tx: BTreeMap::new(),
7096                reliable_rx: BTreeMap::new(),
7097                reliable_return_routes: BTreeMap::new(),
7098                reliable_return_route_order: VecDeque::new(),
7099                end_to_end_reliable_tx: BTreeMap::new(),
7100                end_to_end_reliable_tx_order: VecDeque::new(),
7101                total_handler_failures: 0,
7102                total_handler_retries: 0,
7103                #[cfg(feature = "discovery")]
7104                discovery_routes: BTreeMap::new(),
7105                #[cfg(feature = "discovery")]
7106                discovery_cadence: DiscoveryCadenceState::default(),
7107                #[cfg(feature = "discovery")]
7108                discovery_side_throttle: BTreeMap::new(),
7109                #[cfg(all(feature = "discovery", feature = "timesync"))]
7110                timesync_side_throttle: BTreeMap::new(),
7111            }),
7112            isr_rx_queue: IsrRxQueue::new(MAX_QUEUE_BUDGET, STARTING_QUEUE_SIZE, QUEUE_GROW_STEP),
7113            side_tx_gate: ReentryGate::new(),
7114            clock,
7115            #[cfg(feature = "timesync")]
7116            timesync: RouterMutex::new(TimeSyncRuntime::new(timesync_cfg)),
7117        }
7118    }
7119
7120    #[inline]
7121    fn sender_arc(&self) -> Arc<str> {
7122        self.sender.lock().clone()
7123    }
7124
7125    #[inline]
7126    pub fn sender(&self) -> Arc<str> {
7127        self.sender_arc()
7128    }
7129
7130    pub fn current_address(&self) -> NodeAddress {
7131        self.state.lock().local_address.address
7132    }
7133
7134    pub fn hostname(&self) -> Arc<str> {
7135        self.sender_arc()
7136    }
7137
7138    pub fn address_book(&self) -> Vec<AddressBookEntry> {
7139        self.state.lock().address_book.values().cloned().collect()
7140    }
7141
7142    pub fn resolve_hostname(&self, hostname: &str) -> Option<AddressBookEntry> {
7143        self.state.lock().address_book.get(hostname).cloned()
7144    }
7145
7146    pub fn resolve_address(&self, address: NodeAddress) -> Option<AddressBookEntry> {
7147        let st = self.state.lock();
7148        st.address_by_value
7149            .get(&address)
7150            .and_then(|hostname| st.address_book.get(hostname))
7151            .cloned()
7152    }
7153
7154    pub fn bind_p2p_port<F>(&self, port: P2pPort, f: F) -> TelemetryResult<()>
7155    where
7156        F: Fn(P2pMessage<'_>) -> TelemetryResult<()> + Send + Sync + 'static,
7157    {
7158        if port == 0 {
7159            return Err(TelemetryError::BadArg);
7160        }
7161        let mut st = self.state.lock();
7162        st.p2p_port_handlers
7163            .entry(port)
7164            .or_default()
7165            .push(P2pPortHandler {
7166                handler: Arc::new(f),
7167            });
7168        Ok(())
7169    }
7170
7171    pub fn clear_p2p_port(&self, port: P2pPort) {
7172        self.state.lock().p2p_port_handlers.remove(&port);
7173    }
7174
7175    pub fn bind_p2p_stream_port<F>(&self, port: P2pPort, f: F) -> TelemetryResult<()>
7176    where
7177        F: Fn(P2pStreamEvent<'_>) -> TelemetryResult<()> + Send + Sync + 'static,
7178    {
7179        if port == 0 {
7180            return Err(TelemetryError::BadArg);
7181        }
7182        let mut st = self.state.lock();
7183        st.p2p_stream_handlers
7184            .entry(port)
7185            .or_default()
7186            .push(P2pStreamHandler {
7187                handler: Arc::new(f),
7188            });
7189        Ok(())
7190    }
7191
7192    pub fn clear_p2p_stream_port(&self, port: P2pPort) {
7193        self.state.lock().p2p_stream_handlers.remove(&port);
7194    }
7195
7196    fn send_p2p_to_entry(
7197        &self,
7198        dst: AddressBookEntry,
7199        dst_port: P2pPort,
7200        src_port: P2pPort,
7201        payload: &[u8],
7202    ) -> TelemetryResult<()> {
7203        if dst_port == 0 {
7204            return Err(TelemetryError::BadArg);
7205        }
7206        let local = self.state.lock().local_address.clone();
7207        let payload = Self::encode_p2p_payload(
7208            local.hostname.as_ref(),
7209            local.address,
7210            src_port,
7211            dst_port,
7212            payload,
7213        )?;
7214        let pkt = Packet::new(
7215            DataType::P2pMessage,
7216            &[DataEndpoint::Discovery],
7217            local.hostname.as_ref(),
7218            self.packet_timestamp_ms(),
7219            payload,
7220        )?;
7221        let target = Self::sender_hash(dst.hostname.as_ref());
7222        let item = self.attach_wire_contract_to_item(RouterItem::Packet(pkt.clone()), &[target])?;
7223        if target == Self::sender_hash(self.sender_arc().as_ref()) {
7224            self.dispatch_p2p_packet(&pkt)?;
7225            return Ok(());
7226        }
7227        self.tx_item_impl(RouterTxItem::Broadcast(item), true, false)
7228    }
7229
7230    pub fn send_p2p_to_hostname(
7231        &self,
7232        hostname: &str,
7233        dst_port: P2pPort,
7234        src_port: P2pPort,
7235        payload: &[u8],
7236    ) -> TelemetryResult<()> {
7237        let dst = self
7238            .resolve_hostname(hostname)
7239            .ok_or(TelemetryError::BadArg)?;
7240        self.send_p2p_to_entry(dst, dst_port, src_port, payload)
7241    }
7242
7243    pub fn send_p2p_to_address(
7244        &self,
7245        address: NodeAddress,
7246        dst_port: P2pPort,
7247        src_port: P2pPort,
7248        payload: &[u8],
7249    ) -> TelemetryResult<()> {
7250        let dst = self
7251            .resolve_address(address)
7252            .ok_or(TelemetryError::BadArg)?;
7253        self.send_p2p_to_entry(dst, dst_port, src_port, payload)
7254    }
7255
7256    fn open_p2p_stream_to_entry(
7257        &self,
7258        dst: AddressBookEntry,
7259        dst_port: P2pPort,
7260        src_port: P2pPort,
7261    ) -> TelemetryResult<P2pStreamId> {
7262        if dst_port == 0 || src_port == 0 {
7263            return Err(TelemetryError::BadArg);
7264        }
7265        let stream_id = {
7266            let mut st = self.state.lock();
7267            let stream_id = Self::allocate_p2p_stream_id_locked(&mut st);
7268            if stream_id == 0 {
7269                return Err(TelemetryError::Io("p2p stream id exhausted"));
7270            }
7271            st.p2p_stream_sessions.insert(
7272                stream_id,
7273                P2pStreamSession {
7274                    peer_hostname: dst.hostname.clone(),
7275                    peer_address: dst.address,
7276                    local_port: src_port,
7277                    peer_port: dst_port,
7278                    peer_stream_id: 0,
7279                    next_sequence: 1,
7280                    connected: false,
7281                },
7282            );
7283            stream_id
7284        };
7285        let payload = Self::encode_p2p_stream_payload(P2P_STREAM_SYN, stream_id, 0, 0, &[])?;
7286        self.send_p2p_to_entry(dst, dst_port, src_port, &payload)?;
7287        Ok(stream_id)
7288    }
7289
7290    pub fn open_p2p_stream_to_hostname(
7291        &self,
7292        hostname: &str,
7293        dst_port: P2pPort,
7294        src_port: P2pPort,
7295    ) -> TelemetryResult<P2pStreamId> {
7296        let dst = self
7297            .resolve_hostname(hostname)
7298            .ok_or(TelemetryError::BadArg)?;
7299        self.open_p2p_stream_to_entry(dst, dst_port, src_port)
7300    }
7301
7302    pub fn open_p2p_stream_to_address(
7303        &self,
7304        address: NodeAddress,
7305        dst_port: P2pPort,
7306        src_port: P2pPort,
7307    ) -> TelemetryResult<P2pStreamId> {
7308        let dst = self
7309            .resolve_address(address)
7310            .ok_or(TelemetryError::BadArg)?;
7311        self.open_p2p_stream_to_entry(dst, dst_port, src_port)
7312    }
7313
7314    fn send_p2p_stream_control(
7315        &self,
7316        stream_id: P2pStreamId,
7317        flags: u8,
7318        payload: &[u8],
7319    ) -> TelemetryResult<()> {
7320        let (dst, dst_port, src_port, peer_stream_id, seq, refresh_syn) = {
7321            let mut st = self.state.lock();
7322            let Some(session) = st.p2p_stream_sessions.get_mut(&stream_id) else {
7323                return Err(TelemetryError::BadArg);
7324            };
7325            let peer_hostname = session.peer_hostname.clone();
7326            let peer_address = session.peer_address;
7327            let peer_port = session.peer_port;
7328            let local_port = session.local_port;
7329            let peer_stream_id = session.peer_stream_id;
7330            let refresh_syn = peer_stream_id == 0 && flags & P2P_STREAM_SYN == 0;
7331            let seq = session.next_sequence;
7332            session.next_sequence = session.next_sequence.wrapping_add(1).max(1);
7333            let dst = st
7334                .address_book
7335                .get(peer_hostname.as_ref())
7336                .cloned()
7337                .unwrap_or(AddressBookEntry {
7338                    hostname: peer_hostname.clone(),
7339                    address: peer_address,
7340                    requested_address: peer_address,
7341                    mode: AddressAssignmentMode::Dynamic,
7342                    birth_ms: self.clock.now_ms(),
7343                    owner_hash: Self::sender_hash(peer_hostname.as_ref()),
7344                    last_seen_ms: self.clock.now_ms(),
7345                });
7346            (dst, peer_port, local_port, peer_stream_id, seq, refresh_syn)
7347        };
7348        if refresh_syn {
7349            let syn_payload =
7350                Self::encode_p2p_stream_payload(P2P_STREAM_SYN, stream_id, 0, 0, &[])?;
7351            self.send_p2p_to_entry(dst.clone(), dst_port, src_port, &syn_payload)?;
7352        }
7353        let stream_payload =
7354            Self::encode_p2p_stream_payload(flags, stream_id, peer_stream_id, seq, payload)?;
7355        self.send_p2p_to_entry(dst, dst_port, src_port, &stream_payload)?;
7356        if flags & (P2P_STREAM_FIN | P2P_STREAM_RST) != 0 {
7357            self.state.lock().p2p_stream_sessions.remove(&stream_id);
7358        }
7359        Ok(())
7360    }
7361
7362    pub fn send_p2p_stream(&self, stream_id: P2pStreamId, payload: &[u8]) -> TelemetryResult<()> {
7363        self.send_p2p_stream_control(stream_id, P2P_STREAM_DATA, payload)
7364    }
7365
7366    pub fn close_p2p_stream(&self, stream_id: P2pStreamId) -> TelemetryResult<()> {
7367        self.send_p2p_stream_control(stream_id, P2P_STREAM_FIN, &[])
7368    }
7369
7370    pub fn reset_p2p_stream(&self, stream_id: P2pStreamId) -> TelemetryResult<()> {
7371        self.send_p2p_stream_control(stream_id, P2P_STREAM_RST, &[])
7372    }
7373
7374    pub fn set_sender<S: AsRef<str>>(&self, sender: S) {
7375        let hostname: Arc<str> = Arc::from(sender.as_ref());
7376        *self.sender.lock() = hostname.clone();
7377        let mut st = self.state.lock();
7378        let mut local = st.local_address.clone();
7379        st.address_book.remove(local.hostname.as_ref());
7380        st.address_by_value.remove(&local.address);
7381        local.hostname = hostname.clone();
7382        local.owner_hash = Self::sender_hash(hostname.as_ref());
7383        if matches!(local.mode, AddressAssignmentMode::Dynamic) {
7384            local.address = Self::fallback_address_for_hostname(hostname.as_ref());
7385            local.requested_address = 0;
7386        }
7387        local.last_seen_ms = self.clock.now_ms();
7388        st.address_by_value
7389            .insert(local.address, hostname.to_string());
7390        st.address_book.insert(hostname.to_string(), local.clone());
7391        st.local_address = local;
7392        #[cfg(feature = "discovery")]
7393        Self::note_discovery_topology_change_locked(&mut st, self.clock.now_ms());
7394    }
7395
7396    /// Register a side whose TX callback consumes packed packet bytes.
7397    ///
7398    /// `name` is exported in topology/debug views and does not affect routing semantics.
7399    /// `tx` is called whenever the router decides to send a packet toward this side.
7400    ///
7401    /// The default options disable the router's per-link reliable framing on this side. Use
7402    /// [`Router::add_side_packed_with_options`] when this hop should participate in router
7403    /// reliable ACK/retransmit behavior.
7404    pub fn add_side_packed<F>(&self, name: &'static str, tx: F) -> RouterSideId
7405    where
7406        F: Fn(&[u8]) -> TelemetryResult<()> + Send + Sync + 'static,
7407    {
7408        self.add_side_packed_with_options(name, tx, RouterSideOptions::default())
7409    }
7410
7411    /// Register a packed side with the compact small-packet transport preset enabled.
7412    ///
7413    /// `max_frame_bytes == 0` keeps header-template reuse enabled without chunking.
7414    pub fn add_side_packed_small_packets<F>(
7415        &self,
7416        name: &'static str,
7417        tx: F,
7418        max_frame_bytes: usize,
7419    ) -> RouterSideId
7420    where
7421        F: Fn(&[u8]) -> TelemetryResult<()> + Send + Sync + 'static,
7422    {
7423        self.add_side_packed_with_options(
7424            name,
7425            tx,
7426            RouterSideOptions::default().with_small_packet_transport(max_frame_bytes),
7427        )
7428    }
7429
7430    /// Register a packed-output side with explicit side options.
7431    ///
7432    /// `opts.reliable_enabled` enables the router's per-hop reliable framing on this side only.
7433    /// That means reliable schema traffic on this side uses router-managed ACKs, packet requests,
7434    /// and retransmits before the bytes reach `tx`.
7435    ///
7436    /// `opts.link_local_enabled` allows link-local-only endpoints and discovery routes to use this
7437    /// side. `ingress_enabled` and `egress_enabled` set the initial directional policy.
7438    pub fn add_side_packed_with_options<F>(
7439        &self,
7440        name: &'static str,
7441        tx: F,
7442        opts: RouterSideOptions,
7443    ) -> RouterSideId
7444    where
7445        F: Fn(&[u8]) -> TelemetryResult<()> + Send + Sync + 'static,
7446    {
7447        let mut st = self.state.lock();
7448        let id = st.sides.len();
7449        st.sides.push(Some(RouterSide {
7450            name,
7451            tx_handler: RouterTxHandlerFn::Packed(Arc::new(tx)),
7452            opts,
7453        }));
7454        st.side_runtime_stats
7455            .insert(id, SideRuntimeStatsInner::default());
7456        st.side_transport.insert(id, SideTransportState::default());
7457        #[cfg(feature = "discovery")]
7458        Self::note_discovery_topology_change_locked(&mut st, self.clock.now_ms());
7459        id
7460    }
7461
7462    /// Register a side whose TX callback receives decoded [`Packet`] values.
7463    ///
7464    /// Packet-output sides do not preserve the packed reliable hop framing, so
7465    /// `RouterSideOptions::reliable_enabled` only has effect for packed sides.
7466    pub fn add_side_packet<F>(&self, name: &'static str, tx: F) -> RouterSideId
7467    where
7468        F: Fn(&Packet) -> TelemetryResult<()> + Send + Sync + 'static,
7469    {
7470        self.add_side_packet_with_options(name, tx, RouterSideOptions::default())
7471    }
7472
7473    /// Register a packet-output side with explicit side options.
7474    ///
7475    /// `opts.reliable_enabled` still records the operator's intent for this side, but packet-based
7476    /// callbacks receive decoded packets rather than the router's packed reliable hop framing.
7477    /// For router-managed per-link reliable sequencing and ACKs, use a packed side instead.
7478    pub fn add_side_packet_with_options<F>(
7479        &self,
7480        name: &'static str,
7481        tx: F,
7482        opts: RouterSideOptions,
7483    ) -> RouterSideId
7484    where
7485        F: Fn(&Packet) -> TelemetryResult<()> + Send + Sync + 'static,
7486    {
7487        let mut st = self.state.lock();
7488        let id = st.sides.len();
7489        st.sides.push(Some(RouterSide {
7490            name,
7491            tx_handler: RouterTxHandlerFn::Packet(Arc::new(tx)),
7492            opts,
7493        }));
7494        st.side_runtime_stats
7495            .insert(id, SideRuntimeStatsInner::default());
7496        st.side_transport.insert(id, SideTransportState::default());
7497        #[cfg(feature = "discovery")]
7498        Self::note_discovery_topology_change_locked(&mut st, self.clock.now_ms());
7499        id
7500    }
7501
7502    /// Remove a side while keeping existing side IDs stable.
7503    ///
7504    /// `side` must be an id returned earlier by one of the `add_side_*` calls. Removed side IDs
7505    /// are tombstoned rather than renumbered so cached IDs for the remaining sides stay valid.
7506    pub fn remove_side(&self, side: RouterSideId) -> TelemetryResult<()> {
7507        let now_ms = self.clock.now_ms();
7508        {
7509            let mut st = self.state.lock();
7510            let slot = st.sides.get_mut(side).ok_or(TelemetryError::BadArg)?;
7511            if slot.is_none() {
7512                return Err(TelemetryError::BadArg);
7513            }
7514            *slot = None;
7515            st.route_overrides
7516                .retain(|(src_side, dst_side), _| *src_side != Some(side) && *dst_side != side);
7517            st.typed_route_overrides
7518                .retain(|(src_side, _, dst_side), _| *src_side != Some(side) && *dst_side != side);
7519            st.route_weights
7520                .retain(|(src_side, dst_side), _| *src_side != Some(side) && *dst_side != side);
7521            st.route_priorities
7522                .retain(|(src_side, dst_side), _| *src_side != Some(side) && *dst_side != side);
7523            st.source_route_modes.remove(&Some(side));
7524            st.route_selection_cursors.remove(&Some(side));
7525            st.adaptive_route_stats.remove(&side);
7526            #[cfg(feature = "discovery")]
7527            st.discovery_side_throttle.remove(&side);
7528            #[cfg(all(feature = "discovery", feature = "timesync"))]
7529            st.timesync_side_throttle.remove(&side);
7530            st.side_runtime_stats.remove(&side);
7531            st.side_transport.remove(&side);
7532            st.reliable_return_routes
7533                .retain(|_, route| route.side != side);
7534            st.transmit_queue.retain(
7535                |queued| {
7536                    !matches!(&queued.item, RouterTxItem::ToSide { dst, .. } if *dst == side)
7537                        && !matches!(&queued.item, RouterTxItem::ReliableReplay { dst, .. } if *dst == side)
7538                },
7539            );
7540            st.received_queue.retain(|queued| queued.src != Some(side));
7541            st.reliable_tx.retain(|(side_id, _), _| *side_id != side);
7542            st.reliable_rx.retain(|(side_id, _), _| *side_id != side);
7543            #[cfg(feature = "discovery")]
7544            {
7545                st.discovery_routes.remove(&side);
7546                Self::note_discovery_topology_change_locked(&mut st, now_ms);
7547            }
7548        }
7549        let mut isr_rx = self.isr_rx_queue.try_lock()?;
7550        isr_rx.retain(|queued| queued.src != Some(side));
7551        Ok(())
7552    }
7553
7554    /// Enable or disable ingress processing for a registered side.
7555    ///
7556    /// When `enabled` is `false`, packets tagged as arriving from `side` are rejected before local
7557    /// delivery, discovery learning, or forwarding.
7558    pub fn set_side_ingress_enabled(
7559        &self,
7560        side: RouterSideId,
7561        enabled: bool,
7562    ) -> TelemetryResult<()> {
7563        let now_ms = self.clock.now_ms();
7564        let mut st = self.state.lock();
7565        let side_ref = st
7566            .sides
7567            .get_mut(side)
7568            .and_then(|side| side.as_mut())
7569            .ok_or(TelemetryError::BadArg)?;
7570        side_ref.opts.ingress_enabled = enabled;
7571        #[cfg(feature = "discovery")]
7572        Self::note_discovery_topology_change_locked(&mut st, now_ms);
7573        Ok(())
7574    }
7575
7576    /// Enable or disable egress toward a registered side.
7577    ///
7578    /// When `enabled` is `false`, the router keeps the side but stops routing packets toward it.
7579    pub fn set_side_egress_enabled(
7580        &self,
7581        side: RouterSideId,
7582        enabled: bool,
7583    ) -> TelemetryResult<()> {
7584        let now_ms = self.clock.now_ms();
7585        let mut st = self.state.lock();
7586        let side_ref = st
7587            .sides
7588            .get_mut(side)
7589            .and_then(|side| side.as_mut())
7590            .ok_or(TelemetryError::BadArg)?;
7591        side_ref.opts.egress_enabled = enabled;
7592        if !enabled {
7593            st.transmit_queue.retain(
7594                |queued| {
7595                    !matches!(&queued.item, RouterTxItem::ToSide { dst, .. } if *dst == side)
7596                        && !matches!(&queued.item, RouterTxItem::ReliableReplay { dst, .. } if *dst == side)
7597                },
7598            );
7599        }
7600        #[cfg(feature = "discovery")]
7601        Self::note_discovery_topology_change_locked(&mut st, now_ms);
7602        Ok(())
7603    }
7604
7605    /// Set the route-selection policy for traffic originating from `src`.
7606    ///
7607    /// `src == None` targets locally-originated router TX. `src == Some(side)` targets traffic
7608    /// that was received from a specific ingress side. `mode` only matters when more than one
7609    /// destination side is currently eligible.
7610    pub fn set_source_route_mode(
7611        &self,
7612        src: Option<RouterSideId>,
7613        mode: RouteSelectionMode,
7614    ) -> TelemetryResult<()> {
7615        let now_ms = self.clock.now_ms();
7616        let mut st = self.state.lock();
7617        if let Some(src) = src {
7618            let _ = Self::side_ref(&st, src).map_err(|_| TelemetryError::BadArg)?;
7619        }
7620        if mode == RouteSelectionMode::Fanout {
7621            st.source_route_modes.remove(&src);
7622        } else {
7623            st.source_route_modes.insert(src, mode);
7624        }
7625        st.route_selection_cursors.remove(&src);
7626        #[cfg(feature = "discovery")]
7627        Self::note_discovery_topology_change_locked(&mut st, now_ms);
7628        Ok(())
7629    }
7630
7631    /// Clear any source-specific route-selection override for `src`.
7632    pub fn clear_source_route_mode(&self, src: Option<RouterSideId>) -> TelemetryResult<()> {
7633        self.set_source_route_mode(src, RouteSelectionMode::Fanout)
7634    }
7635
7636    /// Set the weighted-routing weight from `src` toward `dst`.
7637    ///
7638    /// Higher `weight` values make `dst` more likely to be chosen when the source route mode is
7639    /// [`RouteSelectionMode::Weighted`]. `src == None` applies to locally-originated TX.
7640    pub fn set_route_weight(
7641        &self,
7642        src: Option<RouterSideId>,
7643        dst: RouterSideId,
7644        weight: u32,
7645    ) -> TelemetryResult<()> {
7646        let now_ms = self.clock.now_ms();
7647        let mut st = self.state.lock();
7648        let _ = Self::side_ref(&st, dst).map_err(|_| TelemetryError::BadArg)?;
7649        if let Some(src) = src {
7650            let _ = Self::side_ref(&st, src).map_err(|_| TelemetryError::BadArg)?;
7651        }
7652        st.route_weights.insert((src, dst), weight);
7653        st.route_selection_cursors.remove(&src);
7654        #[cfg(feature = "discovery")]
7655        Self::note_discovery_topology_change_locked(&mut st, now_ms);
7656        Ok(())
7657    }
7658
7659    /// Clear a previously configured weighted-routing weight override.
7660    pub fn clear_route_weight(
7661        &self,
7662        src: Option<RouterSideId>,
7663        dst: RouterSideId,
7664    ) -> TelemetryResult<()> {
7665        let now_ms = self.clock.now_ms();
7666        let mut st = self.state.lock();
7667        let _ = Self::side_ref(&st, dst).map_err(|_| TelemetryError::BadArg)?;
7668        if let Some(src) = src {
7669            let _ = Self::side_ref(&st, src).map_err(|_| TelemetryError::BadArg)?;
7670        }
7671        st.route_weights.remove(&(src, dst));
7672        st.route_selection_cursors.remove(&src);
7673        #[cfg(feature = "discovery")]
7674        Self::note_discovery_topology_change_locked(&mut st, now_ms);
7675        Ok(())
7676    }
7677
7678    /// Set the failover priority from `src` toward `dst`.
7679    ///
7680    /// Lower numeric `priority` wins when the source route mode is
7681    /// [`RouteSelectionMode::Failover`]. `src == None` applies to locally-originated TX.
7682    pub fn set_route_priority(
7683        &self,
7684        src: Option<RouterSideId>,
7685        dst: RouterSideId,
7686        priority: u32,
7687    ) -> TelemetryResult<()> {
7688        let now_ms = self.clock.now_ms();
7689        let mut st = self.state.lock();
7690        let _ = Self::side_ref(&st, dst).map_err(|_| TelemetryError::BadArg)?;
7691        if let Some(src) = src {
7692            let _ = Self::side_ref(&st, src).map_err(|_| TelemetryError::BadArg)?;
7693        }
7694        st.route_priorities.insert((src, dst), priority);
7695        #[cfg(feature = "discovery")]
7696        Self::note_discovery_topology_change_locked(&mut st, now_ms);
7697        Ok(())
7698    }
7699
7700    /// Clear a previously configured failover priority override.
7701    pub fn clear_route_priority(
7702        &self,
7703        src: Option<RouterSideId>,
7704        dst: RouterSideId,
7705    ) -> TelemetryResult<()> {
7706        let now_ms = self.clock.now_ms();
7707        let mut st = self.state.lock();
7708        let _ = Self::side_ref(&st, dst).map_err(|_| TelemetryError::BadArg)?;
7709        if let Some(src) = src {
7710            let _ = Self::side_ref(&st, src).map_err(|_| TelemetryError::BadArg)?;
7711        }
7712        st.route_priorities.remove(&(src, dst));
7713        #[cfg(feature = "discovery")]
7714        Self::note_discovery_topology_change_locked(&mut st, now_ms);
7715        Ok(())
7716    }
7717
7718    /// Allow or block routing from `src` toward `dst`.
7719    ///
7720    /// `src == None` controls locally-originated router TX. `enabled == false` is the sink-like
7721    /// building block for disabling specific directions without changing router construction mode.
7722    pub fn set_route(
7723        &self,
7724        src: Option<RouterSideId>,
7725        dst: RouterSideId,
7726        enabled: bool,
7727    ) -> TelemetryResult<()> {
7728        let now_ms = self.clock.now_ms();
7729        let mut st = self.state.lock();
7730        let _ = Self::side_ref(&st, dst).map_err(|_| TelemetryError::BadArg)?;
7731        if let Some(src) = src {
7732            let _ = Self::side_ref(&st, src).map_err(|_| TelemetryError::BadArg)?;
7733        }
7734        st.route_overrides.insert((src, dst), enabled);
7735        #[cfg(feature = "discovery")]
7736        Self::note_discovery_topology_change_locked(&mut st, now_ms);
7737        Ok(())
7738    }
7739
7740    /// Allow or block routing for a specific `DataType` from `src` toward `dst`.
7741    ///
7742    /// Typed route rules act as allowlists once any rule exists for the `(src, ty)` pair.
7743    /// `src == None` targets locally-originated router TX.
7744    pub fn set_typed_route(
7745        &self,
7746        src: Option<RouterSideId>,
7747        ty: DataType,
7748        dst: RouterSideId,
7749        enabled: bool,
7750    ) -> TelemetryResult<()> {
7751        let now_ms = self.clock.now_ms();
7752        let mut st = self.state.lock();
7753        let _ = Self::side_ref(&st, dst).map_err(|_| TelemetryError::BadArg)?;
7754        if let Some(src) = src {
7755            let _ = Self::side_ref(&st, src).map_err(|_| TelemetryError::BadArg)?;
7756        }
7757        st.typed_route_overrides
7758            .insert((src, ty.as_u32(), dst), enabled);
7759        #[cfg(feature = "discovery")]
7760        Self::note_discovery_topology_change_locked(&mut st, now_ms);
7761        Ok(())
7762    }
7763
7764    /// Clear a typed route override for the `(src, ty, dst)` triple.
7765    pub fn clear_typed_route(
7766        &self,
7767        src: Option<RouterSideId>,
7768        ty: DataType,
7769        dst: RouterSideId,
7770    ) -> TelemetryResult<()> {
7771        let now_ms = self.clock.now_ms();
7772        let mut st = self.state.lock();
7773        let _ = Self::side_ref(&st, dst).map_err(|_| TelemetryError::BadArg)?;
7774        if let Some(src) = src {
7775            let _ = Self::side_ref(&st, src).map_err(|_| TelemetryError::BadArg)?;
7776        }
7777        st.typed_route_overrides.remove(&(src, ty.as_u32(), dst));
7778        #[cfg(feature = "discovery")]
7779        Self::note_discovery_topology_change_locked(&mut st, now_ms);
7780        Ok(())
7781    }
7782
7783    /// Clear a non-typed route override so the router falls back to default behavior.
7784    pub fn clear_route(&self, src: Option<RouterSideId>, dst: RouterSideId) -> TelemetryResult<()> {
7785        let now_ms = self.clock.now_ms();
7786        let mut st = self.state.lock();
7787        let _ = Self::side_ref(&st, dst).map_err(|_| TelemetryError::BadArg)?;
7788        if let Some(src) = src {
7789            let _ = Self::side_ref(&st, src).map_err(|_| TelemetryError::BadArg)?;
7790        }
7791        st.route_overrides.remove(&(src, dst));
7792        #[cfg(feature = "discovery")]
7793        Self::note_discovery_topology_change_locked(&mut st, now_ms);
7794        Ok(())
7795    }
7796
7797    /// Queue a built-in discovery advertisement describing this router's local endpoints.
7798    #[cfg(feature = "discovery")]
7799    pub fn announce_discovery(&self) -> TelemetryResult<()> {
7800        self.queue_discovery_announce()
7801    }
7802
7803    /// Broadcast that this router is leaving so peers can prune topology immediately.
7804    #[cfg(feature = "discovery")]
7805    pub fn announce_leave(&self) -> TelemetryResult<()> {
7806        let sender = self.sender_arc();
7807        let pkt = discovery::build_discovery_leave(sender.as_ref(), self.clock.now_ms())?;
7808        self.tx_item(RouterTxItem::Broadcast(RouterItem::Packet(pkt)))
7809    }
7810
7811    /// Queue a discovery advertisement if the adaptive cadence says one is due.
7812    #[cfg(feature = "discovery")]
7813    pub fn poll_discovery(&self) -> TelemetryResult<bool> {
7814        self.poll_discovery_announce()
7815    }
7816
7817    #[cfg(feature = "discovery")]
7818    pub fn request_topology(&self) -> TelemetryResult<()> {
7819        let sender = self.sender_arc();
7820        let pkt =
7821            discovery::build_discovery_topology_request(sender.as_ref(), self.clock.now_ms())?;
7822        self.tx_item(RouterTxItem::Broadcast(RouterItem::Packet(pkt)))
7823    }
7824
7825    #[cfg(feature = "discovery")]
7826    pub fn request_schema(&self) -> TelemetryResult<()> {
7827        let sender = self.sender_arc();
7828        let pkt = discovery::build_discovery_schema_request(sender.as_ref(), self.clock.now_ms())?;
7829        self.tx_item(RouterTxItem::Broadcast(RouterItem::Packet(pkt)))
7830    }
7831
7832    /// Mark a data type as a network-managed variable.
7833    ///
7834    /// The router caches the latest packet for this type when it is locally transmitted or
7835    /// received from the network. Peers can request the latest cached value and the router will
7836    /// replay the original value packet, so user endpoint handlers see the same API shape as a
7837    /// normal update.
7838    pub fn enable_managed_variable(&self, ty: DataType) -> TelemetryResult<()> {
7839        self.enable_network_variable(ty, NetworkVariablePermissions::READ_WRITE)
7840    }
7841
7842    /// Mark a data type as a network variable with local read/write permissions.
7843    ///
7844    /// Users do not register a separate endpoint for network variables. Values are cached by
7845    /// data type and refreshed through SEDSnet's internal managed-variable control packets.
7846    pub fn enable_network_variable(
7847        &self,
7848        ty: DataType,
7849        permissions: NetworkVariablePermissions,
7850    ) -> TelemetryResult<()> {
7851        if is_internal_control_type(ty) {
7852            return Err(TelemetryError::InvalidType);
7853        }
7854        let mut st = self.state.lock();
7855        st.managed_variable_types.insert(ty.as_u32());
7856        st.managed_variable_permissions
7857            .insert(ty.as_u32(), permissions);
7858        Ok(())
7859    }
7860
7861    /// Register a callback that runs when an inbound network update changes this variable cache.
7862    ///
7863    /// The callback is invoked after the router state lock is released, so it may call back into
7864    /// the router. Local `set_network_variable` and `seed_managed_variable` calls update the cache
7865    /// without invoking this network-update callback.
7866    pub fn on_network_variable_update<F>(&self, ty: DataType, f: F) -> TelemetryResult<()>
7867    where
7868        F: Fn(&Packet) -> TelemetryResult<()> + Send + Sync + 'static,
7869    {
7870        if is_internal_control_type(ty) {
7871            return Err(TelemetryError::InvalidType);
7872        }
7873        let mut st = self.state.lock();
7874        st.managed_variable_types.insert(ty.as_u32());
7875        st.network_variable_update_handlers
7876            .entry(ty.as_u32())
7877            .or_default()
7878            .push(NetworkVariableUpdateHandler {
7879                handler: Arc::new(f),
7880            });
7881        Ok(())
7882    }
7883
7884    pub fn disable_managed_variable(&self, ty: DataType) {
7885        let mut st = self.state.lock();
7886        st.managed_variable_types.remove(&ty.as_u32());
7887        st.managed_variable_permissions.remove(&ty.as_u32());
7888        st.managed_variable_latest.remove(&ty.as_u32());
7889        st.network_variable_update_handlers.remove(&ty.as_u32());
7890    }
7891
7892    pub fn seed_managed_variable(&self, pkt: Packet) -> TelemetryResult<()> {
7893        if is_internal_control_type(pkt.data_type()) {
7894            return Err(TelemetryError::InvalidType);
7895        }
7896        pkt.validate()?;
7897        {
7898            let mut st = self.state.lock();
7899            st.managed_variable_types.insert(pkt.data_type().as_u32());
7900        }
7901        self.cache_managed_variable_packet(&pkt, false)
7902    }
7903
7904    pub fn cached_managed_variable(&self, ty: DataType) -> Option<Packet> {
7905        self.managed_variable_latest(ty)
7906    }
7907
7908    /// Set a network variable for the whole network, permission policy allowing.
7909    ///
7910    /// The packet is cached locally and sent as normal user data. Routers that have seen or enabled
7911    /// this variable update their local cache internally; applications do not need to register a
7912    /// managed-variable endpoint.
7913    pub fn set_network_variable(&self, pkt: Packet) -> TelemetryResult<()> {
7914        if is_internal_control_type(pkt.data_type()) {
7915            return Err(TelemetryError::InvalidType);
7916        }
7917        pkt.validate()?;
7918        let ty = pkt.data_type();
7919        {
7920            let mut st = self.state.lock();
7921            st.managed_variable_types.insert(ty.as_u32());
7922            let perms = Self::managed_variable_permissions_locked(&st, ty);
7923            if !perms.write {
7924                return Err(TelemetryError::PermissionDenied);
7925            }
7926        }
7927        self.cache_managed_variable_packet(&pkt, false)?;
7928        self.tx(pkt)
7929    }
7930
7931    /// Read a cached network variable, requesting a refresh if missing or stale.
7932    ///
7933    /// Returns the cached value when present. If the value is missing, this queues an internal
7934    /// managed-variable request and returns `Ok(None)`. If the value is stale, this queues a refresh
7935    /// request and returns the stale cached value so callers can continue operating while the network
7936    /// catches up.
7937    #[cfg(feature = "discovery")]
7938    pub fn get_network_variable(
7939        &self,
7940        ty: DataType,
7941        stale_after_ms: Option<u64>,
7942    ) -> TelemetryResult<Option<Packet>> {
7943        if is_internal_control_type(ty) {
7944            return Err(TelemetryError::InvalidType);
7945        }
7946        {
7947            let mut st = self.state.lock();
7948            st.managed_variable_types.insert(ty.as_u32());
7949            let perms = Self::managed_variable_permissions_locked(&st, ty);
7950            if !perms.read {
7951                return Err(TelemetryError::PermissionDenied);
7952            }
7953        }
7954        let cached = self.managed_variable_latest_with_age(ty);
7955        let needs_refresh = match (cached.as_ref(), stale_after_ms) {
7956            (None, _) => true,
7957            (Some((_pkt, age_ms)), Some(max_age_ms)) => *age_ms > max_age_ms,
7958            (Some(_), None) => false,
7959        };
7960        if needs_refresh {
7961            self.request_managed_variable(ty)?;
7962        }
7963        Ok(cached.map(|(pkt, _age_ms)| pkt))
7964    }
7965
7966    /// Read a cached network variable without issuing a network refresh.
7967    pub fn get_cached_network_variable(&self, ty: DataType) -> TelemetryResult<Option<Packet>> {
7968        if is_internal_control_type(ty) {
7969            return Err(TelemetryError::InvalidType);
7970        }
7971        if !self.can_read_managed_variable(ty) {
7972            return Err(TelemetryError::PermissionDenied);
7973        }
7974        Ok(self.managed_variable_latest(ty))
7975    }
7976
7977    #[cfg(feature = "discovery")]
7978    pub fn request_managed_variable(&self, ty: DataType) -> TelemetryResult<()> {
7979        if is_internal_control_type(ty) {
7980            return Err(TelemetryError::InvalidType);
7981        }
7982        if !self.can_read_managed_variable(ty) {
7983            return Err(TelemetryError::PermissionDenied);
7984        }
7985        let sender = self.sender_arc();
7986        let pkt =
7987            discovery::build_managed_variable_request(sender.as_ref(), self.clock.now_ms(), ty)?;
7988        self.tx_item(RouterTxItem::Broadcast(RouterItem::Packet(pkt)))
7989    }
7990
7991    #[cfg(feature = "discovery")]
7992    pub fn request_managed_variable_by_name(&self, ty_name: &str) -> TelemetryResult<()> {
7993        let ty = DataType::try_named(ty_name).ok_or(TelemetryError::InvalidType)?;
7994        self.request_managed_variable(ty)
7995    }
7996
7997    /// Export the current discovery-driven network topology view.
7998    #[cfg(feature = "discovery")]
7999    pub fn export_topology(&self) -> TopologySnapshot {
8000        let now_ms = self.clock.now_ms();
8001        let mut st = self.state.lock();
8002        if Self::prune_discovery_routes_locked(&mut st, now_ms) {
8003            Self::note_discovery_topology_change_locked(&mut st, now_ms);
8004        }
8005        let routes = st
8006            .discovery_routes
8007            .iter()
8008            .filter_map(|(&side_id, route)| {
8009                let side = st.sides.get(side_id)?.as_ref()?;
8010                let announcers = route
8011                    .announcers
8012                    .iter()
8013                    .map(|(sender_id, sender_state)| TopologyAnnouncerRoute {
8014                        sender_id: sender_id.clone(),
8015                        reachable_endpoints: sender_state
8016                            .reachable
8017                            .iter()
8018                            .copied()
8019                            .filter(|ep| !discovery::is_router_control_endpoint(*ep))
8020                            .collect(),
8021                        reachable_timesync_sources: sender_state.reachable_timesync_sources.clone(),
8022                        routers: sender_state.topology_boards.clone(),
8023                        last_seen_ms: sender_state.last_seen_ms,
8024                        age_ms: now_ms.saturating_sub(sender_state.last_seen_ms),
8025                    })
8026                    .collect();
8027                Some(TopologySideRoute {
8028                    side_id,
8029                    side_name: side.name,
8030                    reachable_endpoints: route
8031                        .reachable
8032                        .iter()
8033                        .copied()
8034                        .filter(|ep| !discovery::is_router_control_endpoint(*ep))
8035                        .collect(),
8036                    reachable_timesync_sources: route.reachable_timesync_sources.clone(),
8037                    announcers,
8038                    last_seen_ms: route.last_seen_ms,
8039                    age_ms: now_ms.saturating_sub(route.last_seen_ms),
8040                })
8041            })
8042            .collect();
8043        let routers = self.advertised_discovery_topology_for_link_locked(&st, now_ms, true);
8044        let advertised_endpoints =
8045            self.advertised_discovery_endpoints_for_link_locked(&st, now_ms, true);
8046        let advertised_timesync_sources =
8047            self.advertised_discovery_timesync_sources_for_link_locked(&st, now_ms);
8048        let links = discovery::topology_links_from_boards(&routers);
8049        TopologySnapshot {
8050            advertised_endpoints,
8051            advertised_timesync_sources,
8052            routers,
8053            links,
8054            routes,
8055            current_announce_interval_ms: st.discovery_cadence.current_interval_ms,
8056            next_announce_ms: st.discovery_cadence.next_announce_ms,
8057        }
8058    }
8059
8060    #[cfg(feature = "discovery")]
8061    pub fn client_stats(&self, sender_id: &str) -> Option<ClientStatsSnapshot> {
8062        let now_ms = self.clock.now_ms();
8063        let st = self.state.lock();
8064        let mut side_ids = Vec::new();
8065        let mut side_names = Vec::new();
8066        let mut last_seen_ms = None::<u64>;
8067        let mut reachable_endpoints = Vec::new();
8068        let mut reachable_timesync_sources = Vec::new();
8069        let mut packets_sent = 0u64;
8070        let mut packets_received = 0u64;
8071        let mut bytes_sent = 0u64;
8072        let mut bytes_received = 0u64;
8073
8074        for (side_id, route) in &st.discovery_routes {
8075            let Some(sender_state) = route.announcers.get(sender_id) else {
8076                continue;
8077            };
8078            side_ids.push(*side_id);
8079            if let Some(side_name) = st
8080                .sides
8081                .get(*side_id)
8082                .and_then(|side| side.as_ref())
8083                .map(|side| side.name)
8084            {
8085                side_names.push(side_name);
8086            }
8087            last_seen_ms = Some(last_seen_ms.unwrap_or(0).max(sender_state.last_seen_ms));
8088            reachable_endpoints.extend(sender_state.reachable.iter().copied());
8089            reachable_timesync_sources
8090                .extend(sender_state.reachable_timesync_sources.iter().cloned());
8091            if let Some(stats) = st.side_runtime_stats.get(side_id) {
8092                packets_sent = packets_sent.saturating_add(stats.tx_packets);
8093                packets_received = packets_received.saturating_add(stats.rx_packets);
8094                bytes_sent = bytes_sent.saturating_add(stats.tx_bytes);
8095                bytes_received = bytes_received.saturating_add(stats.rx_bytes);
8096            }
8097        }
8098
8099        if side_ids.is_empty() {
8100            return None;
8101        }
8102        reachable_endpoints.retain(|ep| !discovery::is_router_control_endpoint(*ep));
8103        reachable_endpoints.sort_unstable();
8104        reachable_endpoints.dedup();
8105        reachable_timesync_sources.sort_unstable();
8106        reachable_timesync_sources.dedup();
8107        side_ids.sort_unstable();
8108        side_ids.dedup();
8109        side_names.sort_unstable();
8110        side_names.dedup();
8111        let age_ms = last_seen_ms.map(|seen| now_ms.saturating_sub(seen));
8112        Some(ClientStatsSnapshot {
8113            sender_id: sender_id.to_string(),
8114            connected: age_ms.is_some_and(|age| age <= DISCOVERY_ROUTE_TTL_MS),
8115            side_ids,
8116            side_names,
8117            last_seen_ms,
8118            age_ms,
8119            reachable_endpoints,
8120            reachable_timesync_sources,
8121            packets_sent,
8122            packets_received,
8123            bytes_sent,
8124            bytes_received,
8125        })
8126    }
8127
8128    pub fn export_runtime_stats(&self) -> RuntimeStatsSnapshot {
8129        let now_ms = self.clock.now_ms();
8130        let isr_rx = self.isr_rx_queue.snapshot().unwrap_or((0, 0));
8131        let st = self.state.lock();
8132
8133        let mut sides = Vec::new();
8134        for (side_id, side) in st.sides.iter().enumerate() {
8135            let Some(side) = side.as_ref() else { continue };
8136            let stats = st
8137                .side_runtime_stats
8138                .get(&side_id)
8139                .cloned()
8140                .unwrap_or_default();
8141            let adaptive = st
8142                .adaptive_route_stats
8143                .get(&side_id)
8144                .cloned()
8145                .unwrap_or_default()
8146                .snapshot(now_ms, true);
8147            let (tx_template_count, rx_template_count) = st
8148                .side_transport
8149                .get(&side_id)
8150                .map(|state| (state.tx_template_count(), state.rx_template_count()))
8151                .unwrap_or((0, 0));
8152            let mut data_types: Vec<RuntimeTypeStats> = stats
8153                .data_types
8154                .into_iter()
8155                .map(|(ty, item)| RuntimeTypeStats {
8156                    data_type: DataType(ty),
8157                    tx_packets: item.tx_packets,
8158                    tx_bytes: item.tx_bytes,
8159                    rx_packets: item.rx_packets,
8160                    rx_bytes: item.rx_bytes,
8161                    relayed_tx_packets: item.relayed_tx_packets,
8162                    relayed_tx_bytes: item.relayed_tx_bytes,
8163                    relayed_rx_packets: item.relayed_rx_packets,
8164                    relayed_rx_bytes: item.relayed_rx_bytes,
8165                    tx_retries: item.tx_retries,
8166                    handler_failures: item.handler_failures,
8167                })
8168                .collect();
8169            data_types.sort_unstable_by_key(|item| item.data_type.as_u32());
8170            sides.push(RuntimeSideStats {
8171                side_id,
8172                side_name: side.name,
8173                reliable_enabled: side.opts.reliable_enabled,
8174                link_local_enabled: side.opts.link_local_enabled,
8175                header_template_enabled: side.opts.header_template_enabled,
8176                max_frame_bytes: side.opts.max_frame_bytes,
8177                compact_header_target_bytes: side.opts.compact_header_target_bytes,
8178                side_transport_profile: side.opts.effective_transport_profile().as_str(),
8179                ingress_enabled: side.opts.ingress_enabled,
8180                egress_enabled: side.opts.egress_enabled,
8181                tx_packets: stats.tx_packets,
8182                tx_bytes: stats.tx_bytes,
8183                rx_packets: stats.rx_packets,
8184                rx_bytes: stats.rx_bytes,
8185                relayed_tx_packets: stats.relayed_tx_packets,
8186                relayed_tx_bytes: stats.relayed_tx_bytes,
8187                relayed_rx_packets: stats.relayed_rx_packets,
8188                relayed_rx_bytes: stats.relayed_rx_bytes,
8189                local_delivery_packets: stats.local_delivery_packets,
8190                tx_retries: stats.tx_retries,
8191                tx_handler_failures: stats.tx_handler_failures,
8192                local_handler_failures: stats.local_handler_failures,
8193                total_handler_retries: stats.total_handler_retries,
8194                side_transport_full_frames: stats.side_transport_full_frames,
8195                side_transport_compact_frames: stats.side_transport_compact_frames,
8196                side_transport_compact_delta_frames: stats.side_transport_compact_delta_frames,
8197                side_transport_compact_omitted_timestamp_frames: stats
8198                    .side_transport_compact_omitted_timestamp_frames,
8199                side_transport_chunk_frames: stats.side_transport_chunk_frames,
8200                side_transport_raw_bytes: stats.side_transport_raw_bytes,
8201                side_transport_wire_bytes: stats.side_transport_wire_bytes,
8202                side_transport_bytes_saved: stats.side_transport_bytes_saved,
8203                side_transport_min_compact_overhead_bytes: stats
8204                    .side_transport_min_compact_overhead_bytes,
8205                side_transport_max_compact_overhead_bytes: stats
8206                    .side_transport_max_compact_overhead_bytes,
8207                side_transport_compact_target_misses: stats.side_transport_compact_target_misses,
8208                side_transport_template_evictions: stats.side_transport_template_evictions,
8209                side_transport_tx_template_count: tx_template_count,
8210                side_transport_rx_template_count: rx_template_count,
8211                max_side_transport_templates: side.opts.max_side_transport_templates,
8212                adaptive,
8213                data_types,
8214            });
8215        }
8216
8217        let mut route_modes: Vec<RouteModeStats> = st
8218            .route_selection_cursors
8219            .iter()
8220            .map(|(src, cursor)| RouteModeStats {
8221                src_side_id: *src,
8222                selection_mode: st.source_route_modes.get(src).copied(),
8223                cursor: *cursor,
8224            })
8225            .collect();
8226        for src in st.source_route_modes.keys() {
8227            if !route_modes.iter().any(|mode| mode.src_side_id == *src) {
8228                route_modes.push(RouteModeStats {
8229                    src_side_id: *src,
8230                    selection_mode: st.source_route_modes.get(src).copied(),
8231                    cursor: 0,
8232                });
8233            }
8234        }
8235        route_modes.sort_unstable_by_key(|mode| mode.src_side_id.unwrap_or(usize::MAX));
8236
8237        let mut route_overrides: Vec<RouteOverrideStats> = st
8238            .route_overrides
8239            .iter()
8240            .map(|((src, dst), enabled)| RouteOverrideStats {
8241                src_side_id: *src,
8242                dst_side_id: *dst,
8243                enabled: *enabled,
8244            })
8245            .collect();
8246        route_overrides.sort_unstable_by_key(|item| {
8247            (item.src_side_id.unwrap_or(usize::MAX), item.dst_side_id)
8248        });
8249
8250        let mut typed_route_overrides: Vec<TypedRouteOverrideStats> = st
8251            .typed_route_overrides
8252            .iter()
8253            .map(|((src, ty, dst), enabled)| TypedRouteOverrideStats {
8254                src_side_id: *src,
8255                data_type: DataType(*ty),
8256                dst_side_id: *dst,
8257                enabled: *enabled,
8258            })
8259            .collect();
8260        typed_route_overrides.sort_unstable_by_key(|item| {
8261            (
8262                item.src_side_id.unwrap_or(usize::MAX),
8263                item.data_type.as_u32(),
8264                item.dst_side_id,
8265            )
8266        });
8267
8268        let mut route_weights: Vec<RouteWeightStats> = st
8269            .route_weights
8270            .iter()
8271            .map(|((src, dst), weight)| RouteWeightStats {
8272                src_side_id: *src,
8273                dst_side_id: *dst,
8274                weight: *weight,
8275            })
8276            .collect();
8277        route_weights.sort_unstable_by_key(|item| {
8278            (item.src_side_id.unwrap_or(usize::MAX), item.dst_side_id)
8279        });
8280
8281        let mut route_priorities: Vec<RoutePriorityStats> = st
8282            .route_priorities
8283            .iter()
8284            .map(|((src, dst), priority)| RoutePriorityStats {
8285                src_side_id: *src,
8286                dst_side_id: *dst,
8287                priority: *priority,
8288            })
8289            .collect();
8290        route_priorities.sort_unstable_by_key(|item| {
8291            (item.src_side_id.unwrap_or(usize::MAX), item.dst_side_id)
8292        });
8293
8294        #[cfg(feature = "discovery")]
8295        let discovery = DiscoveryRuntimeStats {
8296            route_count: st.discovery_routes.len(),
8297            announcer_count: st
8298                .discovery_routes
8299                .values()
8300                .map(|route| route.announcers.len())
8301                .sum(),
8302            current_announce_interval_ms: Some(st.discovery_cadence.current_interval_ms),
8303            next_announce_ms: Some(st.discovery_cadence.next_announce_ms),
8304        };
8305        #[cfg(not(feature = "discovery"))]
8306        let discovery = DiscoveryRuntimeStats {
8307            route_count: 0,
8308            announcer_count: 0,
8309            current_announce_interval_ms: None,
8310            next_announce_ms: None,
8311        };
8312
8313        RuntimeStatsSnapshot {
8314            sides,
8315            route_modes,
8316            route_overrides,
8317            typed_route_overrides,
8318            route_weights,
8319            route_priorities,
8320            queues: QueueRuntimeStats {
8321                rx_len: isr_rx.0.saturating_add(st.received_queue.len()),
8322                rx_bytes: isr_rx.1.saturating_add(st.received_queue.bytes_used()),
8323                tx_len: st.transmit_queue.len(),
8324                tx_bytes: st.transmit_queue.bytes_used(),
8325                replay_len: 0,
8326                replay_bytes: 0,
8327                recent_rx_len: st.recent_rx.len(),
8328                recent_rx_bytes: st.recent_rx.bytes_used(),
8329                reliable_rx_buffered_len: st.reliable_rx_buffer_len(),
8330                reliable_rx_buffered_bytes: st.reliable_rx_buffered_bytes(),
8331                shared_queue_bytes_used: st.shared_queue_bytes_used(),
8332            },
8333            reliable: ReliableRuntimeStats {
8334                reliable_return_route_count: st.reliable_return_routes.len(),
8335                end_to_end_pending_count: st.end_to_end_reliable_tx.len(),
8336                end_to_end_pending_destination_count: st
8337                    .end_to_end_reliable_tx
8338                    .values()
8339                    .map(|sent| sent.pending_destinations.len())
8340                    .sum(),
8341                end_to_end_acked_cache_count: 0,
8342            },
8343            discovery,
8344            total_handler_failures: st.total_handler_failures,
8345            total_handler_retries: st.total_handler_retries,
8346        }
8347    }
8348
8349    /// Export current router memory usage/layout as JSON for profiling.
8350    pub fn export_memory_layout_json(&self) -> String {
8351        let isr_rx = self.isr_rx_queue.snapshot().unwrap_or((0, 0));
8352        let st = self.state.lock();
8353        #[cfg(feature = "discovery")]
8354        let discovery_bytes = st.discovery_bytes_used();
8355        #[cfg(not(feature = "discovery"))]
8356        let discovery_bytes = 0usize;
8357        let schema_bytes = crate::config::schema_bytes_used();
8358        let network_variable_cache_bytes = st
8359            .managed_variable_latest
8360            .values()
8361            .map(|entry| entry.packet.byte_cost())
8362            .sum::<usize>();
8363        let mut out = String::new();
8364        let _ = core::fmt::Write::write_fmt(
8365            &mut out,
8366            format_args!(
8367                "{{\"kind\":\"router\",\
8368                 \"shared_queue_bytes_used\":{},\"shared_queue_bytes_allocated\":{},\
8369                 \"rx_queue_bytes_used\":{},\"rx_queue_bytes_allocated\":{},\"rx_queue_len\":{},\
8370                 \"isr_rx_queue_bytes_used\":{},\"isr_rx_queue_bytes_allocated\":{},\"isr_rx_queue_len\":{},\
8371                 \"tx_queue_bytes_used\":{},\"tx_queue_bytes_allocated\":{},\"tx_queue_len\":{},\
8372                 \"recent_rx_bytes_used\":{},\"recent_rx_bytes_allocated\":{},\"recent_rx_len\":{},\
8373                 \"reliable_rx_buffer_bytes_used\":{},\"reliable_rx_buffer_bytes_allocated\":{},\"reliable_rx_buffer_len\":{},\
8374                 \"discovery_bytes_used\":{},\"discovery_bytes_allocated\":{},\
8375                 \"schema_bytes_used\":{},\"schema_bytes_allocated\":{},\
8376                 \"network_variable_cache_bytes_used\":{},\"network_variable_cache_bytes_allocated\":{},\"network_variable_cache_len\":{}}}",
8377                st.shared_queue_bytes_used(),
8378                MAX_QUEUE_BUDGET,
8379                st.received_queue.bytes_used(),
8380                st.received_queue.max_bytes(),
8381                st.received_queue.len(),
8382                isr_rx.1,
8383                MAX_QUEUE_BUDGET,
8384                isr_rx.0,
8385                st.transmit_queue.bytes_used(),
8386                st.transmit_queue.max_bytes(),
8387                st.transmit_queue.len(),
8388                st.recent_rx.bytes_used(),
8389                st.recent_rx.max_bytes(),
8390                st.recent_rx.len(),
8391                st.reliable_rx_buffered_bytes(),
8392                MAX_QUEUE_BUDGET,
8393                st.reliable_rx_buffer_len(),
8394                discovery_bytes,
8395                MAX_QUEUE_BUDGET,
8396                schema_bytes,
8397                MAX_QUEUE_BUDGET,
8398                network_variable_cache_bytes,
8399                MAX_QUEUE_BUDGET,
8400                st.managed_variable_latest.len(),
8401            ),
8402        );
8403        out
8404    }
8405
8406    #[cfg(test)]
8407    pub(crate) fn debug_end_to_end_pending_destination_count(
8408        &self,
8409        packet_id: u64,
8410    ) -> Option<usize> {
8411        let st = self.state.lock();
8412        st.end_to_end_reliable_tx
8413            .get(&packet_id)
8414            .map(|sent| sent.pending_destinations.len())
8415    }
8416
8417    #[cfg(test)]
8418    pub(crate) fn debug_end_to_end_tracked_count(&self) -> usize {
8419        let st = self.state.lock();
8420        st.end_to_end_reliable_tx.len()
8421    }
8422
8423    #[cfg(test)]
8424    pub(crate) fn debug_reliable_return_route_count(&self) -> usize {
8425        let st = self.state.lock();
8426        st.reliable_return_routes.len()
8427    }
8428
8429    #[cfg(test)]
8430    pub(crate) fn debug_queue_lengths(&self) -> (usize, usize, usize) {
8431        let st = self.state.lock();
8432        (
8433            st.received_queue.len(),
8434            st.transmit_queue.len(),
8435            st.recent_rx.len(),
8436        )
8437    }
8438
8439    #[cfg(test)]
8440    pub(crate) fn debug_shared_queue_bytes_used(&self) -> usize {
8441        let st = self.state.lock();
8442        st.shared_queue_bytes_used()
8443    }
8444
8445    #[cfg(test)]
8446    pub(crate) fn debug_recent_rx_capacity(&self) -> (usize, usize) {
8447        let st = self.state.lock();
8448        (st.recent_rx.capacity(), st.recent_rx.max_bytes())
8449    }
8450
8451    /// Compute a de-dupe hash for a RouterItem.
8452    /// Uses packet ID for Packet items, and attempts to extract packet ID from
8453    /// packed bytes. If extraction fails, hashes raw bytes as a fallback.
8454    fn get_hash(item: &RouterItem) -> u64 {
8455        match item {
8456            RouterItem::Packet(pkt) => pkt.packet_id(),
8457            RouterItem::Packed(bytes) => {
8458                match wire_format::packet_id_from_wire(bytes.as_ref()) {
8459                    Ok(id) => id,
8460                    Err(_e) => {
8461                        // Fallback: if bytes are malformed (or compression feature mismatch),
8462                        // hash raw bytes so we can still dedupe identical network duplicates.
8463                        let h: u64 = 0x9E37_79B9_7F4A_7C15;
8464                        hash_bytes_u64(h, bytes.as_ref())
8465                    }
8466                }
8467            }
8468        }
8469    }
8470
8471    /// Remove a hash from the ring buffer of recent RX IDs.
8472    fn remove_pkt_id(&self, item: &RouterItem) {
8473        let hash = Self::get_hash(item);
8474        let mut st = self.state.lock();
8475        st.recent_rx.remove_value(&hash);
8476    }
8477
8478    /// Compute a de-dupe ID for a RouterItem and record it.
8479    /// Returns true if this item was seen recently (and should be skipped).
8480    fn is_duplicate_pkt(&self, item: &RouterItem) -> TelemetryResult<bool> {
8481        let id = Self::get_hash(item);
8482        let mut st = self.state.lock();
8483        if st.recent_rx.contains(&id) {
8484            Ok(true)
8485        } else {
8486            st.push_recent_rx(id)?;
8487            Ok(false)
8488        }
8489    }
8490
8491    /// Error helper when we have a full Packet.
8492    ///
8493    /// Sends a TelemetryError packet to all local endpoints except the failed one (if any).
8494    /// If no local endpoints remain, falls back to `fallback_stdout`.
8495    fn handle_callback_error(
8496        &self,
8497        pkt: &Packet,
8498        dest: Option<DataEndpoint>,
8499        e: TelemetryError,
8500        called_from_queue: bool,
8501    ) -> TelemetryResult<()> {
8502        let error_msg = match dest {
8503            Some(failed_local) => format!(
8504                "Handler for endpoint {:?} failed on device {:?}: {:?}",
8505                failed_local, DEVICE_IDENTIFIER, e
8506            ),
8507            None => format!(
8508                "TX Handler failed on device {:?}: {:?}",
8509                DEVICE_IDENTIFIER, e
8510            ),
8511        };
8512
8513        let mut recipients: Vec<DataEndpoint> = pkt
8514            .endpoints()
8515            .iter()
8516            .copied()
8517            .filter(|&ep| self.cfg.is_local_endpoint(ep))
8518            .collect();
8519        recipients.sort_unstable();
8520        recipients.dedup();
8521
8522        if let Some(failed_local) = dest {
8523            recipients.retain(|&ep| ep != failed_local);
8524        }
8525
8526        // If no local recipient exists, fall back to original packet endpoints
8527        // so error telemetry can still egress to remote links.
8528        if recipients.is_empty() {
8529            recipients = pkt.endpoints().to_vec();
8530            recipients.sort_unstable();
8531            recipients.dedup();
8532            if let Some(failed_local) = dest {
8533                recipients.retain(|&ep| ep != failed_local);
8534            }
8535        }
8536
8537        if recipients.is_empty() {
8538            fallback_stdout(&error_msg);
8539            return Ok(());
8540        }
8541
8542        let payload = make_error_payload(&error_msg);
8543
8544        let sender = self.sender_arc();
8545        let error_pkt = Packet::new(
8546            DataType::TelemetryError,
8547            &recipients,
8548            sender.as_ref(),
8549            self.packet_timestamp_ms(),
8550            payload,
8551        )?;
8552
8553        self.emit_internal_tx(
8554            RouterTxItem::Broadcast(RouterItem::Packet(error_pkt)),
8555            false,
8556            called_from_queue,
8557        )
8558    }
8559
8560    // ---------- PUBLIC API: queues ----------
8561
8562    /// Drain the transmit queue fully.
8563    #[inline]
8564    pub fn process_tx_queue(&self) -> TelemetryResult<()> {
8565        self.process_tx_queue_with_timeout(0)
8566    }
8567
8568    /// Drain both TX and RX queues fully (same semantics as `*_with_timeout(0)`).
8569    #[inline]
8570    pub fn process_all_queues(&self) -> TelemetryResult<()> {
8571        self.process_all_queues_with_timeout(0)
8572    }
8573
8574    /// Clear both the transmit and receive queues without processing.
8575    #[inline]
8576    pub fn clear_queues(&self) {
8577        let mut st = self.state.lock();
8578        st.transmit_queue.clear();
8579        st.received_queue.clear();
8580        drop(st);
8581        let _ = self.isr_rx_queue.clear();
8582    }
8583
8584    /// Clear only the receive queue without processing.
8585    #[inline]
8586    pub fn clear_rx_queue(&self) {
8587        let mut st = self.state.lock();
8588        st.received_queue.clear();
8589        drop(st);
8590        let _ = self.isr_rx_queue.clear();
8591    }
8592
8593    /// Clear only the transmit queue without processing.
8594    #[inline]
8595    pub fn clear_tx_queue(&self) {
8596        let mut st = self.state.lock();
8597        st.transmit_queue.clear();
8598    }
8599
8600    /// Process packets in the transmit queue for up to `timeout_ms` milliseconds.
8601    /// If `timeout_ms == 0`, drains the queue fully.
8602    fn process_tx_queue_with_timeout_impl(&self, timeout_ms: u32) -> TelemetryResult<()> {
8603        let start = self.clock.now_ms();
8604        loop {
8605            self.process_reliable_timeouts()?;
8606            self.process_end_to_end_reliable_timeouts()?;
8607            #[cfg(feature = "discovery")]
8608            let _ = self.drain_queued_discovery_rx_before_tx()?;
8609            let pkt_opt = {
8610                let mut st = self.state.lock();
8611                st.transmit_queue.pop_front()
8612            };
8613            let Some(pkt) = pkt_opt else { break };
8614            self.tx_item_impl(pkt.item, pkt.ignore_local, true)?;
8615            if timeout_ms != 0 && self.clock.now_ms().wrapping_sub(start) >= timeout_ms as u64 {
8616                break;
8617            }
8618        }
8619        Ok(())
8620    }
8621
8622    /// Process packets in the transmit queue for up to `timeout_ms` milliseconds.
8623    /// If `timeout_ms == 0`, drains the queue fully.
8624    pub fn process_tx_queue_with_timeout(&self, timeout_ms: u32) -> TelemetryResult<()> {
8625        #[cfg(feature = "timesync")]
8626        let _ = self.poll_timesync()?;
8627        #[cfg(feature = "discovery")]
8628        let _ = self.poll_discovery()?;
8629        self.process_tx_queue_with_timeout_impl(timeout_ms)
8630    }
8631
8632    /// Process a single queued receive item.
8633    #[inline]
8634    fn process_rx_queue_item(&self, item: RouterRxItem) -> TelemetryResult<()> {
8635        self.rx_item(&item, true)
8636    }
8637
8638    /// Process packets in the receive queue for up to `timeout_ms` milliseconds.
8639    /// If `timeout_ms == 0`, drains the queue fully.
8640    fn process_rx_queue_with_timeout_impl(&self, timeout_ms: u32) -> TelemetryResult<()> {
8641        let start = self.clock.now_ms();
8642        loop {
8643            let item_opt = self.isr_rx_queue.pop_front().unwrap_or(None).or_else(|| {
8644                let mut st = self.state.lock();
8645                st.received_queue.pop_front()
8646            });
8647            let Some(item) = item_opt else { break };
8648            self.process_rx_queue_item(item)?;
8649            if timeout_ms != 0 && self.clock.now_ms().wrapping_sub(start) >= timeout_ms as u64 {
8650                break;
8651            }
8652        }
8653        Ok(())
8654    }
8655
8656    /// Process packets in the receive queue for up to `timeout_ms` milliseconds.
8657    /// If `timeout_ms == 0`, drains the queue fully.
8658    pub fn process_rx_queue_with_timeout(&self, timeout_ms: u32) -> TelemetryResult<()> {
8659        #[cfg(feature = "timesync")]
8660        let _ = self.poll_timesync()?;
8661        #[cfg(feature = "discovery")]
8662        let _ = self.poll_discovery()?;
8663        self.process_rx_queue_with_timeout_impl(timeout_ms)
8664    }
8665
8666    /// Process both transmit and receive queues for up to `timeout_ms` milliseconds.
8667    /// If `timeout_ms == 0`, drains both queues fully.
8668    fn process_all_queues_with_timeout_impl(&self, timeout_ms: u32) -> TelemetryResult<()> {
8669        if timeout_ms == 0 {
8670            loop {
8671                let mut did_any = false;
8672                self.process_reliable_timeouts()?;
8673                self.process_end_to_end_reliable_timeouts()?;
8674                #[cfg(feature = "discovery")]
8675                if self.drain_queued_discovery_rx_before_tx()? {
8676                    did_any = true;
8677                }
8678
8679                if let Some(pkt) = {
8680                    let mut st = self.state.lock();
8681                    st.transmit_queue.pop_front()
8682                } {
8683                    self.tx_item_impl(pkt.item, pkt.ignore_local, true)?;
8684                    did_any = true;
8685                }
8686
8687                if let Some(item) = self.isr_rx_queue.pop_front().unwrap_or(None).or_else(|| {
8688                    let mut st = self.state.lock();
8689                    st.received_queue.pop_front()
8690                }) {
8691                    self.process_rx_queue_item(item)?;
8692                    did_any = true;
8693                }
8694
8695                if !did_any {
8696                    break;
8697                }
8698            }
8699            return Ok(());
8700        }
8701
8702        let tx_budget_ms = u64::from(timeout_ms / 2);
8703        let rx_budget_ms = u64::from(timeout_ms) - tx_budget_ms;
8704
8705        let tx_start = self.clock.now_ms();
8706        loop {
8707            self.process_reliable_timeouts()?;
8708            self.process_end_to_end_reliable_timeouts()?;
8709            #[cfg(feature = "discovery")]
8710            let _ = self.drain_queued_discovery_rx_before_tx()?;
8711            let pkt_opt = {
8712                let mut st = self.state.lock();
8713                st.transmit_queue.pop_front()
8714            };
8715            let Some(pkt) = pkt_opt else { break };
8716            self.tx_item_impl(pkt.item, pkt.ignore_local, true)?;
8717            if self.clock.now_ms().wrapping_sub(tx_start) >= tx_budget_ms {
8718                break;
8719            }
8720        }
8721
8722        let rx_start = self.clock.now_ms();
8723        loop {
8724            let item_opt = self.isr_rx_queue.pop_front().unwrap_or(None).or_else(|| {
8725                let mut st = self.state.lock();
8726                st.received_queue.pop_front()
8727            });
8728            let Some(item) = item_opt else { break };
8729            self.process_rx_queue_item(item)?;
8730            if self.clock.now_ms().wrapping_sub(rx_start) >= rx_budget_ms {
8731                break;
8732            }
8733        }
8734
8735        Ok(())
8736    }
8737
8738    /// Process both transmit and receive queues for up to `timeout_ms` milliseconds.
8739    /// If `timeout_ms == 0`, drains both queues fully.
8740    pub fn process_all_queues_with_timeout(&self, timeout_ms: u32) -> TelemetryResult<()> {
8741        #[cfg(feature = "timesync")]
8742        let _ = self.poll_timesync()?;
8743        #[cfg(feature = "discovery")]
8744        let _ = self.poll_discovery()?;
8745        self.process_all_queues_with_timeout_impl(timeout_ms)
8746    }
8747
8748    /// Runs one application-loop maintenance cycle.
8749    ///
8750    /// This polls built-in time sync and discovery when those features are compiled in, then
8751    /// drains queued TX/RX work for up to `timeout_ms` milliseconds.
8752    pub fn periodic(&self, timeout_ms: u32) -> TelemetryResult<()> {
8753        #[cfg(feature = "timesync")]
8754        let _ = self.poll_timesync()?;
8755
8756        #[cfg(feature = "discovery")]
8757        {
8758            let _ = self.poll_discovery()?;
8759        }
8760
8761        self.process_all_queues_with_timeout_impl(timeout_ms)
8762    }
8763
8764    /// Runs one application-loop maintenance cycle without polling built-in time sync.
8765    ///
8766    /// Discovery is still polled when that feature is compiled in, then queued TX/RX work is
8767    /// drained for up to `timeout_ms` milliseconds.
8768    pub fn periodic_no_timesync(&self, timeout_ms: u32) -> TelemetryResult<()> {
8769        #[cfg(feature = "discovery")]
8770        {
8771            let _ = self.poll_discovery()?;
8772        }
8773
8774        self.process_all_queues_with_timeout_impl(timeout_ms)
8775    }
8776
8777    /// Enqueue an item for later transmission with flags.
8778    #[inline]
8779    fn tx_queue_item_with_flags(
8780        &self,
8781        item: RouterTxItem,
8782        ignore_local: bool,
8783    ) -> TelemetryResult<()> {
8784        let priority = match &item {
8785            RouterTxItem::Broadcast(data) => Self::router_item_priority(data)?,
8786            RouterTxItem::EndToEndReplay { .. } => {
8787                Self::router_item_priority_bumped(DataType::ReliableAck)
8788            }
8789            RouterTxItem::ToSide { data, .. } => Self::router_item_priority(data)?,
8790            RouterTxItem::ReliableReplay { bytes, .. } => {
8791                let ty = wire_format::peek_envelope(bytes.as_ref())?.ty;
8792                Self::router_item_priority_bumped(ty)
8793            }
8794        };
8795        self.tx_queue_item_with_priority(item, ignore_local, priority)
8796    }
8797
8798    #[inline]
8799    fn tx_queue_item_with_priority(
8800        &self,
8801        item: RouterTxItem,
8802        ignore_local: bool,
8803        priority: u8,
8804    ) -> TelemetryResult<()> {
8805        let mut st = self.state.lock();
8806        st.push_transmit(TxQueued {
8807            item,
8808            ignore_local,
8809            priority,
8810        })?;
8811        Ok(())
8812    }
8813
8814    /// Enqueue an item for later transmission (default: local dispatch enabled).
8815    #[inline]
8816    fn tx_queue_item(&self, item: RouterTxItem) -> TelemetryResult<()> {
8817        self.tx_queue_item_with_flags(item, false)
8818    }
8819
8820    #[inline]
8821    fn try_enter_side_tx(&self) -> Option<crate::lock::ReentryGuard<'_>> {
8822        self.side_tx_gate.try_enter()
8823    }
8824
8825    #[inline]
8826    fn side_tx_active(&self) -> bool {
8827        self.side_tx_gate.is_active()
8828    }
8829
8830    // ---------- PUBLIC API: RX queue ----------
8831
8832    /// Drain the receive queue fully.
8833    #[inline]
8834    pub fn process_rx_queue(&self) -> TelemetryResult<()> {
8835        self.process_rx_queue_with_timeout(0)
8836    }
8837
8838    /// Enqueue packed bytes for RX processing as locally-originated input.
8839    #[inline]
8840    pub fn rx_packed_queue(&self, bytes: &[u8]) -> TelemetryResult<()> {
8841        let data = RouterItem::Packed(Arc::from(bytes));
8842        let priority = Self::router_item_priority(&data)?;
8843        let mut st = self.state.lock();
8844        st.push_received(RouterRxItem {
8845            src: None,
8846            data,
8847            priority,
8848        })?;
8849        Ok(())
8850    }
8851
8852    /// ISR-safe, non-blocking enqueue of packed bytes for RX processing.
8853    ///
8854    /// Returns `TelemetryError::Io("rx queue busy")` if another context is
8855    /// currently mutating the ISR RX queue.
8856    #[inline]
8857    pub fn rx_packed_queue_isr(&self, bytes: &[u8]) -> TelemetryResult<()> {
8858        let data = RouterItem::Packed(Arc::from(bytes));
8859        let priority = Self::router_item_priority(&data)?;
8860        self.isr_rx_queue.push_back_prioritized(RouterRxItem {
8861            src: None,
8862            data,
8863            priority,
8864        })
8865    }
8866
8867    /// Enqueue a decoded packet for RX processing as locally-originated input.
8868    #[inline]
8869    pub fn rx_queue(&self, pkt: Packet) -> TelemetryResult<()> {
8870        pkt.validate()?;
8871        let data = RouterItem::Packet(pkt);
8872        let priority = Self::router_item_priority(&data)?;
8873        let mut st = self.state.lock();
8874        st.push_received(RouterRxItem {
8875            src: None,
8876            data,
8877            priority,
8878        })?;
8879        Ok(())
8880    }
8881
8882    /// ISR-safe, non-blocking enqueue of a packet for RX processing.
8883    ///
8884    /// Returns `TelemetryError::Io("rx queue busy")` if another context is
8885    /// currently mutating the ISR RX queue.
8886    #[inline]
8887    pub fn rx_queue_isr(&self, pkt: Packet) -> TelemetryResult<()> {
8888        pkt.validate()?;
8889        let data = RouterItem::Packet(pkt);
8890        let priority = Self::router_item_priority(&data)?;
8891        self.isr_rx_queue.push_back_prioritized(RouterRxItem {
8892            src: None,
8893            data,
8894            priority,
8895        })
8896    }
8897
8898    /// Enqueue a decoded packet for RX processing with an explicit ingress side.
8899    #[inline]
8900    pub fn rx_queue_from_side(&self, pkt: Packet, side: RouterSideId) -> TelemetryResult<()> {
8901        self.ensure_side_ingress_enabled(side)?;
8902        pkt.validate()?;
8903        let data = RouterItem::Packet(pkt);
8904        let priority = Self::router_item_priority(&data)?;
8905        let mut st = self.state.lock();
8906        st.push_received(RouterRxItem {
8907            src: Some(side),
8908            data,
8909            priority,
8910        })?;
8911        Ok(())
8912    }
8913
8914    /// ISR-safe, non-blocking enqueue of a packet with explicit source side.
8915    ///
8916    /// Returns `TelemetryError::Io("rx queue busy")` if another context is
8917    /// currently mutating the ISR RX queue.
8918    #[inline]
8919    pub fn rx_queue_from_side_isr(&self, pkt: Packet, side: RouterSideId) -> TelemetryResult<()> {
8920        self.ensure_side_ingress_enabled(side)?;
8921        pkt.validate()?;
8922        let data = RouterItem::Packet(pkt);
8923        let priority = Self::router_item_priority(&data)?;
8924        self.isr_rx_queue.push_back_prioritized(RouterRxItem {
8925            src: Some(side),
8926            data,
8927            priority,
8928        })
8929    }
8930
8931    /// Enqueue packed bytes for RX processing with an explicit ingress side.
8932    #[inline]
8933    pub fn rx_packed_queue_from_side(
8934        &self,
8935        bytes: &[u8],
8936        side: RouterSideId,
8937    ) -> TelemetryResult<()> {
8938        self.ensure_side_ingress_enabled(side)?;
8939        let Some(decoded) = self.decode_side_transport_frame(side, bytes)? else {
8940            return Ok(());
8941        };
8942        let data = RouterItem::Packed(decoded);
8943        let priority = Self::router_item_priority(&data)?;
8944        let mut st = self.state.lock();
8945        st.push_received(RouterRxItem {
8946            src: Some(side),
8947            data,
8948            priority,
8949        })?;
8950        Ok(())
8951    }
8952
8953    /// ISR-safe, non-blocking enqueue of packed bytes with source side.
8954    ///
8955    /// Returns `TelemetryError::Io("rx queue busy")` if another context is
8956    /// currently mutating the ISR RX queue.
8957    #[inline]
8958    pub fn rx_packed_queue_from_side_isr(
8959        &self,
8960        bytes: &[u8],
8961        side: RouterSideId,
8962    ) -> TelemetryResult<()> {
8963        self.ensure_side_ingress_enabled(side)?;
8964        let data = RouterItem::Packed(Arc::from(bytes));
8965        let priority = Self::router_item_priority(&data)?;
8966        self.isr_rx_queue.push_back_prioritized(RouterRxItem {
8967            src: Some(side),
8968            data,
8969            priority,
8970        })
8971    }
8972
8973    fn retry_with_attempts<F, T, E>(&self, times: usize, f: F) -> Result<(T, usize), (E, usize)>
8974    where
8975        F: Fn() -> Result<T, E>,
8976    {
8977        let mut last_err = None;
8978        for attempt in 0..times {
8979            match f() {
8980                Ok(v) => return Ok((v, attempt + 1)),
8981                Err(e) => last_err = Some((e, attempt + 1)),
8982            }
8983        }
8984        Err(last_err.expect("times > 0"))
8985    }
8986
8987    /// Check if the specified endpoint has a packet handler registered.
8988    #[inline]
8989    fn endpoint_has_packet_handler(&self, ep: DataEndpoint) -> bool {
8990        self.cfg
8991            .handlers
8992            .iter()
8993            .any(|h| h.endpoint == ep && matches!(h.handler, EndpointHandlerFn::Packet(_)))
8994    }
8995
8996    /// Check if the specified endpoint has a packed handler registered.
8997    #[inline]
8998    fn endpoint_has_packed_handler(&self, ep: DataEndpoint) -> bool {
8999        self.cfg
9000            .handlers
9001            .iter()
9002            .any(|h| h.endpoint == ep && matches!(h.handler, EndpointHandlerFn::Packed(_)))
9003    }
9004
9005    fn packet_has_local_handler(&self, pkt: &Packet) -> bool {
9006        pkt.endpoints()
9007            .iter()
9008            .copied()
9009            .any(|ep| self.endpoint_has_packet_handler(ep) || self.endpoint_has_packed_handler(ep))
9010    }
9011
9012    /// Call the specified endpoint handler with retries on failure.
9013    ///
9014    /// - `data` is present when called from RX processing (queue or immediate).
9015    /// - `pkt_for_ctx` is required for Packet handlers.
9016    /// - `env_for_ctx` provides header-only context when we haven't unpacked.
9017    fn call_handler_with_retries(
9018        &self,
9019        dest: DataEndpoint,
9020        handler: &EndpointHandler,
9021        data: Option<&[u8]>,
9022        pkt_for_ctx: Option<&Packet>,
9023        env_for_ctx: Option<&wire_format::TelemetryEnvelope>,
9024        called_from_queue: bool,
9025    ) -> TelemetryResult<()> {
9026        let owned_tmp: Option<RouterItem>;
9027
9028        let item_for_ctx: &RouterItem = match (data, pkt_for_ctx) {
9029            (Some(d), _) => {
9030                owned_tmp = Some(RouterItem::Packed(Arc::from(d)));
9031                owned_tmp.as_ref().unwrap()
9032            }
9033            (None, Some(pkt)) => {
9034                owned_tmp = Some(RouterItem::Packet(pkt.clone()));
9035                owned_tmp.as_ref().unwrap()
9036            }
9037            (None, None) => {
9038                debug_assert!(
9039                    false,
9040                    "call_handler_with_retries called without data or packet context"
9041                );
9042                return Ok(());
9043            }
9044        };
9045
9046        match (&handler.handler, data) {
9047            (EndpointHandlerFn::Packet(f), _) => {
9048                let pkt = pkt_for_ctx.expect("Packet handler requires Packet context");
9049                with_retries(
9050                    self,
9051                    dest,
9052                    item_for_ctx,
9053                    pkt_for_ctx,
9054                    env_for_ctx,
9055                    called_from_queue,
9056                    || f(pkt),
9057                )
9058            }
9059
9060            (EndpointHandlerFn::Packed(f), Some(bytes)) => with_retries(
9061                self,
9062                dest,
9063                item_for_ctx,
9064                pkt_for_ctx,
9065                env_for_ctx,
9066                called_from_queue,
9067                || f(bytes),
9068            ),
9069
9070            (EndpointHandlerFn::Packed(_), None) => Ok(()),
9071        }
9072    }
9073
9074    /// Error helper when we only have an envelope (no full packet).
9075    ///
9076    /// Sends a TelemetryError packet to all local endpoints except the failed one (if any).
9077    /// If no local endpoints remain, falls back to `fallback_stdout`.
9078    fn handle_callback_error_from_env(
9079        &self,
9080        env: &wire_format::TelemetryEnvelope,
9081        dest: Option<DataEndpoint>,
9082        e: TelemetryError,
9083        called_from_queue: bool,
9084    ) -> TelemetryResult<()> {
9085        let mut recipients: Vec<DataEndpoint> = env
9086            .endpoints
9087            .iter()
9088            .copied()
9089            .filter(|&ep| self.cfg.is_local_endpoint(ep))
9090            .collect();
9091        recipients.sort_unstable();
9092        recipients.dedup();
9093        if let Some(failed) = dest {
9094            recipients.retain(|&ep| ep != failed);
9095        }
9096
9097        if recipients.is_empty() {
9098            recipients = env.endpoints.to_vec();
9099            recipients.sort_unstable();
9100            recipients.dedup();
9101            if let Some(failed) = dest {
9102                recipients.retain(|&ep| ep != failed);
9103            }
9104        }
9105
9106        let error_msg = format!(
9107            "Handler for endpoint {:?} failed on device {:?}: {:?}",
9108            dest, DEVICE_IDENTIFIER, e
9109        );
9110        if recipients.is_empty() {
9111            fallback_stdout(&error_msg);
9112            return Ok(());
9113        }
9114
9115        let payload = make_error_payload(&error_msg);
9116
9117        let error_pkt = Packet::new(
9118            DataType::TelemetryError,
9119            &recipients,
9120            &env.sender.clone(),
9121            env.timestamp_ms,
9122            payload,
9123        )?;
9124        self.emit_internal_tx(
9125            RouterTxItem::Broadcast(RouterItem::Packet(error_pkt)),
9126            false,
9127            called_from_queue,
9128        )
9129    }
9130
9131    fn handle_internal_reliable_packet(
9132        &self,
9133        pkt: &Packet,
9134        src: Option<RouterSideId>,
9135        called_from_queue: bool,
9136    ) -> TelemetryResult<bool> {
9137        if !matches!(
9138            pkt.data_type(),
9139            DataType::ReliableAck | DataType::ReliablePartialAck | DataType::ReliablePacketRequest
9140        ) {
9141            return Ok(false);
9142        }
9143
9144        let Some(src) = src else {
9145            return Ok(false);
9146        };
9147
9148        if pkt.data_type() == DataType::ReliableAck
9149            && Self::is_end_to_end_ack_sender(pkt.sender())
9150            && let Ok(packet_id) = Self::decode_end_to_end_reliable_ack(pkt.payload())
9151        {
9152            let mut st = self.state.lock();
9153            if let Some(sent) = st.end_to_end_reliable_tx.get_mut(&packet_id) {
9154                if let Some(sender_hash) = Self::decode_end_to_end_ack_sender_hash(pkt.sender()) {
9155                    sent.pending_destinations.remove(&sender_hash);
9156                    if sent.pending_destinations.is_empty() {
9157                        st.end_to_end_reliable_tx.remove(&packet_id);
9158                    }
9159                    return Ok(true);
9160                }
9161                st.end_to_end_reliable_tx.remove(&packet_id);
9162                return Ok(true);
9163            }
9164            return Ok(false);
9165        }
9166
9167        let vals = pkt.data_as_u32()?;
9168        if vals.len() != 2 {
9169            return Err(TelemetryError::Unpack("bad reliable control payload"));
9170        }
9171        let ty = DataType::try_from_u32(vals[0]).ok_or(TelemetryError::InvalidType)?;
9172        let seq = vals[1];
9173
9174        match pkt.data_type() {
9175            DataType::ReliableAck => {
9176                self.handle_reliable_ack(src, ty, seq);
9177                Ok(true)
9178            }
9179            DataType::ReliablePartialAck => {
9180                self.handle_reliable_partial_ack(src, ty, seq);
9181                Ok(true)
9182            }
9183            DataType::ReliablePacketRequest => {
9184                self.queue_reliable_retransmit(src, ty, seq, called_from_queue)?;
9185                Ok(true)
9186            }
9187            _ => Ok(false),
9188        }
9189    }
9190
9191    /// Core receive function handling both Packet and Packed items.
9192    ///
9193    /// Relay mode: if a destination endpoint has no matching local handler and the packet has
9194    /// any remotely-forwardable endpoints, the router will rebroadcast the packet ONCE, excluding
9195    /// the ingress side.
9196    fn rx_item(&self, item: &RouterRxItem, called_from_queue: bool) -> TelemetryResult<()> {
9197        if let Some(src) = item.src {
9198            self.ensure_side_ingress_enabled(src)?;
9199            match &item.data {
9200                RouterItem::Packet(pkt) => {
9201                    let bytes = wire_format::pack_packet(pkt).len();
9202                    self.note_side_rx(src, pkt.data_type(), bytes, true);
9203                }
9204                RouterItem::Packed(bytes) => {
9205                    if let Ok(env) = wire_format::peek_envelope(bytes.as_ref()) {
9206                        self.note_side_rx(src, env.ty, bytes.len(), true);
9207                    }
9208                }
9209            }
9210            match &item.data {
9211                RouterItem::Packet(pkt) => {
9212                    if is_reliable_type(pkt.data_type())
9213                        && !is_internal_control_type(pkt.data_type())
9214                    {
9215                        self.note_reliable_return_route(src, pkt.packet_id());
9216                    }
9217                }
9218                RouterItem::Packed(bytes) => {
9219                    if let Ok(env) = wire_format::peek_envelope(bytes.as_ref())
9220                        && is_reliable_type(env.ty)
9221                        && !is_internal_control_type(env.ty)
9222                        && let Ok(packet_id) = wire_format::packet_id_from_wire(bytes.as_ref())
9223                    {
9224                        self.note_reliable_return_route(src, packet_id);
9225                    }
9226                }
9227            }
9228        }
9229        match &item.data {
9230            RouterItem::Packet(pkt) => {
9231                if !is_internal_control_type(pkt.data_type()) {
9232                    self.remember_managed_variable_packet(pkt)?;
9233                }
9234            }
9235            RouterItem::Packed(bytes) => {
9236                if let Ok(env) = wire_format::peek_envelope(bytes.as_ref())
9237                    && !is_internal_control_type(env.ty)
9238                    && self.is_managed_variable_type(env.ty)
9239                {
9240                    let pkt = wire_format::unpack_packet(bytes.as_ref())?;
9241                    pkt.validate()?;
9242                    self.remember_managed_variable_packet(&pkt)?;
9243                }
9244            }
9245        }
9246        let mut released_buffered: Vec<Arc<[u8]>> = Vec::new();
9247        if let (Some(src), RouterItem::Packed(bytes)) = (item.src, &item.data) {
9248            let (_opts, handler_is_packed, hop_reliable_enabled) = {
9249                let st = self.state.lock();
9250                let side_ref = Self::side_ref(&st, src)?;
9251                let opts = side_ref.opts;
9252                (
9253                    opts,
9254                    matches!(side_ref.tx_handler, RouterTxHandlerFn::Packed(_)),
9255                    opts.reliable_enabled
9256                        && self.cfg.reliable_enabled()
9257                        && !self.side_has_multiple_announcers_locked(&st, src, self.clock.now_ms()),
9258                )
9259            };
9260
9261            if hop_reliable_enabled && handler_is_packed {
9262                let frame = match wire_format::peek_frame_info(bytes.as_ref()) {
9263                    Ok(frame) => frame,
9264                    Err(e) => {
9265                        if matches!(e, TelemetryError::Unpack(msg) if msg == "crc32 mismatch") {
9266                            if let Ok(frame) =
9267                                wire_format::peek_frame_info_unchecked(bytes.as_ref())
9268                                && is_reliable_type(frame.envelope.ty)
9269                                && let Some(hdr) = frame.reliable
9270                            {
9271                                let unordered =
9272                                    (hdr.flags & wire_format::RELIABLE_FLAG_UNORDERED) != 0;
9273                                let unsequenced =
9274                                    (hdr.flags & wire_format::RELIABLE_FLAG_UNSEQUENCED) != 0;
9275
9276                                if !unsequenced {
9277                                    let requested = if unordered {
9278                                        hdr.seq
9279                                    } else {
9280                                        let mut st = self.state.lock();
9281                                        let rx_state = self.reliable_rx_state_mut(
9282                                            &mut st,
9283                                            src,
9284                                            frame.envelope.ty,
9285                                        );
9286                                        rx_state.expected_seq.min(hdr.seq)
9287                                    };
9288                                    self.queue_reliable_packet_request(
9289                                        src,
9290                                        frame.envelope.ty,
9291                                        requested,
9292                                        called_from_queue,
9293                                    )?;
9294                                }
9295                            }
9296                            return Ok(());
9297                        }
9298                        return Err(e);
9299                    }
9300                };
9301                if is_reliable_type(frame.envelope.ty)
9302                    && let Some(hdr) = frame.reliable
9303                {
9304                    if frame.ack_only() {
9305                        self.handle_reliable_ack(src, frame.envelope.ty, hdr.ack);
9306                        return Ok(());
9307                    }
9308                    let unordered = (hdr.flags & wire_format::RELIABLE_FLAG_UNORDERED) != 0;
9309                    let unsequenced = (hdr.flags & wire_format::RELIABLE_FLAG_UNSEQUENCED) != 0;
9310
9311                    if !unsequenced {
9312                        if unordered {
9313                            self.queue_reliable_ack(
9314                                src,
9315                                frame.envelope.ty,
9316                                hdr.seq,
9317                                called_from_queue,
9318                            )?;
9319                        } else {
9320                            let mut release: Vec<Arc<[u8]>> = Vec::new();
9321                            let mut last_delivered = None;
9322                            let mut ack_old = None;
9323                            let mut request_missing = None;
9324                            let mut partial_ack = None;
9325                            {
9326                                let mut st = self.state.lock();
9327                                let rx_state =
9328                                    self.reliable_rx_state_mut(&mut st, src, frame.envelope.ty);
9329                                let expected_seq = rx_state.expected_seq;
9330                                if hdr.seq < expected_seq {
9331                                    ack_old = Some(expected_seq.saturating_sub(1));
9332                                } else if hdr.seq > expected_seq {
9333                                    request_missing = Some(expected_seq);
9334                                    partial_ack = Some(hdr.seq);
9335                                    st.buffer_reliable_rx(
9336                                        src,
9337                                        frame.envelope.ty,
9338                                        hdr.seq,
9339                                        bytes.clone(),
9340                                    )?;
9341                                } else {
9342                                    release.push(bytes.clone());
9343                                    last_delivered = Some(hdr.seq);
9344                                    let mut next_expected = hdr.seq.wrapping_add(1);
9345                                    while let Some(buf) = rx_state.buffered.remove(&next_expected) {
9346                                        release.push(buf);
9347                                        last_delivered = Some(next_expected);
9348                                        let next = next_expected.wrapping_add(1);
9349                                        next_expected = if next == 0 { 1 } else { next };
9350                                    }
9351                                    rx_state.expected_seq = next_expected;
9352                                }
9353                            }
9354
9355                            if let Some(ack_seq) = ack_old {
9356                                self.queue_reliable_ack(
9357                                    src,
9358                                    frame.envelope.ty,
9359                                    ack_seq,
9360                                    called_from_queue,
9361                                )?;
9362                                return Ok(());
9363                            }
9364                            if let Some(request_seq) = request_missing {
9365                                if let Some(partial_seq) = partial_ack {
9366                                    self.queue_reliable_partial_ack(
9367                                        src,
9368                                        frame.envelope.ty,
9369                                        partial_seq,
9370                                        called_from_queue,
9371                                    )?;
9372                                }
9373                                self.queue_reliable_packet_request(
9374                                    src,
9375                                    frame.envelope.ty,
9376                                    request_seq,
9377                                    called_from_queue,
9378                                )?;
9379                                return Ok(());
9380                            }
9381
9382                            if let Some(ack_seq) = last_delivered {
9383                                self.queue_reliable_ack(
9384                                    src,
9385                                    frame.envelope.ty,
9386                                    ack_seq,
9387                                    called_from_queue,
9388                                )?;
9389                            }
9390
9391                            released_buffered.extend(release.into_iter().skip(1));
9392                        }
9393                    }
9394                }
9395            } else {
9396                match wire_format::peek_frame_info(bytes.as_ref()) {
9397                    Ok(frame) => {
9398                        if frame.ack_only() {
9399                            return Ok(());
9400                        }
9401                    }
9402                    Err(e) => {
9403                        if matches!(e, TelemetryError::Unpack(msg) if msg == "crc32 mismatch") {
9404                            return Ok(());
9405                        }
9406                        return Err(e);
9407                    }
9408                }
9409            }
9410        }
9411
9412        if self.is_duplicate_pkt(&item.data)? {
9413            if item.src.is_some() {
9414                let local_sender = self.sender_arc();
9415                match &item.data {
9416                    RouterItem::Packet(pkt)
9417                        if (is_reliable_type(pkt.data_type())
9418                            || !pkt.wire_target_senders().is_empty())
9419                            && pkt.sender() != local_sender.as_ref()
9420                            && self.item_targets_local_sender(&item.data)?
9421                            && self.packet_has_local_handler(pkt) =>
9422                    {
9423                        self.queue_end_to_end_reliable_ack(pkt, called_from_queue)?;
9424                    }
9425                    RouterItem::Packed(bytes) => {
9426                        if let Ok(pkt) = wire_format::unpack_packet(bytes.as_ref())
9427                            && (is_reliable_type(pkt.data_type())
9428                                || !pkt.wire_target_senders().is_empty())
9429                            && pkt.sender() != local_sender.as_ref()
9430                            && self.item_targets_local_sender(&item.data)?
9431                            && self.packet_has_local_handler(&pkt)
9432                        {
9433                            let packet_id = wire_format::packet_id_from_wire(bytes.as_ref())
9434                                .unwrap_or_else(|_| pkt.packet_id());
9435                            self.queue_end_to_end_reliable_ack_for_packet_id(
9436                                packet_id,
9437                                called_from_queue,
9438                            )?;
9439                        }
9440                    }
9441                    _ => {}
9442                }
9443            }
9444            return Ok(());
9445        }
9446
9447        self.dispatch_rx_data(item, called_from_queue)?;
9448
9449        for release_bytes in released_buffered {
9450            let release_data = RouterItem::Packed(release_bytes.clone());
9451            if self.is_duplicate_pkt(&release_data)? {
9452                continue;
9453            }
9454            let release_item = RouterRxItem {
9455                src: item.src,
9456                priority: Self::router_item_priority(&release_data)?,
9457                data: release_data,
9458            };
9459            self.dispatch_rx_data(&release_item, called_from_queue)?;
9460        }
9461
9462        Ok(())
9463    }
9464
9465    fn dispatch_rx_data(
9466        &self,
9467        item: &RouterRxItem,
9468        called_from_queue: bool,
9469    ) -> TelemetryResult<()> {
9470        match &item.data {
9471            RouterItem::Packet(pkt) => {
9472                pkt.validate()?;
9473
9474                if self.handle_internal_reliable_packet(pkt, item.src, called_from_queue)? {
9475                    return Ok(());
9476                }
9477
9478                if pkt.data_type() == DataType::P2pMessage {
9479                    if self.item_targets_local_sender(&item.data)? {
9480                        self.dispatch_p2p_packet(pkt)?;
9481                        if item.src.is_some() {
9482                            self.queue_end_to_end_reliable_ack(pkt, called_from_queue)?;
9483                        }
9484                    }
9485                    if self.should_route_remote(&item.data, item.src)? {
9486                        self.relay_send(
9487                            RouterItem::Packet(pkt.to_owned()),
9488                            item.src,
9489                            called_from_queue,
9490                        )?;
9491                    }
9492                    return Ok(());
9493                }
9494
9495                #[cfg(feature = "timesync")]
9496                if matches!(
9497                    pkt.data_type(),
9498                    DataType::TimeSyncAnnounce
9499                        | DataType::TimeSyncRequest
9500                        | DataType::TimeSyncResponse
9501                ) {
9502                    self.handle_internal_timesync_packet(pkt, item.src, called_from_queue)?;
9503                    return Ok(());
9504                }
9505
9506                if self.learn_discovery_packet(pkt, item.src, called_from_queue)? {
9507                    if self.should_route_remote(&item.data, item.src)? {
9508                        self.relay_send(
9509                            RouterItem::Packet(pkt.to_owned()),
9510                            item.src,
9511                            called_from_queue,
9512                        )?;
9513                    }
9514                    return Ok(());
9515                }
9516
9517                let mut eps: Vec<DataEndpoint> = pkt.endpoints().to_vec();
9518                eps.sort_unstable();
9519                eps.dedup();
9520                let had_local_handler = eps.iter().copied().any(|ep| {
9521                    self.endpoint_has_packet_handler(ep) || self.endpoint_has_packed_handler(ep)
9522                });
9523
9524                let has_remote = self.should_route_remote(&item.data, item.src)?;
9525                let targets_local = self.item_targets_local_sender(&item.data)?;
9526
9527                let has_packed_local = eps
9528                    .iter()
9529                    .copied()
9530                    .any(|ep| self.endpoint_has_packed_handler(ep));
9531                let bytes_opt = if has_packed_local {
9532                    Some(wire_format::pack_packet(pkt))
9533                } else {
9534                    None
9535                };
9536
9537                if targets_local {
9538                    for dest in eps {
9539                        for h in self.cfg.handlers.iter().filter(|h| h.endpoint == dest) {
9540                            let result = match (&h.handler, &bytes_opt) {
9541                                (EndpointHandlerFn::Packed(_), Some(bytes)) => self
9542                                    .call_handler_with_retries(
9543                                        dest,
9544                                        h,
9545                                        Some(bytes.as_ref()),
9546                                        Some(pkt),
9547                                        None,
9548                                        called_from_queue,
9549                                    ),
9550                                (EndpointHandlerFn::Packed(_), None) => {
9551                                    let bytes = wire_format::pack_packet(pkt);
9552                                    self.call_handler_with_retries(
9553                                        dest,
9554                                        h,
9555                                        Some(bytes.as_ref()),
9556                                        Some(pkt),
9557                                        None,
9558                                        called_from_queue,
9559                                    )
9560                                }
9561                                (EndpointHandlerFn::Packet(_), _) => self
9562                                    .call_handler_with_retries(
9563                                        dest,
9564                                        h,
9565                                        None,
9566                                        Some(pkt),
9567                                        None,
9568                                        called_from_queue,
9569                                    ),
9570                            };
9571                            if result.is_err()
9572                                && let Some(src) = item.src
9573                            {
9574                                self.note_side_local_handler_failure(
9575                                    src,
9576                                    pkt.data_type(),
9577                                    MAX_HANDLER_RETRIES,
9578                                );
9579                            }
9580                        }
9581                    }
9582                }
9583
9584                if let Some(src) = item.src
9585                    && had_local_handler
9586                    && targets_local
9587                {
9588                    self.note_side_local_delivery(src, pkt.data_type());
9589                }
9590
9591                if item.src.is_some()
9592                    && had_local_handler
9593                    && targets_local
9594                    && (is_reliable_type(pkt.data_type()) || !pkt.wire_target_senders().is_empty())
9595                {
9596                    self.queue_end_to_end_reliable_ack(pkt, called_from_queue)?;
9597                }
9598
9599                if has_remote {
9600                    let relay_item = RouterItem::Packet(pkt.to_owned());
9601                    self.relay_send(relay_item, item.src, called_from_queue)?;
9602                }
9603
9604                Ok(())
9605            }
9606            RouterItem::Packed(bytes) => {
9607                let env = wire_format::peek_envelope(bytes.as_ref())?;
9608
9609                if matches!(
9610                    env.ty,
9611                    DataType::ReliableAck
9612                        | DataType::ReliablePartialAck
9613                        | DataType::ReliablePacketRequest
9614                ) {
9615                    let pkt = wire_format::unpack_packet(bytes.as_ref())?;
9616                    pkt.validate()?;
9617                    let _ =
9618                        self.handle_internal_reliable_packet(&pkt, item.src, called_from_queue)?;
9619                    return Ok(());
9620                }
9621
9622                if env.ty == DataType::P2pMessage {
9623                    let pkt = wire_format::unpack_packet(bytes.as_ref())?;
9624                    pkt.validate()?;
9625                    if self.item_targets_local_sender(&item.data)? {
9626                        self.dispatch_p2p_packet(&pkt)?;
9627                        if item.src.is_some() {
9628                            let packet_id = wire_format::packet_id_from_wire(bytes.as_ref())
9629                                .unwrap_or_else(|_| pkt.packet_id());
9630                            self.queue_end_to_end_reliable_ack_for_packet_id(
9631                                packet_id,
9632                                called_from_queue,
9633                            )?;
9634                        }
9635                    }
9636                    if self.should_route_remote(&item.data, item.src)? {
9637                        self.relay_send(RouterItem::Packet(pkt), item.src, called_from_queue)?;
9638                    }
9639                    return Ok(());
9640                }
9641
9642                #[cfg(feature = "timesync")]
9643                if matches!(
9644                    env.ty,
9645                    DataType::TimeSyncAnnounce
9646                        | DataType::TimeSyncRequest
9647                        | DataType::TimeSyncResponse
9648                ) {
9649                    let pkt = wire_format::unpack_packet(bytes.as_ref())?;
9650                    pkt.validate()?;
9651                    self.handle_internal_timesync_packet(&pkt, item.src, called_from_queue)?;
9652                    return Ok(());
9653                }
9654
9655                #[cfg(feature = "discovery")]
9656                if discovery::is_discovery_type(env.ty) {
9657                    let pkt = wire_format::unpack_packet(bytes.as_ref())?;
9658                    pkt.validate()?;
9659                    let _ = self.learn_discovery_packet(&pkt, item.src, called_from_queue)?;
9660                    if self.should_route_remote(&item.data, item.src)? {
9661                        self.relay_send(RouterItem::Packet(pkt), item.src, called_from_queue)?;
9662                    }
9663                    return Ok(());
9664                }
9665
9666                let any_packet_needed = env
9667                    .endpoints
9668                    .iter()
9669                    .copied()
9670                    .any(|ep| self.endpoint_has_packet_handler(ep));
9671
9672                let mut pkt_opt = if any_packet_needed {
9673                    let pkt = wire_format::unpack_packet(bytes.as_ref())?;
9674                    pkt.validate()?;
9675                    Some(pkt)
9676                } else {
9677                    None
9678                };
9679
9680                let mut eps: Vec<DataEndpoint> = env.endpoints.iter().copied().collect();
9681                eps.sort_unstable();
9682                eps.dedup();
9683                let had_local_handler = eps.iter().copied().any(|ep| {
9684                    self.endpoint_has_packet_handler(ep) || self.endpoint_has_packed_handler(ep)
9685                });
9686
9687                let has_remote = self.should_route_remote(&item.data, item.src)?;
9688                let targets_local = self.item_targets_local_sender(&item.data)?;
9689
9690                if targets_local {
9691                    for dest in eps {
9692                        for h in self.cfg.handlers.iter().filter(|h| h.endpoint == dest) {
9693                            let result = match &h.handler {
9694                                EndpointHandlerFn::Packed(_) => self.call_handler_with_retries(
9695                                    dest,
9696                                    h,
9697                                    Some(bytes.as_ref()),
9698                                    pkt_opt.as_ref(),
9699                                    Some(&env),
9700                                    called_from_queue,
9701                                ),
9702                                EndpointHandlerFn::Packet(_) => {
9703                                    if pkt_opt.is_none() {
9704                                        let pkt = wire_format::unpack_packet(bytes.as_ref())?;
9705                                        pkt.validate()?;
9706                                        pkt_opt = Some(pkt);
9707                                    }
9708                                    let pkt_ref = pkt_opt.as_ref().expect("just set");
9709                                    self.call_handler_with_retries(
9710                                        dest,
9711                                        h,
9712                                        None,
9713                                        Some(pkt_ref),
9714                                        Some(&env),
9715                                        called_from_queue,
9716                                    )
9717                                }
9718                            };
9719                            if result.is_err()
9720                                && let Some(src) = item.src
9721                            {
9722                                self.note_side_local_handler_failure(
9723                                    src,
9724                                    env.ty,
9725                                    MAX_HANDLER_RETRIES,
9726                                );
9727                            }
9728                        }
9729                    }
9730                }
9731
9732                if item.src.is_some()
9733                    && had_local_handler
9734                    && targets_local
9735                    && (is_reliable_type(env.ty) || !env.target_senders.is_empty())
9736                    && let Some(pkt) = pkt_opt.as_ref()
9737                {
9738                    let packet_id = wire_format::packet_id_from_wire(bytes.as_ref())
9739                        .unwrap_or_else(|_| pkt.packet_id());
9740                    self.queue_end_to_end_reliable_ack_for_packet_id(packet_id, called_from_queue)?;
9741                }
9742
9743                if has_remote {
9744                    let relay_item = match pkt_opt {
9745                        Some(ref p) => RouterItem::Packet(p.clone()),
9746                        None => RouterItem::Packed(bytes.clone()),
9747                    };
9748                    self.relay_send(relay_item, item.src, called_from_queue)?;
9749                }
9750
9751                Ok(())
9752            }
9753        }
9754    }
9755
9756    fn dispatch_local_for_item(
9757        &self,
9758        item: &RouterItem,
9759        called_from_queue: bool,
9760    ) -> TelemetryResult<()> {
9761        match item {
9762            RouterItem::Packet(pkt) => {
9763                pkt.validate()?;
9764                if is_internal_control_type(pkt.data_type()) {
9765                    return Ok(());
9766                }
9767                self.ensure_e2e_policy_supported_for_type(pkt.data_type())?;
9768                if !self.item_targets_local_sender(item)? {
9769                    return Ok(());
9770                }
9771
9772                let mut eps: Vec<DataEndpoint> = pkt.endpoints().to_vec();
9773                eps.sort_unstable();
9774                eps.dedup();
9775
9776                let has_packed_local = eps
9777                    .iter()
9778                    .copied()
9779                    .any(|ep| self.endpoint_has_packed_handler(ep));
9780                let bytes_opt = if has_packed_local {
9781                    Some(wire_format::pack_packet(pkt))
9782                } else {
9783                    None
9784                };
9785
9786                for dest in eps {
9787                    for h in self.cfg.handlers.iter().filter(|h| h.endpoint == dest) {
9788                        match (&h.handler, &bytes_opt) {
9789                            (EndpointHandlerFn::Packed(_), Some(bytes)) => {
9790                                self.call_handler_with_retries(
9791                                    dest,
9792                                    h,
9793                                    Some(bytes.as_ref()),
9794                                    Some(pkt),
9795                                    None,
9796                                    called_from_queue,
9797                                )?;
9798                            }
9799                            (EndpointHandlerFn::Packed(_), None) => {
9800                                let bytes = wire_format::pack_packet(pkt);
9801                                self.call_handler_with_retries(
9802                                    dest,
9803                                    h,
9804                                    Some(bytes.as_ref()),
9805                                    Some(pkt),
9806                                    None,
9807                                    called_from_queue,
9808                                )?;
9809                            }
9810                            (EndpointHandlerFn::Packet(_), _) => {
9811                                self.call_handler_with_retries(
9812                                    dest,
9813                                    h,
9814                                    None,
9815                                    Some(pkt),
9816                                    None,
9817                                    called_from_queue,
9818                                )?;
9819                            }
9820                        }
9821                    }
9822                }
9823            }
9824            RouterItem::Packed(bytes) => {
9825                let env = wire_format::peek_envelope(bytes.as_ref())?;
9826                if is_internal_control_type(env.ty) {
9827                    return Ok(());
9828                }
9829                self.ensure_e2e_policy_supported_for_type(env.ty)?;
9830                if !self.item_targets_local_sender(item)? {
9831                    return Ok(());
9832                }
9833
9834                let any_packet_needed = env
9835                    .endpoints
9836                    .iter()
9837                    .copied()
9838                    .any(|ep| self.endpoint_has_packet_handler(ep));
9839
9840                let mut pkt_opt = if any_packet_needed {
9841                    let pkt = wire_format::unpack_packet(bytes.as_ref())?;
9842                    pkt.validate()?;
9843                    Some(pkt)
9844                } else {
9845                    None
9846                };
9847
9848                let mut eps: Vec<DataEndpoint> = env.endpoints.iter().copied().collect();
9849                eps.sort_unstable();
9850                eps.dedup();
9851
9852                for dest in eps {
9853                    for h in self.cfg.handlers.iter().filter(|h| h.endpoint == dest) {
9854                        match &h.handler {
9855                            EndpointHandlerFn::Packed(_) => {
9856                                self.call_handler_with_retries(
9857                                    dest,
9858                                    h,
9859                                    Some(bytes.as_ref()),
9860                                    pkt_opt.as_ref(),
9861                                    Some(&env),
9862                                    called_from_queue,
9863                                )?;
9864                            }
9865                            EndpointHandlerFn::Packet(_) => {
9866                                if pkt_opt.is_none() {
9867                                    let pkt = wire_format::unpack_packet(bytes.as_ref())?;
9868                                    pkt.validate()?;
9869                                    pkt_opt = Some(pkt);
9870                                }
9871                                let pkt_ref = pkt_opt.as_ref().expect("just set");
9872                                self.call_handler_with_retries(
9873                                    dest,
9874                                    h,
9875                                    None,
9876                                    Some(pkt_ref),
9877                                    Some(&env),
9878                                    called_from_queue,
9879                                )?;
9880                            }
9881                        }
9882                    }
9883                }
9884            }
9885        }
9886
9887        Ok(())
9888    }
9889
9890    /// Internal TX implementation used by `tx*()`, `tx_queue*()`, and relay-mode rebroadcast.
9891    ///
9892    /// - Broadcast items are sent to all sides when remote forwarding is required.
9893    /// - ToSide items are sent only to the specified side.
9894    /// - If `ignore_local` is false, local handlers are invoked once.
9895    fn tx_item_impl(
9896        &self,
9897        item: RouterTxItem,
9898        ignore_local: bool,
9899        called_from_queue: bool,
9900    ) -> TelemetryResult<()> {
9901        match item {
9902            RouterTxItem::Broadcast(data) => {
9903                self.ensure_e2e_policy_supported_for_type(Self::item_data_type(&data)?)?;
9904                if let RouterItem::Packet(pkt) = &data
9905                    && !is_internal_control_type(pkt.data_type())
9906                {
9907                    self.remember_managed_variable_packet(pkt)?;
9908                }
9909                #[cfg(feature = "discovery")]
9910                let is_discovery = matches!(&data, RouterItem::Packet(pkt) if discovery::is_discovery_type(pkt.data_type()))
9911                    || matches!(&data, RouterItem::Packed(bytes)
9912                        if wire_format::peek_envelope(bytes.as_ref())
9913                            .map(|env| discovery::is_discovery_type(env.ty))
9914                            .unwrap_or(false));
9915                if !ignore_local {
9916                    if self.is_duplicate_pkt(&data)? {
9917                        return Ok(());
9918                    }
9919                    #[cfg(feature = "discovery")]
9920                    if !is_discovery
9921                        && !matches!(&data, RouterItem::Packet(pkt) if is_internal_control_type(pkt.data_type()))
9922                        && !matches!(&data, RouterItem::Packed(bytes)
9923                            if wire_format::peek_envelope(bytes.as_ref())
9924                                .map(|env| is_internal_control_type(env.ty))
9925                                .unwrap_or(false))
9926                    {
9927                        self.dispatch_local_for_item(&data, called_from_queue)?;
9928                    }
9929                    #[cfg(not(feature = "discovery"))]
9930                    if !matches!(&data, RouterItem::Packet(pkt) if is_internal_control_type(pkt.data_type()))
9931                        && !matches!(&data, RouterItem::Packed(bytes)
9932                            if wire_format::peek_envelope(bytes.as_ref())
9933                                .map(|env| is_internal_control_type(env.ty))
9934                                .unwrap_or(false))
9935                    {
9936                        self.dispatch_local_for_item(&data, called_from_queue)?;
9937                    }
9938                }
9939
9940                let send_remote = match &data {
9941                    RouterItem::Packet(pkt) => {
9942                        pkt.validate()?;
9943                        self.should_route_remote(&data, None)?
9944                    }
9945                    RouterItem::Packed(bytes) => {
9946                        let _ = wire_format::peek_envelope(bytes.as_ref())?;
9947                        self.should_route_remote(&data, None)?
9948                    }
9949                };
9950
9951                if !send_remote {
9952                    return Ok(());
9953                }
9954                let mut data = data;
9955                let ty = match &data {
9956                    RouterItem::Packet(pkt) => pkt.data_type(),
9957                    RouterItem::Packed(bytes) => wire_format::peek_envelope(bytes.as_ref())?.ty,
9958                };
9959                if !ignore_local && !is_internal_control_type(ty) {
9960                    #[cfg(feature = "discovery")]
9961                    {
9962                        let pending = {
9963                            let st = self.state.lock();
9964                            let mut pending =
9965                                self.expected_end_to_end_destinations_locked(&st, &data)?;
9966                            self.filter_trackable_end_to_end_destinations_locked(
9967                                &st,
9968                                ty,
9969                                &mut pending,
9970                            );
9971                            pending
9972                        };
9973                        if !pending.is_empty() {
9974                            let mut targets: Vec<u64> = pending.keys().copied().collect();
9975                            targets.sort_unstable();
9976                            targets.dedup();
9977                            data = self.attach_wire_contract_to_item(data, &targets)?;
9978                            self.register_end_to_end_reliable_tx(&data)?;
9979                        }
9980                    }
9981                }
9982                let RemoteSidePlan::Target(sides) = self.remote_side_plan(&data, None)?;
9983                for (idx, side) in sides.iter().copied().enumerate() {
9984                    if let Err(e) = self.send_reliable_to_side(side, data.clone(), false) {
9985                        if Self::is_side_tx_busy(&e) {
9986                            for retry_side in sides[idx..].iter().copied() {
9987                                self.tx_queue_item_with_flags(
9988                                    RouterTxItem::ToSide {
9989                                        src: None,
9990                                        dst: retry_side,
9991                                        data: data.clone(),
9992                                    },
9993                                    true,
9994                                )?;
9995                            }
9996                            return Ok(());
9997                        }
9998                        match &data {
9999                            RouterItem::Packet(pkt) => {
10000                                let _ = self.handle_callback_error(pkt, None, e, called_from_queue);
10001                            }
10002                            RouterItem::Packed(bytes) => {
10003                                if let Ok(env) = wire_format::peek_envelope(bytes.as_ref()) {
10004                                    let _ = self.handle_callback_error_from_env(
10005                                        &env,
10006                                        None,
10007                                        e,
10008                                        called_from_queue,
10009                                    );
10010                                }
10011                            }
10012                        }
10013                        return Err(TelemetryError::HandlerError("tx handler failed"));
10014                    }
10015                }
10016            }
10017            RouterTxItem::ToSide { src, dst, data } => {
10018                self.ensure_e2e_policy_supported_for_type(Self::item_data_type(&data)?)?;
10019                if let RouterItem::Packet(pkt) = &data
10020                    && !is_internal_control_type(pkt.data_type())
10021                {
10022                    self.remember_managed_variable_packet(pkt)?;
10023                }
10024                if !ignore_local {
10025                    if self.is_duplicate_pkt(&data)? {
10026                        return Ok(());
10027                    }
10028                    let suppress_local = matches!(&data, RouterItem::Packet(pkt) if is_internal_control_type(pkt.data_type()))
10029                        || matches!(&data, RouterItem::Packed(bytes)
10030                            if wire_format::peek_envelope(bytes.as_ref())
10031                                .map(|env| is_internal_control_type(env.ty))
10032                                .unwrap_or(false));
10033                    if !suppress_local {
10034                        self.dispatch_local_for_item(&data, called_from_queue)?;
10035                    }
10036                }
10037                let allowed = {
10038                    let mut st = self.state.lock();
10039                    let ty = match &data {
10040                        RouterItem::Packet(pkt) => Some(pkt.data_type()),
10041                        RouterItem::Packed(bytes) => {
10042                            Some(wire_format::peek_envelope(bytes.as_ref())?.ty)
10043                        }
10044                    };
10045                    let route_allowed = self.route_allowed_locked(&st, src, ty, dst);
10046                    #[cfg(all(feature = "discovery", feature = "timesync"))]
10047                    let timesync_allowed = ty
10048                        .map(|ty| {
10049                            Self::timesync_allowed_for_side_locked(
10050                                &mut st,
10051                                dst,
10052                                ty,
10053                                self.clock.now_ms(),
10054                            )
10055                        })
10056                        .unwrap_or(true);
10057                    #[cfg(not(all(feature = "discovery", feature = "timesync")))]
10058                    let timesync_allowed = true;
10059                    route_allowed && timesync_allowed
10060                };
10061                if !allowed {
10062                    return Ok(());
10063                }
10064                if let Err(e) = self.send_reliable_to_side(dst, data.clone(), src.is_some()) {
10065                    if Self::is_side_tx_busy(&e) {
10066                        self.tx_queue_item_with_flags(
10067                            RouterTxItem::ToSide { src, dst, data },
10068                            true,
10069                        )?;
10070                        return Ok(());
10071                    }
10072                    match &data {
10073                        RouterItem::Packet(pkt) => {
10074                            let _ = self.handle_callback_error(pkt, None, e, called_from_queue);
10075                        }
10076                        RouterItem::Packed(bytes) => {
10077                            if let Ok(env) = wire_format::peek_envelope(bytes.as_ref()) {
10078                                let _ = self.handle_callback_error_from_env(
10079                                    &env,
10080                                    None,
10081                                    e,
10082                                    called_from_queue,
10083                                );
10084                            }
10085                        }
10086                    }
10087                    return Err(TelemetryError::HandlerError("tx handler failed"));
10088                }
10089            }
10090            RouterTxItem::EndToEndReplay { packet_id } => {
10091                let Some((data, mut sides)) = self.end_to_end_retransmit_sides(packet_id) else {
10092                    return Ok(());
10093                };
10094                if sides.is_empty() {
10095                    let RemoteSidePlan::Target(fallback_sides) =
10096                        self.remote_side_plan(&data, None)?;
10097                    sides = fallback_sides;
10098                }
10099                for (idx, side) in sides.iter().copied().enumerate() {
10100                    if let Err(e) = self.send_reliable_to_side(side, data.clone(), false) {
10101                        if Self::is_side_tx_busy(&e) {
10102                            for retry_side in sides[idx..].iter().copied() {
10103                                self.tx_queue_item_with_flags(
10104                                    RouterTxItem::ToSide {
10105                                        src: None,
10106                                        dst: retry_side,
10107                                        data: data.clone(),
10108                                    },
10109                                    true,
10110                                )?;
10111                            }
10112                            return Ok(());
10113                        }
10114                        match &data {
10115                            RouterItem::Packet(pkt) => {
10116                                let _ = self.handle_callback_error(pkt, None, e, called_from_queue);
10117                            }
10118                            RouterItem::Packed(bytes) => {
10119                                if let Ok(env) = wire_format::peek_envelope(bytes.as_ref()) {
10120                                    let _ = self.handle_callback_error_from_env(
10121                                        &env,
10122                                        None,
10123                                        e,
10124                                        called_from_queue,
10125                                    );
10126                                }
10127                            }
10128                        }
10129                        return Err(TelemetryError::HandlerError("tx handler failed"));
10130                    }
10131                }
10132            }
10133            RouterTxItem::ReliableReplay { dst, bytes } => {
10134                let frame = wire_format::peek_frame_info(bytes.as_ref())?;
10135                let ty = frame.envelope.ty;
10136                let Some(hdr) = frame.reliable else {
10137                    return Ok(());
10138                };
10139                {
10140                    let mut st = self.state.lock();
10141                    let tx_state = self.reliable_tx_state_mut(&mut st, dst, ty);
10142                    if !tx_state.sent.contains_key(&hdr.seq) {
10143                        return Ok(());
10144                    }
10145                }
10146                if let Err(e) = self.send_reliable_raw_to_side(dst, bytes.clone(), false) {
10147                    if Self::is_side_tx_busy(&e) {
10148                        self.tx_queue_item_with_flags(
10149                            RouterTxItem::ReliableReplay { dst, bytes },
10150                            true,
10151                        )?;
10152                        return Ok(());
10153                    }
10154                    return Err(e);
10155                }
10156                let mut st = self.state.lock();
10157                let tx_state = self.reliable_tx_state_mut(&mut st, dst, ty);
10158                if let Some(sent) = tx_state.sent.get_mut(&hdr.seq) {
10159                    sent.last_send_ms = self.clock.now_ms();
10160                    sent.queued = false;
10161                }
10162            }
10163        }
10164
10165        Ok(())
10166    }
10167
10168    /// Transmit a telemetry item immediately (remote + local).
10169    #[inline]
10170    fn tx_item(&self, item: RouterTxItem) -> TelemetryResult<()> {
10171        self.tx_item_impl(item, false, false)
10172    }
10173
10174    // ---------- PUBLIC API: RX immediate ----------
10175
10176    /// Process packed bytes immediately as locally-originated input.
10177    ///
10178    /// If this call occurs while a side TX callback is already on the stack, the bytes are queued
10179    /// instead of being processed re-entrantly.
10180    #[inline]
10181    pub fn rx_packed(&self, bytes: &[u8]) -> TelemetryResult<()> {
10182        if self.side_tx_active() {
10183            return self.rx_packed_queue(bytes);
10184        }
10185        let data = RouterItem::Packed(Arc::from(bytes));
10186        let item = RouterRxItem {
10187            src: None,
10188            priority: Self::router_item_priority(&data)?,
10189            data,
10190        };
10191        self.rx_item(&item, false)
10192    }
10193
10194    /// Process a decoded packet immediately as locally-originated input.
10195    ///
10196    /// If this call occurs while a side TX callback is already on the stack, the packet is queued
10197    /// instead of being processed re-entrantly.
10198    #[inline]
10199    pub fn rx(&self, pkt: &Packet) -> TelemetryResult<()> {
10200        if self.side_tx_active() {
10201            return self.rx_queue(pkt.clone());
10202        }
10203        let data = RouterItem::Packet(pkt.clone());
10204        let item = RouterRxItem {
10205            src: None,
10206            priority: Self::router_item_priority(&data)?,
10207            data,
10208        };
10209        self.rx_item(&item, false)
10210    }
10211
10212    /// Process a decoded packet immediately with an explicit ingress side id.
10213    ///
10214    /// If this call occurs while a side TX callback is already on the stack, the packet is queued
10215    /// instead of being processed re-entrantly.
10216    #[inline]
10217    pub fn rx_from_side(&self, pkt: &Packet, side: RouterSideId) -> TelemetryResult<()> {
10218        if self.side_tx_active() {
10219            return self.rx_queue_from_side(pkt.clone(), side);
10220        }
10221        self.ensure_side_ingress_enabled(side)?;
10222        let data = RouterItem::Packet(pkt.clone());
10223        let item = RouterRxItem {
10224            src: Some(side),
10225            priority: Self::router_item_priority(&data)?,
10226            data,
10227        };
10228        self.rx_item(&item, false)
10229    }
10230
10231    /// Process packed bytes immediately with an explicit ingress side id.
10232    ///
10233    /// If this call occurs while a side TX callback is already on the stack, the bytes are queued
10234    /// instead of being processed re-entrantly.
10235    #[inline]
10236    pub fn rx_packed_from_side(&self, bytes: &[u8], side: RouterSideId) -> TelemetryResult<()> {
10237        if self.side_tx_active() {
10238            return self.rx_packed_queue_from_side(bytes, side);
10239        }
10240        self.ensure_side_ingress_enabled(side)?;
10241        let Some(decoded) = self.decode_side_transport_frame(side, bytes)? else {
10242            return Ok(());
10243        };
10244        let data = RouterItem::Packed(decoded);
10245        let item = RouterRxItem {
10246            src: Some(side),
10247            priority: Self::router_item_priority(&data)?,
10248            data,
10249        };
10250        self.rx_item(&item, false)
10251    }
10252
10253    // ---------- PUBLIC API: TX immediate ----------
10254
10255    /// Transmit a decoded packet immediately.
10256    ///
10257    /// The router delivers locally where appropriate and forwards toward eligible sides. If called
10258    /// from inside a side TX callback, the packet is queued instead of being sent re-entrantly.
10259    #[inline]
10260    pub fn tx(&self, pkt: Packet) -> TelemetryResult<()> {
10261        #[cfg(feature = "discovery")]
10262        let _ = self.poll_discovery()?;
10263        if self.side_tx_active() {
10264            return self.tx_queue_item(RouterTxItem::Broadcast(RouterItem::Packet(pkt)));
10265        }
10266        self.tx_item(RouterTxItem::Broadcast(RouterItem::Packet(pkt)))
10267    }
10268
10269    /// Transmit packed bytes immediately.
10270    ///
10271    /// If called from inside a side TX callback, the bytes are queued instead of being sent
10272    /// re-entrantly.
10273    #[inline]
10274    pub fn tx_packed(&self, pkt: Arc<[u8]>) -> TelemetryResult<()> {
10275        #[cfg(feature = "discovery")]
10276        let _ = self.poll_discovery()?;
10277        if self.side_tx_active() {
10278            return self.tx_queue_item(RouterTxItem::Broadcast(RouterItem::Packed(pkt)));
10279        }
10280        self.tx_item(RouterTxItem::Broadcast(RouterItem::Packed(pkt)))
10281    }
10282
10283    // ---------- PUBLIC API: TX queue ----------
10284
10285    /// Queue a decoded packet for later transmission.
10286    #[inline]
10287    pub fn tx_queue(&self, pkt: Packet) -> TelemetryResult<()> {
10288        #[cfg(feature = "discovery")]
10289        let _ = self.poll_discovery()?;
10290        self.tx_queue_item(RouterTxItem::Broadcast(RouterItem::Packet(pkt)))
10291    }
10292
10293    /// Queue packed bytes for later transmission.
10294    #[inline]
10295    pub fn tx_packed_queue(&self, data: Arc<[u8]>) -> TelemetryResult<()> {
10296        #[cfg(feature = "discovery")]
10297        let _ = self.poll_discovery()?;
10298        self.tx_queue_item(RouterTxItem::Broadcast(RouterItem::Packed(data)))
10299    }
10300
10301    // ---------- PUBLIC API: logging ----------
10302
10303    /// Build a packet from typed elements and send it immediately.
10304    ///
10305    /// `ty` selects the schema message type and `data` must match that type's expected element
10306    /// width and count. If called from inside a side TX callback, the built packet is queued.
10307    #[inline]
10308    pub fn log<T: LeBytes>(&self, ty: DataType, data: &[T]) -> TelemetryResult<()> {
10309        #[cfg(feature = "discovery")]
10310        let _ = self.poll_discovery()?;
10311        if self.side_tx_active() {
10312            return self.log_queue(ty, data);
10313        }
10314        let sender = self.sender_arc();
10315        log_raw(
10316            sender.as_ref(),
10317            ty,
10318            data,
10319            self.packet_timestamp_ms(),
10320            |pkt| self.tx_item(RouterTxItem::Broadcast(RouterItem::Packet(pkt))),
10321        )
10322    }
10323
10324    /// Build a packet from typed elements and queue it for later transmission.
10325    #[inline]
10326    pub fn log_queue<T: LeBytes>(&self, ty: DataType, data: &[T]) -> TelemetryResult<()> {
10327        #[cfg(feature = "discovery")]
10328        let _ = self.poll_discovery()?;
10329        let sender = self.sender_arc();
10330        log_raw(
10331            sender.as_ref(),
10332            ty,
10333            data,
10334            self.packet_timestamp_ms(),
10335            |pkt| self.tx_queue_item(RouterTxItem::Broadcast(RouterItem::Packet(pkt))),
10336        )
10337    }
10338
10339    /// Build a packet with an explicit timestamp and send it immediately.
10340    #[inline]
10341    pub fn log_ts<T: LeBytes>(
10342        &self,
10343        ty: DataType,
10344        timestamp: u64,
10345        data: &[T],
10346    ) -> TelemetryResult<()> {
10347        #[cfg(feature = "discovery")]
10348        let _ = self.poll_discovery()?;
10349        if self.side_tx_active() {
10350            return self.log_queue_ts(ty, timestamp, data);
10351        }
10352        let sender = self.sender_arc();
10353        log_raw(sender.as_ref(), ty, data, timestamp, |pkt| {
10354            self.tx_item(RouterTxItem::Broadcast(RouterItem::Packet(pkt)))
10355        })
10356    }
10357
10358    /// Build a packet with an explicit timestamp and queue it for later transmission.
10359    #[inline]
10360    pub fn log_queue_ts<T: LeBytes>(
10361        &self,
10362        ty: DataType,
10363        timestamp: u64,
10364        data: &[T],
10365    ) -> TelemetryResult<()> {
10366        #[cfg(feature = "discovery")]
10367        let _ = self.poll_discovery()?;
10368        let sender = self.sender_arc();
10369        log_raw(sender.as_ref(), ty, data, timestamp, |pkt| {
10370            self.tx_queue_item(RouterTxItem::Broadcast(RouterItem::Packet(pkt)))
10371        })
10372    }
10373}