#[derive(Debug, Clone)]
struct FipsEndpointTransportConfig {
listen_port: u16,
advertised_endpoint: String,
advertise_public_endpoint: bool,
nostr_discovery_enabled: bool,
webrtc_enabled: bool,
stun_servers: Vec<String>,
nostr_relays: Vec<String>,
share_local_candidates: bool,
}
const FIPS_CONFIGURED_PEER_ENDPOINT_PRIORITY: u8 = 10;
const FIPS_DYNAMIC_PEER_ENDPOINT_PRIORITY: u8 = 100;
const FIPS_PRIVATE_DYNAMIC_PEER_ENDPOINT_PRIORITY: u8 = 200;
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct FipsPeerAddressHint {
pub(crate) addr: String,
pub(crate) seen_at_ms: Option<u64>,
pub(crate) priority: u8,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct FipsEndpointPeerTransportConfig {
pub(crate) npub: String,
pub(crate) addresses: Vec<FipsPeerAddressHint>,
pub(crate) auto_reconnect: bool,
pub(crate) discovery_fallback_transit: bool,
}
pub(crate) fn prioritize_join_roster_route(
peers: Vec<FipsEndpointPeerTransportConfig>,
route_pubkey: &str,
) -> Result<(String, Vec<FipsEndpointPeerTransportConfig>)> {
let route_pubkey = normalize_nostr_pubkey(route_pubkey)
.with_context(|| format!("invalid join roster recipient {route_pubkey}"))?;
let route_npub = PublicKey::from_hex(&route_pubkey)?.to_bech32()?;
let route_address = FipsPeerAddressHint {
addr: format!("webrtc:02{route_pubkey}"),
seen_at_ms: None,
priority: FIPS_CONFIGURED_PEER_ENDPOINT_PRIORITY,
};
let peers = prioritize_join_roster_peer(peers, &route_npub, route_address);
Ok((route_npub, peers))
}
fn prioritize_join_roster_peer(
mut peers: Vec<FipsEndpointPeerTransportConfig>,
route_npub: &str,
route_address: FipsPeerAddressHint,
) -> Vec<FipsEndpointPeerTransportConfig> {
let mut route_peer = peers
.iter()
.position(|peer| peer.npub == route_npub)
.map(|index| peers.remove(index))
.unwrap_or_else(|| FipsEndpointPeerTransportConfig {
npub: route_npub.to_string(),
addresses: Vec::new(),
auto_reconnect: true,
discovery_fallback_transit: true,
});
if !route_peer.addresses.contains(&route_address) {
route_peer.addresses.push(route_address);
}
route_peer
.addresses
.retain(|hint| split_peer_transport_addr(&hint.addr).0 != "nostr_relay");
route_peer.auto_reconnect = true;
peers.insert(0, route_peer);
peers
}
fn fips_peer_address_from_hint(hint: &FipsPeerAddressHint) -> PeerAddress {
let (transport, addr) = split_peer_transport_addr(&hint.addr);
let mut peer_address = PeerAddress::with_priority(transport, addr, hint.priority);
if let Some(seen_at_ms) = hint.seen_at_ms {
peer_address = peer_address.learned().with_seen_at_ms(seen_at_ms);
}
peer_address
}
fn retain_enabled_peer_transport_addresses(
peers: &mut [FipsEndpointPeerTransportConfig],
webrtc_enabled: bool,
) {
if webrtc_enabled {
return;
}
for peer in peers {
peer.addresses
.retain(|hint| split_peer_transport_addr(&hint.addr).0 != "webrtc");
}
}
fn dynamic_endpoint_priority(addr: &str) -> u8 {
endpoint_hint_priority(addr, FIPS_DYNAMIC_PEER_ENDPOINT_PRIORITY)
}
fn endpoint_hint_priority(addr: &str, normal_priority: u8) -> u8 {
if endpoint_addr_is_private_or_local(addr) {
FIPS_PRIVATE_DYNAMIC_PEER_ENDPOINT_PRIORITY
} else {
normal_priority
}
}
fn endpoint_addr_is_private_or_local(addr: &str) -> bool {
endpoint_addr_ip(addr).is_some_and(endpoint_ip_is_private_or_local)
}
fn endpoint_ip_is_private_or_local(ip: IpAddr) -> bool {
match ip {
IpAddr::V4(ip) => {
ip.is_private()
|| ipv4_is_cgnat_addr(ip)
|| ip.is_link_local()
|| ip.is_loopback()
|| ip.is_unspecified()
|| ip.is_multicast()
|| ip.is_broadcast()
|| ipv4_is_benchmark_addr(ip)
}
IpAddr::V6(ip) => {
ip.is_unique_local()
|| ip.is_unicast_link_local()
|| ip.is_loopback()
|| ip.is_unspecified()
|| ip.is_multicast()
}
}
}
fn ipv4_is_benchmark_addr(addr: Ipv4Addr) -> bool {
let octets = addr.octets();
octets[0] == 198 && (18..=19).contains(&octets[1])
}
fn fips_endpoint_config_with_open_discovery_limit(
peers: &[FipsEndpointPeerTransportConfig],
transport: Option<&FipsEndpointTransportConfig>,
mesh_mtu: MeshMtu,
nostr_discovery_policy: NostrDiscoveryPolicy,
open_discovery_max_pending: usize,
) -> Config {
let mut config = Config::new();
config.node.control.enabled = false;
config.node.routing.mode = RoutingMode::ReplyLearned;
config.node.retry.base_interval_secs = FIPS_RECONNECT_BACKOFF_BASE_SECS;
config.node.retry.max_backoff_secs = FIPS_RECONNECT_BACKOFF_MAX_SECS;
config.node.heartbeat_interval_secs = FIPS_ENDPOINT_HEARTBEAT_INTERVAL_SECS;
config.node.link_dead_timeout_secs = FIPS_ENDPOINT_LINK_DEAD_TIMEOUT_SECS;
config.node.fast_link_dead_timeout_secs = FIPS_ENDPOINT_FAST_LINK_DEAD_TIMEOUT_SECS;
config.node.session.idle_timeout_secs = FIPS_ENDPOINT_SESSION_IDLE_TIMEOUT_SECS;
config.node.session.pending_packets_per_dest = FIPS_ENDPOINT_PENDING_PACKETS_PER_DEST;
config.node.rekey.after_secs = FIPS_ENDPOINT_REKEY_AFTER_SECS;
config.dns.enabled = false;
config.node.discovery.backoff_base_secs = FIPS_DISCOVERY_BACKOFF_BASE_SECS;
config.node.discovery.backoff_max_secs = FIPS_DISCOVERY_BACKOFF_MAX_SECS;
config.node.discovery.forward_min_interval_secs = FIPS_DISCOVERY_FORWARD_MIN_INTERVAL_SECS;
let advertise_public_endpoint = transport
.map(|transport| transport.advertise_public_endpoint)
.unwrap_or(false);
let nostr_discovery_enabled = transport
.map(|transport| transport.nostr_discovery_enabled)
.unwrap_or(true);
let advertise_on_nostr = nostr_discovery_enabled && transport.is_some();
let nostr_enabled = nostr_discovery_enabled && (transport.is_some() || !peers.is_empty());
config.node.discovery.nostr.enabled = nostr_enabled;
config.node.discovery.nostr.advertise = advertise_on_nostr;
let nostr_relay_fallback_enabled = transport.is_some_and(|transport| {
fips_nostr_relay_fallback_enabled(
transport.nostr_discovery_enabled,
transport.webrtc_enabled,
&transport.nostr_relays,
)
});
if nostr_relay_fallback_enabled {
enable_nostr_relay_fallback_transport(&mut config);
}
config.node.discovery.nostr.policy = nostr_discovery_policy;
config.node.discovery.nostr.open_discovery_max_pending = open_discovery_max_pending;
config.node.discovery.nostr.failure_streak_threshold = FIPS_NOSTR_FAILURE_STREAK_THRESHOLD;
config.node.discovery.nostr.extended_cooldown_secs = FIPS_NOSTR_EXTENDED_COOLDOWN_SECS;
config.node.discovery.nostr.startup_sweep_max_age_secs = FIPS_NOSTR_STARTUP_SWEEP_MAX_AGE_SECS;
config.node.discovery.nostr.share_local_candidates = nostr_discovery_enabled
&& transport
.map(|transport| transport.share_local_candidates)
.unwrap_or(false);
config.node.discovery.lan.enabled = nostr_discovery_enabled
&& transport
.map(|transport| transport.share_local_candidates)
.unwrap_or(false);
let bind_addr = transport.map(fips_udp_bind_addr);
let external_addr = transport
.filter(|_| advertise_public_endpoint)
.and_then(fips_udp_external_addr);
if let Some(transport) = transport {
config.node.discovery.nostr.stun_servers = transport.stun_servers.clone();
if !transport.nostr_relays.is_empty() {
config.node.discovery.nostr.advert_relays = transport.nostr_relays.clone();
}
if nostr_enabled {
configure_fips_webrtc_transport(
&mut config,
transport.webrtc_enabled && advertise_on_nostr,
&transport.stun_servers,
mesh_mtu.underlay_udp,
);
}
}
config.transports.udp = TransportInstances::Single(UdpConfig {
bind_addr,
advertise_on_nostr: Some(advertise_on_nostr),
public: Some(advertise_public_endpoint),
external_addr,
outbound_only: Some(transport.is_none()),
accept_connections: Some(transport.is_some()),
mtu: Some(mesh_mtu.underlay_udp),
send_buf_size: fips_udp_send_buf_size(),
..UdpConfig::default()
});
let needs_tcp = peers.iter().any(|peer| {
peer.addresses
.iter()
.any(|hint| split_peer_transport_addr(&hint.addr).0 == "tcp")
});
if needs_tcp {
#[allow(clippy::default_trait_access)]
{
config.transports.tcp = TransportInstances::Single(Default::default());
}
}
config.peers = peers
.iter()
.map(|peer| FipsPeerConfig {
npub: peer.npub.clone(),
alias: None,
addresses: peer
.addresses
.iter()
.map(fips_peer_address_from_hint)
.collect(),
connect_policy: ConnectPolicy::AutoConnect,
auto_reconnect: peer.auto_reconnect,
discovery_fallback_transit: peer.discovery_fallback_transit,
})
.collect();
config
}
#[cfg(any(target_os = "linux", target_os = "macos", test))]
fn fips_endpoint_config_for_local_ethernet(
peers: &[FipsEndpointPeerTransportConfig],
ethernet: &FipsLocalEthernetUnderlayConfig,
mesh_mtu: MeshMtu,
) -> Config {
let mut config = fips_endpoint_config_with_open_discovery_limit(
peers,
None,
mesh_mtu,
NostrDiscoveryPolicy::ConfiguredOnly,
0,
);
config.node.discovery.nostr.enabled = false;
config.node.discovery.nostr.advertise = false;
config.node.discovery.nostr.share_local_candidates = false;
config.node.discovery.nostr.advert_relays.clear();
config.node.discovery.nostr.stun_servers.clear();
config.node.discovery.lan.enabled = false;
config.node.discovery.lan.scope = Some(ethernet.discovery_scope.clone());
config.node.discovery.local.enabled = false;
config.transports = Default::default();
config.transports.ethernet = TransportInstances::Single(EthernetConfig {
interface: ethernet.interface.clone(),
discovery: Some(true),
announce: Some(true),
auto_connect: Some(true),
accept_connections: Some(true),
discovery_scope: Some(ethernet.discovery_scope.clone()),
mtu: Some(mesh_mtu.underlay_udp),
..EthernetConfig::default()
});
for peer in &mut config.peers {
peer.addresses.clear();
}
config
}
#[cfg(any(target_os = "linux", test))]
pub(crate) fn local_ethernet_only_endpoint_config(
interface: &str,
discovery_scope: &str,
) -> Config {
fips_endpoint_config_for_local_ethernet(
&[],
&FipsLocalEthernetUnderlayConfig {
interface: interface.to_string(),
discovery_scope: discovery_scope.to_string(),
},
resolve_private_mesh_mtu(None, None, None),
)
}
fn fips_endpoint_peers_from_mesh(
mesh_peers: &[FipsMeshPeerConfig],
operator_static_endpoints: Vec<(String, Vec<String>)>,
recent_peer_endpoints: Vec<(String, Vec<(String, u64)>)>,
) -> Vec<FipsEndpointPeerTransportConfig> {
let mut peers = HashMap::<String, FipsEndpointPeerTransportConfig>::new();
for peer in mesh_peers {
let npub = normalize_fips_endpoint_npub(&peer.endpoint_npub);
peers
.entry(npub.clone())
.or_insert_with(|| FipsEndpointPeerTransportConfig {
npub,
addresses: Vec::new(),
auto_reconnect: true,
discovery_fallback_transit: !peer.advertises_default_route(),
});
}
for (npub, addresses) in operator_static_endpoints {
let npub = normalize_fips_endpoint_npub(&npub);
let peer = peers
.entry(npub.clone())
.or_insert_with(|| FipsEndpointPeerTransportConfig {
npub,
addresses: Vec::new(),
auto_reconnect: false,
discovery_fallback_transit: true,
});
for raw in addresses {
let trimmed = raw.trim();
if trimmed.is_empty() {
continue;
}
let priority = FIPS_CONFIGURED_PEER_ENDPOINT_PRIORITY;
if let Some(existing) = peer.addresses.iter_mut().find(|hint| hint.addr == trimmed) {
existing.seen_at_ms = None;
existing.priority = existing.priority.min(priority);
continue;
}
peer.addresses.push(FipsPeerAddressHint {
addr: trimmed.to_string(),
seen_at_ms: None,
priority,
});
}
}
for (npub, addresses) in recent_peer_endpoints {
let npub = normalize_fips_endpoint_npub(&npub);
let peer = peers
.entry(npub.clone())
.or_insert_with(|| FipsEndpointPeerTransportConfig {
npub,
addresses: Vec::new(),
auto_reconnect: false,
discovery_fallback_transit: true,
});
for (addr, seen_at_ms) in addresses {
let trimmed = addr.trim();
if trimmed.is_empty() {
continue;
}
if let Some(existing) = peer.addresses.iter_mut().find(|hint| hint.addr == trimmed) {
if let Some(existing_seen_at_ms) = existing.seen_at_ms {
let priority = dynamic_endpoint_priority(trimmed);
existing.seen_at_ms = Some(existing_seen_at_ms.max(seen_at_ms));
existing.priority = existing.priority.min(priority);
}
continue;
}
peer.addresses.push(FipsPeerAddressHint {
addr: trimmed.to_string(),
seen_at_ms: Some(seen_at_ms),
priority: dynamic_endpoint_priority(trimmed),
});
}
}
let mut peers = peers.into_values().collect::<Vec<_>>();
for peer in &mut peers {
peer.addresses.sort_by(|a, b| a.addr.cmp(&b.addr));
peer.addresses.dedup_by(|a, b| a.addr == b.addr);
}
peers.sort_by(|left, right| left.npub.cmp(&right.npub));
peers
}
fn add_nostr_relay_fallback_for_mesh_peers(
peers: &mut [FipsEndpointPeerTransportConfig],
mesh_peers: &[FipsMeshPeerConfig],
) {
let roster = mesh_peers
.iter()
.map(|peer| normalize_fips_endpoint_npub(&peer.endpoint_npub))
.collect::<HashSet<_>>();
for peer in peers {
if roster.contains(&peer.npub) {
add_nostr_relay_fallback_address(peer);
}
}
}
fn enable_nostr_relay_fallback_transport(config: &mut Config) {
config.transports.nostr_relay = TransportInstances::Single(NostrRelayConfig {
auto_connect: Some(false),
accept_connections: Some(false),
..NostrRelayConfig::default()
});
}
fn add_nostr_relay_fallback_address(peer: &mut FipsEndpointPeerTransportConfig) {
let address = format!("nostr_relay:{}", peer.npub);
if peer.addresses.iter().any(|hint| hint.addr == address) {
return;
}
peer.addresses.push(FipsPeerAddressHint {
addr: address,
seen_at_ms: None,
priority: FIPS_NOSTR_RELAY_FALLBACK_PRIORITY,
});
}
#[cfg(feature = "paid-exit")]
pub(crate) fn fips_endpoint_peers_with_paid_route_admissions(
endpoint_peers: Vec<FipsEndpointPeerTransportConfig>,
admissions: &[FipsPaidRouteAdmission],
) -> Vec<FipsEndpointPeerTransportConfig> {
let mut peers = endpoint_peers
.into_iter()
.map(|mut peer| {
peer.npub = normalize_fips_endpoint_npub(&peer.npub);
(peer.npub.clone(), peer)
})
.collect::<HashMap<_, _>>();
for admission in admissions {
let npub = normalize_fips_endpoint_npub(&admission.participant_pubkey);
if npub.trim().is_empty() {
continue;
}
let peer = peers
.entry(npub.clone())
.or_insert_with(|| FipsEndpointPeerTransportConfig {
npub,
addresses: Vec::new(),
auto_reconnect: true,
discovery_fallback_transit: false,
});
peer.auto_reconnect = true;
peer.discovery_fallback_transit = false;
}
let mut peers = peers.into_values().collect::<Vec<_>>();
for peer in &mut peers {
peer.addresses.sort_by(|a, b| a.addr.cmp(&b.addr));
peer.addresses.dedup_by(|a, b| a.addr == b.addr);
}
peers.sort_by(|left, right| left.npub.cmp(&right.npub));
peers
}
fn normalize_fips_endpoint_npub(value: &str) -> String {
let trimmed = value.trim();
normalize_nostr_pubkey(trimmed)
.ok()
.and_then(|pubkey| {
PublicKey::from_hex(&pubkey)
.ok()
.and_then(|public_key| public_key.to_bech32().ok())
})
.unwrap_or_else(|| trimmed.to_string())
}
fn participant_pubkey_bytes(value: &str) -> Option<ParticipantPubkeyBytes> {
PublicKey::parse(value.trim())
.ok()
.map(|pubkey| *pubkey.as_bytes())
}
fn fips_udp_bind_addr(transport: &FipsEndpointTransportConfig) -> String {
SocketAddr::V4(SocketAddrV4::new(
std::net::Ipv4Addr::UNSPECIFIED,
transport.listen_port,
))
.to_string()
}
fn fips_udp_external_addr(transport: &FipsEndpointTransportConfig) -> Option<String> {
let endpoint = transport.advertised_endpoint.trim();
if endpoint.is_empty() {
return None;
}
let parsed = endpoint.parse::<SocketAddr>().ok()?;
if endpoint_ip_is_private_or_local(parsed.ip()) {
return None;
}
Some(parsed.to_string())
}
fn configure_fips_webrtc_transport(
config: &mut Config,
ambient_discovery_enabled: bool,
stun_servers: &[String],
mtu: u16,
) {
#[allow(clippy::default_trait_access)]
{
config.transports.webrtc = TransportInstances::Single(Default::default());
}
let TransportInstances::Single(webrtc) = &mut config.transports.webrtc else {
return;
};
webrtc.advertise_on_nostr = Some(ambient_discovery_enabled);
webrtc.auto_connect = Some(ambient_discovery_enabled);
webrtc.accept_connections = Some(ambient_discovery_enabled);
webrtc.mtu = Some(mtu);
if !stun_servers.is_empty() {
webrtc.stun_servers = Some(stun_servers.to_vec());
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
struct FipsLocalEthernetUnderlayConfig {
interface: String,
discovery_scope: String,
}
#[derive(Debug, Clone)]
pub(crate) struct FipsPrivateTunnelConfig {
pub(crate) identity_nsec: String,
pub(crate) network_id: String,
pub(crate) iface: String,
pub(crate) local_address: String,
pub(crate) listen_port: u16,
pub(crate) advertised_endpoint: String,
pub(crate) advertise_public_endpoint: bool,
pub(crate) stun_servers: Vec<String>,
pub(crate) nostr_relays: Vec<String>,
pub(crate) nostr_pubsub: nostr_vpn_core::config::NostrPubsubConfig,
pub(crate) control_pubsub_store_path: PathBuf,
pub(crate) share_local_candidates: bool,
pub(crate) peers: Vec<FipsMeshPeerConfig>,
pub(crate) endpoint_peers: Vec<FipsEndpointPeerTransportConfig>,
pub(crate) route_targets: Vec<String>,
pub(crate) magic_dns_records: HashMap<String, Ipv4Addr>,
#[cfg(any(target_os = "linux", target_os = "macos"))]
pub(crate) fips_host: Option<FipsHostTunnelConfig>,
pub(crate) local_advertised_routes: Vec<String>,
#[cfg(any(target_os = "linux", target_os = "macos"))]
pub(crate) local_exit_forwarding_routes: Vec<String>,
pub(crate) paid_route_admissions: Vec<FipsPaidRouteAdmission>,
#[cfg(feature = "paid-exit")]
pub(crate) paid_route_accounting_peers: Vec<FipsPaidRouteAccountingPeer>,
#[cfg(feature = "paid-exit")]
pub(crate) paid_exit: PaidExitConfig,
#[cfg(feature = "paid-exit")]
pub(crate) paid_route_store_path: PathBuf,
#[cfg(feature = "paid-exit")]
pub(crate) paid_route_wallet_data_dir: PathBuf,
#[cfg(feature = "paid-exit")]
pub(crate) paid_route_payment_relays: Vec<String>,
pub(crate) wireguard_exit: WireGuardExitConfig,
#[cfg(target_os = "linux")]
pub(crate) exit_node_leak_protection: bool,
nostr_discovery_enabled: bool,
webrtc_enabled: bool,
nostr_discovery_policy: NostrDiscoveryPolicy,
open_discovery_max_pending: usize,
mesh_mtu: MeshMtu,
#[cfg(target_os = "linux")]
pub(crate) control_plane_bypass_hosts: Vec<Ipv4Addr>,
local_ethernet_underlay: Option<FipsLocalEthernetUnderlayConfig>,
}
#[cfg(test)]
mod endpoint_config_tests {
use super::*;
use nostr_sdk::prelude::Keys;
fn test_peer() -> FipsMeshPeerConfig {
let participant = Keys::generate().public_key().to_hex();
FipsMeshPeerConfig::from_participant_pubkey(&participant, vec!["10.44.1.2/32".to_string()])
.expect("peer config")
}
fn test_transport(
nostr_discovery_enabled: bool,
webrtc_enabled: bool,
) -> FipsEndpointTransportConfig {
FipsEndpointTransportConfig {
listen_port: 51820,
advertised_endpoint: "192.168.50.20:51820".to_string(),
advertise_public_endpoint: false,
nostr_discovery_enabled,
webrtc_enabled,
stun_servers: vec!["stun:stun.example.org:3478".to_string()],
nostr_relays: vec!["wss://relay.example.org".to_string()],
share_local_candidates: true,
}
}
#[test]
fn endpoint_config_configures_webrtc_when_nostr_discovery_on() {
let peer = test_peer();
let endpoint_peers = fips_endpoint_peers_from_mesh(&[peer], Vec::new(), Vec::new());
let transport = test_transport(true, true);
let mesh_mtu = resolve_private_mesh_mtu(None, None, None);
let config = fips_endpoint_config_with_open_discovery_limit(
&endpoint_peers,
Some(&transport),
mesh_mtu,
NostrDiscoveryPolicy::Open,
FIPS_NOSTR_OPEN_DISCOVERY_MAX_PENDING,
);
config
.validate()
.expect("WebRTC-enabled endpoint config should validate");
let TransportInstances::Single(webrtc) = &config.transports.webrtc else {
panic!("expected one WebRTC transport");
};
assert_eq!(webrtc.advertise_on_nostr, Some(true));
assert_eq!(webrtc.auto_connect, Some(true));
assert_eq!(webrtc.accept_connections, Some(true));
assert_eq!(webrtc.mtu, Some(mesh_mtu.underlay_udp));
assert_eq!(
webrtc.stun_servers.as_ref().expect("stun servers"),
&transport.stun_servers
);
let TransportInstances::Single(relay) = &config.transports.nostr_relay else {
panic!("expected one application-owned Nostr relay fallback transport");
};
assert_eq!(relay.auto_connect, Some(false));
assert_eq!(relay.accept_connections, Some(false));
}
#[test]
fn endpoint_config_leaves_webrtc_empty_when_nostr_discovery_off() {
let peer = test_peer();
let endpoint_peers = fips_endpoint_peers_from_mesh(&[peer], Vec::new(), Vec::new());
let transport = test_transport(false, true);
let config = fips_endpoint_config_with_open_discovery_limit(
&endpoint_peers,
Some(&transport),
resolve_private_mesh_mtu(None, None, None),
NostrDiscoveryPolicy::ConfiguredOnly,
FIPS_NOSTR_OPEN_DISCOVERY_MAX_PENDING,
);
assert!(!config.node.discovery.nostr.enabled);
assert!(config.transports.webrtc.is_empty());
assert!(config.transports.nostr_relay.is_empty());
}
#[test]
fn endpoint_config_keeps_dormant_webrtc_for_explicit_browser_routes() {
let peer = test_peer();
let endpoint_peers = fips_endpoint_peers_from_mesh(&[peer], Vec::new(), Vec::new());
let transport = test_transport(true, false);
let config = fips_endpoint_config_with_open_discovery_limit(
&endpoint_peers,
Some(&transport),
resolve_private_mesh_mtu(None, None, None),
NostrDiscoveryPolicy::Open,
FIPS_NOSTR_OPEN_DISCOVERY_MAX_PENDING,
);
assert!(config.node.discovery.nostr.enabled);
assert!(config.node.discovery.nostr.advertise);
let TransportInstances::Single(webrtc) = &config.transports.webrtc else {
panic!("expected a dormant WebRTC transport for explicit browser routes");
};
assert_eq!(webrtc.advertise_on_nostr, Some(false));
assert_eq!(webrtc.auto_connect, Some(false));
assert_eq!(webrtc.accept_connections, Some(false));
assert!(!config.transports.udp.is_empty());
assert!(config.transports.nostr_relay.is_empty());
}
#[test]
fn relay_fallback_is_kept_for_roster_but_removed_from_join_route() {
let roster = test_peer();
let roster_npub = normalize_fips_endpoint_npub(&roster.endpoint_npub);
let ambient_npub = Keys::generate().public_key().to_bech32().expect("npub");
let browser_pubkey = Keys::generate().public_key().to_hex();
let mut peers = fips_endpoint_peers_from_mesh(
std::slice::from_ref(&roster),
vec![
(
roster_npub.clone(),
vec![
"203.0.113.10:51820".to_string(),
"tcp:203.0.113.10:443".to_string(),
format!("webrtc:02{}", Keys::generate().public_key().to_hex()),
],
),
(
ambient_npub.clone(),
vec!["203.0.113.20:51820".to_string()],
),
],
Vec::new(),
);
add_nostr_relay_fallback_for_mesh_peers(&mut peers, std::slice::from_ref(&roster));
let (_, peers) =
prioritize_join_roster_route(peers, &browser_pubkey).expect("explicit browser route");
let roster_peer = peers
.iter()
.find(|peer| peer.npub == roster_npub)
.expect("roster endpoint peer");
for transport in ["udp", "tcp", "webrtc", "nostr_relay"] {
assert!(roster_peer.addresses.iter().any(|address| {
split_peer_transport_addr(&address.addr).0 == transport
}));
}
let relay_priority = |npub: &str| {
peers
.iter()
.find(|peer| peer.npub == npub)
.and_then(|peer| {
peer.addresses.iter().find(|address| {
split_peer_transport_addr(&address.addr).0 == "nostr_relay"
})
})
.map(|address| address.priority)
};
assert_eq!(relay_priority(&roster_npub), Some(FIPS_NOSTR_RELAY_FALLBACK_PRIORITY));
assert_eq!(
relay_priority(&normalize_fips_endpoint_npub(&browser_pubkey)),
None
);
assert_eq!(relay_priority(&ambient_npub), None);
}
#[test]
fn disabled_webrtc_keeps_only_an_explicit_browser_route() {
let ambient_npub = Keys::generate().public_key().to_bech32().expect("npub");
let browser_pubkey = Keys::generate().public_key().to_hex();
let mut peers = vec![FipsEndpointPeerTransportConfig {
npub: ambient_npub,
addresses: vec![
FipsPeerAddressHint {
addr: "udp:203.0.113.20:51820".to_string(),
seen_at_ms: None,
priority: FIPS_CONFIGURED_PEER_ENDPOINT_PRIORITY,
},
FipsPeerAddressHint {
addr: format!(
"webrtc:02{}",
Keys::generate().public_key().to_hex()
),
seen_at_ms: Some(1),
priority: FIPS_DYNAMIC_PEER_ENDPOINT_PRIORITY,
},
],
auto_reconnect: true,
discovery_fallback_transit: true,
}];
retain_enabled_peer_transport_addresses(&mut peers, false);
let (_, peers) =
prioritize_join_roster_route(peers, &browser_pubkey).expect("explicit browser route");
let webrtc_addresses = peers
.iter()
.flat_map(|peer| peer.addresses.iter())
.filter(|hint| split_peer_transport_addr(&hint.addr).0 == "webrtc")
.collect::<Vec<_>>();
assert_eq!(webrtc_addresses.len(), 1);
assert_eq!(
webrtc_addresses[0].addr,
format!("webrtc:02{browser_pubkey}")
);
assert!(peers.iter().any(|peer| {
peer.addresses
.iter()
.any(|hint| hint.addr == "udp:203.0.113.20:51820")
}));
}
#[test]
fn webvm_endpoint_configures_only_scoped_local_ethernet() {
let config = local_ethernet_only_endpoint_config("eth0", "fips-overlay-v1");
config
.validate()
.expect("Ethernet-only WebVM endpoint config should validate");
assert!(!config.node.discovery.nostr.enabled);
assert!(!config.node.discovery.nostr.advertise);
assert!(config.node.discovery.nostr.advert_relays.is_empty());
assert!(config.node.discovery.nostr.stun_servers.is_empty());
assert!(!config.node.discovery.lan.enabled);
assert!(!config.node.discovery.local.enabled);
assert!(config.peers.is_empty());
assert!(config.transports.udp.is_empty());
assert!(config.transports.tcp.is_empty());
assert!(config.transports.tor.is_empty());
assert!(config.transports.webrtc.is_empty());
assert!(config.transports.ble.is_empty());
let TransportInstances::Single(ethernet) = &config.transports.ethernet else {
panic!("expected exactly one Ethernet transport");
};
assert_eq!(ethernet.interface, "eth0");
assert_eq!(ethernet.discovery_scope.as_deref(), Some("fips-overlay-v1"));
assert_eq!(ethernet.discovery, Some(true));
assert_eq!(ethernet.announce, Some(true));
assert_eq!(ethernet.auto_connect, Some(true));
assert_eq!(ethernet.accept_connections, Some(true));
}
}