1use crate::config::{
2 RuntimeMemoryConfig, runtime_reliable_max_end_to_end_ack_cache,
3 runtime_reliable_max_end_to_end_pending, runtime_reliable_max_pending,
4 runtime_reliable_max_retries, runtime_reliable_max_return_routes,
5 runtime_reliable_retransmit_ms,
6};
7use crate::diagnostics::{
8 AdaptiveLinkStats, DiscoveryRuntimeStats, QueueRuntimeStats, ReliableRuntimeStats,
9 RouteModeStats, RouteOverrideStats, RoutePriorityStats, RouteWeightStats, RuntimeSideStats,
10 RuntimeStatsSnapshot, RuntimeTypeStats, TypedRouteOverrideStats,
11};
12#[cfg(feature = "discovery")]
13use crate::discovery::{
14 self, ClientStatsSnapshot, DISCOVERY_INCREMENTAL_RETRY_COUNT, DISCOVERY_ROUTE_TTL_MS,
15 DISCOVERY_SLOW_LINK_FULL_INTERVAL_MS, DISCOVERY_SLOW_LINK_PING_INTERVAL_MS,
16 DiscoveryCadenceState, TIMESYNC_SLOW_LINK_MIN_INTERVAL_MS, TopologyAnnouncerRoute,
17 TopologyBoardNode, TopologySideRoute, TopologySnapshot,
18};
19#[cfg(feature = "discovery")]
20use crate::packet::sender_address_u32;
21use crate::packet::{Packet, hash_bytes_u64};
22use crate::queue::{BoundedDeque, ByteCost};
23use crate::wire_format;
24use crate::{is_reliable_type, message_meta, message_priority, reliable_mode};
25use crate::{
26 router::{Clock, CompactTimestampOmissionPolicy, SideTransportProfile},
27 {
28 RouteSelectionMode, TelemetryError, TelemetryResult,
29 lock::{ReentryGate, ReentryGuard, RouterMutex},
30 },
31};
32use alloc::borrow::ToOwned;
33use alloc::boxed::Box;
34use alloc::collections::{BTreeMap, BTreeSet, VecDeque};
35use alloc::string::{String, ToString};
36use alloc::{sync::Arc, vec, vec::Vec};
37use core::mem::size_of;
38
39const MAX_PRIORITY_BURST: u8 = 8;
40use crc32fast::Hasher as Crc32Hasher;
41
42pub type RelaySideId = usize;
44const SIDE_TRANSPORT_MAGIC: &[u8; 3] = b"SDT";
45const SIDE_TRANSPORT_KIND_FULL: u8 = 0x01;
46const SIDE_TRANSPORT_KIND_COMPACT: u8 = 0x02;
47const SIDE_TRANSPORT_KIND_CHUNK: u8 = 0x03;
48const SIDE_TRANSPORT_KIND_COMPACT_DELTA: u8 = 0x04;
49const SIDE_TRANSPORT_KIND_COMPACT_SAME_TIMESTAMP: u8 = 0x05;
50const SIDE_TRANSPORT_FLAG_PAYLOAD_COMPRESSED: u8 = 0x01;
51const SIDE_TRANSPORT_FLAG_WIRE_CONTRACT: u8 = 0x04;
52const SIDE_TRANSPORT_FLAG_PACKET_NONCE: u8 = 0x08;
53const SIDE_TRANSPORT_FLAG_ENDPOINT_BITMAP_PRESENT: u8 = 0x20;
54const SIDE_TRANSPORT_FLAG_COMPACT_RELIABLE_HEADER: u8 = 0x40;
55const CONTROL_SLOW_LINK_CAPACITY_BPS: u64 = 512;
56const SIDE_TRANSPORT_CHUNK_OVERHEAD: usize = 3 + 1 + 4 + 2 + 2 + wire_format::CRC32_BYTES;
57const SIDE_TRANSPORT_EP_BITMAP_BITS: usize = (crate::MAX_VALUE_DATA_ENDPOINT as usize) + 1;
58const SIDE_TRANSPORT_EP_BITMAP_BYTES: usize = SIDE_TRANSPORT_EP_BITMAP_BITS.div_ceil(8);
59pub const IPV4_LIKE_COMPACT_HEADER_TARGET_BYTES: usize =
60 crate::router::IPV4_LIKE_COMPACT_HEADER_TARGET_BYTES;
61pub const IPV6_LIKE_COMPACT_HEADER_TARGET_BYTES: usize =
62 crate::router::IPV6_LIKE_COMPACT_HEADER_TARGET_BYTES;
63pub const DEFAULT_SIDE_TRANSPORT_TEMPLATE_LIMIT: usize =
64 crate::router::DEFAULT_SIDE_TRANSPORT_TEMPLATE_LIMIT;
65type PacketHandlerFn = dyn Fn(&Packet) -> TelemetryResult<()> + Send + Sync + 'static;
67
68type PackedHandlerFn = dyn Fn(&[u8]) -> TelemetryResult<()> + Send + Sync + 'static;
70
71#[derive(Clone)]
73pub enum RelayTxHandlerFn {
74 Packed(Arc<PackedHandlerFn>),
75 Packet(Arc<PacketHandlerFn>),
76}
77
78#[derive(Clone, Copy, Debug)]
79pub struct RelaySideOptions {
80 pub reliable_enabled: bool,
86 pub link_local_enabled: bool,
88 pub ingress_enabled: bool,
90 pub egress_enabled: bool,
92 pub header_template_enabled: bool,
94 pub max_frame_bytes: usize,
101 pub compact_header_target_bytes: usize,
103 pub max_side_transport_templates: usize,
105 pub omit_unchanged_compact_timestamps: bool,
107 pub compact_timestamp_omission_types: CompactTimestampOmissionPolicy,
109 pub side_transport_profile: SideTransportProfile,
111}
112
113impl Default for RelaySideOptions {
114 fn default() -> Self {
115 Self {
116 reliable_enabled: false,
117 link_local_enabled: false,
118 ingress_enabled: true,
119 egress_enabled: true,
120 header_template_enabled: false,
121 max_frame_bytes: 0,
122 compact_header_target_bytes: 0,
123 max_side_transport_templates: DEFAULT_SIDE_TRANSPORT_TEMPLATE_LIMIT,
124 omit_unchanged_compact_timestamps: false,
125 compact_timestamp_omission_types: CompactTimestampOmissionPolicy::none(),
126 side_transport_profile: SideTransportProfile::Canonical,
127 }
128 }
129}
130
131impl RelaySideOptions {
132 #[inline]
137 pub fn with_small_packet_transport(mut self, max_frame_bytes: usize) -> Self {
138 self.header_template_enabled = true;
139 self.max_frame_bytes = max_frame_bytes;
140 self.compact_header_target_bytes = IPV6_LIKE_COMPACT_HEADER_TARGET_BYTES;
141 self.side_transport_profile = SideTransportProfile::Ipv6Like;
142 self
143 }
144
145 #[inline]
146 pub fn with_ipv4_like_compact_header_target(mut self) -> Self {
147 self.header_template_enabled = true;
148 self.compact_header_target_bytes = IPV4_LIKE_COMPACT_HEADER_TARGET_BYTES;
149 self.omit_unchanged_compact_timestamps = true;
150 self.side_transport_profile = SideTransportProfile::Ipv4Like;
151 self
152 }
153
154 #[inline]
155 pub fn with_ipv6_like_compact_header_target(mut self) -> Self {
156 self.header_template_enabled = true;
157 self.compact_header_target_bytes = IPV6_LIKE_COMPACT_HEADER_TARGET_BYTES;
158 self.side_transport_profile = SideTransportProfile::Ipv6Like;
159 self
160 }
161
162 #[inline]
163 pub fn with_template_transport(mut self) -> Self {
164 self.header_template_enabled = true;
165 self.side_transport_profile = SideTransportProfile::Template;
166 self
167 }
168
169 #[inline]
170 pub fn with_omitted_unchanged_compact_timestamps(mut self) -> Self {
171 self.header_template_enabled = true;
172 self.omit_unchanged_compact_timestamps = true;
173 self
174 }
175
176 #[inline]
177 pub fn with_omitted_unchanged_compact_timestamps_for_type(
178 mut self,
179 ty: crate::DataType,
180 ) -> Self {
181 self.header_template_enabled = true;
182 self.compact_timestamp_omission_types.insert(ty);
183 self
184 }
185
186 #[inline]
187 pub fn effective_transport_profile(self) -> SideTransportProfile {
188 if !self.header_template_enabled && self.max_frame_bytes == 0 {
189 SideTransportProfile::Canonical
190 } else if self.side_transport_profile == SideTransportProfile::Canonical {
191 SideTransportProfile::Template
192 } else {
193 self.side_transport_profile
194 }
195 }
196
197 #[cfg(feature = "discovery")]
198 #[inline]
199 pub fn link_capabilities(self) -> discovery::LinkCapabilities {
200 let mut flags = discovery::LINK_CAPABILITY_END_TO_END_RELIABILITY;
201 if self.header_template_enabled {
202 flags |= discovery::LINK_CAPABILITY_HEADER_TEMPLATES;
203 }
204 if self.max_frame_bytes != 0 {
205 flags |= discovery::LINK_CAPABILITY_CHUNKING;
206 }
207 if self.reliable_enabled {
208 flags |= discovery::LINK_CAPABILITY_RELIABILITY;
209 }
210 if self.omit_unchanged_compact_timestamps
211 || !self.compact_timestamp_omission_types.is_empty()
212 {
213 flags |= discovery::LINK_CAPABILITY_OMIT_UNCHANGED_TIMESTAMPS;
214 }
215 #[cfg(feature = "cryptography")]
216 {
217 flags |= discovery::LINK_CAPABILITY_CRYPTO;
218 }
219 discovery::LinkCapabilities {
220 version: 1,
221 flags,
222 profile: self.effective_transport_profile().discovery_code(),
223 max_frame_bytes: self.max_frame_bytes.min(u32::MAX as usize) as u32,
224 compact_header_target_bytes: self.compact_header_target_bytes.min(u32::MAX as usize)
225 as u32,
226 max_side_transport_templates: self.max_side_transport_templates.min(u32::MAX as usize)
227 as u32,
228 }
229 }
230}
231
232#[derive(Clone)]
234pub struct RelaySide {
235 pub name: Arc<str>,
236 pub tx_handler: RelayTxHandlerFn,
237 pub opts: RelaySideOptions,
238}
239
240#[derive(Clone, Debug, PartialEq, Eq)]
241pub enum RelayItem {
242 Packed(Arc<[u8]>),
243 Packet(Arc<Packet>),
244}
245
246#[derive(Clone, Debug, PartialEq, Eq)]
248struct RelayRxItem {
249 src: RelaySideId,
250 data: RelayItem,
251 priority: u8,
252}
253
254impl ByteCost for RelayRxItem {
255 fn byte_cost(&self) -> usize {
256 match &self.data {
257 RelayItem::Packed(bytes) => bytes.len(),
258 RelayItem::Packet(pkt) => pkt.byte_cost(),
259 }
260 }
261}
262
263#[derive(Clone, Debug, PartialEq, Eq)]
265struct RelayTxItem {
266 src: Option<RelaySideId>,
267 dst: RelaySideId,
268 data: RelayItem,
269 priority: u8,
270}
271
272#[derive(Clone, Debug, PartialEq, Eq)]
273struct RelayReplayItem {
274 dst: RelaySideId,
275 bytes: Arc<[u8]>,
276 priority: u8,
277}
278
279impl ByteCost for RelayTxItem {
280 fn byte_cost(&self) -> usize {
281 match &self.data {
282 RelayItem::Packed(bytes) => bytes.len(),
283 RelayItem::Packet(pkt) => pkt.byte_cost(),
284 }
285 }
286}
287
288impl ByteCost for RelayReplayItem {
289 fn byte_cost(&self) -> usize {
290 self.bytes.len()
291 }
292}
293
294#[derive(Debug, Clone)]
297struct ReliableTxState {
298 next_seq: u32,
299 sent_order: VecDeque<u32>,
300 sent: BTreeMap<u32, ReliableSent>,
301}
302
303#[derive(Debug, Clone)]
304struct ReliableSent {
305 bytes: Arc<[u8]>,
306 last_send_ms: u64,
307 retries: u32,
308 queued: bool,
309 partial_acked: bool,
310}
311
312#[derive(Debug, Clone)]
313struct ReliableRxState {
314 expected_seq: u32,
315 buffered: BTreeMap<u32, Arc<[u8]>>,
316}
317
318#[derive(Debug, Clone)]
319struct ReliableReturnRouteState {
320 side: RelaySideId,
321}
322
323#[cfg(feature = "discovery")]
324#[derive(Debug, Clone, Default, PartialEq, Eq)]
325struct DiscoverySenderState {
326 advertised_address: Option<u32>,
329 reachable: Vec<crate::DataEndpoint>,
330 advertised_reachable: Vec<crate::DataEndpoint>,
331 reachable_timesync_sources: Vec<String>,
332 advertised_reachable_timesync_sources: Vec<String>,
333 topology_boards: Vec<TopologyBoardNode>,
334 last_seen_ms: u64,
335}
336
337#[inline]
338fn is_internal_control_type(ty: crate::DataType) -> bool {
339 if matches!(
340 ty,
341 crate::DataType::ReliableAck
342 | crate::DataType::ReliablePartialAck
343 | crate::DataType::ReliablePacketRequest
344 | crate::DataType::P2pMessage
345 ) {
346 return true;
347 }
348
349 #[cfg(feature = "timesync")]
350 if matches!(
351 ty,
352 crate::DataType::TimeSyncAnnounce
353 | crate::DataType::TimeSyncRequest
354 | crate::DataType::TimeSyncResponse
355 ) {
356 return true;
357 }
358
359 #[cfg(feature = "discovery")]
360 if discovery::is_discovery_type(ty) {
361 return true;
362 }
363
364 let _ = ty;
365 false
366}
367
368#[cfg(feature = "discovery")]
369#[derive(Debug, Clone, Default, PartialEq, Eq)]
370struct DiscoverySideState {
371 reachable: Vec<crate::DataEndpoint>,
372 reachable_timesync_sources: Vec<String>,
373 last_seen_ms: u64,
374 announcers: BTreeMap<String, DiscoverySenderState>,
375}
376
377#[derive(Clone, Debug, Default, PartialEq, Eq)]
378struct SideChunkAssembly {
379 total: u16,
380 received: BTreeMap<u16, Arc<[u8]>>,
381}
382
383#[derive(Clone, Debug, Default)]
384struct SideTransportState {
385 tx_template_ids: BTreeMap<u64, u32>,
386 tx_templates: BTreeMap<u64, SideHeaderTemplate>,
387 tx_last_timestamps: BTreeMap<u32, u64>,
388 tx_compact_uses: BTreeMap<u32, u8>,
389 rx_templates: BTreeMap<u64, SideHeaderTemplate>,
390 rx_templates_by_id: BTreeMap<u32, SideHeaderTemplate>,
391 rx_last_timestamps: BTreeMap<u32, u64>,
392 rx_chunks: BTreeMap<u32, SideChunkAssembly>,
393 next_chunk_id: u32,
394 next_template_id: u32,
395}
396
397impl SideTransportState {
398 fn clear_header_templates(&mut self) {
399 self.tx_template_ids.clear();
400 self.tx_templates.clear();
401 self.tx_last_timestamps.clear();
402 self.tx_compact_uses.clear();
403 self.rx_templates.clear();
404 self.rx_templates_by_id.clear();
405 self.rx_last_timestamps.clear();
406 }
407
408 fn tx_template_count(&self) -> usize {
409 self.tx_template_ids.len()
410 }
411
412 fn rx_template_count(&self) -> usize {
413 self.rx_templates_by_id.len()
414 }
415
416 fn insert_tx_template(
417 &mut self,
418 template: SideHeaderTemplate,
419 template_id: u32,
420 max_templates: usize,
421 ) -> bool {
422 if max_templates == 0 {
423 return false;
424 }
425 let mut evicted = false;
426 if self.tx_template_ids.len() >= max_templates
427 && !self.tx_template_ids.contains_key(&template.hash)
428 && let Some(old_hash) = self.tx_template_ids.keys().next().copied()
429 {
430 if let Some(old_id) = self.tx_template_ids.remove(&old_hash) {
431 self.tx_last_timestamps.remove(&old_id);
432 self.tx_compact_uses.remove(&old_id);
433 }
434 self.tx_templates.remove(&old_hash);
435 evicted = true;
436 }
437 self.tx_template_ids.insert(template.hash, template_id);
438 self.tx_templates.insert(template.hash, template);
439 self.tx_compact_uses.insert(template_id, 0);
440 evicted
441 }
442
443 fn insert_rx_template(
444 &mut self,
445 template_id: u32,
446 template: SideHeaderTemplate,
447 max_templates: usize,
448 ) -> bool {
449 if max_templates == 0 {
450 return false;
451 }
452 let mut evicted = false;
453 if self.rx_templates_by_id.len() >= max_templates
454 && !self.rx_templates_by_id.contains_key(&template_id)
455 && let Some(old_id) = self.rx_templates_by_id.keys().next().copied()
456 && let Some(old_template) = self.rx_templates_by_id.remove(&old_id)
457 {
458 self.rx_templates.remove(&old_template.hash);
459 self.rx_last_timestamps.remove(&old_id);
460 evicted = true;
461 }
462 self.rx_templates_by_id
463 .insert(template_id, template.clone());
464 self.rx_templates.insert(template.hash, template);
465 evicted
466 }
467}
468
469#[derive(Clone, Debug, PartialEq, Eq)]
470struct SideHeaderTemplate {
471 hash: u64,
472 base_flags: u8,
473 prefix: Arc<[u8]>,
474 between: Arc<[u8]>,
475 reliable_flags: Option<u8>,
476 reliable_compact: bool,
477}
478
479type SideTemplateExtract<'a> = (
480 SideHeaderTemplate,
481 crate::DataType,
482 u8,
483 u64,
484 u64,
485 u16,
486 Option<(u32, u32)>,
487 &'a [u8],
488);
489
490#[derive(Clone, Copy, Debug, PartialEq, Eq)]
491enum SideCompactTimestampMode {
492 Absolute,
493 Delta,
494 Omitted,
495}
496
497#[derive(Debug, Clone, Default)]
498struct AdaptiveRouteStats {
499 estimated_bandwidth_bps: u64,
500 peak_bandwidth_bps: u64,
501 last_observed_ms: u64,
502 last_slow_observed_ms: u64,
503 sample_count: u64,
504 window_started_ms: u64,
505 window_bytes: u64,
506 peak_usage_bps: u64,
507}
508
509#[cfg(feature = "discovery")]
510#[derive(Debug, Clone, Default)]
511struct DiscoverySideThrottleState {
512 next_ping_ms: u64,
513 next_full_ms: u64,
514 pending_incremental: bool,
515 incremental_retries_remaining: u8,
516 has_sent_full: bool,
517 last_topology: Vec<TopologyBoardNode>,
518}
519
520#[cfg(all(feature = "discovery", feature = "timesync"))]
521#[derive(Debug, Clone, Default)]
522struct TimeSyncSideThrottleState {
523 next_allowed_ms: u64,
524}
525
526#[cfg(feature = "discovery")]
527#[derive(Debug, Clone, Copy, PartialEq, Eq)]
528enum DiscoveryAdvertiseLevel {
529 MinimalPing,
530 Incremental,
531 Full,
532}
533
534impl AdaptiveRouteStats {
535 #[inline]
536 fn observe(&mut self, bytes: usize, sample_bps: u64, now_ms: u64) {
537 self.estimated_bandwidth_bps = if self.estimated_bandwidth_bps == 0 {
538 sample_bps
539 } else if sample_bps >= self.estimated_bandwidth_bps {
540 self.estimated_bandwidth_bps
541 .saturating_mul(3)
542 .saturating_add(sample_bps.saturating_mul(5))
543 / 8
544 } else {
545 self.estimated_bandwidth_bps
546 .saturating_mul(7)
547 .saturating_add(sample_bps)
548 / 8
549 };
550 self.peak_bandwidth_bps = self.peak_bandwidth_bps.max(sample_bps);
551 self.last_observed_ms = now_ms;
552 if sample_bps > 0 && sample_bps <= CONTROL_SLOW_LINK_CAPACITY_BPS {
553 self.last_slow_observed_ms = now_ms;
554 }
555 self.sample_count = self.sample_count.saturating_add(1);
556 if self.window_started_ms == 0 || now_ms.saturating_sub(self.window_started_ms) > 1_000 {
557 self.window_started_ms = now_ms;
558 self.window_bytes = 0;
559 }
560 self.window_bytes = self.window_bytes.saturating_add(bytes as u64);
561 self.peak_usage_bps = self.peak_usage_bps.max(self.current_usage_bps(now_ms));
562 }
563
564 #[inline]
565 fn current_usage_bps(&self, now_ms: u64) -> u64 {
566 if self.window_started_ms == 0 {
567 return 0;
568 }
569 let elapsed_ms = now_ms.saturating_sub(self.window_started_ms).max(1);
570 (u128::from(self.window_bytes).saturating_mul(1000) / u128::from(elapsed_ms))
571 .min(u128::from(u64::MAX)) as u64
572 }
573
574 #[inline]
575 fn available_headroom_bps(&self, now_ms: u64) -> u64 {
576 let capacity = self
577 .estimated_bandwidth_bps
578 .max(self.peak_bandwidth_bps)
579 .max(1);
580 capacity.saturating_sub(self.current_usage_bps(now_ms))
581 }
582
583 #[inline]
584 fn weight(&self, now_ms: u64) -> u64 {
585 self.available_headroom_bps(now_ms).max(1)
586 }
587
588 #[inline]
589 fn snapshot(&self, now_ms: u64, auto_balancing_enabled: bool) -> AdaptiveLinkStats {
590 let current_usage_bps = self.current_usage_bps(now_ms);
591 let estimated_capacity_bps = self.estimated_bandwidth_bps.max(1);
592 let peak_capacity_bps = self.peak_bandwidth_bps.max(estimated_capacity_bps);
593 let available_headroom_bps = peak_capacity_bps.saturating_sub(current_usage_bps);
594 AdaptiveLinkStats {
595 auto_balancing_enabled,
596 estimated_capacity_bps,
597 peak_capacity_bps,
598 current_usage_bps,
599 peak_usage_bps: self.peak_usage_bps.max(current_usage_bps),
600 available_headroom_bps,
601 effective_weight: available_headroom_bps.max(1),
602 last_observed_ms: self.last_observed_ms,
603 sample_count: self.sample_count,
604 }
605 }
606}
607
608#[derive(Debug, Clone, Default)]
609struct TypeRuntimeStatsInner {
610 tx_packets: u64,
611 tx_bytes: u64,
612 rx_packets: u64,
613 rx_bytes: u64,
614 relayed_tx_packets: u64,
615 relayed_tx_bytes: u64,
616 relayed_rx_packets: u64,
617 relayed_rx_bytes: u64,
618 tx_retries: u64,
619 handler_failures: u64,
620}
621
622#[derive(Debug, Clone, Default)]
623struct SideRuntimeStatsInner {
624 tx_packets: u64,
625 tx_bytes: u64,
626 rx_packets: u64,
627 rx_bytes: u64,
628 relayed_tx_packets: u64,
629 relayed_tx_bytes: u64,
630 relayed_rx_packets: u64,
631 relayed_rx_bytes: u64,
632 tx_retries: u64,
633 tx_handler_failures: u64,
634 total_handler_retries: u64,
635 side_transport_full_frames: u64,
636 side_transport_compact_frames: u64,
637 side_transport_compact_delta_frames: u64,
638 side_transport_compact_omitted_timestamp_frames: u64,
639 side_transport_chunk_frames: u64,
640 side_transport_raw_bytes: u64,
641 side_transport_wire_bytes: u64,
642 side_transport_bytes_saved: u64,
643 side_transport_min_compact_overhead_bytes: Option<usize>,
644 side_transport_max_compact_overhead_bytes: Option<usize>,
645 side_transport_compact_target_misses: u64,
646 side_transport_template_evictions: u64,
647 data_types: BTreeMap<u32, TypeRuntimeStatsInner>,
648}
649
650impl SideRuntimeStatsInner {
651 fn type_stats_mut(&mut self, ty: crate::DataType) -> &mut TypeRuntimeStatsInner {
652 self.data_types.entry(ty.as_u32()).or_default()
653 }
654
655 fn note_tx(&mut self, ty: crate::DataType, bytes: usize, retries: usize) {
656 self.tx_packets = self.tx_packets.saturating_add(1);
657 self.tx_bytes = self.tx_bytes.saturating_add(bytes as u64);
658 self.relayed_tx_packets = self.relayed_tx_packets.saturating_add(1);
659 self.relayed_tx_bytes = self.relayed_tx_bytes.saturating_add(bytes as u64);
660 self.tx_retries = self.tx_retries.saturating_add(retries as u64);
661 self.total_handler_retries = self.total_handler_retries.saturating_add(retries as u64);
662 let stats = self.type_stats_mut(ty);
663 stats.tx_packets = stats.tx_packets.saturating_add(1);
664 stats.tx_bytes = stats.tx_bytes.saturating_add(bytes as u64);
665 stats.relayed_tx_packets = stats.relayed_tx_packets.saturating_add(1);
666 stats.relayed_tx_bytes = stats.relayed_tx_bytes.saturating_add(bytes as u64);
667 stats.tx_retries = stats.tx_retries.saturating_add(retries as u64);
668 }
669
670 fn note_rx(&mut self, ty: crate::DataType, bytes: usize) {
671 self.rx_packets = self.rx_packets.saturating_add(1);
672 self.rx_bytes = self.rx_bytes.saturating_add(bytes as u64);
673 self.relayed_rx_packets = self.relayed_rx_packets.saturating_add(1);
674 self.relayed_rx_bytes = self.relayed_rx_bytes.saturating_add(bytes as u64);
675 let stats = self.type_stats_mut(ty);
676 stats.rx_packets = stats.rx_packets.saturating_add(1);
677 stats.rx_bytes = stats.rx_bytes.saturating_add(bytes as u64);
678 stats.relayed_rx_packets = stats.relayed_rx_packets.saturating_add(1);
679 stats.relayed_rx_bytes = stats.relayed_rx_bytes.saturating_add(bytes as u64);
680 }
681
682 fn note_tx_failure(&mut self, ty: crate::DataType, retries: usize) {
683 self.tx_handler_failures = self.tx_handler_failures.saturating_add(1);
684 self.tx_retries = self.tx_retries.saturating_add(retries as u64);
685 self.total_handler_retries = self.total_handler_retries.saturating_add(retries as u64);
686 let stats = self.type_stats_mut(ty);
687 stats.handler_failures = stats.handler_failures.saturating_add(1);
688 stats.tx_retries = stats.tx_retries.saturating_add(retries as u64);
689 }
690
691 fn note_side_transport_full(&mut self, raw_bytes: usize, wire_bytes: usize) {
692 self.side_transport_full_frames = self.side_transport_full_frames.saturating_add(1);
693 self.note_side_transport_bytes(raw_bytes, wire_bytes);
694 }
695
696 fn note_side_transport_compact(
697 &mut self,
698 raw_bytes: usize,
699 wire_bytes: usize,
700 compact_overhead_bytes: usize,
701 used_timestamp_delta: bool,
702 omitted_timestamp: bool,
703 ) {
704 self.side_transport_compact_frames = self.side_transport_compact_frames.saturating_add(1);
705 if used_timestamp_delta {
706 self.side_transport_compact_delta_frames =
707 self.side_transport_compact_delta_frames.saturating_add(1);
708 }
709 if omitted_timestamp {
710 self.side_transport_compact_omitted_timestamp_frames = self
711 .side_transport_compact_omitted_timestamp_frames
712 .saturating_add(1);
713 }
714 self.note_side_transport_bytes(raw_bytes, wire_bytes);
715 self.side_transport_min_compact_overhead_bytes = Some(
716 self.side_transport_min_compact_overhead_bytes
717 .map_or(compact_overhead_bytes, |v| v.min(compact_overhead_bytes)),
718 );
719 self.side_transport_max_compact_overhead_bytes = Some(
720 self.side_transport_max_compact_overhead_bytes
721 .map_or(compact_overhead_bytes, |v| v.max(compact_overhead_bytes)),
722 );
723 }
724
725 fn note_side_transport_chunks(&mut self, chunks: usize) {
726 self.side_transport_chunk_frames = self
727 .side_transport_chunk_frames
728 .saturating_add(chunks as u64);
729 }
730
731 fn note_side_transport_bytes(&mut self, raw_bytes: usize, wire_bytes: usize) {
732 self.side_transport_raw_bytes = self
733 .side_transport_raw_bytes
734 .saturating_add(raw_bytes as u64);
735 self.side_transport_wire_bytes = self
736 .side_transport_wire_bytes
737 .saturating_add(wire_bytes as u64);
738 if raw_bytes > wire_bytes {
739 self.side_transport_bytes_saved = self
740 .side_transport_bytes_saved
741 .saturating_add((raw_bytes - wire_bytes) as u64);
742 }
743 }
744
745 fn note_side_transport_compact_target_miss(&mut self) {
746 self.side_transport_compact_target_misses =
747 self.side_transport_compact_target_misses.saturating_add(1);
748 }
749
750 fn note_side_transport_template_eviction(&mut self) {
751 self.side_transport_template_evictions =
752 self.side_transport_template_evictions.saturating_add(1);
753 }
754}
755
756#[derive(Clone, Debug)]
757pub struct RelayConfig {
758 sender: Arc<str>,
759 memory: RuntimeMemoryConfig,
760}
761
762impl RelayConfig {
763 pub fn new() -> Self {
764 Self::default()
765 }
766
767 pub fn with_sender<S: AsRef<str>>(mut self, sender: S) -> Self {
768 self.sender = Arc::from(sender.as_ref());
769 self
770 }
771
772 pub fn with_memory_config(mut self, memory: RuntimeMemoryConfig) -> TelemetryResult<Self> {
773 memory.validate()?;
774 self.memory = memory;
775 Ok(self)
776 }
777
778 fn sender(&self) -> Arc<str> {
779 self.sender.clone()
780 }
781
782 fn memory_config(&self) -> RuntimeMemoryConfig {
783 self.memory
784 }
785}
786
787impl Default for RelayConfig {
788 fn default() -> Self {
789 Self {
790 sender: Arc::from("RELAY"),
791 memory: RuntimeMemoryConfig::default(),
792 }
793 }
794}
795
796#[derive(Clone, Copy, Debug, PartialEq, Eq)]
797enum RouteSelectionOrigin {
798 Flood,
799 Discovered,
800}
801
802struct RelayInner {
804 memory: RuntimeMemoryConfig,
805 sides: Vec<Option<RelaySide>>,
806 route_overrides: BTreeMap<(Option<RelaySideId>, RelaySideId), bool>,
807 typed_route_overrides: BTreeMap<(Option<RelaySideId>, u32, RelaySideId), bool>,
808 route_weights: BTreeMap<(Option<RelaySideId>, RelaySideId), u32>,
809 route_priorities: BTreeMap<(Option<RelaySideId>, RelaySideId), u32>,
810 source_route_modes: BTreeMap<Option<RelaySideId>, RouteSelectionMode>,
811 route_selection_cursors: BTreeMap<Option<RelaySideId>, u64>,
812 adaptive_route_stats: BTreeMap<RelaySideId, AdaptiveRouteStats>,
813 side_runtime_stats: BTreeMap<RelaySideId, SideRuntimeStatsInner>,
814 side_transport: BTreeMap<RelaySideId, SideTransportState>,
815 rx_queue: BoundedDeque<RelayRxItem>,
816 tx_queue: BoundedDeque<RelayTxItem>,
817 tx_priority_burst: u8,
818 replay_queue: BoundedDeque<RelayReplayItem>,
819 recent_rx: BoundedDeque<u64>,
820 reliable_tx: BTreeMap<(RelaySideId, u32), ReliableTxState>,
821 reliable_rx: BTreeMap<(RelaySideId, u32), ReliableRxState>,
822 reliable_return_routes: BTreeMap<u64, ReliableReturnRouteState>,
823 reliable_return_route_order: VecDeque<u64>,
824 end_to_end_acked_destinations: BTreeMap<u64, BTreeSet<u64>>,
825 end_to_end_acked_destination_order: VecDeque<u64>,
826 total_handler_failures: u64,
827 total_handler_retries: u64,
828 #[cfg(feature = "discovery")]
829 discovery_routes: BTreeMap<RelaySideId, DiscoverySideState>,
830 #[cfg(feature = "discovery")]
831 discovery_cadence: DiscoveryCadenceState,
832 #[cfg(feature = "discovery")]
833 discovery_side_throttle: BTreeMap<RelaySideId, DiscoverySideThrottleState>,
834 #[cfg(all(feature = "discovery", feature = "timesync"))]
835 timesync_side_throttle: BTreeMap<RelaySideId, TimeSyncSideThrottleState>,
836}
837
838#[derive(Clone, Copy, Debug, PartialEq, Eq)]
839enum RelayQueueKind {
840 Rx,
841 Tx,
842 Replay,
843 Recent,
844 ReliableRxBuffer,
845 #[cfg(feature = "discovery")]
846 Discovery,
847}
848
849impl RelayInner {
850 #[cfg(feature = "discovery")]
851 fn topology_board_byte_cost(board: &TopologyBoardNode) -> usize {
852 board
853 .sender_id
854 .len()
855 .saturating_add(board.reachable_endpoints.len() * size_of::<crate::DataEndpoint>())
856 .saturating_add(
857 board
858 .reachable_timesync_sources
859 .iter()
860 .map(|s| s.len())
861 .sum::<usize>(),
862 )
863 .saturating_add(board.connections.iter().map(|s| s.len()).sum::<usize>())
864 }
865
866 #[cfg(feature = "discovery")]
867 fn discovery_sender_byte_cost(sender: &str, state: &DiscoverySenderState) -> usize {
868 sender
869 .len()
870 .saturating_add(state.reachable.len() * size_of::<crate::DataEndpoint>())
871 .saturating_add(state.advertised_reachable.len() * size_of::<crate::DataEndpoint>())
872 .saturating_add(
873 state
874 .reachable_timesync_sources
875 .iter()
876 .map(|s| s.len())
877 .sum::<usize>(),
878 )
879 .saturating_add(
880 state
881 .advertised_reachable_timesync_sources
882 .iter()
883 .map(|s| s.len())
884 .sum::<usize>(),
885 )
886 .saturating_add(
887 state
888 .topology_boards
889 .iter()
890 .map(Self::topology_board_byte_cost)
891 .sum::<usize>(),
892 )
893 .saturating_add(size_of::<DiscoverySenderState>())
894 }
895
896 #[cfg(feature = "discovery")]
897 fn discovery_route_byte_cost(route: &DiscoverySideState) -> usize {
898 size_of::<DiscoverySideState>()
899 .saturating_add(route.reachable.len() * size_of::<crate::DataEndpoint>())
900 .saturating_add(
901 route
902 .reachable_timesync_sources
903 .iter()
904 .map(|s| s.len())
905 .sum::<usize>(),
906 )
907 .saturating_add(
908 route
909 .announcers
910 .iter()
911 .map(|(sender, state)| Self::discovery_sender_byte_cost(sender, state))
912 .sum::<usize>(),
913 )
914 }
915
916 #[cfg(feature = "discovery")]
917 fn discovery_bytes_used(&self) -> usize {
918 self.discovery_routes
919 .values()
920 .map(Self::discovery_route_byte_cost)
921 .sum()
922 }
923
924 #[inline]
925 fn reliable_rx_buffered_bytes(&self) -> usize {
926 self.reliable_rx
927 .values()
928 .flat_map(|state| state.buffered.values())
929 .map(|bytes| size_of::<Arc<[u8]>>() + bytes.len())
930 .sum()
931 }
932
933 #[inline]
934 fn shared_queue_bytes_used(&self) -> usize {
935 self.rx_queue
936 .bytes_used()
937 .saturating_add(self.tx_queue.bytes_used())
938 .saturating_add(self.replay_queue.bytes_used())
939 .saturating_add(self.recent_rx.max_bytes())
940 .saturating_add(self.reliable_rx_buffered_bytes())
941 .saturating_add(crate::config::schema_bytes_used())
942 .saturating_add({
943 #[cfg(feature = "discovery")]
944 {
945 self.discovery_bytes_used()
946 }
947 #[cfg(not(feature = "discovery"))]
948 {
949 0
950 }
951 })
952 }
953
954 fn reliable_rx_buffer_len(&self) -> usize {
955 self.reliable_rx
956 .values()
957 .map(|state| state.buffered.len())
958 .sum()
959 }
960
961 fn pop_reliable_rx_buffered(&mut self) -> Option<Arc<[u8]>> {
962 let key = self
963 .reliable_rx
964 .iter()
965 .find_map(|(key, state)| (!state.buffered.is_empty()).then_some(*key))?;
966 self.reliable_rx
967 .get_mut(&key)?
968 .buffered
969 .pop_first()
970 .map(|(_, v)| v)
971 }
972
973 fn pop_shared_queue_item(&mut self, preferred: RelayQueueKind) -> bool {
974 match preferred {
975 RelayQueueKind::Rx => self.rx_queue.pop_front().is_some(),
976 RelayQueueKind::Tx => self.tx_queue.pop_front().is_some(),
977 RelayQueueKind::Replay => self.replay_queue.pop_front().is_some(),
978 RelayQueueKind::Recent => self.recent_rx.pop_front().is_some(),
979 RelayQueueKind::ReliableRxBuffer => self.pop_reliable_rx_buffered().is_some(),
980 #[cfg(feature = "discovery")]
981 RelayQueueKind::Discovery => self.pop_discovery_route(),
982 }
983 }
984
985 #[cfg(feature = "discovery")]
986 fn pop_discovery_route(&mut self) -> bool {
987 let Some((&side, _)) = self
988 .discovery_routes
989 .iter()
990 .min_by_key(|(_, route)| route.last_seen_ms)
991 else {
992 return false;
993 };
994 self.discovery_routes.remove(&side);
995 Self::queue_budget_warning("topology route evicted because shared queue budget is full");
996 true
997 }
998
999 fn largest_shared_queue(&self) -> Option<RelayQueueKind> {
1000 let candidates = [
1001 (
1002 RelayQueueKind::Rx,
1003 self.rx_queue.bytes_used(),
1004 self.rx_queue.len(),
1005 ),
1006 (
1007 RelayQueueKind::Tx,
1008 self.tx_queue.bytes_used(),
1009 self.tx_queue.len(),
1010 ),
1011 (
1012 RelayQueueKind::Replay,
1013 self.replay_queue.bytes_used(),
1014 self.replay_queue.len(),
1015 ),
1016 (RelayQueueKind::Recent, 0, 0),
1017 (
1018 RelayQueueKind::ReliableRxBuffer,
1019 self.reliable_rx_buffered_bytes(),
1020 self.reliable_rx_buffer_len(),
1021 ),
1022 #[cfg(feature = "discovery")]
1023 (
1024 RelayQueueKind::Discovery,
1025 self.discovery_bytes_used(),
1026 self.discovery_routes.len(),
1027 ),
1028 ];
1029 candidates
1030 .into_iter()
1031 .filter(|(_, bytes, len)| *bytes > 0 && *len > 0)
1032 .max_by_key(|(kind, bytes, _)| {
1033 (
1034 *bytes,
1035 if *kind == RelayQueueKind::ReliableRxBuffer {
1036 0
1037 } else {
1038 1
1039 },
1040 )
1041 })
1042 .map(|(kind, _, _)| kind)
1043 }
1044
1045 fn make_shared_queue_room(
1046 &mut self,
1047 incoming_cost: usize,
1048 preferred: RelayQueueKind,
1049 ) -> TelemetryResult<()> {
1050 if incoming_cost > self.memory.max_queue_budget {
1051 return Err(TelemetryError::PacketTooLarge(
1052 "Item exceeds maximum shared queue budget",
1053 ));
1054 }
1055
1056 while self.shared_queue_bytes_used().saturating_add(incoming_cost)
1057 > self.memory.max_queue_budget
1058 {
1059 let victim = self.largest_shared_queue().unwrap_or(preferred);
1060 if victim == RelayQueueKind::Discovery {
1061 Self::queue_budget_warning("topology data is using the largest queue budget share");
1062 }
1063 if !self.pop_shared_queue_item(victim) && !self.pop_shared_queue_item(preferred) {
1064 return Err(TelemetryError::PacketTooLarge(
1065 "Item exceeds maximum shared queue budget",
1066 ));
1067 }
1068 }
1069
1070 Ok(())
1071 }
1072
1073 #[inline]
1074 fn queue_budget_warning(msg: &str) {
1075 #[cfg(feature = "std")]
1076 eprintln!("sedsnet queue budget warning: {msg}");
1077 let _ = msg;
1078 }
1079
1080 #[cfg(feature = "discovery")]
1081 fn fit_discovery_budget(&mut self) {
1082 while self.shared_queue_bytes_used() > self.memory.max_queue_budget {
1083 if !self.pop_discovery_route() {
1084 break;
1085 }
1086 }
1087 }
1088
1089 fn push_rx(&mut self, item: RelayRxItem) -> TelemetryResult<()> {
1090 self.make_shared_queue_room(item.byte_cost(), RelayQueueKind::Rx)?;
1091 self.rx_queue
1092 .push_back_prioritized(item, |queued| queued.priority)
1093 }
1094
1095 fn push_tx(&mut self, item: RelayTxItem) -> TelemetryResult<()> {
1096 #[cfg(feature = "discovery")]
1097 {
1098 let incoming = match &item.data {
1099 RelayItem::Packet(pkt) if discovery::is_discovery_type(pkt.data_type()) => {
1100 Some((pkt.data_type(), pkt.sender().to_owned()))
1101 }
1102 RelayItem::Packet(_) | RelayItem::Packed(_) => None,
1103 };
1104 if let Some((incoming_ty, incoming_sender)) = incoming {
1105 self.tx_queue.retain(|queued| {
1106 let same_snapshot = match &queued.data {
1107 RelayItem::Packet(pkt) => {
1108 pkt.data_type() == incoming_ty && pkt.sender() == incoming_sender
1109 }
1110 RelayItem::Packed(_) => false,
1111 };
1112 queued.dst != item.dst || !same_snapshot
1113 });
1114 }
1115 }
1116 self.make_shared_queue_room(item.byte_cost(), RelayQueueKind::Tx)?;
1117 self.tx_queue
1118 .push_back_prioritized(item, |queued| queued.priority)
1119 }
1120
1121 fn push_replay(&mut self, item: RelayReplayItem) -> TelemetryResult<()> {
1122 self.make_shared_queue_room(item.byte_cost(), RelayQueueKind::Replay)?;
1123 self.replay_queue
1124 .push_back_prioritized(item, |queued| queued.priority)
1125 }
1126
1127 fn push_recent_rx(&mut self, id: u64) -> TelemetryResult<()> {
1128 while self.recent_rx.len() >= self.memory.max_recent_rx_ids {
1129 let _ = self.recent_rx.pop_front();
1130 }
1131 self.make_shared_queue_room(0, RelayQueueKind::Recent)?;
1132 self.recent_rx.push_back(id)
1133 }
1134
1135 fn buffer_reliable_rx(
1136 &mut self,
1137 side: RelaySideId,
1138 ty: crate::DataType,
1139 seq: u32,
1140 bytes: Arc<[u8]>,
1141 ) -> TelemetryResult<()> {
1142 let key = Relay::reliable_key(side, ty);
1143 if self
1144 .reliable_rx
1145 .get(&key)
1146 .is_some_and(|state| state.buffered.contains_key(&seq))
1147 {
1148 return Ok(());
1149 }
1150 let cost = size_of::<Arc<[u8]>>() + bytes.len();
1151 self.make_shared_queue_room(cost, RelayQueueKind::ReliableRxBuffer)?;
1152 let rx_state = self
1153 .reliable_rx
1154 .entry(key)
1155 .or_insert_with(|| ReliableRxState {
1156 expected_seq: 1,
1157 buffered: BTreeMap::new(),
1158 });
1159 if rx_state.buffered.len() >= runtime_reliable_max_pending() {
1160 let _ = rx_state.buffered.pop_first();
1161 }
1162 rx_state.buffered.insert(seq, bytes);
1163 Ok(())
1164 }
1165}
1166
1167pub struct Relay {
1172 sender: RouterMutex<Arc<str>>,
1173 state: RouterMutex<RelayInner>,
1174 side_tx_gate: ReentryGate,
1175 clock: Box<dyn Clock + Send + Sync>,
1176}
1177
1178enum RemoteSidePlan {
1179 Target(Vec<RelaySideId>),
1180}
1181
1182impl Relay {
1183 const END_TO_END_ACK_SENDER: &'static str = "E2EACK";
1184 const END_TO_END_ACK_PREFIX: &'static str = "E2EACK:";
1185
1186 fn relay_item_priority(data: &RelayItem) -> TelemetryResult<u8> {
1187 let ty = match data {
1188 RelayItem::Packet(pkt) => pkt.data_type(),
1189 RelayItem::Packed(bytes) => wire_format::peek_envelope(bytes.as_ref())?.ty,
1190 };
1191 Ok(crate::scheduler_priority(ty))
1192 }
1193
1194 #[inline]
1195 fn is_side_tx_busy(err: &TelemetryError) -> bool {
1196 matches!(err, TelemetryError::Io("side tx busy"))
1197 }
1198
1199 fn process_replay_queue_item(&self) -> TelemetryResult<bool> {
1200 let Some(item) = ({
1201 let mut st = self.state.lock();
1202 st.replay_queue.pop_front()
1203 }) else {
1204 return Ok(false);
1205 };
1206 let frame = wire_format::peek_frame_info(item.bytes.as_ref())?;
1207 let ty = frame.envelope.ty;
1208 let Some(hdr) = frame.reliable else {
1209 return Ok(false);
1210 };
1211 {
1212 let mut st = self.state.lock();
1213 let tx_state = self.reliable_tx_state_mut(&mut st, item.dst, ty);
1214 if !tx_state.sent.contains_key(&hdr.seq) {
1215 return Ok(false);
1216 }
1217 }
1218 if let Err(e) = self.send_reliable_raw_to_side(item.dst, item.bytes.clone()) {
1219 if Self::is_side_tx_busy(&e) {
1220 let mut st = self.state.lock();
1221 st.push_replay(item)?;
1222 return Ok(false);
1223 }
1224 return Err(e);
1225 }
1226 let mut st = self.state.lock();
1227 let tx_state = self.reliable_tx_state_mut(&mut st, item.dst, ty);
1228 if let Some(sent) = tx_state.sent.get_mut(&hdr.seq) {
1229 sent.last_send_ms = self.clock.now_ms();
1230 sent.queued = false;
1231 }
1232 Ok(true)
1233 }
1234
1235 fn pop_ready_tx_item(
1236 &self,
1237 ) -> Option<(
1238 Option<RelaySideId>,
1239 RelaySideId,
1240 RelayTxHandlerFn,
1241 RelaySideOptions,
1242 RelayItem,
1243 )> {
1244 let mut st = self.state.lock();
1245 let mut priority_burst = st.tx_priority_burst;
1246 let item = st
1247 .tx_queue
1248 .pop_front_fair(&mut priority_burst, MAX_PRIORITY_BURST, |queued| {
1249 queued.priority
1250 });
1251 st.tx_priority_burst = priority_burst;
1252 if let Some(item) = item {
1253 let side = st.sides.get(item.dst).and_then(|side| side.clone());
1254 side.map(|s| (item.src, item.dst, s.tx_handler, s.opts, item.data))
1255 } else {
1256 None
1257 }
1258 }
1259
1260 fn send_tx_item(
1261 &self,
1262 src: Option<RelaySideId>,
1263 dst: RelaySideId,
1264 handler: RelayTxHandlerFn,
1265 opts: RelaySideOptions,
1266 data: RelayItem,
1267 ) -> TelemetryResult<bool> {
1268 let allowed = {
1269 let mut st = self.state.lock();
1270 let ty = match &data {
1271 RelayItem::Packet(pkt) => Some(pkt.data_type()),
1272 RelayItem::Packed(bytes) => Some(wire_format::peek_envelope(bytes.as_ref())?.ty),
1273 };
1274 let route_allowed = self.route_allowed_locked(&st, src, ty, dst);
1275 #[cfg(all(feature = "discovery", feature = "timesync"))]
1276 let timesync_allowed = ty
1277 .map(|ty| {
1278 Self::timesync_allowed_for_side_locked(&mut st, dst, ty, self.clock.now_ms())
1279 })
1280 .unwrap_or(true);
1281 #[cfg(not(all(feature = "discovery", feature = "timesync")))]
1282 let timesync_allowed = true;
1283 route_allowed && timesync_allowed
1284 };
1285 if !allowed {
1286 return Ok(false);
1287 }
1288 if opts.reliable_enabled && matches!(handler, RelayTxHandlerFn::Packed(_)) {
1289 self.send_reliable_to_side(dst, data)?;
1290 Ok(true)
1291 } else if let Some(adjusted) = self.adjust_reliable_for_side(opts, data)? {
1292 self.call_tx_handler(dst, &handler, &adjusted)?;
1293 Ok(true)
1294 } else {
1295 Ok(false)
1296 }
1297 }
1298
1299 pub fn new(clock: Box<dyn Clock + Send + Sync>) -> Self {
1301 Self::new_with_config(RelayConfig::default(), clock)
1302 }
1303
1304 pub fn new_with_config(cfg: RelayConfig, clock: Box<dyn Clock + Send + Sync>) -> Self {
1306 let memory = cfg.memory_config();
1307 Self {
1308 sender: RouterMutex::new(cfg.sender()),
1309 state: RouterMutex::new(RelayInner {
1310 memory,
1311 sides: Vec::new(),
1312 route_overrides: BTreeMap::new(),
1313 typed_route_overrides: BTreeMap::new(),
1314 route_weights: BTreeMap::new(),
1315 route_priorities: BTreeMap::new(),
1316 source_route_modes: BTreeMap::new(),
1317 route_selection_cursors: BTreeMap::new(),
1318 adaptive_route_stats: BTreeMap::new(),
1319 side_runtime_stats: BTreeMap::new(),
1320 side_transport: BTreeMap::new(),
1321 rx_queue: BoundedDeque::new(
1322 memory.max_queue_budget,
1323 memory.starting_queue_size,
1324 memory.queue_grow_step,
1325 ),
1326 tx_queue: BoundedDeque::new(
1327 memory.max_queue_budget,
1328 memory.starting_queue_size,
1329 memory.queue_grow_step,
1330 ),
1331 tx_priority_burst: 0,
1332 replay_queue: BoundedDeque::new(
1333 memory.max_queue_budget,
1334 memory.starting_queue_size,
1335 memory.queue_grow_step,
1336 ),
1337 recent_rx: BoundedDeque::new(
1338 memory.recent_rx_queue_bytes(),
1339 memory.recent_rx_queue_bytes(),
1340 memory.queue_grow_step,
1341 ),
1342 reliable_tx: BTreeMap::new(),
1343 reliable_rx: BTreeMap::new(),
1344 reliable_return_routes: BTreeMap::new(),
1345 reliable_return_route_order: VecDeque::new(),
1346 end_to_end_acked_destinations: BTreeMap::new(),
1347 end_to_end_acked_destination_order: VecDeque::new(),
1348 total_handler_failures: 0,
1349 total_handler_retries: 0,
1350 #[cfg(feature = "discovery")]
1351 discovery_routes: BTreeMap::new(),
1352 #[cfg(feature = "discovery")]
1353 discovery_cadence: DiscoveryCadenceState::default(),
1354 #[cfg(feature = "discovery")]
1355 discovery_side_throttle: BTreeMap::new(),
1356 #[cfg(all(feature = "discovery", feature = "timesync"))]
1357 timesync_side_throttle: BTreeMap::new(),
1358 }),
1359 side_tx_gate: ReentryGate::new(),
1360 clock,
1361 }
1362 }
1363
1364 #[inline]
1365 fn sender_arc(&self) -> Arc<str> {
1366 self.sender.lock().clone()
1367 }
1368
1369 #[inline]
1370 pub fn sender(&self) -> Arc<str> {
1371 self.sender_arc()
1372 }
1373
1374 pub fn set_sender<S: AsRef<str>>(&self, sender: S) {
1375 *self.sender.lock() = Arc::from(sender.as_ref());
1376 }
1377
1378 #[inline]
1379 fn try_enter_side_tx(&self) -> Option<ReentryGuard<'_>> {
1380 self.side_tx_gate.try_enter()
1381 }
1382
1383 #[inline]
1384 fn side_tx_active(&self) -> bool {
1385 self.side_tx_gate.is_active()
1386 }
1387
1388 #[inline]
1389 fn side_ref(st: &RelayInner, side: RelaySideId) -> TelemetryResult<&RelaySide> {
1390 st.sides
1391 .get(side)
1392 .and_then(|side| side.as_ref())
1393 .ok_or(TelemetryError::HandlerError("relay: invalid side id"))
1394 }
1395
1396 fn note_side_tx_success(
1397 &self,
1398 side: RelaySideId,
1399 ty: crate::DataType,
1400 bytes: usize,
1401 attempts: usize,
1402 ) {
1403 let mut st = self.state.lock();
1404 let entry = st.side_runtime_stats.entry(side).or_default();
1405 entry.note_tx(ty, bytes, attempts.saturating_sub(1));
1406 }
1407
1408 fn note_side_tx_failure(&self, side: RelaySideId, ty: crate::DataType, attempts: usize) {
1409 let mut st = self.state.lock();
1410 st.total_handler_failures = st.total_handler_failures.saturating_add(1);
1411 st.total_handler_retries = st.total_handler_retries.saturating_add(attempts as u64);
1412 let entry = st.side_runtime_stats.entry(side).or_default();
1413 entry.note_tx_failure(ty, attempts);
1414 }
1415
1416 fn note_side_rx(&self, side: RelaySideId, ty: crate::DataType, bytes: usize) {
1417 let mut st = self.state.lock();
1418 let entry = st.side_runtime_stats.entry(side).or_default();
1419 entry.note_rx(ty, bytes);
1420 }
1421
1422 #[inline]
1423 fn ensure_side_ingress_enabled(&self, side: RelaySideId) -> TelemetryResult<()> {
1424 let st = self.state.lock();
1425 let side_ref = Self::side_ref(&st, side)?;
1426 if side_ref.opts.ingress_enabled {
1427 Ok(())
1428 } else {
1429 Err(TelemetryError::HandlerError(
1430 "relay: ingress disabled for side id",
1431 ))
1432 }
1433 }
1434
1435 #[inline]
1436 fn route_allowed_locked(
1437 &self,
1438 st: &RelayInner,
1439 src: Option<RelaySideId>,
1440 ty: Option<crate::DataType>,
1441 dst: RelaySideId,
1442 ) -> bool {
1443 let Ok(dst_side) = Self::side_ref(st, dst) else {
1444 return false;
1445 };
1446 if !dst_side.opts.egress_enabled {
1447 return false;
1448 }
1449 if let Some(src_id) = src {
1450 let Ok(src_side) = Self::side_ref(st, src_id) else {
1451 return false;
1452 };
1453 if !src_side.opts.ingress_enabled || src_id == dst {
1454 return false;
1455 }
1456 }
1457 let base_allowed = st.route_overrides.get(&(src, dst)).copied().unwrap_or(true);
1458 if !base_allowed {
1459 return false;
1460 }
1461
1462 let Some(ty) = ty else {
1463 return true;
1464 };
1465 if st
1466 .typed_route_overrides
1467 .keys()
1468 .any(|(typed_src, typed_ty, _)| *typed_src == src && *typed_ty == ty.as_u32())
1469 {
1470 return st
1471 .typed_route_overrides
1472 .get(&(src, ty.as_u32(), dst))
1473 .copied()
1474 .unwrap_or(false);
1475 }
1476 true
1477 }
1478
1479 fn has_typed_route_overrides_locked(
1480 st: &RelayInner,
1481 src: Option<RelaySideId>,
1482 ty: crate::DataType,
1483 ) -> bool {
1484 st.typed_route_overrides
1485 .keys()
1486 .any(|(typed_src, typed_ty, _)| *typed_src == src && *typed_ty == ty.as_u32())
1487 }
1488
1489 fn eligible_side_ids_locked(
1490 &self,
1491 st: &RelayInner,
1492 src: Option<RelaySideId>,
1493 ty: Option<crate::DataType>,
1494 restrict_link_local: bool,
1495 ) -> Vec<RelaySideId> {
1496 st.sides
1497 .iter()
1498 .enumerate()
1499 .filter_map(|(side_id, side)| {
1500 let side = side.as_ref()?;
1501 if restrict_link_local && !side.opts.link_local_enabled {
1502 return None;
1503 }
1504 if self.route_allowed_locked(st, src, ty, side_id) {
1505 Some(side_id)
1506 } else {
1507 None
1508 }
1509 })
1510 .collect()
1511 }
1512
1513 fn apply_route_selection_locked(
1514 &self,
1515 st: &mut RelayInner,
1516 src: Option<RelaySideId>,
1517 mut sides: Vec<RelaySideId>,
1518 origin: RouteSelectionOrigin,
1519 ) -> Vec<RelaySideId> {
1520 if sides.len() <= 1 {
1521 return sides;
1522 }
1523
1524 let selection_mode = st.source_route_modes.get(&src).copied();
1525 if selection_mode.is_none() && origin == RouteSelectionOrigin::Discovered {
1526 return self.apply_adaptive_discovery_selection_locked(st, src, sides);
1527 }
1528
1529 match selection_mode.unwrap_or(RouteSelectionMode::Fanout) {
1530 RouteSelectionMode::Fanout => sides,
1531 RouteSelectionMode::Weighted => {
1532 sides.sort_unstable();
1533 let total_weight = sides.iter().fold(0_u64, |acc, side| {
1534 acc + u64::from(st.route_weights.get(&(src, *side)).copied().unwrap_or(1))
1535 });
1536 if total_weight == 0 {
1537 return Vec::new();
1538 }
1539 let cursor = st.route_selection_cursors.entry(src).or_insert(0);
1540 let pick = *cursor % total_weight;
1541 *cursor = cursor.wrapping_add(1);
1542 let mut remaining = pick;
1543 for side in sides {
1544 let weight =
1545 u64::from(st.route_weights.get(&(src, side)).copied().unwrap_or(1));
1546 if remaining < weight {
1547 return vec![side];
1548 }
1549 remaining -= weight;
1550 }
1551 Vec::new()
1552 }
1553 RouteSelectionMode::Failover => {
1554 sides.sort_by_key(|side| {
1555 (
1556 st.route_priorities.get(&(src, *side)).copied().unwrap_or(0),
1557 *side,
1558 )
1559 });
1560 sides.truncate(1);
1561 sides
1562 }
1563 }
1564 }
1565
1566 fn apply_adaptive_discovery_selection_locked(
1567 &self,
1568 st: &mut RelayInner,
1569 src: Option<RelaySideId>,
1570 mut sides: Vec<RelaySideId>,
1571 ) -> Vec<RelaySideId> {
1572 sides.sort_unstable();
1573 let mut unmeasured: Vec<_> = sides
1574 .iter()
1575 .copied()
1576 .filter(|side| !st.adaptive_route_stats.contains_key(side))
1577 .collect();
1578 if !unmeasured.is_empty() {
1579 let cursor = st.route_selection_cursors.entry(src).or_insert(0);
1580 let pick = (*cursor as usize) % unmeasured.len();
1581 *cursor = cursor.wrapping_add(1);
1582 return vec![unmeasured.swap_remove(pick)];
1583 }
1584
1585 let now_ms = self.clock.now_ms();
1586 let total_weight = sides.iter().fold(0_u64, |acc, side| {
1587 acc + st
1588 .adaptive_route_stats
1589 .get(side)
1590 .map(|stats| stats.weight(now_ms))
1591 .unwrap_or(1)
1592 });
1593 if total_weight == 0 {
1594 sides.truncate(1);
1595 return sides;
1596 }
1597
1598 let cursor = st.route_selection_cursors.entry(src).or_insert(0);
1599 let pick = *cursor % total_weight;
1600 *cursor = cursor.wrapping_add(1);
1601 let mut remaining = pick;
1602 for side in sides {
1603 let weight = st
1604 .adaptive_route_stats
1605 .get(&side)
1606 .map(|stats| stats.weight(now_ms))
1607 .unwrap_or(1);
1608 if remaining < weight {
1609 return vec![side];
1610 }
1611 remaining -= weight;
1612 }
1613 Vec::new()
1614 }
1615
1616 #[cfg(feature = "discovery")]
1620 fn retain_shortest_discovery_routes_locked(
1621 st: &RelayInner,
1622 sides: &mut Vec<RelaySideId>,
1623 endpoints: &[crate::DataEndpoint],
1624 now_ms: u64,
1625 ) {
1626 if sides.len() <= 1 || endpoints.is_empty() {
1627 return;
1628 }
1629
1630 let route_distance = |side: RelaySideId| -> Option<usize> {
1631 let route = st.discovery_routes.get(&side)?;
1632 route
1633 .announcers
1634 .iter()
1635 .filter(|(_, state)| {
1636 now_ms.saturating_sub(state.last_seen_ms) <= DISCOVERY_ROUTE_TTL_MS
1637 })
1638 .filter_map(|(announcer, state)| {
1639 let targets: BTreeSet<&str> = state
1640 .topology_boards
1641 .iter()
1642 .filter(|board| {
1643 endpoints
1644 .iter()
1645 .any(|endpoint| board.reachable_endpoints.contains(endpoint))
1646 })
1647 .map(|board| board.sender_id.as_str())
1648 .collect();
1649 if targets.is_empty() {
1650 return state
1651 .reachable
1652 .iter()
1653 .any(|endpoint| endpoints.contains(endpoint))
1654 .then_some(usize::MAX / 2);
1655 }
1656
1657 let mut distances: BTreeMap<&str, usize> = BTreeMap::new();
1658 let mut pending = VecDeque::from([(announcer.as_str(), 0usize)]);
1659 while let Some((sender, distance)) = pending.pop_front() {
1660 if distances.contains_key(sender) {
1661 continue;
1662 }
1663 distances.insert(sender, distance);
1664 if targets.contains(sender) {
1665 return Some(distance);
1666 }
1667 for board in &state.topology_boards {
1668 if board.sender_id == sender {
1669 for peer in &board.connections {
1670 if !distances.contains_key(peer.as_str()) {
1671 pending.push_back((peer.as_str(), distance + 1));
1672 }
1673 }
1674 } else if board.connections.iter().any(|peer| peer == sender)
1675 && !distances.contains_key(board.sender_id.as_str())
1676 {
1677 pending.push_back((board.sender_id.as_str(), distance + 1));
1678 }
1679 }
1680 }
1681 None
1682 })
1683 .min()
1684 };
1685
1686 let distances: Vec<(RelaySideId, usize)> = sides
1687 .iter()
1688 .filter_map(|side| route_distance(*side).map(|distance| (*side, distance)))
1689 .collect();
1690 let Some(best) = distances.iter().map(|(_, distance)| *distance).min() else {
1691 return;
1692 };
1693 sides.retain(|side| {
1694 distances
1695 .iter()
1696 .any(|(candidate, distance)| candidate == side && *distance == best)
1697 });
1698 }
1699
1700 fn record_side_tx_sample(
1701 &self,
1702 side: RelaySideId,
1703 bytes: usize,
1704 started_ms: u64,
1705 ended_ms: u64,
1706 ) {
1707 let sample_ms = ended_ms.saturating_sub(started_ms).max(1);
1708 let sample_bps = ((bytes as u128).saturating_mul(1000) / u128::from(sample_ms))
1709 .min(u128::from(u64::MAX)) as u64;
1710 let mut st = self.state.lock();
1711 st.adaptive_route_stats
1712 .entry(side)
1713 .or_default()
1714 .observe(bytes, sample_bps, ended_ms);
1715 }
1716
1717 pub fn note_side_link_probe_sample(
1722 &self,
1723 side: RelaySideId,
1724 bytes: usize,
1725 duration_ms: u64,
1726 ) -> TelemetryResult<()> {
1727 {
1728 let st = self.state.lock();
1729 let _ = Self::side_ref(&st, side).map_err(|_| TelemetryError::BadArg)?;
1730 }
1731 let ended_ms = self.clock.now_ms();
1732 self.record_side_tx_sample(side, bytes, ended_ms.saturating_sub(duration_ms), ended_ms);
1733 Ok(())
1734 }
1735
1736 fn relay_item_wire_len(data: &RelayItem) -> TelemetryResult<usize> {
1737 match data {
1738 RelayItem::Packet(pkt) => Ok(wire_format::pack_packet(pkt).len()),
1739 RelayItem::Packed(bytes) => Ok(bytes.len()),
1740 }
1741 }
1742
1743 #[inline]
1744 fn decode_end_to_end_reliable_ack(payload: &[u8]) -> TelemetryResult<u64> {
1745 if payload.len() != 8 {
1746 return Err(TelemetryError::Unpack("bad reliable e2e ack payload"));
1747 }
1748 Ok(u64::from_le_bytes(payload[0..8].try_into().unwrap()))
1749 }
1750
1751 #[inline]
1752 fn is_end_to_end_ack_sender(sender: &str) -> bool {
1753 sender == Self::END_TO_END_ACK_SENDER || sender.starts_with(Self::END_TO_END_ACK_PREFIX)
1754 }
1755
1756 #[inline]
1757 fn sender_hash(sender: &str) -> u64 {
1758 if let Some(address) = sender
1759 .strip_prefix("@addr:")
1760 .and_then(|value| value.parse::<u32>().ok())
1761 {
1762 return u64::from(address);
1763 }
1764 hash_bytes_u64(0x517C_C1B7_2722_0A95, sender.as_bytes())
1765 }
1766
1767 #[cfg(feature = "discovery")]
1768 fn canonical_sender_locked(st: &RelayInner, sender: &str) -> String {
1769 let Some(address) = sender
1770 .strip_prefix("@addr:")
1771 .and_then(|value| value.parse::<u32>().ok())
1772 else {
1773 return sender.to_string();
1774 };
1775 st.discovery_routes
1776 .values()
1777 .flat_map(|route| route.announcers.iter())
1778 .flat_map(|(announcer, state)| {
1779 core::iter::once(announcer.as_str()).chain(
1780 state
1781 .topology_boards
1782 .iter()
1783 .map(|board| board.sender_id.as_str()),
1784 )
1785 })
1786 .find(|candidate| sender_address_u32(candidate) == address)
1787 .map(ToString::to_string)
1788 .unwrap_or_else(|| sender.to_string())
1789 }
1790
1791 fn decode_end_to_end_ack_sender_hash(sender: &str) -> Option<u64> {
1792 sender
1793 .strip_prefix(Self::END_TO_END_ACK_PREFIX)
1794 .filter(|sender| !sender.is_empty())
1795 .map(Self::sender_hash)
1796 }
1797
1798 #[cfg(feature = "discovery")]
1799 fn is_end_to_end_destination_sender(&self, sender: &str) -> bool {
1800 sender != self.sender_arc().as_ref() && !Self::is_end_to_end_ack_sender(sender)
1801 }
1802
1803 fn reliable_control_target_packet_id(data: &RelayItem) -> TelemetryResult<Option<u64>> {
1812 match data {
1813 RelayItem::Packet(pkt) => {
1814 if pkt.data_type() != crate::DataType::ReliableAck
1815 || !Self::is_end_to_end_ack_sender(pkt.sender())
1816 {
1817 return Ok(None);
1818 }
1819 Self::decode_end_to_end_reliable_ack(pkt.payload()).map(Some)
1820 }
1821 RelayItem::Packed(bytes) => {
1822 if wire_format::peek_frame_info(bytes.as_ref())
1823 .ok()
1824 .is_some_and(|frame| frame.ack_only())
1825 {
1826 return Ok(None);
1827 }
1828 let pkt = wire_format::unpack_packet(bytes.as_ref())?;
1829 if pkt.data_type() != crate::DataType::ReliableAck
1830 || !Self::is_end_to_end_ack_sender(pkt.sender())
1831 {
1832 return Ok(None);
1833 }
1834 Self::decode_end_to_end_reliable_ack(pkt.payload()).map(Some)
1835 }
1836 }
1837 }
1838
1839 fn note_reliable_return_route(&self, side: RelaySideId, packet_id: u64) {
1840 let mut st = self.state.lock();
1841 Self::remember_reliable_return_route_locked(&mut st, packet_id);
1842 st.reliable_return_routes
1843 .insert(packet_id, ReliableReturnRouteState { side });
1844 }
1845
1846 fn remember_reliable_return_route_locked(st: &mut RelayInner, packet_id: u64) {
1852 let cap = runtime_reliable_max_return_routes().max(1);
1853 st.reliable_return_route_order
1854 .retain(|id| st.reliable_return_routes.contains_key(id) && *id != packet_id);
1855 while st.reliable_return_route_order.len() >= cap {
1856 if let Some(oldest) = st.reliable_return_route_order.pop_front() {
1857 st.reliable_return_routes.remove(&oldest);
1858 } else {
1859 break;
1860 }
1861 }
1862 st.reliable_return_route_order.push_back(packet_id);
1863 }
1864
1865 fn note_end_to_end_acked_destination_locked(
1866 st: &mut RelayInner,
1867 packet_id: u64,
1868 sender_hash: u64,
1869 ) {
1870 let entry_cap = runtime_reliable_max_end_to_end_ack_cache().max(1);
1871 st.end_to_end_acked_destination_order
1872 .retain(|id| st.end_to_end_acked_destinations.contains_key(id) && *id != packet_id);
1873 while st.end_to_end_acked_destination_order.len() >= entry_cap {
1874 if let Some(oldest) = st.end_to_end_acked_destination_order.pop_front() {
1875 st.end_to_end_acked_destinations.remove(&oldest);
1876 } else {
1877 break;
1878 }
1879 }
1880 st.end_to_end_acked_destination_order.push_back(packet_id);
1881
1882 let acked = st
1883 .end_to_end_acked_destinations
1884 .entry(packet_id)
1885 .or_default();
1886 let sender_cap = runtime_reliable_max_end_to_end_pending().max(1);
1887 if acked.len() < sender_cap || acked.contains(&sender_hash) {
1888 acked.insert(sender_hash);
1889 }
1890 }
1891
1892 #[inline]
1893 fn reliable_key(side: RelaySideId, ty: crate::DataType) -> (RelaySideId, u32) {
1894 (side, ty.as_u32())
1895 }
1896
1897 fn reliable_tx_state_mut<'a>(
1898 &'a self,
1899 st: &'a mut RelayInner,
1900 side: RelaySideId,
1901 ty: crate::DataType,
1902 ) -> &'a mut ReliableTxState {
1903 let key = Self::reliable_key(side, ty);
1904 st.reliable_tx
1905 .entry(key)
1906 .or_insert_with(|| ReliableTxState {
1907 next_seq: 1,
1908 sent_order: VecDeque::new(),
1909 sent: BTreeMap::new(),
1910 })
1911 }
1912
1913 fn reliable_rx_state_mut<'a>(
1914 &'a self,
1915 st: &'a mut RelayInner,
1916 side: RelaySideId,
1917 ty: crate::DataType,
1918 ) -> &'a mut ReliableRxState {
1919 let key = Self::reliable_key(side, ty);
1920 st.reliable_rx
1921 .entry(key)
1922 .or_insert_with(|| ReliableRxState {
1923 expected_seq: 1,
1924 buffered: BTreeMap::new(),
1925 })
1926 }
1927
1928 fn handle_reliable_ack(&self, side: RelaySideId, ty: crate::DataType, ack: u32) {
1929 let mut st = self.state.lock();
1930 let tx_state = self.reliable_tx_state_mut(&mut st, side, ty);
1931 if matches!(reliable_mode(ty), crate::ReliableMode::Unordered) {
1932 tx_state.sent.remove(&ack);
1933 tx_state.sent_order.retain(|seq| *seq != ack);
1934 return;
1935 }
1936
1937 while let Some(seq) = tx_state.sent_order.front().copied() {
1938 if seq > ack {
1939 break;
1940 }
1941 tx_state.sent_order.pop_front();
1942 tx_state.sent.remove(&seq);
1943 }
1944 }
1945
1946 fn handle_reliable_partial_ack(&self, side: RelaySideId, ty: crate::DataType, seq: u32) {
1947 let mut st = self.state.lock();
1948 let tx_state = self.reliable_tx_state_mut(&mut st, side, ty);
1949 if let Some(sent) = tx_state.sent.get_mut(&seq) {
1950 sent.partial_acked = true;
1951 }
1952 }
1953
1954 fn reliable_control_packet(
1955 &self,
1956 control_ty: crate::DataType,
1957 ty: crate::DataType,
1958 seq: u32,
1959 ) -> TelemetryResult<Packet> {
1960 let sender = self.sender_arc();
1961 Packet::new(
1962 control_ty,
1963 message_meta(control_ty).endpoints_ref(),
1964 sender.as_ref(),
1965 self.clock.now_ms(),
1966 crate::router::encode_slice_le(&[ty.as_u32(), seq]),
1967 )
1968 }
1969
1970 fn queue_reliable_ack(
1971 &self,
1972 side: RelaySideId,
1973 ty: crate::DataType,
1974 seq: u32,
1975 ) -> TelemetryResult<()> {
1976 let pkt = self.reliable_control_packet(crate::DataType::ReliableAck, ty, seq)?;
1977 let data = RelayItem::Packet(Arc::new(pkt));
1978 let priority = Self::relay_item_priority(&data)?;
1979 let mut st = self.state.lock();
1980 st.push_tx(RelayTxItem {
1981 src: None,
1982 dst: side,
1983 data,
1984 priority,
1985 })?;
1986 Ok(())
1987 }
1988
1989 fn queue_reliable_packet_request(
1990 &self,
1991 side: RelaySideId,
1992 ty: crate::DataType,
1993 seq: u32,
1994 ) -> TelemetryResult<()> {
1995 let pkt = self.reliable_control_packet(crate::DataType::ReliablePacketRequest, ty, seq)?;
1996 let data = RelayItem::Packet(Arc::new(pkt));
1997 let priority = Self::relay_item_priority(&data)?;
1998 let mut st = self.state.lock();
1999 st.push_tx(RelayTxItem {
2000 src: None,
2001 dst: side,
2002 data,
2003 priority,
2004 })?;
2005 Ok(())
2006 }
2007
2008 fn queue_reliable_partial_ack(
2009 &self,
2010 side: RelaySideId,
2011 ty: crate::DataType,
2012 seq: u32,
2013 ) -> TelemetryResult<()> {
2014 let pkt = self.reliable_control_packet(crate::DataType::ReliablePartialAck, ty, seq)?;
2015 let data = RelayItem::Packet(Arc::new(pkt));
2016 let priority = Self::relay_item_priority(&data)?;
2017 let mut st = self.state.lock();
2018 st.push_tx(RelayTxItem {
2019 src: None,
2020 dst: side,
2021 data,
2022 priority,
2023 })?;
2024 Ok(())
2025 }
2026
2027 fn queue_reliable_retransmit(
2028 &self,
2029 side: RelaySideId,
2030 ty: crate::DataType,
2031 seq: u32,
2032 ) -> TelemetryResult<()> {
2033 let mut queued = None;
2034 {
2035 let mut st = self.state.lock();
2036 let tx_state = self.reliable_tx_state_mut(&mut st, side, ty);
2037 if let Some(sent) = tx_state.sent.get_mut(&seq)
2038 && !sent.queued
2039 {
2040 sent.queued = true;
2041 sent.partial_acked = false;
2042 queued = Some(sent.bytes.clone());
2043 }
2044 }
2045
2046 if let Some(bytes) = queued {
2047 let mut st = self.state.lock();
2048 st.push_replay(RelayReplayItem {
2049 dst: side,
2050 bytes,
2051 priority: message_priority(ty).saturating_add(16),
2052 })?;
2053 }
2054 Ok(())
2055 }
2056
2057 fn send_reliable_raw_to_side(
2058 &self,
2059 side: RelaySideId,
2060 bytes: Arc<[u8]>,
2061 ) -> TelemetryResult<()> {
2062 let (handler, opts) = {
2063 let st = self.state.lock();
2064 let side_ref = Self::side_ref(&st, side)?;
2065 if !side_ref.opts.egress_enabled {
2066 return Ok(());
2067 }
2068 (side_ref.tx_handler.clone(), side_ref.opts)
2069 };
2070
2071 let Some(_side_tx_guard) = self.try_enter_side_tx() else {
2072 return Err(TelemetryError::Io("side tx busy"));
2073 };
2074 let started_ms = self.clock.now_ms();
2075 let ty = wire_format::peek_envelope(bytes.as_ref())
2076 .map(|env| env.ty)
2077 .unwrap_or(crate::DataType::ReliableAck);
2078 let result = match handler {
2079 RelayTxHandlerFn::Packed(f) => {
2080 let frames = self.encode_side_transport_frames(side, opts, bytes.clone())?;
2081 let mut sent_bytes = 0usize;
2082 for frame in frames {
2083 f(frame.as_ref())?;
2084 sent_bytes = sent_bytes.saturating_add(frame.len());
2085 }
2086 self.record_side_tx_sample(side, sent_bytes, started_ms, self.clock.now_ms());
2087 self.note_side_tx_success(side, ty, sent_bytes, 1);
2088 return Ok(());
2089 }
2090 RelayTxHandlerFn::Packet(f) => {
2091 if wire_format::peek_frame_info(bytes.as_ref())
2092 .ok()
2093 .is_some_and(|frame| frame.ack_only())
2094 {
2095 return Ok(());
2096 }
2097 let pkt = wire_format::unpack_packet(bytes.as_ref())?;
2098 f(&pkt)
2099 }
2100 };
2101 if result.is_ok() {
2102 self.record_side_tx_sample(side, bytes.len(), started_ms, self.clock.now_ms());
2103 self.note_side_tx_success(side, ty, bytes.len(), 1);
2104 } else {
2105 self.note_side_tx_failure(side, ty, 1);
2106 }
2107 result
2108 }
2109
2110 fn send_reliable_to_side(&self, side: RelaySideId, data: RelayItem) -> TelemetryResult<()> {
2111 let (handler, opts, hop_reliable_enabled) = {
2112 let st = self.state.lock();
2113 let side_ref = Self::side_ref(&st, side)?;
2114 let opts = side_ref.opts;
2115 let hop_reliable_enabled = opts.reliable_enabled
2116 && !self.side_has_multiple_announcers_locked(&st, side, self.clock.now_ms());
2117 (side_ref.tx_handler.clone(), opts, hop_reliable_enabled)
2118 };
2119
2120 let RelayTxHandlerFn::Packed(f) = &handler else {
2121 return self.call_tx_handler(side, &handler, &data);
2122 };
2123
2124 if !hop_reliable_enabled {
2125 let mut adjusted_opts = opts;
2126 adjusted_opts.reliable_enabled = false;
2127 if let Some(adjusted) = self.adjust_reliable_for_side(adjusted_opts, data)? {
2128 return self.call_tx_handler(side, &handler, &adjusted);
2129 }
2130 return Ok(());
2131 }
2132
2133 let ty = match &data {
2134 RelayItem::Packet(pkt) => pkt.data_type(),
2135 RelayItem::Packed(bytes) => {
2136 let Ok(frame) = wire_format::peek_frame_info(bytes.as_ref()) else {
2137 return self.call_tx_handler(side, &handler, &data);
2138 };
2139 frame.envelope.ty
2140 }
2141 };
2142
2143 if !is_reliable_type(ty) {
2144 if let Some(adjusted) = self.adjust_reliable_for_side(opts, data)? {
2145 self.call_tx_handler(side, &handler, &adjusted)?;
2146 }
2147 return Ok(());
2148 }
2149
2150 let (seq, flags) = {
2151 let mut st = self.state.lock();
2152 let tx_state = self.reliable_tx_state_mut(&mut st, side, ty);
2153 if tx_state.sent.len() >= runtime_reliable_max_pending() {
2154 return Err(TelemetryError::PacketTooLarge(
2155 "relay reliable history full",
2156 ));
2157 }
2158 let seq = tx_state.next_seq;
2159 let next = tx_state.next_seq.wrapping_add(1);
2160 tx_state.next_seq = if next == 0 { 1 } else { next };
2161 let flags = match reliable_mode(ty) {
2162 crate::ReliableMode::Unordered => wire_format::RELIABLE_FLAG_UNORDERED,
2163 _ => 0,
2164 };
2165 (seq, flags)
2166 };
2167
2168 let bytes: Arc<[u8]> = match data {
2169 RelayItem::Packet(pkt) => wire_format::pack_packet_with_reliable(
2170 &pkt,
2171 wire_format::ReliableHeader { flags, seq, ack: 0 },
2172 ),
2173 RelayItem::Packed(bytes) => {
2174 let Some(rewritten) =
2175 wire_format::rewrite_reliable_header_owned(bytes.as_ref(), flags, seq, 0)?
2176 else {
2177 let Some(_side_tx_guard) = self.try_enter_side_tx() else {
2178 return Err(TelemetryError::Io("side tx busy"));
2179 };
2180 let started_ms = self.clock.now_ms();
2181 let frames = self.encode_side_transport_frames(side, opts, bytes.clone())?;
2182 let mut sent_bytes = 0usize;
2183 for frame in frames {
2184 f(frame.as_ref())?;
2185 sent_bytes = sent_bytes.saturating_add(frame.len());
2186 }
2187 self.record_side_tx_sample(side, sent_bytes, started_ms, self.clock.now_ms());
2188 self.note_side_tx_success(side, ty, sent_bytes, 1);
2189 return Ok(());
2190 };
2191 rewritten
2192 }
2193 };
2194
2195 let Some(_side_tx_guard) = self.try_enter_side_tx() else {
2196 return Err(TelemetryError::Io("side tx busy"));
2197 };
2198 let started_ms = self.clock.now_ms();
2199 let frames = self.encode_side_transport_frames(side, opts, bytes.clone())?;
2200 let mut sent_bytes = 0usize;
2201 for frame in frames {
2202 f(frame.as_ref())?;
2203 sent_bytes = sent_bytes.saturating_add(frame.len());
2204 }
2205 self.record_side_tx_sample(side, sent_bytes, started_ms, self.clock.now_ms());
2206 self.note_side_tx_success(side, ty, sent_bytes, 1);
2207
2208 {
2209 let mut st = self.state.lock();
2210 let tx_state = self.reliable_tx_state_mut(&mut st, side, ty);
2211 tx_state.sent_order.push_back(seq);
2212 tx_state.sent.insert(
2213 seq,
2214 ReliableSent {
2215 bytes: bytes.clone(),
2216 last_send_ms: self.clock.now_ms(),
2217 retries: 0,
2218 queued: false,
2219 partial_acked: false,
2220 },
2221 );
2222 }
2223
2224 Ok(())
2225 }
2226
2227 fn item_route_info(
2228 &self,
2229 data: &RelayItem,
2230 ) -> TelemetryResult<(Vec<crate::DataEndpoint>, crate::DataType)> {
2231 match data {
2232 RelayItem::Packet(pkt) => {
2233 let mut eps = pkt.endpoints().to_vec();
2234 eps.sort_unstable();
2235 eps.dedup();
2236 Ok((eps, pkt.data_type()))
2237 }
2238 RelayItem::Packed(bytes) => {
2239 let env = wire_format::peek_envelope(bytes.as_ref())?;
2240 let mut eps: Vec<crate::DataEndpoint> = env.endpoints.iter().copied().collect();
2241 eps.sort_unstable();
2242 eps.dedup();
2243 Ok((eps, env.ty))
2244 }
2245 }
2246 }
2247
2248 fn endpoints_are_link_local_only(eps: &[crate::DataEndpoint]) -> bool {
2249 !eps.is_empty() && eps.iter().all(|ep| ep.is_link_local_only())
2250 }
2251
2252 fn item_target_senders(&self, data: &RelayItem) -> TelemetryResult<Arc<[u64]>> {
2253 match data {
2254 RelayItem::Packet(pkt) => Ok(Arc::from(pkt.wire_target_senders())),
2255 RelayItem::Packed(bytes) => {
2256 Ok(wire_format::peek_envelope(bytes.as_ref())?.target_senders)
2257 }
2258 }
2259 }
2260
2261 #[cfg(feature = "discovery")]
2262 fn has_explicit_route_policy_locked(
2263 st: &RelayInner,
2264 src: Option<RelaySideId>,
2265 ty: crate::DataType,
2266 ) -> bool {
2267 st.route_overrides
2268 .keys()
2269 .any(|(route_src, _)| *route_src == src)
2270 || Self::has_typed_route_overrides_locked(st, src, ty)
2271 }
2272
2273 #[cfg(feature = "discovery")]
2274 fn side_matches_target_senders_locked(
2275 st: &RelayInner,
2276 side: RelaySideId,
2277 target_senders: &[u64],
2278 now_ms: u64,
2279 ) -> bool {
2280 st.discovery_routes
2281 .get(&side)
2282 .map(|route| {
2283 if now_ms.saturating_sub(route.last_seen_ms) > DISCOVERY_ROUTE_TTL_MS {
2284 return false;
2285 }
2286 route.announcers.iter().any(|(announcer, sender_state)| {
2287 if now_ms.saturating_sub(sender_state.last_seen_ms) > DISCOVERY_ROUTE_TTL_MS {
2288 return false;
2289 }
2290 target_senders.contains(&Self::sender_hash(announcer))
2291 || sender_state
2292 .advertised_address
2293 .is_some_and(|address| target_senders.contains(&u64::from(address)))
2294 || sender_state.topology_boards.iter().any(|board| {
2295 target_senders.contains(&Self::sender_hash(&board.sender_id))
2296 })
2297 })
2298 })
2299 .unwrap_or(false)
2300 }
2301
2302 fn remote_side_plan(
2303 &self,
2304 data: &RelayItem,
2305 exclude: RelaySideId,
2306 ) -> TelemetryResult<RemoteSidePlan> {
2307 #[cfg(feature = "discovery")]
2308 {
2309 let (eps, ty) = self.item_route_info(data)?;
2310 let target_senders = self.item_target_senders(data)?;
2311 let preferred_packet_id = Self::reliable_control_target_packet_id(data)?;
2312 if discovery::is_discovery_type(ty) {
2313 let mut st = self.state.lock();
2314 let sides = self.eligible_side_ids_locked(&st, Some(exclude), Some(ty), false);
2315 return Ok(RemoteSidePlan::Target(self.apply_route_selection_locked(
2316 &mut st,
2317 Some(exclude),
2318 sides,
2319 RouteSelectionOrigin::Flood,
2320 )));
2321 }
2322
2323 #[cfg(feature = "timesync")]
2324 let preferred_timesync_source = self.preferred_timesync_route_source(data, ty)?;
2325 #[cfg(not(feature = "timesync"))]
2326 let preferred_timesync_source: Option<String> = None;
2327 let mut st = self.state.lock();
2328 if let Some(packet_id) = preferred_packet_id {
2329 let target_side = self.allowed_target_side_locked(
2330 &st,
2331 exclude,
2332 ty,
2333 st.reliable_return_routes
2334 .get(&packet_id)
2335 .map(|route| route.side),
2336 );
2337 if let Some(side) = target_side {
2338 #[cfg(feature = "timesync")]
2339 if !Self::timesync_allowed_for_side_locked(
2340 &mut st,
2341 side,
2342 ty,
2343 self.clock.now_ms(),
2344 ) {
2345 return Ok(RemoteSidePlan::Target(Vec::new()));
2346 }
2347 return Ok(RemoteSidePlan::Target(vec![side]));
2348 }
2349 return Ok(RemoteSidePlan::Target(Vec::new()));
2350 }
2351 let restrict_link_local = Self::endpoints_are_link_local_only(&eps);
2352 let discovered_origin = if is_reliable_type(ty) {
2353 RouteSelectionOrigin::Flood
2354 } else {
2355 RouteSelectionOrigin::Discovered
2356 };
2357 if st.discovery_routes.is_empty() {
2358 let mut fallback = self.eligible_side_ids_locked(
2359 &st,
2360 Some(exclude),
2361 Some(ty),
2362 restrict_link_local,
2363 );
2364 #[cfg(feature = "timesync")]
2365 {
2366 fallback = Self::filter_timesync_sides_locked(
2367 &mut st,
2368 ty,
2369 self.clock.now_ms(),
2370 fallback,
2371 );
2372 }
2373 return Ok(RemoteSidePlan::Target(if fallback.len() == 1 {
2374 fallback
2375 } else {
2376 Vec::new()
2377 }));
2378 }
2379 let now_ms = self.clock.now_ms();
2380 let mut had_exact = false;
2381 let mut exact_targets = Vec::new();
2382 let mut had_known = false;
2383 let mut generic_targets = Vec::new();
2384 let mut unique_endpoint_fallbacks = Vec::new();
2385
2386 for (&side, route) in st.discovery_routes.iter() {
2387 if side == exclude
2388 || now_ms.saturating_sub(route.last_seen_ms) > DISCOVERY_ROUTE_TTL_MS
2389 {
2390 continue;
2391 }
2392 if restrict_link_local
2393 && st
2394 .sides
2395 .get(side)
2396 .and_then(|side| side.as_ref())
2397 .map(|s| !s.opts.link_local_enabled)
2398 .unwrap_or(true)
2399 {
2400 continue;
2401 }
2402 if !self.route_allowed_locked(&st, Some(exclude), Some(ty), side) {
2403 continue;
2404 }
2405 if !target_senders.is_empty() {
2406 if !Self::side_matches_target_senders_locked(&st, side, &target_senders, now_ms)
2407 {
2408 if eps.iter().copied().any(|ep| route.reachable.contains(&ep)) {
2409 unique_endpoint_fallbacks.push(side);
2410 }
2411 continue;
2412 }
2413 had_known = true;
2414 generic_targets.push(side);
2415 continue;
2416 }
2417 if preferred_timesync_source.as_deref().is_some_and(|source| {
2418 route.reachable_timesync_sources.iter().any(|s| s == source)
2419 }) {
2420 had_exact = true;
2421 exact_targets.push(side);
2422 continue;
2423 }
2424 if eps.iter().copied().any(|ep| route.reachable.contains(&ep)) {
2425 had_known = true;
2426 generic_targets.push(side);
2427 }
2428 }
2429
2430 if !target_senders.is_empty() && !had_known && unique_endpoint_fallbacks.len() == 1 {
2436 had_known = true;
2437 generic_targets = unique_endpoint_fallbacks;
2438 }
2439
2440 if had_exact {
2441 #[cfg(feature = "timesync")]
2442 {
2443 exact_targets = Self::filter_timesync_sides_locked(
2444 &mut st,
2445 ty,
2446 self.clock.now_ms(),
2447 exact_targets,
2448 );
2449 }
2450 let targets = self.filter_end_to_end_satisfied_sides_locked(
2451 &st,
2452 data,
2453 exact_targets,
2454 &eps,
2455 ty,
2456 )?;
2457 Ok(RemoteSidePlan::Target(self.apply_route_selection_locked(
2458 &mut st,
2459 Some(exclude),
2460 targets,
2461 discovered_origin,
2462 )))
2463 } else if had_known {
2464 Self::retain_shortest_discovery_routes_locked(
2465 &st,
2466 &mut generic_targets,
2467 &eps,
2468 now_ms,
2469 );
2470 #[cfg(feature = "timesync")]
2471 {
2472 generic_targets = Self::filter_timesync_sides_locked(
2473 &mut st,
2474 ty,
2475 self.clock.now_ms(),
2476 generic_targets,
2477 );
2478 }
2479 let targets = self.filter_end_to_end_satisfied_sides_locked(
2480 &st,
2481 data,
2482 generic_targets,
2483 &eps,
2484 ty,
2485 )?;
2486 Ok(RemoteSidePlan::Target(self.apply_route_selection_locked(
2487 &mut st,
2488 Some(exclude),
2489 targets,
2490 discovered_origin,
2491 )))
2492 } else {
2493 let fallback = self.eligible_side_ids_locked(
2494 &st,
2495 Some(exclude),
2496 Some(ty),
2497 restrict_link_local,
2498 );
2499 if fallback.len() == 1 {
2500 return Ok(RemoteSidePlan::Target(fallback));
2501 }
2502 if Self::has_explicit_route_policy_locked(&st, Some(exclude), ty) {
2503 let mut sides = self.eligible_side_ids_locked(
2504 &st,
2505 Some(exclude),
2506 Some(ty),
2507 restrict_link_local,
2508 );
2509 #[cfg(feature = "timesync")]
2510 {
2511 sides = Self::filter_timesync_sides_locked(
2512 &mut st,
2513 ty,
2514 self.clock.now_ms(),
2515 sides,
2516 );
2517 }
2518 Ok(RemoteSidePlan::Target(self.apply_route_selection_locked(
2519 &mut st,
2520 Some(exclude),
2521 sides,
2522 RouteSelectionOrigin::Flood,
2523 )))
2524 } else {
2525 Ok(RemoteSidePlan::Target(Vec::new()))
2526 }
2527 }
2528 }
2529 #[cfg(not(feature = "discovery"))]
2530 {
2531 let (_, ty) = self.item_route_info(data)?;
2532 let mut st = self.state.lock();
2533 if let Some(packet_id) = Self::reliable_control_target_packet_id(data)? {
2534 let target_side = self.allowed_target_side_locked(
2535 &st,
2536 exclude,
2537 ty,
2538 st.reliable_return_routes
2539 .get(&packet_id)
2540 .map(|route| route.side),
2541 );
2542 if let Some(side) = target_side {
2543 return Ok(RemoteSidePlan::Target(vec![side]));
2544 }
2545 return Ok(RemoteSidePlan::Target(Vec::new()));
2546 }
2547 let sides = self.eligible_side_ids_locked(&st, Some(exclude), Some(ty), false);
2548 Ok(RemoteSidePlan::Target(self.apply_route_selection_locked(
2549 &mut st,
2550 Some(exclude),
2551 sides,
2552 RouteSelectionOrigin::Flood,
2553 )))
2554 }
2555 }
2556
2557 #[inline]
2558 fn allowed_target_side_locked(
2559 &self,
2560 st: &RelayInner,
2561 exclude: RelaySideId,
2562 ty: crate::DataType,
2563 target_side: Option<RelaySideId>,
2564 ) -> Option<RelaySideId> {
2565 target_side.filter(|side| self.route_allowed_locked(st, Some(exclude), Some(ty), *side))
2566 }
2567
2568 fn filter_end_to_end_satisfied_sides_locked(
2569 &self,
2570 st: &RelayInner,
2571 data: &RelayItem,
2572 sides: Vec<RelaySideId>,
2573 eps: &[crate::DataEndpoint],
2574 ty: crate::DataType,
2575 ) -> TelemetryResult<Vec<RelaySideId>> {
2576 if !is_reliable_type(ty) || Self::reliable_control_target_packet_id(data)?.is_some() {
2577 return Ok(sides);
2578 }
2579 let packet_id = match data {
2580 RelayItem::Packet(pkt) => pkt.packet_id(),
2581 RelayItem::Packed(bytes) => match wire_format::packet_id_from_wire(bytes.as_ref()) {
2582 Ok(packet_id) => packet_id,
2583 Err(TelemetryError::Unpack("reliable control frame")) => return Ok(sides),
2584 Err(err) => return Err(err),
2585 },
2586 };
2587 let Some(acked) = st.end_to_end_acked_destinations.get(&packet_id) else {
2588 return Ok(sides);
2589 };
2590 let now_ms = self.clock.now_ms();
2591 let mut filtered = Vec::new();
2592 for side in sides {
2593 let Some(route) = st.discovery_routes.get(&side) else {
2594 filtered.push(side);
2595 continue;
2596 };
2597 let mut still_pending = false;
2598 let mut had_destination_board = false;
2599 for sender_state in route.announcers.values() {
2600 if now_ms.saturating_sub(sender_state.last_seen_ms) > DISCOVERY_ROUTE_TTL_MS {
2601 continue;
2602 }
2603 for board in sender_state.topology_boards.iter() {
2604 if !self.is_end_to_end_destination_sender(&board.sender_id) {
2605 continue;
2606 }
2607 had_destination_board = true;
2608 let sender_hash = Self::sender_hash(&board.sender_id);
2609 if acked.contains(&sender_hash) {
2610 continue;
2611 }
2612 if eps
2613 .iter()
2614 .copied()
2615 .any(|ep| board.reachable_endpoints.contains(&ep))
2616 {
2617 still_pending = true;
2618 break;
2619 }
2620 still_pending = true;
2623 break;
2624 }
2625 if still_pending {
2626 break;
2627 }
2628 }
2629 if still_pending || !had_destination_board {
2630 filtered.push(side);
2631 }
2632 }
2633 Ok(filtered)
2634 }
2635
2636 #[cfg(feature = "discovery")]
2637 fn side_has_multiple_announcers_locked(
2638 &self,
2639 st: &RelayInner,
2640 side: RelaySideId,
2641 now_ms: u64,
2642 ) -> bool {
2643 st.discovery_routes
2644 .get(&side)
2645 .map(|route| {
2646 route
2647 .announcers
2648 .values()
2649 .filter(|sender| {
2650 now_ms.saturating_sub(sender.last_seen_ms) <= DISCOVERY_ROUTE_TTL_MS
2651 })
2652 .take(2)
2653 .count()
2654 > 1
2655 })
2656 .unwrap_or(false)
2657 }
2658
2659 #[cfg(not(feature = "discovery"))]
2660 fn side_has_multiple_announcers_locked(
2661 &self,
2662 _st: &RelayInner,
2663 _side: RelaySideId,
2664 _now_ms: u64,
2665 ) -> bool {
2666 false
2667 }
2668
2669 #[cfg(feature = "discovery")]
2670 fn sender_topology_board_mut<'a>(
2671 sender_state: &'a mut DiscoverySenderState,
2672 sender_id: &str,
2673 ) -> &'a mut TopologyBoardNode {
2674 if let Some(idx) = sender_state
2675 .topology_boards
2676 .iter()
2677 .position(|board| board.sender_id == sender_id)
2678 {
2679 return &mut sender_state.topology_boards[idx];
2680 }
2681 sender_state.topology_boards.push(TopologyBoardNode {
2682 sender_id: sender_id.to_string(),
2683 reachable_endpoints: Vec::new(),
2684 reachable_timesync_sources: Vec::new(),
2685 connections: Vec::new(),
2686 });
2687 sender_state
2688 .topology_boards
2689 .last_mut()
2690 .expect("board inserted above")
2691 }
2692
2693 #[cfg(feature = "discovery")]
2694 fn refresh_sender_topology_state(sender_state: &mut DiscoverySenderState) {
2695 discovery::normalize_topology_boards(&mut sender_state.topology_boards);
2696 let (mut reachable, mut reachable_timesync_sources) =
2697 discovery::summarize_topology_boards(&sender_state.topology_boards);
2698 reachable.extend(sender_state.advertised_reachable.iter().copied());
2699 reachable.sort_unstable();
2700 reachable.dedup();
2701 reachable_timesync_sources.extend(
2702 sender_state
2703 .advertised_reachable_timesync_sources
2704 .iter()
2705 .cloned(),
2706 );
2707 reachable_timesync_sources.sort_unstable();
2708 reachable_timesync_sources.dedup();
2709 sender_state.reachable = reachable;
2710 sender_state.reachable_timesync_sources = reachable_timesync_sources;
2711 }
2712
2713 #[cfg(feature = "discovery")]
2714 fn recompute_discovery_side_state(route: &mut DiscoverySideState) {
2715 let mut reachable = Vec::new();
2716 let mut reachable_timesync_sources = Vec::new();
2717 let mut last_seen_ms = 0u64;
2718 for sender in route.announcers.values() {
2719 reachable.extend(sender.reachable.iter().copied());
2720 reachable_timesync_sources.extend(sender.reachable_timesync_sources.iter().cloned());
2721 last_seen_ms = last_seen_ms.max(sender.last_seen_ms);
2722 }
2723 reachable.sort_unstable();
2724 reachable.dedup();
2725 reachable_timesync_sources.sort_unstable();
2726 reachable_timesync_sources.dedup();
2727 route.reachable = reachable;
2728 route.reachable_timesync_sources = reachable_timesync_sources;
2729 route.last_seen_ms = last_seen_ms;
2730 }
2731
2732 #[cfg(feature = "discovery")]
2733 fn local_discovery_topology_board(&self, st: &RelayInner, now_ms: u64) -> TopologyBoardNode {
2734 let mut connections = Vec::new();
2735 for route in st.discovery_routes.values() {
2736 if now_ms.saturating_sub(route.last_seen_ms) > DISCOVERY_ROUTE_TTL_MS {
2737 continue;
2738 }
2739 for (sender, sender_state) in route.announcers.iter() {
2740 if now_ms.saturating_sub(sender_state.last_seen_ms) <= DISCOVERY_ROUTE_TTL_MS {
2741 connections.push(sender.clone());
2742 }
2743 }
2744 }
2745 connections.sort_unstable();
2746 connections.dedup();
2747 let sender = self.sender_arc();
2748 TopologyBoardNode {
2749 sender_id: sender.to_string(),
2750 reachable_endpoints: Vec::new(),
2751 reachable_timesync_sources: Vec::new(),
2752 connections,
2753 }
2754 }
2755
2756 #[cfg(feature = "discovery")]
2757 fn advertised_discovery_topology_for_link_locked(
2758 &self,
2759 st: &RelayInner,
2760 now_ms: u64,
2761 link_local_enabled: bool,
2762 ) -> Vec<TopologyBoardNode> {
2763 let mut boards = vec![self.local_discovery_topology_board(st, now_ms)];
2764 for route in st.discovery_routes.values() {
2765 if now_ms.saturating_sub(route.last_seen_ms) > DISCOVERY_ROUTE_TTL_MS {
2766 continue;
2767 }
2768 for (announcer, sender_state) in route.announcers.iter() {
2769 if now_ms.saturating_sub(sender_state.last_seen_ms) > DISCOVERY_ROUTE_TTL_MS {
2770 continue;
2771 }
2772 let mut sender_boards = sender_state.topology_boards.clone();
2773 if sender_boards.is_empty() {
2774 let sender = self.sender_arc();
2775 sender_boards.push(TopologyBoardNode {
2776 sender_id: announcer.clone(),
2777 reachable_endpoints: sender_state.reachable.clone(),
2778 reachable_timesync_sources: sender_state.reachable_timesync_sources.clone(),
2779 connections: vec![sender.to_string()],
2780 });
2781 } else if let Some(board) = sender_boards
2782 .iter_mut()
2783 .find(|board| board.sender_id == *announcer)
2784 {
2785 board.connections.push(self.sender_arc().to_string());
2786 }
2787 if !link_local_enabled {
2788 for board in sender_boards.iter_mut() {
2789 board
2790 .reachable_endpoints
2791 .retain(|ep| !ep.is_link_local_only());
2792 }
2793 }
2794 discovery::merge_topology_boards(&mut boards, &sender_boards);
2795 }
2796 }
2797 discovery::normalize_topology_boards(&mut boards);
2798 boards
2799 }
2800
2801 #[cfg(feature = "discovery")]
2802 fn note_discovery_topology_change_locked(st: &mut RelayInner, now_ms: u64) {
2803 st.discovery_cadence.on_topology_change(now_ms);
2804 for side_id in st
2805 .sides
2806 .iter()
2807 .enumerate()
2808 .filter_map(|(side_id, side)| side.as_ref().map(|_| side_id))
2809 {
2810 st.discovery_side_throttle
2811 .entry(side_id)
2812 .or_default()
2813 .pending_incremental = true;
2814 st.discovery_side_throttle
2815 .entry(side_id)
2816 .or_default()
2817 .incremental_retries_remaining = DISCOVERY_INCREMENTAL_RETRY_COUNT;
2818 }
2819 for transport in st.side_transport.values_mut() {
2820 transport.clear_header_templates();
2821 }
2822 }
2823
2824 #[cfg(feature = "discovery")]
2825 fn prune_discovery_routes_locked(st: &mut RelayInner, now_ms: u64) -> bool {
2826 let before = st.discovery_routes.clone();
2827 st.discovery_routes.retain(|_, route| {
2828 route.announcers.retain(|_, sender| {
2829 now_ms.saturating_sub(sender.last_seen_ms) <= DISCOVERY_ROUTE_TTL_MS
2830 });
2831 Self::recompute_discovery_side_state(route);
2832 !route.announcers.is_empty()
2833 });
2834 st.discovery_routes != before
2835 }
2836
2837 #[cfg(feature = "discovery")]
2838 fn reconcile_end_to_end_acked_destinations_locked(&self, st: &mut RelayInner) {
2839 let mut active_senders = BTreeSet::new();
2840 for route in st.discovery_routes.values() {
2841 for sender_state in route.announcers.values() {
2842 for board in sender_state.topology_boards.iter() {
2843 if self.is_end_to_end_destination_sender(&board.sender_id) {
2844 active_senders.insert(Self::sender_hash(&board.sender_id));
2845 }
2846 }
2847 }
2848 }
2849 st.end_to_end_acked_destinations.retain(|_, acked| {
2850 acked.retain(|sender_hash| active_senders.contains(sender_hash));
2851 !acked.is_empty()
2852 });
2853 }
2854
2855 #[cfg(feature = "discovery")]
2856 fn advertised_discovery_endpoints_for_link_locked(
2857 &self,
2858 st: &RelayInner,
2859 now_ms: u64,
2860 link_local_enabled: bool,
2861 ) -> Vec<crate::DataEndpoint> {
2862 let (reachable_endpoints, _) = discovery::summarize_topology_boards(
2863 &self.advertised_discovery_topology_for_link_locked(st, now_ms, link_local_enabled),
2864 );
2865 reachable_endpoints
2866 .into_iter()
2867 .filter(|ep| {
2868 !discovery::is_discovery_endpoint(*ep)
2869 && (link_local_enabled || !ep.is_link_local_only())
2870 })
2871 .collect()
2872 }
2873
2874 #[cfg(feature = "discovery")]
2875 fn advertised_discovery_timesync_sources_for_link_locked(
2876 &self,
2877 st: &RelayInner,
2878 now_ms: u64,
2879 ) -> Vec<String> {
2880 let (_, sources) = discovery::summarize_topology_boards(
2881 &self.advertised_discovery_topology_for_link_locked(st, now_ms, true),
2882 );
2883 sources
2884 }
2885
2886 #[cfg(feature = "discovery")]
2887 #[cfg(feature = "timesync")]
2888 fn preferred_timesync_route_source(
2889 &self,
2890 data: &RelayItem,
2891 ty: crate::DataType,
2892 ) -> TelemetryResult<Option<String>> {
2893 if !matches!(
2894 ty,
2895 crate::DataType::TimeSyncAnnounce | crate::DataType::TimeSyncResponse
2896 ) {
2897 return Ok(None);
2898 }
2899
2900 let sender = match data {
2901 RelayItem::Packet(pkt) => pkt.sender().to_owned(),
2902 RelayItem::Packed(bytes) => {
2903 if wire_format::peek_frame_info(bytes.as_ref())
2904 .ok()
2905 .is_some_and(|frame| frame.ack_only())
2906 {
2907 return Ok(None);
2908 }
2909 wire_format::unpack_packet(bytes.as_ref())?
2910 .sender()
2911 .to_owned()
2912 }
2913 };
2914 Ok(Some(sender))
2915 }
2916
2917 #[cfg(feature = "discovery")]
2918 #[inline]
2919 fn side_is_slow_control_link_locked(
2920 st: &RelayInner,
2921 side_id: RelaySideId,
2922 now_ms: u64,
2923 ) -> bool {
2924 st.adaptive_route_stats.get(&side_id).is_some_and(|stats| {
2925 let recent_slow = stats.last_slow_observed_ms > 0
2926 && now_ms.saturating_sub(stats.last_slow_observed_ms)
2927 <= DISCOVERY_SLOW_LINK_FULL_INTERVAL_MS;
2928 stats.sample_count > 0
2929 && ((stats.estimated_bandwidth_bps > 0
2930 && stats.estimated_bandwidth_bps <= CONTROL_SLOW_LINK_CAPACITY_BPS)
2931 || recent_slow)
2932 })
2933 }
2934
2935 #[cfg(feature = "discovery")]
2936 fn discovery_level_for_side_locked(
2937 st: &mut RelayInner,
2938 side_id: RelaySideId,
2939 now_ms: u64,
2940 ) -> Option<DiscoveryAdvertiseLevel> {
2941 let slow = Self::side_is_slow_control_link_locked(st, side_id, now_ms);
2942 let throttle = st.discovery_side_throttle.entry(side_id).or_default();
2943 if !throttle.has_sent_full || now_ms >= throttle.next_full_ms {
2944 throttle.has_sent_full = true;
2945 throttle.pending_incremental = false;
2946 throttle.incremental_retries_remaining = 0;
2947 throttle.next_full_ms = now_ms.saturating_add(DISCOVERY_SLOW_LINK_FULL_INTERVAL_MS);
2948 throttle.next_ping_ms = now_ms.saturating_add(DISCOVERY_SLOW_LINK_PING_INTERVAL_MS);
2949 return Some(DiscoveryAdvertiseLevel::Full);
2950 }
2951 if throttle.pending_incremental {
2952 throttle.incremental_retries_remaining =
2953 throttle.incremental_retries_remaining.saturating_sub(1);
2954 throttle.pending_incremental = throttle.incremental_retries_remaining > 0;
2955 return Some(DiscoveryAdvertiseLevel::Incremental);
2956 }
2957 if slow && now_ms >= throttle.next_ping_ms {
2958 throttle.next_ping_ms = now_ms.saturating_add(DISCOVERY_SLOW_LINK_PING_INTERVAL_MS);
2959 return Some(DiscoveryAdvertiseLevel::MinimalPing);
2960 }
2961 if slow {
2962 None
2963 } else {
2964 Some(DiscoveryAdvertiseLevel::Incremental)
2965 }
2966 }
2967
2968 #[cfg(all(feature = "discovery", feature = "timesync"))]
2969 #[inline]
2970 fn is_timesync_type(ty: crate::DataType) -> bool {
2971 matches!(
2972 ty,
2973 crate::DataType::TimeSyncAnnounce
2974 | crate::DataType::TimeSyncRequest
2975 | crate::DataType::TimeSyncResponse
2976 )
2977 }
2978
2979 #[cfg(all(feature = "discovery", feature = "timesync"))]
2980 fn timesync_allowed_for_side_locked(
2981 st: &mut RelayInner,
2982 side_id: RelaySideId,
2983 ty: crate::DataType,
2984 now_ms: u64,
2985 ) -> bool {
2986 if !Self::is_timesync_type(ty) {
2987 return true;
2988 }
2989 if !Self::side_is_slow_control_link_locked(st, side_id, now_ms) {
2990 st.timesync_side_throttle.remove(&side_id);
2991 return true;
2992 }
2993
2994 let throttle = st.timesync_side_throttle.entry(side_id).or_default();
2995 if now_ms >= throttle.next_allowed_ms {
2996 throttle.next_allowed_ms = now_ms.saturating_add(TIMESYNC_SLOW_LINK_MIN_INTERVAL_MS);
2997 return true;
2998 }
2999 false
3000 }
3001
3002 #[cfg(all(feature = "discovery", feature = "timesync"))]
3003 fn filter_timesync_sides_locked(
3004 st: &mut RelayInner,
3005 ty: crate::DataType,
3006 now_ms: u64,
3007 sides: Vec<RelaySideId>,
3008 ) -> Vec<RelaySideId> {
3009 sides
3010 .into_iter()
3011 .filter(|side| Self::timesync_allowed_for_side_locked(st, *side, ty, now_ms))
3012 .collect()
3013 }
3014
3015 #[cfg(feature = "discovery")]
3016 fn queue_discovery_announce(
3017 &self,
3018 include_schema: bool,
3019 skip_in_flight_sides: bool,
3020 ) -> TelemetryResult<()> {
3021 #[cfg(not(feature = "std"))]
3022 let _ = include_schema;
3023 let now_ms = self.clock.now_ms();
3024 let per_side = {
3025 let mut st = self.state.lock();
3026 if Self::prune_discovery_routes_locked(&mut st, now_ms) {
3027 self.reconcile_end_to_end_acked_destinations_locked(&mut st);
3028 Self::note_discovery_topology_change_locked(&mut st, now_ms);
3029 }
3030 st.fit_discovery_budget();
3031 if !st.sides.iter().any(|side| side.is_some()) {
3032 return Ok(());
3033 }
3034 if include_schema {
3035 for throttle in st.discovery_side_throttle.values_mut() {
3036 throttle.has_sent_full = false;
3037 }
3038 }
3039 st.discovery_cadence.on_announce_sent(now_ms);
3040 let side_entries = st
3041 .sides
3042 .iter()
3043 .enumerate()
3044 .filter_map(|(side_id, side)| {
3045 side.as_ref()
3046 .map(|side| (side_id, side.opts.link_local_enabled, side.opts))
3047 })
3048 .collect::<Vec<_>>();
3049 let mut per_side = Vec::new();
3050 for (side_id, link_local_enabled, opts) in side_entries {
3051 if skip_in_flight_sides
3052 && st.reliable_tx.iter().any(|((pending_side, ty), tx_state)| {
3053 *pending_side == side_id
3054 && !tx_state.sent.is_empty()
3055 && crate::DataType::try_from_u32(*ty)
3056 .is_some_and(discovery::is_discovery_type)
3057 })
3058 {
3059 continue;
3060 }
3061 if !self.route_allowed_locked(
3062 &st,
3063 None,
3064 Some(crate::DataType::DiscoveryAnnounce),
3065 side_id,
3066 ) {
3067 continue;
3068 }
3069 let Some(level) = Self::discovery_level_for_side_locked(&mut st, side_id, now_ms)
3070 else {
3071 continue;
3072 };
3073 let capabilities = opts.link_capabilities();
3074 if level == DiscoveryAdvertiseLevel::MinimalPing {
3075 per_side.push((
3076 side_id,
3077 level,
3078 Vec::new(),
3079 Vec::new(),
3080 Vec::new(),
3081 Vec::new(),
3082 capabilities,
3083 ));
3084 continue;
3085 }
3086 let endpoints = self.advertised_discovery_endpoints_for_link_locked(
3087 &st,
3088 now_ms,
3089 link_local_enabled,
3090 );
3091 let timesync_sources =
3092 self.advertised_discovery_timesync_sources_for_link_locked(&st, now_ms);
3093 let current_topology = self.advertised_discovery_topology_for_link_locked(
3094 &st,
3095 now_ms,
3096 link_local_enabled,
3097 );
3098 let (topology, removed_topology) = match level {
3099 DiscoveryAdvertiseLevel::Full => (current_topology.clone(), Vec::new()),
3100 DiscoveryAdvertiseLevel::Incremental => {
3101 let previous = &st
3102 .discovery_side_throttle
3103 .get(&side_id)
3104 .expect("discovery throttle exists")
3105 .last_topology;
3106 discovery::topology_delta(previous, ¤t_topology)
3107 }
3108 DiscoveryAdvertiseLevel::MinimalPing => (Vec::new(), Vec::new()),
3109 };
3110 let throttle = st
3111 .discovery_side_throttle
3112 .get_mut(&side_id)
3113 .expect("discovery throttle exists");
3114 if level == DiscoveryAdvertiseLevel::Full
3115 || (level == DiscoveryAdvertiseLevel::Incremental
3116 && throttle.incremental_retries_remaining == 0)
3117 {
3118 throttle.last_topology = current_topology;
3119 }
3120 per_side.push((
3121 side_id,
3122 level,
3123 endpoints,
3124 timesync_sources,
3125 topology,
3126 removed_topology,
3127 capabilities,
3128 ));
3129 }
3130 per_side
3131 };
3132 let mut st = self.state.lock();
3133 for (dst, level, endpoints, timesync_sources, topology, removed_topology, capabilities) in
3134 per_side
3135 {
3136 let sender = self.sender_arc();
3137 if include_schema && level == DiscoveryAdvertiseLevel::Full {
3138 let pkt = discovery::build_discovery_schema(sender.as_ref(), now_ms)?;
3139 let data = RelayItem::Packet(Arc::new(pkt));
3140 let priority = Self::relay_item_priority(&data)?;
3141 st.push_tx(RelayTxItem {
3142 src: None,
3143 dst,
3144 data,
3145 priority,
3146 })?;
3147 }
3148 if level == DiscoveryAdvertiseLevel::Full {
3149 let pkt = discovery::build_discovery_link_capabilities(
3150 sender.as_ref(),
3151 now_ms,
3152 capabilities,
3153 )?;
3154 let data = RelayItem::Packet(Arc::new(pkt));
3155 let priority = Self::relay_item_priority(&data)?;
3156 st.push_tx(RelayTxItem {
3157 src: None,
3158 dst,
3159 data,
3160 priority,
3161 })?;
3162 }
3163 if level != DiscoveryAdvertiseLevel::Full || !endpoints.is_empty() {
3164 let pkt = discovery::build_discovery_announce(
3165 sender.as_ref(),
3166 now_ms,
3167 endpoints.as_slice(),
3168 )?;
3169 let data = RelayItem::Packet(Arc::new(pkt));
3170 let priority = Self::relay_item_priority(&data)?;
3171 st.push_tx(RelayTxItem {
3172 src: None,
3173 dst,
3174 data,
3175 priority,
3176 })?;
3177 }
3178 if level != DiscoveryAdvertiseLevel::MinimalPing && !timesync_sources.is_empty() {
3179 let pkt = discovery::build_discovery_timesync_sources(
3180 sender.as_ref(),
3181 now_ms,
3182 timesync_sources.as_slice(),
3183 )?;
3184 let data = RelayItem::Packet(Arc::new(pkt));
3185 let priority = Self::relay_item_priority(&data)?;
3186 st.push_tx(RelayTxItem {
3187 src: None,
3188 dst,
3189 data,
3190 priority,
3191 })?;
3192 }
3193 if level != DiscoveryAdvertiseLevel::MinimalPing
3194 && (!topology.is_empty() || !removed_topology.is_empty())
3195 {
3196 let pkt = if level == DiscoveryAdvertiseLevel::Full {
3197 discovery::build_discovery_topology(sender.as_ref(), now_ms, &topology)?
3198 } else {
3199 discovery::build_discovery_topology_delta(
3200 sender.as_ref(),
3201 now_ms,
3202 &topology,
3203 &removed_topology,
3204 )?
3205 };
3206 let data = RelayItem::Packet(Arc::new(pkt));
3207 let priority = Self::relay_item_priority(&data)?;
3208 st.push_tx(RelayTxItem {
3209 src: None,
3210 dst,
3211 data,
3212 priority,
3213 })?;
3214 }
3215 }
3216 Ok(())
3217 }
3218
3219 #[cfg(feature = "discovery")]
3220 fn poll_discovery_announce(&self) -> TelemetryResult<bool> {
3221 let now_ms = self.clock.now_ms();
3222 let due = {
3223 let mut st = self.state.lock();
3224 let removed = Self::prune_discovery_routes_locked(&mut st, now_ms);
3225 if removed {
3226 self.reconcile_end_to_end_acked_destinations_locked(&mut st);
3227 Self::note_discovery_topology_change_locked(&mut st, now_ms);
3228 }
3229 st.fit_discovery_budget();
3230 let has_any = st.sides.iter().enumerate().any(|(side_id, side)| {
3231 let Some(side) = side.as_ref() else {
3232 return false;
3233 };
3234 if !self.route_allowed_locked(
3235 &st,
3236 None,
3237 Some(crate::DataType::DiscoveryAnnounce),
3238 side_id,
3239 ) {
3240 return false;
3241 }
3242 let _ = side;
3243 !st.reliable_tx.iter().any(|((pending_side, ty), tx_state)| {
3244 *pending_side == side_id
3245 && !tx_state.sent.is_empty()
3246 && crate::DataType::try_from_u32(*ty)
3247 .is_some_and(discovery::is_discovery_type)
3248 })
3249 });
3250 if !st.sides.iter().any(|side| side.is_some()) || !has_any {
3251 return Ok(false);
3252 }
3253 st.discovery_cadence.due(now_ms)
3254 };
3255 if !due {
3256 return Ok(false);
3257 }
3258 self.queue_discovery_announce(false, true)?;
3261 Ok(true)
3262 }
3263
3264 #[cfg(feature = "discovery")]
3265 fn learn_discovery_item(&self, src: RelaySideId, data: &RelayItem) -> TelemetryResult<()> {
3266 let pkt = match data {
3267 RelayItem::Packet(pkt) => {
3268 if !discovery::is_discovery_type(pkt.data_type()) {
3269 return Ok(());
3270 }
3271 pkt.as_ref().clone()
3272 }
3273 RelayItem::Packed(bytes) => {
3274 let env = wire_format::peek_envelope(bytes.as_ref())?;
3275 if !discovery::is_discovery_type(env.ty) {
3276 return Ok(());
3277 }
3278 if wire_format::peek_frame_info(bytes.as_ref())
3279 .ok()
3280 .is_some_and(|frame| frame.ack_only())
3281 {
3282 return Ok(());
3283 }
3284 wire_format::unpack_packet(bytes.as_ref())?
3285 }
3286 };
3287
3288 let now_ms = self.clock.now_ms();
3289 if pkt.data_type() == crate::DataType::DiscoverySchema {
3290 let snapshot = discovery::decode_discovery_schema(&pkt)?;
3291 let incoming_cost = crate::config::owned_schema_byte_cost(&snapshot);
3292 let mut st = self.state.lock();
3293 st.make_shared_queue_room(incoming_cost, RelayQueueKind::Discovery)?;
3294 let budget = st.memory.max_queue_budget;
3295 drop(st);
3296 let report = crate::config::merge_owned_schema_snapshot_with_budget(snapshot, budget)?;
3297 if report.changed() {
3298 let mut st = self.state.lock();
3299 st.fit_discovery_budget();
3300 Self::note_discovery_topology_change_locked(&mut st, now_ms);
3301 }
3302 return Ok(());
3303 }
3304 if pkt.data_type() == crate::DataType::DiscoveryLinkCapabilities {
3305 let _ = discovery::decode_discovery_link_capabilities(&pkt)?;
3306 return Ok(());
3307 }
3308 let mut st = self.state.lock();
3309 if pkt.data_type() == crate::DataType::DiscoveryLeave {
3310 let leaving = pkt.sender();
3311 let before = st.discovery_routes.clone();
3312 for route in st.discovery_routes.values_mut() {
3313 route.announcers.remove(leaving);
3314 for sender_state in route.announcers.values_mut() {
3315 sender_state
3316 .topology_boards
3317 .retain(|board| board.sender_id != leaving);
3318 for board in sender_state.topology_boards.iter_mut() {
3319 board.connections.retain(|peer| peer != leaving);
3320 }
3321 Self::refresh_sender_topology_state(sender_state);
3322 }
3323 Self::recompute_discovery_side_state(route);
3324 }
3325 st.discovery_routes
3326 .retain(|_, route| !route.announcers.is_empty());
3327 if st.discovery_routes != before {
3328 Self::note_discovery_topology_change_locked(&mut st, now_ms);
3329 }
3330 let _ = Self::prune_discovery_routes_locked(&mut st, now_ms);
3331 self.reconcile_end_to_end_acked_destinations_locked(&mut st);
3332 return Ok(());
3333 }
3334 let address_ad = if pkt.data_type() == crate::DataType::DiscoveryAddress {
3335 Some(discovery::decode_discovery_address(&pkt)?)
3336 } else {
3337 None
3338 };
3339 let mut topology_ad = if pkt.data_type() == crate::DataType::DiscoveryTopology {
3340 Some(discovery::decode_discovery_topology_update(&pkt)?)
3341 } else {
3342 None
3343 };
3344 let announcer_id = if let Some(ad) = address_ad.as_ref() {
3345 ad.hostname.clone()
3346 } else {
3347 let canonical = Self::canonical_sender_locked(&st, pkt.sender());
3348 if canonical != pkt.sender() {
3349 canonical
3350 } else if let Some(address) = pkt
3351 .sender()
3352 .strip_prefix("@addr:")
3353 .and_then(|value| value.parse::<u32>().ok())
3354 {
3355 topology_ad
3356 .as_ref()
3357 .and_then(|update| {
3358 update
3359 .boards
3360 .iter()
3361 .find(|board| sender_address_u32(&board.sender_id) == address)
3362 })
3363 .map(|board| board.sender_id.clone())
3364 .unwrap_or(canonical)
3365 } else {
3366 canonical
3367 }
3368 };
3369 let mut route = st.discovery_routes.get(&src).cloned().unwrap_or_default();
3370 if pkt.sender() != announcer_id {
3371 route.announcers.remove(pkt.sender());
3372 }
3373 let side_link_local_enabled = st
3374 .sides
3375 .get(src)
3376 .and_then(|entry| entry.as_ref())
3377 .map(|side_ref| side_ref.opts.link_local_enabled)
3378 .unwrap_or(false);
3379 let mut sender_state = route
3380 .announcers
3381 .get(&announcer_id)
3382 .cloned()
3383 .unwrap_or_default();
3384 let changed = match pkt.data_type() {
3385 crate::DataType::DiscoveryAddress => {
3386 let ad = address_ad.expect("decoded above");
3387 let advertised_address = ad.address;
3388 let mut reachable = ad.reachable_endpoints;
3389 if !side_link_local_enabled {
3390 reachable.retain(|ep| !ep.is_link_local_only());
3391 }
3392 let changed = sender_state.advertised_address != Some(advertised_address)
3395 || sender_state.advertised_reachable != reachable
3396 || sender_state.advertised_reachable_timesync_sources
3397 != ad.reachable_timesync_sources;
3398 sender_state.advertised_address = Some(advertised_address);
3399 sender_state.advertised_reachable = reachable;
3400 sender_state.advertised_reachable_timesync_sources = ad.reachable_timesync_sources;
3401 Self::refresh_sender_topology_state(&mut sender_state);
3402 changed
3403 }
3404 crate::DataType::DiscoveryAnnounce => {
3405 let mut reachable = discovery::decode_discovery_announce(&pkt)?;
3406 if !side_link_local_enabled {
3407 reachable.retain(|ep| !ep.is_link_local_only());
3408 }
3409 let board = Self::sender_topology_board_mut(&mut sender_state, &announcer_id);
3410 let changed = board.reachable_endpoints != reachable;
3411 board.reachable_endpoints = reachable;
3412 Self::refresh_sender_topology_state(&mut sender_state);
3413 changed
3414 }
3415 crate::DataType::DiscoveryTimeSyncSources => {
3416 let sources = discovery::decode_discovery_timesync_sources(&pkt)?;
3417 let board = Self::sender_topology_board_mut(&mut sender_state, &announcer_id);
3418 let changed = board.reachable_timesync_sources != sources;
3419 board.reachable_timesync_sources = sources;
3420 Self::refresh_sender_topology_state(&mut sender_state);
3421 changed
3422 }
3423 crate::DataType::DiscoveryTopology => {
3424 let mut update = topology_ad
3425 .take()
3426 .expect("topology packet was decoded before route selection");
3427 for board in update.boards.iter_mut() {
3428 board.sender_id = Self::canonical_sender_locked(&st, &board.sender_id);
3429 for peer in board.connections.iter_mut() {
3430 *peer = Self::canonical_sender_locked(&st, peer);
3431 }
3432 }
3433 if !side_link_local_enabled {
3434 for board in update.boards.iter_mut() {
3435 board
3436 .reachable_endpoints
3437 .retain(|ep| !ep.is_link_local_only());
3438 }
3439 }
3440 let before = sender_state.topology_boards.clone();
3441 if update.incremental {
3442 sender_state.topology_boards.retain(|board| {
3443 !update
3444 .removed
3445 .iter()
3446 .any(|removed| removed == &board.sender_id)
3447 && !update
3448 .boards
3449 .iter()
3450 .any(|replacement| replacement.sender_id == board.sender_id)
3451 });
3452 discovery::merge_topology_boards(
3453 &mut sender_state.topology_boards,
3454 &update.boards,
3455 );
3456 } else {
3457 sender_state.topology_boards = update.boards;
3458 }
3459 let changed = sender_state.topology_boards != before;
3460 Self::refresh_sender_topology_state(&mut sender_state);
3461 changed
3462 }
3463 crate::DataType::DiscoverySchema => false,
3464 _ => false,
3465 };
3466 sender_state.last_seen_ms = now_ms;
3467 route.announcers.insert(announcer_id, sender_state);
3468 Self::recompute_discovery_side_state(&mut route);
3469 st.discovery_routes.insert(src, route);
3470 st.fit_discovery_budget();
3471 if changed {
3472 Self::note_discovery_topology_change_locked(&mut st, now_ms);
3473 }
3474 let _ = Self::prune_discovery_routes_locked(&mut st, now_ms);
3475 self.reconcile_end_to_end_acked_destinations_locked(&mut st);
3476 Ok(())
3477 }
3478
3479 #[cfg(not(feature = "discovery"))]
3480 fn learn_discovery_item(&self, _src: RelaySideId, _data: &RelayItem) -> TelemetryResult<()> {
3481 Ok(())
3482 }
3483
3484 #[cfg(not(feature = "discovery"))]
3485 fn queue_discovery_announce(&self) -> TelemetryResult<()> {
3486 Ok(())
3487 }
3488
3489 #[cfg(not(feature = "discovery"))]
3490 fn poll_discovery_announce(&self) -> TelemetryResult<bool> {
3491 Ok(false)
3492 }
3493
3494 fn process_reliable_timeouts(&self) -> TelemetryResult<()> {
3495 let now = self.clock.now_ms();
3496 let mut requeue: Vec<(RelaySideId, crate::DataType, u32)> = Vec::new();
3497
3498 {
3499 let mut st = self.state.lock();
3500 if st.reliable_tx.is_empty() {
3501 return Ok(());
3502 }
3503
3504 for ((side, ty_u32), tx_state) in st.reliable_tx.iter_mut() {
3505 let Some(ty) = crate::DataType::try_from_u32(*ty_u32) else {
3506 continue;
3507 };
3508 let sent_order: Vec<u32> = tx_state.sent_order.iter().copied().collect();
3509 for seq in sent_order {
3510 let Some(sent) = tx_state.sent.get_mut(&seq) else {
3511 continue;
3512 };
3513 if sent.queued
3514 || now.wrapping_sub(sent.last_send_ms) < runtime_reliable_retransmit_ms()
3515 {
3516 continue;
3517 }
3518 if sent.partial_acked {
3519 sent.partial_acked = false;
3523 sent.last_send_ms = now;
3524 continue;
3525 }
3526 if sent.retries >= runtime_reliable_max_retries() {
3527 tx_state.sent.remove(&seq);
3528 tx_state.sent_order.retain(|existing| *existing != seq);
3529 continue;
3530 }
3531 sent.retries += 1;
3532 requeue.push((*side, ty, seq));
3533 }
3534 }
3535 }
3536
3537 for (side, ty, seq) in requeue {
3538 self.queue_reliable_retransmit(side, ty, seq)?;
3539 }
3540
3541 Ok(())
3542 }
3543
3544 fn get_hash(item: &RelayRxItem) -> u64 {
3548 match &item.data {
3549 RelayItem::Packet(pkt) => pkt.packet_id(),
3550 RelayItem::Packed(bytes) => {
3551 let reliable_seq = wire_format::peek_frame_info(bytes.as_ref())
3552 .ok()
3553 .and_then(|frame| frame.reliable)
3554 .and_then(|hdr| {
3555 if (hdr.flags & wire_format::RELIABLE_FLAG_ACK_ONLY) != 0 {
3556 None
3557 } else {
3558 Some(hdr.seq)
3559 }
3560 });
3561
3562 match wire_format::packet_id_from_wire(bytes.as_ref()) {
3563 Ok(id) => {
3564 if let Some(seq) = reliable_seq {
3565 hash_bytes_u64(id, &seq.to_le_bytes())
3566 } else {
3567 id
3568 }
3569 }
3570 Err(_e) => {
3571 let h: u64 = 0x9E37_79B9_7F4A_7C15;
3574 hash_bytes_u64(h, bytes.as_ref())
3575 }
3576 }
3577 }
3578 }
3579 }
3580
3581 fn is_duplicate_pkt(&self, item: &RelayRxItem) -> TelemetryResult<bool> {
3585 let id = Self::get_hash(item);
3586
3587 let mut st = self.state.lock();
3588 if st.recent_rx.contains(&id) {
3589 Ok(true)
3590 } else {
3591 st.push_recent_rx(id)?;
3592 Ok(false)
3593 }
3594 }
3595
3596 fn should_forward_duplicate_reliable_item(&self, item: &RelayRxItem) -> TelemetryResult<bool> {
3597 let (_, ty) = self.item_route_info(&item.data)?;
3598 if !is_reliable_type(ty)
3599 || matches!(
3600 ty,
3601 crate::DataType::ReliableAck
3602 | crate::DataType::ReliablePartialAck
3603 | crate::DataType::ReliablePacketRequest
3604 )
3605 {
3606 return Ok(false);
3607 }
3608
3609 let RemoteSidePlan::Target(sides) = self.remote_side_plan(&item.data, item.src)?;
3610 let st = self.state.lock();
3611 let now_ms = self.clock.now_ms();
3612 Ok(sides
3613 .into_iter()
3614 .any(|side| self.side_has_multiple_announcers_locked(&st, side, now_ms)))
3615 }
3616
3617 pub fn add_side_packed<N, F>(&self, name: N, tx: F) -> RelaySideId
3622 where
3623 N: AsRef<str>,
3624 F: Fn(&[u8]) -> TelemetryResult<()> + Send + Sync + 'static,
3625 {
3626 self.add_side_packed_with_options(name, tx, RelaySideOptions::default())
3627 }
3628
3629 pub fn add_side_packed_small_packets<N, F>(
3633 &self,
3634 name: N,
3635 tx: F,
3636 max_frame_bytes: usize,
3637 ) -> RelaySideId
3638 where
3639 N: AsRef<str>,
3640 F: Fn(&[u8]) -> TelemetryResult<()> + Send + Sync + 'static,
3641 {
3642 self.add_side_packed_with_options(
3643 name,
3644 tx,
3645 RelaySideOptions::default().with_small_packet_transport(max_frame_bytes),
3646 )
3647 }
3648
3649 pub fn add_side_packed_with_options<N, F>(
3655 &self,
3656 name: N,
3657 tx: F,
3658 opts: RelaySideOptions,
3659 ) -> RelaySideId
3660 where
3661 N: AsRef<str>,
3662 F: Fn(&[u8]) -> TelemetryResult<()> + Send + Sync + 'static,
3663 {
3664 let mut st = self.state.lock();
3665 let side = Some(RelaySide {
3666 name: Arc::from(name.as_ref()),
3667 tx_handler: RelayTxHandlerFn::Packed(Arc::new(tx)),
3668 opts,
3669 });
3670 let id = if let Some(id) = st.sides.iter().position(Option::is_none) {
3671 st.sides[id] = side;
3672 id
3673 } else {
3674 let id = st.sides.len();
3675 st.sides.push(side);
3676 id
3677 };
3678 st.side_runtime_stats
3679 .insert(id, SideRuntimeStatsInner::default());
3680 st.side_transport.insert(id, SideTransportState::default());
3681 #[cfg(feature = "discovery")]
3682 Self::note_discovery_topology_change_locked(&mut st, self.clock.now_ms());
3683 id
3684 }
3685
3686 pub fn add_side_packet<N, F>(&self, name: N, tx: F) -> RelaySideId
3691 where
3692 N: AsRef<str>,
3693 F: Fn(&Packet) -> TelemetryResult<()> + Send + Sync + 'static,
3694 {
3695 self.add_side_packet_with_options(name, tx, RelaySideOptions::default())
3696 }
3697
3698 pub fn add_side_packet_with_options<N, F>(
3700 &self,
3701 name: N,
3702 tx: F,
3703 opts: RelaySideOptions,
3704 ) -> RelaySideId
3705 where
3706 N: AsRef<str>,
3707 F: Fn(&Packet) -> TelemetryResult<()> + Send + Sync + 'static,
3708 {
3709 let mut st = self.state.lock();
3710 let side = Some(RelaySide {
3711 name: Arc::from(name.as_ref()),
3712 tx_handler: RelayTxHandlerFn::Packet(Arc::new(tx)),
3713 opts,
3714 });
3715 let id = if let Some(id) = st.sides.iter().position(Option::is_none) {
3716 st.sides[id] = side;
3717 id
3718 } else {
3719 let id = st.sides.len();
3720 st.sides.push(side);
3721 id
3722 };
3723 st.side_runtime_stats
3724 .insert(id, SideRuntimeStatsInner::default());
3725 st.side_transport.insert(id, SideTransportState::default());
3726 #[cfg(feature = "discovery")]
3727 Self::note_discovery_topology_change_locked(&mut st, self.clock.now_ms());
3728 id
3729 }
3730
3731 pub fn remove_side(&self, side: RelaySideId) -> TelemetryResult<()> {
3736 let now_ms = self.clock.now_ms();
3737 let mut st = self.state.lock();
3738 let slot = st.sides.get_mut(side).ok_or(TelemetryError::BadArg)?;
3739 if slot.is_none() {
3740 return Err(TelemetryError::BadArg);
3741 }
3742 *slot = None;
3743 while st.sides.last().is_some_and(Option::is_none) {
3744 st.sides.pop();
3745 }
3746 st.route_overrides
3749 .retain(|(src_side, dst_side), _| *src_side != Some(side) && *dst_side != side);
3750 st.typed_route_overrides
3751 .retain(|(src_side, _, dst_side), _| *src_side != Some(side) && *dst_side != side);
3752 st.route_weights
3753 .retain(|(src_side, dst_side), _| *src_side != Some(side) && *dst_side != side);
3754 st.route_priorities
3755 .retain(|(src_side, dst_side), _| *src_side != Some(side) && *dst_side != side);
3756 st.source_route_modes.remove(&Some(side));
3757 st.route_selection_cursors.remove(&Some(side));
3758 st.adaptive_route_stats.remove(&side);
3759 #[cfg(feature = "discovery")]
3760 st.discovery_side_throttle.remove(&side);
3761 #[cfg(all(feature = "discovery", feature = "timesync"))]
3762 st.timesync_side_throttle.remove(&side);
3763 st.side_runtime_stats.remove(&side);
3764 st.reliable_return_routes
3765 .retain(|_, route| route.side != side);
3766 st.rx_queue.retain(|queued| queued.src != side);
3767 st.tx_queue
3768 .retain(|queued| queued.dst != side && queued.src != Some(side));
3769 st.replay_queue.retain(|queued| queued.dst != side);
3770 st.reliable_tx.retain(|(side_id, _), _| *side_id != side);
3771 st.reliable_rx.retain(|(side_id, _), _| *side_id != side);
3772 #[cfg(feature = "discovery")]
3773 {
3774 st.discovery_routes.remove(&side);
3775 Self::note_discovery_topology_change_locked(&mut st, now_ms);
3776 }
3777 Ok(())
3778 }
3779
3780 pub fn set_side_ingress_enabled(
3782 &self,
3783 side: RelaySideId,
3784 enabled: bool,
3785 ) -> TelemetryResult<()> {
3786 let now_ms = self.clock.now_ms();
3787 let mut st = self.state.lock();
3788 let side_ref = st
3789 .sides
3790 .get_mut(side)
3791 .and_then(|side| side.as_mut())
3792 .ok_or(TelemetryError::BadArg)?;
3793 side_ref.opts.ingress_enabled = enabled;
3794 #[cfg(feature = "discovery")]
3795 Self::note_discovery_topology_change_locked(&mut st, now_ms);
3796 Ok(())
3797 }
3798
3799 pub fn set_side_egress_enabled(&self, side: RelaySideId, enabled: bool) -> TelemetryResult<()> {
3801 let now_ms = self.clock.now_ms();
3802 let mut st = self.state.lock();
3803 let side_ref = st
3804 .sides
3805 .get_mut(side)
3806 .and_then(|side| side.as_mut())
3807 .ok_or(TelemetryError::BadArg)?;
3808 side_ref.opts.egress_enabled = enabled;
3809 if !enabled {
3810 st.tx_queue.retain(|queued| queued.dst != side);
3811 st.replay_queue.retain(|queued| queued.dst != side);
3812 }
3813 #[cfg(feature = "discovery")]
3814 Self::note_discovery_topology_change_locked(&mut st, now_ms);
3815 Ok(())
3816 }
3817
3818 pub fn set_source_route_mode(
3822 &self,
3823 src: Option<RelaySideId>,
3824 mode: RouteSelectionMode,
3825 ) -> TelemetryResult<()> {
3826 let now_ms = self.clock.now_ms();
3827 let mut st = self.state.lock();
3828 if let Some(src) = src {
3829 let _ = Self::side_ref(&st, src).map_err(|_| TelemetryError::BadArg)?;
3830 }
3831 st.source_route_modes.insert(src, mode);
3834 st.route_selection_cursors.remove(&src);
3835 #[cfg(feature = "discovery")]
3836 Self::note_discovery_topology_change_locked(&mut st, now_ms);
3837 Ok(())
3838 }
3839
3840 pub fn clear_source_route_mode(&self, src: Option<RelaySideId>) -> TelemetryResult<()> {
3842 let now_ms = self.clock.now_ms();
3843 let mut st = self.state.lock();
3844 if let Some(src) = src {
3845 let _ = Self::side_ref(&st, src).map_err(|_| TelemetryError::BadArg)?;
3846 }
3847 st.source_route_modes.remove(&src);
3848 st.route_selection_cursors.remove(&src);
3849 #[cfg(feature = "discovery")]
3850 Self::note_discovery_topology_change_locked(&mut st, now_ms);
3851 Ok(())
3852 }
3853
3854 pub fn set_route_weight(
3856 &self,
3857 src: Option<RelaySideId>,
3858 dst: RelaySideId,
3859 weight: u32,
3860 ) -> TelemetryResult<()> {
3861 let now_ms = self.clock.now_ms();
3862 let mut st = self.state.lock();
3863 let _ = Self::side_ref(&st, dst).map_err(|_| TelemetryError::BadArg)?;
3864 if let Some(src) = src {
3865 let _ = Self::side_ref(&st, src).map_err(|_| TelemetryError::BadArg)?;
3866 }
3867 st.route_weights.insert((src, dst), weight);
3868 st.route_selection_cursors.remove(&src);
3869 #[cfg(feature = "discovery")]
3870 Self::note_discovery_topology_change_locked(&mut st, now_ms);
3871 Ok(())
3872 }
3873
3874 pub fn clear_route_weight(
3876 &self,
3877 src: Option<RelaySideId>,
3878 dst: RelaySideId,
3879 ) -> TelemetryResult<()> {
3880 let now_ms = self.clock.now_ms();
3881 let mut st = self.state.lock();
3882 let _ = Self::side_ref(&st, dst).map_err(|_| TelemetryError::BadArg)?;
3883 if let Some(src) = src {
3884 let _ = Self::side_ref(&st, src).map_err(|_| TelemetryError::BadArg)?;
3885 }
3886 st.route_weights.remove(&(src, dst));
3887 st.route_selection_cursors.remove(&src);
3888 #[cfg(feature = "discovery")]
3889 Self::note_discovery_topology_change_locked(&mut st, now_ms);
3890 Ok(())
3891 }
3892
3893 pub fn set_route_priority(
3895 &self,
3896 src: Option<RelaySideId>,
3897 dst: RelaySideId,
3898 priority: u32,
3899 ) -> TelemetryResult<()> {
3900 let now_ms = self.clock.now_ms();
3901 let mut st = self.state.lock();
3902 let _ = Self::side_ref(&st, dst).map_err(|_| TelemetryError::BadArg)?;
3903 if let Some(src) = src {
3904 let _ = Self::side_ref(&st, src).map_err(|_| TelemetryError::BadArg)?;
3905 }
3906 st.route_priorities.insert((src, dst), priority);
3907 #[cfg(feature = "discovery")]
3908 Self::note_discovery_topology_change_locked(&mut st, now_ms);
3909 Ok(())
3910 }
3911
3912 pub fn clear_route_priority(
3914 &self,
3915 src: Option<RelaySideId>,
3916 dst: RelaySideId,
3917 ) -> TelemetryResult<()> {
3918 let now_ms = self.clock.now_ms();
3919 let mut st = self.state.lock();
3920 let _ = Self::side_ref(&st, dst).map_err(|_| TelemetryError::BadArg)?;
3921 if let Some(src) = src {
3922 let _ = Self::side_ref(&st, src).map_err(|_| TelemetryError::BadArg)?;
3923 }
3924 st.route_priorities.remove(&(src, dst));
3925 #[cfg(feature = "discovery")]
3926 Self::note_discovery_topology_change_locked(&mut st, now_ms);
3927 Ok(())
3928 }
3929
3930 pub fn set_route(
3932 &self,
3933 src: Option<RelaySideId>,
3934 dst: RelaySideId,
3935 enabled: bool,
3936 ) -> TelemetryResult<()> {
3937 let now_ms = self.clock.now_ms();
3938 let mut st = self.state.lock();
3939 let _ = Self::side_ref(&st, dst).map_err(|_| TelemetryError::BadArg)?;
3940 if let Some(src) = src {
3941 let _ = Self::side_ref(&st, src).map_err(|_| TelemetryError::BadArg)?;
3942 }
3943 st.route_overrides.insert((src, dst), enabled);
3944 #[cfg(feature = "discovery")]
3945 Self::note_discovery_topology_change_locked(&mut st, now_ms);
3946 Ok(())
3947 }
3948
3949 pub fn set_typed_route(
3951 &self,
3952 src: Option<RelaySideId>,
3953 ty: crate::DataType,
3954 dst: RelaySideId,
3955 enabled: bool,
3956 ) -> TelemetryResult<()> {
3957 let now_ms = self.clock.now_ms();
3958 let mut st = self.state.lock();
3959 let _ = Self::side_ref(&st, dst).map_err(|_| TelemetryError::BadArg)?;
3960 if let Some(src) = src {
3961 let _ = Self::side_ref(&st, src).map_err(|_| TelemetryError::BadArg)?;
3962 }
3963 st.typed_route_overrides
3964 .insert((src, ty.as_u32(), dst), enabled);
3965 #[cfg(feature = "discovery")]
3966 Self::note_discovery_topology_change_locked(&mut st, now_ms);
3967 Ok(())
3968 }
3969
3970 pub fn clear_typed_route(
3972 &self,
3973 src: Option<RelaySideId>,
3974 ty: crate::DataType,
3975 dst: RelaySideId,
3976 ) -> TelemetryResult<()> {
3977 let now_ms = self.clock.now_ms();
3978 let mut st = self.state.lock();
3979 let _ = Self::side_ref(&st, dst).map_err(|_| TelemetryError::BadArg)?;
3980 if let Some(src) = src {
3981 let _ = Self::side_ref(&st, src).map_err(|_| TelemetryError::BadArg)?;
3982 }
3983 st.typed_route_overrides.remove(&(src, ty.as_u32(), dst));
3984 #[cfg(feature = "discovery")]
3985 Self::note_discovery_topology_change_locked(&mut st, now_ms);
3986 Ok(())
3987 }
3988
3989 pub fn clear_route(&self, src: Option<RelaySideId>, dst: RelaySideId) -> TelemetryResult<()> {
3991 let now_ms = self.clock.now_ms();
3992 let mut st = self.state.lock();
3993 let _ = Self::side_ref(&st, dst).map_err(|_| TelemetryError::BadArg)?;
3994 if let Some(src) = src {
3995 let _ = Self::side_ref(&st, src).map_err(|_| TelemetryError::BadArg)?;
3996 }
3997 st.route_overrides.remove(&(src, dst));
3998 #[cfg(feature = "discovery")]
3999 Self::note_discovery_topology_change_locked(&mut st, now_ms);
4000 Ok(())
4001 }
4002
4003 #[cfg(feature = "discovery")]
4004 pub fn announce_discovery(&self) -> TelemetryResult<()> {
4006 self.queue_discovery_announce(true, false)
4007 }
4008
4009 pub fn announce_leave(&self) -> TelemetryResult<()> {
4011 let pkt = discovery::build_discovery_leave("relay", self.clock.now_ms())?;
4012 let mut st = self.state.lock();
4013 let dsts: Vec<usize> = st
4014 .sides
4015 .iter()
4016 .enumerate()
4017 .filter_map(|(idx, side)| side.as_ref().map(|_| idx))
4018 .collect();
4019 for dst in dsts {
4020 let data = RelayItem::Packet(Arc::new(pkt.clone()));
4021 let priority = Self::relay_item_priority(&data)?;
4022 st.push_tx(RelayTxItem {
4023 src: None,
4024 dst,
4025 data,
4026 priority,
4027 })?;
4028 }
4029 Ok(())
4030 }
4031
4032 #[cfg(feature = "discovery")]
4033 pub fn poll_discovery(&self) -> TelemetryResult<bool> {
4035 self.poll_discovery_announce()
4036 }
4037
4038 #[cfg(feature = "discovery")]
4039 pub fn export_topology(&self) -> TopologySnapshot {
4041 let now_ms = self.clock.now_ms();
4042 let mut st = self.state.lock();
4043 if Self::prune_discovery_routes_locked(&mut st, now_ms) {
4044 self.reconcile_end_to_end_acked_destinations_locked(&mut st);
4045 Self::note_discovery_topology_change_locked(&mut st, now_ms);
4046 }
4047 let routes = st
4048 .discovery_routes
4049 .iter()
4050 .filter_map(|(&side_id, route)| {
4051 let side = st.sides.get(side_id).and_then(|side| side.as_ref())?;
4052 let announcers = route
4053 .announcers
4054 .iter()
4055 .map(|(sender_id, sender_state)| TopologyAnnouncerRoute {
4056 sender_id: sender_id.clone(),
4057 reachable_endpoints: sender_state
4058 .reachable
4059 .iter()
4060 .copied()
4061 .filter(|ep| !discovery::is_router_control_endpoint(*ep))
4062 .collect(),
4063 reachable_network_variables: Vec::new(),
4064 reachable_timesync_sources: sender_state.reachable_timesync_sources.clone(),
4065 routers: sender_state.topology_boards.clone(),
4066 last_seen_ms: sender_state.last_seen_ms,
4067 age_ms: now_ms.saturating_sub(sender_state.last_seen_ms),
4068 })
4069 .collect();
4070 Some(TopologySideRoute {
4071 side_id,
4072 side_name: side.name.to_string(),
4073 reachable_endpoints: route
4074 .reachable
4075 .iter()
4076 .copied()
4077 .filter(|ep| !discovery::is_router_control_endpoint(*ep))
4078 .collect(),
4079 reachable_network_variables: Vec::new(),
4080 reachable_timesync_sources: route.reachable_timesync_sources.clone(),
4081 announcers,
4082 last_seen_ms: route.last_seen_ms,
4083 age_ms: now_ms.saturating_sub(route.last_seen_ms),
4084 })
4085 })
4086 .collect();
4087 let routers = self.advertised_discovery_topology_for_link_locked(&st, now_ms, true);
4088 let advertised_endpoints =
4089 self.advertised_discovery_endpoints_for_link_locked(&st, now_ms, true);
4090 let advertised_timesync_sources =
4091 self.advertised_discovery_timesync_sources_for_link_locked(&st, now_ms);
4092 let links = discovery::topology_links_from_boards(&routers);
4093 TopologySnapshot {
4094 advertised_endpoints,
4095 advertised_timesync_sources,
4096 routers,
4097 links,
4098 routes,
4099 current_announce_interval_ms: st.discovery_cadence.current_interval_ms,
4100 next_announce_ms: st.discovery_cadence.next_announce_ms,
4101 }
4102 }
4103
4104 #[cfg(feature = "discovery")]
4105 pub fn client_stats(&self, sender_id: &str) -> Option<ClientStatsSnapshot> {
4106 let now_ms = self.clock.now_ms();
4107 let st = self.state.lock();
4108 let mut side_ids = Vec::new();
4109 let mut side_names = Vec::new();
4110 let mut last_seen_ms = None::<u64>;
4111 let mut reachable_endpoints = Vec::new();
4112 let mut reachable_timesync_sources = Vec::new();
4113 let mut packets_sent = 0u64;
4114 let mut packets_received = 0u64;
4115 let mut bytes_sent = 0u64;
4116 let mut bytes_received = 0u64;
4117
4118 for (side_id, route) in &st.discovery_routes {
4119 let Some(sender_state) = route.announcers.get(sender_id) else {
4120 continue;
4121 };
4122 side_ids.push(*side_id);
4123 if let Some(side_name) = st
4124 .sides
4125 .get(*side_id)
4126 .and_then(|side| side.as_ref())
4127 .map(|side| side.name.clone())
4128 {
4129 side_names.push(side_name.to_string());
4130 }
4131 last_seen_ms = Some(last_seen_ms.unwrap_or(0).max(sender_state.last_seen_ms));
4132 reachable_endpoints.extend(sender_state.reachable.iter().copied());
4133 reachable_timesync_sources
4134 .extend(sender_state.reachable_timesync_sources.iter().cloned());
4135 if let Some(stats) = st.side_runtime_stats.get(side_id) {
4136 packets_sent = packets_sent.saturating_add(stats.tx_packets);
4137 packets_received = packets_received.saturating_add(stats.rx_packets);
4138 bytes_sent = bytes_sent.saturating_add(stats.tx_bytes);
4139 bytes_received = bytes_received.saturating_add(stats.rx_bytes);
4140 }
4141 }
4142
4143 if side_ids.is_empty() {
4144 return None;
4145 }
4146 reachable_endpoints.retain(|ep| !discovery::is_router_control_endpoint(*ep));
4147 reachable_endpoints.sort_unstable();
4148 reachable_endpoints.dedup();
4149 reachable_timesync_sources.sort_unstable();
4150 reachable_timesync_sources.dedup();
4151 side_ids.sort_unstable();
4152 side_ids.dedup();
4153 side_names.sort_unstable();
4154 side_names.dedup();
4155 let age_ms = last_seen_ms.map(|seen| now_ms.saturating_sub(seen));
4156 Some(ClientStatsSnapshot {
4157 sender_id: sender_id.to_string(),
4158 connected: age_ms.is_some_and(|age| age <= DISCOVERY_ROUTE_TTL_MS),
4159 side_ids,
4160 side_names,
4161 last_seen_ms,
4162 age_ms,
4163 reachable_endpoints,
4164 reachable_timesync_sources,
4165 packets_sent,
4166 packets_received,
4167 bytes_sent,
4168 bytes_received,
4169 })
4170 }
4171
4172 pub fn export_runtime_stats(&self) -> RuntimeStatsSnapshot {
4173 let now_ms = self.clock.now_ms();
4174 let st = self.state.lock();
4175
4176 let mut sides = Vec::new();
4177 for (side_id, side) in st.sides.iter().enumerate() {
4178 let Some(side) = side.as_ref() else { continue };
4179 let stats = st
4180 .side_runtime_stats
4181 .get(&side_id)
4182 .cloned()
4183 .unwrap_or_default();
4184 let adaptive = st
4185 .adaptive_route_stats
4186 .get(&side_id)
4187 .cloned()
4188 .unwrap_or_default()
4189 .snapshot(now_ms, true);
4190 let (tx_template_count, rx_template_count) = st
4191 .side_transport
4192 .get(&side_id)
4193 .map(|state| (state.tx_template_count(), state.rx_template_count()))
4194 .unwrap_or((0, 0));
4195 let mut data_types: Vec<RuntimeTypeStats> = stats
4196 .data_types
4197 .into_iter()
4198 .map(|(ty, item)| RuntimeTypeStats {
4199 data_type: crate::DataType(ty),
4200 tx_packets: item.tx_packets,
4201 tx_bytes: item.tx_bytes,
4202 rx_packets: item.rx_packets,
4203 rx_bytes: item.rx_bytes,
4204 relayed_tx_packets: item.relayed_tx_packets,
4205 relayed_tx_bytes: item.relayed_tx_bytes,
4206 relayed_rx_packets: item.relayed_rx_packets,
4207 relayed_rx_bytes: item.relayed_rx_bytes,
4208 tx_retries: item.tx_retries,
4209 handler_failures: item.handler_failures,
4210 })
4211 .collect();
4212 data_types.sort_unstable_by_key(|item| item.data_type.as_u32());
4213 sides.push(RuntimeSideStats {
4214 side_id,
4215 side_name: side.name.to_string(),
4216 reliable_enabled: side.opts.reliable_enabled,
4217 link_local_enabled: side.opts.link_local_enabled,
4218 header_template_enabled: side.opts.header_template_enabled,
4219 max_frame_bytes: side.opts.max_frame_bytes,
4220 compact_header_target_bytes: side.opts.compact_header_target_bytes,
4221 side_transport_profile: side.opts.effective_transport_profile().as_str(),
4222 ingress_enabled: side.opts.ingress_enabled,
4223 egress_enabled: side.opts.egress_enabled,
4224 tx_packets: stats.tx_packets,
4225 tx_bytes: stats.tx_bytes,
4226 rx_packets: stats.rx_packets,
4227 rx_bytes: stats.rx_bytes,
4228 relayed_tx_packets: stats.relayed_tx_packets,
4229 relayed_tx_bytes: stats.relayed_tx_bytes,
4230 relayed_rx_packets: stats.relayed_rx_packets,
4231 relayed_rx_bytes: stats.relayed_rx_bytes,
4232 local_delivery_packets: 0,
4233 tx_retries: stats.tx_retries,
4234 tx_handler_failures: stats.tx_handler_failures,
4235 local_handler_failures: 0,
4236 total_handler_retries: stats.total_handler_retries,
4237 side_transport_full_frames: stats.side_transport_full_frames,
4238 side_transport_compact_frames: stats.side_transport_compact_frames,
4239 side_transport_compact_delta_frames: stats.side_transport_compact_delta_frames,
4240 side_transport_compact_omitted_timestamp_frames: stats
4241 .side_transport_compact_omitted_timestamp_frames,
4242 side_transport_chunk_frames: stats.side_transport_chunk_frames,
4243 side_transport_raw_bytes: stats.side_transport_raw_bytes,
4244 side_transport_wire_bytes: stats.side_transport_wire_bytes,
4245 side_transport_bytes_saved: stats.side_transport_bytes_saved,
4246 side_transport_min_compact_overhead_bytes: stats
4247 .side_transport_min_compact_overhead_bytes,
4248 side_transport_max_compact_overhead_bytes: stats
4249 .side_transport_max_compact_overhead_bytes,
4250 side_transport_compact_target_misses: stats.side_transport_compact_target_misses,
4251 side_transport_template_evictions: stats.side_transport_template_evictions,
4252 side_transport_tx_template_count: tx_template_count,
4253 side_transport_rx_template_count: rx_template_count,
4254 max_side_transport_templates: side.opts.max_side_transport_templates,
4255 adaptive,
4256 data_types,
4257 });
4258 }
4259
4260 let mut route_modes: Vec<RouteModeStats> = st
4261 .route_selection_cursors
4262 .iter()
4263 .map(|(src, cursor)| RouteModeStats {
4264 src_side_id: *src,
4265 selection_mode: st.source_route_modes.get(src).copied(),
4266 cursor: *cursor,
4267 })
4268 .collect();
4269 for src in st.source_route_modes.keys() {
4270 if !route_modes.iter().any(|mode| mode.src_side_id == *src) {
4271 route_modes.push(RouteModeStats {
4272 src_side_id: *src,
4273 selection_mode: st.source_route_modes.get(src).copied(),
4274 cursor: 0,
4275 });
4276 }
4277 }
4278 route_modes.sort_unstable_by_key(|mode| mode.src_side_id.unwrap_or(usize::MAX));
4279
4280 let mut route_overrides: Vec<RouteOverrideStats> = st
4281 .route_overrides
4282 .iter()
4283 .map(|((src, dst), enabled)| RouteOverrideStats {
4284 src_side_id: *src,
4285 dst_side_id: *dst,
4286 enabled: *enabled,
4287 })
4288 .collect();
4289 route_overrides.sort_unstable_by_key(|item| {
4290 (item.src_side_id.unwrap_or(usize::MAX), item.dst_side_id)
4291 });
4292
4293 let mut typed_route_overrides: Vec<TypedRouteOverrideStats> = st
4294 .typed_route_overrides
4295 .iter()
4296 .map(|((src, ty, dst), enabled)| TypedRouteOverrideStats {
4297 src_side_id: *src,
4298 data_type: crate::DataType(*ty),
4299 dst_side_id: *dst,
4300 enabled: *enabled,
4301 })
4302 .collect();
4303 typed_route_overrides.sort_unstable_by_key(|item| {
4304 (
4305 item.src_side_id.unwrap_or(usize::MAX),
4306 item.data_type.as_u32(),
4307 item.dst_side_id,
4308 )
4309 });
4310
4311 let mut route_weights: Vec<RouteWeightStats> = st
4312 .route_weights
4313 .iter()
4314 .map(|((src, dst), weight)| RouteWeightStats {
4315 src_side_id: *src,
4316 dst_side_id: *dst,
4317 weight: *weight,
4318 })
4319 .collect();
4320 route_weights.sort_unstable_by_key(|item| {
4321 (item.src_side_id.unwrap_or(usize::MAX), item.dst_side_id)
4322 });
4323
4324 let mut route_priorities: Vec<RoutePriorityStats> = st
4325 .route_priorities
4326 .iter()
4327 .map(|((src, dst), priority)| RoutePriorityStats {
4328 src_side_id: *src,
4329 dst_side_id: *dst,
4330 priority: *priority,
4331 })
4332 .collect();
4333 route_priorities.sort_unstable_by_key(|item| {
4334 (item.src_side_id.unwrap_or(usize::MAX), item.dst_side_id)
4335 });
4336
4337 #[cfg(feature = "discovery")]
4338 let discovery = DiscoveryRuntimeStats {
4339 route_count: st.discovery_routes.len(),
4340 announcer_count: st
4341 .discovery_routes
4342 .values()
4343 .map(|route| route.announcers.len())
4344 .sum(),
4345 current_announce_interval_ms: Some(st.discovery_cadence.current_interval_ms),
4346 next_announce_ms: Some(st.discovery_cadence.next_announce_ms),
4347 };
4348 #[cfg(not(feature = "discovery"))]
4349 let discovery = DiscoveryRuntimeStats {
4350 route_count: 0,
4351 announcer_count: 0,
4352 current_announce_interval_ms: None,
4353 next_announce_ms: None,
4354 };
4355
4356 RuntimeStatsSnapshot {
4357 sides,
4358 route_modes,
4359 route_overrides,
4360 typed_route_overrides,
4361 route_weights,
4362 route_priorities,
4363 queues: QueueRuntimeStats {
4364 rx_len: st.rx_queue.len(),
4365 rx_bytes: st.rx_queue.bytes_used(),
4366 tx_len: st.tx_queue.len(),
4367 tx_bytes: st.tx_queue.bytes_used(),
4368 replay_len: st.replay_queue.len(),
4369 replay_bytes: st.replay_queue.bytes_used(),
4370 recent_rx_len: st.recent_rx.len(),
4371 recent_rx_bytes: st.recent_rx.bytes_used(),
4372 reliable_rx_buffered_len: st.reliable_rx_buffer_len(),
4373 reliable_rx_buffered_bytes: st.reliable_rx_buffered_bytes(),
4374 shared_queue_bytes_used: st.shared_queue_bytes_used(),
4375 },
4376 reliable: ReliableRuntimeStats {
4377 reliable_return_route_count: st.reliable_return_routes.len(),
4378 end_to_end_pending_count: 0,
4379 end_to_end_pending_destination_count: 0,
4380 end_to_end_acked_cache_count: st.end_to_end_acked_destinations.len(),
4381 },
4382 discovery,
4383 total_handler_failures: st.total_handler_failures,
4384 total_handler_retries: st.total_handler_retries,
4385 }
4386 }
4387
4388 pub fn export_memory_layout_json(&self) -> String {
4390 let st = self.state.lock();
4391 #[cfg(feature = "discovery")]
4392 let discovery_bytes = st.discovery_bytes_used();
4393 #[cfg(not(feature = "discovery"))]
4394 let discovery_bytes = 0usize;
4395 let schema_bytes = crate::config::schema_bytes_used();
4396 let memory = st.memory;
4397 let mut out = String::new();
4398 let _ = core::fmt::Write::write_fmt(
4399 &mut out,
4400 format_args!(
4401 "{{\"kind\":\"relay\",\
4402 \"shared_queue_bytes_used\":{},\"shared_queue_bytes_allocated\":{},\
4403 \"rx_queue_bytes_used\":{},\"rx_queue_bytes_allocated\":{},\"rx_queue_len\":{},\
4404 \"tx_queue_bytes_used\":{},\"tx_queue_bytes_allocated\":{},\"tx_queue_len\":{},\
4405 \"replay_queue_bytes_used\":{},\"replay_queue_bytes_allocated\":{},\"replay_queue_len\":{},\
4406 \"recent_rx_bytes_used\":{},\"recent_rx_bytes_allocated\":{},\"recent_rx_len\":{},\
4407 \"reliable_rx_buffer_bytes_used\":{},\"reliable_rx_buffer_bytes_allocated\":{},\"reliable_rx_buffer_len\":{},\
4408 \"discovery_bytes_used\":{},\"discovery_bytes_allocated\":{},\
4409 \"schema_bytes_used\":{},\"schema_bytes_allocated\":{}}}",
4410 st.shared_queue_bytes_used(),
4411 memory.max_queue_budget,
4412 st.rx_queue.bytes_used(),
4413 st.rx_queue.max_bytes(),
4414 st.rx_queue.len(),
4415 st.tx_queue.bytes_used(),
4416 st.tx_queue.max_bytes(),
4417 st.tx_queue.len(),
4418 st.replay_queue.bytes_used(),
4419 st.replay_queue.max_bytes(),
4420 st.replay_queue.len(),
4421 st.recent_rx.bytes_used(),
4422 st.recent_rx.max_bytes(),
4423 st.recent_rx.len(),
4424 st.reliable_rx_buffered_bytes(),
4425 memory.max_queue_budget,
4426 st.reliable_rx_buffer_len(),
4427 discovery_bytes,
4428 memory.max_queue_budget,
4429 schema_bytes,
4430 memory.max_queue_budget,
4431 ),
4432 );
4433 out
4434 }
4435
4436 #[cfg(test)]
4437 pub(crate) fn debug_end_to_end_acked_destination_count(&self, packet_id: u64) -> Option<usize> {
4438 let st = self.state.lock();
4439 st.end_to_end_acked_destinations
4440 .get(&packet_id)
4441 .map(BTreeSet::len)
4442 }
4443
4444 #[cfg(test)]
4445 pub(crate) fn debug_end_to_end_acked_packet_count(&self) -> usize {
4446 let st = self.state.lock();
4447 st.end_to_end_acked_destinations.len()
4448 }
4449
4450 #[cfg(test)]
4451 pub(crate) fn debug_reliable_return_route_count(&self) -> usize {
4452 let st = self.state.lock();
4453 st.reliable_return_routes.len()
4454 }
4455
4456 #[cfg(test)]
4457 pub(crate) fn debug_side_storage(&self) -> (usize, usize) {
4458 let st = self.state.lock();
4459 (st.sides.len(), st.sides.capacity())
4460 }
4461
4462 pub fn rx_packed_from_side(&self, src: RelaySideId, bytes: &[u8]) -> TelemetryResult<()> {
4467 self.ensure_side_ingress_enabled(src)?;
4468 let Some(bytes) = self.decode_side_transport_frame(src, bytes)? else {
4469 return Ok(());
4470 };
4471 let mut st = self.state.lock();
4472
4473 let data = RelayItem::Packed(bytes);
4474 let priority = Self::relay_item_priority(&data)?;
4475 st.push_rx(RelayRxItem {
4476 src,
4477 data,
4478 priority,
4479 })
4480 }
4481
4482 pub fn rx_from_side(&self, src: RelaySideId, packet: Packet) -> TelemetryResult<()> {
4486 self.ensure_side_ingress_enabled(src)?;
4487 let mut st = self.state.lock();
4488
4489 let data = RelayItem::Packet(Arc::new(packet));
4490 let priority = Self::relay_item_priority(&data)?;
4491 st.push_rx(RelayRxItem {
4492 src,
4493 data,
4494 priority,
4495 })
4496 }
4497
4498 pub fn clear_queues(&self) {
4500 let mut st = self.state.lock();
4501 st.rx_queue.clear();
4502 st.tx_queue.clear();
4503 st.tx_priority_burst = 0;
4504 }
4505
4506 pub fn clear_rx_queue(&self) {
4508 let mut st = self.state.lock();
4509 st.rx_queue.clear();
4510 }
4511
4512 pub fn clear_tx_queue(&self) {
4514 let mut st = self.state.lock();
4515 st.tx_queue.clear();
4516 st.tx_priority_burst = 0;
4517 st.replay_queue.clear();
4518 }
4519
4520 fn process_rx_queue_item(&self, item: RelayRxItem) -> TelemetryResult<()> {
4524 self.ensure_side_ingress_enabled(item.src)?;
4525 match &item.data {
4526 RelayItem::Packet(pkt) => {
4527 let bytes = wire_format::pack_packet(pkt).len();
4528 self.note_side_rx(item.src, pkt.data_type(), bytes);
4529 }
4530 RelayItem::Packed(bytes) => {
4531 if let Ok(env) = wire_format::peek_envelope(bytes.as_ref()) {
4532 self.note_side_rx(item.src, env.ty, bytes.len());
4533 }
4534 }
4535 }
4536 match &item.data {
4537 RelayItem::Packet(pkt) => {
4538 if is_reliable_type(pkt.data_type()) && !is_internal_control_type(pkt.data_type()) {
4539 self.note_reliable_return_route(item.src, pkt.packet_id());
4540 }
4541 }
4542 RelayItem::Packed(bytes) => {
4543 if let Ok(env) = wire_format::peek_envelope(bytes.as_ref())
4544 && is_reliable_type(env.ty)
4545 && !is_internal_control_type(env.ty)
4546 && let Ok(packet_id) = wire_format::packet_id_from_wire(bytes.as_ref())
4547 {
4548 self.note_reliable_return_route(item.src, packet_id);
4549 }
4550 }
4551 }
4552 let mut released_buffered: Vec<Arc<[u8]>> = Vec::new();
4553 if let RelayItem::Packed(bytes) = &item.data {
4554 let (_opts, handler_is_packed, hop_reliable_enabled) = {
4555 let st = self.state.lock();
4556 let side_ref = Self::side_ref(&st, item.src)?;
4557 let opts = side_ref.opts;
4558 (
4559 opts,
4560 matches!(side_ref.tx_handler, RelayTxHandlerFn::Packed(_)),
4561 opts.reliable_enabled
4562 && !self.side_has_multiple_announcers_locked(
4563 &st,
4564 item.src,
4565 self.clock.now_ms(),
4566 ),
4567 )
4568 };
4569
4570 let frame = match wire_format::peek_frame_info(bytes.as_ref()) {
4571 Ok(frame) => frame,
4572 Err(e) => {
4573 if matches!(e, TelemetryError::Unpack(msg) if msg == "crc32 mismatch")
4574 && hop_reliable_enabled
4575 && handler_is_packed
4576 && let Ok(frame) = wire_format::peek_frame_info_unchecked(bytes.as_ref())
4577 {
4578 if is_reliable_type(frame.envelope.ty)
4579 && let Some(hdr) = frame.reliable
4580 {
4581 let unordered = (hdr.flags & wire_format::RELIABLE_FLAG_UNORDERED) != 0;
4582 let unsequenced =
4583 (hdr.flags & wire_format::RELIABLE_FLAG_UNSEQUENCED) != 0;
4584
4585 if !unsequenced {
4586 let requested = if unordered {
4587 hdr.seq
4588 } else {
4589 let mut st = self.state.lock();
4590 let rx_state = self.reliable_rx_state_mut(
4591 &mut st,
4592 item.src,
4593 frame.envelope.ty,
4594 );
4595 rx_state.expected_seq.min(hdr.seq)
4596 };
4597 self.queue_reliable_packet_request(
4598 item.src,
4599 frame.envelope.ty,
4600 requested,
4601 )?;
4602 }
4603 }
4604 return Ok(());
4605 }
4606 return Err(e);
4607 }
4608 };
4609
4610 if hop_reliable_enabled
4611 && handler_is_packed
4612 && is_reliable_type(frame.envelope.ty)
4613 && let Some(hdr) = frame.reliable
4614 {
4615 if frame.ack_only() {
4616 self.handle_reliable_ack(item.src, frame.envelope.ty, hdr.ack);
4617 return Ok(());
4618 }
4619 let unordered = (hdr.flags & wire_format::RELIABLE_FLAG_UNORDERED) != 0;
4620 let unsequenced = (hdr.flags & wire_format::RELIABLE_FLAG_UNSEQUENCED) != 0;
4621
4622 if !unsequenced {
4623 if unordered {
4624 self.queue_reliable_ack(item.src, frame.envelope.ty, hdr.seq)?;
4625 } else {
4626 let mut release: Vec<Arc<[u8]>> = Vec::new();
4627 let mut last_delivered = None;
4628 let mut ack_old = None;
4629 let mut request_missing = None;
4630 let mut partial_ack = None;
4631 {
4632 let mut st = self.state.lock();
4633 let rx_state =
4634 self.reliable_rx_state_mut(&mut st, item.src, frame.envelope.ty);
4635 let expected_seq = rx_state.expected_seq;
4636 if hdr.seq < expected_seq {
4637 ack_old = Some(expected_seq.saturating_sub(1));
4638 } else if hdr.seq > expected_seq {
4639 request_missing = Some(expected_seq);
4640 partial_ack = Some(hdr.seq);
4641 st.buffer_reliable_rx(
4642 item.src,
4643 frame.envelope.ty,
4644 hdr.seq,
4645 bytes.clone(),
4646 )?;
4647 } else {
4648 release.push(bytes.clone());
4649 last_delivered = Some(hdr.seq);
4650 let mut next_expected = hdr.seq.wrapping_add(1);
4651 while let Some(buf) = rx_state.buffered.remove(&next_expected) {
4652 release.push(buf);
4653 last_delivered = Some(next_expected);
4654 let next = next_expected.wrapping_add(1);
4655 next_expected = if next == 0 { 1 } else { next };
4656 }
4657 rx_state.expected_seq = next_expected;
4658 }
4659 }
4660
4661 if let Some(ack_seq) = ack_old {
4662 self.queue_reliable_ack(item.src, frame.envelope.ty, ack_seq)?;
4663 return Ok(());
4664 }
4665 if let Some(request_seq) = request_missing {
4666 if let Some(partial_seq) = partial_ack {
4667 self.queue_reliable_partial_ack(
4668 item.src,
4669 frame.envelope.ty,
4670 partial_seq,
4671 )?;
4672 }
4673 self.queue_reliable_packet_request(
4674 item.src,
4675 frame.envelope.ty,
4676 request_seq,
4677 )?;
4678 return Ok(());
4679 }
4680 if let Some(ack_seq) = last_delivered {
4681 self.queue_reliable_ack(item.src, frame.envelope.ty, ack_seq)?;
4682 }
4683 released_buffered.extend(release.into_iter().skip(1));
4684 }
4685 }
4686 }
4687 }
4688
4689 if self.is_duplicate_pkt(&item)? && !self.should_forward_duplicate_reliable_item(&item)? {
4690 return Ok(());
4692 }
4693
4694 self.dispatch_relay_rx_item(&item)?;
4695
4696 for release_bytes in released_buffered {
4697 let release_item = RelayRxItem {
4698 src: item.src,
4699 priority: Self::relay_item_priority(&RelayItem::Packed(release_bytes.clone()))?,
4700 data: RelayItem::Packed(release_bytes),
4701 };
4702 if self.is_duplicate_pkt(&release_item)?
4703 && !self.should_forward_duplicate_reliable_item(&release_item)?
4704 {
4705 continue;
4706 }
4707 self.dispatch_relay_rx_item(&release_item)?;
4708 }
4709 Ok(())
4710 }
4711
4712 fn dispatch_relay_rx_item(&self, item: &RelayRxItem) -> TelemetryResult<()> {
4713 match &item.data {
4714 RelayItem::Packet(pkt) => {
4715 if matches!(
4716 pkt.data_type(),
4717 crate::DataType::ReliableAck
4718 | crate::DataType::ReliablePartialAck
4719 | crate::DataType::ReliablePacketRequest
4720 ) {
4721 if pkt.data_type() == crate::DataType::ReliableAck
4722 && Self::is_end_to_end_ack_sender(pkt.sender())
4723 && Self::decode_end_to_end_reliable_ack(pkt.payload()).is_ok()
4724 {
4725 if let Ok(packet_id) = Self::decode_end_to_end_reliable_ack(pkt.payload())
4726 && let Some(sender_hash) =
4727 Self::decode_end_to_end_ack_sender_hash(pkt.sender())
4728 {
4729 let mut st = self.state.lock();
4730 Self::note_end_to_end_acked_destination_locked(
4731 &mut st,
4732 packet_id,
4733 sender_hash,
4734 );
4735 }
4736 } else {
4737 let vals = pkt.data_as_u32()?;
4738 if vals.len() != 2 {
4739 return Err(TelemetryError::Unpack("bad reliable control payload"));
4740 }
4741 let ty = crate::DataType::try_from_u32(vals[0])
4742 .ok_or(TelemetryError::InvalidType)?;
4743 let seq = vals[1];
4744 match pkt.data_type() {
4745 crate::DataType::ReliableAck => {
4746 self.handle_reliable_ack(item.src, ty, seq)
4747 }
4748 crate::DataType::ReliablePartialAck => {
4749 self.handle_reliable_partial_ack(item.src, ty, seq)
4750 }
4751 crate::DataType::ReliablePacketRequest => {
4752 self.queue_reliable_retransmit(item.src, ty, seq)?
4753 }
4754 _ => {}
4755 }
4756 return Ok(());
4757 }
4758 }
4759 }
4760 RelayItem::Packed(bytes) => {
4761 let env = wire_format::peek_envelope(bytes.as_ref())?;
4762 if matches!(
4763 env.ty,
4764 crate::DataType::ReliableAck
4765 | crate::DataType::ReliablePacketRequest
4766 | crate::DataType::ReliablePartialAck
4767 ) {
4768 let pkt = wire_format::unpack_packet(bytes.as_ref())?;
4769 return self.dispatch_relay_rx_item(&RelayRxItem {
4770 src: item.src,
4771 data: RelayItem::Packet(Arc::new(pkt)),
4772 priority: item.priority,
4773 });
4774 }
4775 }
4776 }
4777
4778 let src = item.src;
4779 let data = item.data.clone();
4780 self.learn_discovery_item(src, &data)?;
4781
4782 let plan = self.remote_side_plan(&data, src)?;
4783 let mut st = self.state.lock();
4784 let RemoteSidePlan::Target(sides) = plan;
4785 for dst in sides {
4786 let priority = Self::relay_item_priority(&data)?;
4787 st.push_tx(RelayTxItem {
4788 src: Some(src),
4789 dst,
4790 data: data.clone(),
4791 priority,
4792 })?;
4793 }
4794 Ok(())
4795 }
4796
4797 #[inline]
4798 fn crc32_bytes(data: &[u8]) -> u32 {
4799 let mut hasher = Crc32Hasher::new();
4800 hasher.update(data);
4801 hasher.finalize()
4802 }
4803
4804 fn wrap_side_transport_frame(kind: u8, body: &[u8]) -> Arc<[u8]> {
4805 let mut out = Vec::with_capacity(
4806 SIDE_TRANSPORT_MAGIC.len() + 1 + body.len() + wire_format::CRC32_BYTES,
4807 );
4808 out.extend_from_slice(SIDE_TRANSPORT_MAGIC);
4809 out.push(kind);
4810 out.extend_from_slice(body);
4811 let crc = Self::crc32_bytes(&out);
4812 out.extend_from_slice(&crc.to_le_bytes());
4813 Arc::from(out)
4814 }
4815
4816 fn parse_side_transport_wrapper(bytes: &[u8]) -> TelemetryResult<Option<(u8, &[u8])>> {
4817 if bytes.len() < SIDE_TRANSPORT_MAGIC.len() + 1 + wire_format::CRC32_BYTES {
4818 return Ok(None);
4819 }
4820 if &bytes[..SIDE_TRANSPORT_MAGIC.len()] != SIDE_TRANSPORT_MAGIC {
4821 return Ok(None);
4822 }
4823 let data_len = bytes.len() - wire_format::CRC32_BYTES;
4824 let expected = u32::from_le_bytes([
4825 bytes[data_len],
4826 bytes[data_len + 1],
4827 bytes[data_len + 2],
4828 bytes[data_len + 3],
4829 ]);
4830 let data = &bytes[..data_len];
4831 if Self::crc32_bytes(data) != expected {
4832 return Err(TelemetryError::Unpack("side transport crc32 mismatch"));
4833 }
4834 let kind = data[SIDE_TRANSPORT_MAGIC.len()];
4835 Ok(Some((kind, &data[SIDE_TRANSPORT_MAGIC.len() + 1..])))
4836 }
4837
4838 fn read_uleb128_local(buf: &[u8], off: &mut usize) -> TelemetryResult<u64> {
4839 let mut result = 0u64;
4840 let mut shift = 0u32;
4841 for _ in 0..10 {
4842 let byte = *buf.get(*off).ok_or(TelemetryError::Unpack("short read"))?;
4843 *off += 1;
4844 result |= u64::from(byte & 0x7F) << shift;
4845 if (byte & 0x80) == 0 {
4846 return Ok(result);
4847 }
4848 shift += 7;
4849 }
4850 Err(TelemetryError::Unpack("uleb128 too long"))
4851 }
4852
4853 fn write_uleb128_local(mut value: u64, out: &mut Vec<u8>) {
4854 loop {
4855 let mut byte = (value & 0x7F) as u8;
4856 value >>= 7;
4857 if value != 0 {
4858 byte |= 0x80;
4859 }
4860 out.push(byte);
4861 if value == 0 {
4862 break;
4863 }
4864 }
4865 }
4866
4867 fn uleb128_len_local(mut value: u64) -> usize {
4868 let mut len = 1;
4869 while value >= 0x80 {
4870 value >>= 7;
4871 len += 1;
4872 }
4873 len
4874 }
4875
4876 fn extract_side_header_template(bytes: &[u8]) -> TelemetryResult<SideTemplateExtract<'_>> {
4877 if bytes.len() < wire_format::CRC32_BYTES + 4 {
4878 return Err(TelemetryError::Unpack("short buffer"));
4879 }
4880 let data_len = bytes.len() - wire_format::CRC32_BYTES;
4881 let data = &bytes[..data_len];
4882 let mut off = 0usize;
4883 let flags = *data
4884 .get(off)
4885 .ok_or(TelemetryError::Unpack("short prelude"))?;
4886 off += 1;
4887 off += 1; let ty_u64 = Self::read_uleb128_local(data, &mut off)?;
4889 let ty_u32 = u32::try_from(ty_u64).map_err(|_| TelemetryError::Unpack("bad data type"))?;
4890 if ty_u32 > crate::MAX_VALUE_DATA_TYPE {
4891 return Err(TelemetryError::Unpack("bad data type"));
4892 }
4893 let ty = crate::DataType(ty_u32);
4894 let data_size_off = off;
4895 let data_size = Self::read_uleb128_local(data, &mut off)?;
4896 let timestamp = Self::read_uleb128_local(data, &mut off)?;
4897 let nonce = if (flags & SIDE_TRANSPORT_FLAG_PACKET_NONCE) != 0 {
4898 u16::try_from(Self::read_uleb128_local(data, &mut off)?)
4899 .map_err(|_| TelemetryError::Unpack("packet nonce too large"))?
4900 } else {
4901 0
4902 };
4903 let between_start = off;
4904 let _source_address = u32::try_from(Self::read_uleb128_local(data, &mut off)?)
4905 .map_err(|_| TelemetryError::Unpack("source address too large"))?;
4906 let endpoint_bitmap_bytes = if (flags & SIDE_TRANSPORT_FLAG_ENDPOINT_BITMAP_PRESENT) != 0 {
4907 SIDE_TRANSPORT_EP_BITMAP_BYTES
4908 } else {
4909 0
4910 };
4911 if data.len() < off + endpoint_bitmap_bytes {
4912 return Err(TelemetryError::Unpack("short buffer"));
4913 }
4914 off += endpoint_bitmap_bytes;
4915 if (flags & SIDE_TRANSPORT_FLAG_WIRE_CONTRACT) != 0 {
4916 let contract_len = usize::try_from(Self::read_uleb128_local(data, &mut off)?)
4917 .map_err(|_| TelemetryError::Unpack("wire contract length"))?;
4918 if data.len() < off + contract_len {
4919 return Err(TelemetryError::Unpack("short buffer"));
4920 }
4921 off += contract_len;
4922 }
4923 let reliable_span = wire_format::reliable_header_span(bytes)?;
4924 let (reliable_flags, reliable_seq_ack, reliable_compact, payload_off) =
4925 if let Some((rel_off, rel_len, hdr)) = reliable_span {
4926 if data.len() < rel_off + rel_len {
4927 return Err(TelemetryError::Unpack("short buffer"));
4928 }
4929 (
4930 Some(hdr.flags),
4931 Some((hdr.seq, hdr.ack)),
4932 (flags & SIDE_TRANSPORT_FLAG_COMPACT_RELIABLE_HEADER) != 0,
4933 rel_off + rel_len,
4934 )
4935 } else {
4936 (None, None, false, off)
4937 };
4938 if payload_off > data.len() {
4939 return Err(TelemetryError::Unpack("short buffer"));
4940 }
4941 let payload = &data[payload_off..];
4942 let prefix = Arc::<[u8]>::from(&data[1..data_size_off]);
4943 let between_end = reliable_span
4944 .map(|(rel_off, _, _)| rel_off)
4945 .unwrap_or(payload_off);
4946 let between = Arc::<[u8]>::from(&data[between_start..between_end]);
4947 let base_flags =
4948 flags & !(SIDE_TRANSPORT_FLAG_PAYLOAD_COMPRESSED | SIDE_TRANSPORT_FLAG_PACKET_NONCE);
4949 let mut hash = 0xD1B5_4A32_9C7E_01F3u64;
4950 hash = hash_bytes_u64(hash, &[base_flags]);
4951 hash = hash_bytes_u64(hash, &prefix);
4952 hash = hash_bytes_u64(hash, &between);
4953 if let Some(rel_flags) = reliable_flags {
4954 hash = hash_bytes_u64(hash, &[rel_flags]);
4955 }
4956 let template = SideHeaderTemplate {
4957 hash,
4958 base_flags,
4959 prefix,
4960 between,
4961 reliable_flags,
4962 reliable_compact,
4963 };
4964 Ok((
4965 template,
4966 ty,
4967 flags,
4968 data_size,
4969 timestamp,
4970 nonce,
4971 reliable_seq_ack,
4972 payload,
4973 ))
4974 }
4975
4976 fn reconstruct_side_compact_frame(
4977 template: &SideHeaderTemplate,
4978 body: &[u8],
4979 timestamp_mode: SideCompactTimestampMode,
4980 timestamp_base: Option<u64>,
4981 ) -> TelemetryResult<(Arc<[u8]>, u64)> {
4982 if body.is_empty() {
4983 return Err(TelemetryError::Unpack("short side compact frame"));
4984 }
4985 let mut off = 0usize;
4986 let flags = body[off];
4987 off += 1;
4988 if (flags & !(SIDE_TRANSPORT_FLAG_PAYLOAD_COMPRESSED | SIDE_TRANSPORT_FLAG_PACKET_NONCE))
4989 != template.base_flags
4990 {
4991 return Err(TelemetryError::Unpack("side compact flags mismatch"));
4992 }
4993 let data_size = Self::read_uleb128_local(body, &mut off)?;
4994 let timestamp = match timestamp_mode {
4995 SideCompactTimestampMode::Absolute => Self::read_uleb128_local(body, &mut off)?,
4996 SideCompactTimestampMode::Delta => {
4997 let timestamp_field = Self::read_uleb128_local(body, &mut off)?;
4998 let base = timestamp_base.ok_or(TelemetryError::Unpack(
4999 "missing side compact timestamp context",
5000 ))?;
5001 base.checked_add(timestamp_field)
5002 .ok_or(TelemetryError::Unpack(
5003 "side compact timestamp delta overflow",
5004 ))?
5005 }
5006 SideCompactTimestampMode::Omitted => timestamp_base.ok_or(TelemetryError::Unpack(
5007 "missing side compact timestamp context",
5008 ))?,
5009 };
5010 let nonce = if (flags & SIDE_TRANSPORT_FLAG_PACKET_NONCE) != 0 {
5011 Some(Self::read_uleb128_local(body, &mut off)?)
5012 } else {
5013 None
5014 };
5015 let reliable_seq_ack = if template.reliable_flags.is_some() {
5016 let seq = u32::try_from(Self::read_uleb128_local(body, &mut off)?)
5017 .map_err(|_| TelemetryError::Unpack("side compact reliable seq too large"))?;
5018 let ack = u32::try_from(Self::read_uleb128_local(body, &mut off)?)
5019 .map_err(|_| TelemetryError::Unpack("side compact reliable ack too large"))?;
5020 Some((seq, ack))
5021 } else {
5022 None
5023 };
5024 let payload = &body[off..];
5025 let mut raw = Vec::with_capacity(
5026 1 + template.prefix.len() + template.between.len() + payload.len() + 32,
5027 );
5028 raw.push(flags);
5029 raw.extend_from_slice(&template.prefix);
5030 Self::write_uleb128_local(data_size, &mut raw);
5031 Self::write_uleb128_local(timestamp, &mut raw);
5032 if let Some(nonce) = nonce {
5033 Self::write_uleb128_local(nonce, &mut raw);
5034 }
5035 raw.extend_from_slice(&template.between);
5036 if let Some(rel_flags) = template.reliable_flags {
5037 let (seq, ack) =
5038 reliable_seq_ack.ok_or(TelemetryError::Unpack("missing side compact reliable"))?;
5039 wire_format::write_reliable_header_encoded(
5040 wire_format::ReliableHeader {
5041 flags: rel_flags,
5042 seq,
5043 ack,
5044 },
5045 template.reliable_compact,
5046 &mut raw,
5047 );
5048 }
5049 raw.extend_from_slice(payload);
5050 let crc = Self::crc32_bytes(&raw);
5051 raw.extend_from_slice(&crc.to_le_bytes());
5052 Ok((Arc::from(raw), timestamp))
5053 }
5054
5055 fn split_side_transport_frame(
5056 &self,
5057 side: RelaySideId,
5058 frame: Arc<[u8]>,
5059 max_frame_bytes: usize,
5060 ) -> TelemetryResult<Vec<Arc<[u8]>>> {
5061 if max_frame_bytes <= SIDE_TRANSPORT_CHUNK_OVERHEAD {
5062 return Err(TelemetryError::BadArg);
5063 }
5064 let payload_budget = max_frame_bytes - SIDE_TRANSPORT_CHUNK_OVERHEAD;
5065 let mut st = self.state.lock();
5066 let side_state = st
5067 .side_transport
5068 .get_mut(&side)
5069 .ok_or(TelemetryError::BadArg)?;
5070 let transfer_id = side_state.next_chunk_id.wrapping_add(1).max(1);
5071 side_state.next_chunk_id = transfer_id;
5072 drop(st);
5073
5074 let total = frame.len().div_ceil(payload_budget);
5075 let total_u16 =
5076 u16::try_from(total).map_err(|_| TelemetryError::PacketTooLarge("too many chunks"))?;
5077 let mut frames = Vec::with_capacity(total);
5078 for (idx, chunk) in frame.chunks(payload_budget).enumerate() {
5079 let mut body = Vec::with_capacity(8 + chunk.len());
5080 body.extend_from_slice(&transfer_id.to_le_bytes());
5081 body.extend_from_slice(&(idx as u16).to_le_bytes());
5082 body.extend_from_slice(&total_u16.to_le_bytes());
5083 body.extend_from_slice(chunk);
5084 frames.push(Self::wrap_side_transport_frame(
5085 SIDE_TRANSPORT_KIND_CHUNK,
5086 &body,
5087 ));
5088 }
5089 Ok(frames)
5090 }
5091
5092 fn encode_side_transport_frames(
5093 &self,
5094 side: RelaySideId,
5095 opts: RelaySideOptions,
5096 raw: Arc<[u8]>,
5097 ) -> TelemetryResult<Vec<Arc<[u8]>>> {
5098 if !opts.header_template_enabled && opts.max_frame_bytes == 0 {
5099 return Ok(vec![raw]);
5100 }
5101 let raw_len = raw.len();
5102 let mut compact_payload_len = None;
5103 let mut used_compact = false;
5104 let mut used_timestamp_delta = false;
5105 let mut omitted_timestamp = false;
5106 let (template, ty, flags, data_size, timestamp, nonce, reliable_seq_ack, payload) =
5107 Self::extract_side_header_template(raw.as_ref())?;
5108 let (template_id, use_compact, previous_timestamp) = {
5109 let mut st = self.state.lock();
5110 let side_state = st
5111 .side_transport
5112 .get_mut(&side)
5113 .ok_or(TelemetryError::BadArg)?;
5114 if let Some(id) = side_state.tx_template_ids.get(&template.hash).copied() {
5115 let previous = side_state.tx_last_timestamps.get(&id).copied();
5116 const FULL_REFRESH_AFTER_COMPACT_FRAMES: u8 = 8;
5117 let compact_uses = side_state.tx_compact_uses.entry(id).or_default();
5118 let use_compact = *compact_uses < FULL_REFRESH_AFTER_COMPACT_FRAMES;
5119 if use_compact {
5120 *compact_uses = compact_uses.saturating_add(1);
5121 } else {
5122 *compact_uses = 0;
5123 }
5124 (id, use_compact, previous)
5125 } else {
5126 let next = side_state.next_template_id.wrapping_add(1).max(1);
5127 side_state.next_template_id = next;
5128 let evicted = side_state.insert_tx_template(
5129 template,
5130 next,
5131 opts.max_side_transport_templates,
5132 );
5133 if evicted {
5134 st.side_runtime_stats
5135 .entry(side)
5136 .or_default()
5137 .note_side_transport_template_eviction();
5138 }
5139 if let Some(side_state) = st.side_transport.get_mut(&side) {
5140 side_state.tx_last_timestamps.insert(next, timestamp);
5141 }
5142 (next, false, None)
5143 }
5144 };
5145 let wrapped = if use_compact {
5146 used_compact = true;
5147 compact_payload_len = Some(payload.len());
5148 let timestamp_field = if let Some(previous) = previous_timestamp {
5149 let delta = timestamp.saturating_sub(previous);
5150 let omit_timestamp = opts.omit_unchanged_compact_timestamps
5151 || opts.compact_timestamp_omission_types.contains(ty);
5152 if omit_timestamp && timestamp == previous {
5153 omitted_timestamp = true;
5154 None
5155 } else if timestamp >= previous
5156 && Self::uleb128_len_local(delta) < Self::uleb128_len_local(timestamp)
5157 {
5158 used_timestamp_delta = true;
5159 Some(delta)
5160 } else {
5161 Some(timestamp)
5162 }
5163 } else {
5164 Some(timestamp)
5165 };
5166 let mut body = Vec::with_capacity(payload.len() + 32);
5167 body.push(flags);
5168 Self::write_uleb128_local(u64::from(template_id), &mut body);
5169 Self::write_uleb128_local(data_size, &mut body);
5170 if let Some(timestamp_field) = timestamp_field {
5171 Self::write_uleb128_local(timestamp_field, &mut body);
5172 }
5173 if (flags & SIDE_TRANSPORT_FLAG_PACKET_NONCE) != 0 {
5174 Self::write_uleb128_local(u64::from(nonce), &mut body);
5175 }
5176 if let Some((seq, ack)) = reliable_seq_ack {
5177 Self::write_uleb128_local(u64::from(seq), &mut body);
5178 Self::write_uleb128_local(u64::from(ack), &mut body);
5179 }
5180 body.extend_from_slice(payload);
5181 {
5182 let mut st = self.state.lock();
5183 if let Some(side_state) = st.side_transport.get_mut(&side) {
5184 side_state.tx_last_timestamps.insert(template_id, timestamp);
5185 }
5186 }
5187 let kind = if omitted_timestamp {
5188 SIDE_TRANSPORT_KIND_COMPACT_SAME_TIMESTAMP
5189 } else if used_timestamp_delta {
5190 SIDE_TRANSPORT_KIND_COMPACT_DELTA
5191 } else {
5192 SIDE_TRANSPORT_KIND_COMPACT
5193 };
5194 Self::wrap_side_transport_frame(kind, &body)
5195 } else {
5196 let mut body = Vec::with_capacity(raw.len() + 4);
5197 Self::write_uleb128_local(u64::from(template_id), &mut body);
5198 body.extend_from_slice(raw.as_ref());
5199 Self::wrap_side_transport_frame(SIDE_TRANSPORT_KIND_FULL, &body)
5200 };
5201 let frames = if opts.max_frame_bytes != 0 && wrapped.len() > opts.max_frame_bytes {
5202 self.split_side_transport_frame(side, wrapped, opts.max_frame_bytes)
5203 } else {
5204 Ok(vec![wrapped])
5205 }?;
5206 let wire_len = frames.iter().map(|frame| frame.len()).sum::<usize>();
5207 let mut st = self.state.lock();
5208 let stats = st.side_runtime_stats.entry(side).or_default();
5209 if used_compact {
5210 let overhead = compact_payload_len
5211 .map(|payload_len| wire_len.saturating_sub(payload_len))
5212 .unwrap_or(wire_len);
5213 stats.note_side_transport_compact(
5214 raw_len,
5215 wire_len,
5216 overhead,
5217 used_timestamp_delta,
5218 omitted_timestamp,
5219 );
5220 if opts.compact_header_target_bytes != 0 && overhead > opts.compact_header_target_bytes
5221 {
5222 stats.note_side_transport_compact_target_miss();
5223 }
5224 } else {
5225 stats.note_side_transport_full(raw_len, wire_len);
5226 }
5227 if frames.len() > 1 {
5228 stats.note_side_transport_chunks(frames.len());
5229 }
5230 Ok(frames)
5231 }
5232
5233 fn decode_side_transport_frame(
5234 &self,
5235 side: RelaySideId,
5236 bytes: &[u8],
5237 ) -> TelemetryResult<Option<Arc<[u8]>>> {
5238 let Some((kind, body)) = Self::parse_side_transport_wrapper(bytes)? else {
5239 return Ok(Some(Arc::from(bytes)));
5240 };
5241 match kind {
5242 SIDE_TRANSPORT_KIND_FULL => {
5243 let mut off = 0usize;
5244 let template_id = u32::try_from(Self::read_uleb128_local(body, &mut off)?)
5245 .map_err(|_| TelemetryError::Unpack("side template id too large"))?;
5246 let raw = Arc::<[u8]>::from(&body[off..]);
5247 if let Ok((template, _, _, _, timestamp, _, _, _)) =
5248 Self::extract_side_header_template(raw.as_ref())
5249 {
5250 let mut st = self.state.lock();
5251 let max_templates = st
5252 .sides
5253 .get(side)
5254 .and_then(|side| side.as_ref())
5255 .map(|side| side.opts.max_side_transport_templates)
5256 .unwrap_or(DEFAULT_SIDE_TRANSPORT_TEMPLATE_LIMIT);
5257 let evicted = st.side_transport.get_mut(&side).is_some_and(|side_state| {
5258 let evicted =
5259 side_state.insert_rx_template(template_id, template, max_templates);
5260 side_state.rx_last_timestamps.insert(template_id, timestamp);
5261 evicted
5262 });
5263 if evicted {
5264 st.side_runtime_stats
5265 .entry(side)
5266 .or_default()
5267 .note_side_transport_template_eviction();
5268 }
5269 }
5270 Ok(Some(raw))
5271 }
5272 SIDE_TRANSPORT_KIND_COMPACT
5273 | SIDE_TRANSPORT_KIND_COMPACT_DELTA
5274 | SIDE_TRANSPORT_KIND_COMPACT_SAME_TIMESTAMP => {
5275 if body.is_empty() {
5276 return Err(TelemetryError::Unpack("short side compact frame"));
5277 }
5278 let mut off = 1usize;
5279 let template_id = u32::try_from(Self::read_uleb128_local(body, &mut off)?)
5280 .map_err(|_| TelemetryError::Unpack("side template id too large"))?;
5281 let mut compact_body = Vec::with_capacity(1 + body.len().saturating_sub(off));
5282 compact_body.push(body[0]);
5283 compact_body.extend_from_slice(&body[off..]);
5284 let (template, timestamp_base) = {
5285 let st = self.state.lock();
5286 let state = st.side_transport.get(&side);
5287 let template = state
5288 .and_then(|state| state.rx_templates_by_id.get(&template_id))
5289 .cloned();
5290 let timestamp_base = if matches!(
5291 kind,
5292 SIDE_TRANSPORT_KIND_COMPACT_DELTA
5293 | SIDE_TRANSPORT_KIND_COMPACT_SAME_TIMESTAMP
5294 ) {
5295 state
5296 .and_then(|state| state.rx_last_timestamps.get(&template_id))
5297 .copied()
5298 } else {
5299 None
5300 };
5301 (template, timestamp_base)
5302 };
5303 let template =
5304 template.ok_or(TelemetryError::Unpack("unknown side compact template"))?;
5305 let timestamp_mode = match kind {
5306 SIDE_TRANSPORT_KIND_COMPACT_DELTA => SideCompactTimestampMode::Delta,
5307 SIDE_TRANSPORT_KIND_COMPACT_SAME_TIMESTAMP => SideCompactTimestampMode::Omitted,
5308 _ => SideCompactTimestampMode::Absolute,
5309 };
5310 let (frame, timestamp) = Self::reconstruct_side_compact_frame(
5311 &template,
5312 &compact_body,
5313 timestamp_mode,
5314 timestamp_base,
5315 )?;
5316 let mut st = self.state.lock();
5317 if let Some(side_state) = st.side_transport.get_mut(&side) {
5318 side_state.rx_last_timestamps.insert(template_id, timestamp);
5319 }
5320 Ok(Some(frame))
5321 }
5322 SIDE_TRANSPORT_KIND_CHUNK => {
5323 if body.len() < 8 {
5324 return Err(TelemetryError::Unpack("short side chunk frame"));
5325 }
5326 let transfer_id = u32::from_le_bytes([body[0], body[1], body[2], body[3]]);
5327 let index = u16::from_le_bytes([body[4], body[5]]);
5328 let total = u16::from_le_bytes([body[6], body[7]]);
5329 let payload = Arc::<[u8]>::from(&body[8..]);
5330 let assembled = {
5331 let mut st = self.state.lock();
5332 let side_state = st
5333 .side_transport
5334 .get_mut(&side)
5335 .ok_or(TelemetryError::BadArg)?;
5336 let entry = side_state.rx_chunks.entry(transfer_id).or_default();
5337 if entry.total == 0 {
5338 entry.total = total;
5339 } else if entry.total != total {
5340 side_state.rx_chunks.remove(&transfer_id);
5341 return Err(TelemetryError::Unpack("side chunk total mismatch"));
5342 }
5343 entry.received.entry(index).or_insert(payload);
5344 if entry.received.len() == usize::from(total) {
5345 let entry = side_state
5346 .rx_chunks
5347 .remove(&transfer_id)
5348 .ok_or(TelemetryError::Unpack("side chunk missing"))?;
5349 let mut out = Vec::new();
5350 for idx in 0..entry.total {
5351 let chunk = entry
5352 .received
5353 .get(&idx)
5354 .ok_or(TelemetryError::Unpack("side chunk gap"))?;
5355 out.extend_from_slice(chunk);
5356 }
5357 Some(Arc::<[u8]>::from(out))
5358 } else {
5359 None
5360 }
5361 };
5362 match assembled {
5363 Some(frame) => self.decode_side_transport_frame(side, frame.as_ref()),
5364 None => Ok(None),
5365 }
5366 }
5367 _ => Err(TelemetryError::Unpack("unknown side transport frame")),
5368 }
5369 }
5370
5371 fn call_tx_handler(
5377 &self,
5378 side: RelaySideId,
5379 handler: &RelayTxHandlerFn,
5380 data: &RelayItem,
5381 ) -> TelemetryResult<()> {
5382 let opts = {
5383 let st = self.state.lock();
5384 Self::side_ref(&st, side)?.opts
5385 };
5386 let Some(_side_tx_guard) = self.try_enter_side_tx() else {
5387 return Err(TelemetryError::Io("side tx busy"));
5388 };
5389 let started_ms = self.clock.now_ms();
5390 let ty = match data {
5391 RelayItem::Packet(pkt) => pkt.data_type(),
5392 RelayItem::Packed(bytes) => wire_format::peek_envelope(bytes.as_ref())?.ty,
5393 };
5394 let result = match (handler, data) {
5395 (RelayTxHandlerFn::Packed(f), RelayItem::Packed(bytes)) => {
5397 let frames = self.encode_side_transport_frames(side, opts, bytes.clone())?;
5398 let mut sent_bytes = 0usize;
5399 for frame in frames {
5400 f(frame.as_ref())?;
5401 sent_bytes = sent_bytes.saturating_add(frame.len());
5402 }
5403 self.record_side_tx_sample(side, sent_bytes, started_ms, self.clock.now_ms());
5404 self.note_side_tx_success(side, ty, sent_bytes, 1);
5405 return Ok(());
5406 }
5407 (RelayTxHandlerFn::Packet(f), RelayItem::Packet(pkt)) => f(pkt),
5408
5409 (RelayTxHandlerFn::Packed(f), RelayItem::Packet(pkt)) => {
5411 let owned = wire_format::pack_packet(pkt);
5412 let frames = self.encode_side_transport_frames(side, opts, owned)?;
5413 let mut sent_bytes = 0usize;
5414 for frame in frames {
5415 f(frame.as_ref())?;
5416 sent_bytes = sent_bytes.saturating_add(frame.len());
5417 }
5418 self.record_side_tx_sample(side, sent_bytes, started_ms, self.clock.now_ms());
5419 self.note_side_tx_success(side, ty, sent_bytes, 1);
5420 return Ok(());
5421 }
5422 (RelayTxHandlerFn::Packet(f), RelayItem::Packed(bytes)) => {
5423 if wire_format::peek_frame_info(bytes.as_ref())
5424 .ok()
5425 .is_some_and(|frame| frame.ack_only())
5426 {
5427 return Ok(());
5428 }
5429 let pkt = wire_format::unpack_packet(bytes.as_ref())?;
5430 f(&pkt)
5431 }
5432 };
5433 if result.is_ok()
5434 && let Ok(bytes) = Self::relay_item_wire_len(data)
5435 {
5436 self.record_side_tx_sample(side, bytes, started_ms, self.clock.now_ms());
5437 self.note_side_tx_success(side, ty, bytes, 1);
5438 } else if result.is_err() {
5439 self.note_side_tx_failure(side, ty, 1);
5440 }
5441 result
5442 }
5443
5444 fn adjust_reliable_for_side(
5445 &self,
5446 opts: RelaySideOptions,
5447 data: RelayItem,
5448 ) -> TelemetryResult<Option<RelayItem>> {
5449 if opts.reliable_enabled {
5450 return Ok(Some(data));
5451 }
5452
5453 match data {
5454 RelayItem::Packed(bytes) => {
5455 let frame = match wire_format::peek_frame_info(bytes.as_ref()) {
5456 Ok(frame) => frame,
5457 Err(_) => return Ok(Some(RelayItem::Packed(bytes))),
5458 };
5459 if is_reliable_type(frame.envelope.ty)
5460 && let Some(hdr) = frame.reliable
5461 {
5462 if (hdr.flags & wire_format::RELIABLE_FLAG_ACK_ONLY) != 0 {
5463 return Ok(None);
5464 }
5465 if (hdr.flags & wire_format::RELIABLE_FLAG_UNSEQUENCED) == 0 {
5466 let Some(rewritten) = wire_format::rewrite_reliable_header_owned(
5467 bytes.as_ref(),
5468 wire_format::RELIABLE_FLAG_UNSEQUENCED,
5469 hdr.seq,
5470 0,
5471 )?
5472 else {
5473 return Ok(Some(RelayItem::Packed(bytes)));
5474 };
5475 return Ok(Some(RelayItem::Packed(rewritten)));
5476 }
5477 }
5478 Ok(Some(RelayItem::Packed(bytes)))
5479 }
5480 RelayItem::Packet(pkt) => {
5481 if matches!(
5482 pkt.data_type(),
5483 crate::DataType::ReliableAck
5484 | crate::DataType::ReliablePartialAck
5485 | crate::DataType::ReliablePacketRequest
5486 ) {
5487 return Ok(None);
5488 }
5489 Ok(Some(RelayItem::Packet(pkt)))
5490 }
5491 }
5492 }
5493
5494 #[inline]
5496 pub fn process_rx_queue(&self) -> TelemetryResult<()> {
5497 self.process_rx_queue_with_timeout(0)
5498 }
5499
5500 #[inline]
5505 pub fn process_tx_queue(&self) -> TelemetryResult<()> {
5506 self.process_tx_queue_with_timeout(0)
5507 }
5508
5509 #[inline]
5511 pub fn process_all_queues(&self) -> TelemetryResult<()> {
5512 self.process_all_queues_with_timeout(0)
5513 }
5514
5515 pub fn process_tx_queue_with_timeout(&self, timeout_ms: u32) -> TelemetryResult<()> {
5520 if self.side_tx_active() {
5521 return Ok(());
5522 }
5523 #[cfg(feature = "discovery")]
5524 {
5525 let _ = self.poll_discovery()?;
5526 }
5527 let start = self.clock.now_ms();
5528 loop {
5529 self.process_reliable_timeouts()?;
5530 if self.process_replay_queue_item()? {
5531 if timeout_ms != 0 && self.clock.now_ms().wrapping_sub(start) >= timeout_ms as u64 {
5532 break;
5533 }
5534 continue;
5535 }
5536 let Some((src, dst, handler, opts, data)) = self.pop_ready_tx_item() else {
5537 break;
5538 };
5539 match self.send_tx_item(src, dst, handler, opts, data.clone()) {
5540 Ok(sent) => {
5541 if sent
5542 && timeout_ms != 0
5543 && self.clock.now_ms().wrapping_sub(start) >= timeout_ms as u64
5544 {
5545 break;
5546 }
5547 }
5548 Err(e) if Self::is_side_tx_busy(&e) => {
5549 let priority = Self::relay_item_priority(&data)?;
5550 let mut st = self.state.lock();
5551 st.push_tx(RelayTxItem {
5552 src,
5553 dst,
5554 data,
5555 priority,
5556 })?;
5557 break;
5558 }
5559 Err(e) => return Err(e),
5560 }
5561 }
5562 Ok(())
5563 }
5564
5565 pub fn process_rx_queue_with_timeout(&self, timeout_ms: u32) -> TelemetryResult<()> {
5567 #[cfg(feature = "discovery")]
5568 {
5569 let _ = self.poll_discovery()?;
5570 }
5571 let start = self.clock.now_ms();
5572 loop {
5573 let item_opt = {
5574 let mut st = self.state.lock();
5575 st.rx_queue.pop_front()
5576 };
5577 let Some(item) = item_opt else { break };
5578 self.process_rx_queue_item(item)?;
5579
5580 if timeout_ms != 0 && self.clock.now_ms().wrapping_sub(start) >= timeout_ms as u64 {
5581 break;
5582 }
5583 }
5584 Ok(())
5585 }
5586
5587 pub fn process_all_queues_with_timeout(&self, timeout_ms: u32) -> TelemetryResult<()> {
5592 if self.side_tx_active() {
5593 return Ok(());
5594 }
5595 #[cfg(feature = "discovery")]
5596 {
5597 let _ = self.poll_discovery()?;
5598 }
5599 let drain_fully = timeout_ms == 0;
5600 let start = if drain_fully { 0 } else { self.clock.now_ms() };
5601
5602 loop {
5603 let mut did_any = false;
5604 self.process_reliable_timeouts()?;
5605
5606 if let Some(item) = {
5608 let mut st = self.state.lock();
5609 st.rx_queue.pop_front()
5610 } {
5611 self.process_rx_queue_item(item)?;
5612 did_any = true;
5613 }
5614
5615 if !drain_fully && self.clock.now_ms().wrapping_sub(start) >= timeout_ms as u64 {
5616 break;
5617 }
5618
5619 if self.process_replay_queue_item()? {
5620 did_any = true;
5621 }
5622
5623 let sent_one = if let Some((src, dst, handler, opts, data)) = self.pop_ready_tx_item() {
5625 self.send_tx_item(src, dst, handler, opts, data)?
5626 } else {
5627 false
5628 };
5629
5630 if sent_one {
5631 did_any = true;
5632 }
5633
5634 if !drain_fully && self.clock.now_ms().wrapping_sub(start) >= timeout_ms as u64 {
5635 break;
5636 }
5637
5638 if !did_any {
5639 break;
5640 }
5641 }
5642
5643 Ok(())
5644 }
5645
5646 pub fn periodic(&self, timeout_ms: u32) -> TelemetryResult<()> {
5651 #[cfg(feature = "discovery")]
5652 {
5653 let _ = self.poll_discovery()?;
5654 }
5655
5656 self.process_all_queues_with_timeout(timeout_ms)
5657 }
5658}