1use crate::config::{EthernetConfig, NostrDiscoveryPolicy, TransportInstances, UdpConfig};
7#[cfg(test)]
8use crate::node::ENDPOINT_EVENT_PRIORITY_MAX_LEN;
9use crate::node::{
10 EndpointCommandLane, EndpointDataPayload, EndpointEventSender, EndpointPayloadClass,
11 NodeEndpointCommand, NodeEndpointEvent,
12};
13use crate::{
14 Config, FipsAddress, IdentityConfig, Node, NodeAddr, NodeDeliveredPacket, NodeError,
15 PeerIdentity,
16};
17use std::sync::Arc;
18use thiserror::Error;
19use tokio::sync::{Mutex, mpsc, oneshot};
20use tokio::task::JoinHandle;
21
22const ENDPOINT_SEND_BATCH_COMMAND_MAX: usize = 64;
23const ENDPOINT_RECV_BATCH_MAX: usize = 128;
24
25mod builder;
26mod receive;
27mod status;
28
29#[cfg(test)]
30mod tests;
31
32pub use builder::FipsEndpointBuilder;
33use receive::EndpointReceiveState;
34pub use status::{FipsEndpointPeer, FipsEndpointRelayStatus};
35
36#[derive(Clone, Debug, PartialEq, Eq)]
43pub struct FipsEndpointPayload {
44 bytes: Vec<u8>,
45 class: EndpointPayloadClass,
46}
47
48impl FipsEndpointPayload {
49 pub fn new(bytes: Vec<u8>) -> Self {
50 let class = crate::node::classify_endpoint_payload(&bytes);
51 Self { bytes, class }
52 }
53
54 pub fn from_classified(bytes: Vec<u8>, class: EndpointPayloadClass) -> Self {
55 Self { bytes, class }
56 }
57
58 pub fn class(&self) -> EndpointPayloadClass {
59 self.class
60 }
61
62 pub fn as_slice(&self) -> &[u8] {
63 &self.bytes
64 }
65
66 pub fn len(&self) -> usize {
67 self.bytes.len()
68 }
69
70 pub fn is_empty(&self) -> bool {
71 self.bytes.is_empty()
72 }
73
74 pub fn into_bytes(self) -> Vec<u8> {
75 self.bytes
76 }
77}
78
79impl From<FipsEndpointPayload> for EndpointDataPayload {
80 fn from(payload: FipsEndpointPayload) -> Self {
81 EndpointDataPayload::from_classified(payload.bytes, payload.class)
82 }
83}
84
85#[derive(Debug)]
86enum EndpointPayloadLaneBatches {
87 Empty,
88 Single {
89 lane: EndpointCommandLane,
90 payloads: Vec<EndpointDataPayload>,
91 },
92 Split {
93 priority_payloads: Vec<EndpointDataPayload>,
94 bulk_payloads: Vec<EndpointDataPayload>,
95 },
96}
97
98#[cfg(debug_assertions)]
99fn endpoint_debug_log(message: impl AsRef<str>) {
100 use std::io::Write as _;
101
102 if let Ok(mut file) = std::fs::OpenOptions::new()
103 .create(true)
104 .append(true)
105 .open(std::env::temp_dir().join("nvpn-fips-endpoint-debug.log"))
106 {
107 let _ = writeln!(
108 file,
109 "{:?} {}",
110 std::time::SystemTime::now(),
111 message.as_ref()
112 );
113 }
114}
115
116#[cfg(not(debug_assertions))]
117fn endpoint_debug_log(_message: impl AsRef<str>) {}
118
119#[derive(Debug, Error)]
121pub enum FipsEndpointError {
122 #[error("node error: {0}")]
123 Node(#[from] NodeError),
124
125 #[error("endpoint task failed: {0}")]
126 TaskJoin(#[from] tokio::task::JoinError),
127
128 #[error("endpoint is closed")]
129 Closed,
130
131 #[error("invalid remote npub '{npub}': {reason}")]
132 InvalidRemoteNpub { npub: String, reason: String },
133}
134
135#[derive(Debug, Clone, PartialEq, Eq)]
137pub struct FipsEndpointMessage {
138 pub source_peer: PeerIdentity,
140 pub data: Vec<u8>,
142}
143
144impl FipsEndpointMessage {
145 pub fn source_node_addr(&self) -> &NodeAddr {
147 self.source_peer.node_addr()
148 }
149
150 pub fn source_npub(&self) -> String {
152 self.source_peer.npub()
153 }
154}
155
156#[derive(Debug, Clone, Default, PartialEq, Eq)]
158pub struct UpdatePeersOutcome {
159 pub added: usize,
162 pub removed: usize,
166 pub updated: usize,
171 pub unchanged: usize,
173}
174
175impl From<crate::node::UpdatePeersOutcome> for UpdatePeersOutcome {
176 fn from(value: crate::node::UpdatePeersOutcome) -> Self {
177 Self {
178 added: value.added,
179 removed: value.removed,
180 updated: value.updated,
181 unchanged: value.unchanged,
182 }
183 }
184}
185
186fn apply_default_scoped_discovery(config: &mut Config, scope: &str) {
187 if config.node.discovery.nostr.enabled || !config.transports.is_empty() {
188 return;
189 }
190
191 config.node.discovery.nostr.enabled = true;
192 config.node.discovery.nostr.advertise = true;
193 config.node.discovery.nostr.policy = NostrDiscoveryPolicy::Open;
194 config.node.discovery.nostr.share_local_candidates = true;
195 config.node.discovery.nostr.app = scope.to_string();
196 config.node.discovery.lan.scope = Some(scope.to_string());
197 config.node.discovery.local.enabled = true;
198 config.transports.udp = TransportInstances::Single(UdpConfig {
199 bind_addr: Some("0.0.0.0:0".to_string()),
200 advertise_on_nostr: Some(true),
201 public: Some(false),
202 outbound_only: Some(false),
203 accept_connections: Some(true),
204 ..UdpConfig::default()
205 });
206}
207
208fn endpoint_ethernet_config(interface: &str, scope: Option<&str>) -> EthernetConfig {
209 EthernetConfig {
210 interface: interface.to_string(),
211 discovery: Some(true),
212 announce: Some(true),
213 auto_connect: Some(true),
214 accept_connections: Some(true),
215 discovery_scope: scope
216 .map(str::trim)
217 .filter(|s| !s.is_empty())
218 .map(str::to_string),
219 ..EthernetConfig::default()
220 }
221}
222
223fn add_endpoint_ethernet_transport(config: &mut Config, interface: &str, scope: Option<&str>) {
224 let eth = endpoint_ethernet_config(interface, scope);
225 if config.transports.ethernet.is_empty() {
226 config.transports.ethernet = TransportInstances::Single(eth);
227 return;
228 }
229
230 let existing = std::mem::take(&mut config.transports.ethernet);
231 let mut named = match existing {
232 TransportInstances::Single(config) => {
233 let mut map = std::collections::HashMap::new();
234 map.insert("default".to_string(), config);
235 map
236 }
237 TransportInstances::Named(map) => map,
238 };
239
240 let base_name = endpoint_ethernet_instance_name(interface);
241 let mut name = base_name.clone();
242 let mut suffix = 2usize;
243 while named.contains_key(&name) {
244 name = format!("{base_name}-{suffix}");
245 suffix += 1;
246 }
247 named.insert(name, eth);
248 config.transports.ethernet = TransportInstances::Named(named);
249}
250
251fn endpoint_ethernet_instance_name(interface: &str) -> String {
252 let suffix: String = interface
253 .chars()
254 .map(|c| {
255 if c.is_ascii_alphanumeric() {
256 c.to_ascii_lowercase()
257 } else {
258 '-'
259 }
260 })
261 .collect();
262 let suffix = suffix.trim_matches('-');
263 if suffix.is_empty() {
264 "local-ethernet".to_string()
265 } else {
266 format!("local-ethernet-{suffix}")
267 }
268}
269
270fn spawn_node_task(
271 mut node: Node,
272 shutdown_rx: oneshot::Receiver<()>,
273) -> JoinHandle<Result<(), NodeError>> {
274 tokio::spawn(async move {
275 tokio::pin!(shutdown_rx);
276 let loop_result = tokio::select! {
277 result = node.run_rx_loop() => result,
278 _ = &mut shutdown_rx => Ok(()),
279 };
280 let stop_result = if node.state().can_stop() {
281 node.stop().await
282 } else {
283 Ok(())
284 };
285 loop_result?;
286 stop_result
287 })
288}
289
290pub struct FipsEndpoint {
292 identity: PeerIdentity,
293 npub: String,
294 node_addr: NodeAddr,
295 address: FipsAddress,
296 discovery_scope: Option<String>,
297 outbound_packets: mpsc::Sender<Vec<u8>>,
298 delivered_packets: Arc<Mutex<mpsc::Receiver<NodeDeliveredPacket>>>,
299 endpoint_priority_commands: mpsc::Sender<NodeEndpointCommand>,
300 endpoint_commands: mpsc::Sender<NodeEndpointCommand>,
301 inbound_endpoint_tx: EndpointEventSender,
307 inbound_endpoint_rx: Arc<Mutex<EndpointReceiveState>>,
314 peer_identity_cache: std::sync::Mutex<std::collections::HashMap<String, PeerIdentity>>,
319 shutdown_tx: Option<oneshot::Sender<()>>,
320 task: JoinHandle<Result<(), NodeError>>,
321}
322
323impl FipsEndpoint {
324 pub fn builder() -> FipsEndpointBuilder {
326 FipsEndpointBuilder::default()
327 }
328
329 pub fn npub(&self) -> &str {
331 &self.npub
332 }
333
334 pub fn node_addr(&self) -> &NodeAddr {
336 &self.node_addr
337 }
338
339 pub fn address(&self) -> FipsAddress {
341 self.address
342 }
343
344 pub fn discovery_scope(&self) -> Option<&str> {
346 self.discovery_scope.as_deref()
347 }
348
349 pub async fn send(
362 &self,
363 remote_npub: impl Into<String>,
364 data: impl Into<Vec<u8>>,
365 ) -> Result<(), FipsEndpointError> {
366 let remote_npub = remote_npub.into();
367 let data = data.into();
368 if remote_npub == self.npub {
369 return self.send_loopback(data);
370 }
371
372 let remote = self.resolve_peer_identity(&remote_npub)?;
373 self.send_to_peer(remote, data).await
374 }
375
376 pub async fn send_to_peer(
383 &self,
384 remote: PeerIdentity,
385 data: impl Into<Vec<u8>>,
386 ) -> Result<(), FipsEndpointError> {
387 let data = data.into();
388 if *remote.node_addr() == self.node_addr {
389 return self.send_loopback(data);
390 }
391 let command = NodeEndpointCommand::send_oneway(remote, data, crate::perf_profile::stamp());
396 send_endpoint_command(
397 command,
398 &self.endpoint_priority_commands,
399 &self.endpoint_commands,
400 )
401 .await?;
402 Ok(())
403 }
404
405 pub async fn send_batch_to_peer(
411 &self,
412 remote: PeerIdentity,
413 payloads: Vec<Vec<u8>>,
414 ) -> Result<(), FipsEndpointError> {
415 let payloads = payloads.into_iter().map(FipsEndpointPayload::new).collect();
416 self.send_classified_batch_to_peer(remote, payloads).await
417 }
418
419 pub async fn send_classified_batch_to_peer(
421 &self,
422 remote: PeerIdentity,
423 payloads: Vec<FipsEndpointPayload>,
424 ) -> Result<(), FipsEndpointError> {
425 if *remote.node_addr() == self.node_addr {
426 for payload in payloads {
427 self.send_loopback(payload.into_bytes())?;
428 }
429 return Ok(());
430 }
431
432 let queued_at = crate::perf_profile::stamp();
433 match endpoint_payload_lane_batches(payloads) {
434 EndpointPayloadLaneBatches::Empty => {}
435 EndpointPayloadLaneBatches::Single { lane, payloads } => {
436 self.send_endpoint_command_batch(remote, payloads, queued_at, lane)
437 .await?;
438 }
439 EndpointPayloadLaneBatches::Split {
440 priority_payloads,
441 bulk_payloads,
442 } => {
443 self.send_endpoint_command_batch(
444 remote,
445 priority_payloads,
446 queued_at,
447 EndpointCommandLane::Priority,
448 )
449 .await?;
450 self.send_endpoint_command_batch(
451 remote,
452 bulk_payloads,
453 queued_at,
454 EndpointCommandLane::Bulk,
455 )
456 .await?;
457 }
458 }
459 Ok(())
460 }
461
462 async fn send_endpoint_command_batch(
463 &self,
464 remote: PeerIdentity,
465 mut payloads: Vec<EndpointDataPayload>,
466 queued_at: Option<crate::perf_profile::TraceStamp>,
467 lane: EndpointCommandLane,
468 ) -> Result<(), FipsEndpointError> {
469 while !payloads.is_empty() {
470 let tail = if payloads.len() > ENDPOINT_SEND_BATCH_COMMAND_MAX {
471 payloads.split_off(ENDPOINT_SEND_BATCH_COMMAND_MAX)
472 } else {
473 Vec::new()
474 };
475 let batch = std::mem::replace(&mut payloads, tail);
476 let Some(command) =
477 NodeEndpointCommand::send_batch_oneway(remote, batch, queued_at, lane)
478 else {
479 continue;
480 };
481 send_endpoint_command(
482 command,
483 &self.endpoint_priority_commands,
484 &self.endpoint_commands,
485 )
486 .await?;
487 }
488 Ok(())
489 }
490
491 fn resolve_peer_identity(&self, remote_npub: &str) -> Result<PeerIdentity, FipsEndpointError> {
492 if let Ok(cache) = self.peer_identity_cache.lock()
495 && let Some(remote) = cache.get(remote_npub)
496 {
497 return Ok(*remote);
498 }
499
500 let remote = PeerIdentity::from_npub(remote_npub).map_err(|error| {
501 FipsEndpointError::InvalidRemoteNpub {
502 npub: remote_npub.to_string(),
503 reason: error.to_string(),
504 }
505 })?;
506
507 if let Ok(mut cache) = self.peer_identity_cache.lock() {
508 cache.entry(remote_npub.to_string()).or_insert(remote);
509 }
510 Ok(remote)
511 }
512
513 fn send_loopback(&self, data: Vec<u8>) -> Result<(), FipsEndpointError> {
514 self.inbound_endpoint_tx
515 .send(NodeEndpointEvent::Data {
516 source_peer: self.identity,
517 payload: data,
518 queued_at: crate::perf_profile::stamp(),
519 })
520 .map_err(|_| FipsEndpointError::Closed)
521 }
522
523 pub async fn recv(&self) -> Option<FipsEndpointMessage> {
530 let mut state = self.inbound_endpoint_rx.lock().await;
531 if let Some(message) = state.pop_pending_priority() {
532 return Some(message);
533 }
534 if let Ok(event) = state.rx.try_recv_priority() {
535 return state.first_from_event(event);
536 }
537 if let Some(message) = state.pop_pending_bulk() {
538 return Some(message);
539 }
540 let event = state.rx.recv().await?;
541 state.first_from_event(event)
542 }
543
544 pub async fn recv_batch(&self, max: usize) -> Option<Vec<FipsEndpointMessage>> {
551 let max = max.clamp(1, ENDPOINT_RECV_BATCH_MAX);
552 let mut messages = Vec::with_capacity(max);
553 self.recv_batch_into(&mut messages, max).await?;
554 Some(messages)
555 }
556
557 pub async fn recv_batch_into(
563 &self,
564 messages: &mut Vec<FipsEndpointMessage>,
565 max: usize,
566 ) -> Option<usize> {
567 let max = max.clamp(1, ENDPOINT_RECV_BATCH_MAX);
568 messages.clear();
569
570 let mut state = self.inbound_endpoint_rx.lock().await;
571 state.drain_priority_pending_into(messages, max);
572 while messages.len() < max {
573 match state.rx.try_recv_priority() {
574 Ok(event) => state.push_event_into(event, messages, max),
575 Err(_) => break,
576 }
577 }
578 state.drain_bulk_pending_into(messages, max);
579
580 while messages.len() < max {
581 let event = if messages.is_empty() {
582 state.rx.recv().await?
583 } else {
584 match state.rx.try_recv() {
585 Ok(event) => event,
586 Err(_) => break,
587 }
588 };
589 state.push_event_into(event, messages, max);
590 }
591
592 Some(messages.len())
593 }
594
595 pub fn blocking_send(
605 &self,
606 remote_npub: impl Into<String>,
607 data: impl Into<Vec<u8>>,
608 ) -> Result<(), FipsEndpointError> {
609 let remote_npub = remote_npub.into();
610 let data = data.into();
611 if remote_npub == self.npub {
612 return self.send_loopback(data);
613 }
614 let remote = self.resolve_peer_identity(&remote_npub)?;
615 self.blocking_send_to_peer(remote, data)
616 }
617
618 pub fn blocking_send_to_peer(
623 &self,
624 remote: PeerIdentity,
625 data: impl Into<Vec<u8>>,
626 ) -> Result<(), FipsEndpointError> {
627 let data = data.into();
628 if *remote.node_addr() == self.node_addr {
629 return self.send_loopback(data);
630 }
631 let (response_tx, _response_rx) = oneshot::channel();
632 let command =
633 NodeEndpointCommand::send(remote, data, crate::perf_profile::stamp(), response_tx);
634 endpoint_command_tx_for_command(
635 &command,
636 &self.endpoint_priority_commands,
637 &self.endpoint_commands,
638 )
639 .blocking_send(command)
640 .map_err(|_| FipsEndpointError::Closed)?;
641 Ok(())
642 }
643
644 pub fn blocking_recv(&self) -> Option<FipsEndpointMessage> {
660 let mut state = self.inbound_endpoint_rx.blocking_lock();
661 if let Some(message) = state.pop_pending_priority() {
662 return Some(message);
663 }
664 if let Ok(event) = state.rx.try_recv_priority() {
665 return state.first_from_event(event);
666 }
667 if let Some(message) = state.pop_pending_bulk() {
668 return Some(message);
669 }
670 let event = state.rx.blocking_recv()?;
671 state.first_from_event(event)
672 }
673
674 pub fn blocking_recv_batch_into(
682 &self,
683 messages: &mut Vec<FipsEndpointMessage>,
684 max: usize,
685 ) -> Option<usize> {
686 messages.clear();
687 self.blocking_recv_batch_for_each(max, |message| {
688 messages.push(message);
689 true
690 })
691 }
692
693 pub fn blocking_recv_batch_for_each(
704 &self,
705 max: usize,
706 mut handle_message: impl FnMut(FipsEndpointMessage) -> bool,
707 ) -> Option<usize> {
708 let max = max.clamp(1, ENDPOINT_RECV_BATCH_MAX);
709 let mut drained = 0usize;
710
711 let mut state = self.inbound_endpoint_rx.blocking_lock();
712 if !state.drain_priority_pending_for_each(&mut drained, max, &mut handle_message) {
713 return Some(drained);
714 }
715 while drained < max {
716 match state.rx.try_recv_priority() {
717 Ok(event) => {
718 if !state.push_event_for_each(event, &mut drained, max, &mut handle_message) {
719 return Some(drained);
720 }
721 }
722 Err(_) => break,
723 }
724 }
725 if !state.drain_bulk_pending_for_each(&mut drained, max, &mut handle_message) {
726 return Some(drained);
727 }
728
729 while drained < max {
730 let event = if drained == 0 {
731 state.rx.blocking_recv()?
732 } else {
733 match state.rx.try_recv() {
734 Ok(event) => event,
735 Err(_) => break,
736 }
737 };
738 if !state.push_event_for_each(event, &mut drained, max, &mut handle_message) {
739 return Some(drained);
740 }
741 }
742
743 Some(drained)
744 }
745
746 pub fn try_recv(&self) -> Option<FipsEndpointMessage> {
766 let mut state = self.inbound_endpoint_rx.try_lock().ok()?;
767 if let Some(message) = state.pop_pending_priority() {
768 return Some(message);
769 }
770 if let Ok(event) = state.rx.try_recv_priority() {
771 return state.first_from_event(event);
772 }
773 if let Some(message) = state.pop_pending_bulk() {
774 return Some(message);
775 }
776 let event = state.rx.try_recv().ok()?;
777 state.first_from_event(event)
778 }
779
780 pub async fn update_peers(
790 &self,
791 peers: Vec<crate::config::PeerConfig>,
792 ) -> Result<UpdatePeersOutcome, FipsEndpointError> {
793 let (response_tx, response_rx) = oneshot::channel();
794 self.endpoint_priority_commands
795 .send(NodeEndpointCommand::UpdatePeers { peers, response_tx })
796 .await
797 .map_err(|_| FipsEndpointError::Closed)?;
798
799 match response_rx.await.map_err(|_| FipsEndpointError::Closed)? {
800 Ok(outcome) => Ok(UpdatePeersOutcome::from(outcome)),
801 Err(error) => Err(FipsEndpointError::Node(error)),
802 }
803 }
804
805 pub async fn peers(&self) -> Result<Vec<FipsEndpointPeer>, FipsEndpointError> {
807 let (response_tx, response_rx) = oneshot::channel();
808 self.endpoint_priority_commands
809 .send(NodeEndpointCommand::PeerSnapshot { response_tx })
810 .await
811 .map_err(|_| FipsEndpointError::Closed)?;
812
813 response_rx
814 .await
815 .map(|peers| peers.into_iter().map(FipsEndpointPeer::from).collect())
816 .map_err(|_| FipsEndpointError::Closed)
817 }
818
819 pub async fn relay_statuses(&self) -> Result<Vec<FipsEndpointRelayStatus>, FipsEndpointError> {
821 let (response_tx, response_rx) = oneshot::channel();
822 self.endpoint_priority_commands
823 .send(NodeEndpointCommand::RelaySnapshot { response_tx })
824 .await
825 .map_err(|_| FipsEndpointError::Closed)?;
826
827 response_rx
828 .await
829 .map(|relays| {
830 relays
831 .into_iter()
832 .map(FipsEndpointRelayStatus::from)
833 .collect()
834 })
835 .map_err(|_| FipsEndpointError::Closed)
836 }
837
838 pub async fn update_relays(
840 &self,
841 advert_relays: Vec<String>,
842 dm_relays: Vec<String>,
843 ) -> Result<(), FipsEndpointError> {
844 let (response_tx, response_rx) = oneshot::channel();
845 self.endpoint_priority_commands
846 .send(NodeEndpointCommand::UpdateRelays {
847 advert_relays,
848 dm_relays,
849 response_tx,
850 })
851 .await
852 .map_err(|_| FipsEndpointError::Closed)?;
853
854 response_rx
855 .await
856 .map_err(|_| FipsEndpointError::Closed)?
857 .map_err(FipsEndpointError::Node)
858 }
859
860 pub async fn send_ip_packet(
862 &self,
863 packet: impl Into<Vec<u8>>,
864 ) -> Result<(), FipsEndpointError> {
865 self.outbound_packets
866 .send(packet.into())
867 .await
868 .map_err(|_| FipsEndpointError::Closed)
869 }
870
871 pub async fn recv_ip_packet(&self) -> Option<NodeDeliveredPacket> {
873 self.delivered_packets.lock().await.recv().await
874 }
875
876 pub async fn shutdown(mut self) -> Result<(), FipsEndpointError> {
878 if let Some(shutdown_tx) = self.shutdown_tx.take() {
879 let _ = shutdown_tx.send(());
880 }
881 self.task.await??;
882 Ok(())
883 }
884}
885
886fn endpoint_command_tx_for_command<'a>(
887 command: &NodeEndpointCommand,
888 priority_tx: &'a mpsc::Sender<NodeEndpointCommand>,
889 bulk_tx: &'a mpsc::Sender<NodeEndpointCommand>,
890) -> &'a mpsc::Sender<NodeEndpointCommand> {
891 match command.lane() {
892 EndpointCommandLane::Priority => priority_tx,
893 EndpointCommandLane::Bulk => bulk_tx,
894 }
895}
896
897fn endpoint_payload_lane_batches(payloads: Vec<FipsEndpointPayload>) -> EndpointPayloadLaneBatches {
898 let payload_count = payloads.len();
899 let mut raw_payloads = payloads.into_iter();
900 let Some(first) = raw_payloads.next() else {
901 return EndpointPayloadLaneBatches::Empty;
902 };
903
904 let first = EndpointDataPayload::from(first);
905 let mut first_lane_payloads = Vec::with_capacity(payload_count);
906 let first_lane = first.lane();
907 first_lane_payloads.push(first);
908 let mut batches = EndpointPayloadLaneBatches::Single {
909 lane: first_lane,
910 payloads: first_lane_payloads,
911 };
912
913 for payload in raw_payloads.map(EndpointDataPayload::from) {
914 let payload_lane = payload.lane();
915 match &mut batches {
916 EndpointPayloadLaneBatches::Empty => unreachable!("first payload exists"),
917 EndpointPayloadLaneBatches::Single { lane, payloads } if payload_lane == *lane => {
918 payloads.push(payload);
919 }
920 EndpointPayloadLaneBatches::Single { lane, payloads } => {
921 let first_lane_payloads = std::mem::take(payloads);
922 let mut priority_payloads = Vec::new();
923 let mut bulk_payloads = Vec::new();
924 match *lane {
925 EndpointCommandLane::Priority => priority_payloads = first_lane_payloads,
926 EndpointCommandLane::Bulk => bulk_payloads = first_lane_payloads,
927 }
928 match payload_lane {
929 EndpointCommandLane::Priority => priority_payloads.push(payload),
930 EndpointCommandLane::Bulk => bulk_payloads.push(payload),
931 }
932 batches = EndpointPayloadLaneBatches::Split {
933 priority_payloads,
934 bulk_payloads,
935 };
936 }
937 EndpointPayloadLaneBatches::Split {
938 priority_payloads,
939 bulk_payloads,
940 } => match payload_lane {
941 EndpointCommandLane::Priority => priority_payloads.push(payload),
942 EndpointCommandLane::Bulk => bulk_payloads.push(payload),
943 },
944 }
945 }
946
947 batches
948}
949
950async fn send_endpoint_command(
951 command: NodeEndpointCommand,
952 priority_tx: &mpsc::Sender<NodeEndpointCommand>,
953 bulk_tx: &mpsc::Sender<NodeEndpointCommand>,
954) -> Result<(), FipsEndpointError> {
955 let command_tx = endpoint_command_tx_for_command(&command, priority_tx, bulk_tx);
956
957 if command.drop_on_backpressure() {
958 match command_tx.try_send(command) {
959 Ok(()) => return Ok(()),
960 Err(mpsc::error::TrySendError::Full(command)) => {
961 crate::perf_profile::record_event_count(
962 crate::perf_profile::Event::EndpointCommandBulkDropped,
963 command.drain_cost() as u64,
964 );
965 return Ok(());
966 }
967 Err(mpsc::error::TrySendError::Closed(_)) => return Err(FipsEndpointError::Closed),
968 }
969 }
970
971 command_tx
972 .send(command)
973 .await
974 .map_err(|_| FipsEndpointError::Closed)
975}