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_rendezvous: bool,
10 local_instance_roles: Vec<crate::discovery::local::LocalInstanceCapability>,
11 disable_system_networking: bool,
12 packet_channel_capacity: usize,
13 #[cfg(test)]
14 test_nostr_discovery: Option<TestNostrDiscovery>,
15 #[cfg(feature = "host-ble-transport")]
16 host_ble: Option<HostBleAttachment>,
17 #[cfg(feature = "host-ble-transport")]
18 host_ble_config: Option<crate::config::BleConfig>,
19}
20
21#[cfg(test)]
22#[derive(Clone)]
23struct TestNostrDiscovery(Arc<crate::discovery::nostr::NostrDiscovery>);
24
25#[cfg(test)]
26impl std::fmt::Debug for TestNostrDiscovery {
27 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
28 formatter.write_str("TestNostrDiscovery(..)")
29 }
30}
31
32#[cfg(feature = "host-ble-transport")]
33#[derive(Clone)]
34struct HostBleAttachment(Arc<std::sync::Mutex<Option<crate::transport::ble::host::HostBleIo>>>);
35
36#[cfg(feature = "host-ble-transport")]
37impl std::fmt::Debug for HostBleAttachment {
38 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
39 formatter.write_str("HostBleAttachment(..)")
40 }
41}
42
43const DEFAULT_ENDPOINT_PACKET_CHANNEL_CAPACITY: usize = 4096;
44
45impl Default for FipsEndpointBuilder {
46 fn default() -> Self {
47 Self {
48 config: Config::new(),
49 identity_nsec: None,
50 discovery_scope: None,
51 local_rendezvous: false,
52 local_instance_roles: Vec::new(),
53 disable_system_networking: true,
54 packet_channel_capacity: DEFAULT_ENDPOINT_PACKET_CHANNEL_CAPACITY,
55 #[cfg(test)]
56 test_nostr_discovery: None,
57 #[cfg(feature = "host-ble-transport")]
58 host_ble: None,
59 #[cfg(feature = "host-ble-transport")]
60 host_ble_config: None,
61 }
62 }
63}
64
65impl FipsEndpointBuilder {
66 pub fn config(mut self, config: Config) -> Self {
68 self.config = config;
69 self
70 }
71
72 pub fn identity_nsec(mut self, nsec: impl Into<String>) -> Self {
74 self.identity_nsec = Some(nsec.into());
75 self
76 }
77
78 pub fn discovery_scope(mut self, scope: impl Into<String>) -> Self {
88 self.discovery_scope = Some(scope.into());
89 self
90 }
91
92 pub fn local_rendezvous(mut self) -> Self {
94 self.local_rendezvous = true;
95 self
96 }
97
98 pub fn local_role(mut self, name: impl Into<String>, priority: i16) -> Self {
101 let name = name.into().trim().to_string();
102 if crate::discovery::local_udp::local_capability_name_is_valid(&name)
103 && self.local_instance_roles.len()
104 < crate::discovery::local_udp::LOCAL_CAPABILITY_MAX_COUNT
105 {
106 self.local_instance_roles.push(
107 crate::discovery::local::LocalInstanceCapability::role(name)
108 .with_priority(priority),
109 );
110 }
111 self
112 }
113
114 pub fn without_system_tun(mut self) -> Self {
116 self.disable_system_networking = true;
117 self
118 }
119
120 pub fn packet_channel_capacity(mut self, capacity: usize) -> Self {
122 self.packet_channel_capacity = capacity.max(1);
123 self
124 }
125
126 #[cfg(feature = "host-ble-transport")]
131 pub fn host_ble(
132 mut self,
133 io: crate::transport::ble::host::HostBleIo,
134 config: crate::config::BleConfig,
135 ) -> Self {
136 self.host_ble = Some(HostBleAttachment(Arc::new(std::sync::Mutex::new(Some(io)))));
137 self.host_ble_config = Some(config);
138 self
139 }
140
141 pub(super) fn prepared_config(&self) -> Config {
142 let mut config = self.config.clone();
143 if let Some(nsec) = &self.identity_nsec {
144 config.node.identity = IdentityConfig {
145 nsec: Some(nsec.clone()),
146 persistent: false,
147 };
148 }
149 if self.disable_system_networking {
150 config.tun.enabled = false;
151 config.dns.enabled = false;
152 config.node.system_files_enabled = false;
153 }
154 #[cfg(feature = "host-ble-transport")]
155 if let Some(ble_config) = &self.host_ble_config {
156 config.transports.ble = crate::config::TransportInstances::Single(ble_config.clone());
160 }
161 if self.local_rendezvous {
162 config.node.discovery.local.enabled = true;
163 }
164 if let Some(scope) = self.discovery_scope.as_deref() {
165 if config
166 .node
167 .discovery
168 .lan
169 .scope
170 .as_deref()
171 .is_none_or(|scope| scope.trim().is_empty())
172 {
173 config.node.discovery.lan.scope = Some(scope.to_string());
174 }
175 apply_default_scoped_discovery(&mut config, scope);
176 }
177 config
178 }
179
180 pub async fn bind(self) -> Result<FipsEndpoint, FipsEndpointError> {
182 self.bind_inner(None).await
183 }
184
185 pub async fn bind_with_direct_receiver(
187 self,
188 ) -> Result<(FipsEndpoint, FipsEndpointDirectReceiver), FipsEndpointError> {
189 let (sink, receiver) = FipsEndpointDirectReceiver::channel();
190 let endpoint = self.bind_with_direct_sink(sink).await?;
191 Ok((endpoint, receiver))
192 }
193
194 pub async fn bind_with_direct_sink<S>(self, sink: S) -> Result<FipsEndpoint, FipsEndpointError>
200 where
201 S: FipsEndpointDirectSink,
202 {
203 self.bind_inner(Some(EndpointDirectSink::new(sink))).await
204 }
205
206 async fn bind_inner(
207 self,
208 direct_sink: Option<EndpointDirectSink>,
209 ) -> Result<FipsEndpoint, FipsEndpointError> {
210 let config = self.prepared_config();
211 #[cfg(test)]
212 let test_nostr_discovery = self
213 .test_nostr_discovery
214 .as_ref()
215 .map(|value| value.0.clone());
216
217 let mut node = Node::new(config)?;
218 node.set_local_instance_roles(self.local_instance_roles);
219 #[cfg(test)]
220 if let Some(discovery) = test_nostr_discovery {
221 node.set_nostr_discovery_for_test(discovery);
222 }
223 #[cfg(feature = "host-ble-transport")]
224 if let Some(attachment) = &self.host_ble {
225 let io = attachment
226 .0
227 .lock()
228 .unwrap_or_else(|error| error.into_inner())
229 .take()
230 .ok_or(FipsEndpointError::HostBleAdapterConsumed)?;
231 node.set_host_ble_io(io);
232 }
233 let identity = PeerIdentity::from_pubkey_full(node.identity().pubkey_full());
234 let npub = identity.npub();
235 let node_addr = *identity.node_addr();
236 let address = *identity.address();
237 let packet_io = node.attach_external_packet_io(self.packet_channel_capacity)?;
238 let endpoint_data_io = match direct_sink {
239 Some(sink) => {
240 node.attach_endpoint_data_io_with_direct_sink(self.packet_channel_capacity, sink)?
241 }
242 None => node.attach_endpoint_data_io(self.packet_channel_capacity)?,
243 };
244 node.start().await?;
245 let local_capability_directory = node.local_capability_directory();
246
247 let (shutdown_tx, shutdown_rx) = oneshot::channel();
248 let task = spawn_node_task(node, shutdown_rx);
249 let endpoint_control_tx = endpoint_data_io.control_tx;
250 let endpoint_data_batches = endpoint_data_io.data_batch_tx;
251 let inbound_service_tx = endpoint_data_io.service_event_tx;
252
253 Ok(FipsEndpoint {
254 identity,
255 npub,
256 node_addr,
257 address,
258 discovery_scope: self.discovery_scope,
259 local_capability_directory,
260 outbound_packets: packet_io.outbound_tx,
261 delivered_packets: Arc::new(Mutex::new(packet_io.inbound_rx)),
262 endpoint_control_tx,
263 endpoint_data_batches,
264 inbound_endpoint_tx: endpoint_data_io.event_tx,
265 inbound_endpoint_rx: Arc::new(Mutex::new(EndpointReceiveState::new(
266 endpoint_data_io.event_rx,
267 ))),
268 inbound_service_tx,
269 inbound_service_rx: Arc::new(Mutex::new(ServiceReceiveState::new(
270 endpoint_data_io.service_event_rx,
271 ))),
272 registered_services: Arc::new(StdMutex::new(HashMap::new())),
273 service_channel_capacity: self.packet_channel_capacity,
274 shutdown_tx: std::sync::Mutex::new(Some(shutdown_tx)),
275 task: std::sync::Mutex::new(Some(task)),
276 })
277 }
278}
279
280#[cfg(test)]
281impl FipsEndpointBuilder {
282 pub(super) async fn bind_with_nostr_discovery_for_test(
283 mut self,
284 discovery: Arc<crate::discovery::nostr::NostrDiscovery>,
285 ) -> Result<FipsEndpoint, FipsEndpointError> {
286 self.test_nostr_discovery = Some(TestNostrDiscovery(discovery));
287 self.bind().await
288 }
289}