Skip to main content

fips_core/
discovery.rs

1//! Bootstrap handoff types.
2//!
3//! These types model the boundary between an external rendezvous/bootstrap
4//! runtime and the core FIPS transport/handshake stack. The rendezvous side
5//! owns Nostr/STUN/UDP hole punching; once a direct UDP path is established,
6//! it hands the live socket and selected remote endpoint to FIPS so the
7//! existing Noise/FMP transport path can take over.
8
9pub mod lan;
10pub mod nostr;
11
12use crate::config::UdpConfig;
13use crate::{NodeAddr, TransportId};
14use std::net::{SocketAddr, UdpSocket};
15
16/// Punch-probe magic ("NPTC", network byte order). First byte `0x4E`
17/// collides with FMP's prefix-version high-nibble check, so the UDP
18/// transport silently filters packets carrying this magic to keep
19/// post-adoption handshake logs clean. Defined at the top-level
20/// `discovery` module so the UDP filter and the nostr submodule's
21/// punch sender share the same constant.
22pub const PUNCH_MAGIC: u32 = 0x4E505443;
23
24/// Punch-probe-ack magic ("NPTA", network byte order). Same filter as
25/// [`PUNCH_MAGIC`].
26pub const PUNCH_ACK_MAGIC: u32 = 0x4E505441;
27
28/// Returns `true` if the first four bytes of `data` match a punch-probe or
29/// punch-ack magic. Used by the UDP transport's receive loop to silently
30/// drop stray probes that arrive on an adopted socket after the remote
31/// peer's punch attempt has already timed out.
32pub fn is_punch_packet(data: &[u8]) -> bool {
33    if data.len() < 4 {
34        return false;
35    }
36    let magic = u32::from_be_bytes([data[0], data[1], data[2], data[3]]);
37    magic == PUNCH_MAGIC || magic == PUNCH_ACK_MAGIC
38}
39
40/// Result of handing an established traversal session into FIPS.
41#[derive(Debug, Clone)]
42pub struct BootstrapHandoffResult {
43    /// Newly allocated transport ID used for the adopted UDP socket.
44    pub transport_id: TransportId,
45    /// Local socket address now owned by the FIPS UDP transport.
46    pub local_addr: SocketAddr,
47    /// Confirmed remote UDP endpoint selected by traversal.
48    pub remote_addr: SocketAddr,
49    /// Peer node address derived from the supplied peer identity.
50    pub peer_node_addr: NodeAddr,
51    /// Nostr session identifier used by the bootstrap runtime.
52    pub session_id: String,
53}
54
55/// Established UDP traversal ready to be handed into FIPS.
56///
57/// The socket must already be bound and must be the same socket used for the
58/// traversal runtime's STUN and punch traffic so the NAT mapping is preserved.
59#[derive(Debug)]
60pub struct EstablishedTraversal {
61    /// Rendezvous session identifier for logging/correlation.
62    pub session_id: String,
63    /// Remote peer identity in `npub` form.
64    pub peer_npub: String,
65    /// The selected remote UDP endpoint to use for the FIPS handshake.
66    pub remote_addr: SocketAddr,
67    /// The live UDP socket carrying the established mapping.
68    pub socket: UdpSocket,
69    /// Optional name for the adopted UDP transport.
70    pub transport_name: Option<String>,
71    /// Optional UDP transport tuning overrides.
72    pub transport_config: Option<UdpConfig>,
73}
74
75impl EstablishedTraversal {
76    /// Construct an established traversal handoff.
77    pub fn new(
78        session_id: impl Into<String>,
79        peer_npub: impl Into<String>,
80        remote_addr: SocketAddr,
81        socket: UdpSocket,
82    ) -> Self {
83        Self {
84            session_id: session_id.into(),
85            peer_npub: peer_npub.into(),
86            remote_addr,
87            socket,
88            transport_name: None,
89            transport_config: None,
90        }
91    }
92
93    /// Attach an explicit transport name to the adopted UDP transport.
94    pub fn with_transport_name(mut self, name: impl Into<String>) -> Self {
95        self.transport_name = Some(name.into());
96        self
97    }
98
99    /// Override UDP transport tuning for the adopted socket.
100    pub fn with_transport_config(mut self, config: UdpConfig) -> Self {
101        self.transport_config = Some(config);
102        self
103    }
104}