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