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