1use crate::config::{EthernetConfig, NostrDiscoveryPolicy, TransportInstances, UdpConfig};
7#[cfg(test)]
8use crate::node::ENDPOINT_EVENT_TEST_PAYLOAD_LEN;
9use crate::node::{
10 EndpointDataBatchTx, EndpointDataBulkBody, EndpointDataBulkBodyBuilder, EndpointDirectSink,
11 EndpointEventSender, 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 FipsEndpointDirectPacketRunMeta, FipsEndpointDirectSink, FipsEndpointDirectSourceRun,
36};
37pub use builder::FipsEndpointBuilder;
38use receive::EndpointReceiveState;
39pub use status::{FipsEndpointPeer, FipsEndpointRelayStatus};
40
41pub type FipsEndpointData = crate::transport::PacketBuffer;
46
47#[derive(Clone, Debug, Eq, PartialEq)]
53pub struct FipsEndpointBulkData(EndpointDataBulkBody);
54
55impl FipsEndpointBulkData {
56 pub fn from_encoded_body(body: Vec<u8>) -> Option<Self> {
58 EndpointDataBulkBody::from_encoded_body(body).map(Self)
59 }
60
61 pub fn from_packet_slices<'a, I>(packets: I) -> Option<Self>
63 where
64 I: IntoIterator<Item = &'a [u8]>,
65 {
66 let mut builder = FipsEndpointBulkDataBuilder::new();
67 for packet in packets {
68 if !builder.push_packet(packet) {
69 return None;
70 }
71 }
72 builder.finish()
73 }
74
75 pub fn body_len(&self) -> usize {
77 self.0.body_len()
78 }
79
80 pub fn packet_count(&self) -> usize {
82 self.0.packet_count()
83 }
84
85 pub fn packet_bytes(&self) -> usize {
87 self.0.packet_bytes()
88 }
89
90 fn into_node_body(self) -> EndpointDataBulkBody {
91 self.0
92 }
93
94 fn into_packet_payloads(self) -> Vec<Vec<u8>> {
95 self.0.into_packet_payloads()
96 }
97}
98
99#[derive(Debug, Default)]
101pub struct FipsEndpointBulkDataBuilder(EndpointDataBulkBodyBuilder);
102
103impl FipsEndpointBulkDataBuilder {
104 pub fn new() -> Self {
106 Self(EndpointDataBulkBodyBuilder::new())
107 }
108
109 pub fn is_empty(&self) -> bool {
111 self.0.is_empty()
112 }
113
114 pub fn packet_count(&self) -> usize {
116 self.0.packet_count()
117 }
118
119 pub fn packet_bytes(&self) -> usize {
121 self.0.packet_bytes()
122 }
123
124 pub fn body_len(&self) -> usize {
126 self.0.body_len()
127 }
128
129 pub fn can_push_packet(&self, packet: &[u8]) -> bool {
131 self.0.can_push_packet(packet)
132 }
133
134 pub fn push_packet(&mut self, packet: &[u8]) -> bool {
136 self.0.push_packet(packet)
137 }
138
139 pub fn finish(self) -> Option<FipsEndpointBulkData> {
141 self.0.finish().map(FipsEndpointBulkData)
142 }
143}
144
145#[cfg(debug_assertions)]
146fn endpoint_debug_log(message: impl AsRef<str>) {
147 use std::io::Write as _;
148
149 if let Ok(mut file) = std::fs::OpenOptions::new()
150 .create(true)
151 .append(true)
152 .open(std::env::temp_dir().join("nvpn-fips-endpoint-debug.log"))
153 {
154 let _ = writeln!(
155 file,
156 "{:?} {}",
157 std::time::SystemTime::now(),
158 message.as_ref()
159 );
160 }
161}
162
163#[cfg(not(debug_assertions))]
164fn endpoint_debug_log(_message: impl AsRef<str>) {}
165
166#[derive(Debug, Error)]
168pub enum FipsEndpointError {
169 #[error("node error: {0}")]
170 Node(#[from] NodeError),
171
172 #[error("endpoint task failed: {0}")]
173 TaskJoin(#[from] tokio::task::JoinError),
174
175 #[error("endpoint is closed")]
176 Closed,
177
178 #[error("invalid remote npub '{npub}': {reason}")]
179 InvalidRemoteNpub { npub: String, reason: String },
180}
181
182#[derive(Debug, Clone, PartialEq, Eq)]
184pub struct FipsEndpointMessage {
185 pub source_peer: PeerIdentity,
187 pub data: FipsEndpointData,
189 pub enqueued_at_ms: u64,
191}
192
193impl FipsEndpointMessage {
194 pub fn source_node_addr(&self) -> &NodeAddr {
196 self.source_peer.node_addr()
197 }
198
199 pub fn source_npub(&self) -> String {
201 self.source_peer.npub()
202 }
203}
204
205#[derive(Debug, Clone, Default, PartialEq, Eq)]
207pub struct UpdatePeersOutcome {
208 pub added: usize,
211 pub removed: usize,
215 pub updated: usize,
220 pub unchanged: usize,
222}
223
224impl From<crate::node::UpdatePeersOutcome> for UpdatePeersOutcome {
225 fn from(value: crate::node::UpdatePeersOutcome) -> Self {
226 Self {
227 added: value.added,
228 removed: value.removed,
229 updated: value.updated,
230 unchanged: value.unchanged,
231 }
232 }
233}
234
235fn apply_default_scoped_discovery(config: &mut Config, scope: &str) {
236 if config.node.discovery.nostr.enabled || !config.transports.is_empty() {
237 return;
238 }
239
240 config.node.discovery.nostr.enabled = true;
241 config.node.discovery.nostr.advertise = true;
242 config.node.discovery.nostr.policy = NostrDiscoveryPolicy::Open;
243 config.node.discovery.nostr.share_local_candidates = true;
244 config.node.discovery.nostr.app = scope.to_string();
245 config.node.discovery.lan.scope = Some(scope.to_string());
246 config.node.discovery.local.enabled = true;
247 config.transports.udp = TransportInstances::Single(UdpConfig {
248 bind_addr: Some("0.0.0.0:0".to_string()),
249 advertise_on_nostr: Some(true),
250 public: Some(false),
251 outbound_only: Some(false),
252 accept_connections: Some(true),
253 ..UdpConfig::default()
254 });
255}
256
257fn endpoint_ethernet_config(interface: &str, scope: Option<&str>) -> EthernetConfig {
258 EthernetConfig {
259 interface: interface.to_string(),
260 discovery: Some(true),
261 announce: Some(true),
262 auto_connect: Some(true),
263 accept_connections: Some(true),
264 discovery_scope: scope
265 .map(str::trim)
266 .filter(|s| !s.is_empty())
267 .map(str::to_string),
268 ..EthernetConfig::default()
269 }
270}
271
272fn add_endpoint_ethernet_transport(config: &mut Config, interface: &str, scope: Option<&str>) {
273 let eth = endpoint_ethernet_config(interface, scope);
274 if config.transports.ethernet.is_empty() {
275 config.transports.ethernet = TransportInstances::Single(eth);
276 return;
277 }
278
279 let existing = std::mem::take(&mut config.transports.ethernet);
280 let mut named = match existing {
281 TransportInstances::Single(config) => {
282 let mut map = std::collections::HashMap::new();
283 map.insert("default".to_string(), config);
284 map
285 }
286 TransportInstances::Named(map) => map,
287 };
288
289 let base_name = endpoint_ethernet_instance_name(interface);
290 let mut name = base_name.clone();
291 let mut suffix = 2usize;
292 while named.contains_key(&name) {
293 name = format!("{base_name}-{suffix}");
294 suffix += 1;
295 }
296 named.insert(name, eth);
297 config.transports.ethernet = TransportInstances::Named(named);
298}
299
300fn endpoint_ethernet_instance_name(interface: &str) -> String {
301 let suffix: String = interface
302 .chars()
303 .map(|c| {
304 if c.is_ascii_alphanumeric() {
305 c.to_ascii_lowercase()
306 } else {
307 '-'
308 }
309 })
310 .collect();
311 let suffix = suffix.trim_matches('-');
312 if suffix.is_empty() {
313 "local-ethernet".to_string()
314 } else {
315 format!("local-ethernet-{suffix}")
316 }
317}
318
319fn spawn_node_task(
320 mut node: Node,
321 shutdown_rx: oneshot::Receiver<()>,
322) -> JoinHandle<Result<(), NodeError>> {
323 tokio::spawn(async move {
324 tokio::pin!(shutdown_rx);
325 let loop_result = tokio::select! {
326 result = node.run_rx_loop() => result,
327 _ = &mut shutdown_rx => Ok(()),
328 };
329 let stop_result = if node.state().can_stop() {
330 node.stop().await
331 } else {
332 Ok(())
333 };
334 loop_result?;
335 stop_result
336 })
337}
338
339pub struct FipsEndpoint {
341 identity: PeerIdentity,
342 npub: String,
343 node_addr: NodeAddr,
344 address: FipsAddress,
345 discovery_scope: Option<String>,
346 outbound_packets: TunOutboundTx,
347 delivered_packets: Arc<Mutex<mpsc::Receiver<NodeDeliveredPacket>>>,
348 endpoint_control_tx: mpsc::Sender<NodeEndpointControlCommand>,
349 endpoint_data_batches: EndpointDataBatchTx,
350 inbound_endpoint_tx: EndpointEventSender,
356 inbound_endpoint_rx: Arc<Mutex<EndpointReceiveState>>,
363 peer_identity_cache: std::sync::Mutex<std::collections::HashMap<String, PeerIdentity>>,
368 shutdown_tx: StdMutex<Option<oneshot::Sender<()>>>,
369 task: StdMutex<Option<JoinHandle<Result<(), NodeError>>>>,
370}
371
372impl FipsEndpoint {
373 pub fn builder() -> FipsEndpointBuilder {
375 FipsEndpointBuilder::default()
376 }
377
378 pub fn npub(&self) -> &str {
380 &self.npub
381 }
382
383 pub fn node_addr(&self) -> &NodeAddr {
385 &self.node_addr
386 }
387
388 pub fn address(&self) -> FipsAddress {
390 self.address
391 }
392
393 pub fn discovery_scope(&self) -> Option<&str> {
395 self.discovery_scope.as_deref()
396 }
397
398 pub async fn send(
408 &self,
409 remote_npub: impl Into<String>,
410 data: impl Into<Vec<u8>>,
411 ) -> Result<(), FipsEndpointError> {
412 let remote_npub = remote_npub.into();
413 let data = data.into();
414 if remote_npub == self.npub {
415 return self.send_loopback(data);
416 }
417
418 let remote = self.resolve_peer_identity(&remote_npub)?;
419 self.send_to_peer(remote, data).await
420 }
421
422 pub async fn send_to_peer(
429 &self,
430 remote: PeerIdentity,
431 data: impl Into<Vec<u8>>,
432 ) -> Result<(), FipsEndpointError> {
433 let data = data.into();
434 if *remote.node_addr() == self.node_addr {
435 return self.send_loopback(data);
436 }
437 if let Some(batch) =
442 NodeEndpointDataBatch::batch(remote, vec![data], crate::perf_profile::stamp())
443 {
444 self.endpoint_data_batches
445 .send_or_drop(batch)
446 .map_err(|_| FipsEndpointError::Closed)?;
447 }
448 Ok(())
449 }
450
451 pub async fn send_batch_to_peer(
453 &self,
454 remote: PeerIdentity,
455 payloads: Vec<Vec<u8>>,
456 ) -> Result<(), FipsEndpointError> {
457 if *remote.node_addr() == self.node_addr {
458 for payload in payloads {
459 self.send_loopback(payload)?;
460 }
461 return Ok(());
462 }
463
464 if payloads.is_empty() {
465 return Ok(());
466 }
467 self.send_endpoint_data_batch(remote, payloads)?;
468 Ok(())
469 }
470
471 pub async fn send_endpoint_bulk_data_batch_to_peer(
477 &self,
478 remote: PeerIdentity,
479 bulk_bodies: Vec<FipsEndpointBulkData>,
480 ) -> Result<(), FipsEndpointError> {
481 self.send_endpoint_bulk_data_batch(remote, bulk_bodies)
482 }
483
484 fn send_endpoint_data_batch(
485 &self,
486 remote: PeerIdentity,
487 mut payloads: Vec<Vec<u8>>,
488 ) -> Result<(), FipsEndpointError> {
489 while !payloads.is_empty() {
490 let tail = if payloads.len() > ENDPOINT_DATA_BATCH_MAX {
491 payloads.split_off(ENDPOINT_DATA_BATCH_MAX)
492 } else {
493 Vec::new()
494 };
495 let payload_batch = std::mem::replace(&mut payloads, tail);
496 let queued_at = crate::perf_profile::stamp();
497 let Some(batch) = NodeEndpointDataBatch::batch(remote, payload_batch, queued_at) else {
498 continue;
499 };
500 self.endpoint_data_batches
501 .send_or_drop(batch)
502 .map_err(|_| FipsEndpointError::Closed)?;
503 }
504 Ok(())
505 }
506
507 fn send_endpoint_bulk_data_batch(
508 &self,
509 remote: PeerIdentity,
510 bulk_bodies: Vec<FipsEndpointBulkData>,
511 ) -> Result<(), FipsEndpointError> {
512 if bulk_bodies.is_empty() {
513 return Ok(());
514 }
515
516 if *remote.node_addr() == self.node_addr {
517 for body in bulk_bodies {
518 for payload in body.into_packet_payloads() {
519 self.send_loopback(payload)?;
520 }
521 }
522 return Ok(());
523 }
524
525 let mut batch_bodies = Vec::new();
526 let mut batch_packets = 0usize;
527 for body in bulk_bodies {
528 let body = body.into_node_body();
529 let body_packets = body.packet_count();
530 if !batch_bodies.is_empty()
531 && batch_packets.saturating_add(body_packets) > ENDPOINT_DATA_BATCH_MAX
532 {
533 let queued_at = crate::perf_profile::stamp();
534 let Some(batch) =
535 NodeEndpointDataBatch::bulk_bodies(remote, batch_bodies, queued_at)
536 else {
537 batch_bodies = Vec::new();
538 batch_packets = 0;
539 continue;
540 };
541 self.endpoint_data_batches
542 .send_or_drop(batch)
543 .map_err(|_| FipsEndpointError::Closed)?;
544 batch_bodies = Vec::new();
545 batch_packets = 0;
546 }
547 batch_packets = batch_packets.saturating_add(body_packets);
548 batch_bodies.push(body);
549 }
550
551 if !batch_bodies.is_empty() {
552 let queued_at = crate::perf_profile::stamp();
553 let Some(batch) = NodeEndpointDataBatch::bulk_bodies(remote, batch_bodies, queued_at)
554 else {
555 return Ok(());
556 };
557 self.endpoint_data_batches
558 .send_or_drop(batch)
559 .map_err(|_| FipsEndpointError::Closed)?;
560 }
561 Ok(())
562 }
563
564 fn resolve_peer_identity(&self, remote_npub: &str) -> Result<PeerIdentity, FipsEndpointError> {
565 if let Ok(cache) = self.peer_identity_cache.lock()
568 && let Some(remote) = cache.get(remote_npub)
569 {
570 return Ok(*remote);
571 }
572
573 let remote = PeerIdentity::from_npub(remote_npub).map_err(|error| {
574 FipsEndpointError::InvalidRemoteNpub {
575 npub: remote_npub.to_string(),
576 reason: error.to_string(),
577 }
578 })?;
579
580 if let Ok(mut cache) = self.peer_identity_cache.lock() {
581 cache.entry(remote_npub.to_string()).or_insert(remote);
582 }
583 Ok(remote)
584 }
585
586 fn send_loopback(&self, data: Vec<u8>) -> Result<(), FipsEndpointError> {
587 self.inbound_endpoint_tx
588 .send(NodeEndpointEvent {
589 messages: vec![crate::node::EndpointDataDelivery {
590 source_peer: self.identity,
591 payload: data.into(),
592 enqueued_at_ms: crate::time::now_ms(),
593 }],
594 queued_at: crate::perf_profile::stamp(),
595 })
596 .map_err(|_| FipsEndpointError::Closed)
597 }
598
599 pub async fn recv(&self) -> Option<FipsEndpointMessage> {
606 let mut state = self.inbound_endpoint_rx.lock().await;
607 if let Some(message) = state.pop_pending() {
608 return Some(message);
609 }
610 let event = state.rx.recv().await?;
611 state.first_from_event(event)
612 }
613
614 pub async fn recv_batch(&self, max: usize) -> Option<Vec<FipsEndpointMessage>> {
621 let max = max.clamp(1, ENDPOINT_RECV_BATCH_MAX);
622 let mut messages = Vec::with_capacity(max);
623 self.recv_batch_into(&mut messages, max).await?;
624 Some(messages)
625 }
626
627 pub async fn recv_batch_into(
633 &self,
634 messages: &mut Vec<FipsEndpointMessage>,
635 max: usize,
636 ) -> Option<usize> {
637 let max = max.clamp(1, ENDPOINT_RECV_BATCH_MAX);
638 messages.clear();
639
640 let mut state = self.inbound_endpoint_rx.lock().await;
641 state.drain_pending_into(messages, max);
642
643 while messages.len() < max {
644 let event = if messages.is_empty() {
645 state.rx.recv().await?
646 } else {
647 match state.rx.try_recv() {
648 Ok(event) => event,
649 Err(_) => break,
650 }
651 };
652 state.push_event_into(event, messages, max);
653 }
654
655 Some(messages.len())
656 }
657
658 pub fn blocking_send(
668 &self,
669 remote_npub: impl Into<String>,
670 data: impl Into<Vec<u8>>,
671 ) -> Result<(), FipsEndpointError> {
672 let remote_npub = remote_npub.into();
673 let data = data.into();
674 if remote_npub == self.npub {
675 return self.send_loopback(data);
676 }
677 let remote = self.resolve_peer_identity(&remote_npub)?;
678 self.blocking_send_to_peer(remote, data)
679 }
680
681 pub fn blocking_send_to_peer(
686 &self,
687 remote: PeerIdentity,
688 data: impl Into<Vec<u8>>,
689 ) -> Result<(), FipsEndpointError> {
690 let data = data.into();
691 if *remote.node_addr() == self.node_addr {
692 return self.send_loopback(data);
693 }
694 if let Some(batch) =
695 NodeEndpointDataBatch::batch(remote, vec![data], crate::perf_profile::stamp())
696 {
697 self.endpoint_data_batches
698 .send_or_drop(batch)
699 .map_err(|_| FipsEndpointError::Closed)?;
700 }
701 Ok(())
702 }
703
704 pub fn blocking_send_batch_to_peer(
710 &self,
711 remote: PeerIdentity,
712 payloads: Vec<Vec<u8>>,
713 ) -> Result<(), FipsEndpointError> {
714 if *remote.node_addr() == self.node_addr {
715 for payload in payloads {
716 self.send_loopback(payload)?;
717 }
718 return Ok(());
719 }
720
721 if payloads.is_empty() {
722 return Ok(());
723 }
724 self.send_endpoint_data_batch(remote, payloads)
725 }
726
727 pub fn blocking_send_endpoint_bulk_data_batch_to_peer(
729 &self,
730 remote: PeerIdentity,
731 bulk_bodies: Vec<FipsEndpointBulkData>,
732 ) -> Result<(), FipsEndpointError> {
733 self.send_endpoint_bulk_data_batch(remote, bulk_bodies)
734 }
735
736 pub fn blocking_recv(&self) -> Option<FipsEndpointMessage> {
752 let mut state = self.inbound_endpoint_rx.blocking_lock();
753 if let Some(message) = state.pop_pending() {
754 return Some(message);
755 }
756 let event = state.rx.blocking_recv()?;
757 state.first_from_event(event)
758 }
759
760 pub fn blocking_recv_batch_into(
768 &self,
769 messages: &mut Vec<FipsEndpointMessage>,
770 max: usize,
771 ) -> Option<usize> {
772 messages.clear();
773 self.blocking_recv_batch_for_each(max, |message| {
774 messages.push(message);
775 true
776 })
777 }
778
779 pub fn blocking_recv_batch_for_each(
789 &self,
790 max: usize,
791 mut handle_message: impl FnMut(FipsEndpointMessage) -> bool,
792 ) -> Option<usize> {
793 let max = max.clamp(1, ENDPOINT_RECV_BATCH_MAX);
794 let mut drained = 0usize;
795
796 let mut state = self.inbound_endpoint_rx.blocking_lock();
797 if !state.drain_pending_for_each(&mut drained, max, &mut handle_message) {
798 return Some(drained);
799 }
800
801 while drained < max {
802 let event = if drained == 0 {
803 state.rx.blocking_recv()?
804 } else {
805 match state.rx.try_recv() {
806 Ok(event) => event,
807 Err(_) => break,
808 }
809 };
810 if !state.push_event_for_each(event, &mut drained, max, &mut handle_message) {
811 return Some(drained);
812 }
813 }
814
815 Some(drained)
816 }
817
818 pub fn try_recv(&self) -> Option<FipsEndpointMessage> {
838 let mut state = self.inbound_endpoint_rx.try_lock().ok()?;
839 if let Some(message) = state.pop_pending() {
840 return Some(message);
841 }
842 let event = state.rx.try_recv().ok()?;
843 state.first_from_event(event)
844 }
845
846 pub async fn update_peers(
856 &self,
857 peers: Vec<crate::config::PeerConfig>,
858 ) -> Result<UpdatePeersOutcome, FipsEndpointError> {
859 let (response_tx, response_rx) = oneshot::channel();
860 self.endpoint_control_tx
861 .send(NodeEndpointControlCommand::UpdatePeers { peers, response_tx })
862 .await
863 .map_err(|_| FipsEndpointError::Closed)?;
864
865 match response_rx.await.map_err(|_| FipsEndpointError::Closed)? {
866 Ok(outcome) => Ok(UpdatePeersOutcome::from(outcome)),
867 Err(error) => Err(FipsEndpointError::Node(error)),
868 }
869 }
870
871 pub async fn refresh_peer_paths(
877 &self,
878 peers: Vec<PeerIdentity>,
879 ) -> Result<usize, FipsEndpointError> {
880 let (response_tx, response_rx) = oneshot::channel();
881 let npubs = peers.into_iter().map(|peer| peer.npub()).collect();
882 self.endpoint_control_tx
883 .send(NodeEndpointControlCommand::RefreshPeerPaths { npubs, response_tx })
884 .await
885 .map_err(|_| FipsEndpointError::Closed)?;
886
887 match response_rx.await.map_err(|_| FipsEndpointError::Closed)? {
888 Ok(refreshed) => Ok(refreshed),
889 Err(error) => Err(FipsEndpointError::Node(error)),
890 }
891 }
892
893 pub async fn peers(&self) -> Result<Vec<FipsEndpointPeer>, FipsEndpointError> {
895 let (response_tx, response_rx) = oneshot::channel();
896 self.endpoint_control_tx
897 .send(NodeEndpointControlCommand::PeerSnapshot { response_tx })
898 .await
899 .map_err(|_| FipsEndpointError::Closed)?;
900
901 response_rx
902 .await
903 .map(|peers| peers.into_iter().map(FipsEndpointPeer::from).collect())
904 .map_err(|_| FipsEndpointError::Closed)
905 }
906
907 pub async fn local_advertised_endpoints(
910 &self,
911 ) -> Result<Vec<crate::discovery::nostr::OverlayEndpointAdvert>, FipsEndpointError> {
912 let (response_tx, response_rx) = oneshot::channel();
913 self.endpoint_control_tx
914 .send(NodeEndpointControlCommand::LocalAdvertSnapshot { response_tx })
915 .await
916 .map_err(|_| FipsEndpointError::Closed)?;
917
918 response_rx.await.map_err(|_| FipsEndpointError::Closed)
919 }
920
921 pub async fn relay_statuses(&self) -> Result<Vec<FipsEndpointRelayStatus>, FipsEndpointError> {
923 let (response_tx, response_rx) = oneshot::channel();
924 self.endpoint_control_tx
925 .send(NodeEndpointControlCommand::RelaySnapshot { response_tx })
926 .await
927 .map_err(|_| FipsEndpointError::Closed)?;
928
929 response_rx
930 .await
931 .map(|relays| {
932 relays
933 .into_iter()
934 .map(FipsEndpointRelayStatus::from)
935 .collect()
936 })
937 .map_err(|_| FipsEndpointError::Closed)
938 }
939
940 pub async fn update_relays(
942 &self,
943 advert_relays: Vec<String>,
944 dm_relays: Vec<String>,
945 ) -> Result<(), FipsEndpointError> {
946 let (response_tx, response_rx) = oneshot::channel();
947 self.endpoint_control_tx
948 .send(NodeEndpointControlCommand::UpdateRelays {
949 advert_relays,
950 dm_relays,
951 response_tx,
952 })
953 .await
954 .map_err(|_| FipsEndpointError::Closed)?;
955
956 response_rx
957 .await
958 .map_err(|_| FipsEndpointError::Closed)?
959 .map_err(FipsEndpointError::Node)
960 }
961
962 pub async fn send_ip_packet(
964 &self,
965 packet: impl Into<Vec<u8>>,
966 ) -> Result<(), FipsEndpointError> {
967 self.outbound_packets
968 .send(packet.into())
969 .await
970 .map_err(|_| FipsEndpointError::Closed)
971 }
972
973 pub async fn recv_ip_packet(&self) -> Option<NodeDeliveredPacket> {
975 self.delivered_packets.lock().await.recv().await
976 }
977
978 pub async fn shutdown(&self) -> Result<(), FipsEndpointError> {
980 let shutdown_tx = self
981 .shutdown_tx
982 .lock()
983 .map_err(|_| FipsEndpointError::Closed)?
984 .take();
985 if let Some(shutdown_tx) = shutdown_tx {
986 let _ = shutdown_tx.send(());
987 }
988 let task = self
989 .task
990 .lock()
991 .map_err(|_| FipsEndpointError::Closed)?
992 .take();
993 if let Some(task) = task {
994 task.await??;
995 }
996 Ok(())
997 }
998}
999
1000impl Drop for FipsEndpoint {
1001 fn drop(&mut self) {
1002 if let Ok(mut shutdown_tx) = self.shutdown_tx.lock()
1003 && let Some(shutdown_tx) = shutdown_tx.take()
1004 {
1005 let _ = shutdown_tx.send(());
1006 }
1007 if let Ok(mut task) = self.task.lock()
1008 && let Some(task) = task.take()
1009 {
1010 task.abort();
1011 }
1012 }
1013}