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
22mod handle;
23mod packet_channel;
24
25#[cfg(test)]
26mod tests;
27
28pub use handle::TransportHandle;
29#[cfg(any(target_os = "linux", target_os = "macos"))]
30pub(crate) use packet_channel::received_timestamp_ms;
31pub use packet_channel::{PacketBuffer, PacketRx, PacketTx, ReceivedPacket, packet_channel};
32
33use secp256k1::XOnlyPublicKey;
34use std::fmt;
35use std::net::SocketAddr;
36use std::sync::Arc;
37use std::time::Duration;
38use thiserror::Error;
39
40#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
46pub struct TransportId(u32);
47
48impl TransportId {
49 pub fn new(id: u32) -> Self {
51 Self(id)
52 }
53
54 pub fn as_u32(&self) -> u32 {
56 self.0
57 }
58}
59
60impl fmt::Display for TransportId {
61 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
62 write!(f, "transport:{}", self.0)
63 }
64}
65
66#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
68pub struct LinkId(u64);
69
70impl LinkId {
71 pub fn new(id: u64) -> Self {
73 Self(id)
74 }
75
76 pub fn as_u64(&self) -> u64 {
78 self.0
79 }
80}
81
82impl fmt::Display for LinkId {
83 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
84 write!(f, "link:{}", self.0)
85 }
86}
87
88#[derive(Debug, Error)]
94pub enum TransportError {
95 #[error("transport not started")]
96 NotStarted,
97
98 #[error("transport already started")]
99 AlreadyStarted,
100
101 #[error("transport failed to start: {0}")]
102 StartFailed(String),
103
104 #[error("transport shutdown failed: {0}")]
105 ShutdownFailed(String),
106
107 #[error("link failed: {0}")]
108 LinkFailed(String),
109
110 #[error("send failed: {0}")]
111 SendFailed(String),
112
113 #[error("receive failed: {0}")]
114 RecvFailed(String),
115
116 #[error("invalid transport address: {0}")]
117 InvalidAddress(String),
118
119 #[error("mtu exceeded: packet {packet_size} > mtu {mtu}")]
120 MtuExceeded { packet_size: usize, mtu: u16 },
121
122 #[error("transport timeout")]
123 Timeout,
124
125 #[error("connection refused")]
126 ConnectionRefused,
127
128 #[error("transport not supported: {0}")]
129 NotSupported(String),
130
131 #[error("io error: {0}")]
132 Io(#[from] std::io::Error),
133}
134
135impl TransportError {
136 pub fn is_local_route_unavailable(&self) -> bool {
139 match self {
140 TransportError::Io(error) => is_local_route_error_kind(error.kind()),
141 TransportError::SendFailed(message) => is_local_route_error_text(message),
142 _ => false,
143 }
144 }
145}
146
147fn is_local_route_error_kind(kind: std::io::ErrorKind) -> bool {
148 matches!(
149 kind,
150 std::io::ErrorKind::NetworkUnreachable
151 | std::io::ErrorKind::HostUnreachable
152 | std::io::ErrorKind::AddrNotAvailable
153 )
154}
155
156fn is_local_route_error_text(message: &str) -> bool {
157 let lower = message.to_ascii_lowercase();
158 lower.contains("network is unreachable")
159 || lower.contains("no route to host")
160 || lower.contains("host is unreachable")
161 || lower.contains("can't assign requested address")
162 || lower.contains("cannot assign requested address")
163 || lower.contains("os error 51")
164 || lower.contains("os error 65")
165 || lower.contains("os error 49")
166}
167
168#[derive(Clone, Debug, PartialEq, Eq)]
174pub struct TransportType {
175 pub name: &'static str,
177 pub connection_oriented: bool,
179 pub reliable: bool,
181}
182
183impl TransportType {
184 pub const UDP: TransportType = TransportType {
186 name: "udp",
187 connection_oriented: false,
188 reliable: false,
189 };
190
191 pub const TCP: TransportType = TransportType {
193 name: "tcp",
194 connection_oriented: true,
195 reliable: true,
196 };
197
198 pub const ETHERNET: TransportType = TransportType {
200 name: "ethernet",
201 connection_oriented: false,
202 reliable: false,
203 };
204
205 pub const WIFI: TransportType = TransportType {
207 name: "wifi",
208 connection_oriented: false,
209 reliable: false,
210 };
211
212 pub const TOR: TransportType = TransportType {
214 name: "tor",
215 connection_oriented: true,
216 reliable: true,
217 };
218
219 pub const SERIAL: TransportType = TransportType {
221 name: "serial",
222 connection_oriented: false,
223 reliable: true, };
225
226 pub const BLE: TransportType = TransportType {
228 name: "ble",
229 connection_oriented: true,
230 reliable: true, };
232
233 pub const WEBRTC: TransportType = TransportType {
235 name: "webrtc",
236 connection_oriented: true,
237 reliable: false,
238 };
239
240 #[cfg(feature = "sim-transport")]
242 pub const SIM: TransportType = TransportType {
243 name: "sim",
244 connection_oriented: false,
245 reliable: false,
246 };
247
248 pub fn is_connectionless(&self) -> bool {
250 !self.connection_oriented
251 }
252}
253
254impl fmt::Display for TransportType {
255 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
256 write!(f, "{}", self.name)
257 }
258}
259
260#[derive(Clone, Copy, Debug, PartialEq, Eq)]
266pub enum TransportState {
267 Configured,
269 Starting,
271 Up,
273 Down,
275 Failed,
277}
278
279impl TransportState {
280 pub fn is_operational(&self) -> bool {
282 matches!(self, TransportState::Up)
283 }
284
285 pub fn can_start(&self) -> bool {
287 matches!(
288 self,
289 TransportState::Configured | TransportState::Down | TransportState::Failed
290 )
291 }
292
293 pub fn is_terminal(&self) -> bool {
295 matches!(self, TransportState::Failed)
296 }
297}
298
299impl fmt::Display for TransportState {
300 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
301 let s = match self {
302 TransportState::Configured => "configured",
303 TransportState::Starting => "starting",
304 TransportState::Up => "up",
305 TransportState::Down => "down",
306 TransportState::Failed => "failed",
307 };
308 write!(f, "{}", s)
309 }
310}
311
312#[derive(Clone, Copy, Debug, PartialEq, Eq)]
318pub enum LinkState {
319 Connecting,
321 Connected,
323 Disconnected,
325 Failed,
327}
328
329impl LinkState {
330 pub fn is_operational(&self) -> bool {
332 matches!(self, LinkState::Connected)
333 }
334
335 pub fn is_terminal(&self) -> bool {
337 matches!(self, LinkState::Disconnected | LinkState::Failed)
338 }
339}
340
341impl fmt::Display for LinkState {
342 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
343 let s = match self {
344 LinkState::Connecting => "connecting",
345 LinkState::Connected => "connected",
346 LinkState::Disconnected => "disconnected",
347 LinkState::Failed => "failed",
348 };
349 write!(f, "{}", s)
350 }
351}
352
353#[derive(Clone, Copy, Debug, PartialEq, Eq)]
355pub enum LinkDirection {
356 Outbound,
358 Inbound,
360}
361
362impl fmt::Display for LinkDirection {
363 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
364 let s = match self {
365 LinkDirection::Outbound => "outbound",
366 LinkDirection::Inbound => "inbound",
367 };
368 write!(f, "{}", s)
369 }
370}
371
372#[derive(Clone, PartialEq, Eq, Hash)]
385pub struct TransportAddr(Arc<[u8]>);
386
387impl TransportAddr {
388 pub fn new(bytes: Vec<u8>) -> Self {
390 Self(bytes.into())
391 }
392
393 pub fn from_bytes(bytes: &[u8]) -> Self {
395 Self(Arc::from(bytes))
396 }
397
398 pub fn from_string(s: &str) -> Self {
400 Self(Arc::from(s.as_bytes()))
401 }
402
403 pub fn from_socket_addr(addr: std::net::SocketAddr) -> Self {
415 use std::io::Write;
416 let mut buf = Vec::with_capacity(56);
420 write!(&mut buf, "{addr}").expect("Vec<u8>::write_fmt is infallible");
424 Self(buf.into())
425 }
426
427 pub fn as_bytes(&self) -> &[u8] {
429 &self.0
430 }
431
432 pub fn as_str(&self) -> Option<&str> {
434 std::str::from_utf8(&self.0).ok()
435 }
436
437 pub fn len(&self) -> usize {
439 self.0.len()
440 }
441
442 pub fn is_empty(&self) -> bool {
444 self.0.is_empty()
445 }
446}
447
448impl fmt::Debug for TransportAddr {
449 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
450 match self.as_str() {
451 Some(s) => write!(f, "TransportAddr(\"{}\")", s),
452 None => write!(f, "TransportAddr({:?})", self.0),
453 }
454 }
455}
456
457impl fmt::Display for TransportAddr {
458 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
459 match self.as_str() {
461 Some(s) => write!(f, "{}", s),
462 None => {
463 for byte in self.0.iter() {
464 write!(f, "{:02x}", byte)?;
465 }
466 Ok(())
467 }
468 }
469 }
470}
471
472impl From<&str> for TransportAddr {
473 fn from(s: &str) -> Self {
474 Self::from_string(s)
475 }
476}
477
478impl From<String> for TransportAddr {
479 fn from(s: String) -> Self {
480 Self(s.into_bytes().into())
481 }
482}
483
484#[derive(Clone, Debug, Default)]
490pub struct LinkStats {
491 pub packets_sent: u64,
493 pub packets_recv: u64,
495 pub bytes_sent: u64,
497 pub bytes_recv: u64,
499 pub last_recv_ms: u64,
501 rtt_estimate: Option<Duration>,
503 pub loss_rate: f32,
505 pub throughput_estimate: u64,
507}
508
509impl LinkStats {
510 pub fn new() -> Self {
512 Self::default()
513 }
514
515 pub fn record_sent(&mut self, bytes: usize) {
517 self.packets_sent += 1;
518 self.bytes_sent += bytes as u64;
519 }
520
521 pub fn record_sent_batch(&mut self, packets: usize, bytes: usize) {
523 self.packets_sent += packets as u64;
524 self.bytes_sent += bytes as u64;
525 }
526
527 pub fn record_recv(&mut self, bytes: usize, timestamp_ms: u64) {
529 self.packets_recv += 1;
530 self.bytes_recv += bytes as u64;
531 self.last_recv_ms = timestamp_ms;
532 }
533
534 pub fn rtt_estimate(&self) -> Option<Duration> {
536 self.rtt_estimate
537 }
538
539 pub fn update_rtt(&mut self, rtt: Duration) {
543 match self.rtt_estimate {
544 Some(old_rtt) => {
545 let alpha = 0.2;
546 let new_rtt_nanos = (alpha * rtt.as_nanos() as f64
547 + (1.0 - alpha) * old_rtt.as_nanos() as f64)
548 as u64;
549 self.rtt_estimate = Some(Duration::from_nanos(new_rtt_nanos));
550 }
551 None => {
552 self.rtt_estimate = Some(rtt);
553 }
554 }
555 }
556
557 pub fn time_since_recv(&self, current_time_ms: u64) -> u64 {
559 if self.last_recv_ms == 0 {
560 return u64::MAX;
561 }
562 current_time_ms.saturating_sub(self.last_recv_ms)
563 }
564
565 pub fn reset(&mut self) {
567 *self = Self::default();
568 }
569}
570
571#[derive(Clone, Debug)]
577pub struct Link {
578 link_id: LinkId,
580 transport_id: TransportId,
582 remote_addr: TransportAddr,
584 direction: LinkDirection,
586 state: LinkState,
588 base_rtt: Duration,
590 stats: LinkStats,
592 created_at: u64,
594}
595
596impl Link {
597 pub fn new(
599 link_id: LinkId,
600 transport_id: TransportId,
601 remote_addr: TransportAddr,
602 direction: LinkDirection,
603 base_rtt: Duration,
604 ) -> Self {
605 Self {
606 link_id,
607 transport_id,
608 remote_addr,
609 direction,
610 state: LinkState::Connecting,
611 base_rtt,
612 stats: LinkStats::new(),
613 created_at: 0,
614 }
615 }
616
617 pub fn new_with_timestamp(
619 link_id: LinkId,
620 transport_id: TransportId,
621 remote_addr: TransportAddr,
622 direction: LinkDirection,
623 base_rtt: Duration,
624 created_at: u64,
625 ) -> Self {
626 let mut link = Self::new(link_id, transport_id, remote_addr, direction, base_rtt);
627 link.created_at = created_at;
628 link
629 }
630
631 pub fn connectionless(
636 link_id: LinkId,
637 transport_id: TransportId,
638 remote_addr: TransportAddr,
639 direction: LinkDirection,
640 base_rtt: Duration,
641 ) -> Self {
642 let mut link = Self::new(link_id, transport_id, remote_addr, direction, base_rtt);
643 link.state = LinkState::Connected;
644 link
645 }
646
647 pub fn link_id(&self) -> LinkId {
649 self.link_id
650 }
651
652 pub fn transport_id(&self) -> TransportId {
654 self.transport_id
655 }
656
657 pub fn remote_addr(&self) -> &TransportAddr {
659 &self.remote_addr
660 }
661
662 pub fn direction(&self) -> LinkDirection {
664 self.direction
665 }
666
667 pub fn state(&self) -> LinkState {
669 self.state
670 }
671
672 pub fn base_rtt(&self) -> Duration {
674 self.base_rtt
675 }
676
677 pub fn stats(&self) -> &LinkStats {
679 &self.stats
680 }
681
682 pub fn stats_mut(&mut self) -> &mut LinkStats {
684 &mut self.stats
685 }
686
687 pub fn created_at(&self) -> u64 {
689 self.created_at
690 }
691
692 pub fn set_created_at(&mut self, timestamp: u64) {
694 self.created_at = timestamp;
695 }
696
697 pub fn set_connected(&mut self) {
699 self.state = LinkState::Connected;
700 }
701
702 pub fn set_disconnected(&mut self) {
704 self.state = LinkState::Disconnected;
705 }
706
707 pub fn set_failed(&mut self) {
709 self.state = LinkState::Failed;
710 }
711
712 pub fn is_operational(&self) -> bool {
714 self.state.is_operational()
715 }
716
717 pub fn is_terminal(&self) -> bool {
719 self.state.is_terminal()
720 }
721
722 pub fn effective_rtt(&self) -> Duration {
724 self.stats.rtt_estimate().unwrap_or(self.base_rtt)
725 }
726
727 pub fn age(&self, current_time_ms: u64) -> u64 {
729 if self.created_at == 0 {
730 return 0;
731 }
732 current_time_ms.saturating_sub(self.created_at)
733 }
734}
735
736#[derive(Clone, Debug)]
742pub struct DiscoveredPeer {
743 pub transport_id: TransportId,
745 pub addr: TransportAddr,
747 pub pubkey_hint: Option<XOnlyPublicKey>,
749}
750
751impl DiscoveredPeer {
752 pub fn new(transport_id: TransportId, addr: TransportAddr) -> Self {
754 Self {
755 transport_id,
756 addr,
757 pubkey_hint: None,
758 }
759 }
760
761 pub fn with_hint(
763 transport_id: TransportId,
764 addr: TransportAddr,
765 pubkey: XOnlyPublicKey,
766 ) -> Self {
767 Self {
768 transport_id,
769 addr,
770 pubkey_hint: Some(pubkey),
771 }
772 }
773}
774
775pub trait Transport {
784 fn transport_id(&self) -> TransportId;
786
787 fn transport_type(&self) -> &TransportType;
789
790 fn state(&self) -> TransportState;
792
793 fn mtu(&self) -> u16;
795
796 fn link_mtu(&self, addr: &TransportAddr) -> u16 {
802 let _ = addr;
803 self.mtu()
804 }
805
806 fn start(&mut self) -> Result<(), TransportError>;
808
809 fn stop(&mut self) -> Result<(), TransportError>;
811
812 fn send(&self, addr: &TransportAddr, data: &[u8]) -> Result<(), TransportError>;
814
815 fn discover(&self) -> Result<Vec<DiscoveredPeer>, TransportError>;
817
818 fn auto_connect(&self) -> bool {
821 false
822 }
823
824 fn accept_connections(&self) -> bool {
827 true
828 }
829
830 fn close_connection(&self, _addr: &TransportAddr) {
836 }
838}
839
840#[derive(Clone, Debug, PartialEq, Eq)]
849pub enum ConnectionState {
850 None,
852 Connecting,
854 Connected,
856 Failed(String),
858}
859
860#[derive(Clone, Debug, Default)]
869pub struct TransportCongestion {
870 pub recv_drops: Option<u64>,
873 pub socket_recv_drops: Option<u64>,
876 pub namespace_recv_drops: Option<u64>,
880}
881
882pub(crate) async fn resolve_socket_addr(
893 addr: &TransportAddr,
894) -> Result<SocketAddr, TransportError> {
895 resolve_socket_addrs(addr)
896 .await?
897 .into_iter()
898 .next()
899 .ok_or_else(|| {
900 TransportError::InvalidAddress(format!(
901 "DNS resolution returned no addresses for {}",
902 addr.as_str().unwrap_or("<non-utf8>")
903 ))
904 })
905}
906
907pub(crate) async fn resolve_socket_addrs(
914 addr: &TransportAddr,
915) -> Result<Vec<SocketAddr>, TransportError> {
916 let s = addr
917 .as_str()
918 .ok_or_else(|| TransportError::InvalidAddress("not valid UTF-8".into()))?;
919
920 if let Ok(sock_addr) = s.parse::<SocketAddr>() {
922 return Ok(vec![sock_addr]);
923 }
924
925 let addrs = tokio::net::lookup_host(s)
927 .await
928 .map_err(|e| {
929 TransportError::InvalidAddress(format!("DNS resolution failed for {}: {}", s, e))
930 })?
931 .collect::<Vec<_>>();
932 if addrs.is_empty() {
933 return Err(TransportError::InvalidAddress(format!(
934 "DNS resolution returned no addresses for {}",
935 s
936 )));
937 }
938 Ok(addrs)
939}