fips_core/config/transport.rs
1//! Transport configuration types.
2//!
3//! Generic transport instance handling (single vs. named) and
4//! transport-specific configuration structs.
5
6use std::collections::HashMap;
7use std::net::{IpAddr, SocketAddr};
8
9use serde::{Deserialize, Serialize};
10
11/// Parse an `external_addr` config string against a known bind port,
12/// producing the absolute `SocketAddr` to advertise on Nostr.
13///
14/// Accepts either a bare IP (`"198.51.100.1"` or `"[::1]"`) — in which
15/// case the bind port is appended — or a full `host:port` form
16/// (`"198.51.100.1:443"` or `"[::1]:443"`). Returns `None` on any parse
17/// error. IPv6 must use bracket notation when supplying a port.
18fn parse_external_advert_addr(raw: &str, bind_port: u16) -> Option<SocketAddr> {
19 if let Ok(sa) = raw.parse::<SocketAddr>() {
20 return Some(sa);
21 }
22 let ip: IpAddr = raw.parse().ok()?;
23 Some(SocketAddr::new(ip, bind_port))
24}
25
26/// Extract the port from a `bind_addr` string. Returns `None` if the
27/// string can't be parsed (e.g. a bare hostname without port).
28fn parse_bind_port(raw: &str) -> Option<u16> {
29 raw.parse::<SocketAddr>().ok().map(|sa| sa.port())
30}
31
32/// Default UDP bind address.
33const DEFAULT_UDP_BIND_ADDR: &str = "0.0.0.0:2121";
34
35/// Default UDP MTU (IPv6 minimum).
36const DEFAULT_UDP_MTU: u16 = 1280;
37
38/// Default MTU for FIPS datagrams carried by ephemeral Nostr relay events.
39const DEFAULT_NOSTR_RELAY_MTU: u16 = 1280;
40
41/// Default number of signed relay events waiting for the application adapter.
42const DEFAULT_NOSTR_RELAY_PENDING_EVENTS: usize = 1024;
43
44/// Ephemeral Nostr relay fallback transport configuration.
45///
46/// Relay URLs deliberately do not live here. The embedding application owns
47/// relay selection and delivery through the external Nostr relay adapter.
48#[derive(Debug, Clone, Default, Serialize, Deserialize)]
49#[serde(deny_unknown_fields)]
50pub struct NostrRelayConfig {
51 /// Maximum FIPS wire datagram size before base64 encoding.
52 #[serde(default, skip_serializing_if = "Option::is_none")]
53 pub mtu: Option<u16>,
54
55 /// Whether public Nostr adverts should auto-connect over this fallback.
56 #[serde(default, skip_serializing_if = "Option::is_none")]
57 pub auto_connect: Option<bool>,
58
59 /// Accept inbound FIPS handshakes received through relay events.
60 #[serde(default, skip_serializing_if = "Option::is_none")]
61 pub accept_connections: Option<bool>,
62
63 /// Maximum signed events waiting for the external relay adapter.
64 #[serde(default, skip_serializing_if = "Option::is_none")]
65 pub max_pending_events: Option<usize>,
66}
67
68impl NostrRelayConfig {
69 pub fn mtu(&self) -> u16 {
70 self.mtu.unwrap_or(DEFAULT_NOSTR_RELAY_MTU)
71 }
72
73 pub fn auto_connect(&self) -> bool {
74 self.auto_connect.unwrap_or(true)
75 }
76
77 pub fn accept_connections(&self) -> bool {
78 self.accept_connections.unwrap_or(true)
79 }
80
81 pub fn max_pending_events(&self) -> usize {
82 self.max_pending_events
83 .unwrap_or(DEFAULT_NOSTR_RELAY_PENDING_EVENTS)
84 .max(1)
85 }
86}
87
88/// Default UDP receive buffer size (16 MiB).
89///
90/// At sustained multi-Gbps single-stream the kernel UDP queue
91/// drained ~113 kpps × ~1.5 KiB ≈ 170 MiB/s, so a few-hundred-ms
92/// userspace stall would fill a 2 MiB buffer in <20 ms — small
93/// enough that ordinary jitter (GC, allocator-coalesce, scheduler
94/// context-switch on a busy host) trips RcvbufErrors and tanks TCP
95/// throughput via cwnd-halving. 16 MiB gives ~100 ms of headroom.
96///
97/// On platforms whose `net.core.rmem_max` is smaller than this, the
98/// UDP socket layer falls back to SO_RCVBUFFORCE (CAP_NET_ADMIN
99/// required) before honouring the kernel ceiling. See
100/// `transport/udp/socket.rs::UdpRawSocket::open`.
101const DEFAULT_UDP_RECV_BUF: usize = 16 * 1024 * 1024;
102
103/// Default UDP send buffer size (8 MiB). Mirrors the receive-side
104/// reasoning at half the size — outbound burst absorption matters
105/// less because we control the producer rate via the rx_loop's
106/// per-drain sendmmsg flush.
107const DEFAULT_UDP_SEND_BUF: usize = 8 * 1024 * 1024;
108
109/// UDP transport instance configuration.
110#[derive(Debug, Clone, Default, Serialize, Deserialize)]
111#[serde(deny_unknown_fields)]
112pub struct UdpConfig {
113 /// Bind address (`bind_addr`). Defaults to "0.0.0.0:2121".
114 ///
115 /// When `outbound_only = true`, this field is ignored and the transport
116 /// binds to `0.0.0.0:0` (kernel-assigned ephemeral port) regardless.
117 #[serde(default, skip_serializing_if = "Option::is_none")]
118 pub bind_addr: Option<String>,
119
120 /// UDP MTU (`mtu`). Defaults to 1280 (IPv6 minimum).
121 #[serde(default, skip_serializing_if = "Option::is_none")]
122 pub mtu: Option<u16>,
123
124 /// UDP receive buffer size in bytes (`recv_buf_size`). Defaults to 16 MiB.
125 #[serde(default, skip_serializing_if = "Option::is_none")]
126 pub recv_buf_size: Option<usize>,
127
128 /// UDP send buffer size in bytes (`send_buf_size`). Defaults to 8 MiB.
129 #[serde(default, skip_serializing_if = "Option::is_none")]
130 pub send_buf_size: Option<usize>,
131
132 /// Whether this transport should be advertised on Nostr overlay discovery.
133 /// Default: false. Implicitly forced false when `outbound_only = true`.
134 #[serde(default, skip_serializing_if = "Option::is_none")]
135 pub advertise_on_nostr: Option<bool>,
136
137 /// Whether UDP should be advertised as directly reachable (`host:port`) on
138 /// Nostr. When false and advertised, UDP is emitted as `addr: "nat"` to
139 /// trigger rendezvous traversal.
140 ///
141 /// Default: false.
142 #[serde(default, skip_serializing_if = "Option::is_none")]
143 pub public: Option<bool>,
144 /// Optional explicit public address to advertise when `public: true`
145 /// is set. Takes precedence over both the bound address and any
146 /// STUN-derived autodiscovery. Accepts either a bare IP
147 /// (`"198.51.100.1"` — the configured `bind_addr` port is appended)
148 /// or a full `host:port` (`"198.51.100.1:443"`). Useful when the
149 /// public IP isn't on a local interface (e.g. AWS EIP / cloud 1:1
150 /// NAT) and the operator wants to skip STUN autodiscovery for a
151 /// deterministic value.
152 #[serde(default, skip_serializing_if = "Option::is_none")]
153 pub external_addr: Option<String>,
154 /// Outbound-only mode. When true, the transport binds to a kernel-
155 /// assigned ephemeral port (`0.0.0.0:0`) instead of the configured
156 /// `bind_addr`, refuses inbound handshake msg1, and is never
157 /// advertised on Nostr regardless of `advertise_on_nostr`. Use this
158 /// to participate in the mesh as a pure client — initiate outbound
159 /// links without exposing an inbound listener on a known port.
160 /// Default: false.
161 #[serde(default, skip_serializing_if = "Option::is_none")]
162 pub outbound_only: Option<bool>,
163
164 /// Accept inbound handshake msg1 from new peers. Default: true.
165 /// Setting this to false combined with `auto_connect: true` on
166 /// peer-side configurations gives a "client" posture: this node
167 /// initiates outbound links but refuses inbound handshakes from
168 /// unfamiliar addresses. The Node-level gate at
169 /// `src/node/handlers/handshake.rs` carves out msg1 from peers
170 /// already established on this transport (so rekey continues to
171 /// work) — see ISSUE-2026-0004.
172 #[serde(default, skip_serializing_if = "Option::is_none")]
173 pub accept_connections: Option<bool>,
174}
175
176impl UdpConfig {
177 /// Get the bind address, using default if not configured.
178 ///
179 /// When `outbound_only = true`, returns `0.0.0.0:0` so the kernel picks
180 /// an ephemeral source port and no listener is exposed on a known port.
181 pub fn bind_addr(&self) -> &str {
182 if self.outbound_only() {
183 "0.0.0.0:0"
184 } else {
185 self.bind_addr.as_deref().unwrap_or(DEFAULT_UDP_BIND_ADDR)
186 }
187 }
188
189 /// Get the UDP MTU, using default if not configured.
190 pub fn mtu(&self) -> u16 {
191 self.mtu.unwrap_or(DEFAULT_UDP_MTU)
192 }
193
194 /// Get the receive buffer size, using default if not configured.
195 pub fn recv_buf_size(&self) -> usize {
196 self.recv_buf_size.unwrap_or(DEFAULT_UDP_RECV_BUF)
197 }
198
199 /// Get the send buffer size, using default if not configured.
200 pub fn send_buf_size(&self) -> usize {
201 self.send_buf_size.unwrap_or(DEFAULT_UDP_SEND_BUF)
202 }
203
204 /// Whether this UDP transport should be advertised on Nostr discovery.
205 /// Always false when `outbound_only = true`.
206 pub fn advertise_on_nostr(&self) -> bool {
207 if self.outbound_only() {
208 false
209 } else {
210 self.advertise_on_nostr.unwrap_or(false)
211 }
212 }
213
214 /// Whether this UDP transport should be advertised as directly reachable.
215 pub fn is_public(&self) -> bool {
216 self.public.unwrap_or(false)
217 }
218
219 /// Parse `external_addr` against the configured `bind_addr` port,
220 /// returning the absolute `SocketAddr` to advertise on Nostr.
221 /// Returns `None` if `external_addr` is unset or malformed, or if
222 /// the port cannot be inferred.
223 pub fn external_advert_addr(&self) -> Option<SocketAddr> {
224 let raw = self.external_addr.as_deref()?;
225 let bind_port = parse_bind_port(self.bind_addr())?;
226 parse_external_advert_addr(raw, bind_port)
227 }
228
229 /// Whether this transport runs in outbound-only mode. Default: false.
230 pub fn outbound_only(&self) -> bool {
231 self.outbound_only.unwrap_or(false)
232 }
233
234 /// Whether this transport accepts inbound handshakes. Default: true.
235 pub fn accept_connections(&self) -> bool {
236 self.accept_connections.unwrap_or(true)
237 }
238}
239
240/// Default simulated transport MTU (IPv6 minimum).
241#[cfg(feature = "sim-transport")]
242const DEFAULT_SIM_MTU: u16 = 1280;
243
244/// Default simulated network registry name.
245#[cfg(feature = "sim-transport")]
246const DEFAULT_SIM_NETWORK: &str = "default";
247
248/// In-memory simulated transport instance configuration.
249///
250/// This transport is intended for production-backed simulations. It uses the
251/// normal node/session/routing stack, but delivers transport packets through a
252/// registered in-process network that can model latency, throughput, loss, and
253/// churn without binding real sockets.
254#[cfg(feature = "sim-transport")]
255#[derive(Debug, Clone, Default, Serialize, Deserialize)]
256#[serde(deny_unknown_fields)]
257pub struct SimTransportConfig {
258 /// Registry name of the in-process simulated network.
259 #[serde(default, skip_serializing_if = "Option::is_none")]
260 pub network: Option<String>,
261
262 /// Address of this simulated endpoint within the network.
263 #[serde(default, skip_serializing_if = "Option::is_none")]
264 pub addr: Option<String>,
265
266 /// Transport MTU. Defaults to 1280.
267 #[serde(default, skip_serializing_if = "Option::is_none")]
268 pub mtu: Option<u16>,
269
270 /// Whether discovery should auto-connect to discovered peers.
271 #[serde(default, skip_serializing_if = "Option::is_none")]
272 pub auto_connect: Option<bool>,
273
274 /// Accept inbound handshake msg1 from new peers. Default: true.
275 #[serde(default, skip_serializing_if = "Option::is_none")]
276 pub accept_connections: Option<bool>,
277}
278
279#[cfg(feature = "sim-transport")]
280impl SimTransportConfig {
281 /// Registry name of the in-process simulated network.
282 pub fn network(&self) -> &str {
283 self.network.as_deref().unwrap_or(DEFAULT_SIM_NETWORK)
284 }
285
286 /// Get the simulated MTU.
287 pub fn mtu(&self) -> u16 {
288 self.mtu.unwrap_or(DEFAULT_SIM_MTU)
289 }
290
291 /// Whether this transport auto-connects to discovered peers.
292 pub fn auto_connect(&self) -> bool {
293 self.auto_connect.unwrap_or(false)
294 }
295
296 /// Whether this transport accepts inbound handshakes.
297 pub fn accept_connections(&self) -> bool {
298 self.accept_connections.unwrap_or(true)
299 }
300}
301
302/// Transport instances - either a single config or named instances.
303///
304/// Allows both simple single-instance config:
305/// ```yaml
306/// transports:
307/// udp:
308/// bind_addr: "0.0.0.0:2121"
309/// ```
310///
311/// And multiple named instances:
312/// ```yaml
313/// transports:
314/// udp:
315/// main:
316/// bind_addr: "0.0.0.0:2121"
317/// backup:
318/// bind_addr: "192.168.1.100:2122"
319/// ```
320#[derive(Debug, Clone, Serialize, Deserialize)]
321#[serde(untagged)]
322pub enum TransportInstances<T> {
323 /// Single unnamed instance (config fields directly under transport type).
324 Single(T),
325 /// Multiple named instances.
326 Named(HashMap<String, T>),
327}
328
329impl<T> TransportInstances<T> {
330 /// Get the number of instances.
331 pub fn len(&self) -> usize {
332 match self {
333 TransportInstances::Single(_) => 1,
334 TransportInstances::Named(map) => map.len(),
335 }
336 }
337
338 /// Check if there are no instances.
339 pub fn is_empty(&self) -> bool {
340 match self {
341 TransportInstances::Single(_) => false,
342 TransportInstances::Named(map) => map.is_empty(),
343 }
344 }
345
346 /// Iterate over all instances as (name, config) pairs.
347 ///
348 /// Single instances have `None` as the name.
349 /// Named instances have `Some(name)`.
350 pub fn iter(&self) -> impl Iterator<Item = (Option<&str>, &T)> {
351 match self {
352 TransportInstances::Single(config) => vec![(None, config)].into_iter(),
353 TransportInstances::Named(map) => map
354 .iter()
355 .map(|(k, v)| (Some(k.as_str()), v))
356 .collect::<Vec<_>>()
357 .into_iter(),
358 }
359 }
360}
361
362impl<T> Default for TransportInstances<T> {
363 fn default() -> Self {
364 TransportInstances::Named(HashMap::new())
365 }
366}
367
368/// Default Ethernet EtherType (FIPS default).
369const DEFAULT_ETHERNET_ETHERTYPE: u16 = 0x2121;
370
371/// Default Ethernet receive buffer size (2 MB).
372const DEFAULT_ETHERNET_RECV_BUF: usize = 2 * 1024 * 1024;
373
374/// Default Ethernet send buffer size (2 MB).
375const DEFAULT_ETHERNET_SEND_BUF: usize = 2 * 1024 * 1024;
376
377/// Default beacon announcement interval in seconds.
378const DEFAULT_BEACON_INTERVAL_SECS: u64 = 30;
379
380/// Minimum beacon announcement interval in seconds.
381const MIN_BEACON_INTERVAL_SECS: u64 = 10;
382
383/// Ethernet transport instance configuration.
384///
385/// EthernetConfig is always compiled (for config parsing on any platform),
386/// but the transport runtime currently requires Linux or macOS raw sockets.
387#[derive(Debug, Clone, Default, Serialize, Deserialize)]
388#[serde(deny_unknown_fields)]
389pub struct EthernetConfig {
390 /// Network interface name (e.g., "eth0", "enp3s0"). Required.
391 pub interface: String,
392
393 /// Custom EtherType (default: 0x2121).
394 #[serde(default, skip_serializing_if = "Option::is_none")]
395 pub ethertype: Option<u16>,
396
397 /// MTU override. Defaults to the interface's MTU minus 1 (for frame type prefix).
398 /// Cannot exceed the interface's actual MTU.
399 #[serde(default, skip_serializing_if = "Option::is_none")]
400 pub mtu: Option<u16>,
401
402 /// Receive buffer size in bytes. Default: 2 MB.
403 #[serde(default, skip_serializing_if = "Option::is_none")]
404 pub recv_buf_size: Option<usize>,
405
406 /// Send buffer size in bytes. Default: 2 MB.
407 #[serde(default, skip_serializing_if = "Option::is_none")]
408 pub send_buf_size: Option<usize>,
409
410 /// Listen for discovery beacons from other nodes. Default: true.
411 #[serde(default, skip_serializing_if = "Option::is_none")]
412 pub discovery: Option<bool>,
413
414 /// Broadcast announcement beacons on the LAN. Default: false.
415 #[serde(default, skip_serializing_if = "Option::is_none")]
416 pub announce: Option<bool>,
417
418 /// Auto-connect to discovered peers. Default: false.
419 #[serde(default, skip_serializing_if = "Option::is_none")]
420 pub auto_connect: Option<bool>,
421
422 /// Accept incoming connection attempts. Default: false.
423 #[serde(default, skip_serializing_if = "Option::is_none")]
424 pub accept_connections: Option<bool>,
425
426 /// Optional discovery scope carried in Ethernet beacons.
427 ///
428 /// When set, this transport ignores Ethernet beacons from other scopes.
429 /// This is a discovery/noise filter, not an access-control mechanism. If
430 /// unset, the node-level LAN discovery scope is used when available.
431 #[serde(default, skip_serializing_if = "Option::is_none")]
432 pub discovery_scope: Option<String>,
433
434 /// Announcement beacon interval in seconds. Default: 30.
435 #[serde(default, skip_serializing_if = "Option::is_none")]
436 pub beacon_interval_secs: Option<u64>,
437}
438
439impl EthernetConfig {
440 /// Get the EtherType, using default if not configured.
441 pub fn ethertype(&self) -> u16 {
442 self.ethertype.unwrap_or(DEFAULT_ETHERNET_ETHERTYPE)
443 }
444
445 /// Get the receive buffer size, using default if not configured.
446 pub fn recv_buf_size(&self) -> usize {
447 self.recv_buf_size.unwrap_or(DEFAULT_ETHERNET_RECV_BUF)
448 }
449
450 /// Get the send buffer size, using default if not configured.
451 pub fn send_buf_size(&self) -> usize {
452 self.send_buf_size.unwrap_or(DEFAULT_ETHERNET_SEND_BUF)
453 }
454
455 /// Whether to listen for discovery beacons. Default: true.
456 pub fn discovery(&self) -> bool {
457 self.discovery.unwrap_or(true)
458 }
459
460 /// Whether to broadcast announcement beacons. Default: false.
461 pub fn announce(&self) -> bool {
462 self.announce.unwrap_or(false)
463 }
464
465 /// Whether to auto-connect to discovered peers. Default: false.
466 pub fn auto_connect(&self) -> bool {
467 self.auto_connect.unwrap_or(false)
468 }
469
470 /// Whether to accept incoming connections. Default: false.
471 pub fn accept_connections(&self) -> bool {
472 self.accept_connections.unwrap_or(false)
473 }
474
475 /// Optional discovery scope for Ethernet beacons.
476 pub fn discovery_scope(&self) -> Option<&str> {
477 self.discovery_scope.as_deref().filter(|s| !s.is_empty())
478 }
479
480 /// Get the beacon interval, clamped to minimum. Default: 30s.
481 pub fn beacon_interval_secs(&self) -> u64 {
482 self.beacon_interval_secs
483 .unwrap_or(DEFAULT_BEACON_INTERVAL_SECS)
484 .max(MIN_BEACON_INTERVAL_SECS)
485 }
486}
487
488// ============================================================================
489// TCP Transport Configuration
490// ============================================================================
491
492/// Default TCP MTU (conservative, matches typical Ethernet MSS minus overhead).
493const DEFAULT_TCP_MTU: u16 = 1400;
494
495/// Default TCP connect timeout in milliseconds.
496const DEFAULT_TCP_CONNECT_TIMEOUT_MS: u64 = 5000;
497
498/// Default timeout for an accepted inbound TCP connection to deliver its
499/// first complete FMP frame.
500const DEFAULT_TCP_FIRST_FRAME_TIMEOUT_MS: u64 = 3000;
501
502/// Default TCP keepalive interval in seconds.
503const DEFAULT_TCP_KEEPALIVE_SECS: u64 = 30;
504
505/// Default TCP receive buffer size (2 MB).
506const DEFAULT_TCP_RECV_BUF: usize = 2 * 1024 * 1024;
507
508/// Default TCP send buffer size (2 MB).
509const DEFAULT_TCP_SEND_BUF: usize = 2 * 1024 * 1024;
510
511/// Default maximum inbound TCP connections.
512const DEFAULT_TCP_MAX_INBOUND: usize = 256;
513
514/// TCP transport instance configuration.
515#[derive(Debug, Clone, Default, Serialize, Deserialize)]
516#[serde(deny_unknown_fields)]
517pub struct TcpConfig {
518 /// Listen address (e.g., "0.0.0.0:443"). If not set, outbound-only.
519 #[serde(default, skip_serializing_if = "Option::is_none")]
520 pub bind_addr: Option<String>,
521
522 /// Default MTU for TCP connections. Defaults to 1400.
523 /// Per-connection MTU is derived from TCP_MAXSEG when available.
524 #[serde(default, skip_serializing_if = "Option::is_none")]
525 pub mtu: Option<u16>,
526
527 /// Outbound connect timeout in milliseconds. Defaults to 5000.
528 #[serde(default, skip_serializing_if = "Option::is_none")]
529 pub connect_timeout_ms: Option<u64>,
530
531 /// Inbound first-frame timeout in milliseconds. Accepted connections
532 /// must deliver one complete FMP frame within this window or they are
533 /// closed. Set to 0 to disable. Defaults to 3000.
534 #[serde(default, skip_serializing_if = "Option::is_none")]
535 pub first_frame_timeout_ms: Option<u64>,
536
537 /// Enable TCP_NODELAY (disable Nagle). Defaults to true.
538 #[serde(default, skip_serializing_if = "Option::is_none")]
539 pub nodelay: Option<bool>,
540
541 /// TCP keepalive interval in seconds. 0 = disabled. Defaults to 30.
542 #[serde(default, skip_serializing_if = "Option::is_none")]
543 pub keepalive_secs: Option<u64>,
544
545 /// TCP receive buffer size in bytes. Defaults to 2 MB.
546 #[serde(default, skip_serializing_if = "Option::is_none")]
547 pub recv_buf_size: Option<usize>,
548
549 /// TCP send buffer size in bytes. Defaults to 2 MB.
550 #[serde(default, skip_serializing_if = "Option::is_none")]
551 pub send_buf_size: Option<usize>,
552
553 /// Maximum simultaneous inbound connections. Defaults to 256.
554 #[serde(default, skip_serializing_if = "Option::is_none")]
555 pub max_inbound_connections: Option<usize>,
556
557 /// Whether this transport should be advertised on Nostr overlay discovery.
558 /// Default: false.
559 #[serde(default, skip_serializing_if = "Option::is_none")]
560 pub advertise_on_nostr: Option<bool>,
561
562 /// Optional explicit public address to advertise. Required when
563 /// `bind_addr` is wildcard (e.g. `"0.0.0.0:443"`) and
564 /// `advertise_on_nostr: true`, since TCP has no STUN equivalent
565 /// for autodiscovery. Accepts either a bare IP (`"198.51.100.1"`
566 /// — the configured `bind_addr` port is appended) or a full
567 /// `host:port`. Common pattern on AWS EIP / cloud 1:1 NAT setups
568 /// where the public IP isn't bindable on the host.
569 #[serde(default, skip_serializing_if = "Option::is_none")]
570 pub external_addr: Option<String>,
571}
572
573impl TcpConfig {
574 /// Get the default MTU.
575 pub fn mtu(&self) -> u16 {
576 self.mtu.unwrap_or(DEFAULT_TCP_MTU)
577 }
578
579 /// Get the connect timeout in milliseconds.
580 pub fn connect_timeout_ms(&self) -> u64 {
581 self.connect_timeout_ms
582 .unwrap_or(DEFAULT_TCP_CONNECT_TIMEOUT_MS)
583 }
584
585 /// Get the inbound first-frame timeout in milliseconds. 0 disables it.
586 pub fn first_frame_timeout_ms(&self) -> u64 {
587 self.first_frame_timeout_ms
588 .unwrap_or(DEFAULT_TCP_FIRST_FRAME_TIMEOUT_MS)
589 }
590
591 /// Whether TCP_NODELAY is enabled. Default: true.
592 pub fn nodelay(&self) -> bool {
593 self.nodelay.unwrap_or(true)
594 }
595
596 /// Get the keepalive interval in seconds. 0 = disabled. Default: 30.
597 pub fn keepalive_secs(&self) -> u64 {
598 self.keepalive_secs.unwrap_or(DEFAULT_TCP_KEEPALIVE_SECS)
599 }
600
601 /// Get the receive buffer size. Default: 2 MB.
602 pub fn recv_buf_size(&self) -> usize {
603 self.recv_buf_size.unwrap_or(DEFAULT_TCP_RECV_BUF)
604 }
605
606 /// Get the send buffer size. Default: 2 MB.
607 pub fn send_buf_size(&self) -> usize {
608 self.send_buf_size.unwrap_or(DEFAULT_TCP_SEND_BUF)
609 }
610
611 /// Get the maximum number of inbound connections. Default: 256.
612 pub fn max_inbound_connections(&self) -> usize {
613 self.max_inbound_connections
614 .unwrap_or(DEFAULT_TCP_MAX_INBOUND)
615 }
616
617 /// Whether this TCP transport should be advertised on Nostr discovery.
618 pub fn advertise_on_nostr(&self) -> bool {
619 self.advertise_on_nostr.unwrap_or(false)
620 }
621
622 /// Parse `external_addr` against the configured `bind_addr` port,
623 /// returning the absolute `SocketAddr` to advertise on Nostr.
624 /// Returns `None` if `external_addr` is unset or malformed, or if
625 /// `bind_addr` is unset / unparseable so no port can be inferred.
626 pub fn external_advert_addr(&self) -> Option<SocketAddr> {
627 let raw = self.external_addr.as_deref()?;
628 let bind_port = parse_bind_port(self.bind_addr.as_deref()?)?;
629 parse_external_advert_addr(raw, bind_port)
630 }
631}
632
633// ============================================================================
634// Tor Transport Configuration
635// ============================================================================
636
637/// Default Tor SOCKS5 proxy address.
638const DEFAULT_TOR_SOCKS5_ADDR: &str = "127.0.0.1:9050";
639
640/// Default Tor control port address.
641const DEFAULT_TOR_CONTROL_ADDR: &str = "/run/tor/control";
642
643/// Default Tor control cookie file path (Debian standard location).
644const DEFAULT_TOR_COOKIE_PATH: &str = "/var/run/tor/control.authcookie";
645
646/// Default Tor connect timeout in milliseconds (120s — Tor circuit
647/// establishment can take 30-60s on first connect, plus SOCKS5 handshake).
648const DEFAULT_TOR_CONNECT_TIMEOUT_MS: u64 = 120_000;
649
650/// Default Tor MTU (same as TCP).
651const DEFAULT_TOR_MTU: u16 = 1400;
652
653/// Default max inbound connections via onion service.
654const DEFAULT_TOR_MAX_INBOUND: usize = 64;
655
656/// Default HiddenServiceDir hostname file path.
657const DEFAULT_HOSTNAME_FILE: &str = "/var/lib/tor/fips_onion_service/hostname";
658
659/// Default directory mode bind address.
660const DEFAULT_DIRECTORY_BIND_ADDR: &str = "127.0.0.1:8443";
661
662/// Default advertised onion port for Nostr overlay discovery. Matches the
663/// Tor convention of `HiddenServicePort 443 127.0.0.1:<bind_port>` in torrc.
664const DEFAULT_TOR_ADVERTISED_PORT: u16 = 443;
665
666/// Tor transport instance configuration.
667///
668/// Supports three modes:
669/// - `socks5`: Outbound-only connections through a Tor SOCKS5 proxy.
670/// - `control_port`: Full bidirectional support — outbound via SOCKS5
671/// plus inbound via Tor onion service managed through the control port.
672/// - `directory`: Full bidirectional support — outbound via SOCKS5,
673/// inbound via a Tor-managed `HiddenServiceDir` onion service. No
674/// control port needed. Enables Tor `Sandbox 1` mode.
675#[derive(Debug, Clone, Default, Serialize, Deserialize)]
676#[serde(deny_unknown_fields)]
677pub struct TorConfig {
678 /// Tor access mode: "socks5", "control_port", or "directory".
679 /// Default: "socks5".
680 #[serde(default, skip_serializing_if = "Option::is_none")]
681 pub mode: Option<String>,
682
683 /// SOCKS5 proxy address (host:port). Defaults to "127.0.0.1:9050".
684 #[serde(default, skip_serializing_if = "Option::is_none")]
685 pub socks5_addr: Option<String>,
686
687 /// Outbound connect timeout in milliseconds. Defaults to 120000 (120s).
688 /// Tor circuit establishment can take 30-60s, so this must be generous.
689 #[serde(default, skip_serializing_if = "Option::is_none")]
690 pub connect_timeout_ms: Option<u64>,
691
692 /// Default MTU for Tor connections. Defaults to 1400.
693 #[serde(default, skip_serializing_if = "Option::is_none")]
694 pub mtu: Option<u16>,
695
696 /// Control port address: a Unix socket path (`/run/tor/control`) or
697 /// TCP address (`host:port`). Unix sockets are preferred for security.
698 /// Defaults to "/run/tor/control".
699 #[serde(default, skip_serializing_if = "Option::is_none")]
700 pub control_addr: Option<String>,
701
702 /// Control port authentication method:
703 /// `"cookie"` (read from default path),
704 /// `"cookie:/path/to/cookie"` (read from specified path), or
705 /// `"password:secret"` (password auth). Default: `"cookie"`.
706 #[serde(default, skip_serializing_if = "Option::is_none")]
707 pub control_auth: Option<String>,
708
709 /// Path to the Tor control cookie file. Used when control_auth is "cookie".
710 /// Defaults to "/var/run/tor/control.authcookie".
711 #[serde(default, skip_serializing_if = "Option::is_none")]
712 pub cookie_path: Option<String>,
713
714 /// Maximum number of inbound connections via onion service. Default: 64.
715 #[serde(default, skip_serializing_if = "Option::is_none")]
716 pub max_inbound_connections: Option<usize>,
717
718 /// Directory-mode onion service configuration. Only valid in
719 /// "directory" mode. Tor manages the onion service via HiddenServiceDir
720 /// in torrc; fips reads the .onion hostname from a file.
721 #[serde(default, skip_serializing_if = "Option::is_none")]
722 pub directory_service: Option<DirectoryServiceConfig>,
723
724 /// Whether this transport should be advertised on Nostr overlay discovery.
725 /// Default: false.
726 #[serde(default, skip_serializing_if = "Option::is_none")]
727 pub advertise_on_nostr: Option<bool>,
728
729 /// Public-facing onion port published in Nostr overlay adverts. Must
730 /// match the virtual port in torrc's `HiddenServicePort <port>
731 /// 127.0.0.1:<bind_port>` directive — that is the port other peers
732 /// will use to reach this onion. Default: 443.
733 #[serde(default, skip_serializing_if = "Option::is_none")]
734 pub advertised_port: Option<u16>,
735}
736
737/// Directory-mode onion service configuration.
738///
739/// In `directory` mode, Tor manages the onion service via `HiddenServiceDir`
740/// in torrc. FIPS reads the `.onion` address from the hostname file and
741/// binds a local TCP listener for Tor to forward inbound connections to.
742/// This mode requires no control port and enables Tor's `Sandbox 1`.
743#[derive(Debug, Clone, Default, Serialize, Deserialize)]
744#[serde(deny_unknown_fields)]
745pub struct DirectoryServiceConfig {
746 /// Path to the Tor-managed hostname file containing the .onion address.
747 /// Defaults to "/var/lib/tor/fips_onion_service/hostname".
748 #[serde(default, skip_serializing_if = "Option::is_none")]
749 pub hostname_file: Option<String>,
750
751 /// Local bind address for the listener that Tor forwards inbound
752 /// connections to. Must match the target in torrc's `HiddenServicePort`.
753 /// Defaults to "127.0.0.1:8443".
754 #[serde(default, skip_serializing_if = "Option::is_none")]
755 pub bind_addr: Option<String>,
756}
757
758impl DirectoryServiceConfig {
759 /// Path to the hostname file. Default: "/var/lib/tor/fips_onion_service/hostname".
760 pub fn hostname_file(&self) -> &str {
761 self.hostname_file
762 .as_deref()
763 .unwrap_or(DEFAULT_HOSTNAME_FILE)
764 }
765
766 /// Local bind address for the listener. Default: "127.0.0.1:8443".
767 pub fn bind_addr(&self) -> &str {
768 self.bind_addr
769 .as_deref()
770 .unwrap_or(DEFAULT_DIRECTORY_BIND_ADDR)
771 }
772}
773
774impl TorConfig {
775 /// Get the access mode. Default: "socks5".
776 pub fn mode(&self) -> &str {
777 self.mode.as_deref().unwrap_or("socks5")
778 }
779
780 /// Get the SOCKS5 proxy address. Default: "127.0.0.1:9050".
781 pub fn socks5_addr(&self) -> &str {
782 self.socks5_addr
783 .as_deref()
784 .unwrap_or(DEFAULT_TOR_SOCKS5_ADDR)
785 }
786
787 /// Get the control port address. Default: "/run/tor/control".
788 pub fn control_addr(&self) -> &str {
789 self.control_addr
790 .as_deref()
791 .unwrap_or(DEFAULT_TOR_CONTROL_ADDR)
792 }
793
794 /// Get the control auth string. Default: "cookie".
795 pub fn control_auth(&self) -> &str {
796 self.control_auth.as_deref().unwrap_or("cookie")
797 }
798
799 /// Get the cookie file path. Default: "/var/run/tor/control.authcookie".
800 pub fn cookie_path(&self) -> &str {
801 self.cookie_path
802 .as_deref()
803 .unwrap_or(DEFAULT_TOR_COOKIE_PATH)
804 }
805
806 /// Get the connect timeout in milliseconds. Default: 120000.
807 pub fn connect_timeout_ms(&self) -> u64 {
808 self.connect_timeout_ms
809 .unwrap_or(DEFAULT_TOR_CONNECT_TIMEOUT_MS)
810 }
811
812 /// Get the default MTU. Default: 1400.
813 pub fn mtu(&self) -> u16 {
814 self.mtu.unwrap_or(DEFAULT_TOR_MTU)
815 }
816
817 /// Get the max inbound connections. Default: 64.
818 pub fn max_inbound_connections(&self) -> usize {
819 self.max_inbound_connections
820 .unwrap_or(DEFAULT_TOR_MAX_INBOUND)
821 }
822
823 /// Whether this Tor transport should be advertised on Nostr discovery.
824 pub fn advertise_on_nostr(&self) -> bool {
825 self.advertise_on_nostr.unwrap_or(false)
826 }
827
828 /// Public-facing onion port published in Nostr overlay adverts.
829 /// Default: 443.
830 pub fn advertised_port(&self) -> u16 {
831 self.advertised_port.unwrap_or(DEFAULT_TOR_ADVERTISED_PORT)
832 }
833}
834
835// ============================================================================
836// WebRTC Transport Configuration
837// ============================================================================
838
839/// Default WebRTC data-channel MTU.
840const DEFAULT_WEBRTC_MTU: u16 = 1200;
841
842/// Default WebRTC connection timeout in milliseconds.
843const DEFAULT_WEBRTC_CONNECT_TIMEOUT_MS: u64 = 30_000;
844
845/// Default non-trickle ICE gathering timeout in milliseconds.
846const DEFAULT_WEBRTC_ICE_GATHER_TIMEOUT_MS: u64 = 2_000;
847
848/// Default maximum simultaneous WebRTC peer connections.
849const DEFAULT_WEBRTC_MAX_CONNECTIONS: usize = 6;
850
851/// Default WebRTC data channel label.
852const DEFAULT_WEBRTC_DATA_CHANNEL_LABEL: &str = "fips";
853
854/// WebRTC transport instance configuration.
855///
856/// WebRTC negotiates over an existing authenticated FIPS session and carries
857/// ordinary FIPS datagrams over an SCTP data channel.
858#[derive(Debug, Clone, Default, Serialize, Deserialize)]
859#[serde(deny_unknown_fields)]
860pub struct WebRtcConfig {
861 /// Whether this transport should be advertised on Nostr overlay discovery.
862 /// Default: false.
863 #[serde(default, skip_serializing_if = "Option::is_none")]
864 pub advertise_on_nostr: Option<bool>,
865
866 /// Whether to automatically connect to discovered WebRTC peers.
867 /// Default: false.
868 #[serde(default, skip_serializing_if = "Option::is_none")]
869 pub auto_connect: Option<bool>,
870
871 /// Accept inbound WebRTC offers. Defaults to `advertise_on_nostr`: a
872 /// non-advertising adapter has no inbound listener policy unless enabled.
873 #[serde(default, skip_serializing_if = "Option::is_none")]
874 pub accept_connections: Option<bool>,
875
876 /// Data-channel MTU. Defaults to 1200.
877 #[serde(default, skip_serializing_if = "Option::is_none")]
878 pub mtu: Option<u16>,
879
880 /// Maximum simultaneous WebRTC peer connections. Defaults to 6 and is
881 /// additionally bounded by the configured ICE socket budget.
882 #[serde(default, skip_serializing_if = "Option::is_none")]
883 pub max_connections: Option<usize>,
884
885 /// Outbound connect timeout in milliseconds. Defaults to 30000.
886 #[serde(default, skip_serializing_if = "Option::is_none")]
887 pub connect_timeout_ms: Option<u64>,
888
889 /// Non-trickle ICE gathering timeout in milliseconds. Defaults to 2000.
890 #[serde(default, skip_serializing_if = "Option::is_none")]
891 pub ice_gather_timeout_ms: Option<u64>,
892
893 /// Data channel label. Defaults to "fips".
894 #[serde(default, skip_serializing_if = "Option::is_none")]
895 pub data_channel_label: Option<String>,
896
897 /// Ordered data channel delivery. Default: true.
898 #[serde(default, skip_serializing_if = "Option::is_none")]
899 pub ordered: Option<bool>,
900
901 /// Maximum retransmits for partial reliability. Default: unset, which uses
902 /// WebRTC's reliable data-channel mode. Set to 0 for datagram-like delivery.
903 #[serde(default, skip_serializing_if = "Option::is_none")]
904 pub max_retransmits: Option<u16>,
905
906 /// Override STUN servers for this transport. Supports up to three `stun:`
907 /// URLs. When unset, `node.discovery.nostr.stun_servers` is used.
908 #[serde(default, skip_serializing_if = "Option::is_none")]
909 pub stun_servers: Option<Vec<String>>,
910
911 /// Resolve browser `.local` ICE candidates through one shared mDNS owner.
912 /// Every peer connection keeps its own ICE mDNS mode disabled. Default:
913 /// true. Disable this for environments where multicast DNS is unavailable.
914 #[serde(default, skip_serializing_if = "Option::is_none")]
915 pub resolve_mdns_candidates: Option<bool>,
916}
917
918impl WebRtcConfig {
919 /// Whether this WebRTC transport should be advertised on Nostr discovery.
920 pub fn advertise_on_nostr(&self) -> bool {
921 self.advertise_on_nostr.unwrap_or(false)
922 }
923
924 /// Whether this transport auto-connects to discovered peers.
925 pub fn auto_connect(&self) -> bool {
926 self.auto_connect.unwrap_or(false)
927 }
928
929 /// Whether this transport accepts inbound offers.
930 pub fn accept_connections(&self) -> bool {
931 self.accept_connections
932 .unwrap_or_else(|| self.advertise_on_nostr())
933 }
934
935 /// Get the data-channel MTU.
936 pub fn mtu(&self) -> u16 {
937 self.mtu.unwrap_or(DEFAULT_WEBRTC_MTU)
938 }
939
940 /// Get the maximum number of peer connections.
941 pub fn max_connections(&self) -> usize {
942 self.max_connections
943 .unwrap_or(DEFAULT_WEBRTC_MAX_CONNECTIONS)
944 }
945
946 /// Get the connect timeout in milliseconds.
947 pub fn connect_timeout_ms(&self) -> u64 {
948 self.connect_timeout_ms
949 .unwrap_or(DEFAULT_WEBRTC_CONNECT_TIMEOUT_MS)
950 }
951
952 /// Get the ICE gathering timeout in milliseconds.
953 pub fn ice_gather_timeout_ms(&self) -> u64 {
954 self.ice_gather_timeout_ms
955 .unwrap_or(DEFAULT_WEBRTC_ICE_GATHER_TIMEOUT_MS)
956 }
957
958 /// Get the data channel label.
959 pub fn data_channel_label(&self) -> &str {
960 self.data_channel_label
961 .as_deref()
962 .unwrap_or(DEFAULT_WEBRTC_DATA_CHANNEL_LABEL)
963 }
964
965 /// Whether the data channel is ordered.
966 pub fn ordered(&self) -> bool {
967 self.ordered.unwrap_or(true)
968 }
969
970 /// Get the configured max retransmits. None uses WebRTC's reliable mode.
971 pub fn max_retransmits(&self) -> Option<u16> {
972 self.max_retransmits
973 }
974
975 /// Whether browser `.local` ICE candidates should be resolved.
976 pub fn resolve_mdns_candidates(&self) -> bool {
977 self.resolve_mdns_candidates.unwrap_or(true)
978 }
979
980 /// Resolve STUN servers, falling back to node discovery STUN servers.
981 pub fn stun_servers<'a>(&'a self, fallback: &'a [String]) -> Vec<String> {
982 self.stun_servers
983 .as_ref()
984 .cloned()
985 .unwrap_or_else(|| fallback.to_vec())
986 }
987}
988
989mod aggregate;
990mod ble;
991#[cfg(test)]
992mod tests;
993
994pub use aggregate::TransportsConfig;
995pub use ble::BleConfig;