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