1pub mod tcp;
8pub mod tor;
9pub mod udp;
10#[cfg(feature = "webrtc-transport")]
11pub mod webrtc;
12
13#[cfg(feature = "sim-transport")]
14pub mod sim;
15
16#[cfg(any(target_os = "linux", target_os = "macos"))]
17pub mod ethernet;
18
19#[cfg(target_os = "linux")]
20pub mod ble;
21
22#[cfg(feature = "webrtc-transport")]
23use self::webrtc::WebRtcTransport;
24#[cfg(target_os = "linux")]
25use ble::DefaultBleTransport;
26#[cfg(any(target_os = "linux", target_os = "macos"))]
27use ethernet::EthernetTransport;
28use secp256k1::XOnlyPublicKey;
29#[cfg(feature = "sim-transport")]
30use sim::SimTransport;
31use std::fmt;
32use std::net::SocketAddr;
33use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
34use tcp::TcpTransport;
35use thiserror::Error;
36use tor::TorTransport;
37use tor::control::TorMonitoringInfo;
38use udp::UdpTransport;
39
40#[derive(Clone, Debug)]
46pub struct ReceivedPacket {
47 pub transport_id: TransportId,
49 pub remote_addr: TransportAddr,
51 pub data: Vec<u8>,
53 pub timestamp_ms: u64,
55 #[doc(hidden)]
57 pub trace_enqueued_at: Option<Instant>,
58}
59
60impl ReceivedPacket {
61 pub fn new(transport_id: TransportId, remote_addr: TransportAddr, data: Vec<u8>) -> Self {
63 let timestamp_ms = SystemTime::now()
64 .duration_since(UNIX_EPOCH)
65 .map(|d| d.as_millis() as u64)
66 .unwrap_or(0);
67 Self {
68 transport_id,
69 remote_addr,
70 data,
71 timestamp_ms,
72 trace_enqueued_at: crate::perf_profile::stamp(),
73 }
74 }
75
76 pub fn with_timestamp(
78 transport_id: TransportId,
79 remote_addr: TransportAddr,
80 data: Vec<u8>,
81 timestamp_ms: u64,
82 ) -> Self {
83 Self {
84 transport_id,
85 remote_addr,
86 data,
87 timestamp_ms,
88 trace_enqueued_at: crate::perf_profile::stamp(),
89 }
90 }
91}
92
93pub type PacketTx = tokio::sync::mpsc::UnboundedSender<ReceivedPacket>;
111
112pub type PacketRx = tokio::sync::mpsc::UnboundedReceiver<ReceivedPacket>;
114
115pub fn packet_channel(_buffer: usize) -> (PacketTx, PacketRx) {
122 tokio::sync::mpsc::unbounded_channel()
123}
124
125#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
131pub struct TransportId(u32);
132
133impl TransportId {
134 pub fn new(id: u32) -> Self {
136 Self(id)
137 }
138
139 pub fn as_u32(&self) -> u32 {
141 self.0
142 }
143}
144
145impl fmt::Display for TransportId {
146 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
147 write!(f, "transport:{}", self.0)
148 }
149}
150
151#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
153pub struct LinkId(u64);
154
155impl LinkId {
156 pub fn new(id: u64) -> Self {
158 Self(id)
159 }
160
161 pub fn as_u64(&self) -> u64 {
163 self.0
164 }
165}
166
167impl fmt::Display for LinkId {
168 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
169 write!(f, "link:{}", self.0)
170 }
171}
172
173#[derive(Debug, Error)]
179pub enum TransportError {
180 #[error("transport not started")]
181 NotStarted,
182
183 #[error("transport already started")]
184 AlreadyStarted,
185
186 #[error("transport failed to start: {0}")]
187 StartFailed(String),
188
189 #[error("transport shutdown failed: {0}")]
190 ShutdownFailed(String),
191
192 #[error("link failed: {0}")]
193 LinkFailed(String),
194
195 #[error("send failed: {0}")]
196 SendFailed(String),
197
198 #[error("receive failed: {0}")]
199 RecvFailed(String),
200
201 #[error("invalid transport address: {0}")]
202 InvalidAddress(String),
203
204 #[error("mtu exceeded: packet {packet_size} > mtu {mtu}")]
205 MtuExceeded { packet_size: usize, mtu: u16 },
206
207 #[error("transport timeout")]
208 Timeout,
209
210 #[error("connection refused")]
211 ConnectionRefused,
212
213 #[error("transport not supported: {0}")]
214 NotSupported(String),
215
216 #[error("io error: {0}")]
217 Io(#[from] std::io::Error),
218}
219
220impl TransportError {
221 pub fn is_local_route_unavailable(&self) -> bool {
224 match self {
225 TransportError::Io(error) => is_local_route_error_kind(error.kind()),
226 TransportError::SendFailed(message) => is_local_route_error_text(message),
227 _ => false,
228 }
229 }
230}
231
232fn is_local_route_error_kind(kind: std::io::ErrorKind) -> bool {
233 matches!(
234 kind,
235 std::io::ErrorKind::NetworkUnreachable
236 | std::io::ErrorKind::HostUnreachable
237 | std::io::ErrorKind::AddrNotAvailable
238 )
239}
240
241fn is_local_route_error_text(message: &str) -> bool {
242 let lower = message.to_ascii_lowercase();
243 lower.contains("network is unreachable")
244 || lower.contains("no route to host")
245 || lower.contains("host is unreachable")
246 || lower.contains("can't assign requested address")
247 || lower.contains("cannot assign requested address")
248 || lower.contains("os error 51")
249 || lower.contains("os error 65")
250 || lower.contains("os error 49")
251}
252
253#[derive(Clone, Debug, PartialEq, Eq)]
259pub struct TransportType {
260 pub name: &'static str,
262 pub connection_oriented: bool,
264 pub reliable: bool,
266}
267
268impl TransportType {
269 pub const UDP: TransportType = TransportType {
271 name: "udp",
272 connection_oriented: false,
273 reliable: false,
274 };
275
276 pub const TCP: TransportType = TransportType {
278 name: "tcp",
279 connection_oriented: true,
280 reliable: true,
281 };
282
283 pub const ETHERNET: TransportType = TransportType {
285 name: "ethernet",
286 connection_oriented: false,
287 reliable: false,
288 };
289
290 pub const WIFI: TransportType = TransportType {
292 name: "wifi",
293 connection_oriented: false,
294 reliable: false,
295 };
296
297 pub const TOR: TransportType = TransportType {
299 name: "tor",
300 connection_oriented: true,
301 reliable: true,
302 };
303
304 pub const SERIAL: TransportType = TransportType {
306 name: "serial",
307 connection_oriented: false,
308 reliable: true, };
310
311 pub const BLE: TransportType = TransportType {
313 name: "ble",
314 connection_oriented: true,
315 reliable: true, };
317
318 pub const WEBRTC: TransportType = TransportType {
320 name: "webrtc",
321 connection_oriented: true,
322 reliable: false,
323 };
324
325 #[cfg(feature = "sim-transport")]
327 pub const SIM: TransportType = TransportType {
328 name: "sim",
329 connection_oriented: false,
330 reliable: false,
331 };
332
333 pub fn is_connectionless(&self) -> bool {
335 !self.connection_oriented
336 }
337}
338
339impl fmt::Display for TransportType {
340 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
341 write!(f, "{}", self.name)
342 }
343}
344
345#[derive(Clone, Copy, Debug, PartialEq, Eq)]
351pub enum TransportState {
352 Configured,
354 Starting,
356 Up,
358 Down,
360 Failed,
362}
363
364impl TransportState {
365 pub fn is_operational(&self) -> bool {
367 matches!(self, TransportState::Up)
368 }
369
370 pub fn can_start(&self) -> bool {
372 matches!(
373 self,
374 TransportState::Configured | TransportState::Down | TransportState::Failed
375 )
376 }
377
378 pub fn is_terminal(&self) -> bool {
380 matches!(self, TransportState::Failed)
381 }
382}
383
384impl fmt::Display for TransportState {
385 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
386 let s = match self {
387 TransportState::Configured => "configured",
388 TransportState::Starting => "starting",
389 TransportState::Up => "up",
390 TransportState::Down => "down",
391 TransportState::Failed => "failed",
392 };
393 write!(f, "{}", s)
394 }
395}
396
397#[derive(Clone, Copy, Debug, PartialEq, Eq)]
403pub enum LinkState {
404 Connecting,
406 Connected,
408 Disconnected,
410 Failed,
412}
413
414impl LinkState {
415 pub fn is_operational(&self) -> bool {
417 matches!(self, LinkState::Connected)
418 }
419
420 pub fn is_terminal(&self) -> bool {
422 matches!(self, LinkState::Disconnected | LinkState::Failed)
423 }
424}
425
426impl fmt::Display for LinkState {
427 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
428 let s = match self {
429 LinkState::Connecting => "connecting",
430 LinkState::Connected => "connected",
431 LinkState::Disconnected => "disconnected",
432 LinkState::Failed => "failed",
433 };
434 write!(f, "{}", s)
435 }
436}
437
438#[derive(Clone, Copy, Debug, PartialEq, Eq)]
440pub enum LinkDirection {
441 Outbound,
443 Inbound,
445}
446
447impl fmt::Display for LinkDirection {
448 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
449 let s = match self {
450 LinkDirection::Outbound => "outbound",
451 LinkDirection::Inbound => "inbound",
452 };
453 write!(f, "{}", s)
454 }
455}
456
457#[derive(Clone, PartialEq, Eq, Hash)]
467pub struct TransportAddr(Vec<u8>);
468
469impl TransportAddr {
470 pub fn new(bytes: Vec<u8>) -> Self {
472 Self(bytes)
473 }
474
475 pub fn from_bytes(bytes: &[u8]) -> Self {
477 Self(bytes.to_vec())
478 }
479
480 pub fn from_string(s: &str) -> Self {
482 Self(s.as_bytes().to_vec())
483 }
484
485 pub fn from_socket_addr(addr: std::net::SocketAddr) -> Self {
497 use std::io::Write;
498 let mut buf = Vec::with_capacity(56);
502 write!(&mut buf, "{addr}").expect("Vec<u8>::write_fmt is infallible");
506 Self(buf)
507 }
508
509 pub fn as_bytes(&self) -> &[u8] {
511 &self.0
512 }
513
514 pub fn as_str(&self) -> Option<&str> {
516 std::str::from_utf8(&self.0).ok()
517 }
518
519 pub fn len(&self) -> usize {
521 self.0.len()
522 }
523
524 pub fn is_empty(&self) -> bool {
526 self.0.is_empty()
527 }
528}
529
530impl fmt::Debug for TransportAddr {
531 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
532 match self.as_str() {
533 Some(s) => write!(f, "TransportAddr(\"{}\")", s),
534 None => write!(f, "TransportAddr({:?})", self.0),
535 }
536 }
537}
538
539impl fmt::Display for TransportAddr {
540 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
541 match self.as_str() {
543 Some(s) => write!(f, "{}", s),
544 None => {
545 for byte in &self.0 {
546 write!(f, "{:02x}", byte)?;
547 }
548 Ok(())
549 }
550 }
551 }
552}
553
554impl From<&str> for TransportAddr {
555 fn from(s: &str) -> Self {
556 Self::from_string(s)
557 }
558}
559
560impl From<String> for TransportAddr {
561 fn from(s: String) -> Self {
562 Self(s.into_bytes())
563 }
564}
565
566#[derive(Clone, Debug, Default)]
572pub struct LinkStats {
573 pub packets_sent: u64,
575 pub packets_recv: u64,
577 pub bytes_sent: u64,
579 pub bytes_recv: u64,
581 pub last_recv_ms: u64,
583 rtt_estimate: Option<Duration>,
585 pub loss_rate: f32,
587 pub throughput_estimate: u64,
589}
590
591impl LinkStats {
592 pub fn new() -> Self {
594 Self::default()
595 }
596
597 pub fn record_sent(&mut self, bytes: usize) {
599 self.packets_sent += 1;
600 self.bytes_sent += bytes as u64;
601 }
602
603 pub fn record_recv(&mut self, bytes: usize, timestamp_ms: u64) {
605 self.packets_recv += 1;
606 self.bytes_recv += bytes as u64;
607 self.last_recv_ms = timestamp_ms;
608 }
609
610 pub fn rtt_estimate(&self) -> Option<Duration> {
612 self.rtt_estimate
613 }
614
615 pub fn update_rtt(&mut self, rtt: Duration) {
619 match self.rtt_estimate {
620 Some(old_rtt) => {
621 let alpha = 0.2;
622 let new_rtt_nanos = (alpha * rtt.as_nanos() as f64
623 + (1.0 - alpha) * old_rtt.as_nanos() as f64)
624 as u64;
625 self.rtt_estimate = Some(Duration::from_nanos(new_rtt_nanos));
626 }
627 None => {
628 self.rtt_estimate = Some(rtt);
629 }
630 }
631 }
632
633 pub fn time_since_recv(&self, current_time_ms: u64) -> u64 {
635 if self.last_recv_ms == 0 {
636 return u64::MAX;
637 }
638 current_time_ms.saturating_sub(self.last_recv_ms)
639 }
640
641 pub fn reset(&mut self) {
643 *self = Self::default();
644 }
645}
646
647#[derive(Clone, Debug)]
653pub struct Link {
654 link_id: LinkId,
656 transport_id: TransportId,
658 remote_addr: TransportAddr,
660 direction: LinkDirection,
662 state: LinkState,
664 base_rtt: Duration,
666 stats: LinkStats,
668 created_at: u64,
670}
671
672impl Link {
673 pub fn new(
675 link_id: LinkId,
676 transport_id: TransportId,
677 remote_addr: TransportAddr,
678 direction: LinkDirection,
679 base_rtt: Duration,
680 ) -> Self {
681 Self {
682 link_id,
683 transport_id,
684 remote_addr,
685 direction,
686 state: LinkState::Connecting,
687 base_rtt,
688 stats: LinkStats::new(),
689 created_at: 0,
690 }
691 }
692
693 pub fn new_with_timestamp(
695 link_id: LinkId,
696 transport_id: TransportId,
697 remote_addr: TransportAddr,
698 direction: LinkDirection,
699 base_rtt: Duration,
700 created_at: u64,
701 ) -> Self {
702 let mut link = Self::new(link_id, transport_id, remote_addr, direction, base_rtt);
703 link.created_at = created_at;
704 link
705 }
706
707 pub fn connectionless(
712 link_id: LinkId,
713 transport_id: TransportId,
714 remote_addr: TransportAddr,
715 direction: LinkDirection,
716 base_rtt: Duration,
717 ) -> Self {
718 let mut link = Self::new(link_id, transport_id, remote_addr, direction, base_rtt);
719 link.state = LinkState::Connected;
720 link
721 }
722
723 pub fn link_id(&self) -> LinkId {
725 self.link_id
726 }
727
728 pub fn transport_id(&self) -> TransportId {
730 self.transport_id
731 }
732
733 pub fn remote_addr(&self) -> &TransportAddr {
735 &self.remote_addr
736 }
737
738 pub fn direction(&self) -> LinkDirection {
740 self.direction
741 }
742
743 pub fn state(&self) -> LinkState {
745 self.state
746 }
747
748 pub fn base_rtt(&self) -> Duration {
750 self.base_rtt
751 }
752
753 pub fn stats(&self) -> &LinkStats {
755 &self.stats
756 }
757
758 pub fn stats_mut(&mut self) -> &mut LinkStats {
760 &mut self.stats
761 }
762
763 pub fn created_at(&self) -> u64 {
765 self.created_at
766 }
767
768 pub fn set_created_at(&mut self, timestamp: u64) {
770 self.created_at = timestamp;
771 }
772
773 pub fn set_connected(&mut self) {
775 self.state = LinkState::Connected;
776 }
777
778 pub fn set_disconnected(&mut self) {
780 self.state = LinkState::Disconnected;
781 }
782
783 pub fn set_failed(&mut self) {
785 self.state = LinkState::Failed;
786 }
787
788 pub fn is_operational(&self) -> bool {
790 self.state.is_operational()
791 }
792
793 pub fn is_terminal(&self) -> bool {
795 self.state.is_terminal()
796 }
797
798 pub fn effective_rtt(&self) -> Duration {
800 self.stats.rtt_estimate().unwrap_or(self.base_rtt)
801 }
802
803 pub fn age(&self, current_time_ms: u64) -> u64 {
805 if self.created_at == 0 {
806 return 0;
807 }
808 current_time_ms.saturating_sub(self.created_at)
809 }
810}
811
812#[derive(Clone, Debug)]
818pub struct DiscoveredPeer {
819 pub transport_id: TransportId,
821 pub addr: TransportAddr,
823 pub pubkey_hint: Option<XOnlyPublicKey>,
825}
826
827impl DiscoveredPeer {
828 pub fn new(transport_id: TransportId, addr: TransportAddr) -> Self {
830 Self {
831 transport_id,
832 addr,
833 pubkey_hint: None,
834 }
835 }
836
837 pub fn with_hint(
839 transport_id: TransportId,
840 addr: TransportAddr,
841 pubkey: XOnlyPublicKey,
842 ) -> Self {
843 Self {
844 transport_id,
845 addr,
846 pubkey_hint: Some(pubkey),
847 }
848 }
849}
850
851pub trait Transport {
860 fn transport_id(&self) -> TransportId;
862
863 fn transport_type(&self) -> &TransportType;
865
866 fn state(&self) -> TransportState;
868
869 fn mtu(&self) -> u16;
871
872 fn link_mtu(&self, addr: &TransportAddr) -> u16 {
878 let _ = addr;
879 self.mtu()
880 }
881
882 fn start(&mut self) -> Result<(), TransportError>;
884
885 fn stop(&mut self) -> Result<(), TransportError>;
887
888 fn send(&self, addr: &TransportAddr, data: &[u8]) -> Result<(), TransportError>;
890
891 fn discover(&self) -> Result<Vec<DiscoveredPeer>, TransportError>;
893
894 fn auto_connect(&self) -> bool {
897 false
898 }
899
900 fn accept_connections(&self) -> bool {
903 true
904 }
905
906 fn close_connection(&self, _addr: &TransportAddr) {
912 }
914}
915
916#[derive(Clone, Debug, PartialEq, Eq)]
925pub enum ConnectionState {
926 None,
928 Connecting,
930 Connected,
932 Failed(String),
934}
935
936#[derive(Clone, Debug, Default)]
945pub struct TransportCongestion {
946 pub recv_drops: Option<u64>,
949}
950
951pub enum TransportHandle {
960 Udp(UdpTransport),
962 #[cfg(feature = "sim-transport")]
964 Sim(SimTransport),
965 #[cfg(any(target_os = "linux", target_os = "macos"))]
967 Ethernet(EthernetTransport),
968 Tcp(TcpTransport),
970 Tor(TorTransport),
972 #[cfg(feature = "webrtc-transport")]
974 WebRtc(Box<WebRtcTransport>),
975 #[cfg(target_os = "linux")]
977 Ble(DefaultBleTransport),
978}
979
980impl TransportHandle {
981 pub async fn start(&mut self) -> Result<(), TransportError> {
983 match self {
984 TransportHandle::Udp(t) => t.start_async().await,
985 #[cfg(feature = "sim-transport")]
986 TransportHandle::Sim(t) => t.start_async().await,
987 #[cfg(any(target_os = "linux", target_os = "macos"))]
988 TransportHandle::Ethernet(t) => t.start_async().await,
989 TransportHandle::Tcp(t) => t.start_async().await,
990 TransportHandle::Tor(t) => t.start_async().await,
991 #[cfg(feature = "webrtc-transport")]
992 TransportHandle::WebRtc(t) => t.start_async().await,
993 #[cfg(target_os = "linux")]
994 TransportHandle::Ble(t) => t.start_async().await,
995 }
996 }
997
998 pub async fn stop(&mut self) -> Result<(), TransportError> {
1000 match self {
1001 TransportHandle::Udp(t) => t.stop_async().await,
1002 #[cfg(feature = "sim-transport")]
1003 TransportHandle::Sim(t) => t.stop_async().await,
1004 #[cfg(any(target_os = "linux", target_os = "macos"))]
1005 TransportHandle::Ethernet(t) => t.stop_async().await,
1006 TransportHandle::Tcp(t) => t.stop_async().await,
1007 TransportHandle::Tor(t) => t.stop_async().await,
1008 #[cfg(feature = "webrtc-transport")]
1009 TransportHandle::WebRtc(t) => t.stop_async().await,
1010 #[cfg(target_os = "linux")]
1011 TransportHandle::Ble(t) => t.stop_async().await,
1012 }
1013 }
1014
1015 pub async fn send(&self, addr: &TransportAddr, data: &[u8]) -> Result<usize, TransportError> {
1017 match self {
1018 TransportHandle::Udp(t) => t.send_async(addr, data).await,
1019 #[cfg(feature = "sim-transport")]
1020 TransportHandle::Sim(t) => t.send_async(addr, data).await,
1021 #[cfg(any(target_os = "linux", target_os = "macos"))]
1022 TransportHandle::Ethernet(t) => t.send_async(addr, data).await,
1023 TransportHandle::Tcp(t) => t.send_async(addr, data).await,
1024 TransportHandle::Tor(t) => t.send_async(addr, data).await,
1025 #[cfg(feature = "webrtc-transport")]
1026 TransportHandle::WebRtc(t) => t.send_async(addr, data).await,
1027 #[cfg(target_os = "linux")]
1028 TransportHandle::Ble(t) => t.send_async(addr, data).await,
1029 }
1030 }
1031
1032 pub async fn flush_pending_send(&self) {
1038 if let TransportHandle::Udp(t) = self {
1039 t.flush_pending_send().await;
1040 }
1041 }
1042
1043 pub fn transport_id(&self) -> TransportId {
1045 match self {
1046 TransportHandle::Udp(t) => t.transport_id(),
1047 #[cfg(feature = "sim-transport")]
1048 TransportHandle::Sim(t) => t.transport_id(),
1049 #[cfg(any(target_os = "linux", target_os = "macos"))]
1050 TransportHandle::Ethernet(t) => t.transport_id(),
1051 TransportHandle::Tcp(t) => t.transport_id(),
1052 TransportHandle::Tor(t) => t.transport_id(),
1053 #[cfg(feature = "webrtc-transport")]
1054 TransportHandle::WebRtc(t) => t.transport_id(),
1055 #[cfg(target_os = "linux")]
1056 TransportHandle::Ble(t) => t.transport_id(),
1057 }
1058 }
1059
1060 pub fn name(&self) -> Option<&str> {
1062 match self {
1063 TransportHandle::Udp(t) => t.name(),
1064 #[cfg(feature = "sim-transport")]
1065 TransportHandle::Sim(t) => t.name(),
1066 #[cfg(any(target_os = "linux", target_os = "macos"))]
1067 TransportHandle::Ethernet(t) => t.name(),
1068 TransportHandle::Tcp(t) => t.name(),
1069 TransportHandle::Tor(t) => t.name(),
1070 #[cfg(feature = "webrtc-transport")]
1071 TransportHandle::WebRtc(t) => t.name(),
1072 #[cfg(target_os = "linux")]
1073 TransportHandle::Ble(t) => t.name(),
1074 }
1075 }
1076
1077 pub fn transport_type(&self) -> &TransportType {
1079 match self {
1080 TransportHandle::Udp(t) => t.transport_type(),
1081 #[cfg(feature = "sim-transport")]
1082 TransportHandle::Sim(t) => t.transport_type(),
1083 #[cfg(any(target_os = "linux", target_os = "macos"))]
1084 TransportHandle::Ethernet(t) => t.transport_type(),
1085 TransportHandle::Tcp(t) => t.transport_type(),
1086 TransportHandle::Tor(t) => t.transport_type(),
1087 #[cfg(feature = "webrtc-transport")]
1088 TransportHandle::WebRtc(t) => t.transport_type(),
1089 #[cfg(target_os = "linux")]
1090 TransportHandle::Ble(t) => t.transport_type(),
1091 }
1092 }
1093
1094 pub fn state(&self) -> TransportState {
1096 match self {
1097 TransportHandle::Udp(t) => t.state(),
1098 #[cfg(feature = "sim-transport")]
1099 TransportHandle::Sim(t) => t.state(),
1100 #[cfg(any(target_os = "linux", target_os = "macos"))]
1101 TransportHandle::Ethernet(t) => t.state(),
1102 TransportHandle::Tcp(t) => t.state(),
1103 TransportHandle::Tor(t) => t.state(),
1104 #[cfg(feature = "webrtc-transport")]
1105 TransportHandle::WebRtc(t) => t.state(),
1106 #[cfg(target_os = "linux")]
1107 TransportHandle::Ble(t) => t.state(),
1108 }
1109 }
1110
1111 pub fn mtu(&self) -> u16 {
1113 match self {
1114 TransportHandle::Udp(t) => t.mtu(),
1115 #[cfg(feature = "sim-transport")]
1116 TransportHandle::Sim(t) => t.mtu(),
1117 #[cfg(any(target_os = "linux", target_os = "macos"))]
1118 TransportHandle::Ethernet(t) => t.mtu(),
1119 TransportHandle::Tcp(t) => t.mtu(),
1120 TransportHandle::Tor(t) => t.mtu(),
1121 #[cfg(feature = "webrtc-transport")]
1122 TransportHandle::WebRtc(t) => t.mtu(),
1123 #[cfg(target_os = "linux")]
1124 TransportHandle::Ble(t) => t.mtu(),
1125 }
1126 }
1127
1128 pub fn link_mtu(&self, addr: &TransportAddr) -> u16 {
1133 match self {
1134 TransportHandle::Udp(t) => t.link_mtu(addr),
1135 #[cfg(feature = "sim-transport")]
1136 TransportHandle::Sim(t) => t.link_mtu(addr),
1137 #[cfg(any(target_os = "linux", target_os = "macos"))]
1138 TransportHandle::Ethernet(t) => t.link_mtu(addr),
1139 TransportHandle::Tcp(t) => t.link_mtu(addr),
1140 TransportHandle::Tor(t) => t.link_mtu(addr),
1141 #[cfg(feature = "webrtc-transport")]
1142 TransportHandle::WebRtc(t) => t.link_mtu(addr),
1143 #[cfg(target_os = "linux")]
1144 TransportHandle::Ble(t) => t.link_mtu(addr),
1145 }
1146 }
1147
1148 pub fn local_addr(&self) -> Option<std::net::SocketAddr> {
1150 match self {
1151 TransportHandle::Udp(t) => t.local_addr(),
1152 #[cfg(feature = "sim-transport")]
1153 TransportHandle::Sim(_) => None,
1154 #[cfg(any(target_os = "linux", target_os = "macos"))]
1155 TransportHandle::Ethernet(_) => None,
1156 TransportHandle::Tcp(t) => t.local_addr(),
1157 TransportHandle::Tor(_) => None,
1158 #[cfg(feature = "webrtc-transport")]
1159 TransportHandle::WebRtc(_) => None,
1160 #[cfg(target_os = "linux")]
1161 TransportHandle::Ble(_) => None,
1162 }
1163 }
1164
1165 pub fn interface_name(&self) -> Option<&str> {
1167 match self {
1168 TransportHandle::Udp(_) => None,
1169 #[cfg(feature = "sim-transport")]
1170 TransportHandle::Sim(_) => None,
1171 #[cfg(any(target_os = "linux", target_os = "macos"))]
1172 TransportHandle::Ethernet(t) => Some(t.interface_name()),
1173 TransportHandle::Tcp(_) => None,
1174 TransportHandle::Tor(_) => None,
1175 #[cfg(feature = "webrtc-transport")]
1176 TransportHandle::WebRtc(_) => None,
1177 #[cfg(target_os = "linux")]
1178 TransportHandle::Ble(_) => None,
1179 }
1180 }
1181
1182 pub fn onion_address(&self) -> Option<&str> {
1184 match self {
1185 TransportHandle::Tor(t) => t.onion_address(),
1186 _ => None,
1187 }
1188 }
1189
1190 pub fn tor_monitoring(&self) -> Option<TorMonitoringInfo> {
1192 match self {
1193 TransportHandle::Tor(t) => t.cached_monitoring(),
1194 _ => None,
1195 }
1196 }
1197
1198 pub fn tor_mode(&self) -> Option<&str> {
1200 match self {
1201 TransportHandle::Tor(t) => Some(t.mode()),
1202 _ => None,
1203 }
1204 }
1205
1206 pub fn discover(&self) -> Result<Vec<DiscoveredPeer>, TransportError> {
1208 match self {
1209 TransportHandle::Udp(t) => t.discover(),
1210 #[cfg(feature = "sim-transport")]
1211 TransportHandle::Sim(t) => t.discover(),
1212 #[cfg(any(target_os = "linux", target_os = "macos"))]
1213 TransportHandle::Ethernet(t) => t.discover(),
1214 TransportHandle::Tcp(t) => t.discover(),
1215 TransportHandle::Tor(t) => t.discover(),
1216 #[cfg(feature = "webrtc-transport")]
1217 TransportHandle::WebRtc(t) => t.discover(),
1218 #[cfg(target_os = "linux")]
1219 TransportHandle::Ble(t) => t.discover(),
1220 }
1221 }
1222
1223 pub fn auto_connect(&self) -> bool {
1225 match self {
1226 TransportHandle::Udp(t) => t.auto_connect(),
1227 #[cfg(feature = "sim-transport")]
1228 TransportHandle::Sim(t) => t.auto_connect(),
1229 #[cfg(any(target_os = "linux", target_os = "macos"))]
1230 TransportHandle::Ethernet(t) => t.auto_connect(),
1231 TransportHandle::Tcp(t) => t.auto_connect(),
1232 TransportHandle::Tor(t) => t.auto_connect(),
1233 #[cfg(feature = "webrtc-transport")]
1234 TransportHandle::WebRtc(t) => t.auto_connect(),
1235 #[cfg(target_os = "linux")]
1236 TransportHandle::Ble(t) => t.auto_connect(),
1237 }
1238 }
1239
1240 pub fn accept_connections(&self) -> bool {
1242 match self {
1243 TransportHandle::Udp(t) => t.accept_connections(),
1244 #[cfg(feature = "sim-transport")]
1245 TransportHandle::Sim(t) => t.accept_connections(),
1246 #[cfg(any(target_os = "linux", target_os = "macos"))]
1247 TransportHandle::Ethernet(t) => t.accept_connections(),
1248 TransportHandle::Tcp(t) => t.accept_connections(),
1249 TransportHandle::Tor(t) => t.accept_connections(),
1250 #[cfg(feature = "webrtc-transport")]
1251 TransportHandle::WebRtc(t) => t.accept_connections(),
1252 #[cfg(target_os = "linux")]
1253 TransportHandle::Ble(t) => t.accept_connections(),
1254 }
1255 }
1256
1257 pub async fn connect(&self, addr: &TransportAddr) -> Result<(), TransportError> {
1265 match self {
1266 TransportHandle::Udp(_) => Ok(()), #[cfg(feature = "sim-transport")]
1268 TransportHandle::Sim(_) => Ok(()), #[cfg(any(target_os = "linux", target_os = "macos"))]
1270 TransportHandle::Ethernet(_) => Ok(()), TransportHandle::Tcp(t) => t.connect_async(addr).await,
1272 TransportHandle::Tor(t) => t.connect_async(addr).await,
1273 #[cfg(feature = "webrtc-transport")]
1274 TransportHandle::WebRtc(t) => t.connect_async(addr).await,
1275 #[cfg(target_os = "linux")]
1276 TransportHandle::Ble(t) => t.connect_async(addr).await,
1277 }
1278 }
1279
1280 pub fn connection_state(&self, addr: &TransportAddr) -> ConnectionState {
1286 match self {
1287 TransportHandle::Udp(_) => ConnectionState::Connected,
1288 #[cfg(feature = "sim-transport")]
1289 TransportHandle::Sim(_) => ConnectionState::Connected,
1290 #[cfg(any(target_os = "linux", target_os = "macos"))]
1291 TransportHandle::Ethernet(_) => ConnectionState::Connected,
1292 TransportHandle::Tcp(t) => t.connection_state_sync(addr),
1293 TransportHandle::Tor(t) => t.connection_state_sync(addr),
1294 #[cfg(feature = "webrtc-transport")]
1295 TransportHandle::WebRtc(t) => t.connection_state_sync(addr),
1296 #[cfg(target_os = "linux")]
1297 TransportHandle::Ble(t) => t.connection_state_sync(addr),
1298 }
1299 }
1300
1301 pub async fn close_connection(&self, addr: &TransportAddr) {
1306 match self {
1307 TransportHandle::Udp(t) => t.close_connection(addr),
1308 #[cfg(feature = "sim-transport")]
1309 TransportHandle::Sim(t) => t.close_connection(addr),
1310 #[cfg(any(target_os = "linux", target_os = "macos"))]
1311 TransportHandle::Ethernet(t) => t.close_connection(addr),
1312 TransportHandle::Tcp(t) => t.close_connection_async(addr).await,
1313 TransportHandle::Tor(t) => t.close_connection_async(addr).await,
1314 #[cfg(feature = "webrtc-transport")]
1315 TransportHandle::WebRtc(t) => t.close_connection_async(addr).await,
1316 #[cfg(target_os = "linux")]
1317 TransportHandle::Ble(t) => t.close_connection_async(addr).await,
1318 }
1319 }
1320
1321 pub fn is_operational(&self) -> bool {
1323 self.state().is_operational()
1324 }
1325
1326 pub fn congestion(&self) -> TransportCongestion {
1332 match self {
1333 TransportHandle::Udp(t) => t.congestion(),
1334 #[cfg(feature = "sim-transport")]
1335 TransportHandle::Sim(_) => TransportCongestion::default(),
1336 #[cfg(any(target_os = "linux", target_os = "macos"))]
1337 TransportHandle::Ethernet(_) => TransportCongestion::default(),
1338 TransportHandle::Tcp(_) => TransportCongestion::default(),
1339 TransportHandle::Tor(_) => TransportCongestion::default(),
1340 #[cfg(feature = "webrtc-transport")]
1341 TransportHandle::WebRtc(_) => TransportCongestion::default(),
1342 #[cfg(target_os = "linux")]
1343 TransportHandle::Ble(_) => TransportCongestion::default(),
1344 }
1345 }
1346
1347 pub fn transport_stats(&self) -> serde_json::Value {
1351 match self {
1352 TransportHandle::Udp(t) => {
1353 serde_json::to_value(t.stats().snapshot()).unwrap_or_default()
1354 }
1355 #[cfg(feature = "sim-transport")]
1356 TransportHandle::Sim(t) => serde_json::to_value(t.stats()).unwrap_or_default(),
1357 #[cfg(any(target_os = "linux", target_os = "macos"))]
1358 TransportHandle::Ethernet(t) => {
1359 let snap = t.stats().snapshot();
1360 serde_json::json!({
1361 "frames_sent": snap.frames_sent,
1362 "frames_recv": snap.frames_recv,
1363 "bytes_sent": snap.bytes_sent,
1364 "bytes_recv": snap.bytes_recv,
1365 "send_errors": snap.send_errors,
1366 "recv_errors": snap.recv_errors,
1367 "beacons_sent": snap.beacons_sent,
1368 "beacons_recv": snap.beacons_recv,
1369 "frames_too_short": snap.frames_too_short,
1370 "frames_too_long": snap.frames_too_long,
1371 })
1372 }
1373 TransportHandle::Tcp(t) => {
1374 serde_json::to_value(t.stats().snapshot()).unwrap_or_default()
1375 }
1376 TransportHandle::Tor(t) => {
1377 serde_json::to_value(t.stats().snapshot()).unwrap_or_default()
1378 }
1379 #[cfg(feature = "webrtc-transport")]
1380 TransportHandle::WebRtc(t) => serde_json::json!({
1381 "mtu": t.mtu(),
1382 "state": t.state().to_string(),
1383 }),
1384 #[cfg(target_os = "linux")]
1385 TransportHandle::Ble(t) => {
1386 serde_json::to_value(t.stats().snapshot()).unwrap_or_default()
1387 }
1388 }
1389 }
1390}
1391
1392pub(crate) async fn resolve_socket_addr(
1403 addr: &TransportAddr,
1404) -> Result<SocketAddr, TransportError> {
1405 resolve_socket_addrs(addr)
1406 .await?
1407 .into_iter()
1408 .next()
1409 .ok_or_else(|| {
1410 TransportError::InvalidAddress(format!(
1411 "DNS resolution returned no addresses for {}",
1412 addr.as_str().unwrap_or("<non-utf8>")
1413 ))
1414 })
1415}
1416
1417pub(crate) async fn resolve_socket_addrs(
1424 addr: &TransportAddr,
1425) -> Result<Vec<SocketAddr>, TransportError> {
1426 let s = addr
1427 .as_str()
1428 .ok_or_else(|| TransportError::InvalidAddress("not valid UTF-8".into()))?;
1429
1430 if let Ok(sock_addr) = s.parse::<SocketAddr>() {
1432 return Ok(vec![sock_addr]);
1433 }
1434
1435 let addrs = tokio::net::lookup_host(s)
1437 .await
1438 .map_err(|e| {
1439 TransportError::InvalidAddress(format!("DNS resolution failed for {}: {}", s, e))
1440 })?
1441 .collect::<Vec<_>>();
1442 if addrs.is_empty() {
1443 return Err(TransportError::InvalidAddress(format!(
1444 "DNS resolution returned no addresses for {}",
1445 s
1446 )));
1447 }
1448 Ok(addrs)
1449}
1450
1451#[cfg(test)]
1456mod tests {
1457 use super::*;
1458
1459 #[test]
1460 fn test_transport_id() {
1461 let id = TransportId::new(42);
1462 assert_eq!(id.as_u32(), 42);
1463 assert_eq!(format!("{}", id), "transport:42");
1464 }
1465
1466 #[test]
1467 fn test_link_id() {
1468 let id = LinkId::new(12345);
1469 assert_eq!(id.as_u64(), 12345);
1470 assert_eq!(format!("{}", id), "link:12345");
1471 }
1472
1473 #[test]
1474 fn test_transport_state_transitions() {
1475 assert!(TransportState::Configured.can_start());
1476 assert!(TransportState::Down.can_start());
1477 assert!(TransportState::Failed.can_start());
1478 assert!(!TransportState::Starting.can_start());
1479 assert!(!TransportState::Up.can_start());
1480
1481 assert!(TransportState::Up.is_operational());
1482 assert!(!TransportState::Starting.is_operational());
1483 assert!(!TransportState::Failed.is_operational());
1484 }
1485
1486 #[test]
1487 fn test_link_state() {
1488 assert!(LinkState::Connected.is_operational());
1489 assert!(!LinkState::Connecting.is_operational());
1490 assert!(!LinkState::Disconnected.is_operational());
1491 assert!(!LinkState::Failed.is_operational());
1492
1493 assert!(LinkState::Disconnected.is_terminal());
1494 assert!(LinkState::Failed.is_terminal());
1495 assert!(!LinkState::Connected.is_terminal());
1496 }
1497
1498 #[test]
1499 #[allow(clippy::assertions_on_constants)]
1500 fn test_transport_type_constants() {
1501 assert!(!TransportType::UDP.connection_oriented);
1503 assert!(!TransportType::UDP.reliable);
1504 assert!(TransportType::UDP.is_connectionless());
1505
1506 assert!(TransportType::TOR.connection_oriented);
1507 assert!(TransportType::TOR.reliable);
1508 assert!(!TransportType::TOR.is_connectionless());
1509
1510 assert_eq!(TransportType::UDP.name, "udp");
1511 assert_eq!(TransportType::ETHERNET.name, "ethernet");
1512 }
1513
1514 #[test]
1515 fn test_transport_addr_string() {
1516 let addr = TransportAddr::from_string("192.168.1.1:2121");
1517 assert_eq!(format!("{}", addr), "192.168.1.1:2121");
1518 assert_eq!(addr.as_str(), Some("192.168.1.1:2121"));
1519 }
1520
1521 #[test]
1522 fn test_transport_addr_binary() {
1523 let binary = TransportAddr::new(vec![0xff, 0x80, 0x2b, 0x3c, 0x4d, 0x5e]);
1525 assert_eq!(format!("{}", binary), "ff802b3c4d5e");
1526 assert!(binary.as_str().is_none());
1527 assert_eq!(binary.len(), 6);
1528 }
1529
1530 #[test]
1531 fn test_transport_addr_from_string() {
1532 let addr: TransportAddr = "test:1234".into();
1533 assert_eq!(addr.as_str(), Some("test:1234"));
1534
1535 let addr2: TransportAddr = String::from("hello").into();
1536 assert_eq!(addr2.as_str(), Some("hello"));
1537 }
1538
1539 #[test]
1540 fn test_link_stats_basic() {
1541 let mut stats = LinkStats::new();
1542
1543 stats.record_sent(100);
1544 stats.record_recv(200, 1000);
1545
1546 assert_eq!(stats.packets_sent, 1);
1547 assert_eq!(stats.bytes_sent, 100);
1548 assert_eq!(stats.packets_recv, 1);
1549 assert_eq!(stats.bytes_recv, 200);
1550 assert_eq!(stats.last_recv_ms, 1000);
1551 }
1552
1553 #[test]
1554 fn test_link_stats_rtt() {
1555 let mut stats = LinkStats::new();
1556
1557 assert!(stats.rtt_estimate().is_none());
1558
1559 stats.update_rtt(Duration::from_millis(100));
1560 assert_eq!(stats.rtt_estimate(), Some(Duration::from_millis(100)));
1561
1562 stats.update_rtt(Duration::from_millis(200));
1564 let rtt = stats.rtt_estimate().unwrap();
1566 assert!(rtt.as_millis() >= 110 && rtt.as_millis() <= 130);
1567 }
1568
1569 #[test]
1570 fn test_link_stats_time_since_recv() {
1571 let mut stats = LinkStats::new();
1572
1573 assert_eq!(stats.time_since_recv(1000), u64::MAX);
1575
1576 stats.record_recv(100, 500);
1577 assert_eq!(stats.time_since_recv(1000), 500);
1578 assert_eq!(stats.time_since_recv(500), 0);
1579 }
1580
1581 #[test]
1582 fn test_link_creation() {
1583 let link = Link::new(
1584 LinkId::new(1),
1585 TransportId::new(1),
1586 TransportAddr::from_string("test"),
1587 LinkDirection::Outbound,
1588 Duration::from_millis(50),
1589 );
1590
1591 assert_eq!(link.state(), LinkState::Connecting);
1592 assert!(!link.is_operational());
1593 assert_eq!(link.direction(), LinkDirection::Outbound);
1594 }
1595
1596 #[test]
1597 fn test_link_connectionless() {
1598 let link = Link::connectionless(
1599 LinkId::new(1),
1600 TransportId::new(1),
1601 TransportAddr::from_string("test"),
1602 LinkDirection::Inbound,
1603 Duration::from_millis(5),
1604 );
1605
1606 assert_eq!(link.state(), LinkState::Connected);
1607 assert!(link.is_operational());
1608 }
1609
1610 #[test]
1611 fn test_link_state_changes() {
1612 let mut link = Link::new(
1613 LinkId::new(1),
1614 TransportId::new(1),
1615 TransportAddr::from_string("test"),
1616 LinkDirection::Outbound,
1617 Duration::from_millis(50),
1618 );
1619
1620 assert!(!link.is_operational());
1621
1622 link.set_connected();
1623 assert!(link.is_operational());
1624 assert!(!link.is_terminal());
1625
1626 link.set_disconnected();
1627 assert!(!link.is_operational());
1628 assert!(link.is_terminal());
1629 }
1630
1631 #[test]
1632 fn test_link_effective_rtt() {
1633 let mut link = Link::connectionless(
1634 LinkId::new(1),
1635 TransportId::new(1),
1636 TransportAddr::from_string("test"),
1637 LinkDirection::Inbound,
1638 Duration::from_millis(50),
1639 );
1640
1641 assert_eq!(link.effective_rtt(), Duration::from_millis(50));
1643
1644 link.stats_mut().update_rtt(Duration::from_millis(100));
1646 assert_eq!(link.effective_rtt(), Duration::from_millis(100));
1647 }
1648
1649 #[test]
1650 fn test_link_age() {
1651 let mut link = Link::new(
1652 LinkId::new(1),
1653 TransportId::new(1),
1654 TransportAddr::from_string("test"),
1655 LinkDirection::Outbound,
1656 Duration::from_millis(50),
1657 );
1658
1659 assert_eq!(link.age(1000), 0);
1661
1662 link.set_created_at(500);
1663 assert_eq!(link.age(1000), 500);
1664 assert_eq!(link.age(500), 0);
1665 }
1666
1667 #[test]
1668 fn test_discovered_peer() {
1669 let peer = DiscoveredPeer::new(
1670 TransportId::new(1),
1671 TransportAddr::from_string("192.168.1.1:2121"),
1672 );
1673
1674 assert_eq!(peer.transport_id, TransportId::new(1));
1675 assert!(peer.pubkey_hint.is_none());
1676 }
1677
1678 #[test]
1679 fn test_link_direction_display() {
1680 assert_eq!(format!("{}", LinkDirection::Outbound), "outbound");
1681 assert_eq!(format!("{}", LinkDirection::Inbound), "inbound");
1682 }
1683
1684 #[test]
1685 fn test_transport_state_display() {
1686 assert_eq!(format!("{}", TransportState::Up), "up");
1687 assert_eq!(format!("{}", TransportState::Failed), "failed");
1688 }
1689
1690 #[test]
1691 fn test_received_packet() {
1692 let packet = ReceivedPacket::new(
1693 TransportId::new(1),
1694 TransportAddr::from_string("192.168.1.1:2121"),
1695 vec![1, 2, 3, 4],
1696 );
1697
1698 assert_eq!(packet.transport_id, TransportId::new(1));
1699 assert_eq!(packet.data, vec![1, 2, 3, 4]);
1700 assert!(packet.timestamp_ms > 0);
1701 }
1702
1703 #[test]
1704 fn test_received_packet_with_timestamp() {
1705 let packet = ReceivedPacket::with_timestamp(
1706 TransportId::new(1),
1707 TransportAddr::from_string("test"),
1708 vec![5, 6],
1709 12345,
1710 );
1711
1712 assert_eq!(packet.timestamp_ms, 12345);
1713 }
1714
1715 #[tokio::test]
1716 async fn test_packet_channel() {
1717 let (tx, mut rx) = packet_channel(10);
1718
1719 let packet = ReceivedPacket::new(
1720 TransportId::new(1),
1721 TransportAddr::from_string("test"),
1722 vec![1, 2, 3],
1723 );
1724
1725 tx.send(packet.clone()).unwrap();
1726
1727 let received = rx.recv().await.unwrap();
1728 assert_eq!(received.data, vec![1, 2, 3]);
1729 }
1730
1731 struct MockTransport {
1737 id: TransportId,
1738 mtu_value: u16,
1739 }
1740
1741 impl MockTransport {
1742 fn new(mtu: u16) -> Self {
1743 Self {
1744 id: TransportId::new(99),
1745 mtu_value: mtu,
1746 }
1747 }
1748 }
1749
1750 impl Transport for MockTransport {
1751 fn transport_id(&self) -> TransportId {
1752 self.id
1753 }
1754 fn transport_type(&self) -> &TransportType {
1755 &TransportType::UDP
1756 }
1757 fn state(&self) -> TransportState {
1758 TransportState::Up
1759 }
1760 fn mtu(&self) -> u16 {
1761 self.mtu_value
1762 }
1763 fn start(&mut self) -> Result<(), TransportError> {
1764 Ok(())
1765 }
1766 fn stop(&mut self) -> Result<(), TransportError> {
1767 Ok(())
1768 }
1769 fn send(&self, _addr: &TransportAddr, _data: &[u8]) -> Result<(), TransportError> {
1770 Ok(())
1771 }
1772 fn discover(&self) -> Result<Vec<DiscoveredPeer>, TransportError> {
1773 Ok(vec![])
1774 }
1775 }
1776
1777 struct PerLinkMtuTransport {
1779 id: TransportId,
1780 default_mtu: u16,
1781 overrides: Vec<(TransportAddr, u16)>,
1783 }
1784
1785 impl PerLinkMtuTransport {
1786 fn new(default_mtu: u16, overrides: Vec<(TransportAddr, u16)>) -> Self {
1787 Self {
1788 id: TransportId::new(100),
1789 default_mtu,
1790 overrides,
1791 }
1792 }
1793 }
1794
1795 impl Transport for PerLinkMtuTransport {
1796 fn transport_id(&self) -> TransportId {
1797 self.id
1798 }
1799 fn transport_type(&self) -> &TransportType {
1800 &TransportType::UDP
1801 }
1802 fn state(&self) -> TransportState {
1803 TransportState::Up
1804 }
1805 fn mtu(&self) -> u16 {
1806 self.default_mtu
1807 }
1808 fn link_mtu(&self, addr: &TransportAddr) -> u16 {
1809 for (a, mtu) in &self.overrides {
1810 if a == addr {
1811 return *mtu;
1812 }
1813 }
1814 self.mtu()
1815 }
1816 fn start(&mut self) -> Result<(), TransportError> {
1817 Ok(())
1818 }
1819 fn stop(&mut self) -> Result<(), TransportError> {
1820 Ok(())
1821 }
1822 fn send(&self, _addr: &TransportAddr, _data: &[u8]) -> Result<(), TransportError> {
1823 Ok(())
1824 }
1825 fn discover(&self) -> Result<Vec<DiscoveredPeer>, TransportError> {
1826 Ok(vec![])
1827 }
1828 }
1829
1830 #[test]
1831 fn test_link_mtu_default_falls_back_to_mtu() {
1832 let transport = MockTransport::new(1280);
1833 let addr = TransportAddr::from_string("192.168.1.1:2121");
1834
1835 assert_eq!(transport.link_mtu(&addr), 1280);
1837 assert_eq!(transport.link_mtu(&addr), transport.mtu());
1838
1839 let other_addr = TransportAddr::from_string("10.0.0.1:5000");
1841 assert_eq!(transport.link_mtu(&other_addr), 1280);
1842 }
1843
1844 #[test]
1845 fn test_link_mtu_per_link_override() {
1846 let addr_a = TransportAddr::from_string("192.168.1.1:2121");
1847 let addr_b = TransportAddr::from_string("10.0.0.1:5000");
1848 let addr_unknown = TransportAddr::from_string("172.16.0.1:6000");
1849
1850 let transport =
1851 PerLinkMtuTransport::new(1280, vec![(addr_a.clone(), 512), (addr_b.clone(), 247)]);
1852
1853 assert_eq!(transport.link_mtu(&addr_a), 512);
1855 assert_eq!(transport.link_mtu(&addr_b), 247);
1856
1857 assert_eq!(transport.link_mtu(&addr_unknown), 1280);
1859 assert_eq!(transport.mtu(), 1280);
1860 }
1861
1862 #[test]
1863 fn test_transport_handle_link_mtu_delegation() {
1864 use crate::config::UdpConfig;
1865 use crate::transport::udp::UdpTransport;
1866
1867 let config = UdpConfig::default();
1868 let expected_mtu = config.mtu();
1869 let (tx, _rx) = packet_channel(1);
1870 let transport = UdpTransport::new(TransportId::new(1), None, config, tx);
1871 let handle = TransportHandle::Udp(transport);
1872
1873 let addr = TransportAddr::from_string("192.168.1.1:2121");
1874
1875 assert_eq!(handle.link_mtu(&addr), expected_mtu);
1878 assert_eq!(handle.link_mtu(&addr), handle.mtu());
1879 }
1880}