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
22#[cfg(target_os = "linux")]
27pub(crate) const ENDPOINT_SEND_BATCH_COMMAND_MAX: usize = 128;
28#[cfg(target_os = "macos")]
29pub(crate) const ENDPOINT_SEND_BATCH_COMMAND_MAX: usize = 16;
30#[cfg(not(any(target_os = "linux", target_os = "macos")))]
31pub(crate) const ENDPOINT_SEND_BATCH_COMMAND_MAX: usize = 64;
32const ENDPOINT_RECV_BATCH_MAX: usize = 128;
33
34mod builder;
35mod receive;
36mod status;
37
38#[cfg(test)]
39mod tests;
40
41pub use builder::FipsEndpointBuilder;
42use receive::EndpointReceiveState;
43pub use status::{FipsEndpointPeer, FipsEndpointRelayStatus};
44
45#[derive(Clone, Debug, PartialEq, Eq)]
52pub struct FipsEndpointPayload {
53 bytes: Vec<u8>,
54 class: EndpointPayloadClass,
55 direct_fmp_endpoint_allowed: bool,
56}
57
58impl FipsEndpointPayload {
59 pub fn new(bytes: Vec<u8>) -> Self {
60 let class = crate::node::classify_endpoint_payload(&bytes);
61 Self {
62 bytes,
63 class,
64 direct_fmp_endpoint_allowed: false,
65 }
66 }
67
68 pub fn from_classified(bytes: Vec<u8>, class: EndpointPayloadClass) -> Self {
69 Self {
70 bytes,
71 class,
72 direct_fmp_endpoint_allowed: false,
73 }
74 }
75
76 pub fn with_direct_fmp_endpoint_allowed(mut self) -> Self {
81 self.direct_fmp_endpoint_allowed = true;
82 self
83 }
84
85 pub fn class(&self) -> EndpointPayloadClass {
86 self.class
87 }
88
89 pub fn as_slice(&self) -> &[u8] {
90 &self.bytes
91 }
92
93 pub fn len(&self) -> usize {
94 self.bytes.len()
95 }
96
97 pub fn is_empty(&self) -> bool {
98 self.bytes.is_empty()
99 }
100
101 pub fn into_bytes(self) -> Vec<u8> {
102 self.bytes
103 }
104}
105
106impl From<FipsEndpointPayload> for EndpointDataPayload {
107 fn from(payload: FipsEndpointPayload) -> Self {
108 EndpointDataPayload::from_classified_with_direct_fmp_endpoint_allowed(
109 payload.bytes,
110 payload.class,
111 payload.direct_fmp_endpoint_allowed,
112 )
113 }
114}
115
116#[derive(Debug)]
117enum EndpointPayloadLaneBatches {
118 Empty,
119 Single {
120 lane: EndpointCommandLane,
121 payloads: Vec<EndpointDataPayload>,
122 },
123 Split {
124 priority_payloads: Vec<EndpointDataPayload>,
125 bulk_payloads: Vec<EndpointDataPayload>,
126 },
127}
128
129#[cfg(debug_assertions)]
130fn endpoint_debug_log(message: impl AsRef<str>) {
131 use std::io::Write as _;
132
133 if let Ok(mut file) = std::fs::OpenOptions::new()
134 .create(true)
135 .append(true)
136 .open(std::env::temp_dir().join("nvpn-fips-endpoint-debug.log"))
137 {
138 let _ = writeln!(
139 file,
140 "{:?} {}",
141 std::time::SystemTime::now(),
142 message.as_ref()
143 );
144 }
145}
146
147#[cfg(not(debug_assertions))]
148fn endpoint_debug_log(_message: impl AsRef<str>) {}
149
150#[derive(Debug, Error)]
152pub enum FipsEndpointError {
153 #[error("node error: {0}")]
154 Node(#[from] NodeError),
155
156 #[error("endpoint task failed: {0}")]
157 TaskJoin(#[from] tokio::task::JoinError),
158
159 #[error("endpoint is closed")]
160 Closed,
161
162 #[error("invalid remote npub '{npub}': {reason}")]
163 InvalidRemoteNpub { npub: String, reason: String },
164}
165
166#[derive(Debug, Clone, PartialEq, Eq)]
168pub struct FipsEndpointMessage {
169 pub source_peer: PeerIdentity,
171 pub data: Vec<u8>,
173}
174
175impl FipsEndpointMessage {
176 pub fn source_node_addr(&self) -> &NodeAddr {
178 self.source_peer.node_addr()
179 }
180
181 pub fn source_npub(&self) -> String {
183 self.source_peer.npub()
184 }
185}
186
187#[derive(Debug, Clone, Default, PartialEq, Eq)]
189pub struct UpdatePeersOutcome {
190 pub added: usize,
193 pub removed: usize,
197 pub updated: usize,
202 pub unchanged: usize,
204}
205
206impl From<crate::node::UpdatePeersOutcome> for UpdatePeersOutcome {
207 fn from(value: crate::node::UpdatePeersOutcome) -> Self {
208 Self {
209 added: value.added,
210 removed: value.removed,
211 updated: value.updated,
212 unchanged: value.unchanged,
213 }
214 }
215}
216
217fn apply_default_scoped_discovery(config: &mut Config, scope: &str) {
218 if config.node.discovery.nostr.enabled || !config.transports.is_empty() {
219 return;
220 }
221
222 config.node.discovery.nostr.enabled = true;
223 config.node.discovery.nostr.advertise = true;
224 config.node.discovery.nostr.policy = NostrDiscoveryPolicy::Open;
225 config.node.discovery.nostr.share_local_candidates = true;
226 config.node.discovery.nostr.app = scope.to_string();
227 config.node.discovery.lan.scope = Some(scope.to_string());
228 config.node.discovery.local.enabled = true;
229 config.transports.udp = TransportInstances::Single(UdpConfig {
230 bind_addr: Some("0.0.0.0:0".to_string()),
231 advertise_on_nostr: Some(true),
232 public: Some(false),
233 outbound_only: Some(false),
234 accept_connections: Some(true),
235 ..UdpConfig::default()
236 });
237}
238
239fn endpoint_ethernet_config(interface: &str, scope: Option<&str>) -> EthernetConfig {
240 EthernetConfig {
241 interface: interface.to_string(),
242 discovery: Some(true),
243 announce: Some(true),
244 auto_connect: Some(true),
245 accept_connections: Some(true),
246 discovery_scope: scope
247 .map(str::trim)
248 .filter(|s| !s.is_empty())
249 .map(str::to_string),
250 ..EthernetConfig::default()
251 }
252}
253
254fn add_endpoint_ethernet_transport(config: &mut Config, interface: &str, scope: Option<&str>) {
255 let eth = endpoint_ethernet_config(interface, scope);
256 if config.transports.ethernet.is_empty() {
257 config.transports.ethernet = TransportInstances::Single(eth);
258 return;
259 }
260
261 let existing = std::mem::take(&mut config.transports.ethernet);
262 let mut named = match existing {
263 TransportInstances::Single(config) => {
264 let mut map = std::collections::HashMap::new();
265 map.insert("default".to_string(), config);
266 map
267 }
268 TransportInstances::Named(map) => map,
269 };
270
271 let base_name = endpoint_ethernet_instance_name(interface);
272 let mut name = base_name.clone();
273 let mut suffix = 2usize;
274 while named.contains_key(&name) {
275 name = format!("{base_name}-{suffix}");
276 suffix += 1;
277 }
278 named.insert(name, eth);
279 config.transports.ethernet = TransportInstances::Named(named);
280}
281
282fn endpoint_ethernet_instance_name(interface: &str) -> String {
283 let suffix: String = interface
284 .chars()
285 .map(|c| {
286 if c.is_ascii_alphanumeric() {
287 c.to_ascii_lowercase()
288 } else {
289 '-'
290 }
291 })
292 .collect();
293 let suffix = suffix.trim_matches('-');
294 if suffix.is_empty() {
295 "local-ethernet".to_string()
296 } else {
297 format!("local-ethernet-{suffix}")
298 }
299}
300
301fn spawn_node_task(
302 mut node: Node,
303 shutdown_rx: oneshot::Receiver<()>,
304) -> JoinHandle<Result<(), NodeError>> {
305 tokio::spawn(async move {
306 tokio::pin!(shutdown_rx);
307 let loop_result = tokio::select! {
308 result = node.run_rx_loop() => result,
309 _ = &mut shutdown_rx => Ok(()),
310 };
311 let stop_result = if node.state().can_stop() {
312 node.stop().await
313 } else {
314 Ok(())
315 };
316 loop_result?;
317 stop_result
318 })
319}
320
321pub struct FipsEndpoint {
323 identity: PeerIdentity,
324 npub: String,
325 node_addr: NodeAddr,
326 address: FipsAddress,
327 discovery_scope: Option<String>,
328 outbound_packets: mpsc::Sender<Vec<u8>>,
329 delivered_packets: Arc<Mutex<mpsc::Receiver<NodeDeliveredPacket>>>,
330 endpoint_priority_commands: mpsc::Sender<NodeEndpointCommand>,
331 endpoint_commands: mpsc::Sender<NodeEndpointCommand>,
332 inbound_endpoint_tx: EndpointEventSender,
338 inbound_endpoint_rx: Arc<Mutex<EndpointReceiveState>>,
345 peer_identity_cache: std::sync::Mutex<std::collections::HashMap<String, PeerIdentity>>,
350 shutdown_tx: Option<oneshot::Sender<()>>,
351 task: JoinHandle<Result<(), NodeError>>,
352}
353
354impl FipsEndpoint {
355 pub fn builder() -> FipsEndpointBuilder {
357 FipsEndpointBuilder::default()
358 }
359
360 pub fn npub(&self) -> &str {
362 &self.npub
363 }
364
365 pub fn node_addr(&self) -> &NodeAddr {
367 &self.node_addr
368 }
369
370 pub fn address(&self) -> FipsAddress {
372 self.address
373 }
374
375 pub fn discovery_scope(&self) -> Option<&str> {
377 self.discovery_scope.as_deref()
378 }
379
380 pub async fn send(
393 &self,
394 remote_npub: impl Into<String>,
395 data: impl Into<Vec<u8>>,
396 ) -> Result<(), FipsEndpointError> {
397 let remote_npub = remote_npub.into();
398 let data = data.into();
399 if remote_npub == self.npub {
400 return self.send_loopback(data);
401 }
402
403 let remote = self.resolve_peer_identity(&remote_npub)?;
404 self.send_to_peer(remote, data).await
405 }
406
407 pub async fn send_to_peer(
414 &self,
415 remote: PeerIdentity,
416 data: impl Into<Vec<u8>>,
417 ) -> Result<(), FipsEndpointError> {
418 let data = data.into();
419 if *remote.node_addr() == self.node_addr {
420 return self.send_loopback(data);
421 }
422 let command = NodeEndpointCommand::send_oneway(remote, data, crate::perf_profile::stamp());
427 send_endpoint_command(
428 command,
429 &self.endpoint_priority_commands,
430 &self.endpoint_commands,
431 )
432 .await?;
433 Ok(())
434 }
435
436 pub async fn send_batch_to_peer(
442 &self,
443 remote: PeerIdentity,
444 payloads: Vec<Vec<u8>>,
445 ) -> Result<(), FipsEndpointError> {
446 let payloads = payloads.into_iter().map(FipsEndpointPayload::new).collect();
447 self.send_classified_batch_to_peer(remote, payloads).await
448 }
449
450 pub async fn send_classified_batch_to_peer(
452 &self,
453 remote: PeerIdentity,
454 payloads: Vec<FipsEndpointPayload>,
455 ) -> Result<(), FipsEndpointError> {
456 if *remote.node_addr() == self.node_addr {
457 for payload in payloads {
458 self.send_loopback(payload.into_bytes())?;
459 }
460 return Ok(());
461 }
462
463 let queued_at = crate::perf_profile::stamp();
464 match endpoint_payload_lane_batches(payloads) {
465 EndpointPayloadLaneBatches::Empty => {}
466 EndpointPayloadLaneBatches::Single { lane, payloads } => {
467 self.send_endpoint_command_batch(remote, payloads, queued_at, lane)
468 .await?;
469 }
470 EndpointPayloadLaneBatches::Split {
471 priority_payloads,
472 bulk_payloads,
473 } => {
474 self.send_endpoint_command_batch(
475 remote,
476 priority_payloads,
477 queued_at,
478 EndpointCommandLane::Priority,
479 )
480 .await?;
481 self.send_endpoint_command_batch(
482 remote,
483 bulk_payloads,
484 queued_at,
485 EndpointCommandLane::Bulk,
486 )
487 .await?;
488 }
489 }
490 Ok(())
491 }
492
493 async fn send_endpoint_command_batch(
494 &self,
495 remote: PeerIdentity,
496 mut payloads: Vec<EndpointDataPayload>,
497 queued_at: Option<crate::perf_profile::TraceStamp>,
498 lane: EndpointCommandLane,
499 ) -> Result<(), FipsEndpointError> {
500 while !payloads.is_empty() {
501 let tail = if payloads.len() > ENDPOINT_SEND_BATCH_COMMAND_MAX {
502 payloads.split_off(ENDPOINT_SEND_BATCH_COMMAND_MAX)
503 } else {
504 Vec::new()
505 };
506 let batch = std::mem::replace(&mut payloads, tail);
507 let Some(command) =
508 NodeEndpointCommand::send_batch_oneway(remote, batch, queued_at, lane)
509 else {
510 continue;
511 };
512 send_endpoint_command(
513 command,
514 &self.endpoint_priority_commands,
515 &self.endpoint_commands,
516 )
517 .await?;
518 }
519 Ok(())
520 }
521
522 fn resolve_peer_identity(&self, remote_npub: &str) -> Result<PeerIdentity, FipsEndpointError> {
523 if let Ok(cache) = self.peer_identity_cache.lock()
526 && let Some(remote) = cache.get(remote_npub)
527 {
528 return Ok(*remote);
529 }
530
531 let remote = PeerIdentity::from_npub(remote_npub).map_err(|error| {
532 FipsEndpointError::InvalidRemoteNpub {
533 npub: remote_npub.to_string(),
534 reason: error.to_string(),
535 }
536 })?;
537
538 if let Ok(mut cache) = self.peer_identity_cache.lock() {
539 cache.entry(remote_npub.to_string()).or_insert(remote);
540 }
541 Ok(remote)
542 }
543
544 fn send_loopback(&self, data: Vec<u8>) -> Result<(), FipsEndpointError> {
545 self.inbound_endpoint_tx
546 .send(NodeEndpointEvent::Data {
547 source_peer: self.identity,
548 payload: data,
549 queued_at: crate::perf_profile::stamp(),
550 })
551 .map_err(|_| FipsEndpointError::Closed)
552 }
553
554 pub async fn recv(&self) -> Option<FipsEndpointMessage> {
561 let mut state = self.inbound_endpoint_rx.lock().await;
562 if let Some(message) = state.pop_pending_priority() {
563 return Some(message);
564 }
565 if let Ok(event) = state.rx.try_recv_priority() {
566 return state.first_from_event(event);
567 }
568 if let Some(message) = state.pop_pending_bulk() {
569 return Some(message);
570 }
571 let event = state.rx.recv().await?;
572 state.first_from_event(event)
573 }
574
575 pub async fn recv_batch(&self, max: usize) -> Option<Vec<FipsEndpointMessage>> {
582 let max = max.clamp(1, ENDPOINT_RECV_BATCH_MAX);
583 let mut messages = Vec::with_capacity(max);
584 self.recv_batch_into(&mut messages, max).await?;
585 Some(messages)
586 }
587
588 pub async fn recv_batch_into(
594 &self,
595 messages: &mut Vec<FipsEndpointMessage>,
596 max: usize,
597 ) -> Option<usize> {
598 let max = max.clamp(1, ENDPOINT_RECV_BATCH_MAX);
599 messages.clear();
600
601 let mut state = self.inbound_endpoint_rx.lock().await;
602 state.drain_priority_pending_into(messages, max);
603 while messages.len() < max {
604 match state.rx.try_recv_priority() {
605 Ok(event) => state.push_event_into(event, messages, max),
606 Err(_) => break,
607 }
608 }
609 state.drain_bulk_pending_into(messages, max);
610
611 while messages.len() < max {
612 let event = if messages.is_empty() {
613 state.rx.recv().await?
614 } else {
615 match state.rx.try_recv() {
616 Ok(event) => event,
617 Err(_) => break,
618 }
619 };
620 state.push_event_into(event, messages, max);
621 }
622
623 Some(messages.len())
624 }
625
626 pub fn blocking_send(
636 &self,
637 remote_npub: impl Into<String>,
638 data: impl Into<Vec<u8>>,
639 ) -> Result<(), FipsEndpointError> {
640 let remote_npub = remote_npub.into();
641 let data = data.into();
642 if remote_npub == self.npub {
643 return self.send_loopback(data);
644 }
645 let remote = self.resolve_peer_identity(&remote_npub)?;
646 self.blocking_send_to_peer(remote, data)
647 }
648
649 pub fn blocking_send_to_peer(
654 &self,
655 remote: PeerIdentity,
656 data: impl Into<Vec<u8>>,
657 ) -> Result<(), FipsEndpointError> {
658 let data = data.into();
659 if *remote.node_addr() == self.node_addr {
660 return self.send_loopback(data);
661 }
662 let (response_tx, _response_rx) = oneshot::channel();
663 let command =
664 NodeEndpointCommand::send(remote, data, crate::perf_profile::stamp(), response_tx);
665 endpoint_command_tx_for_command(
666 &command,
667 &self.endpoint_priority_commands,
668 &self.endpoint_commands,
669 )
670 .blocking_send(command)
671 .map_err(|_| FipsEndpointError::Closed)?;
672 Ok(())
673 }
674
675 pub fn blocking_recv(&self) -> Option<FipsEndpointMessage> {
691 let mut state = self.inbound_endpoint_rx.blocking_lock();
692 if let Some(message) = state.pop_pending_priority() {
693 return Some(message);
694 }
695 if let Ok(event) = state.rx.try_recv_priority() {
696 return state.first_from_event(event);
697 }
698 if let Some(message) = state.pop_pending_bulk() {
699 return Some(message);
700 }
701 let event = state.rx.blocking_recv()?;
702 state.first_from_event(event)
703 }
704
705 pub fn blocking_recv_batch_into(
713 &self,
714 messages: &mut Vec<FipsEndpointMessage>,
715 max: usize,
716 ) -> Option<usize> {
717 messages.clear();
718 self.blocking_recv_batch_for_each(max, |message| {
719 messages.push(message);
720 true
721 })
722 }
723
724 pub fn blocking_recv_batch_for_each(
735 &self,
736 max: usize,
737 mut handle_message: impl FnMut(FipsEndpointMessage) -> bool,
738 ) -> Option<usize> {
739 let max = max.clamp(1, ENDPOINT_RECV_BATCH_MAX);
740 let mut drained = 0usize;
741
742 let mut state = self.inbound_endpoint_rx.blocking_lock();
743 if !state.drain_priority_pending_for_each(&mut drained, max, &mut handle_message) {
744 return Some(drained);
745 }
746 while drained < max {
747 match state.rx.try_recv_priority() {
748 Ok(event) => {
749 if !state.push_event_for_each(event, &mut drained, max, &mut handle_message) {
750 return Some(drained);
751 }
752 }
753 Err(_) => break,
754 }
755 }
756 if !state.drain_bulk_pending_for_each(&mut drained, max, &mut handle_message) {
757 return Some(drained);
758 }
759
760 while drained < max {
761 let event = if drained == 0 {
762 state.rx.blocking_recv()?
763 } else {
764 match state.rx.try_recv() {
765 Ok(event) => event,
766 Err(_) => break,
767 }
768 };
769 if !state.push_event_for_each(event, &mut drained, max, &mut handle_message) {
770 return Some(drained);
771 }
772 }
773
774 Some(drained)
775 }
776
777 pub fn try_recv(&self) -> Option<FipsEndpointMessage> {
797 let mut state = self.inbound_endpoint_rx.try_lock().ok()?;
798 if let Some(message) = state.pop_pending_priority() {
799 return Some(message);
800 }
801 if let Ok(event) = state.rx.try_recv_priority() {
802 return state.first_from_event(event);
803 }
804 if let Some(message) = state.pop_pending_bulk() {
805 return Some(message);
806 }
807 let event = state.rx.try_recv().ok()?;
808 state.first_from_event(event)
809 }
810
811 pub async fn update_peers(
821 &self,
822 peers: Vec<crate::config::PeerConfig>,
823 ) -> Result<UpdatePeersOutcome, FipsEndpointError> {
824 let (response_tx, response_rx) = oneshot::channel();
825 self.endpoint_priority_commands
826 .send(NodeEndpointCommand::UpdatePeers { peers, response_tx })
827 .await
828 .map_err(|_| FipsEndpointError::Closed)?;
829
830 match response_rx.await.map_err(|_| FipsEndpointError::Closed)? {
831 Ok(outcome) => Ok(UpdatePeersOutcome::from(outcome)),
832 Err(error) => Err(FipsEndpointError::Node(error)),
833 }
834 }
835
836 pub async fn peers(&self) -> Result<Vec<FipsEndpointPeer>, FipsEndpointError> {
838 let (response_tx, response_rx) = oneshot::channel();
839 self.endpoint_priority_commands
840 .send(NodeEndpointCommand::PeerSnapshot { response_tx })
841 .await
842 .map_err(|_| FipsEndpointError::Closed)?;
843
844 response_rx
845 .await
846 .map(|peers| peers.into_iter().map(FipsEndpointPeer::from).collect())
847 .map_err(|_| FipsEndpointError::Closed)
848 }
849
850 pub async fn relay_statuses(&self) -> Result<Vec<FipsEndpointRelayStatus>, FipsEndpointError> {
852 let (response_tx, response_rx) = oneshot::channel();
853 self.endpoint_priority_commands
854 .send(NodeEndpointCommand::RelaySnapshot { response_tx })
855 .await
856 .map_err(|_| FipsEndpointError::Closed)?;
857
858 response_rx
859 .await
860 .map(|relays| {
861 relays
862 .into_iter()
863 .map(FipsEndpointRelayStatus::from)
864 .collect()
865 })
866 .map_err(|_| FipsEndpointError::Closed)
867 }
868
869 pub async fn update_relays(
871 &self,
872 advert_relays: Vec<String>,
873 dm_relays: Vec<String>,
874 ) -> Result<(), FipsEndpointError> {
875 let (response_tx, response_rx) = oneshot::channel();
876 self.endpoint_priority_commands
877 .send(NodeEndpointCommand::UpdateRelays {
878 advert_relays,
879 dm_relays,
880 response_tx,
881 })
882 .await
883 .map_err(|_| FipsEndpointError::Closed)?;
884
885 response_rx
886 .await
887 .map_err(|_| FipsEndpointError::Closed)?
888 .map_err(FipsEndpointError::Node)
889 }
890
891 pub async fn send_ip_packet(
893 &self,
894 packet: impl Into<Vec<u8>>,
895 ) -> Result<(), FipsEndpointError> {
896 self.outbound_packets
897 .send(packet.into())
898 .await
899 .map_err(|_| FipsEndpointError::Closed)
900 }
901
902 pub async fn recv_ip_packet(&self) -> Option<NodeDeliveredPacket> {
904 self.delivered_packets.lock().await.recv().await
905 }
906
907 pub async fn shutdown(mut self) -> Result<(), FipsEndpointError> {
909 if let Some(shutdown_tx) = self.shutdown_tx.take() {
910 let _ = shutdown_tx.send(());
911 }
912 self.task.await??;
913 Ok(())
914 }
915}
916
917fn endpoint_command_tx_for_command<'a>(
918 command: &NodeEndpointCommand,
919 priority_tx: &'a mpsc::Sender<NodeEndpointCommand>,
920 bulk_tx: &'a mpsc::Sender<NodeEndpointCommand>,
921) -> &'a mpsc::Sender<NodeEndpointCommand> {
922 match command.lane() {
923 EndpointCommandLane::Priority => priority_tx,
924 EndpointCommandLane::Bulk => bulk_tx,
925 }
926}
927
928fn endpoint_payload_lane_batches(payloads: Vec<FipsEndpointPayload>) -> EndpointPayloadLaneBatches {
929 let payload_count = payloads.len();
930 let mut raw_payloads = payloads.into_iter();
931 let Some(first) = raw_payloads.next() else {
932 return EndpointPayloadLaneBatches::Empty;
933 };
934
935 let first = EndpointDataPayload::from(first);
936 let mut first_lane_payloads = Vec::with_capacity(payload_count);
937 let first_lane = first.lane();
938 first_lane_payloads.push(first);
939 let mut batches = EndpointPayloadLaneBatches::Single {
940 lane: first_lane,
941 payloads: first_lane_payloads,
942 };
943
944 for payload in raw_payloads.map(EndpointDataPayload::from) {
945 let payload_lane = payload.lane();
946 match &mut batches {
947 EndpointPayloadLaneBatches::Empty => unreachable!("first payload exists"),
948 EndpointPayloadLaneBatches::Single { lane, payloads } if payload_lane == *lane => {
949 payloads.push(payload);
950 }
951 EndpointPayloadLaneBatches::Single { lane, payloads } => {
952 let first_lane_payloads = std::mem::take(payloads);
953 let mut priority_payloads = Vec::new();
954 let mut bulk_payloads = Vec::new();
955 match *lane {
956 EndpointCommandLane::Priority => priority_payloads = first_lane_payloads,
957 EndpointCommandLane::Bulk => bulk_payloads = first_lane_payloads,
958 }
959 match payload_lane {
960 EndpointCommandLane::Priority => priority_payloads.push(payload),
961 EndpointCommandLane::Bulk => bulk_payloads.push(payload),
962 }
963 batches = EndpointPayloadLaneBatches::Split {
964 priority_payloads,
965 bulk_payloads,
966 };
967 }
968 EndpointPayloadLaneBatches::Split {
969 priority_payloads,
970 bulk_payloads,
971 } => match payload_lane {
972 EndpointCommandLane::Priority => priority_payloads.push(payload),
973 EndpointCommandLane::Bulk => bulk_payloads.push(payload),
974 },
975 }
976 }
977
978 batches
979}
980
981async fn send_endpoint_command(
982 mut command: NodeEndpointCommand,
983 priority_tx: &mpsc::Sender<NodeEndpointCommand>,
984 bulk_tx: &mpsc::Sender<NodeEndpointCommand>,
985) -> Result<(), FipsEndpointError> {
986 let command_tx = endpoint_command_tx_for_command(&command, priority_tx, bulk_tx);
987 let enqueue_trace = EndpointCommandEnqueueTrace::new(&command);
988
989 if command.drop_on_backpressure() {
990 command.set_queued_at(crate::perf_profile::stamp());
991 match command_tx.try_send(command) {
992 Ok(()) => {
993 enqueue_trace.record();
994 return Ok(());
995 }
996 Err(mpsc::error::TrySendError::Full(command)) => {
997 crate::perf_profile::record_event_count(
998 crate::perf_profile::Event::EndpointCommandBulkDropped,
999 command.drain_cost() as u64,
1000 );
1001 return Ok(());
1002 }
1003 Err(mpsc::error::TrySendError::Closed(_)) => return Err(FipsEndpointError::Closed),
1004 }
1005 }
1006
1007 let permit = command_tx
1008 .reserve()
1009 .await
1010 .map_err(|_| FipsEndpointError::Closed)?;
1011 enqueue_trace.record();
1012 command.set_queued_at(crate::perf_profile::stamp());
1013 permit.send(command);
1014 Ok(())
1015}
1016
1017#[derive(Copy, Clone)]
1018struct EndpointCommandEnqueueTrace {
1019 started_at: Option<crate::perf_profile::TraceStamp>,
1020 lane: EndpointCommandLane,
1021 count: u64,
1022}
1023
1024impl EndpointCommandEnqueueTrace {
1025 fn new(command: &NodeEndpointCommand) -> Self {
1026 Self {
1027 started_at: crate::perf_profile::stamp(),
1028 lane: command.lane(),
1029 count: command.drain_cost() as u64,
1030 }
1031 }
1032
1033 fn record(self) {
1034 let (priority_count, bulk_count) = match self.lane {
1035 EndpointCommandLane::Priority => (self.count, 0),
1036 EndpointCommandLane::Bulk => (0, self.count),
1037 };
1038 crate::perf_profile::record_since_split_count(
1039 crate::perf_profile::Stage::EndpointCommandEnqueueWait,
1040 crate::perf_profile::Stage::EndpointPriorityCommandEnqueueWait,
1041 crate::perf_profile::Stage::EndpointBulkCommandEnqueueWait,
1042 self.started_at,
1043 self.count,
1044 priority_count,
1045 bulk_count,
1046 );
1047 }
1048}