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