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