use super::*;
#[derive(Debug, Clone)]
pub struct FipsEndpointBuilder {
config: Config,
identity_nsec: Option<String>,
discovery_scope: Option<String>,
local_ethernet_interfaces: Vec<String>,
disable_system_networking: bool,
packet_channel_capacity: usize,
}
const DEFAULT_ENDPOINT_PACKET_CHANNEL_CAPACITY: usize = 4096;
const MAX_ENDPOINT_PACKET_CHANNEL_CAPACITY: usize = 65_536;
impl Default for FipsEndpointBuilder {
fn default() -> Self {
Self {
config: Config::new(),
identity_nsec: None,
discovery_scope: None,
local_ethernet_interfaces: Vec::new(),
disable_system_networking: true,
packet_channel_capacity: default_endpoint_packet_channel_capacity(),
}
}
}
fn default_endpoint_packet_channel_capacity() -> usize {
parse_endpoint_packet_channel_capacity(
std::env::var("FIPS_ENDPOINT_PACKET_CHANNEL_CAP")
.ok()
.as_deref(),
DEFAULT_ENDPOINT_PACKET_CHANNEL_CAPACITY,
)
}
fn parse_endpoint_packet_channel_capacity(raw: Option<&str>, default: usize) -> usize {
raw.and_then(|raw| raw.trim().parse::<usize>().ok())
.unwrap_or(default)
.clamp(1, MAX_ENDPOINT_PACKET_CHANNEL_CAPACITY)
}
impl FipsEndpointBuilder {
pub fn config(mut self, config: Config) -> Self {
self.config = config;
self
}
pub fn identity_nsec(mut self, nsec: impl Into<String>) -> Self {
self.identity_nsec = Some(nsec.into());
self
}
pub fn discovery_scope(mut self, scope: impl Into<String>) -> Self {
self.discovery_scope = Some(scope.into());
self
}
pub fn local_ethernet(mut self, interface: impl Into<String>) -> Self {
self.local_ethernet_interfaces.push(interface.into());
self
}
pub fn without_system_tun(mut self) -> Self {
self.disable_system_networking = true;
self
}
pub fn packet_channel_capacity(mut self, capacity: usize) -> Self {
self.packet_channel_capacity = capacity.max(1);
self
}
pub(super) fn prepared_config(&self) -> Config {
let mut config = self.config.clone();
if let Some(nsec) = &self.identity_nsec {
config.node.identity = IdentityConfig {
nsec: Some(nsec.clone()),
persistent: false,
};
}
if self.disable_system_networking {
config.tun.enabled = false;
config.dns.enabled = false;
config.node.system_files_enabled = false;
}
if let Some(scope) = self.discovery_scope.as_deref() {
config.node.discovery.lan.scope = Some(scope.to_string());
config.node.discovery.local.enabled = true;
apply_default_scoped_discovery(&mut config, scope);
}
for interface in &self.local_ethernet_interfaces {
add_endpoint_ethernet_transport(
&mut config,
interface,
self.discovery_scope.as_deref(),
);
}
config
}
pub async fn bind(self) -> Result<FipsEndpoint, FipsEndpointError> {
endpoint_debug_log("FipsEndpointBuilder::bind begin");
let config = self.prepared_config();
endpoint_debug_log("FipsEndpointBuilder::bind config prepared");
let mut node = Node::new(config)?;
endpoint_debug_log("FipsEndpointBuilder::bind node created");
let identity = PeerIdentity::from_pubkey_full(node.identity().pubkey_full());
let npub = identity.npub();
let node_addr = *identity.node_addr();
let address = *identity.address();
let packet_io = node.attach_external_packet_io(self.packet_channel_capacity)?;
endpoint_debug_log("FipsEndpointBuilder::bind packet io attached");
let endpoint_data_io = node.attach_endpoint_data_io(self.packet_channel_capacity)?;
endpoint_debug_log("FipsEndpointBuilder::bind endpoint data io attached");
endpoint_debug_log("FipsEndpointBuilder::bind node.start begin");
node.start().await?;
endpoint_debug_log("FipsEndpointBuilder::bind node.start complete");
let (shutdown_tx, shutdown_rx) = oneshot::channel();
let task = spawn_node_task(node, shutdown_rx);
endpoint_debug_log("FipsEndpointBuilder::bind node task spawned");
let endpoint_priority_commands = endpoint_data_io.priority_command_tx;
let endpoint_commands = endpoint_data_io.command_tx;
#[cfg(unix)]
let endpoint_bulk_send_runtime = endpoint_data_io.bulk_send_runtime;
Ok(FipsEndpoint {
identity,
npub,
node_addr,
address,
discovery_scope: self.discovery_scope,
outbound_packets: packet_io.outbound_tx,
delivered_packets: Arc::new(Mutex::new(packet_io.inbound_rx)),
endpoint_priority_commands,
endpoint_commands,
#[cfg(unix)]
endpoint_bulk_send_runtime,
inbound_endpoint_tx: endpoint_data_io.event_tx,
inbound_endpoint_rx: Arc::new(Mutex::new(EndpointReceiveState::new(
endpoint_data_io.event_rx,
))),
peer_identity_cache: std::sync::Mutex::new(std::collections::HashMap::new()),
shutdown_tx: std::sync::Mutex::new(Some(shutdown_tx)),
task: std::sync::Mutex::new(Some(task)),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn endpoint_packet_channel_capacity_env_keeps_safe_bounds() {
assert_eq!(
parse_endpoint_packet_channel_capacity(None, DEFAULT_ENDPOINT_PACKET_CHANNEL_CAPACITY),
DEFAULT_ENDPOINT_PACKET_CHANNEL_CAPACITY
);
assert_eq!(
parse_endpoint_packet_channel_capacity(
Some(""),
DEFAULT_ENDPOINT_PACKET_CHANNEL_CAPACITY
),
DEFAULT_ENDPOINT_PACKET_CHANNEL_CAPACITY
);
assert_eq!(
parse_endpoint_packet_channel_capacity(
Some("not-a-number"),
DEFAULT_ENDPOINT_PACKET_CHANNEL_CAPACITY
),
DEFAULT_ENDPOINT_PACKET_CHANNEL_CAPACITY
);
assert_eq!(
parse_endpoint_packet_channel_capacity(
Some("0"),
DEFAULT_ENDPOINT_PACKET_CHANNEL_CAPACITY
),
1
);
assert_eq!(
parse_endpoint_packet_channel_capacity(
Some("8192"),
DEFAULT_ENDPOINT_PACKET_CHANNEL_CAPACITY
),
8192
);
assert_eq!(
parse_endpoint_packet_channel_capacity(
Some("999999"),
DEFAULT_ENDPOINT_PACKET_CHANNEL_CAPACITY
),
MAX_ENDPOINT_PACKET_CHANNEL_CAPACITY
);
}
}