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 relay_statuses(&self) -> Result<Vec<FipsEndpointRelayStatus>, FipsEndpointError> {
848 let (response_tx, response_rx) = oneshot::channel();
849 self.endpoint_priority_commands
850 .send(NodeEndpointCommand::RelaySnapshot { response_tx })
851 .await
852 .map_err(|_| FipsEndpointError::Closed)?;
853
854 response_rx
855 .await
856 .map(|relays| {
857 relays
858 .into_iter()
859 .map(FipsEndpointRelayStatus::from)
860 .collect()
861 })
862 .map_err(|_| FipsEndpointError::Closed)
863 }
864
865 pub async fn update_relays(
867 &self,
868 advert_relays: Vec<String>,
869 dm_relays: Vec<String>,
870 ) -> Result<(), FipsEndpointError> {
871 let (response_tx, response_rx) = oneshot::channel();
872 self.endpoint_priority_commands
873 .send(NodeEndpointCommand::UpdateRelays {
874 advert_relays,
875 dm_relays,
876 response_tx,
877 })
878 .await
879 .map_err(|_| FipsEndpointError::Closed)?;
880
881 response_rx
882 .await
883 .map_err(|_| FipsEndpointError::Closed)?
884 .map_err(FipsEndpointError::Node)
885 }
886
887 pub async fn send_ip_packet(
889 &self,
890 packet: impl Into<Vec<u8>>,
891 ) -> Result<(), FipsEndpointError> {
892 self.outbound_packets
893 .send(packet.into())
894 .await
895 .map_err(|_| FipsEndpointError::Closed)
896 }
897
898 pub async fn recv_ip_packet(&self) -> Option<NodeDeliveredPacket> {
900 self.delivered_packets.lock().await.recv().await
901 }
902
903 pub async fn shutdown(&self) -> Result<(), FipsEndpointError> {
905 let shutdown_tx = self
906 .shutdown_tx
907 .lock()
908 .map_err(|_| FipsEndpointError::Closed)?
909 .take();
910 if let Some(shutdown_tx) = shutdown_tx {
911 let _ = shutdown_tx.send(());
912 }
913 let task = self
914 .task
915 .lock()
916 .map_err(|_| FipsEndpointError::Closed)?
917 .take();
918 if let Some(task) = task {
919 task.await??;
920 }
921 Ok(())
922 }
923}
924
925impl Drop for FipsEndpoint {
926 fn drop(&mut self) {
927 if let Ok(mut shutdown_tx) = self.shutdown_tx.lock()
928 && let Some(shutdown_tx) = shutdown_tx.take()
929 {
930 let _ = shutdown_tx.send(());
931 }
932 if let Ok(mut task) = self.task.lock()
933 && let Some(task) = task.take()
934 {
935 task.abort();
936 }
937 }
938}
939
940fn endpoint_command_tx_for_command<'a>(
941 command: &NodeEndpointCommand,
942 priority_tx: &'a mpsc::Sender<NodeEndpointCommand>,
943 bulk_tx: &'a mpsc::Sender<NodeEndpointCommand>,
944) -> &'a mpsc::Sender<NodeEndpointCommand> {
945 match command.lane() {
946 EndpointCommandLane::Priority => priority_tx,
947 EndpointCommandLane::Bulk => bulk_tx,
948 }
949}
950
951fn endpoint_payload_lane_batches(payloads: Vec<FipsEndpointPayload>) -> EndpointPayloadLaneBatches {
952 let payload_count = payloads.len();
953 let mut raw_payloads = payloads.into_iter();
954 let Some(first) = raw_payloads.next() else {
955 return EndpointPayloadLaneBatches::Empty;
956 };
957
958 let first = EndpointDataPayload::from(first);
959 let mut first_lane_payloads = Vec::with_capacity(payload_count);
960 let first_lane = first.lane();
961 first_lane_payloads.push(first);
962 let mut batches = EndpointPayloadLaneBatches::Single {
963 lane: first_lane,
964 payloads: first_lane_payloads,
965 };
966
967 for payload in raw_payloads.map(EndpointDataPayload::from) {
968 let payload_lane = payload.lane();
969 match &mut batches {
970 EndpointPayloadLaneBatches::Empty => unreachable!("first payload exists"),
971 EndpointPayloadLaneBatches::Single { lane, payloads } if payload_lane == *lane => {
972 payloads.push(payload);
973 }
974 EndpointPayloadLaneBatches::Single { lane, payloads } => {
975 let first_lane_payloads = std::mem::take(payloads);
976 let mut priority_payloads = Vec::new();
977 let mut bulk_payloads = Vec::new();
978 match *lane {
979 EndpointCommandLane::Priority => priority_payloads = first_lane_payloads,
980 EndpointCommandLane::Bulk => bulk_payloads = first_lane_payloads,
981 }
982 match payload_lane {
983 EndpointCommandLane::Priority => priority_payloads.push(payload),
984 EndpointCommandLane::Bulk => bulk_payloads.push(payload),
985 }
986 batches = EndpointPayloadLaneBatches::Split {
987 priority_payloads,
988 bulk_payloads,
989 };
990 }
991 EndpointPayloadLaneBatches::Split {
992 priority_payloads,
993 bulk_payloads,
994 } => match payload_lane {
995 EndpointCommandLane::Priority => priority_payloads.push(payload),
996 EndpointCommandLane::Bulk => bulk_payloads.push(payload),
997 },
998 }
999 }
1000
1001 batches
1002}
1003
1004async fn send_endpoint_command(
1005 command: NodeEndpointCommand,
1006 priority_tx: &mpsc::Sender<NodeEndpointCommand>,
1007 bulk_tx: &mpsc::Sender<NodeEndpointCommand>,
1008) -> Result<(), FipsEndpointError> {
1009 let command_tx = endpoint_command_tx_for_command(&command, priority_tx, bulk_tx);
1010
1011 if command.drop_on_backpressure() {
1012 match command_tx.try_send(command) {
1013 Ok(()) => return Ok(()),
1014 Err(mpsc::error::TrySendError::Full(command)) => {
1015 crate::perf_profile::record_event_count(
1016 crate::perf_profile::Event::EndpointCommandBulkDropped,
1017 command.packet_count() as u64,
1018 );
1019 return Ok(());
1020 }
1021 Err(mpsc::error::TrySendError::Closed(_)) => return Err(FipsEndpointError::Closed),
1022 }
1023 }
1024
1025 command_tx
1026 .send(command)
1027 .await
1028 .map_err(|_| FipsEndpointError::Closed)
1029}