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
220#[derive(Clone, Debug, PartialEq, Eq)]
226pub struct TransportType {
227 pub name: &'static str,
229 pub connection_oriented: bool,
231 pub reliable: bool,
233}
234
235impl TransportType {
236 pub const UDP: TransportType = TransportType {
238 name: "udp",
239 connection_oriented: false,
240 reliable: false,
241 };
242
243 pub const TCP: TransportType = TransportType {
245 name: "tcp",
246 connection_oriented: true,
247 reliable: true,
248 };
249
250 pub const ETHERNET: TransportType = TransportType {
252 name: "ethernet",
253 connection_oriented: false,
254 reliable: false,
255 };
256
257 pub const WIFI: TransportType = TransportType {
259 name: "wifi",
260 connection_oriented: false,
261 reliable: false,
262 };
263
264 pub const TOR: TransportType = TransportType {
266 name: "tor",
267 connection_oriented: true,
268 reliable: true,
269 };
270
271 pub const SERIAL: TransportType = TransportType {
273 name: "serial",
274 connection_oriented: false,
275 reliable: true, };
277
278 pub const BLE: TransportType = TransportType {
280 name: "ble",
281 connection_oriented: true,
282 reliable: true, };
284
285 pub const WEBRTC: TransportType = TransportType {
287 name: "webrtc",
288 connection_oriented: true,
289 reliable: false,
290 };
291
292 #[cfg(feature = "sim-transport")]
294 pub const SIM: TransportType = TransportType {
295 name: "sim",
296 connection_oriented: false,
297 reliable: false,
298 };
299
300 pub fn is_connectionless(&self) -> bool {
302 !self.connection_oriented
303 }
304}
305
306impl fmt::Display for TransportType {
307 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
308 write!(f, "{}", self.name)
309 }
310}
311
312#[derive(Clone, Copy, Debug, PartialEq, Eq)]
318pub enum TransportState {
319 Configured,
321 Starting,
323 Up,
325 Down,
327 Failed,
329}
330
331impl TransportState {
332 pub fn is_operational(&self) -> bool {
334 matches!(self, TransportState::Up)
335 }
336
337 pub fn can_start(&self) -> bool {
339 matches!(
340 self,
341 TransportState::Configured | TransportState::Down | TransportState::Failed
342 )
343 }
344
345 pub fn is_terminal(&self) -> bool {
347 matches!(self, TransportState::Failed)
348 }
349}
350
351impl fmt::Display for TransportState {
352 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
353 let s = match self {
354 TransportState::Configured => "configured",
355 TransportState::Starting => "starting",
356 TransportState::Up => "up",
357 TransportState::Down => "down",
358 TransportState::Failed => "failed",
359 };
360 write!(f, "{}", s)
361 }
362}
363
364#[derive(Clone, Copy, Debug, PartialEq, Eq)]
370pub enum LinkState {
371 Connecting,
373 Connected,
375 Disconnected,
377 Failed,
379}
380
381impl LinkState {
382 pub fn is_operational(&self) -> bool {
384 matches!(self, LinkState::Connected)
385 }
386
387 pub fn is_terminal(&self) -> bool {
389 matches!(self, LinkState::Disconnected | LinkState::Failed)
390 }
391}
392
393impl fmt::Display for LinkState {
394 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
395 let s = match self {
396 LinkState::Connecting => "connecting",
397 LinkState::Connected => "connected",
398 LinkState::Disconnected => "disconnected",
399 LinkState::Failed => "failed",
400 };
401 write!(f, "{}", s)
402 }
403}
404
405#[derive(Clone, Copy, Debug, PartialEq, Eq)]
407pub enum LinkDirection {
408 Outbound,
410 Inbound,
412}
413
414impl fmt::Display for LinkDirection {
415 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
416 let s = match self {
417 LinkDirection::Outbound => "outbound",
418 LinkDirection::Inbound => "inbound",
419 };
420 write!(f, "{}", s)
421 }
422}
423
424#[derive(Clone, PartialEq, Eq, Hash)]
434pub struct TransportAddr(Vec<u8>);
435
436impl TransportAddr {
437 pub fn new(bytes: Vec<u8>) -> Self {
439 Self(bytes)
440 }
441
442 pub fn from_bytes(bytes: &[u8]) -> Self {
444 Self(bytes.to_vec())
445 }
446
447 pub fn from_string(s: &str) -> Self {
449 Self(s.as_bytes().to_vec())
450 }
451
452 pub fn from_socket_addr(addr: std::net::SocketAddr) -> Self {
464 use std::io::Write;
465 let mut buf = Vec::with_capacity(56);
469 write!(&mut buf, "{addr}").expect("Vec<u8>::write_fmt is infallible");
473 Self(buf)
474 }
475
476 pub fn as_bytes(&self) -> &[u8] {
478 &self.0
479 }
480
481 pub fn as_str(&self) -> Option<&str> {
483 std::str::from_utf8(&self.0).ok()
484 }
485
486 pub fn len(&self) -> usize {
488 self.0.len()
489 }
490
491 pub fn is_empty(&self) -> bool {
493 self.0.is_empty()
494 }
495}
496
497impl fmt::Debug for TransportAddr {
498 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
499 match self.as_str() {
500 Some(s) => write!(f, "TransportAddr(\"{}\")", s),
501 None => write!(f, "TransportAddr({:?})", self.0),
502 }
503 }
504}
505
506impl fmt::Display for TransportAddr {
507 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
508 match self.as_str() {
510 Some(s) => write!(f, "{}", s),
511 None => {
512 for byte in &self.0 {
513 write!(f, "{:02x}", byte)?;
514 }
515 Ok(())
516 }
517 }
518 }
519}
520
521impl From<&str> for TransportAddr {
522 fn from(s: &str) -> Self {
523 Self::from_string(s)
524 }
525}
526
527impl From<String> for TransportAddr {
528 fn from(s: String) -> Self {
529 Self(s.into_bytes())
530 }
531}
532
533#[derive(Clone, Debug, Default)]
539pub struct LinkStats {
540 pub packets_sent: u64,
542 pub packets_recv: u64,
544 pub bytes_sent: u64,
546 pub bytes_recv: u64,
548 pub last_recv_ms: u64,
550 rtt_estimate: Option<Duration>,
552 pub loss_rate: f32,
554 pub throughput_estimate: u64,
556}
557
558impl LinkStats {
559 pub fn new() -> Self {
561 Self::default()
562 }
563
564 pub fn record_sent(&mut self, bytes: usize) {
566 self.packets_sent += 1;
567 self.bytes_sent += bytes as u64;
568 }
569
570 pub fn record_recv(&mut self, bytes: usize, timestamp_ms: u64) {
572 self.packets_recv += 1;
573 self.bytes_recv += bytes as u64;
574 self.last_recv_ms = timestamp_ms;
575 }
576
577 pub fn rtt_estimate(&self) -> Option<Duration> {
579 self.rtt_estimate
580 }
581
582 pub fn update_rtt(&mut self, rtt: Duration) {
586 match self.rtt_estimate {
587 Some(old_rtt) => {
588 let alpha = 0.2;
589 let new_rtt_nanos = (alpha * rtt.as_nanos() as f64
590 + (1.0 - alpha) * old_rtt.as_nanos() as f64)
591 as u64;
592 self.rtt_estimate = Some(Duration::from_nanos(new_rtt_nanos));
593 }
594 None => {
595 self.rtt_estimate = Some(rtt);
596 }
597 }
598 }
599
600 pub fn time_since_recv(&self, current_time_ms: u64) -> u64 {
602 if self.last_recv_ms == 0 {
603 return u64::MAX;
604 }
605 current_time_ms.saturating_sub(self.last_recv_ms)
606 }
607
608 pub fn reset(&mut self) {
610 *self = Self::default();
611 }
612}
613
614#[derive(Clone, Debug)]
620pub struct Link {
621 link_id: LinkId,
623 transport_id: TransportId,
625 remote_addr: TransportAddr,
627 direction: LinkDirection,
629 state: LinkState,
631 base_rtt: Duration,
633 stats: LinkStats,
635 created_at: u64,
637}
638
639impl Link {
640 pub fn new(
642 link_id: LinkId,
643 transport_id: TransportId,
644 remote_addr: TransportAddr,
645 direction: LinkDirection,
646 base_rtt: Duration,
647 ) -> Self {
648 Self {
649 link_id,
650 transport_id,
651 remote_addr,
652 direction,
653 state: LinkState::Connecting,
654 base_rtt,
655 stats: LinkStats::new(),
656 created_at: 0,
657 }
658 }
659
660 pub fn new_with_timestamp(
662 link_id: LinkId,
663 transport_id: TransportId,
664 remote_addr: TransportAddr,
665 direction: LinkDirection,
666 base_rtt: Duration,
667 created_at: u64,
668 ) -> Self {
669 let mut link = Self::new(link_id, transport_id, remote_addr, direction, base_rtt);
670 link.created_at = created_at;
671 link
672 }
673
674 pub fn connectionless(
679 link_id: LinkId,
680 transport_id: TransportId,
681 remote_addr: TransportAddr,
682 direction: LinkDirection,
683 base_rtt: Duration,
684 ) -> Self {
685 let mut link = Self::new(link_id, transport_id, remote_addr, direction, base_rtt);
686 link.state = LinkState::Connected;
687 link
688 }
689
690 pub fn link_id(&self) -> LinkId {
692 self.link_id
693 }
694
695 pub fn transport_id(&self) -> TransportId {
697 self.transport_id
698 }
699
700 pub fn remote_addr(&self) -> &TransportAddr {
702 &self.remote_addr
703 }
704
705 pub fn direction(&self) -> LinkDirection {
707 self.direction
708 }
709
710 pub fn state(&self) -> LinkState {
712 self.state
713 }
714
715 pub fn base_rtt(&self) -> Duration {
717 self.base_rtt
718 }
719
720 pub fn stats(&self) -> &LinkStats {
722 &self.stats
723 }
724
725 pub fn stats_mut(&mut self) -> &mut LinkStats {
727 &mut self.stats
728 }
729
730 pub fn created_at(&self) -> u64 {
732 self.created_at
733 }
734
735 pub fn set_created_at(&mut self, timestamp: u64) {
737 self.created_at = timestamp;
738 }
739
740 pub fn set_connected(&mut self) {
742 self.state = LinkState::Connected;
743 }
744
745 pub fn set_disconnected(&mut self) {
747 self.state = LinkState::Disconnected;
748 }
749
750 pub fn set_failed(&mut self) {
752 self.state = LinkState::Failed;
753 }
754
755 pub fn is_operational(&self) -> bool {
757 self.state.is_operational()
758 }
759
760 pub fn is_terminal(&self) -> bool {
762 self.state.is_terminal()
763 }
764
765 pub fn effective_rtt(&self) -> Duration {
767 self.stats.rtt_estimate().unwrap_or(self.base_rtt)
768 }
769
770 pub fn age(&self, current_time_ms: u64) -> u64 {
772 if self.created_at == 0 {
773 return 0;
774 }
775 current_time_ms.saturating_sub(self.created_at)
776 }
777}
778
779#[derive(Clone, Debug)]
785pub struct DiscoveredPeer {
786 pub transport_id: TransportId,
788 pub addr: TransportAddr,
790 pub pubkey_hint: Option<XOnlyPublicKey>,
792}
793
794impl DiscoveredPeer {
795 pub fn new(transport_id: TransportId, addr: TransportAddr) -> Self {
797 Self {
798 transport_id,
799 addr,
800 pubkey_hint: None,
801 }
802 }
803
804 pub fn with_hint(
806 transport_id: TransportId,
807 addr: TransportAddr,
808 pubkey: XOnlyPublicKey,
809 ) -> Self {
810 Self {
811 transport_id,
812 addr,
813 pubkey_hint: Some(pubkey),
814 }
815 }
816}
817
818pub trait Transport {
827 fn transport_id(&self) -> TransportId;
829
830 fn transport_type(&self) -> &TransportType;
832
833 fn state(&self) -> TransportState;
835
836 fn mtu(&self) -> u16;
838
839 fn link_mtu(&self, addr: &TransportAddr) -> u16 {
845 let _ = addr;
846 self.mtu()
847 }
848
849 fn start(&mut self) -> Result<(), TransportError>;
851
852 fn stop(&mut self) -> Result<(), TransportError>;
854
855 fn send(&self, addr: &TransportAddr, data: &[u8]) -> Result<(), TransportError>;
857
858 fn discover(&self) -> Result<Vec<DiscoveredPeer>, TransportError>;
860
861 fn auto_connect(&self) -> bool {
864 false
865 }
866
867 fn accept_connections(&self) -> bool {
870 true
871 }
872
873 fn close_connection(&self, _addr: &TransportAddr) {
879 }
881}
882
883#[derive(Clone, Debug, PartialEq, Eq)]
892pub enum ConnectionState {
893 None,
895 Connecting,
897 Connected,
899 Failed(String),
901}
902
903#[derive(Clone, Debug, Default)]
912pub struct TransportCongestion {
913 pub recv_drops: Option<u64>,
916}
917
918pub enum TransportHandle {
927 Udp(UdpTransport),
929 #[cfg(feature = "sim-transport")]
931 Sim(SimTransport),
932 #[cfg(any(target_os = "linux", target_os = "macos"))]
934 Ethernet(EthernetTransport),
935 Tcp(TcpTransport),
937 Tor(TorTransport),
939 #[cfg(feature = "webrtc-transport")]
941 WebRtc(Box<WebRtcTransport>),
942 #[cfg(target_os = "linux")]
944 Ble(DefaultBleTransport),
945}
946
947impl TransportHandle {
948 pub async fn start(&mut self) -> Result<(), TransportError> {
950 match self {
951 TransportHandle::Udp(t) => t.start_async().await,
952 #[cfg(feature = "sim-transport")]
953 TransportHandle::Sim(t) => t.start_async().await,
954 #[cfg(any(target_os = "linux", target_os = "macos"))]
955 TransportHandle::Ethernet(t) => t.start_async().await,
956 TransportHandle::Tcp(t) => t.start_async().await,
957 TransportHandle::Tor(t) => t.start_async().await,
958 #[cfg(feature = "webrtc-transport")]
959 TransportHandle::WebRtc(t) => t.start_async().await,
960 #[cfg(target_os = "linux")]
961 TransportHandle::Ble(t) => t.start_async().await,
962 }
963 }
964
965 pub async fn stop(&mut self) -> Result<(), TransportError> {
967 match self {
968 TransportHandle::Udp(t) => t.stop_async().await,
969 #[cfg(feature = "sim-transport")]
970 TransportHandle::Sim(t) => t.stop_async().await,
971 #[cfg(any(target_os = "linux", target_os = "macos"))]
972 TransportHandle::Ethernet(t) => t.stop_async().await,
973 TransportHandle::Tcp(t) => t.stop_async().await,
974 TransportHandle::Tor(t) => t.stop_async().await,
975 #[cfg(feature = "webrtc-transport")]
976 TransportHandle::WebRtc(t) => t.stop_async().await,
977 #[cfg(target_os = "linux")]
978 TransportHandle::Ble(t) => t.stop_async().await,
979 }
980 }
981
982 pub async fn send(&self, addr: &TransportAddr, data: &[u8]) -> Result<usize, TransportError> {
984 match self {
985 TransportHandle::Udp(t) => t.send_async(addr, data).await,
986 #[cfg(feature = "sim-transport")]
987 TransportHandle::Sim(t) => t.send_async(addr, data).await,
988 #[cfg(any(target_os = "linux", target_os = "macos"))]
989 TransportHandle::Ethernet(t) => t.send_async(addr, data).await,
990 TransportHandle::Tcp(t) => t.send_async(addr, data).await,
991 TransportHandle::Tor(t) => t.send_async(addr, data).await,
992 #[cfg(feature = "webrtc-transport")]
993 TransportHandle::WebRtc(t) => t.send_async(addr, data).await,
994 #[cfg(target_os = "linux")]
995 TransportHandle::Ble(t) => t.send_async(addr, data).await,
996 }
997 }
998
999 pub async fn flush_pending_send(&self) {
1005 if let TransportHandle::Udp(t) = self {
1006 t.flush_pending_send().await;
1007 }
1008 }
1009
1010 pub fn transport_id(&self) -> TransportId {
1012 match self {
1013 TransportHandle::Udp(t) => t.transport_id(),
1014 #[cfg(feature = "sim-transport")]
1015 TransportHandle::Sim(t) => t.transport_id(),
1016 #[cfg(any(target_os = "linux", target_os = "macos"))]
1017 TransportHandle::Ethernet(t) => t.transport_id(),
1018 TransportHandle::Tcp(t) => t.transport_id(),
1019 TransportHandle::Tor(t) => t.transport_id(),
1020 #[cfg(feature = "webrtc-transport")]
1021 TransportHandle::WebRtc(t) => t.transport_id(),
1022 #[cfg(target_os = "linux")]
1023 TransportHandle::Ble(t) => t.transport_id(),
1024 }
1025 }
1026
1027 pub fn name(&self) -> Option<&str> {
1029 match self {
1030 TransportHandle::Udp(t) => t.name(),
1031 #[cfg(feature = "sim-transport")]
1032 TransportHandle::Sim(t) => t.name(),
1033 #[cfg(any(target_os = "linux", target_os = "macos"))]
1034 TransportHandle::Ethernet(t) => t.name(),
1035 TransportHandle::Tcp(t) => t.name(),
1036 TransportHandle::Tor(t) => t.name(),
1037 #[cfg(feature = "webrtc-transport")]
1038 TransportHandle::WebRtc(t) => t.name(),
1039 #[cfg(target_os = "linux")]
1040 TransportHandle::Ble(t) => t.name(),
1041 }
1042 }
1043
1044 pub fn transport_type(&self) -> &TransportType {
1046 match self {
1047 TransportHandle::Udp(t) => t.transport_type(),
1048 #[cfg(feature = "sim-transport")]
1049 TransportHandle::Sim(t) => t.transport_type(),
1050 #[cfg(any(target_os = "linux", target_os = "macos"))]
1051 TransportHandle::Ethernet(t) => t.transport_type(),
1052 TransportHandle::Tcp(t) => t.transport_type(),
1053 TransportHandle::Tor(t) => t.transport_type(),
1054 #[cfg(feature = "webrtc-transport")]
1055 TransportHandle::WebRtc(t) => t.transport_type(),
1056 #[cfg(target_os = "linux")]
1057 TransportHandle::Ble(t) => t.transport_type(),
1058 }
1059 }
1060
1061 pub fn state(&self) -> TransportState {
1063 match self {
1064 TransportHandle::Udp(t) => t.state(),
1065 #[cfg(feature = "sim-transport")]
1066 TransportHandle::Sim(t) => t.state(),
1067 #[cfg(any(target_os = "linux", target_os = "macos"))]
1068 TransportHandle::Ethernet(t) => t.state(),
1069 TransportHandle::Tcp(t) => t.state(),
1070 TransportHandle::Tor(t) => t.state(),
1071 #[cfg(feature = "webrtc-transport")]
1072 TransportHandle::WebRtc(t) => t.state(),
1073 #[cfg(target_os = "linux")]
1074 TransportHandle::Ble(t) => t.state(),
1075 }
1076 }
1077
1078 pub fn mtu(&self) -> u16 {
1080 match self {
1081 TransportHandle::Udp(t) => t.mtu(),
1082 #[cfg(feature = "sim-transport")]
1083 TransportHandle::Sim(t) => t.mtu(),
1084 #[cfg(any(target_os = "linux", target_os = "macos"))]
1085 TransportHandle::Ethernet(t) => t.mtu(),
1086 TransportHandle::Tcp(t) => t.mtu(),
1087 TransportHandle::Tor(t) => t.mtu(),
1088 #[cfg(feature = "webrtc-transport")]
1089 TransportHandle::WebRtc(t) => t.mtu(),
1090 #[cfg(target_os = "linux")]
1091 TransportHandle::Ble(t) => t.mtu(),
1092 }
1093 }
1094
1095 pub fn link_mtu(&self, addr: &TransportAddr) -> u16 {
1100 match self {
1101 TransportHandle::Udp(t) => t.link_mtu(addr),
1102 #[cfg(feature = "sim-transport")]
1103 TransportHandle::Sim(t) => t.link_mtu(addr),
1104 #[cfg(any(target_os = "linux", target_os = "macos"))]
1105 TransportHandle::Ethernet(t) => t.link_mtu(addr),
1106 TransportHandle::Tcp(t) => t.link_mtu(addr),
1107 TransportHandle::Tor(t) => t.link_mtu(addr),
1108 #[cfg(feature = "webrtc-transport")]
1109 TransportHandle::WebRtc(t) => t.link_mtu(addr),
1110 #[cfg(target_os = "linux")]
1111 TransportHandle::Ble(t) => t.link_mtu(addr),
1112 }
1113 }
1114
1115 pub fn local_addr(&self) -> Option<std::net::SocketAddr> {
1117 match self {
1118 TransportHandle::Udp(t) => t.local_addr(),
1119 #[cfg(feature = "sim-transport")]
1120 TransportHandle::Sim(_) => None,
1121 #[cfg(any(target_os = "linux", target_os = "macos"))]
1122 TransportHandle::Ethernet(_) => None,
1123 TransportHandle::Tcp(t) => t.local_addr(),
1124 TransportHandle::Tor(_) => None,
1125 #[cfg(feature = "webrtc-transport")]
1126 TransportHandle::WebRtc(_) => None,
1127 #[cfg(target_os = "linux")]
1128 TransportHandle::Ble(_) => None,
1129 }
1130 }
1131
1132 pub fn interface_name(&self) -> Option<&str> {
1134 match self {
1135 TransportHandle::Udp(_) => None,
1136 #[cfg(feature = "sim-transport")]
1137 TransportHandle::Sim(_) => None,
1138 #[cfg(any(target_os = "linux", target_os = "macos"))]
1139 TransportHandle::Ethernet(t) => Some(t.interface_name()),
1140 TransportHandle::Tcp(_) => None,
1141 TransportHandle::Tor(_) => None,
1142 #[cfg(feature = "webrtc-transport")]
1143 TransportHandle::WebRtc(_) => None,
1144 #[cfg(target_os = "linux")]
1145 TransportHandle::Ble(_) => None,
1146 }
1147 }
1148
1149 pub fn onion_address(&self) -> Option<&str> {
1151 match self {
1152 TransportHandle::Tor(t) => t.onion_address(),
1153 _ => None,
1154 }
1155 }
1156
1157 pub fn tor_monitoring(&self) -> Option<TorMonitoringInfo> {
1159 match self {
1160 TransportHandle::Tor(t) => t.cached_monitoring(),
1161 _ => None,
1162 }
1163 }
1164
1165 pub fn tor_mode(&self) -> Option<&str> {
1167 match self {
1168 TransportHandle::Tor(t) => Some(t.mode()),
1169 _ => None,
1170 }
1171 }
1172
1173 pub fn discover(&self) -> Result<Vec<DiscoveredPeer>, TransportError> {
1175 match self {
1176 TransportHandle::Udp(t) => t.discover(),
1177 #[cfg(feature = "sim-transport")]
1178 TransportHandle::Sim(t) => t.discover(),
1179 #[cfg(any(target_os = "linux", target_os = "macos"))]
1180 TransportHandle::Ethernet(t) => t.discover(),
1181 TransportHandle::Tcp(t) => t.discover(),
1182 TransportHandle::Tor(t) => t.discover(),
1183 #[cfg(feature = "webrtc-transport")]
1184 TransportHandle::WebRtc(t) => t.discover(),
1185 #[cfg(target_os = "linux")]
1186 TransportHandle::Ble(t) => t.discover(),
1187 }
1188 }
1189
1190 pub fn auto_connect(&self) -> bool {
1192 match self {
1193 TransportHandle::Udp(t) => t.auto_connect(),
1194 #[cfg(feature = "sim-transport")]
1195 TransportHandle::Sim(t) => t.auto_connect(),
1196 #[cfg(any(target_os = "linux", target_os = "macos"))]
1197 TransportHandle::Ethernet(t) => t.auto_connect(),
1198 TransportHandle::Tcp(t) => t.auto_connect(),
1199 TransportHandle::Tor(t) => t.auto_connect(),
1200 #[cfg(feature = "webrtc-transport")]
1201 TransportHandle::WebRtc(t) => t.auto_connect(),
1202 #[cfg(target_os = "linux")]
1203 TransportHandle::Ble(t) => t.auto_connect(),
1204 }
1205 }
1206
1207 pub fn accept_connections(&self) -> bool {
1209 match self {
1210 TransportHandle::Udp(t) => t.accept_connections(),
1211 #[cfg(feature = "sim-transport")]
1212 TransportHandle::Sim(t) => t.accept_connections(),
1213 #[cfg(any(target_os = "linux", target_os = "macos"))]
1214 TransportHandle::Ethernet(t) => t.accept_connections(),
1215 TransportHandle::Tcp(t) => t.accept_connections(),
1216 TransportHandle::Tor(t) => t.accept_connections(),
1217 #[cfg(feature = "webrtc-transport")]
1218 TransportHandle::WebRtc(t) => t.accept_connections(),
1219 #[cfg(target_os = "linux")]
1220 TransportHandle::Ble(t) => t.accept_connections(),
1221 }
1222 }
1223
1224 pub async fn connect(&self, addr: &TransportAddr) -> Result<(), TransportError> {
1232 match self {
1233 TransportHandle::Udp(_) => Ok(()), #[cfg(feature = "sim-transport")]
1235 TransportHandle::Sim(_) => Ok(()), #[cfg(any(target_os = "linux", target_os = "macos"))]
1237 TransportHandle::Ethernet(_) => Ok(()), TransportHandle::Tcp(t) => t.connect_async(addr).await,
1239 TransportHandle::Tor(t) => t.connect_async(addr).await,
1240 #[cfg(feature = "webrtc-transport")]
1241 TransportHandle::WebRtc(t) => t.connect_async(addr).await,
1242 #[cfg(target_os = "linux")]
1243 TransportHandle::Ble(t) => t.connect_async(addr).await,
1244 }
1245 }
1246
1247 pub fn connection_state(&self, addr: &TransportAddr) -> ConnectionState {
1253 match self {
1254 TransportHandle::Udp(_) => ConnectionState::Connected,
1255 #[cfg(feature = "sim-transport")]
1256 TransportHandle::Sim(_) => ConnectionState::Connected,
1257 #[cfg(any(target_os = "linux", target_os = "macos"))]
1258 TransportHandle::Ethernet(_) => ConnectionState::Connected,
1259 TransportHandle::Tcp(t) => t.connection_state_sync(addr),
1260 TransportHandle::Tor(t) => t.connection_state_sync(addr),
1261 #[cfg(feature = "webrtc-transport")]
1262 TransportHandle::WebRtc(t) => t.connection_state_sync(addr),
1263 #[cfg(target_os = "linux")]
1264 TransportHandle::Ble(t) => t.connection_state_sync(addr),
1265 }
1266 }
1267
1268 pub async fn close_connection(&self, addr: &TransportAddr) {
1273 match self {
1274 TransportHandle::Udp(t) => t.close_connection(addr),
1275 #[cfg(feature = "sim-transport")]
1276 TransportHandle::Sim(t) => t.close_connection(addr),
1277 #[cfg(any(target_os = "linux", target_os = "macos"))]
1278 TransportHandle::Ethernet(t) => t.close_connection(addr),
1279 TransportHandle::Tcp(t) => t.close_connection_async(addr).await,
1280 TransportHandle::Tor(t) => t.close_connection_async(addr).await,
1281 #[cfg(feature = "webrtc-transport")]
1282 TransportHandle::WebRtc(t) => t.close_connection_async(addr).await,
1283 #[cfg(target_os = "linux")]
1284 TransportHandle::Ble(t) => t.close_connection_async(addr).await,
1285 }
1286 }
1287
1288 pub fn is_operational(&self) -> bool {
1290 self.state().is_operational()
1291 }
1292
1293 pub fn congestion(&self) -> TransportCongestion {
1299 match self {
1300 TransportHandle::Udp(t) => t.congestion(),
1301 #[cfg(feature = "sim-transport")]
1302 TransportHandle::Sim(_) => TransportCongestion::default(),
1303 #[cfg(any(target_os = "linux", target_os = "macos"))]
1304 TransportHandle::Ethernet(_) => TransportCongestion::default(),
1305 TransportHandle::Tcp(_) => TransportCongestion::default(),
1306 TransportHandle::Tor(_) => TransportCongestion::default(),
1307 #[cfg(feature = "webrtc-transport")]
1308 TransportHandle::WebRtc(_) => TransportCongestion::default(),
1309 #[cfg(target_os = "linux")]
1310 TransportHandle::Ble(_) => TransportCongestion::default(),
1311 }
1312 }
1313
1314 pub fn transport_stats(&self) -> serde_json::Value {
1318 match self {
1319 TransportHandle::Udp(t) => {
1320 serde_json::to_value(t.stats().snapshot()).unwrap_or_default()
1321 }
1322 #[cfg(feature = "sim-transport")]
1323 TransportHandle::Sim(t) => serde_json::to_value(t.stats()).unwrap_or_default(),
1324 #[cfg(any(target_os = "linux", target_os = "macos"))]
1325 TransportHandle::Ethernet(t) => {
1326 let snap = t.stats().snapshot();
1327 serde_json::json!({
1328 "frames_sent": snap.frames_sent,
1329 "frames_recv": snap.frames_recv,
1330 "bytes_sent": snap.bytes_sent,
1331 "bytes_recv": snap.bytes_recv,
1332 "send_errors": snap.send_errors,
1333 "recv_errors": snap.recv_errors,
1334 "beacons_sent": snap.beacons_sent,
1335 "beacons_recv": snap.beacons_recv,
1336 "frames_too_short": snap.frames_too_short,
1337 "frames_too_long": snap.frames_too_long,
1338 })
1339 }
1340 TransportHandle::Tcp(t) => {
1341 serde_json::to_value(t.stats().snapshot()).unwrap_or_default()
1342 }
1343 TransportHandle::Tor(t) => {
1344 serde_json::to_value(t.stats().snapshot()).unwrap_or_default()
1345 }
1346 #[cfg(feature = "webrtc-transport")]
1347 TransportHandle::WebRtc(t) => serde_json::json!({
1348 "mtu": t.mtu(),
1349 "state": t.state().to_string(),
1350 }),
1351 #[cfg(target_os = "linux")]
1352 TransportHandle::Ble(t) => {
1353 serde_json::to_value(t.stats().snapshot()).unwrap_or_default()
1354 }
1355 }
1356 }
1357}
1358
1359pub(crate) async fn resolve_socket_addr(
1370 addr: &TransportAddr,
1371) -> Result<SocketAddr, TransportError> {
1372 let s = addr
1373 .as_str()
1374 .ok_or_else(|| TransportError::InvalidAddress("not valid UTF-8".into()))?;
1375
1376 if let Ok(sock_addr) = s.parse::<SocketAddr>() {
1378 return Ok(sock_addr);
1379 }
1380
1381 tokio::net::lookup_host(s)
1383 .await
1384 .map_err(|e| {
1385 TransportError::InvalidAddress(format!("DNS resolution failed for {}: {}", s, e))
1386 })?
1387 .next()
1388 .ok_or_else(|| {
1389 TransportError::InvalidAddress(format!(
1390 "DNS resolution returned no addresses for {}",
1391 s
1392 ))
1393 })
1394}
1395
1396#[cfg(test)]
1401mod tests {
1402 use super::*;
1403
1404 #[test]
1405 fn test_transport_id() {
1406 let id = TransportId::new(42);
1407 assert_eq!(id.as_u32(), 42);
1408 assert_eq!(format!("{}", id), "transport:42");
1409 }
1410
1411 #[test]
1412 fn test_link_id() {
1413 let id = LinkId::new(12345);
1414 assert_eq!(id.as_u64(), 12345);
1415 assert_eq!(format!("{}", id), "link:12345");
1416 }
1417
1418 #[test]
1419 fn test_transport_state_transitions() {
1420 assert!(TransportState::Configured.can_start());
1421 assert!(TransportState::Down.can_start());
1422 assert!(TransportState::Failed.can_start());
1423 assert!(!TransportState::Starting.can_start());
1424 assert!(!TransportState::Up.can_start());
1425
1426 assert!(TransportState::Up.is_operational());
1427 assert!(!TransportState::Starting.is_operational());
1428 assert!(!TransportState::Failed.is_operational());
1429 }
1430
1431 #[test]
1432 fn test_link_state() {
1433 assert!(LinkState::Connected.is_operational());
1434 assert!(!LinkState::Connecting.is_operational());
1435 assert!(!LinkState::Disconnected.is_operational());
1436 assert!(!LinkState::Failed.is_operational());
1437
1438 assert!(LinkState::Disconnected.is_terminal());
1439 assert!(LinkState::Failed.is_terminal());
1440 assert!(!LinkState::Connected.is_terminal());
1441 }
1442
1443 #[test]
1444 #[allow(clippy::assertions_on_constants)]
1445 fn test_transport_type_constants() {
1446 assert!(!TransportType::UDP.connection_oriented);
1448 assert!(!TransportType::UDP.reliable);
1449 assert!(TransportType::UDP.is_connectionless());
1450
1451 assert!(TransportType::TOR.connection_oriented);
1452 assert!(TransportType::TOR.reliable);
1453 assert!(!TransportType::TOR.is_connectionless());
1454
1455 assert_eq!(TransportType::UDP.name, "udp");
1456 assert_eq!(TransportType::ETHERNET.name, "ethernet");
1457 }
1458
1459 #[test]
1460 fn test_transport_addr_string() {
1461 let addr = TransportAddr::from_string("192.168.1.1:2121");
1462 assert_eq!(format!("{}", addr), "192.168.1.1:2121");
1463 assert_eq!(addr.as_str(), Some("192.168.1.1:2121"));
1464 }
1465
1466 #[test]
1467 fn test_transport_addr_binary() {
1468 let binary = TransportAddr::new(vec![0xff, 0x80, 0x2b, 0x3c, 0x4d, 0x5e]);
1470 assert_eq!(format!("{}", binary), "ff802b3c4d5e");
1471 assert!(binary.as_str().is_none());
1472 assert_eq!(binary.len(), 6);
1473 }
1474
1475 #[test]
1476 fn test_transport_addr_from_string() {
1477 let addr: TransportAddr = "test:1234".into();
1478 assert_eq!(addr.as_str(), Some("test:1234"));
1479
1480 let addr2: TransportAddr = String::from("hello").into();
1481 assert_eq!(addr2.as_str(), Some("hello"));
1482 }
1483
1484 #[test]
1485 fn test_link_stats_basic() {
1486 let mut stats = LinkStats::new();
1487
1488 stats.record_sent(100);
1489 stats.record_recv(200, 1000);
1490
1491 assert_eq!(stats.packets_sent, 1);
1492 assert_eq!(stats.bytes_sent, 100);
1493 assert_eq!(stats.packets_recv, 1);
1494 assert_eq!(stats.bytes_recv, 200);
1495 assert_eq!(stats.last_recv_ms, 1000);
1496 }
1497
1498 #[test]
1499 fn test_link_stats_rtt() {
1500 let mut stats = LinkStats::new();
1501
1502 assert!(stats.rtt_estimate().is_none());
1503
1504 stats.update_rtt(Duration::from_millis(100));
1505 assert_eq!(stats.rtt_estimate(), Some(Duration::from_millis(100)));
1506
1507 stats.update_rtt(Duration::from_millis(200));
1509 let rtt = stats.rtt_estimate().unwrap();
1511 assert!(rtt.as_millis() >= 110 && rtt.as_millis() <= 130);
1512 }
1513
1514 #[test]
1515 fn test_link_stats_time_since_recv() {
1516 let mut stats = LinkStats::new();
1517
1518 assert_eq!(stats.time_since_recv(1000), u64::MAX);
1520
1521 stats.record_recv(100, 500);
1522 assert_eq!(stats.time_since_recv(1000), 500);
1523 assert_eq!(stats.time_since_recv(500), 0);
1524 }
1525
1526 #[test]
1527 fn test_link_creation() {
1528 let link = Link::new(
1529 LinkId::new(1),
1530 TransportId::new(1),
1531 TransportAddr::from_string("test"),
1532 LinkDirection::Outbound,
1533 Duration::from_millis(50),
1534 );
1535
1536 assert_eq!(link.state(), LinkState::Connecting);
1537 assert!(!link.is_operational());
1538 assert_eq!(link.direction(), LinkDirection::Outbound);
1539 }
1540
1541 #[test]
1542 fn test_link_connectionless() {
1543 let link = Link::connectionless(
1544 LinkId::new(1),
1545 TransportId::new(1),
1546 TransportAddr::from_string("test"),
1547 LinkDirection::Inbound,
1548 Duration::from_millis(5),
1549 );
1550
1551 assert_eq!(link.state(), LinkState::Connected);
1552 assert!(link.is_operational());
1553 }
1554
1555 #[test]
1556 fn test_link_state_changes() {
1557 let mut link = Link::new(
1558 LinkId::new(1),
1559 TransportId::new(1),
1560 TransportAddr::from_string("test"),
1561 LinkDirection::Outbound,
1562 Duration::from_millis(50),
1563 );
1564
1565 assert!(!link.is_operational());
1566
1567 link.set_connected();
1568 assert!(link.is_operational());
1569 assert!(!link.is_terminal());
1570
1571 link.set_disconnected();
1572 assert!(!link.is_operational());
1573 assert!(link.is_terminal());
1574 }
1575
1576 #[test]
1577 fn test_link_effective_rtt() {
1578 let mut link = Link::connectionless(
1579 LinkId::new(1),
1580 TransportId::new(1),
1581 TransportAddr::from_string("test"),
1582 LinkDirection::Inbound,
1583 Duration::from_millis(50),
1584 );
1585
1586 assert_eq!(link.effective_rtt(), Duration::from_millis(50));
1588
1589 link.stats_mut().update_rtt(Duration::from_millis(100));
1591 assert_eq!(link.effective_rtt(), Duration::from_millis(100));
1592 }
1593
1594 #[test]
1595 fn test_link_age() {
1596 let mut link = Link::new(
1597 LinkId::new(1),
1598 TransportId::new(1),
1599 TransportAddr::from_string("test"),
1600 LinkDirection::Outbound,
1601 Duration::from_millis(50),
1602 );
1603
1604 assert_eq!(link.age(1000), 0);
1606
1607 link.set_created_at(500);
1608 assert_eq!(link.age(1000), 500);
1609 assert_eq!(link.age(500), 0);
1610 }
1611
1612 #[test]
1613 fn test_discovered_peer() {
1614 let peer = DiscoveredPeer::new(
1615 TransportId::new(1),
1616 TransportAddr::from_string("192.168.1.1:2121"),
1617 );
1618
1619 assert_eq!(peer.transport_id, TransportId::new(1));
1620 assert!(peer.pubkey_hint.is_none());
1621 }
1622
1623 #[test]
1624 fn test_link_direction_display() {
1625 assert_eq!(format!("{}", LinkDirection::Outbound), "outbound");
1626 assert_eq!(format!("{}", LinkDirection::Inbound), "inbound");
1627 }
1628
1629 #[test]
1630 fn test_transport_state_display() {
1631 assert_eq!(format!("{}", TransportState::Up), "up");
1632 assert_eq!(format!("{}", TransportState::Failed), "failed");
1633 }
1634
1635 #[test]
1636 fn test_received_packet() {
1637 let packet = ReceivedPacket::new(
1638 TransportId::new(1),
1639 TransportAddr::from_string("192.168.1.1:2121"),
1640 vec![1, 2, 3, 4],
1641 );
1642
1643 assert_eq!(packet.transport_id, TransportId::new(1));
1644 assert_eq!(packet.data, vec![1, 2, 3, 4]);
1645 assert!(packet.timestamp_ms > 0);
1646 }
1647
1648 #[test]
1649 fn test_received_packet_with_timestamp() {
1650 let packet = ReceivedPacket::with_timestamp(
1651 TransportId::new(1),
1652 TransportAddr::from_string("test"),
1653 vec![5, 6],
1654 12345,
1655 );
1656
1657 assert_eq!(packet.timestamp_ms, 12345);
1658 }
1659
1660 #[tokio::test]
1661 async fn test_packet_channel() {
1662 let (tx, mut rx) = packet_channel(10);
1663
1664 let packet = ReceivedPacket::new(
1665 TransportId::new(1),
1666 TransportAddr::from_string("test"),
1667 vec![1, 2, 3],
1668 );
1669
1670 tx.send(packet.clone()).unwrap();
1671
1672 let received = rx.recv().await.unwrap();
1673 assert_eq!(received.data, vec![1, 2, 3]);
1674 }
1675
1676 struct MockTransport {
1682 id: TransportId,
1683 mtu_value: u16,
1684 }
1685
1686 impl MockTransport {
1687 fn new(mtu: u16) -> Self {
1688 Self {
1689 id: TransportId::new(99),
1690 mtu_value: mtu,
1691 }
1692 }
1693 }
1694
1695 impl Transport for MockTransport {
1696 fn transport_id(&self) -> TransportId {
1697 self.id
1698 }
1699 fn transport_type(&self) -> &TransportType {
1700 &TransportType::UDP
1701 }
1702 fn state(&self) -> TransportState {
1703 TransportState::Up
1704 }
1705 fn mtu(&self) -> u16 {
1706 self.mtu_value
1707 }
1708 fn start(&mut self) -> Result<(), TransportError> {
1709 Ok(())
1710 }
1711 fn stop(&mut self) -> Result<(), TransportError> {
1712 Ok(())
1713 }
1714 fn send(&self, _addr: &TransportAddr, _data: &[u8]) -> Result<(), TransportError> {
1715 Ok(())
1716 }
1717 fn discover(&self) -> Result<Vec<DiscoveredPeer>, TransportError> {
1718 Ok(vec![])
1719 }
1720 }
1721
1722 struct PerLinkMtuTransport {
1724 id: TransportId,
1725 default_mtu: u16,
1726 overrides: Vec<(TransportAddr, u16)>,
1728 }
1729
1730 impl PerLinkMtuTransport {
1731 fn new(default_mtu: u16, overrides: Vec<(TransportAddr, u16)>) -> Self {
1732 Self {
1733 id: TransportId::new(100),
1734 default_mtu,
1735 overrides,
1736 }
1737 }
1738 }
1739
1740 impl Transport for PerLinkMtuTransport {
1741 fn transport_id(&self) -> TransportId {
1742 self.id
1743 }
1744 fn transport_type(&self) -> &TransportType {
1745 &TransportType::UDP
1746 }
1747 fn state(&self) -> TransportState {
1748 TransportState::Up
1749 }
1750 fn mtu(&self) -> u16 {
1751 self.default_mtu
1752 }
1753 fn link_mtu(&self, addr: &TransportAddr) -> u16 {
1754 for (a, mtu) in &self.overrides {
1755 if a == addr {
1756 return *mtu;
1757 }
1758 }
1759 self.mtu()
1760 }
1761 fn start(&mut self) -> Result<(), TransportError> {
1762 Ok(())
1763 }
1764 fn stop(&mut self) -> Result<(), TransportError> {
1765 Ok(())
1766 }
1767 fn send(&self, _addr: &TransportAddr, _data: &[u8]) -> Result<(), TransportError> {
1768 Ok(())
1769 }
1770 fn discover(&self) -> Result<Vec<DiscoveredPeer>, TransportError> {
1771 Ok(vec![])
1772 }
1773 }
1774
1775 #[test]
1776 fn test_link_mtu_default_falls_back_to_mtu() {
1777 let transport = MockTransport::new(1280);
1778 let addr = TransportAddr::from_string("192.168.1.1:2121");
1779
1780 assert_eq!(transport.link_mtu(&addr), 1280);
1782 assert_eq!(transport.link_mtu(&addr), transport.mtu());
1783
1784 let other_addr = TransportAddr::from_string("10.0.0.1:5000");
1786 assert_eq!(transport.link_mtu(&other_addr), 1280);
1787 }
1788
1789 #[test]
1790 fn test_link_mtu_per_link_override() {
1791 let addr_a = TransportAddr::from_string("192.168.1.1:2121");
1792 let addr_b = TransportAddr::from_string("10.0.0.1:5000");
1793 let addr_unknown = TransportAddr::from_string("172.16.0.1:6000");
1794
1795 let transport =
1796 PerLinkMtuTransport::new(1280, vec![(addr_a.clone(), 512), (addr_b.clone(), 247)]);
1797
1798 assert_eq!(transport.link_mtu(&addr_a), 512);
1800 assert_eq!(transport.link_mtu(&addr_b), 247);
1801
1802 assert_eq!(transport.link_mtu(&addr_unknown), 1280);
1804 assert_eq!(transport.mtu(), 1280);
1805 }
1806
1807 #[test]
1808 fn test_transport_handle_link_mtu_delegation() {
1809 use crate::config::UdpConfig;
1810 use crate::transport::udp::UdpTransport;
1811
1812 let config = UdpConfig::default();
1813 let expected_mtu = config.mtu();
1814 let (tx, _rx) = packet_channel(1);
1815 let transport = UdpTransport::new(TransportId::new(1), None, config, tx);
1816 let handle = TransportHandle::Udp(transport);
1817
1818 let addr = TransportAddr::from_string("192.168.1.1:2121");
1819
1820 assert_eq!(handle.link_mtu(&addr), expected_mtu);
1823 assert_eq!(handle.link_mtu(&addr), handle.mtu());
1824 }
1825}