Skip to main content

sedsnet/
relay.rs

1#[cfg(all(test, feature = "std"))]
2#[path = "tests/independent_side_tx_relay.rs"]
3mod independent_side_tx;
4#[cfg(all(test, feature = "std", feature = "discovery", feature = "compression"))]
5#[path = "tests/payload_codec_relay.rs"]
6mod payload_codec_negotiation;
7#[cfg(all(test, feature = "std"))]
8#[path = "tests/reliable_tx_window_backpressure_relay.rs"]
9mod reliable_tx_window_backpressure_tests;
10#[cfg(all(test, feature = "std", feature = "discovery"))]
11#[path = "tests/shared_chunk_budget_relay.rs"]
12mod shared_chunk_budget_tests;
13// Apply the same cache contract independently to router and relay state.
14#[cfg(all(test, feature = "discovery"))]
15#[path = "tests/relay_ack_recovery.rs"]
16mod ack_recovery_tests;
17#[cfg(all(test, feature = "discovery"))]
18#[path = "tests/relay_ack_split_horizon.rs"]
19mod ack_split_horizon;
20#[cfg(all(test, feature = "std"))]
21#[path = "tests/relay_control_priority.rs"]
22mod control_priority_tests;
23#[cfg(all(test, feature = "std"))]
24#[path = "tests/replay_ownership_relay.rs"]
25mod replay_ownership_tests;
26#[cfg(test)]
27#[allow(clippy::duplicate_mod)]
28#[path = "tests/side_template_index.rs"]
29mod side_template_index_tests;
30use crate::side_transport::{
31    SIDE_TRANSPORT_FLAG_PACKET_NONCE, SIDE_TRANSPORT_KIND_CHUNK, SIDE_TRANSPORT_KIND_COMPACT,
32    SIDE_TRANSPORT_KIND_COMPACT_DELTA, SIDE_TRANSPORT_KIND_COMPACT_SAME_TIMESTAMP,
33    SIDE_TRANSPORT_KIND_FULL, SideChunkAssembly, SideCompactTimestampMode, SideHeaderTemplate,
34    SideTransportFrames, extract_side_header_template, extract_side_header_template_view,
35    parse_side_transport_wrapper, read_uleb128_local, reconstruct_side_compact_frame,
36    wrap_side_transport_frame, write_uleb128_local,
37};
38#[cfg(all(test, feature = "discovery"))]
39#[path = "tests/relay_restart_transport.rs"]
40mod restart_transport_tests;
41
42#[cfg(all(test, feature = "discovery"))]
43mod compact_summary_tests {
44    use super::*;
45
46    #[test]
47    fn compact_loss_and_reordering_preserve_packet_identity() {
48        crate::tests::ensure_common_test_schema();
49        let tx = Relay::new(Box::new(|| 0));
50        let rx = Relay::new(Box::new(|| 0));
51        let opts = RelaySideOptions::default().with_small_packet_transport(0);
52        let a = tx.add_side_packed_with_options("wire", |_| Ok(()), opts.clone());
53        let b = rx.add_side_packed_with_options("wire", |_| Ok(()), opts.clone());
54        let mut encoded = Vec::new();
55        for timestamp in [10000, 10010, 10020, 10030] {
56            let pkt = Packet::new(
57                crate::DataType::named("GPS_DATA"),
58                &[crate::DataEndpoint::named("RADIO")],
59                "VB",
60                timestamp,
61                Arc::<[u8]>::from([0u8; 12]),
62            )
63            .unwrap();
64            let raw = wire_format::pack_packet(&pkt);
65            let frames = tx
66                .encode_side_transport_frames(
67                    a,
68                    opts.clone(),
69                    crate::shared_bytes::convert(raw.clone()),
70                )
71                .unwrap();
72            assert_eq!(frames.len(), 1);
73            encoded.push((raw, frames.into_iter().next().unwrap()));
74        }
75        // Establish template, lose frame 1, reorder and duplicate later frames.
76        for index in [0, 3, 2, 3] {
77            let (raw, frame) = &encoded[index];
78            let received = rx.decode_side_transport_frame(b, frame).unwrap().unwrap();
79            assert_eq!(&received, raw);
80        }
81    }
82
83    #[test]
84    fn relay_preserves_compact_routes_without_inventing_endpoint_ownership() {
85        crate::tests::ensure_common_test_schema();
86        let endpoint = crate::DataEndpoint::named("RADIO");
87        let relay = Relay::new(Box::new(|| 0));
88        let mut sender = DiscoverySenderState {
89            advertised_reachable: vec![endpoint],
90            advertised_reachable_timesync_sources: vec!["clock-behind-bridge".into()],
91            topology_boards: Arc::new(vec![TopologyBoardNode {
92                sender_id: "bridge".into(),
93                reachable_endpoints: vec![],
94                reachable_timesync_sources: vec![],
95                connections: vec![],
96            }]),
97            ..Default::default()
98        };
99        Relay::refresh_sender_topology_state(&mut sender);
100        let mut route = DiscoverySideState::default();
101        route.announcers.insert("bridge".into(), sender);
102        Relay::recompute_discovery_side_state(&mut route);
103        let mut state = relay.state.lock();
104        state.discovery_routes.insert(0, route);
105        assert!(
106            relay
107                .advertised_discovery_endpoints_for_link_locked(&state, 0, true, Some(1))
108                .contains(&endpoint)
109        );
110        assert!(
111            !relay
112                .advertised_discovery_endpoints_for_link_locked(&state, 0, true, Some(0))
113                .contains(&endpoint)
114        );
115        assert!(
116            !relay
117                .advertised_discovery_endpoints_for_link_locked(
118                    &state,
119                    DISCOVERY_ROUTE_TTL_MS + 1,
120                    true,
121                    Some(1)
122                )
123                .contains(&endpoint)
124        );
125        assert_eq!(
126            relay.advertised_discovery_timesync_sources_for_link_locked(&state, 0, Some(1)),
127            vec!["clock-behind-bridge".to_string()]
128        );
129        assert!(
130            relay
131                .advertised_discovery_timesync_sources_for_link_locked(&state, 0, Some(0))
132                .is_empty()
133        );
134        assert!(
135            relay
136                .advertised_discovery_timesync_sources_for_link_locked(
137                    &state,
138                    DISCOVERY_ROUTE_TTL_MS + 1,
139                    Some(1)
140                )
141                .is_empty()
142        );
143        let topology =
144            relay.advertised_discovery_topology_for_link_locked(&state, 0, true, Some(1));
145        assert!(
146            topology
147                .iter()
148                .find(|node| node.sender_id == "bridge")
149                .unwrap()
150                .reachable_endpoints
151                .is_empty()
152        );
153    }
154}
155
156use crate::config::{
157    RuntimeMemoryConfig, runtime_reliable_max_end_to_end_ack_cache,
158    runtime_reliable_max_end_to_end_pending, runtime_reliable_max_pending,
159    runtime_reliable_max_retries, runtime_reliable_max_return_routes,
160    runtime_reliable_retransmit_ms,
161};
162use crate::diagnostics::{
163    AdaptiveLinkStats, DiscoveryRuntimeStats, QueueRuntimeStats, ReliableRuntimeStats,
164    RouteModeStats, RouteOverrideStats, RoutePriorityStats, RouteWeightStats, RuntimeSideStats,
165    RuntimeStatsSnapshot, RuntimeTypeStats, TypedRouteOverrideStats,
166};
167#[cfg(feature = "discovery")]
168use crate::discovery::{
169    self, ClientStatsSnapshot, DISCOVERY_INCREMENTAL_RETRY_COUNT, DISCOVERY_ROUTE_TTL_MS,
170    DISCOVERY_SLOW_LINK_PING_INTERVAL_MS, DiscoveryCadenceState,
171    TIMESYNC_SLOW_LINK_MIN_INTERVAL_MS, TopologyAnnouncerRoute, TopologyBoardNode,
172    TopologySideRoute, TopologySnapshot,
173};
174#[cfg(feature = "discovery")]
175use crate::packet::sender_address_u32;
176use crate::packet::{Packet, hash_bytes_u64};
177use crate::queue::{BoundedDeque, ByteCost};
178use crate::wire_format;
179use crate::{is_reliable_type, message_meta, message_priority, reliable_mode};
180use crate::{
181    router::{Clock, CompactTimestampOmissionPolicy, SideTransportProfile},
182    {
183        RouteSelectionMode, TelemetryError, TelemetryResult,
184        lock::{ReentryGate, RouterMutex},
185    },
186};
187#[cfg(feature = "discovery")]
188use alloc::borrow::ToOwned;
189use alloc::boxed::Box;
190use alloc::collections::{BTreeMap, BTreeSet, VecDeque};
191use alloc::string::{String, ToString};
192use alloc::{sync::Arc, vec, vec::Vec};
193use core::mem::size_of;
194
195const MAX_PRIORITY_BURST: u8 = 8;
196
197/// Logical side index (CAN, UART, RADIO, etc.)
198pub type RelaySideId = usize;
199const CONTROL_SLOW_LINK_CAPACITY_BPS: u64 = 512;
200pub const IPV4_LIKE_COMPACT_HEADER_TARGET_BYTES: usize =
201    crate::router::IPV4_LIKE_COMPACT_HEADER_TARGET_BYTES;
202pub const IPV6_LIKE_COMPACT_HEADER_TARGET_BYTES: usize =
203    crate::router::IPV6_LIKE_COMPACT_HEADER_TARGET_BYTES;
204pub const DEFAULT_SIDE_TRANSPORT_TEMPLATE_LIMIT: usize =
205    crate::router::DEFAULT_SIDE_TRANSPORT_TEMPLATE_LIMIT;
206/// Packet Handler function type
207type PacketHandlerFn = dyn Fn(&Packet) -> TelemetryResult<()> + Send + Sync + 'static;
208
209/// Packed Handler function type
210type PackedHandlerFn = dyn Fn(&[u8]) -> TelemetryResult<()> + Send + Sync + 'static;
211
212/// TX handler for a relay side: either packed or packet-based.
213#[derive(Clone)]
214pub enum RelayTxHandlerFn {
215    Packed(Arc<PackedHandlerFn>),
216    Packet(Arc<PacketHandlerFn>),
217}
218
219#[derive(Clone, Debug)]
220pub struct RelaySideOptions {
221    /// Enables the relay's per-link reliable transport layer on this side.
222    ///
223    /// When `true` and the side uses a packed TX handler, reliable schema traffic on this hop
224    /// gains relay-managed sequence numbers, ACKs, packet requests, and retransmits.
225    /// Packet-output sides still receive decoded packets rather than packed reliable framing.
226    pub reliable_enabled: bool,
227    /// Marks the side as eligible for link-local-only endpoints and discovery routes.
228    pub link_local_enabled: bool,
229    /// Allows packets received from this side to enter relay processing.
230    pub ingress_enabled: bool,
231    /// Allows the relay to transmit packets toward this side.
232    pub egress_enabled: bool,
233    /// Enables side-local header-template reuse for packed transport.
234    pub header_template_enabled: bool,
235    /// Maximum number of bytes to emit per packed TX callback.
236    ///
237    /// When non-zero and a packed frame would exceed this size, the relay
238    /// splits it into ordered side-transport chunks and reassembles them on RX
239    /// before normal relay processing. This is intended for fixed-size links
240    /// such as CAN or I2C while keeping the user API packet-oriented.
241    pub max_frame_bytes: usize,
242    /// Target total side-transport overhead for compact follow-up frames.
243    pub compact_header_target_bytes: usize,
244    /// Maximum side-local header templates retained for TX and RX dictionaries.
245    pub max_side_transport_templates: usize,
246    /// Omits the timestamp field from compact follow-up frames when it is unchanged.
247    pub omit_unchanged_compact_timestamps: bool,
248    /// Optional per-data-type timestamp omission policy for compact follow-up frames.
249    pub compact_timestamp_omission_types: CompactTimestampOmissionPolicy,
250    /// Declared compact-link profile for stats and future negotiation.
251    pub side_transport_profile: SideTransportProfile,
252}
253
254impl Default for RelaySideOptions {
255    fn default() -> Self {
256        Self {
257            reliable_enabled: false,
258            link_local_enabled: false,
259            ingress_enabled: true,
260            egress_enabled: true,
261            header_template_enabled: false,
262            max_frame_bytes: 0,
263            compact_header_target_bytes: 0,
264            max_side_transport_templates: DEFAULT_SIDE_TRANSPORT_TEMPLATE_LIMIT,
265            omit_unchanged_compact_timestamps: false,
266            compact_timestamp_omission_types: CompactTimestampOmissionPolicy::none(),
267            side_transport_profile: SideTransportProfile::Canonical,
268        }
269    }
270}
271
272impl RelaySideOptions {
273    /// Convenience preset for bounded packed-side transport.
274    ///
275    /// `max_frame_bytes == 0` leaves packed frames unbounded. Values greater
276    /// than zero enable relay-managed chunking/reassembly on this side.
277    #[inline]
278    pub fn with_small_packet_transport(mut self, max_frame_bytes: usize) -> Self {
279        self.header_template_enabled = true;
280        self.max_frame_bytes = max_frame_bytes;
281        self.compact_header_target_bytes = IPV6_LIKE_COMPACT_HEADER_TARGET_BYTES;
282        self.side_transport_profile = SideTransportProfile::Ipv6Like;
283        self
284    }
285
286    #[inline]
287    pub fn with_ipv4_like_compact_header_target(mut self) -> Self {
288        self.header_template_enabled = true;
289        self.compact_header_target_bytes = IPV4_LIKE_COMPACT_HEADER_TARGET_BYTES;
290        self.omit_unchanged_compact_timestamps = true;
291        self.side_transport_profile = SideTransportProfile::Ipv4Like;
292        self
293    }
294
295    #[inline]
296    pub fn with_ipv6_like_compact_header_target(mut self) -> Self {
297        self.header_template_enabled = true;
298        self.compact_header_target_bytes = IPV6_LIKE_COMPACT_HEADER_TARGET_BYTES;
299        self.side_transport_profile = SideTransportProfile::Ipv6Like;
300        self
301    }
302
303    #[inline]
304    pub fn with_template_transport(mut self) -> Self {
305        self.header_template_enabled = true;
306        self.side_transport_profile = SideTransportProfile::Template;
307        self
308    }
309
310    #[inline]
311    pub fn with_omitted_unchanged_compact_timestamps(mut self) -> Self {
312        self.header_template_enabled = true;
313        self.omit_unchanged_compact_timestamps = true;
314        self
315    }
316
317    #[inline]
318    pub fn with_omitted_unchanged_compact_timestamps_for_type(
319        mut self,
320        ty: crate::DataType,
321    ) -> Self {
322        self.header_template_enabled = true;
323        self.compact_timestamp_omission_types.insert(ty);
324        self
325    }
326
327    #[inline]
328    pub fn effective_transport_profile(&self) -> SideTransportProfile {
329        if !self.header_template_enabled && self.max_frame_bytes == 0 {
330            SideTransportProfile::Canonical
331        } else if self.side_transport_profile == SideTransportProfile::Canonical {
332            SideTransportProfile::Template
333        } else {
334            self.side_transport_profile
335        }
336    }
337
338    #[cfg(feature = "discovery")]
339    #[inline]
340    pub fn link_capabilities(&self) -> discovery::LinkCapabilities {
341        let mut flags = discovery::LINK_CAPABILITY_END_TO_END_RELIABILITY;
342        if self.header_template_enabled {
343            flags |= discovery::LINK_CAPABILITY_HEADER_TEMPLATES;
344        }
345        if self.max_frame_bytes != 0 {
346            flags |= discovery::LINK_CAPABILITY_CHUNKING;
347        }
348        if self.reliable_enabled {
349            flags |= discovery::LINK_CAPABILITY_RELIABILITY;
350        }
351        if self.omit_unchanged_compact_timestamps
352            || !self.compact_timestamp_omission_types.is_empty()
353        {
354            flags |= discovery::LINK_CAPABILITY_OMIT_UNCHANGED_TIMESTAMPS;
355        }
356        #[cfg(feature = "cryptography")]
357        {
358            flags |= discovery::LINK_CAPABILITY_CRYPTO;
359        }
360        #[cfg(feature = "compression")]
361        {
362            flags |= discovery::LINK_CAPABILITY_PAYLOAD_COMPRESSION;
363        }
364        discovery::LinkCapabilities {
365            version: 1,
366            flags,
367            profile: self.effective_transport_profile().discovery_code(),
368            max_frame_bytes: self.max_frame_bytes.min(u32::MAX as usize) as u32,
369            compact_header_target_bytes: self.compact_header_target_bytes.min(u32::MAX as usize)
370                as u32,
371            max_side_transport_templates: self.max_side_transport_templates.min(u32::MAX as usize)
372                as u32,
373        }
374    }
375}
376
377/// One side of the relay – a name + TX handler.
378#[derive(Clone)]
379pub struct RelaySide {
380    pub name: Arc<str>,
381    pub tx_handler: RelayTxHandlerFn,
382    pub opts: RelaySideOptions,
383}
384
385#[derive(Clone, Debug, PartialEq, Eq)]
386pub enum RelayItem {
387    Packed(crate::SharedBytes),
388    Packet(Arc<Packet>),
389}
390
391/// Item that was received by the relay from some side.
392#[derive(Clone, Debug, PartialEq, Eq)]
393struct RelayRxItem {
394    src: RelaySideId,
395    data: RelayItem,
396    priority: u8,
397}
398
399impl ByteCost for RelayRxItem {
400    #[cfg(feature = "compact-packet-store")]
401    fn park_for_queue(&mut self) -> TelemetryResult<()> {
402        match &mut self.data {
403            RelayItem::Packet(packet) => Arc::make_mut(packet).park_for_queue()?,
404            RelayItem::Packed(bytes) => bytes.park_for_queue()?,
405        }
406        Ok(())
407    }
408    fn byte_cost(&self) -> usize {
409        match &self.data {
410            RelayItem::Packed(bytes) => bytes.len(),
411            RelayItem::Packet(pkt) => pkt.byte_cost(),
412        }
413    }
414}
415
416/// Item that is ready to be transmitted out a destination side.
417#[derive(Clone, Debug, PartialEq, Eq)]
418struct RelayTxItem {
419    src: Option<RelaySideId>,
420    dst: RelaySideId,
421    data: RelayItem,
422    priority: u8,
423    retry_after_ms: u64,
424}
425
426#[derive(Clone, Debug, PartialEq, Eq)]
427struct RelayReplayItem {
428    dst: RelaySideId,
429    ty: crate::DataType,
430    seq: u32,
431    bytes: crate::SharedBytes,
432    priority: u8,
433    retry_after_ms: u64,
434}
435
436impl ByteCost for RelayTxItem {
437    #[cfg(feature = "compact-packet-store")]
438    fn park_for_queue(&mut self) -> TelemetryResult<()> {
439        match &mut self.data {
440            RelayItem::Packet(packet) => Arc::make_mut(packet).park_for_queue()?,
441            RelayItem::Packed(bytes) => bytes.park_for_queue()?,
442        }
443        Ok(())
444    }
445    fn byte_cost(&self) -> usize {
446        (match &self.data {
447            RelayItem::Packed(bytes) => bytes.len(),
448            RelayItem::Packet(pkt) => pkt.byte_cost(),
449        })
450        .saturating_add(size_of::<u64>())
451    }
452}
453
454impl ByteCost for RelayReplayItem {
455    #[cfg(feature = "compact-packet-store")]
456    fn park_for_queue(&mut self) -> TelemetryResult<()> {
457        self.bytes.park_for_queue()
458    }
459    fn byte_cost(&self) -> usize {
460        (self.bytes.len()).saturating_add(2 * size_of::<u64>())
461    }
462}
463
464// -------------------- Reliable delivery state (relay) --------------------
465
466#[derive(Debug, Clone)]
467struct ReliableTxState {
468    next_seq: u32,
469    sent_order: VecDeque<u32>,
470    sent: BTreeMap<u32, ReliableSent>,
471}
472
473#[derive(Debug, Clone)]
474struct ReliableSent {
475    bytes: crate::SharedBytes,
476    last_send_ms: u64,
477    retries: u32,
478    queued: bool,
479    partial_acked: bool,
480}
481
482#[derive(Debug, Clone)]
483struct ReliableRxState {
484    expected_seq: u32,
485    buffered: BTreeMap<u32, crate::SharedBytes>,
486}
487
488#[derive(Debug, Clone)]
489struct ReliableReturnRouteState {
490    side: RelaySideId,
491    last_replay_ms: u64,
492}
493
494#[cfg(feature = "discovery")]
495#[derive(Debug, Clone, Default, PartialEq, Eq)]
496struct DiscoverySenderState {
497    /// Numeric identity advertised for this hostname. Compact frames may carry
498    /// this address instead of the hostname, so routing must accept either.
499    advertised_address: Option<u32>,
500    reachable: Vec<crate::DataEndpoint>,
501    advertised_reachable: Vec<crate::DataEndpoint>,
502    reachable_timesync_sources: Vec<String>,
503    advertised_reachable_timesync_sources: Vec<String>,
504    topology_boards: Arc<Vec<TopologyBoardNode>>,
505    has_full_topology: bool,
506    has_schema: bool,
507    last_seen_ms: u64,
508}
509
510#[inline]
511fn is_internal_control_type(ty: crate::DataType) -> bool {
512    if matches!(
513        ty,
514        crate::DataType::ReliableAck
515            | crate::DataType::ReliablePartialAck
516            | crate::DataType::ReliablePacketRequest
517            | crate::DataType::P2pMessage
518    ) {
519        return true;
520    }
521
522    #[cfg(feature = "timesync")]
523    if matches!(
524        ty,
525        crate::DataType::TimeSyncAnnounce
526            | crate::DataType::TimeSyncRequest
527            | crate::DataType::TimeSyncResponse
528    ) {
529        return true;
530    }
531
532    #[cfg(feature = "discovery")]
533    if discovery::is_discovery_type(ty) {
534        return true;
535    }
536
537    let _ = ty;
538    false
539}
540
541#[cfg(feature = "discovery")]
542#[derive(Debug, Clone, Default, PartialEq, Eq)]
543struct DiscoverySideState {
544    reachable: Vec<crate::DataEndpoint>,
545    reachable_timesync_sources: Vec<String>,
546    last_seen_ms: u64,
547    announcers: BTreeMap<String, DiscoverySenderState>,
548}
549
550#[derive(Clone, Debug, Default)]
551struct SideTransportState {
552    #[cfg(feature = "std")]
553    tx_callback_gate: Arc<ReentryGate>,
554    tx_chunk_progress: Option<Box<crate::side_transport::SideTxProgress>>,
555    #[cfg(all(feature = "compression", feature = "discovery"))]
556    payload_codec: crate::payload_codec::Ledger,
557    #[cfg(all(feature = "compression", feature = "discovery"))]
558    payload_codec_choice: u64,
559    tx_template_ids: BTreeMap<u64, u32>,
560    tx_templates: BTreeMap<u64, SideHeaderTemplate>,
561    tx_last_timestamps: BTreeMap<u32, u64>,
562    tx_compact_uses: BTreeMap<u32, u8>,
563    rx_templates: BTreeMap<u64, u32>,
564    rx_templates_by_id: BTreeMap<u32, SideHeaderTemplate>,
565    rx_last_timestamps: BTreeMap<u32, u64>,
566    rx_chunks: BTreeMap<u32, SideChunkAssembly>,
567    rx_chunk_reclaims: u32,
568    rx_chunk_bytes: usize,
569    next_template_id: u32,
570}
571
572impl SideTransportState {
573    #[inline(never)]
574    fn take_chunk(&mut self, id: u32, reclaimed: bool) -> Option<SideChunkAssembly> {
575        let value = SideChunkAssembly::remove(&mut self.rx_chunks, &mut self.rx_chunk_bytes, id)?;
576        if reclaimed {
577            self.rx_chunk_reclaims = self.rx_chunk_reclaims.saturating_add(1);
578        }
579        Some(value)
580    }
581    /// Seed fixtures that construct raw maps directly.
582    #[cfg(test)]
583    #[inline(never)]
584    #[cfg(feature = "discovery")]
585    fn refresh_rx_chunk_bytes(&mut self) {
586        self.rx_chunk_bytes = self
587            .rx_chunks
588            .values()
589            .map(SideChunkAssembly::byte_cost)
590            .sum();
591    }
592    fn refresh_transmit_header_templates(&mut self) {
593        // Refresh on the wire, not by forgetting the eviction history. RX
594        // retains its dictionary; clearing only TX makes bounded caches evict
595        // different entries after the next insertion/capacity negotiation.
596        for uses in self.tx_compact_uses.values_mut() {
597            *uses = u8::MAX;
598        }
599        // Local topology changes do not invalidate dictionaries received from
600        // peers. Dropping RX state here loses already-in-flight compact ACKs.
601    }
602
603    fn tx_template_count(&self) -> usize {
604        self.tx_template_ids.len()
605    }
606
607    fn rx_template_count(&self) -> usize {
608        self.rx_templates_by_id.len()
609    }
610
611    fn trim_tx_templates_to(&mut self, limit: usize) -> usize {
612        let mut removed = 0usize;
613        while self.tx_template_ids.len() > limit {
614            let Some(old_hash) = self.tx_template_ids.keys().next().copied() else {
615                break;
616            };
617            if let Some(old_id) = self.tx_template_ids.remove(&old_hash) {
618                self.tx_last_timestamps.remove(&old_id);
619                self.tx_compact_uses.remove(&old_id);
620            }
621            self.tx_templates.remove(&old_hash);
622            removed += 1;
623        }
624        removed
625    }
626
627    fn insert_tx_template(
628        &mut self,
629        template: SideHeaderTemplate,
630        template_id: u32,
631        max_templates: usize,
632    ) -> bool {
633        if max_templates == 0 {
634            return false;
635        }
636        let mut evicted = false;
637        if self.tx_template_ids.len() >= max_templates
638            && !self.tx_template_ids.contains_key(&template.hash)
639            && let Some(old_hash) = self.tx_template_ids.keys().next().copied()
640        {
641            if let Some(old_id) = self.tx_template_ids.remove(&old_hash) {
642                self.tx_last_timestamps.remove(&old_id);
643                self.tx_compact_uses.remove(&old_id);
644            }
645            self.tx_templates.remove(&old_hash);
646            evicted = true;
647        }
648        self.tx_template_ids.insert(template.hash, template_id);
649        self.tx_templates.insert(template.hash, template);
650        self.tx_compact_uses.insert(template_id, 0);
651        evicted
652    }
653
654    fn insert_rx_template(
655        &mut self,
656        template_id: u32,
657        template: SideHeaderTemplate,
658        max_templates: usize,
659    ) -> bool {
660        if max_templates == 0 {
661            return false;
662        }
663        // Keep one owned header. The hash index needs only its wire ID, not
664        // another copy of the header and its shared-buffer owners.
665        if let Some(old_id) = self.rx_templates.get(&template.hash).copied()
666            && old_id != template_id
667        {
668            self.rx_templates_by_id.remove(&old_id);
669            self.rx_last_timestamps.remove(&old_id);
670            self.rx_templates.remove(&template.hash);
671        }
672        if let Some(previous) = self.rx_templates_by_id.get(&template_id)
673            && previous.hash != template.hash
674        {
675            self.rx_templates.remove(&previous.hash);
676            self.rx_last_timestamps.remove(&template_id);
677        }
678        let mut evicted = false;
679        if self.rx_templates_by_id.len() >= max_templates
680            && !self.rx_templates_by_id.contains_key(&template_id)
681            // Match TX's deterministic lowest-hash eviction on both routers
682            // and relays; lowest wire ID can select a different live header.
683            && let Some((&old_hash, &old_id)) = self.rx_templates.first_key_value()
684        {
685            self.rx_templates.remove(&old_hash);
686            self.rx_templates_by_id.remove(&old_id);
687            self.rx_last_timestamps.remove(&old_id);
688            evicted = true;
689        }
690        self.rx_templates.insert(template.hash, template_id);
691        self.rx_templates_by_id.insert(template_id, template);
692        evicted
693    }
694}
695
696#[derive(Debug, Clone, Default)]
697struct AdaptiveRouteStats {
698    estimated_bandwidth_bps: u64,
699    peak_bandwidth_bps: u64,
700    last_observed_ms: u64,
701    last_slow_observed_ms: u64,
702    sample_count: u64,
703    window_started_ms: u64,
704    window_bytes: u64,
705    peak_usage_bps: u64,
706}
707
708#[cfg(feature = "discovery")]
709#[derive(Debug, Clone, Default)]
710struct DiscoverySideThrottleState {
711    next_topology_request_ms: u64,
712    next_schema_request_ms: u64,
713    next_ping_ms: u64,
714    // Scheduled and accepted sends differ when a constrained link is backed up.
715    last_liveness_tx_ms: u64,
716    pending_incremental: bool,
717    incremental_retries_remaining: u8,
718    has_sent_full: bool,
719    last_topology: Vec<TopologyBoardNode>,
720}
721
722#[cfg(feature = "discovery")]
723impl DiscoverySideThrottleState {
724    fn liveness_due(&self, now_ms: u64) -> bool {
725        now_ms >= self.next_ping_ms
726            && now_ms.saturating_sub(self.last_liveness_tx_ms)
727                >= DISCOVERY_SLOW_LINK_PING_INTERVAL_MS
728    }
729}
730
731#[cfg(all(feature = "discovery", feature = "timesync"))]
732#[derive(Debug, Clone, Default)]
733struct TimeSyncSideThrottleState {
734    next_allowed_ms: u64,
735}
736
737#[cfg(feature = "discovery")]
738#[derive(Debug, Clone, Copy, PartialEq, Eq)]
739enum DiscoveryAdvertiseLevel {
740    MinimalPing,
741    Incremental,
742    Full,
743}
744
745impl AdaptiveRouteStats {
746    #[inline]
747    fn observe(&mut self, bytes: usize, sample_bps: u64, now_ms: u64) {
748        self.estimated_bandwidth_bps = if self.estimated_bandwidth_bps == 0 {
749            sample_bps
750        } else if sample_bps >= self.estimated_bandwidth_bps {
751            self.estimated_bandwidth_bps
752                .saturating_mul(3)
753                .saturating_add(sample_bps.saturating_mul(5))
754                / 8
755        } else {
756            self.estimated_bandwidth_bps
757                .saturating_mul(7)
758                .saturating_add(sample_bps)
759                / 8
760        };
761        self.peak_bandwidth_bps = self.peak_bandwidth_bps.max(sample_bps);
762        self.last_observed_ms = now_ms;
763        if sample_bps > 0 && sample_bps <= CONTROL_SLOW_LINK_CAPACITY_BPS {
764            self.last_slow_observed_ms = now_ms;
765        }
766        self.sample_count = self.sample_count.saturating_add(1);
767        if self.window_started_ms == 0 || now_ms.saturating_sub(self.window_started_ms) > 1_000 {
768            self.window_started_ms = now_ms;
769            self.window_bytes = 0;
770        }
771        self.window_bytes = self.window_bytes.saturating_add(bytes as u64);
772        self.peak_usage_bps = self.peak_usage_bps.max(self.current_usage_bps(now_ms));
773    }
774
775    #[inline]
776    fn current_usage_bps(&self, now_ms: u64) -> u64 {
777        if self.window_started_ms == 0 {
778            return 0;
779        }
780        let elapsed_ms = now_ms.saturating_sub(self.window_started_ms).max(1);
781        (u128::from(self.window_bytes).saturating_mul(1000) / u128::from(elapsed_ms))
782            .min(u128::from(u64::MAX)) as u64
783    }
784
785    #[inline]
786    fn available_headroom_bps(&self, now_ms: u64) -> u64 {
787        let capacity = self
788            .estimated_bandwidth_bps
789            .max(self.peak_bandwidth_bps)
790            .max(1);
791        capacity.saturating_sub(self.current_usage_bps(now_ms))
792    }
793
794    #[inline]
795    fn weight(&self, now_ms: u64) -> u64 {
796        self.available_headroom_bps(now_ms).max(1)
797    }
798
799    #[inline]
800    fn snapshot(&self, now_ms: u64, auto_balancing_enabled: bool) -> AdaptiveLinkStats {
801        let current_usage_bps = self.current_usage_bps(now_ms);
802        let estimated_capacity_bps = self.estimated_bandwidth_bps.max(1);
803        let peak_capacity_bps = self.peak_bandwidth_bps.max(estimated_capacity_bps);
804        let available_headroom_bps = peak_capacity_bps.saturating_sub(current_usage_bps);
805        AdaptiveLinkStats {
806            auto_balancing_enabled,
807            estimated_capacity_bps,
808            peak_capacity_bps,
809            current_usage_bps,
810            peak_usage_bps: self.peak_usage_bps.max(current_usage_bps),
811            available_headroom_bps,
812            effective_weight: available_headroom_bps.max(1),
813            last_observed_ms: self.last_observed_ms,
814            sample_count: self.sample_count,
815        }
816    }
817}
818
819use crate::type_stats::TypeRuntimeStatsInner;
820
821#[derive(Debug, Clone, Default)]
822struct SideRuntimeStatsInner {
823    tx_packets: u64,
824    tx_bytes: u64,
825    rx_packets: u64,
826    rx_bytes: u64,
827    relayed_tx_packets: u64,
828    relayed_tx_bytes: u64,
829    relayed_rx_packets: u64,
830    relayed_rx_bytes: u64,
831    tx_retries: u64,
832    tx_handler_failures: u64,
833    total_handler_retries: u64,
834    side_transport_full_frames: u64,
835    side_transport_compact_frames: u64,
836    side_transport_compact_delta_frames: u64,
837    side_transport_compact_omitted_timestamp_frames: u64,
838    side_transport_chunk_frames: u64,
839    side_transport_raw_bytes: u64,
840    side_transport_wire_bytes: u64,
841    side_transport_bytes_saved: u64,
842    side_transport_min_compact_overhead_bytes: Option<usize>,
843    side_transport_max_compact_overhead_bytes: Option<usize>,
844    side_transport_compact_target_misses: u64,
845    side_transport_template_evictions: u64,
846    data_types: BTreeMap<u32, TypeRuntimeStatsInner>,
847}
848
849impl SideRuntimeStatsInner {
850    fn type_stats_mut(&mut self, ty: crate::DataType) -> &mut TypeRuntimeStatsInner {
851        self.data_types.entry(ty.as_u32()).or_default()
852    }
853
854    fn note_tx(&mut self, ty: crate::DataType, bytes: usize, retries: usize) {
855        self.tx_packets = self.tx_packets.saturating_add(1);
856        self.tx_bytes = self.tx_bytes.saturating_add(bytes as u64);
857        self.relayed_tx_packets = self.relayed_tx_packets.saturating_add(1);
858        self.relayed_tx_bytes = self.relayed_tx_bytes.saturating_add(bytes as u64);
859        self.tx_retries = self.tx_retries.saturating_add(retries as u64);
860        self.total_handler_retries = self.total_handler_retries.saturating_add(retries as u64);
861        self.type_stats_mut(ty)
862            .tx(bytes as u64, true, retries as u64);
863    }
864
865    fn note_rx(&mut self, ty: crate::DataType, bytes: usize) {
866        self.rx_packets = self.rx_packets.saturating_add(1);
867        self.rx_bytes = self.rx_bytes.saturating_add(bytes as u64);
868        self.relayed_rx_packets = self.relayed_rx_packets.saturating_add(1);
869        self.relayed_rx_bytes = self.relayed_rx_bytes.saturating_add(bytes as u64);
870        self.type_stats_mut(ty).rx(bytes as u64, true);
871    }
872
873    fn note_tx_failure(&mut self, ty: crate::DataType, retries: usize) {
874        self.tx_handler_failures = self.tx_handler_failures.saturating_add(1);
875        self.tx_retries = self.tx_retries.saturating_add(retries as u64);
876        self.total_handler_retries = self.total_handler_retries.saturating_add(retries as u64);
877        self.type_stats_mut(ty).failure(retries as u64);
878    }
879
880    fn note_side_transport_full(&mut self, raw_bytes: usize, wire_bytes: usize) {
881        self.side_transport_full_frames = self.side_transport_full_frames.saturating_add(1);
882        self.note_side_transport_bytes(raw_bytes, wire_bytes);
883    }
884
885    fn note_side_transport_compact(
886        &mut self,
887        raw_bytes: usize,
888        wire_bytes: usize,
889        compact_overhead_bytes: usize,
890        used_timestamp_delta: bool,
891        omitted_timestamp: bool,
892    ) {
893        self.side_transport_compact_frames = self.side_transport_compact_frames.saturating_add(1);
894        if used_timestamp_delta {
895            self.side_transport_compact_delta_frames =
896                self.side_transport_compact_delta_frames.saturating_add(1);
897        }
898        if omitted_timestamp {
899            self.side_transport_compact_omitted_timestamp_frames = self
900                .side_transport_compact_omitted_timestamp_frames
901                .saturating_add(1);
902        }
903        self.note_side_transport_bytes(raw_bytes, wire_bytes);
904        self.side_transport_min_compact_overhead_bytes = Some(
905            self.side_transport_min_compact_overhead_bytes
906                .map_or(compact_overhead_bytes, |v| v.min(compact_overhead_bytes)),
907        );
908        self.side_transport_max_compact_overhead_bytes = Some(
909            self.side_transport_max_compact_overhead_bytes
910                .map_or(compact_overhead_bytes, |v| v.max(compact_overhead_bytes)),
911        );
912    }
913
914    fn note_side_transport_chunks(&mut self, chunks: usize) {
915        self.side_transport_chunk_frames = self
916            .side_transport_chunk_frames
917            .saturating_add(chunks as u64);
918    }
919
920    fn note_side_transport_bytes(&mut self, raw_bytes: usize, wire_bytes: usize) {
921        self.side_transport_raw_bytes = self
922            .side_transport_raw_bytes
923            .saturating_add(raw_bytes as u64);
924        self.side_transport_wire_bytes = self
925            .side_transport_wire_bytes
926            .saturating_add(wire_bytes as u64);
927        if raw_bytes > wire_bytes {
928            self.side_transport_bytes_saved = self
929                .side_transport_bytes_saved
930                .saturating_add((raw_bytes - wire_bytes) as u64);
931        }
932    }
933
934    fn note_side_transport_compact_target_miss(&mut self) {
935        self.side_transport_compact_target_misses =
936            self.side_transport_compact_target_misses.saturating_add(1);
937    }
938
939    fn note_side_transport_template_eviction(&mut self) {
940        self.side_transport_template_evictions =
941            self.side_transport_template_evictions.saturating_add(1);
942    }
943}
944
945#[derive(Clone, Debug)]
946pub struct RelayConfig {
947    sender: Arc<str>,
948    memory: RuntimeMemoryConfig,
949}
950
951impl RelayConfig {
952    pub fn new() -> Self {
953        Self::default()
954    }
955
956    pub fn with_sender<S: AsRef<str>>(mut self, sender: S) -> Self {
957        self.sender = Arc::from(sender.as_ref());
958        self
959    }
960
961    pub fn with_memory_config(mut self, memory: RuntimeMemoryConfig) -> TelemetryResult<Self> {
962        memory.validate()?;
963        self.memory = memory;
964        Ok(self)
965    }
966
967    fn sender(&self) -> Arc<str> {
968        self.sender.clone()
969    }
970
971    fn memory_config(&self) -> RuntimeMemoryConfig {
972        self.memory
973    }
974}
975
976impl Default for RelayConfig {
977    fn default() -> Self {
978        Self {
979            sender: Arc::from("RELAY"),
980            memory: RuntimeMemoryConfig::default(),
981        }
982    }
983}
984
985#[derive(Clone, Copy, Debug, PartialEq, Eq)]
986enum RouteSelectionOrigin {
987    Flood,
988    Discovered,
989}
990
991/// Internal state, protected by RouterMutex so all public methods can take &self.
992struct RelayInner {
993    #[cfg(feature = "compression")]
994    codec_reserved_bytes: usize,
995    memory: RuntimeMemoryConfig,
996    sides: Vec<Option<RelaySide>>,
997    route_overrides: BTreeMap<(Option<RelaySideId>, RelaySideId), bool>,
998    typed_route_overrides: BTreeMap<(Option<RelaySideId>, u32, RelaySideId), bool>,
999    route_weights: BTreeMap<(Option<RelaySideId>, RelaySideId), u32>,
1000    route_priorities: BTreeMap<(Option<RelaySideId>, RelaySideId), u32>,
1001    source_route_modes: BTreeMap<Option<RelaySideId>, RouteSelectionMode>,
1002    route_selection_cursors: BTreeMap<Option<RelaySideId>, u64>,
1003    adaptive_route_stats: BTreeMap<RelaySideId, AdaptiveRouteStats>,
1004    // Sparse side tables must not reserve eleven full metadata values in
1005    // every B-tree leaf. Own each side once; keep the index pointer-sized.
1006    side_runtime_stats: BTreeMap<RelaySideId, Box<SideRuntimeStatsInner>>,
1007    side_transport: BTreeMap<RelaySideId, Box<SideTransportState>>,
1008    rx_queue: BoundedDeque<RelayRxItem>,
1009    tx_queue: BoundedDeque<RelayTxItem>,
1010    tx_priority_burst: u8,
1011    rx_priority_burst: u8,
1012    replay_queue: BoundedDeque<RelayReplayItem>,
1013    recent_rx: BoundedDeque<u64>,
1014    reliable_tx: BTreeMap<(RelaySideId, u32), ReliableTxState>,
1015    reliable_rx: BTreeMap<(RelaySideId, u32), ReliableRxState>,
1016    reliable_return_routes: BTreeMap<u64, ReliableReturnRouteState>,
1017    reliable_return_route_order: VecDeque<u64>,
1018    end_to_end_acked_destinations: BTreeMap<u64, BTreeSet<u64>>,
1019    end_to_end_acked_destination_order: VecDeque<u64>,
1020    total_handler_failures: u64,
1021    total_handler_retries: u64,
1022    #[cfg(feature = "discovery")]
1023    discovery_routes: BTreeMap<RelaySideId, DiscoverySideState>,
1024    #[cfg(feature = "discovery")]
1025    discovery_cadence: DiscoveryCadenceState,
1026    #[cfg(feature = "discovery")]
1027    discovery_side_throttle: BTreeMap<RelaySideId, DiscoverySideThrottleState>,
1028    #[cfg(all(feature = "discovery", feature = "timesync"))]
1029    timesync_side_throttle: BTreeMap<RelaySideId, TimeSyncSideThrottleState>,
1030}
1031
1032#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1033enum RelayQueueKind {
1034    Rx,
1035    Tx,
1036    Replay,
1037}
1038
1039#[cfg(feature = "compression")]
1040impl crate::payload_codec::QueueBudget for RelayInner {
1041    fn codec_reserved(&mut self) -> &mut usize {
1042        &mut self.codec_reserved_bytes
1043    }
1044    fn codec_used(&self) -> usize {
1045        self.shared_queue_bytes_used()
1046    }
1047    fn codec_limit(&self) -> usize {
1048        self.memory.max_queue_budget
1049    }
1050}
1051
1052impl RelayInner {
1053    #[cfg(all(feature = "compression", feature = "discovery"))]
1054    fn reclaim_codec_metadata(&mut self) -> bool {
1055        self.side_transport.values_mut().any(|transport| {
1056            transport
1057                .payload_codec
1058                .evict_for_budget(DISCOVERY_ROUTE_TTL_MS)
1059        })
1060    }
1061
1062    /// Prepare required plaintext before publishing its queue owner. The state
1063    /// lock prevents another producer taking the checked expanded byte credit.
1064    /// A refused optional preparation leaves the original accepted work owned;
1065    /// dispatch will backpressure it until the required decoder scratch fits.
1066    #[cfg(feature = "compression")]
1067    fn prepare_codec_queue_owner(&mut self, side: Option<usize>, data: &mut RelayItem) {
1068        let RelayItem::Packed(bytes) = data else {
1069            return;
1070        };
1071        if !bytes.first().is_some_and(|flags| flags & 0x11 == 1) {
1072            return;
1073        }
1074        let capable = {
1075            #[cfg(feature = "discovery")]
1076            {
1077                side.and_then(|side| self.side_transport.get(&side))
1078                    .is_some_and(|transport| {
1079                        transport.payload_codec.retention_can_stay_compressed()
1080                    })
1081            }
1082            #[cfg(not(feature = "discovery"))]
1083            {
1084                let _ = side;
1085                false
1086            }
1087        };
1088        if capable {
1089            return;
1090        }
1091        let Ok(required) = wire_format::decoded_frame_upper_bound(bytes.as_ref()) else {
1092            return;
1093        };
1094        if self
1095            .shared_queue_bytes_used()
1096            .checked_add(required)
1097            .and_then(|used| used.checked_add(size_of::<u64>()))
1098            .is_none_or(|used| used > self.memory.max_queue_budget)
1099        {
1100            return;
1101        }
1102        if let Ok(Some(raw)) = wire_format::payload_encoding_for_retention(
1103            bytes.as_ref(),
1104            self.memory.max_queue_budget,
1105        ) {
1106            *bytes = crate::shared_bytes::convert(raw);
1107        }
1108    }
1109
1110    fn retain_replays<F>(&mut self, keep: F)
1111    where
1112        F: FnMut(&RelayReplayItem) -> bool,
1113    {
1114        let reliable_tx = &mut self.reliable_tx;
1115        self.replay_queue.retain_observed(keep, |evicted| {
1116            if let Some(sent) = reliable_tx
1117                .get_mut(&(evicted.dst, evicted.ty.as_u32()))
1118                .and_then(|tx| tx.sent.get_mut(&evicted.seq))
1119            {
1120                sent.queued = false;
1121            }
1122        });
1123    }
1124
1125    #[cfg(any(feature = "std", feature = "fair-admission"))]
1126    fn fair_bulk_owner(data: &RelayItem, priority: u8) -> Option<u32> {
1127        match data {
1128            RelayItem::Packet(packet) => crate::admission_fairness::packet_owner(packet, priority),
1129            RelayItem::Packed(bytes) => crate::shared_bytes::with_slice(bytes, |view| {
1130                crate::admission_fairness::packed_owner(view, priority)
1131            })
1132            .ok()
1133            .flatten(),
1134        }
1135    }
1136    #[cfg(any(feature = "std", feature = "fair-admission"))]
1137    fn bulk_stream(data: &RelayItem) -> Option<u32> {
1138        match data {
1139            RelayItem::Packet(packet) => Some(packet.data_type().as_u32()),
1140            RelayItem::Packed(bytes) => crate::shared_bytes::with_slice(bytes, |view| {
1141                wire_format::scheduling_type(view).map(|ty| ty.as_u32())
1142            })
1143            .ok()
1144            .flatten(),
1145        }
1146    }
1147    #[cfg(any(feature = "std", feature = "fair-admission"))]
1148    fn bulk_loads(&self, priority: u8) -> crate::admission_fairness::Loads {
1149        let mut loads = crate::admission_fairness::Loads::new();
1150        for item in self.rx_queue.iter() {
1151            if item.priority <= priority
1152                && let Some(owner) = Self::fair_bulk_owner(&item.data, item.priority)
1153            {
1154                loads.record(owner, item.byte_cost());
1155            }
1156        }
1157        for item in self.tx_queue.iter() {
1158            if item.priority <= priority
1159                && let Some(owner) = Self::fair_bulk_owner(&item.data, item.priority)
1160            {
1161                loads.record(owner, item.byte_cost());
1162            }
1163        }
1164        loads
1165    }
1166    #[cfg(any(feature = "std", feature = "fair-admission"))]
1167    fn competing_bulk_owner(&self, incoming: u32, priority: u8) -> Option<u32> {
1168        self.bulk_loads(priority).dominant_other(incoming)
1169    }
1170    #[cfg(any(feature = "std", feature = "fair-admission"))]
1171    fn reclaim_bulk_from(
1172        &mut self,
1173        owner: u32,
1174        priority: u8,
1175        incoming_stream: Option<u32>,
1176    ) -> bool {
1177        let mut streams = crate::admission_fairness::Loads::new();
1178        for item in self.rx_queue.iter() {
1179            if item.priority <= priority
1180                && Self::fair_bulk_owner(&item.data, item.priority) == Some(owner)
1181                && let Some(ty) = Self::bulk_stream(&item.data)
1182            {
1183                streams.record(ty, item.byte_cost());
1184            }
1185        }
1186        for item in self.tx_queue.iter() {
1187            if item.priority <= priority
1188                && Self::fair_bulk_owner(&item.data, item.priority) == Some(owner)
1189                && let Some(ty) = Self::bulk_stream(&item.data)
1190            {
1191                streams.record(ty, item.byte_cost());
1192            }
1193        }
1194        let stream = streams.queue_victim(incoming_stream);
1195        let rx = self
1196            .rx_queue
1197            .iter()
1198            .enumerate()
1199            .filter(|(_, item)| {
1200                item.priority <= priority
1201                    && Self::fair_bulk_owner(&item.data, item.priority) == Some(owner)
1202                    && stream.is_none_or(|ty| Self::bulk_stream(&item.data) == Some(ty))
1203            })
1204            .min_by_key(|(index, item)| (item.priority, *index))
1205            .map(|(index, item)| (index, item.priority));
1206        let tx = self
1207            .tx_queue
1208            .iter()
1209            .enumerate()
1210            .filter(|(_, item)| {
1211                item.priority <= priority
1212                    && Self::fair_bulk_owner(&item.data, item.priority) == Some(owner)
1213                    && stream.is_none_or(|ty| Self::bulk_stream(&item.data) == Some(ty))
1214            })
1215            .min_by_key(|(index, item)| (item.priority, *index))
1216            .map(|(index, item)| (index, item.priority));
1217        match (rx, tx) {
1218            (Some((index, rp)), Some((_, tp))) if rp <= tp => {
1219                self.rx_queue.remove_pos(index).is_some()
1220            }
1221            (Some((index, _)), None) => self.rx_queue.remove_pos(index).is_some(),
1222            (_, Some((index, _))) => self.tx_queue.remove_pos(index).is_some(),
1223            _ => false,
1224        }
1225    }
1226
1227    #[cfg(feature = "discovery")]
1228    fn topology_board_byte_cost(board: &TopologyBoardNode) -> usize {
1229        board
1230            .sender_id
1231            .len()
1232            .saturating_add(board.reachable_endpoints.len() * size_of::<crate::DataEndpoint>())
1233            .saturating_add(
1234                board
1235                    .reachable_timesync_sources
1236                    .iter()
1237                    .map(|s| s.len())
1238                    .sum::<usize>(),
1239            )
1240            .saturating_add(board.connections.iter().map(|s| s.len()).sum::<usize>())
1241    }
1242
1243    #[cfg(feature = "discovery")]
1244    fn discovery_sender_byte_cost(sender: &str, state: &DiscoverySenderState) -> usize {
1245        sender
1246            .len()
1247            .saturating_add(state.reachable.len() * size_of::<crate::DataEndpoint>())
1248            .saturating_add(state.advertised_reachable.len() * size_of::<crate::DataEndpoint>())
1249            .saturating_add(
1250                state
1251                    .reachable_timesync_sources
1252                    .iter()
1253                    .map(|s| s.len())
1254                    .sum::<usize>(),
1255            )
1256            .saturating_add(
1257                state
1258                    .advertised_reachable_timesync_sources
1259                    .iter()
1260                    .map(|s| s.len())
1261                    .sum::<usize>(),
1262            )
1263            .saturating_add(
1264                state
1265                    .topology_boards
1266                    .iter()
1267                    .map(Self::topology_board_byte_cost)
1268                    .sum::<usize>(),
1269            )
1270            .saturating_add(size_of::<DiscoverySenderState>())
1271    }
1272
1273    #[cfg(feature = "discovery")]
1274    fn discovery_route_byte_cost(route: &DiscoverySideState) -> usize {
1275        size_of::<DiscoverySideState>()
1276            .saturating_add(route.reachable.len() * size_of::<crate::DataEndpoint>())
1277            .saturating_add(
1278                route
1279                    .reachable_timesync_sources
1280                    .iter()
1281                    .map(|s| s.len())
1282                    .sum::<usize>(),
1283            )
1284            .saturating_add(
1285                route
1286                    .announcers
1287                    .iter()
1288                    .map(|(sender, state)| Self::discovery_sender_byte_cost(sender, state))
1289                    .sum::<usize>(),
1290            )
1291    }
1292
1293    #[cfg(feature = "discovery")]
1294    fn discovery_bytes_used(&self) -> usize {
1295        self.discovery_routes
1296            .values()
1297            .map(Self::discovery_route_byte_cost)
1298            .sum()
1299    }
1300
1301    #[inline]
1302    fn reliable_rx_buffered_bytes(&self) -> usize {
1303        self.reliable_rx
1304            .values()
1305            .flat_map(|state| state.buffered.values())
1306            .map(|bytes| size_of::<crate::SharedBytes>() + bytes.len())
1307            .sum()
1308    }
1309
1310    #[inline(never)]
1311    fn side_chunk_bytes_used(&self) -> usize {
1312        self.side_transport
1313            .values()
1314            .map(|side| side.rx_chunk_bytes)
1315            .sum()
1316    }
1317    fn discard_side_chunk(&mut self, side: RelaySideId, id: u32) -> bool {
1318        self.side_transport
1319            .get_mut(&side)
1320            .is_some_and(|state| state.take_chunk(id, true).is_some())
1321    }
1322    fn reclaim_oldest_side_chunk(&mut self, keep: Option<(RelaySideId, u32)>) -> bool {
1323        let mut victim = None;
1324        let mut time = u64::MAX;
1325        for (&side, state) in &self.side_transport {
1326            let preserved = match keep {
1327                Some((owner, id)) if owner == side => Some(id),
1328                _ => None,
1329            };
1330            if let Some((id, seen)) = SideChunkAssembly::oldest(&state.rx_chunks, preserved)
1331                && (victim.is_none() || seen < time)
1332            {
1333                time = seen;
1334                victim = Some((side, id));
1335            }
1336        }
1337        match victim {
1338            Some((side, id)) => self.discard_side_chunk(side, id),
1339            None => false,
1340        }
1341    }
1342    fn make_side_chunk_room(
1343        &mut self,
1344        side: RelaySideId,
1345        id: u32,
1346        index: u16,
1347        total: u16,
1348        payload_len: usize,
1349    ) -> TelemetryResult<()> {
1350        let budget = self
1351            .memory
1352            .max_queue_budget
1353            .saturating_sub(self.protected_metadata_bytes_used());
1354        let state = self
1355            .side_transport
1356            .get_mut(&side)
1357            .ok_or(TelemetryError::BadArg)?;
1358        let existing = state.rx_chunks.get(&id).map(|a| a.total);
1359        if existing.is_some_and(|old| old != 0 && old != total) {
1360            state.take_chunk(id, true);
1361            return Err(TelemetryError::Unpack("side chunk total mismatch"));
1362        }
1363        let before = state.rx_chunks.len();
1364        let room = SideChunkAssembly::make_room(
1365            &mut state.rx_chunks,
1366            &mut state.rx_chunk_bytes,
1367            id,
1368            index,
1369            total,
1370            payload_len,
1371            budget,
1372        );
1373        if before != state.rx_chunks.len() {
1374            state.rx_chunk_reclaims = state
1375                .rx_chunk_reclaims
1376                .saturating_add((before - state.rx_chunks.len()) as u32);
1377        }
1378        let cost = room?;
1379        if cost == 0 {
1380            return Ok(());
1381        }
1382        if existing.is_none()
1383            && state.rx_chunks.len() >= 4
1384            && let Some((oldest, _)) = SideChunkAssembly::oldest(&state.rx_chunks, None)
1385        {
1386            state.take_chunk(oldest, true);
1387        }
1388        while self.shared_queue_bytes_used().saturating_add(cost) > self.memory.max_queue_budget {
1389            #[cfg(any(feature = "std", feature = "fair-admission"))]
1390            if let Some(owner) = self.bulk_loads(199).queue_victim(None)
1391                && self.reclaim_bulk_from(owner, 199, None)
1392            {
1393                continue;
1394            }
1395            if self.reclaim_oldest_side_chunk(Some((side, id))) {
1396                continue;
1397            }
1398            // The sender retries this fragment, not the earlier accepted ones.
1399            // Keep the bounded partial assembly while protected queues drain.
1400            return Err(TelemetryError::Io("memory pressure"));
1401        }
1402        Ok(())
1403    }
1404
1405    #[inline(never)]
1406    fn protected_metadata_bytes_used(&self) -> usize {
1407        let codec_bytes = {
1408            #[cfg(all(feature = "compression", feature = "discovery"))]
1409            {
1410                self.side_transport
1411                    .values()
1412                    .map(|side| side.payload_codec.heap_bytes())
1413                    .sum::<usize>()
1414            }
1415            #[cfg(not(all(feature = "compression", feature = "discovery")))]
1416            {
1417                0usize
1418            }
1419        };
1420        codec_bytes.saturating_add(
1421            self.recent_rx
1422                .max_bytes()
1423                .saturating_add(self.reliable_rx_buffered_bytes())
1424                .saturating_add(crate::config::schema_bytes_used())
1425                .saturating_add({
1426                    #[cfg(feature = "discovery")]
1427                    {
1428                        self.discovery_bytes_used()
1429                    }
1430                    #[cfg(not(feature = "discovery"))]
1431                    {
1432                        0
1433                    }
1434                }),
1435        )
1436    }
1437    #[inline]
1438    fn shared_queue_bytes_used(&self) -> usize {
1439        self.rx_queue
1440            .bytes_used()
1441            .saturating_add(self.tx_queue.bytes_used())
1442            .saturating_add(self.replay_queue.bytes_used())
1443            .saturating_add(self.side_chunk_bytes_used())
1444            .saturating_add(self.protected_metadata_bytes_used())
1445            .saturating_add({
1446                #[cfg(feature = "compression")]
1447                {
1448                    self.codec_reserved_bytes
1449                }
1450                #[cfg(not(feature = "compression"))]
1451                {
1452                    0
1453                }
1454            })
1455    }
1456
1457    fn reliable_rx_buffer_len(&self) -> usize {
1458        self.reliable_rx
1459            .values()
1460            .map(|state| state.buffered.len())
1461            .sum()
1462    }
1463
1464    fn pop_reliable_rx_buffered(&mut self) -> Option<crate::SharedBytes> {
1465        let key = self
1466            .reliable_rx
1467            .iter()
1468            .find_map(|(key, state)| (!state.buffered.is_empty()).then_some(*key))?;
1469        self.reliable_rx
1470            .get_mut(&key)?
1471            .buffered
1472            .pop_first()
1473            .map(|(_, v)| v)
1474    }
1475
1476    fn pop_lowest_packet_queue_item(&mut self, below: usize) -> bool {
1477        let victim = [
1478            (
1479                RelayQueueKind::Rx,
1480                self.rx_queue.lowest_priority(|item| item.priority),
1481            ),
1482            (
1483                RelayQueueKind::Tx,
1484                self.tx_queue.lowest_priority(|item| item.priority),
1485            ),
1486            (
1487                RelayQueueKind::Replay,
1488                self.replay_queue.lowest_priority(|item| item.priority),
1489            ),
1490        ]
1491        .into_iter()
1492        .filter_map(|(kind, priority)| priority.map(|priority| (kind, priority)))
1493        .filter(|(_, priority)| usize::from(*priority) < below)
1494        .min_by_key(|(_, priority)| *priority)
1495        .map(|(kind, _)| kind);
1496        match victim {
1497            Some(RelayQueueKind::Rx) => self.rx_queue.pop_lowest_priority().is_some(),
1498            Some(RelayQueueKind::Tx) => self.tx_queue.pop_lowest_priority().is_some(),
1499            Some(RelayQueueKind::Replay) => {
1500                let Some(item) = self.replay_queue.pop_lowest_priority() else {
1501                    return false;
1502                };
1503                if let Some(sent) = self
1504                    .reliable_tx
1505                    .get_mut(&(item.dst, item.ty.as_u32()))
1506                    .and_then(|tx| tx.sent.get_mut(&item.seq))
1507                {
1508                    sent.queued = false;
1509                }
1510                true
1511            }
1512            _ => false,
1513        }
1514    }
1515
1516    fn make_shared_queue_room(
1517        &mut self,
1518        incoming_cost: usize,
1519        _incoming_priority: u8,
1520        _incoming: Option<&RelayItem>,
1521    ) -> TelemetryResult<()> {
1522        if incoming_cost > self.memory.max_queue_budget {
1523            return Err(TelemetryError::PacketTooLarge(
1524                "Item exceeds maximum shared queue budget",
1525            ));
1526        }
1527
1528        #[cfg(any(feature = "std", feature = "fair-admission"))]
1529        let mut fair_victim = None;
1530        while self.shared_queue_bytes_used().saturating_add(incoming_cost)
1531            > self.memory.max_queue_budget
1532        {
1533            #[cfg(all(feature = "compression", feature = "discovery"))]
1534            if self.reclaim_codec_metadata() {
1535                continue;
1536            }
1537
1538            #[cfg(any(feature = "std", feature = "fair-admission"))]
1539            {
1540                let incoming =
1541                    _incoming.and_then(|data| Self::fair_bulk_owner(data, _incoming_priority));
1542                let victim = *fair_victim.get_or_insert_with(|| {
1543                    self.bulk_loads(_incoming_priority.min(199))
1544                        .queue_victim(incoming)
1545                });
1546                if let Some(victim) = victim
1547                    && self.reclaim_bulk_from(
1548                        victim,
1549                        _incoming_priority.min(199),
1550                        (incoming == Some(victim))
1551                            .then(|| _incoming.and_then(Self::bulk_stream))
1552                            .flatten(),
1553                    )
1554                {
1555                    continue;
1556                }
1557                if incoming.is_some() {
1558                    // Best effort cannot replace another peer's sole report,
1559                    // reliable replay, schema, or protected control.
1560                    return Err(TelemetryError::Io("shared fair queue saturated"));
1561                }
1562            }
1563            // Incoming work cannot evict the routes required to deliver it.
1564            // Prefer low-priority packets; refuse admission once only metadata
1565            // remains rather than treating topology as an ordinary queue.
1566            if !self.pop_lowest_packet_queue_item(256) && self.pop_reliable_rx_buffered().is_none()
1567            {
1568                if _incoming_priority >= 200 && self.reclaim_oldest_side_chunk(None) {
1569                    continue;
1570                }
1571                return Err(TelemetryError::PacketTooLarge(
1572                    "Item exceeds maximum shared queue budget",
1573                ));
1574            }
1575        }
1576
1577        Ok(())
1578    }
1579
1580    #[cfg(feature = "discovery")]
1581    fn schema_growth_budget(&self) -> usize {
1582        // Queued packets are reclaimable. Existing reachability and active
1583        // reliable reassembly remain protected when admitting a new schema.
1584        let protected = self
1585            .recent_rx
1586            .max_bytes()
1587            .saturating_add(self.discovery_bytes_used())
1588            .saturating_add(self.reliable_rx_buffered_bytes())
1589            .saturating_add({
1590                #[cfg(feature = "compression")]
1591                {
1592                    self.codec_reserved_bytes
1593                }
1594                #[cfg(not(feature = "compression"))]
1595                {
1596                    0
1597                }
1598            });
1599        self.memory.max_queue_budget.saturating_sub(protected)
1600    }
1601
1602    #[cfg(feature = "discovery")]
1603    fn commit_discovery_route(
1604        &mut self,
1605        side: RelaySideId,
1606        route: DiscoverySideState,
1607    ) -> TelemetryResult<()> {
1608        let previous = self
1609            .discovery_routes
1610            .get(&side)
1611            .map_or(0, Self::discovery_route_byte_cost);
1612        let incoming = Self::discovery_route_byte_cost(&route);
1613        let growth = incoming.saturating_sub(previous);
1614        while self.shared_queue_bytes_used().saturating_add(growth) > self.memory.max_queue_budget {
1615            #[cfg(all(feature = "compression", feature = "discovery"))]
1616            if self.reclaim_codec_metadata() {
1617                continue;
1618            }
1619
1620            if !self.pop_lowest_packet_queue_item(256) {
1621                return Err(TelemetryError::Io("shared priority queue saturated"));
1622            }
1623        }
1624        self.discovery_routes.insert(side, route);
1625        Ok(())
1626    }
1627
1628    #[cfg(feature = "discovery")]
1629    fn fit_discovery_budget(&mut self) {
1630        while self.shared_queue_bytes_used() > self.memory.max_queue_budget {
1631            #[cfg(all(feature = "compression", feature = "discovery"))]
1632            if self.reclaim_codec_metadata() {
1633                continue;
1634            }
1635
1636            if self.pop_lowest_packet_queue_item(256) {
1637                continue;
1638            }
1639            // Live routes are protected. Growth is admitted transactionally;
1640            // external registry/config changes cannot justify erasing routes.
1641            break;
1642        }
1643    }
1644
1645    fn pop_received_fair(&mut self) -> Option<RelayRxItem> {
1646        self.rx_queue
1647            .pop_front_bulk_fair(&mut self.rx_priority_burst, MAX_PRIORITY_BURST, |item| {
1648                item.priority
1649            })
1650    }
1651
1652    fn push_rx(&mut self, item: RelayRxItem) -> TelemetryResult<()> {
1653        self.make_shared_queue_room(item.byte_cost(), item.priority, Some(&item.data))?;
1654        #[cfg(any(feature = "std", feature = "fair-admission"))]
1655        {
1656            #[cfg(any(feature = "std", feature = "fair-admission"))]
1657            let mut fair_victim = None;
1658            self.rx_queue.push_back_prioritized_select(
1659                item,
1660                |queued| queued.priority,
1661                |queue, _incoming| {
1662                    #[cfg(any(feature = "std", feature = "fair-admission"))]
1663                    {
1664                        crate::admission_fairness::queue_victim(
1665                            queue,
1666                            _incoming,
1667                            |item| Self::fair_bulk_owner(&item.data, item.priority),
1668                            |item| item.priority,
1669                            |item| Self::bulk_stream(&item.data),
1670                            &mut fair_victim,
1671                        )
1672                    }
1673                },
1674                core::mem::drop,
1675            )
1676        }
1677        #[cfg(not(any(feature = "std", feature = "fair-admission")))]
1678        {
1679            self.rx_queue
1680                .push_back_prioritized(item, |queued| queued.priority)
1681        }
1682    }
1683
1684    fn push_tx(&mut self, item: RelayTxItem) -> TelemetryResult<()> {
1685        #[cfg(feature = "compression")]
1686        let mut item = item;
1687        #[cfg(feature = "compression")]
1688        self.prepare_codec_queue_owner(Some(item.dst), &mut item.data);
1689        #[cfg(feature = "discovery")]
1690        {
1691            // Preserve the transfer identity of an unchanged queued catalog.
1692            if let RelayItem::Packet(incoming) = &item.data
1693                && self.tx_queue.iter().any(|queued| {
1694                    queued.dst == item.dst && matches!(&queued.data,
1695                        RelayItem::Packet(existing) if discovery::same_schema_snapshot(existing, incoming))
1696                })
1697            {
1698                return Ok(());
1699            }
1700            let incoming = match &item.data {
1701                RelayItem::Packet(pkt) if discovery::is_discovery_type(pkt.data_type()) => {
1702                    Some((pkt.data_type(), pkt.sender().to_owned()))
1703                }
1704                RelayItem::Packet(_) | RelayItem::Packed(_) => None,
1705            };
1706            if let Some((incoming_ty, incoming_sender)) = incoming {
1707                self.tx_queue.retain(|queued| {
1708                    let same_snapshot = match &queued.data {
1709                        RelayItem::Packet(pkt) => {
1710                            pkt.data_type() == incoming_ty && pkt.sender() == incoming_sender
1711                        }
1712                        RelayItem::Packed(_) => false,
1713                    };
1714                    queued.dst != item.dst || !same_snapshot
1715                });
1716            }
1717        }
1718        self.make_shared_queue_room(item.byte_cost(), item.priority, Some(&item.data))?;
1719        #[cfg(any(feature = "std", feature = "fair-admission"))]
1720        {
1721            #[cfg(any(feature = "std", feature = "fair-admission"))]
1722            let mut fair_victim = None;
1723            self.tx_queue.push_back_prioritized_select(
1724                item,
1725                |queued| queued.priority,
1726                |queue, _incoming| {
1727                    #[cfg(any(feature = "std", feature = "fair-admission"))]
1728                    {
1729                        crate::admission_fairness::queue_victim(
1730                            queue,
1731                            _incoming,
1732                            |item| Self::fair_bulk_owner(&item.data, item.priority),
1733                            |item| item.priority,
1734                            |item| Self::bulk_stream(&item.data),
1735                            &mut fair_victim,
1736                        )
1737                    }
1738                },
1739                core::mem::drop,
1740            )
1741        }
1742        #[cfg(not(any(feature = "std", feature = "fair-admission")))]
1743        {
1744            self.tx_queue
1745                .push_back_prioritized(item, |queued| queued.priority)
1746        }
1747    }
1748
1749    fn push_replay(&mut self, item: RelayReplayItem) -> TelemetryResult<()> {
1750        let key = (item.dst, item.ty.as_u32());
1751        let seq = item.seq;
1752        if self
1753            .reliable_tx
1754            .get(&key)
1755            .and_then(|tx| tx.sent.get(&seq))
1756            .is_none_or(|sent| sent.queued)
1757        {
1758            return Ok(());
1759        }
1760        self.make_shared_queue_room(item.byte_cost(), item.priority, None)?;
1761        let reliable_tx = &mut self.reliable_tx;
1762        let result = self.replay_queue.push_back_prioritized_observed(
1763            item,
1764            |queued| queued.priority,
1765            |evicted| {
1766                if let Some(sent) = reliable_tx
1767                    .get_mut(&(evicted.dst, evicted.ty.as_u32()))
1768                    .and_then(|tx| tx.sent.get_mut(&evicted.seq))
1769                {
1770                    sent.queued = false;
1771                }
1772            },
1773        );
1774        if result.is_ok()
1775            && let Some(sent) = reliable_tx
1776                .get_mut(&key)
1777                .and_then(|tx| tx.sent.get_mut(&seq))
1778        {
1779            sent.queued = true;
1780        }
1781        result
1782    }
1783
1784    fn push_recent_rx(&mut self, id: u64) -> TelemetryResult<()> {
1785        while self.recent_rx.len() >= self.memory.max_recent_rx_ids {
1786            let _ = self.recent_rx.pop_front();
1787        }
1788        self.make_shared_queue_room(0, 0, None)?;
1789        self.recent_rx.push_back(id)
1790    }
1791
1792    #[allow(unused_mut)]
1793    fn buffer_reliable_rx(
1794        &mut self,
1795        side: RelaySideId,
1796        ty: crate::DataType,
1797        seq: u32,
1798        mut bytes: crate::SharedBytes,
1799    ) -> TelemetryResult<()> {
1800        let key = Relay::reliable_key(side, ty);
1801        if self
1802            .reliable_rx
1803            .get(&key)
1804            .is_some_and(|state| state.buffered.contains_key(&seq))
1805        {
1806            return Ok(());
1807        }
1808        let cost = size_of::<crate::SharedBytes>() + bytes.len();
1809        self.make_shared_queue_room(cost, crate::transport_priority(ty), None)?;
1810        #[cfg(feature = "compact-packet-store")]
1811        bytes.park_for_queue()?;
1812        let rx_state = self
1813            .reliable_rx
1814            .entry(key)
1815            .or_insert_with(|| ReliableRxState {
1816                expected_seq: 1,
1817                buffered: BTreeMap::new(),
1818            });
1819        if rx_state.buffered.len() >= runtime_reliable_max_pending() {
1820            let _ = rx_state.buffered.pop_first();
1821        }
1822        rx_state.buffered.insert(seq, bytes);
1823        Ok(())
1824    }
1825}
1826
1827/// Relay that fans out packets from one side to all others.
1828/// - Supports both packed bytes and full Packet.
1829/// - Has RX & TX queues, like Router.
1830/// - Uses a Clock for the *_with_timeout APIs, same style as Router.
1831pub struct Relay {
1832    sender: RouterMutex<Arc<str>>,
1833    state: RouterMutex<RelayInner>,
1834    #[cfg(not(feature = "std"))]
1835    side_tx_gate: ReentryGate,
1836    #[cfg(feature = "std")]
1837    side_tx_activity: crate::lock::SideTxActivity,
1838    clock: Box<dyn Clock + Send + Sync>,
1839}
1840
1841enum RemoteSidePlan {
1842    Target(Vec<RelaySideId>),
1843}
1844
1845impl Relay {
1846    #[cfg(feature = "discovery")]
1847    const END_TO_END_ACK_SENDER: &'static str = "E2EACK";
1848    const END_TO_END_ACK_PREFIX: &'static str = "E2EACK:";
1849
1850    fn item_type_priority(data: &RelayItem) -> TelemetryResult<(crate::DataType, u8)> {
1851        let (ty, control) = match data {
1852            RelayItem::Packet(pkt) => (
1853                pkt.data_type(),
1854                pkt.payload().len() <= 16
1855                    && pkt.endpoints().len() <= 3
1856                    && crate::small_frozen_control(
1857                        pkt.data_type(),
1858                        pkt.wire_target_senders().len(),
1859                        pkt.wire_shape(),
1860                    ),
1861            ),
1862            RelayItem::Packed(bytes) => {
1863                let frame = wire_format::peek_routing_frame_info(bytes.as_ref())?;
1864                let env = &frame.envelope;
1865                // Hop ACKs carry the original data type. Their validated
1866                // control header, not that type's bulk priority, releases credit.
1867                if bytes.len() <= 128 && frame.ack_only() {
1868                    return Ok((
1869                        env.ty,
1870                        crate::transport_priority(crate::DataType::ReliableAck),
1871                    ));
1872                }
1873                let control = bytes.len() <= 128
1874                    && bytes.first().is_some_and(|flags| flags & 0x11 == 0)
1875                    && env.endpoints().take(4).count() <= 3
1876                    && crate::small_frozen_control(
1877                        env.ty,
1878                        env.target_senders().len(),
1879                        env.wire_shape,
1880                    );
1881                (env.ty, control)
1882            }
1883        };
1884        let priority = crate::scheduler_priority(ty);
1885        Ok((ty, if control { priority.max(254) } else { priority }))
1886    }
1887
1888    fn relay_item_priority(data: &RelayItem) -> TelemetryResult<u8> {
1889        Self::item_type_priority(data).map(|(_, priority)| priority)
1890    }
1891
1892    #[inline]
1893    fn is_transient_tx_error(err: &TelemetryError) -> bool {
1894        matches!(
1895            err,
1896            TelemetryError::Io("side tx busy" | "memory pressure" | "reliable window full")
1897        )
1898    }
1899
1900    fn process_replay_queue_item(&self) -> TelemetryResult<bool> {
1901        let Some(mut item) = ({
1902            let mut st = self.state.lock();
1903            let ready = st
1904                .replay_queue
1905                .iter()
1906                .position(|item| item.retry_after_ms <= self.clock.now_ms());
1907            ready.and_then(|index| st.replay_queue.remove_pos(index))
1908        }) else {
1909            return Ok(false);
1910        };
1911        let ty = item.ty;
1912        let seq = item.seq;
1913        {
1914            let mut st = self.state.lock();
1915            let Some(sent) = st
1916                .reliable_tx
1917                .get_mut(&Self::reliable_key(item.dst, ty))
1918                .and_then(|tx| tx.sent.get_mut(&seq))
1919            else {
1920                return Ok(false);
1921            };
1922            sent.queued = false;
1923            sent.last_send_ms = self.clock.now_ms();
1924        }
1925        if let Err(e) = self.send_reliable_raw_to_side(item.dst, item.bytes.clone()) {
1926            if Self::is_transient_tx_error(&e) {
1927                let mut st = self.state.lock();
1928                item.retry_after_ms = self.clock.now_ms().saturating_add(1);
1929                st.push_replay(item)?;
1930                return Ok(true);
1931            }
1932            return Err(e);
1933        }
1934        let mut st = self.state.lock();
1935        let tx_state = self.reliable_tx_state_mut(&mut st, item.dst, ty);
1936        if let Some(sent) = tx_state.sent.get_mut(&seq) {
1937            sent.last_send_ms = self.clock.now_ms();
1938        }
1939        Ok(true)
1940    }
1941
1942    fn pop_ready_tx_item(
1943        &self,
1944    ) -> Option<(
1945        Option<RelaySideId>,
1946        RelaySideId,
1947        RelayTxHandlerFn,
1948        RelaySideOptions,
1949        RelayItem,
1950        u8,
1951    )> {
1952        let mut st = self.state.lock();
1953        let mut priority_burst = st.tx_priority_burst;
1954        let item = st.tx_queue.pop_front_fair_if(
1955            &mut priority_burst,
1956            MAX_PRIORITY_BURST,
1957            |queued| queued.priority,
1958            |queued| queued.retry_after_ms <= self.clock.now_ms(),
1959        );
1960        st.tx_priority_burst = priority_burst;
1961        if let Some(item) = item {
1962            let side = st.sides.get(item.dst).and_then(|side| side.clone());
1963            side.map(|s| {
1964                (
1965                    item.src,
1966                    item.dst,
1967                    s.tx_handler,
1968                    s.opts,
1969                    item.data,
1970                    item.priority,
1971                )
1972            })
1973        } else {
1974            None
1975        }
1976    }
1977
1978    fn send_tx_item(
1979        &self,
1980        src: Option<RelaySideId>,
1981        dst: RelaySideId,
1982        handler: RelayTxHandlerFn,
1983        opts: RelaySideOptions,
1984        data: RelayItem,
1985    ) -> TelemetryResult<bool> {
1986        let allowed = {
1987            let st = self.state.lock();
1988            let ty = match &data {
1989                RelayItem::Packet(pkt) => Some(pkt.data_type()),
1990                RelayItem::Packed(bytes) => {
1991                    Some(wire_format::peek_routing_envelope(bytes.as_ref())?.ty)
1992                }
1993            };
1994            let route_allowed = self.route_allowed_locked(&st, src, ty, dst);
1995            #[cfg(all(feature = "discovery", feature = "timesync"))]
1996            let timesync_allowed = ty
1997                .map(|ty| Self::timesync_allowed_for_side_locked(&st, dst, ty, self.clock.now_ms()))
1998                .unwrap_or(true);
1999            #[cfg(not(all(feature = "discovery", feature = "timesync")))]
2000            let timesync_allowed = true;
2001            route_allowed && timesync_allowed
2002        };
2003        if !allowed {
2004            return Ok(false);
2005        }
2006        if opts.reliable_enabled && matches!(handler, RelayTxHandlerFn::Packed(_)) {
2007            self.send_reliable_to_side(dst, data)?;
2008            Ok(true)
2009        } else if let Some(adjusted) = self.adjust_reliable_for_side(opts, data)? {
2010            self.call_tx_handler(dst, &handler, &adjusted)?;
2011            Ok(true)
2012        } else {
2013            Ok(false)
2014        }
2015    }
2016
2017    /// Create a new relay with the given clock.
2018    pub fn new(clock: Box<dyn Clock + Send + Sync>) -> Self {
2019        Self::new_with_config(RelayConfig::default(), clock)
2020    }
2021
2022    /// Create a new relay with explicit runtime configuration and clock.
2023    pub fn new_with_config(cfg: RelayConfig, clock: Box<dyn Clock + Send + Sync>) -> Self {
2024        let memory = cfg.memory_config();
2025        Self {
2026            sender: RouterMutex::new(cfg.sender()),
2027            state: RouterMutex::new(RelayInner {
2028                #[cfg(feature = "compression")]
2029                codec_reserved_bytes: 0,
2030                memory,
2031                sides: Vec::new(),
2032                route_overrides: BTreeMap::new(),
2033                typed_route_overrides: BTreeMap::new(),
2034                route_weights: BTreeMap::new(),
2035                route_priorities: BTreeMap::new(),
2036                source_route_modes: BTreeMap::new(),
2037                route_selection_cursors: BTreeMap::new(),
2038                adaptive_route_stats: BTreeMap::new(),
2039                side_runtime_stats: BTreeMap::new(),
2040                side_transport: BTreeMap::new(),
2041                rx_queue: BoundedDeque::new(
2042                    memory.max_queue_budget,
2043                    memory.starting_queue_size,
2044                    memory.queue_grow_step,
2045                ),
2046                tx_queue: BoundedDeque::new(
2047                    memory.max_queue_budget,
2048                    memory.starting_queue_size,
2049                    memory.queue_grow_step,
2050                ),
2051                tx_priority_burst: 0,
2052                rx_priority_burst: 0,
2053                replay_queue: BoundedDeque::new(
2054                    memory.max_queue_budget,
2055                    memory.starting_queue_size,
2056                    memory.queue_grow_step,
2057                ),
2058                recent_rx: BoundedDeque::new(
2059                    memory.recent_rx_queue_bytes(),
2060                    memory.recent_rx_queue_bytes(),
2061                    memory.queue_grow_step,
2062                ),
2063                reliable_tx: BTreeMap::new(),
2064                reliable_rx: BTreeMap::new(),
2065                reliable_return_routes: BTreeMap::new(),
2066                reliable_return_route_order: VecDeque::new(),
2067                end_to_end_acked_destinations: BTreeMap::new(),
2068                end_to_end_acked_destination_order: VecDeque::new(),
2069                total_handler_failures: 0,
2070                total_handler_retries: 0,
2071                #[cfg(feature = "discovery")]
2072                discovery_routes: BTreeMap::new(),
2073                #[cfg(feature = "discovery")]
2074                discovery_cadence: DiscoveryCadenceState::default(),
2075                #[cfg(feature = "discovery")]
2076                discovery_side_throttle: BTreeMap::new(),
2077                #[cfg(all(feature = "discovery", feature = "timesync"))]
2078                timesync_side_throttle: BTreeMap::new(),
2079            }),
2080            #[cfg(not(feature = "std"))]
2081            side_tx_gate: ReentryGate::new(),
2082            #[cfg(feature = "std")]
2083            side_tx_activity: crate::lock::SideTxActivity::default(),
2084            clock,
2085        }
2086    }
2087
2088    #[inline]
2089    fn sender_arc(&self) -> Arc<str> {
2090        self.sender.lock().clone()
2091    }
2092
2093    #[inline]
2094    pub fn sender(&self) -> Arc<str> {
2095        self.sender_arc()
2096    }
2097
2098    pub fn set_sender<S: AsRef<str>>(&self, sender: S) {
2099        *self.sender.lock() = Arc::from(sender.as_ref());
2100    }
2101
2102    #[cfg(feature = "std")]
2103    #[inline]
2104    fn try_enter_side_tx(&self, side: usize) -> Option<crate::lock::ActiveSideTxGuard<'_>> {
2105        let gate = self
2106            .state
2107            .lock()
2108            .side_transport
2109            .get(&side)?
2110            .tx_callback_gate
2111            .clone();
2112        self.side_tx_activity.try_enter(gate)
2113    }
2114    #[cfg(not(feature = "std"))]
2115    #[inline]
2116    fn try_enter_side_tx(&self, _side: usize) -> Option<crate::lock::ReentryGuard<'_>> {
2117        self.side_tx_gate.try_enter()
2118    }
2119    #[inline]
2120    fn side_tx_active(&self) -> bool {
2121        #[cfg(feature = "std")]
2122        {
2123            self.side_tx_activity.is_active()
2124        }
2125        #[cfg(not(feature = "std"))]
2126        {
2127            self.side_tx_gate.is_active()
2128        }
2129    }
2130
2131    #[inline]
2132    fn side_ref(st: &RelayInner, side: RelaySideId) -> TelemetryResult<&RelaySide> {
2133        st.sides
2134            .get(side)
2135            .and_then(|side| side.as_ref())
2136            .ok_or(TelemetryError::HandlerError("relay: invalid side id"))
2137    }
2138
2139    fn note_side_tx_success(
2140        &self,
2141        side: RelaySideId,
2142        ty: crate::DataType,
2143        bytes: usize,
2144        attempts: usize,
2145    ) {
2146        let mut st = self.state.lock();
2147        #[cfg(feature = "discovery")]
2148        if ty == crate::DataType::DiscoveryAnnounce
2149            && let Some(throttle) = st.discovery_side_throttle.get_mut(&side)
2150        {
2151            throttle.last_liveness_tx_ms = self.clock.now_ms();
2152        }
2153        #[cfg(all(feature = "discovery", feature = "timesync"))]
2154        if Self::is_timesync_type(ty) {
2155            let now = self.clock.now_ms();
2156            if Self::side_is_slow_control_link_locked(&st, side, now) {
2157                st.timesync_side_throttle
2158                    .entry(side)
2159                    .or_default()
2160                    .next_allowed_ms = now.saturating_add(TIMESYNC_SLOW_LINK_MIN_INTERVAL_MS);
2161            } else {
2162                st.timesync_side_throttle.remove(&side);
2163            }
2164        }
2165        let entry = st.side_runtime_stats.entry(side).or_default();
2166        entry.note_tx(ty, bytes, attempts.saturating_sub(1));
2167    }
2168
2169    fn note_side_tx_failure(&self, side: RelaySideId, ty: crate::DataType, attempts: usize) {
2170        let mut st = self.state.lock();
2171        st.total_handler_failures = st.total_handler_failures.saturating_add(1);
2172        st.total_handler_retries = st.total_handler_retries.saturating_add(attempts as u64);
2173        let entry = st.side_runtime_stats.entry(side).or_default();
2174        entry.note_tx_failure(ty, attempts);
2175    }
2176
2177    fn note_side_rx(&self, side: RelaySideId, ty: crate::DataType, bytes: usize) {
2178        let mut st = self.state.lock();
2179        let entry = st.side_runtime_stats.entry(side).or_default();
2180        entry.note_rx(ty, bytes);
2181    }
2182
2183    #[inline]
2184    fn ensure_side_ingress_enabled(&self, side: RelaySideId) -> TelemetryResult<()> {
2185        let st = self.state.lock();
2186        let side_ref = Self::side_ref(&st, side)?;
2187        if side_ref.opts.ingress_enabled {
2188            Ok(())
2189        } else {
2190            Err(TelemetryError::HandlerError(
2191                "relay: ingress disabled for side id",
2192            ))
2193        }
2194    }
2195
2196    #[inline]
2197    fn route_allowed_locked(
2198        &self,
2199        st: &RelayInner,
2200        src: Option<RelaySideId>,
2201        ty: Option<crate::DataType>,
2202        dst: RelaySideId,
2203    ) -> bool {
2204        let Ok(dst_side) = Self::side_ref(st, dst) else {
2205            return false;
2206        };
2207        if !dst_side.opts.egress_enabled {
2208            return false;
2209        }
2210        if let Some(src_id) = src {
2211            let Ok(src_side) = Self::side_ref(st, src_id) else {
2212                return false;
2213            };
2214            if !src_side.opts.ingress_enabled || src_id == dst {
2215                return false;
2216            }
2217        }
2218        let base_allowed = st.route_overrides.get(&(src, dst)).copied().unwrap_or(true);
2219        if !base_allowed {
2220            return false;
2221        }
2222
2223        let Some(ty) = ty else {
2224            return true;
2225        };
2226        if st
2227            .typed_route_overrides
2228            .keys()
2229            .any(|(typed_src, typed_ty, _)| *typed_src == src && *typed_ty == ty.as_u32())
2230        {
2231            return st
2232                .typed_route_overrides
2233                .get(&(src, ty.as_u32(), dst))
2234                .copied()
2235                .unwrap_or(false);
2236        }
2237        true
2238    }
2239
2240    #[cfg(feature = "discovery")]
2241    fn has_typed_route_overrides_locked(
2242        st: &RelayInner,
2243        src: Option<RelaySideId>,
2244        ty: crate::DataType,
2245    ) -> bool {
2246        st.typed_route_overrides
2247            .keys()
2248            .any(|(typed_src, typed_ty, _)| *typed_src == src && *typed_ty == ty.as_u32())
2249    }
2250
2251    fn eligible_side_ids_locked(
2252        &self,
2253        st: &RelayInner,
2254        src: Option<RelaySideId>,
2255        ty: Option<crate::DataType>,
2256        restrict_link_local: bool,
2257    ) -> Vec<RelaySideId> {
2258        st.sides
2259            .iter()
2260            .enumerate()
2261            .filter_map(|(side_id, side)| {
2262                let side = side.as_ref()?;
2263                if restrict_link_local && !side.opts.link_local_enabled {
2264                    return None;
2265                }
2266                if self.route_allowed_locked(st, src, ty, side_id) {
2267                    Some(side_id)
2268                } else {
2269                    None
2270                }
2271            })
2272            .collect()
2273    }
2274
2275    fn apply_route_selection_locked(
2276        &self,
2277        st: &mut RelayInner,
2278        src: Option<RelaySideId>,
2279        mut sides: Vec<RelaySideId>,
2280        origin: RouteSelectionOrigin,
2281    ) -> Vec<RelaySideId> {
2282        if sides.len() <= 1 {
2283            return sides;
2284        }
2285
2286        let selection_mode = st.source_route_modes.get(&src).copied();
2287        if selection_mode.is_none() && origin == RouteSelectionOrigin::Discovered {
2288            return self.apply_adaptive_discovery_selection_locked(st, src, sides);
2289        }
2290
2291        match selection_mode.unwrap_or(RouteSelectionMode::Fanout) {
2292            RouteSelectionMode::Fanout => sides,
2293            RouteSelectionMode::Weighted => {
2294                crate::small_sort::sort(&mut sides);
2295                let total_weight = sides.iter().fold(0_u64, |acc, side| {
2296                    acc + u64::from(st.route_weights.get(&(src, *side)).copied().unwrap_or(1))
2297                });
2298                if total_weight == 0 {
2299                    return Vec::new();
2300                }
2301                let cursor = st.route_selection_cursors.entry(src).or_insert(0);
2302                let pick = *cursor % total_weight;
2303                *cursor = cursor.wrapping_add(1);
2304                let mut remaining = pick;
2305                for side in sides {
2306                    let weight =
2307                        u64::from(st.route_weights.get(&(src, side)).copied().unwrap_or(1));
2308                    if remaining < weight {
2309                        return vec![side];
2310                    }
2311                    remaining -= weight;
2312                }
2313                Vec::new()
2314            }
2315            RouteSelectionMode::Failover => {
2316                sides.sort_by_key(|side| {
2317                    (
2318                        st.route_priorities.get(&(src, *side)).copied().unwrap_or(0),
2319                        *side,
2320                    )
2321                });
2322                sides.truncate(1);
2323                sides
2324            }
2325        }
2326    }
2327
2328    fn apply_adaptive_discovery_selection_locked(
2329        &self,
2330        st: &mut RelayInner,
2331        src: Option<RelaySideId>,
2332        mut sides: Vec<RelaySideId>,
2333    ) -> Vec<RelaySideId> {
2334        crate::small_sort::sort(&mut sides);
2335        let mut unmeasured: Vec<_> = sides
2336            .iter()
2337            .copied()
2338            .filter(|side| !st.adaptive_route_stats.contains_key(side))
2339            .collect();
2340        if !unmeasured.is_empty() {
2341            let cursor = st.route_selection_cursors.entry(src).or_insert(0);
2342            let pick = (*cursor as usize) % unmeasured.len();
2343            *cursor = cursor.wrapping_add(1);
2344            return vec![unmeasured.swap_remove(pick)];
2345        }
2346
2347        let now_ms = self.clock.now_ms();
2348        let total_weight = sides.iter().fold(0_u64, |acc, side| {
2349            acc + st
2350                .adaptive_route_stats
2351                .get(side)
2352                .map(|stats| stats.weight(now_ms))
2353                .unwrap_or(1)
2354        });
2355        if total_weight == 0 {
2356            sides.truncate(1);
2357            return sides;
2358        }
2359
2360        let cursor = st.route_selection_cursors.entry(src).or_insert(0);
2361        let pick = *cursor % total_weight;
2362        *cursor = cursor.wrapping_add(1);
2363        let mut remaining = pick;
2364        for side in sides {
2365            let weight = st
2366                .adaptive_route_stats
2367                .get(&side)
2368                .map(|stats| stats.weight(now_ms))
2369                .unwrap_or(1);
2370            if remaining < weight {
2371                return vec![side];
2372            }
2373            remaining -= weight;
2374        }
2375        Vec::new()
2376    }
2377
2378    /// Prefer the discovery side with the shortest advertised topology path
2379    /// to the packet's destination. This prevents a route reflected through
2380    /// another router from competing equally with the direct route.
2381    #[cfg(feature = "discovery")]
2382    fn retain_shortest_discovery_routes_locked(
2383        st: &RelayInner,
2384        sides: &mut Vec<RelaySideId>,
2385        endpoints: &[crate::DataEndpoint],
2386        now_ms: u64,
2387    ) {
2388        if sides.len() <= 1 || endpoints.is_empty() {
2389            return;
2390        }
2391
2392        let route_distance = |side: RelaySideId| -> Option<usize> {
2393            let route = st.discovery_routes.get(&side)?;
2394            route
2395                .announcers
2396                .iter()
2397                .filter(|(_, state)| {
2398                    now_ms.saturating_sub(state.last_seen_ms) <= DISCOVERY_ROUTE_TTL_MS
2399                })
2400                .filter_map(|(announcer, state)| {
2401                    let targets: BTreeSet<&str> = state
2402                        .topology_boards
2403                        .iter()
2404                        .filter(|board| {
2405                            endpoints
2406                                .iter()
2407                                .any(|endpoint| board.reachable_endpoints.contains(endpoint))
2408                        })
2409                        .map(|board| board.sender_id.as_str())
2410                        .collect();
2411                    if targets.is_empty() {
2412                        return state
2413                            .reachable
2414                            .iter()
2415                            .any(|endpoint| endpoints.contains(endpoint))
2416                            .then_some(usize::MAX / 2);
2417                    }
2418
2419                    let mut distances: BTreeMap<&str, usize> = BTreeMap::new();
2420                    let mut pending = VecDeque::from([(announcer.as_str(), 0usize)]);
2421                    while let Some((sender, distance)) = pending.pop_front() {
2422                        if distances.contains_key(sender) {
2423                            continue;
2424                        }
2425                        distances.insert(sender, distance);
2426                        if targets.contains(sender) {
2427                            return Some(distance);
2428                        }
2429                        for board in state.topology_boards.iter() {
2430                            if board.sender_id == sender {
2431                                for peer in &board.connections {
2432                                    if !distances.contains_key(peer.as_str()) {
2433                                        pending.push_back((peer.as_str(), distance + 1));
2434                                    }
2435                                }
2436                            } else if board.connections.iter().any(|peer| peer == sender)
2437                                && !distances.contains_key(board.sender_id.as_str())
2438                            {
2439                                pending.push_back((board.sender_id.as_str(), distance + 1));
2440                            }
2441                        }
2442                    }
2443                    None
2444                })
2445                .min()
2446        };
2447
2448        let distances: Vec<(RelaySideId, usize)> = sides
2449            .iter()
2450            .filter_map(|side| route_distance(*side).map(|distance| (*side, distance)))
2451            .collect();
2452        let Some(best) = distances.iter().map(|(_, distance)| *distance).min() else {
2453            return;
2454        };
2455        sides.retain(|side| {
2456            distances
2457                .iter()
2458                .any(|(candidate, distance)| candidate == side && *distance == best)
2459        });
2460    }
2461
2462    fn record_side_tx_sample(
2463        &self,
2464        side: RelaySideId,
2465        bytes: usize,
2466        started_ms: u64,
2467        ended_ms: u64,
2468    ) {
2469        let sample_ms = ended_ms.saturating_sub(started_ms).max(1);
2470        let sample_bps = ((bytes as u128).saturating_mul(1000) / u128::from(sample_ms))
2471            .min(u128::from(u64::MAX)) as u64;
2472        let mut st = self.state.lock();
2473        st.adaptive_route_stats
2474            .entry(side)
2475            .or_default()
2476            .observe(bytes, sample_bps, ended_ms);
2477    }
2478
2479    /// Seed adaptive route selection with a transport-measured link probe.
2480    ///
2481    /// Call this after a side-specific bring-up probe, or whenever the transport already knows the
2482    /// duration for a frame. The relay does not emit synthetic probe frames by itself.
2483    pub fn note_side_link_probe_sample(
2484        &self,
2485        side: RelaySideId,
2486        bytes: usize,
2487        duration_ms: u64,
2488    ) -> TelemetryResult<()> {
2489        {
2490            let st = self.state.lock();
2491            let _ = Self::side_ref(&st, side).map_err(|_| TelemetryError::BadArg)?;
2492        }
2493        let ended_ms = self.clock.now_ms();
2494        self.record_side_tx_sample(side, bytes, ended_ms.saturating_sub(duration_ms), ended_ms);
2495        Ok(())
2496    }
2497
2498    fn relay_item_wire_len(data: &RelayItem) -> TelemetryResult<usize> {
2499        match data {
2500            RelayItem::Packet(pkt) => Ok(wire_format::packet_wire_size(pkt)),
2501            RelayItem::Packed(bytes) => Ok(bytes.len()),
2502        }
2503    }
2504
2505    #[inline]
2506    fn decode_end_to_end_reliable_ack(payload: &[u8]) -> TelemetryResult<u64> {
2507        if payload.len() != 8 {
2508            return Err(TelemetryError::Unpack("bad reliable e2e ack payload"));
2509        }
2510        Ok(u64::from_le_bytes(payload[0..8].try_into().unwrap()))
2511    }
2512
2513    #[inline]
2514    #[cfg(feature = "discovery")]
2515    fn is_end_to_end_ack_sender(sender: &str) -> bool {
2516        sender == Self::END_TO_END_ACK_SENDER || sender.starts_with(Self::END_TO_END_ACK_PREFIX)
2517    }
2518
2519    #[inline]
2520    fn sender_hash(sender: &str) -> u64 {
2521        if let Some(address) = sender
2522            .strip_prefix("@addr:")
2523            .and_then(|value| value.parse::<u32>().ok())
2524        {
2525            return u64::from(address);
2526        }
2527        hash_bytes_u64(0x517C_C1B7_2722_0A95, sender.as_bytes())
2528    }
2529
2530    #[cfg(feature = "discovery")]
2531    fn canonical_sender_locked(st: &RelayInner, sender: &str) -> String {
2532        let Some(address) = sender
2533            .strip_prefix("@addr:")
2534            .and_then(|value| value.parse::<u32>().ok())
2535        else {
2536            return sender.to_string();
2537        };
2538        Self::hostname_for_wire_address_locked(st, address).unwrap_or_else(|| sender.to_string())
2539    }
2540
2541    #[cfg(feature = "discovery")]
2542    fn hostname_for_wire_address_locked(st: &RelayInner, address: u32) -> Option<String> {
2543        Self::hostname_for_wire_address_borrowed_locked(st, address).map(str::to_string)
2544    }
2545
2546    #[cfg(feature = "discovery")]
2547    fn hostname_for_wire_address_borrowed_locked<'a>(
2548        st: &'a RelayInner,
2549        address: u32,
2550    ) -> Option<&'a str> {
2551        let mut found: Option<&'a str> = None;
2552        let mut collision = false;
2553        let mut consider = |candidate: &'a str| {
2554            if sender_address_u32(candidate) != address {
2555                return;
2556            }
2557            match found {
2558                None => found = Some(candidate),
2559                Some(existing) if existing == candidate => {}
2560                Some(_) => collision = true,
2561            }
2562        };
2563        for route in st.discovery_routes.values() {
2564            for (announcer, state) in &route.announcers {
2565                consider(announcer);
2566                for board in state.topology_boards.iter() {
2567                    consider(&board.sender_id);
2568                }
2569            }
2570        }
2571        if collision { None } else { found }
2572    }
2573
2574    fn decode_end_to_end_ack_sender_hash(sender: &str) -> Option<u64> {
2575        sender
2576            .strip_prefix(Self::END_TO_END_ACK_PREFIX)
2577            .filter(|sender| !sender.is_empty())
2578            .map(Self::sender_hash)
2579    }
2580
2581    fn end_to_end_ack_sender_hash(pkt: &Packet) -> Option<u64> {
2582        if pkt.data_type() != crate::DataType::ReliableAck {
2583            return None;
2584        }
2585        pkt.wire_target_senders()
2586            .first()
2587            .copied()
2588            .or_else(|| Self::decode_end_to_end_ack_sender_hash(pkt.sender()))
2589    }
2590
2591    #[cfg(feature = "discovery")]
2592    fn is_end_to_end_destination_sender(&self, sender: &str) -> bool {
2593        sender != self.sender_arc().as_ref() && !Self::is_end_to_end_ack_sender(sender)
2594    }
2595
2596    /// Extract the logical packet ID targeted by an end-to-end reliable ACK item.
2597    ///
2598    /// Relay queues can hold either decoded packets or packed frames. This
2599    /// helper normalizes both forms so relay ACK-routing logic can treat them
2600    /// uniformly.
2601    ///
2602    /// Only relay-visible end-to-end `ReliableAck` packets qualify here.
2603    /// Unrelated traffic returns `Ok(None)`.
2604    fn reliable_control_target_packet_id(data: &RelayItem) -> TelemetryResult<Option<u64>> {
2605        match data {
2606            RelayItem::Packet(pkt) => {
2607                if Self::end_to_end_ack_sender_hash(pkt).is_none() {
2608                    return Ok(None);
2609                }
2610                Self::decode_end_to_end_reliable_ack(pkt.payload()).map(Some)
2611            }
2612            RelayItem::Packed(bytes) => {
2613                if wire_format::peek_routing_frame_info(bytes.as_ref())
2614                    .ok()
2615                    .is_some_and(|frame| {
2616                        frame.envelope.ty != crate::DataType::ReliableAck || frame.ack_only()
2617                    })
2618                {
2619                    return Ok(None);
2620                }
2621                let pkt = wire_format::unpack_packet(bytes.as_ref())?;
2622                if Self::end_to_end_ack_sender_hash(&pkt).is_none() {
2623                    return Ok(None);
2624                }
2625                Self::decode_end_to_end_reliable_ack(pkt.payload()).map(Some)
2626            }
2627        }
2628    }
2629
2630    fn note_reliable_return_route(&self, side: RelaySideId, packet_id: u64) {
2631        let mut st = self.state.lock();
2632        // A retry can remain in RX dedupe after its smaller return-path cache
2633        // entry was evicted. Relearning that path must not restart the replay
2634        // delay on every retry, or sustained cache churn suppresses it forever.
2635        let now = self.clock.now_ms();
2636        let retry_ms = runtime_reliable_retransmit_ms();
2637        let duplicate = st.recent_rx.contains(&packet_id);
2638        let last_replay_ms = if duplicate {
2639            now.saturating_sub(retry_ms)
2640        } else {
2641            now
2642        };
2643        // A reflected copy is not evidence that the original publisher
2644        // moved. Keep the first live ingress during the current retry
2645        // window, otherwise an echo can redirect an already queued ACK into
2646        // the downstream bus. A retry-window expiry or disabled/removed side
2647        // still permits alternate-path recovery without explicit operator action.
2648        let replace_side = !duplicate
2649            || st
2650                .reliable_return_routes
2651                .get(&packet_id)
2652                .is_none_or(|route| {
2653                    now.saturating_sub(route.last_replay_ms) >= retry_ms
2654                        || !Self::side_ref(&st, route.side)
2655                            .is_ok_and(|side| side.opts.egress_enabled)
2656                });
2657        Self::remember_reliable_return_route_locked(&mut st, packet_id);
2658        st.reliable_return_routes
2659            .entry(packet_id)
2660            .and_modify(|route| {
2661                if replace_side {
2662                    route.side = side;
2663                }
2664            })
2665            .or_insert(ReliableReturnRouteState {
2666                side,
2667                last_replay_ms,
2668            });
2669    }
2670
2671    /// Refresh or insert `packet_id` in the bounded reliable return-route cache.
2672    ///
2673    /// The relay uses this cache to route end-to-end acknowledgements back
2674    /// toward the original live ingress of the corresponding
2675    /// reliable data packet.
2676    fn remember_reliable_return_route_locked(st: &mut RelayInner, packet_id: u64) {
2677        let cap = runtime_reliable_max_return_routes().max(1);
2678        st.reliable_return_route_order
2679            .retain(|id| st.reliable_return_routes.contains_key(id) && *id != packet_id);
2680        while st.reliable_return_route_order.len() >= cap {
2681            if let Some(oldest) = st.reliable_return_route_order.pop_front() {
2682                st.reliable_return_routes.remove(&oldest);
2683            } else {
2684                break;
2685            }
2686        }
2687        st.reliable_return_route_order.push_back(packet_id);
2688    }
2689
2690    fn note_end_to_end_acked_destination_locked(
2691        st: &mut RelayInner,
2692        packet_id: u64,
2693        sender_hash: u64,
2694    ) {
2695        let entry_cap = runtime_reliable_max_end_to_end_ack_cache().max(1);
2696        st.end_to_end_acked_destination_order
2697            .retain(|id| st.end_to_end_acked_destinations.contains_key(id) && *id != packet_id);
2698        while st.end_to_end_acked_destination_order.len() >= entry_cap {
2699            if let Some(oldest) = st.end_to_end_acked_destination_order.pop_front() {
2700                st.end_to_end_acked_destinations.remove(&oldest);
2701            } else {
2702                break;
2703            }
2704        }
2705        st.end_to_end_acked_destination_order.push_back(packet_id);
2706
2707        let acked = st
2708            .end_to_end_acked_destinations
2709            .entry(packet_id)
2710            .or_default();
2711        let sender_cap = runtime_reliable_max_end_to_end_pending().max(1);
2712        if acked.len() < sender_cap || acked.contains(&sender_hash) {
2713            acked.insert(sender_hash);
2714        }
2715    }
2716
2717    #[inline]
2718    fn reliable_key(side: RelaySideId, ty: crate::DataType) -> (RelaySideId, u32) {
2719        (side, ty.as_u32())
2720    }
2721
2722    fn reliable_tx_state_mut<'a>(
2723        &'a self,
2724        st: &'a mut RelayInner,
2725        side: RelaySideId,
2726        ty: crate::DataType,
2727    ) -> &'a mut ReliableTxState {
2728        let key = Self::reliable_key(side, ty);
2729        st.reliable_tx
2730            .entry(key)
2731            .or_insert_with(|| ReliableTxState {
2732                next_seq: 1,
2733                sent_order: VecDeque::new(),
2734                sent: BTreeMap::new(),
2735            })
2736    }
2737
2738    fn reliable_rx_state_mut<'a>(
2739        &'a self,
2740        st: &'a mut RelayInner,
2741        side: RelaySideId,
2742        ty: crate::DataType,
2743    ) -> &'a mut ReliableRxState {
2744        let key = Self::reliable_key(side, ty);
2745        st.reliable_rx
2746            .entry(key)
2747            .or_insert_with(|| ReliableRxState {
2748                expected_seq: 1,
2749                buffered: BTreeMap::new(),
2750            })
2751    }
2752
2753    fn handle_reliable_ack(&self, side: RelaySideId, ty: crate::DataType, ack: u32) {
2754        let mut st = self.state.lock();
2755        let tx_state = self.reliable_tx_state_mut(&mut st, side, ty);
2756        if matches!(reliable_mode(ty), crate::ReliableMode::Unordered) {
2757            tx_state.sent.remove(&ack);
2758            tx_state.sent_order.retain(|seq| *seq != ack);
2759            return;
2760        }
2761
2762        while let Some(seq) = tx_state.sent_order.front().copied() {
2763            if seq > ack {
2764                break;
2765            }
2766            tx_state.sent_order.pop_front();
2767            tx_state.sent.remove(&seq);
2768        }
2769    }
2770
2771    fn handle_reliable_partial_ack(&self, side: RelaySideId, ty: crate::DataType, seq: u32) {
2772        let mut st = self.state.lock();
2773        let tx_state = self.reliable_tx_state_mut(&mut st, side, ty);
2774        if let Some(sent) = tx_state.sent.get_mut(&seq) {
2775            sent.partial_acked = true;
2776        }
2777    }
2778
2779    fn reliable_control_packet(
2780        &self,
2781        control_ty: crate::DataType,
2782        ty: crate::DataType,
2783        seq: u32,
2784    ) -> TelemetryResult<Packet> {
2785        let sender = self.sender_arc();
2786        Packet::new(
2787            control_ty,
2788            message_meta(control_ty).endpoints_ref(),
2789            sender.as_ref(),
2790            self.clock.now_ms(),
2791            crate::router::encode_slice_le(&[ty.as_u32(), seq]),
2792        )
2793    }
2794
2795    fn queue_reliable_ack(
2796        &self,
2797        side: RelaySideId,
2798        ty: crate::DataType,
2799        seq: u32,
2800    ) -> TelemetryResult<()> {
2801        let pkt = self.reliable_control_packet(crate::DataType::ReliableAck, ty, seq)?;
2802        let data = RelayItem::Packet(Arc::new(pkt));
2803        let priority = Self::relay_item_priority(&data)?;
2804        let mut st = self.state.lock();
2805        st.push_tx(RelayTxItem {
2806            src: None,
2807            dst: side,
2808            data,
2809            priority,
2810            retry_after_ms: 0,
2811        })?;
2812        Ok(())
2813    }
2814
2815    fn queue_reliable_packet_request(
2816        &self,
2817        side: RelaySideId,
2818        ty: crate::DataType,
2819        seq: u32,
2820    ) -> TelemetryResult<()> {
2821        let pkt = self.reliable_control_packet(crate::DataType::ReliablePacketRequest, ty, seq)?;
2822        let data = RelayItem::Packet(Arc::new(pkt));
2823        let priority = Self::relay_item_priority(&data)?;
2824        let mut st = self.state.lock();
2825        st.push_tx(RelayTxItem {
2826            src: None,
2827            dst: side,
2828            data,
2829            priority,
2830            retry_after_ms: 0,
2831        })?;
2832        Ok(())
2833    }
2834
2835    fn queue_reliable_partial_ack(
2836        &self,
2837        side: RelaySideId,
2838        ty: crate::DataType,
2839        seq: u32,
2840    ) -> TelemetryResult<()> {
2841        let pkt = self.reliable_control_packet(crate::DataType::ReliablePartialAck, ty, seq)?;
2842        let data = RelayItem::Packet(Arc::new(pkt));
2843        let priority = Self::relay_item_priority(&data)?;
2844        let mut st = self.state.lock();
2845        st.push_tx(RelayTxItem {
2846            src: None,
2847            dst: side,
2848            data,
2849            priority,
2850            retry_after_ms: 0,
2851        })?;
2852        Ok(())
2853    }
2854
2855    fn queue_reliable_retransmit(
2856        &self,
2857        side: RelaySideId,
2858        ty: crate::DataType,
2859        seq: u32,
2860    ) -> TelemetryResult<()> {
2861        let mut queued = None;
2862        {
2863            let mut st = self.state.lock();
2864            let tx_state = self.reliable_tx_state_mut(&mut st, side, ty);
2865            if let Some(sent) = tx_state.sent.get_mut(&seq)
2866                && !sent.queued
2867            {
2868                sent.partial_acked = false;
2869                queued = Some(sent.bytes.clone());
2870            }
2871        }
2872
2873        if let Some(bytes) = queued {
2874            let now = self.clock.now_ms();
2875            let mut st = self.state.lock();
2876            st.push_replay(RelayReplayItem {
2877                dst: side,
2878                ty,
2879                seq,
2880                bytes,
2881                priority: message_priority(ty).saturating_add(16),
2882                retry_after_ms: 0,
2883            })?;
2884            if let Some(sent) = st
2885                .reliable_tx
2886                .get_mut(&Self::reliable_key(side, ty))
2887                .and_then(|tx| tx.sent.get_mut(&seq))
2888            {
2889                sent.last_send_ms = sent.last_send_ms.max(now);
2890            }
2891        }
2892        Ok(())
2893    }
2894
2895    fn send_reliable_raw_to_side(
2896        &self,
2897        side: RelaySideId,
2898        bytes: crate::SharedBytes,
2899    ) -> TelemetryResult<()> {
2900        let (handler, opts) = {
2901            let st = self.state.lock();
2902            let side_ref = Self::side_ref(&st, side)?;
2903            if !side_ref.opts.egress_enabled {
2904                return Ok(());
2905            }
2906            (side_ref.tx_handler.clone(), side_ref.opts.clone())
2907        };
2908
2909        let Some(_side_tx_guard) = self.try_enter_side_tx(side) else {
2910            return Err(TelemetryError::Io("side tx busy"));
2911        };
2912        let started_ms = self.clock.now_ms();
2913        let ty = wire_format::peek_routing_envelope(bytes.as_ref())
2914            .map(|env| env.ty)
2915            .unwrap_or(crate::DataType::ReliableAck);
2916        let result = match handler {
2917            RelayTxHandlerFn::Packed(f) => {
2918                let frames = self.encode_side_transport_frames(side, opts, bytes.clone())?;
2919                let mut sent_bytes = 0usize;
2920                self.send_side_frames_resumable(side, frames, |frame| {
2921                    f(frame)?;
2922                    sent_bytes = sent_bytes.saturating_add(frame.len());
2923
2924                    Ok(())
2925                })?;
2926                self.record_side_tx_sample(side, sent_bytes, started_ms, self.clock.now_ms());
2927                self.note_side_tx_success(side, ty, sent_bytes, 1);
2928                return Ok(());
2929            }
2930            RelayTxHandlerFn::Packet(f) => {
2931                if wire_format::peek_routing_frame_info(bytes.as_ref())
2932                    .ok()
2933                    .is_some_and(|frame| frame.ack_only())
2934                {
2935                    return Ok(());
2936                }
2937                let pkt = wire_format::unpack_packet(bytes.as_ref())?;
2938                f(&pkt)
2939            }
2940        };
2941        if result.is_ok() {
2942            self.record_side_tx_sample(side, bytes.len(), started_ms, self.clock.now_ms());
2943            self.note_side_tx_success(side, ty, bytes.len(), 1);
2944        } else {
2945            self.note_side_tx_failure(side, ty, 1);
2946        }
2947        result
2948    }
2949
2950    #[allow(unused_mut)]
2951    fn send_reliable_to_side(&self, side: RelaySideId, data: RelayItem) -> TelemetryResult<()> {
2952        let (ty, addressed) = match &data {
2953            RelayItem::Packet(pkt) => (pkt.data_type(), !pkt.wire_target_senders().is_empty()),
2954            RelayItem::Packed(bytes) => {
2955                let env = wire_format::peek_routing_envelope(bytes)?;
2956                (env.ty, env.target_senders().len() != 0)
2957            }
2958        };
2959        let end_to_end_ordered =
2960            addressed && matches!(reliable_mode(ty), crate::ReliableMode::Ordered);
2961        let (handler, opts, hop_reliable_enabled) = {
2962            let st = self.state.lock();
2963            let side_ref = Self::side_ref(&st, side)?;
2964            let opts = side_ref.opts.clone();
2965            let hop_reliable_enabled = opts.reliable_enabled
2966                && !end_to_end_ordered
2967                && !self.side_has_multiple_announcers_locked(&st, side, self.clock.now_ms());
2968            (side_ref.tx_handler.clone(), opts, hop_reliable_enabled)
2969        };
2970
2971        let RelayTxHandlerFn::Packed(f) = &handler else {
2972            return self.call_tx_handler(side, &handler, &data);
2973        };
2974
2975        if !hop_reliable_enabled {
2976            let mut adjusted_opts = opts;
2977            adjusted_opts.reliable_enabled = false;
2978            if let Some(adjusted) = self.adjust_reliable_for_side(adjusted_opts, data)? {
2979                return self.call_tx_handler(side, &handler, &adjusted);
2980            }
2981            return Ok(());
2982        }
2983
2984        let ty = match &data {
2985            RelayItem::Packet(pkt) => pkt.data_type(),
2986            RelayItem::Packed(bytes) => {
2987                let Ok(frame) = wire_format::peek_routing_frame_info(bytes.as_ref()) else {
2988                    return self.call_tx_handler(side, &handler, &data);
2989                };
2990                frame.envelope.ty
2991            }
2992        };
2993
2994        if !is_reliable_type(ty) {
2995            if let Some(adjusted) = self.adjust_reliable_for_side(opts, data)? {
2996                self.call_tx_handler(side, &handler, &adjusted)?;
2997            }
2998            return Ok(());
2999        }
3000
3001        #[cfg(feature = "compression")]
3002        let data = match data {
3003            RelayItem::Packed(bytes) => {
3004                let budget = self.state.lock().memory.max_queue_budget;
3005                let normalized =
3006                    wire_format::payload_encoding_for_retention(bytes.as_ref(), budget)?;
3007                RelayItem::Packed(
3008                    normalized
3009                        .map(crate::shared_bytes::convert)
3010                        .unwrap_or(bytes),
3011                )
3012            }
3013            packet => packet,
3014        };
3015        let (seq, flags) = {
3016            let mut st = self.state.lock();
3017            let tx_state = self.reliable_tx_state_mut(&mut st, side, ty);
3018            if tx_state.sent.len() >= runtime_reliable_max_pending() {
3019                return Err(TelemetryError::Io("reliable window full"));
3020            }
3021            let seq = tx_state.next_seq;
3022            let next = tx_state.next_seq.wrapping_add(1);
3023            tx_state.next_seq = if next == 0 { 1 } else { next };
3024            let flags = match reliable_mode(ty) {
3025                crate::ReliableMode::Unordered => wire_format::RELIABLE_FLAG_UNORDERED,
3026                _ => 0,
3027            };
3028            (seq, flags)
3029        };
3030
3031        let bytes: crate::SharedBytes = match data {
3032            RelayItem::Packet(pkt) => {
3033                #[cfg(feature = "compression")]
3034                {
3035                    crate::shared_bytes::convert(self.pack_relay_packet(
3036                        &pkt,
3037                        Some(wire_format::ReliableHeader { flags, seq, ack: 0 }),
3038                    )?)
3039                }
3040                #[cfg(not(feature = "compression"))]
3041                {
3042                    crate::shared_bytes::convert(wire_format::pack_packet_with_reliable(
3043                        &pkt,
3044                        wire_format::ReliableHeader { flags, seq, ack: 0 },
3045                    ))
3046                }
3047            }
3048            RelayItem::Packed(bytes) => {
3049                let Some(rewritten) =
3050                    wire_format::rewrite_reliable_header_owned(bytes.as_ref(), flags, seq, 0)?
3051                else {
3052                    let Some(_side_tx_guard) = self.try_enter_side_tx(side) else {
3053                        return Err(TelemetryError::Io("side tx busy"));
3054                    };
3055                    let started_ms = self.clock.now_ms();
3056                    let frames = self.encode_side_transport_frames(side, opts, bytes.clone())?;
3057                    let mut sent_bytes = 0usize;
3058                    self.send_side_frames_resumable(side, frames, |frame| {
3059                        f(frame)?;
3060                        sent_bytes = sent_bytes.saturating_add(frame.len());
3061
3062                        Ok(())
3063                    })?;
3064                    self.record_side_tx_sample(side, sent_bytes, started_ms, self.clock.now_ms());
3065                    self.note_side_tx_success(side, ty, sent_bytes, 1);
3066                    return Ok(());
3067                };
3068                crate::shared_bytes::convert(rewritten)
3069            }
3070        };
3071
3072        #[cfg(feature = "compact-packet-store")]
3073        let mut bytes = bytes;
3074
3075        let Some(_side_tx_guard) = self.try_enter_side_tx(side) else {
3076            return Err(TelemetryError::Io("side tx busy"));
3077        };
3078        let started_ms = self.clock.now_ms();
3079        let frames = self.encode_side_transport_frames(side, opts, bytes.clone())?;
3080        let mut sent_bytes = 0usize;
3081        self.send_side_frames_resumable(side, frames, |frame| {
3082            f(frame)?;
3083            sent_bytes = sent_bytes.saturating_add(frame.len());
3084
3085            Ok(())
3086        })?;
3087        self.record_side_tx_sample(side, sent_bytes, started_ms, self.clock.now_ms());
3088        self.note_side_tx_success(side, ty, sent_bytes, 1);
3089
3090        #[cfg(feature = "compact-packet-store")]
3091        {
3092            let _ = bytes.park_for_queue();
3093        }
3094
3095        {
3096            let mut st = self.state.lock();
3097            let tx_state = self.reliable_tx_state_mut(&mut st, side, ty);
3098            tx_state.sent_order.push_back(seq);
3099            tx_state.sent.insert(
3100                seq,
3101                ReliableSent {
3102                    bytes: bytes.clone(),
3103                    last_send_ms: self.clock.now_ms(),
3104                    retries: 0,
3105                    queued: false,
3106                    partial_acked: false,
3107                },
3108            );
3109        }
3110
3111        Ok(())
3112    }
3113
3114    fn item_route_info(
3115        &self,
3116        data: &RelayItem,
3117    ) -> TelemetryResult<(Vec<crate::DataEndpoint>, crate::DataType)> {
3118        match data {
3119            RelayItem::Packet(pkt) => {
3120                let mut eps = pkt.endpoints().to_vec();
3121                crate::small_sort::sort(&mut eps);
3122                eps.dedup();
3123                Ok((eps, pkt.data_type()))
3124            }
3125            RelayItem::Packed(bytes) => {
3126                let env = wire_format::peek_routing_envelope(bytes.as_ref())?;
3127                let mut eps: Vec<crate::DataEndpoint> = env.endpoints().collect();
3128                crate::small_sort::sort(&mut eps);
3129                eps.dedup();
3130                Ok((eps, env.ty))
3131            }
3132        }
3133    }
3134
3135    #[cfg(feature = "discovery")]
3136    fn endpoints_are_link_local_only(eps: &[crate::DataEndpoint]) -> bool {
3137        !eps.is_empty() && eps.iter().all(|ep| ep.is_link_local_only())
3138    }
3139
3140    #[cfg(feature = "discovery")]
3141    fn item_target_senders<'a>(
3142        &self,
3143        data: &'a RelayItem,
3144    ) -> TelemetryResult<wire_format::TargetSenderSet<'a>> {
3145        match data {
3146            RelayItem::Packet(pkt) => Ok(wire_format::TargetSenderSet::Hashes(
3147                pkt.wire_target_senders(),
3148            )),
3149            RelayItem::Packed(bytes) => {
3150                Ok(wire_format::peek_routing_envelope(bytes.as_ref())?.target_sender_set())
3151            }
3152        }
3153    }
3154
3155    #[cfg(feature = "discovery")]
3156    fn has_explicit_route_policy_locked(
3157        st: &RelayInner,
3158        src: Option<RelaySideId>,
3159        ty: crate::DataType,
3160    ) -> bool {
3161        st.route_overrides
3162            .keys()
3163            .any(|(route_src, _)| *route_src == src)
3164            || Self::has_typed_route_overrides_locked(st, src, ty)
3165    }
3166
3167    #[cfg(feature = "discovery")]
3168    fn side_matches_target_senders_locked(
3169        st: &RelayInner,
3170        side: RelaySideId,
3171        target_senders: wire_format::TargetSenderSet<'_>,
3172        now_ms: u64,
3173    ) -> bool {
3174        st.discovery_routes
3175            .get(&side)
3176            .map(|route| {
3177                if now_ms.saturating_sub(route.last_seen_ms) > DISCOVERY_ROUTE_TTL_MS {
3178                    return false;
3179                }
3180                route.announcers.iter().any(|(announcer, sender_state)| {
3181                    if now_ms.saturating_sub(sender_state.last_seen_ms) > DISCOVERY_ROUTE_TTL_MS {
3182                        return false;
3183                    }
3184                    target_senders.contains(Self::sender_hash(announcer))
3185                        || sender_state
3186                            .advertised_address
3187                            .is_some_and(|address| target_senders.contains(u64::from(address)))
3188                        || sender_state.topology_boards.iter().any(|board| {
3189                            target_senders.contains(Self::sender_hash(&board.sender_id))
3190                        })
3191                })
3192            })
3193            .unwrap_or(false)
3194    }
3195
3196    fn remote_side_plan(
3197        &self,
3198        data: &RelayItem,
3199        exclude: RelaySideId,
3200    ) -> TelemetryResult<RemoteSidePlan> {
3201        #[cfg(feature = "discovery")]
3202        {
3203            let (eps, ty) = self.item_route_info(data)?;
3204            let target_senders = self.item_target_senders(data)?;
3205            let preferred_packet_id = Self::reliable_control_target_packet_id(data)?;
3206            if discovery::is_discovery_type(ty) {
3207                // Publish an aggregate split-horizon topology instead of
3208                // leaking far-side topology as direct neighbors. Address
3209                // allocation advertisements still cross transparent relays:
3210                // static/requested address conflicts must be resolved globally.
3211                if matches!(
3212                    ty,
3213                    crate::DataType::DiscoveryAnnounce
3214                        | crate::DataType::DiscoveryTimeSyncSources
3215                        | crate::DataType::DiscoveryTopology
3216                        | crate::DataType::DiscoveryLinkCapabilities
3217                ) {
3218                    return Ok(RemoteSidePlan::Target(Vec::new()));
3219                }
3220                let mut st = self.state.lock();
3221                let sides = self.eligible_side_ids_locked(&st, Some(exclude), Some(ty), false);
3222                return Ok(RemoteSidePlan::Target(self.apply_route_selection_locked(
3223                    &mut st,
3224                    Some(exclude),
3225                    sides,
3226                    RouteSelectionOrigin::Flood,
3227                )));
3228            }
3229
3230            #[cfg(feature = "timesync")]
3231            let preferred_timesync_source = self.preferred_timesync_route_source(data, ty)?;
3232            #[cfg(not(feature = "timesync"))]
3233            let preferred_timesync_source: Option<String> = None;
3234            let mut st = self.state.lock();
3235            if let Some(packet_id) = preferred_packet_id {
3236                let target_side = self.allowed_target_side_locked(
3237                    &st,
3238                    exclude,
3239                    ty,
3240                    st.reliable_return_routes
3241                        .get(&packet_id)
3242                        .map(|route| route.side),
3243                );
3244                if let Some(side) = target_side {
3245                    #[cfg(feature = "timesync")]
3246                    if !Self::timesync_allowed_for_side_locked(&st, side, ty, self.clock.now_ms()) {
3247                        return Ok(RemoteSidePlan::Target(Vec::new()));
3248                    }
3249                    return Ok(RemoteSidePlan::Target(vec![side]));
3250                }
3251                let now_ms = self.clock.now_ms();
3252                let eligible = self.eligible_side_ids_locked(&st, Some(exclude), Some(ty), false);
3253                let original = target_senders.skip(1);
3254                let exact: Vec<_> = eligible
3255                    .iter()
3256                    .copied()
3257                    .filter(|side| {
3258                        !original.is_empty()
3259                            && Self::side_matches_target_senders_locked(
3260                                &st, *side, original, now_ms,
3261                            )
3262                    })
3263                    .collect();
3264                if !exact.is_empty() {
3265                    return Ok(RemoteSidePlan::Target(self.apply_route_selection_locked(
3266                        &mut st,
3267                        Some(exclude),
3268                        exact,
3269                        RouteSelectionOrigin::Discovered,
3270                    )));
3271                }
3272                // A known publisher on an excluded/denied route must not
3273                // leak onto an unrelated segment through the legacy fallback.
3274                let publisher_known = !original.is_empty()
3275                    && st.discovery_routes.keys().any(|&side| {
3276                        Self::side_matches_target_senders_locked(&st, side, original, now_ms)
3277                    });
3278                return Ok(RemoteSidePlan::Target(
3279                    if !publisher_known && eligible.len() == 1 {
3280                        eligible
3281                    } else {
3282                        Vec::new()
3283                    },
3284                ));
3285            }
3286            let restrict_link_local = Self::endpoints_are_link_local_only(&eps);
3287            let discovered_origin = if is_reliable_type(ty) {
3288                RouteSelectionOrigin::Flood
3289            } else {
3290                RouteSelectionOrigin::Discovered
3291            };
3292            if st.discovery_routes.is_empty() {
3293                let mut fallback = self.eligible_side_ids_locked(
3294                    &st,
3295                    Some(exclude),
3296                    Some(ty),
3297                    restrict_link_local,
3298                );
3299                #[cfg(feature = "timesync")]
3300                {
3301                    fallback =
3302                        Self::filter_timesync_sides_locked(&st, ty, self.clock.now_ms(), fallback);
3303                }
3304                return Ok(RemoteSidePlan::Target(if fallback.len() == 1 {
3305                    fallback
3306                } else {
3307                    Vec::new()
3308                }));
3309            }
3310            let now_ms = self.clock.now_ms();
3311            let mut had_exact = false;
3312            let mut exact_targets = Vec::new();
3313            let mut had_known = false;
3314            let mut generic_targets = Vec::new();
3315            let mut unique_endpoint_fallbacks = Vec::new();
3316
3317            for (&side, route) in st.discovery_routes.iter() {
3318                if side == exclude
3319                    || now_ms.saturating_sub(route.last_seen_ms) > DISCOVERY_ROUTE_TTL_MS
3320                {
3321                    continue;
3322                }
3323                if restrict_link_local
3324                    && st
3325                        .sides
3326                        .get(side)
3327                        .and_then(|side| side.as_ref())
3328                        .map(|s| !s.opts.link_local_enabled)
3329                        .unwrap_or(true)
3330                {
3331                    continue;
3332                }
3333                if !self.route_allowed_locked(&st, Some(exclude), Some(ty), side) {
3334                    continue;
3335                }
3336                if !target_senders.is_empty() {
3337                    if !Self::side_matches_target_senders_locked(&st, side, target_senders, now_ms)
3338                    {
3339                        if eps.iter().copied().any(|ep| route.reachable.contains(&ep)) {
3340                            unique_endpoint_fallbacks.push(side);
3341                        }
3342                        continue;
3343                    }
3344                    had_known = true;
3345                    generic_targets.push(side);
3346                    continue;
3347                }
3348                if preferred_timesync_source.as_deref().is_some_and(|source| {
3349                    route.reachable_timesync_sources.iter().any(|s| s == source)
3350                }) {
3351                    had_exact = true;
3352                    exact_targets.push(side);
3353                    continue;
3354                }
3355                if eps.iter().copied().any(|ep| route.reachable.contains(&ep)) {
3356                    had_known = true;
3357                    generic_targets.push(side);
3358                }
3359            }
3360
3361            // Compact/DHCP links can temporarily disagree on whether a frozen
3362            // destination is represented by hostname hash or assigned address.
3363            // Preserve autonomous endpoint routing only when discovery gives
3364            // one unambiguous non-ingress route; never fan an unknown target
3365            // out across multiple sides.
3366            if !target_senders.is_empty() && !had_known && unique_endpoint_fallbacks.len() == 1 {
3367                had_known = true;
3368                generic_targets = unique_endpoint_fallbacks;
3369            }
3370
3371            if had_exact {
3372                #[cfg(feature = "timesync")]
3373                {
3374                    exact_targets = Self::filter_timesync_sides_locked(
3375                        &st,
3376                        ty,
3377                        self.clock.now_ms(),
3378                        exact_targets,
3379                    );
3380                }
3381                let targets = self.filter_end_to_end_satisfied_sides_locked(
3382                    &st,
3383                    data,
3384                    exact_targets,
3385                    &eps,
3386                    ty,
3387                )?;
3388                Ok(RemoteSidePlan::Target(self.apply_route_selection_locked(
3389                    &mut st,
3390                    Some(exclude),
3391                    targets,
3392                    discovered_origin,
3393                )))
3394            } else if had_known {
3395                Self::retain_shortest_discovery_routes_locked(
3396                    &st,
3397                    &mut generic_targets,
3398                    &eps,
3399                    now_ms,
3400                );
3401                #[cfg(feature = "timesync")]
3402                {
3403                    generic_targets = Self::filter_timesync_sides_locked(
3404                        &st,
3405                        ty,
3406                        self.clock.now_ms(),
3407                        generic_targets,
3408                    );
3409                }
3410                let targets = self.filter_end_to_end_satisfied_sides_locked(
3411                    &st,
3412                    data,
3413                    generic_targets,
3414                    &eps,
3415                    ty,
3416                )?;
3417                Ok(RemoteSidePlan::Target(self.apply_route_selection_locked(
3418                    &mut st,
3419                    Some(exclude),
3420                    targets,
3421                    discovered_origin,
3422                )))
3423            } else {
3424                let fallback = self.eligible_side_ids_locked(
3425                    &st,
3426                    Some(exclude),
3427                    Some(ty),
3428                    restrict_link_local,
3429                );
3430                if fallback.len() == 1 {
3431                    return Ok(RemoteSidePlan::Target(fallback));
3432                }
3433                if Self::has_explicit_route_policy_locked(&st, Some(exclude), ty) {
3434                    let mut sides = self.eligible_side_ids_locked(
3435                        &st,
3436                        Some(exclude),
3437                        Some(ty),
3438                        restrict_link_local,
3439                    );
3440                    #[cfg(feature = "timesync")]
3441                    {
3442                        sides =
3443                            Self::filter_timesync_sides_locked(&st, ty, self.clock.now_ms(), sides);
3444                    }
3445                    Ok(RemoteSidePlan::Target(self.apply_route_selection_locked(
3446                        &mut st,
3447                        Some(exclude),
3448                        sides,
3449                        RouteSelectionOrigin::Flood,
3450                    )))
3451                } else {
3452                    Ok(RemoteSidePlan::Target(Vec::new()))
3453                }
3454            }
3455        }
3456        #[cfg(not(feature = "discovery"))]
3457        {
3458            let (_, ty) = self.item_route_info(data)?;
3459            let mut st = self.state.lock();
3460            if let Some(packet_id) = Self::reliable_control_target_packet_id(data)? {
3461                let target_side = self.allowed_target_side_locked(
3462                    &st,
3463                    exclude,
3464                    ty,
3465                    st.reliable_return_routes
3466                        .get(&packet_id)
3467                        .map(|route| route.side),
3468                );
3469                if let Some(side) = target_side {
3470                    return Ok(RemoteSidePlan::Target(vec![side]));
3471                }
3472                let sides = self.eligible_side_ids_locked(&st, Some(exclude), Some(ty), false);
3473                return Ok(RemoteSidePlan::Target(if sides.len() == 1 {
3474                    sides
3475                } else {
3476                    Vec::new()
3477                }));
3478            }
3479            let sides = self.eligible_side_ids_locked(&st, Some(exclude), Some(ty), false);
3480            Ok(RemoteSidePlan::Target(self.apply_route_selection_locked(
3481                &mut st,
3482                Some(exclude),
3483                sides,
3484                RouteSelectionOrigin::Flood,
3485            )))
3486        }
3487    }
3488
3489    #[inline]
3490    fn allowed_target_side_locked(
3491        &self,
3492        st: &RelayInner,
3493        exclude: RelaySideId,
3494        ty: crate::DataType,
3495        target_side: Option<RelaySideId>,
3496    ) -> Option<RelaySideId> {
3497        target_side.filter(|side| self.route_allowed_locked(st, Some(exclude), Some(ty), *side))
3498    }
3499
3500    #[cfg(feature = "discovery")]
3501    fn filter_end_to_end_satisfied_sides_locked(
3502        &self,
3503        st: &RelayInner,
3504        data: &RelayItem,
3505        sides: Vec<RelaySideId>,
3506        eps: &[crate::DataEndpoint],
3507        ty: crate::DataType,
3508    ) -> TelemetryResult<Vec<RelaySideId>> {
3509        if !is_reliable_type(ty) || Self::reliable_control_target_packet_id(data)?.is_some() {
3510            return Ok(sides);
3511        }
3512        let packet_id = match data {
3513            RelayItem::Packet(pkt) => pkt.packet_id(),
3514            RelayItem::Packed(bytes) => match wire_format::packet_id_from_wire(bytes.as_ref()) {
3515                Ok(packet_id) => packet_id,
3516                Err(TelemetryError::Unpack("reliable control frame")) => return Ok(sides),
3517                Err(err) => return Err(err),
3518            },
3519        };
3520        let Some(acked) = st.end_to_end_acked_destinations.get(&packet_id) else {
3521            return Ok(sides);
3522        };
3523        let now_ms = self.clock.now_ms();
3524        let mut filtered = Vec::new();
3525        for side in sides {
3526            let Some(route) = st.discovery_routes.get(&side) else {
3527                filtered.push(side);
3528                continue;
3529            };
3530            let mut still_pending = false;
3531            let mut had_destination_board = false;
3532            for sender_state in route.announcers.values() {
3533                if now_ms.saturating_sub(sender_state.last_seen_ms) > DISCOVERY_ROUTE_TTL_MS {
3534                    continue;
3535                }
3536                for board in sender_state.topology_boards.iter() {
3537                    if !self.is_end_to_end_destination_sender(&board.sender_id) {
3538                        continue;
3539                    }
3540                    had_destination_board = true;
3541                    let sender_hash = Self::sender_hash(&board.sender_id);
3542                    if acked.contains(&sender_hash) {
3543                        continue;
3544                    }
3545                    if eps
3546                        .iter()
3547                        .copied()
3548                        .any(|ep| board.reachable_endpoints.contains(&ep))
3549                    {
3550                        still_pending = true;
3551                        break;
3552                    }
3553                    // Keep forwarding while any discovered destination sender for this packet
3554                    // remains unacked, even if topology/schema metadata changed for new packets.
3555                    still_pending = true;
3556                    break;
3557                }
3558                if still_pending {
3559                    break;
3560                }
3561            }
3562            if still_pending || !had_destination_board {
3563                filtered.push(side);
3564            }
3565        }
3566        Ok(filtered)
3567    }
3568
3569    #[cfg(feature = "discovery")]
3570    fn side_has_multiple_announcers_locked(
3571        &self,
3572        st: &RelayInner,
3573        side: RelaySideId,
3574        now_ms: u64,
3575    ) -> bool {
3576        st.discovery_routes
3577            .get(&side)
3578            .map(|route| {
3579                route
3580                    .announcers
3581                    .values()
3582                    .filter(|sender| {
3583                        now_ms.saturating_sub(sender.last_seen_ms) <= DISCOVERY_ROUTE_TTL_MS
3584                    })
3585                    .take(2)
3586                    .count()
3587                    > 1
3588            })
3589            .unwrap_or(false)
3590    }
3591
3592    #[cfg(not(feature = "discovery"))]
3593    fn side_has_multiple_announcers_locked(
3594        &self,
3595        _st: &RelayInner,
3596        _side: RelaySideId,
3597        _now_ms: u64,
3598    ) -> bool {
3599        false
3600    }
3601
3602    #[cfg(feature = "discovery")]
3603    fn sender_topology_board_mut<'a>(
3604        sender_state: &'a mut DiscoverySenderState,
3605        sender_id: &str,
3606    ) -> &'a mut TopologyBoardNode {
3607        let boards = Arc::make_mut(&mut sender_state.topology_boards);
3608        if let Some(idx) = boards.iter().position(|board| board.sender_id == sender_id) {
3609            return &mut boards[idx];
3610        }
3611        boards.push(TopologyBoardNode {
3612            sender_id: sender_id.to_string(),
3613            reachable_endpoints: Vec::new(),
3614            reachable_timesync_sources: Vec::new(),
3615            connections: Vec::new(),
3616        });
3617        boards.last_mut().expect("inserted above")
3618    }
3619
3620    #[cfg(feature = "discovery")]
3621    fn refresh_sender_topology_state(sender_state: &mut DiscoverySenderState) {
3622        if let Some(boards) = Arc::get_mut(&mut sender_state.topology_boards) {
3623            discovery::normalize_topology_boards(boards);
3624        }
3625        let (mut reachable, mut reachable_timesync_sources) =
3626            discovery::summarize_topology_boards(&sender_state.topology_boards);
3627        reachable.extend(sender_state.advertised_reachable.iter().copied());
3628        crate::small_sort::sort(&mut reachable);
3629        reachable.dedup();
3630        reachable_timesync_sources.extend(
3631            sender_state
3632                .advertised_reachable_timesync_sources
3633                .iter()
3634                .cloned(),
3635        );
3636        reachable_timesync_sources.sort_unstable();
3637        reachable_timesync_sources.dedup();
3638        sender_state.reachable = reachable;
3639        sender_state.reachable_timesync_sources = reachable_timesync_sources;
3640    }
3641
3642    #[cfg(feature = "discovery")]
3643    fn recompute_discovery_side_state(route: &mut DiscoverySideState) {
3644        let mut reachable = Vec::new();
3645        let mut reachable_timesync_sources = Vec::new();
3646        let mut last_seen_ms = 0u64;
3647        for sender in route.announcers.values() {
3648            reachable.extend(sender.reachable.iter().copied());
3649            reachable_timesync_sources.extend(sender.reachable_timesync_sources.iter().cloned());
3650            last_seen_ms = last_seen_ms.max(sender.last_seen_ms);
3651        }
3652        crate::small_sort::sort(&mut reachable);
3653        reachable.dedup();
3654        reachable_timesync_sources.sort_unstable();
3655        reachable_timesync_sources.dedup();
3656        route.reachable = reachable;
3657        route.reachable_timesync_sources = reachable_timesync_sources;
3658        route.last_seen_ms = last_seen_ms;
3659    }
3660
3661    #[cfg(feature = "discovery")]
3662    fn local_discovery_topology_board(&self, st: &RelayInner, now_ms: u64) -> TopologyBoardNode {
3663        let mut connections = Vec::new();
3664        for route in st.discovery_routes.values() {
3665            if now_ms.saturating_sub(route.last_seen_ms) > DISCOVERY_ROUTE_TTL_MS {
3666                continue;
3667            }
3668            for (sender, sender_state) in route.announcers.iter() {
3669                if now_ms.saturating_sub(sender_state.last_seen_ms) <= DISCOVERY_ROUTE_TTL_MS {
3670                    connections.push(sender.clone());
3671                }
3672            }
3673        }
3674        connections.sort_unstable();
3675        connections.dedup();
3676        let sender = self.sender_arc();
3677        TopologyBoardNode {
3678            sender_id: sender.to_string(),
3679            reachable_endpoints: Vec::new(),
3680            reachable_timesync_sources: Vec::new(),
3681            connections,
3682        }
3683    }
3684
3685    #[cfg(feature = "discovery")]
3686    fn advertised_discovery_topology_for_link_locked(
3687        &self,
3688        st: &RelayInner,
3689        now_ms: u64,
3690        link_local_enabled: bool,
3691        exclude_side: Option<RelaySideId>,
3692    ) -> Vec<TopologyBoardNode> {
3693        let mut boards = vec![self.local_discovery_topology_board(st, now_ms)];
3694        for (&route_side, route) in st.discovery_routes.iter() {
3695            if exclude_side == Some(route_side) {
3696                continue;
3697            }
3698            if now_ms.saturating_sub(route.last_seen_ms) > DISCOVERY_ROUTE_TTL_MS {
3699                continue;
3700            }
3701            for (announcer, sender_state) in route.announcers.iter() {
3702                if now_ms.saturating_sub(sender_state.last_seen_ms) > DISCOVERY_ROUTE_TTL_MS {
3703                    continue;
3704                }
3705                if sender_state.topology_boards.is_empty() {
3706                    let sender = self.sender_arc();
3707                    let fallback = TopologyBoardNode {
3708                        sender_id: announcer.clone(),
3709                        reachable_endpoints: sender_state.reachable.clone(),
3710                        reachable_timesync_sources: sender_state.reachable_timesync_sources.clone(),
3711                        connections: vec![sender.to_string()],
3712                    };
3713                    discovery::merge_topology_boards(&mut boards, core::slice::from_ref(&fallback));
3714                } else {
3715                    // Merge the retained view directly. Cloning it first and
3716                    // cloning again into the output doubled temporary strings.
3717                    discovery::merge_topology_boards(&mut boards, &sender_state.topology_boards);
3718                    if let Some(board) = boards
3719                        .iter_mut()
3720                        .find(|board| board.sender_id == *announcer)
3721                    {
3722                        let sender = self.sender_arc();
3723                        if !board.connections.iter().any(|peer| peer == sender.as_ref()) {
3724                            board.connections.push(sender.to_string());
3725                        }
3726                    }
3727                }
3728            }
3729        }
3730        if !link_local_enabled {
3731            for board in &mut boards {
3732                board
3733                    .reachable_endpoints
3734                    .retain(|ep| !ep.is_link_local_only());
3735            }
3736        }
3737        discovery::normalize_topology_boards(&mut boards);
3738        boards
3739    }
3740
3741    #[cfg(feature = "discovery")]
3742    fn note_discovery_topology_change_locked(st: &mut RelayInner, now_ms: u64) {
3743        st.discovery_cadence.on_topology_change(now_ms);
3744        for side_id in st
3745            .sides
3746            .iter()
3747            .enumerate()
3748            .filter_map(|(side_id, side)| side.as_ref().map(|_| side_id))
3749        {
3750            let throttle = st.discovery_side_throttle.entry(side_id).or_default();
3751            throttle.pending_incremental = true;
3752            throttle.incremental_retries_remaining = DISCOVERY_INCREMENTAL_RETRY_COUNT;
3753        }
3754        for transport in st.side_transport.values_mut() {
3755            transport.refresh_transmit_header_templates();
3756        }
3757    }
3758
3759    #[cfg(feature = "discovery")]
3760    fn prune_discovery_routes_locked(st: &mut RelayInner, now_ms: u64) -> bool {
3761        // Match router maintenance: unchanged routes require no heap work.
3762        let mut changed = false;
3763        st.discovery_routes.retain(|_, route| {
3764            let before_peers = route.announcers.len();
3765            route.announcers.retain(|_, sender| {
3766                now_ms.saturating_sub(sender.last_seen_ms) <= DISCOVERY_ROUTE_TTL_MS
3767            });
3768            if before_peers != route.announcers.len() {
3769                Self::recompute_discovery_side_state(route);
3770                changed = true;
3771            }
3772            let keep = !route.announcers.is_empty();
3773            changed |= !keep;
3774            keep
3775        });
3776        changed
3777    }
3778
3779    #[cfg(feature = "discovery")]
3780    fn reconcile_end_to_end_acked_destinations_locked(&self, st: &mut RelayInner) {
3781        if st.end_to_end_acked_destinations.is_empty() {
3782            return;
3783        }
3784        let mut active_senders = BTreeSet::new();
3785        for route in st.discovery_routes.values() {
3786            for sender_state in route.announcers.values() {
3787                for board in sender_state.topology_boards.iter() {
3788                    if self.is_end_to_end_destination_sender(&board.sender_id) {
3789                        active_senders.insert(Self::sender_hash(&board.sender_id));
3790                    }
3791                }
3792            }
3793        }
3794        st.end_to_end_acked_destinations.retain(|_, acked| {
3795            acked.retain(|sender_hash| active_senders.contains(sender_hash));
3796            !acked.is_empty()
3797        });
3798    }
3799
3800    #[cfg(feature = "discovery")]
3801    fn advertised_summaries_from_topology_locked(
3802        &self,
3803        st: &RelayInner,
3804        now_ms: u64,
3805        link_local_enabled: bool,
3806        exclude_side: Option<RelaySideId>,
3807        topology: &[TopologyBoardNode],
3808    ) -> (Vec<crate::DataEndpoint>, Vec<String>) {
3809        let (mut endpoints, mut sources) = discovery::summarize_topology_boards(topology);
3810        for (&side, route) in &st.discovery_routes {
3811            if exclude_side == Some(side)
3812                || now_ms.saturating_sub(route.last_seen_ms) > DISCOVERY_ROUTE_TTL_MS
3813            {
3814                continue;
3815            }
3816            for peer in route.announcers.values() {
3817                if now_ms.saturating_sub(peer.last_seen_ms) > DISCOVERY_ROUTE_TTL_MS {
3818                    continue;
3819                }
3820                for endpoint in &peer.advertised_reachable {
3821                    if !discovery::is_discovery_endpoint(*endpoint)
3822                        && (link_local_enabled || !endpoint.is_link_local_only())
3823                        && !endpoints.contains(endpoint)
3824                    {
3825                        endpoints.push(*endpoint);
3826                    }
3827                }
3828                for source in &peer.advertised_reachable_timesync_sources {
3829                    if !sources.contains(source) {
3830                        sources.push(source.clone());
3831                    }
3832                }
3833            }
3834        }
3835        endpoints.retain(|endpoint| {
3836            !discovery::is_discovery_endpoint(*endpoint)
3837                && (link_local_enabled || !endpoint.is_link_local_only())
3838        });
3839        crate::small_sort::sort(&mut endpoints);
3840        sources.sort_unstable();
3841        (endpoints, sources)
3842    }
3843
3844    #[cfg(feature = "discovery")]
3845    fn advertised_discovery_endpoints_for_link_locked(
3846        &self,
3847        st: &RelayInner,
3848        now_ms: u64,
3849        link_local_enabled: bool,
3850        exclude_side: Option<RelaySideId>,
3851    ) -> Vec<crate::DataEndpoint> {
3852        let (mut reachable_endpoints, _) = discovery::summarize_topology_boards(
3853            &self.advertised_discovery_topology_for_link_locked(
3854                st,
3855                now_ms,
3856                link_local_enabled,
3857                exclude_side,
3858            ),
3859        );
3860        // Compact advertisements can know routes before detailed topology arrives.
3861        // Preserve that reachability without assigning downstream endpoints to the bridge.
3862        for (&side, route) in &st.discovery_routes {
3863            if exclude_side == Some(side)
3864                || now_ms.saturating_sub(route.last_seen_ms) > DISCOVERY_ROUTE_TTL_MS
3865            {
3866                continue;
3867            }
3868            for sender in route.announcers.values() {
3869                if now_ms.saturating_sub(sender.last_seen_ms) <= DISCOVERY_ROUTE_TTL_MS {
3870                    reachable_endpoints.extend(sender.advertised_reachable.iter().copied());
3871                }
3872            }
3873        }
3874        crate::small_sort::sort(&mut reachable_endpoints);
3875        reachable_endpoints.dedup();
3876        reachable_endpoints
3877            .into_iter()
3878            .filter(|ep| {
3879                !discovery::is_discovery_endpoint(*ep)
3880                    && (link_local_enabled || !ep.is_link_local_only())
3881            })
3882            .collect()
3883    }
3884
3885    #[cfg(feature = "discovery")]
3886    fn advertised_discovery_timesync_sources_for_link_locked(
3887        &self,
3888        st: &RelayInner,
3889        now_ms: u64,
3890        exclude_side: Option<RelaySideId>,
3891    ) -> Vec<String> {
3892        let (_, mut sources) = discovery::summarize_topology_boards(
3893            &self.advertised_discovery_topology_for_link_locked(st, now_ms, true, exclude_side),
3894        );
3895        for (&side, route) in &st.discovery_routes {
3896            if exclude_side == Some(side)
3897                || now_ms.saturating_sub(route.last_seen_ms) > DISCOVERY_ROUTE_TTL_MS
3898            {
3899                continue;
3900            }
3901            for sender in route.announcers.values() {
3902                if now_ms.saturating_sub(sender.last_seen_ms) <= DISCOVERY_ROUTE_TTL_MS {
3903                    sources.extend(sender.advertised_reachable_timesync_sources.iter().cloned());
3904                }
3905            }
3906        }
3907        sources.sort_unstable();
3908        sources.dedup();
3909        sources
3910    }
3911
3912    #[cfg(feature = "discovery")]
3913    #[cfg(feature = "timesync")]
3914    fn preferred_timesync_route_source(
3915        &self,
3916        data: &RelayItem,
3917        ty: crate::DataType,
3918    ) -> TelemetryResult<Option<String>> {
3919        if !matches!(
3920            ty,
3921            crate::DataType::TimeSyncAnnounce | crate::DataType::TimeSyncResponse
3922        ) {
3923            return Ok(None);
3924        }
3925
3926        let sender = match data {
3927            RelayItem::Packet(pkt) => pkt.sender().to_owned(),
3928            RelayItem::Packed(bytes) => {
3929                if wire_format::peek_routing_frame_info(bytes.as_ref())
3930                    .ok()
3931                    .is_some_and(|frame| frame.ack_only())
3932                {
3933                    return Ok(None);
3934                }
3935                wire_format::unpack_packet(bytes.as_ref())?
3936                    .sender()
3937                    .to_owned()
3938            }
3939        };
3940        Ok(Some(sender))
3941    }
3942
3943    #[cfg(all(feature = "discovery", feature = "timesync"))]
3944    #[inline]
3945    fn side_is_slow_control_link_locked(
3946        st: &RelayInner,
3947        side_id: RelaySideId,
3948        now_ms: u64,
3949    ) -> bool {
3950        st.adaptive_route_stats.get(&side_id).is_some_and(|stats| {
3951            let recent_slow = stats.last_slow_observed_ms > 0
3952                && now_ms.saturating_sub(stats.last_slow_observed_ms)
3953                    <= discovery::DISCOVERY_SLOW_LINK_FULL_INTERVAL_MS;
3954            stats.sample_count > 0
3955                && ((stats.estimated_bandwidth_bps > 0
3956                    && stats.estimated_bandwidth_bps <= CONTROL_SLOW_LINK_CAPACITY_BPS)
3957                    || recent_slow)
3958        })
3959    }
3960
3961    #[cfg(feature = "discovery")]
3962    fn discovery_level_for_side_locked(
3963        st: &mut RelayInner,
3964        side_id: RelaySideId,
3965        now_ms: u64,
3966    ) -> Option<DiscoveryAdvertiseLevel> {
3967        let throttle = st.discovery_side_throttle.entry(side_id).or_default();
3968        if !throttle.has_sent_full {
3969            throttle.has_sent_full = true;
3970            throttle.pending_incremental = false;
3971            throttle.incremental_retries_remaining = 0;
3972            throttle.next_ping_ms = now_ms.saturating_add(DISCOVERY_SLOW_LINK_PING_INTERVAL_MS);
3973            return Some(DiscoveryAdvertiseLevel::Full);
3974        }
3975        if throttle.pending_incremental {
3976            throttle.incremental_retries_remaining =
3977                throttle.incremental_retries_remaining.saturating_sub(1);
3978            throttle.pending_incremental = throttle.incremental_retries_remaining > 0;
3979            return Some(DiscoveryAdvertiseLevel::Incremental);
3980        }
3981        // Stable links carry only a small liveness announcement. Route and
3982        // schema snapshots are emitted on changes or explicit recovery requests.
3983        if throttle.liveness_due(now_ms) {
3984            throttle.next_ping_ms = now_ms.saturating_add(DISCOVERY_SLOW_LINK_PING_INTERVAL_MS);
3985            return Some(DiscoveryAdvertiseLevel::MinimalPing);
3986        }
3987        None
3988    }
3989
3990    #[cfg(all(feature = "discovery", feature = "timesync"))]
3991    #[inline]
3992    fn is_timesync_type(ty: crate::DataType) -> bool {
3993        matches!(
3994            ty,
3995            crate::DataType::TimeSyncAnnounce
3996                | crate::DataType::TimeSyncRequest
3997                | crate::DataType::TimeSyncResponse
3998        )
3999    }
4000
4001    #[cfg(all(feature = "discovery", feature = "timesync"))]
4002    #[inline(never)]
4003    fn timesync_allowed_for_side_locked(
4004        st: &RelayInner,
4005        side_id: RelaySideId,
4006        ty: crate::DataType,
4007        now_ms: u64,
4008    ) -> bool {
4009        if !Self::is_timesync_type(ty) {
4010            return true;
4011        }
4012        if !Self::side_is_slow_control_link_locked(st, side_id, now_ms) {
4013            return true;
4014        }
4015        // A routing/availability check is not a successful transmission. Keep
4016        // the allowance available across retries and commit only on acceptance.
4017        st.timesync_side_throttle
4018            .get(&side_id)
4019            .is_none_or(|throttle| now_ms >= throttle.next_allowed_ms)
4020    }
4021
4022    #[cfg(all(feature = "discovery", feature = "timesync"))]
4023    fn filter_timesync_sides_locked(
4024        st: &RelayInner,
4025        ty: crate::DataType,
4026        now_ms: u64,
4027        sides: Vec<RelaySideId>,
4028    ) -> Vec<RelaySideId> {
4029        sides
4030            .into_iter()
4031            .filter(|side| Self::timesync_allowed_for_side_locked(st, *side, ty, now_ms))
4032            .collect()
4033    }
4034
4035    #[cfg(feature = "discovery")]
4036    fn queue_discovery_announce(
4037        &self,
4038        include_schema: bool,
4039        skip_in_flight_sides: bool,
4040    ) -> TelemetryResult<()> {
4041        self.queue_discovery_announce_on_side(include_schema, skip_in_flight_sides, None)
4042    }
4043
4044    #[cfg(feature = "discovery")]
4045    fn refresh_known_discovery_liveness(
4046        &self,
4047        pkt: &Packet,
4048        side: RelaySideId,
4049    ) -> TelemetryResult<bool> {
4050        if pkt.data_type() == crate::DataType::DiscoveryAddress {
4051            return self.refresh_known_address_payload(pkt.payload(), side);
4052        }
4053        if pkt.data_type() == crate::DataType::DiscoveryTopology {
4054            // No decoded graph or retained-side clone is needed for a retry
4055            // whose payload already describes precisely the retained state.
4056            // Keep changed topology behind the full admission guard.
4057            crate::memory_admission::check(512, 128)?;
4058            let now = self.clock.now_ms();
4059            let mut st = self.state.lock();
4060            let canonical = Self::canonical_sender_locked(&st, pkt.sender());
4061            let Some(route) = st.discovery_routes.get_mut(&side) else {
4062                return Ok(false);
4063            };
4064            let Some(peer) = route.announcers.get_mut(canonical.as_str()) else {
4065                return Ok(false);
4066            };
4067            let Some(full) =
4068                discovery::topology_payload_matches_retained(pkt.payload(), &peer.topology_boards)
4069            else {
4070                return Ok(false);
4071            };
4072            peer.has_full_topology |= full;
4073            peer.last_seen_ms = now;
4074            route.last_seen_ms = route.last_seen_ms.max(now);
4075            return Ok(true);
4076        }
4077        if pkt.data_type() != crate::DataType::DiscoveryAnnounce || !pkt.payload().is_empty() {
4078            return Ok(false);
4079        }
4080        crate::memory_admission::check(512, 128)?;
4081        let now = self.clock.now_ms();
4082        let mut st = self.state.lock();
4083        let canonical = Self::canonical_sender_locked(&st, pkt.sender());
4084        let Some(route) = st.discovery_routes.get_mut(&side) else {
4085            return Ok(false);
4086        };
4087        let Some(peer) = route.announcers.get_mut(canonical.as_str()) else {
4088            return Ok(false);
4089        };
4090        peer.last_seen_ms = now;
4091        route.last_seen_ms = route.last_seen_ms.max(now);
4092        Ok(true)
4093    }
4094
4095    #[cfg(feature = "discovery")]
4096    fn refresh_known_address_payload(
4097        &self,
4098        payload: &[u8],
4099        side: RelaySideId,
4100    ) -> TelemetryResult<bool> {
4101        let ad = discovery::decode_address_view(payload)?;
4102        let now = self.clock.now_ms();
4103        let mut st = self.state.lock();
4104        let link_local = st
4105            .sides
4106            .get(side)
4107            .and_then(|entry| entry.as_ref())
4108            .is_some_and(|entry| entry.opts.link_local_enabled);
4109        let sender = ad.hostname;
4110        let Some(route) = st.discovery_routes.get_mut(&side) else {
4111            return Ok(false);
4112        };
4113        let Some(peer) = route.announcers.get_mut(sender) else {
4114            return Ok(false);
4115        };
4116        if peer.advertised_address != Some(ad.address)
4117            || !ad
4118                .endpoints()
4119                .filter(|ep| link_local || !ep.is_link_local_only())
4120                .eq(peer.advertised_reachable.iter().copied())
4121            || !ad.sources().eq(peer
4122                .advertised_reachable_timesync_sources
4123                .iter()
4124                .map(String::as_str))
4125        {
4126            return Ok(false);
4127        }
4128        // A liveness update cannot add ownership, change identity, or enlarge
4129        // discovery state. Leave new/changed summaries behind the full guard.
4130        peer.last_seen_ms = now;
4131        route.last_seen_ms = route.last_seen_ms.max(now);
4132
4133        #[cfg(feature = "compression")]
4134        {
4135            drop(st);
4136            self.note_payload_codec_address(side, payload, true);
4137        }
4138        Ok(true)
4139    }
4140
4141    #[cfg(feature = "discovery")]
4142    fn queue_pressure_keepalives(
4143        &self,
4144        requested_side: Option<RelaySideId>,
4145    ) -> TelemetryResult<()> {
4146        let now = self.clock.now_ms();
4147        let sender = self.sender_arc();
4148        crate::memory_admission::check(
4149            256 + sender.len().saturating_mul(4),
4150            sender.len().max(256),
4151        )?;
4152        let side_count = self.state.lock().sides.len();
4153        for side in 0..side_count {
4154            let due = {
4155                let st = self.state.lock();
4156                requested_side.is_none_or(|wanted| wanted == side)
4157                    && st.sides[side].is_some()
4158                    && self.route_allowed_locked(
4159                        &st,
4160                        None,
4161                        Some(crate::DataType::DiscoveryAnnounce),
4162                        side,
4163                    )
4164                    && st
4165                        .discovery_side_throttle
4166                        .get(&side)
4167                        .is_none_or(|throttle| now >= throttle.next_ping_ms)
4168            };
4169            if !due {
4170                continue;
4171            }
4172            let pkt = discovery::build_discovery_announce(sender.as_ref(), now, &[])?;
4173            self.state.lock().push_tx(RelayTxItem {
4174                src: None,
4175                dst: side,
4176                data: RelayItem::Packet(Arc::new(pkt)),
4177                priority: 255,
4178                retry_after_ms: 0,
4179            })?;
4180            self.state
4181                .lock()
4182                .discovery_side_throttle
4183                .entry(side)
4184                .or_default()
4185                .next_ping_ms = now.saturating_add(discovery::DISCOVERY_SLOW_INTERVAL_MS);
4186        }
4187        Ok(())
4188    }
4189
4190    #[cfg(feature = "discovery")]
4191    fn queue_discovery_announce_on_side(
4192        &self,
4193        include_schema: bool,
4194        skip_in_flight_sides: bool,
4195        requested_side: Option<RelaySideId>,
4196    ) -> TelemetryResult<()> {
4197        #[cfg(not(feature = "std"))]
4198        let _ = include_schema;
4199        if let Some(side) = requested_side {
4200            return self.queue_discovery_announce_one_side(
4201                include_schema,
4202                skip_in_flight_sides,
4203                Some(side),
4204                true,
4205            );
4206        }
4207        let count = self.state.lock().sides.len();
4208        let mut advertised = false;
4209        for side in 0..count {
4210            if self
4211                .state
4212                .lock()
4213                .sides
4214                .get(side)
4215                .is_some_and(|side| side.is_some())
4216            {
4217                self.queue_discovery_announce_one_side(
4218                    include_schema,
4219                    skip_in_flight_sides,
4220                    Some(side),
4221                    false,
4222                )?;
4223                advertised = true;
4224            }
4225        }
4226        // The shared cadence advances once per announcement, regardless of
4227        // side count. Advancing it once per side silently doubled backoff.
4228        if advertised {
4229            self.state
4230                .lock()
4231                .discovery_cadence
4232                .on_announce_sent(self.clock.now_ms());
4233        }
4234        Ok(())
4235    }
4236
4237    #[cfg(feature = "discovery")]
4238    fn queue_discovery_announce_one_side(
4239        &self,
4240        include_schema: bool,
4241        skip_in_flight_sides: bool,
4242        requested_side: Option<RelaySideId>,
4243        advance_cadence: bool,
4244    ) -> TelemetryResult<()> {
4245        let (scratch, largest) = {
4246            let now = self.clock.now_ms();
4247            let sender = self.sender_arc();
4248            let st = self.state.lock();
4249            discovery::topology_snapshot_scratch(
4250                sender.as_ref(),
4251                0,
4252                0,
4253                4,
4254                true,
4255                st.discovery_routes
4256                    .iter()
4257                    .filter(|(side, route)| {
4258                        Some(**side) != requested_side
4259                            && now.saturating_sub(route.last_seen_ms) <= DISCOVERY_ROUTE_TTL_MS
4260                    })
4261                    .flat_map(|(_, route)| route.announcers.iter())
4262                    .filter(|(_, peer)| {
4263                        now.saturating_sub(peer.last_seen_ms) <= DISCOVERY_ROUTE_TTL_MS
4264                    })
4265                    .map(|(name, peer)| {
4266                        (
4267                            name.as_str(),
4268                            peer.reachable.as_slice(),
4269                            peer.reachable_timesync_sources.as_slice(),
4270                            peer.topology_boards.as_slice(),
4271                        )
4272                    }),
4273            )
4274        };
4275        // Full metadata also carries the learned topology. Its working set
4276        // must never bypass the graph estimate merely because schema is due.
4277        let (scratch, largest) = if include_schema {
4278            (scratch.max(8192), largest.max(2048))
4279        } else {
4280            (scratch, largest)
4281        };
4282        // Snapshot construction may clone several route/schema summaries.
4283        // Reclaim ordinary queued telemetry before refusing discovery scratch.
4284        // Priority >= 200 commands, ACKs and metadata stay protected; do not
4285        // evict routes or reliable receive/reassembly state.
4286        while let Err(err) = crate::memory_admission::check(scratch, largest) {
4287            if matches!(err, TelemetryError::Io("memory pressure"))
4288                && crate::memory_admission::check(512, 256).is_ok()
4289            {
4290                let mut st = self.state.lock();
4291                if st.pop_lowest_packet_queue_item(200) {
4292                    continue;
4293                }
4294            }
4295            if !include_schema && matches!(err, TelemetryError::Io("memory pressure")) {
4296                return self.queue_pressure_keepalives(requested_side);
4297            }
4298            return Err(err);
4299        }
4300        let now_ms = self.clock.now_ms();
4301        let per_side = {
4302            let mut st = self.state.lock();
4303            if Self::prune_discovery_routes_locked(&mut st, now_ms) {
4304                self.reconcile_end_to_end_acked_destinations_locked(&mut st);
4305                Self::note_discovery_topology_change_locked(&mut st, now_ms);
4306            }
4307            st.fit_discovery_budget();
4308            if !st.sides.iter().any(|side| side.is_some()) {
4309                return Ok(());
4310            }
4311            if include_schema {
4312                for (side, throttle) in st.discovery_side_throttle.iter_mut() {
4313                    if requested_side.is_none_or(|requested| requested == *side) {
4314                        throttle.has_sent_full = false;
4315                    }
4316                }
4317            }
4318            if advance_cadence {
4319                st.discovery_cadence.on_announce_sent(now_ms);
4320            }
4321            // The caller selects one side; keep its plan on the stack.
4322            let mut per_side = None;
4323            for side_id in 0..st.sides.len() {
4324                let Some(side) = st.sides[side_id].as_ref() else {
4325                    continue;
4326                };
4327                let opts = side.opts.clone();
4328                let link_local_enabled = opts.link_local_enabled;
4329                if requested_side.is_some_and(|requested| requested != side_id) {
4330                    continue;
4331                }
4332                let discovery_in_flight = skip_in_flight_sides
4333                    && st.reliable_tx.iter().any(|((pending_side, ty), tx_state)| {
4334                        *pending_side == side_id
4335                            && !tx_state.sent.is_empty()
4336                            && crate::DataType::try_from_u32(*ty)
4337                                .is_some_and(discovery::is_discovery_type)
4338                    });
4339                if !self.route_allowed_locked(
4340                    &st,
4341                    None,
4342                    Some(crate::DataType::DiscoveryAnnounce),
4343                    side_id,
4344                ) {
4345                    continue;
4346                }
4347                // Preserve neighbor liveness even if a reliable discovery
4348                // baseline is still waiting for its hop ACK.
4349                let level = if discovery_in_flight {
4350                    let throttle = st.discovery_side_throttle.entry(side_id).or_default();
4351                    if !throttle.liveness_due(now_ms) {
4352                        None
4353                    } else {
4354                        throttle.next_ping_ms =
4355                            now_ms.saturating_add(DISCOVERY_SLOW_LINK_PING_INTERVAL_MS);
4356                        Some(DiscoveryAdvertiseLevel::MinimalPing)
4357                    }
4358                } else {
4359                    Self::discovery_level_for_side_locked(&mut st, side_id, now_ms)
4360                };
4361                let Some(level) = level else {
4362                    continue;
4363                };
4364                let capabilities = opts.link_capabilities();
4365                if level == DiscoveryAdvertiseLevel::MinimalPing {
4366                    per_side = Some((
4367                        side_id,
4368                        level,
4369                        Vec::new(),
4370                        Vec::new(),
4371                        Vec::new(),
4372                        Vec::new(),
4373                        capabilities,
4374                    ));
4375                    continue;
4376                }
4377                let current_topology = self.advertised_discovery_topology_for_link_locked(
4378                    &st,
4379                    now_ms,
4380                    link_local_enabled,
4381                    Some(side_id),
4382                );
4383                let (endpoints, timesync_sources) = self.advertised_summaries_from_topology_locked(
4384                    &st,
4385                    now_ms,
4386                    link_local_enabled,
4387                    Some(side_id),
4388                    &current_topology,
4389                );
4390                let (topology, removed_topology) = match level {
4391                    DiscoveryAdvertiseLevel::Full => (current_topology.clone(), Vec::new()),
4392                    DiscoveryAdvertiseLevel::Incremental => {
4393                        let previous = &st
4394                            .discovery_side_throttle
4395                            .get(&side_id)
4396                            .expect("discovery throttle exists")
4397                            .last_topology;
4398                        discovery::topology_delta(previous, &current_topology)
4399                    }
4400                    DiscoveryAdvertiseLevel::MinimalPing => (Vec::new(), Vec::new()),
4401                };
4402                let throttle = st
4403                    .discovery_side_throttle
4404                    .get_mut(&side_id)
4405                    .expect("discovery throttle exists");
4406                if level == DiscoveryAdvertiseLevel::Full
4407                    || (level == DiscoveryAdvertiseLevel::Incremental
4408                        && throttle.incremental_retries_remaining == 0)
4409                {
4410                    throttle.last_topology = current_topology;
4411                }
4412                per_side = Some((
4413                    side_id,
4414                    level,
4415                    endpoints,
4416                    timesync_sources,
4417                    topology,
4418                    removed_topology,
4419                    capabilities,
4420                ));
4421            }
4422            per_side
4423        };
4424        let mut st = self.state.lock();
4425        if let Some((
4426            dst,
4427            level,
4428            endpoints,
4429            timesync_sources,
4430            topology,
4431            removed_topology,
4432            capabilities,
4433        )) = per_side
4434        {
4435            let sender = self.sender_arc();
4436            if level == DiscoveryAdvertiseLevel::Full {
4437                let pkt = discovery::build_discovery_link_capabilities(
4438                    sender.as_ref(),
4439                    now_ms,
4440                    capabilities,
4441                )?;
4442                let data = RelayItem::Packet(Arc::new(pkt));
4443                let priority = Self::relay_item_priority(&data)?;
4444                st.push_tx(RelayTxItem {
4445                    src: None,
4446                    dst,
4447                    data,
4448                    priority,
4449                    retry_after_ms: 0,
4450                })?;
4451            }
4452            if level != DiscoveryAdvertiseLevel::Full || !endpoints.is_empty() {
4453                let pkt = discovery::build_discovery_announce(
4454                    sender.as_ref(),
4455                    now_ms,
4456                    endpoints.as_slice(),
4457                )?;
4458                let data = RelayItem::Packet(Arc::new(pkt));
4459                let priority = Self::relay_item_priority(&data)?;
4460                st.push_tx(RelayTxItem {
4461                    src: None,
4462                    dst,
4463                    data,
4464                    priority,
4465                    retry_after_ms: 0,
4466                })?;
4467            }
4468            if level != DiscoveryAdvertiseLevel::MinimalPing && !timesync_sources.is_empty() {
4469                let pkt = discovery::build_discovery_timesync_sources(
4470                    sender.as_ref(),
4471                    now_ms,
4472                    timesync_sources.as_slice(),
4473                )?;
4474                let data = RelayItem::Packet(Arc::new(pkt));
4475                let priority = Self::relay_item_priority(&data)?;
4476                st.push_tx(RelayTxItem {
4477                    src: None,
4478                    dst,
4479                    data,
4480                    priority,
4481                    retry_after_ms: 0,
4482                })?;
4483            }
4484            if level != DiscoveryAdvertiseLevel::MinimalPing
4485                && (!topology.is_empty() || !removed_topology.is_empty())
4486            {
4487                let pkt = if level == DiscoveryAdvertiseLevel::Full {
4488                    discovery::build_discovery_topology(sender.as_ref(), now_ms, &topology)?
4489                } else {
4490                    discovery::build_discovery_topology_delta(
4491                        sender.as_ref(),
4492                        now_ms,
4493                        &topology,
4494                        &removed_topology,
4495                    )?
4496                };
4497                let data = RelayItem::Packet(Arc::new(pkt));
4498                let priority = Self::relay_item_priority(&data)?;
4499                st.push_tx(RelayTxItem {
4500                    src: None,
4501                    dst,
4502                    data,
4503                    priority,
4504                    retry_after_ms: 0,
4505                })?;
4506            }
4507            // Match the router's phase ordering: route information first,
4508            // then release snapshot buffers before constructing schema.
4509            drop((endpoints, timesync_sources, topology, removed_topology));
4510            if include_schema && level == DiscoveryAdvertiseLevel::Full {
4511                let pkt = discovery::build_discovery_schema(sender.as_ref(), now_ms)?;
4512                let data = RelayItem::Packet(Arc::new(pkt));
4513                let priority = Self::relay_item_priority(&data)?;
4514                st.push_tx(RelayTxItem {
4515                    src: None,
4516                    dst,
4517                    data,
4518                    priority,
4519                    retry_after_ms: 0,
4520                })?;
4521            }
4522        }
4523        Ok(())
4524    }
4525
4526    #[cfg(feature = "discovery")]
4527    fn note_schema_received(&self, side: RelaySideId, sender: &str) {
4528        let mut st = self.state.lock();
4529        // Schema snapshots can be forwarded. They must never introduce a new
4530        // link-local announcer or change shared-bus reliability semantics.
4531        // If schema precedes topology, bounded recovery will request it again.
4532        let address = sender
4533            .strip_prefix("@addr:")
4534            .and_then(|value| value.parse::<u32>().ok());
4535        if let Some(route) = st.discovery_routes.get_mut(&side) {
4536            for (name, peer) in &mut route.announcers {
4537                if name == sender
4538                    || address.is_some_and(|address| {
4539                        peer.advertised_address == Some(address)
4540                            || sender_address_u32(name) == address
4541                    })
4542                {
4543                    peer.has_schema = true;
4544                    break;
4545                }
4546            }
4547        }
4548    }
4549
4550    #[cfg(feature = "discovery")]
4551    fn poll_discovery_announce(&self) -> TelemetryResult<bool> {
4552        let now_ms = self.clock.now_ms();
4553        let recovering = {
4554            let mut st = self.state.lock();
4555            let mut recovering = false;
4556            for side in 0..st.sides.len() {
4557                let missing = st.discovery_routes.get(&side).is_some_and(|route| {
4558                    route.announcers.iter().any(|(name, peer)| {
4559                        !peer.has_full_topology
4560                            && now_ms.saturating_sub(peer.last_seen_ms) < DISCOVERY_ROUTE_TTL_MS
4561                            && !route.announcers.values().any(|bridge| {
4562                                bridge.has_full_topology
4563                                    && now_ms.saturating_sub(bridge.last_seen_ms)
4564                                        < DISCOVERY_ROUTE_TTL_MS
4565                                    && bridge
4566                                        .topology_boards
4567                                        .iter()
4568                                        .any(|board| board.sender_id == *name)
4569                            })
4570                    })
4571                });
4572                if !missing {
4573                    continue;
4574                }
4575                let throttle = st.discovery_side_throttle.entry(side).or_default();
4576                if throttle.next_topology_request_ms == 0 {
4577                    throttle.next_topology_request_ms =
4578                        now_ms.saturating_add(discovery::DISCOVERY_BASELINE_GRACE_MS);
4579                    continue;
4580                }
4581                if now_ms < throttle.next_topology_request_ms {
4582                    continue;
4583                }
4584                throttle.next_topology_request_ms =
4585                    now_ms.saturating_add(discovery::DISCOVERY_BASELINE_RETRY_MS);
4586                let pkt = discovery::build_discovery_topology_request(
4587                    self.sender_arc().as_ref(),
4588                    now_ms,
4589                )?;
4590                let data = RelayItem::Packet(Arc::new(pkt));
4591                let priority = Self::relay_item_priority(&data)?;
4592                st.push_tx(RelayTxItem {
4593                    src: None,
4594                    dst: side,
4595                    data,
4596                    priority,
4597                    retry_after_ms: 0,
4598                })?;
4599                recovering = true;
4600            }
4601            recovering
4602        };
4603        let schema_recovery_sides = {
4604            let mut st = self.state.lock();
4605            let mut due = Vec::new();
4606            for side in 0..st.sides.len() {
4607                let missing = st.discovery_routes.get(&side).is_some_and(|route| {
4608                    route.announcers.values().any(|peer| {
4609                        peer.has_full_topology
4610                            && !peer.has_schema
4611                            && now_ms.saturating_sub(peer.last_seen_ms) < DISCOVERY_ROUTE_TTL_MS
4612                    })
4613                });
4614                if !missing {
4615                    continue;
4616                }
4617                let throttle = st.discovery_side_throttle.entry(side).or_default();
4618                if throttle.next_schema_request_ms == 0 {
4619                    throttle.next_schema_request_ms =
4620                        now_ms.saturating_add(discovery::DISCOVERY_BASELINE_GRACE_MS);
4621                } else if now_ms >= throttle.next_schema_request_ms {
4622                    throttle.next_schema_request_ms =
4623                        now_ms.saturating_add(discovery::DISCOVERY_BASELINE_RETRY_MS);
4624                    due.push(side);
4625                }
4626            }
4627            due
4628        };
4629        let recovering = recovering || !schema_recovery_sides.is_empty();
4630        for side in schema_recovery_sides {
4631            let pkt =
4632                discovery::build_discovery_schema_request(self.sender_arc().as_ref(), now_ms)?;
4633            let data = RelayItem::Packet(Arc::new(pkt));
4634            let priority = Self::relay_item_priority(&data)?;
4635            self.state.lock().push_tx(RelayTxItem {
4636                src: None,
4637                dst: side,
4638                data,
4639                priority,
4640                retry_after_ms: 0,
4641            })?;
4642        }
4643        let due = {
4644            let mut st = self.state.lock();
4645            let removed = Self::prune_discovery_routes_locked(&mut st, now_ms);
4646            if removed {
4647                self.reconcile_end_to_end_acked_destinations_locked(&mut st);
4648                Self::note_discovery_topology_change_locked(&mut st, now_ms);
4649            }
4650            st.fit_discovery_budget();
4651            let has_any = st.sides.iter().enumerate().any(|(side_id, side)| {
4652                let Some(side) = side.as_ref() else {
4653                    return false;
4654                };
4655                if !self.route_allowed_locked(
4656                    &st,
4657                    None,
4658                    Some(crate::DataType::DiscoveryAnnounce),
4659                    side_id,
4660                ) {
4661                    return false;
4662                }
4663                let _ = side;
4664                let in_flight = st.reliable_tx.iter().any(|((pending_side, ty), tx_state)| {
4665                    *pending_side == side_id
4666                        && !tx_state.sent.is_empty()
4667                        && crate::DataType::try_from_u32(*ty)
4668                            .is_some_and(discovery::is_discovery_type)
4669                });
4670                st.discovery_side_throttle
4671                    .get(&side_id)
4672                    .is_none_or(|throttle| {
4673                        (!in_flight && (!throttle.has_sent_full || throttle.pending_incremental))
4674                            || throttle.liveness_due(now_ms)
4675                    })
4676            });
4677            if !st.sides.iter().any(|side| side.is_some()) || !has_any {
4678                return Ok(recovering);
4679            }
4680            st.discovery_cadence.due(now_ms)
4681        };
4682        if !due {
4683            return Ok(recovering);
4684        }
4685        // Keep periodic discovery lightweight so a hosted relay cannot fill a
4686        // constrained link with repeated schema snapshots.
4687        self.queue_discovery_announce(false, true)?;
4688        Ok(true)
4689    }
4690
4691    #[cfg(feature = "discovery")]
4692    fn learn_discovery_item(&self, src: RelaySideId, data: &RelayItem) -> TelemetryResult<()> {
4693        if let RelayItem::Packed(bytes) = data
4694            && let Some((_, payload)) =
4695                wire_format::peek_plain_discovery_payload(bytes, crate::DataType::DiscoveryAddress)?
4696            && self.refresh_known_address_payload(payload, src)?
4697        {
4698            // The existing owned frame still reaches remote_side_plan below;
4699            // only its redundant local summary decode/clone is skipped.
4700            return Ok(());
4701        }
4702        let pkt = match data {
4703            RelayItem::Packet(pkt) => {
4704                if !discovery::is_discovery_type(pkt.data_type()) {
4705                    return Ok(());
4706                }
4707                pkt.as_ref().clone()
4708            }
4709            RelayItem::Packed(bytes) => {
4710                let env = wire_format::peek_routing_envelope(bytes.as_ref())?;
4711                if !discovery::is_discovery_type(env.ty) {
4712                    return Ok(());
4713                }
4714                if wire_format::peek_routing_frame_info(bytes.as_ref())
4715                    .ok()
4716                    .is_some_and(|frame| frame.ack_only())
4717                {
4718                    return Ok(());
4719                }
4720                wire_format::unpack_packet(bytes.as_ref())?
4721            }
4722        };
4723
4724        #[cfg(feature = "compression")]
4725        self.note_payload_codec_packet(src, &pkt, false);
4726        if self.refresh_known_discovery_liveness(&pkt, src)? {
4727            return Ok(());
4728        }
4729        #[cfg(not(feature = "std"))]
4730        if pkt.data_type() == crate::DataType::DiscoverySchema {
4731            self.merge_schema_changes(pkt.payload())?;
4732            self.note_schema_received(src, pkt.sender());
4733            return Ok(());
4734        }
4735        // A compact address summary does not clone the complete topology or
4736        // schema. Account for its ingress-side aggregates instead; charging
4737        // every new peer for a full snapshot can prevent restart recovery.
4738        if matches!(
4739            pkt.data_type(),
4740            crate::DataType::DiscoveryTopologyRequest | crate::DataType::DiscoverySchemaRequest
4741        ) {
4742            crate::memory_admission::check(1024, 256)?;
4743            // A restarted receiver has no compact-header dictionary even if
4744            // discovery still has exactly the same routes. Refresh TX only on
4745            // its ingress link; do not erase RX templates or routing state.
4746            if let Some(transport) = self.state.lock().side_transport.get_mut(&src) {
4747                transport.refresh_transmit_header_templates();
4748            }
4749            // Like routers, relays retain a split-horizon baseline and must
4750            // answer a restarting neighbor without waiting for master election.
4751            {
4752                let mut st = self.state.lock();
4753                let throttle = st.discovery_side_throttle.entry(src).or_default();
4754                throttle.has_sent_full = false;
4755                // A full reply replaces this derived delta baseline. Release
4756                // it before snapshot admission, preserving live routing state.
4757                throttle.last_topology = Vec::new();
4758            }
4759            return self.queue_discovery_announce_on_side(
4760                pkt.data_type() == crate::DataType::DiscoverySchemaRequest,
4761                false,
4762                Some(src),
4763            );
4764        }
4765        // Standalone relays forward variable requests; they do not own a
4766        // subscription registry or decode a schema for this four-byte payload.
4767        if pkt.data_type() == crate::DataType::ManagedVariableRequest {
4768            discovery::decode_managed_variable_request(&pkt)?;
4769            return Ok(());
4770        }
4771        let small_topology_scratch = if pkt.data_type() == crate::DataType::DiscoveryTopology
4772            && pkt.payload().len() <= 128
4773        {
4774            let st = self.state.lock();
4775            let sender = pkt
4776                .sender()
4777                .strip_prefix("@addr:")
4778                .and_then(|value| value.parse::<u32>().ok())
4779                .and_then(|address| Self::hostname_for_wire_address_borrowed_locked(&st, address))
4780                .unwrap_or(pkt.sender());
4781            st.discovery_routes.get(&src).and_then(|route| {
4782                let peer = route.announcers.get(sender)?;
4783                let mut shallow = discovery::CloneScratch::default();
4784                shallow.buffer(route.reachable.len().saturating_mul(4));
4785                shallow.strings(&route.reachable_timesync_sources);
4786                for (name, value) in &route.announcers {
4787                    shallow.buffer(name.len());
4788                    shallow.buffer(value.reachable.len().saturating_mul(4));
4789                    shallow.buffer(value.advertised_reachable.len().saturating_mul(4));
4790                    shallow.strings(&value.reachable_timesync_sources);
4791                    shallow.strings(&value.advertised_reachable_timesync_sources);
4792                }
4793                let entry =
4794                    core::mem::size_of::<String>() + core::mem::size_of::<DiscoverySenderState>();
4795                shallow.bytes = shallow
4796                    .bytes
4797                    .saturating_add(route.announcers.len().saturating_mul(entry));
4798                shallow.largest = shallow
4799                    .largest
4800                    .max(entry.saturating_mul(11).saturating_add(128));
4801                let mut deep = discovery::CloneScratch::default();
4802                if discovery::topology_payload_is_incremental(pkt.payload()) {
4803                    deep.graph(&peer.topology_boards, pkt.payload().len() / 16);
4804                }
4805                Some((
4806                    2048usize
4807                        .saturating_add(shallow.bytes.saturating_mul(3))
4808                        .saturating_add(deep.bytes)
4809                        .saturating_add(pkt.payload().len().saturating_mul(4)),
4810                    shallow.largest.max(deep.largest).max(2048),
4811                ))
4812            })
4813        } else {
4814            None
4815        };
4816        let retained_summary = if small_topology_scratch.is_none()
4817            && matches!(
4818                pkt.data_type(),
4819                crate::DataType::DiscoveryAddress
4820                    | crate::DataType::DiscoveryTopology
4821                    | crate::DataType::DiscoveryAnnounce
4822                    | crate::DataType::DiscoveryTimeSyncSources
4823                    | crate::DataType::ManagedVariableRequest
4824            ) {
4825            self.state
4826                .lock()
4827                .discovery_routes
4828                .get(&src)
4829                .map_or(0, RelayInner::discovery_route_byte_cost)
4830        } else {
4831            0
4832        };
4833        let (decode_bytes, largest_block) = if let Some(scratch) = small_topology_scratch {
4834            scratch
4835        } else {
4836            let scratch = if pkt.data_type() == crate::DataType::DiscoveryAddress {
4837                4096usize.saturating_add(retained_summary)
4838            } else {
4839                8192usize.saturating_add(retained_summary)
4840            };
4841            let decode_bytes = pkt
4842                .payload()
4843                .len()
4844                .saturating_mul(4)
4845                .saturating_add(scratch);
4846            let largest_block = pkt.payload().len().saturating_mul(2).max(2048);
4847            (decode_bytes, largest_block)
4848        };
4849        while let Err(err) = crate::memory_admission::check(decode_bytes, largest_block) {
4850            if !matches!(err, TelemetryError::Io("memory pressure")) {
4851                return Err(err);
4852            }
4853            let mut st = self.state.lock();
4854            #[cfg(feature = "compression")]
4855            if st.reclaim_codec_metadata() {
4856                continue;
4857            }
4858
4859            if !st.pop_lowest_packet_queue_item(256) {
4860                return Err(err);
4861            }
4862        }
4863        let now_ms = self.clock.now_ms();
4864
4865        #[cfg(feature = "std")]
4866        if pkt.data_type() == crate::DataType::DiscoverySchema {
4867            #[cfg(feature = "compression")]
4868            let snapshot =
4869                self.decode_required_schema(|| discovery::decode_discovery_schema(&pkt))?;
4870            #[cfg(not(feature = "compression"))]
4871            let snapshot = discovery::decode_discovery_schema(&pkt)?;
4872            let mut st = self.state.lock();
4873            let budget = st.schema_growth_budget();
4874            let report = crate::config::merge_owned_schema_snapshot_with_budget(snapshot, budget)?;
4875            if report.changed() {
4876                st.fit_discovery_budget();
4877                Self::note_discovery_topology_change_locked(&mut st, now_ms);
4878            }
4879            drop(st);
4880            self.note_schema_received(src, pkt.sender());
4881            return Ok(());
4882        }
4883        if pkt.data_type() == crate::DataType::DiscoveryLinkCapabilities {
4884            let _ = discovery::decode_discovery_link_capabilities(&pkt)?;
4885            #[cfg(feature = "compression")]
4886            self.note_payload_codec_packet(src, &pkt, true);
4887            return Ok(());
4888        }
4889        let mut st = self.state.lock();
4890        if pkt.data_type() == crate::DataType::DiscoveryLeave {
4891            let leaving = pkt.sender();
4892            let before = st.discovery_routes.clone();
4893            for route in st.discovery_routes.values_mut() {
4894                route.announcers.remove(leaving);
4895                for sender_state in route.announcers.values_mut() {
4896                    Arc::make_mut(&mut sender_state.topology_boards)
4897                        .retain(|board| board.sender_id != leaving);
4898                    for board in Arc::make_mut(&mut sender_state.topology_boards).iter_mut() {
4899                        board.connections.retain(|peer| peer != leaving);
4900                    }
4901                    Self::refresh_sender_topology_state(sender_state);
4902                }
4903                Self::recompute_discovery_side_state(route);
4904            }
4905            st.discovery_routes
4906                .retain(|_, route| !route.announcers.is_empty());
4907            if st.discovery_routes != before {
4908                Self::note_discovery_topology_change_locked(&mut st, now_ms);
4909            }
4910            let _ = Self::prune_discovery_routes_locked(&mut st, now_ms);
4911            self.reconcile_end_to_end_acked_destinations_locked(&mut st);
4912            #[cfg(feature = "compression")]
4913            {
4914                drop(st);
4915                self.note_payload_codec_packet(src, &pkt, true);
4916            }
4917            return Ok(());
4918        }
4919        let address_ad = if pkt.data_type() == crate::DataType::DiscoveryAddress {
4920            Some(discovery::decode_discovery_address(&pkt)?)
4921        } else {
4922            None
4923        };
4924        let mut topology_ad = if pkt.data_type() == crate::DataType::DiscoveryTopology {
4925            Some(discovery::decode_discovery_topology_update(&pkt)?)
4926        } else {
4927            None
4928        };
4929        let announcer_id = if let Some(ad) = address_ad.as_ref() {
4930            ad.hostname.clone()
4931        } else {
4932            let canonical = Self::canonical_sender_locked(&st, pkt.sender());
4933            if canonical != pkt.sender() {
4934                canonical
4935            } else if let Some(address) = pkt
4936                .sender()
4937                .strip_prefix("@addr:")
4938                .and_then(|value| value.parse::<u32>().ok())
4939            {
4940                topology_ad
4941                    .as_ref()
4942                    .and_then(|update| {
4943                        update
4944                            .boards
4945                            .iter()
4946                            .find(|board| sender_address_u32(&board.sender_id) == address)
4947                    })
4948                    .map(|board| board.sender_id.clone())
4949                    .unwrap_or(canonical)
4950            } else {
4951                canonical
4952            }
4953        };
4954        // Parse fallible payloads and resolve aliases before moving retained state.
4955        let announced = if pkt.data_type() == crate::DataType::DiscoveryAnnounce {
4956            Some(discovery::decode_discovery_announce(&pkt)?)
4957        } else {
4958            None
4959        };
4960        let timesync_sources = if pkt.data_type() == crate::DataType::DiscoveryTimeSyncSources {
4961            Some(discovery::decode_discovery_timesync_sources(&pkt)?)
4962        } else {
4963            None
4964        };
4965        if let Some(update) = topology_ad.as_mut() {
4966            for board in &mut update.boards {
4967                board.sender_id = Self::canonical_sender_locked(&st, &board.sender_id);
4968                for peer in &mut board.connections {
4969                    *peer = Self::canonical_sender_locked(&st, peer);
4970                }
4971            }
4972        }
4973        let mut route = st.discovery_routes.get(&src).cloned().unwrap_or_default();
4974        if pkt.sender() != announcer_id {
4975            route.announcers.remove(pkt.sender());
4976        }
4977        let side_link_local_enabled = st
4978            .sides
4979            .get(src)
4980            .and_then(|entry| entry.as_ref())
4981            .map(|side_ref| side_ref.opts.link_local_enabled)
4982            .unwrap_or(false);
4983        let mut sender_state =
4984            core::mem::take(route.announcers.entry(announcer_id.clone()).or_default());
4985        let changed = match pkt.data_type() {
4986            crate::DataType::DiscoveryAddress => {
4987                let ad = address_ad.expect("decoded above");
4988                let advertised_address = ad.address;
4989                let mut reachable = ad.reachable_endpoints;
4990                if !side_link_local_enabled {
4991                    reachable.retain(|ep| !ep.is_link_local_only());
4992                }
4993                // This packet is an aggregate route summary, not a claim that
4994                // the announcing bridge owns every endpoint behind it.
4995                let changed = sender_state.advertised_address != Some(advertised_address)
4996                    || sender_state.advertised_reachable != reachable
4997                    || sender_state.advertised_reachable_timesync_sources
4998                        != ad.reachable_timesync_sources;
4999                sender_state.advertised_address = Some(advertised_address);
5000                sender_state.advertised_reachable = reachable;
5001                sender_state.advertised_reachable_timesync_sources = ad.reachable_timesync_sources;
5002                Self::refresh_sender_topology_state(&mut sender_state);
5003                changed
5004            }
5005            crate::DataType::DiscoveryAnnounce => {
5006                let mut reachable = announced.expect("parsed before moving route state");
5007                // Empty announcements are slow-link keepalives, not endpoint
5008                // withdrawals. Preserve ownership until an explicit topology
5009                // update, leave, or expiry removes it. The common receive tail
5010                // still refreshes last_seen_ms without scheduling rediscovery.
5011                if reachable.is_empty() {
5012                    false
5013                } else {
5014                    if !side_link_local_enabled {
5015                        reachable.retain(|ep| !ep.is_link_local_only());
5016                    }
5017                    let board = Self::sender_topology_board_mut(&mut sender_state, &announcer_id);
5018                    let changed = board.reachable_endpoints != reachable;
5019                    board.reachable_endpoints = reachable;
5020                    Self::refresh_sender_topology_state(&mut sender_state);
5021                    changed
5022                }
5023            }
5024            crate::DataType::DiscoveryTimeSyncSources => {
5025                let sources = timesync_sources.expect("parsed before moving route state");
5026                let board = Self::sender_topology_board_mut(&mut sender_state, &announcer_id);
5027                let changed = board.reachable_timesync_sources != sources;
5028                board.reachable_timesync_sources = sources;
5029                Self::refresh_sender_topology_state(&mut sender_state);
5030                changed
5031            }
5032            crate::DataType::DiscoveryTopology => {
5033                let mut update = topology_ad
5034                    .take()
5035                    .expect("topology packet was decoded before route selection");
5036                if !side_link_local_enabled {
5037                    for board in update.boards.iter_mut() {
5038                        board
5039                            .reachable_endpoints
5040                            .retain(|ep| !ep.is_link_local_only());
5041                    }
5042                }
5043                if !update.incremental {
5044                    sender_state.has_full_topology = true;
5045                }
5046                let changed = discovery::apply_shared_topology_update(
5047                    &mut sender_state.topology_boards,
5048                    update,
5049                );
5050                Self::refresh_sender_topology_state(&mut sender_state);
5051                changed
5052            }
5053            crate::DataType::DiscoverySchema => false,
5054            _ => false,
5055        };
5056        sender_state.last_seen_ms = now_ms;
5057        route.announcers.insert(announcer_id, sender_state);
5058        Self::recompute_discovery_side_state(&mut route);
5059        st.commit_discovery_route(src, route)?;
5060        if changed {
5061            Self::note_discovery_topology_change_locked(&mut st, now_ms);
5062        }
5063        let _ = Self::prune_discovery_routes_locked(&mut st, now_ms);
5064        self.reconcile_end_to_end_acked_destinations_locked(&mut st);
5065        #[cfg(feature = "compression")]
5066        {
5067            drop(st);
5068            self.note_payload_codec_packet(src, &pkt, true);
5069        }
5070        Ok(())
5071    }
5072
5073    #[cfg(not(feature = "discovery"))]
5074    fn learn_discovery_item(&self, _src: RelaySideId, _data: &RelayItem) -> TelemetryResult<()> {
5075        Ok(())
5076    }
5077
5078    fn process_reliable_timeouts(&self) -> TelemetryResult<()> {
5079        let now = self.clock.now_ms();
5080        let mut requeue: Vec<(RelaySideId, crate::DataType, u32)> = Vec::new();
5081
5082        {
5083            let mut st = self.state.lock();
5084            if st.reliable_tx.is_empty() {
5085                return Ok(());
5086            }
5087
5088            for ((side, ty_u32), tx_state) in st.reliable_tx.iter_mut() {
5089                let Some(ty) = crate::DataType::try_from_u32(*ty_u32) else {
5090                    continue;
5091                };
5092                let sent_order: Vec<u32> = tx_state.sent_order.iter().copied().collect();
5093                for seq in sent_order {
5094                    let Some(sent) = tx_state.sent.get_mut(&seq) else {
5095                        continue;
5096                    };
5097                    if sent.queued
5098                        || now.wrapping_sub(sent.last_send_ms) < runtime_reliable_retransmit_ms()
5099                    {
5100                        continue;
5101                    }
5102                    if sent.partial_acked {
5103                        // Do not let a lost packet-request pin a partially
5104                        // acknowledged entry forever. Allow one interval for
5105                        // the explicit request, then return to bounded replay.
5106                        sent.partial_acked = false;
5107                        sent.last_send_ms = now;
5108                        continue;
5109                    }
5110                    if sent.retries >= runtime_reliable_max_retries() {
5111                        tx_state.sent.remove(&seq);
5112                        tx_state.sent_order.retain(|existing| *existing != seq);
5113                        continue;
5114                    }
5115                    sent.retries += 1;
5116                    requeue.push((*side, ty, seq));
5117                }
5118            }
5119        }
5120
5121        for (side, ty, seq) in requeue {
5122            self.queue_reliable_retransmit(side, ty, seq)?;
5123        }
5124
5125        Ok(())
5126    }
5127
5128    /// Compute a de-dupe hash for a QueueItem.
5129    /// Uses packet ID for Packet items, and attempts to extract packet ID from
5130    /// packed bytes. If extraction fails, hashes raw bytes as a fallback.
5131    fn get_hash(item: &RelayRxItem) -> u64 {
5132        match &item.data {
5133            RelayItem::Packet(pkt) => pkt.packet_id(),
5134            RelayItem::Packed(bytes) => {
5135                let reliable_seq = wire_format::peek_routing_frame_info(bytes.as_ref())
5136                    .ok()
5137                    .and_then(|frame| frame.reliable)
5138                    .and_then(|hdr| {
5139                        if (hdr.flags & wire_format::RELIABLE_FLAG_ACK_ONLY) != 0 {
5140                            None
5141                        } else {
5142                            Some(hdr.seq)
5143                        }
5144                    });
5145
5146                match wire_format::packet_id_from_wire(bytes.as_ref()) {
5147                    Ok(id) => {
5148                        if let Some(seq) = reliable_seq {
5149                            hash_bytes_u64(id, &seq.to_le_bytes())
5150                        } else {
5151                            id
5152                        }
5153                    }
5154                    Err(_e) => {
5155                        // Fallback: if bytes are malformed (or compression feature mismatch),
5156                        // hash raw bytes so we can still dedupe identical network duplicates.
5157                        let h: u64 = 0x9E37_79B9_7F4A_7C15;
5158                        hash_bytes_u64(h, bytes.as_ref())
5159                    }
5160                }
5161            }
5162        }
5163    }
5164
5165    /// Compute a dedupe ID for an incoming RelayRxItem.
5166    /// Note: we intentionally do *not* include `src` so that the same
5167    /// packet coming from multiple sides is only processed once.
5168    fn is_duplicate_pkt(&self, item: &RelayRxItem) -> TelemetryResult<bool> {
5169        let id = Self::get_hash(item);
5170
5171        let mut st = self.state.lock();
5172        if st.recent_rx.contains(&id) {
5173            Ok(true)
5174        } else {
5175            st.push_recent_rx(id)?;
5176            Ok(false)
5177        }
5178    }
5179
5180    fn should_forward_duplicate_reliable_item(&self, item: &RelayRxItem) -> TelemetryResult<bool> {
5181        let (ty, addressed) = match &item.data {
5182            RelayItem::Packet(pkt) => (pkt.data_type(), !pkt.wire_target_senders().is_empty()),
5183            RelayItem::Packed(bytes) => {
5184                let env = wire_format::peek_routing_envelope(bytes)?;
5185                (env.ty, env.target_senders().len() != 0)
5186            }
5187        };
5188        if (!is_reliable_type(ty) && !addressed)
5189            || matches!(
5190                ty,
5191                crate::DataType::ReliableAck
5192                    | crate::DataType::ReliablePartialAck
5193                    | crate::DataType::ReliablePacketRequest
5194            )
5195        {
5196            return Ok(false);
5197        }
5198
5199        if addressed {
5200            let id = match &item.data {
5201                RelayItem::Packet(pkt) => pkt.packet_id(),
5202                RelayItem::Packed(bytes) => wire_format::packet_id_from_wire(bytes)?,
5203            };
5204            let mut st = self.state.lock();
5205            return Ok(st.reliable_return_routes.get_mut(&id).is_some_and(|route| {
5206                let now = self.clock.now_ms();
5207                if now.saturating_sub(route.last_replay_ms) < runtime_reliable_retransmit_ms() {
5208                    return false;
5209                }
5210                route.last_replay_ms = now;
5211                true
5212            }));
5213        }
5214
5215        let RemoteSidePlan::Target(sides) = self.remote_side_plan(&item.data, item.src)?;
5216        let st = self.state.lock();
5217        let now_ms = self.clock.now_ms();
5218        Ok(sides
5219            .into_iter()
5220            .any(|side| self.side_has_multiple_announcers_locked(&st, side, now_ms)))
5221    }
5222
5223    /// Register a side whose TX callback consumes packed packet bytes.
5224    ///
5225    /// Returns the side id later used for ingress APIs such as `rx_packed_from_side`.
5226    /// The default options disable the relay's per-link reliable framing on this side.
5227    pub fn add_side_packed<N, F>(&self, name: N, tx: F) -> RelaySideId
5228    where
5229        N: AsRef<str>,
5230        F: Fn(&[u8]) -> TelemetryResult<()> + Send + Sync + 'static,
5231    {
5232        self.add_side_packed_with_options(name, tx, RelaySideOptions::default())
5233    }
5234
5235    /// Register a packed side with bounded-frame transport enabled.
5236    ///
5237    /// `max_frame_bytes == 0` leaves frames unbounded.
5238    pub fn add_side_packed_small_packets<N, F>(
5239        &self,
5240        name: N,
5241        tx: F,
5242        max_frame_bytes: usize,
5243    ) -> RelaySideId
5244    where
5245        N: AsRef<str>,
5246        F: Fn(&[u8]) -> TelemetryResult<()> + Send + Sync + 'static,
5247    {
5248        self.add_side_packed_with_options(
5249            name,
5250            tx,
5251            RelaySideOptions::default().with_small_packet_transport(max_frame_bytes),
5252        )
5253    }
5254
5255    /// Register a packed-output side with explicit side options.
5256    ///
5257    /// `opts.reliable_enabled` enables relay-managed per-hop ACK/retransmit behavior on this side.
5258    /// `opts.link_local_enabled` gates link-local-only forwarding and discovery use of this side.
5259    /// `ingress_enabled` and `egress_enabled` set the initial directional policy.
5260    pub fn add_side_packed_with_options<N, F>(
5261        &self,
5262        name: N,
5263        tx: F,
5264        opts: RelaySideOptions,
5265    ) -> RelaySideId
5266    where
5267        N: AsRef<str>,
5268        F: Fn(&[u8]) -> TelemetryResult<()> + Send + Sync + 'static,
5269    {
5270        let mut st = self.state.lock();
5271        let side = Some(RelaySide {
5272            name: Arc::from(name.as_ref()),
5273            tx_handler: RelayTxHandlerFn::Packed(Arc::new(tx)),
5274            opts,
5275        });
5276        let id = if let Some(id) = st.sides.iter().position(Option::is_none) {
5277            st.sides[id] = side;
5278            id
5279        } else {
5280            let id = st.sides.len();
5281            st.sides.push(side);
5282            id
5283        };
5284        st.side_runtime_stats
5285            .insert(id, Box::<SideRuntimeStatsInner>::default());
5286        st.side_transport
5287            .insert(id, Box::<SideTransportState>::default());
5288        #[cfg(feature = "discovery")]
5289        Self::note_discovery_topology_change_locked(&mut st, self.clock.now_ms());
5290        id
5291    }
5292
5293    /// Register a side whose TX callback receives decoded [`Packet`] values.
5294    ///
5295    /// Packet-output sides do not preserve the relay's packed reliable hop framing, so use a
5296    /// packed side when this hop should participate in relay-managed per-link reliability.
5297    pub fn add_side_packet<N, F>(&self, name: N, tx: F) -> RelaySideId
5298    where
5299        N: AsRef<str>,
5300        F: Fn(&Packet) -> TelemetryResult<()> + Send + Sync + 'static,
5301    {
5302        self.add_side_packet_with_options(name, tx, RelaySideOptions::default())
5303    }
5304
5305    /// Register a packet-output side with explicit side options.
5306    pub fn add_side_packet_with_options<N, F>(
5307        &self,
5308        name: N,
5309        tx: F,
5310        opts: RelaySideOptions,
5311    ) -> RelaySideId
5312    where
5313        N: AsRef<str>,
5314        F: Fn(&Packet) -> TelemetryResult<()> + Send + Sync + 'static,
5315    {
5316        let mut st = self.state.lock();
5317        let side = Some(RelaySide {
5318            name: Arc::from(name.as_ref()),
5319            tx_handler: RelayTxHandlerFn::Packet(Arc::new(tx)),
5320            opts,
5321        });
5322        let id = if let Some(id) = st.sides.iter().position(Option::is_none) {
5323            st.sides[id] = side;
5324            id
5325        } else {
5326            let id = st.sides.len();
5327            st.sides.push(side);
5328            id
5329        };
5330        st.side_runtime_stats
5331            .insert(id, Box::<SideRuntimeStatsInner>::default());
5332        st.side_transport
5333            .insert(id, Box::<SideTransportState>::default());
5334        #[cfg(feature = "discovery")]
5335        Self::note_discovery_topology_change_locked(&mut st, self.clock.now_ms());
5336        id
5337    }
5338
5339    /// Remove a side while keeping existing side IDs stable.
5340    ///
5341    /// `side` must be an id returned by one of the `add_side_*` calls. Remaining side ids are not
5342    /// renumbered.
5343    pub fn remove_side(&self, side: RelaySideId) -> TelemetryResult<()> {
5344        #[cfg(feature = "discovery")]
5345        let now_ms = self.clock.now_ms();
5346        let mut st = self.state.lock();
5347        #[cfg(feature = "std")]
5348        if st
5349            .side_transport
5350            .get(&side)
5351            .is_some_and(|state| state.tx_callback_gate.is_active())
5352        {
5353            return Err(TelemetryError::Io("side tx busy"));
5354        }
5355        let slot = st.sides.get_mut(side).ok_or(TelemetryError::BadArg)?;
5356        if slot.is_none() {
5357            return Err(TelemetryError::BadArg);
5358        }
5359        *slot = None;
5360        while st.sides.last().is_some_and(Option::is_none) {
5361            st.sides.pop();
5362        }
5363        // Preserve capacity for link churn. It is bounded by the maximum number
5364        // of sides seen by this relay and is reclaimed when the relay drops.
5365        st.route_overrides
5366            .retain(|(src_side, dst_side), _| *src_side != Some(side) && *dst_side != side);
5367        st.typed_route_overrides
5368            .retain(|(src_side, _, dst_side), _| *src_side != Some(side) && *dst_side != side);
5369        st.route_weights
5370            .retain(|(src_side, dst_side), _| *src_side != Some(side) && *dst_side != side);
5371        st.route_priorities
5372            .retain(|(src_side, dst_side), _| *src_side != Some(side) && *dst_side != side);
5373        st.source_route_modes.remove(&Some(side));
5374        st.route_selection_cursors.remove(&Some(side));
5375        st.adaptive_route_stats.remove(&side);
5376        #[cfg(feature = "discovery")]
5377        st.discovery_side_throttle.remove(&side);
5378        #[cfg(all(feature = "discovery", feature = "timesync"))]
5379        st.timesync_side_throttle.remove(&side);
5380        st.side_runtime_stats.remove(&side);
5381        st.reliable_return_routes
5382            .retain(|_, route| route.side != side);
5383        st.rx_queue.retain(|queued| queued.src != side);
5384        st.tx_queue
5385            .retain(|queued| queued.dst != side && queued.src != Some(side));
5386        st.retain_replays(|queued| queued.dst != side);
5387        st.reliable_tx.retain(|(side_id, _), _| *side_id != side);
5388        st.reliable_rx.retain(|(side_id, _), _| *side_id != side);
5389        #[cfg(feature = "discovery")]
5390        {
5391            st.discovery_routes.remove(&side);
5392            Self::note_discovery_topology_change_locked(&mut st, now_ms);
5393        }
5394        Ok(())
5395    }
5396
5397    /// Enable or disable ingress processing for a registered side.
5398    pub fn set_side_ingress_enabled(
5399        &self,
5400        side: RelaySideId,
5401        enabled: bool,
5402    ) -> TelemetryResult<()> {
5403        #[cfg(feature = "discovery")]
5404        let now_ms = self.clock.now_ms();
5405        let mut st = self.state.lock();
5406        let side_ref = st
5407            .sides
5408            .get_mut(side)
5409            .and_then(|side| side.as_mut())
5410            .ok_or(TelemetryError::BadArg)?;
5411        side_ref.opts.ingress_enabled = enabled;
5412        #[cfg(feature = "discovery")]
5413        Self::note_discovery_topology_change_locked(&mut st, now_ms);
5414        Ok(())
5415    }
5416
5417    /// Enable or disable egress toward a registered side.
5418    pub fn set_side_egress_enabled(&self, side: RelaySideId, enabled: bool) -> TelemetryResult<()> {
5419        #[cfg(feature = "discovery")]
5420        let now_ms = self.clock.now_ms();
5421        let mut st = self.state.lock();
5422        let side_ref = st
5423            .sides
5424            .get_mut(side)
5425            .and_then(|side| side.as_mut())
5426            .ok_or(TelemetryError::BadArg)?;
5427        side_ref.opts.egress_enabled = enabled;
5428        if !enabled {
5429            st.tx_queue.retain(|queued| queued.dst != side);
5430            st.retain_replays(|queued| queued.dst != side);
5431        }
5432        #[cfg(feature = "discovery")]
5433        Self::note_discovery_topology_change_locked(&mut st, now_ms);
5434        Ok(())
5435    }
5436
5437    /// Set the route-selection policy for traffic originating from `src`.
5438    ///
5439    /// `src == None` targets locally-originated relay traffic such as discovery output.
5440    pub fn set_source_route_mode(
5441        &self,
5442        src: Option<RelaySideId>,
5443        mode: RouteSelectionMode,
5444    ) -> TelemetryResult<()> {
5445        #[cfg(feature = "discovery")]
5446        let now_ms = self.clock.now_ms();
5447        let mut st = self.state.lock();
5448        if let Some(src) = src {
5449            let _ = Self::side_ref(&st, src).map_err(|_| TelemetryError::BadArg)?;
5450        }
5451        // Preserve explicit Fanout so discovery does not replace it with its
5452        // adaptive single-path default. Clearing is a separate operation.
5453        st.source_route_modes.insert(src, mode);
5454        st.route_selection_cursors.remove(&src);
5455        #[cfg(feature = "discovery")]
5456        Self::note_discovery_topology_change_locked(&mut st, now_ms);
5457        Ok(())
5458    }
5459
5460    /// Clear a source-specific route-selection override.
5461    pub fn clear_source_route_mode(&self, src: Option<RelaySideId>) -> TelemetryResult<()> {
5462        #[cfg(feature = "discovery")]
5463        let now_ms = self.clock.now_ms();
5464        let mut st = self.state.lock();
5465        if let Some(src) = src {
5466            let _ = Self::side_ref(&st, src).map_err(|_| TelemetryError::BadArg)?;
5467        }
5468        st.source_route_modes.remove(&src);
5469        st.route_selection_cursors.remove(&src);
5470        #[cfg(feature = "discovery")]
5471        Self::note_discovery_topology_change_locked(&mut st, now_ms);
5472        Ok(())
5473    }
5474
5475    /// Set the weighted-routing weight from `src` toward `dst`.
5476    pub fn set_route_weight(
5477        &self,
5478        src: Option<RelaySideId>,
5479        dst: RelaySideId,
5480        weight: u32,
5481    ) -> TelemetryResult<()> {
5482        #[cfg(feature = "discovery")]
5483        let now_ms = self.clock.now_ms();
5484        let mut st = self.state.lock();
5485        let _ = Self::side_ref(&st, dst).map_err(|_| TelemetryError::BadArg)?;
5486        if let Some(src) = src {
5487            let _ = Self::side_ref(&st, src).map_err(|_| TelemetryError::BadArg)?;
5488        }
5489        st.route_weights.insert((src, dst), weight);
5490        st.route_selection_cursors.remove(&src);
5491        #[cfg(feature = "discovery")]
5492        Self::note_discovery_topology_change_locked(&mut st, now_ms);
5493        Ok(())
5494    }
5495
5496    /// Clear a previously configured weighted-routing weight override.
5497    pub fn clear_route_weight(
5498        &self,
5499        src: Option<RelaySideId>,
5500        dst: RelaySideId,
5501    ) -> TelemetryResult<()> {
5502        #[cfg(feature = "discovery")]
5503        let now_ms = self.clock.now_ms();
5504        let mut st = self.state.lock();
5505        let _ = Self::side_ref(&st, dst).map_err(|_| TelemetryError::BadArg)?;
5506        if let Some(src) = src {
5507            let _ = Self::side_ref(&st, src).map_err(|_| TelemetryError::BadArg)?;
5508        }
5509        st.route_weights.remove(&(src, dst));
5510        st.route_selection_cursors.remove(&src);
5511        #[cfg(feature = "discovery")]
5512        Self::note_discovery_topology_change_locked(&mut st, now_ms);
5513        Ok(())
5514    }
5515
5516    /// Set the failover priority from `src` toward `dst`.
5517    pub fn set_route_priority(
5518        &self,
5519        src: Option<RelaySideId>,
5520        dst: RelaySideId,
5521        priority: u32,
5522    ) -> TelemetryResult<()> {
5523        #[cfg(feature = "discovery")]
5524        let now_ms = self.clock.now_ms();
5525        let mut st = self.state.lock();
5526        let _ = Self::side_ref(&st, dst).map_err(|_| TelemetryError::BadArg)?;
5527        if let Some(src) = src {
5528            let _ = Self::side_ref(&st, src).map_err(|_| TelemetryError::BadArg)?;
5529        }
5530        st.route_priorities.insert((src, dst), priority);
5531        #[cfg(feature = "discovery")]
5532        Self::note_discovery_topology_change_locked(&mut st, now_ms);
5533        Ok(())
5534    }
5535
5536    /// Clear a previously configured failover priority override.
5537    pub fn clear_route_priority(
5538        &self,
5539        src: Option<RelaySideId>,
5540        dst: RelaySideId,
5541    ) -> TelemetryResult<()> {
5542        #[cfg(feature = "discovery")]
5543        let now_ms = self.clock.now_ms();
5544        let mut st = self.state.lock();
5545        let _ = Self::side_ref(&st, dst).map_err(|_| TelemetryError::BadArg)?;
5546        if let Some(src) = src {
5547            let _ = Self::side_ref(&st, src).map_err(|_| TelemetryError::BadArg)?;
5548        }
5549        st.route_priorities.remove(&(src, dst));
5550        #[cfg(feature = "discovery")]
5551        Self::note_discovery_topology_change_locked(&mut st, now_ms);
5552        Ok(())
5553    }
5554
5555    /// Allow or block routing from `src` toward `dst`.
5556    pub fn set_route(
5557        &self,
5558        src: Option<RelaySideId>,
5559        dst: RelaySideId,
5560        enabled: bool,
5561    ) -> TelemetryResult<()> {
5562        #[cfg(feature = "discovery")]
5563        let now_ms = self.clock.now_ms();
5564        let mut st = self.state.lock();
5565        let _ = Self::side_ref(&st, dst).map_err(|_| TelemetryError::BadArg)?;
5566        if let Some(src) = src {
5567            let _ = Self::side_ref(&st, src).map_err(|_| TelemetryError::BadArg)?;
5568        }
5569        st.route_overrides.insert((src, dst), enabled);
5570        #[cfg(feature = "discovery")]
5571        Self::note_discovery_topology_change_locked(&mut st, now_ms);
5572        Ok(())
5573    }
5574
5575    /// Allow or block routing for a specific `DataType` from `src` toward `dst`.
5576    pub fn set_typed_route(
5577        &self,
5578        src: Option<RelaySideId>,
5579        ty: crate::DataType,
5580        dst: RelaySideId,
5581        enabled: bool,
5582    ) -> TelemetryResult<()> {
5583        #[cfg(feature = "discovery")]
5584        let now_ms = self.clock.now_ms();
5585        let mut st = self.state.lock();
5586        let _ = Self::side_ref(&st, dst).map_err(|_| TelemetryError::BadArg)?;
5587        if let Some(src) = src {
5588            let _ = Self::side_ref(&st, src).map_err(|_| TelemetryError::BadArg)?;
5589        }
5590        st.typed_route_overrides
5591            .insert((src, ty.as_u32(), dst), enabled);
5592        #[cfg(feature = "discovery")]
5593        Self::note_discovery_topology_change_locked(&mut st, now_ms);
5594        Ok(())
5595    }
5596
5597    /// Clear a typed route override for the `(src, ty, dst)` triple.
5598    pub fn clear_typed_route(
5599        &self,
5600        src: Option<RelaySideId>,
5601        ty: crate::DataType,
5602        dst: RelaySideId,
5603    ) -> TelemetryResult<()> {
5604        #[cfg(feature = "discovery")]
5605        let now_ms = self.clock.now_ms();
5606        let mut st = self.state.lock();
5607        let _ = Self::side_ref(&st, dst).map_err(|_| TelemetryError::BadArg)?;
5608        if let Some(src) = src {
5609            let _ = Self::side_ref(&st, src).map_err(|_| TelemetryError::BadArg)?;
5610        }
5611        st.typed_route_overrides.remove(&(src, ty.as_u32(), dst));
5612        #[cfg(feature = "discovery")]
5613        Self::note_discovery_topology_change_locked(&mut st, now_ms);
5614        Ok(())
5615    }
5616
5617    /// Clear a non-typed route override so the relay falls back to default behavior.
5618    pub fn clear_route(&self, src: Option<RelaySideId>, dst: RelaySideId) -> TelemetryResult<()> {
5619        #[cfg(feature = "discovery")]
5620        let now_ms = self.clock.now_ms();
5621        let mut st = self.state.lock();
5622        let _ = Self::side_ref(&st, dst).map_err(|_| TelemetryError::BadArg)?;
5623        if let Some(src) = src {
5624            let _ = Self::side_ref(&st, src).map_err(|_| TelemetryError::BadArg)?;
5625        }
5626        st.route_overrides.remove(&(src, dst));
5627        #[cfg(feature = "discovery")]
5628        Self::note_discovery_topology_change_locked(&mut st, now_ms);
5629        Ok(())
5630    }
5631
5632    #[cfg(feature = "discovery")]
5633    /// Queues an immediate discovery announcement for this relay.
5634    pub fn announce_discovery(&self) -> TelemetryResult<()> {
5635        self.queue_discovery_announce(true, false)
5636    }
5637
5638    #[cfg(feature = "discovery")]
5639    /// Broadcast that this relay is leaving so peers can prune topology immediately.
5640    pub fn announce_leave(&self) -> TelemetryResult<()> {
5641        let pkt = discovery::build_discovery_leave("relay", self.clock.now_ms())?;
5642        let mut st = self.state.lock();
5643        let dsts: Vec<usize> = st
5644            .sides
5645            .iter()
5646            .enumerate()
5647            .filter_map(|(idx, side)| side.as_ref().map(|_| idx))
5648            .collect();
5649        for dst in dsts {
5650            let data = RelayItem::Packet(Arc::new(pkt.clone()));
5651            let priority = Self::relay_item_priority(&data)?;
5652            st.push_tx(RelayTxItem {
5653                src: None,
5654                dst,
5655                data,
5656                priority,
5657                retry_after_ms: 0,
5658            })?;
5659        }
5660        Ok(())
5661    }
5662
5663    #[cfg(feature = "discovery")]
5664    /// Polls discovery state and queues an announce if the cadence says one is due.
5665    pub fn poll_discovery(&self) -> TelemetryResult<bool> {
5666        self.poll_discovery_announce()
5667    }
5668
5669    #[cfg(feature = "discovery")]
5670    /// Exports the relay's current discovered topology snapshot.
5671    pub fn export_topology(&self) -> TopologySnapshot {
5672        let now_ms = self.clock.now_ms();
5673        let mut st = self.state.lock();
5674        if Self::prune_discovery_routes_locked(&mut st, now_ms) {
5675            self.reconcile_end_to_end_acked_destinations_locked(&mut st);
5676            Self::note_discovery_topology_change_locked(&mut st, now_ms);
5677        }
5678        let routes = st
5679            .discovery_routes
5680            .iter()
5681            .filter_map(|(&side_id, route)| {
5682                let side = st.sides.get(side_id).and_then(|side| side.as_ref())?;
5683                let announcers = route
5684                    .announcers
5685                    .iter()
5686                    .map(|(sender_id, sender_state)| TopologyAnnouncerRoute {
5687                        sender_id: sender_id.clone(),
5688                        reachable_endpoints: sender_state
5689                            .reachable
5690                            .iter()
5691                            .copied()
5692                            .filter(|ep| !discovery::is_router_control_endpoint(*ep))
5693                            .collect(),
5694                        reachable_network_variables: Vec::new(),
5695                        reachable_timesync_sources: sender_state.reachable_timesync_sources.clone(),
5696                        routers: sender_state.topology_boards.as_ref().clone(),
5697                        last_seen_ms: sender_state.last_seen_ms,
5698                        age_ms: now_ms.saturating_sub(sender_state.last_seen_ms),
5699                    })
5700                    .collect();
5701                Some(TopologySideRoute {
5702                    side_id,
5703                    side_name: side.name.to_string(),
5704                    reachable_endpoints: route
5705                        .reachable
5706                        .iter()
5707                        .copied()
5708                        .filter(|ep| !discovery::is_router_control_endpoint(*ep))
5709                        .collect(),
5710                    reachable_network_variables: Vec::new(),
5711                    reachable_timesync_sources: route.reachable_timesync_sources.clone(),
5712                    announcers,
5713                    last_seen_ms: route.last_seen_ms,
5714                    age_ms: now_ms.saturating_sub(route.last_seen_ms),
5715                })
5716            })
5717            .collect();
5718        let routers = self.advertised_discovery_topology_for_link_locked(&st, now_ms, true, None);
5719        let advertised_endpoints =
5720            self.advertised_discovery_endpoints_for_link_locked(&st, now_ms, true, None);
5721        let advertised_timesync_sources =
5722            self.advertised_discovery_timesync_sources_for_link_locked(&st, now_ms, None);
5723        let links = discovery::topology_links_from_boards(&routers);
5724        TopologySnapshot {
5725            advertised_endpoints,
5726            advertised_timesync_sources,
5727            routers,
5728            links,
5729            routes,
5730            current_announce_interval_ms: st.discovery_cadence.current_interval_ms,
5731            next_announce_ms: st.discovery_cadence.next_announce_ms,
5732        }
5733    }
5734
5735    #[cfg(feature = "discovery")]
5736    pub fn client_stats(&self, sender_id: &str) -> Option<ClientStatsSnapshot> {
5737        let now_ms = self.clock.now_ms();
5738        let st = self.state.lock();
5739        let mut side_ids = Vec::new();
5740        let mut side_names = Vec::new();
5741        let mut last_seen_ms = None::<u64>;
5742        let mut reachable_endpoints = Vec::new();
5743        let mut reachable_timesync_sources = Vec::new();
5744        let mut packets_sent = 0u64;
5745        let mut packets_received = 0u64;
5746        let mut bytes_sent = 0u64;
5747        let mut bytes_received = 0u64;
5748
5749        for (side_id, route) in &st.discovery_routes {
5750            let Some(sender_state) = route.announcers.get(sender_id) else {
5751                continue;
5752            };
5753            side_ids.push(*side_id);
5754            if let Some(side_name) = st
5755                .sides
5756                .get(*side_id)
5757                .and_then(|side| side.as_ref())
5758                .map(|side| side.name.clone())
5759            {
5760                side_names.push(side_name.to_string());
5761            }
5762            last_seen_ms = Some(last_seen_ms.unwrap_or(0).max(sender_state.last_seen_ms));
5763            reachable_endpoints.extend(sender_state.reachable.iter().copied());
5764            reachable_timesync_sources
5765                .extend(sender_state.reachable_timesync_sources.iter().cloned());
5766            if let Some(stats) = st.side_runtime_stats.get(side_id) {
5767                packets_sent = packets_sent.saturating_add(stats.tx_packets);
5768                packets_received = packets_received.saturating_add(stats.rx_packets);
5769                bytes_sent = bytes_sent.saturating_add(stats.tx_bytes);
5770                bytes_received = bytes_received.saturating_add(stats.rx_bytes);
5771            }
5772        }
5773
5774        if side_ids.is_empty() {
5775            return None;
5776        }
5777        reachable_endpoints.retain(|ep| !discovery::is_router_control_endpoint(*ep));
5778        crate::small_sort::sort(&mut reachable_endpoints);
5779        reachable_endpoints.dedup();
5780        reachable_timesync_sources.sort_unstable();
5781        reachable_timesync_sources.dedup();
5782        crate::small_sort::sort(&mut side_ids);
5783        side_ids.dedup();
5784        side_names.sort_unstable();
5785        side_names.dedup();
5786        let age_ms = last_seen_ms.map(|seen| now_ms.saturating_sub(seen));
5787        Some(ClientStatsSnapshot {
5788            sender_id: sender_id.to_string(),
5789            connected: age_ms.is_some_and(|age| age <= DISCOVERY_ROUTE_TTL_MS),
5790            side_ids,
5791            side_names,
5792            last_seen_ms,
5793            age_ms,
5794            reachable_endpoints,
5795            reachable_timesync_sources,
5796            packets_sent,
5797            packets_received,
5798            bytes_sent,
5799            bytes_received,
5800        })
5801    }
5802
5803    pub fn export_runtime_stats(&self) -> RuntimeStatsSnapshot {
5804        let now_ms = self.clock.now_ms();
5805        let st = self.state.lock();
5806
5807        let mut sides = Vec::new();
5808        for (side_id, side) in st.sides.iter().enumerate() {
5809            let Some(side) = side.as_ref() else { continue };
5810            let empty_stats = SideRuntimeStatsInner::default();
5811            let stats = st
5812                .side_runtime_stats
5813                .get(&side_id)
5814                .map(Box::as_ref)
5815                .unwrap_or(&empty_stats);
5816            let adaptive = st
5817                .adaptive_route_stats
5818                .get(&side_id)
5819                .cloned()
5820                .unwrap_or_default()
5821                .snapshot(now_ms, true);
5822            let (tx_template_count, rx_template_count) = st
5823                .side_transport
5824                .get(&side_id)
5825                .map(|state| (state.tx_template_count(), state.rx_template_count()))
5826                .unwrap_or((0, 0));
5827            let mut data_types: Vec<RuntimeTypeStats> = stats
5828                .data_types
5829                .iter()
5830                .map(|(&ty, item)| {
5831                    let item = item.counters();
5832                    RuntimeTypeStats {
5833                        data_type: crate::DataType(ty),
5834                        tx_packets: item.tx_packets,
5835                        tx_bytes: item.tx_bytes,
5836                        rx_packets: item.rx_packets,
5837                        rx_bytes: item.rx_bytes,
5838                        relayed_tx_packets: item.relayed_tx_packets,
5839                        relayed_tx_bytes: item.relayed_tx_bytes,
5840                        relayed_rx_packets: item.relayed_rx_packets,
5841                        relayed_rx_bytes: item.relayed_rx_bytes,
5842                        tx_retries: item.tx_retries,
5843                        handler_failures: item.handler_failures,
5844                    }
5845                })
5846                .collect();
5847            data_types.sort_unstable_by_key(|item| item.data_type.as_u32());
5848            sides.push(RuntimeSideStats {
5849                side_id,
5850                side_name: side.name.to_string(),
5851                reliable_enabled: side.opts.reliable_enabled,
5852                link_local_enabled: side.opts.link_local_enabled,
5853                header_template_enabled: side.opts.header_template_enabled,
5854                max_frame_bytes: side.opts.max_frame_bytes,
5855                compact_header_target_bytes: side.opts.compact_header_target_bytes,
5856                side_transport_profile: side.opts.effective_transport_profile().as_str(),
5857                ingress_enabled: side.opts.ingress_enabled,
5858                egress_enabled: side.opts.egress_enabled,
5859                tx_packets: stats.tx_packets,
5860                tx_bytes: stats.tx_bytes,
5861                rx_packets: stats.rx_packets,
5862                rx_bytes: stats.rx_bytes,
5863                relayed_tx_packets: stats.relayed_tx_packets,
5864                relayed_tx_bytes: stats.relayed_tx_bytes,
5865                relayed_rx_packets: stats.relayed_rx_packets,
5866                relayed_rx_bytes: stats.relayed_rx_bytes,
5867                local_delivery_packets: 0,
5868                tx_retries: stats.tx_retries,
5869                tx_handler_failures: stats.tx_handler_failures,
5870                local_handler_failures: 0,
5871                total_handler_retries: stats.total_handler_retries,
5872                side_transport_full_frames: stats.side_transport_full_frames,
5873                side_transport_compact_frames: stats.side_transport_compact_frames,
5874                side_transport_compact_delta_frames: stats.side_transport_compact_delta_frames,
5875                side_transport_compact_omitted_timestamp_frames: stats
5876                    .side_transport_compact_omitted_timestamp_frames,
5877                side_transport_chunk_frames: stats.side_transport_chunk_frames,
5878                side_transport_chunk_reclaims: st
5879                    .side_transport
5880                    .get(&side_id)
5881                    .map_or(0, |state| u64::from(state.rx_chunk_reclaims)),
5882                side_transport_raw_bytes: stats.side_transport_raw_bytes,
5883                side_transport_wire_bytes: stats.side_transport_wire_bytes,
5884                side_transport_bytes_saved: stats.side_transport_bytes_saved,
5885                side_transport_min_compact_overhead_bytes: stats
5886                    .side_transport_min_compact_overhead_bytes,
5887                side_transport_max_compact_overhead_bytes: stats
5888                    .side_transport_max_compact_overhead_bytes,
5889                side_transport_compact_target_misses: stats.side_transport_compact_target_misses,
5890                side_transport_template_evictions: stats.side_transport_template_evictions,
5891                side_transport_tx_template_count: tx_template_count,
5892                side_transport_rx_template_count: rx_template_count,
5893                max_side_transport_templates: side.opts.max_side_transport_templates,
5894                adaptive,
5895                data_types,
5896            });
5897        }
5898
5899        let mut route_modes: Vec<RouteModeStats> = st
5900            .route_selection_cursors
5901            .iter()
5902            .map(|(src, cursor)| RouteModeStats {
5903                src_side_id: *src,
5904                selection_mode: st.source_route_modes.get(src).copied(),
5905                cursor: *cursor,
5906            })
5907            .collect();
5908        for src in st.source_route_modes.keys() {
5909            if !route_modes.iter().any(|mode| mode.src_side_id == *src) {
5910                route_modes.push(RouteModeStats {
5911                    src_side_id: *src,
5912                    selection_mode: st.source_route_modes.get(src).copied(),
5913                    cursor: 0,
5914                });
5915            }
5916        }
5917        route_modes.sort_unstable_by_key(|mode| mode.src_side_id.unwrap_or(usize::MAX));
5918
5919        let mut route_overrides: Vec<RouteOverrideStats> = st
5920            .route_overrides
5921            .iter()
5922            .map(|((src, dst), enabled)| RouteOverrideStats {
5923                src_side_id: *src,
5924                dst_side_id: *dst,
5925                enabled: *enabled,
5926            })
5927            .collect();
5928        route_overrides.sort_unstable_by_key(|item| {
5929            (item.src_side_id.unwrap_or(usize::MAX), item.dst_side_id)
5930        });
5931
5932        let mut typed_route_overrides: Vec<TypedRouteOverrideStats> = st
5933            .typed_route_overrides
5934            .iter()
5935            .map(|((src, ty, dst), enabled)| TypedRouteOverrideStats {
5936                src_side_id: *src,
5937                data_type: crate::DataType(*ty),
5938                dst_side_id: *dst,
5939                enabled: *enabled,
5940            })
5941            .collect();
5942        typed_route_overrides.sort_unstable_by_key(|item| {
5943            (
5944                item.src_side_id.unwrap_or(usize::MAX),
5945                item.data_type.as_u32(),
5946                item.dst_side_id,
5947            )
5948        });
5949
5950        let mut route_weights: Vec<RouteWeightStats> = st
5951            .route_weights
5952            .iter()
5953            .map(|((src, dst), weight)| RouteWeightStats {
5954                src_side_id: *src,
5955                dst_side_id: *dst,
5956                weight: *weight,
5957            })
5958            .collect();
5959        route_weights.sort_unstable_by_key(|item| {
5960            (item.src_side_id.unwrap_or(usize::MAX), item.dst_side_id)
5961        });
5962
5963        let mut route_priorities: Vec<RoutePriorityStats> = st
5964            .route_priorities
5965            .iter()
5966            .map(|((src, dst), priority)| RoutePriorityStats {
5967                src_side_id: *src,
5968                dst_side_id: *dst,
5969                priority: *priority,
5970            })
5971            .collect();
5972        route_priorities.sort_unstable_by_key(|item| {
5973            (item.src_side_id.unwrap_or(usize::MAX), item.dst_side_id)
5974        });
5975
5976        #[cfg(feature = "discovery")]
5977        let discovery = DiscoveryRuntimeStats {
5978            route_count: st.discovery_routes.len(),
5979            announcer_count: st
5980                .discovery_routes
5981                .values()
5982                .map(|route| route.announcers.len())
5983                .sum(),
5984            current_announce_interval_ms: Some(st.discovery_cadence.current_interval_ms),
5985            next_announce_ms: Some(st.discovery_cadence.next_announce_ms),
5986        };
5987        #[cfg(not(feature = "discovery"))]
5988        let discovery = DiscoveryRuntimeStats {
5989            route_count: 0,
5990            announcer_count: 0,
5991            current_announce_interval_ms: None,
5992            next_announce_ms: None,
5993        };
5994
5995        RuntimeStatsSnapshot {
5996            sides,
5997            route_modes,
5998            route_overrides,
5999            typed_route_overrides,
6000            route_weights,
6001            route_priorities,
6002            queues: QueueRuntimeStats {
6003                rx_len: st.rx_queue.len(),
6004                rx_bytes: st.rx_queue.bytes_used(),
6005                tx_len: st.tx_queue.len(),
6006                tx_bytes: st.tx_queue.bytes_used(),
6007                replay_len: st.replay_queue.len(),
6008                replay_bytes: st.replay_queue.bytes_used(),
6009                recent_rx_len: st.recent_rx.len(),
6010                recent_rx_bytes: st.recent_rx.bytes_used(),
6011                reliable_rx_buffered_len: st.reliable_rx_buffer_len(),
6012                reliable_rx_buffered_bytes: st.reliable_rx_buffered_bytes(),
6013                shared_queue_bytes_used: st.shared_queue_bytes_used(),
6014            },
6015            reliable: ReliableRuntimeStats {
6016                reliable_return_route_count: st.reliable_return_routes.len(),
6017                end_to_end_pending_count: 0,
6018                end_to_end_pending_destination_count: 0,
6019                end_to_end_acked_cache_count: st.end_to_end_acked_destinations.len(),
6020            },
6021            discovery,
6022            total_handler_failures: st.total_handler_failures,
6023            total_handler_retries: st.total_handler_retries,
6024        }
6025    }
6026
6027    /// Export current relay memory usage/layout as JSON for profiling.
6028    pub fn export_memory_layout_json(&self) -> String {
6029        let st = self.state.lock();
6030        #[cfg(feature = "discovery")]
6031        let discovery_bytes = st.discovery_bytes_used();
6032        #[cfg(not(feature = "discovery"))]
6033        let discovery_bytes = 0usize;
6034        let schema_bytes = crate::config::schema_bytes_used();
6035        let memory = st.memory;
6036        let mut out = String::new();
6037        let _ = core::fmt::Write::write_fmt(
6038            &mut out,
6039            format_args!(
6040                "{{\"kind\":\"relay\",\
6041                 \"shared_queue_bytes_used\":{},\"shared_queue_bytes_allocated\":{},\
6042                 \"rx_queue_bytes_used\":{},\"rx_queue_bytes_allocated\":{},\"rx_queue_len\":{},\
6043                 \"tx_queue_bytes_used\":{},\"tx_queue_bytes_allocated\":{},\"tx_queue_len\":{},\
6044                 \"replay_queue_bytes_used\":{},\"replay_queue_bytes_allocated\":{},\"replay_queue_len\":{},\
6045                 \"recent_rx_bytes_used\":{},\"recent_rx_bytes_allocated\":{},\"recent_rx_len\":{},\
6046                 \"reliable_rx_buffer_bytes_used\":{},\"reliable_rx_buffer_bytes_allocated\":{},\"reliable_rx_buffer_len\":{},\
6047                 \"discovery_bytes_used\":{},\"discovery_bytes_allocated\":{},\
6048                 \"schema_bytes_used\":{},\"schema_bytes_allocated\":{}}}",
6049                st.shared_queue_bytes_used(),
6050                memory.max_queue_budget,
6051                st.rx_queue.bytes_used(),
6052                st.rx_queue.max_bytes(),
6053                st.rx_queue.len(),
6054                st.tx_queue.bytes_used(),
6055                st.tx_queue.max_bytes(),
6056                st.tx_queue.len(),
6057                st.replay_queue.bytes_used(),
6058                st.replay_queue.max_bytes(),
6059                st.replay_queue.len(),
6060                st.recent_rx.bytes_used(),
6061                st.recent_rx.max_bytes(),
6062                st.recent_rx.len(),
6063                st.reliable_rx_buffered_bytes(),
6064                memory.max_queue_budget,
6065                st.reliable_rx_buffer_len(),
6066                discovery_bytes,
6067                memory.max_queue_budget,
6068                schema_bytes,
6069                memory.max_queue_budget,
6070            ),
6071        );
6072        out
6073    }
6074
6075    #[cfg(test)]
6076    #[cfg(feature = "discovery")]
6077    pub(crate) fn debug_end_to_end_acked_destination_count(&self, packet_id: u64) -> Option<usize> {
6078        let st = self.state.lock();
6079        st.end_to_end_acked_destinations
6080            .get(&packet_id)
6081            .map(BTreeSet::len)
6082    }
6083
6084    #[cfg(test)]
6085    #[cfg(feature = "discovery")]
6086    pub(crate) fn debug_end_to_end_acked_packet_count(&self) -> usize {
6087        let st = self.state.lock();
6088        st.end_to_end_acked_destinations.len()
6089    }
6090
6091    #[cfg(test)]
6092    #[cfg(feature = "discovery")]
6093    pub(crate) fn debug_reliable_return_route_count(&self) -> usize {
6094        let st = self.state.lock();
6095        st.reliable_return_routes.len()
6096    }
6097
6098    #[cfg(test)]
6099    pub(crate) fn debug_side_storage(&self) -> (usize, usize) {
6100        let st = self.state.lock();
6101        (st.sides.len(), st.sides.capacity())
6102    }
6103
6104    /// Enqueue packed bytes that originated from `src` into the relay RX queue.
6105    ///
6106    /// Note: `Arc::from(bytes)` allocates and copies `len` bytes into a new `crate::SharedBytes`.
6107    /// This is still “fast enough” for many cases, but it is not allocation-free / ISR-safe.
6108    pub fn rx_packed_from_side(&self, src: RelaySideId, bytes: &[u8]) -> TelemetryResult<()> {
6109        self.ensure_side_ingress_enabled(src)?;
6110        let Some(bytes) = self.decode_side_transport_frame(src, bytes)? else {
6111            return Ok(());
6112        };
6113        let mut st = self.state.lock();
6114
6115        let data = RelayItem::Packed(bytes);
6116        let priority = Self::relay_item_priority(&data)?;
6117        st.push_rx(RelayRxItem {
6118            src,
6119            data,
6120            priority,
6121        })
6122    }
6123
6124    /// Enqueue a full packet that originated from `src` into the relay RX queue.
6125    ///
6126    /// The packet is wrapped in `Arc<Packet>` so fanout can clone the pointer cheaply.
6127    pub fn rx_from_side(&self, src: RelaySideId, packet: Packet) -> TelemetryResult<()> {
6128        self.ensure_side_ingress_enabled(src)?;
6129        let mut st = self.state.lock();
6130
6131        let data = RelayItem::Packet(Arc::new(packet));
6132        let priority = Self::relay_item_priority(&data)?;
6133        st.push_rx(RelayRxItem {
6134            src,
6135            data,
6136            priority,
6137        })
6138    }
6139
6140    /// Clear both RX and TX queues.
6141    pub fn clear_queues(&self) {
6142        let mut st = self.state.lock();
6143        st.rx_queue.clear();
6144        st.rx_priority_burst = 0;
6145        st.tx_queue.clear();
6146        st.retain_replays(|_| false);
6147        st.tx_priority_burst = 0;
6148    }
6149
6150    /// Clear only RX queue.
6151    pub fn clear_rx_queue(&self) {
6152        let mut st = self.state.lock();
6153        st.rx_queue.clear();
6154        st.rx_priority_burst = 0;
6155    }
6156
6157    /// Clear queued TX, including replay markers. Retained delivery can retry later.
6158    pub fn clear_tx_queue(&self) {
6159        let mut st = self.state.lock();
6160        st.tx_queue.clear();
6161        st.tx_priority_burst = 0;
6162        st.retain_replays(|_| false);
6163    }
6164
6165    /// Internal: expand one RX item into TX items for all other sides.
6166    ///
6167    /// Fanout is cheap: the `RelayItem` is cloned (Arc bump) and reused across all destinations.
6168    fn drain_queued_receipts_before_timeouts(
6169        &self,
6170        start: u64,
6171        timeout_ms: u32,
6172    ) -> TelemetryResult<bool> {
6173        let mut did_any = false;
6174        loop {
6175            let item = {
6176                let mut st = self.state.lock();
6177                let index = st.rx_queue.iter().position(|item| {
6178                    // Receipts occupy255. Keep discovery's exact type check,
6179                    // but skip header decoding for every lower-band data item.
6180                    if item.priority < crate::transport_priority(crate::DataType::ReliableAck) {
6181                        return false;
6182                    }
6183                    let ty = match &item.data {
6184                        RelayItem::Packet(pkt) => Some(pkt.data_type()),
6185                        RelayItem::Packed(bytes) => {
6186                            if bytes.len() <= 128
6187                                && wire_format::peek_routing_frame_info(bytes)
6188                                    .is_ok_and(|frame| frame.ack_only())
6189                            {
6190                                return true;
6191                            }
6192                            wire_format::scheduling_type(bytes.as_ref())
6193                        }
6194                    };
6195                    matches!(
6196                        ty,
6197                        Some(
6198                            crate::DataType::ReliableAck
6199                                | crate::DataType::ReliablePartialAck
6200                                | crate::DataType::ReliablePacketRequest
6201                        )
6202                    )
6203                });
6204                index.and_then(|index| st.rx_queue.remove_pos(index))
6205            };
6206            let Some(item) = item else {
6207                return Ok(did_any);
6208            };
6209            self.process_rx_queue_item(item)?;
6210            did_any = true;
6211            if timeout_ms != 0 && self.clock.now_ms().wrapping_sub(start) >= u64::from(timeout_ms) {
6212                return Ok(did_any);
6213            }
6214        }
6215    }
6216
6217    fn process_rx_queue_item(&self, item: RelayRxItem) -> TelemetryResult<()> {
6218        // Reuse this CRC-validated borrowed envelope for accounting and
6219        // role/return-route checks in this invocation. Dispatch still rechecks
6220        // current routing metadata; no validation result survives queueing.
6221        let packed_frame = match &item.data {
6222            RelayItem::Packed(bytes) => Some(wire_format::peek_routing_frame_info(bytes.as_ref())),
6223            RelayItem::Packet(_) => None,
6224        };
6225        if crate::memory_admission::supports_work_kinds()
6226            && let RelayItem::Packed(bytes) = &item.data
6227        {
6228            self.admit_received_frame_with_view(
6229                item.src,
6230                bytes,
6231                packed_frame.as_ref().and_then(|frame| frame.as_ref().ok()),
6232            )?;
6233        }
6234        let packed_routing = packed_frame
6235            .as_ref()
6236            .and_then(|frame| frame.as_ref().ok())
6237            .map(|frame| &frame.envelope);
6238        self.ensure_side_ingress_enabled(item.src)?;
6239        match &item.data {
6240            RelayItem::Packet(pkt) => {
6241                pkt.validate()?;
6242                #[cfg(feature = "discovery")]
6243                self.refresh_peer_liveness(item.src, sender_address_u32(pkt.sender()));
6244                let bytes = wire_format::packet_wire_size(pkt);
6245                self.note_side_rx(item.src, pkt.data_type(), bytes);
6246            }
6247            RelayItem::Packed(bytes) => {
6248                if let Some(env) = packed_routing.as_ref() {
6249                    self.note_side_rx(item.src, env.ty, bytes.len());
6250                }
6251            }
6252        }
6253        match &item.data {
6254            RelayItem::Packet(pkt) => {
6255                if (is_reliable_type(pkt.data_type()) || !pkt.wire_target_senders().is_empty())
6256                    && !is_internal_control_type(pkt.data_type())
6257                {
6258                    self.note_reliable_return_route(item.src, pkt.packet_id());
6259                }
6260            }
6261            RelayItem::Packed(bytes) => {
6262                if let Some(env) = packed_routing.as_ref()
6263                    && (is_reliable_type(env.ty) || env.target_senders().len() != 0)
6264                    && !is_internal_control_type(env.ty)
6265                    && let Ok(packet_id) = wire_format::packet_id_from_wire(bytes.as_ref())
6266                {
6267                    self.note_reliable_return_route(item.src, packet_id);
6268                }
6269            }
6270        }
6271        let mut released_buffered: Vec<crate::SharedBytes> = Vec::new();
6272        if let RelayItem::Packed(bytes) = &item.data {
6273            let (_opts, handler_is_packed, hop_reliable_enabled) = {
6274                let st = self.state.lock();
6275                let side_ref = Self::side_ref(&st, item.src)?;
6276                let opts = side_ref.opts.clone();
6277                (
6278                    opts.clone(),
6279                    matches!(side_ref.tx_handler, RelayTxHandlerFn::Packed(_)),
6280                    opts.reliable_enabled
6281                        && !self.side_has_multiple_announcers_locked(
6282                            &st,
6283                            item.src,
6284                            self.clock.now_ms(),
6285                        ),
6286                )
6287            };
6288
6289            let frame = match packed_frame
6290                .as_ref()
6291                .expect("packed frame")
6292                .as_ref()
6293                .map_err(Clone::clone)
6294            {
6295                Ok(frame) => frame,
6296                Err(e) => {
6297                    if matches!(e, TelemetryError::Unpack(msg) if msg == "crc32 mismatch")
6298                        && hop_reliable_enabled
6299                        && handler_is_packed
6300                        && let Ok(frame) = wire_format::peek_frame_info_unchecked(bytes.as_ref())
6301                    {
6302                        if is_reliable_type(frame.envelope.ty)
6303                            && let Some(hdr) = frame.reliable
6304                        {
6305                            let unordered = (hdr.flags & wire_format::RELIABLE_FLAG_UNORDERED) != 0;
6306                            let unsequenced =
6307                                (hdr.flags & wire_format::RELIABLE_FLAG_UNSEQUENCED) != 0;
6308
6309                            if !unsequenced {
6310                                let requested = if unordered {
6311                                    hdr.seq
6312                                } else {
6313                                    let mut st = self.state.lock();
6314                                    let rx_state = self.reliable_rx_state_mut(
6315                                        &mut st,
6316                                        item.src,
6317                                        frame.envelope.ty,
6318                                    );
6319                                    rx_state.expected_seq.min(hdr.seq)
6320                                };
6321                                self.queue_reliable_packet_request(
6322                                    item.src,
6323                                    frame.envelope.ty,
6324                                    requested,
6325                                )?;
6326                            }
6327                        }
6328                        return Ok(());
6329                    }
6330                    return Err(e);
6331                }
6332            };
6333
6334            if hop_reliable_enabled
6335                && handler_is_packed
6336                && is_reliable_type(frame.envelope.ty)
6337                && let Some(hdr) = frame.reliable
6338            {
6339                if frame.ack_only() {
6340                    self.handle_reliable_ack(item.src, frame.envelope.ty, hdr.ack);
6341                    return Ok(());
6342                }
6343                let unordered = (hdr.flags & wire_format::RELIABLE_FLAG_UNORDERED) != 0;
6344                let unsequenced = (hdr.flags & wire_format::RELIABLE_FLAG_UNSEQUENCED) != 0;
6345
6346                if !unsequenced {
6347                    if unordered {
6348                        self.queue_reliable_ack(item.src, frame.envelope.ty, hdr.seq)?;
6349                    } else {
6350                        let mut release: Vec<crate::SharedBytes> = Vec::new();
6351                        let mut last_delivered = None;
6352                        let mut ack_old = None;
6353                        let mut request_missing = None;
6354                        let mut partial_ack = None;
6355                        {
6356                            let mut st = self.state.lock();
6357                            let rx_state =
6358                                self.reliable_rx_state_mut(&mut st, item.src, frame.envelope.ty);
6359                            let expected_seq = rx_state.expected_seq;
6360                            if hdr.seq < expected_seq {
6361                                ack_old = Some(expected_seq.saturating_sub(1));
6362                            } else if hdr.seq > expected_seq {
6363                                request_missing = Some(expected_seq);
6364                                partial_ack = Some(hdr.seq);
6365                                st.buffer_reliable_rx(
6366                                    item.src,
6367                                    frame.envelope.ty,
6368                                    hdr.seq,
6369                                    bytes.clone(),
6370                                )?;
6371                            } else {
6372                                release.push(bytes.clone());
6373                                last_delivered = Some(hdr.seq);
6374                                let mut next_expected = hdr.seq.wrapping_add(1);
6375                                while let Some(buf) = rx_state.buffered.remove(&next_expected) {
6376                                    release.push(buf);
6377                                    last_delivered = Some(next_expected);
6378                                    let next = next_expected.wrapping_add(1);
6379                                    next_expected = if next == 0 { 1 } else { next };
6380                                }
6381                                rx_state.expected_seq = next_expected;
6382                            }
6383                        }
6384
6385                        if let Some(ack_seq) = ack_old {
6386                            self.queue_reliable_ack(item.src, frame.envelope.ty, ack_seq)?;
6387                            return Ok(());
6388                        }
6389                        if let Some(request_seq) = request_missing {
6390                            if let Some(partial_seq) = partial_ack {
6391                                self.queue_reliable_partial_ack(
6392                                    item.src,
6393                                    frame.envelope.ty,
6394                                    partial_seq,
6395                                )?;
6396                            }
6397                            self.queue_reliable_packet_request(
6398                                item.src,
6399                                frame.envelope.ty,
6400                                request_seq,
6401                            )?;
6402                            return Ok(());
6403                        }
6404                        if let Some(ack_seq) = last_delivered {
6405                            self.queue_reliable_ack(item.src, frame.envelope.ty, ack_seq)?;
6406                        }
6407                        released_buffered.extend(release.into_iter().skip(1));
6408                    }
6409                }
6410            }
6411        }
6412
6413        if self.is_duplicate_pkt(&item)? && !self.should_forward_duplicate_reliable_item(&item)? {
6414            // Already fanned out this packet recently; skip.
6415            return Ok(());
6416        }
6417
6418        self.dispatch_relay_rx_item(&item)?;
6419
6420        for release_bytes in released_buffered {
6421            let release_item = RelayRxItem {
6422                src: item.src,
6423                priority: Self::relay_item_priority(&RelayItem::Packed(release_bytes.clone()))?,
6424                data: RelayItem::Packed(release_bytes),
6425            };
6426            if self.is_duplicate_pkt(&release_item)?
6427                && !self.should_forward_duplicate_reliable_item(&release_item)?
6428            {
6429                continue;
6430            }
6431            self.dispatch_relay_rx_item(&release_item)?;
6432        }
6433        Ok(())
6434    }
6435
6436    fn dispatch_relay_rx_item(&self, item: &RelayRxItem) -> TelemetryResult<()> {
6437        match &item.data {
6438            RelayItem::Packet(pkt) => {
6439                if matches!(
6440                    pkt.data_type(),
6441                    crate::DataType::ReliableAck
6442                        | crate::DataType::ReliablePartialAck
6443                        | crate::DataType::ReliablePacketRequest
6444                ) {
6445                    if Self::end_to_end_ack_sender_hash(pkt).is_some()
6446                        && Self::decode_end_to_end_reliable_ack(pkt.payload()).is_ok()
6447                    {
6448                        if let Ok(packet_id) = Self::decode_end_to_end_reliable_ack(pkt.payload())
6449                            && let Some(sender_hash) = Self::end_to_end_ack_sender_hash(pkt)
6450                        {
6451                            let mut st = self.state.lock();
6452                            Self::note_end_to_end_acked_destination_locked(
6453                                &mut st,
6454                                packet_id,
6455                                sender_hash,
6456                            );
6457                        }
6458                    } else {
6459                        let vals = pkt.data_as_u32()?;
6460                        if vals.len() != 2 {
6461                            return Err(TelemetryError::Unpack("bad reliable control payload"));
6462                        }
6463                        let ty = crate::DataType::try_from_u32(vals[0])
6464                            .ok_or(TelemetryError::InvalidType)?;
6465                        let seq = vals[1];
6466                        match pkt.data_type() {
6467                            crate::DataType::ReliableAck => {
6468                                self.handle_reliable_ack(item.src, ty, seq)
6469                            }
6470                            crate::DataType::ReliablePartialAck => {
6471                                self.handle_reliable_partial_ack(item.src, ty, seq)
6472                            }
6473                            crate::DataType::ReliablePacketRequest => {
6474                                self.queue_reliable_retransmit(item.src, ty, seq)?
6475                            }
6476                            _ => {}
6477                        }
6478                        return Ok(());
6479                    }
6480                }
6481            }
6482            RelayItem::Packed(bytes) => {
6483                let env = wire_format::peek_routing_envelope(bytes.as_ref())?;
6484                if matches!(
6485                    env.ty,
6486                    crate::DataType::ReliableAck
6487                        | crate::DataType::ReliablePacketRequest
6488                        | crate::DataType::ReliablePartialAck
6489                ) {
6490                    let pkt = wire_format::unpack_packet(bytes.as_ref())?;
6491                    return self.dispatch_relay_rx_item(&RelayRxItem {
6492                        src: item.src,
6493                        data: RelayItem::Packet(Arc::new(pkt)),
6494                        priority: item.priority,
6495                    });
6496                }
6497            }
6498        }
6499
6500        let src = item.src;
6501        let data = item.data.clone();
6502        self.learn_discovery_item(src, &data)?;
6503
6504        let plan = self.remote_side_plan(&data, src)?;
6505        let mut st = self.state.lock();
6506        let RemoteSidePlan::Target(sides) = plan;
6507        for dst in sides {
6508            let priority = Self::relay_item_priority(&data)?;
6509            st.push_tx(RelayTxItem {
6510                src: Some(src),
6511                dst,
6512                data: data.clone(),
6513                priority,
6514                retry_after_ms: 0,
6515            })?;
6516        }
6517        Ok(())
6518    }
6519
6520    fn run_side_chunk_transfer<T>(
6521        &self,
6522        side: RelaySideId,
6523        key: u64,
6524        send: impl FnOnce(&mut usize) -> TelemetryResult<T>,
6525    ) -> TelemetryResult<T> {
6526        let mut next = {
6527            let mut st = self.state.lock();
6528            let transport = st
6529                .side_transport
6530                .get_mut(&side)
6531                .ok_or(TelemetryError::BadArg)?;
6532            if transport.tx_chunk_progress.is_none() {
6533                crate::memory_admission::check(
6534                    512,
6535                    core::mem::size_of::<crate::side_transport::SideTxProgress>(),
6536                )?;
6537                transport.tx_chunk_progress =
6538                    Some(Box::new(crate::side_transport::SideTxProgress::default()));
6539            }
6540            transport
6541                .tx_chunk_progress
6542                .as_mut()
6543                .expect("initialized")
6544                .begin(key, self.clock.now_ms())?
6545        };
6546        let result = send(&mut next);
6547        if let Some(progress) = self
6548            .state
6549            .lock()
6550            .side_transport
6551            .get_mut(&side)
6552            .and_then(|transport| transport.tx_chunk_progress.as_mut())
6553        {
6554            progress.finish(key, next, self.clock.now_ms(), &result);
6555        }
6556        result
6557    }
6558
6559    fn send_side_frames_resumable(
6560        &self,
6561        side: RelaySideId,
6562        mut frames: SideTransportFrames,
6563        mut send: impl FnMut(&[u8]) -> TelemetryResult<()>,
6564    ) -> TelemetryResult<()> {
6565        let bootstraps_template = frames.bootstraps_template();
6566        let result = if frames.len() == 1 {
6567            frames.send_resuming(&mut 0, send)
6568        } else {
6569            let key = frames.progress_key();
6570            self.run_side_chunk_transfer(side, key, |next| frames.send_resuming(next, &mut send))
6571        };
6572        if result.is_err() && bootstraps_template {
6573            // Encoding has already inserted the TX template, but a busy or
6574            // failed handler did not accept its bootstrap. Keep dictionary
6575            // eviction history and force a full refresh on the next attempt.
6576            // Never clear RX templates or allocate during this recovery.
6577            if let Some(state) = self.state.lock().side_transport.get_mut(&side) {
6578                state.refresh_transmit_header_templates();
6579            }
6580        }
6581        result
6582    }
6583
6584    fn split_side_transport_frame(
6585        &self,
6586        _side: RelaySideId,
6587        frame: crate::SharedBytes,
6588        max_frame_bytes: usize,
6589    ) -> TelemetryResult<SideTransportFrames> {
6590        SideTransportFrames::split(frame, max_frame_bytes, self.sender_arc().as_bytes())
6591    }
6592
6593    #[cfg(all(feature = "compression", feature = "discovery"))]
6594    fn note_payload_codec_packet(&self, side: RelaySideId, pkt: &Packet, accepted: bool) {
6595        let now = self.clock.now_ms();
6596        let mut st = self.state.lock();
6597        if !st
6598            .sides
6599            .get(side)
6600            .and_then(|entry| entry.as_ref())
6601            .is_some_and(|entry| matches!(entry.tx_handler, RelayTxHandlerFn::Packed(_)))
6602        {
6603            return;
6604        }
6605        if accepted && pkt.data_type() == crate::DataType::DiscoveryLeave {
6606            for transport in st.side_transport.values_mut() {
6607                let _ = transport.payload_codec.discovery_packet(
6608                    pkt,
6609                    true,
6610                    now,
6611                    DISCOVERY_ROUTE_TTL_MS,
6612                );
6613            }
6614        } else if let Some(transport) = st.side_transport.get_mut(&side) {
6615            let _ = transport.payload_codec.discovery_packet(
6616                pkt,
6617                accepted,
6618                now,
6619                DISCOVERY_ROUTE_TTL_MS,
6620            );
6621        }
6622        if st.shared_queue_bytes_used() > st.memory.max_queue_budget
6623            && let Some(transport) = st.side_transport.get_mut(&side)
6624        {
6625            transport.payload_codec.evict(now, DISCOVERY_ROUTE_TTL_MS);
6626        }
6627    }
6628    #[cfg(all(feature = "compression", feature = "discovery"))]
6629    fn note_payload_codec_address(&self, side: RelaySideId, payload: &[u8], accepted: bool) {
6630        let now = self.clock.now_ms();
6631        let mut st = self.state.lock();
6632        if !st
6633            .sides
6634            .get(side)
6635            .and_then(|entry| entry.as_ref())
6636            .is_some_and(|entry| matches!(entry.tx_handler, RelayTxHandlerFn::Packed(_)))
6637        {
6638            return;
6639        }
6640        if let Some(transport) = st.side_transport.get_mut(&side) {
6641            let _ = transport.payload_codec.address_payload(
6642                payload,
6643                accepted,
6644                now,
6645                DISCOVERY_ROUTE_TTL_MS,
6646            );
6647        }
6648        if st.shared_queue_bytes_used() > st.memory.max_queue_budget
6649            && let Some(transport) = st.side_transport.get_mut(&side)
6650        {
6651            transport.payload_codec.evict(now, DISCOVERY_ROUTE_TTL_MS);
6652        }
6653    }
6654
6655    #[cfg(feature = "compression")]
6656    fn payload_for_transport_side(
6657        &self,
6658        side: RelaySideId,
6659        raw: crate::SharedBytes,
6660    ) -> TelemetryResult<crate::SharedBytes> {
6661        #[cfg(not(feature = "discovery"))]
6662        let _ = side;
6663        let (allowed, budget) = {
6664            #[cfg(feature = "discovery")]
6665            let mut st = self.state.lock();
6666            #[cfg(not(feature = "discovery"))]
6667            let st = self.state.lock();
6668            let budget = st.memory.max_queue_budget;
6669            #[cfg(feature = "discovery")]
6670            let allowed = {
6671                let transport = st
6672                    .side_transport
6673                    .get_mut(&side)
6674                    .ok_or(TelemetryError::BadArg)?;
6675                let generation = transport.payload_codec.generation();
6676                if transport.payload_codec_choice != generation {
6677                    transport.payload_codec_choice = generation;
6678                    transport.tx_chunk_progress = None;
6679                    transport.refresh_transmit_header_templates();
6680                }
6681                transport.payload_codec.allowed(self.clock.now_ms())
6682            };
6683            #[cfg(not(feature = "discovery"))]
6684            let allowed = false;
6685            (allowed, budget)
6686        };
6687        Ok(
6688            match wire_format::payload_encoding_for_side(raw.as_ref(), allowed, budget)? {
6689                Some(frame) => crate::shared_bytes::convert(frame),
6690                None => raw,
6691            },
6692        )
6693    }
6694
6695    #[cfg(feature = "compression")]
6696    fn pack_relay_packet(
6697        &self,
6698        pkt: &Packet,
6699        reliable: Option<wire_format::ReliableHeader>,
6700    ) -> TelemetryResult<Arc<[u8]>> {
6701        let reliable = reliable.or_else(|| {
6702            is_reliable_type(pkt.data_type()).then_some(wire_format::ReliableHeader {
6703                flags: wire_format::RELIABLE_FLAG_UNSEQUENCED,
6704                seq: 0,
6705                ack: 0,
6706            })
6707        });
6708        wire_format::pack_routed_packet(
6709            pkt,
6710            reliable,
6711            pkt.wire_shape(),
6712            pkt.wire_target_senders(),
6713            None,
6714        )
6715    }
6716
6717    /// Persist an admitted downgrade in the queue owner before a handler can
6718    /// backpressure it. Retrying retained traffic must not need fresh decoder
6719    /// scratch merely to reproduce the same already-admitted plaintext frame.
6720    #[cfg(feature = "compression")]
6721    fn prepare_queued_payload(
6722        &self,
6723        side: RelaySideId,
6724        data: &mut RelayItem,
6725    ) -> TelemetryResult<Option<crate::payload_codec::QueueReservation<'_, RelayInner>>> {
6726        let RelayItem::Packed(bytes) = data else {
6727            return Ok(None);
6728        };
6729        if !bytes.first().is_some_and(|flags| flags & 0x11 == 1) {
6730            return Ok(None);
6731        }
6732        let capable = {
6733            #[cfg(feature = "discovery")]
6734            {
6735                self.state
6736                    .lock()
6737                    .side_transport
6738                    .get(&side)
6739                    .is_some_and(|transport| transport.payload_codec.allowed(self.clock.now_ms()))
6740            }
6741            #[cfg(not(feature = "discovery"))]
6742            {
6743                false
6744            }
6745        };
6746        if capable {
6747            return Ok(None);
6748        }
6749        let required = wire_format::decoded_frame_upper_bound(bytes.as_ref())?
6750            .checked_add(size_of::<u64>())
6751            .ok_or(TelemetryError::Io("memory pressure"))?;
6752        let reservation = crate::payload_codec::QueueReservation::acquire(&self.state, required)?;
6753        *bytes = self.payload_for_transport_side(side, bytes.clone())?;
6754        Ok(Some(reservation))
6755    }
6756
6757    fn encode_side_transport_frames(
6758        &self,
6759        side: RelaySideId,
6760        opts: RelaySideOptions,
6761        raw: crate::SharedBytes,
6762    ) -> TelemetryResult<SideTransportFrames> {
6763        #[cfg(feature = "compression")]
6764        let raw = self.payload_for_transport_side(side, raw)?;
6765        if !opts.header_template_enabled && opts.max_frame_bytes == 0 {
6766            return Ok(SideTransportFrames::single(raw));
6767        }
6768        let raw_len = raw.len();
6769        if opts.max_frame_bytes != 0 && raw_len > opts.max_frame_bytes {
6770            // Canonical bytes keep a partially accepted transfer independent
6771            // of dictionary churn while other traffic is serviced.
6772            let frames = self.split_side_transport_frame(side, raw, opts.max_frame_bytes)?;
6773            let mut st = self.state.lock();
6774            let stats = st.side_runtime_stats.entry(side).or_default();
6775            stats.note_side_transport_full(raw_len, frames.wire_len());
6776            stats.note_side_transport_chunks(frames.len());
6777            return Ok(frames);
6778        }
6779
6780        if opts.header_template_enabled {
6781            // Shared buses cannot retain one coherent dictionary across
6782            // independent producers. Do not build owned templates or wrap
6783            // canonical bytes when no dictionary can be used.
6784            let frame = wire_format::peek_routing_frame_info(raw.as_ref())?;
6785            let without_dictionary = opts.max_side_transport_templates == 0 || {
6786                let st = self.state.lock();
6787                self.side_has_multiple_announcers_locked(&st, side, self.clock.now_ms())
6788            };
6789            let reliable = is_reliable_type(frame.envelope.ty)
6790                || frame.reliable.is_some()
6791                || frame.envelope.target_senders().len() != 0
6792                || matches!(
6793                    frame.envelope.ty,
6794                    crate::DataType::ReliableAck
6795                        | crate::DataType::ReliablePartialAck
6796                        | crate::DataType::ReliablePacketRequest
6797                );
6798            if without_dictionary || reliable {
6799                let mut st = self.state.lock();
6800                if without_dictionary {
6801                    let removed = st
6802                        .side_transport
6803                        .get_mut(&side)
6804                        .ok_or(TelemetryError::BadArg)?
6805                        .trim_tx_templates_to(0);
6806                    for _ in 0..removed {
6807                        st.side_runtime_stats
6808                            .entry(side)
6809                            .or_default()
6810                            .note_side_transport_template_eviction();
6811                    }
6812                }
6813                st.side_runtime_stats
6814                    .entry(side)
6815                    .or_default()
6816                    .note_side_transport_full(raw_len, raw_len);
6817                return Ok(SideTransportFrames::single(raw));
6818            }
6819        }
6820
6821        if opts.header_template_enabled && crate::memory_admission::supports_work_kinds() {
6822            let (template, ..) = extract_side_header_template_view(raw.as_ref())?;
6823            let cached = self
6824                .state
6825                .lock()
6826                .side_transport
6827                .get(&side)
6828                .is_some_and(|transport| {
6829                    transport.tx_template_ids.contains_key(&template.hash)
6830                        && transport.tx_templates.contains_key(&template.hash)
6831                });
6832            if !cached
6833                && matches!(
6834                    crate::memory_admission::check_receive(raw.as_ref()),
6835                    Err(TelemetryError::Io("memory pressure"))
6836                )
6837            {
6838                // Compression is optional. Retain the canonical shared frame
6839                // instead of allocating a dictionary entry under pressure.
6840                // Oversized frames returned through the normal chunker above.
6841                self.state
6842                    .lock()
6843                    .side_runtime_stats
6844                    .entry(side)
6845                    .or_default()
6846                    .note_side_transport_full(raw_len, raw_len);
6847                return Ok(SideTransportFrames::single(raw));
6848            }
6849        }
6850
6851        let mut compact_payload_len = None;
6852        let mut used_compact = false;
6853        let used_timestamp_delta = false;
6854        let omitted_timestamp = false;
6855        let (template, _ty, flags, data_size, timestamp, nonce, reliable_seq_ack, payload) =
6856            extract_side_header_template_view(raw.as_ref())?;
6857        // Reliable frames never use dictionary compression. Send the native,
6858        // self-describing wire packet without allocating/retaining an unused
6859        // template on either peer. Raw packets are accepted by both decoders.
6860        if is_reliable_type(_ty)
6861            || reliable_seq_ack.is_some()
6862            || matches!(
6863                _ty,
6864                crate::DataType::ReliableAck
6865                    | crate::DataType::ReliablePartialAck
6866                    | crate::DataType::ReliablePacketRequest
6867            )
6868        {
6869            let frames = if opts.max_frame_bytes != 0 && raw_len > opts.max_frame_bytes {
6870                self.split_side_transport_frame(side, raw, opts.max_frame_bytes)?
6871            } else {
6872                SideTransportFrames::single(raw)
6873            };
6874            let wire_len = frames.wire_len();
6875            let mut st = self.state.lock();
6876            let stats = st.side_runtime_stats.entry(side).or_default();
6877            stats.note_side_transport_full(raw_len, wire_len);
6878            if frames.len() > 1 {
6879                stats.note_side_transport_chunks(frames.len());
6880            }
6881            return Ok(frames);
6882        }
6883        let (template_id, use_compact, _previous_timestamp) = {
6884            let mut st = self.state.lock();
6885            let side_state = st
6886                .side_transport
6887                .get_mut(&side)
6888                .ok_or(TelemetryError::BadArg)?;
6889            if let Some(id) = side_state.tx_template_ids.get(&template.hash).copied() {
6890                let previous = side_state.tx_last_timestamps.get(&id).copied();
6891                const FULL_REFRESH_AFTER_COMPACT_FRAMES: u8 = 8;
6892                let compact_uses = side_state.tx_compact_uses.entry(id).or_default();
6893                // Discovery must bootstrap even when a receiver lost its dictionary.
6894                #[cfg(feature = "discovery")]
6895                let discovery_refresh = discovery::is_discovery_type(_ty);
6896                #[cfg(not(feature = "discovery"))]
6897                let discovery_refresh = false;
6898                let use_compact =
6899                    *compact_uses < FULL_REFRESH_AFTER_COMPACT_FRAMES && !discovery_refresh;
6900                if use_compact {
6901                    *compact_uses = compact_uses.saturating_add(1);
6902                } else {
6903                    *compact_uses = 0;
6904                }
6905                (id, use_compact, previous)
6906            } else {
6907                let next = side_state.next_template_id.wrapping_add(1).max(1);
6908                side_state.next_template_id = next;
6909                let evicted = side_state.insert_tx_template(
6910                    template.into_owned(),
6911                    next,
6912                    opts.max_side_transport_templates,
6913                );
6914                if evicted {
6915                    st.side_runtime_stats
6916                        .entry(side)
6917                        .or_default()
6918                        .note_side_transport_template_eviction();
6919                }
6920                if let Some(side_state) = st.side_transport.get_mut(&side)
6921                    && opts.max_side_transport_templates > 0
6922                {
6923                    side_state.tx_last_timestamps.insert(next, timestamp);
6924                }
6925                (next, false, None)
6926            }
6927        };
6928        let wrapped = if use_compact {
6929            used_compact = true;
6930            compact_payload_len = Some(payload.len());
6931            // A previous transmitted frame is not a confirmed receive base.
6932            // Absolute timestamps preserve identity after loss or reordering.
6933            let timestamp_field = Some(timestamp);
6934            let mut body = Vec::with_capacity(payload.len() + 32);
6935            body.push(flags);
6936            write_uleb128_local(u64::from(template_id), &mut body);
6937            write_uleb128_local(data_size, &mut body);
6938            if let Some(timestamp_field) = timestamp_field {
6939                write_uleb128_local(timestamp_field, &mut body);
6940            }
6941            if (flags & SIDE_TRANSPORT_FLAG_PACKET_NONCE) != 0 {
6942                write_uleb128_local(u64::from(nonce), &mut body);
6943            }
6944            if let Some((seq, ack)) = reliable_seq_ack {
6945                write_uleb128_local(u64::from(seq), &mut body);
6946                write_uleb128_local(u64::from(ack), &mut body);
6947            }
6948            body.extend_from_slice(payload);
6949            {
6950                let mut st = self.state.lock();
6951                if let Some(side_state) = st.side_transport.get_mut(&side) {
6952                    side_state.tx_last_timestamps.insert(template_id, timestamp);
6953                }
6954            }
6955            let kind = if omitted_timestamp {
6956                SIDE_TRANSPORT_KIND_COMPACT_SAME_TIMESTAMP
6957            } else if used_timestamp_delta {
6958                SIDE_TRANSPORT_KIND_COMPACT_DELTA
6959            } else {
6960                SIDE_TRANSPORT_KIND_COMPACT
6961            };
6962            wrap_side_transport_frame(kind, &body)
6963        } else {
6964            let mut body = Vec::with_capacity(raw.len() + 4);
6965            write_uleb128_local(u64::from(template_id), &mut body);
6966            body.extend_from_slice(raw.as_ref());
6967            wrap_side_transport_frame(SIDE_TRANSPORT_KIND_FULL, &body)
6968        };
6969        let frames = if opts.max_frame_bytes != 0 && wrapped.len() > opts.max_frame_bytes {
6970            self.split_side_transport_frame(side, wrapped, opts.max_frame_bytes)
6971        } else {
6972            Ok(SideTransportFrames::single(wrapped))
6973        }?;
6974        let wire_len = frames.wire_len();
6975        let mut st = self.state.lock();
6976        let stats = st.side_runtime_stats.entry(side).or_default();
6977        if used_compact {
6978            let overhead = compact_payload_len
6979                .map(|payload_len| wire_len.saturating_sub(payload_len))
6980                .unwrap_or(wire_len);
6981            stats.note_side_transport_compact(
6982                raw_len,
6983                wire_len,
6984                overhead,
6985                used_timestamp_delta,
6986                omitted_timestamp,
6987            );
6988            if opts.compact_header_target_bytes != 0 && overhead > opts.compact_header_target_bytes
6989            {
6990                stats.note_side_transport_compact_target_miss();
6991            }
6992        } else {
6993            stats.note_side_transport_full(raw_len, wire_len);
6994        }
6995        if frames.len() > 1 {
6996            stats.note_side_transport_chunks(frames.len());
6997        }
6998        Ok(frames)
6999    }
7000
7001    fn expire_side_chunks(&self) {
7002        let now = self.clock.now_ms();
7003        let mut st = self.state.lock();
7004        for side in st.side_transport.values_mut() {
7005            let side = side.as_mut();
7006            #[cfg(all(feature = "compression", feature = "discovery"))]
7007            side.payload_codec.expire(now, DISCOVERY_ROUTE_TTL_MS);
7008            let reclaimed =
7009                SideChunkAssembly::expire(&mut side.rx_chunks, &mut side.rx_chunk_bytes, now);
7010            side.rx_chunk_reclaims = side.rx_chunk_reclaims.saturating_add(reclaimed);
7011        }
7012    }
7013
7014    // A validated frame from a previously discovered direct peer proves that
7015    // its link is alive, even when allocation admission refuses its payload.
7016    // Update existing timestamps only: no allocation, schema merge or new route.
7017    #[cfg(feature = "discovery")]
7018    fn refresh_ingress_peer_liveness<'a>(
7019        &self,
7020        side: RelaySideId,
7021        bytes: &'a [u8],
7022    ) -> Option<wire_format::RoutingFrameInfo<'a>> {
7023        let frame = wire_format::peek_routing_frame_info(bytes).ok()?;
7024        self.refresh_peer_liveness(side, frame.envelope.source_address);
7025        #[cfg(feature = "compression")]
7026        if frame.envelope.ty == crate::DataType::DiscoveryAddress
7027            && let Ok(Some((_, payload))) =
7028                wire_format::peek_plain_discovery_payload(bytes, crate::DataType::DiscoveryAddress)
7029        {
7030            self.note_payload_codec_address(side, payload, false);
7031        }
7032        Some(frame)
7033    }
7034
7035    #[cfg(feature = "discovery")]
7036    #[cfg_attr(not(feature = "std"), inline(never))]
7037    fn refresh_peer_liveness(&self, side: RelaySideId, address: u32) {
7038        let now = self.clock.now_ms();
7039        let mut st = self.state.lock();
7040        #[cfg(feature = "compression")]
7041        if let Some(transport) = st.side_transport.get_mut(&side) {
7042            transport
7043                .payload_codec
7044                .observe(address, now, DISCOVERY_ROUTE_TTL_MS);
7045        }
7046        let Some(route) = st.discovery_routes.get_mut(&side) else {
7047            return;
7048        };
7049        let mut found = None;
7050        for (name, peer) in &mut route.announcers {
7051            if peer.advertised_address == Some(address) || sender_address_u32(name) == address {
7052                // Ambiguous numeric identities must be resolved by discovery.
7053                if found.is_some() {
7054                    return;
7055                }
7056                found = Some(peer);
7057            }
7058        }
7059        let Some(peer) = found else {
7060            return;
7061        };
7062        peer.last_seen_ms = now;
7063        route.last_seen_ms = route.last_seen_ms.max(now);
7064    }
7065
7066    /// Consume only an exact unsequenced topology retry on its ingress link.
7067    /// CRC, shape, payload length and reliable-header restrictions are checked
7068    /// before this path; it cannot bypass receipts, update a graph or allocate.
7069    #[cfg(feature = "discovery")]
7070    fn consume_known_packed_topology(
7071        &self,
7072        side: RelaySideId,
7073        bytes: &[u8],
7074    ) -> TelemetryResult<bool> {
7075        let Some((address, payload)) =
7076            wire_format::peek_plain_discovery_payload(bytes, crate::DataType::DiscoveryTopology)?
7077        else {
7078            return Ok(false);
7079        };
7080        let now = self.clock.now_ms();
7081        let mut st = self.state.lock();
7082        let Some(route) = st.discovery_routes.get_mut(&side) else {
7083            return Ok(false);
7084        };
7085        let mut found = None;
7086        for (name, peer) in &mut route.announcers {
7087            if peer.advertised_address == Some(address) || sender_address_u32(name) == address {
7088                if found.is_some() {
7089                    return Ok(false);
7090                }
7091                found = Some(peer);
7092            }
7093        }
7094        let Some(peer) = found else {
7095            return Ok(false);
7096        };
7097        let Some(full) =
7098            discovery::topology_payload_matches_retained(payload, &peer.topology_boards)
7099        else {
7100            return Ok(false);
7101        };
7102        peer.has_full_topology |= full;
7103        peer.last_seen_ms = now;
7104        route.last_seen_ms = route.last_seen_ms.max(now);
7105        Ok(true)
7106    }
7107
7108    #[cfg(all(feature = "discovery", not(feature = "std")))]
7109    fn consume_known_packed_schema(
7110        &self,
7111        side: RelaySideId,
7112        bytes: &[u8],
7113    ) -> TelemetryResult<bool> {
7114        let Some((address, payload)) =
7115            wire_format::peek_plain_discovery_payload(bytes, crate::DataType::DiscoverySchema)?
7116        else {
7117            return Ok(false);
7118        };
7119        self.merge_schema_changes(payload)?;
7120        let mut st = self.state.lock();
7121        if let Some(route) = st.discovery_routes.get_mut(&side) {
7122            for (name, peer) in &mut route.announcers {
7123                if peer.advertised_address == Some(address) || sender_address_u32(name) == address {
7124                    peer.has_schema = true;
7125                    break;
7126                }
7127            }
7128        }
7129        Ok(true)
7130    }
7131
7132    #[cfg(all(feature = "compression", feature = "discovery"))]
7133    fn decode_required_schema<T>(
7134        &self,
7135        mut decode: impl FnMut() -> TelemetryResult<T>,
7136    ) -> TelemetryResult<T> {
7137        loop {
7138            match decode() {
7139                Err(error @ TelemetryError::Io("memory pressure")) => {
7140                    if !self.state.lock().reclaim_codec_metadata() {
7141                        return Err(error);
7142                    }
7143                }
7144                result => return result,
7145            }
7146        }
7147    }
7148
7149    #[cfg(all(feature = "discovery", not(feature = "std")))]
7150    #[inline(never)]
7151    fn merge_schema_changes(&self, payload: &[u8]) -> TelemetryResult<()> {
7152        // Validate the entire catalog, then materialize only changed entries.
7153        // Packed and already-decoded ingress share one atomic budgeted merge.
7154        #[cfg(feature = "compression")]
7155        let snapshot = self.decode_required_schema(|| discovery::decode_schema_changes(payload))?;
7156        #[cfg(not(feature = "compression"))]
7157        let snapshot = discovery::decode_schema_changes(payload)?;
7158        let mut st = self.state.lock();
7159        let budget = st.schema_growth_budget();
7160        let report = crate::config::merge_owned_schema_snapshot_with_budget(snapshot, budget)?;
7161        if report.changed() {
7162            st.fit_discovery_budget();
7163        }
7164        Ok(())
7165    }
7166
7167    // A two-sided, fully warmed packed bridge needs no new large metadata.
7168    // Re-check before dispatch: schemas/templates/routes can change while queued.
7169    #[cfg(feature = "discovery")]
7170    fn cached_transit_ready(
7171        &self,
7172        side: RelaySideId,
7173        bytes: &[u8],
7174        view: Option<&wire_format::RoutingFrameInfo<'_>>,
7175    ) -> TelemetryResult<bool> {
7176        // Compressed payloads stay opaque on this fully warmed packed-only
7177        // path. No decompression workspace is needed unless the current schema,
7178        // route or local/managed role changes (all rechecked below).
7179        // Encrypted frames retain their independent conservative admission.
7180        if bytes.len() > 128 || bytes.first().is_none_or(|flags| flags & 0x10 != 0) {
7181            return Ok(false);
7182        }
7183        let parsed;
7184        let frame = match view {
7185            Some(frame) => frame,
7186            None => {
7187                parsed = wire_format::peek_routing_frame_info(bytes)?;
7188                &parsed
7189            }
7190        };
7191        let ty = frame.envelope.ty;
7192        if is_internal_control_type(ty)
7193            || frame.envelope.is_reliable
7194            || frame.reliable.is_some()
7195            || frame.envelope.target_senders().len() != 0
7196            // A matching inline schema is immutable metadata on the existing
7197            // shared frame. It needs no owned decoder state. Unknown or changed
7198            // shapes still require full admission and payload decoding.
7199            || frame.envelope.wire_shape.is_some_and(|shape| {
7200                shape != crate::message_meta(ty).element
7201            })
7202            || !(1..=3).contains(&frame.envelope.endpoints().take(4).count())
7203        {
7204            return Ok(false);
7205        }
7206
7207        // A known peer's bounded canonical frame needs no optional dictionary
7208        // entry. Avoid parsing/hashing that header merely to rediscover this
7209        // fallback. Recheck peer/route state below before granting admission.
7210        let canonical_possible = {
7211            let st = self.state.lock();
7212            st.discovery_routes.get(&side).is_some_and(|route| {
7213                route.announcers.iter().any(|(name, peer)| {
7214                    peer.advertised_address == Some(frame.envelope.source_address)
7215                        || sender_address_u32(name) == frame.envelope.source_address
7216                })
7217            }) && st.sides.iter().flatten().all(|entry| {
7218                entry.opts.max_frame_bytes == 0 || bytes.len() <= entry.opts.max_frame_bytes
7219            })
7220        };
7221        let template_hash = if canonical_possible {
7222            None
7223        } else {
7224            Some(extract_side_header_template_view(bytes)?.0.hash)
7225        };
7226        let st = self.state.lock();
7227        let active: usize = st.sides.iter().filter(|entry| entry.is_some()).count();
7228        if active != 2 {
7229            return Ok(false);
7230        }
7231        for (id, entry) in st
7232            .sides
7233            .iter()
7234            .enumerate()
7235            .filter_map(|(id, entry)| entry.as_ref().map(|e| (id, e)))
7236        {
7237            if entry.opts.reliable_enabled
7238                || !matches!(entry.tx_handler, RelayTxHandlerFn::Packed(_))
7239                || !st
7240                    .side_runtime_stats
7241                    .get(&id)
7242                    .is_some_and(|stats| stats.data_types.contains_key(&ty.as_u32()))
7243            {
7244                return Ok(false);
7245            }
7246            if id == side {
7247                continue;
7248            }
7249            #[cfg(feature = "discovery")]
7250            if !st.discovery_routes.get(&id).is_some_and(|route| {
7251                self.clock.now_ms().saturating_sub(route.last_seen_ms) <= DISCOVERY_ROUTE_TTL_MS
7252                    && frame
7253                        .envelope
7254                        .endpoints()
7255                        .any(|ep| route.reachable.contains(&ep))
7256            }) {
7257                return Ok(false);
7258            }
7259            if entry.opts.header_template_enabled
7260                && entry.opts.max_frame_bytes != 0
7261                && entry.opts.max_side_transport_templates != 0
7262                && !self.side_has_multiple_announcers_locked(&st, id, self.clock.now_ms())
7263                && !template_hash.is_some_and(|hash| {
7264                    st.side_transport.get(&id).is_some_and(|transport| {
7265                        transport.tx_template_ids.contains_key(&hash)
7266                            && transport.tx_templates.contains_key(&hash)
7267                    })
7268                })
7269            {
7270                // An optional compression entry may have been evicted while
7271                // routing/statistics remain warm. Canonical forwarding needs
7272                // no replacement entry, but must fit the transport and belong
7273                // to an already discovered ingress peer.
7274                if (entry.opts.max_frame_bytes == 0 || bytes.len() <= entry.opts.max_frame_bytes)
7275                    && st.discovery_routes.get(&side).is_some_and(|route| {
7276                        route.announcers.iter().any(|(name, peer)| {
7277                            peer.advertised_address == Some(frame.envelope.source_address)
7278                                || sender_address_u32(name) == frame.envelope.source_address
7279                        })
7280                    })
7281                {
7282                    continue;
7283                }
7284                return Ok(false);
7285            }
7286        }
7287        Ok(true)
7288    }
7289    #[cfg(not(feature = "discovery"))]
7290    fn cached_transit_ready(
7291        &self,
7292        _side: RelaySideId,
7293        _bytes: &[u8],
7294        _view: Option<&wire_format::RoutingFrameInfo<'_>>,
7295    ) -> TelemetryResult<bool> {
7296        Ok(false)
7297    }
7298    #[cfg(any(feature = "std", feature = "fair-admission"))]
7299    fn reclaim_for_receive(&self, bytes: &[u8], victim: &mut Option<(u32, u8)>) -> bool {
7300        let Some((incoming, priority)) = crate::admission_fairness::incoming(bytes) else {
7301            return false;
7302        };
7303        self.reclaim_for_incoming(incoming, priority, victim)
7304    }
7305    #[cfg(any(feature = "std", feature = "fair-admission"))]
7306    fn reclaim_for_incoming(
7307        &self,
7308        incoming: u32,
7309        priority: u8,
7310        victim: &mut Option<(u32, u8)>,
7311    ) -> bool {
7312        let mut st = self.state.lock();
7313        let (owner, priority) = if let Some(selected) = *victim {
7314            selected
7315        } else {
7316            let Some(owner) = st.competing_bulk_owner(incoming, priority) else {
7317                return false;
7318            };
7319            *victim = Some((owner, priority));
7320            (owner, priority)
7321        };
7322        st.reclaim_bulk_from(owner, priority, None)
7323    }
7324    fn admit_received_frame(&self, side: RelaySideId, bytes: &[u8]) -> TelemetryResult<()> {
7325        self.admit_received_frame_with_view(side, bytes, None)
7326    }
7327
7328    // Reuse only the view validated in this ingress call. Queue dispatch still
7329    // rechecks bytes and current route/schema state independently.
7330    fn admit_received_frame_with_view(
7331        &self,
7332        side: RelaySideId,
7333        bytes: &[u8],
7334        view: Option<&wire_format::RoutingFrameInfo<'_>>,
7335    ) -> TelemetryResult<()> {
7336        #[cfg(any(feature = "std", feature = "fair-admission"))]
7337        let mut fair_victim = None;
7338        if crate::memory_admission::supports_work_kinds()
7339            && self.cached_transit_ready(side, bytes, view)?
7340        {
7341            let result = crate::memory_admission::check_cached_transit(bytes.len());
7342            #[cfg(all(feature = "compression", feature = "discovery"))]
7343            let result = {
7344                let mut result = result;
7345                while matches!(result, Err(TelemetryError::Io("memory pressure")))
7346                    && self.state.lock().reclaim_codec_metadata()
7347                {
7348                    result = crate::memory_admission::check_cached_transit(bytes.len());
7349                }
7350                result
7351            };
7352
7353            #[cfg(any(feature = "std", feature = "fair-admission"))]
7354            let result = {
7355                let mut result = result;
7356                while matches!(result, Err(TelemetryError::Io("memory pressure"))) {
7357                    if !self.reclaim_for_receive(bytes, &mut fair_victim) {
7358                        break;
7359                    }
7360                    result = crate::memory_admission::check_cached_transit(bytes.len());
7361                }
7362                result
7363            };
7364            result
7365        } else {
7366            let mut protected = None;
7367            loop {
7368                match crate::memory_admission::check_receive(bytes) {
7369                    Err(TelemetryError::Io("memory pressure")) => {
7370                        #[cfg(all(feature = "compression", feature = "discovery"))]
7371                        if self.state.lock().reclaim_codec_metadata() {
7372                            continue;
7373                        }
7374
7375                        let protected = *protected.get_or_insert_with(|| {
7376                            wire_format::peek_routing_frame_info(bytes)
7377                                .ok()
7378                                .is_some_and(|frame| {
7379                                    message_priority(frame.envelope.ty) >= 200
7380                                        || crate::small_frozen_control(
7381                                            frame.envelope.ty,
7382                                            frame.envelope.target_senders().len(),
7383                                            frame.envelope.wire_shape,
7384                                        )
7385                                })
7386                        });
7387                        if !protected {
7388                            #[cfg(any(feature = "std", feature = "fair-admission"))]
7389                            if self.reclaim_for_receive(bytes, &mut fair_victim) {
7390                                continue;
7391                            }
7392                            return Err(TelemetryError::Io("memory pressure"));
7393                        }
7394                        // Queue priority cannot help before the wire copy is
7395                        // admitted. Reclaim bulk payloads, preserving control
7396                        // traffic and every learned route/schema.
7397                        let mut st = self.state.lock();
7398                        let reclaimed = st.pop_lowest_packet_queue_item(200);
7399                        if !reclaimed {
7400                            return Err(TelemetryError::Io("memory pressure"));
7401                        }
7402                    }
7403                    result => return result,
7404                }
7405            }
7406        }
7407    }
7408
7409    #[cfg(feature = "discovery")]
7410    fn consume_known_packed_discovery(
7411        &self,
7412        side: RelaySideId,
7413        bytes: &[u8],
7414    ) -> TelemetryResult<bool> {
7415        // Ordinary telemetry pays one bounded type peek, not a parse for each
7416        // discovery subtype. The selected consumer still checks CRC and shape.
7417        match wire_format::scheduling_type(bytes) {
7418            #[cfg(not(feature = "std"))]
7419            Some(crate::DataType::DiscoverySchema) => self.consume_known_packed_schema(side, bytes),
7420            Some(crate::DataType::DiscoveryTopology) => {
7421                self.consume_known_packed_topology(side, bytes)
7422            }
7423            Some(crate::DataType::DiscoveryAddress) => {
7424                let Some((_, payload)) = wire_format::peek_plain_discovery_payload(
7425                    bytes,
7426                    crate::DataType::DiscoveryAddress,
7427                )?
7428                else {
7429                    return Ok(false);
7430                };
7431                // Transparent relays must still forward allocation/address
7432                // advertisements for far-side collision arbitration. Refresh
7433                // retained liveness now, but keep normal owned queue admission.
7434                self.refresh_known_address_payload(payload, side)?;
7435                Ok(false)
7436            }
7437            _ => Ok(false),
7438        }
7439    }
7440
7441    fn decode_side_transport_frame(
7442        &self,
7443        side: RelaySideId,
7444        bytes: &[u8],
7445    ) -> TelemetryResult<Option<crate::SharedBytes>> {
7446        self.expire_side_chunks();
7447        let Some((kind, body)) = parse_side_transport_wrapper(bytes)? else {
7448            #[cfg(feature = "discovery")]
7449            let view = self.refresh_ingress_peer_liveness(side, bytes);
7450            #[cfg(not(feature = "discovery"))]
7451            let view = None;
7452            #[cfg(feature = "discovery")]
7453            if self.consume_known_packed_discovery(side, bytes)? {
7454                return Ok(None);
7455            }
7456            self.admit_received_frame_with_view(side, bytes, view.as_ref())?;
7457            return Ok(Some(crate::shared_bytes::convert(Arc::<[u8]>::from(bytes))));
7458        };
7459        match kind {
7460            SIDE_TRANSPORT_KIND_FULL => {
7461                let mut off = 0usize;
7462                let template_id = u32::try_from(read_uleb128_local(body, &mut off)?)
7463                    .map_err(|_| TelemetryError::Unpack("side template id too large"))?;
7464                #[cfg(feature = "discovery")]
7465                let view = self.refresh_ingress_peer_liveness(side, &body[off..]);
7466                #[cfg(not(feature = "discovery"))]
7467                let view = None;
7468                #[cfg(feature = "discovery")]
7469                if self.consume_known_packed_discovery(side, &body[off..])? {
7470                    return Ok(None);
7471                }
7472                self.admit_received_frame_with_view(side, &body[off..], view.as_ref())?;
7473                let raw = Arc::<[u8]>::from(&body[off..]);
7474                if let Ok((template, _, _, _, timestamp, _, _, _)) =
7475                    extract_side_header_template(raw.as_ref())
7476                {
7477                    let mut st = self.state.lock();
7478                    let max_templates = st
7479                        .sides
7480                        .get(side)
7481                        .and_then(|side| side.as_ref())
7482                        .map(|side| side.opts.max_side_transport_templates)
7483                        .unwrap_or(DEFAULT_SIDE_TRANSPORT_TEMPLATE_LIMIT);
7484                    let evicted = st.side_transport.get_mut(&side).is_some_and(|side_state| {
7485                        let evicted =
7486                            side_state.insert_rx_template(template_id, template, max_templates);
7487                        if max_templates > 0 {
7488                            side_state.rx_last_timestamps.insert(template_id, timestamp);
7489                        }
7490                        evicted
7491                    });
7492                    if evicted {
7493                        st.side_runtime_stats
7494                            .entry(side)
7495                            .or_default()
7496                            .note_side_transport_template_eviction();
7497                    }
7498                }
7499                Ok(Some(crate::shared_bytes::convert(raw)))
7500            }
7501            SIDE_TRANSPORT_KIND_COMPACT
7502            | SIDE_TRANSPORT_KIND_COMPACT_DELTA
7503            | SIDE_TRANSPORT_KIND_COMPACT_SAME_TIMESTAMP => {
7504                if body.is_empty() {
7505                    return Err(TelemetryError::Unpack("short side compact frame"));
7506                }
7507                let mut off = 1usize;
7508                let template_id = u32::try_from(read_uleb128_local(body, &mut off)?)
7509                    .map_err(|_| TelemetryError::Unpack("side template id too large"))?;
7510                let (template, timestamp_base) = {
7511                    let st = self.state.lock();
7512                    let state = st.side_transport.get(&side);
7513                    let template = state
7514                        .and_then(|state| state.rx_templates_by_id.get(&template_id))
7515                        .cloned();
7516                    let timestamp_base = if matches!(
7517                        kind,
7518                        SIDE_TRANSPORT_KIND_COMPACT_DELTA
7519                            | SIDE_TRANSPORT_KIND_COMPACT_SAME_TIMESTAMP
7520                    ) {
7521                        state
7522                            .and_then(|state| state.rx_last_timestamps.get(&template_id))
7523                            .copied()
7524                    } else {
7525                        None
7526                    };
7527                    (template, timestamp_base)
7528                };
7529                let template =
7530                    template.ok_or(TelemetryError::Unpack("unknown side compact template"))?;
7531                // The wrapper CRC and known template identify this peer even
7532                // if payload allocation is refused. No payload copy is needed.
7533                #[cfg(feature = "discovery")]
7534                if let Some(address) = template.source_address() {
7535                    self.refresh_peer_liveness(side, address);
7536                }
7537                let reconstructed_bound = body
7538                    .len()
7539                    .saturating_add(template.prefix().len())
7540                    .saturating_add(template.between().len())
7541                    .saturating_add(32);
7542                // Wrapper CRC and the retained full template are validated
7543                // before compact reconstruction. Preserve the same control
7544                // priority as canonical ingress while reclaiming only bulk.
7545                let protected = template
7546                    .data_type()
7547                    .is_some_and(|ty| message_priority(ty) >= 200);
7548                let ack = template.is_ack();
7549                #[cfg(any(feature = "std", feature = "fair-admission"))]
7550                let mut fair_incoming = None;
7551                #[cfg(any(feature = "std", feature = "fair-admission"))]
7552                let mut fair_victim = None;
7553                loop {
7554                    match crate::memory_admission::check_compact_reconstruction(
7555                        reconstructed_bound,
7556                        ack,
7557                    ) {
7558                        Err(TelemetryError::Io("memory pressure")) if protected => {
7559                            let mut st = self.state.lock();
7560                            let reclaimed = st.pop_lowest_packet_queue_item(200);
7561                            if !reclaimed {
7562                                return Err(TelemetryError::Io("memory pressure"));
7563                            }
7564                        }
7565                        Err(TelemetryError::Io("memory pressure")) => {
7566                            // Compact reports need the same fairness before
7567                            // allocation as canonical ingress. Otherwise they
7568                            // never reach the canonical admission/reclaim path.
7569                            #[cfg(any(feature = "std", feature = "fair-admission"))]
7570                            if let Some((incoming, priority)) =
7571                                *fair_incoming.get_or_insert_with(|| {
7572                                    crate::admission_fairness::compact_incoming(&template)
7573                                })
7574                                && self.reclaim_for_incoming(incoming, priority, &mut fair_victim)
7575                            {
7576                                continue;
7577                            }
7578                            return Err(TelemetryError::Io("memory pressure"));
7579                        }
7580                        result => {
7581                            result?;
7582                            break;
7583                        }
7584                    }
7585                }
7586                let timestamp_mode = match kind {
7587                    SIDE_TRANSPORT_KIND_COMPACT_DELTA => SideCompactTimestampMode::Delta,
7588                    SIDE_TRANSPORT_KIND_COMPACT_SAME_TIMESTAMP => SideCompactTimestampMode::Omitted,
7589                    _ => SideCompactTimestampMode::Absolute,
7590                };
7591                let (frame, timestamp) = reconstruct_side_compact_frame(
7592                    &template,
7593                    body[0],
7594                    &body[off..],
7595                    timestamp_mode,
7596                    timestamp_base,
7597                )?;
7598                {
7599                    let mut st = self.state.lock();
7600                    if let Some(side_state) = st.side_transport.get_mut(&side) {
7601                        side_state.rx_last_timestamps.insert(template_id, timestamp);
7602                    }
7603                }
7604                // Byte reconstruction is not decoder/forwarding admission.
7605                // Check the canonical frame before queueing, and recheck at
7606                // dispatch if routes, schemas or available memory have changed.
7607                if crate::memory_admission::supports_work_kinds() {
7608                    self.admit_received_frame(side, frame.as_ref())?;
7609                }
7610                Ok(Some(frame))
7611            }
7612            SIDE_TRANSPORT_KIND_CHUNK => {
7613                if body.len() < 8 {
7614                    return Err(TelemetryError::Unpack("short side chunk frame"));
7615                }
7616                let transfer_id = u32::from_le_bytes([body[0], body[1], body[2], body[3]]);
7617                let index = u16::from_le_bytes([body[4], body[5]]);
7618                let total = u16::from_le_bytes([body[6], body[7]]);
7619                if total == 0 || index >= total {
7620                    return Err(TelemetryError::Unpack("invalid side chunk index"));
7621                }
7622                let assembled = {
7623                    let mut st = self.state.lock();
7624                    let now_ms = self.clock.now_ms();
7625                    st.make_side_chunk_room(side, transfer_id, index, total, body.len() - 8)?;
7626                    // A CRC-validated retry of a retained fragment needs no
7627                    // allocation. Do not discard other traffic or reject the
7628                    // retry because unrelated work consumed the idle reserve.
7629                    if let Some(entry) = st
7630                        .side_transport
7631                        .get_mut(&side)
7632                        .and_then(|state| state.rx_chunks.get_mut(&transfer_id))
7633                        && entry.contains_index(index)
7634                    {
7635                        // Retries are not progress: only new fragments extend
7636                        // the timeout of a retained, incomplete transfer.
7637                        return Ok(None);
7638                    }
7639                    // Budget only insertion into an existing bounded tree.
7640                    // The full packet has independent assembly/decode guards.
7641                    let retained = st
7642                        .side_transport
7643                        .get(&side)
7644                        .and_then(|state| state.rx_chunks.get(&transfer_id))
7645                        .map(|entry| entry.received_len());
7646                    loop {
7647                        match crate::memory_admission::check_retained_fragment(bytes, retained) {
7648                            Err(TelemetryError::Io("memory pressure")) => {
7649                                if st.pop_lowest_packet_queue_item(200)
7650                                    || st.reclaim_oldest_side_chunk(Some((side, transfer_id)))
7651                                {
7652                                    continue;
7653                                }
7654                                return Err(TelemetryError::Io("memory pressure"));
7655                            }
7656                            result => {
7657                                result?;
7658                                break;
7659                            }
7660                        }
7661                    }
7662                    let side_state = st
7663                        .side_transport
7664                        .get_mut(&side)
7665                        .ok_or(TelemetryError::BadArg)?;
7666                    let entry = side_state.rx_chunks.entry(transfer_id).or_default();
7667                    entry.total = total;
7668                    let added =
7669                        entry.insert_with(index, now_ms, || Arc::<[u8]>::from(&body[8..]))?;
7670                    side_state.rx_chunk_bytes = side_state.rx_chunk_bytes.saturating_add(added);
7671                    if entry.received_len() == usize::from(total) {
7672                        let entry = side_state
7673                            .take_chunk(transfer_id, false)
7674                            .ok_or(TelemetryError::Unpack("side chunk missing"))?;
7675                        Some(entry.assemble().inspect_err(|_| {
7676                            side_state.rx_chunk_reclaims =
7677                                side_state.rx_chunk_reclaims.saturating_add(1);
7678                        })?)
7679                    } else {
7680                        None
7681                    }
7682                };
7683                match assembled {
7684                    Some(frame) => self.decode_side_transport_frame(side, frame.as_ref()),
7685                    None => Ok(None),
7686                }
7687            }
7688            _ => Err(TelemetryError::Unpack("unknown side transport frame")),
7689        }
7690    }
7691
7692    /// Helper: call a TX handler with the best representation we have.
7693    /// - Packet handler + Packet item: direct.
7694    /// - Packed handler + Packed item: direct.
7695    /// - Packet handler + Packed item: unpack for this call.
7696    /// - Packed handler + Packet item: pack for this call.
7697    fn call_tx_handler(
7698        &self,
7699        side: RelaySideId,
7700        handler: &RelayTxHandlerFn,
7701        data: &RelayItem,
7702    ) -> TelemetryResult<()> {
7703        let opts = {
7704            let st = self.state.lock();
7705            Self::side_ref(&st, side)?.opts.clone()
7706        };
7707        let Some(_side_tx_guard) = self.try_enter_side_tx(side) else {
7708            return Err(TelemetryError::Io("side tx busy"));
7709        };
7710        let started_ms = self.clock.now_ms();
7711        let ty = match data {
7712            RelayItem::Packet(pkt) => pkt.data_type(),
7713            RelayItem::Packed(bytes) => wire_format::peek_routing_envelope(bytes.as_ref())?.ty,
7714        };
7715        #[cfg(all(feature = "discovery", feature = "timesync"))]
7716        if !Self::timesync_allowed_for_side_locked(
7717            &self.state.lock(),
7718            side,
7719            ty,
7720            self.clock.now_ms(),
7721        ) {
7722            return Ok(());
7723        }
7724        let result = match (handler, data) {
7725            // Fast paths
7726            (RelayTxHandlerFn::Packed(f), RelayItem::Packed(bytes)) => {
7727                let frames = self.encode_side_transport_frames(side, opts, bytes.clone())?;
7728                let mut sent_bytes = 0usize;
7729                self.send_side_frames_resumable(side, frames, |frame| {
7730                    f(frame)?;
7731                    sent_bytes = sent_bytes.saturating_add(frame.len());
7732
7733                    Ok(())
7734                })?;
7735                self.record_side_tx_sample(side, sent_bytes, started_ms, self.clock.now_ms());
7736                self.note_side_tx_success(side, ty, sent_bytes, 1);
7737                return Ok(());
7738            }
7739            (RelayTxHandlerFn::Packet(f), RelayItem::Packet(pkt)) => f(pkt),
7740
7741            // Conversion paths
7742            (RelayTxHandlerFn::Packed(f), RelayItem::Packet(pkt)) => {
7743                if ty == crate::DataType::DiscoverySchema
7744                    && opts.max_frame_bytes != 0
7745                    && pkt.payload().len() > opts.max_frame_bytes
7746                {
7747                    let key = crate::packet::hash_bytes_u64(
7748                        pkt.packet_id(),
7749                        &opts.max_frame_bytes.to_le_bytes(),
7750                    );
7751                    let result = self.run_side_chunk_transfer(side, key, |next| {
7752                        crate::side_transport::send_packet_chunks(
7753                            pkt,
7754                            opts.max_frame_bytes,
7755                            self.sender_arc().as_bytes(),
7756                            next,
7757                            |frame| f(frame),
7758                        )
7759                    });
7760                    let (raw_len, wire_len, chunks) = match result {
7761                        Ok(stats) => stats,
7762                        Err(err) => {
7763                            self.note_side_tx_failure(side, ty, 1);
7764                            return Err(err);
7765                        }
7766                    };
7767                    {
7768                        let mut st = self.state.lock();
7769                        let stats = st.side_runtime_stats.entry(side).or_default();
7770                        stats.note_side_transport_full(raw_len, wire_len);
7771                        if chunks > 1 {
7772                            stats.note_side_transport_chunks(chunks);
7773                        }
7774                    }
7775                    self.record_side_tx_sample(side, wire_len, started_ms, self.clock.now_ms());
7776                    self.note_side_tx_success(side, ty, wire_len, 1);
7777                    return Ok(());
7778                }
7779                #[cfg(feature = "compression")]
7780                let owned = self.pack_relay_packet(pkt, None)?;
7781                #[cfg(not(feature = "compression"))]
7782                let owned = wire_format::pack_packet(pkt);
7783                let frames = self.encode_side_transport_frames(
7784                    side,
7785                    opts.clone(),
7786                    crate::shared_bytes::convert(owned),
7787                )?;
7788                let mut sent_bytes = 0usize;
7789                self.send_side_frames_resumable(side, frames, |frame| {
7790                    f(frame)?;
7791                    sent_bytes = sent_bytes.saturating_add(frame.len());
7792
7793                    Ok(())
7794                })?;
7795                self.record_side_tx_sample(side, sent_bytes, started_ms, self.clock.now_ms());
7796                self.note_side_tx_success(side, ty, sent_bytes, 1);
7797                return Ok(());
7798            }
7799            (RelayTxHandlerFn::Packet(f), RelayItem::Packed(bytes)) => {
7800                if wire_format::peek_routing_frame_info(bytes.as_ref())
7801                    .ok()
7802                    .is_some_and(|frame| frame.ack_only())
7803                {
7804                    return Ok(());
7805                }
7806                let pkt = wire_format::unpack_packet(bytes.as_ref())?;
7807                f(&pkt)
7808            }
7809        };
7810        if result.is_ok()
7811            && let Ok(bytes) = Self::relay_item_wire_len(data)
7812        {
7813            self.record_side_tx_sample(side, bytes, started_ms, self.clock.now_ms());
7814            self.note_side_tx_success(side, ty, bytes, 1);
7815        } else if result.is_err() {
7816            self.note_side_tx_failure(side, ty, 1);
7817        }
7818        result
7819    }
7820
7821    fn adjust_reliable_for_side(
7822        &self,
7823        opts: RelaySideOptions,
7824        data: RelayItem,
7825    ) -> TelemetryResult<Option<RelayItem>> {
7826        if opts.reliable_enabled {
7827            return Ok(Some(data));
7828        }
7829
7830        match data {
7831            RelayItem::Packed(bytes) => {
7832                let frame = match wire_format::peek_routing_frame_info(bytes.as_ref()) {
7833                    Ok(frame) => frame,
7834                    Err(_) => return Ok(Some(RelayItem::Packed(bytes))),
7835                };
7836                if is_reliable_type(frame.envelope.ty)
7837                    && let Some(hdr) = frame.reliable
7838                {
7839                    if (hdr.flags & wire_format::RELIABLE_FLAG_ACK_ONLY) != 0 {
7840                        return Ok(None);
7841                    }
7842                    if (hdr.flags & wire_format::RELIABLE_FLAG_UNSEQUENCED) == 0 {
7843                        let Some(rewritten) = wire_format::rewrite_reliable_header_owned(
7844                            bytes.as_ref(),
7845                            wire_format::RELIABLE_FLAG_UNSEQUENCED,
7846                            hdr.seq,
7847                            0,
7848                        )?
7849                        else {
7850                            return Ok(Some(RelayItem::Packed(bytes)));
7851                        };
7852                        return Ok(Some(RelayItem::Packed(crate::shared_bytes::convert(
7853                            rewritten,
7854                        ))));
7855                    }
7856                }
7857                Ok(Some(RelayItem::Packed(bytes)))
7858            }
7859            RelayItem::Packet(pkt) => {
7860                if matches!(
7861                    pkt.data_type(),
7862                    crate::DataType::ReliableAck
7863                        | crate::DataType::ReliablePartialAck
7864                        | crate::DataType::ReliablePacketRequest
7865                ) {
7866                    // Hop reliability can be disabled on a physical bridge,
7867                    // but the publisher's end-to-end ACK must still cross it.
7868                    if Self::end_to_end_ack_sender_hash(&pkt).is_some()
7869                        && Self::decode_end_to_end_reliable_ack(pkt.payload()).is_ok()
7870                    {
7871                        return Ok(Some(RelayItem::Packet(pkt)));
7872                    }
7873                    return Ok(None);
7874                }
7875                Ok(Some(RelayItem::Packet(pkt)))
7876            }
7877        }
7878    }
7879
7880    /// Drain the RX queue fully, expanding to TX items.
7881    #[inline]
7882    pub fn process_rx_queue(&self) -> TelemetryResult<()> {
7883        self.process_rx_queue_with_timeout(0)
7884    }
7885
7886    /// Drain the TX queue fully, invoking per-side TX handlers.
7887    ///
7888    /// If called from inside a side TX callback, this becomes a no-op so relay TX handlers cannot
7889    /// recurse into nested queue drains on the same stack.
7890    #[inline]
7891    pub fn process_tx_queue(&self) -> TelemetryResult<()> {
7892        self.process_tx_queue_with_timeout(0)
7893    }
7894
7895    /// Drain RX then TX queues fully (one pass).
7896    #[inline]
7897    pub fn process_all_queues(&self) -> TelemetryResult<()> {
7898        self.process_all_queues_with_timeout(0)
7899    }
7900
7901    /// Process the TX queue for up to `timeout_ms` milliseconds.
7902    ///
7903    /// `timeout_ms == 0` drains fully. If called from inside a side TX callback, this becomes a
7904    /// no-op so relay TX handlers cannot recurse into nested queue drains on the same stack.
7905    pub fn process_tx_queue_with_timeout(&self, timeout_ms: u32) -> TelemetryResult<()> {
7906        self.expire_side_chunks();
7907        if self.side_tx_active() {
7908            return Ok(());
7909        }
7910        #[cfg(feature = "discovery")]
7911        {
7912            let _ = crate::memory_admission::allow_drain(self.poll_discovery())?;
7913        }
7914        let start = self.clock.now_ms();
7915        loop {
7916            let received_control = crate::memory_admission::allow_drain(
7917                self.drain_queued_receipts_before_timeouts(start, timeout_ms),
7918            )?;
7919            let control_budget_used = received_control
7920                && timeout_ms != 0
7921                && self.clock.now_ms().wrapping_sub(start) >= u64::from(timeout_ms);
7922            if !control_budget_used {
7923                crate::memory_admission::allow_drain(self.process_reliable_timeouts())?;
7924            }
7925            if !control_budget_used && self.process_replay_queue_item()? {
7926                if timeout_ms != 0 && self.clock.now_ms().wrapping_sub(start) >= timeout_ms as u64 {
7927                    break;
7928                }
7929                continue;
7930            }
7931            let Some((src, dst, handler, opts, data, priority)) = self.pop_ready_tx_item() else {
7932                break;
7933            };
7934            #[cfg(feature = "compression")]
7935            let mut data = data;
7936            #[cfg(feature = "compression")]
7937            let prepared = self.prepare_queued_payload(dst, &mut data);
7938            #[cfg(feature = "compression")]
7939            let (send_result, mut reservation) = match prepared {
7940                Ok(reservation) => (
7941                    self.send_tx_item(src, dst, handler, opts, data.clone()),
7942                    reservation,
7943                ),
7944                Err(error) => (Err(error), None),
7945            };
7946            #[cfg(not(feature = "compression"))]
7947            let send_result = self.send_tx_item(src, dst, handler, opts, data.clone());
7948            match send_result {
7949                Ok(sent) => {
7950                    if sent
7951                        && timeout_ms != 0
7952                        && self.clock.now_ms().wrapping_sub(start) >= timeout_ms as u64
7953                    {
7954                        break;
7955                    }
7956                }
7957                Err(e) if Self::is_transient_tx_error(&e) => {
7958                    let mut st = self.state.lock();
7959                    #[cfg(feature = "compression")]
7960                    if let Some(reservation) = &mut reservation {
7961                        reservation.release_locked(&mut st);
7962                    }
7963                    st.push_tx(RelayTxItem {
7964                        src,
7965                        dst,
7966                        data,
7967                        priority,
7968                        retry_after_ms: self.clock.now_ms().saturating_add(1),
7969                    })?;
7970                    continue;
7971                }
7972                Err(e) => return Err(e),
7973            }
7974        }
7975        Ok(())
7976    }
7977
7978    /// Process RX queue with timeout.
7979    pub fn process_rx_queue_with_timeout(&self, timeout_ms: u32) -> TelemetryResult<()> {
7980        self.expire_side_chunks();
7981        #[cfg(feature = "discovery")]
7982        {
7983            let _ = crate::memory_admission::allow_drain(self.poll_discovery())?;
7984        }
7985        let start = self.clock.now_ms();
7986        loop {
7987            let item_opt = {
7988                let mut st = self.state.lock();
7989                st.pop_received_fair()
7990            };
7991            let Some(item) = item_opt else { break };
7992            self.process_rx_queue_item(item)?;
7993
7994            if timeout_ms != 0 && self.clock.now_ms().wrapping_sub(start) >= timeout_ms as u64 {
7995                break;
7996            }
7997        }
7998        Ok(())
7999    }
8000
8001    /// Process RX and TX queues interleaved for up to `timeout_ms` milliseconds.
8002    ///
8003    /// `timeout_ms == 0` drains fully. If called from inside a side TX callback, this becomes a
8004    /// no-op so relay TX handlers cannot recurse into nested queue drains on the same stack.
8005    pub fn process_all_queues_with_timeout(&self, timeout_ms: u32) -> TelemetryResult<()> {
8006        if self.side_tx_active() {
8007            return Ok(());
8008        }
8009        #[cfg(feature = "discovery")]
8010        let _ = crate::memory_admission::allow_drain(self.poll_discovery())?;
8011        self.drain_queues_with_timeout(timeout_ms)
8012    }
8013
8014    /// Drain RX/TX work without generating periodic discovery announcements.
8015    /// Reliable retries remain active. Call `periodic` or `poll_discovery`
8016    /// separately on a regular cadence. Nested side callbacks remain a no-op.
8017    pub fn drain_queues_with_timeout(&self, timeout_ms: u32) -> TelemetryResult<()> {
8018        self.expire_side_chunks();
8019        if self.side_tx_active() {
8020            return Ok(());
8021        }
8022        let drain_fully = timeout_ms == 0;
8023        let start = if drain_fully { 0 } else { self.clock.now_ms() };
8024
8025        loop {
8026            let mut did_any;
8027            let received_control = crate::memory_admission::allow_drain(
8028                self.drain_queued_receipts_before_timeouts(start, timeout_ms),
8029            )?;
8030            did_any = received_control;
8031            let control_budget_used = received_control
8032                && timeout_ms != 0
8033                && self.clock.now_ms().wrapping_sub(start) >= u64::from(timeout_ms);
8034            if !control_budget_used {
8035                crate::memory_admission::allow_drain(self.process_reliable_timeouts())?;
8036            }
8037
8038            // One TX/RX pair is atomic for fairness under a short budget.
8039            if !control_budget_used
8040                && let Some(item) = {
8041                    let mut st = self.state.lock();
8042                    st.pop_received_fair()
8043                }
8044            {
8045                self.process_rx_queue_item(item)?;
8046                did_any = true;
8047            }
8048
8049            if !control_budget_used && self.process_replay_queue_item()? {
8050                did_any = true;
8051            }
8052
8053            // Then send out TX
8054            let sent_one =
8055                if let Some((src, dst, handler, opts, data, priority)) = self.pop_ready_tx_item() {
8056                    #[cfg(feature = "compression")]
8057                    let mut data = data;
8058                    #[cfg(feature = "compression")]
8059                    let prepared = self.prepare_queued_payload(dst, &mut data);
8060                    #[cfg(feature = "compression")]
8061                    let (send_result, mut reservation) = match prepared {
8062                        Ok(reservation) => (
8063                            self.send_tx_item(src, dst, handler, opts, data.clone()),
8064                            reservation,
8065                        ),
8066                        Err(error) => (Err(error), None),
8067                    };
8068                    #[cfg(not(feature = "compression"))]
8069                    let send_result = self.send_tx_item(src, dst, handler, opts, data.clone());
8070                    match send_result {
8071                        Ok(sent) => sent,
8072                        Err(error) if Self::is_transient_tx_error(&error) => {
8073                            let mut state = self.state.lock();
8074                            #[cfg(feature = "compression")]
8075                            if let Some(reservation) = &mut reservation {
8076                                reservation.release_locked(&mut state);
8077                            }
8078                            state.push_tx(RelayTxItem {
8079                                src,
8080                                dst,
8081                                data,
8082                                priority,
8083                                retry_after_ms: self.clock.now_ms().saturating_add(1),
8084                            })?;
8085                            did_any = true;
8086                            false
8087                        }
8088                        Err(error) => return Err(error),
8089                    }
8090                } else {
8091                    false
8092                };
8093
8094            if sent_one {
8095                did_any = true;
8096            }
8097
8098            if !drain_fully && self.clock.now_ms().wrapping_sub(start) >= timeout_ms as u64 {
8099                break;
8100            }
8101
8102            if !did_any {
8103                break;
8104            }
8105        }
8106
8107        Ok(())
8108    }
8109
8110    /// Runs one application-loop maintenance cycle.
8111    ///
8112    /// This polls built-in discovery when that feature is compiled in, then drains queued RX/TX
8113    /// work for up to `timeout_ms` milliseconds.
8114    pub fn periodic(&self, timeout_ms: u32) -> TelemetryResult<()> {
8115        #[cfg(feature = "discovery")]
8116        {
8117            let _ = crate::memory_admission::allow_drain(self.poll_discovery())?;
8118        }
8119
8120        self.drain_queues_with_timeout(timeout_ms)
8121    }
8122}
8123
8124#[cfg(all(test, feature = "std", feature = "discovery"))]
8125mod discovery_reclaim_tests {
8126    use super::*;
8127    use crate::DataType;
8128    use core::sync::atomic::{AtomicUsize, Ordering};
8129    #[test]
8130    fn relay_discovery_reclaims_telemetry_without_discarding_controls() {
8131        crate::tests::ensure_common_test_schema();
8132        static REFUSALS: AtomicUsize = AtomicUsize::new(0);
8133        extern "C" fn scratch_after_reclaim(additional: usize, _: usize) -> bool {
8134            additional < 2048 || REFUSALS.fetch_add(1, Ordering::SeqCst) >= 2
8135        }
8136        struct Reset;
8137        impl Drop for Reset {
8138            fn drop(&mut self) {
8139                crate::memory_admission::set_probe(None);
8140            }
8141        }
8142        let relay = Relay::new(Box::new(|| 1));
8143        let side = relay.add_side_packet("CAN", |_| Ok(()));
8144        for priority in [1, 2, 200, 254, 255] {
8145            relay
8146                .state
8147                .lock()
8148                .push_tx(RelayTxItem {
8149                    src: None,
8150                    dst: side,
8151                    data: RelayItem::Packed(crate::shared_bytes::convert(Arc::<[u8]>::from(
8152                        [0; 32],
8153                    ))),
8154                    priority,
8155                    retry_after_ms: 0,
8156                })
8157                .unwrap();
8158        }
8159        REFUSALS.store(0, Ordering::SeqCst);
8160        let _reset = Reset;
8161        crate::memory_admission::set_probe(Some(scratch_after_reclaim));
8162        relay
8163            .queue_discovery_announce_on_side(false, true, None)
8164            .unwrap();
8165        let st = relay.state.lock();
8166        assert_eq!(st.tx_queue.lowest_priority(|item| item.priority), Some(200));
8167        for priority in [200, 254] {
8168            assert_eq!(
8169                st.tx_queue
8170                    .iter()
8171                    .filter(|item| item.priority == priority)
8172                    .count(),
8173                1
8174            );
8175        }
8176        assert!(
8177            st.tx_queue
8178                .iter()
8179                .any(|item| matches!(&item.data, RelayItem::Packet(pkt)
8180            if pkt.data_type() == DataType::DiscoveryLinkCapabilities))
8181        );
8182    }
8183}
8184
8185#[cfg(all(test, feature = "std", feature = "discovery"))]
8186mod queued_schema_identity_tests {
8187    use super::*;
8188
8189    fn catalog(timestamp: u64, byte: u8) -> Packet {
8190        Packet::new(
8191            crate::DataType::DiscoverySchema,
8192            &[crate::DataEndpoint::Discovery],
8193            "SCHEMA_PEER",
8194            timestamp,
8195            crate::shared_bytes::convert(Arc::<[u8]>::from(vec![byte; 1024])),
8196        )
8197        .unwrap()
8198    }
8199
8200    #[test]
8201    fn repeated_schema_preserves_partial_transfer_but_updates_and_other_sides_do_not_coalesce() {
8202        let relay = Relay::new(Box::new(|| 0u64));
8203        let side = relay.add_side_packed("A", |_| Ok(()));
8204        let other_side = relay.add_side_packed("B", |_| Ok(()));
8205        let original = catalog(1, 42);
8206        let duplicate = catalog(2, 42);
8207        assert_ne!(original.packet_id(), duplicate.packet_id());
8208        let mut st = relay.state.lock();
8209        st.tx_queue.clear();
8210        let queued = |dst, pkt| RelayTxItem {
8211            src: None,
8212            dst,
8213            data: RelayItem::Packet(Arc::new(pkt)),
8214            priority: 255,
8215            retry_after_ms: 123,
8216        };
8217        st.push_tx(queued(side, original.clone())).unwrap();
8218        st.push_tx(queued(side, duplicate.clone())).unwrap();
8219        assert_eq!(st.tx_queue.len(), 1);
8220        let pending = st.tx_queue.iter().next().unwrap();
8221        assert_eq!(pending.retry_after_ms, 123);
8222        assert!(matches!(&pending.data, RelayItem::Packet(pkt)
8223            if pkt.packet_id() == original.packet_id()));
8224        st.push_tx(queued(other_side, duplicate)).unwrap();
8225        assert_eq!(st.tx_queue.len(), 2);
8226        let changed = catalog(3, 43);
8227        st.push_tx(queued(side, changed.clone())).unwrap();
8228        assert_eq!(st.tx_queue.len(), 2);
8229        assert!(st.tx_queue.iter().any(|queued| queued.dst == side
8230            && matches!(&queued.data,
8231            RelayItem::Packet(pkt) if pkt.packet_id() == changed.packet_id())));
8232    }
8233
8234    #[test]
8235    fn repeated_announcements_complete_one_fragment_per_dispatch() {
8236        use core::sync::atomic::{AtomicUsize, Ordering};
8237        let allowance = Arc::new(AtomicUsize::new(0));
8238        let accepted = Arc::new(std::sync::Mutex::new(Vec::<Arc<[u8]>>::new()));
8239        let available = allowance.clone();
8240        let frames = accepted.clone();
8241        let node = Relay::new(Box::new(|| 1u64));
8242        let side = node.add_side_packed_with_options(
8243            "bounded CAN",
8244            move |bytes| {
8245                if available
8246                    .try_update(Ordering::Relaxed, Ordering::Relaxed, |count| {
8247                        count.checked_sub(1)
8248                    })
8249                    .is_err()
8250                {
8251                    return Err(TelemetryError::Io("side tx busy"));
8252                }
8253                frames.lock().unwrap().push(Arc::<[u8]>::from(bytes));
8254                Ok(())
8255            },
8256            RelaySideOptions::default().with_small_packet_transport(128),
8257        );
8258        let handler = node.state.lock().sides[side]
8259            .as_ref()
8260            .unwrap()
8261            .tx_handler
8262            .clone();
8263        let mut complete = false;
8264        for timestamp in 1..100 {
8265            // Incompressible bytes ensure this really uses several chunks.
8266            let mut state = 0x517cc1b7u32;
8267            let payload: Vec<u8> = (0..3600)
8268                .map(|_| {
8269                    state ^= state << 13;
8270                    state ^= state >> 17;
8271                    state ^= state << 5;
8272                    state as u8
8273                })
8274                .collect();
8275            let packet = Packet::new(
8276                crate::DataType::DiscoverySchema,
8277                &[crate::DataEndpoint::Discovery],
8278                "SCHEMA_PEER",
8279                timestamp,
8280                crate::shared_bytes::convert(Arc::<[u8]>::from(payload)),
8281            )
8282            .unwrap();
8283            node.state
8284                .lock()
8285                .push_tx(RelayTxItem {
8286                    src: None,
8287                    dst: side,
8288                    data: RelayItem::Packet(Arc::new(packet)),
8289                    priority: 255,
8290                    retry_after_ms: 0,
8291                })
8292                .unwrap();
8293            let data = node
8294                .state
8295                .lock()
8296                .tx_queue
8297                .iter()
8298                .next()
8299                .unwrap()
8300                .data
8301                .clone();
8302            allowance.store(1, Ordering::Relaxed);
8303            match node.call_tx_handler(side, &handler, &data) {
8304                Ok(()) => {
8305                    complete = true;
8306                    break;
8307                }
8308                Err(TelemetryError::Io(_)) => {}
8309                other => panic!("unexpected result: {other:?}"),
8310            }
8311        }
8312        assert!(
8313            complete,
8314            "new announcement timestamps must not replay the prefix forever"
8315        );
8316        let received = accepted.lock().unwrap();
8317        // One transfer ID and consecutive fragment indices prove prefix reuse
8318        // did not create incomplete transfers or duplicate accepted fragments.
8319        assert!(
8320            received.len() > 1,
8321            "fixture must exercise fragmentation, not a compressed single frame"
8322        );
8323        let first = &received[0];
8324        assert_eq!(&first[..4], &[83, 68, 84, 3]);
8325        assert_eq!(
8326            u16::from_le_bytes([first[10], first[11]]) as usize,
8327            received.len()
8328        );
8329        let transfer_id = &first[4..8];
8330        for (index, frame) in received.iter().enumerate() {
8331            assert_eq!(&frame[4..8], transfer_id);
8332            assert_eq!(u16::from_le_bytes([frame[8], frame[9]]) as usize, index);
8333        }
8334    }
8335    #[test]
8336    fn schema_receipt_resolves_assigned_numeric_address_without_adding_peers() {
8337        let relay = Relay::new(Box::new(|| 0u64));
8338        let side = relay.add_side_packed("A", |_| Ok(()));
8339        let assigned = 0x1234u32;
8340        assert_ne!(sender_address_u32("SCHEMA_PEER"), assigned);
8341        {
8342            let mut st = relay.state.lock();
8343            st.discovery_routes
8344                .entry(side)
8345                .or_default()
8346                .announcers
8347                .insert(
8348                    "SCHEMA_PEER".into(),
8349                    DiscoverySenderState {
8350                        advertised_address: Some(assigned),
8351                        ..DiscoverySenderState::default()
8352                    },
8353                );
8354        }
8355        relay.note_schema_received(side, "@addr:4660");
8356        relay.note_schema_received(side, "@addr:4661");
8357        let st = relay.state.lock();
8358        let peers = &st.discovery_routes.get(&side).unwrap().announcers;
8359        assert_eq!(peers.len(), 1);
8360        assert!(peers["SCHEMA_PEER"].has_schema);
8361    }
8362}
8363
8364#[cfg(all(test, feature = "discovery", feature = "std"))]
8365fn untemplated_test_node() -> Relay {
8366    Relay::new(Box::new(|| 0))
8367}
8368#[cfg(all(test, feature = "discovery", feature = "std"))]
8369fn untemplated_test_options() -> RelaySideOptions {
8370    RelaySideOptions::default().with_small_packet_transport(64)
8371}
8372#[cfg(all(test, feature = "discovery", feature = "std"))]
8373#[allow(clippy::duplicate_mod)]
8374#[path = "tests/untemplated_transport.rs"]
8375mod untemplated_transport_tests;
8376
8377#[cfg(all(test, feature = "std"))]
8378mod frozen_control_delivery_tests {
8379    use super::*;
8380    use crate::router::{NetworkVariablePermissions, Router, RouterConfig, RouterSideOptions};
8381    use core::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
8382    use std::sync::Mutex;
8383
8384    fn control() -> Packet {
8385        let ep = crate::config::register_endpoint("FROZEN_RELAY_CONTROL", false).unwrap();
8386        let ty = crate::config::register_data_type_with_description(
8387            "FROZEN_RELAY_CONTROL",
8388            "",
8389            crate::MessageElement::Static(
8390                1,
8391                crate::MessageDataType::UInt8,
8392                crate::MessageClass::Data,
8393            ),
8394            &[ep],
8395            crate::ReliableMode::None,
8396            16,
8397        )
8398        .unwrap();
8399        Packet::new_with_wire_contract(
8400            ty,
8401            &[ep],
8402            "GS",
8403            1,
8404            7,
8405            Arc::<[u8]>::from([1]),
8406            None,
8407            Arc::from([Relay::sender_hash("FC")]),
8408        )
8409        .unwrap()
8410    }
8411
8412    #[test]
8413    fn foreign_frozen_control_retains_priority_and_full_reserved_admission() {
8414        let packet = control();
8415        let bytes = wire_format::pack_packet(&packet);
8416        let relay = Relay::new(Box::new(|| 0u64));
8417        let side = relay.add_side_packed("radio", |_| Ok(()));
8418        relay.rx_packed_from_side(side, &bytes).unwrap();
8419        relay.clear_queues();
8420        let foreign = Router::new(RouterConfig::new([]).with_sender("FOREIGN"));
8421        let foreign_side = foreign.add_side_packed("radio", |_| Ok(()));
8422        foreign
8423            .rx_packed_queue_from_side(&bytes, foreign_side)
8424            .unwrap();
8425        foreign.clear_queues();
8426        assert_eq!(
8427            Relay::relay_item_priority(&RelayItem::Packet(Arc::new(packet))).unwrap(),
8428            254
8429        );
8430        assert_eq!(
8431            Relay::relay_item_priority(&RelayItem::Packed(crate::shared_bytes::convert(
8432                bytes.clone()
8433            )))
8434            .unwrap(),
8435            254
8436        );
8437        extern "C" fn protected_only(additional: usize, largest: usize, kind: u32) -> bool {
8438            kind == crate::memory_admission::WORK_SMALL_PROTECTED
8439                && (2048..=2432).contains(&additional)
8440                && largest <= 160
8441        }
8442        struct Reset;
8443        impl Drop for Reset {
8444            fn drop(&mut self) {
8445                crate::memory_admission::set_probe_v2(None);
8446            }
8447        }
8448        let _reset = Reset;
8449        crate::memory_admission::set_probe_v2(Some(protected_only));
8450        assert_eq!(wire_format::unpack_packet(&bytes).unwrap().payload(), &[1]);
8451        relay.rx_packed_from_side(side, &bytes).unwrap();
8452        foreign
8453            .rx_packed_queue_from_side(&bytes, foreign_side)
8454            .unwrap();
8455        let mut corrupt = bytes.to_vec();
8456        *corrupt.last_mut().unwrap() ^= 1;
8457        assert!(relay.rx_packed_from_side(side, &corrupt).is_err());
8458    }
8459
8460    #[test]
8461    fn best_effort_schema_frozen_contract_retries_through_standalone_relay() {
8462        let packet = control();
8463        let bytes = wire_format::pack_packet(&packet);
8464        let now = Arc::new(AtomicU64::new(0));
8465        let clock = now.clone();
8466        let relay = Relay::new(Box::new(move || clock.load(Ordering::Relaxed)));
8467        let upstream = Arc::new(Mutex::new(Vec::<Vec<u8>>::new()));
8468        let output = upstream.clone();
8469        let radio = relay.add_side_packed_with_options(
8470            "radio",
8471            move |bytes| {
8472                output.lock().unwrap().push(bytes.to_vec());
8473                Ok(())
8474            },
8475            RelaySideOptions {
8476                header_template_enabled: false,
8477                reliable_enabled: false,
8478                ..Default::default()
8479            },
8480        );
8481        let downstream = Arc::new(Mutex::new(Vec::<Vec<u8>>::new()));
8482        let output = downstream.clone();
8483        let can = relay.add_side_packed_with_options(
8484            "CAN",
8485            move |bytes| {
8486                output.lock().unwrap().push(bytes.to_vec());
8487                Ok(())
8488            },
8489            RelaySideOptions {
8490                header_template_enabled: false,
8491                reliable_enabled: false,
8492                ..Default::default()
8493            },
8494        );
8495        relay.set_route(Some(radio), can, true).unwrap();
8496        relay.set_route(Some(can), radio, true).unwrap();
8497        let child = Router::new(RouterConfig::new([]).with_sender("FC"));
8498        child
8499            .enable_network_variable(packet.data_type(), NetworkVariablePermissions::READ_ONLY)
8500            .unwrap();
8501        let effects = Arc::new(AtomicUsize::new(0));
8502        let count = effects.clone();
8503        child
8504            .on_network_variable_update(packet.data_type(), move |_| {
8505                count.fetch_add(1, Ordering::Relaxed);
8506                Ok(())
8507            })
8508            .unwrap();
8509        let receipts = Arc::new(Mutex::new(Vec::<Vec<u8>>::new()));
8510        let output = receipts.clone();
8511        let child_side = child.add_side_packed_with_options(
8512            "CAN",
8513            move |bytes| {
8514                output.lock().unwrap().push(bytes.to_vec());
8515                Ok(())
8516            },
8517            RouterSideOptions {
8518                header_template_enabled: false,
8519                reliable_enabled: false,
8520                ..Default::default()
8521            },
8522        );
8523        relay.clear_queues();
8524        child.clear_queues();
8525        for attempt in 0..3 {
8526            now.store(
8527                u64::from(crate::config::runtime_tuning_config().reliable_retransmit_ms) * attempt,
8528                Ordering::Relaxed,
8529            );
8530            relay.rx_packed_from_side(radio, &bytes).unwrap();
8531            relay.drain_queues_with_timeout(0).unwrap();
8532            let frames = core::mem::take(&mut *downstream.lock().unwrap());
8533            let values: Vec<_> = frames
8534                .into_iter()
8535                .filter(|bytes| wire_format::peek_envelope(bytes).unwrap().ty == packet.data_type())
8536                .collect();
8537            assert!(
8538                !values.is_empty(),
8539                "frozen Off-schema retry was swallowed by relay dedupe"
8540            );
8541            if attempt == 0 {
8542                continue;
8543            } // The first data transmission is lost.
8544            for bytes in values {
8545                child.rx_packed_queue_from_side(&bytes, child_side).unwrap();
8546            }
8547            child.drain_queues_with_timeout(0).unwrap();
8548            for bytes in core::mem::take(&mut *receipts.lock().unwrap()) {
8549                relay.rx_packed_from_side(can, &bytes).unwrap();
8550            }
8551            relay.drain_queues_with_timeout(0).unwrap();
8552            assert_eq!(
8553                effects.load(Ordering::Relaxed),
8554                1,
8555                "receipt retry repeated application effect"
8556            );
8557            assert!(
8558                core::mem::take(&mut *upstream.lock().unwrap())
8559                    .iter()
8560                    .any(|bytes| {
8561                        let ack = wire_format::unpack_packet(bytes).unwrap();
8562                        ack.data_type() == crate::DataType::ReliableAck
8563                            && ack.payload() == packet.packet_id().to_le_bytes()
8564                    }),
8565                "the endpoint receipt did not return through the relay"
8566            );
8567        }
8568    }
8569}
8570
8571#[cfg(all(test, feature = "std"))]
8572mod bounded_fair_queue_tests {
8573    use super::*;
8574    #[test]
8575    fn small_canonical_reports_survive_fast_senders_fixed_ring_bursts() {
8576        let ep = crate::config::register_endpoint("RELAY_SMALL_QUEUE_DEST", false).unwrap();
8577        let ty = crate::config::register_data_type_with_description(
8578            "RELAY_SMALL_QUEUE_REPORT",
8579            "",
8580            crate::MessageElement::Static(
8581                4,
8582                crate::MessageDataType::UInt8,
8583                crate::MessageClass::Data,
8584            ),
8585            &[ep],
8586            crate::ReliableMode::None,
8587            0,
8588        )
8589        .unwrap();
8590        for tx in [false, true] {
8591            for slow_last in [false, true] {
8592                let node = Relay::new(Box::new(|| 0));
8593                let make = |sender| {
8594                    RelayItem::Packed(crate::shared_bytes::convert(wire_format::pack_packet(
8595                        &Packet::new(ty, &[ep], sender, 1, Arc::<[u8]>::from([1; 4])).unwrap(),
8596                    )))
8597                };
8598                let rx = |data| RelayRxItem {
8599                    src: 0,
8600                    data,
8601                    priority: 0,
8602                };
8603                let tx_item = |data| RelayTxItem {
8604                    src: None,
8605                    dst: 1,
8606                    data,
8607                    priority: 0,
8608                    retry_after_ms: 0,
8609                };
8610                let mut st = node.state.lock();
8611                st.memory.max_queue_budget = usize::MAX;
8612                if tx {
8613                    st.tx_queue = BoundedDeque::new(8192, 3 * size_of::<RelayTxItem>(), 1.0);
8614                } else {
8615                    st.rx_queue = BoundedDeque::new(8192, 3 * size_of::<RelayRxItem>(), 1.0);
8616                }
8617                let order = if slow_last {
8618                    ["FAST", "FAST", "SLOW"]
8619                } else {
8620                    ["SLOW", "FAST", "FAST"]
8621                };
8622                for sender in order.into_iter().chain(core::iter::repeat_n("FAST", 32)) {
8623                    if tx {
8624                        st.push_tx(tx_item(make(sender))).unwrap();
8625                    } else {
8626                        st.push_rx(rx(make(sender))).unwrap();
8627                    }
8628                }
8629                let slow = wire_format::source_address_for_sender("SLOW");
8630                let retained = if tx {
8631                    st.tx_queue.iter().any(|item| {
8632                        RelayInner::fair_bulk_owner(&item.data, item.priority) == Some(slow)
8633                    })
8634                } else {
8635                    st.rx_queue.iter().any(|item| {
8636                        RelayInner::fair_bulk_owner(&item.data, item.priority) == Some(slow)
8637                    })
8638                };
8639                assert!(
8640                    retained,
8641                    "small report was displaced: tx={tx}, slow_last={slow_last}"
8642                );
8643            }
8644        }
8645    }
8646
8647    #[test]
8648    fn slow_stream_survives_same_board_fast_stream_bursts() {
8649        let ep = crate::config::register_endpoint("RELAY_STREAM_QUEUE_DEST", false).unwrap();
8650        let ty = crate::config::register_data_type_with_description(
8651            "RELAY_STREAM_QUEUE_FAST",
8652            "",
8653            crate::MessageElement::Static(
8654                64,
8655                crate::MessageDataType::UInt8,
8656                crate::MessageClass::Data,
8657            ),
8658            &[ep],
8659            crate::ReliableMode::None,
8660            0,
8661        )
8662        .unwrap();
8663        let slow_ty = crate::config::register_data_type_with_description(
8664            "RELAY_STREAM_QUEUE_SLOW",
8665            "",
8666            crate::MessageElement::Static(
8667                64,
8668                crate::MessageDataType::UInt8,
8669                crate::MessageClass::Data,
8670            ),
8671            &[ep],
8672            crate::ReliableMode::None,
8673            0,
8674        )
8675        .unwrap();
8676        for mode in 0..3 {
8677            for tx in [false, true] {
8678                for slow_last in [false, true] {
8679                    let node = Relay::new(Box::new(|| 0));
8680                    let make = |sender| {
8681                        let packet = Packet::new(
8682                            if sender == "SLOW" { slow_ty } else { ty },
8683                            &[ep],
8684                            "SAME_BOARD",
8685                            1,
8686                            Arc::<[u8]>::from([1; 64]),
8687                        )
8688                        .unwrap();
8689                        if mode == 1 {
8690                            RelayItem::Packet(packet.into())
8691                        } else {
8692                            RelayItem::Packed(crate::shared_bytes::convert(
8693                                wire_format::pack_packet(&packet),
8694                            ))
8695                        }
8696                    };
8697                    let rx = |data| RelayRxItem {
8698                        src: 0,
8699                        data,
8700                        priority: 0,
8701                    };
8702                    let tx_item = |data| RelayTxItem {
8703                        src: None,
8704                        dst: 1,
8705                        data,
8706                        priority: 0,
8707                        retry_after_ms: 0,
8708                    };
8709                    let mut st = node.state.lock();
8710                    st.memory.max_queue_budget = usize::MAX;
8711                    if mode == 0 {
8712                        if tx {
8713                            st.tx_queue =
8714                                BoundedDeque::new(8192, 3 * size_of::<RelayTxItem>(), 1.0);
8715                        } else {
8716                            st.rx_queue =
8717                                BoundedDeque::new(8192, 3 * size_of::<RelayRxItem>(), 1.0);
8718                        }
8719                    } else if mode == 1 {
8720                        let cost = if tx {
8721                            tx_item(make("SLOW")).byte_cost()
8722                                + 2 * tx_item(make("FAST")).byte_cost()
8723                        } else {
8724                            rx(make("SLOW")).byte_cost() + 2 * rx(make("FAST")).byte_cost()
8725                        };
8726                        if tx {
8727                            st.tx_queue = BoundedDeque::new(cost, cost, 2.0);
8728                        } else {
8729                            st.rx_queue = BoundedDeque::new(cost, cost, 2.0);
8730                        }
8731                    }
8732                    let order = if slow_last {
8733                        ["FAST", "FAST", "SLOW"]
8734                    } else {
8735                        ["SLOW", "FAST", "FAST"]
8736                    };
8737                    for (index, sender) in order
8738                        .into_iter()
8739                        .chain(core::iter::repeat_n("FAST", 32))
8740                        .enumerate()
8741                    {
8742                        if mode == 2 && index == 3 {
8743                            st.memory.max_queue_budget = st.shared_queue_bytes_used();
8744                        }
8745                        if tx {
8746                            st.push_tx(tx_item(make(sender))).unwrap();
8747                        } else {
8748                            st.push_rx(rx(make(sender))).unwrap();
8749                        }
8750                    }
8751                    let is_slow = |data: &RelayItem| match data {
8752                        RelayItem::Packet(p) => p.data_type() == slow_ty,
8753                        RelayItem::Packed(b) => {
8754                            wire_format::scheduling_type(b.as_ref()) == Some(slow_ty)
8755                        }
8756                    };
8757                    let retained = if tx {
8758                        st.tx_queue.iter().any(|item| {
8759                            let data = &item.data;
8760                            is_slow(data)
8761                        })
8762                    } else {
8763                        st.rx_queue.iter().any(|item| is_slow(&item.data))
8764                    };
8765                    assert!(
8766                        retained,
8767                        "slow stream displaced: mode={mode}, tx={tx}, slow_last={slow_last}"
8768                    );
8769                }
8770            }
8771        }
8772    }
8773
8774    #[test]
8775    fn fast_producer_preserves_slow_reports_at_each_queue_boundary() {
8776        let ep = crate::config::register_endpoint("RELAY_QUEUE_FAIR_DEST", false).unwrap();
8777        let ty = crate::config::register_data_type_with_description(
8778            "RELAY_QUEUE_FAIR_REPORT",
8779            "",
8780            crate::MessageElement::Static(
8781                64,
8782                crate::MessageDataType::UInt8,
8783                crate::MessageClass::Data,
8784            ),
8785            &[ep],
8786            crate::ReliableMode::None,
8787            0,
8788        )
8789        .unwrap();
8790        for mode in 0..3 {
8791            for tx in [false, true] {
8792                for slow_last in [false, true] {
8793                    for next_sender in ["FAST", "NEW_SLOW"] {
8794                        let router = Relay::new(Box::new(|| 0));
8795                        let make = |sender| {
8796                            RelayItem::Packet(Arc::new(
8797                                Packet::new(ty, &[ep], sender, 1, Arc::<[u8]>::from([1; 64]))
8798                                    .unwrap(),
8799                            ))
8800                        };
8801                        let rx = |data| RelayRxItem {
8802                            src: 0,
8803                            data,
8804                            priority: 0,
8805                        };
8806                        let tx_item = |data| RelayTxItem {
8807                            src: None,
8808                            dst: 1,
8809                            data,
8810                            priority: 0,
8811                            retry_after_ms: 0,
8812                        };
8813                        let order = if slow_last {
8814                            ["FAST", "FAST", "SLOW"]
8815                        } else {
8816                            ["SLOW", "FAST", "FAST"]
8817                        };
8818                        let mut st = router.state.lock();
8819                        st.memory.max_queue_budget = usize::MAX;
8820                        let cost = if tx {
8821                            order.iter().map(|s| tx_item(make(s)).byte_cost()).sum()
8822                        } else {
8823                            order.iter().map(|s| rx(make(s)).byte_cost()).sum()
8824                        };
8825                        if mode == 0 {
8826                            if tx {
8827                                st.tx_queue = BoundedDeque::new(cost, cost, 2.0);
8828                            } else {
8829                                st.rx_queue = BoundedDeque::new(cost, cost, 2.0);
8830                            }
8831                        } else if mode == 1 {
8832                            if tx {
8833                                st.tx_queue =
8834                                    BoundedDeque::new(cost * 10, 3 * size_of::<RelayTxItem>(), 1.0);
8835                            } else {
8836                                st.rx_queue =
8837                                    BoundedDeque::new(cost * 10, 3 * size_of::<RelayRxItem>(), 1.0);
8838                            }
8839                        }
8840                        for sender in order {
8841                            if tx {
8842                                st.push_tx(tx_item(make(sender))).unwrap();
8843                            } else {
8844                                st.push_rx(rx(make(sender))).unwrap();
8845                            }
8846                        }
8847                        if mode == 2 {
8848                            st.memory.max_queue_budget = st.shared_queue_bytes_used();
8849                        }
8850                        if tx {
8851                            st.push_tx(tx_item(make(next_sender))).unwrap_or_else(|error| panic!("admit error {error:?}: mode={mode} tx={tx} slow_last={slow_last} incoming={next_sender}"));
8852                        } else {
8853                            st.push_rx(rx(make(next_sender))).unwrap_or_else(|error| panic!("admit error {error:?}: mode={mode} tx={tx} slow_last={slow_last} incoming={next_sender}"));
8854                        }
8855                        let slow = wire_format::source_address_for_sender("SLOW");
8856                        let retained = if tx {
8857                            st.tx_queue.iter().any(|item| {
8858                                RelayInner::fair_bulk_owner(&item.data, item.priority) == Some(slow)
8859                            })
8860                        } else {
8861                            st.rx_queue.iter().any(|item| {
8862                                RelayInner::fair_bulk_owner(&item.data, item.priority) == Some(slow)
8863                            })
8864                        };
8865                        assert!(
8866                            retained,
8867                            "slow report evicted: mode={mode} tx={tx} slow_last={slow_last} incoming={next_sender}"
8868                        );
8869                    }
8870                }
8871            }
8872        }
8873    }
8874}
8875
8876#[cfg(all(test, feature = "discovery"))]
8877#[test]
8878fn cow_discovery_rejected_growth_keeps_original_graph_and_routes() {
8879    crate::tests::ensure_common_test_schema();
8880    let node = Relay::new_with_config(RelayConfig::default().with_sender("GB"), Box::new(|| 0));
8881    let side = node.add_side_packed("can", |_| Ok(()));
8882    let baseline = discovery::build_discovery_topology(
8883        "PEER",
8884        0,
8885        &[TopologyBoardNode {
8886            sender_id: "PEER".into(),
8887            reachable_endpoints: Vec::new(),
8888            reachable_timesync_sources: Vec::new(),
8889            connections: Vec::new(),
8890        }],
8891    )
8892    .unwrap();
8893    node.rx_from_side(side, baseline).unwrap();
8894    node.process_all_queues().unwrap();
8895    node.clear_queues();
8896    let before = node.export_topology();
8897    let original = {
8898        let mut st = node.state.lock();
8899        let graph = st.discovery_routes[&side].announcers["PEER"]
8900            .topology_boards
8901            .clone();
8902        st.memory.max_queue_budget = st.shared_queue_bytes_used();
8903        graph
8904    };
8905    let update = discovery::build_discovery_topology_delta(
8906        "PEER",
8907        1,
8908        &[TopologyBoardNode {
8909            sender_id: "PEER".into(),
8910            reachable_endpoints: Vec::new(),
8911            reachable_timesync_sources: vec!["NEW_CLOCK".into()],
8912            connections: vec!["NEW_NEIGHBOR".into()],
8913        }],
8914        &[],
8915    )
8916    .unwrap();
8917    assert!(
8918        node.rx_from_side(side, update)
8919            .and_then(|_| node.process_all_queues())
8920            .is_err(),
8921        "growth must fail when no queue budget can be reclaimed"
8922    );
8923    assert_eq!(node.export_topology().routes, before.routes);
8924    let st = node.state.lock();
8925    assert!(Arc::ptr_eq(
8926        &original,
8927        &st.discovery_routes[&side].announcers["PEER"].topology_boards
8928    ));
8929    assert!(original[0].connections.is_empty());
8930    assert!(original[0].reachable_timesync_sources.is_empty());
8931}
8932
8933#[cfg(all(test, feature = "std"))]
8934mod bounded_rx_priority_tests {
8935    use super::*;
8936    #[test]
8937    fn retained_slow_report_cannot_starve_behind_continuous_fast_data() {
8938        let ep = crate::config::register_endpoint("RelayRxItem_FAIR_EP_RelayInner", false).unwrap();
8939        let ty = crate::config::register_data_type_with_description(
8940            "RelayRxItem_FAIR_DATA_RelayInner",
8941            "",
8942            crate::MessageElement::Static(
8943                1,
8944                crate::MessageDataType::Float32,
8945                crate::MessageClass::Data,
8946            ),
8947            &[ep],
8948            crate::ReliableMode::None,
8949            0,
8950        )
8951        .unwrap();
8952        let node = Relay::new(Box::new(|| 0));
8953        let mut st = node.state.lock();
8954        st.memory.max_queue_budget = 65536;
8955        st.rx_queue = BoundedDeque::new(32768, 16 * size_of::<RelayRxItem>(), 1.0);
8956        let make = |sender: &str, stamp, priority| {
8957            let packet = Packet::new(ty, &[ep], sender, stamp, Arc::<[u8]>::from([0; 4])).unwrap();
8958            RelayRxItem {
8959                src: 0,
8960                data: RelayItem::Packet(Arc::new(packet)),
8961                priority,
8962            }
8963        };
8964        st.push_rx(make("VALVE", 0, 0)).unwrap();
8965        let mut delivered = false;
8966        for tick in 1..=32u64 {
8967            for index in 0..3 {
8968                st.push_rx(make("DAQ", tick * 3 + index, 16)).unwrap();
8969            }
8970            let item = st.pop_received_fair().unwrap();
8971            let sender = match &item.data {
8972                RelayItem::Packet(packet) => packet.sender(),
8973                _ => panic!("expected packet"),
8974            };
8975            if sender == "VALVE" {
8976                delivered = true;
8977                assert!(tick <= u64::from(MAX_PRIORITY_BURST) + 1);
8978                break;
8979            }
8980        }
8981        assert!(
8982            delivered,
8983            "retention alone does not deliver low-rate reports under a continuous higher-priority source"
8984        );
8985        st.rx_queue.clear();
8986        st.rx_priority_burst = MAX_PRIORITY_BURST;
8987        st.push_rx(make("VALVE", 100, 0)).unwrap();
8988        st.push_rx(make("VALVE", 101, 0)).unwrap();
8989        st.push_rx(make("DAQ", 102, 16)).unwrap();
8990        st.push_rx(make("COMMAND", 103, 254)).unwrap();
8991        assert_eq!(
8992            st.pop_received_fair().unwrap().priority,
8993            254,
8994            "a fairness turn must never delay protected control"
8995        );
8996        let mut first_slow = None;
8997        for tick in 0..=MAX_PRIORITY_BURST {
8998            st.push_rx(make("DAQ", 200 + u64::from(tick), 16)).unwrap();
8999            let item = st.pop_received_fair().unwrap();
9000            if let RelayItem::Packet(packet) = &item.data
9001                && packet.sender() == "VALVE"
9002            {
9003                first_slow = Some(packet.timestamp());
9004                break;
9005            }
9006        }
9007        assert_eq!(
9008            first_slow,
9009            Some(100),
9010            "fairness preserves same-priority FIFO order"
9011        );
9012        st.rx_priority_burst = MAX_PRIORITY_BURST;
9013        drop(st);
9014        node.clear_rx_queue();
9015        assert_eq!(node.state.lock().rx_priority_burst, 0);
9016    }
9017}