Skip to main content

sedsnet/
relay.rs

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