use std::collections::HashMap;
use std::net::{IpAddr, SocketAddr};
use serde::{Deserialize, Serialize};
fn parse_external_advert_addr(raw: &str, bind_port: u16) -> Option<SocketAddr> {
if let Ok(sa) = raw.parse::<SocketAddr>() {
return Some(sa);
}
let ip: IpAddr = raw.parse().ok()?;
Some(SocketAddr::new(ip, bind_port))
}
fn parse_bind_port(raw: &str) -> Option<u16> {
raw.parse::<SocketAddr>().ok().map(|sa| sa.port())
}
const DEFAULT_UDP_BIND_ADDR: &str = "0.0.0.0:2121";
const DEFAULT_UDP_MTU: u16 = 1280;
const DEFAULT_UDP_RECV_BUF: usize = 16 * 1024 * 1024;
const DEFAULT_UDP_SEND_BUF: usize = 8 * 1024 * 1024;
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct UdpConfig {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub bind_addr: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub mtu: Option<u16>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub recv_buf_size: Option<usize>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub send_buf_size: Option<usize>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub advertise_on_nostr: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub public: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub external_addr: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub outbound_only: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub accept_connections: Option<bool>,
}
impl UdpConfig {
pub fn bind_addr(&self) -> &str {
if self.outbound_only() {
"0.0.0.0:0"
} else {
self.bind_addr.as_deref().unwrap_or(DEFAULT_UDP_BIND_ADDR)
}
}
pub fn mtu(&self) -> u16 {
self.mtu.unwrap_or(DEFAULT_UDP_MTU)
}
pub fn recv_buf_size(&self) -> usize {
self.recv_buf_size.unwrap_or(DEFAULT_UDP_RECV_BUF)
}
pub fn send_buf_size(&self) -> usize {
self.send_buf_size.unwrap_or(DEFAULT_UDP_SEND_BUF)
}
pub fn advertise_on_nostr(&self) -> bool {
if self.outbound_only() {
false
} else {
self.advertise_on_nostr.unwrap_or(false)
}
}
pub fn is_public(&self) -> bool {
self.public.unwrap_or(false)
}
pub fn external_advert_addr(&self) -> Option<SocketAddr> {
let raw = self.external_addr.as_deref()?;
let bind_port = parse_bind_port(self.bind_addr())?;
parse_external_advert_addr(raw, bind_port)
}
pub fn outbound_only(&self) -> bool {
self.outbound_only.unwrap_or(false)
}
pub fn accept_connections(&self) -> bool {
self.accept_connections.unwrap_or(true)
}
}
#[cfg(feature = "sim-transport")]
const DEFAULT_SIM_MTU: u16 = 1280;
#[cfg(feature = "sim-transport")]
const DEFAULT_SIM_NETWORK: &str = "default";
#[cfg(feature = "sim-transport")]
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct SimTransportConfig {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub network: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub addr: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub mtu: Option<u16>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub auto_connect: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub accept_connections: Option<bool>,
}
#[cfg(feature = "sim-transport")]
impl SimTransportConfig {
pub fn network(&self) -> &str {
self.network.as_deref().unwrap_or(DEFAULT_SIM_NETWORK)
}
pub fn mtu(&self) -> u16 {
self.mtu.unwrap_or(DEFAULT_SIM_MTU)
}
pub fn auto_connect(&self) -> bool {
self.auto_connect.unwrap_or(false)
}
pub fn accept_connections(&self) -> bool {
self.accept_connections.unwrap_or(true)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum TransportInstances<T> {
Single(T),
Named(HashMap<String, T>),
}
impl<T> TransportInstances<T> {
pub fn len(&self) -> usize {
match self {
TransportInstances::Single(_) => 1,
TransportInstances::Named(map) => map.len(),
}
}
pub fn is_empty(&self) -> bool {
match self {
TransportInstances::Single(_) => false,
TransportInstances::Named(map) => map.is_empty(),
}
}
pub fn iter(&self) -> impl Iterator<Item = (Option<&str>, &T)> {
match self {
TransportInstances::Single(config) => vec![(None, config)].into_iter(),
TransportInstances::Named(map) => map
.iter()
.map(|(k, v)| (Some(k.as_str()), v))
.collect::<Vec<_>>()
.into_iter(),
}
}
}
impl<T> Default for TransportInstances<T> {
fn default() -> Self {
TransportInstances::Named(HashMap::new())
}
}
const DEFAULT_ETHERNET_ETHERTYPE: u16 = 0x2121;
const DEFAULT_ETHERNET_RECV_BUF: usize = 2 * 1024 * 1024;
const DEFAULT_ETHERNET_SEND_BUF: usize = 2 * 1024 * 1024;
const DEFAULT_BEACON_INTERVAL_SECS: u64 = 30;
const MIN_BEACON_INTERVAL_SECS: u64 = 10;
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct EthernetConfig {
pub interface: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub ethertype: Option<u16>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub mtu: Option<u16>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub recv_buf_size: Option<usize>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub send_buf_size: Option<usize>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub discovery: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub announce: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub auto_connect: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub accept_connections: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub discovery_scope: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub beacon_interval_secs: Option<u64>,
}
impl EthernetConfig {
pub fn ethertype(&self) -> u16 {
self.ethertype.unwrap_or(DEFAULT_ETHERNET_ETHERTYPE)
}
pub fn recv_buf_size(&self) -> usize {
self.recv_buf_size.unwrap_or(DEFAULT_ETHERNET_RECV_BUF)
}
pub fn send_buf_size(&self) -> usize {
self.send_buf_size.unwrap_or(DEFAULT_ETHERNET_SEND_BUF)
}
pub fn discovery(&self) -> bool {
self.discovery.unwrap_or(true)
}
pub fn announce(&self) -> bool {
self.announce.unwrap_or(false)
}
pub fn auto_connect(&self) -> bool {
self.auto_connect.unwrap_or(false)
}
pub fn accept_connections(&self) -> bool {
self.accept_connections.unwrap_or(false)
}
pub fn discovery_scope(&self) -> Option<&str> {
self.discovery_scope.as_deref().filter(|s| !s.is_empty())
}
pub fn beacon_interval_secs(&self) -> u64 {
self.beacon_interval_secs
.unwrap_or(DEFAULT_BEACON_INTERVAL_SECS)
.max(MIN_BEACON_INTERVAL_SECS)
}
}
const DEFAULT_TCP_MTU: u16 = 1400;
const DEFAULT_TCP_CONNECT_TIMEOUT_MS: u64 = 5000;
const DEFAULT_TCP_FIRST_FRAME_TIMEOUT_MS: u64 = 3000;
const DEFAULT_TCP_KEEPALIVE_SECS: u64 = 30;
const DEFAULT_TCP_RECV_BUF: usize = 2 * 1024 * 1024;
const DEFAULT_TCP_SEND_BUF: usize = 2 * 1024 * 1024;
const DEFAULT_TCP_MAX_INBOUND: usize = 256;
const DEFAULT_WEBSOCKET_PATH: &str = "/fips";
const DEFAULT_WEBSOCKET_MTU: u16 = 1400;
const DEFAULT_WEBSOCKET_MAX_FRAME_BYTES: usize = 66 * 1024;
const DEFAULT_WEBSOCKET_CONNECT_TIMEOUT_MS: u64 = 5_000;
const DEFAULT_WEBSOCKET_KEY_HINT_TIMEOUT_MS: u64 = 3_000;
const DEFAULT_WEBSOCKET_RECONNECT_INITIAL_MS: u64 = 1_000;
const DEFAULT_WEBSOCKET_RECONNECT_MAX_MS: u64 = 30_000;
const DEFAULT_WEBSOCKET_MAX_CONNECTIONS: usize = 256;
const DEFAULT_WEBSOCKET_MAX_INBOUND: usize = 128;
const DEFAULT_WEBSOCKET_MAX_SEND_QUEUE: usize = 256;
const DEFAULT_WEBSOCKET_PING_INTERVAL_SECS: u64 = 20;
const DEFAULT_WEBSOCKET_IDLE_TIMEOUT_SECS: u64 = 90;
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct TcpConfig {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub bind_addr: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub mtu: Option<u16>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub connect_timeout_ms: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub first_frame_timeout_ms: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub nodelay: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub keepalive_secs: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub recv_buf_size: Option<usize>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub send_buf_size: Option<usize>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub max_inbound_connections: Option<usize>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub advertise_on_nostr: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub external_addr: Option<String>,
}
impl TcpConfig {
pub fn mtu(&self) -> u16 {
self.mtu.unwrap_or(DEFAULT_TCP_MTU)
}
pub fn connect_timeout_ms(&self) -> u64 {
self.connect_timeout_ms
.unwrap_or(DEFAULT_TCP_CONNECT_TIMEOUT_MS)
}
pub fn first_frame_timeout_ms(&self) -> u64 {
self.first_frame_timeout_ms
.unwrap_or(DEFAULT_TCP_FIRST_FRAME_TIMEOUT_MS)
}
pub fn nodelay(&self) -> bool {
self.nodelay.unwrap_or(true)
}
pub fn keepalive_secs(&self) -> u64 {
self.keepalive_secs.unwrap_or(DEFAULT_TCP_KEEPALIVE_SECS)
}
pub fn recv_buf_size(&self) -> usize {
self.recv_buf_size.unwrap_or(DEFAULT_TCP_RECV_BUF)
}
pub fn send_buf_size(&self) -> usize {
self.send_buf_size.unwrap_or(DEFAULT_TCP_SEND_BUF)
}
pub fn max_inbound_connections(&self) -> usize {
self.max_inbound_connections
.unwrap_or(DEFAULT_TCP_MAX_INBOUND)
}
pub fn advertise_on_nostr(&self) -> bool {
self.advertise_on_nostr.unwrap_or(false)
}
pub fn external_advert_addr(&self) -> Option<SocketAddr> {
let raw = self.external_addr.as_deref()?;
let bind_port = parse_bind_port(self.bind_addr.as_deref()?)?;
parse_external_advert_addr(raw, bind_port)
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct WebSocketConfig {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub bind_addr: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub public_url: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub seed_urls: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub path: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub mtu: Option<u16>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub max_frame_bytes: Option<usize>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub max_send_queue: Option<usize>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub max_connections: Option<usize>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub max_inbound_connections: Option<usize>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub connect_timeout_ms: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub key_hint_timeout_ms: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub reconnect_initial_ms: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub reconnect_max_ms: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub ping_interval_secs: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub idle_timeout_secs: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub accept_connections: Option<bool>,
}
impl WebSocketConfig {
pub fn path(&self) -> &str {
self.path.as_deref().unwrap_or(DEFAULT_WEBSOCKET_PATH)
}
pub fn mtu(&self) -> u16 {
self.mtu.unwrap_or(DEFAULT_WEBSOCKET_MTU)
}
pub fn max_frame_bytes(&self) -> usize {
self.max_frame_bytes
.unwrap_or(DEFAULT_WEBSOCKET_MAX_FRAME_BYTES)
}
pub fn max_send_queue(&self) -> usize {
self.max_send_queue
.unwrap_or(DEFAULT_WEBSOCKET_MAX_SEND_QUEUE)
.max(1)
}
pub fn max_connections(&self) -> usize {
self.max_connections
.unwrap_or(DEFAULT_WEBSOCKET_MAX_CONNECTIONS)
.max(1)
}
pub fn max_inbound_connections(&self) -> usize {
self.max_inbound_connections
.unwrap_or(DEFAULT_WEBSOCKET_MAX_INBOUND)
.max(1)
.min(self.max_connections())
}
pub fn connect_timeout_ms(&self) -> u64 {
self.connect_timeout_ms
.unwrap_or(DEFAULT_WEBSOCKET_CONNECT_TIMEOUT_MS)
.max(1)
}
pub fn key_hint_timeout_ms(&self) -> u64 {
self.key_hint_timeout_ms
.unwrap_or(DEFAULT_WEBSOCKET_KEY_HINT_TIMEOUT_MS)
.max(1)
}
pub fn reconnect_initial_ms(&self) -> u64 {
self.reconnect_initial_ms
.unwrap_or(DEFAULT_WEBSOCKET_RECONNECT_INITIAL_MS)
.max(1)
}
pub fn reconnect_max_ms(&self) -> u64 {
self.reconnect_max_ms
.unwrap_or(DEFAULT_WEBSOCKET_RECONNECT_MAX_MS)
.max(self.reconnect_initial_ms())
}
pub fn ping_interval_secs(&self) -> u64 {
self.ping_interval_secs
.unwrap_or(DEFAULT_WEBSOCKET_PING_INTERVAL_SECS)
}
pub fn idle_timeout_secs(&self) -> u64 {
self.idle_timeout_secs
.unwrap_or(DEFAULT_WEBSOCKET_IDLE_TIMEOUT_SECS)
}
pub fn accept_connections(&self) -> bool {
self.accept_connections
.unwrap_or_else(|| self.bind_addr.is_some() || !self.seed_urls.is_empty())
}
pub fn validate(&self) -> Result<(), String> {
if let Some(bind_addr) = self.bind_addr.as_deref() {
bind_addr
.parse::<SocketAddr>()
.map_err(|error| format!("invalid bind_addr {bind_addr:?}: {error}"))?;
}
if !self.path().starts_with('/') || self.path().contains('?') || self.path().contains('#') {
return Err("path must be an absolute HTTP path without query or fragment".into());
}
if let Some(public_url) = self.public_url.as_deref() {
validate_websocket_url(public_url, false)?;
let uri = public_url
.parse::<tokio_tungstenite::tungstenite::http::Uri>()
.map_err(|error| format!("invalid public_url: {error}"))?;
if uri.path() != self.path() {
return Err(format!(
"public_url path {:?} does not match configured path {:?}",
uri.path(),
self.path()
));
}
if self.bind_addr.is_none() {
return Err("public_url requires bind_addr".into());
}
}
let mut unique = std::collections::HashSet::new();
for seed_url in &self.seed_urls {
validate_websocket_url(seed_url, true)?;
if !unique.insert(seed_url) {
return Err(format!("duplicate seed URL {seed_url:?}"));
}
}
let minimum_frame = usize::from(self.mtu()).saturating_add(64);
if self.max_frame_bytes() < minimum_frame || self.max_frame_bytes() > 1024 * 1024 {
return Err(format!(
"max_frame_bytes must be between {minimum_frame} and 1048576"
));
}
if self.max_send_queue() > 4096 {
return Err("max_send_queue must not exceed 4096".into());
}
if self.max_connections() > 4096 {
return Err("max_connections must not exceed 4096".into());
}
if self.max_inbound_connections() > self.max_connections() {
return Err("max_inbound_connections must not exceed max_connections".into());
}
if self.ping_interval_secs() > 0
&& self.idle_timeout_secs() > 0
&& self.idle_timeout_secs() <= self.ping_interval_secs()
{
return Err("idle_timeout_secs must exceed ping_interval_secs".into());
}
Ok(())
}
}
fn validate_websocket_url(raw: &str, allow_loopback_plaintext: bool) -> Result<(), String> {
let uri = raw
.parse::<tokio_tungstenite::tungstenite::http::Uri>()
.map_err(|error| format!("invalid WebSocket URL {raw:?}: {error}"))?;
let scheme = uri
.scheme_str()
.ok_or_else(|| format!("WebSocket URL {raw:?} is missing a scheme"))?;
let host = uri
.host()
.ok_or_else(|| format!("WebSocket URL {raw:?} is missing a host"))?;
if uri.authority().is_none() || uri.path().is_empty() {
return Err(format!("invalid WebSocket URL {raw:?}"));
}
match scheme {
"wss" => Ok(()),
"ws" if allow_loopback_plaintext && websocket_host_is_loopback(host) => Ok(()),
"ws" => Err(format!(
"plaintext WebSocket URL {raw:?} is allowed only for loopback seeds"
)),
_ => Err(format!("WebSocket URL {raw:?} must use wss://")),
}
}
fn websocket_host_is_loopback(host: &str) -> bool {
host.eq_ignore_ascii_case("localhost")
|| host
.trim_matches(['[', ']'])
.parse::<IpAddr>()
.is_ok_and(|ip| ip.is_loopback())
}
const DEFAULT_TOR_SOCKS5_ADDR: &str = "127.0.0.1:9050";
const DEFAULT_TOR_CONTROL_ADDR: &str = "/run/tor/control";
const DEFAULT_TOR_COOKIE_PATH: &str = "/var/run/tor/control.authcookie";
const DEFAULT_TOR_CONNECT_TIMEOUT_MS: u64 = 120_000;
const DEFAULT_TOR_MTU: u16 = 1400;
const DEFAULT_TOR_MAX_INBOUND: usize = 64;
const DEFAULT_HOSTNAME_FILE: &str = "/var/lib/tor/fips_onion_service/hostname";
const DEFAULT_DIRECTORY_BIND_ADDR: &str = "127.0.0.1:8443";
const DEFAULT_TOR_ADVERTISED_PORT: u16 = 443;
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct TorConfig {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub mode: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub socks5_addr: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub connect_timeout_ms: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub mtu: Option<u16>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub control_addr: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub control_auth: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub cookie_path: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub max_inbound_connections: Option<usize>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub directory_service: Option<DirectoryServiceConfig>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub advertise_on_nostr: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub advertised_port: Option<u16>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct DirectoryServiceConfig {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub hostname_file: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub bind_addr: Option<String>,
}
impl DirectoryServiceConfig {
pub fn hostname_file(&self) -> &str {
self.hostname_file
.as_deref()
.unwrap_or(DEFAULT_HOSTNAME_FILE)
}
pub fn bind_addr(&self) -> &str {
self.bind_addr
.as_deref()
.unwrap_or(DEFAULT_DIRECTORY_BIND_ADDR)
}
}
impl TorConfig {
pub fn mode(&self) -> &str {
self.mode.as_deref().unwrap_or("socks5")
}
pub fn socks5_addr(&self) -> &str {
self.socks5_addr
.as_deref()
.unwrap_or(DEFAULT_TOR_SOCKS5_ADDR)
}
pub fn control_addr(&self) -> &str {
self.control_addr
.as_deref()
.unwrap_or(DEFAULT_TOR_CONTROL_ADDR)
}
pub fn control_auth(&self) -> &str {
self.control_auth.as_deref().unwrap_or("cookie")
}
pub fn cookie_path(&self) -> &str {
self.cookie_path
.as_deref()
.unwrap_or(DEFAULT_TOR_COOKIE_PATH)
}
pub fn connect_timeout_ms(&self) -> u64 {
self.connect_timeout_ms
.unwrap_or(DEFAULT_TOR_CONNECT_TIMEOUT_MS)
}
pub fn mtu(&self) -> u16 {
self.mtu.unwrap_or(DEFAULT_TOR_MTU)
}
pub fn max_inbound_connections(&self) -> usize {
self.max_inbound_connections
.unwrap_or(DEFAULT_TOR_MAX_INBOUND)
}
pub fn advertise_on_nostr(&self) -> bool {
self.advertise_on_nostr.unwrap_or(false)
}
pub fn advertised_port(&self) -> u16 {
self.advertised_port.unwrap_or(DEFAULT_TOR_ADVERTISED_PORT)
}
}
const DEFAULT_WEBRTC_MTU: u16 = 1200;
const DEFAULT_WEBRTC_CONNECT_TIMEOUT_MS: u64 = 30_000;
const DEFAULT_WEBRTC_ICE_GATHER_TIMEOUT_MS: u64 = 2_000;
const DEFAULT_WEBRTC_MAX_CONNECTIONS: usize = 6;
const DEFAULT_WEBRTC_DATA_CHANNEL_LABEL: &str = "fips";
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct WebRtcConfig {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub advertise_on_nostr: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub auto_connect: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub accept_connections: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub mtu: Option<u16>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub max_connections: Option<usize>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub connect_timeout_ms: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub ice_gather_timeout_ms: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub data_channel_label: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub ordered: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub max_retransmits: Option<u16>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub stun_servers: Option<Vec<String>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub resolve_mdns_candidates: Option<bool>,
}
impl WebRtcConfig {
pub fn advertise_on_nostr(&self) -> bool {
self.advertise_on_nostr.unwrap_or(false)
}
pub fn auto_connect(&self) -> bool {
self.auto_connect.unwrap_or(false)
}
pub fn accept_connections(&self) -> bool {
self.accept_connections
.unwrap_or_else(|| self.advertise_on_nostr())
}
pub fn mtu(&self) -> u16 {
self.mtu.unwrap_or(DEFAULT_WEBRTC_MTU)
}
pub fn max_connections(&self) -> usize {
self.max_connections
.unwrap_or(DEFAULT_WEBRTC_MAX_CONNECTIONS)
}
pub fn connect_timeout_ms(&self) -> u64 {
self.connect_timeout_ms
.unwrap_or(DEFAULT_WEBRTC_CONNECT_TIMEOUT_MS)
}
pub fn ice_gather_timeout_ms(&self) -> u64 {
self.ice_gather_timeout_ms
.unwrap_or(DEFAULT_WEBRTC_ICE_GATHER_TIMEOUT_MS)
}
pub fn data_channel_label(&self) -> &str {
self.data_channel_label
.as_deref()
.unwrap_or(DEFAULT_WEBRTC_DATA_CHANNEL_LABEL)
}
pub fn ordered(&self) -> bool {
self.ordered.unwrap_or(true)
}
pub fn max_retransmits(&self) -> Option<u16> {
self.max_retransmits
}
pub fn resolve_mdns_candidates(&self) -> bool {
self.resolve_mdns_candidates.unwrap_or(true)
}
pub fn stun_servers<'a>(&'a self, fallback: &'a [String]) -> Vec<String> {
self.stun_servers
.as_ref()
.cloned()
.unwrap_or_else(|| fallback.to_vec())
}
}
mod aggregate;
mod ble;
#[cfg(test)]
mod tests;
pub use aggregate::TransportsConfig;
pub use ble::BleConfig;