Skip to main content

sedsnet/
relay.rs

1// Apply the same cache contract independently to router and relay state.
2#[cfg(all(test, feature = "discovery"))]
3#[path = "tests/relay_ack_recovery.rs"]
4mod ack_recovery_tests;
5#[cfg(test)]
6#[allow(clippy::duplicate_mod)]
7#[path = "tests/side_template_index.rs"]
8mod side_template_index_tests;
9use crate::side_transport::{
10    SIDE_TRANSPORT_FLAG_PACKET_NONCE, SIDE_TRANSPORT_KIND_CHUNK, SIDE_TRANSPORT_KIND_COMPACT,
11    SIDE_TRANSPORT_KIND_COMPACT_DELTA, SIDE_TRANSPORT_KIND_COMPACT_SAME_TIMESTAMP,
12    SIDE_TRANSPORT_KIND_FULL, SideChunkAssembly, SideCompactTimestampMode, SideHeaderTemplate,
13    SideTransportFrames, extract_side_header_template, extract_side_header_template_view,
14    parse_side_transport_wrapper, read_uleb128_local, reconstruct_side_compact_frame,
15    wrap_side_transport_frame, write_uleb128_local,
16};
17#[cfg(all(test, feature = "discovery"))]
18#[path = "tests/relay_restart_transport.rs"]
19mod restart_transport_tests;
20
21#[cfg(all(test, feature = "discovery"))]
22mod compact_summary_tests {
23    use super::*;
24
25    #[test]
26    fn compact_loss_and_reordering_preserve_packet_identity() {
27        crate::tests::ensure_common_test_schema();
28        let tx = Relay::new(Box::new(|| 0));
29        let rx = Relay::new(Box::new(|| 0));
30        let opts = RelaySideOptions::default().with_small_packet_transport(0);
31        let a = tx.add_side_packed_with_options("wire", |_| Ok(()), opts.clone());
32        let b = rx.add_side_packed_with_options("wire", |_| Ok(()), opts.clone());
33        let mut encoded = Vec::new();
34        for timestamp in [10000, 10010, 10020, 10030] {
35            let pkt = Packet::new(
36                crate::DataType::named("GPS_DATA"),
37                &[crate::DataEndpoint::named("RADIO")],
38                "VB",
39                timestamp,
40                Arc::<[u8]>::from([0u8; 12]),
41            )
42            .unwrap();
43            let raw = wire_format::pack_packet(&pkt);
44            let frames = tx
45                .encode_side_transport_frames(
46                    a,
47                    opts.clone(),
48                    crate::shared_bytes::convert(raw.clone()),
49                )
50                .unwrap();
51            assert_eq!(frames.len(), 1);
52            encoded.push((raw, frames.into_iter().next().unwrap()));
53        }
54        // Establish template, lose frame 1, reorder and duplicate later frames.
55        for index in [0, 3, 2, 3] {
56            let (raw, frame) = &encoded[index];
57            let received = rx.decode_side_transport_frame(b, frame).unwrap().unwrap();
58            assert_eq!(&received, raw);
59        }
60    }
61
62    #[test]
63    fn relay_preserves_compact_routes_without_inventing_endpoint_ownership() {
64        crate::tests::ensure_common_test_schema();
65        let endpoint = crate::DataEndpoint::named("RADIO");
66        let relay = Relay::new(Box::new(|| 0));
67        let mut sender = DiscoverySenderState {
68            advertised_reachable: vec![endpoint],
69            advertised_reachable_timesync_sources: vec!["clock-behind-bridge".into()],
70            topology_boards: vec![TopologyBoardNode {
71                sender_id: "bridge".into(),
72                reachable_endpoints: vec![],
73                reachable_timesync_sources: vec![],
74                connections: vec![],
75            }],
76            ..Default::default()
77        };
78        Relay::refresh_sender_topology_state(&mut sender);
79        let mut route = DiscoverySideState::default();
80        route.announcers.insert("bridge".into(), sender);
81        Relay::recompute_discovery_side_state(&mut route);
82        let mut state = relay.state.lock();
83        state.discovery_routes.insert(0, route);
84        assert!(
85            relay
86                .advertised_discovery_endpoints_for_link_locked(&state, 0, true, Some(1))
87                .contains(&endpoint)
88        );
89        assert!(
90            !relay
91                .advertised_discovery_endpoints_for_link_locked(&state, 0, true, Some(0))
92                .contains(&endpoint)
93        );
94        assert!(
95            !relay
96                .advertised_discovery_endpoints_for_link_locked(
97                    &state,
98                    DISCOVERY_ROUTE_TTL_MS + 1,
99                    true,
100                    Some(1)
101                )
102                .contains(&endpoint)
103        );
104        assert_eq!(
105            relay.advertised_discovery_timesync_sources_for_link_locked(&state, 0, Some(1)),
106            vec!["clock-behind-bridge".to_string()]
107        );
108        assert!(
109            relay
110                .advertised_discovery_timesync_sources_for_link_locked(&state, 0, Some(0))
111                .is_empty()
112        );
113        assert!(
114            relay
115                .advertised_discovery_timesync_sources_for_link_locked(
116                    &state,
117                    DISCOVERY_ROUTE_TTL_MS + 1,
118                    Some(1)
119                )
120                .is_empty()
121        );
122        let topology =
123            relay.advertised_discovery_topology_for_link_locked(&state, 0, true, Some(1));
124        assert!(
125            topology
126                .iter()
127                .find(|node| node.sender_id == "bridge")
128                .unwrap()
129                .reachable_endpoints
130                .is_empty()
131        );
132    }
133}
134
135use crate::config::{
136    RuntimeMemoryConfig, runtime_reliable_max_end_to_end_ack_cache,
137    runtime_reliable_max_end_to_end_pending, runtime_reliable_max_pending,
138    runtime_reliable_max_retries, runtime_reliable_max_return_routes,
139    runtime_reliable_retransmit_ms,
140};
141use crate::diagnostics::{
142    AdaptiveLinkStats, DiscoveryRuntimeStats, QueueRuntimeStats, ReliableRuntimeStats,
143    RouteModeStats, RouteOverrideStats, RoutePriorityStats, RouteWeightStats, RuntimeSideStats,
144    RuntimeStatsSnapshot, RuntimeTypeStats, TypedRouteOverrideStats,
145};
146#[cfg(feature = "discovery")]
147use crate::discovery::{
148    self, ClientStatsSnapshot, DISCOVERY_INCREMENTAL_RETRY_COUNT, DISCOVERY_ROUTE_TTL_MS,
149    DISCOVERY_SLOW_LINK_PING_INTERVAL_MS, DiscoveryCadenceState,
150    TIMESYNC_SLOW_LINK_MIN_INTERVAL_MS, TopologyAnnouncerRoute, TopologyBoardNode,
151    TopologySideRoute, TopologySnapshot,
152};
153#[cfg(feature = "discovery")]
154use crate::packet::sender_address_u32;
155use crate::packet::{Packet, hash_bytes_u64};
156use crate::queue::{BoundedDeque, ByteCost};
157use crate::wire_format;
158use crate::{is_reliable_type, message_meta, message_priority, reliable_mode};
159use crate::{
160    router::{Clock, CompactTimestampOmissionPolicy, SideTransportProfile},
161    {
162        RouteSelectionMode, TelemetryError, TelemetryResult,
163        lock::{ReentryGate, ReentryGuard, RouterMutex},
164    },
165};
166use alloc::borrow::ToOwned;
167use alloc::boxed::Box;
168use alloc::collections::{BTreeMap, BTreeSet, VecDeque};
169use alloc::string::{String, ToString};
170use alloc::{sync::Arc, vec, vec::Vec};
171use core::mem::size_of;
172
173const MAX_PRIORITY_BURST: u8 = 8;
174
175/// Logical side index (CAN, UART, RADIO, etc.)
176pub type RelaySideId = usize;
177const CONTROL_SLOW_LINK_CAPACITY_BPS: u64 = 512;
178pub const IPV4_LIKE_COMPACT_HEADER_TARGET_BYTES: usize =
179    crate::router::IPV4_LIKE_COMPACT_HEADER_TARGET_BYTES;
180pub const IPV6_LIKE_COMPACT_HEADER_TARGET_BYTES: usize =
181    crate::router::IPV6_LIKE_COMPACT_HEADER_TARGET_BYTES;
182pub const DEFAULT_SIDE_TRANSPORT_TEMPLATE_LIMIT: usize =
183    crate::router::DEFAULT_SIDE_TRANSPORT_TEMPLATE_LIMIT;
184/// Packet Handler function type
185type PacketHandlerFn = dyn Fn(&Packet) -> TelemetryResult<()> + Send + Sync + 'static;
186
187/// Packed Handler function type
188type PackedHandlerFn = dyn Fn(&[u8]) -> TelemetryResult<()> + Send + Sync + 'static;
189
190/// TX handler for a relay side: either packed or packet-based.
191#[derive(Clone)]
192pub enum RelayTxHandlerFn {
193    Packed(Arc<PackedHandlerFn>),
194    Packet(Arc<PacketHandlerFn>),
195}
196
197#[derive(Clone, Debug)]
198pub struct RelaySideOptions {
199    /// Enables the relay's per-link reliable transport layer on this side.
200    ///
201    /// When `true` and the side uses a packed TX handler, reliable schema traffic on this hop
202    /// gains relay-managed sequence numbers, ACKs, packet requests, and retransmits.
203    /// Packet-output sides still receive decoded packets rather than packed reliable framing.
204    pub reliable_enabled: bool,
205    /// Marks the side as eligible for link-local-only endpoints and discovery routes.
206    pub link_local_enabled: bool,
207    /// Allows packets received from this side to enter relay processing.
208    pub ingress_enabled: bool,
209    /// Allows the relay to transmit packets toward this side.
210    pub egress_enabled: bool,
211    /// Enables side-local header-template reuse for packed transport.
212    pub header_template_enabled: bool,
213    /// Maximum number of bytes to emit per packed TX callback.
214    ///
215    /// When non-zero and a packed frame would exceed this size, the relay
216    /// splits it into ordered side-transport chunks and reassembles them on RX
217    /// before normal relay processing. This is intended for fixed-size links
218    /// such as CAN or I2C while keeping the user API packet-oriented.
219    pub max_frame_bytes: usize,
220    /// Target total side-transport overhead for compact follow-up frames.
221    pub compact_header_target_bytes: usize,
222    /// Maximum side-local header templates retained for TX and RX dictionaries.
223    pub max_side_transport_templates: usize,
224    /// Omits the timestamp field from compact follow-up frames when it is unchanged.
225    pub omit_unchanged_compact_timestamps: bool,
226    /// Optional per-data-type timestamp omission policy for compact follow-up frames.
227    pub compact_timestamp_omission_types: CompactTimestampOmissionPolicy,
228    /// Declared compact-link profile for stats and future negotiation.
229    pub side_transport_profile: SideTransportProfile,
230}
231
232impl Default for RelaySideOptions {
233    fn default() -> Self {
234        Self {
235            reliable_enabled: false,
236            link_local_enabled: false,
237            ingress_enabled: true,
238            egress_enabled: true,
239            header_template_enabled: false,
240            max_frame_bytes: 0,
241            compact_header_target_bytes: 0,
242            max_side_transport_templates: DEFAULT_SIDE_TRANSPORT_TEMPLATE_LIMIT,
243            omit_unchanged_compact_timestamps: false,
244            compact_timestamp_omission_types: CompactTimestampOmissionPolicy::none(),
245            side_transport_profile: SideTransportProfile::Canonical,
246        }
247    }
248}
249
250impl RelaySideOptions {
251    /// Convenience preset for bounded packed-side transport.
252    ///
253    /// `max_frame_bytes == 0` leaves packed frames unbounded. Values greater
254    /// than zero enable relay-managed chunking/reassembly on this side.
255    #[inline]
256    pub fn with_small_packet_transport(mut self, max_frame_bytes: usize) -> Self {
257        self.header_template_enabled = true;
258        self.max_frame_bytes = max_frame_bytes;
259        self.compact_header_target_bytes = IPV6_LIKE_COMPACT_HEADER_TARGET_BYTES;
260        self.side_transport_profile = SideTransportProfile::Ipv6Like;
261        self
262    }
263
264    #[inline]
265    pub fn with_ipv4_like_compact_header_target(mut self) -> Self {
266        self.header_template_enabled = true;
267        self.compact_header_target_bytes = IPV4_LIKE_COMPACT_HEADER_TARGET_BYTES;
268        self.omit_unchanged_compact_timestamps = true;
269        self.side_transport_profile = SideTransportProfile::Ipv4Like;
270        self
271    }
272
273    #[inline]
274    pub fn with_ipv6_like_compact_header_target(mut self) -> Self {
275        self.header_template_enabled = true;
276        self.compact_header_target_bytes = IPV6_LIKE_COMPACT_HEADER_TARGET_BYTES;
277        self.side_transport_profile = SideTransportProfile::Ipv6Like;
278        self
279    }
280
281    #[inline]
282    pub fn with_template_transport(mut self) -> Self {
283        self.header_template_enabled = true;
284        self.side_transport_profile = SideTransportProfile::Template;
285        self
286    }
287
288    #[inline]
289    pub fn with_omitted_unchanged_compact_timestamps(mut self) -> Self {
290        self.header_template_enabled = true;
291        self.omit_unchanged_compact_timestamps = true;
292        self
293    }
294
295    #[inline]
296    pub fn with_omitted_unchanged_compact_timestamps_for_type(
297        mut self,
298        ty: crate::DataType,
299    ) -> Self {
300        self.header_template_enabled = true;
301        self.compact_timestamp_omission_types.insert(ty);
302        self
303    }
304
305    #[inline]
306    pub fn effective_transport_profile(&self) -> SideTransportProfile {
307        if !self.header_template_enabled && self.max_frame_bytes == 0 {
308            SideTransportProfile::Canonical
309        } else if self.side_transport_profile == SideTransportProfile::Canonical {
310            SideTransportProfile::Template
311        } else {
312            self.side_transport_profile
313        }
314    }
315
316    #[cfg(feature = "discovery")]
317    #[inline]
318    pub fn link_capabilities(&self) -> discovery::LinkCapabilities {
319        let mut flags = discovery::LINK_CAPABILITY_END_TO_END_RELIABILITY;
320        if self.header_template_enabled {
321            flags |= discovery::LINK_CAPABILITY_HEADER_TEMPLATES;
322        }
323        if self.max_frame_bytes != 0 {
324            flags |= discovery::LINK_CAPABILITY_CHUNKING;
325        }
326        if self.reliable_enabled {
327            flags |= discovery::LINK_CAPABILITY_RELIABILITY;
328        }
329        if self.omit_unchanged_compact_timestamps
330            || !self.compact_timestamp_omission_types.is_empty()
331        {
332            flags |= discovery::LINK_CAPABILITY_OMIT_UNCHANGED_TIMESTAMPS;
333        }
334        #[cfg(feature = "cryptography")]
335        {
336            flags |= discovery::LINK_CAPABILITY_CRYPTO;
337        }
338        discovery::LinkCapabilities {
339            version: 1,
340            flags,
341            profile: self.effective_transport_profile().discovery_code(),
342            max_frame_bytes: self.max_frame_bytes.min(u32::MAX as usize) as u32,
343            compact_header_target_bytes: self.compact_header_target_bytes.min(u32::MAX as usize)
344                as u32,
345            max_side_transport_templates: self.max_side_transport_templates.min(u32::MAX as usize)
346                as u32,
347        }
348    }
349}
350
351/// One side of the relay – a name + TX handler.
352#[derive(Clone)]
353pub struct RelaySide {
354    pub name: Arc<str>,
355    pub tx_handler: RelayTxHandlerFn,
356    pub opts: RelaySideOptions,
357}
358
359#[derive(Clone, Debug, PartialEq, Eq)]
360pub enum RelayItem {
361    Packed(crate::SharedBytes),
362    Packet(Arc<Packet>),
363}
364
365/// Item that was received by the relay from some side.
366#[derive(Clone, Debug, PartialEq, Eq)]
367struct RelayRxItem {
368    src: RelaySideId,
369    data: RelayItem,
370    priority: u8,
371}
372
373impl ByteCost for RelayRxItem {
374    #[cfg(feature = "compact-packet-store")]
375    fn park_for_queue(&mut self) -> TelemetryResult<()> {
376        match &mut self.data {
377            RelayItem::Packet(packet) => Arc::make_mut(packet).park_for_queue()?,
378            RelayItem::Packed(bytes) => bytes.park_for_queue()?,
379        }
380        Ok(())
381    }
382    fn byte_cost(&self) -> usize {
383        match &self.data {
384            RelayItem::Packed(bytes) => bytes.len(),
385            RelayItem::Packet(pkt) => pkt.byte_cost(),
386        }
387    }
388}
389
390/// Item that is ready to be transmitted out a destination side.
391#[derive(Clone, Debug, PartialEq, Eq)]
392struct RelayTxItem {
393    src: Option<RelaySideId>,
394    dst: RelaySideId,
395    data: RelayItem,
396    priority: u8,
397    retry_after_ms: u64,
398}
399
400#[derive(Clone, Debug, PartialEq, Eq)]
401struct RelayReplayItem {
402    dst: RelaySideId,
403    bytes: crate::SharedBytes,
404    priority: u8,
405    retry_after_ms: u64,
406}
407
408impl ByteCost for RelayTxItem {
409    #[cfg(feature = "compact-packet-store")]
410    fn park_for_queue(&mut self) -> TelemetryResult<()> {
411        match &mut self.data {
412            RelayItem::Packet(packet) => Arc::make_mut(packet).park_for_queue()?,
413            RelayItem::Packed(bytes) => bytes.park_for_queue()?,
414        }
415        Ok(())
416    }
417    fn byte_cost(&self) -> usize {
418        (match &self.data {
419            RelayItem::Packed(bytes) => bytes.len(),
420            RelayItem::Packet(pkt) => pkt.byte_cost(),
421        })
422        .saturating_add(size_of::<u64>())
423    }
424}
425
426impl ByteCost for RelayReplayItem {
427    #[cfg(feature = "compact-packet-store")]
428    fn park_for_queue(&mut self) -> TelemetryResult<()> {
429        self.bytes.park_for_queue()
430    }
431    fn byte_cost(&self) -> usize {
432        (self.bytes.len()).saturating_add(size_of::<u64>())
433    }
434}
435
436// -------------------- Reliable delivery state (relay) --------------------
437
438#[derive(Debug, Clone)]
439struct ReliableTxState {
440    next_seq: u32,
441    sent_order: VecDeque<u32>,
442    sent: BTreeMap<u32, ReliableSent>,
443}
444
445#[derive(Debug, Clone)]
446struct ReliableSent {
447    bytes: crate::SharedBytes,
448    last_send_ms: u64,
449    retries: u32,
450    queued: bool,
451    partial_acked: bool,
452}
453
454#[derive(Debug, Clone)]
455struct ReliableRxState {
456    expected_seq: u32,
457    buffered: BTreeMap<u32, crate::SharedBytes>,
458}
459
460#[derive(Debug, Clone)]
461struct ReliableReturnRouteState {
462    side: RelaySideId,
463    last_replay_ms: u64,
464}
465
466#[cfg(feature = "discovery")]
467#[derive(Debug, Clone, Default, PartialEq, Eq)]
468struct DiscoverySenderState {
469    /// Numeric identity advertised for this hostname. Compact frames may carry
470    /// this address instead of the hostname, so routing must accept either.
471    advertised_address: Option<u32>,
472    reachable: Vec<crate::DataEndpoint>,
473    advertised_reachable: Vec<crate::DataEndpoint>,
474    reachable_timesync_sources: Vec<String>,
475    advertised_reachable_timesync_sources: Vec<String>,
476    topology_boards: Vec<TopologyBoardNode>,
477    has_full_topology: bool,
478    has_schema: bool,
479    last_seen_ms: u64,
480}
481
482#[inline]
483fn is_internal_control_type(ty: crate::DataType) -> bool {
484    if matches!(
485        ty,
486        crate::DataType::ReliableAck
487            | crate::DataType::ReliablePartialAck
488            | crate::DataType::ReliablePacketRequest
489            | crate::DataType::P2pMessage
490    ) {
491        return true;
492    }
493
494    #[cfg(feature = "timesync")]
495    if matches!(
496        ty,
497        crate::DataType::TimeSyncAnnounce
498            | crate::DataType::TimeSyncRequest
499            | crate::DataType::TimeSyncResponse
500    ) {
501        return true;
502    }
503
504    #[cfg(feature = "discovery")]
505    if discovery::is_discovery_type(ty) {
506        return true;
507    }
508
509    let _ = ty;
510    false
511}
512
513#[cfg(feature = "discovery")]
514#[derive(Debug, Clone, Default, PartialEq, Eq)]
515struct DiscoverySideState {
516    reachable: Vec<crate::DataEndpoint>,
517    reachable_timesync_sources: Vec<String>,
518    last_seen_ms: u64,
519    announcers: BTreeMap<String, DiscoverySenderState>,
520}
521
522#[derive(Clone, Debug, Default)]
523struct SideTransportState {
524    tx_chunk_progress: Option<Box<crate::side_transport::SideTxProgress>>,
525    tx_template_ids: BTreeMap<u64, u32>,
526    tx_templates: BTreeMap<u64, SideHeaderTemplate>,
527    tx_last_timestamps: BTreeMap<u32, u64>,
528    tx_compact_uses: BTreeMap<u32, u8>,
529    rx_templates: BTreeMap<u64, u32>,
530    rx_templates_by_id: BTreeMap<u32, SideHeaderTemplate>,
531    rx_last_timestamps: BTreeMap<u32, u64>,
532    rx_chunks: BTreeMap<u32, SideChunkAssembly>,
533    next_template_id: u32,
534}
535
536impl SideTransportState {
537    fn refresh_transmit_header_templates(&mut self) {
538        // Refresh on the wire, not by forgetting the eviction history. RX
539        // retains its dictionary; clearing only TX makes bounded caches evict
540        // different entries after the next insertion/capacity negotiation.
541        for uses in self.tx_compact_uses.values_mut() {
542            *uses = u8::MAX;
543        }
544        // Local topology changes do not invalidate dictionaries received from
545        // peers. Dropping RX state here loses already-in-flight compact ACKs.
546    }
547
548    fn tx_template_count(&self) -> usize {
549        self.tx_template_ids.len()
550    }
551
552    fn rx_template_count(&self) -> usize {
553        self.rx_templates_by_id.len()
554    }
555
556    fn trim_tx_templates_to(&mut self, limit: usize) -> usize {
557        let mut removed = 0usize;
558        while self.tx_template_ids.len() > limit {
559            let Some(old_hash) = self.tx_template_ids.keys().next().copied() else {
560                break;
561            };
562            if let Some(old_id) = self.tx_template_ids.remove(&old_hash) {
563                self.tx_last_timestamps.remove(&old_id);
564                self.tx_compact_uses.remove(&old_id);
565            }
566            self.tx_templates.remove(&old_hash);
567            removed += 1;
568        }
569        removed
570    }
571
572    fn insert_tx_template(
573        &mut self,
574        template: SideHeaderTemplate,
575        template_id: u32,
576        max_templates: usize,
577    ) -> bool {
578        if max_templates == 0 {
579            return false;
580        }
581        let mut evicted = false;
582        if self.tx_template_ids.len() >= max_templates
583            && !self.tx_template_ids.contains_key(&template.hash)
584            && let Some(old_hash) = self.tx_template_ids.keys().next().copied()
585        {
586            if let Some(old_id) = self.tx_template_ids.remove(&old_hash) {
587                self.tx_last_timestamps.remove(&old_id);
588                self.tx_compact_uses.remove(&old_id);
589            }
590            self.tx_templates.remove(&old_hash);
591            evicted = true;
592        }
593        self.tx_template_ids.insert(template.hash, template_id);
594        self.tx_templates.insert(template.hash, template);
595        self.tx_compact_uses.insert(template_id, 0);
596        evicted
597    }
598
599    fn insert_rx_template(
600        &mut self,
601        template_id: u32,
602        template: SideHeaderTemplate,
603        max_templates: usize,
604    ) -> bool {
605        if max_templates == 0 {
606            return false;
607        }
608        // Keep one owned header. The hash index needs only its wire ID, not
609        // another copy of the header and its shared-buffer owners.
610        if let Some(old_id) = self.rx_templates.get(&template.hash).copied()
611            && old_id != template_id
612        {
613            self.rx_templates_by_id.remove(&old_id);
614            self.rx_last_timestamps.remove(&old_id);
615            self.rx_templates.remove(&template.hash);
616        }
617        if let Some(previous) = self.rx_templates_by_id.get(&template_id)
618            && previous.hash != template.hash
619        {
620            self.rx_templates.remove(&previous.hash);
621            self.rx_last_timestamps.remove(&template_id);
622        }
623        let mut evicted = false;
624        if self.rx_templates_by_id.len() >= max_templates
625            && !self.rx_templates_by_id.contains_key(&template_id)
626            // Match TX's deterministic lowest-hash eviction on both routers
627            // and relays; lowest wire ID can select a different live header.
628            && let Some((&old_hash, &old_id)) = self.rx_templates.first_key_value()
629        {
630            self.rx_templates.remove(&old_hash);
631            self.rx_templates_by_id.remove(&old_id);
632            self.rx_last_timestamps.remove(&old_id);
633            evicted = true;
634        }
635        self.rx_templates.insert(template.hash, template_id);
636        self.rx_templates_by_id.insert(template_id, template);
637        evicted
638    }
639}
640
641#[derive(Debug, Clone, Default)]
642struct AdaptiveRouteStats {
643    estimated_bandwidth_bps: u64,
644    peak_bandwidth_bps: u64,
645    last_observed_ms: u64,
646    last_slow_observed_ms: u64,
647    sample_count: u64,
648    window_started_ms: u64,
649    window_bytes: u64,
650    peak_usage_bps: u64,
651}
652
653#[cfg(feature = "discovery")]
654#[derive(Debug, Clone, Default)]
655struct DiscoverySideThrottleState {
656    next_topology_request_ms: u64,
657    next_schema_request_ms: u64,
658    next_ping_ms: u64,
659    // Scheduled and accepted sends differ when a constrained link is backed up.
660    last_liveness_tx_ms: u64,
661    pending_incremental: bool,
662    incremental_retries_remaining: u8,
663    has_sent_full: bool,
664    last_topology: Vec<TopologyBoardNode>,
665}
666
667#[cfg(feature = "discovery")]
668impl DiscoverySideThrottleState {
669    fn liveness_due(&self, now_ms: u64) -> bool {
670        now_ms >= self.next_ping_ms
671            && now_ms.saturating_sub(self.last_liveness_tx_ms)
672                >= DISCOVERY_SLOW_LINK_PING_INTERVAL_MS
673    }
674}
675
676#[cfg(all(feature = "discovery", feature = "timesync"))]
677#[derive(Debug, Clone, Default)]
678struct TimeSyncSideThrottleState {
679    next_allowed_ms: u64,
680}
681
682#[cfg(feature = "discovery")]
683#[derive(Debug, Clone, Copy, PartialEq, Eq)]
684enum DiscoveryAdvertiseLevel {
685    MinimalPing,
686    Incremental,
687    Full,
688}
689
690impl AdaptiveRouteStats {
691    #[inline]
692    fn observe(&mut self, bytes: usize, sample_bps: u64, now_ms: u64) {
693        self.estimated_bandwidth_bps = if self.estimated_bandwidth_bps == 0 {
694            sample_bps
695        } else if sample_bps >= self.estimated_bandwidth_bps {
696            self.estimated_bandwidth_bps
697                .saturating_mul(3)
698                .saturating_add(sample_bps.saturating_mul(5))
699                / 8
700        } else {
701            self.estimated_bandwidth_bps
702                .saturating_mul(7)
703                .saturating_add(sample_bps)
704                / 8
705        };
706        self.peak_bandwidth_bps = self.peak_bandwidth_bps.max(sample_bps);
707        self.last_observed_ms = now_ms;
708        if sample_bps > 0 && sample_bps <= CONTROL_SLOW_LINK_CAPACITY_BPS {
709            self.last_slow_observed_ms = now_ms;
710        }
711        self.sample_count = self.sample_count.saturating_add(1);
712        if self.window_started_ms == 0 || now_ms.saturating_sub(self.window_started_ms) > 1_000 {
713            self.window_started_ms = now_ms;
714            self.window_bytes = 0;
715        }
716        self.window_bytes = self.window_bytes.saturating_add(bytes as u64);
717        self.peak_usage_bps = self.peak_usage_bps.max(self.current_usage_bps(now_ms));
718    }
719
720    #[inline]
721    fn current_usage_bps(&self, now_ms: u64) -> u64 {
722        if self.window_started_ms == 0 {
723            return 0;
724        }
725        let elapsed_ms = now_ms.saturating_sub(self.window_started_ms).max(1);
726        (u128::from(self.window_bytes).saturating_mul(1000) / u128::from(elapsed_ms))
727            .min(u128::from(u64::MAX)) as u64
728    }
729
730    #[inline]
731    fn available_headroom_bps(&self, now_ms: u64) -> u64 {
732        let capacity = self
733            .estimated_bandwidth_bps
734            .max(self.peak_bandwidth_bps)
735            .max(1);
736        capacity.saturating_sub(self.current_usage_bps(now_ms))
737    }
738
739    #[inline]
740    fn weight(&self, now_ms: u64) -> u64 {
741        self.available_headroom_bps(now_ms).max(1)
742    }
743
744    #[inline]
745    fn snapshot(&self, now_ms: u64, auto_balancing_enabled: bool) -> AdaptiveLinkStats {
746        let current_usage_bps = self.current_usage_bps(now_ms);
747        let estimated_capacity_bps = self.estimated_bandwidth_bps.max(1);
748        let peak_capacity_bps = self.peak_bandwidth_bps.max(estimated_capacity_bps);
749        let available_headroom_bps = peak_capacity_bps.saturating_sub(current_usage_bps);
750        AdaptiveLinkStats {
751            auto_balancing_enabled,
752            estimated_capacity_bps,
753            peak_capacity_bps,
754            current_usage_bps,
755            peak_usage_bps: self.peak_usage_bps.max(current_usage_bps),
756            available_headroom_bps,
757            effective_weight: available_headroom_bps.max(1),
758            last_observed_ms: self.last_observed_ms,
759            sample_count: self.sample_count,
760        }
761    }
762}
763
764#[derive(Debug, Clone, Default)]
765struct TypeRuntimeStatsInner {
766    tx_packets: u64,
767    tx_bytes: u64,
768    rx_packets: u64,
769    rx_bytes: u64,
770    relayed_tx_packets: u64,
771    relayed_tx_bytes: u64,
772    relayed_rx_packets: u64,
773    relayed_rx_bytes: u64,
774    tx_retries: u64,
775    handler_failures: u64,
776}
777
778#[derive(Debug, Clone, Default)]
779struct SideRuntimeStatsInner {
780    tx_packets: u64,
781    tx_bytes: u64,
782    rx_packets: u64,
783    rx_bytes: u64,
784    relayed_tx_packets: u64,
785    relayed_tx_bytes: u64,
786    relayed_rx_packets: u64,
787    relayed_rx_bytes: u64,
788    tx_retries: u64,
789    tx_handler_failures: u64,
790    total_handler_retries: u64,
791    side_transport_full_frames: u64,
792    side_transport_compact_frames: u64,
793    side_transport_compact_delta_frames: u64,
794    side_transport_compact_omitted_timestamp_frames: u64,
795    side_transport_chunk_frames: u64,
796    side_transport_raw_bytes: u64,
797    side_transport_wire_bytes: u64,
798    side_transport_bytes_saved: u64,
799    side_transport_min_compact_overhead_bytes: Option<usize>,
800    side_transport_max_compact_overhead_bytes: Option<usize>,
801    side_transport_compact_target_misses: u64,
802    side_transport_template_evictions: u64,
803    data_types: BTreeMap<u32, TypeRuntimeStatsInner>,
804}
805
806impl SideRuntimeStatsInner {
807    fn type_stats_mut(&mut self, ty: crate::DataType) -> &mut TypeRuntimeStatsInner {
808        self.data_types.entry(ty.as_u32()).or_default()
809    }
810
811    fn note_tx(&mut self, ty: crate::DataType, bytes: usize, retries: usize) {
812        self.tx_packets = self.tx_packets.saturating_add(1);
813        self.tx_bytes = self.tx_bytes.saturating_add(bytes as u64);
814        self.relayed_tx_packets = self.relayed_tx_packets.saturating_add(1);
815        self.relayed_tx_bytes = self.relayed_tx_bytes.saturating_add(bytes as u64);
816        self.tx_retries = self.tx_retries.saturating_add(retries as u64);
817        self.total_handler_retries = self.total_handler_retries.saturating_add(retries as u64);
818        let stats = self.type_stats_mut(ty);
819        stats.tx_packets = stats.tx_packets.saturating_add(1);
820        stats.tx_bytes = stats.tx_bytes.saturating_add(bytes as u64);
821        stats.relayed_tx_packets = stats.relayed_tx_packets.saturating_add(1);
822        stats.relayed_tx_bytes = stats.relayed_tx_bytes.saturating_add(bytes as u64);
823        stats.tx_retries = stats.tx_retries.saturating_add(retries as u64);
824    }
825
826    fn note_rx(&mut self, ty: crate::DataType, bytes: usize) {
827        self.rx_packets = self.rx_packets.saturating_add(1);
828        self.rx_bytes = self.rx_bytes.saturating_add(bytes as u64);
829        self.relayed_rx_packets = self.relayed_rx_packets.saturating_add(1);
830        self.relayed_rx_bytes = self.relayed_rx_bytes.saturating_add(bytes as u64);
831        let stats = self.type_stats_mut(ty);
832        stats.rx_packets = stats.rx_packets.saturating_add(1);
833        stats.rx_bytes = stats.rx_bytes.saturating_add(bytes as u64);
834        stats.relayed_rx_packets = stats.relayed_rx_packets.saturating_add(1);
835        stats.relayed_rx_bytes = stats.relayed_rx_bytes.saturating_add(bytes as u64);
836    }
837
838    fn note_tx_failure(&mut self, ty: crate::DataType, retries: usize) {
839        self.tx_handler_failures = self.tx_handler_failures.saturating_add(1);
840        self.tx_retries = self.tx_retries.saturating_add(retries as u64);
841        self.total_handler_retries = self.total_handler_retries.saturating_add(retries as u64);
842        let stats = self.type_stats_mut(ty);
843        stats.handler_failures = stats.handler_failures.saturating_add(1);
844        stats.tx_retries = stats.tx_retries.saturating_add(retries as u64);
845    }
846
847    fn note_side_transport_full(&mut self, raw_bytes: usize, wire_bytes: usize) {
848        self.side_transport_full_frames = self.side_transport_full_frames.saturating_add(1);
849        self.note_side_transport_bytes(raw_bytes, wire_bytes);
850    }
851
852    fn note_side_transport_compact(
853        &mut self,
854        raw_bytes: usize,
855        wire_bytes: usize,
856        compact_overhead_bytes: usize,
857        used_timestamp_delta: bool,
858        omitted_timestamp: bool,
859    ) {
860        self.side_transport_compact_frames = self.side_transport_compact_frames.saturating_add(1);
861        if used_timestamp_delta {
862            self.side_transport_compact_delta_frames =
863                self.side_transport_compact_delta_frames.saturating_add(1);
864        }
865        if omitted_timestamp {
866            self.side_transport_compact_omitted_timestamp_frames = self
867                .side_transport_compact_omitted_timestamp_frames
868                .saturating_add(1);
869        }
870        self.note_side_transport_bytes(raw_bytes, wire_bytes);
871        self.side_transport_min_compact_overhead_bytes = Some(
872            self.side_transport_min_compact_overhead_bytes
873                .map_or(compact_overhead_bytes, |v| v.min(compact_overhead_bytes)),
874        );
875        self.side_transport_max_compact_overhead_bytes = Some(
876            self.side_transport_max_compact_overhead_bytes
877                .map_or(compact_overhead_bytes, |v| v.max(compact_overhead_bytes)),
878        );
879    }
880
881    fn note_side_transport_chunks(&mut self, chunks: usize) {
882        self.side_transport_chunk_frames = self
883            .side_transport_chunk_frames
884            .saturating_add(chunks as u64);
885    }
886
887    fn note_side_transport_bytes(&mut self, raw_bytes: usize, wire_bytes: usize) {
888        self.side_transport_raw_bytes = self
889            .side_transport_raw_bytes
890            .saturating_add(raw_bytes as u64);
891        self.side_transport_wire_bytes = self
892            .side_transport_wire_bytes
893            .saturating_add(wire_bytes as u64);
894        if raw_bytes > wire_bytes {
895            self.side_transport_bytes_saved = self
896                .side_transport_bytes_saved
897                .saturating_add((raw_bytes - wire_bytes) as u64);
898        }
899    }
900
901    fn note_side_transport_compact_target_miss(&mut self) {
902        self.side_transport_compact_target_misses =
903            self.side_transport_compact_target_misses.saturating_add(1);
904    }
905
906    fn note_side_transport_template_eviction(&mut self) {
907        self.side_transport_template_evictions =
908            self.side_transport_template_evictions.saturating_add(1);
909    }
910}
911
912#[derive(Clone, Debug)]
913pub struct RelayConfig {
914    sender: Arc<str>,
915    memory: RuntimeMemoryConfig,
916}
917
918impl RelayConfig {
919    pub fn new() -> Self {
920        Self::default()
921    }
922
923    pub fn with_sender<S: AsRef<str>>(mut self, sender: S) -> Self {
924        self.sender = Arc::from(sender.as_ref());
925        self
926    }
927
928    pub fn with_memory_config(mut self, memory: RuntimeMemoryConfig) -> TelemetryResult<Self> {
929        memory.validate()?;
930        self.memory = memory;
931        Ok(self)
932    }
933
934    fn sender(&self) -> Arc<str> {
935        self.sender.clone()
936    }
937
938    fn memory_config(&self) -> RuntimeMemoryConfig {
939        self.memory
940    }
941}
942
943impl Default for RelayConfig {
944    fn default() -> Self {
945        Self {
946            sender: Arc::from("RELAY"),
947            memory: RuntimeMemoryConfig::default(),
948        }
949    }
950}
951
952#[derive(Clone, Copy, Debug, PartialEq, Eq)]
953enum RouteSelectionOrigin {
954    Flood,
955    Discovered,
956}
957
958/// Internal state, protected by RouterMutex so all public methods can take &self.
959struct RelayInner {
960    memory: RuntimeMemoryConfig,
961    sides: Vec<Option<RelaySide>>,
962    route_overrides: BTreeMap<(Option<RelaySideId>, RelaySideId), bool>,
963    typed_route_overrides: BTreeMap<(Option<RelaySideId>, u32, RelaySideId), bool>,
964    route_weights: BTreeMap<(Option<RelaySideId>, RelaySideId), u32>,
965    route_priorities: BTreeMap<(Option<RelaySideId>, RelaySideId), u32>,
966    source_route_modes: BTreeMap<Option<RelaySideId>, RouteSelectionMode>,
967    route_selection_cursors: BTreeMap<Option<RelaySideId>, u64>,
968    adaptive_route_stats: BTreeMap<RelaySideId, AdaptiveRouteStats>,
969    side_runtime_stats: BTreeMap<RelaySideId, SideRuntimeStatsInner>,
970    side_transport: BTreeMap<RelaySideId, SideTransportState>,
971    rx_queue: BoundedDeque<RelayRxItem>,
972    tx_queue: BoundedDeque<RelayTxItem>,
973    tx_priority_burst: u8,
974    replay_queue: BoundedDeque<RelayReplayItem>,
975    recent_rx: BoundedDeque<u64>,
976    reliable_tx: BTreeMap<(RelaySideId, u32), ReliableTxState>,
977    reliable_rx: BTreeMap<(RelaySideId, u32), ReliableRxState>,
978    reliable_return_routes: BTreeMap<u64, ReliableReturnRouteState>,
979    reliable_return_route_order: VecDeque<u64>,
980    end_to_end_acked_destinations: BTreeMap<u64, BTreeSet<u64>>,
981    end_to_end_acked_destination_order: VecDeque<u64>,
982    total_handler_failures: u64,
983    total_handler_retries: u64,
984    #[cfg(feature = "discovery")]
985    discovery_routes: BTreeMap<RelaySideId, DiscoverySideState>,
986    #[cfg(feature = "discovery")]
987    discovery_cadence: DiscoveryCadenceState,
988    #[cfg(feature = "discovery")]
989    discovery_side_throttle: BTreeMap<RelaySideId, DiscoverySideThrottleState>,
990    #[cfg(all(feature = "discovery", feature = "timesync"))]
991    timesync_side_throttle: BTreeMap<RelaySideId, TimeSyncSideThrottleState>,
992}
993
994#[derive(Clone, Copy, Debug, PartialEq, Eq)]
995enum RelayQueueKind {
996    Rx,
997    Tx,
998    Replay,
999    Recent,
1000    ReliableRxBuffer,
1001}
1002
1003impl RelayInner {
1004    #[cfg(feature = "discovery")]
1005    fn topology_board_byte_cost(board: &TopologyBoardNode) -> usize {
1006        board
1007            .sender_id
1008            .len()
1009            .saturating_add(board.reachable_endpoints.len() * size_of::<crate::DataEndpoint>())
1010            .saturating_add(
1011                board
1012                    .reachable_timesync_sources
1013                    .iter()
1014                    .map(|s| s.len())
1015                    .sum::<usize>(),
1016            )
1017            .saturating_add(board.connections.iter().map(|s| s.len()).sum::<usize>())
1018    }
1019
1020    #[cfg(feature = "discovery")]
1021    fn discovery_sender_byte_cost(sender: &str, state: &DiscoverySenderState) -> usize {
1022        sender
1023            .len()
1024            .saturating_add(state.reachable.len() * size_of::<crate::DataEndpoint>())
1025            .saturating_add(state.advertised_reachable.len() * size_of::<crate::DataEndpoint>())
1026            .saturating_add(
1027                state
1028                    .reachable_timesync_sources
1029                    .iter()
1030                    .map(|s| s.len())
1031                    .sum::<usize>(),
1032            )
1033            .saturating_add(
1034                state
1035                    .advertised_reachable_timesync_sources
1036                    .iter()
1037                    .map(|s| s.len())
1038                    .sum::<usize>(),
1039            )
1040            .saturating_add(
1041                state
1042                    .topology_boards
1043                    .iter()
1044                    .map(Self::topology_board_byte_cost)
1045                    .sum::<usize>(),
1046            )
1047            .saturating_add(size_of::<DiscoverySenderState>())
1048    }
1049
1050    #[cfg(feature = "discovery")]
1051    fn discovery_route_byte_cost(route: &DiscoverySideState) -> usize {
1052        size_of::<DiscoverySideState>()
1053            .saturating_add(route.reachable.len() * size_of::<crate::DataEndpoint>())
1054            .saturating_add(
1055                route
1056                    .reachable_timesync_sources
1057                    .iter()
1058                    .map(|s| s.len())
1059                    .sum::<usize>(),
1060            )
1061            .saturating_add(
1062                route
1063                    .announcers
1064                    .iter()
1065                    .map(|(sender, state)| Self::discovery_sender_byte_cost(sender, state))
1066                    .sum::<usize>(),
1067            )
1068    }
1069
1070    #[cfg(feature = "discovery")]
1071    fn discovery_bytes_used(&self) -> usize {
1072        self.discovery_routes
1073            .values()
1074            .map(Self::discovery_route_byte_cost)
1075            .sum()
1076    }
1077
1078    #[inline]
1079    fn reliable_rx_buffered_bytes(&self) -> usize {
1080        self.reliable_rx
1081            .values()
1082            .flat_map(|state| state.buffered.values())
1083            .map(|bytes| size_of::<crate::SharedBytes>() + bytes.len())
1084            .sum()
1085    }
1086
1087    #[inline]
1088    fn shared_queue_bytes_used(&self) -> usize {
1089        self.rx_queue
1090            .bytes_used()
1091            .saturating_add(self.tx_queue.bytes_used())
1092            .saturating_add(self.replay_queue.bytes_used())
1093            .saturating_add(self.recent_rx.max_bytes())
1094            .saturating_add(self.reliable_rx_buffered_bytes())
1095            .saturating_add(crate::config::schema_bytes_used())
1096            .saturating_add({
1097                #[cfg(feature = "discovery")]
1098                {
1099                    self.discovery_bytes_used()
1100                }
1101                #[cfg(not(feature = "discovery"))]
1102                {
1103                    0
1104                }
1105            })
1106    }
1107
1108    fn reliable_rx_buffer_len(&self) -> usize {
1109        self.reliable_rx
1110            .values()
1111            .map(|state| state.buffered.len())
1112            .sum()
1113    }
1114
1115    fn pop_reliable_rx_buffered(&mut self) -> Option<crate::SharedBytes> {
1116        let key = self
1117            .reliable_rx
1118            .iter()
1119            .find_map(|(key, state)| (!state.buffered.is_empty()).then_some(*key))?;
1120        self.reliable_rx
1121            .get_mut(&key)?
1122            .buffered
1123            .pop_first()
1124            .map(|(_, v)| v)
1125    }
1126
1127    fn pop_lowest_packet_queue_item(&mut self, below: usize) -> bool {
1128        let victim = [
1129            (
1130                RelayQueueKind::Rx,
1131                self.rx_queue.lowest_priority(|item| item.priority),
1132            ),
1133            (
1134                RelayQueueKind::Tx,
1135                self.tx_queue.lowest_priority(|item| item.priority),
1136            ),
1137            (
1138                RelayQueueKind::Replay,
1139                self.replay_queue.lowest_priority(|item| item.priority),
1140            ),
1141        ]
1142        .into_iter()
1143        .filter_map(|(kind, priority)| priority.map(|priority| (kind, priority)))
1144        .filter(|(_, priority)| usize::from(*priority) < below)
1145        .min_by_key(|(_, priority)| *priority)
1146        .map(|(kind, _)| kind);
1147        match victim {
1148            Some(RelayQueueKind::Rx) => self.rx_queue.pop_lowest_priority().is_some(),
1149            Some(RelayQueueKind::Tx) => self.tx_queue.pop_lowest_priority().is_some(),
1150            Some(RelayQueueKind::Replay) => self.replay_queue.pop_lowest_priority().is_some(),
1151            _ => false,
1152        }
1153    }
1154
1155    fn make_shared_queue_room(
1156        &mut self,
1157        incoming_cost: usize,
1158        _preferred: RelayQueueKind,
1159    ) -> TelemetryResult<()> {
1160        if incoming_cost > self.memory.max_queue_budget {
1161            return Err(TelemetryError::PacketTooLarge(
1162                "Item exceeds maximum shared queue budget",
1163            ));
1164        }
1165
1166        while self.shared_queue_bytes_used().saturating_add(incoming_cost)
1167            > self.memory.max_queue_budget
1168        {
1169            // Incoming work cannot evict the routes required to deliver it.
1170            // Prefer low-priority packets; refuse admission once only metadata
1171            // remains rather than treating topology as an ordinary queue.
1172            if !self.pop_lowest_packet_queue_item(256) && self.pop_reliable_rx_buffered().is_none()
1173            {
1174                return Err(TelemetryError::PacketTooLarge(
1175                    "Item exceeds maximum shared queue budget",
1176                ));
1177            }
1178        }
1179
1180        Ok(())
1181    }
1182
1183    #[cfg(feature = "discovery")]
1184    fn schema_growth_budget(&self) -> usize {
1185        // Queued packets are reclaimable. Existing reachability and active
1186        // reliable reassembly remain protected when admitting a new schema.
1187        let protected = self
1188            .recent_rx
1189            .max_bytes()
1190            .saturating_add(self.discovery_bytes_used())
1191            .saturating_add(self.reliable_rx_buffered_bytes());
1192        self.memory.max_queue_budget.saturating_sub(protected)
1193    }
1194
1195    #[cfg(feature = "discovery")]
1196    fn commit_discovery_route(
1197        &mut self,
1198        side: RelaySideId,
1199        route: DiscoverySideState,
1200    ) -> TelemetryResult<()> {
1201        let previous = self
1202            .discovery_routes
1203            .get(&side)
1204            .map_or(0, Self::discovery_route_byte_cost);
1205        let incoming = Self::discovery_route_byte_cost(&route);
1206        let growth = incoming.saturating_sub(previous);
1207        while self.shared_queue_bytes_used().saturating_add(growth) > self.memory.max_queue_budget {
1208            if !self.pop_lowest_packet_queue_item(256) {
1209                return Err(TelemetryError::Io("shared priority queue saturated"));
1210            }
1211        }
1212        self.discovery_routes.insert(side, route);
1213        Ok(())
1214    }
1215
1216    #[cfg(feature = "discovery")]
1217    fn fit_discovery_budget(&mut self) {
1218        while self.shared_queue_bytes_used() > self.memory.max_queue_budget {
1219            if self.pop_lowest_packet_queue_item(256) {
1220                continue;
1221            }
1222            // Live routes are protected. Growth is admitted transactionally;
1223            // external registry/config changes cannot justify erasing routes.
1224            break;
1225        }
1226    }
1227
1228    fn push_rx(&mut self, item: RelayRxItem) -> TelemetryResult<()> {
1229        self.make_shared_queue_room(item.byte_cost(), RelayQueueKind::Rx)?;
1230        self.rx_queue
1231            .push_back_prioritized(item, |queued| queued.priority)
1232    }
1233
1234    fn push_tx(&mut self, item: RelayTxItem) -> TelemetryResult<()> {
1235        #[cfg(feature = "discovery")]
1236        {
1237            // Preserve the transfer identity of an unchanged queued catalog.
1238            if let RelayItem::Packet(incoming) = &item.data
1239                && self.tx_queue.iter().any(|queued| {
1240                    queued.dst == item.dst && matches!(&queued.data,
1241                        RelayItem::Packet(existing) if discovery::same_schema_snapshot(existing, incoming))
1242                })
1243            {
1244                return Ok(());
1245            }
1246            let incoming = match &item.data {
1247                RelayItem::Packet(pkt) if discovery::is_discovery_type(pkt.data_type()) => {
1248                    Some((pkt.data_type(), pkt.sender().to_owned()))
1249                }
1250                RelayItem::Packet(_) | RelayItem::Packed(_) => None,
1251            };
1252            if let Some((incoming_ty, incoming_sender)) = incoming {
1253                self.tx_queue.retain(|queued| {
1254                    let same_snapshot = match &queued.data {
1255                        RelayItem::Packet(pkt) => {
1256                            pkt.data_type() == incoming_ty && pkt.sender() == incoming_sender
1257                        }
1258                        RelayItem::Packed(_) => false,
1259                    };
1260                    queued.dst != item.dst || !same_snapshot
1261                });
1262            }
1263        }
1264        self.make_shared_queue_room(item.byte_cost(), RelayQueueKind::Tx)?;
1265        self.tx_queue
1266            .push_back_prioritized(item, |queued| queued.priority)
1267    }
1268
1269    fn push_replay(&mut self, item: RelayReplayItem) -> TelemetryResult<()> {
1270        self.make_shared_queue_room(item.byte_cost(), RelayQueueKind::Replay)?;
1271        self.replay_queue
1272            .push_back_prioritized(item, |queued| queued.priority)
1273    }
1274
1275    fn push_recent_rx(&mut self, id: u64) -> TelemetryResult<()> {
1276        while self.recent_rx.len() >= self.memory.max_recent_rx_ids {
1277            let _ = self.recent_rx.pop_front();
1278        }
1279        self.make_shared_queue_room(0, RelayQueueKind::Recent)?;
1280        self.recent_rx.push_back(id)
1281    }
1282
1283    #[allow(unused_mut)]
1284    fn buffer_reliable_rx(
1285        &mut self,
1286        side: RelaySideId,
1287        ty: crate::DataType,
1288        seq: u32,
1289        mut bytes: crate::SharedBytes,
1290    ) -> TelemetryResult<()> {
1291        let key = Relay::reliable_key(side, ty);
1292        if self
1293            .reliable_rx
1294            .get(&key)
1295            .is_some_and(|state| state.buffered.contains_key(&seq))
1296        {
1297            return Ok(());
1298        }
1299        let cost = size_of::<crate::SharedBytes>() + bytes.len();
1300        self.make_shared_queue_room(cost, RelayQueueKind::ReliableRxBuffer)?;
1301        #[cfg(feature = "compact-packet-store")]
1302        bytes.park_for_queue()?;
1303        let rx_state = self
1304            .reliable_rx
1305            .entry(key)
1306            .or_insert_with(|| ReliableRxState {
1307                expected_seq: 1,
1308                buffered: BTreeMap::new(),
1309            });
1310        if rx_state.buffered.len() >= runtime_reliable_max_pending() {
1311            let _ = rx_state.buffered.pop_first();
1312        }
1313        rx_state.buffered.insert(seq, bytes);
1314        Ok(())
1315    }
1316}
1317
1318/// Relay that fans out packets from one side to all others.
1319/// - Supports both packed bytes and full Packet.
1320/// - Has RX & TX queues, like Router.
1321/// - Uses a Clock for the *_with_timeout APIs, same style as Router.
1322pub struct Relay {
1323    sender: RouterMutex<Arc<str>>,
1324    state: RouterMutex<RelayInner>,
1325    side_tx_gate: ReentryGate,
1326    clock: Box<dyn Clock + Send + Sync>,
1327}
1328
1329enum RemoteSidePlan {
1330    Target(Vec<RelaySideId>),
1331}
1332
1333impl Relay {
1334    const END_TO_END_ACK_SENDER: &'static str = "E2EACK";
1335    const END_TO_END_ACK_PREFIX: &'static str = "E2EACK:";
1336
1337    fn relay_item_priority(data: &RelayItem) -> TelemetryResult<u8> {
1338        let ty = match data {
1339            RelayItem::Packet(pkt) => pkt.data_type(),
1340            RelayItem::Packed(bytes) => wire_format::peek_routing_envelope(bytes.as_ref())?.ty,
1341        };
1342        Ok(crate::scheduler_priority(ty))
1343    }
1344
1345    #[inline]
1346    fn is_side_tx_busy(err: &TelemetryError) -> bool {
1347        matches!(err, TelemetryError::Io("side tx busy"))
1348    }
1349
1350    fn process_replay_queue_item(&self) -> TelemetryResult<bool> {
1351        let Some(mut item) = ({
1352            let mut st = self.state.lock();
1353            let ready = st
1354                .replay_queue
1355                .iter()
1356                .position(|item| item.retry_after_ms <= self.clock.now_ms());
1357            ready.and_then(|index| st.replay_queue.remove_pos(index))
1358        }) else {
1359            return Ok(false);
1360        };
1361        let frame = wire_format::peek_frame_info(item.bytes.as_ref())?;
1362        let ty = frame.envelope.ty;
1363        let Some(hdr) = frame.reliable else {
1364            return Ok(false);
1365        };
1366        {
1367            let mut st = self.state.lock();
1368            let tx_state = self.reliable_tx_state_mut(&mut st, item.dst, ty);
1369            if !tx_state.sent.contains_key(&hdr.seq) {
1370                return Ok(false);
1371            }
1372        }
1373        if let Err(e) = self.send_reliable_raw_to_side(item.dst, item.bytes.clone()) {
1374            if Self::is_side_tx_busy(&e) {
1375                let mut st = self.state.lock();
1376                item.retry_after_ms = self.clock.now_ms().saturating_add(1);
1377                st.push_replay(item)?;
1378                return Ok(true);
1379            }
1380            return Err(e);
1381        }
1382        let mut st = self.state.lock();
1383        let tx_state = self.reliable_tx_state_mut(&mut st, item.dst, ty);
1384        if let Some(sent) = tx_state.sent.get_mut(&hdr.seq) {
1385            sent.last_send_ms = self.clock.now_ms();
1386            sent.queued = false;
1387        }
1388        Ok(true)
1389    }
1390
1391    fn pop_ready_tx_item(
1392        &self,
1393    ) -> Option<(
1394        Option<RelaySideId>,
1395        RelaySideId,
1396        RelayTxHandlerFn,
1397        RelaySideOptions,
1398        RelayItem,
1399    )> {
1400        let mut st = self.state.lock();
1401        let mut priority_burst = st.tx_priority_burst;
1402        let item = st.tx_queue.pop_front_fair_if(
1403            &mut priority_burst,
1404            MAX_PRIORITY_BURST,
1405            |queued| queued.priority,
1406            |queued| queued.retry_after_ms <= self.clock.now_ms(),
1407        );
1408        st.tx_priority_burst = priority_burst;
1409        if let Some(item) = item {
1410            let side = st.sides.get(item.dst).and_then(|side| side.clone());
1411            side.map(|s| (item.src, item.dst, s.tx_handler, s.opts, item.data))
1412        } else {
1413            None
1414        }
1415    }
1416
1417    fn send_tx_item(
1418        &self,
1419        src: Option<RelaySideId>,
1420        dst: RelaySideId,
1421        handler: RelayTxHandlerFn,
1422        opts: RelaySideOptions,
1423        data: RelayItem,
1424    ) -> TelemetryResult<bool> {
1425        let allowed = {
1426            let mut st = self.state.lock();
1427            let ty = match &data {
1428                RelayItem::Packet(pkt) => Some(pkt.data_type()),
1429                RelayItem::Packed(bytes) => {
1430                    Some(wire_format::peek_routing_envelope(bytes.as_ref())?.ty)
1431                }
1432            };
1433            let route_allowed = self.route_allowed_locked(&st, src, ty, dst);
1434            #[cfg(all(feature = "discovery", feature = "timesync"))]
1435            let timesync_allowed = ty
1436                .map(|ty| {
1437                    Self::timesync_allowed_for_side_locked(&mut st, dst, ty, self.clock.now_ms())
1438                })
1439                .unwrap_or(true);
1440            #[cfg(not(all(feature = "discovery", feature = "timesync")))]
1441            let timesync_allowed = true;
1442            route_allowed && timesync_allowed
1443        };
1444        if !allowed {
1445            return Ok(false);
1446        }
1447        if opts.reliable_enabled && matches!(handler, RelayTxHandlerFn::Packed(_)) {
1448            self.send_reliable_to_side(dst, data)?;
1449            Ok(true)
1450        } else if let Some(adjusted) = self.adjust_reliable_for_side(opts, data)? {
1451            self.call_tx_handler(dst, &handler, &adjusted)?;
1452            Ok(true)
1453        } else {
1454            Ok(false)
1455        }
1456    }
1457
1458    /// Create a new relay with the given clock.
1459    pub fn new(clock: Box<dyn Clock + Send + Sync>) -> Self {
1460        Self::new_with_config(RelayConfig::default(), clock)
1461    }
1462
1463    /// Create a new relay with explicit runtime configuration and clock.
1464    pub fn new_with_config(cfg: RelayConfig, clock: Box<dyn Clock + Send + Sync>) -> Self {
1465        let memory = cfg.memory_config();
1466        Self {
1467            sender: RouterMutex::new(cfg.sender()),
1468            state: RouterMutex::new(RelayInner {
1469                memory,
1470                sides: Vec::new(),
1471                route_overrides: BTreeMap::new(),
1472                typed_route_overrides: BTreeMap::new(),
1473                route_weights: BTreeMap::new(),
1474                route_priorities: BTreeMap::new(),
1475                source_route_modes: BTreeMap::new(),
1476                route_selection_cursors: BTreeMap::new(),
1477                adaptive_route_stats: BTreeMap::new(),
1478                side_runtime_stats: BTreeMap::new(),
1479                side_transport: BTreeMap::new(),
1480                rx_queue: BoundedDeque::new(
1481                    memory.max_queue_budget,
1482                    memory.starting_queue_size,
1483                    memory.queue_grow_step,
1484                ),
1485                tx_queue: BoundedDeque::new(
1486                    memory.max_queue_budget,
1487                    memory.starting_queue_size,
1488                    memory.queue_grow_step,
1489                ),
1490                tx_priority_burst: 0,
1491                replay_queue: BoundedDeque::new(
1492                    memory.max_queue_budget,
1493                    memory.starting_queue_size,
1494                    memory.queue_grow_step,
1495                ),
1496                recent_rx: BoundedDeque::new(
1497                    memory.recent_rx_queue_bytes(),
1498                    memory.recent_rx_queue_bytes(),
1499                    memory.queue_grow_step,
1500                ),
1501                reliable_tx: BTreeMap::new(),
1502                reliable_rx: BTreeMap::new(),
1503                reliable_return_routes: BTreeMap::new(),
1504                reliable_return_route_order: VecDeque::new(),
1505                end_to_end_acked_destinations: BTreeMap::new(),
1506                end_to_end_acked_destination_order: VecDeque::new(),
1507                total_handler_failures: 0,
1508                total_handler_retries: 0,
1509                #[cfg(feature = "discovery")]
1510                discovery_routes: BTreeMap::new(),
1511                #[cfg(feature = "discovery")]
1512                discovery_cadence: DiscoveryCadenceState::default(),
1513                #[cfg(feature = "discovery")]
1514                discovery_side_throttle: BTreeMap::new(),
1515                #[cfg(all(feature = "discovery", feature = "timesync"))]
1516                timesync_side_throttle: BTreeMap::new(),
1517            }),
1518            side_tx_gate: ReentryGate::new(),
1519            clock,
1520        }
1521    }
1522
1523    #[inline]
1524    fn sender_arc(&self) -> Arc<str> {
1525        self.sender.lock().clone()
1526    }
1527
1528    #[inline]
1529    pub fn sender(&self) -> Arc<str> {
1530        self.sender_arc()
1531    }
1532
1533    pub fn set_sender<S: AsRef<str>>(&self, sender: S) {
1534        *self.sender.lock() = Arc::from(sender.as_ref());
1535    }
1536
1537    #[inline]
1538    fn try_enter_side_tx(&self) -> Option<ReentryGuard<'_>> {
1539        self.side_tx_gate.try_enter()
1540    }
1541
1542    #[inline]
1543    fn side_tx_active(&self) -> bool {
1544        self.side_tx_gate.is_active()
1545    }
1546
1547    #[inline]
1548    fn side_ref(st: &RelayInner, side: RelaySideId) -> TelemetryResult<&RelaySide> {
1549        st.sides
1550            .get(side)
1551            .and_then(|side| side.as_ref())
1552            .ok_or(TelemetryError::HandlerError("relay: invalid side id"))
1553    }
1554
1555    fn note_side_tx_success(
1556        &self,
1557        side: RelaySideId,
1558        ty: crate::DataType,
1559        bytes: usize,
1560        attempts: usize,
1561    ) {
1562        let mut st = self.state.lock();
1563        #[cfg(feature = "discovery")]
1564        if ty == crate::DataType::DiscoveryAnnounce
1565            && let Some(throttle) = st.discovery_side_throttle.get_mut(&side)
1566        {
1567            throttle.last_liveness_tx_ms = self.clock.now_ms();
1568        }
1569        let entry = st.side_runtime_stats.entry(side).or_default();
1570        entry.note_tx(ty, bytes, attempts.saturating_sub(1));
1571    }
1572
1573    fn note_side_tx_failure(&self, side: RelaySideId, ty: crate::DataType, attempts: usize) {
1574        let mut st = self.state.lock();
1575        st.total_handler_failures = st.total_handler_failures.saturating_add(1);
1576        st.total_handler_retries = st.total_handler_retries.saturating_add(attempts as u64);
1577        let entry = st.side_runtime_stats.entry(side).or_default();
1578        entry.note_tx_failure(ty, attempts);
1579    }
1580
1581    fn note_side_rx(&self, side: RelaySideId, ty: crate::DataType, bytes: usize) {
1582        let mut st = self.state.lock();
1583        let entry = st.side_runtime_stats.entry(side).or_default();
1584        entry.note_rx(ty, bytes);
1585    }
1586
1587    #[inline]
1588    fn ensure_side_ingress_enabled(&self, side: RelaySideId) -> TelemetryResult<()> {
1589        let st = self.state.lock();
1590        let side_ref = Self::side_ref(&st, side)?;
1591        if side_ref.opts.ingress_enabled {
1592            Ok(())
1593        } else {
1594            Err(TelemetryError::HandlerError(
1595                "relay: ingress disabled for side id",
1596            ))
1597        }
1598    }
1599
1600    #[inline]
1601    fn route_allowed_locked(
1602        &self,
1603        st: &RelayInner,
1604        src: Option<RelaySideId>,
1605        ty: Option<crate::DataType>,
1606        dst: RelaySideId,
1607    ) -> bool {
1608        let Ok(dst_side) = Self::side_ref(st, dst) else {
1609            return false;
1610        };
1611        if !dst_side.opts.egress_enabled {
1612            return false;
1613        }
1614        if let Some(src_id) = src {
1615            let Ok(src_side) = Self::side_ref(st, src_id) else {
1616                return false;
1617            };
1618            if !src_side.opts.ingress_enabled || src_id == dst {
1619                return false;
1620            }
1621        }
1622        let base_allowed = st.route_overrides.get(&(src, dst)).copied().unwrap_or(true);
1623        if !base_allowed {
1624            return false;
1625        }
1626
1627        let Some(ty) = ty else {
1628            return true;
1629        };
1630        if st
1631            .typed_route_overrides
1632            .keys()
1633            .any(|(typed_src, typed_ty, _)| *typed_src == src && *typed_ty == ty.as_u32())
1634        {
1635            return st
1636                .typed_route_overrides
1637                .get(&(src, ty.as_u32(), dst))
1638                .copied()
1639                .unwrap_or(false);
1640        }
1641        true
1642    }
1643
1644    fn has_typed_route_overrides_locked(
1645        st: &RelayInner,
1646        src: Option<RelaySideId>,
1647        ty: crate::DataType,
1648    ) -> bool {
1649        st.typed_route_overrides
1650            .keys()
1651            .any(|(typed_src, typed_ty, _)| *typed_src == src && *typed_ty == ty.as_u32())
1652    }
1653
1654    fn eligible_side_ids_locked(
1655        &self,
1656        st: &RelayInner,
1657        src: Option<RelaySideId>,
1658        ty: Option<crate::DataType>,
1659        restrict_link_local: bool,
1660    ) -> Vec<RelaySideId> {
1661        st.sides
1662            .iter()
1663            .enumerate()
1664            .filter_map(|(side_id, side)| {
1665                let side = side.as_ref()?;
1666                if restrict_link_local && !side.opts.link_local_enabled {
1667                    return None;
1668                }
1669                if self.route_allowed_locked(st, src, ty, side_id) {
1670                    Some(side_id)
1671                } else {
1672                    None
1673                }
1674            })
1675            .collect()
1676    }
1677
1678    fn apply_route_selection_locked(
1679        &self,
1680        st: &mut RelayInner,
1681        src: Option<RelaySideId>,
1682        mut sides: Vec<RelaySideId>,
1683        origin: RouteSelectionOrigin,
1684    ) -> Vec<RelaySideId> {
1685        if sides.len() <= 1 {
1686            return sides;
1687        }
1688
1689        let selection_mode = st.source_route_modes.get(&src).copied();
1690        if selection_mode.is_none() && origin == RouteSelectionOrigin::Discovered {
1691            return self.apply_adaptive_discovery_selection_locked(st, src, sides);
1692        }
1693
1694        match selection_mode.unwrap_or(RouteSelectionMode::Fanout) {
1695            RouteSelectionMode::Fanout => sides,
1696            RouteSelectionMode::Weighted => {
1697                crate::small_sort::sort(&mut sides);
1698                let total_weight = sides.iter().fold(0_u64, |acc, side| {
1699                    acc + u64::from(st.route_weights.get(&(src, *side)).copied().unwrap_or(1))
1700                });
1701                if total_weight == 0 {
1702                    return Vec::new();
1703                }
1704                let cursor = st.route_selection_cursors.entry(src).or_insert(0);
1705                let pick = *cursor % total_weight;
1706                *cursor = cursor.wrapping_add(1);
1707                let mut remaining = pick;
1708                for side in sides {
1709                    let weight =
1710                        u64::from(st.route_weights.get(&(src, side)).copied().unwrap_or(1));
1711                    if remaining < weight {
1712                        return vec![side];
1713                    }
1714                    remaining -= weight;
1715                }
1716                Vec::new()
1717            }
1718            RouteSelectionMode::Failover => {
1719                sides.sort_by_key(|side| {
1720                    (
1721                        st.route_priorities.get(&(src, *side)).copied().unwrap_or(0),
1722                        *side,
1723                    )
1724                });
1725                sides.truncate(1);
1726                sides
1727            }
1728        }
1729    }
1730
1731    fn apply_adaptive_discovery_selection_locked(
1732        &self,
1733        st: &mut RelayInner,
1734        src: Option<RelaySideId>,
1735        mut sides: Vec<RelaySideId>,
1736    ) -> Vec<RelaySideId> {
1737        crate::small_sort::sort(&mut sides);
1738        let mut unmeasured: Vec<_> = sides
1739            .iter()
1740            .copied()
1741            .filter(|side| !st.adaptive_route_stats.contains_key(side))
1742            .collect();
1743        if !unmeasured.is_empty() {
1744            let cursor = st.route_selection_cursors.entry(src).or_insert(0);
1745            let pick = (*cursor as usize) % unmeasured.len();
1746            *cursor = cursor.wrapping_add(1);
1747            return vec![unmeasured.swap_remove(pick)];
1748        }
1749
1750        let now_ms = self.clock.now_ms();
1751        let total_weight = sides.iter().fold(0_u64, |acc, side| {
1752            acc + st
1753                .adaptive_route_stats
1754                .get(side)
1755                .map(|stats| stats.weight(now_ms))
1756                .unwrap_or(1)
1757        });
1758        if total_weight == 0 {
1759            sides.truncate(1);
1760            return sides;
1761        }
1762
1763        let cursor = st.route_selection_cursors.entry(src).or_insert(0);
1764        let pick = *cursor % total_weight;
1765        *cursor = cursor.wrapping_add(1);
1766        let mut remaining = pick;
1767        for side in sides {
1768            let weight = st
1769                .adaptive_route_stats
1770                .get(&side)
1771                .map(|stats| stats.weight(now_ms))
1772                .unwrap_or(1);
1773            if remaining < weight {
1774                return vec![side];
1775            }
1776            remaining -= weight;
1777        }
1778        Vec::new()
1779    }
1780
1781    /// Prefer the discovery side with the shortest advertised topology path
1782    /// to the packet's destination. This prevents a route reflected through
1783    /// another router from competing equally with the direct route.
1784    #[cfg(feature = "discovery")]
1785    fn retain_shortest_discovery_routes_locked(
1786        st: &RelayInner,
1787        sides: &mut Vec<RelaySideId>,
1788        endpoints: &[crate::DataEndpoint],
1789        now_ms: u64,
1790    ) {
1791        if sides.len() <= 1 || endpoints.is_empty() {
1792            return;
1793        }
1794
1795        let route_distance = |side: RelaySideId| -> Option<usize> {
1796            let route = st.discovery_routes.get(&side)?;
1797            route
1798                .announcers
1799                .iter()
1800                .filter(|(_, state)| {
1801                    now_ms.saturating_sub(state.last_seen_ms) <= DISCOVERY_ROUTE_TTL_MS
1802                })
1803                .filter_map(|(announcer, state)| {
1804                    let targets: BTreeSet<&str> = state
1805                        .topology_boards
1806                        .iter()
1807                        .filter(|board| {
1808                            endpoints
1809                                .iter()
1810                                .any(|endpoint| board.reachable_endpoints.contains(endpoint))
1811                        })
1812                        .map(|board| board.sender_id.as_str())
1813                        .collect();
1814                    if targets.is_empty() {
1815                        return state
1816                            .reachable
1817                            .iter()
1818                            .any(|endpoint| endpoints.contains(endpoint))
1819                            .then_some(usize::MAX / 2);
1820                    }
1821
1822                    let mut distances: BTreeMap<&str, usize> = BTreeMap::new();
1823                    let mut pending = VecDeque::from([(announcer.as_str(), 0usize)]);
1824                    while let Some((sender, distance)) = pending.pop_front() {
1825                        if distances.contains_key(sender) {
1826                            continue;
1827                        }
1828                        distances.insert(sender, distance);
1829                        if targets.contains(sender) {
1830                            return Some(distance);
1831                        }
1832                        for board in &state.topology_boards {
1833                            if board.sender_id == sender {
1834                                for peer in &board.connections {
1835                                    if !distances.contains_key(peer.as_str()) {
1836                                        pending.push_back((peer.as_str(), distance + 1));
1837                                    }
1838                                }
1839                            } else if board.connections.iter().any(|peer| peer == sender)
1840                                && !distances.contains_key(board.sender_id.as_str())
1841                            {
1842                                pending.push_back((board.sender_id.as_str(), distance + 1));
1843                            }
1844                        }
1845                    }
1846                    None
1847                })
1848                .min()
1849        };
1850
1851        let distances: Vec<(RelaySideId, usize)> = sides
1852            .iter()
1853            .filter_map(|side| route_distance(*side).map(|distance| (*side, distance)))
1854            .collect();
1855        let Some(best) = distances.iter().map(|(_, distance)| *distance).min() else {
1856            return;
1857        };
1858        sides.retain(|side| {
1859            distances
1860                .iter()
1861                .any(|(candidate, distance)| candidate == side && *distance == best)
1862        });
1863    }
1864
1865    fn record_side_tx_sample(
1866        &self,
1867        side: RelaySideId,
1868        bytes: usize,
1869        started_ms: u64,
1870        ended_ms: u64,
1871    ) {
1872        let sample_ms = ended_ms.saturating_sub(started_ms).max(1);
1873        let sample_bps = ((bytes as u128).saturating_mul(1000) / u128::from(sample_ms))
1874            .min(u128::from(u64::MAX)) as u64;
1875        let mut st = self.state.lock();
1876        st.adaptive_route_stats
1877            .entry(side)
1878            .or_default()
1879            .observe(bytes, sample_bps, ended_ms);
1880    }
1881
1882    /// Seed adaptive route selection with a transport-measured link probe.
1883    ///
1884    /// Call this after a side-specific bring-up probe, or whenever the transport already knows the
1885    /// duration for a frame. The relay does not emit synthetic probe frames by itself.
1886    pub fn note_side_link_probe_sample(
1887        &self,
1888        side: RelaySideId,
1889        bytes: usize,
1890        duration_ms: u64,
1891    ) -> TelemetryResult<()> {
1892        {
1893            let st = self.state.lock();
1894            let _ = Self::side_ref(&st, side).map_err(|_| TelemetryError::BadArg)?;
1895        }
1896        let ended_ms = self.clock.now_ms();
1897        self.record_side_tx_sample(side, bytes, ended_ms.saturating_sub(duration_ms), ended_ms);
1898        Ok(())
1899    }
1900
1901    fn relay_item_wire_len(data: &RelayItem) -> TelemetryResult<usize> {
1902        match data {
1903            RelayItem::Packet(pkt) => Ok(wire_format::packet_wire_size(pkt)),
1904            RelayItem::Packed(bytes) => Ok(bytes.len()),
1905        }
1906    }
1907
1908    #[inline]
1909    fn decode_end_to_end_reliable_ack(payload: &[u8]) -> TelemetryResult<u64> {
1910        if payload.len() != 8 {
1911            return Err(TelemetryError::Unpack("bad reliable e2e ack payload"));
1912        }
1913        Ok(u64::from_le_bytes(payload[0..8].try_into().unwrap()))
1914    }
1915
1916    #[inline]
1917    fn is_end_to_end_ack_sender(sender: &str) -> bool {
1918        sender == Self::END_TO_END_ACK_SENDER || sender.starts_with(Self::END_TO_END_ACK_PREFIX)
1919    }
1920
1921    #[inline]
1922    fn sender_hash(sender: &str) -> u64 {
1923        if let Some(address) = sender
1924            .strip_prefix("@addr:")
1925            .and_then(|value| value.parse::<u32>().ok())
1926        {
1927            return u64::from(address);
1928        }
1929        hash_bytes_u64(0x517C_C1B7_2722_0A95, sender.as_bytes())
1930    }
1931
1932    #[cfg(feature = "discovery")]
1933    fn canonical_sender_locked(st: &RelayInner, sender: &str) -> String {
1934        let Some(address) = sender
1935            .strip_prefix("@addr:")
1936            .and_then(|value| value.parse::<u32>().ok())
1937        else {
1938            return sender.to_string();
1939        };
1940        Self::hostname_for_wire_address_locked(st, address).unwrap_or_else(|| sender.to_string())
1941    }
1942
1943    #[cfg(feature = "discovery")]
1944    fn hostname_for_wire_address_locked(st: &RelayInner, address: u32) -> Option<String> {
1945        let mut found: Option<String> = None;
1946        let mut collision = false;
1947        let mut consider = |candidate: &str| {
1948            if sender_address_u32(candidate) != address {
1949                return;
1950            }
1951            match found.as_deref() {
1952                None => found = Some(candidate.to_string()),
1953                Some(existing) if existing == candidate => {}
1954                Some(_) => collision = true,
1955            }
1956        };
1957        for route in st.discovery_routes.values() {
1958            for (announcer, state) in &route.announcers {
1959                consider(announcer);
1960                for board in &state.topology_boards {
1961                    consider(&board.sender_id);
1962                }
1963            }
1964        }
1965        if collision { None } else { found }
1966    }
1967
1968    fn decode_end_to_end_ack_sender_hash(sender: &str) -> Option<u64> {
1969        sender
1970            .strip_prefix(Self::END_TO_END_ACK_PREFIX)
1971            .filter(|sender| !sender.is_empty())
1972            .map(Self::sender_hash)
1973    }
1974
1975    fn end_to_end_ack_sender_hash(pkt: &Packet) -> Option<u64> {
1976        if pkt.data_type() != crate::DataType::ReliableAck {
1977            return None;
1978        }
1979        pkt.wire_target_senders()
1980            .first()
1981            .copied()
1982            .or_else(|| Self::decode_end_to_end_ack_sender_hash(pkt.sender()))
1983    }
1984
1985    #[cfg(feature = "discovery")]
1986    fn is_end_to_end_destination_sender(&self, sender: &str) -> bool {
1987        sender != self.sender_arc().as_ref() && !Self::is_end_to_end_ack_sender(sender)
1988    }
1989
1990    /// Extract the logical packet ID targeted by an end-to-end reliable ACK item.
1991    ///
1992    /// Relay queues can hold either decoded packets or packed frames. This
1993    /// helper normalizes both forms so relay ACK-routing logic can treat them
1994    /// uniformly.
1995    ///
1996    /// Only relay-visible end-to-end `ReliableAck` packets qualify here.
1997    /// Unrelated traffic returns `Ok(None)`.
1998    fn reliable_control_target_packet_id(data: &RelayItem) -> TelemetryResult<Option<u64>> {
1999        match data {
2000            RelayItem::Packet(pkt) => {
2001                if Self::end_to_end_ack_sender_hash(pkt).is_none() {
2002                    return Ok(None);
2003                }
2004                Self::decode_end_to_end_reliable_ack(pkt.payload()).map(Some)
2005            }
2006            RelayItem::Packed(bytes) => {
2007                if wire_format::peek_routing_frame_info(bytes.as_ref())
2008                    .ok()
2009                    .is_some_and(|frame| {
2010                        frame.envelope.ty != crate::DataType::ReliableAck || frame.ack_only()
2011                    })
2012                {
2013                    return Ok(None);
2014                }
2015                let pkt = wire_format::unpack_packet(bytes.as_ref())?;
2016                if Self::end_to_end_ack_sender_hash(&pkt).is_none() {
2017                    return Ok(None);
2018                }
2019                Self::decode_end_to_end_reliable_ack(pkt.payload()).map(Some)
2020            }
2021        }
2022    }
2023
2024    fn note_reliable_return_route(&self, side: RelaySideId, packet_id: u64) {
2025        let mut st = self.state.lock();
2026        // A retry can remain in RX dedupe after its smaller return-path cache
2027        // entry was evicted. Relearning that path must not restart the replay
2028        // delay on every retry, or sustained cache churn suppresses it forever.
2029        let last_replay_ms = if st.recent_rx.contains(&packet_id) {
2030            self.clock
2031                .now_ms()
2032                .saturating_sub(runtime_reliable_retransmit_ms())
2033        } else {
2034            self.clock.now_ms()
2035        };
2036        Self::remember_reliable_return_route_locked(&mut st, packet_id);
2037        st.reliable_return_routes
2038            .entry(packet_id)
2039            .and_modify(|route| route.side = side)
2040            .or_insert(ReliableReturnRouteState {
2041                side,
2042                last_replay_ms,
2043            });
2044    }
2045
2046    /// Refresh or insert `packet_id` in the bounded reliable return-route cache.
2047    ///
2048    /// The relay uses this cache to route end-to-end acknowledgements back
2049    /// toward the source side that most recently forwarded the corresponding
2050    /// reliable data packet.
2051    fn remember_reliable_return_route_locked(st: &mut RelayInner, packet_id: u64) {
2052        let cap = runtime_reliable_max_return_routes().max(1);
2053        st.reliable_return_route_order
2054            .retain(|id| st.reliable_return_routes.contains_key(id) && *id != packet_id);
2055        while st.reliable_return_route_order.len() >= cap {
2056            if let Some(oldest) = st.reliable_return_route_order.pop_front() {
2057                st.reliable_return_routes.remove(&oldest);
2058            } else {
2059                break;
2060            }
2061        }
2062        st.reliable_return_route_order.push_back(packet_id);
2063    }
2064
2065    fn note_end_to_end_acked_destination_locked(
2066        st: &mut RelayInner,
2067        packet_id: u64,
2068        sender_hash: u64,
2069    ) {
2070        let entry_cap = runtime_reliable_max_end_to_end_ack_cache().max(1);
2071        st.end_to_end_acked_destination_order
2072            .retain(|id| st.end_to_end_acked_destinations.contains_key(id) && *id != packet_id);
2073        while st.end_to_end_acked_destination_order.len() >= entry_cap {
2074            if let Some(oldest) = st.end_to_end_acked_destination_order.pop_front() {
2075                st.end_to_end_acked_destinations.remove(&oldest);
2076            } else {
2077                break;
2078            }
2079        }
2080        st.end_to_end_acked_destination_order.push_back(packet_id);
2081
2082        let acked = st
2083            .end_to_end_acked_destinations
2084            .entry(packet_id)
2085            .or_default();
2086        let sender_cap = runtime_reliable_max_end_to_end_pending().max(1);
2087        if acked.len() < sender_cap || acked.contains(&sender_hash) {
2088            acked.insert(sender_hash);
2089        }
2090    }
2091
2092    #[inline]
2093    fn reliable_key(side: RelaySideId, ty: crate::DataType) -> (RelaySideId, u32) {
2094        (side, ty.as_u32())
2095    }
2096
2097    fn reliable_tx_state_mut<'a>(
2098        &'a self,
2099        st: &'a mut RelayInner,
2100        side: RelaySideId,
2101        ty: crate::DataType,
2102    ) -> &'a mut ReliableTxState {
2103        let key = Self::reliable_key(side, ty);
2104        st.reliable_tx
2105            .entry(key)
2106            .or_insert_with(|| ReliableTxState {
2107                next_seq: 1,
2108                sent_order: VecDeque::new(),
2109                sent: BTreeMap::new(),
2110            })
2111    }
2112
2113    fn reliable_rx_state_mut<'a>(
2114        &'a self,
2115        st: &'a mut RelayInner,
2116        side: RelaySideId,
2117        ty: crate::DataType,
2118    ) -> &'a mut ReliableRxState {
2119        let key = Self::reliable_key(side, ty);
2120        st.reliable_rx
2121            .entry(key)
2122            .or_insert_with(|| ReliableRxState {
2123                expected_seq: 1,
2124                buffered: BTreeMap::new(),
2125            })
2126    }
2127
2128    fn handle_reliable_ack(&self, side: RelaySideId, ty: crate::DataType, ack: u32) {
2129        let mut st = self.state.lock();
2130        let tx_state = self.reliable_tx_state_mut(&mut st, side, ty);
2131        if matches!(reliable_mode(ty), crate::ReliableMode::Unordered) {
2132            tx_state.sent.remove(&ack);
2133            tx_state.sent_order.retain(|seq| *seq != ack);
2134            return;
2135        }
2136
2137        while let Some(seq) = tx_state.sent_order.front().copied() {
2138            if seq > ack {
2139                break;
2140            }
2141            tx_state.sent_order.pop_front();
2142            tx_state.sent.remove(&seq);
2143        }
2144    }
2145
2146    fn handle_reliable_partial_ack(&self, side: RelaySideId, ty: crate::DataType, seq: u32) {
2147        let mut st = self.state.lock();
2148        let tx_state = self.reliable_tx_state_mut(&mut st, side, ty);
2149        if let Some(sent) = tx_state.sent.get_mut(&seq) {
2150            sent.partial_acked = true;
2151        }
2152    }
2153
2154    fn reliable_control_packet(
2155        &self,
2156        control_ty: crate::DataType,
2157        ty: crate::DataType,
2158        seq: u32,
2159    ) -> TelemetryResult<Packet> {
2160        let sender = self.sender_arc();
2161        Packet::new(
2162            control_ty,
2163            message_meta(control_ty).endpoints_ref(),
2164            sender.as_ref(),
2165            self.clock.now_ms(),
2166            crate::router::encode_slice_le(&[ty.as_u32(), seq]),
2167        )
2168    }
2169
2170    fn queue_reliable_ack(
2171        &self,
2172        side: RelaySideId,
2173        ty: crate::DataType,
2174        seq: u32,
2175    ) -> TelemetryResult<()> {
2176        let pkt = self.reliable_control_packet(crate::DataType::ReliableAck, ty, seq)?;
2177        let data = RelayItem::Packet(Arc::new(pkt));
2178        let priority = Self::relay_item_priority(&data)?;
2179        let mut st = self.state.lock();
2180        st.push_tx(RelayTxItem {
2181            src: None,
2182            dst: side,
2183            data,
2184            priority,
2185            retry_after_ms: 0,
2186        })?;
2187        Ok(())
2188    }
2189
2190    fn queue_reliable_packet_request(
2191        &self,
2192        side: RelaySideId,
2193        ty: crate::DataType,
2194        seq: u32,
2195    ) -> TelemetryResult<()> {
2196        let pkt = self.reliable_control_packet(crate::DataType::ReliablePacketRequest, ty, seq)?;
2197        let data = RelayItem::Packet(Arc::new(pkt));
2198        let priority = Self::relay_item_priority(&data)?;
2199        let mut st = self.state.lock();
2200        st.push_tx(RelayTxItem {
2201            src: None,
2202            dst: side,
2203            data,
2204            priority,
2205            retry_after_ms: 0,
2206        })?;
2207        Ok(())
2208    }
2209
2210    fn queue_reliable_partial_ack(
2211        &self,
2212        side: RelaySideId,
2213        ty: crate::DataType,
2214        seq: u32,
2215    ) -> TelemetryResult<()> {
2216        let pkt = self.reliable_control_packet(crate::DataType::ReliablePartialAck, ty, seq)?;
2217        let data = RelayItem::Packet(Arc::new(pkt));
2218        let priority = Self::relay_item_priority(&data)?;
2219        let mut st = self.state.lock();
2220        st.push_tx(RelayTxItem {
2221            src: None,
2222            dst: side,
2223            data,
2224            priority,
2225            retry_after_ms: 0,
2226        })?;
2227        Ok(())
2228    }
2229
2230    fn queue_reliable_retransmit(
2231        &self,
2232        side: RelaySideId,
2233        ty: crate::DataType,
2234        seq: u32,
2235    ) -> TelemetryResult<()> {
2236        let mut queued = None;
2237        {
2238            let mut st = self.state.lock();
2239            let tx_state = self.reliable_tx_state_mut(&mut st, side, ty);
2240            if let Some(sent) = tx_state.sent.get_mut(&seq)
2241                && !sent.queued
2242            {
2243                sent.queued = true;
2244                sent.partial_acked = false;
2245                queued = Some(sent.bytes.clone());
2246            }
2247        }
2248
2249        if let Some(bytes) = queued {
2250            let mut st = self.state.lock();
2251            st.push_replay(RelayReplayItem {
2252                dst: side,
2253                bytes,
2254                priority: message_priority(ty).saturating_add(16),
2255                retry_after_ms: 0,
2256            })?;
2257        }
2258        Ok(())
2259    }
2260
2261    fn send_reliable_raw_to_side(
2262        &self,
2263        side: RelaySideId,
2264        bytes: crate::SharedBytes,
2265    ) -> TelemetryResult<()> {
2266        let (handler, opts) = {
2267            let st = self.state.lock();
2268            let side_ref = Self::side_ref(&st, side)?;
2269            if !side_ref.opts.egress_enabled {
2270                return Ok(());
2271            }
2272            (side_ref.tx_handler.clone(), side_ref.opts.clone())
2273        };
2274
2275        let Some(_side_tx_guard) = self.try_enter_side_tx() else {
2276            return Err(TelemetryError::Io("side tx busy"));
2277        };
2278        let started_ms = self.clock.now_ms();
2279        let ty = wire_format::peek_routing_envelope(bytes.as_ref())
2280            .map(|env| env.ty)
2281            .unwrap_or(crate::DataType::ReliableAck);
2282        let result = match handler {
2283            RelayTxHandlerFn::Packed(f) => {
2284                let frames = self.encode_side_transport_frames(side, opts, bytes.clone())?;
2285                let mut sent_bytes = 0usize;
2286                self.send_side_frames_resumable(side, frames, |frame| {
2287                    f(frame)?;
2288                    sent_bytes = sent_bytes.saturating_add(frame.len());
2289
2290                    Ok(())
2291                })?;
2292                self.record_side_tx_sample(side, sent_bytes, started_ms, self.clock.now_ms());
2293                self.note_side_tx_success(side, ty, sent_bytes, 1);
2294                return Ok(());
2295            }
2296            RelayTxHandlerFn::Packet(f) => {
2297                if wire_format::peek_frame_info(bytes.as_ref())
2298                    .ok()
2299                    .is_some_and(|frame| frame.ack_only())
2300                {
2301                    return Ok(());
2302                }
2303                let pkt = wire_format::unpack_packet(bytes.as_ref())?;
2304                f(&pkt)
2305            }
2306        };
2307        if result.is_ok() {
2308            self.record_side_tx_sample(side, bytes.len(), started_ms, self.clock.now_ms());
2309            self.note_side_tx_success(side, ty, bytes.len(), 1);
2310        } else {
2311            self.note_side_tx_failure(side, ty, 1);
2312        }
2313        result
2314    }
2315
2316    #[allow(unused_mut)]
2317    fn send_reliable_to_side(&self, side: RelaySideId, data: RelayItem) -> TelemetryResult<()> {
2318        let ty = match &data {
2319            RelayItem::Packet(pkt) => pkt.data_type(),
2320            RelayItem::Packed(bytes) => wire_format::peek_routing_envelope(bytes)?.ty,
2321        };
2322        let end_to_end_ordered = !self.item_target_senders(&data)?.is_empty()
2323            && matches!(reliable_mode(ty), crate::ReliableMode::Ordered);
2324        let (handler, opts, hop_reliable_enabled) = {
2325            let st = self.state.lock();
2326            let side_ref = Self::side_ref(&st, side)?;
2327            let opts = side_ref.opts.clone();
2328            let hop_reliable_enabled = opts.reliable_enabled
2329                && !end_to_end_ordered
2330                && !self.side_has_multiple_announcers_locked(&st, side, self.clock.now_ms());
2331            (side_ref.tx_handler.clone(), opts, hop_reliable_enabled)
2332        };
2333
2334        let RelayTxHandlerFn::Packed(f) = &handler else {
2335            return self.call_tx_handler(side, &handler, &data);
2336        };
2337
2338        if !hop_reliable_enabled {
2339            let mut adjusted_opts = opts;
2340            adjusted_opts.reliable_enabled = false;
2341            if let Some(adjusted) = self.adjust_reliable_for_side(adjusted_opts, data)? {
2342                return self.call_tx_handler(side, &handler, &adjusted);
2343            }
2344            return Ok(());
2345        }
2346
2347        let ty = match &data {
2348            RelayItem::Packet(pkt) => pkt.data_type(),
2349            RelayItem::Packed(bytes) => {
2350                let Ok(frame) = wire_format::peek_frame_info(bytes.as_ref()) else {
2351                    return self.call_tx_handler(side, &handler, &data);
2352                };
2353                frame.envelope.ty
2354            }
2355        };
2356
2357        if !is_reliable_type(ty) {
2358            if let Some(adjusted) = self.adjust_reliable_for_side(opts, data)? {
2359                self.call_tx_handler(side, &handler, &adjusted)?;
2360            }
2361            return Ok(());
2362        }
2363
2364        let (seq, flags) = {
2365            let mut st = self.state.lock();
2366            let tx_state = self.reliable_tx_state_mut(&mut st, side, ty);
2367            if tx_state.sent.len() >= runtime_reliable_max_pending() {
2368                return Err(TelemetryError::PacketTooLarge(
2369                    "relay reliable history full",
2370                ));
2371            }
2372            let seq = tx_state.next_seq;
2373            let next = tx_state.next_seq.wrapping_add(1);
2374            tx_state.next_seq = if next == 0 { 1 } else { next };
2375            let flags = match reliable_mode(ty) {
2376                crate::ReliableMode::Unordered => wire_format::RELIABLE_FLAG_UNORDERED,
2377                _ => 0,
2378            };
2379            (seq, flags)
2380        };
2381
2382        let mut bytes: crate::SharedBytes = match data {
2383            RelayItem::Packet(pkt) => {
2384                crate::shared_bytes::convert(wire_format::pack_packet_with_reliable(
2385                    &pkt,
2386                    wire_format::ReliableHeader { flags, seq, ack: 0 },
2387                ))
2388            }
2389            RelayItem::Packed(bytes) => {
2390                let Some(rewritten) =
2391                    wire_format::rewrite_reliable_header_owned(bytes.as_ref(), flags, seq, 0)?
2392                else {
2393                    let Some(_side_tx_guard) = self.try_enter_side_tx() else {
2394                        return Err(TelemetryError::Io("side tx busy"));
2395                    };
2396                    let started_ms = self.clock.now_ms();
2397                    let frames = self.encode_side_transport_frames(side, opts, bytes.clone())?;
2398                    let mut sent_bytes = 0usize;
2399                    self.send_side_frames_resumable(side, frames, |frame| {
2400                        f(frame)?;
2401                        sent_bytes = sent_bytes.saturating_add(frame.len());
2402
2403                        Ok(())
2404                    })?;
2405                    self.record_side_tx_sample(side, sent_bytes, started_ms, self.clock.now_ms());
2406                    self.note_side_tx_success(side, ty, sent_bytes, 1);
2407                    return Ok(());
2408                };
2409                crate::shared_bytes::convert(rewritten)
2410            }
2411        };
2412
2413        let Some(_side_tx_guard) = self.try_enter_side_tx() else {
2414            return Err(TelemetryError::Io("side tx busy"));
2415        };
2416        let started_ms = self.clock.now_ms();
2417        let frames = self.encode_side_transport_frames(side, opts, bytes.clone())?;
2418        let mut sent_bytes = 0usize;
2419        self.send_side_frames_resumable(side, frames, |frame| {
2420            f(frame)?;
2421            sent_bytes = sent_bytes.saturating_add(frame.len());
2422
2423            Ok(())
2424        })?;
2425        self.record_side_tx_sample(side, sent_bytes, started_ms, self.clock.now_ms());
2426        self.note_side_tx_success(side, ty, sent_bytes, 1);
2427
2428        #[cfg(feature = "compact-packet-store")]
2429        {
2430            let _ = bytes.park_for_queue();
2431        }
2432
2433        {
2434            let mut st = self.state.lock();
2435            let tx_state = self.reliable_tx_state_mut(&mut st, side, ty);
2436            tx_state.sent_order.push_back(seq);
2437            tx_state.sent.insert(
2438                seq,
2439                ReliableSent {
2440                    bytes: bytes.clone(),
2441                    last_send_ms: self.clock.now_ms(),
2442                    retries: 0,
2443                    queued: false,
2444                    partial_acked: false,
2445                },
2446            );
2447        }
2448
2449        Ok(())
2450    }
2451
2452    fn item_route_info(
2453        &self,
2454        data: &RelayItem,
2455    ) -> TelemetryResult<(Vec<crate::DataEndpoint>, crate::DataType)> {
2456        match data {
2457            RelayItem::Packet(pkt) => {
2458                let mut eps = pkt.endpoints().to_vec();
2459                crate::small_sort::sort(&mut eps);
2460                eps.dedup();
2461                Ok((eps, pkt.data_type()))
2462            }
2463            RelayItem::Packed(bytes) => {
2464                let env = wire_format::peek_envelope(bytes.as_ref())?;
2465                let mut eps: Vec<crate::DataEndpoint> = env.endpoints.iter().copied().collect();
2466                crate::small_sort::sort(&mut eps);
2467                eps.dedup();
2468                Ok((eps, env.ty))
2469            }
2470        }
2471    }
2472
2473    fn endpoints_are_link_local_only(eps: &[crate::DataEndpoint]) -> bool {
2474        !eps.is_empty() && eps.iter().all(|ep| ep.is_link_local_only())
2475    }
2476
2477    fn item_target_senders<'a>(
2478        &self,
2479        data: &'a RelayItem,
2480    ) -> TelemetryResult<alloc::borrow::Cow<'a, [u64]>> {
2481        match data {
2482            RelayItem::Packet(pkt) => Ok(alloc::borrow::Cow::Borrowed(pkt.wire_target_senders())),
2483            RelayItem::Packed(bytes) => {
2484                let env = wire_format::peek_routing_envelope(bytes.as_ref())?;
2485                if env.target_senders().len() == 0 {
2486                    Ok(alloc::borrow::Cow::Borrowed(&[]))
2487                } else {
2488                    Ok(alloc::borrow::Cow::Owned(env.target_senders().collect()))
2489                }
2490            }
2491        }
2492    }
2493
2494    #[cfg(feature = "discovery")]
2495    fn has_explicit_route_policy_locked(
2496        st: &RelayInner,
2497        src: Option<RelaySideId>,
2498        ty: crate::DataType,
2499    ) -> bool {
2500        st.route_overrides
2501            .keys()
2502            .any(|(route_src, _)| *route_src == src)
2503            || Self::has_typed_route_overrides_locked(st, src, ty)
2504    }
2505
2506    #[cfg(feature = "discovery")]
2507    fn side_matches_target_senders_locked(
2508        st: &RelayInner,
2509        side: RelaySideId,
2510        target_senders: &[u64],
2511        now_ms: u64,
2512    ) -> bool {
2513        st.discovery_routes
2514            .get(&side)
2515            .map(|route| {
2516                if now_ms.saturating_sub(route.last_seen_ms) > DISCOVERY_ROUTE_TTL_MS {
2517                    return false;
2518                }
2519                route.announcers.iter().any(|(announcer, sender_state)| {
2520                    if now_ms.saturating_sub(sender_state.last_seen_ms) > DISCOVERY_ROUTE_TTL_MS {
2521                        return false;
2522                    }
2523                    target_senders.contains(&Self::sender_hash(announcer))
2524                        || sender_state
2525                            .advertised_address
2526                            .is_some_and(|address| target_senders.contains(&u64::from(address)))
2527                        || sender_state.topology_boards.iter().any(|board| {
2528                            target_senders.contains(&Self::sender_hash(&board.sender_id))
2529                        })
2530                })
2531            })
2532            .unwrap_or(false)
2533    }
2534
2535    fn remote_side_plan(
2536        &self,
2537        data: &RelayItem,
2538        exclude: RelaySideId,
2539    ) -> TelemetryResult<RemoteSidePlan> {
2540        #[cfg(feature = "discovery")]
2541        {
2542            let (eps, ty) = self.item_route_info(data)?;
2543            let target_senders = self.item_target_senders(data)?;
2544            let preferred_packet_id = Self::reliable_control_target_packet_id(data)?;
2545            if discovery::is_discovery_type(ty) {
2546                // Publish an aggregate split-horizon topology instead of
2547                // leaking far-side topology as direct neighbors. Address
2548                // allocation advertisements still cross transparent relays:
2549                // static/requested address conflicts must be resolved globally.
2550                if matches!(
2551                    ty,
2552                    crate::DataType::DiscoveryAnnounce
2553                        | crate::DataType::DiscoveryTimeSyncSources
2554                        | crate::DataType::DiscoveryTopology
2555                        | crate::DataType::DiscoveryLinkCapabilities
2556                ) {
2557                    return Ok(RemoteSidePlan::Target(Vec::new()));
2558                }
2559                let mut st = self.state.lock();
2560                let sides = self.eligible_side_ids_locked(&st, Some(exclude), Some(ty), false);
2561                return Ok(RemoteSidePlan::Target(self.apply_route_selection_locked(
2562                    &mut st,
2563                    Some(exclude),
2564                    sides,
2565                    RouteSelectionOrigin::Flood,
2566                )));
2567            }
2568
2569            #[cfg(feature = "timesync")]
2570            let preferred_timesync_source = self.preferred_timesync_route_source(data, ty)?;
2571            #[cfg(not(feature = "timesync"))]
2572            let preferred_timesync_source: Option<String> = None;
2573            let mut st = self.state.lock();
2574            if let Some(packet_id) = preferred_packet_id {
2575                let target_side = self.allowed_target_side_locked(
2576                    &st,
2577                    exclude,
2578                    ty,
2579                    st.reliable_return_routes
2580                        .get(&packet_id)
2581                        .map(|route| route.side),
2582                );
2583                if let Some(side) = target_side {
2584                    #[cfg(feature = "timesync")]
2585                    if !Self::timesync_allowed_for_side_locked(
2586                        &mut st,
2587                        side,
2588                        ty,
2589                        self.clock.now_ms(),
2590                    ) {
2591                        return Ok(RemoteSidePlan::Target(Vec::new()));
2592                    }
2593                    return Ok(RemoteSidePlan::Target(vec![side]));
2594                }
2595                let eligible = self.eligible_side_ids_locked(&st, Some(exclude), Some(ty), false);
2596                let original = target_senders.get(1..).unwrap_or(&[]);
2597                let exact: Vec<_> = eligible
2598                    .iter()
2599                    .copied()
2600                    .filter(|side| {
2601                        !original.is_empty()
2602                            && Self::side_matches_target_senders_locked(
2603                                &st,
2604                                *side,
2605                                original,
2606                                self.clock.now_ms(),
2607                            )
2608                    })
2609                    .collect();
2610                if !exact.is_empty() {
2611                    return Ok(RemoteSidePlan::Target(self.apply_route_selection_locked(
2612                        &mut st,
2613                        Some(exclude),
2614                        exact,
2615                        RouteSelectionOrigin::Discovered,
2616                    )));
2617                }
2618                return Ok(RemoteSidePlan::Target(if eligible.len() == 1 {
2619                    eligible
2620                } else {
2621                    Vec::new()
2622                }));
2623            }
2624            let restrict_link_local = Self::endpoints_are_link_local_only(&eps);
2625            let discovered_origin = if is_reliable_type(ty) {
2626                RouteSelectionOrigin::Flood
2627            } else {
2628                RouteSelectionOrigin::Discovered
2629            };
2630            if st.discovery_routes.is_empty() {
2631                let mut fallback = self.eligible_side_ids_locked(
2632                    &st,
2633                    Some(exclude),
2634                    Some(ty),
2635                    restrict_link_local,
2636                );
2637                #[cfg(feature = "timesync")]
2638                {
2639                    fallback = Self::filter_timesync_sides_locked(
2640                        &mut st,
2641                        ty,
2642                        self.clock.now_ms(),
2643                        fallback,
2644                    );
2645                }
2646                return Ok(RemoteSidePlan::Target(if fallback.len() == 1 {
2647                    fallback
2648                } else {
2649                    Vec::new()
2650                }));
2651            }
2652            let now_ms = self.clock.now_ms();
2653            let mut had_exact = false;
2654            let mut exact_targets = Vec::new();
2655            let mut had_known = false;
2656            let mut generic_targets = Vec::new();
2657            let mut unique_endpoint_fallbacks = Vec::new();
2658
2659            for (&side, route) in st.discovery_routes.iter() {
2660                if side == exclude
2661                    || now_ms.saturating_sub(route.last_seen_ms) > DISCOVERY_ROUTE_TTL_MS
2662                {
2663                    continue;
2664                }
2665                if restrict_link_local
2666                    && st
2667                        .sides
2668                        .get(side)
2669                        .and_then(|side| side.as_ref())
2670                        .map(|s| !s.opts.link_local_enabled)
2671                        .unwrap_or(true)
2672                {
2673                    continue;
2674                }
2675                if !self.route_allowed_locked(&st, Some(exclude), Some(ty), side) {
2676                    continue;
2677                }
2678                if !target_senders.is_empty() {
2679                    if !Self::side_matches_target_senders_locked(&st, side, &target_senders, now_ms)
2680                    {
2681                        if eps.iter().copied().any(|ep| route.reachable.contains(&ep)) {
2682                            unique_endpoint_fallbacks.push(side);
2683                        }
2684                        continue;
2685                    }
2686                    had_known = true;
2687                    generic_targets.push(side);
2688                    continue;
2689                }
2690                if preferred_timesync_source.as_deref().is_some_and(|source| {
2691                    route.reachable_timesync_sources.iter().any(|s| s == source)
2692                }) {
2693                    had_exact = true;
2694                    exact_targets.push(side);
2695                    continue;
2696                }
2697                if eps.iter().copied().any(|ep| route.reachable.contains(&ep)) {
2698                    had_known = true;
2699                    generic_targets.push(side);
2700                }
2701            }
2702
2703            // Compact/DHCP links can temporarily disagree on whether a frozen
2704            // destination is represented by hostname hash or assigned address.
2705            // Preserve autonomous endpoint routing only when discovery gives
2706            // one unambiguous non-ingress route; never fan an unknown target
2707            // out across multiple sides.
2708            if !target_senders.is_empty() && !had_known && unique_endpoint_fallbacks.len() == 1 {
2709                had_known = true;
2710                generic_targets = unique_endpoint_fallbacks;
2711            }
2712
2713            if had_exact {
2714                #[cfg(feature = "timesync")]
2715                {
2716                    exact_targets = Self::filter_timesync_sides_locked(
2717                        &mut st,
2718                        ty,
2719                        self.clock.now_ms(),
2720                        exact_targets,
2721                    );
2722                }
2723                let targets = self.filter_end_to_end_satisfied_sides_locked(
2724                    &st,
2725                    data,
2726                    exact_targets,
2727                    &eps,
2728                    ty,
2729                )?;
2730                Ok(RemoteSidePlan::Target(self.apply_route_selection_locked(
2731                    &mut st,
2732                    Some(exclude),
2733                    targets,
2734                    discovered_origin,
2735                )))
2736            } else if had_known {
2737                Self::retain_shortest_discovery_routes_locked(
2738                    &st,
2739                    &mut generic_targets,
2740                    &eps,
2741                    now_ms,
2742                );
2743                #[cfg(feature = "timesync")]
2744                {
2745                    generic_targets = Self::filter_timesync_sides_locked(
2746                        &mut st,
2747                        ty,
2748                        self.clock.now_ms(),
2749                        generic_targets,
2750                    );
2751                }
2752                let targets = self.filter_end_to_end_satisfied_sides_locked(
2753                    &st,
2754                    data,
2755                    generic_targets,
2756                    &eps,
2757                    ty,
2758                )?;
2759                Ok(RemoteSidePlan::Target(self.apply_route_selection_locked(
2760                    &mut st,
2761                    Some(exclude),
2762                    targets,
2763                    discovered_origin,
2764                )))
2765            } else {
2766                let fallback = self.eligible_side_ids_locked(
2767                    &st,
2768                    Some(exclude),
2769                    Some(ty),
2770                    restrict_link_local,
2771                );
2772                if fallback.len() == 1 {
2773                    return Ok(RemoteSidePlan::Target(fallback));
2774                }
2775                if Self::has_explicit_route_policy_locked(&st, Some(exclude), ty) {
2776                    let mut sides = self.eligible_side_ids_locked(
2777                        &st,
2778                        Some(exclude),
2779                        Some(ty),
2780                        restrict_link_local,
2781                    );
2782                    #[cfg(feature = "timesync")]
2783                    {
2784                        sides = Self::filter_timesync_sides_locked(
2785                            &mut st,
2786                            ty,
2787                            self.clock.now_ms(),
2788                            sides,
2789                        );
2790                    }
2791                    Ok(RemoteSidePlan::Target(self.apply_route_selection_locked(
2792                        &mut st,
2793                        Some(exclude),
2794                        sides,
2795                        RouteSelectionOrigin::Flood,
2796                    )))
2797                } else {
2798                    Ok(RemoteSidePlan::Target(Vec::new()))
2799                }
2800            }
2801        }
2802        #[cfg(not(feature = "discovery"))]
2803        {
2804            let (_, ty) = self.item_route_info(data)?;
2805            let mut st = self.state.lock();
2806            if let Some(packet_id) = Self::reliable_control_target_packet_id(data)? {
2807                let target_side = self.allowed_target_side_locked(
2808                    &st,
2809                    exclude,
2810                    ty,
2811                    st.reliable_return_routes
2812                        .get(&packet_id)
2813                        .map(|route| route.side),
2814                );
2815                if let Some(side) = target_side {
2816                    return Ok(RemoteSidePlan::Target(vec![side]));
2817                }
2818                let sides = self.eligible_side_ids_locked(&st, Some(exclude), Some(ty), false);
2819                return Ok(RemoteSidePlan::Target(if sides.len() == 1 {
2820                    sides
2821                } else {
2822                    Vec::new()
2823                }));
2824            }
2825            let sides = self.eligible_side_ids_locked(&st, Some(exclude), Some(ty), false);
2826            Ok(RemoteSidePlan::Target(self.apply_route_selection_locked(
2827                &mut st,
2828                Some(exclude),
2829                sides,
2830                RouteSelectionOrigin::Flood,
2831            )))
2832        }
2833    }
2834
2835    #[inline]
2836    fn allowed_target_side_locked(
2837        &self,
2838        st: &RelayInner,
2839        exclude: RelaySideId,
2840        ty: crate::DataType,
2841        target_side: Option<RelaySideId>,
2842    ) -> Option<RelaySideId> {
2843        target_side.filter(|side| self.route_allowed_locked(st, Some(exclude), Some(ty), *side))
2844    }
2845
2846    #[cfg(feature = "discovery")]
2847    fn filter_end_to_end_satisfied_sides_locked(
2848        &self,
2849        st: &RelayInner,
2850        data: &RelayItem,
2851        sides: Vec<RelaySideId>,
2852        eps: &[crate::DataEndpoint],
2853        ty: crate::DataType,
2854    ) -> TelemetryResult<Vec<RelaySideId>> {
2855        if !is_reliable_type(ty) || Self::reliable_control_target_packet_id(data)?.is_some() {
2856            return Ok(sides);
2857        }
2858        let packet_id = match data {
2859            RelayItem::Packet(pkt) => pkt.packet_id(),
2860            RelayItem::Packed(bytes) => match wire_format::packet_id_from_wire(bytes.as_ref()) {
2861                Ok(packet_id) => packet_id,
2862                Err(TelemetryError::Unpack("reliable control frame")) => return Ok(sides),
2863                Err(err) => return Err(err),
2864            },
2865        };
2866        let Some(acked) = st.end_to_end_acked_destinations.get(&packet_id) else {
2867            return Ok(sides);
2868        };
2869        let now_ms = self.clock.now_ms();
2870        let mut filtered = Vec::new();
2871        for side in sides {
2872            let Some(route) = st.discovery_routes.get(&side) else {
2873                filtered.push(side);
2874                continue;
2875            };
2876            let mut still_pending = false;
2877            let mut had_destination_board = false;
2878            for sender_state in route.announcers.values() {
2879                if now_ms.saturating_sub(sender_state.last_seen_ms) > DISCOVERY_ROUTE_TTL_MS {
2880                    continue;
2881                }
2882                for board in sender_state.topology_boards.iter() {
2883                    if !self.is_end_to_end_destination_sender(&board.sender_id) {
2884                        continue;
2885                    }
2886                    had_destination_board = true;
2887                    let sender_hash = Self::sender_hash(&board.sender_id);
2888                    if acked.contains(&sender_hash) {
2889                        continue;
2890                    }
2891                    if eps
2892                        .iter()
2893                        .copied()
2894                        .any(|ep| board.reachable_endpoints.contains(&ep))
2895                    {
2896                        still_pending = true;
2897                        break;
2898                    }
2899                    // Keep forwarding while any discovered destination sender for this packet
2900                    // remains unacked, even if topology/schema metadata changed for new packets.
2901                    still_pending = true;
2902                    break;
2903                }
2904                if still_pending {
2905                    break;
2906                }
2907            }
2908            if still_pending || !had_destination_board {
2909                filtered.push(side);
2910            }
2911        }
2912        Ok(filtered)
2913    }
2914
2915    #[cfg(not(feature = "discovery"))]
2916    fn filter_end_to_end_satisfied_sides_locked(
2917        &self,
2918        _st: &RelayInner,
2919        _data: &RelayItem,
2920        sides: Vec<RelaySideId>,
2921        _eps: &[crate::DataEndpoint],
2922        _ty: crate::DataType,
2923    ) -> TelemetryResult<Vec<RelaySideId>> {
2924        Ok(sides)
2925    }
2926
2927    #[cfg(feature = "discovery")]
2928    fn side_has_multiple_announcers_locked(
2929        &self,
2930        st: &RelayInner,
2931        side: RelaySideId,
2932        now_ms: u64,
2933    ) -> bool {
2934        st.discovery_routes
2935            .get(&side)
2936            .map(|route| {
2937                route
2938                    .announcers
2939                    .values()
2940                    .filter(|sender| {
2941                        now_ms.saturating_sub(sender.last_seen_ms) <= DISCOVERY_ROUTE_TTL_MS
2942                    })
2943                    .take(2)
2944                    .count()
2945                    > 1
2946            })
2947            .unwrap_or(false)
2948    }
2949
2950    #[cfg(not(feature = "discovery"))]
2951    fn side_has_multiple_announcers_locked(
2952        &self,
2953        _st: &RelayInner,
2954        _side: RelaySideId,
2955        _now_ms: u64,
2956    ) -> bool {
2957        false
2958    }
2959
2960    #[cfg(feature = "discovery")]
2961    fn sender_topology_board_mut<'a>(
2962        sender_state: &'a mut DiscoverySenderState,
2963        sender_id: &str,
2964    ) -> &'a mut TopologyBoardNode {
2965        if let Some(idx) = sender_state
2966            .topology_boards
2967            .iter()
2968            .position(|board| board.sender_id == sender_id)
2969        {
2970            return &mut sender_state.topology_boards[idx];
2971        }
2972        sender_state.topology_boards.push(TopologyBoardNode {
2973            sender_id: sender_id.to_string(),
2974            reachable_endpoints: Vec::new(),
2975            reachable_timesync_sources: Vec::new(),
2976            connections: Vec::new(),
2977        });
2978        sender_state
2979            .topology_boards
2980            .last_mut()
2981            .expect("board inserted above")
2982    }
2983
2984    #[cfg(feature = "discovery")]
2985    fn refresh_sender_topology_state(sender_state: &mut DiscoverySenderState) {
2986        discovery::normalize_topology_boards(&mut sender_state.topology_boards);
2987        let (mut reachable, mut reachable_timesync_sources) =
2988            discovery::summarize_topology_boards(&sender_state.topology_boards);
2989        reachable.extend(sender_state.advertised_reachable.iter().copied());
2990        crate::small_sort::sort(&mut reachable);
2991        reachable.dedup();
2992        reachable_timesync_sources.extend(
2993            sender_state
2994                .advertised_reachable_timesync_sources
2995                .iter()
2996                .cloned(),
2997        );
2998        reachable_timesync_sources.sort_unstable();
2999        reachable_timesync_sources.dedup();
3000        sender_state.reachable = reachable;
3001        sender_state.reachable_timesync_sources = reachable_timesync_sources;
3002    }
3003
3004    #[cfg(feature = "discovery")]
3005    fn recompute_discovery_side_state(route: &mut DiscoverySideState) {
3006        let mut reachable = Vec::new();
3007        let mut reachable_timesync_sources = Vec::new();
3008        let mut last_seen_ms = 0u64;
3009        for sender in route.announcers.values() {
3010            reachable.extend(sender.reachable.iter().copied());
3011            reachable_timesync_sources.extend(sender.reachable_timesync_sources.iter().cloned());
3012            last_seen_ms = last_seen_ms.max(sender.last_seen_ms);
3013        }
3014        crate::small_sort::sort(&mut reachable);
3015        reachable.dedup();
3016        reachable_timesync_sources.sort_unstable();
3017        reachable_timesync_sources.dedup();
3018        route.reachable = reachable;
3019        route.reachable_timesync_sources = reachable_timesync_sources;
3020        route.last_seen_ms = last_seen_ms;
3021    }
3022
3023    #[cfg(feature = "discovery")]
3024    fn local_discovery_topology_board(&self, st: &RelayInner, now_ms: u64) -> TopologyBoardNode {
3025        let mut connections = Vec::new();
3026        for route in st.discovery_routes.values() {
3027            if now_ms.saturating_sub(route.last_seen_ms) > DISCOVERY_ROUTE_TTL_MS {
3028                continue;
3029            }
3030            for (sender, sender_state) in route.announcers.iter() {
3031                if now_ms.saturating_sub(sender_state.last_seen_ms) <= DISCOVERY_ROUTE_TTL_MS {
3032                    connections.push(sender.clone());
3033                }
3034            }
3035        }
3036        connections.sort_unstable();
3037        connections.dedup();
3038        let sender = self.sender_arc();
3039        TopologyBoardNode {
3040            sender_id: sender.to_string(),
3041            reachable_endpoints: Vec::new(),
3042            reachable_timesync_sources: Vec::new(),
3043            connections,
3044        }
3045    }
3046
3047    #[cfg(feature = "discovery")]
3048    fn advertised_discovery_topology_for_link_locked(
3049        &self,
3050        st: &RelayInner,
3051        now_ms: u64,
3052        link_local_enabled: bool,
3053        exclude_side: Option<RelaySideId>,
3054    ) -> Vec<TopologyBoardNode> {
3055        let mut boards = vec![self.local_discovery_topology_board(st, now_ms)];
3056        for (&route_side, route) in st.discovery_routes.iter() {
3057            if exclude_side == Some(route_side) {
3058                continue;
3059            }
3060            if now_ms.saturating_sub(route.last_seen_ms) > DISCOVERY_ROUTE_TTL_MS {
3061                continue;
3062            }
3063            for (announcer, sender_state) in route.announcers.iter() {
3064                if now_ms.saturating_sub(sender_state.last_seen_ms) > DISCOVERY_ROUTE_TTL_MS {
3065                    continue;
3066                }
3067                if sender_state.topology_boards.is_empty() {
3068                    let sender = self.sender_arc();
3069                    let fallback = TopologyBoardNode {
3070                        sender_id: announcer.clone(),
3071                        reachable_endpoints: sender_state.reachable.clone(),
3072                        reachable_timesync_sources: sender_state.reachable_timesync_sources.clone(),
3073                        connections: vec![sender.to_string()],
3074                    };
3075                    discovery::merge_topology_boards(&mut boards, core::slice::from_ref(&fallback));
3076                } else {
3077                    // Merge the retained view directly. Cloning it first and
3078                    // cloning again into the output doubled temporary strings.
3079                    discovery::merge_topology_boards(&mut boards, &sender_state.topology_boards);
3080                    if let Some(board) = boards
3081                        .iter_mut()
3082                        .find(|board| board.sender_id == *announcer)
3083                    {
3084                        let sender = self.sender_arc();
3085                        if !board.connections.iter().any(|peer| peer == sender.as_ref()) {
3086                            board.connections.push(sender.to_string());
3087                        }
3088                    }
3089                }
3090            }
3091        }
3092        if !link_local_enabled {
3093            for board in &mut boards {
3094                board
3095                    .reachable_endpoints
3096                    .retain(|ep| !ep.is_link_local_only());
3097            }
3098        }
3099        discovery::normalize_topology_boards(&mut boards);
3100        boards
3101    }
3102
3103    #[cfg(feature = "discovery")]
3104    fn note_discovery_topology_change_locked(st: &mut RelayInner, now_ms: u64) {
3105        st.discovery_cadence.on_topology_change(now_ms);
3106        for side_id in st
3107            .sides
3108            .iter()
3109            .enumerate()
3110            .filter_map(|(side_id, side)| side.as_ref().map(|_| side_id))
3111        {
3112            let throttle = st.discovery_side_throttle.entry(side_id).or_default();
3113            throttle.pending_incremental = true;
3114            throttle.incremental_retries_remaining = DISCOVERY_INCREMENTAL_RETRY_COUNT;
3115        }
3116        for transport in st.side_transport.values_mut() {
3117            transport.refresh_transmit_header_templates();
3118        }
3119    }
3120
3121    #[cfg(feature = "discovery")]
3122    fn prune_discovery_routes_locked(st: &mut RelayInner, now_ms: u64) -> bool {
3123        // Match router maintenance: unchanged routes require no heap work.
3124        let mut changed = false;
3125        st.discovery_routes.retain(|_, route| {
3126            let before_peers = route.announcers.len();
3127            route.announcers.retain(|_, sender| {
3128                now_ms.saturating_sub(sender.last_seen_ms) <= DISCOVERY_ROUTE_TTL_MS
3129            });
3130            if before_peers != route.announcers.len() {
3131                Self::recompute_discovery_side_state(route);
3132                changed = true;
3133            }
3134            let keep = !route.announcers.is_empty();
3135            changed |= !keep;
3136            keep
3137        });
3138        changed
3139    }
3140
3141    #[cfg(feature = "discovery")]
3142    fn reconcile_end_to_end_acked_destinations_locked(&self, st: &mut RelayInner) {
3143        if st.end_to_end_acked_destinations.is_empty() {
3144            return;
3145        }
3146        let mut active_senders = BTreeSet::new();
3147        for route in st.discovery_routes.values() {
3148            for sender_state in route.announcers.values() {
3149                for board in sender_state.topology_boards.iter() {
3150                    if self.is_end_to_end_destination_sender(&board.sender_id) {
3151                        active_senders.insert(Self::sender_hash(&board.sender_id));
3152                    }
3153                }
3154            }
3155        }
3156        st.end_to_end_acked_destinations.retain(|_, acked| {
3157            acked.retain(|sender_hash| active_senders.contains(sender_hash));
3158            !acked.is_empty()
3159        });
3160    }
3161
3162    #[cfg(feature = "discovery")]
3163    fn advertised_summaries_from_topology_locked(
3164        &self,
3165        st: &RelayInner,
3166        now_ms: u64,
3167        link_local_enabled: bool,
3168        exclude_side: Option<RelaySideId>,
3169        topology: &[TopologyBoardNode],
3170    ) -> (Vec<crate::DataEndpoint>, Vec<String>) {
3171        let (mut endpoints, mut sources) = discovery::summarize_topology_boards(topology);
3172        for (&side, route) in &st.discovery_routes {
3173            if exclude_side == Some(side)
3174                || now_ms.saturating_sub(route.last_seen_ms) > DISCOVERY_ROUTE_TTL_MS
3175            {
3176                continue;
3177            }
3178            for peer in route.announcers.values() {
3179                if now_ms.saturating_sub(peer.last_seen_ms) > DISCOVERY_ROUTE_TTL_MS {
3180                    continue;
3181                }
3182                for endpoint in &peer.advertised_reachable {
3183                    if !discovery::is_discovery_endpoint(*endpoint)
3184                        && (link_local_enabled || !endpoint.is_link_local_only())
3185                        && !endpoints.contains(endpoint)
3186                    {
3187                        endpoints.push(*endpoint);
3188                    }
3189                }
3190                for source in &peer.advertised_reachable_timesync_sources {
3191                    if !sources.contains(source) {
3192                        sources.push(source.clone());
3193                    }
3194                }
3195            }
3196        }
3197        endpoints.retain(|endpoint| {
3198            !discovery::is_discovery_endpoint(*endpoint)
3199                && (link_local_enabled || !endpoint.is_link_local_only())
3200        });
3201        crate::small_sort::sort(&mut endpoints);
3202        sources.sort_unstable();
3203        (endpoints, sources)
3204    }
3205
3206    #[cfg(feature = "discovery")]
3207    fn advertised_discovery_endpoints_for_link_locked(
3208        &self,
3209        st: &RelayInner,
3210        now_ms: u64,
3211        link_local_enabled: bool,
3212        exclude_side: Option<RelaySideId>,
3213    ) -> Vec<crate::DataEndpoint> {
3214        let (mut reachable_endpoints, _) = discovery::summarize_topology_boards(
3215            &self.advertised_discovery_topology_for_link_locked(
3216                st,
3217                now_ms,
3218                link_local_enabled,
3219                exclude_side,
3220            ),
3221        );
3222        // Compact advertisements can know routes before detailed topology arrives.
3223        // Preserve that reachability without assigning downstream endpoints to the bridge.
3224        for (&side, route) in &st.discovery_routes {
3225            if exclude_side == Some(side)
3226                || now_ms.saturating_sub(route.last_seen_ms) > DISCOVERY_ROUTE_TTL_MS
3227            {
3228                continue;
3229            }
3230            for sender in route.announcers.values() {
3231                if now_ms.saturating_sub(sender.last_seen_ms) <= DISCOVERY_ROUTE_TTL_MS {
3232                    reachable_endpoints.extend(sender.advertised_reachable.iter().copied());
3233                }
3234            }
3235        }
3236        crate::small_sort::sort(&mut reachable_endpoints);
3237        reachable_endpoints.dedup();
3238        reachable_endpoints
3239            .into_iter()
3240            .filter(|ep| {
3241                !discovery::is_discovery_endpoint(*ep)
3242                    && (link_local_enabled || !ep.is_link_local_only())
3243            })
3244            .collect()
3245    }
3246
3247    #[cfg(feature = "discovery")]
3248    fn advertised_discovery_timesync_sources_for_link_locked(
3249        &self,
3250        st: &RelayInner,
3251        now_ms: u64,
3252        exclude_side: Option<RelaySideId>,
3253    ) -> Vec<String> {
3254        let (_, mut sources) = discovery::summarize_topology_boards(
3255            &self.advertised_discovery_topology_for_link_locked(st, now_ms, true, exclude_side),
3256        );
3257        for (&side, route) in &st.discovery_routes {
3258            if exclude_side == Some(side)
3259                || now_ms.saturating_sub(route.last_seen_ms) > DISCOVERY_ROUTE_TTL_MS
3260            {
3261                continue;
3262            }
3263            for sender in route.announcers.values() {
3264                if now_ms.saturating_sub(sender.last_seen_ms) <= DISCOVERY_ROUTE_TTL_MS {
3265                    sources.extend(sender.advertised_reachable_timesync_sources.iter().cloned());
3266                }
3267            }
3268        }
3269        sources.sort_unstable();
3270        sources.dedup();
3271        sources
3272    }
3273
3274    #[cfg(feature = "discovery")]
3275    #[cfg(feature = "timesync")]
3276    fn preferred_timesync_route_source(
3277        &self,
3278        data: &RelayItem,
3279        ty: crate::DataType,
3280    ) -> TelemetryResult<Option<String>> {
3281        if !matches!(
3282            ty,
3283            crate::DataType::TimeSyncAnnounce | crate::DataType::TimeSyncResponse
3284        ) {
3285            return Ok(None);
3286        }
3287
3288        let sender = match data {
3289            RelayItem::Packet(pkt) => pkt.sender().to_owned(),
3290            RelayItem::Packed(bytes) => {
3291                if wire_format::peek_frame_info(bytes.as_ref())
3292                    .ok()
3293                    .is_some_and(|frame| frame.ack_only())
3294                {
3295                    return Ok(None);
3296                }
3297                wire_format::unpack_packet(bytes.as_ref())?
3298                    .sender()
3299                    .to_owned()
3300            }
3301        };
3302        Ok(Some(sender))
3303    }
3304
3305    #[cfg(all(feature = "discovery", feature = "timesync"))]
3306    #[inline]
3307    fn side_is_slow_control_link_locked(
3308        st: &RelayInner,
3309        side_id: RelaySideId,
3310        now_ms: u64,
3311    ) -> bool {
3312        st.adaptive_route_stats.get(&side_id).is_some_and(|stats| {
3313            let recent_slow = stats.last_slow_observed_ms > 0
3314                && now_ms.saturating_sub(stats.last_slow_observed_ms)
3315                    <= discovery::DISCOVERY_SLOW_LINK_FULL_INTERVAL_MS;
3316            stats.sample_count > 0
3317                && ((stats.estimated_bandwidth_bps > 0
3318                    && stats.estimated_bandwidth_bps <= CONTROL_SLOW_LINK_CAPACITY_BPS)
3319                    || recent_slow)
3320        })
3321    }
3322
3323    #[cfg(feature = "discovery")]
3324    fn discovery_level_for_side_locked(
3325        st: &mut RelayInner,
3326        side_id: RelaySideId,
3327        now_ms: u64,
3328    ) -> Option<DiscoveryAdvertiseLevel> {
3329        let throttle = st.discovery_side_throttle.entry(side_id).or_default();
3330        if !throttle.has_sent_full {
3331            throttle.has_sent_full = true;
3332            throttle.pending_incremental = false;
3333            throttle.incremental_retries_remaining = 0;
3334            throttle.next_ping_ms = now_ms.saturating_add(DISCOVERY_SLOW_LINK_PING_INTERVAL_MS);
3335            return Some(DiscoveryAdvertiseLevel::Full);
3336        }
3337        if throttle.pending_incremental {
3338            throttle.incremental_retries_remaining =
3339                throttle.incremental_retries_remaining.saturating_sub(1);
3340            throttle.pending_incremental = throttle.incremental_retries_remaining > 0;
3341            return Some(DiscoveryAdvertiseLevel::Incremental);
3342        }
3343        // Stable links carry only a small liveness announcement. Route and
3344        // schema snapshots are emitted on changes or explicit recovery requests.
3345        if throttle.liveness_due(now_ms) {
3346            throttle.next_ping_ms = now_ms.saturating_add(DISCOVERY_SLOW_LINK_PING_INTERVAL_MS);
3347            return Some(DiscoveryAdvertiseLevel::MinimalPing);
3348        }
3349        None
3350    }
3351
3352    #[cfg(all(feature = "discovery", feature = "timesync"))]
3353    #[inline]
3354    fn is_timesync_type(ty: crate::DataType) -> bool {
3355        matches!(
3356            ty,
3357            crate::DataType::TimeSyncAnnounce
3358                | crate::DataType::TimeSyncRequest
3359                | crate::DataType::TimeSyncResponse
3360        )
3361    }
3362
3363    #[cfg(all(feature = "discovery", feature = "timesync"))]
3364    fn timesync_allowed_for_side_locked(
3365        st: &mut RelayInner,
3366        side_id: RelaySideId,
3367        ty: crate::DataType,
3368        now_ms: u64,
3369    ) -> bool {
3370        if !Self::is_timesync_type(ty) {
3371            return true;
3372        }
3373        if !Self::side_is_slow_control_link_locked(st, side_id, now_ms) {
3374            st.timesync_side_throttle.remove(&side_id);
3375            return true;
3376        }
3377
3378        let throttle = st.timesync_side_throttle.entry(side_id).or_default();
3379        if now_ms >= throttle.next_allowed_ms {
3380            throttle.next_allowed_ms = now_ms.saturating_add(TIMESYNC_SLOW_LINK_MIN_INTERVAL_MS);
3381            return true;
3382        }
3383        false
3384    }
3385
3386    #[cfg(all(feature = "discovery", feature = "timesync"))]
3387    fn filter_timesync_sides_locked(
3388        st: &mut RelayInner,
3389        ty: crate::DataType,
3390        now_ms: u64,
3391        sides: Vec<RelaySideId>,
3392    ) -> Vec<RelaySideId> {
3393        sides
3394            .into_iter()
3395            .filter(|side| Self::timesync_allowed_for_side_locked(st, *side, ty, now_ms))
3396            .collect()
3397    }
3398
3399    #[cfg(feature = "discovery")]
3400    fn queue_discovery_announce(
3401        &self,
3402        include_schema: bool,
3403        skip_in_flight_sides: bool,
3404    ) -> TelemetryResult<()> {
3405        self.queue_discovery_announce_on_side(include_schema, skip_in_flight_sides, None)
3406    }
3407
3408    #[cfg(feature = "discovery")]
3409    fn refresh_known_discovery_liveness(
3410        &self,
3411        pkt: &Packet,
3412        side: RelaySideId,
3413    ) -> TelemetryResult<bool> {
3414        let empty_ping =
3415            pkt.data_type() == crate::DataType::DiscoveryAnnounce && pkt.payload().is_empty();
3416        if !empty_ping && pkt.data_type() != crate::DataType::DiscoveryAddress {
3417            return Ok(false);
3418        }
3419        crate::memory_admission::check(
3420            if empty_ping {
3421                512
3422            } else {
3423                1024 + pkt.payload().len().saturating_mul(4)
3424            },
3425            pkt.payload().len().saturating_mul(2).max(128),
3426        )?;
3427        let mut address = if empty_ping {
3428            None
3429        } else {
3430            Some(discovery::decode_discovery_address(pkt)?)
3431        };
3432        let now = self.clock.now_ms();
3433        let mut st = self.state.lock();
3434        if let Some(ad) = &mut address {
3435            let link_local = st
3436                .sides
3437                .get(side)
3438                .and_then(|entry| entry.as_ref())
3439                .is_some_and(|entry| entry.opts.link_local_enabled);
3440            if !link_local {
3441                ad.reachable_endpoints.retain(|ep| !ep.is_link_local_only());
3442            }
3443        }
3444        let canonical;
3445        let sender = if let Some(ad) = &address {
3446            ad.hostname.as_str()
3447        } else {
3448            canonical = Self::canonical_sender_locked(&st, pkt.sender());
3449            canonical.as_str()
3450        };
3451        let Some(route) = st.discovery_routes.get_mut(&side) else {
3452            return Ok(false);
3453        };
3454        let Some(peer) = route.announcers.get_mut(sender) else {
3455            return Ok(false);
3456        };
3457        if let Some(ad) = &address
3458            && (peer.advertised_address != Some(ad.address)
3459                || peer.advertised_reachable != ad.reachable_endpoints
3460                || peer.advertised_reachable_timesync_sources != ad.reachable_timesync_sources)
3461        {
3462            return Ok(false);
3463        }
3464        // A liveness update cannot add ownership, change identity, or enlarge
3465        // discovery state. Leave new/changed summaries behind the full guard.
3466        peer.last_seen_ms = now;
3467        route.last_seen_ms = route.last_seen_ms.max(now);
3468
3469        Ok(true)
3470    }
3471
3472    #[cfg(feature = "discovery")]
3473    fn queue_pressure_keepalives(
3474        &self,
3475        requested_side: Option<RelaySideId>,
3476    ) -> TelemetryResult<()> {
3477        let now = self.clock.now_ms();
3478        let sender = self.sender_arc();
3479        crate::memory_admission::check(
3480            256 + sender.len().saturating_mul(4),
3481            sender.len().max(256),
3482        )?;
3483        let side_count = self.state.lock().sides.len();
3484        for side in 0..side_count {
3485            let due = {
3486                let st = self.state.lock();
3487                requested_side.is_none_or(|wanted| wanted == side)
3488                    && st.sides[side].is_some()
3489                    && self.route_allowed_locked(
3490                        &st,
3491                        None,
3492                        Some(crate::DataType::DiscoveryAnnounce),
3493                        side,
3494                    )
3495                    && st
3496                        .discovery_side_throttle
3497                        .get(&side)
3498                        .is_none_or(|throttle| now >= throttle.next_ping_ms)
3499            };
3500            if !due {
3501                continue;
3502            }
3503            let pkt = discovery::build_discovery_announce(sender.as_ref(), now, &[])?;
3504            self.state.lock().push_tx(RelayTxItem {
3505                src: None,
3506                dst: side,
3507                data: RelayItem::Packet(Arc::new(pkt)),
3508                priority: 255,
3509                retry_after_ms: 0,
3510            })?;
3511            self.state
3512                .lock()
3513                .discovery_side_throttle
3514                .entry(side)
3515                .or_default()
3516                .next_ping_ms = now.saturating_add(discovery::DISCOVERY_SLOW_INTERVAL_MS);
3517        }
3518        Ok(())
3519    }
3520
3521    #[cfg(feature = "discovery")]
3522    fn queue_discovery_announce_on_side(
3523        &self,
3524        include_schema: bool,
3525        skip_in_flight_sides: bool,
3526        requested_side: Option<RelaySideId>,
3527    ) -> TelemetryResult<()> {
3528        #[cfg(not(feature = "std"))]
3529        let _ = include_schema;
3530        if let Some(side) = requested_side {
3531            return self.queue_discovery_announce_one_side(
3532                include_schema,
3533                skip_in_flight_sides,
3534                Some(side),
3535                true,
3536            );
3537        }
3538        let count = self.state.lock().sides.len();
3539        let mut advertised = false;
3540        for side in 0..count {
3541            if self
3542                .state
3543                .lock()
3544                .sides
3545                .get(side)
3546                .is_some_and(|side| side.is_some())
3547            {
3548                self.queue_discovery_announce_one_side(
3549                    include_schema,
3550                    skip_in_flight_sides,
3551                    Some(side),
3552                    false,
3553                )?;
3554                advertised = true;
3555            }
3556        }
3557        // The shared cadence advances once per announcement, regardless of
3558        // side count. Advancing it once per side silently doubled backoff.
3559        if advertised {
3560            self.state
3561                .lock()
3562                .discovery_cadence
3563                .on_announce_sent(self.clock.now_ms());
3564        }
3565        Ok(())
3566    }
3567
3568    #[cfg(feature = "discovery")]
3569    fn queue_discovery_announce_one_side(
3570        &self,
3571        include_schema: bool,
3572        skip_in_flight_sides: bool,
3573        requested_side: Option<RelaySideId>,
3574        advance_cadence: bool,
3575    ) -> TelemetryResult<()> {
3576        let (scratch, largest) = if include_schema {
3577            (8192, 2048)
3578        } else {
3579            let now = self.clock.now_ms();
3580            let sender = self.sender_arc();
3581            let st = self.state.lock();
3582            discovery::topology_snapshot_scratch(
3583                sender.as_ref(),
3584                0,
3585                0,
3586                4,
3587                true,
3588                st.discovery_routes
3589                    .iter()
3590                    .filter(|(side, route)| {
3591                        Some(**side) != requested_side
3592                            && now.saturating_sub(route.last_seen_ms) <= DISCOVERY_ROUTE_TTL_MS
3593                    })
3594                    .flat_map(|(_, route)| route.announcers.iter())
3595                    .filter(|(_, peer)| {
3596                        now.saturating_sub(peer.last_seen_ms) <= DISCOVERY_ROUTE_TTL_MS
3597                    })
3598                    .map(|(name, peer)| {
3599                        (
3600                            name.as_str(),
3601                            peer.reachable.as_slice(),
3602                            peer.reachable_timesync_sources.as_slice(),
3603                            peer.topology_boards.as_slice(),
3604                        )
3605                    }),
3606            )
3607        };
3608        // Snapshot construction may clone several route/schema summaries.
3609        // Reclaim ordinary queued telemetry before refusing discovery scratch.
3610        // Priority >= 200 commands, ACKs and metadata stay protected; do not
3611        // evict routes or reliable receive/reassembly state.
3612        while let Err(err) = crate::memory_admission::check(scratch, largest) {
3613            if matches!(err, TelemetryError::Io("memory pressure"))
3614                && crate::memory_admission::check(512, 256).is_ok()
3615            {
3616                let mut st = self.state.lock();
3617                if st.pop_lowest_packet_queue_item(200) {
3618                    continue;
3619                }
3620            }
3621            if !include_schema && matches!(err, TelemetryError::Io("memory pressure")) {
3622                return self.queue_pressure_keepalives(requested_side);
3623            }
3624            return Err(err);
3625        }
3626        let now_ms = self.clock.now_ms();
3627        let per_side = {
3628            let mut st = self.state.lock();
3629            if Self::prune_discovery_routes_locked(&mut st, now_ms) {
3630                self.reconcile_end_to_end_acked_destinations_locked(&mut st);
3631                Self::note_discovery_topology_change_locked(&mut st, now_ms);
3632            }
3633            st.fit_discovery_budget();
3634            if !st.sides.iter().any(|side| side.is_some()) {
3635                return Ok(());
3636            }
3637            if include_schema {
3638                for (side, throttle) in st.discovery_side_throttle.iter_mut() {
3639                    if requested_side.is_none_or(|requested| requested == *side) {
3640                        throttle.has_sent_full = false;
3641                    }
3642                }
3643            }
3644            if advance_cadence {
3645                st.discovery_cadence.on_announce_sent(now_ms);
3646            }
3647            let mut per_side = Vec::new();
3648            for side_id in 0..st.sides.len() {
3649                let Some(side) = st.sides[side_id].as_ref() else {
3650                    continue;
3651                };
3652                let opts = side.opts.clone();
3653                let link_local_enabled = opts.link_local_enabled;
3654                if requested_side.is_some_and(|requested| requested != side_id) {
3655                    continue;
3656                }
3657                let discovery_in_flight = skip_in_flight_sides
3658                    && st.reliable_tx.iter().any(|((pending_side, ty), tx_state)| {
3659                        *pending_side == side_id
3660                            && !tx_state.sent.is_empty()
3661                            && crate::DataType::try_from_u32(*ty)
3662                                .is_some_and(discovery::is_discovery_type)
3663                    });
3664                if !self.route_allowed_locked(
3665                    &st,
3666                    None,
3667                    Some(crate::DataType::DiscoveryAnnounce),
3668                    side_id,
3669                ) {
3670                    continue;
3671                }
3672                // Preserve neighbor liveness even if a reliable discovery
3673                // baseline is still waiting for its hop ACK.
3674                let level = if discovery_in_flight {
3675                    let throttle = st.discovery_side_throttle.entry(side_id).or_default();
3676                    if !throttle.liveness_due(now_ms) {
3677                        None
3678                    } else {
3679                        throttle.next_ping_ms =
3680                            now_ms.saturating_add(DISCOVERY_SLOW_LINK_PING_INTERVAL_MS);
3681                        Some(DiscoveryAdvertiseLevel::MinimalPing)
3682                    }
3683                } else {
3684                    Self::discovery_level_for_side_locked(&mut st, side_id, now_ms)
3685                };
3686                let Some(level) = level else {
3687                    continue;
3688                };
3689                let capabilities = opts.link_capabilities();
3690                if level == DiscoveryAdvertiseLevel::MinimalPing {
3691                    per_side.push((
3692                        side_id,
3693                        level,
3694                        Vec::new(),
3695                        Vec::new(),
3696                        Vec::new(),
3697                        Vec::new(),
3698                        capabilities,
3699                    ));
3700                    continue;
3701                }
3702                let current_topology = self.advertised_discovery_topology_for_link_locked(
3703                    &st,
3704                    now_ms,
3705                    link_local_enabled,
3706                    Some(side_id),
3707                );
3708                let (endpoints, timesync_sources) = self.advertised_summaries_from_topology_locked(
3709                    &st,
3710                    now_ms,
3711                    link_local_enabled,
3712                    Some(side_id),
3713                    &current_topology,
3714                );
3715                let (topology, removed_topology) = match level {
3716                    DiscoveryAdvertiseLevel::Full => (current_topology.clone(), Vec::new()),
3717                    DiscoveryAdvertiseLevel::Incremental => {
3718                        let previous = &st
3719                            .discovery_side_throttle
3720                            .get(&side_id)
3721                            .expect("discovery throttle exists")
3722                            .last_topology;
3723                        discovery::topology_delta(previous, &current_topology)
3724                    }
3725                    DiscoveryAdvertiseLevel::MinimalPing => (Vec::new(), Vec::new()),
3726                };
3727                let throttle = st
3728                    .discovery_side_throttle
3729                    .get_mut(&side_id)
3730                    .expect("discovery throttle exists");
3731                if level == DiscoveryAdvertiseLevel::Full
3732                    || (level == DiscoveryAdvertiseLevel::Incremental
3733                        && throttle.incremental_retries_remaining == 0)
3734                {
3735                    throttle.last_topology = current_topology;
3736                }
3737                per_side.push((
3738                    side_id,
3739                    level,
3740                    endpoints,
3741                    timesync_sources,
3742                    topology,
3743                    removed_topology,
3744                    capabilities,
3745                ));
3746            }
3747            per_side
3748        };
3749        let mut st = self.state.lock();
3750        for (dst, level, endpoints, timesync_sources, topology, removed_topology, capabilities) in
3751            per_side
3752        {
3753            let sender = self.sender_arc();
3754            if include_schema && level == DiscoveryAdvertiseLevel::Full {
3755                let pkt = discovery::build_discovery_schema(sender.as_ref(), now_ms)?;
3756                let data = RelayItem::Packet(Arc::new(pkt));
3757                let priority = Self::relay_item_priority(&data)?;
3758                st.push_tx(RelayTxItem {
3759                    src: None,
3760                    dst,
3761                    data,
3762                    priority,
3763                    retry_after_ms: 0,
3764                })?;
3765            }
3766            if level == DiscoveryAdvertiseLevel::Full {
3767                let pkt = discovery::build_discovery_link_capabilities(
3768                    sender.as_ref(),
3769                    now_ms,
3770                    capabilities,
3771                )?;
3772                let data = RelayItem::Packet(Arc::new(pkt));
3773                let priority = Self::relay_item_priority(&data)?;
3774                st.push_tx(RelayTxItem {
3775                    src: None,
3776                    dst,
3777                    data,
3778                    priority,
3779                    retry_after_ms: 0,
3780                })?;
3781            }
3782            if level != DiscoveryAdvertiseLevel::Full || !endpoints.is_empty() {
3783                let pkt = discovery::build_discovery_announce(
3784                    sender.as_ref(),
3785                    now_ms,
3786                    endpoints.as_slice(),
3787                )?;
3788                let data = RelayItem::Packet(Arc::new(pkt));
3789                let priority = Self::relay_item_priority(&data)?;
3790                st.push_tx(RelayTxItem {
3791                    src: None,
3792                    dst,
3793                    data,
3794                    priority,
3795                    retry_after_ms: 0,
3796                })?;
3797            }
3798            if level != DiscoveryAdvertiseLevel::MinimalPing && !timesync_sources.is_empty() {
3799                let pkt = discovery::build_discovery_timesync_sources(
3800                    sender.as_ref(),
3801                    now_ms,
3802                    timesync_sources.as_slice(),
3803                )?;
3804                let data = RelayItem::Packet(Arc::new(pkt));
3805                let priority = Self::relay_item_priority(&data)?;
3806                st.push_tx(RelayTxItem {
3807                    src: None,
3808                    dst,
3809                    data,
3810                    priority,
3811                    retry_after_ms: 0,
3812                })?;
3813            }
3814            if level != DiscoveryAdvertiseLevel::MinimalPing
3815                && (!topology.is_empty() || !removed_topology.is_empty())
3816            {
3817                let pkt = if level == DiscoveryAdvertiseLevel::Full {
3818                    discovery::build_discovery_topology(sender.as_ref(), now_ms, &topology)?
3819                } else {
3820                    discovery::build_discovery_topology_delta(
3821                        sender.as_ref(),
3822                        now_ms,
3823                        &topology,
3824                        &removed_topology,
3825                    )?
3826                };
3827                let data = RelayItem::Packet(Arc::new(pkt));
3828                let priority = Self::relay_item_priority(&data)?;
3829                st.push_tx(RelayTxItem {
3830                    src: None,
3831                    dst,
3832                    data,
3833                    priority,
3834                    retry_after_ms: 0,
3835                })?;
3836            }
3837        }
3838        Ok(())
3839    }
3840
3841    #[cfg(feature = "discovery")]
3842    fn note_schema_received(&self, side: RelaySideId, sender: &str) {
3843        let mut st = self.state.lock();
3844        // Schema snapshots can be forwarded. They must never introduce a new
3845        // link-local announcer or change shared-bus reliability semantics.
3846        // If schema precedes topology, bounded recovery will request it again.
3847        let address = sender
3848            .strip_prefix("@addr:")
3849            .and_then(|value| value.parse::<u32>().ok());
3850        if let Some(route) = st.discovery_routes.get_mut(&side) {
3851            for (name, peer) in &mut route.announcers {
3852                if name == sender
3853                    || address.is_some_and(|address| {
3854                        peer.advertised_address == Some(address)
3855                            || sender_address_u32(name) == address
3856                    })
3857                {
3858                    peer.has_schema = true;
3859                    break;
3860                }
3861            }
3862        }
3863    }
3864
3865    #[cfg(feature = "discovery")]
3866    fn poll_discovery_announce(&self) -> TelemetryResult<bool> {
3867        let now_ms = self.clock.now_ms();
3868        let recovering = {
3869            let mut st = self.state.lock();
3870            let mut recovering = false;
3871            for side in 0..st.sides.len() {
3872                let missing = st.discovery_routes.get(&side).is_some_and(|route| {
3873                    route.announcers.iter().any(|(name, peer)| {
3874                        !peer.has_full_topology
3875                            && now_ms.saturating_sub(peer.last_seen_ms) < DISCOVERY_ROUTE_TTL_MS
3876                            && !route.announcers.values().any(|bridge| {
3877                                bridge.has_full_topology
3878                                    && now_ms.saturating_sub(bridge.last_seen_ms)
3879                                        < DISCOVERY_ROUTE_TTL_MS
3880                                    && bridge
3881                                        .topology_boards
3882                                        .iter()
3883                                        .any(|board| board.sender_id == *name)
3884                            })
3885                    })
3886                });
3887                if !missing {
3888                    continue;
3889                }
3890                let throttle = st.discovery_side_throttle.entry(side).or_default();
3891                if throttle.next_topology_request_ms == 0 {
3892                    throttle.next_topology_request_ms =
3893                        now_ms.saturating_add(discovery::DISCOVERY_BASELINE_GRACE_MS);
3894                    continue;
3895                }
3896                if now_ms < throttle.next_topology_request_ms {
3897                    continue;
3898                }
3899                throttle.next_topology_request_ms =
3900                    now_ms.saturating_add(discovery::DISCOVERY_BASELINE_RETRY_MS);
3901                let pkt = discovery::build_discovery_topology_request(
3902                    self.sender_arc().as_ref(),
3903                    now_ms,
3904                )?;
3905                let data = RelayItem::Packet(Arc::new(pkt));
3906                let priority = Self::relay_item_priority(&data)?;
3907                st.push_tx(RelayTxItem {
3908                    src: None,
3909                    dst: side,
3910                    data,
3911                    priority,
3912                    retry_after_ms: 0,
3913                })?;
3914                recovering = true;
3915            }
3916            recovering
3917        };
3918        let schema_recovery_sides = {
3919            let mut st = self.state.lock();
3920            let mut due = Vec::new();
3921            for side in 0..st.sides.len() {
3922                let missing = st.discovery_routes.get(&side).is_some_and(|route| {
3923                    route.announcers.values().any(|peer| {
3924                        peer.has_full_topology
3925                            && !peer.has_schema
3926                            && now_ms.saturating_sub(peer.last_seen_ms) < DISCOVERY_ROUTE_TTL_MS
3927                    })
3928                });
3929                if !missing {
3930                    continue;
3931                }
3932                let throttle = st.discovery_side_throttle.entry(side).or_default();
3933                if throttle.next_schema_request_ms == 0 {
3934                    throttle.next_schema_request_ms =
3935                        now_ms.saturating_add(discovery::DISCOVERY_BASELINE_GRACE_MS);
3936                } else if now_ms >= throttle.next_schema_request_ms {
3937                    throttle.next_schema_request_ms =
3938                        now_ms.saturating_add(discovery::DISCOVERY_BASELINE_RETRY_MS);
3939                    due.push(side);
3940                }
3941            }
3942            due
3943        };
3944        let recovering = recovering || !schema_recovery_sides.is_empty();
3945        for side in schema_recovery_sides {
3946            let pkt =
3947                discovery::build_discovery_schema_request(self.sender_arc().as_ref(), now_ms)?;
3948            let data = RelayItem::Packet(Arc::new(pkt));
3949            let priority = Self::relay_item_priority(&data)?;
3950            self.state.lock().push_tx(RelayTxItem {
3951                src: None,
3952                dst: side,
3953                data,
3954                priority,
3955                retry_after_ms: 0,
3956            })?;
3957        }
3958        let due = {
3959            let mut st = self.state.lock();
3960            let removed = Self::prune_discovery_routes_locked(&mut st, now_ms);
3961            if removed {
3962                self.reconcile_end_to_end_acked_destinations_locked(&mut st);
3963                Self::note_discovery_topology_change_locked(&mut st, now_ms);
3964            }
3965            st.fit_discovery_budget();
3966            let has_any = st.sides.iter().enumerate().any(|(side_id, side)| {
3967                let Some(side) = side.as_ref() else {
3968                    return false;
3969                };
3970                if !self.route_allowed_locked(
3971                    &st,
3972                    None,
3973                    Some(crate::DataType::DiscoveryAnnounce),
3974                    side_id,
3975                ) {
3976                    return false;
3977                }
3978                let _ = side;
3979                let in_flight = st.reliable_tx.iter().any(|((pending_side, ty), tx_state)| {
3980                    *pending_side == side_id
3981                        && !tx_state.sent.is_empty()
3982                        && crate::DataType::try_from_u32(*ty)
3983                            .is_some_and(discovery::is_discovery_type)
3984                });
3985                st.discovery_side_throttle
3986                    .get(&side_id)
3987                    .is_none_or(|throttle| {
3988                        (!in_flight && (!throttle.has_sent_full || throttle.pending_incremental))
3989                            || throttle.liveness_due(now_ms)
3990                    })
3991            });
3992            if !st.sides.iter().any(|side| side.is_some()) || !has_any {
3993                return Ok(recovering);
3994            }
3995            st.discovery_cadence.due(now_ms)
3996        };
3997        if !due {
3998            return Ok(recovering);
3999        }
4000        // Keep periodic discovery lightweight so a hosted relay cannot fill a
4001        // constrained link with repeated schema snapshots.
4002        self.queue_discovery_announce(false, true)?;
4003        Ok(true)
4004    }
4005
4006    #[cfg(feature = "discovery")]
4007    fn learn_discovery_item(&self, src: RelaySideId, data: &RelayItem) -> TelemetryResult<()> {
4008        let pkt = match data {
4009            RelayItem::Packet(pkt) => {
4010                if !discovery::is_discovery_type(pkt.data_type()) {
4011                    return Ok(());
4012                }
4013                pkt.as_ref().clone()
4014            }
4015            RelayItem::Packed(bytes) => {
4016                let env = wire_format::peek_routing_envelope(bytes.as_ref())?;
4017                if !discovery::is_discovery_type(env.ty) {
4018                    return Ok(());
4019                }
4020                if wire_format::peek_frame_info(bytes.as_ref())
4021                    .ok()
4022                    .is_some_and(|frame| frame.ack_only())
4023                {
4024                    return Ok(());
4025                }
4026                wire_format::unpack_packet(bytes.as_ref())?
4027            }
4028        };
4029
4030        if self.refresh_known_discovery_liveness(&pkt, src)? {
4031            return Ok(());
4032        }
4033        #[cfg(not(feature = "std"))]
4034        if pkt.data_type() == crate::DataType::DiscoverySchema
4035            && discovery::discovery_schema_payload_is_fully_known(pkt.payload())?
4036        {
4037            self.note_schema_received(src, pkt.sender());
4038            return Ok(());
4039        }
4040        // A compact address summary does not clone the complete topology or
4041        // schema. Account for its ingress-side aggregates instead; charging
4042        // every new peer for a full snapshot can prevent restart recovery.
4043        if matches!(
4044            pkt.data_type(),
4045            crate::DataType::DiscoveryTopologyRequest | crate::DataType::DiscoverySchemaRequest
4046        ) {
4047            crate::memory_admission::check(1024, 256)?;
4048            // A restarted receiver has no compact-header dictionary even if
4049            // discovery still has exactly the same routes. Refresh TX only on
4050            // its ingress link; do not erase RX templates or routing state.
4051            if let Some(transport) = self.state.lock().side_transport.get_mut(&src) {
4052                transport.refresh_transmit_header_templates();
4053            }
4054            // Like routers, relays retain a split-horizon baseline and must
4055            // answer a restarting neighbor without waiting for master election.
4056            self.state
4057                .lock()
4058                .discovery_side_throttle
4059                .entry(src)
4060                .or_default()
4061                .has_sent_full = false;
4062            return self.queue_discovery_announce_on_side(
4063                pkt.data_type() == crate::DataType::DiscoverySchemaRequest,
4064                false,
4065                Some(src),
4066            );
4067        }
4068        // Standalone relays forward variable requests; they do not own a
4069        // subscription registry or decode a schema for this four-byte payload.
4070        if pkt.data_type() == crate::DataType::ManagedVariableRequest {
4071            discovery::decode_managed_variable_request(&pkt)?;
4072            return Ok(());
4073        }
4074        let retained_summary = if matches!(
4075            pkt.data_type(),
4076            crate::DataType::DiscoveryAddress
4077                | crate::DataType::DiscoveryTopology
4078                | crate::DataType::DiscoveryAnnounce
4079                | crate::DataType::DiscoveryTimeSyncSources
4080                | crate::DataType::ManagedVariableRequest
4081        ) {
4082            self.state
4083                .lock()
4084                .discovery_routes
4085                .get(&src)
4086                .map_or(0, RelayInner::discovery_route_byte_cost)
4087        } else {
4088            0
4089        };
4090        let scratch = if pkt.data_type() == crate::DataType::DiscoveryAddress {
4091            4096usize.saturating_add(retained_summary)
4092        } else {
4093            8192usize.saturating_add(retained_summary)
4094        };
4095        let decode_bytes = pkt
4096            .payload()
4097            .len()
4098            .saturating_mul(4)
4099            .saturating_add(scratch);
4100        let largest_block = pkt.payload().len().saturating_mul(2).max(2048);
4101        while let Err(err) = crate::memory_admission::check(decode_bytes, largest_block) {
4102            if !matches!(err, TelemetryError::Io("memory pressure")) {
4103                return Err(err);
4104            }
4105            let mut st = self.state.lock();
4106            if !st.pop_lowest_packet_queue_item(256) {
4107                return Err(err);
4108            }
4109        }
4110        let now_ms = self.clock.now_ms();
4111
4112        if pkt.data_type() == crate::DataType::DiscoverySchema {
4113            let snapshot = discovery::decode_discovery_schema(&pkt)?;
4114            let mut st = self.state.lock();
4115            let budget = st.schema_growth_budget();
4116            let report = crate::config::merge_owned_schema_snapshot_with_budget(snapshot, budget)?;
4117            if report.changed() {
4118                st.fit_discovery_budget();
4119                Self::note_discovery_topology_change_locked(&mut st, now_ms);
4120            }
4121            drop(st);
4122            self.note_schema_received(src, pkt.sender());
4123            return Ok(());
4124        }
4125        if pkt.data_type() == crate::DataType::DiscoveryLinkCapabilities {
4126            let _ = discovery::decode_discovery_link_capabilities(&pkt)?;
4127            return Ok(());
4128        }
4129        let mut st = self.state.lock();
4130        if pkt.data_type() == crate::DataType::DiscoveryLeave {
4131            let leaving = pkt.sender();
4132            let before = st.discovery_routes.clone();
4133            for route in st.discovery_routes.values_mut() {
4134                route.announcers.remove(leaving);
4135                for sender_state in route.announcers.values_mut() {
4136                    sender_state
4137                        .topology_boards
4138                        .retain(|board| board.sender_id != leaving);
4139                    for board in sender_state.topology_boards.iter_mut() {
4140                        board.connections.retain(|peer| peer != leaving);
4141                    }
4142                    Self::refresh_sender_topology_state(sender_state);
4143                }
4144                Self::recompute_discovery_side_state(route);
4145            }
4146            st.discovery_routes
4147                .retain(|_, route| !route.announcers.is_empty());
4148            if st.discovery_routes != before {
4149                Self::note_discovery_topology_change_locked(&mut st, now_ms);
4150            }
4151            let _ = Self::prune_discovery_routes_locked(&mut st, now_ms);
4152            self.reconcile_end_to_end_acked_destinations_locked(&mut st);
4153            return Ok(());
4154        }
4155        let address_ad = if pkt.data_type() == crate::DataType::DiscoveryAddress {
4156            Some(discovery::decode_discovery_address(&pkt)?)
4157        } else {
4158            None
4159        };
4160        let mut topology_ad = if pkt.data_type() == crate::DataType::DiscoveryTopology {
4161            Some(discovery::decode_discovery_topology_update(&pkt)?)
4162        } else {
4163            None
4164        };
4165        let announcer_id = if let Some(ad) = address_ad.as_ref() {
4166            ad.hostname.clone()
4167        } else {
4168            let canonical = Self::canonical_sender_locked(&st, pkt.sender());
4169            if canonical != pkt.sender() {
4170                canonical
4171            } else if let Some(address) = pkt
4172                .sender()
4173                .strip_prefix("@addr:")
4174                .and_then(|value| value.parse::<u32>().ok())
4175            {
4176                topology_ad
4177                    .as_ref()
4178                    .and_then(|update| {
4179                        update
4180                            .boards
4181                            .iter()
4182                            .find(|board| sender_address_u32(&board.sender_id) == address)
4183                    })
4184                    .map(|board| board.sender_id.clone())
4185                    .unwrap_or(canonical)
4186            } else {
4187                canonical
4188            }
4189        };
4190        // Parse fallible payloads and resolve aliases before moving retained state.
4191        let announced = if pkt.data_type() == crate::DataType::DiscoveryAnnounce {
4192            Some(discovery::decode_discovery_announce(&pkt)?)
4193        } else {
4194            None
4195        };
4196        let timesync_sources = if pkt.data_type() == crate::DataType::DiscoveryTimeSyncSources {
4197            Some(discovery::decode_discovery_timesync_sources(&pkt)?)
4198        } else {
4199            None
4200        };
4201        if let Some(update) = topology_ad.as_mut() {
4202            for board in &mut update.boards {
4203                board.sender_id = Self::canonical_sender_locked(&st, &board.sender_id);
4204                for peer in &mut board.connections {
4205                    *peer = Self::canonical_sender_locked(&st, peer);
4206                }
4207            }
4208        }
4209        let mut route = st.discovery_routes.get(&src).cloned().unwrap_or_default();
4210        if pkt.sender() != announcer_id {
4211            route.announcers.remove(pkt.sender());
4212        }
4213        let side_link_local_enabled = st
4214            .sides
4215            .get(src)
4216            .and_then(|entry| entry.as_ref())
4217            .map(|side_ref| side_ref.opts.link_local_enabled)
4218            .unwrap_or(false);
4219        let mut sender_state =
4220            core::mem::take(route.announcers.entry(announcer_id.clone()).or_default());
4221        let changed = match pkt.data_type() {
4222            crate::DataType::DiscoveryAddress => {
4223                let ad = address_ad.expect("decoded above");
4224                let advertised_address = ad.address;
4225                let mut reachable = ad.reachable_endpoints;
4226                if !side_link_local_enabled {
4227                    reachable.retain(|ep| !ep.is_link_local_only());
4228                }
4229                // This packet is an aggregate route summary, not a claim that
4230                // the announcing bridge owns every endpoint behind it.
4231                let changed = sender_state.advertised_address != Some(advertised_address)
4232                    || sender_state.advertised_reachable != reachable
4233                    || sender_state.advertised_reachable_timesync_sources
4234                        != ad.reachable_timesync_sources;
4235                sender_state.advertised_address = Some(advertised_address);
4236                sender_state.advertised_reachable = reachable;
4237                sender_state.advertised_reachable_timesync_sources = ad.reachable_timesync_sources;
4238                Self::refresh_sender_topology_state(&mut sender_state);
4239                changed
4240            }
4241            crate::DataType::DiscoveryAnnounce => {
4242                let mut reachable = announced.expect("parsed before moving route state");
4243                // Empty announcements are slow-link keepalives, not endpoint
4244                // withdrawals. Preserve ownership until an explicit topology
4245                // update, leave, or expiry removes it. The common receive tail
4246                // still refreshes last_seen_ms without scheduling rediscovery.
4247                if reachable.is_empty() {
4248                    false
4249                } else {
4250                    if !side_link_local_enabled {
4251                        reachable.retain(|ep| !ep.is_link_local_only());
4252                    }
4253                    let board = Self::sender_topology_board_mut(&mut sender_state, &announcer_id);
4254                    let changed = board.reachable_endpoints != reachable;
4255                    board.reachable_endpoints = reachable;
4256                    Self::refresh_sender_topology_state(&mut sender_state);
4257                    changed
4258                }
4259            }
4260            crate::DataType::DiscoveryTimeSyncSources => {
4261                let sources = timesync_sources.expect("parsed before moving route state");
4262                let board = Self::sender_topology_board_mut(&mut sender_state, &announcer_id);
4263                let changed = board.reachable_timesync_sources != sources;
4264                board.reachable_timesync_sources = sources;
4265                Self::refresh_sender_topology_state(&mut sender_state);
4266                changed
4267            }
4268            crate::DataType::DiscoveryTopology => {
4269                let mut update = topology_ad
4270                    .take()
4271                    .expect("topology packet was decoded before route selection");
4272                if !side_link_local_enabled {
4273                    for board in update.boards.iter_mut() {
4274                        board
4275                            .reachable_endpoints
4276                            .retain(|ep| !ep.is_link_local_only());
4277                    }
4278                }
4279                if !update.incremental {
4280                    sender_state.has_full_topology = true;
4281                }
4282                let changed =
4283                    discovery::apply_topology_update(&mut sender_state.topology_boards, update);
4284                Self::refresh_sender_topology_state(&mut sender_state);
4285                changed
4286            }
4287            crate::DataType::DiscoverySchema => false,
4288            _ => false,
4289        };
4290        sender_state.last_seen_ms = now_ms;
4291        route.announcers.insert(announcer_id, sender_state);
4292        Self::recompute_discovery_side_state(&mut route);
4293        st.commit_discovery_route(src, route)?;
4294        if changed {
4295            Self::note_discovery_topology_change_locked(&mut st, now_ms);
4296        }
4297        let _ = Self::prune_discovery_routes_locked(&mut st, now_ms);
4298        self.reconcile_end_to_end_acked_destinations_locked(&mut st);
4299        Ok(())
4300    }
4301
4302    #[cfg(not(feature = "discovery"))]
4303    fn learn_discovery_item(&self, _src: RelaySideId, _data: &RelayItem) -> TelemetryResult<()> {
4304        Ok(())
4305    }
4306
4307    #[cfg(not(feature = "discovery"))]
4308    fn queue_discovery_announce(&self) -> TelemetryResult<()> {
4309        Ok(())
4310    }
4311
4312    #[cfg(not(feature = "discovery"))]
4313    fn poll_discovery_announce(&self) -> TelemetryResult<bool> {
4314        Ok(false)
4315    }
4316
4317    fn process_reliable_timeouts(&self) -> TelemetryResult<()> {
4318        let now = self.clock.now_ms();
4319        let mut requeue: Vec<(RelaySideId, crate::DataType, u32)> = Vec::new();
4320
4321        {
4322            let mut st = self.state.lock();
4323            if st.reliable_tx.is_empty() {
4324                return Ok(());
4325            }
4326
4327            for ((side, ty_u32), tx_state) in st.reliable_tx.iter_mut() {
4328                let Some(ty) = crate::DataType::try_from_u32(*ty_u32) else {
4329                    continue;
4330                };
4331                let sent_order: Vec<u32> = tx_state.sent_order.iter().copied().collect();
4332                for seq in sent_order {
4333                    let Some(sent) = tx_state.sent.get_mut(&seq) else {
4334                        continue;
4335                    };
4336                    if sent.queued
4337                        || now.wrapping_sub(sent.last_send_ms) < runtime_reliable_retransmit_ms()
4338                    {
4339                        continue;
4340                    }
4341                    if sent.partial_acked {
4342                        // Do not let a lost packet-request pin a partially
4343                        // acknowledged entry forever. Allow one interval for
4344                        // the explicit request, then return to bounded replay.
4345                        sent.partial_acked = false;
4346                        sent.last_send_ms = now;
4347                        continue;
4348                    }
4349                    if sent.retries >= runtime_reliable_max_retries() {
4350                        tx_state.sent.remove(&seq);
4351                        tx_state.sent_order.retain(|existing| *existing != seq);
4352                        continue;
4353                    }
4354                    sent.retries += 1;
4355                    requeue.push((*side, ty, seq));
4356                }
4357            }
4358        }
4359
4360        for (side, ty, seq) in requeue {
4361            self.queue_reliable_retransmit(side, ty, seq)?;
4362        }
4363
4364        Ok(())
4365    }
4366
4367    /// Compute a de-dupe hash for a QueueItem.
4368    /// Uses packet ID for Packet items, and attempts to extract packet ID from
4369    /// packed bytes. If extraction fails, hashes raw bytes as a fallback.
4370    fn get_hash(item: &RelayRxItem) -> u64 {
4371        match &item.data {
4372            RelayItem::Packet(pkt) => pkt.packet_id(),
4373            RelayItem::Packed(bytes) => {
4374                let reliable_seq = wire_format::peek_frame_info(bytes.as_ref())
4375                    .ok()
4376                    .and_then(|frame| frame.reliable)
4377                    .and_then(|hdr| {
4378                        if (hdr.flags & wire_format::RELIABLE_FLAG_ACK_ONLY) != 0 {
4379                            None
4380                        } else {
4381                            Some(hdr.seq)
4382                        }
4383                    });
4384
4385                match wire_format::packet_id_from_wire(bytes.as_ref()) {
4386                    Ok(id) => {
4387                        if let Some(seq) = reliable_seq {
4388                            hash_bytes_u64(id, &seq.to_le_bytes())
4389                        } else {
4390                            id
4391                        }
4392                    }
4393                    Err(_e) => {
4394                        // Fallback: if bytes are malformed (or compression feature mismatch),
4395                        // hash raw bytes so we can still dedupe identical network duplicates.
4396                        let h: u64 = 0x9E37_79B9_7F4A_7C15;
4397                        hash_bytes_u64(h, bytes.as_ref())
4398                    }
4399                }
4400            }
4401        }
4402    }
4403
4404    /// Compute a dedupe ID for an incoming RelayRxItem.
4405    /// Note: we intentionally do *not* include `src` so that the same
4406    /// packet coming from multiple sides is only processed once.
4407    fn is_duplicate_pkt(&self, item: &RelayRxItem) -> TelemetryResult<bool> {
4408        let id = Self::get_hash(item);
4409
4410        let mut st = self.state.lock();
4411        if st.recent_rx.contains(&id) {
4412            Ok(true)
4413        } else {
4414            st.push_recent_rx(id)?;
4415            Ok(false)
4416        }
4417    }
4418
4419    fn should_forward_duplicate_reliable_item(&self, item: &RelayRxItem) -> TelemetryResult<bool> {
4420        let (_, ty) = self.item_route_info(&item.data)?;
4421        if !is_reliable_type(ty)
4422            || matches!(
4423                ty,
4424                crate::DataType::ReliableAck
4425                    | crate::DataType::ReliablePartialAck
4426                    | crate::DataType::ReliablePacketRequest
4427            )
4428        {
4429            return Ok(false);
4430        }
4431
4432        if !self.item_target_senders(&item.data)?.is_empty() {
4433            let id = match &item.data {
4434                RelayItem::Packet(pkt) => pkt.packet_id(),
4435                RelayItem::Packed(bytes) => wire_format::packet_id_from_wire(bytes)?,
4436            };
4437            let mut st = self.state.lock();
4438            return Ok(st.reliable_return_routes.get_mut(&id).is_some_and(|route| {
4439                let now = self.clock.now_ms();
4440                if now.saturating_sub(route.last_replay_ms) < runtime_reliable_retransmit_ms() {
4441                    return false;
4442                }
4443                route.last_replay_ms = now;
4444                true
4445            }));
4446        }
4447
4448        let RemoteSidePlan::Target(sides) = self.remote_side_plan(&item.data, item.src)?;
4449        let st = self.state.lock();
4450        let now_ms = self.clock.now_ms();
4451        Ok(sides
4452            .into_iter()
4453            .any(|side| self.side_has_multiple_announcers_locked(&st, side, now_ms)))
4454    }
4455
4456    /// Register a side whose TX callback consumes packed packet bytes.
4457    ///
4458    /// Returns the side id later used for ingress APIs such as `rx_packed_from_side`.
4459    /// The default options disable the relay's per-link reliable framing on this side.
4460    pub fn add_side_packed<N, F>(&self, name: N, tx: F) -> RelaySideId
4461    where
4462        N: AsRef<str>,
4463        F: Fn(&[u8]) -> TelemetryResult<()> + Send + Sync + 'static,
4464    {
4465        self.add_side_packed_with_options(name, tx, RelaySideOptions::default())
4466    }
4467
4468    /// Register a packed side with bounded-frame transport enabled.
4469    ///
4470    /// `max_frame_bytes == 0` leaves frames unbounded.
4471    pub fn add_side_packed_small_packets<N, F>(
4472        &self,
4473        name: N,
4474        tx: F,
4475        max_frame_bytes: usize,
4476    ) -> RelaySideId
4477    where
4478        N: AsRef<str>,
4479        F: Fn(&[u8]) -> TelemetryResult<()> + Send + Sync + 'static,
4480    {
4481        self.add_side_packed_with_options(
4482            name,
4483            tx,
4484            RelaySideOptions::default().with_small_packet_transport(max_frame_bytes),
4485        )
4486    }
4487
4488    /// Register a packed-output side with explicit side options.
4489    ///
4490    /// `opts.reliable_enabled` enables relay-managed per-hop ACK/retransmit behavior on this side.
4491    /// `opts.link_local_enabled` gates link-local-only forwarding and discovery use of this side.
4492    /// `ingress_enabled` and `egress_enabled` set the initial directional policy.
4493    pub fn add_side_packed_with_options<N, F>(
4494        &self,
4495        name: N,
4496        tx: F,
4497        opts: RelaySideOptions,
4498    ) -> RelaySideId
4499    where
4500        N: AsRef<str>,
4501        F: Fn(&[u8]) -> TelemetryResult<()> + Send + Sync + 'static,
4502    {
4503        let mut st = self.state.lock();
4504        let side = Some(RelaySide {
4505            name: Arc::from(name.as_ref()),
4506            tx_handler: RelayTxHandlerFn::Packed(Arc::new(tx)),
4507            opts,
4508        });
4509        let id = if let Some(id) = st.sides.iter().position(Option::is_none) {
4510            st.sides[id] = side;
4511            id
4512        } else {
4513            let id = st.sides.len();
4514            st.sides.push(side);
4515            id
4516        };
4517        st.side_runtime_stats
4518            .insert(id, SideRuntimeStatsInner::default());
4519        st.side_transport.insert(id, SideTransportState::default());
4520        #[cfg(feature = "discovery")]
4521        Self::note_discovery_topology_change_locked(&mut st, self.clock.now_ms());
4522        id
4523    }
4524
4525    /// Register a side whose TX callback receives decoded [`Packet`] values.
4526    ///
4527    /// Packet-output sides do not preserve the relay's packed reliable hop framing, so use a
4528    /// packed side when this hop should participate in relay-managed per-link reliability.
4529    pub fn add_side_packet<N, F>(&self, name: N, tx: F) -> RelaySideId
4530    where
4531        N: AsRef<str>,
4532        F: Fn(&Packet) -> TelemetryResult<()> + Send + Sync + 'static,
4533    {
4534        self.add_side_packet_with_options(name, tx, RelaySideOptions::default())
4535    }
4536
4537    /// Register a packet-output side with explicit side options.
4538    pub fn add_side_packet_with_options<N, F>(
4539        &self,
4540        name: N,
4541        tx: F,
4542        opts: RelaySideOptions,
4543    ) -> RelaySideId
4544    where
4545        N: AsRef<str>,
4546        F: Fn(&Packet) -> TelemetryResult<()> + Send + Sync + 'static,
4547    {
4548        let mut st = self.state.lock();
4549        let side = Some(RelaySide {
4550            name: Arc::from(name.as_ref()),
4551            tx_handler: RelayTxHandlerFn::Packet(Arc::new(tx)),
4552            opts,
4553        });
4554        let id = if let Some(id) = st.sides.iter().position(Option::is_none) {
4555            st.sides[id] = side;
4556            id
4557        } else {
4558            let id = st.sides.len();
4559            st.sides.push(side);
4560            id
4561        };
4562        st.side_runtime_stats
4563            .insert(id, SideRuntimeStatsInner::default());
4564        st.side_transport.insert(id, SideTransportState::default());
4565        #[cfg(feature = "discovery")]
4566        Self::note_discovery_topology_change_locked(&mut st, self.clock.now_ms());
4567        id
4568    }
4569
4570    /// Remove a side while keeping existing side IDs stable.
4571    ///
4572    /// `side` must be an id returned by one of the `add_side_*` calls. Remaining side ids are not
4573    /// renumbered.
4574    pub fn remove_side(&self, side: RelaySideId) -> TelemetryResult<()> {
4575        let now_ms = self.clock.now_ms();
4576        let mut st = self.state.lock();
4577        let slot = st.sides.get_mut(side).ok_or(TelemetryError::BadArg)?;
4578        if slot.is_none() {
4579            return Err(TelemetryError::BadArg);
4580        }
4581        *slot = None;
4582        while st.sides.last().is_some_and(Option::is_none) {
4583            st.sides.pop();
4584        }
4585        // Preserve capacity for link churn. It is bounded by the maximum number
4586        // of sides seen by this relay and is reclaimed when the relay drops.
4587        st.route_overrides
4588            .retain(|(src_side, dst_side), _| *src_side != Some(side) && *dst_side != side);
4589        st.typed_route_overrides
4590            .retain(|(src_side, _, dst_side), _| *src_side != Some(side) && *dst_side != side);
4591        st.route_weights
4592            .retain(|(src_side, dst_side), _| *src_side != Some(side) && *dst_side != side);
4593        st.route_priorities
4594            .retain(|(src_side, dst_side), _| *src_side != Some(side) && *dst_side != side);
4595        st.source_route_modes.remove(&Some(side));
4596        st.route_selection_cursors.remove(&Some(side));
4597        st.adaptive_route_stats.remove(&side);
4598        #[cfg(feature = "discovery")]
4599        st.discovery_side_throttle.remove(&side);
4600        #[cfg(all(feature = "discovery", feature = "timesync"))]
4601        st.timesync_side_throttle.remove(&side);
4602        st.side_runtime_stats.remove(&side);
4603        st.reliable_return_routes
4604            .retain(|_, route| route.side != side);
4605        st.rx_queue.retain(|queued| queued.src != side);
4606        st.tx_queue
4607            .retain(|queued| queued.dst != side && queued.src != Some(side));
4608        st.replay_queue.retain(|queued| queued.dst != side);
4609        st.reliable_tx.retain(|(side_id, _), _| *side_id != side);
4610        st.reliable_rx.retain(|(side_id, _), _| *side_id != side);
4611        #[cfg(feature = "discovery")]
4612        {
4613            st.discovery_routes.remove(&side);
4614            Self::note_discovery_topology_change_locked(&mut st, now_ms);
4615        }
4616        Ok(())
4617    }
4618
4619    /// Enable or disable ingress processing for a registered side.
4620    pub fn set_side_ingress_enabled(
4621        &self,
4622        side: RelaySideId,
4623        enabled: bool,
4624    ) -> TelemetryResult<()> {
4625        let now_ms = self.clock.now_ms();
4626        let mut st = self.state.lock();
4627        let side_ref = st
4628            .sides
4629            .get_mut(side)
4630            .and_then(|side| side.as_mut())
4631            .ok_or(TelemetryError::BadArg)?;
4632        side_ref.opts.ingress_enabled = enabled;
4633        #[cfg(feature = "discovery")]
4634        Self::note_discovery_topology_change_locked(&mut st, now_ms);
4635        Ok(())
4636    }
4637
4638    /// Enable or disable egress toward a registered side.
4639    pub fn set_side_egress_enabled(&self, side: RelaySideId, enabled: bool) -> TelemetryResult<()> {
4640        let now_ms = self.clock.now_ms();
4641        let mut st = self.state.lock();
4642        let side_ref = st
4643            .sides
4644            .get_mut(side)
4645            .and_then(|side| side.as_mut())
4646            .ok_or(TelemetryError::BadArg)?;
4647        side_ref.opts.egress_enabled = enabled;
4648        if !enabled {
4649            st.tx_queue.retain(|queued| queued.dst != side);
4650            st.replay_queue.retain(|queued| queued.dst != side);
4651        }
4652        #[cfg(feature = "discovery")]
4653        Self::note_discovery_topology_change_locked(&mut st, now_ms);
4654        Ok(())
4655    }
4656
4657    /// Set the route-selection policy for traffic originating from `src`.
4658    ///
4659    /// `src == None` targets locally-originated relay traffic such as discovery output.
4660    pub fn set_source_route_mode(
4661        &self,
4662        src: Option<RelaySideId>,
4663        mode: RouteSelectionMode,
4664    ) -> TelemetryResult<()> {
4665        let now_ms = self.clock.now_ms();
4666        let mut st = self.state.lock();
4667        if let Some(src) = src {
4668            let _ = Self::side_ref(&st, src).map_err(|_| TelemetryError::BadArg)?;
4669        }
4670        // Preserve explicit Fanout so discovery does not replace it with its
4671        // adaptive single-path default. Clearing is a separate operation.
4672        st.source_route_modes.insert(src, mode);
4673        st.route_selection_cursors.remove(&src);
4674        #[cfg(feature = "discovery")]
4675        Self::note_discovery_topology_change_locked(&mut st, now_ms);
4676        Ok(())
4677    }
4678
4679    /// Clear a source-specific route-selection override.
4680    pub fn clear_source_route_mode(&self, src: Option<RelaySideId>) -> TelemetryResult<()> {
4681        let now_ms = self.clock.now_ms();
4682        let mut st = self.state.lock();
4683        if let Some(src) = src {
4684            let _ = Self::side_ref(&st, src).map_err(|_| TelemetryError::BadArg)?;
4685        }
4686        st.source_route_modes.remove(&src);
4687        st.route_selection_cursors.remove(&src);
4688        #[cfg(feature = "discovery")]
4689        Self::note_discovery_topology_change_locked(&mut st, now_ms);
4690        Ok(())
4691    }
4692
4693    /// Set the weighted-routing weight from `src` toward `dst`.
4694    pub fn set_route_weight(
4695        &self,
4696        src: Option<RelaySideId>,
4697        dst: RelaySideId,
4698        weight: u32,
4699    ) -> TelemetryResult<()> {
4700        let now_ms = self.clock.now_ms();
4701        let mut st = self.state.lock();
4702        let _ = Self::side_ref(&st, dst).map_err(|_| TelemetryError::BadArg)?;
4703        if let Some(src) = src {
4704            let _ = Self::side_ref(&st, src).map_err(|_| TelemetryError::BadArg)?;
4705        }
4706        st.route_weights.insert((src, dst), weight);
4707        st.route_selection_cursors.remove(&src);
4708        #[cfg(feature = "discovery")]
4709        Self::note_discovery_topology_change_locked(&mut st, now_ms);
4710        Ok(())
4711    }
4712
4713    /// Clear a previously configured weighted-routing weight override.
4714    pub fn clear_route_weight(
4715        &self,
4716        src: Option<RelaySideId>,
4717        dst: RelaySideId,
4718    ) -> TelemetryResult<()> {
4719        let now_ms = self.clock.now_ms();
4720        let mut st = self.state.lock();
4721        let _ = Self::side_ref(&st, dst).map_err(|_| TelemetryError::BadArg)?;
4722        if let Some(src) = src {
4723            let _ = Self::side_ref(&st, src).map_err(|_| TelemetryError::BadArg)?;
4724        }
4725        st.route_weights.remove(&(src, dst));
4726        st.route_selection_cursors.remove(&src);
4727        #[cfg(feature = "discovery")]
4728        Self::note_discovery_topology_change_locked(&mut st, now_ms);
4729        Ok(())
4730    }
4731
4732    /// Set the failover priority from `src` toward `dst`.
4733    pub fn set_route_priority(
4734        &self,
4735        src: Option<RelaySideId>,
4736        dst: RelaySideId,
4737        priority: u32,
4738    ) -> TelemetryResult<()> {
4739        let now_ms = self.clock.now_ms();
4740        let mut st = self.state.lock();
4741        let _ = Self::side_ref(&st, dst).map_err(|_| TelemetryError::BadArg)?;
4742        if let Some(src) = src {
4743            let _ = Self::side_ref(&st, src).map_err(|_| TelemetryError::BadArg)?;
4744        }
4745        st.route_priorities.insert((src, dst), priority);
4746        #[cfg(feature = "discovery")]
4747        Self::note_discovery_topology_change_locked(&mut st, now_ms);
4748        Ok(())
4749    }
4750
4751    /// Clear a previously configured failover priority override.
4752    pub fn clear_route_priority(
4753        &self,
4754        src: Option<RelaySideId>,
4755        dst: RelaySideId,
4756    ) -> TelemetryResult<()> {
4757        let now_ms = self.clock.now_ms();
4758        let mut st = self.state.lock();
4759        let _ = Self::side_ref(&st, dst).map_err(|_| TelemetryError::BadArg)?;
4760        if let Some(src) = src {
4761            let _ = Self::side_ref(&st, src).map_err(|_| TelemetryError::BadArg)?;
4762        }
4763        st.route_priorities.remove(&(src, dst));
4764        #[cfg(feature = "discovery")]
4765        Self::note_discovery_topology_change_locked(&mut st, now_ms);
4766        Ok(())
4767    }
4768
4769    /// Allow or block routing from `src` toward `dst`.
4770    pub fn set_route(
4771        &self,
4772        src: Option<RelaySideId>,
4773        dst: RelaySideId,
4774        enabled: bool,
4775    ) -> TelemetryResult<()> {
4776        let now_ms = self.clock.now_ms();
4777        let mut st = self.state.lock();
4778        let _ = Self::side_ref(&st, dst).map_err(|_| TelemetryError::BadArg)?;
4779        if let Some(src) = src {
4780            let _ = Self::side_ref(&st, src).map_err(|_| TelemetryError::BadArg)?;
4781        }
4782        st.route_overrides.insert((src, dst), enabled);
4783        #[cfg(feature = "discovery")]
4784        Self::note_discovery_topology_change_locked(&mut st, now_ms);
4785        Ok(())
4786    }
4787
4788    /// Allow or block routing for a specific `DataType` from `src` toward `dst`.
4789    pub fn set_typed_route(
4790        &self,
4791        src: Option<RelaySideId>,
4792        ty: crate::DataType,
4793        dst: RelaySideId,
4794        enabled: bool,
4795    ) -> TelemetryResult<()> {
4796        let now_ms = self.clock.now_ms();
4797        let mut st = self.state.lock();
4798        let _ = Self::side_ref(&st, dst).map_err(|_| TelemetryError::BadArg)?;
4799        if let Some(src) = src {
4800            let _ = Self::side_ref(&st, src).map_err(|_| TelemetryError::BadArg)?;
4801        }
4802        st.typed_route_overrides
4803            .insert((src, ty.as_u32(), dst), enabled);
4804        #[cfg(feature = "discovery")]
4805        Self::note_discovery_topology_change_locked(&mut st, now_ms);
4806        Ok(())
4807    }
4808
4809    /// Clear a typed route override for the `(src, ty, dst)` triple.
4810    pub fn clear_typed_route(
4811        &self,
4812        src: Option<RelaySideId>,
4813        ty: crate::DataType,
4814        dst: RelaySideId,
4815    ) -> TelemetryResult<()> {
4816        let now_ms = self.clock.now_ms();
4817        let mut st = self.state.lock();
4818        let _ = Self::side_ref(&st, dst).map_err(|_| TelemetryError::BadArg)?;
4819        if let Some(src) = src {
4820            let _ = Self::side_ref(&st, src).map_err(|_| TelemetryError::BadArg)?;
4821        }
4822        st.typed_route_overrides.remove(&(src, ty.as_u32(), dst));
4823        #[cfg(feature = "discovery")]
4824        Self::note_discovery_topology_change_locked(&mut st, now_ms);
4825        Ok(())
4826    }
4827
4828    /// Clear a non-typed route override so the relay falls back to default behavior.
4829    pub fn clear_route(&self, src: Option<RelaySideId>, dst: RelaySideId) -> TelemetryResult<()> {
4830        let now_ms = self.clock.now_ms();
4831        let mut st = self.state.lock();
4832        let _ = Self::side_ref(&st, dst).map_err(|_| TelemetryError::BadArg)?;
4833        if let Some(src) = src {
4834            let _ = Self::side_ref(&st, src).map_err(|_| TelemetryError::BadArg)?;
4835        }
4836        st.route_overrides.remove(&(src, dst));
4837        #[cfg(feature = "discovery")]
4838        Self::note_discovery_topology_change_locked(&mut st, now_ms);
4839        Ok(())
4840    }
4841
4842    #[cfg(feature = "discovery")]
4843    /// Queues an immediate discovery announcement for this relay.
4844    pub fn announce_discovery(&self) -> TelemetryResult<()> {
4845        self.queue_discovery_announce(true, false)
4846    }
4847
4848    #[cfg(feature = "discovery")]
4849    /// Broadcast that this relay is leaving so peers can prune topology immediately.
4850    pub fn announce_leave(&self) -> TelemetryResult<()> {
4851        let pkt = discovery::build_discovery_leave("relay", self.clock.now_ms())?;
4852        let mut st = self.state.lock();
4853        let dsts: Vec<usize> = st
4854            .sides
4855            .iter()
4856            .enumerate()
4857            .filter_map(|(idx, side)| side.as_ref().map(|_| idx))
4858            .collect();
4859        for dst in dsts {
4860            let data = RelayItem::Packet(Arc::new(pkt.clone()));
4861            let priority = Self::relay_item_priority(&data)?;
4862            st.push_tx(RelayTxItem {
4863                src: None,
4864                dst,
4865                data,
4866                priority,
4867                retry_after_ms: 0,
4868            })?;
4869        }
4870        Ok(())
4871    }
4872
4873    #[cfg(feature = "discovery")]
4874    /// Polls discovery state and queues an announce if the cadence says one is due.
4875    pub fn poll_discovery(&self) -> TelemetryResult<bool> {
4876        self.poll_discovery_announce()
4877    }
4878
4879    #[cfg(feature = "discovery")]
4880    /// Exports the relay's current discovered topology snapshot.
4881    pub fn export_topology(&self) -> TopologySnapshot {
4882        let now_ms = self.clock.now_ms();
4883        let mut st = self.state.lock();
4884        if Self::prune_discovery_routes_locked(&mut st, now_ms) {
4885            self.reconcile_end_to_end_acked_destinations_locked(&mut st);
4886            Self::note_discovery_topology_change_locked(&mut st, now_ms);
4887        }
4888        let routes = st
4889            .discovery_routes
4890            .iter()
4891            .filter_map(|(&side_id, route)| {
4892                let side = st.sides.get(side_id).and_then(|side| side.as_ref())?;
4893                let announcers = route
4894                    .announcers
4895                    .iter()
4896                    .map(|(sender_id, sender_state)| TopologyAnnouncerRoute {
4897                        sender_id: sender_id.clone(),
4898                        reachable_endpoints: sender_state
4899                            .reachable
4900                            .iter()
4901                            .copied()
4902                            .filter(|ep| !discovery::is_router_control_endpoint(*ep))
4903                            .collect(),
4904                        reachable_network_variables: Vec::new(),
4905                        reachable_timesync_sources: sender_state.reachable_timesync_sources.clone(),
4906                        routers: sender_state.topology_boards.clone(),
4907                        last_seen_ms: sender_state.last_seen_ms,
4908                        age_ms: now_ms.saturating_sub(sender_state.last_seen_ms),
4909                    })
4910                    .collect();
4911                Some(TopologySideRoute {
4912                    side_id,
4913                    side_name: side.name.to_string(),
4914                    reachable_endpoints: route
4915                        .reachable
4916                        .iter()
4917                        .copied()
4918                        .filter(|ep| !discovery::is_router_control_endpoint(*ep))
4919                        .collect(),
4920                    reachable_network_variables: Vec::new(),
4921                    reachable_timesync_sources: route.reachable_timesync_sources.clone(),
4922                    announcers,
4923                    last_seen_ms: route.last_seen_ms,
4924                    age_ms: now_ms.saturating_sub(route.last_seen_ms),
4925                })
4926            })
4927            .collect();
4928        let routers = self.advertised_discovery_topology_for_link_locked(&st, now_ms, true, None);
4929        let advertised_endpoints =
4930            self.advertised_discovery_endpoints_for_link_locked(&st, now_ms, true, None);
4931        let advertised_timesync_sources =
4932            self.advertised_discovery_timesync_sources_for_link_locked(&st, now_ms, None);
4933        let links = discovery::topology_links_from_boards(&routers);
4934        TopologySnapshot {
4935            advertised_endpoints,
4936            advertised_timesync_sources,
4937            routers,
4938            links,
4939            routes,
4940            current_announce_interval_ms: st.discovery_cadence.current_interval_ms,
4941            next_announce_ms: st.discovery_cadence.next_announce_ms,
4942        }
4943    }
4944
4945    #[cfg(feature = "discovery")]
4946    pub fn client_stats(&self, sender_id: &str) -> Option<ClientStatsSnapshot> {
4947        let now_ms = self.clock.now_ms();
4948        let st = self.state.lock();
4949        let mut side_ids = Vec::new();
4950        let mut side_names = Vec::new();
4951        let mut last_seen_ms = None::<u64>;
4952        let mut reachable_endpoints = Vec::new();
4953        let mut reachable_timesync_sources = Vec::new();
4954        let mut packets_sent = 0u64;
4955        let mut packets_received = 0u64;
4956        let mut bytes_sent = 0u64;
4957        let mut bytes_received = 0u64;
4958
4959        for (side_id, route) in &st.discovery_routes {
4960            let Some(sender_state) = route.announcers.get(sender_id) else {
4961                continue;
4962            };
4963            side_ids.push(*side_id);
4964            if let Some(side_name) = st
4965                .sides
4966                .get(*side_id)
4967                .and_then(|side| side.as_ref())
4968                .map(|side| side.name.clone())
4969            {
4970                side_names.push(side_name.to_string());
4971            }
4972            last_seen_ms = Some(last_seen_ms.unwrap_or(0).max(sender_state.last_seen_ms));
4973            reachable_endpoints.extend(sender_state.reachable.iter().copied());
4974            reachable_timesync_sources
4975                .extend(sender_state.reachable_timesync_sources.iter().cloned());
4976            if let Some(stats) = st.side_runtime_stats.get(side_id) {
4977                packets_sent = packets_sent.saturating_add(stats.tx_packets);
4978                packets_received = packets_received.saturating_add(stats.rx_packets);
4979                bytes_sent = bytes_sent.saturating_add(stats.tx_bytes);
4980                bytes_received = bytes_received.saturating_add(stats.rx_bytes);
4981            }
4982        }
4983
4984        if side_ids.is_empty() {
4985            return None;
4986        }
4987        reachable_endpoints.retain(|ep| !discovery::is_router_control_endpoint(*ep));
4988        crate::small_sort::sort(&mut reachable_endpoints);
4989        reachable_endpoints.dedup();
4990        reachable_timesync_sources.sort_unstable();
4991        reachable_timesync_sources.dedup();
4992        crate::small_sort::sort(&mut side_ids);
4993        side_ids.dedup();
4994        side_names.sort_unstable();
4995        side_names.dedup();
4996        let age_ms = last_seen_ms.map(|seen| now_ms.saturating_sub(seen));
4997        Some(ClientStatsSnapshot {
4998            sender_id: sender_id.to_string(),
4999            connected: age_ms.is_some_and(|age| age <= DISCOVERY_ROUTE_TTL_MS),
5000            side_ids,
5001            side_names,
5002            last_seen_ms,
5003            age_ms,
5004            reachable_endpoints,
5005            reachable_timesync_sources,
5006            packets_sent,
5007            packets_received,
5008            bytes_sent,
5009            bytes_received,
5010        })
5011    }
5012
5013    pub fn export_runtime_stats(&self) -> RuntimeStatsSnapshot {
5014        let now_ms = self.clock.now_ms();
5015        let st = self.state.lock();
5016
5017        let mut sides = Vec::new();
5018        for (side_id, side) in st.sides.iter().enumerate() {
5019            let Some(side) = side.as_ref() else { continue };
5020            let stats = st
5021                .side_runtime_stats
5022                .get(&side_id)
5023                .cloned()
5024                .unwrap_or_default();
5025            let adaptive = st
5026                .adaptive_route_stats
5027                .get(&side_id)
5028                .cloned()
5029                .unwrap_or_default()
5030                .snapshot(now_ms, true);
5031            let (tx_template_count, rx_template_count) = st
5032                .side_transport
5033                .get(&side_id)
5034                .map(|state| (state.tx_template_count(), state.rx_template_count()))
5035                .unwrap_or((0, 0));
5036            let mut data_types: Vec<RuntimeTypeStats> = stats
5037                .data_types
5038                .into_iter()
5039                .map(|(ty, item)| RuntimeTypeStats {
5040                    data_type: crate::DataType(ty),
5041                    tx_packets: item.tx_packets,
5042                    tx_bytes: item.tx_bytes,
5043                    rx_packets: item.rx_packets,
5044                    rx_bytes: item.rx_bytes,
5045                    relayed_tx_packets: item.relayed_tx_packets,
5046                    relayed_tx_bytes: item.relayed_tx_bytes,
5047                    relayed_rx_packets: item.relayed_rx_packets,
5048                    relayed_rx_bytes: item.relayed_rx_bytes,
5049                    tx_retries: item.tx_retries,
5050                    handler_failures: item.handler_failures,
5051                })
5052                .collect();
5053            data_types.sort_unstable_by_key(|item| item.data_type.as_u32());
5054            sides.push(RuntimeSideStats {
5055                side_id,
5056                side_name: side.name.to_string(),
5057                reliable_enabled: side.opts.reliable_enabled,
5058                link_local_enabled: side.opts.link_local_enabled,
5059                header_template_enabled: side.opts.header_template_enabled,
5060                max_frame_bytes: side.opts.max_frame_bytes,
5061                compact_header_target_bytes: side.opts.compact_header_target_bytes,
5062                side_transport_profile: side.opts.effective_transport_profile().as_str(),
5063                ingress_enabled: side.opts.ingress_enabled,
5064                egress_enabled: side.opts.egress_enabled,
5065                tx_packets: stats.tx_packets,
5066                tx_bytes: stats.tx_bytes,
5067                rx_packets: stats.rx_packets,
5068                rx_bytes: stats.rx_bytes,
5069                relayed_tx_packets: stats.relayed_tx_packets,
5070                relayed_tx_bytes: stats.relayed_tx_bytes,
5071                relayed_rx_packets: stats.relayed_rx_packets,
5072                relayed_rx_bytes: stats.relayed_rx_bytes,
5073                local_delivery_packets: 0,
5074                tx_retries: stats.tx_retries,
5075                tx_handler_failures: stats.tx_handler_failures,
5076                local_handler_failures: 0,
5077                total_handler_retries: stats.total_handler_retries,
5078                side_transport_full_frames: stats.side_transport_full_frames,
5079                side_transport_compact_frames: stats.side_transport_compact_frames,
5080                side_transport_compact_delta_frames: stats.side_transport_compact_delta_frames,
5081                side_transport_compact_omitted_timestamp_frames: stats
5082                    .side_transport_compact_omitted_timestamp_frames,
5083                side_transport_chunk_frames: stats.side_transport_chunk_frames,
5084                side_transport_raw_bytes: stats.side_transport_raw_bytes,
5085                side_transport_wire_bytes: stats.side_transport_wire_bytes,
5086                side_transport_bytes_saved: stats.side_transport_bytes_saved,
5087                side_transport_min_compact_overhead_bytes: stats
5088                    .side_transport_min_compact_overhead_bytes,
5089                side_transport_max_compact_overhead_bytes: stats
5090                    .side_transport_max_compact_overhead_bytes,
5091                side_transport_compact_target_misses: stats.side_transport_compact_target_misses,
5092                side_transport_template_evictions: stats.side_transport_template_evictions,
5093                side_transport_tx_template_count: tx_template_count,
5094                side_transport_rx_template_count: rx_template_count,
5095                max_side_transport_templates: side.opts.max_side_transport_templates,
5096                adaptive,
5097                data_types,
5098            });
5099        }
5100
5101        let mut route_modes: Vec<RouteModeStats> = st
5102            .route_selection_cursors
5103            .iter()
5104            .map(|(src, cursor)| RouteModeStats {
5105                src_side_id: *src,
5106                selection_mode: st.source_route_modes.get(src).copied(),
5107                cursor: *cursor,
5108            })
5109            .collect();
5110        for src in st.source_route_modes.keys() {
5111            if !route_modes.iter().any(|mode| mode.src_side_id == *src) {
5112                route_modes.push(RouteModeStats {
5113                    src_side_id: *src,
5114                    selection_mode: st.source_route_modes.get(src).copied(),
5115                    cursor: 0,
5116                });
5117            }
5118        }
5119        route_modes.sort_unstable_by_key(|mode| mode.src_side_id.unwrap_or(usize::MAX));
5120
5121        let mut route_overrides: Vec<RouteOverrideStats> = st
5122            .route_overrides
5123            .iter()
5124            .map(|((src, dst), enabled)| RouteOverrideStats {
5125                src_side_id: *src,
5126                dst_side_id: *dst,
5127                enabled: *enabled,
5128            })
5129            .collect();
5130        route_overrides.sort_unstable_by_key(|item| {
5131            (item.src_side_id.unwrap_or(usize::MAX), item.dst_side_id)
5132        });
5133
5134        let mut typed_route_overrides: Vec<TypedRouteOverrideStats> = st
5135            .typed_route_overrides
5136            .iter()
5137            .map(|((src, ty, dst), enabled)| TypedRouteOverrideStats {
5138                src_side_id: *src,
5139                data_type: crate::DataType(*ty),
5140                dst_side_id: *dst,
5141                enabled: *enabled,
5142            })
5143            .collect();
5144        typed_route_overrides.sort_unstable_by_key(|item| {
5145            (
5146                item.src_side_id.unwrap_or(usize::MAX),
5147                item.data_type.as_u32(),
5148                item.dst_side_id,
5149            )
5150        });
5151
5152        let mut route_weights: Vec<RouteWeightStats> = st
5153            .route_weights
5154            .iter()
5155            .map(|((src, dst), weight)| RouteWeightStats {
5156                src_side_id: *src,
5157                dst_side_id: *dst,
5158                weight: *weight,
5159            })
5160            .collect();
5161        route_weights.sort_unstable_by_key(|item| {
5162            (item.src_side_id.unwrap_or(usize::MAX), item.dst_side_id)
5163        });
5164
5165        let mut route_priorities: Vec<RoutePriorityStats> = st
5166            .route_priorities
5167            .iter()
5168            .map(|((src, dst), priority)| RoutePriorityStats {
5169                src_side_id: *src,
5170                dst_side_id: *dst,
5171                priority: *priority,
5172            })
5173            .collect();
5174        route_priorities.sort_unstable_by_key(|item| {
5175            (item.src_side_id.unwrap_or(usize::MAX), item.dst_side_id)
5176        });
5177
5178        #[cfg(feature = "discovery")]
5179        let discovery = DiscoveryRuntimeStats {
5180            route_count: st.discovery_routes.len(),
5181            announcer_count: st
5182                .discovery_routes
5183                .values()
5184                .map(|route| route.announcers.len())
5185                .sum(),
5186            current_announce_interval_ms: Some(st.discovery_cadence.current_interval_ms),
5187            next_announce_ms: Some(st.discovery_cadence.next_announce_ms),
5188        };
5189        #[cfg(not(feature = "discovery"))]
5190        let discovery = DiscoveryRuntimeStats {
5191            route_count: 0,
5192            announcer_count: 0,
5193            current_announce_interval_ms: None,
5194            next_announce_ms: None,
5195        };
5196
5197        RuntimeStatsSnapshot {
5198            sides,
5199            route_modes,
5200            route_overrides,
5201            typed_route_overrides,
5202            route_weights,
5203            route_priorities,
5204            queues: QueueRuntimeStats {
5205                rx_len: st.rx_queue.len(),
5206                rx_bytes: st.rx_queue.bytes_used(),
5207                tx_len: st.tx_queue.len(),
5208                tx_bytes: st.tx_queue.bytes_used(),
5209                replay_len: st.replay_queue.len(),
5210                replay_bytes: st.replay_queue.bytes_used(),
5211                recent_rx_len: st.recent_rx.len(),
5212                recent_rx_bytes: st.recent_rx.bytes_used(),
5213                reliable_rx_buffered_len: st.reliable_rx_buffer_len(),
5214                reliable_rx_buffered_bytes: st.reliable_rx_buffered_bytes(),
5215                shared_queue_bytes_used: st.shared_queue_bytes_used(),
5216            },
5217            reliable: ReliableRuntimeStats {
5218                reliable_return_route_count: st.reliable_return_routes.len(),
5219                end_to_end_pending_count: 0,
5220                end_to_end_pending_destination_count: 0,
5221                end_to_end_acked_cache_count: st.end_to_end_acked_destinations.len(),
5222            },
5223            discovery,
5224            total_handler_failures: st.total_handler_failures,
5225            total_handler_retries: st.total_handler_retries,
5226        }
5227    }
5228
5229    /// Export current relay memory usage/layout as JSON for profiling.
5230    pub fn export_memory_layout_json(&self) -> String {
5231        let st = self.state.lock();
5232        #[cfg(feature = "discovery")]
5233        let discovery_bytes = st.discovery_bytes_used();
5234        #[cfg(not(feature = "discovery"))]
5235        let discovery_bytes = 0usize;
5236        let schema_bytes = crate::config::schema_bytes_used();
5237        let memory = st.memory;
5238        let mut out = String::new();
5239        let _ = core::fmt::Write::write_fmt(
5240            &mut out,
5241            format_args!(
5242                "{{\"kind\":\"relay\",\
5243                 \"shared_queue_bytes_used\":{},\"shared_queue_bytes_allocated\":{},\
5244                 \"rx_queue_bytes_used\":{},\"rx_queue_bytes_allocated\":{},\"rx_queue_len\":{},\
5245                 \"tx_queue_bytes_used\":{},\"tx_queue_bytes_allocated\":{},\"tx_queue_len\":{},\
5246                 \"replay_queue_bytes_used\":{},\"replay_queue_bytes_allocated\":{},\"replay_queue_len\":{},\
5247                 \"recent_rx_bytes_used\":{},\"recent_rx_bytes_allocated\":{},\"recent_rx_len\":{},\
5248                 \"reliable_rx_buffer_bytes_used\":{},\"reliable_rx_buffer_bytes_allocated\":{},\"reliable_rx_buffer_len\":{},\
5249                 \"discovery_bytes_used\":{},\"discovery_bytes_allocated\":{},\
5250                 \"schema_bytes_used\":{},\"schema_bytes_allocated\":{}}}",
5251                st.shared_queue_bytes_used(),
5252                memory.max_queue_budget,
5253                st.rx_queue.bytes_used(),
5254                st.rx_queue.max_bytes(),
5255                st.rx_queue.len(),
5256                st.tx_queue.bytes_used(),
5257                st.tx_queue.max_bytes(),
5258                st.tx_queue.len(),
5259                st.replay_queue.bytes_used(),
5260                st.replay_queue.max_bytes(),
5261                st.replay_queue.len(),
5262                st.recent_rx.bytes_used(),
5263                st.recent_rx.max_bytes(),
5264                st.recent_rx.len(),
5265                st.reliable_rx_buffered_bytes(),
5266                memory.max_queue_budget,
5267                st.reliable_rx_buffer_len(),
5268                discovery_bytes,
5269                memory.max_queue_budget,
5270                schema_bytes,
5271                memory.max_queue_budget,
5272            ),
5273        );
5274        out
5275    }
5276
5277    #[cfg(test)]
5278    pub(crate) fn debug_end_to_end_acked_destination_count(&self, packet_id: u64) -> Option<usize> {
5279        let st = self.state.lock();
5280        st.end_to_end_acked_destinations
5281            .get(&packet_id)
5282            .map(BTreeSet::len)
5283    }
5284
5285    #[cfg(test)]
5286    pub(crate) fn debug_end_to_end_acked_packet_count(&self) -> usize {
5287        let st = self.state.lock();
5288        st.end_to_end_acked_destinations.len()
5289    }
5290
5291    #[cfg(test)]
5292    pub(crate) fn debug_reliable_return_route_count(&self) -> usize {
5293        let st = self.state.lock();
5294        st.reliable_return_routes.len()
5295    }
5296
5297    #[cfg(test)]
5298    pub(crate) fn debug_side_storage(&self) -> (usize, usize) {
5299        let st = self.state.lock();
5300        (st.sides.len(), st.sides.capacity())
5301    }
5302
5303    /// Enqueue packed bytes that originated from `src` into the relay RX queue.
5304    ///
5305    /// Note: `Arc::from(bytes)` allocates and copies `len` bytes into a new `crate::SharedBytes`.
5306    /// This is still “fast enough” for many cases, but it is not allocation-free / ISR-safe.
5307    pub fn rx_packed_from_side(&self, src: RelaySideId, bytes: &[u8]) -> TelemetryResult<()> {
5308        self.ensure_side_ingress_enabled(src)?;
5309        let Some(bytes) = self.decode_side_transport_frame(src, bytes)? else {
5310            return Ok(());
5311        };
5312        let mut st = self.state.lock();
5313
5314        let data = RelayItem::Packed(bytes);
5315        let priority = Self::relay_item_priority(&data)?;
5316        st.push_rx(RelayRxItem {
5317            src,
5318            data,
5319            priority,
5320        })
5321    }
5322
5323    /// Enqueue a full packet that originated from `src` into the relay RX queue.
5324    ///
5325    /// The packet is wrapped in `Arc<Packet>` so fanout can clone the pointer cheaply.
5326    pub fn rx_from_side(&self, src: RelaySideId, packet: Packet) -> TelemetryResult<()> {
5327        self.ensure_side_ingress_enabled(src)?;
5328        let mut st = self.state.lock();
5329
5330        let data = RelayItem::Packet(Arc::new(packet));
5331        let priority = Self::relay_item_priority(&data)?;
5332        st.push_rx(RelayRxItem {
5333            src,
5334            data,
5335            priority,
5336        })
5337    }
5338
5339    /// Clear both RX and TX queues.
5340    pub fn clear_queues(&self) {
5341        let mut st = self.state.lock();
5342        st.rx_queue.clear();
5343        st.tx_queue.clear();
5344        st.tx_priority_burst = 0;
5345    }
5346
5347    /// Clear only RX queue.
5348    pub fn clear_rx_queue(&self) {
5349        let mut st = self.state.lock();
5350        st.rx_queue.clear();
5351    }
5352
5353    /// Clear only TX queue.
5354    pub fn clear_tx_queue(&self) {
5355        let mut st = self.state.lock();
5356        st.tx_queue.clear();
5357        st.tx_priority_burst = 0;
5358        st.replay_queue.clear();
5359    }
5360
5361    /// Internal: expand one RX item into TX items for all other sides.
5362    ///
5363    /// Fanout is cheap: the `RelayItem` is cloned (Arc bump) and reused across all destinations.
5364    fn drain_queued_receipts_before_timeouts(
5365        &self,
5366        start: u64,
5367        timeout_ms: u32,
5368    ) -> TelemetryResult<bool> {
5369        let mut did_any = false;
5370        loop {
5371            let item = {
5372                let mut st = self.state.lock();
5373                let index = st.rx_queue.iter().position(|item| {
5374                    let ty = match &item.data {
5375                        RelayItem::Packet(pkt) => Some(pkt.data_type()),
5376                        RelayItem::Packed(bytes) => wire_format::scheduling_type(bytes.as_ref()),
5377                    };
5378                    matches!(
5379                        ty,
5380                        Some(
5381                            crate::DataType::ReliableAck
5382                                | crate::DataType::ReliablePartialAck
5383                                | crate::DataType::ReliablePacketRequest
5384                        )
5385                    )
5386                });
5387                index.and_then(|index| st.rx_queue.remove_pos(index))
5388            };
5389            let Some(item) = item else {
5390                return Ok(did_any);
5391            };
5392            self.process_rx_queue_item(item)?;
5393            did_any = true;
5394            if timeout_ms != 0 && self.clock.now_ms().wrapping_sub(start) >= u64::from(timeout_ms) {
5395                return Ok(did_any);
5396            }
5397        }
5398    }
5399
5400    fn process_rx_queue_item(&self, item: RelayRxItem) -> TelemetryResult<()> {
5401        if crate::memory_admission::supports_work_kinds()
5402            && let RelayItem::Packed(bytes) = &item.data
5403        {
5404            self.admit_received_frame(item.src, bytes)?;
5405        }
5406        self.ensure_side_ingress_enabled(item.src)?;
5407        match &item.data {
5408            RelayItem::Packet(pkt) => {
5409                pkt.validate()?;
5410                #[cfg(feature = "discovery")]
5411                self.refresh_peer_liveness(item.src, sender_address_u32(pkt.sender()));
5412                let bytes = wire_format::packet_wire_size(pkt);
5413                self.note_side_rx(item.src, pkt.data_type(), bytes);
5414            }
5415            RelayItem::Packed(bytes) => {
5416                if let Ok(env) = wire_format::peek_routing_envelope(bytes.as_ref()) {
5417                    self.note_side_rx(item.src, env.ty, bytes.len());
5418                }
5419            }
5420        }
5421        match &item.data {
5422            RelayItem::Packet(pkt) => {
5423                if is_reliable_type(pkt.data_type()) && !is_internal_control_type(pkt.data_type()) {
5424                    self.note_reliable_return_route(item.src, pkt.packet_id());
5425                }
5426            }
5427            RelayItem::Packed(bytes) => {
5428                if let Ok(env) = wire_format::peek_routing_envelope(bytes.as_ref())
5429                    && is_reliable_type(env.ty)
5430                    && !is_internal_control_type(env.ty)
5431                    && let Ok(packet_id) = wire_format::packet_id_from_wire(bytes.as_ref())
5432                {
5433                    self.note_reliable_return_route(item.src, packet_id);
5434                }
5435            }
5436        }
5437        let mut released_buffered: Vec<crate::SharedBytes> = Vec::new();
5438        if let RelayItem::Packed(bytes) = &item.data {
5439            let (_opts, handler_is_packed, hop_reliable_enabled) = {
5440                let st = self.state.lock();
5441                let side_ref = Self::side_ref(&st, item.src)?;
5442                let opts = side_ref.opts.clone();
5443                (
5444                    opts.clone(),
5445                    matches!(side_ref.tx_handler, RelayTxHandlerFn::Packed(_)),
5446                    opts.reliable_enabled
5447                        && !self.side_has_multiple_announcers_locked(
5448                            &st,
5449                            item.src,
5450                            self.clock.now_ms(),
5451                        ),
5452                )
5453            };
5454
5455            let frame = match wire_format::peek_frame_info(bytes.as_ref()) {
5456                Ok(frame) => frame,
5457                Err(e) => {
5458                    if matches!(e, TelemetryError::Unpack(msg) if msg == "crc32 mismatch")
5459                        && hop_reliable_enabled
5460                        && handler_is_packed
5461                        && let Ok(frame) = wire_format::peek_frame_info_unchecked(bytes.as_ref())
5462                    {
5463                        if is_reliable_type(frame.envelope.ty)
5464                            && let Some(hdr) = frame.reliable
5465                        {
5466                            let unordered = (hdr.flags & wire_format::RELIABLE_FLAG_UNORDERED) != 0;
5467                            let unsequenced =
5468                                (hdr.flags & wire_format::RELIABLE_FLAG_UNSEQUENCED) != 0;
5469
5470                            if !unsequenced {
5471                                let requested = if unordered {
5472                                    hdr.seq
5473                                } else {
5474                                    let mut st = self.state.lock();
5475                                    let rx_state = self.reliable_rx_state_mut(
5476                                        &mut st,
5477                                        item.src,
5478                                        frame.envelope.ty,
5479                                    );
5480                                    rx_state.expected_seq.min(hdr.seq)
5481                                };
5482                                self.queue_reliable_packet_request(
5483                                    item.src,
5484                                    frame.envelope.ty,
5485                                    requested,
5486                                )?;
5487                            }
5488                        }
5489                        return Ok(());
5490                    }
5491                    return Err(e);
5492                }
5493            };
5494
5495            if hop_reliable_enabled
5496                && handler_is_packed
5497                && is_reliable_type(frame.envelope.ty)
5498                && let Some(hdr) = frame.reliable
5499            {
5500                if frame.ack_only() {
5501                    self.handle_reliable_ack(item.src, frame.envelope.ty, hdr.ack);
5502                    return Ok(());
5503                }
5504                let unordered = (hdr.flags & wire_format::RELIABLE_FLAG_UNORDERED) != 0;
5505                let unsequenced = (hdr.flags & wire_format::RELIABLE_FLAG_UNSEQUENCED) != 0;
5506
5507                if !unsequenced {
5508                    if unordered {
5509                        self.queue_reliable_ack(item.src, frame.envelope.ty, hdr.seq)?;
5510                    } else {
5511                        let mut release: Vec<crate::SharedBytes> = Vec::new();
5512                        let mut last_delivered = None;
5513                        let mut ack_old = None;
5514                        let mut request_missing = None;
5515                        let mut partial_ack = None;
5516                        {
5517                            let mut st = self.state.lock();
5518                            let rx_state =
5519                                self.reliable_rx_state_mut(&mut st, item.src, frame.envelope.ty);
5520                            let expected_seq = rx_state.expected_seq;
5521                            if hdr.seq < expected_seq {
5522                                ack_old = Some(expected_seq.saturating_sub(1));
5523                            } else if hdr.seq > expected_seq {
5524                                request_missing = Some(expected_seq);
5525                                partial_ack = Some(hdr.seq);
5526                                st.buffer_reliable_rx(
5527                                    item.src,
5528                                    frame.envelope.ty,
5529                                    hdr.seq,
5530                                    bytes.clone(),
5531                                )?;
5532                            } else {
5533                                release.push(bytes.clone());
5534                                last_delivered = Some(hdr.seq);
5535                                let mut next_expected = hdr.seq.wrapping_add(1);
5536                                while let Some(buf) = rx_state.buffered.remove(&next_expected) {
5537                                    release.push(buf);
5538                                    last_delivered = Some(next_expected);
5539                                    let next = next_expected.wrapping_add(1);
5540                                    next_expected = if next == 0 { 1 } else { next };
5541                                }
5542                                rx_state.expected_seq = next_expected;
5543                            }
5544                        }
5545
5546                        if let Some(ack_seq) = ack_old {
5547                            self.queue_reliable_ack(item.src, frame.envelope.ty, ack_seq)?;
5548                            return Ok(());
5549                        }
5550                        if let Some(request_seq) = request_missing {
5551                            if let Some(partial_seq) = partial_ack {
5552                                self.queue_reliable_partial_ack(
5553                                    item.src,
5554                                    frame.envelope.ty,
5555                                    partial_seq,
5556                                )?;
5557                            }
5558                            self.queue_reliable_packet_request(
5559                                item.src,
5560                                frame.envelope.ty,
5561                                request_seq,
5562                            )?;
5563                            return Ok(());
5564                        }
5565                        if let Some(ack_seq) = last_delivered {
5566                            self.queue_reliable_ack(item.src, frame.envelope.ty, ack_seq)?;
5567                        }
5568                        released_buffered.extend(release.into_iter().skip(1));
5569                    }
5570                }
5571            }
5572        }
5573
5574        if self.is_duplicate_pkt(&item)? && !self.should_forward_duplicate_reliable_item(&item)? {
5575            // Already fanned out this packet recently; skip.
5576            return Ok(());
5577        }
5578
5579        self.dispatch_relay_rx_item(&item)?;
5580
5581        for release_bytes in released_buffered {
5582            let release_item = RelayRxItem {
5583                src: item.src,
5584                priority: Self::relay_item_priority(&RelayItem::Packed(release_bytes.clone()))?,
5585                data: RelayItem::Packed(release_bytes),
5586            };
5587            if self.is_duplicate_pkt(&release_item)?
5588                && !self.should_forward_duplicate_reliable_item(&release_item)?
5589            {
5590                continue;
5591            }
5592            self.dispatch_relay_rx_item(&release_item)?;
5593        }
5594        Ok(())
5595    }
5596
5597    fn dispatch_relay_rx_item(&self, item: &RelayRxItem) -> TelemetryResult<()> {
5598        match &item.data {
5599            RelayItem::Packet(pkt) => {
5600                if matches!(
5601                    pkt.data_type(),
5602                    crate::DataType::ReliableAck
5603                        | crate::DataType::ReliablePartialAck
5604                        | crate::DataType::ReliablePacketRequest
5605                ) {
5606                    if Self::end_to_end_ack_sender_hash(pkt).is_some()
5607                        && Self::decode_end_to_end_reliable_ack(pkt.payload()).is_ok()
5608                    {
5609                        if let Ok(packet_id) = Self::decode_end_to_end_reliable_ack(pkt.payload())
5610                            && let Some(sender_hash) = Self::end_to_end_ack_sender_hash(pkt)
5611                        {
5612                            let mut st = self.state.lock();
5613                            Self::note_end_to_end_acked_destination_locked(
5614                                &mut st,
5615                                packet_id,
5616                                sender_hash,
5617                            );
5618                        }
5619                    } else {
5620                        let vals = pkt.data_as_u32()?;
5621                        if vals.len() != 2 {
5622                            return Err(TelemetryError::Unpack("bad reliable control payload"));
5623                        }
5624                        let ty = crate::DataType::try_from_u32(vals[0])
5625                            .ok_or(TelemetryError::InvalidType)?;
5626                        let seq = vals[1];
5627                        match pkt.data_type() {
5628                            crate::DataType::ReliableAck => {
5629                                self.handle_reliable_ack(item.src, ty, seq)
5630                            }
5631                            crate::DataType::ReliablePartialAck => {
5632                                self.handle_reliable_partial_ack(item.src, ty, seq)
5633                            }
5634                            crate::DataType::ReliablePacketRequest => {
5635                                self.queue_reliable_retransmit(item.src, ty, seq)?
5636                            }
5637                            _ => {}
5638                        }
5639                        return Ok(());
5640                    }
5641                }
5642            }
5643            RelayItem::Packed(bytes) => {
5644                let env = wire_format::peek_routing_envelope(bytes.as_ref())?;
5645                if matches!(
5646                    env.ty,
5647                    crate::DataType::ReliableAck
5648                        | crate::DataType::ReliablePacketRequest
5649                        | crate::DataType::ReliablePartialAck
5650                ) {
5651                    let pkt = wire_format::unpack_packet(bytes.as_ref())?;
5652                    return self.dispatch_relay_rx_item(&RelayRxItem {
5653                        src: item.src,
5654                        data: RelayItem::Packet(Arc::new(pkt)),
5655                        priority: item.priority,
5656                    });
5657                }
5658            }
5659        }
5660
5661        let src = item.src;
5662        let data = item.data.clone();
5663        self.learn_discovery_item(src, &data)?;
5664
5665        let plan = self.remote_side_plan(&data, src)?;
5666        let mut st = self.state.lock();
5667        let RemoteSidePlan::Target(sides) = plan;
5668        for dst in sides {
5669            let priority = Self::relay_item_priority(&data)?;
5670            st.push_tx(RelayTxItem {
5671                src: Some(src),
5672                dst,
5673                data: data.clone(),
5674                priority,
5675                retry_after_ms: 0,
5676            })?;
5677        }
5678        Ok(())
5679    }
5680
5681    fn run_side_chunk_transfer<T>(
5682        &self,
5683        side: RelaySideId,
5684        key: u64,
5685        send: impl FnOnce(&mut usize) -> TelemetryResult<T>,
5686    ) -> TelemetryResult<T> {
5687        let mut next = {
5688            let mut st = self.state.lock();
5689            let transport = st
5690                .side_transport
5691                .get_mut(&side)
5692                .ok_or(TelemetryError::BadArg)?;
5693            if transport.tx_chunk_progress.is_none() {
5694                crate::memory_admission::check(
5695                    512,
5696                    core::mem::size_of::<crate::side_transport::SideTxProgress>(),
5697                )?;
5698                transport.tx_chunk_progress =
5699                    Some(Box::new(crate::side_transport::SideTxProgress::default()));
5700            }
5701            transport
5702                .tx_chunk_progress
5703                .as_mut()
5704                .expect("initialized")
5705                .begin(key, self.clock.now_ms())?
5706        };
5707        let result = send(&mut next);
5708        if let Some(progress) = self
5709            .state
5710            .lock()
5711            .side_transport
5712            .get_mut(&side)
5713            .and_then(|transport| transport.tx_chunk_progress.as_mut())
5714        {
5715            progress.finish(key, next, self.clock.now_ms(), &result);
5716        }
5717        result
5718    }
5719
5720    fn send_side_frames_resumable(
5721        &self,
5722        side: RelaySideId,
5723        mut frames: SideTransportFrames,
5724        mut send: impl FnMut(&[u8]) -> TelemetryResult<()>,
5725    ) -> TelemetryResult<()> {
5726        let bootstraps_template = frames.bootstraps_template();
5727        let result = if frames.len() == 1 {
5728            frames.send_resuming(&mut 0, send)
5729        } else {
5730            let key = frames.progress_key();
5731            self.run_side_chunk_transfer(side, key, |next| frames.send_resuming(next, &mut send))
5732        };
5733        if result.is_err() && bootstraps_template {
5734            // Encoding has already inserted the TX template, but a busy or
5735            // failed handler did not accept its bootstrap. Keep dictionary
5736            // eviction history and force a full refresh on the next attempt.
5737            // Never clear RX templates or allocate during this recovery.
5738            if let Some(state) = self.state.lock().side_transport.get_mut(&side) {
5739                state.refresh_transmit_header_templates();
5740            }
5741        }
5742        result
5743    }
5744
5745    fn split_side_transport_frame(
5746        &self,
5747        _side: RelaySideId,
5748        frame: crate::SharedBytes,
5749        max_frame_bytes: usize,
5750    ) -> TelemetryResult<SideTransportFrames> {
5751        SideTransportFrames::split(frame, max_frame_bytes, self.sender_arc().as_bytes())
5752    }
5753
5754    fn encode_side_transport_frames(
5755        &self,
5756        side: RelaySideId,
5757        opts: RelaySideOptions,
5758        raw: crate::SharedBytes,
5759    ) -> TelemetryResult<SideTransportFrames> {
5760        if !opts.header_template_enabled && opts.max_frame_bytes == 0 {
5761            return Ok(SideTransportFrames::single(raw));
5762        }
5763        let raw_len = raw.len();
5764        if opts.max_frame_bytes != 0 && raw_len > opts.max_frame_bytes {
5765            // Canonical bytes keep a partially accepted transfer independent
5766            // of dictionary churn while other traffic is serviced.
5767            let frames = self.split_side_transport_frame(side, raw, opts.max_frame_bytes)?;
5768            let mut st = self.state.lock();
5769            let stats = st.side_runtime_stats.entry(side).or_default();
5770            stats.note_side_transport_full(raw_len, frames.wire_len());
5771            stats.note_side_transport_chunks(frames.len());
5772            return Ok(frames);
5773        }
5774
5775        if opts.header_template_enabled {
5776            // Shared buses cannot retain one coherent dictionary across
5777            // independent producers. Do not build owned templates or wrap
5778            // canonical bytes when no dictionary can be used.
5779            let frame = wire_format::peek_routing_frame_info(raw.as_ref())?;
5780            let without_dictionary = opts.max_side_transport_templates == 0 || {
5781                let st = self.state.lock();
5782                self.side_has_multiple_announcers_locked(&st, side, self.clock.now_ms())
5783            };
5784            let reliable = is_reliable_type(frame.envelope.ty)
5785                || frame.reliable.is_some()
5786                || matches!(
5787                    frame.envelope.ty,
5788                    crate::DataType::ReliableAck
5789                        | crate::DataType::ReliablePartialAck
5790                        | crate::DataType::ReliablePacketRequest
5791                );
5792            if without_dictionary || reliable {
5793                let mut st = self.state.lock();
5794                if without_dictionary {
5795                    let removed = st
5796                        .side_transport
5797                        .get_mut(&side)
5798                        .ok_or(TelemetryError::BadArg)?
5799                        .trim_tx_templates_to(0);
5800                    for _ in 0..removed {
5801                        st.side_runtime_stats
5802                            .entry(side)
5803                            .or_default()
5804                            .note_side_transport_template_eviction();
5805                    }
5806                }
5807                st.side_runtime_stats
5808                    .entry(side)
5809                    .or_default()
5810                    .note_side_transport_full(raw_len, raw_len);
5811                return Ok(SideTransportFrames::single(raw));
5812            }
5813        }
5814
5815        let mut compact_payload_len = None;
5816        let mut used_compact = false;
5817        let used_timestamp_delta = false;
5818        let omitted_timestamp = false;
5819        let (template, _ty, flags, data_size, timestamp, nonce, reliable_seq_ack, payload) =
5820            extract_side_header_template_view(raw.as_ref())?;
5821        // Reliable frames never use dictionary compression. Send the native,
5822        // self-describing wire packet without allocating/retaining an unused
5823        // template on either peer. Raw packets are accepted by both decoders.
5824        if is_reliable_type(_ty)
5825            || reliable_seq_ack.is_some()
5826            || matches!(
5827                _ty,
5828                crate::DataType::ReliableAck
5829                    | crate::DataType::ReliablePartialAck
5830                    | crate::DataType::ReliablePacketRequest
5831            )
5832        {
5833            let frames = if opts.max_frame_bytes != 0 && raw_len > opts.max_frame_bytes {
5834                self.split_side_transport_frame(side, raw, opts.max_frame_bytes)?
5835            } else {
5836                SideTransportFrames::single(raw)
5837            };
5838            let wire_len = frames.wire_len();
5839            let mut st = self.state.lock();
5840            let stats = st.side_runtime_stats.entry(side).or_default();
5841            stats.note_side_transport_full(raw_len, wire_len);
5842            if frames.len() > 1 {
5843                stats.note_side_transport_chunks(frames.len());
5844            }
5845            return Ok(frames);
5846        }
5847        let (template_id, use_compact, _previous_timestamp) = {
5848            let mut st = self.state.lock();
5849            let side_state = st
5850                .side_transport
5851                .get_mut(&side)
5852                .ok_or(TelemetryError::BadArg)?;
5853            if let Some(id) = side_state.tx_template_ids.get(&template.hash).copied() {
5854                let previous = side_state.tx_last_timestamps.get(&id).copied();
5855                const FULL_REFRESH_AFTER_COMPACT_FRAMES: u8 = 8;
5856                let compact_uses = side_state.tx_compact_uses.entry(id).or_default();
5857                // Discovery must bootstrap even when a receiver lost its dictionary.
5858                #[cfg(feature = "discovery")]
5859                let discovery_refresh = discovery::is_discovery_type(_ty);
5860                #[cfg(not(feature = "discovery"))]
5861                let discovery_refresh = false;
5862                let use_compact =
5863                    *compact_uses < FULL_REFRESH_AFTER_COMPACT_FRAMES && !discovery_refresh;
5864                if use_compact {
5865                    *compact_uses = compact_uses.saturating_add(1);
5866                } else {
5867                    *compact_uses = 0;
5868                }
5869                (id, use_compact, previous)
5870            } else {
5871                let next = side_state.next_template_id.wrapping_add(1).max(1);
5872                side_state.next_template_id = next;
5873                let evicted = side_state.insert_tx_template(
5874                    template.into_owned(),
5875                    next,
5876                    opts.max_side_transport_templates,
5877                );
5878                if evicted {
5879                    st.side_runtime_stats
5880                        .entry(side)
5881                        .or_default()
5882                        .note_side_transport_template_eviction();
5883                }
5884                if let Some(side_state) = st.side_transport.get_mut(&side)
5885                    && opts.max_side_transport_templates > 0
5886                {
5887                    side_state.tx_last_timestamps.insert(next, timestamp);
5888                }
5889                (next, false, None)
5890            }
5891        };
5892        let wrapped = if use_compact {
5893            used_compact = true;
5894            compact_payload_len = Some(payload.len());
5895            // A previous transmitted frame is not a confirmed receive base.
5896            // Absolute timestamps preserve identity after loss or reordering.
5897            let timestamp_field = Some(timestamp);
5898            let mut body = Vec::with_capacity(payload.len() + 32);
5899            body.push(flags);
5900            write_uleb128_local(u64::from(template_id), &mut body);
5901            write_uleb128_local(data_size, &mut body);
5902            if let Some(timestamp_field) = timestamp_field {
5903                write_uleb128_local(timestamp_field, &mut body);
5904            }
5905            if (flags & SIDE_TRANSPORT_FLAG_PACKET_NONCE) != 0 {
5906                write_uleb128_local(u64::from(nonce), &mut body);
5907            }
5908            if let Some((seq, ack)) = reliable_seq_ack {
5909                write_uleb128_local(u64::from(seq), &mut body);
5910                write_uleb128_local(u64::from(ack), &mut body);
5911            }
5912            body.extend_from_slice(payload);
5913            {
5914                let mut st = self.state.lock();
5915                if let Some(side_state) = st.side_transport.get_mut(&side) {
5916                    side_state.tx_last_timestamps.insert(template_id, timestamp);
5917                }
5918            }
5919            let kind = if omitted_timestamp {
5920                SIDE_TRANSPORT_KIND_COMPACT_SAME_TIMESTAMP
5921            } else if used_timestamp_delta {
5922                SIDE_TRANSPORT_KIND_COMPACT_DELTA
5923            } else {
5924                SIDE_TRANSPORT_KIND_COMPACT
5925            };
5926            wrap_side_transport_frame(kind, &body)
5927        } else {
5928            let mut body = Vec::with_capacity(raw.len() + 4);
5929            write_uleb128_local(u64::from(template_id), &mut body);
5930            body.extend_from_slice(raw.as_ref());
5931            wrap_side_transport_frame(SIDE_TRANSPORT_KIND_FULL, &body)
5932        };
5933        let frames = if opts.max_frame_bytes != 0 && wrapped.len() > opts.max_frame_bytes {
5934            self.split_side_transport_frame(side, wrapped, opts.max_frame_bytes)
5935        } else {
5936            Ok(SideTransportFrames::single(wrapped))
5937        }?;
5938        let wire_len = frames.wire_len();
5939        let mut st = self.state.lock();
5940        let stats = st.side_runtime_stats.entry(side).or_default();
5941        if used_compact {
5942            let overhead = compact_payload_len
5943                .map(|payload_len| wire_len.saturating_sub(payload_len))
5944                .unwrap_or(wire_len);
5945            stats.note_side_transport_compact(
5946                raw_len,
5947                wire_len,
5948                overhead,
5949                used_timestamp_delta,
5950                omitted_timestamp,
5951            );
5952            if opts.compact_header_target_bytes != 0 && overhead > opts.compact_header_target_bytes
5953            {
5954                stats.note_side_transport_compact_target_miss();
5955            }
5956        } else {
5957            stats.note_side_transport_full(raw_len, wire_len);
5958        }
5959        if frames.len() > 1 {
5960            stats.note_side_transport_chunks(frames.len());
5961        }
5962        Ok(frames)
5963    }
5964
5965    fn expire_side_chunks(&self) {
5966        let now = self.clock.now_ms();
5967        let mut st = self.state.lock();
5968        for side in st.side_transport.values_mut() {
5969            side.rx_chunks
5970                .retain(|_, assembly| now.saturating_sub(assembly.last_seen_ms) <= 2000);
5971        }
5972    }
5973
5974    // A validated frame from a previously discovered direct peer proves that
5975    // its link is alive, even when allocation admission refuses its payload.
5976    // Update existing timestamps only: no allocation, schema merge or new route.
5977    #[cfg(feature = "discovery")]
5978    fn refresh_ingress_peer_liveness(&self, side: RelaySideId, bytes: &[u8]) {
5979        let Ok(frame) = wire_format::peek_routing_frame_info(bytes) else {
5980            return;
5981        };
5982        let address = frame.envelope.source_address;
5983        self.refresh_peer_liveness(side, address);
5984    }
5985
5986    #[cfg(feature = "discovery")]
5987    #[cfg_attr(not(feature = "std"), inline(never))]
5988    fn refresh_peer_liveness(&self, side: RelaySideId, address: u32) {
5989        let now = self.clock.now_ms();
5990        let mut st = self.state.lock();
5991        let Some(route) = st.discovery_routes.get_mut(&side) else {
5992            return;
5993        };
5994        let mut found = None;
5995        for (name, peer) in &mut route.announcers {
5996            if peer.advertised_address == Some(address) || sender_address_u32(name) == address {
5997                // Ambiguous numeric identities must be resolved by discovery.
5998                if found.is_some() {
5999                    return;
6000                }
6001                found = Some(peer);
6002            }
6003        }
6004        let Some(peer) = found else {
6005            return;
6006        };
6007        peer.last_seen_ms = now;
6008        route.last_seen_ms = route.last_seen_ms.max(now);
6009    }
6010
6011    #[cfg(all(feature = "discovery", not(feature = "std")))]
6012    fn consume_known_packed_schema(
6013        &self,
6014        side: RelaySideId,
6015        bytes: &[u8],
6016    ) -> TelemetryResult<bool> {
6017        let Some((address, payload)) = wire_format::peek_plain_schema_payload(bytes)? else {
6018            return Ok(false);
6019        };
6020        if !discovery::discovery_schema_payload_is_fully_known(payload)? {
6021            return Ok(false);
6022        }
6023        // Discovery schemas are always sent as full refreshes. No compact
6024        // dictionary entry or second owned payload is needed for this no-op.
6025        let mut st = self.state.lock();
6026        if let Some(route) = st.discovery_routes.get_mut(&side) {
6027            for (name, peer) in &mut route.announcers {
6028                if peer.advertised_address == Some(address) || sender_address_u32(name) == address {
6029                    peer.has_schema = true;
6030                    break;
6031                }
6032            }
6033        }
6034        Ok(true)
6035    }
6036
6037    // A two-sided, fully warmed packed bridge needs no new large metadata.
6038    // Re-check before dispatch: schemas/templates/routes can change while queued.
6039    #[cfg(feature = "discovery")]
6040    fn cached_transit_ready(&self, side: RelaySideId, bytes: &[u8]) -> TelemetryResult<bool> {
6041        if bytes.len() > 128 || bytes.first().is_none_or(|flags| flags & 0x15 != 0) {
6042            return Ok(false);
6043        }
6044        let frame = wire_format::peek_routing_frame_info(bytes)?;
6045        let ty = frame.envelope.ty;
6046        if is_internal_control_type(ty)
6047            || frame.envelope.is_reliable
6048            || frame.reliable.is_some()
6049            || frame.envelope.target_senders().len() != 0
6050            || !(1..=3).contains(&frame.envelope.endpoints().take(4).count())
6051        {
6052            return Ok(false);
6053        }
6054
6055        let (template, ..) = extract_side_header_template_view(bytes)?;
6056        let st = self.state.lock();
6057        let active: usize = st.sides.iter().filter(|entry| entry.is_some()).count();
6058        if active != 2 {
6059            return Ok(false);
6060        }
6061        for (id, entry) in st
6062            .sides
6063            .iter()
6064            .enumerate()
6065            .filter_map(|(id, entry)| entry.as_ref().map(|e| (id, e)))
6066        {
6067            if entry.opts.reliable_enabled
6068                || !matches!(entry.tx_handler, RelayTxHandlerFn::Packed(_))
6069                || !st
6070                    .side_runtime_stats
6071                    .get(&id)
6072                    .is_some_and(|stats| stats.data_types.contains_key(&ty.as_u32()))
6073            {
6074                return Ok(false);
6075            }
6076            if id == side {
6077                continue;
6078            }
6079            #[cfg(feature = "discovery")]
6080            if !st.discovery_routes.get(&id).is_some_and(|route| {
6081                self.clock.now_ms().saturating_sub(route.last_seen_ms) <= DISCOVERY_ROUTE_TTL_MS
6082                    && frame
6083                        .envelope
6084                        .endpoints()
6085                        .any(|ep| route.reachable.contains(&ep))
6086            }) {
6087                return Ok(false);
6088            }
6089            if entry.opts.header_template_enabled
6090                && entry.opts.max_frame_bytes != 0
6091                && entry.opts.max_side_transport_templates != 0
6092                && !self.side_has_multiple_announcers_locked(&st, id, self.clock.now_ms())
6093                && !st.side_transport.get(&id).is_some_and(|transport| {
6094                    transport.tx_template_ids.contains_key(&template.hash)
6095                        && transport.tx_templates.contains_key(&template.hash)
6096                })
6097            {
6098                return Ok(false);
6099            }
6100        }
6101        Ok(true)
6102    }
6103    #[cfg(not(feature = "discovery"))]
6104    fn cached_transit_ready(&self, _side: RelaySideId, _bytes: &[u8]) -> TelemetryResult<bool> {
6105        Ok(false)
6106    }
6107    fn admit_received_frame(&self, side: RelaySideId, bytes: &[u8]) -> TelemetryResult<()> {
6108        if crate::memory_admission::supports_work_kinds()
6109            && self.cached_transit_ready(side, bytes)?
6110        {
6111            crate::memory_admission::check_cached_transit(bytes.len())
6112        } else {
6113            let mut protected = None;
6114            loop {
6115                match crate::memory_admission::check_receive(bytes) {
6116                    Err(TelemetryError::Io("memory pressure")) => {
6117                        let protected = *protected.get_or_insert_with(|| {
6118                            wire_format::peek_routing_frame_info(bytes)
6119                                .ok()
6120                                .is_some_and(|frame| message_priority(frame.envelope.ty) >= 200)
6121                        });
6122                        if !protected {
6123                            return Err(TelemetryError::Io("memory pressure"));
6124                        }
6125                        // Queue priority cannot help before the wire copy is
6126                        // admitted. Reclaim bulk payloads, preserving control
6127                        // traffic and every learned route/schema.
6128                        let mut st = self.state.lock();
6129                        let reclaimed = st.pop_lowest_packet_queue_item(200);
6130                        if !reclaimed {
6131                            return Err(TelemetryError::Io("memory pressure"));
6132                        }
6133                    }
6134                    result => return result,
6135                }
6136            }
6137        }
6138    }
6139
6140    fn decode_side_transport_frame(
6141        &self,
6142        side: RelaySideId,
6143        bytes: &[u8],
6144    ) -> TelemetryResult<Option<crate::SharedBytes>> {
6145        self.expire_side_chunks();
6146        let Some((kind, body)) = parse_side_transport_wrapper(bytes)? else {
6147            #[cfg(feature = "discovery")]
6148            self.refresh_ingress_peer_liveness(side, bytes);
6149            #[cfg(all(feature = "discovery", not(feature = "std")))]
6150            if self.consume_known_packed_schema(side, bytes)? {
6151                return Ok(None);
6152            }
6153            self.admit_received_frame(side, bytes)?;
6154            return Ok(Some(crate::shared_bytes::convert(Arc::<[u8]>::from(bytes))));
6155        };
6156        match kind {
6157            SIDE_TRANSPORT_KIND_FULL => {
6158                let mut off = 0usize;
6159                let template_id = u32::try_from(read_uleb128_local(body, &mut off)?)
6160                    .map_err(|_| TelemetryError::Unpack("side template id too large"))?;
6161                #[cfg(feature = "discovery")]
6162                self.refresh_ingress_peer_liveness(side, &body[off..]);
6163                #[cfg(all(feature = "discovery", not(feature = "std")))]
6164                if self.consume_known_packed_schema(side, &body[off..])? {
6165                    return Ok(None);
6166                }
6167                self.admit_received_frame(side, &body[off..])?;
6168                let raw = Arc::<[u8]>::from(&body[off..]);
6169                if let Ok((template, _, _, _, timestamp, _, _, _)) =
6170                    extract_side_header_template(raw.as_ref())
6171                {
6172                    let mut st = self.state.lock();
6173                    let max_templates = st
6174                        .sides
6175                        .get(side)
6176                        .and_then(|side| side.as_ref())
6177                        .map(|side| side.opts.max_side_transport_templates)
6178                        .unwrap_or(DEFAULT_SIDE_TRANSPORT_TEMPLATE_LIMIT);
6179                    let evicted = st.side_transport.get_mut(&side).is_some_and(|side_state| {
6180                        let evicted =
6181                            side_state.insert_rx_template(template_id, template, max_templates);
6182                        if max_templates > 0 {
6183                            side_state.rx_last_timestamps.insert(template_id, timestamp);
6184                        }
6185                        evicted
6186                    });
6187                    if evicted {
6188                        st.side_runtime_stats
6189                            .entry(side)
6190                            .or_default()
6191                            .note_side_transport_template_eviction();
6192                    }
6193                }
6194                Ok(Some(crate::shared_bytes::convert(raw)))
6195            }
6196            SIDE_TRANSPORT_KIND_COMPACT
6197            | SIDE_TRANSPORT_KIND_COMPACT_DELTA
6198            | SIDE_TRANSPORT_KIND_COMPACT_SAME_TIMESTAMP => {
6199                if body.is_empty() {
6200                    return Err(TelemetryError::Unpack("short side compact frame"));
6201                }
6202                let mut off = 1usize;
6203                let template_id = u32::try_from(read_uleb128_local(body, &mut off)?)
6204                    .map_err(|_| TelemetryError::Unpack("side template id too large"))?;
6205                let (template, timestamp_base) = {
6206                    let st = self.state.lock();
6207                    let state = st.side_transport.get(&side);
6208                    let template = state
6209                        .and_then(|state| state.rx_templates_by_id.get(&template_id))
6210                        .cloned();
6211                    let timestamp_base = if matches!(
6212                        kind,
6213                        SIDE_TRANSPORT_KIND_COMPACT_DELTA
6214                            | SIDE_TRANSPORT_KIND_COMPACT_SAME_TIMESTAMP
6215                    ) {
6216                        state
6217                            .and_then(|state| state.rx_last_timestamps.get(&template_id))
6218                            .copied()
6219                    } else {
6220                        None
6221                    };
6222                    (template, timestamp_base)
6223                };
6224                let template =
6225                    template.ok_or(TelemetryError::Unpack("unknown side compact template"))?;
6226                // The wrapper CRC and known template identify this peer even
6227                // if payload allocation is refused. No payload copy is needed.
6228                #[cfg(feature = "discovery")]
6229                if let Some(address) = template.source_address() {
6230                    self.refresh_peer_liveness(side, address);
6231                }
6232                let reconstructed_bound = body
6233                    .len()
6234                    .saturating_add(template.prefix.len())
6235                    .saturating_add(template.between.len())
6236                    .saturating_add(32);
6237                // Wrapper CRC and the retained full template are validated
6238                // before compact reconstruction. Preserve the same control
6239                // priority as canonical ingress while reclaiming only bulk.
6240                let protected = template
6241                    .data_type()
6242                    .is_some_and(|ty| message_priority(ty) >= 200);
6243                let ack = template.is_ack();
6244                loop {
6245                    match crate::memory_admission::check_frame(reconstructed_bound, ack) {
6246                        Err(TelemetryError::Io("memory pressure")) if protected => {
6247                            let mut st = self.state.lock();
6248                            let reclaimed = st.pop_lowest_packet_queue_item(200);
6249                            if !reclaimed {
6250                                return Err(TelemetryError::Io("memory pressure"));
6251                            }
6252                        }
6253                        result => {
6254                            result?;
6255                            break;
6256                        }
6257                    }
6258                }
6259                let timestamp_mode = match kind {
6260                    SIDE_TRANSPORT_KIND_COMPACT_DELTA => SideCompactTimestampMode::Delta,
6261                    SIDE_TRANSPORT_KIND_COMPACT_SAME_TIMESTAMP => SideCompactTimestampMode::Omitted,
6262                    _ => SideCompactTimestampMode::Absolute,
6263                };
6264                let (frame, timestamp) = reconstruct_side_compact_frame(
6265                    &template,
6266                    body[0],
6267                    &body[off..],
6268                    timestamp_mode,
6269                    timestamp_base,
6270                )?;
6271                let mut st = self.state.lock();
6272                if let Some(side_state) = st.side_transport.get_mut(&side) {
6273                    side_state.rx_last_timestamps.insert(template_id, timestamp);
6274                }
6275                Ok(Some(frame))
6276            }
6277            SIDE_TRANSPORT_KIND_CHUNK => {
6278                crate::memory_admission::check_receive(bytes)?;
6279                if body.len() < 8 {
6280                    return Err(TelemetryError::Unpack("short side chunk frame"));
6281                }
6282                let transfer_id = u32::from_le_bytes([body[0], body[1], body[2], body[3]]);
6283                let index = u16::from_le_bytes([body[4], body[5]]);
6284                let total = u16::from_le_bytes([body[6], body[7]]);
6285                if total == 0 || index >= total {
6286                    return Err(TelemetryError::Unpack("invalid side chunk index"));
6287                }
6288                let assembled = {
6289                    let mut st = self.state.lock();
6290                    let budget = st.memory.max_queue_budget;
6291                    let now_ms = self.clock.now_ms();
6292                    let side_state = st
6293                        .side_transport
6294                        .get_mut(&side)
6295                        .ok_or(TelemetryError::BadArg)?;
6296                    side_state
6297                        .rx_chunks
6298                        .retain(|_, assembly| now_ms.saturating_sub(assembly.last_seen_ms) <= 2000);
6299                    if !side_state.rx_chunks.contains_key(&transfer_id)
6300                        && side_state.rx_chunks.len() >= 4
6301                        && let Some(oldest) = side_state
6302                            .rx_chunks
6303                            .iter()
6304                            .min_by_key(|(_, a)| a.last_seen_ms)
6305                            .map(|(id, _)| *id)
6306                    {
6307                        side_state.rx_chunks.remove(&oldest);
6308                    }
6309                    SideChunkAssembly::make_room(
6310                        &mut side_state.rx_chunks,
6311                        transfer_id,
6312                        index,
6313                        total,
6314                        body.len() - 8,
6315                        budget,
6316                    )?;
6317                    let entry = side_state.rx_chunks.entry(transfer_id).or_default();
6318                    entry.last_seen_ms = now_ms;
6319                    if entry.total == 0 {
6320                        entry.total = total;
6321                    } else if entry.total != total {
6322                        side_state.rx_chunks.remove(&transfer_id);
6323                        return Err(TelemetryError::Unpack("side chunk total mismatch"));
6324                    }
6325                    entry.received.entry(index).or_insert_with(|| {
6326                        crate::shared_bytes::convert(Arc::<[u8]>::from(&body[8..]))
6327                    });
6328                    if entry.received.len() == usize::from(total) {
6329                        let entry = side_state
6330                            .rx_chunks
6331                            .remove(&transfer_id)
6332                            .ok_or(TelemetryError::Unpack("side chunk missing"))?;
6333                        Some(entry.assemble()?)
6334                    } else {
6335                        None
6336                    }
6337                };
6338                match assembled {
6339                    Some(frame) => self.decode_side_transport_frame(side, frame.as_ref()),
6340                    None => Ok(None),
6341                }
6342            }
6343            _ => Err(TelemetryError::Unpack("unknown side transport frame")),
6344        }
6345    }
6346
6347    /// Helper: call a TX handler with the best representation we have.
6348    /// - Packet handler + Packet item: direct.
6349    /// - Packed handler + Packed item: direct.
6350    /// - Packet handler + Packed item: unpack for this call.
6351    /// - Packed handler + Packet item: pack for this call.
6352    fn call_tx_handler(
6353        &self,
6354        side: RelaySideId,
6355        handler: &RelayTxHandlerFn,
6356        data: &RelayItem,
6357    ) -> TelemetryResult<()> {
6358        let opts = {
6359            let st = self.state.lock();
6360            Self::side_ref(&st, side)?.opts.clone()
6361        };
6362        let Some(_side_tx_guard) = self.try_enter_side_tx() else {
6363            return Err(TelemetryError::Io("side tx busy"));
6364        };
6365        let started_ms = self.clock.now_ms();
6366        let ty = match data {
6367            RelayItem::Packet(pkt) => pkt.data_type(),
6368            RelayItem::Packed(bytes) => wire_format::peek_routing_envelope(bytes.as_ref())?.ty,
6369        };
6370        let result = match (handler, data) {
6371            // Fast paths
6372            (RelayTxHandlerFn::Packed(f), RelayItem::Packed(bytes)) => {
6373                let frames = self.encode_side_transport_frames(side, opts, bytes.clone())?;
6374                let mut sent_bytes = 0usize;
6375                self.send_side_frames_resumable(side, frames, |frame| {
6376                    f(frame)?;
6377                    sent_bytes = sent_bytes.saturating_add(frame.len());
6378
6379                    Ok(())
6380                })?;
6381                self.record_side_tx_sample(side, sent_bytes, started_ms, self.clock.now_ms());
6382                self.note_side_tx_success(side, ty, sent_bytes, 1);
6383                return Ok(());
6384            }
6385            (RelayTxHandlerFn::Packet(f), RelayItem::Packet(pkt)) => f(pkt),
6386
6387            // Conversion paths
6388            (RelayTxHandlerFn::Packed(f), RelayItem::Packet(pkt)) => {
6389                if ty == crate::DataType::DiscoverySchema
6390                    && opts.max_frame_bytes != 0
6391                    && pkt.payload().len() > opts.max_frame_bytes
6392                {
6393                    let key = crate::packet::hash_bytes_u64(
6394                        pkt.packet_id(),
6395                        &opts.max_frame_bytes.to_le_bytes(),
6396                    );
6397                    let result = self.run_side_chunk_transfer(side, key, |next| {
6398                        crate::side_transport::send_packet_chunks(
6399                            pkt,
6400                            opts.max_frame_bytes,
6401                            self.sender_arc().as_bytes(),
6402                            next,
6403                            |frame| f(frame),
6404                        )
6405                    });
6406                    let (raw_len, wire_len, chunks) = match result {
6407                        Ok(stats) => stats,
6408                        Err(err) => {
6409                            self.note_side_tx_failure(side, ty, 1);
6410                            return Err(err);
6411                        }
6412                    };
6413                    {
6414                        let mut st = self.state.lock();
6415                        let stats = st.side_runtime_stats.entry(side).or_default();
6416                        stats.note_side_transport_full(raw_len, wire_len);
6417                        if chunks > 1 {
6418                            stats.note_side_transport_chunks(chunks);
6419                        }
6420                    }
6421                    self.record_side_tx_sample(side, wire_len, started_ms, self.clock.now_ms());
6422                    self.note_side_tx_success(side, ty, wire_len, 1);
6423                    return Ok(());
6424                }
6425                let owned = wire_format::pack_packet(pkt);
6426                let frames = self.encode_side_transport_frames(
6427                    side,
6428                    opts.clone(),
6429                    crate::shared_bytes::convert(owned),
6430                )?;
6431                let mut sent_bytes = 0usize;
6432                self.send_side_frames_resumable(side, frames, |frame| {
6433                    f(frame)?;
6434                    sent_bytes = sent_bytes.saturating_add(frame.len());
6435
6436                    Ok(())
6437                })?;
6438                self.record_side_tx_sample(side, sent_bytes, started_ms, self.clock.now_ms());
6439                self.note_side_tx_success(side, ty, sent_bytes, 1);
6440                return Ok(());
6441            }
6442            (RelayTxHandlerFn::Packet(f), RelayItem::Packed(bytes)) => {
6443                if wire_format::peek_frame_info(bytes.as_ref())
6444                    .ok()
6445                    .is_some_and(|frame| frame.ack_only())
6446                {
6447                    return Ok(());
6448                }
6449                let pkt = wire_format::unpack_packet(bytes.as_ref())?;
6450                f(&pkt)
6451            }
6452        };
6453        if result.is_ok()
6454            && let Ok(bytes) = Self::relay_item_wire_len(data)
6455        {
6456            self.record_side_tx_sample(side, bytes, started_ms, self.clock.now_ms());
6457            self.note_side_tx_success(side, ty, bytes, 1);
6458        } else if result.is_err() {
6459            self.note_side_tx_failure(side, ty, 1);
6460        }
6461        result
6462    }
6463
6464    fn adjust_reliable_for_side(
6465        &self,
6466        opts: RelaySideOptions,
6467        data: RelayItem,
6468    ) -> TelemetryResult<Option<RelayItem>> {
6469        if opts.reliable_enabled {
6470            return Ok(Some(data));
6471        }
6472
6473        match data {
6474            RelayItem::Packed(bytes) => {
6475                let frame = match wire_format::peek_routing_frame_info(bytes.as_ref()) {
6476                    Ok(frame) => frame,
6477                    Err(_) => return Ok(Some(RelayItem::Packed(bytes))),
6478                };
6479                if is_reliable_type(frame.envelope.ty)
6480                    && let Some(hdr) = frame.reliable
6481                {
6482                    if (hdr.flags & wire_format::RELIABLE_FLAG_ACK_ONLY) != 0 {
6483                        return Ok(None);
6484                    }
6485                    if (hdr.flags & wire_format::RELIABLE_FLAG_UNSEQUENCED) == 0 {
6486                        let Some(rewritten) = wire_format::rewrite_reliable_header_owned(
6487                            bytes.as_ref(),
6488                            wire_format::RELIABLE_FLAG_UNSEQUENCED,
6489                            hdr.seq,
6490                            0,
6491                        )?
6492                        else {
6493                            return Ok(Some(RelayItem::Packed(bytes)));
6494                        };
6495                        return Ok(Some(RelayItem::Packed(crate::shared_bytes::convert(
6496                            rewritten,
6497                        ))));
6498                    }
6499                }
6500                Ok(Some(RelayItem::Packed(bytes)))
6501            }
6502            RelayItem::Packet(pkt) => {
6503                if matches!(
6504                    pkt.data_type(),
6505                    crate::DataType::ReliableAck
6506                        | crate::DataType::ReliablePartialAck
6507                        | crate::DataType::ReliablePacketRequest
6508                ) {
6509                    // Hop reliability can be disabled on a physical bridge,
6510                    // but the publisher's end-to-end ACK must still cross it.
6511                    if Self::end_to_end_ack_sender_hash(&pkt).is_some()
6512                        && Self::decode_end_to_end_reliable_ack(pkt.payload()).is_ok()
6513                    {
6514                        return Ok(Some(RelayItem::Packet(pkt)));
6515                    }
6516                    return Ok(None);
6517                }
6518                Ok(Some(RelayItem::Packet(pkt)))
6519            }
6520        }
6521    }
6522
6523    /// Drain the RX queue fully, expanding to TX items.
6524    #[inline]
6525    pub fn process_rx_queue(&self) -> TelemetryResult<()> {
6526        self.process_rx_queue_with_timeout(0)
6527    }
6528
6529    /// Drain the TX queue fully, invoking per-side TX handlers.
6530    ///
6531    /// If called from inside a side TX callback, this becomes a no-op so relay TX handlers cannot
6532    /// recurse into nested queue drains on the same stack.
6533    #[inline]
6534    pub fn process_tx_queue(&self) -> TelemetryResult<()> {
6535        self.process_tx_queue_with_timeout(0)
6536    }
6537
6538    /// Drain RX then TX queues fully (one pass).
6539    #[inline]
6540    pub fn process_all_queues(&self) -> TelemetryResult<()> {
6541        self.process_all_queues_with_timeout(0)
6542    }
6543
6544    /// Process the TX queue for up to `timeout_ms` milliseconds.
6545    ///
6546    /// `timeout_ms == 0` drains fully. If called from inside a side TX callback, this becomes a
6547    /// no-op so relay TX handlers cannot recurse into nested queue drains on the same stack.
6548    pub fn process_tx_queue_with_timeout(&self, timeout_ms: u32) -> TelemetryResult<()> {
6549        self.expire_side_chunks();
6550        if self.side_tx_active() {
6551            return Ok(());
6552        }
6553        #[cfg(feature = "discovery")]
6554        {
6555            let _ = crate::memory_admission::allow_drain(self.poll_discovery())?;
6556        }
6557        let start = self.clock.now_ms();
6558        loop {
6559            let received_control = crate::memory_admission::allow_drain(
6560                self.drain_queued_receipts_before_timeouts(start, timeout_ms),
6561            )?;
6562            let control_budget_used = received_control
6563                && timeout_ms != 0
6564                && self.clock.now_ms().wrapping_sub(start) >= u64::from(timeout_ms);
6565            if !control_budget_used {
6566                crate::memory_admission::allow_drain(self.process_reliable_timeouts())?;
6567            }
6568            if !control_budget_used && self.process_replay_queue_item()? {
6569                if timeout_ms != 0 && self.clock.now_ms().wrapping_sub(start) >= timeout_ms as u64 {
6570                    break;
6571                }
6572                continue;
6573            }
6574            let Some((src, dst, handler, opts, data)) = self.pop_ready_tx_item() else {
6575                break;
6576            };
6577            match self.send_tx_item(src, dst, handler, opts, data.clone()) {
6578                Ok(sent) => {
6579                    if sent
6580                        && timeout_ms != 0
6581                        && self.clock.now_ms().wrapping_sub(start) >= timeout_ms as u64
6582                    {
6583                        break;
6584                    }
6585                }
6586                Err(e) if Self::is_side_tx_busy(&e) => {
6587                    let priority = Self::relay_item_priority(&data)?;
6588                    let mut st = self.state.lock();
6589                    st.push_tx(RelayTxItem {
6590                        src,
6591                        dst,
6592                        data,
6593                        priority,
6594                        retry_after_ms: self.clock.now_ms().saturating_add(1),
6595                    })?;
6596                    continue;
6597                }
6598                Err(e) => return Err(e),
6599            }
6600        }
6601        Ok(())
6602    }
6603
6604    /// Process RX queue with timeout.
6605    pub fn process_rx_queue_with_timeout(&self, timeout_ms: u32) -> TelemetryResult<()> {
6606        self.expire_side_chunks();
6607        #[cfg(feature = "discovery")]
6608        {
6609            let _ = crate::memory_admission::allow_drain(self.poll_discovery())?;
6610        }
6611        let start = self.clock.now_ms();
6612        loop {
6613            let item_opt = {
6614                let mut st = self.state.lock();
6615                st.rx_queue.pop_front()
6616            };
6617            let Some(item) = item_opt else { break };
6618            self.process_rx_queue_item(item)?;
6619
6620            if timeout_ms != 0 && self.clock.now_ms().wrapping_sub(start) >= timeout_ms as u64 {
6621                break;
6622            }
6623        }
6624        Ok(())
6625    }
6626
6627    /// Process RX and TX queues interleaved for up to `timeout_ms` milliseconds.
6628    ///
6629    /// `timeout_ms == 0` drains fully. If called from inside a side TX callback, this becomes a
6630    /// no-op so relay TX handlers cannot recurse into nested queue drains on the same stack.
6631    pub fn process_all_queues_with_timeout(&self, timeout_ms: u32) -> TelemetryResult<()> {
6632        if self.side_tx_active() {
6633            return Ok(());
6634        }
6635        #[cfg(feature = "discovery")]
6636        let _ = crate::memory_admission::allow_drain(self.poll_discovery())?;
6637        self.drain_queues_with_timeout(timeout_ms)
6638    }
6639
6640    /// Drain RX/TX work without generating periodic discovery announcements.
6641    /// Reliable retries remain active. Call `periodic` or `poll_discovery`
6642    /// separately on a regular cadence. Nested side callbacks remain a no-op.
6643    pub fn drain_queues_with_timeout(&self, timeout_ms: u32) -> TelemetryResult<()> {
6644        self.expire_side_chunks();
6645        if self.side_tx_active() {
6646            return Ok(());
6647        }
6648        let drain_fully = timeout_ms == 0;
6649        let start = if drain_fully { 0 } else { self.clock.now_ms() };
6650
6651        loop {
6652            let mut did_any;
6653            let received_control = crate::memory_admission::allow_drain(
6654                self.drain_queued_receipts_before_timeouts(start, timeout_ms),
6655            )?;
6656            did_any = received_control;
6657            let control_budget_used = received_control
6658                && timeout_ms != 0
6659                && self.clock.now_ms().wrapping_sub(start) >= u64::from(timeout_ms);
6660            if !control_budget_used {
6661                crate::memory_admission::allow_drain(self.process_reliable_timeouts())?;
6662            }
6663
6664            // One TX/RX pair is atomic for fairness under a short budget.
6665            if !control_budget_used
6666                && let Some(item) = {
6667                    let mut st = self.state.lock();
6668                    st.rx_queue.pop_front()
6669                }
6670            {
6671                self.process_rx_queue_item(item)?;
6672                did_any = true;
6673            }
6674
6675            if !control_budget_used && self.process_replay_queue_item()? {
6676                did_any = true;
6677            }
6678
6679            // Then send out TX
6680            let sent_one = if let Some((src, dst, handler, opts, data)) = self.pop_ready_tx_item() {
6681                match self.send_tx_item(src, dst, handler, opts, data.clone()) {
6682                    Ok(sent) => sent,
6683                    Err(error) if Self::is_side_tx_busy(&error) => {
6684                        let priority = Self::relay_item_priority(&data)?;
6685                        self.state.lock().push_tx(RelayTxItem {
6686                            src,
6687                            dst,
6688                            data,
6689                            priority,
6690                            retry_after_ms: self.clock.now_ms().saturating_add(1),
6691                        })?;
6692                        did_any = true;
6693                        false
6694                    }
6695                    Err(error) => return Err(error),
6696                }
6697            } else {
6698                false
6699            };
6700
6701            if sent_one {
6702                did_any = true;
6703            }
6704
6705            if !drain_fully && self.clock.now_ms().wrapping_sub(start) >= timeout_ms as u64 {
6706                break;
6707            }
6708
6709            if !did_any {
6710                break;
6711            }
6712        }
6713
6714        Ok(())
6715    }
6716
6717    /// Runs one application-loop maintenance cycle.
6718    ///
6719    /// This polls built-in discovery when that feature is compiled in, then drains queued RX/TX
6720    /// work for up to `timeout_ms` milliseconds.
6721    pub fn periodic(&self, timeout_ms: u32) -> TelemetryResult<()> {
6722        #[cfg(feature = "discovery")]
6723        {
6724            let _ = crate::memory_admission::allow_drain(self.poll_discovery())?;
6725        }
6726
6727        self.drain_queues_with_timeout(timeout_ms)
6728    }
6729}
6730
6731#[cfg(all(test, feature = "std", feature = "discovery"))]
6732mod discovery_reclaim_tests {
6733    use super::*;
6734    use crate::DataType;
6735    use core::sync::atomic::{AtomicUsize, Ordering};
6736    #[test]
6737    fn relay_discovery_reclaims_telemetry_without_discarding_controls() {
6738        crate::tests::ensure_common_test_schema();
6739        static REFUSALS: AtomicUsize = AtomicUsize::new(0);
6740        extern "C" fn scratch_after_reclaim(additional: usize, _: usize) -> bool {
6741            additional < 2048 || REFUSALS.fetch_add(1, Ordering::SeqCst) >= 2
6742        }
6743        struct Reset;
6744        impl Drop for Reset {
6745            fn drop(&mut self) {
6746                crate::memory_admission::set_probe(None);
6747            }
6748        }
6749        let relay = Relay::new(Box::new(|| 1));
6750        let side = relay.add_side_packet("CAN", |_| Ok(()));
6751        for priority in [1, 2, 200, 254, 255] {
6752            relay
6753                .state
6754                .lock()
6755                .push_tx(RelayTxItem {
6756                    src: None,
6757                    dst: side,
6758                    data: RelayItem::Packed(crate::shared_bytes::convert(Arc::<[u8]>::from(
6759                        [0; 32],
6760                    ))),
6761                    priority,
6762                    retry_after_ms: 0,
6763                })
6764                .unwrap();
6765        }
6766        REFUSALS.store(0, Ordering::SeqCst);
6767        let _reset = Reset;
6768        crate::memory_admission::set_probe(Some(scratch_after_reclaim));
6769        relay
6770            .queue_discovery_announce_on_side(false, true, None)
6771            .unwrap();
6772        let st = relay.state.lock();
6773        assert_eq!(st.tx_queue.lowest_priority(|item| item.priority), Some(200));
6774        for priority in [200, 254] {
6775            assert_eq!(
6776                st.tx_queue
6777                    .iter()
6778                    .filter(|item| item.priority == priority)
6779                    .count(),
6780                1
6781            );
6782        }
6783        assert!(
6784            st.tx_queue
6785                .iter()
6786                .any(|item| matches!(&item.data, RelayItem::Packet(pkt)
6787            if pkt.data_type() == DataType::DiscoveryLinkCapabilities))
6788        );
6789    }
6790}
6791
6792#[cfg(all(test, feature = "std", feature = "discovery"))]
6793mod queued_schema_identity_tests {
6794    use super::*;
6795
6796    fn catalog(timestamp: u64, byte: u8) -> Packet {
6797        Packet::new(
6798            crate::DataType::DiscoverySchema,
6799            &[crate::DataEndpoint::Discovery],
6800            "SCHEMA_PEER",
6801            timestamp,
6802            crate::shared_bytes::convert(Arc::<[u8]>::from(vec![byte; 1024])),
6803        )
6804        .unwrap()
6805    }
6806
6807    #[test]
6808    fn repeated_schema_preserves_partial_transfer_but_updates_and_other_sides_do_not_coalesce() {
6809        let relay = Relay::new(Box::new(|| 0u64));
6810        let side = relay.add_side_packed("A", |_| Ok(()));
6811        let other_side = relay.add_side_packed("B", |_| Ok(()));
6812        let original = catalog(1, 42);
6813        let duplicate = catalog(2, 42);
6814        assert_ne!(original.packet_id(), duplicate.packet_id());
6815        let mut st = relay.state.lock();
6816        st.tx_queue.clear();
6817        let queued = |dst, pkt| RelayTxItem {
6818            src: None,
6819            dst,
6820            data: RelayItem::Packet(Arc::new(pkt)),
6821            priority: 255,
6822            retry_after_ms: 123,
6823        };
6824        st.push_tx(queued(side, original.clone())).unwrap();
6825        st.push_tx(queued(side, duplicate.clone())).unwrap();
6826        assert_eq!(st.tx_queue.len(), 1);
6827        let pending = st.tx_queue.iter().next().unwrap();
6828        assert_eq!(pending.retry_after_ms, 123);
6829        assert!(matches!(&pending.data, RelayItem::Packet(pkt)
6830            if pkt.packet_id() == original.packet_id()));
6831        st.push_tx(queued(other_side, duplicate)).unwrap();
6832        assert_eq!(st.tx_queue.len(), 2);
6833        let changed = catalog(3, 43);
6834        st.push_tx(queued(side, changed.clone())).unwrap();
6835        assert_eq!(st.tx_queue.len(), 2);
6836        assert!(st.tx_queue.iter().any(|queued| queued.dst == side
6837            && matches!(&queued.data,
6838            RelayItem::Packet(pkt) if pkt.packet_id() == changed.packet_id())));
6839    }
6840
6841    #[test]
6842    fn repeated_announcements_complete_one_fragment_per_dispatch() {
6843        use core::sync::atomic::{AtomicUsize, Ordering};
6844        let allowance = Arc::new(AtomicUsize::new(0));
6845        let accepted = Arc::new(std::sync::Mutex::new(Vec::<Arc<[u8]>>::new()));
6846        let available = allowance.clone();
6847        let frames = accepted.clone();
6848        let node = Relay::new(Box::new(|| 1u64));
6849        let side = node.add_side_packed_with_options(
6850            "bounded CAN",
6851            move |bytes| {
6852                if available
6853                    .try_update(Ordering::Relaxed, Ordering::Relaxed, |count| {
6854                        count.checked_sub(1)
6855                    })
6856                    .is_err()
6857                {
6858                    return Err(TelemetryError::Io("side tx busy"));
6859                }
6860                frames.lock().unwrap().push(Arc::<[u8]>::from(bytes));
6861                Ok(())
6862            },
6863            RelaySideOptions::default().with_small_packet_transport(128),
6864        );
6865        let handler = node.state.lock().sides[side]
6866            .as_ref()
6867            .unwrap()
6868            .tx_handler
6869            .clone();
6870        let mut complete = false;
6871        for timestamp in 1..100 {
6872            // Incompressible bytes ensure this really uses several chunks.
6873            let mut state = 0x517cc1b7u32;
6874            let payload: Vec<u8> = (0..3600)
6875                .map(|_| {
6876                    state ^= state << 13;
6877                    state ^= state >> 17;
6878                    state ^= state << 5;
6879                    state as u8
6880                })
6881                .collect();
6882            let packet = Packet::new(
6883                crate::DataType::DiscoverySchema,
6884                &[crate::DataEndpoint::Discovery],
6885                "SCHEMA_PEER",
6886                timestamp,
6887                crate::shared_bytes::convert(Arc::<[u8]>::from(payload)),
6888            )
6889            .unwrap();
6890            node.state
6891                .lock()
6892                .push_tx(RelayTxItem {
6893                    src: None,
6894                    dst: side,
6895                    data: RelayItem::Packet(Arc::new(packet)),
6896                    priority: 255,
6897                    retry_after_ms: 0,
6898                })
6899                .unwrap();
6900            let data = node
6901                .state
6902                .lock()
6903                .tx_queue
6904                .iter()
6905                .next()
6906                .unwrap()
6907                .data
6908                .clone();
6909            allowance.store(1, Ordering::Relaxed);
6910            match node.call_tx_handler(side, &handler, &data) {
6911                Ok(()) => {
6912                    complete = true;
6913                    break;
6914                }
6915                Err(TelemetryError::Io(_)) => {}
6916                other => panic!("unexpected result: {other:?}"),
6917            }
6918        }
6919        assert!(
6920            complete,
6921            "new announcement timestamps must not replay the prefix forever"
6922        );
6923        let received = accepted.lock().unwrap();
6924        // One transfer ID and consecutive fragment indices prove prefix reuse
6925        // did not create incomplete transfers or duplicate accepted fragments.
6926        assert!(
6927            received.len() > 1,
6928            "fixture must exercise fragmentation, not a compressed single frame"
6929        );
6930        let first = &received[0];
6931        assert_eq!(&first[..4], &[83, 68, 84, 3]);
6932        assert_eq!(
6933            u16::from_le_bytes([first[10], first[11]]) as usize,
6934            received.len()
6935        );
6936        let transfer_id = &first[4..8];
6937        for (index, frame) in received.iter().enumerate() {
6938            assert_eq!(&frame[4..8], transfer_id);
6939            assert_eq!(u16::from_le_bytes([frame[8], frame[9]]) as usize, index);
6940        }
6941    }
6942    #[test]
6943    fn schema_receipt_resolves_assigned_numeric_address_without_adding_peers() {
6944        let relay = Relay::new(Box::new(|| 0u64));
6945        let side = relay.add_side_packed("A", |_| Ok(()));
6946        let assigned = 0x1234u32;
6947        assert_ne!(sender_address_u32("SCHEMA_PEER"), assigned);
6948        {
6949            let mut st = relay.state.lock();
6950            st.discovery_routes
6951                .entry(side)
6952                .or_default()
6953                .announcers
6954                .insert(
6955                    "SCHEMA_PEER".into(),
6956                    DiscoverySenderState {
6957                        advertised_address: Some(assigned),
6958                        ..DiscoverySenderState::default()
6959                    },
6960                );
6961        }
6962        relay.note_schema_received(side, "@addr:4660");
6963        relay.note_schema_received(side, "@addr:4661");
6964        let st = relay.state.lock();
6965        let peers = &st.discovery_routes.get(&side).unwrap().announcers;
6966        assert_eq!(peers.len(), 1);
6967        assert!(peers["SCHEMA_PEER"].has_schema);
6968    }
6969}
6970
6971#[cfg(all(test, feature = "discovery", feature = "std"))]
6972fn untemplated_test_node() -> Relay {
6973    Relay::new(Box::new(|| 0))
6974}
6975#[cfg(all(test, feature = "discovery", feature = "std"))]
6976fn untemplated_test_options() -> RelaySideOptions {
6977    RelaySideOptions::default().with_small_packet_transport(64)
6978}
6979#[cfg(all(test, feature = "discovery", feature = "std"))]
6980#[allow(clippy::duplicate_mod)]
6981#[path = "tests/untemplated_transport.rs"]
6982mod untemplated_transport_tests;