1use crate::config::{EthernetConfig, NostrDiscoveryPolicy, TransportInstances, UdpConfig};
7#[cfg(test)]
8use crate::node::ENDPOINT_EVENT_PRIORITY_MAX_LEN;
9#[cfg(unix)]
10use crate::node::EndpointBulkSendRuntime;
11use crate::node::{
12 EndpointCommandLane, EndpointDataPayload, EndpointEventSender, EndpointPayloadClass,
13 NodeEndpointCommand, NodeEndpointEvent,
14};
15use crate::{
16 Config, FipsAddress, IdentityConfig, Node, NodeAddr, NodeDeliveredPacket, NodeError,
17 PeerIdentity,
18};
19use std::sync::{Arc, Mutex as StdMutex};
20use thiserror::Error;
21use tokio::sync::{Mutex, mpsc, oneshot};
22use tokio::task::JoinHandle;
23
24const ENDPOINT_SEND_BATCH_COMMAND_MAX: usize = 128;
25const ENDPOINT_RECV_BATCH_MAX: usize = 128;
26
27mod builder;
28mod receive;
29mod status;
30
31#[cfg(test)]
32mod tests;
33
34pub use builder::FipsEndpointBuilder;
35use receive::EndpointReceiveState;
36pub use status::{FipsEndpointPeer, FipsEndpointRelayStatus};
37
38#[derive(Clone, Debug, PartialEq, Eq)]
45pub struct FipsEndpointPayload {
46 bytes: Vec<u8>,
47 class: EndpointPayloadClass,
48}
49
50impl FipsEndpointPayload {
51 pub fn new(bytes: Vec<u8>) -> Self {
52 let class = crate::node::classify_endpoint_payload(&bytes);
53 Self { bytes, class }
54 }
55
56 pub fn from_classified(bytes: Vec<u8>, class: EndpointPayloadClass) -> Self {
57 Self { bytes, class }
58 }
59
60 pub fn class(&self) -> EndpointPayloadClass {
61 self.class
62 }
63
64 pub fn as_slice(&self) -> &[u8] {
65 &self.bytes
66 }
67
68 pub fn len(&self) -> usize {
69 self.bytes.len()
70 }
71
72 pub fn is_empty(&self) -> bool {
73 self.bytes.is_empty()
74 }
75
76 pub fn into_bytes(self) -> Vec<u8> {
77 self.bytes
78 }
79}
80
81impl From<FipsEndpointPayload> for EndpointDataPayload {
82 fn from(payload: FipsEndpointPayload) -> Self {
83 EndpointDataPayload::from_classified(payload.bytes, payload.class)
84 }
85}
86
87#[derive(Debug)]
88enum EndpointPayloadLaneBatches {
89 Empty,
90 Single {
91 lane: EndpointCommandLane,
92 payloads: Vec<EndpointDataPayload>,
93 },
94 Split {
95 priority_payloads: Vec<EndpointDataPayload>,
96 bulk_payloads: Vec<EndpointDataPayload>,
97 },
98}
99
100#[cfg(debug_assertions)]
101fn endpoint_debug_log(message: impl AsRef<str>) {
102 use std::io::Write as _;
103
104 if let Ok(mut file) = std::fs::OpenOptions::new()
105 .create(true)
106 .append(true)
107 .open(std::env::temp_dir().join("nvpn-fips-endpoint-debug.log"))
108 {
109 let _ = writeln!(
110 file,
111 "{:?} {}",
112 std::time::SystemTime::now(),
113 message.as_ref()
114 );
115 }
116}
117
118#[cfg(not(debug_assertions))]
119fn endpoint_debug_log(_message: impl AsRef<str>) {}
120
121#[derive(Debug, Error)]
123pub enum FipsEndpointError {
124 #[error("node error: {0}")]
125 Node(#[from] NodeError),
126
127 #[error("endpoint task failed: {0}")]
128 TaskJoin(#[from] tokio::task::JoinError),
129
130 #[error("endpoint is closed")]
131 Closed,
132
133 #[error("invalid remote npub '{npub}': {reason}")]
134 InvalidRemoteNpub { npub: String, reason: String },
135}
136
137#[derive(Debug, Clone, PartialEq, Eq)]
139pub struct FipsEndpointMessage {
140 pub source_peer: PeerIdentity,
142 pub data: Vec<u8>,
144}
145
146impl FipsEndpointMessage {
147 pub fn source_node_addr(&self) -> &NodeAddr {
149 self.source_peer.node_addr()
150 }
151
152 pub fn source_npub(&self) -> String {
154 self.source_peer.npub()
155 }
156}
157
158#[derive(Debug, Clone, Default, PartialEq, Eq)]
160pub struct UpdatePeersOutcome {
161 pub added: usize,
164 pub removed: usize,
168 pub updated: usize,
173 pub unchanged: usize,
175}
176
177impl From<crate::node::UpdatePeersOutcome> for UpdatePeersOutcome {
178 fn from(value: crate::node::UpdatePeersOutcome) -> Self {
179 Self {
180 added: value.added,
181 removed: value.removed,
182 updated: value.updated,
183 unchanged: value.unchanged,
184 }
185 }
186}
187
188fn apply_default_scoped_discovery(config: &mut Config, scope: &str) {
189 if config.node.discovery.nostr.enabled || !config.transports.is_empty() {
190 return;
191 }
192
193 config.node.discovery.nostr.enabled = true;
194 config.node.discovery.nostr.advertise = true;
195 config.node.discovery.nostr.policy = NostrDiscoveryPolicy::Open;
196 config.node.discovery.nostr.share_local_candidates = true;
197 config.node.discovery.nostr.app = scope.to_string();
198 config.node.discovery.lan.scope = Some(scope.to_string());
199 config.node.discovery.local.enabled = true;
200 config.transports.udp = TransportInstances::Single(UdpConfig {
201 bind_addr: Some("0.0.0.0:0".to_string()),
202 advertise_on_nostr: Some(true),
203 public: Some(false),
204 outbound_only: Some(false),
205 accept_connections: Some(true),
206 ..UdpConfig::default()
207 });
208}
209
210fn endpoint_ethernet_config(interface: &str, scope: Option<&str>) -> EthernetConfig {
211 EthernetConfig {
212 interface: interface.to_string(),
213 discovery: Some(true),
214 announce: Some(true),
215 auto_connect: Some(true),
216 accept_connections: Some(true),
217 discovery_scope: scope
218 .map(str::trim)
219 .filter(|s| !s.is_empty())
220 .map(str::to_string),
221 ..EthernetConfig::default()
222 }
223}
224
225fn add_endpoint_ethernet_transport(config: &mut Config, interface: &str, scope: Option<&str>) {
226 let eth = endpoint_ethernet_config(interface, scope);
227 if config.transports.ethernet.is_empty() {
228 config.transports.ethernet = TransportInstances::Single(eth);
229 return;
230 }
231
232 let existing = std::mem::take(&mut config.transports.ethernet);
233 let mut named = match existing {
234 TransportInstances::Single(config) => {
235 let mut map = std::collections::HashMap::new();
236 map.insert("default".to_string(), config);
237 map
238 }
239 TransportInstances::Named(map) => map,
240 };
241
242 let base_name = endpoint_ethernet_instance_name(interface);
243 let mut name = base_name.clone();
244 let mut suffix = 2usize;
245 while named.contains_key(&name) {
246 name = format!("{base_name}-{suffix}");
247 suffix += 1;
248 }
249 named.insert(name, eth);
250 config.transports.ethernet = TransportInstances::Named(named);
251}
252
253fn endpoint_ethernet_instance_name(interface: &str) -> String {
254 let suffix: String = interface
255 .chars()
256 .map(|c| {
257 if c.is_ascii_alphanumeric() {
258 c.to_ascii_lowercase()
259 } else {
260 '-'
261 }
262 })
263 .collect();
264 let suffix = suffix.trim_matches('-');
265 if suffix.is_empty() {
266 "local-ethernet".to_string()
267 } else {
268 format!("local-ethernet-{suffix}")
269 }
270}
271
272fn spawn_node_task(
273 mut node: Node,
274 shutdown_rx: oneshot::Receiver<()>,
275) -> JoinHandle<Result<(), NodeError>> {
276 tokio::spawn(async move {
277 tokio::pin!(shutdown_rx);
278 let loop_result = tokio::select! {
279 result = node.run_rx_loop() => result,
280 _ = &mut shutdown_rx => Ok(()),
281 };
282 let stop_result = if node.state().can_stop() {
283 node.stop().await
284 } else {
285 Ok(())
286 };
287 loop_result?;
288 stop_result
289 })
290}
291
292pub struct FipsEndpoint {
294 identity: PeerIdentity,
295 npub: String,
296 node_addr: NodeAddr,
297 address: FipsAddress,
298 discovery_scope: Option<String>,
299 outbound_packets: mpsc::Sender<Vec<u8>>,
300 delivered_packets: Arc<Mutex<mpsc::Receiver<NodeDeliveredPacket>>>,
301 endpoint_priority_commands: mpsc::Sender<NodeEndpointCommand>,
302 endpoint_commands: mpsc::Sender<NodeEndpointCommand>,
303 #[cfg(unix)]
304 endpoint_bulk_send_runtime: EndpointBulkSendRuntime,
305 inbound_endpoint_tx: EndpointEventSender,
311 inbound_endpoint_rx: Arc<Mutex<EndpointReceiveState>>,
318 peer_identity_cache: std::sync::Mutex<std::collections::HashMap<String, PeerIdentity>>,
323 shutdown_tx: StdMutex<Option<oneshot::Sender<()>>>,
324 task: StdMutex<Option<JoinHandle<Result<(), NodeError>>>>,
325}
326
327impl FipsEndpoint {
328 pub fn builder() -> FipsEndpointBuilder {
330 FipsEndpointBuilder::default()
331 }
332
333 pub fn npub(&self) -> &str {
335 &self.npub
336 }
337
338 pub fn node_addr(&self) -> &NodeAddr {
340 &self.node_addr
341 }
342
343 pub fn address(&self) -> FipsAddress {
345 self.address
346 }
347
348 pub fn discovery_scope(&self) -> Option<&str> {
350 self.discovery_scope.as_deref()
351 }
352
353 pub async fn send(
366 &self,
367 remote_npub: impl Into<String>,
368 data: impl Into<Vec<u8>>,
369 ) -> Result<(), FipsEndpointError> {
370 let remote_npub = remote_npub.into();
371 let data = data.into();
372 if remote_npub == self.npub {
373 return self.send_loopback(data);
374 }
375
376 let remote = self.resolve_peer_identity(&remote_npub)?;
377 self.send_to_peer(remote, data).await
378 }
379
380 pub async fn send_to_peer(
387 &self,
388 remote: PeerIdentity,
389 data: impl Into<Vec<u8>>,
390 ) -> Result<(), FipsEndpointError> {
391 let data = data.into();
392 if *remote.node_addr() == self.node_addr {
393 return self.send_loopback(data);
394 }
395 let command = NodeEndpointCommand::send_oneway(remote, data, crate::perf_profile::stamp());
400 send_endpoint_command(
401 command,
402 &self.endpoint_priority_commands,
403 &self.endpoint_commands,
404 )
405 .await?;
406 Ok(())
407 }
408
409 pub async fn send_batch_to_peer(
415 &self,
416 remote: PeerIdentity,
417 payloads: Vec<Vec<u8>>,
418 ) -> Result<(), FipsEndpointError> {
419 let payloads = payloads.into_iter().map(FipsEndpointPayload::new).collect();
420 self.send_classified_batch_to_peer(remote, payloads).await
421 }
422
423 pub async fn send_classified_batch_to_peer(
425 &self,
426 remote: PeerIdentity,
427 payloads: Vec<FipsEndpointPayload>,
428 ) -> Result<(), FipsEndpointError> {
429 if *remote.node_addr() == self.node_addr {
430 for payload in payloads {
431 self.send_loopback(payload.into_bytes())?;
432 }
433 return Ok(());
434 }
435
436 match endpoint_payload_lane_batches(payloads) {
437 EndpointPayloadLaneBatches::Empty => {}
438 EndpointPayloadLaneBatches::Single { lane, payloads } => {
439 self.send_endpoint_command_batch(remote, payloads, lane)
440 .await?;
441 }
442 EndpointPayloadLaneBatches::Split {
443 priority_payloads,
444 bulk_payloads,
445 } => {
446 self.send_endpoint_command_batch(
447 remote,
448 priority_payloads,
449 EndpointCommandLane::Priority,
450 )
451 .await?;
452 self.send_endpoint_command_batch(remote, bulk_payloads, EndpointCommandLane::Bulk)
453 .await?;
454 }
455 }
456 Ok(())
457 }
458
459 async fn send_endpoint_command_batch(
460 &self,
461 remote: PeerIdentity,
462 mut payloads: Vec<EndpointDataPayload>,
463 lane: EndpointCommandLane,
464 ) -> Result<(), FipsEndpointError> {
465 while !payloads.is_empty() {
466 let tail = if payloads.len() > ENDPOINT_SEND_BATCH_COMMAND_MAX {
467 payloads.split_off(ENDPOINT_SEND_BATCH_COMMAND_MAX)
468 } else {
469 Vec::new()
470 };
471 let batch = std::mem::replace(&mut payloads, tail);
472 #[cfg(unix)]
473 if lane == EndpointCommandLane::Bulk
474 && self
475 .endpoint_bulk_send_runtime
476 .try_send_bulk_batch_to_peer(remote, &batch)
477 {
478 continue;
479 }
480 let queued_at = crate::perf_profile::stamp();
481 let Some(command) =
482 NodeEndpointCommand::send_batch_oneway(remote, batch, queued_at, lane)
483 else {
484 continue;
485 };
486 send_endpoint_command(
487 command,
488 &self.endpoint_priority_commands,
489 &self.endpoint_commands,
490 )
491 .await?;
492 }
493 Ok(())
494 }
495
496 fn resolve_peer_identity(&self, remote_npub: &str) -> Result<PeerIdentity, FipsEndpointError> {
497 if let Ok(cache) = self.peer_identity_cache.lock()
500 && let Some(remote) = cache.get(remote_npub)
501 {
502 return Ok(*remote);
503 }
504
505 let remote = PeerIdentity::from_npub(remote_npub).map_err(|error| {
506 FipsEndpointError::InvalidRemoteNpub {
507 npub: remote_npub.to_string(),
508 reason: error.to_string(),
509 }
510 })?;
511
512 if let Ok(mut cache) = self.peer_identity_cache.lock() {
513 cache.entry(remote_npub.to_string()).or_insert(remote);
514 }
515 Ok(remote)
516 }
517
518 fn send_loopback(&self, data: Vec<u8>) -> Result<(), FipsEndpointError> {
519 self.inbound_endpoint_tx
520 .send(NodeEndpointEvent::Data {
521 source_peer: self.identity,
522 payload: data.into(),
523 queued_at: crate::perf_profile::stamp(),
524 })
525 .map_err(|_| FipsEndpointError::Closed)
526 }
527
528 pub async fn recv(&self) -> Option<FipsEndpointMessage> {
535 let mut state = self.inbound_endpoint_rx.lock().await;
536 if let Some(message) = state.pop_pending_priority() {
537 return Some(message);
538 }
539 if let Ok(event) = state.rx.try_recv_priority() {
540 return state.first_from_event(event);
541 }
542 if let Some(message) = state.pop_pending_bulk() {
543 return Some(message);
544 }
545 let event = state.rx.recv().await?;
546 state.first_from_event(event)
547 }
548
549 pub async fn recv_batch(&self, max: usize) -> Option<Vec<FipsEndpointMessage>> {
556 let max = max.clamp(1, ENDPOINT_RECV_BATCH_MAX);
557 let mut messages = Vec::with_capacity(max);
558 self.recv_batch_into(&mut messages, max).await?;
559 Some(messages)
560 }
561
562 pub async fn recv_batch_into(
568 &self,
569 messages: &mut Vec<FipsEndpointMessage>,
570 max: usize,
571 ) -> Option<usize> {
572 let max = max.clamp(1, ENDPOINT_RECV_BATCH_MAX);
573 messages.clear();
574
575 let mut state = self.inbound_endpoint_rx.lock().await;
576 state.drain_priority_pending_into(messages, max);
577 while messages.len() < max {
578 match state.rx.try_recv_priority() {
579 Ok(event) => state.push_event_into(event, messages, max),
580 Err(_) => break,
581 }
582 }
583 state.drain_bulk_pending_into(messages, max);
584
585 while messages.len() < max {
586 let event = if messages.is_empty() {
587 state.rx.recv().await?
588 } else {
589 match state.rx.try_recv() {
590 Ok(event) => event,
591 Err(_) => break,
592 }
593 };
594 state.push_event_into(event, messages, max);
595 }
596
597 Some(messages.len())
598 }
599
600 pub fn blocking_send(
610 &self,
611 remote_npub: impl Into<String>,
612 data: impl Into<Vec<u8>>,
613 ) -> Result<(), FipsEndpointError> {
614 let remote_npub = remote_npub.into();
615 let data = data.into();
616 if remote_npub == self.npub {
617 return self.send_loopback(data);
618 }
619 let remote = self.resolve_peer_identity(&remote_npub)?;
620 self.blocking_send_to_peer(remote, data)
621 }
622
623 pub fn blocking_send_to_peer(
628 &self,
629 remote: PeerIdentity,
630 data: impl Into<Vec<u8>>,
631 ) -> Result<(), FipsEndpointError> {
632 let data = data.into();
633 if *remote.node_addr() == self.node_addr {
634 return self.send_loopback(data);
635 }
636 let (response_tx, _response_rx) = oneshot::channel();
637 let command =
638 NodeEndpointCommand::send(remote, data, crate::perf_profile::stamp(), response_tx);
639 endpoint_command_tx_for_command(
640 &command,
641 &self.endpoint_priority_commands,
642 &self.endpoint_commands,
643 )
644 .blocking_send(command)
645 .map_err(|_| FipsEndpointError::Closed)?;
646 Ok(())
647 }
648
649 pub fn blocking_recv(&self) -> Option<FipsEndpointMessage> {
665 let mut state = self.inbound_endpoint_rx.blocking_lock();
666 if let Some(message) = state.pop_pending_priority() {
667 return Some(message);
668 }
669 if let Ok(event) = state.rx.try_recv_priority() {
670 return state.first_from_event(event);
671 }
672 if let Some(message) = state.pop_pending_bulk() {
673 return Some(message);
674 }
675 let event = state.rx.blocking_recv()?;
676 state.first_from_event(event)
677 }
678
679 pub fn blocking_recv_batch_into(
687 &self,
688 messages: &mut Vec<FipsEndpointMessage>,
689 max: usize,
690 ) -> Option<usize> {
691 messages.clear();
692 self.blocking_recv_batch_for_each(max, |message| {
693 messages.push(message);
694 true
695 })
696 }
697
698 pub fn blocking_recv_batch_for_each(
709 &self,
710 max: usize,
711 mut handle_message: impl FnMut(FipsEndpointMessage) -> bool,
712 ) -> Option<usize> {
713 let max = max.clamp(1, ENDPOINT_RECV_BATCH_MAX);
714 let mut drained = 0usize;
715
716 let mut state = self.inbound_endpoint_rx.blocking_lock();
717 if !state.drain_priority_pending_for_each(&mut drained, max, &mut handle_message) {
718 return Some(drained);
719 }
720 while drained < max {
721 match state.rx.try_recv_priority() {
722 Ok(event) => {
723 if !state.push_event_for_each(event, &mut drained, max, &mut handle_message) {
724 return Some(drained);
725 }
726 }
727 Err(_) => break,
728 }
729 }
730 if !state.drain_bulk_pending_for_each(&mut drained, max, &mut handle_message) {
731 return Some(drained);
732 }
733
734 while drained < max {
735 let event = if drained == 0 {
736 state.rx.blocking_recv()?
737 } else {
738 match state.rx.try_recv() {
739 Ok(event) => event,
740 Err(_) => break,
741 }
742 };
743 if !state.push_event_for_each(event, &mut drained, max, &mut handle_message) {
744 return Some(drained);
745 }
746 }
747
748 Some(drained)
749 }
750
751 pub fn try_recv(&self) -> Option<FipsEndpointMessage> {
771 let mut state = self.inbound_endpoint_rx.try_lock().ok()?;
772 if let Some(message) = state.pop_pending_priority() {
773 return Some(message);
774 }
775 if let Ok(event) = state.rx.try_recv_priority() {
776 return state.first_from_event(event);
777 }
778 if let Some(message) = state.pop_pending_bulk() {
779 return Some(message);
780 }
781 let event = state.rx.try_recv().ok()?;
782 state.first_from_event(event)
783 }
784
785 pub async fn update_peers(
795 &self,
796 peers: Vec<crate::config::PeerConfig>,
797 ) -> Result<UpdatePeersOutcome, FipsEndpointError> {
798 let (response_tx, response_rx) = oneshot::channel();
799 self.endpoint_priority_commands
800 .send(NodeEndpointCommand::UpdatePeers { peers, response_tx })
801 .await
802 .map_err(|_| FipsEndpointError::Closed)?;
803
804 match response_rx.await.map_err(|_| FipsEndpointError::Closed)? {
805 Ok(outcome) => Ok(UpdatePeersOutcome::from(outcome)),
806 Err(error) => Err(FipsEndpointError::Node(error)),
807 }
808 }
809
810 pub async fn refresh_peer_paths(
816 &self,
817 peers: Vec<PeerIdentity>,
818 ) -> Result<usize, FipsEndpointError> {
819 let (response_tx, response_rx) = oneshot::channel();
820 let npubs = peers.into_iter().map(|peer| peer.npub()).collect();
821 self.endpoint_priority_commands
822 .send(NodeEndpointCommand::RefreshPeerPaths { npubs, response_tx })
823 .await
824 .map_err(|_| FipsEndpointError::Closed)?;
825
826 match response_rx.await.map_err(|_| FipsEndpointError::Closed)? {
827 Ok(refreshed) => Ok(refreshed),
828 Err(error) => Err(FipsEndpointError::Node(error)),
829 }
830 }
831
832 pub async fn peers(&self) -> Result<Vec<FipsEndpointPeer>, FipsEndpointError> {
834 let (response_tx, response_rx) = oneshot::channel();
835 self.endpoint_priority_commands
836 .send(NodeEndpointCommand::PeerSnapshot { response_tx })
837 .await
838 .map_err(|_| FipsEndpointError::Closed)?;
839
840 response_rx
841 .await
842 .map(|peers| peers.into_iter().map(FipsEndpointPeer::from).collect())
843 .map_err(|_| FipsEndpointError::Closed)
844 }
845
846 pub async fn local_advertised_endpoints(
849 &self,
850 ) -> Result<Vec<crate::discovery::nostr::OverlayEndpointAdvert>, FipsEndpointError> {
851 let (response_tx, response_rx) = oneshot::channel();
852 self.endpoint_priority_commands
853 .send(NodeEndpointCommand::LocalAdvertSnapshot { response_tx })
854 .await
855 .map_err(|_| FipsEndpointError::Closed)?;
856
857 response_rx.await.map_err(|_| FipsEndpointError::Closed)
858 }
859
860 pub async fn relay_statuses(&self) -> Result<Vec<FipsEndpointRelayStatus>, FipsEndpointError> {
862 let (response_tx, response_rx) = oneshot::channel();
863 self.endpoint_priority_commands
864 .send(NodeEndpointCommand::RelaySnapshot { response_tx })
865 .await
866 .map_err(|_| FipsEndpointError::Closed)?;
867
868 response_rx
869 .await
870 .map(|relays| {
871 relays
872 .into_iter()
873 .map(FipsEndpointRelayStatus::from)
874 .collect()
875 })
876 .map_err(|_| FipsEndpointError::Closed)
877 }
878
879 pub async fn update_relays(
881 &self,
882 advert_relays: Vec<String>,
883 dm_relays: Vec<String>,
884 ) -> Result<(), FipsEndpointError> {
885 let (response_tx, response_rx) = oneshot::channel();
886 self.endpoint_priority_commands
887 .send(NodeEndpointCommand::UpdateRelays {
888 advert_relays,
889 dm_relays,
890 response_tx,
891 })
892 .await
893 .map_err(|_| FipsEndpointError::Closed)?;
894
895 response_rx
896 .await
897 .map_err(|_| FipsEndpointError::Closed)?
898 .map_err(FipsEndpointError::Node)
899 }
900
901 pub async fn send_ip_packet(
903 &self,
904 packet: impl Into<Vec<u8>>,
905 ) -> Result<(), FipsEndpointError> {
906 self.outbound_packets
907 .send(packet.into())
908 .await
909 .map_err(|_| FipsEndpointError::Closed)
910 }
911
912 pub async fn recv_ip_packet(&self) -> Option<NodeDeliveredPacket> {
914 self.delivered_packets.lock().await.recv().await
915 }
916
917 pub async fn shutdown(&self) -> Result<(), FipsEndpointError> {
919 let shutdown_tx = self
920 .shutdown_tx
921 .lock()
922 .map_err(|_| FipsEndpointError::Closed)?
923 .take();
924 if let Some(shutdown_tx) = shutdown_tx {
925 let _ = shutdown_tx.send(());
926 }
927 let task = self
928 .task
929 .lock()
930 .map_err(|_| FipsEndpointError::Closed)?
931 .take();
932 if let Some(task) = task {
933 task.await??;
934 }
935 Ok(())
936 }
937}
938
939impl Drop for FipsEndpoint {
940 fn drop(&mut self) {
941 if let Ok(mut shutdown_tx) = self.shutdown_tx.lock()
942 && let Some(shutdown_tx) = shutdown_tx.take()
943 {
944 let _ = shutdown_tx.send(());
945 }
946 if let Ok(mut task) = self.task.lock()
947 && let Some(task) = task.take()
948 {
949 task.abort();
950 }
951 }
952}
953
954fn endpoint_command_tx_for_command<'a>(
955 command: &NodeEndpointCommand,
956 priority_tx: &'a mpsc::Sender<NodeEndpointCommand>,
957 bulk_tx: &'a mpsc::Sender<NodeEndpointCommand>,
958) -> &'a mpsc::Sender<NodeEndpointCommand> {
959 match command.lane() {
960 EndpointCommandLane::Priority => priority_tx,
961 EndpointCommandLane::Bulk => bulk_tx,
962 }
963}
964
965fn endpoint_payload_lane_batches(payloads: Vec<FipsEndpointPayload>) -> EndpointPayloadLaneBatches {
966 let payload_count = payloads.len();
967 let mut raw_payloads = payloads.into_iter();
968 let Some(first) = raw_payloads.next() else {
969 return EndpointPayloadLaneBatches::Empty;
970 };
971
972 let first = EndpointDataPayload::from(first);
973 let mut first_lane_payloads = Vec::with_capacity(payload_count);
974 let first_lane = first.lane();
975 first_lane_payloads.push(first);
976 let mut batches = EndpointPayloadLaneBatches::Single {
977 lane: first_lane,
978 payloads: first_lane_payloads,
979 };
980
981 for payload in raw_payloads.map(EndpointDataPayload::from) {
982 let payload_lane = payload.lane();
983 match &mut batches {
984 EndpointPayloadLaneBatches::Empty => unreachable!("first payload exists"),
985 EndpointPayloadLaneBatches::Single { lane, payloads } if payload_lane == *lane => {
986 payloads.push(payload);
987 }
988 EndpointPayloadLaneBatches::Single { lane, payloads } => {
989 let first_lane_payloads = std::mem::take(payloads);
990 let mut priority_payloads = Vec::new();
991 let mut bulk_payloads = Vec::new();
992 match *lane {
993 EndpointCommandLane::Priority => priority_payloads = first_lane_payloads,
994 EndpointCommandLane::Bulk => bulk_payloads = first_lane_payloads,
995 }
996 match payload_lane {
997 EndpointCommandLane::Priority => priority_payloads.push(payload),
998 EndpointCommandLane::Bulk => bulk_payloads.push(payload),
999 }
1000 batches = EndpointPayloadLaneBatches::Split {
1001 priority_payloads,
1002 bulk_payloads,
1003 };
1004 }
1005 EndpointPayloadLaneBatches::Split {
1006 priority_payloads,
1007 bulk_payloads,
1008 } => match payload_lane {
1009 EndpointCommandLane::Priority => priority_payloads.push(payload),
1010 EndpointCommandLane::Bulk => bulk_payloads.push(payload),
1011 },
1012 }
1013 }
1014
1015 batches
1016}
1017
1018async fn send_endpoint_command(
1019 command: NodeEndpointCommand,
1020 priority_tx: &mpsc::Sender<NodeEndpointCommand>,
1021 bulk_tx: &mpsc::Sender<NodeEndpointCommand>,
1022) -> Result<(), FipsEndpointError> {
1023 let command_tx = endpoint_command_tx_for_command(&command, priority_tx, bulk_tx);
1024
1025 if command.drop_on_backpressure() {
1026 match command_tx.try_send(command) {
1027 Ok(()) => return Ok(()),
1028 Err(mpsc::error::TrySendError::Full(command)) => {
1029 crate::perf_profile::record_event_count(
1030 crate::perf_profile::Event::EndpointCommandBulkDropped,
1031 command.packet_count() as u64,
1032 );
1033 return Ok(());
1034 }
1035 Err(mpsc::error::TrySendError::Closed(_)) => return Err(FipsEndpointError::Closed),
1036 }
1037 }
1038
1039 command_tx
1040 .send(command)
1041 .await
1042 .map_err(|_| FipsEndpointError::Closed)
1043}