1mod batch;
34pub mod behavior;
35#[cfg_attr(not(feature = "cortex"), allow(dead_code))]
42mod cancel_registry;
43pub mod channel;
44pub mod compute;
45mod config;
46pub mod contested;
47pub mod continuity;
48#[cfg(feature = "cortex")]
49pub mod cortex;
50mod crypto;
51mod failure;
52pub mod identity;
53mod mesh;
54#[cfg(feature = "dataforts")]
61pub mod dataforts;
62#[cfg(feature = "cortex")]
63pub mod mesh_rpc;
64#[cfg(feature = "cortex")]
65pub mod mesh_rpc_metrics;
66#[cfg(feature = "netdb")]
70pub mod netdb;
71#[cfg(feature = "cortex")]
72pub mod org_admission_gate;
73mod pool;
74mod protocol;
75mod proxy;
76#[cfg(feature = "redex")]
77pub mod redex;
78mod reliability;
79mod reroute;
80mod route;
81mod router;
82pub mod secret_file;
83mod session;
84pub mod state;
85mod stream;
86pub mod subnet;
87pub mod subprotocol;
88mod swarm;
89mod transport;
90#[cfg(feature = "nat-traversal")]
91pub mod traversal;
92
93#[cfg(target_os = "linux")]
94mod linux;
95
96pub use batch::AdaptiveBatcher;
97pub use channel::{
98 queue_group_hash, AckReason, AclPrincipal, AuthGuard, AuthVerdict, ChannelConfig,
99 ChannelConfigRegistry, ChannelError, ChannelHash, ChannelId, ChannelName, ChannelPublisher,
100 ChannelRegistry, MembershipMsg, OnFailure, OriginBinding, PublishConfig, PublishReport,
101 QueueGroupPolicy, ResolvedConfig, SubscriberRoster, Visibility, SUBPROTOCOL_CHANNEL_MEMBERSHIP,
102};
103pub use compute::{
104 DaemonError, DaemonFactoryRegistry, DaemonHost, DaemonHostConfig, DaemonRegistry, DaemonStats,
105 FactoryEntry, MeshDaemon, MigrationError, MigrationMessage, MigrationOrchestrator,
106 MigrationPhase, MigrationSourceHandler, MigrationState, MigrationTargetHandler,
107 PlacementDecision, Scheduler, SchedulerError, SUBPROTOCOL_MIGRATION,
108};
109pub use config::{ConnectionRole, NetAdapterConfig, ReliabilityConfig};
110pub use contested::{
111 CorrelatedFailureConfig, CorrelatedFailureDetector, CorrelationVerdict, FailureCause,
112 PartitionDetector, PartitionPhase, PartitionRecord, ReconcileOutcome, Side,
113 SUBPROTOCOL_PARTITION,
114};
115pub use continuity::{
116 assess_continuity, CausalCone, Causality, ContinuityProof, ContinuityStatus, Discontinuity,
117 DiscontinuityReason, ForkRecord, HorizonDivergence, ObservationWindow, ProofError,
118 PropagationModel, SuperpositionPhase, SuperpositionState, SUBPROTOCOL_CONTINUITY,
119};
120#[cfg(feature = "cortex")]
121pub use cortex::{
122 CortexAdapter, CortexAdapterConfig, CortexAdapterError, EventEnvelope, EventMeta,
123 FoldErrorPolicy, IntoRedexPayload, StartPosition, EVENT_META_SIZE,
124};
125pub use crypto::{CryptoError, SessionKeys, StaticKeypair};
126pub use failure::{
127 CircuitBreaker, CircuitState, FailureDetector, FailureDetectorConfig, FailureStats,
128 LossSimulator, NodeStatus, PeerFailureEvent, RecoveryAction, RecoveryManager, RecoveryStats,
129 VerdictStatus,
130};
131pub use identity::{
132 EntityError, EntityId, EntityKeypair, OriginStamp, PermissionToken, TokenCache, TokenError,
133 TokenScope,
134};
135#[cfg(feature = "nat-traversal")]
138pub use mesh::UpgradeAttemptGuard;
139pub use mesh::{
140 ControlPlaneStats, MeshNode, MeshNodeConfig, PartitionFilter, SensingReadinessOverlay,
141 SensingRegistrationError, UnregisteredChannelPolicy, ACK_RANGES_CAPABILITY_TAG,
142};
143#[cfg(feature = "netdb")]
144pub use netdb::{MemoriesFilter, NetDb, NetDbBuilder, NetDbError, NetDbSnapshot, TasksFilter};
145pub use pool::{PacketBuilder, PacketPool, SharedLocalPool, ThreadLocalPool};
151pub use protocol::{
152 EventFrame, NackPayload, NetHeader, PacketFlags, HEADER_SIZE, NONCE_SIZE, TAG_SIZE,
153};
154pub use proxy::{
155 ForwardResult, HopStats, MultiHopPacketBuilder, NetProxy, ProxyConfig, ProxyError, ProxyStats,
156};
157#[cfg(feature = "redex")]
158pub use redex::{
159 FsyncPolicy, IndexOp, IndexStart, OrderedAppender, Redex, RedexEntry, RedexError, RedexEvent,
160 RedexFile, RedexFileConfig, RedexFlags, RedexFold, RedexIndex, TypedRedexFile,
161};
162pub use reliability::{FireAndForget, ReliabilityMode, ReliableStream, RetransmitDescriptor};
163pub use reroute::ReroutePolicy;
164pub use route::{
165 AggregateStats, AlternateProvenance, RouteCandidateView, RouteEntry, RouteFlags,
166 RouteObservation, RoutingHeader, RoutingTable, SchedulerStreamStats, TransitionOutcome,
167 ROUTING_HEADER_SIZE,
168};
169pub use router::{FairScheduler, NetRouter, RouteAction, RouterConfig, RouterError, RouterStats};
170#[doc(hidden)]
174pub use router::{
175 arm_send_drain_histo, send_batch_stats, send_drain_histo_snapshot, send_drain_max,
176};
177pub use session::{NetSession, SessionManager, StreamState, TxAdmit, TxSlotGuard};
178pub use state::{
179 CausalChainBuilder, CausalEvent, CausalLink, ChainError, EntityLog, HorizonEncoder, LogError,
180 LogIndex, ObservedHorizon, SnapshotStore, StateSnapshot, CAUSAL_LINK_SIZE, SUBPROTOCOL_CAUSAL,
181 SUBPROTOCOL_SNAPSHOT,
182};
183pub use stream::{
184 CloseBehavior, Reliability, Stream, StreamConfig, StreamError, StreamStats,
185 DEFAULT_STREAM_WINDOW_BYTES,
186};
187pub use subnet::{DropReason, ForwardDecision, SubnetGateway, SubnetId, SubnetPolicy, SubnetRule};
188pub use subprotocol::{
189 negotiate, MigrationHandlerHooks, MigrationOrchestratorPolicy, MigrationSubprotocolHandler,
190 NegotiatedSet, OutboundMigrationMessage, SubprotocolDescriptor, SubprotocolManifest,
191 SubprotocolRegistry, SubprotocolVersion, SUBPROTOCOL_NEGOTIATION,
192};
193pub use swarm::{
194 Capabilities, CapabilityAd, EdgeInfo, GraphStats, LocalGraph, NodeInfo, Pingwave,
195 MAX_GRAPH_NODES, MAX_SEEN_PINGWAVES, PINGWAVE_SIZE,
196};
197pub use transport::{NetSocket, PacketReceiver, PacketSender, ParsedPacket, SocketBufferConfig};
198#[cfg(feature = "batched-ingress")]
204#[doc(hidden)]
205pub use transport::{
206 arm_recv_drain_histo, recv_batch_stats, recv_drain_histo_snapshot, recv_drain_max,
207 RECV_DRAIN_BUCKETS,
208};
209
210use async_trait::async_trait;
211use bytes::Bytes;
212use crossbeam_queue::SegQueue;
213use dashmap::DashMap;
214use std::sync::atomic::{AtomicBool, Ordering};
215use std::sync::Arc;
216use tokio::sync::Mutex as TokioMutex;
217use tokio::sync::Notify;
218use tokio::task::JoinHandle;
219
220use crate::adapter::{Adapter, ShardPollResult};
221use crate::error::AdapterError;
222use crate::event::{Batch, StoredEvent};
223
224use crypto::NoiseHandshake;
225use session::SessionManager as SessionMgr;
226use transport::NetSocket as Socket;
227
228pub use routing::{route_to_shard, stream_id_from_bytes, stream_id_from_key};
230
231const COARSE_CLOCK_REFRESH_NS: u64 = 1_000_000; #[inline]
262pub(crate) fn current_timestamp() -> u64 {
263 thread_local! {
284 static COARSE_CLOCK: std::cell::Cell<Option<(std::time::Instant, u64)>>
285 = const { std::cell::Cell::new(None) };
286 }
287 COARSE_CLOCK.with(|cell| {
288 let now_inst = std::time::Instant::now();
289 let (store, ns) = coarse_clock_advance(cell.get(), now_inst, || {
290 let elapsed = std::time::SystemTime::now()
291 .duration_since(std::time::UNIX_EPOCH)
292 .unwrap_or_default();
293 u64::try_from(elapsed.as_nanos()).unwrap_or(u64::MAX)
294 });
295 if let Some(pair) = store {
296 cell.set(Some(pair));
297 }
298 ns
299 })
300}
301
302#[inline]
319fn coarse_clock_advance(
320 cached: Option<(std::time::Instant, u64)>,
321 now_inst: std::time::Instant,
322 read_wall: impl FnOnce() -> u64,
323) -> (Option<(std::time::Instant, u64)>, u64) {
324 if let Some((last_inst, last_ns)) = cached {
325 if now_inst.duration_since(last_inst).as_nanos() < COARSE_CLOCK_REFRESH_NS as u128 {
326 return (None, last_ns);
327 }
328 }
329 let ns = read_wall();
330 (Some((now_inst, ns)), ns)
331}
332
333#[inline]
339pub(crate) fn current_timestamp_micros() -> u64 {
340 std::time::SystemTime::now()
341 .duration_since(std::time::UNIX_EPOCH)
342 .map(|d| d.as_micros() as u64)
343 .unwrap_or(0)
344}
345
346mod routing {
350 use xxhash_rust::xxh3::xxh3_64;
351
352 #[inline]
356 pub fn stream_id_from_bytes(data: &[u8]) -> u64 {
357 xxh3_64(data)
358 }
359
360 #[inline]
364 pub fn stream_id_from_key(key: &str) -> u64 {
365 xxh3_64(key.as_bytes())
366 }
367
368 #[inline]
376 pub fn route_to_shard(data: &[u8], num_shards: u16) -> u16 {
377 assert!(num_shards > 0, "num_shards must be > 0");
378 (xxh3_64(data) % num_shards as u64) as u16
379 }
380
381 #[cfg(test)]
382 mod tests {
383 use super::*;
384
385 #[test]
386 fn test_stream_id_deterministic() {
387 let data = b"test event data";
388 let id1 = stream_id_from_bytes(data);
389 let id2 = stream_id_from_bytes(data);
390 assert_eq!(id1, id2);
391 }
392
393 #[test]
394 fn test_stream_id_different_for_different_data() {
395 let id1 = stream_id_from_bytes(b"event1");
396 let id2 = stream_id_from_bytes(b"event2");
397 assert_ne!(id1, id2);
398 }
399
400 #[test]
401 fn test_stream_id_from_key() {
402 let id = stream_id_from_key("user:12345");
403 assert_ne!(id, 0);
404 }
405
406 #[test]
407 fn test_route_to_shard_range() {
408 let num_shards = 16u16;
409 for i in 0..1000 {
410 let data = format!("event_{}", i);
411 let shard = route_to_shard(data.as_bytes(), num_shards);
412 assert!(shard < num_shards);
413 }
414 }
415
416 #[test]
417 #[should_panic(expected = "num_shards must be > 0")]
418 fn test_route_to_shard_zero_shards_panics() {
419 route_to_shard(b"test", 0);
422 }
423
424 #[test]
425 fn test_route_to_shard_distribution() {
426 let num_shards = 8u16;
427 let mut counts = [0u32; 8];
428
429 for i in 0..8000 {
430 let data = format!("event_{}", i);
431 let shard = route_to_shard(data.as_bytes(), num_shards);
432 counts[shard as usize] += 1;
433 }
434
435 let expected = 1000;
437 for count in counts {
438 assert!(count > expected / 2, "shard count {} too low", count);
439 assert!(count < expected * 2, "shard count {} too high", count);
440 }
441 }
442 }
443}
444
445type InboundQueues = Arc<DashMap<u16, SegQueue<StoredEvent>>>;
447
448pub(crate) struct HandshakePacer {
462 entries: std::collections::HashMap<std::net::SocketAddr, (u32, std::time::Instant)>,
464 max_per_window: u32,
466 window: std::time::Duration,
468 last_gc: std::time::Instant,
470 gc_size_threshold: usize,
474}
475
476impl HandshakePacer {
477 pub(crate) fn new(max_per_window: u32, window: std::time::Duration) -> Self {
478 Self {
479 entries: std::collections::HashMap::new(),
480 max_per_window,
481 window,
482 last_gc: std::time::Instant::now(),
483 gc_size_threshold: 4096,
487 }
488 }
489
490 pub(crate) fn check_and_record(&mut self, source: std::net::SocketAddr) -> bool {
494 let now = std::time::Instant::now();
495 if now.duration_since(self.last_gc) >= self.window
504 || self.entries.len() >= self.gc_size_threshold
505 {
506 let cutoff = self.window.saturating_mul(2);
507 self.entries
508 .retain(|_, (_, start)| now.duration_since(*start) < cutoff);
509 self.last_gc = now;
510 }
511
512 let entry = self.entries.entry(source).or_insert((0, now));
513 if now.duration_since(entry.1) > self.window {
514 entry.0 = 0;
516 entry.1 = now;
517 }
518 entry.0 = entry.0.saturating_add(1);
519 entry.0 <= self.max_per_window
520 }
521}
522
523pub struct NetAdapter {
525 config: NetAdapterConfig,
527 socket: Option<Arc<Socket>>,
529 session: Option<Arc<NetSession>>,
531 session_manager: SessionMgr,
533 inbound: InboundQueues,
535 tasks: TokioMutex<Vec<JoinHandle<()>>>,
537 shutdown: Arc<AtomicBool>,
539 shutdown_notify: Arc<Notify>,
541 initialized: AtomicBool,
543 handshake_pacer: parking_lot::Mutex<HandshakePacer>,
548}
549
550impl NetAdapter {
551 pub fn new(config: NetAdapterConfig) -> Result<Self, AdapterError> {
553 config
554 .validate()
555 .map_err(|e| AdapterError::Fatal(format!("invalid config: {}", e)))?;
556
557 Ok(Self {
558 session_manager: SessionMgr::new(config.session_timeout),
559 config,
560 socket: None,
561 session: None,
562 inbound: Arc::new(DashMap::new()),
563 tasks: TokioMutex::new(Vec::new()),
564 shutdown: Arc::new(AtomicBool::new(false)),
565 shutdown_notify: Arc::new(Notify::new()),
566 initialized: AtomicBool::new(false),
567 handshake_pacer: parking_lot::Mutex::new(HandshakePacer::new(
571 5,
572 std::time::Duration::from_secs(1),
573 )),
574 })
575 }
576
577 async fn perform_handshake(
580 &self,
581 socket: &Socket,
582 ) -> Result<(SessionKeys, std::net::SocketAddr), AdapterError> {
583 let mut attempt = 0;
584 let max_attempts = self.config.handshake_retries;
585
586 const HANDSHAKE_RETRY_SLEEP_CAP_MS: u64 = 5_000;
594
595 loop {
596 attempt += 1;
597 match self.try_handshake(socket).await {
598 Ok(result) => return Ok(result),
599 Err(e) if attempt < max_attempts => {
600 tracing::warn!(
601 attempt = attempt,
602 max = max_attempts,
603 error = %e,
604 "handshake failed, retrying"
605 );
606 let backoff_ms =
607 (100u64.saturating_mul(attempt as u64)).min(HANDSHAKE_RETRY_SLEEP_CAP_MS);
608 tokio::time::sleep(std::time::Duration::from_millis(backoff_ms)).await;
609 }
610 Err(e) => return Err(e),
611 }
612 }
613 }
614
615 async fn try_handshake(
618 &self,
619 socket: &Socket,
620 ) -> Result<(SessionKeys, std::net::SocketAddr), AdapterError> {
621 let timeout = self.config.handshake_timeout;
622 let socket_arc = socket.socket_arc();
623
624 if self.config.is_initiator() {
625 let peer_pubkey = self
627 .config
628 .peer_static_pubkey
629 .as_ref()
630 .ok_or_else(|| AdapterError::Fatal("missing peer public key".into()))?;
631
632 let mut handshake = NoiseHandshake::initiator(&self.config.psk, peer_pubkey)
633 .map_err(|e| AdapterError::Fatal(format!("handshake init failed: {}", e)))?;
634
635 let msg1 = handshake
637 .write_message(&[])
638 .map_err(|e| AdapterError::Connection(format!("write_message failed: {}", e)))?;
639
640 let mut builder = PacketBuilder::new(&[0u8; 32], 0);
641 let packet = builder.build_handshake(&msg1);
642
643 socket
644 .send_to(&packet, self.config.peer_addr)
645 .await
646 .map_err(|e| AdapterError::Connection(format!("send failed: {}", e)))?;
647
648 let (parsed, _source) = tokio::time::timeout(timeout, async {
652 let mut recv_buf = [0u8; protocol::MAX_PACKET_SIZE];
665 loop {
666 let (n, source) = socket_arc
667 .recv_from(&mut recv_buf)
668 .await
669 .map_err(|e| AdapterError::Connection(format!("recv failed: {}", e)))?;
670
671 if source != self.config.peer_addr {
673 continue;
674 }
675
676 let data = bytes::Bytes::copy_from_slice(&recv_buf[..n]);
677
678 if let Some(p) = ParsedPacket::parse(data, source) {
679 if p.header.flags.is_handshake() {
680 return Ok::<_, AdapterError>((p, source));
681 }
682 }
683 }
685 })
686 .await
687 .map_err(|_| AdapterError::Connection("handshake timeout".into()))??;
688
689 handshake
691 .read_message(&parsed.payload)
692 .map_err(|e| AdapterError::Connection(format!("read_message failed: {}", e)))?;
693
694 let keys = handshake
696 .into_session_keys()
697 .map_err(|e| AdapterError::Fatal(format!("key extraction failed: {}", e)))?;
698 Ok((keys, self.config.peer_addr))
699 } else {
700 let keypair = self
702 .config
703 .static_keypair
704 .as_ref()
705 .ok_or_else(|| AdapterError::Fatal("missing static keypair".into()))?;
706
707 let (parsed, source) = tokio::time::timeout(timeout, async {
714 loop {
715 let mut recv_buf = bytes::BytesMut::with_capacity(protocol::MAX_PACKET_SIZE);
716 recv_buf.resize(protocol::MAX_PACKET_SIZE, 0);
717
718 let (n, source) = socket_arc
719 .recv_from(&mut recv_buf)
720 .await
721 .map_err(|e| AdapterError::Connection(format!("recv failed: {}", e)))?;
722
723 recv_buf.truncate(n);
724 let data = recv_buf.freeze();
725
726 if let Some(p) = ParsedPacket::parse(data, source) {
727 if p.header.flags.is_handshake() {
728 let allowed = self.handshake_pacer.lock().check_and_record(source);
731 if !allowed {
732 tracing::debug!(
733 %source,
734 "handshake responder: dropping packet from \
735 rate-limited source"
736 );
737 continue;
738 }
739 return Ok::<_, AdapterError>((p, source));
740 }
741 }
742 }
744 })
745 .await
746 .map_err(|_| AdapterError::Connection("handshake timeout".into()))??;
747
748 let mut handshake = NoiseHandshake::responder(&self.config.psk, keypair)
749 .map_err(|e| AdapterError::Fatal(format!("handshake init failed: {}", e)))?;
750
751 handshake
753 .read_message(&parsed.payload)
754 .map_err(|e| AdapterError::Connection(format!("read_message failed: {}", e)))?;
755
756 let msg2 = handshake
758 .write_message(&[])
759 .map_err(|e| AdapterError::Connection(format!("write_message failed: {}", e)))?;
760
761 let mut builder = PacketBuilder::new(&[0u8; 32], 0);
762 let packet = builder.build_handshake(&msg2);
763
764 socket
767 .send_to(&packet, source)
768 .await
769 .map_err(|e| AdapterError::Connection(format!("send failed: {}", e)))?;
770
771 let keys = handshake
773 .into_session_keys()
774 .map_err(|e| AdapterError::Fatal(format!("key extraction failed: {}", e)))?;
775 Ok((keys, source))
776 }
777 }
778
779 fn process_packet(
781 data: Bytes,
782 source: std::net::SocketAddr,
783 session: &NetSession,
784 inbound: &InboundQueues,
785 num_shards: u16,
786 ) {
787 let mut parsed = match ParsedPacket::parse(data, source) {
789 Some(p) => p,
790 None => return,
791 };
792
793 if !parsed.header.flags.is_handshake()
797 && !parsed.header.flags.is_heartbeat()
798 && !parsed.is_valid_length()
799 {
800 return;
801 }
802
803 if parsed.header.flags.is_handshake() {
805 return;
806 }
807
808 if parsed.header.session_id != session.session_id() {
810 return;
811 }
812
813 if parsed.header.flags.is_heartbeat() {
830 if source == session.peer_addr() {
831 session.verify_and_touch_heartbeat(&parsed);
832 }
833 return;
834 }
835
836 let aad = parsed.header.aad();
841 let counter = u64::from_le_bytes(parsed.header.nonce[4..12].try_into().unwrap_or([0u8; 8]));
842 let rx_cipher = session.rx_cipher();
843 let payload = std::mem::take(&mut parsed.payload);
844 let decrypted = match rx_cipher.decrypt_to_bytes(counter, &aad, payload) {
852 Ok(d) => {
853 if !rx_cipher.try_admit_rx_counter(counter) {
854 return;
855 }
856 d
857 }
858 Err(_) => return,
859 };
860
861 let events = EventFrame::read_events(decrypted, parsed.header.event_count);
863
864 let stream_id = parsed.header.stream_id;
866 let shard_id = if num_shards > 0 {
867 (stream_id % num_shards as u64) as u16
868 } else {
869 0
870 };
871
872 let is_fresh = {
885 let stream = session.get_or_create_stream(stream_id);
886 let fresh = stream.with_reliability(|r| r.on_receive(parsed.header.sequence));
892 stream.update_rx_seq(parsed.header.sequence);
893 fresh
894 };
895
896 if is_fresh {
897 let queue = inbound.entry(shard_id).or_default();
899 let seq = parsed.header.sequence;
900 for (i, event_data) in events.into_iter().enumerate() {
901 use std::fmt::Write;
902 let mut event_id = String::with_capacity(24);
903 let _ = write!(event_id, "{}:{}", seq, i);
904 queue.push(StoredEvent::new(event_id, event_data, seq, shard_id));
905 }
906 } else {
907 tracing::debug!(
908 seq = parsed.header.sequence,
909 stream_id,
910 "Dropping duplicate packet"
911 );
912 }
913
914 session.touch();
915 }
916
917 #[cfg(target_os = "linux")]
928 fn spawn_receiver(
929 shutdown: Arc<AtomicBool>,
930 shutdown_notify: Arc<Notify>,
931 socket: Arc<Socket>,
932 session: Arc<NetSession>,
933 inbound: InboundQueues,
934 num_shards: u16,
935 ) -> JoinHandle<()> {
936 let mut receiver = transport::BatchedPacketReceiver::new(socket.socket_arc());
937
938 tokio::spawn(async move {
939 while !shutdown.load(Ordering::Acquire) {
940 tokio::select! {
941 result = receiver.recv() => {
942 match result {
943 Ok((data, source)) => {
944 Self::process_packet(data, source, &session, &inbound, num_shards);
945 }
946 Err(e) if e.kind() == std::io::ErrorKind::ConnectionReset => {
947 tracing::warn!("batch receiver thread exited, stopping receiver");
948 break;
949 }
950 Err(e) => {
951 if !shutdown.load(Ordering::Acquire) {
952 tracing::warn!(error = %e, "receive error");
953 }
954 }
955 }
956 }
957 _ = shutdown_notify.notified() => {
958 break;
959 }
960 }
961 }
962 })
963 }
964
965 #[cfg(not(target_os = "linux"))]
967 fn spawn_receiver(
968 shutdown: Arc<AtomicBool>,
969 shutdown_notify: Arc<Notify>,
970 socket: Arc<Socket>,
971 session: Arc<NetSession>,
972 inbound: InboundQueues,
973 num_shards: u16,
974 ) -> JoinHandle<()> {
975 tokio::spawn(async move {
976 let mut receiver = PacketReceiver::new(socket.socket_arc());
977
978 while !shutdown.load(Ordering::Acquire) {
979 tokio::select! {
983 result = receiver.recv() => {
984 match result {
985 Ok((data, source)) => {
986 Self::process_packet(data, source, &session, &inbound, num_shards);
987 }
988 Err(e) => {
989 if !shutdown.load(Ordering::Acquire) {
990 tracing::warn!(error = %e, "receive error");
991 }
992 }
993 }
994 }
995 _ = shutdown_notify.notified() => {
996 break;
997 }
998 }
999 }
1000 })
1001 }
1002
1003 fn spawn_heartbeat(
1005 shutdown: Arc<AtomicBool>,
1006 shutdown_notify: Arc<Notify>,
1007 socket: Arc<Socket>,
1008 session: Arc<NetSession>,
1009 interval: std::time::Duration,
1010 peer_addr: std::net::SocketAddr,
1011 ) -> JoinHandle<()> {
1012 tokio::spawn(async move {
1013 let mut ticker = tokio::time::interval(interval);
1014
1015 loop {
1016 tokio::select! {
1017 _ = ticker.tick() => {
1018 if shutdown.load(Ordering::Acquire) || !session.is_active() {
1019 break;
1020 }
1021
1022 let packet = session.build_heartbeat();
1036
1037 if let Err(e) = socket.send_to(&packet, peer_addr).await {
1038 tracing::warn!(error = %e, "heartbeat send failed");
1039 }
1040 }
1041 _ = shutdown_notify.notified() => {
1042 break;
1043 }
1044 }
1045 }
1046 })
1047 }
1048}
1049
1050#[async_trait]
1051impl Adapter for NetAdapter {
1052 async fn init(&mut self) -> Result<(), AdapterError> {
1053 if self.initialized.load(Ordering::Acquire) {
1054 return Ok(());
1055 }
1056
1057 let socket_config = match (
1059 self.config.socket_recv_buffer,
1060 self.config.socket_send_buffer,
1061 ) {
1062 (Some(recv), Some(send)) => transport::SocketBufferConfig {
1063 recv_buffer_size: recv,
1064 send_buffer_size: send,
1065 },
1066 _ => transport::SocketBufferConfig::default(),
1067 };
1068 let socket = Socket::with_config(self.config.bind_addr, socket_config)
1069 .await
1070 .map_err(|e| AdapterError::Connection(format!("socket creation failed: {}", e)))?;
1071
1072 let socket = Arc::new(socket);
1073 self.socket = Some(socket.clone());
1074
1075 let (keys, actual_peer) = self.perform_handshake(&socket).await?;
1077
1078 let session = Arc::new(NetSession::new(
1082 keys,
1083 actual_peer,
1084 self.config.packet_pool_size,
1085 self.config.default_reliability.is_reliable(),
1086 ));
1087 self.session = Some(session.clone());
1088
1089 self.session_manager.set_session_arc(session.clone());
1091
1092 let recv_task = Self::spawn_receiver(
1094 self.shutdown.clone(),
1095 self.shutdown_notify.clone(),
1096 socket.clone(),
1097 session.clone(),
1098 self.inbound.clone(),
1099 self.config.num_shards,
1100 );
1101
1102 let heartbeat_task = Self::spawn_heartbeat(
1103 self.shutdown.clone(),
1104 self.shutdown_notify.clone(),
1105 socket,
1106 session,
1107 self.config.heartbeat_interval,
1108 actual_peer,
1109 );
1110
1111 {
1112 let mut tasks = self.tasks.lock().await;
1113 tasks.push(recv_task);
1114 tasks.push(heartbeat_task);
1115 }
1116
1117 self.initialized.store(true, Ordering::Release);
1118
1119 tracing::info!(
1120 bind_addr = %self.config.bind_addr,
1121 peer_addr = %self.config.peer_addr,
1122 role = ?self.config.role,
1123 "Net adapter initialized"
1124 );
1125
1126 Ok(())
1127 }
1128
1129 async fn on_batch(&self, batch: std::sync::Arc<Batch>) -> Result<(), AdapterError> {
1130 let session = self
1131 .session
1132 .as_ref()
1133 .ok_or_else(|| AdapterError::Connection("not connected".into()))?;
1134
1135 let socket = self
1136 .socket
1137 .as_ref()
1138 .ok_or_else(|| AdapterError::Connection("socket not initialized".into()))?;
1139
1140 let stream_id = batch.shard_id as u64;
1141 let peer_addr = session.peer_addr();
1142
1143 let reliable = {
1147 let stream = session.get_or_create_stream(stream_id);
1148 stream.with_reliability(|r| r.needs_ack())
1149 };
1151
1152 let mut current_batch: Vec<Bytes> = Vec::with_capacity(64);
1154 let mut current_size = 0usize;
1155
1156 let pool = session.thread_local_pool();
1158 let mut builder = pool.get();
1159
1160 for event in &batch.events {
1161 let event_bytes = event.raw.clone();
1162 let frame_size = EventFrame::LEN_SIZE + event_bytes.len();
1163
1164 if current_size + frame_size > protocol::MAX_PAYLOAD_SIZE && !current_batch.is_empty() {
1166 let seq;
1168 {
1169 let stream = session.get_or_create_stream(stream_id);
1170 seq = stream.next_tx_seq();
1171 }
1172
1173 let flags = if reliable {
1174 PacketFlags::RELIABLE
1175 } else {
1176 PacketFlags::NONE
1177 };
1178
1179 let packet = builder.build(stream_id, seq, ¤t_batch, flags);
1180
1181 socket
1183 .send_to(&packet, peer_addr)
1184 .await
1185 .map_err(|e| AdapterError::Connection(format!("send failed: {}", e)))?;
1186
1187 if reliable {
1194 let descriptor = std::sync::Arc::new(reliability::RetransmitDescriptor {
1199 seq,
1200 stream_id,
1201 events: current_batch.clone(),
1202 flags,
1203 });
1204 let stream = session.get_or_create_stream(stream_id);
1205 stream.with_reliability(|r| r.on_send(descriptor));
1206 }
1207
1208 current_batch.clear();
1209 current_size = 0;
1210 }
1211
1212 current_batch.push(event_bytes);
1213 current_size += frame_size;
1214 }
1215
1216 if !current_batch.is_empty() {
1218 let seq;
1219 {
1220 let stream = session.get_or_create_stream(stream_id);
1221 seq = stream.next_tx_seq();
1222 }
1223
1224 let flags = if reliable {
1225 PacketFlags::RELIABLE
1226 } else {
1227 PacketFlags::NONE
1228 };
1229
1230 let packet = builder.build(stream_id, seq, ¤t_batch, flags);
1231
1232 socket
1233 .send_to(&packet, peer_addr)
1234 .await
1235 .map_err(|e| AdapterError::Connection(format!("send failed: {}", e)))?;
1236
1237 if reliable {
1238 let descriptor = std::sync::Arc::new(reliability::RetransmitDescriptor {
1240 seq,
1241 stream_id,
1242 events: current_batch.clone(),
1243 flags,
1244 });
1245 let stream = session.get_or_create_stream(stream_id);
1246 stream.with_reliability(|r| r.on_send(descriptor));
1247 }
1248 }
1249
1250 session.touch();
1251
1252 Ok(())
1253 }
1254
1255 async fn poll_shard(
1256 &self,
1257 shard_id: u16,
1258 from_id: Option<&str>,
1259 limit: usize,
1260 ) -> Result<ShardPollResult, AdapterError> {
1261 let mut events = Vec::with_capacity(limit);
1262
1263 if let Some(queue) = self.inbound.get(&shard_id) {
1264 while events.len() < limit {
1265 if let Some(event) = queue.pop() {
1266 if from_id.is_none() || event_id_gt(&event.id, from_id.unwrap_or("")) {
1267 events.push(event);
1268 }
1269 } else {
1274 break;
1275 }
1276 }
1277 }
1278
1279 let has_more = self
1280 .inbound
1281 .get(&shard_id)
1282 .map(|q| !q.is_empty())
1283 .unwrap_or(false);
1284 let next_id = events.last().map(|e| e.id.clone());
1285
1286 Ok(ShardPollResult {
1287 events,
1288 next_id,
1289 has_more,
1290 })
1291 }
1292
1293 async fn flush(&self) -> Result<(), AdapterError> {
1294 Ok(())
1297 }
1298
1299 async fn shutdown(&self) -> Result<(), AdapterError> {
1300 self.shutdown.store(true, Ordering::Release);
1301
1302 self.shutdown_notify.notify_waiters();
1305
1306 self.session_manager.clear_session();
1308
1309 let mut tasks = self.tasks.lock().await;
1311 for task in tasks.drain(..) {
1312 let _ = task.await;
1313 }
1314
1315 self.initialized.store(false, Ordering::Release);
1316
1317 tracing::info!("Net adapter shutdown complete");
1318
1319 Ok(())
1320 }
1321
1322 fn name(&self) -> &'static str {
1323 "net"
1324 }
1325
1326 async fn is_healthy(&self) -> bool {
1327 self.initialized.load(Ordering::Acquire) && self.session_manager.check_session()
1328 }
1329}
1330
1331impl std::fmt::Debug for NetAdapter {
1332 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1333 f.debug_struct("NetAdapter")
1334 .field("config", &self.config)
1335 .field("initialized", &self.initialized.load(Ordering::Relaxed))
1336 .finish()
1337 }
1338}
1339
1340fn event_id_gt(a: &str, b: &str) -> bool {
1347 fn parse_id(id: &str) -> Option<(u64, u64)> {
1348 let (seq, idx) = id.split_once(':')?;
1349 Some((seq.parse().ok()?, idx.parse().ok()?))
1350 }
1351
1352 match (parse_id(a), parse_id(b)) {
1353 (Some(a), Some(b)) => a > b,
1354 _ => a > b, }
1356}
1357
1358#[cfg(test)]
1359mod tests {
1360 use super::*;
1361
1362 #[test]
1363 fn test_adapter_creation() {
1364 let psk = [0x42u8; 32];
1365 let peer_pubkey = [0x24u8; 32];
1366
1367 let config = NetAdapterConfig::initiator(
1368 "127.0.0.1:0".parse().unwrap(),
1369 "127.0.0.1:9999".parse().unwrap(),
1370 psk,
1371 peer_pubkey,
1372 );
1373
1374 let adapter = NetAdapter::new(config).unwrap();
1375 assert_eq!(adapter.name(), "net");
1376 }
1377
1378 #[test]
1385 fn coarse_clock_reuses_cache_within_refresh_window() {
1386 let t0 = std::time::Instant::now();
1387 let within = t0 + std::time::Duration::from_nanos(COARSE_CLOCK_REFRESH_NS - 1);
1388 let (store, ns) = coarse_clock_advance(Some((t0, 42)), within, || {
1389 panic!("cache hit must not read the wall clock")
1390 });
1391 assert_eq!(ns, 42, "hit must return the cached reading");
1392 assert!(store.is_none(), "hit must keep the hit path store-free");
1393 }
1394
1395 #[test]
1399 fn coarse_clock_refreshes_at_and_past_the_window() {
1400 let t0 = std::time::Instant::now();
1401 let at_boundary = t0 + std::time::Duration::from_nanos(COARSE_CLOCK_REFRESH_NS);
1402 let (store, ns) = coarse_clock_advance(Some((t0, 42)), at_boundary, || 100);
1403 assert_eq!(ns, 100, "boundary read must refresh");
1404 assert_eq!(
1405 store,
1406 Some((at_boundary, 100)),
1407 "refresh must rebase the window on the read instant"
1408 );
1409 }
1410
1411 #[test]
1414 fn coarse_clock_cold_start_reads_wall_clock() {
1415 let t0 = std::time::Instant::now();
1416 let (store, ns) = coarse_clock_advance(None, t0, || 7);
1417 assert_eq!(ns, 7);
1418 assert_eq!(store, Some((t0, 7)));
1419 }
1420
1421 #[test]
1422 fn current_timestamp_advances_after_refresh_interval() {
1423 let first = current_timestamp();
1428 std::thread::sleep(std::time::Duration::from_millis(5));
1429 let later = current_timestamp();
1430 assert!(
1431 later > first,
1432 "post-refresh reading must advance: first={}, later={}",
1433 first,
1434 later
1435 );
1436 }
1437
1438 #[test]
1439 fn test_shard_id_from_stream_id_uses_modulo() {
1440 let num_shards: u16 = 8;
1444
1445 let stream_a: u64 = 0xDEAD_BEEF_0000_0003;
1450 let stream_b: u64 = 0xCAFE_BABE_0000_0003;
1451
1452 let shard_a = (stream_a % num_shards as u64) as u16;
1453 let shard_b = (stream_b % num_shards as u64) as u16;
1454
1455 assert!(
1456 shard_a < num_shards,
1457 "shard must be in range [0, num_shards)"
1458 );
1459 assert!(
1460 shard_b < num_shards,
1461 "shard must be in range [0, num_shards)"
1462 );
1463
1464 let big_stream: u64 = 0xFFFF_FFFF_FFFF_FFFF;
1466 let shard_big = (big_stream % num_shards as u64) as u16;
1467 assert!(shard_big < num_shards);
1468
1469 assert_ne!(
1472 big_stream as u16, shard_big,
1473 "modulo must differ from truncation for large stream IDs"
1474 );
1475 }
1476
1477 #[test]
1478 fn test_invalid_config() {
1479 let psk = [0x42u8; 32];
1480 let peer_pubkey = [0x24u8; 32];
1481
1482 let mut config = NetAdapterConfig::initiator(
1483 "127.0.0.1:0".parse().unwrap(),
1484 "127.0.0.1:9999".parse().unwrap(),
1485 psk,
1486 peer_pubkey,
1487 );
1488 config.peer_static_pubkey = None;
1489
1490 let result = NetAdapter::new(config);
1491 assert!(result.is_err());
1492 }
1493
1494 #[test]
1497 fn test_event_id_gt_numeric_ordering() {
1498 assert!(event_id_gt("2:0", "1:0"));
1500 assert!(!event_id_gt("1:0", "2:0"));
1501 assert!(!event_id_gt("1:0", "1:0"));
1502
1503 assert!(event_id_gt("10:0", "9:0"));
1505 assert!(event_id_gt("100:0", "99:0"));
1506 assert!(!event_id_gt("9:0", "10:0"));
1507
1508 assert!(event_id_gt("5:2", "5:1"));
1510 assert!(!event_id_gt("5:1", "5:2"));
1511
1512 assert!(event_id_gt("1000000:0", "999999:0"));
1514 }
1515
1516 #[test]
1522 fn test_event_id_gt_edge_cases() {
1523 assert!(event_id_gt("1:0", ""));
1525 assert!(event_id_gt("b", "a"));
1527 assert!(!event_id_gt("a", "b"));
1528 }
1529
1530 #[test]
1535 fn test_build_then_process_packet_roundtrip() {
1536 use crate::adapter::net::crypto::{NoiseHandshake, StaticKeypair};
1537 use dashmap::DashMap;
1538 use std::sync::Arc;
1539
1540 let psk = [0x42u8; 32];
1542 let responder_kp = StaticKeypair::generate();
1543
1544 let mut initiator = NoiseHandshake::initiator(&psk, &responder_kp.public).unwrap();
1545 let mut responder = NoiseHandshake::responder(&psk, &responder_kp).unwrap();
1546
1547 let msg1 = initiator.write_message(&[]).unwrap();
1548 responder.read_message(&msg1).unwrap();
1549 let msg2 = responder.write_message(&[]).unwrap();
1550 initiator.read_message(&msg2).unwrap();
1551
1552 let init_keys = initiator.into_session_keys().unwrap();
1553 let resp_keys = responder.into_session_keys().unwrap();
1554
1555 let mut builder = PacketBuilder::new(&init_keys.tx_key, init_keys.session_id);
1557 let events = vec![
1558 Bytes::from(r#"{"token":"hello"}"#),
1559 Bytes::from(r#"{"token":"world"}"#),
1560 ];
1561 let packet = builder.build(0, 0, &events, PacketFlags::NONE);
1562
1563 let resp_session = Arc::new(NetSession::new(
1565 resp_keys,
1566 "127.0.0.1:5000".parse().unwrap(),
1567 4,
1568 false,
1569 ));
1570 let inbound: InboundQueues = Arc::new(DashMap::new());
1571 let source: std::net::SocketAddr = "127.0.0.1:5000".parse().unwrap();
1572
1573 NetAdapter::process_packet(packet, source, &resp_session, &inbound, 1);
1574
1575 let queue = inbound.get(&0).expect("shard 0 should have events");
1577 assert_eq!(queue.len(), 2, "expected 2 events, got {}", queue.len());
1578
1579 let e1 = queue.pop().unwrap();
1580 assert_eq!(&e1.raw[..], br#"{"token":"hello"}"#);
1581
1582 let e2 = queue.pop().unwrap();
1583 assert_eq!(&e2.raw[..], br#"{"token":"world"}"#);
1584 }
1585
1586 fn make_session_keys() -> (SessionKeys, SessionKeys) {
1588 use crate::adapter::net::crypto::{NoiseHandshake, StaticKeypair};
1589
1590 let psk = [0x42u8; 32];
1591 let responder_kp = StaticKeypair::generate();
1592
1593 let mut initiator = NoiseHandshake::initiator(&psk, &responder_kp.public).unwrap();
1594 let mut responder = NoiseHandshake::responder(&psk, &responder_kp).unwrap();
1595
1596 let msg1 = initiator.write_message(&[]).unwrap();
1597 responder.read_message(&msg1).unwrap();
1598 let msg2 = responder.write_message(&[]).unwrap();
1599 initiator.read_message(&msg2).unwrap();
1600
1601 (
1602 initiator.into_session_keys().unwrap(),
1603 responder.into_session_keys().unwrap(),
1604 )
1605 }
1606
1607 #[test]
1608 fn test_process_packet_rejects_truncated_packet() {
1609 use dashmap::DashMap;
1610 use std::sync::Arc;
1611
1612 let (init_keys, resp_keys) = make_session_keys();
1613
1614 let mut builder = PacketBuilder::new(&init_keys.tx_key, init_keys.session_id);
1616 let packet = builder.build(0, 0, &[Bytes::from_static(b"hello")], PacketFlags::NONE);
1617
1618 let resp_session = Arc::new(NetSession::new(
1619 resp_keys,
1620 "127.0.0.1:5000".parse().unwrap(),
1621 4,
1622 false,
1623 ));
1624 let inbound: InboundQueues = Arc::new(DashMap::new());
1625 let source: std::net::SocketAddr = "127.0.0.1:5000".parse().unwrap();
1626
1627 let truncated = packet.slice(..packet.len() - 10);
1629 NetAdapter::process_packet(truncated, source, &resp_session, &inbound, 1);
1630 assert!(
1631 inbound.get(&0).is_none() || inbound.get(&0).unwrap().is_empty(),
1632 "truncated packet must be silently dropped"
1633 );
1634 }
1635
1636 #[test]
1637 fn test_process_packet_rejects_tampered_payload() {
1638 use dashmap::DashMap;
1639 use std::sync::Arc;
1640
1641 let (init_keys, resp_keys) = make_session_keys();
1642
1643 let mut builder = PacketBuilder::new(&init_keys.tx_key, init_keys.session_id);
1644 let packet = builder.build(0, 0, &[Bytes::from_static(b"hello")], PacketFlags::NONE);
1645
1646 let resp_session = Arc::new(NetSession::new(
1647 resp_keys,
1648 "127.0.0.1:5000".parse().unwrap(),
1649 4,
1650 false,
1651 ));
1652 let inbound: InboundQueues = Arc::new(DashMap::new());
1653 let source: std::net::SocketAddr = "127.0.0.1:5000".parse().unwrap();
1654
1655 let mut tampered = bytes::BytesMut::from(&packet[..]);
1657 tampered[super::protocol::HEADER_SIZE + 2] ^= 0xFF;
1658 NetAdapter::process_packet(tampered.freeze(), source, &resp_session, &inbound, 1);
1659
1660 assert!(
1661 inbound.get(&0).is_none() || inbound.get(&0).unwrap().is_empty(),
1662 "tampered packet must be rejected by AEAD"
1663 );
1664 }
1665
1666 #[test]
1667 fn test_process_packet_rejects_wrong_session_id() {
1668 use dashmap::DashMap;
1669 use std::sync::Arc;
1670
1671 let (init_keys, resp_keys) = make_session_keys();
1672
1673 let mut builder = PacketBuilder::new(&init_keys.tx_key, init_keys.session_id);
1674 let packet = builder.build(0, 0, &[Bytes::from_static(b"hello")], PacketFlags::NONE);
1675
1676 let mut wrong_keys = resp_keys;
1678 wrong_keys.session_id = 0xDEAD;
1679 let resp_session = Arc::new(NetSession::new(
1680 wrong_keys,
1681 "127.0.0.1:5000".parse().unwrap(),
1682 4,
1683 false,
1684 ));
1685 let inbound: InboundQueues = Arc::new(DashMap::new());
1686 let source: std::net::SocketAddr = "127.0.0.1:5000".parse().unwrap();
1687
1688 NetAdapter::process_packet(packet, source, &resp_session, &inbound, 1);
1689
1690 assert!(
1691 inbound.get(&0).is_none() || inbound.get(&0).unwrap().is_empty(),
1692 "packet with wrong session_id must be dropped"
1693 );
1694 }
1695
1696 #[test]
1697 fn test_process_packet_multi_packet_batch_all_events_arrive() {
1698 use dashmap::DashMap;
1699 use std::sync::Arc;
1700
1701 let (init_keys, resp_keys) = make_session_keys();
1702
1703 let resp_session = Arc::new(NetSession::new(
1704 resp_keys,
1705 "127.0.0.1:5000".parse().unwrap(),
1706 4,
1707 false,
1708 ));
1709 let inbound: InboundQueues = Arc::new(DashMap::new());
1710 let source: std::net::SocketAddr = "127.0.0.1:5000".parse().unwrap();
1711
1712 let mut builder = PacketBuilder::new(&init_keys.tx_key, init_keys.session_id);
1716 let total_events = 200;
1717 let mut seq = 0u64;
1718
1719 let mut current_batch: Vec<Bytes> = Vec::new();
1721 let mut current_size = 0;
1722
1723 for i in 0..total_events {
1724 let data = format!("{{\"i\":{},\"pad\":\"{}\"}}", i, "x".repeat(150));
1725 let event_bytes = Bytes::from(data);
1726 let frame_size = EventFrame::LEN_SIZE + event_bytes.len();
1727
1728 if current_size + frame_size > protocol::MAX_PAYLOAD_SIZE && !current_batch.is_empty() {
1729 let packet = builder.build(0, seq, ¤t_batch, PacketFlags::NONE);
1730 NetAdapter::process_packet(packet, source, &resp_session, &inbound, 1);
1731 seq += 1;
1732 current_batch.clear();
1733 current_size = 0;
1734 }
1735
1736 current_batch.push(event_bytes);
1737 current_size += frame_size;
1738 }
1739
1740 if !current_batch.is_empty() {
1741 let packet = builder.build(0, seq, ¤t_batch, PacketFlags::NONE);
1742 NetAdapter::process_packet(packet, source, &resp_session, &inbound, 1);
1743 }
1744
1745 let queue = inbound.get(&0).expect("shard 0 should have events");
1747 assert_eq!(
1748 queue.len(),
1749 total_events,
1750 "all {} events must arrive across multiple packets",
1751 total_events
1752 );
1753 }
1754
1755 #[test]
1756 fn test_build_then_process_packet_both_directions() {
1757 use dashmap::DashMap;
1758 use std::sync::Arc;
1759
1760 let (init_keys, resp_keys) = make_session_keys();
1761 let source: std::net::SocketAddr = "127.0.0.1:5000".parse().unwrap();
1762
1763 {
1765 let mut builder = PacketBuilder::new(&init_keys.tx_key, init_keys.session_id);
1766 let packet = builder.build(0, 0, &[Bytes::from_static(b"i2r")], PacketFlags::NONE);
1767
1768 let session = Arc::new(NetSession::new(resp_keys.clone(), source, 4, false));
1769 let inbound: InboundQueues = Arc::new(DashMap::new());
1770 NetAdapter::process_packet(packet, source, &session, &inbound, 1);
1771
1772 let queue = inbound.get(&0).expect("i2r: shard 0 should have events");
1773 assert_eq!(queue.len(), 1, "i2r: expected 1 event");
1774 assert_eq!(&queue.pop().unwrap().raw[..], b"i2r");
1775 }
1776
1777 {
1779 let mut builder = PacketBuilder::new(&resp_keys.tx_key, resp_keys.session_id);
1780 let packet = builder.build(0, 0, &[Bytes::from_static(b"r2i")], PacketFlags::NONE);
1781
1782 let session = Arc::new(NetSession::new(init_keys.clone(), source, 4, false));
1783 let inbound: InboundQueues = Arc::new(DashMap::new());
1784 NetAdapter::process_packet(packet, source, &session, &inbound, 1);
1785
1786 let queue = inbound.get(&0).expect("r2i: shard 0 should have events");
1787 assert_eq!(queue.len(), 1, "r2i: expected 1 event");
1788 assert_eq!(&queue.pop().unwrap().raw[..], b"r2i");
1789 }
1790 }
1791
1792 #[test]
1793 fn test_poll_shard_cursor_drops_consumed_events() {
1794 use std::sync::Arc;
1799
1800 let (init_keys, resp_keys) = make_session_keys();
1801
1802 let resp_session = Arc::new(NetSession::new(
1803 resp_keys,
1804 "127.0.0.1:5000".parse().unwrap(),
1805 4,
1806 false,
1807 ));
1808 let inbound: InboundQueues = Arc::new(DashMap::new());
1809 let source: std::net::SocketAddr = "127.0.0.1:5000".parse().unwrap();
1810
1811 let mut builder = PacketBuilder::new(&init_keys.tx_key, init_keys.session_id);
1813 for seq in 0..3u64 {
1814 let events = vec![Bytes::from(format!("event-{}", seq))];
1815 let packet = builder.build(0, seq, &events, PacketFlags::NONE);
1816 NetAdapter::process_packet(packet, source, &resp_session, &inbound, 1);
1817 }
1818
1819 let queue = inbound.get(&0u16).unwrap();
1820 assert_eq!(queue.len(), 3);
1821
1822 let from_id = "0:0";
1825 let mut events = Vec::new();
1826 while events.len() < 10 {
1827 if let Some(event) = queue.pop() {
1828 if event_id_gt(&event.id, from_id) {
1829 events.push(event);
1830 }
1831 } else {
1833 break;
1834 }
1835 }
1836
1837 assert_eq!(events.len(), 2, "should get 2 events after cursor 0:0");
1838 assert_eq!(events[0].id, "1:0");
1839 assert_eq!(events[1].id, "2:0");
1840
1841 assert_eq!(queue.len(), 0, "queue should be empty after poll drains it");
1843 }
1844
1845 #[test]
1846 fn test_process_packet_old_counter_rejected() {
1847 use std::sync::Arc;
1850
1851 let (init_keys, resp_keys) = make_session_keys();
1852 let resp_session = Arc::new(NetSession::new(
1853 resp_keys,
1854 "127.0.0.1:5000".parse().unwrap(),
1855 4,
1856 false,
1857 ));
1858 let inbound: InboundQueues = Arc::new(DashMap::new());
1859 let source: std::net::SocketAddr = "127.0.0.1:5000".parse().unwrap();
1860
1861 let mut builder = PacketBuilder::new(&init_keys.tx_key, init_keys.session_id);
1863 for seq in 0..1100u64 {
1864 let packet = builder.build(0, seq, &[Bytes::from_static(b"x")], PacketFlags::NONE);
1865 NetAdapter::process_packet(packet, source, &resp_session, &inbound, 1);
1866 }
1867 assert_eq!(inbound.get(&0).unwrap().len(), 1100);
1868
1869 let mut stale_builder = PacketBuilder::new(&init_keys.tx_key, init_keys.session_id);
1873 let stale_packet =
1874 stale_builder.build(0, 9999, &[Bytes::from_static(b"stale")], PacketFlags::NONE);
1875 NetAdapter::process_packet(stale_packet, source, &resp_session, &inbound, 1);
1876
1877 assert_eq!(
1879 inbound.get(&0).unwrap().len(),
1880 1100,
1881 "packet with stale counter must be rejected"
1882 );
1883 }
1884
1885 #[test]
1886 fn test_process_packet_far_future_counter_rejected() {
1887 use std::sync::Arc;
1891
1892 let (_init_keys, resp_keys) = make_session_keys();
1893
1894 let resp_session = Arc::new(NetSession::new(
1899 resp_keys,
1900 "127.0.0.1:5000".parse().unwrap(),
1901 4,
1902 false,
1903 ));
1904
1905 let rx_cipher = resp_session.rx_cipher();
1907 assert!(
1908 !rx_cipher.is_valid_rx_counter(u64::MAX),
1909 "counter at u64::MAX must be rejected (far beyond MAX_FORWARD)"
1910 );
1911 assert!(
1912 rx_cipher.is_valid_rx_counter(0),
1913 "counter 0 should be valid initially"
1914 );
1915 }
1916
1917 #[test]
1934 fn process_packet_drops_duplicates_per_reliability_decision() {
1935 use dashmap::DashMap;
1936 use std::sync::Arc;
1937
1938 let (init_keys, resp_keys) = make_session_keys();
1939
1940 let resp_session = Arc::new(NetSession::new(
1944 resp_keys,
1945 "127.0.0.1:5000".parse().unwrap(),
1946 4,
1947 true, ));
1949 let inbound: InboundQueues = Arc::new(DashMap::new());
1950 let source: std::net::SocketAddr = "127.0.0.1:5000".parse().unwrap();
1951
1952 let mut builder = PacketBuilder::new(&init_keys.tx_key, init_keys.session_id);
1958 let packet0 = builder.build(7, 0, &[Bytes::from(r#"{"first":0}"#)], PacketFlags::NONE);
1959 let packet1 = builder.build(7, 1, &[Bytes::from(r#"{"first":1}"#)], PacketFlags::NONE);
1960 let packet0_dup = builder.build(
1964 7,
1965 0,
1966 &[Bytes::from(r#"{"dup":"should_not_appear"}"#)],
1967 PacketFlags::NONE,
1968 );
1969
1970 NetAdapter::process_packet(packet0, source, &resp_session, &inbound, 1);
1971 NetAdapter::process_packet(packet1, source, &resp_session, &inbound, 1);
1972 NetAdapter::process_packet(packet0_dup, source, &resp_session, &inbound, 1);
1973
1974 let queue = inbound.get(&0).expect("shard 0 should exist");
1975 assert_eq!(
1976 queue.len(),
1977 2,
1978 "duplicate packet must NOT enqueue (BUG_REPORT.md #5); \
1979 got {} events, expected exactly 2 (seq=0 and seq=1, no dup)",
1980 queue.len()
1981 );
1982
1983 let e0 = queue.pop().unwrap();
1986 assert_eq!(&e0.raw[..], br#"{"first":0}"#);
1987 let e1 = queue.pop().unwrap();
1988 assert_eq!(&e1.raw[..], br#"{"first":1}"#);
1989 assert!(queue.is_empty());
1990 }
1991
1992 #[test]
2000 fn heartbeat_is_aead_authenticated() {
2001 use crate::adapter::net::pool::PacketBuilder;
2002 use dashmap::DashMap;
2003 use std::sync::Arc;
2004
2005 let (init_keys, resp_keys) = make_session_keys();
2006
2007 let resp_session = Arc::new(NetSession::new(
2008 resp_keys,
2009 "127.0.0.1:5000".parse().unwrap(),
2010 4,
2011 false,
2012 ));
2013 let inbound: InboundQueues = Arc::new(DashMap::new());
2014 let source: std::net::SocketAddr = "127.0.0.1:5000".parse().unwrap();
2015
2016 let mut builder = PacketBuilder::new(&init_keys.tx_key, init_keys.session_id);
2019 let heartbeat = builder.build_heartbeat();
2020 let last_activity_before = resp_session.last_activity_ns();
2021 std::thread::sleep(std::time::Duration::from_millis(2));
2022
2023 NetAdapter::process_packet(heartbeat, source, &resp_session, &inbound, 1);
2025 let last_activity_after = resp_session.last_activity_ns();
2026 assert!(
2027 last_activity_after > last_activity_before,
2028 "legitimate AEAD-tagged heartbeat must call session.touch()"
2029 );
2030
2031 let mut forged = bytes::BytesMut::new();
2035 let header = NetHeader::heartbeat(resp_session.session_id());
2036 forged.extend_from_slice(&header.to_bytes());
2037 let forged = forged.freeze();
2038 let last_activity_before = resp_session.last_activity_ns();
2039 std::thread::sleep(std::time::Duration::from_millis(2));
2040 NetAdapter::process_packet(forged, source, &resp_session, &inbound, 1);
2041 let last_activity_after = resp_session.last_activity_ns();
2042 assert_eq!(
2043 last_activity_before, last_activity_after,
2044 "unauthenticated heartbeat (no AEAD tag) must NOT touch the session"
2045 );
2046
2047 let mut forged_tag = bytes::BytesMut::new();
2050 let mut header_bytes = NetHeader::heartbeat(resp_session.session_id()).to_bytes();
2051 header_bytes[12..16].copy_from_slice(&[0u8; 4]);
2054 header_bytes[16..24].copy_from_slice(&1u64.to_le_bytes());
2055 forged_tag.extend_from_slice(&header_bytes);
2056 forged_tag.extend_from_slice(&[0xAAu8; 16]); let forged_tag = forged_tag.freeze();
2058 let last_activity_before = resp_session.last_activity_ns();
2059 std::thread::sleep(std::time::Duration::from_millis(2));
2060 NetAdapter::process_packet(forged_tag, source, &resp_session, &inbound, 1);
2061 let last_activity_after = resp_session.last_activity_ns();
2062 assert_eq!(
2063 last_activity_before, last_activity_after,
2064 "heartbeat with garbage AEAD tag must NOT touch the session"
2065 );
2066 }
2067
2068 #[test]
2074 fn handshake_pacer_rejects_floods_per_source() {
2075 use std::time::Duration;
2076 let mut pacer = HandshakePacer::new(3, Duration::from_millis(50));
2077
2078 let attacker: std::net::SocketAddr = "10.0.0.1:9000".parse().unwrap();
2079 let legit: std::net::SocketAddr = "10.0.0.2:9000".parse().unwrap();
2080
2081 for _ in 0..3 {
2083 assert!(pacer.check_and_record(attacker));
2084 }
2085 for _ in 0..10 {
2087 assert!(
2088 !pacer.check_and_record(attacker),
2089 "attacker exceeding budget must be dropped"
2090 );
2091 }
2092
2093 assert!(
2096 pacer.check_and_record(legit),
2097 "legitimate source must still get through despite attacker flood"
2098 );
2099
2100 std::thread::sleep(Duration::from_millis(55));
2102 assert!(
2103 pacer.check_and_record(attacker),
2104 "attacker budget must refill after window"
2105 );
2106 }
2107}