1use crate::config::{EthernetConfig, NostrDiscoveryPolicy, TransportInstances, UdpConfig};
7#[cfg(test)]
8use crate::node::ENDPOINT_EVENT_TEST_PAYLOAD_LEN;
9use crate::node::{
10 EndpointDataBatchTx, EndpointDataPayload, EndpointDirectSink, EndpointEventSender,
11 NodeEndpointControlCommand, NodeEndpointDataBatch, NodeEndpointEvent,
12};
13use crate::upper::tun::TunOutboundTx;
14use crate::{
15 Config, FipsAddress, IdentityConfig, Node, NodeAddr, NodeDeliveredPacket, NodeError,
16 PeerIdentity,
17};
18use std::sync::{Arc, Mutex as StdMutex};
19use thiserror::Error;
20use tokio::sync::{Mutex, mpsc, oneshot};
21use tokio::task::JoinHandle;
22
23const ENDPOINT_DATA_BATCH_MAX: usize = 128;
24const ENDPOINT_RECV_BATCH_MAX: usize = 128;
25
26mod builder;
27mod receive;
28mod status;
29
30#[cfg(test)]
31mod tests;
32
33pub use crate::node::{
34 FipsEndpointDirectDeliveryError, FipsEndpointDirectPacketBatch, FipsEndpointDirectPacketRun,
35 FipsEndpointDirectSink,
36};
37pub use builder::FipsEndpointBuilder;
38use receive::EndpointReceiveState;
39pub use status::{FipsEndpointPeer, FipsEndpointRelayStatus};
40
41pub type FipsEndpointData = crate::transport::PacketBuffer;
46
47#[derive(Debug, Error)]
49pub enum FipsEndpointError {
50 #[error("node error: {0}")]
51 Node(#[from] NodeError),
52
53 #[error("endpoint task failed: {0}")]
54 TaskJoin(#[from] tokio::task::JoinError),
55
56 #[error("endpoint is closed")]
57 Closed,
58
59 #[error("endpoint data payload is too large: {len} bytes exceeds max {max} bytes")]
60 EndpointDataTooLarge { len: usize, max: usize },
61}
62
63#[derive(Debug, Clone, PartialEq, Eq)]
65pub struct FipsEndpointMessage {
66 pub source_peer: PeerIdentity,
68 pub data: FipsEndpointData,
70 pub enqueued_at_ms: u64,
72}
73
74#[derive(Debug, Clone, Default, PartialEq, Eq)]
76pub struct UpdatePeersOutcome {
77 pub added: usize,
80 pub removed: usize,
84 pub updated: usize,
89 pub unchanged: usize,
91}
92
93impl From<crate::node::UpdatePeersOutcome> for UpdatePeersOutcome {
94 fn from(value: crate::node::UpdatePeersOutcome) -> Self {
95 Self {
96 added: value.added,
97 removed: value.removed,
98 updated: value.updated,
99 unchanged: value.unchanged,
100 }
101 }
102}
103
104fn apply_default_scoped_discovery(config: &mut Config, scope: &str) {
105 if config.node.discovery.nostr.enabled || !config.transports.is_empty() {
106 return;
107 }
108
109 config.node.discovery.nostr.enabled = true;
110 config.node.discovery.nostr.advertise = true;
111 config.node.discovery.nostr.policy = NostrDiscoveryPolicy::Open;
112 config.node.discovery.nostr.share_local_candidates = true;
113 config.node.discovery.nostr.app = scope.to_string();
114 config.node.discovery.lan.scope = Some(scope.to_string());
115 config.node.discovery.local.enabled = true;
116 config.transports.udp = TransportInstances::Single(UdpConfig {
117 bind_addr: Some("0.0.0.0:0".to_string()),
118 advertise_on_nostr: Some(true),
119 public: Some(false),
120 outbound_only: Some(false),
121 accept_connections: Some(true),
122 ..UdpConfig::default()
123 });
124}
125
126fn endpoint_ethernet_config(interface: &str, scope: Option<&str>) -> EthernetConfig {
127 EthernetConfig {
128 interface: interface.to_string(),
129 discovery: Some(true),
130 announce: Some(true),
131 auto_connect: Some(true),
132 accept_connections: Some(true),
133 discovery_scope: scope
134 .map(str::trim)
135 .filter(|s| !s.is_empty())
136 .map(str::to_string),
137 ..EthernetConfig::default()
138 }
139}
140
141fn add_endpoint_ethernet_transport(config: &mut Config, interface: &str, scope: Option<&str>) {
142 let eth = endpoint_ethernet_config(interface, scope);
143 if config.transports.ethernet.is_empty() {
144 config.transports.ethernet = TransportInstances::Single(eth);
145 return;
146 }
147
148 let existing = std::mem::take(&mut config.transports.ethernet);
149 let mut named = match existing {
150 TransportInstances::Single(config) => {
151 let mut map = std::collections::HashMap::new();
152 map.insert("default".to_string(), config);
153 map
154 }
155 TransportInstances::Named(map) => map,
156 };
157
158 let base_name = endpoint_ethernet_instance_name(interface);
159 let mut name = base_name.clone();
160 let mut suffix = 2usize;
161 while named.contains_key(&name) {
162 name = format!("{base_name}-{suffix}");
163 suffix += 1;
164 }
165 named.insert(name, eth);
166 config.transports.ethernet = TransportInstances::Named(named);
167}
168
169fn endpoint_ethernet_instance_name(interface: &str) -> String {
170 let suffix: String = interface
171 .chars()
172 .map(|c| {
173 if c.is_ascii_alphanumeric() {
174 c.to_ascii_lowercase()
175 } else {
176 '-'
177 }
178 })
179 .collect();
180 let suffix = suffix.trim_matches('-');
181 if suffix.is_empty() {
182 "local-ethernet".to_string()
183 } else {
184 format!("local-ethernet-{suffix}")
185 }
186}
187
188fn endpoint_data_payloads_from_vecs(
189 payloads: Vec<Vec<u8>>,
190) -> Result<Vec<EndpointDataPayload>, FipsEndpointError> {
191 let mut converted = Vec::with_capacity(payloads.len());
192 for payload in payloads {
193 let len = payload.len();
194 let Some(payload) = EndpointDataPayload::from_packet_payload(payload) else {
195 let max = crate::node::session_wire::fsp_endpoint_data_max_body_len();
196 return Err(FipsEndpointError::EndpointDataTooLarge { len, max });
197 };
198 converted.push(payload);
199 }
200 Ok(converted)
201}
202
203fn spawn_node_task(
204 mut node: Node,
205 shutdown_rx: oneshot::Receiver<()>,
206) -> JoinHandle<Result<(), NodeError>> {
207 tokio::spawn(async move {
208 tokio::pin!(shutdown_rx);
209 let loop_result = tokio::select! {
210 result = node.run_rx_loop() => result,
211 _ = &mut shutdown_rx => Ok(()),
212 };
213 let stop_result = if node.state().can_stop() {
214 node.stop().await
215 } else {
216 Ok(())
217 };
218 loop_result?;
219 stop_result
220 })
221}
222
223pub struct FipsEndpoint {
225 identity: PeerIdentity,
226 npub: String,
227 node_addr: NodeAddr,
228 address: FipsAddress,
229 discovery_scope: Option<String>,
230 outbound_packets: TunOutboundTx,
231 delivered_packets: Arc<Mutex<mpsc::Receiver<NodeDeliveredPacket>>>,
232 endpoint_control_tx: mpsc::Sender<NodeEndpointControlCommand>,
233 endpoint_data_batches: EndpointDataBatchTx,
234 inbound_endpoint_tx: EndpointEventSender,
240 inbound_endpoint_rx: Arc<Mutex<EndpointReceiveState>>,
247 shutdown_tx: StdMutex<Option<oneshot::Sender<()>>>,
248 task: StdMutex<Option<JoinHandle<Result<(), NodeError>>>>,
249}
250
251impl FipsEndpoint {
252 pub fn builder() -> FipsEndpointBuilder {
254 FipsEndpointBuilder::default()
255 }
256
257 pub fn npub(&self) -> &str {
259 &self.npub
260 }
261
262 pub fn node_addr(&self) -> &NodeAddr {
264 &self.node_addr
265 }
266
267 pub fn address(&self) -> FipsAddress {
269 self.address
270 }
271
272 pub fn discovery_scope(&self) -> Option<&str> {
274 self.discovery_scope.as_deref()
275 }
276
277 pub async fn send_batch_to_peer(
285 &self,
286 remote: PeerIdentity,
287 payloads: Vec<Vec<u8>>,
288 ) -> Result<(), FipsEndpointError> {
289 self.send_payloads_to_peer(remote, payloads)
290 }
291
292 fn send_payloads_to_peer(
293 &self,
294 remote: PeerIdentity,
295 payloads: Vec<Vec<u8>>,
296 ) -> Result<(), FipsEndpointError> {
297 let payloads = endpoint_data_payloads_from_vecs(payloads)?;
298 if *remote.node_addr() == self.node_addr {
299 for payload in payloads {
300 self.send_loopback(payload)?;
301 }
302 return Ok(());
303 }
304
305 self.send_endpoint_data_batch(remote, payloads)
306 }
307
308 fn send_endpoint_data_batch(
309 &self,
310 remote: PeerIdentity,
311 payloads: Vec<EndpointDataPayload>,
312 ) -> Result<(), FipsEndpointError> {
313 if payloads.is_empty() {
314 return Ok(());
315 }
316
317 if payloads.len() <= ENDPOINT_DATA_BATCH_MAX {
318 self.enqueue_endpoint_data_batch(remote, payloads)?;
319 return Ok(());
320 }
321
322 let mut payloads = payloads.into_iter();
323 loop {
324 let payload_batch: Vec<_> = payloads.by_ref().take(ENDPOINT_DATA_BATCH_MAX).collect();
325 if payload_batch.is_empty() {
326 break;
327 }
328 self.enqueue_endpoint_data_batch(remote, payload_batch)?;
329 }
330 Ok(())
331 }
332
333 fn enqueue_endpoint_data_batch(
334 &self,
335 remote: PeerIdentity,
336 payload_batch: Vec<EndpointDataPayload>,
337 ) -> Result<(), FipsEndpointError> {
338 if let Some(batch) = NodeEndpointDataBatch::from_payloads(
343 remote,
344 payload_batch,
345 crate::perf_profile::stamp(),
346 ) {
347 self.endpoint_data_batches
348 .send_or_drop(batch)
349 .map_err(|_| FipsEndpointError::Closed)?;
350 }
351 Ok(())
352 }
353
354 fn send_loopback(&self, payload: EndpointDataPayload) -> Result<(), FipsEndpointError> {
355 self.inbound_endpoint_tx
356 .send(NodeEndpointEvent {
357 messages: vec![crate::node::EndpointDataDelivery::new(
358 self.identity,
359 payload.into_body(),
360 )],
361 queued_at: crate::perf_profile::stamp(),
362 })
363 .map_err(|_| FipsEndpointError::Closed)
364 }
365
366 pub async fn recv_batch_into(
373 &self,
374 messages: &mut Vec<FipsEndpointMessage>,
375 max: usize,
376 ) -> Option<usize> {
377 let max = max.clamp(1, ENDPOINT_RECV_BATCH_MAX);
378 messages.clear();
379
380 let mut state = self.inbound_endpoint_rx.lock().await;
381 state.drain_pending_into(messages, max);
382
383 while messages.len() < max {
384 let event = if messages.is_empty() {
385 state.rx.recv().await?
386 } else {
387 match state.rx.try_recv() {
388 Ok(event) => event,
389 Err(_) => break,
390 }
391 };
392 state.push_event_into(event, messages, max);
393 }
394
395 Some(messages.len())
396 }
397
398 pub fn blocking_send_batch_to_peer(
404 &self,
405 remote: PeerIdentity,
406 payloads: Vec<Vec<u8>>,
407 ) -> Result<(), FipsEndpointError> {
408 self.send_payloads_to_peer(remote, payloads)
409 }
410
411 pub fn blocking_recv_batch_into(
419 &self,
420 messages: &mut Vec<FipsEndpointMessage>,
421 max: usize,
422 ) -> Option<usize> {
423 let max = max.clamp(1, ENDPOINT_RECV_BATCH_MAX);
424 messages.clear();
425
426 let mut state = self.inbound_endpoint_rx.blocking_lock();
427 state.drain_pending_into(messages, max);
428
429 while messages.len() < max {
430 let event = if messages.is_empty() {
431 state.rx.blocking_recv()?
432 } else {
433 match state.rx.try_recv() {
434 Ok(event) => event,
435 Err(_) => break,
436 }
437 };
438 state.push_event_into(event, messages, max);
439 }
440
441 Some(messages.len())
442 }
443
444 pub async fn update_peers(
454 &self,
455 peers: Vec<crate::config::PeerConfig>,
456 ) -> Result<UpdatePeersOutcome, FipsEndpointError> {
457 let (response_tx, response_rx) = oneshot::channel();
458 self.endpoint_control_tx
459 .send(NodeEndpointControlCommand::UpdatePeers { peers, response_tx })
460 .await
461 .map_err(|_| FipsEndpointError::Closed)?;
462
463 match response_rx.await.map_err(|_| FipsEndpointError::Closed)? {
464 Ok(outcome) => Ok(UpdatePeersOutcome::from(outcome)),
465 Err(error) => Err(FipsEndpointError::Node(error)),
466 }
467 }
468
469 pub async fn refresh_peer_paths(
475 &self,
476 peers: Vec<PeerIdentity>,
477 ) -> Result<usize, FipsEndpointError> {
478 let (response_tx, response_rx) = oneshot::channel();
479 let npubs = peers.into_iter().map(|peer| peer.npub()).collect();
480 self.endpoint_control_tx
481 .send(NodeEndpointControlCommand::RefreshPeerPaths { npubs, response_tx })
482 .await
483 .map_err(|_| FipsEndpointError::Closed)?;
484
485 match response_rx.await.map_err(|_| FipsEndpointError::Closed)? {
486 Ok(refreshed) => Ok(refreshed),
487 Err(error) => Err(FipsEndpointError::Node(error)),
488 }
489 }
490
491 pub async fn peers(&self) -> Result<Vec<FipsEndpointPeer>, FipsEndpointError> {
493 let (response_tx, response_rx) = oneshot::channel();
494 self.endpoint_control_tx
495 .send(NodeEndpointControlCommand::PeerSnapshot { response_tx })
496 .await
497 .map_err(|_| FipsEndpointError::Closed)?;
498
499 response_rx
500 .await
501 .map(|peers| peers.into_iter().map(FipsEndpointPeer::from).collect())
502 .map_err(|_| FipsEndpointError::Closed)
503 }
504
505 pub async fn local_advertised_endpoints(
508 &self,
509 ) -> Result<Vec<crate::discovery::nostr::OverlayEndpointAdvert>, FipsEndpointError> {
510 let (response_tx, response_rx) = oneshot::channel();
511 self.endpoint_control_tx
512 .send(NodeEndpointControlCommand::LocalAdvertSnapshot { response_tx })
513 .await
514 .map_err(|_| FipsEndpointError::Closed)?;
515
516 response_rx.await.map_err(|_| FipsEndpointError::Closed)
517 }
518
519 pub async fn relay_statuses(&self) -> Result<Vec<FipsEndpointRelayStatus>, FipsEndpointError> {
521 let (response_tx, response_rx) = oneshot::channel();
522 self.endpoint_control_tx
523 .send(NodeEndpointControlCommand::RelaySnapshot { response_tx })
524 .await
525 .map_err(|_| FipsEndpointError::Closed)?;
526
527 response_rx
528 .await
529 .map(|relays| {
530 relays
531 .into_iter()
532 .map(FipsEndpointRelayStatus::from)
533 .collect()
534 })
535 .map_err(|_| FipsEndpointError::Closed)
536 }
537
538 pub async fn update_relays(
540 &self,
541 advert_relays: Vec<String>,
542 dm_relays: Vec<String>,
543 ) -> Result<(), FipsEndpointError> {
544 let (response_tx, response_rx) = oneshot::channel();
545 self.endpoint_control_tx
546 .send(NodeEndpointControlCommand::UpdateRelays {
547 advert_relays,
548 dm_relays,
549 response_tx,
550 })
551 .await
552 .map_err(|_| FipsEndpointError::Closed)?;
553
554 response_rx
555 .await
556 .map_err(|_| FipsEndpointError::Closed)?
557 .map_err(FipsEndpointError::Node)
558 }
559
560 pub async fn send_ip_packet(
562 &self,
563 packet: impl Into<Vec<u8>>,
564 ) -> Result<(), FipsEndpointError> {
565 self.outbound_packets
566 .send(packet.into())
567 .await
568 .map_err(|_| FipsEndpointError::Closed)
569 }
570
571 pub async fn recv_ip_packet(&self) -> Option<NodeDeliveredPacket> {
573 self.delivered_packets.lock().await.recv().await
574 }
575
576 pub async fn shutdown(&self) -> Result<(), FipsEndpointError> {
578 let shutdown_tx = self
579 .shutdown_tx
580 .lock()
581 .map_err(|_| FipsEndpointError::Closed)?
582 .take();
583 if let Some(shutdown_tx) = shutdown_tx {
584 let _ = shutdown_tx.send(());
585 }
586 let task = self
587 .task
588 .lock()
589 .map_err(|_| FipsEndpointError::Closed)?
590 .take();
591 if let Some(task) = task {
592 task.await??;
593 }
594 Ok(())
595 }
596}
597
598impl Drop for FipsEndpoint {
599 fn drop(&mut self) {
600 if let Ok(mut shutdown_tx) = self.shutdown_tx.lock()
601 && let Some(shutdown_tx) = shutdown_tx.take()
602 {
603 let _ = shutdown_tx.send(());
604 }
605 if let Ok(mut task) = self.task.lock()
606 && let Some(task) = task.take()
607 {
608 task.abort();
609 }
610 }
611}