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