1use std::collections::HashMap;
7use std::net::{IpAddr, SocketAddr};
8
9use serde::{Deserialize, Serialize};
10
11fn 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
26fn parse_bind_port(raw: &str) -> Option<u16> {
29 raw.parse::<SocketAddr>().ok().map(|sa| sa.port())
30}
31
32const DEFAULT_UDP_BIND_ADDR: &str = "0.0.0.0:2121";
34
35const DEFAULT_UDP_MTU: u16 = 1280;
37
38const DEFAULT_UDP_RECV_BUF: usize = 16 * 1024 * 1024;
52
53const DEFAULT_UDP_SEND_BUF: usize = 8 * 1024 * 1024;
58
59#[derive(Debug, Clone, Default, Serialize, Deserialize)]
61#[serde(deny_unknown_fields)]
62pub struct UdpConfig {
63 #[serde(default, skip_serializing_if = "Option::is_none")]
68 pub bind_addr: Option<String>,
69
70 #[serde(default, skip_serializing_if = "Option::is_none")]
77 pub bind_interface: Option<String>,
78
79 #[serde(default, skip_serializing_if = "Option::is_none")]
81 pub mtu: Option<u16>,
82
83 #[serde(default, skip_serializing_if = "Option::is_none")]
85 pub recv_buf_size: Option<usize>,
86
87 #[serde(default, skip_serializing_if = "Option::is_none")]
89 pub send_buf_size: Option<usize>,
90
91 #[serde(default, skip_serializing_if = "Option::is_none")]
94 pub advertise_on_nostr: Option<bool>,
95
96 #[serde(default, skip_serializing_if = "Option::is_none")]
102 pub public: Option<bool>,
103 #[serde(default, skip_serializing_if = "Option::is_none")]
112 pub external_addr: Option<String>,
113 #[serde(default, skip_serializing_if = "Option::is_none")]
121 pub outbound_only: Option<bool>,
122
123 #[serde(default, skip_serializing_if = "Option::is_none")]
132 pub accept_connections: Option<bool>,
133}
134
135impl UdpConfig {
136 pub fn bind_addr(&self) -> &str {
141 if self.outbound_only() {
142 "0.0.0.0:0"
143 } else {
144 self.bind_addr.as_deref().unwrap_or(DEFAULT_UDP_BIND_ADDR)
145 }
146 }
147
148 pub fn mtu(&self) -> u16 {
150 self.mtu.unwrap_or(DEFAULT_UDP_MTU)
151 }
152
153 pub fn recv_buf_size(&self) -> usize {
155 self.recv_buf_size.unwrap_or(DEFAULT_UDP_RECV_BUF)
156 }
157
158 pub fn send_buf_size(&self) -> usize {
160 self.send_buf_size.unwrap_or(DEFAULT_UDP_SEND_BUF)
161 }
162
163 pub fn advertise_on_nostr(&self) -> bool {
166 if self.outbound_only() {
167 false
168 } else {
169 self.advertise_on_nostr.unwrap_or(false)
170 }
171 }
172
173 pub fn is_public(&self) -> bool {
175 self.public.unwrap_or(false)
176 }
177
178 pub fn external_advert_addr(&self) -> Option<SocketAddr> {
183 let raw = self.external_addr.as_deref()?;
184 let bind_port = parse_bind_port(self.bind_addr())?;
185 parse_external_advert_addr(raw, bind_port)
186 }
187
188 pub fn outbound_only(&self) -> bool {
190 self.outbound_only.unwrap_or(false)
191 }
192
193 pub fn accept_connections(&self) -> bool {
195 self.accept_connections.unwrap_or(true)
196 }
197}
198
199#[cfg(feature = "sim-transport")]
201const DEFAULT_SIM_MTU: u16 = 1280;
202
203#[cfg(feature = "sim-transport")]
205const DEFAULT_SIM_NETWORK: &str = "default";
206
207#[cfg(feature = "sim-transport")]
214#[derive(Debug, Clone, Default, Serialize, Deserialize)]
215#[serde(deny_unknown_fields)]
216pub struct SimTransportConfig {
217 #[serde(default, skip_serializing_if = "Option::is_none")]
219 pub network: Option<String>,
220
221 #[serde(default, skip_serializing_if = "Option::is_none")]
223 pub addr: Option<String>,
224
225 #[serde(default, skip_serializing_if = "Option::is_none")]
227 pub mtu: Option<u16>,
228
229 #[serde(default, skip_serializing_if = "Option::is_none")]
231 pub auto_connect: Option<bool>,
232
233 #[serde(default, skip_serializing_if = "Option::is_none")]
235 pub accept_connections: Option<bool>,
236}
237
238#[cfg(feature = "sim-transport")]
239impl SimTransportConfig {
240 pub fn network(&self) -> &str {
242 self.network.as_deref().unwrap_or(DEFAULT_SIM_NETWORK)
243 }
244
245 pub fn mtu(&self) -> u16 {
247 self.mtu.unwrap_or(DEFAULT_SIM_MTU)
248 }
249
250 pub fn auto_connect(&self) -> bool {
252 self.auto_connect.unwrap_or(false)
253 }
254
255 pub fn accept_connections(&self) -> bool {
257 self.accept_connections.unwrap_or(true)
258 }
259}
260
261#[derive(Debug, Clone, Serialize, Deserialize)]
280#[serde(untagged)]
281pub enum TransportInstances<T> {
282 Single(T),
284 Named(HashMap<String, T>),
286}
287
288impl<T> TransportInstances<T> {
289 pub fn len(&self) -> usize {
291 match self {
292 TransportInstances::Single(_) => 1,
293 TransportInstances::Named(map) => map.len(),
294 }
295 }
296
297 pub fn is_empty(&self) -> bool {
299 match self {
300 TransportInstances::Single(_) => false,
301 TransportInstances::Named(map) => map.is_empty(),
302 }
303 }
304
305 pub fn iter(&self) -> impl Iterator<Item = (Option<&str>, &T)> {
310 match self {
311 TransportInstances::Single(config) => vec![(None, config)].into_iter(),
312 TransportInstances::Named(map) => map
313 .iter()
314 .map(|(k, v)| (Some(k.as_str()), v))
315 .collect::<Vec<_>>()
316 .into_iter(),
317 }
318 }
319}
320
321impl<T> Default for TransportInstances<T> {
322 fn default() -> Self {
323 TransportInstances::Named(HashMap::new())
324 }
325}
326
327const DEFAULT_ETHERNET_ETHERTYPE: u16 = 0x2121;
329
330const DEFAULT_ETHERNET_RECV_BUF: usize = 2 * 1024 * 1024;
332
333const DEFAULT_ETHERNET_SEND_BUF: usize = 2 * 1024 * 1024;
335
336const DEFAULT_BEACON_INTERVAL_SECS: u64 = 30;
338
339const MIN_BEACON_INTERVAL_SECS: u64 = 10;
341
342#[derive(Debug, Clone, Default, Serialize, Deserialize)]
347#[serde(deny_unknown_fields)]
348pub struct EthernetConfig {
349 pub interface: String,
351
352 #[serde(default, skip_serializing_if = "Option::is_none")]
354 pub ethertype: Option<u16>,
355
356 #[serde(default, skip_serializing_if = "Option::is_none")]
359 pub mtu: Option<u16>,
360
361 #[serde(default, skip_serializing_if = "Option::is_none")]
363 pub recv_buf_size: Option<usize>,
364
365 #[serde(default, skip_serializing_if = "Option::is_none")]
367 pub send_buf_size: Option<usize>,
368
369 #[serde(default, skip_serializing_if = "Option::is_none")]
371 pub discovery: Option<bool>,
372
373 #[serde(default, skip_serializing_if = "Option::is_none")]
375 pub announce: Option<bool>,
376
377 #[serde(default, skip_serializing_if = "Option::is_none")]
379 pub auto_connect: Option<bool>,
380
381 #[serde(default, skip_serializing_if = "Option::is_none")]
383 pub accept_connections: Option<bool>,
384
385 #[serde(default, skip_serializing_if = "Option::is_none")]
391 pub discovery_scope: Option<String>,
392
393 #[serde(default, skip_serializing_if = "Option::is_none")]
395 pub beacon_interval_secs: Option<u64>,
396}
397
398impl EthernetConfig {
399 pub fn ethertype(&self) -> u16 {
401 self.ethertype.unwrap_or(DEFAULT_ETHERNET_ETHERTYPE)
402 }
403
404 pub fn recv_buf_size(&self) -> usize {
406 self.recv_buf_size.unwrap_or(DEFAULT_ETHERNET_RECV_BUF)
407 }
408
409 pub fn send_buf_size(&self) -> usize {
411 self.send_buf_size.unwrap_or(DEFAULT_ETHERNET_SEND_BUF)
412 }
413
414 pub fn discovery(&self) -> bool {
416 self.discovery.unwrap_or(true)
417 }
418
419 pub fn announce(&self) -> bool {
421 self.announce.unwrap_or(false)
422 }
423
424 pub fn auto_connect(&self) -> bool {
426 self.auto_connect.unwrap_or(false)
427 }
428
429 pub fn accept_connections(&self) -> bool {
431 self.accept_connections.unwrap_or(false)
432 }
433
434 pub fn discovery_scope(&self) -> Option<&str> {
436 self.discovery_scope.as_deref().filter(|s| !s.is_empty())
437 }
438
439 pub fn beacon_interval_secs(&self) -> u64 {
441 self.beacon_interval_secs
442 .unwrap_or(DEFAULT_BEACON_INTERVAL_SECS)
443 .max(MIN_BEACON_INTERVAL_SECS)
444 }
445}
446
447const DEFAULT_TCP_MTU: u16 = 1400;
453
454const DEFAULT_TCP_CONNECT_TIMEOUT_MS: u64 = 5000;
456
457const DEFAULT_TCP_FIRST_FRAME_TIMEOUT_MS: u64 = 3000;
460
461const DEFAULT_TCP_KEEPALIVE_SECS: u64 = 30;
463
464const DEFAULT_TCP_RECV_BUF: usize = 2 * 1024 * 1024;
466
467const DEFAULT_TCP_SEND_BUF: usize = 2 * 1024 * 1024;
469
470const DEFAULT_TCP_MAX_INBOUND: usize = 256;
472
473const DEFAULT_WEBSOCKET_PATH: &str = "/fips";
475
476const DEFAULT_WEBSOCKET_MTU: u16 = 1400;
478
479const DEFAULT_WEBSOCKET_MAX_FRAME_BYTES: usize = 66 * 1024;
481
482const DEFAULT_WEBSOCKET_CONNECT_TIMEOUT_MS: u64 = 5_000;
483const DEFAULT_WEBSOCKET_KEY_HINT_TIMEOUT_MS: u64 = 3_000;
484const DEFAULT_WEBSOCKET_RECONNECT_INITIAL_MS: u64 = 1_000;
485const DEFAULT_WEBSOCKET_RECONNECT_MAX_MS: u64 = 30_000;
486const DEFAULT_WEBSOCKET_MAX_CONNECTIONS: usize = 256;
487const DEFAULT_WEBSOCKET_MAX_INBOUND: usize = 128;
488const DEFAULT_WEBSOCKET_MAX_SEND_QUEUE: usize = 256;
489const DEFAULT_WEBSOCKET_PING_INTERVAL_SECS: u64 = 20;
490const DEFAULT_WEBSOCKET_IDLE_TIMEOUT_SECS: u64 = 90;
491
492#[derive(Debug, Clone, Default, Serialize, Deserialize)]
494#[serde(deny_unknown_fields)]
495pub struct TcpConfig {
496 #[serde(default, skip_serializing_if = "Option::is_none")]
498 pub bind_addr: Option<String>,
499
500 #[serde(default, skip_serializing_if = "Option::is_none")]
504 pub mtu: Option<u16>,
505
506 #[serde(default, skip_serializing_if = "Option::is_none")]
508 pub connect_timeout_ms: Option<u64>,
509
510 #[serde(default, skip_serializing_if = "Option::is_none")]
514 pub first_frame_timeout_ms: Option<u64>,
515
516 #[serde(default, skip_serializing_if = "Option::is_none")]
518 pub nodelay: Option<bool>,
519
520 #[serde(default, skip_serializing_if = "Option::is_none")]
522 pub keepalive_secs: Option<u64>,
523
524 #[serde(default, skip_serializing_if = "Option::is_none")]
526 pub recv_buf_size: Option<usize>,
527
528 #[serde(default, skip_serializing_if = "Option::is_none")]
530 pub send_buf_size: Option<usize>,
531
532 #[serde(default, skip_serializing_if = "Option::is_none")]
534 pub max_inbound_connections: Option<usize>,
535
536 #[serde(default, skip_serializing_if = "Option::is_none")]
539 pub advertise_on_nostr: Option<bool>,
540
541 #[serde(default, skip_serializing_if = "Option::is_none")]
549 pub external_addr: Option<String>,
550}
551
552impl TcpConfig {
553 pub fn mtu(&self) -> u16 {
555 self.mtu.unwrap_or(DEFAULT_TCP_MTU)
556 }
557
558 pub fn connect_timeout_ms(&self) -> u64 {
560 self.connect_timeout_ms
561 .unwrap_or(DEFAULT_TCP_CONNECT_TIMEOUT_MS)
562 }
563
564 pub fn first_frame_timeout_ms(&self) -> u64 {
566 self.first_frame_timeout_ms
567 .unwrap_or(DEFAULT_TCP_FIRST_FRAME_TIMEOUT_MS)
568 }
569
570 pub fn nodelay(&self) -> bool {
572 self.nodelay.unwrap_or(true)
573 }
574
575 pub fn keepalive_secs(&self) -> u64 {
577 self.keepalive_secs.unwrap_or(DEFAULT_TCP_KEEPALIVE_SECS)
578 }
579
580 pub fn recv_buf_size(&self) -> usize {
582 self.recv_buf_size.unwrap_or(DEFAULT_TCP_RECV_BUF)
583 }
584
585 pub fn send_buf_size(&self) -> usize {
587 self.send_buf_size.unwrap_or(DEFAULT_TCP_SEND_BUF)
588 }
589
590 pub fn max_inbound_connections(&self) -> usize {
592 self.max_inbound_connections
593 .unwrap_or(DEFAULT_TCP_MAX_INBOUND)
594 }
595
596 pub fn advertise_on_nostr(&self) -> bool {
598 self.advertise_on_nostr.unwrap_or(false)
599 }
600
601 pub fn external_advert_addr(&self) -> Option<SocketAddr> {
606 let raw = self.external_addr.as_deref()?;
607 let bind_port = parse_bind_port(self.bind_addr.as_deref()?)?;
608 parse_external_advert_addr(raw, bind_port)
609 }
610}
611
612#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
622#[serde(deny_unknown_fields)]
623pub struct WebSocketConfig {
624 #[serde(default, skip_serializing_if = "Option::is_none")]
626 pub bind_addr: Option<String>,
627
628 #[serde(default, skip_serializing_if = "Option::is_none")]
630 pub public_url: Option<String>,
631
632 #[serde(default, skip_serializing_if = "Vec::is_empty")]
634 pub seed_urls: Vec<String>,
635
636 #[serde(default, skip_serializing_if = "Option::is_none")]
638 pub path: Option<String>,
639
640 #[serde(default, skip_serializing_if = "Option::is_none")]
642 pub mtu: Option<u16>,
643
644 #[serde(default, skip_serializing_if = "Option::is_none")]
646 pub max_frame_bytes: Option<usize>,
647
648 #[serde(default, skip_serializing_if = "Option::is_none")]
650 pub max_send_queue: Option<usize>,
651
652 #[serde(default, skip_serializing_if = "Option::is_none")]
654 pub max_connections: Option<usize>,
655
656 #[serde(default, skip_serializing_if = "Option::is_none")]
658 pub max_inbound_connections: Option<usize>,
659
660 #[serde(default, skip_serializing_if = "Option::is_none")]
662 pub connect_timeout_ms: Option<u64>,
663
664 #[serde(default, skip_serializing_if = "Option::is_none")]
666 pub key_hint_timeout_ms: Option<u64>,
667
668 #[serde(default, skip_serializing_if = "Option::is_none")]
670 pub reconnect_initial_ms: Option<u64>,
671
672 #[serde(default, skip_serializing_if = "Option::is_none")]
674 pub reconnect_max_ms: Option<u64>,
675
676 #[serde(default, skip_serializing_if = "Option::is_none")]
678 pub ping_interval_secs: Option<u64>,
679
680 #[serde(default, skip_serializing_if = "Option::is_none")]
682 pub idle_timeout_secs: Option<u64>,
683
684 #[serde(default, skip_serializing_if = "Option::is_none")]
688 pub accept_connections: Option<bool>,
689}
690
691impl WebSocketConfig {
692 pub fn path(&self) -> &str {
693 self.path.as_deref().unwrap_or(DEFAULT_WEBSOCKET_PATH)
694 }
695
696 pub fn mtu(&self) -> u16 {
697 self.mtu.unwrap_or(DEFAULT_WEBSOCKET_MTU)
698 }
699
700 pub fn max_frame_bytes(&self) -> usize {
701 self.max_frame_bytes
702 .unwrap_or(DEFAULT_WEBSOCKET_MAX_FRAME_BYTES)
703 }
704
705 pub fn max_send_queue(&self) -> usize {
706 self.max_send_queue
707 .unwrap_or(DEFAULT_WEBSOCKET_MAX_SEND_QUEUE)
708 .max(1)
709 }
710
711 pub fn max_connections(&self) -> usize {
712 self.max_connections
713 .unwrap_or(DEFAULT_WEBSOCKET_MAX_CONNECTIONS)
714 .max(1)
715 }
716
717 pub fn max_inbound_connections(&self) -> usize {
718 self.max_inbound_connections
719 .unwrap_or(DEFAULT_WEBSOCKET_MAX_INBOUND)
720 .max(1)
721 .min(self.max_connections())
722 }
723
724 pub fn connect_timeout_ms(&self) -> u64 {
725 self.connect_timeout_ms
726 .unwrap_or(DEFAULT_WEBSOCKET_CONNECT_TIMEOUT_MS)
727 .max(1)
728 }
729
730 pub fn key_hint_timeout_ms(&self) -> u64 {
731 self.key_hint_timeout_ms
732 .unwrap_or(DEFAULT_WEBSOCKET_KEY_HINT_TIMEOUT_MS)
733 .max(1)
734 }
735
736 pub fn reconnect_initial_ms(&self) -> u64 {
737 self.reconnect_initial_ms
738 .unwrap_or(DEFAULT_WEBSOCKET_RECONNECT_INITIAL_MS)
739 .max(1)
740 }
741
742 pub fn reconnect_max_ms(&self) -> u64 {
743 self.reconnect_max_ms
744 .unwrap_or(DEFAULT_WEBSOCKET_RECONNECT_MAX_MS)
745 .max(self.reconnect_initial_ms())
746 }
747
748 pub fn ping_interval_secs(&self) -> u64 {
749 self.ping_interval_secs
750 .unwrap_or(DEFAULT_WEBSOCKET_PING_INTERVAL_SECS)
751 }
752
753 pub fn idle_timeout_secs(&self) -> u64 {
754 self.idle_timeout_secs
755 .unwrap_or(DEFAULT_WEBSOCKET_IDLE_TIMEOUT_SECS)
756 }
757
758 pub fn accept_connections(&self) -> bool {
759 self.accept_connections
760 .unwrap_or_else(|| self.bind_addr.is_some() || !self.seed_urls.is_empty())
761 }
762
763 pub fn validate(&self) -> Result<(), String> {
764 if let Some(bind_addr) = self.bind_addr.as_deref() {
765 bind_addr
766 .parse::<SocketAddr>()
767 .map_err(|error| format!("invalid bind_addr {bind_addr:?}: {error}"))?;
768 }
769 if !self.path().starts_with('/') || self.path().contains('?') || self.path().contains('#') {
770 return Err("path must be an absolute HTTP path without query or fragment".into());
771 }
772 if let Some(public_url) = self.public_url.as_deref() {
773 validate_websocket_url(public_url, false)?;
774 let uri = public_url
775 .parse::<tokio_tungstenite::tungstenite::http::Uri>()
776 .map_err(|error| format!("invalid public_url: {error}"))?;
777 if uri.path() != self.path() {
778 return Err(format!(
779 "public_url path {:?} does not match configured path {:?}",
780 uri.path(),
781 self.path()
782 ));
783 }
784 if self.bind_addr.is_none() {
785 return Err("public_url requires bind_addr".into());
786 }
787 }
788 let mut unique = std::collections::HashSet::new();
789 for seed_url in &self.seed_urls {
790 validate_websocket_url(seed_url, true)?;
791 if !unique.insert(seed_url) {
792 return Err(format!("duplicate seed URL {seed_url:?}"));
793 }
794 }
795 let minimum_frame = usize::from(self.mtu()).saturating_add(64);
796 if self.max_frame_bytes() < minimum_frame || self.max_frame_bytes() > 1024 * 1024 {
797 return Err(format!(
798 "max_frame_bytes must be between {minimum_frame} and 1048576"
799 ));
800 }
801 if self.max_send_queue() > 4096 {
802 return Err("max_send_queue must not exceed 4096".into());
803 }
804 if self.max_connections() > 4096 {
805 return Err("max_connections must not exceed 4096".into());
806 }
807 if self.max_inbound_connections() > self.max_connections() {
808 return Err("max_inbound_connections must not exceed max_connections".into());
809 }
810 if self.ping_interval_secs() > 0
811 && self.idle_timeout_secs() > 0
812 && self.idle_timeout_secs() <= self.ping_interval_secs()
813 {
814 return Err("idle_timeout_secs must exceed ping_interval_secs".into());
815 }
816 Ok(())
817 }
818}
819
820fn validate_websocket_url(raw: &str, allow_loopback_plaintext: bool) -> Result<(), String> {
821 let uri = raw
822 .parse::<tokio_tungstenite::tungstenite::http::Uri>()
823 .map_err(|error| format!("invalid WebSocket URL {raw:?}: {error}"))?;
824 let scheme = uri
825 .scheme_str()
826 .ok_or_else(|| format!("WebSocket URL {raw:?} is missing a scheme"))?;
827 let host = uri
828 .host()
829 .ok_or_else(|| format!("WebSocket URL {raw:?} is missing a host"))?;
830 if uri.authority().is_none() || uri.path().is_empty() {
831 return Err(format!("invalid WebSocket URL {raw:?}"));
832 }
833 match scheme {
834 "wss" => Ok(()),
835 "ws" if allow_loopback_plaintext && websocket_host_is_loopback(host) => Ok(()),
836 "ws" => Err(format!(
837 "plaintext WebSocket URL {raw:?} is allowed only for loopback seeds"
838 )),
839 _ => Err(format!("WebSocket URL {raw:?} must use wss://")),
840 }
841}
842
843fn websocket_host_is_loopback(host: &str) -> bool {
844 host.eq_ignore_ascii_case("localhost")
845 || host
846 .trim_matches(['[', ']'])
847 .parse::<IpAddr>()
848 .is_ok_and(|ip| ip.is_loopback())
849}
850
851mod aggregate;
852mod ble;
853#[cfg(test)]
854mod tests;
855mod tor;
856mod webrtc;
857
858pub use aggregate::TransportsConfig;
859pub use ble::BleConfig;
860pub use tor::{DirectoryServiceConfig, TorConfig};
861pub use webrtc::WebRtcConfig;