Skip to main content

dig_nat/
peer.rs

1//! The peer the caller wants to reach ([`PeerTarget`]) and the connection they get back
2//! ([`PeerConnection`]).
3//!
4//! To the caller, [`crate::connect`] "just connects to a peer" — they describe the peer once and
5//! receive an mTLS-authenticated [`PeerConnection`] whose remote `peer_id` has been verified. Which
6//! traversal method got there is reported (observability) but never something the caller must
7//! choose or handle.
8
9use std::net::SocketAddr;
10
11use crate::identity::PeerId;
12use crate::method::TraversalKind;
13use crate::mux::{PeerSession, PeerStream};
14
15/// A description of the peer to connect to.
16///
17/// The caller supplies the peer's stable [`PeerId`] (for mTLS verification) and any candidate
18/// addresses it knows (from discovery / the address manager / the relay peer list). At least one
19/// direct candidate OR `peer_id`+relay reachability is needed; the strategy uses the candidate list
20/// for the direct/mapping methods and `peer_id` for the relay-coordinated + relayed methods.
21///
22/// ## Address-family policy — IPv6-first, IPv4-fallback (via `dig-ip`)
23///
24/// The candidate list is stored in DISCOVERY order — the IPv6-first preference + the local∩peer
25/// family intersection are applied at DIAL time by the canonical `dig-ip` crate ([`crate::dialer`]),
26/// not by this type. So a peer reachable over IPv6 is dialed over IPv6 and IPv4 is used only as a
27/// fallback, but a caller supplies candidates in whatever order it discovered them. Use
28/// [`PeerTarget::with_addrs`] to supply several candidates; [`PeerTarget::with_addr`] for a single
29/// one.
30#[derive(Debug, Clone, PartialEq, Eq)]
31pub struct PeerTarget {
32    /// The peer's network identity — SHA-256 of its TLS SPKI DER. Used to VERIFY the mTLS peer and
33    /// to address it over the relay. Required.
34    pub peer_id: PeerId,
35
36    /// The peer's directly-dialable candidate `ip:port`s, in discovery order (family selection +
37    /// IPv6-first preference are applied at dial time by `dig-ip`; see the type-level address-family
38    /// policy). Empty when the peer is only reachable via the relay. Prefer the
39    /// [`PeerTarget::with_addrs`]/[`PeerTarget::with_addr`] constructors or
40    /// [`PeerTarget::set_direct_addrs`] over mutating this directly; read it via
41    /// [`PeerTarget::direct_addrs`] (all) or [`PeerTarget::direct_addr`] (the first candidate).
42    direct_addrs: Vec<SocketAddr>,
43
44    /// The network id the peer registered under (relay `network_id`, e.g. `DIG_MAINNET`). Used to
45    /// scope relay peer lookups + hole-punch coordination.
46    pub network_id: String,
47}
48
49impl PeerTarget {
50    /// A peer known by a single direct address (public / port-forwarded / discovered).
51    pub fn with_addr(
52        peer_id: PeerId,
53        direct_addr: SocketAddr,
54        network_id: impl Into<String>,
55    ) -> Self {
56        PeerTarget::with_addrs(peer_id, vec![direct_addr], network_id)
57    }
58
59    /// A peer known by one OR MORE direct candidate addresses, kept in the order supplied. The
60    /// dialer (`dig-ip`) applies the IPv6-first preference + local∩peer family intersection at dial
61    /// time, so the caller need not pre-order the candidates.
62    pub fn with_addrs(
63        peer_id: PeerId,
64        direct_addrs: Vec<SocketAddr>,
65        network_id: impl Into<String>,
66    ) -> Self {
67        PeerTarget {
68            peer_id,
69            direct_addrs,
70            network_id: network_id.into(),
71        }
72    }
73
74    /// A peer known only by identity — reachable via relay-coordinated methods.
75    pub fn relay_only(peer_id: PeerId, network_id: impl Into<String>) -> Self {
76        PeerTarget {
77            peer_id,
78            direct_addrs: Vec::new(),
79            network_id: network_id.into(),
80        }
81    }
82
83    /// The peer's candidate addresses, in discovery order. Empty for a relay-only target. The dial
84    /// order (IPv6-first, intersected with the local host's families) is computed by `dig-ip` at
85    /// dial time.
86    pub fn direct_addrs(&self) -> &[SocketAddr] {
87        &self.direct_addrs
88    }
89
90    /// The first candidate address, or `None` for a relay-only target.
91    ///
92    /// A convenience accessor for callers that want ONE address; it returns the first candidate in
93    /// discovery order. Prefer [`PeerTarget::direct_addrs`] so the dialer honours the full
94    /// family-aware happy-eyeballs fallback (`dig-ip`).
95    pub fn direct_addr(&self) -> Option<SocketAddr> {
96        self.direct_addrs.first().copied()
97    }
98
99    /// Replace the candidate list (kept in the order supplied).
100    pub fn set_direct_addrs(&mut self, addrs: Vec<SocketAddr>) {
101        self.direct_addrs = addrs;
102    }
103}
104
105/// An established, mutually-authenticated, **multiplexed** connection to a peer.
106///
107/// The connection is one mTLS byte stream whose remote presented a certificate whose `peer_id`
108/// equals [`Self::peer_id`] (verified during the handshake by [`crate::mtls::PeerIdPinningVerifier`]),
109/// wrapped in a [`PeerSession`] so the caller can open **many concurrent logical streams**
110/// ([`open_stream`](Self::open_stream)) or **byte-range streams**
111/// ([`open_range_stream`](Self::open_range_stream)) — streaming-first, no head-of-line blocking.
112/// [`Self::method`] reports which traversal technique established it — observability only; the caller
113/// opens streams identically regardless of the tier.
114pub struct PeerConnection {
115    /// The verified remote identity (== the [`PeerTarget::peer_id`] the caller asked for).
116    pub peer_id: PeerId,
117    /// The traversal technique that established this connection (Direct, Upnp, …, Relayed).
118    pub method: TraversalKind,
119    /// The remote address the mTLS session runs over (the peer's endpoint, or the relay for a
120    /// relayed transport).
121    pub remote_addr: SocketAddr,
122    /// The multiplexed session over the authenticated, encrypted byte stream to the peer.
123    pub session: PeerSession,
124}
125
126impl PeerConnection {
127    /// Open a new concurrent logical stream to the peer (cheap; open as many as you need for
128    /// simultaneous transfers without head-of-line blocking).
129    pub async fn open_stream(&mut self) -> std::io::Result<PeerStream> {
130        self.session.open_stream().await
131    }
132
133    /// Open a `dig.fetchRange` stream for `req` (writes the range-request preamble, then streams
134    /// [`crate::mux::RangeFrame`]s). The primitive for multi-source parallel range downloads.
135    pub async fn open_range_stream(
136        &mut self,
137        req: &crate::mux::RangeRequest,
138    ) -> std::io::Result<PeerStream> {
139        self.session.open_range_stream(req).await
140    }
141
142    /// Availability pre-check (`dig.getAvailability`) — ask whether this peer holds `items` BEFORE
143    /// opening range streams (see [`crate::mux::PeerSession::query_availability`]).
144    pub async fn query_availability(
145        &mut self,
146        items: Vec<crate::mux::AvailabilityItem>,
147    ) -> std::io::Result<crate::mux::AvailabilityResponse> {
148        self.session.query_availability(items).await
149    }
150}
151
152impl std::fmt::Debug for PeerConnection {
153    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
154        f.debug_struct("PeerConnection")
155            .field("peer_id", &self.peer_id)
156            .field("method", &self.method)
157            .field("remote_addr", &self.remote_addr)
158            .field("session", &"<multiplexed mTLS session>")
159            .finish()
160    }
161}