fips_core/endpoint/
builder.rs1use super::*;
2
3#[derive(Debug, Clone)]
5pub struct FipsEndpointBuilder {
6 config: Config,
7 identity_nsec: Option<String>,
8 discovery_scope: Option<String>,
9 local_ethernet_interfaces: Vec<String>,
10 disable_system_networking: bool,
11 packet_channel_capacity: usize,
12}
13
14const DEFAULT_ENDPOINT_PACKET_CHANNEL_CAPACITY: usize = 4096;
15const MAX_ENDPOINT_PACKET_CHANNEL_CAPACITY: usize = 65_536;
16
17impl Default for FipsEndpointBuilder {
18 fn default() -> Self {
19 Self {
20 config: Config::new(),
21 identity_nsec: None,
22 discovery_scope: None,
23 local_ethernet_interfaces: Vec::new(),
24 disable_system_networking: true,
25 packet_channel_capacity: default_endpoint_packet_channel_capacity(),
26 }
27 }
28}
29
30fn default_endpoint_packet_channel_capacity() -> usize {
31 parse_endpoint_packet_channel_capacity(
32 std::env::var("FIPS_ENDPOINT_PACKET_CHANNEL_CAP")
33 .ok()
34 .as_deref(),
35 DEFAULT_ENDPOINT_PACKET_CHANNEL_CAPACITY,
36 )
37}
38
39fn parse_endpoint_packet_channel_capacity(raw: Option<&str>, default: usize) -> usize {
40 raw.and_then(|raw| raw.trim().parse::<usize>().ok())
41 .unwrap_or(default)
42 .clamp(1, MAX_ENDPOINT_PACKET_CHANNEL_CAPACITY)
43}
44
45impl FipsEndpointBuilder {
46 pub fn config(mut self, config: Config) -> Self {
48 self.config = config;
49 self
50 }
51
52 pub fn identity_nsec(mut self, nsec: impl Into<String>) -> Self {
54 self.identity_nsec = Some(nsec.into());
55 self
56 }
57
58 pub fn discovery_scope(mut self, scope: impl Into<String>) -> Self {
66 self.discovery_scope = Some(scope.into());
67 self
68 }
69
70 pub fn local_ethernet(mut self, interface: impl Into<String>) -> Self {
77 self.local_ethernet_interfaces.push(interface.into());
78 self
79 }
80
81 pub fn without_system_tun(mut self) -> Self {
83 self.disable_system_networking = true;
84 self
85 }
86
87 pub fn packet_channel_capacity(mut self, capacity: usize) -> Self {
89 self.packet_channel_capacity = capacity.max(1);
90 self
91 }
92
93 pub(super) fn prepared_config(&self) -> Config {
94 let mut config = self.config.clone();
95 if let Some(nsec) = &self.identity_nsec {
96 config.node.identity = IdentityConfig {
97 nsec: Some(nsec.clone()),
98 persistent: false,
99 };
100 }
101 if self.disable_system_networking {
102 config.tun.enabled = false;
103 config.dns.enabled = false;
104 config.node.system_files_enabled = false;
105 }
106 if let Some(scope) = self.discovery_scope.as_deref() {
107 config.node.discovery.lan.scope = Some(scope.to_string());
108 config.node.discovery.local.enabled = true;
109 apply_default_scoped_discovery(&mut config, scope);
110 }
111 for interface in &self.local_ethernet_interfaces {
112 add_endpoint_ethernet_transport(
113 &mut config,
114 interface,
115 self.discovery_scope.as_deref(),
116 );
117 }
118 config
119 }
120
121 pub async fn bind(self) -> Result<FipsEndpoint, FipsEndpointError> {
123 endpoint_debug_log("FipsEndpointBuilder::bind begin");
124 let config = self.prepared_config();
125 endpoint_debug_log("FipsEndpointBuilder::bind config prepared");
126
127 let mut node = Node::new(config)?;
128 endpoint_debug_log("FipsEndpointBuilder::bind node created");
129 let identity = PeerIdentity::from_pubkey_full(node.identity().pubkey_full());
130 let npub = identity.npub();
131 let node_addr = *identity.node_addr();
132 let address = *identity.address();
133 let packet_io = node.attach_external_packet_io(self.packet_channel_capacity)?;
134 endpoint_debug_log("FipsEndpointBuilder::bind packet io attached");
135 let endpoint_data_io = node.attach_endpoint_data_io(self.packet_channel_capacity)?;
136 endpoint_debug_log("FipsEndpointBuilder::bind endpoint data io attached");
137 endpoint_debug_log("FipsEndpointBuilder::bind node.start begin");
138 node.start().await?;
139 endpoint_debug_log("FipsEndpointBuilder::bind node.start complete");
140
141 let (shutdown_tx, shutdown_rx) = oneshot::channel();
142 let task = spawn_node_task(node, shutdown_rx);
143 endpoint_debug_log("FipsEndpointBuilder::bind node task spawned");
144 let endpoint_priority_commands = endpoint_data_io.priority_command_tx;
145 let endpoint_commands = endpoint_data_io.command_tx;
146 #[cfg(unix)]
147 let endpoint_bulk_send_runtime = endpoint_data_io.bulk_send_runtime;
148
149 Ok(FipsEndpoint {
150 identity,
151 npub,
152 node_addr,
153 address,
154 discovery_scope: self.discovery_scope,
155 outbound_packets: packet_io.outbound_tx,
156 delivered_packets: Arc::new(Mutex::new(packet_io.inbound_rx)),
157 endpoint_priority_commands,
158 endpoint_commands,
159 #[cfg(unix)]
160 endpoint_bulk_send_runtime,
161 inbound_endpoint_tx: endpoint_data_io.event_tx,
162 inbound_endpoint_rx: Arc::new(Mutex::new(EndpointReceiveState::new(
163 endpoint_data_io.event_rx,
164 ))),
165 peer_identity_cache: std::sync::Mutex::new(std::collections::HashMap::new()),
166 shutdown_tx: std::sync::Mutex::new(Some(shutdown_tx)),
167 task: std::sync::Mutex::new(Some(task)),
168 })
169 }
170}
171
172#[cfg(test)]
173mod tests {
174 use super::*;
175
176 #[test]
177 fn endpoint_packet_channel_capacity_env_keeps_safe_bounds() {
178 assert_eq!(
179 parse_endpoint_packet_channel_capacity(None, DEFAULT_ENDPOINT_PACKET_CHANNEL_CAPACITY),
180 DEFAULT_ENDPOINT_PACKET_CHANNEL_CAPACITY
181 );
182 assert_eq!(
183 parse_endpoint_packet_channel_capacity(
184 Some(""),
185 DEFAULT_ENDPOINT_PACKET_CHANNEL_CAPACITY
186 ),
187 DEFAULT_ENDPOINT_PACKET_CHANNEL_CAPACITY
188 );
189 assert_eq!(
190 parse_endpoint_packet_channel_capacity(
191 Some("not-a-number"),
192 DEFAULT_ENDPOINT_PACKET_CHANNEL_CAPACITY
193 ),
194 DEFAULT_ENDPOINT_PACKET_CHANNEL_CAPACITY
195 );
196 assert_eq!(
197 parse_endpoint_packet_channel_capacity(
198 Some("0"),
199 DEFAULT_ENDPOINT_PACKET_CHANNEL_CAPACITY
200 ),
201 1
202 );
203 assert_eq!(
204 parse_endpoint_packet_channel_capacity(
205 Some("8192"),
206 DEFAULT_ENDPOINT_PACKET_CHANNEL_CAPACITY
207 ),
208 8192
209 );
210 assert_eq!(
211 parse_endpoint_packet_channel_capacity(
212 Some("999999"),
213 DEFAULT_ENDPOINT_PACKET_CHANNEL_CAPACITY
214 ),
215 MAX_ENDPOINT_PACKET_CHANNEL_CAPACITY
216 );
217 }
218}