Skip to main content

sectorsync_transport/
lib.rs

1//! Transport traits and fake transport support for `SectorSync`.
2
3#![forbid(unsafe_code)]
4
5use std::cmp::Reverse;
6use std::collections::{BTreeMap, BTreeSet, BinaryHeap, HashMap, HashSet, VecDeque};
7use std::hash::Hash;
8use std::io;
9use std::net::{SocketAddr, ToSocketAddrs, UdpSocket};
10use std::sync::{Arc, Mutex};
11
12use sectorsync_core::prelude::{ClientId, StationId, Tick};
13
14const HASHED_BOUNDED_SET_MIN_CAPACITY: usize = 256;
15const HASHED_ENDPOINT_MAP_MIN_ENTRIES: usize = 2_048;
16const IN_MEMORY_BATCH_LOCK_PACKETS: usize = 64;
17const RETRY_DEADLINE_COMPACTION_MULTIPLE: usize = 4;
18
19#[derive(Clone, Debug)]
20enum AdaptiveEndpointMap<K, V> {
21    Ordered(BTreeMap<K, V>),
22    Hashed(HashMap<K, V>),
23}
24
25impl<K: Copy + Eq + Hash + Ord, V> AdaptiveEndpointMap<K, V> {
26    fn new() -> Self {
27        Self::Ordered(BTreeMap::new())
28    }
29
30    fn insert(&mut self, key: K, value: V) -> Option<V> {
31        let promote = match self {
32            Self::Ordered(entries) => {
33                entries.len() >= HASHED_ENDPOINT_MAP_MIN_ENTRIES.saturating_sub(1)
34                    && !entries.contains_key(&key)
35            }
36            Self::Hashed(_) => false,
37        };
38        if promote {
39            let Self::Ordered(ordered) = std::mem::replace(self, Self::Hashed(HashMap::new()))
40            else {
41                unreachable!("promotion starts from ordered endpoint storage");
42            };
43            let mut hashed = HashMap::with_capacity(ordered.len().saturating_add(1));
44            hashed.extend(ordered);
45            *self = Self::Hashed(hashed);
46        }
47        match self {
48            Self::Ordered(entries) => entries.insert(key, value),
49            Self::Hashed(entries) => entries.insert(key, value),
50        }
51    }
52
53    fn get(&self, key: &K) -> Option<&V> {
54        match self {
55            Self::Ordered(entries) => entries.get(key),
56            Self::Hashed(entries) => entries.get(key),
57        }
58    }
59
60    fn get_mut(&mut self, key: &K) -> Option<&mut V> {
61        match self {
62            Self::Ordered(entries) => entries.get_mut(key),
63            Self::Hashed(entries) => entries.get_mut(key),
64        }
65    }
66
67    fn remove(&mut self, key: &K) -> Option<V> {
68        match self {
69            Self::Ordered(entries) => entries.remove(key),
70            Self::Hashed(entries) => entries.remove(key),
71        }
72    }
73
74    fn values(&self) -> AdaptiveEndpointValues<'_, K, V> {
75        match self {
76            Self::Ordered(entries) => AdaptiveEndpointValues::Ordered(entries.values()),
77            Self::Hashed(entries) => AdaptiveEndpointValues::Hashed(entries.values()),
78        }
79    }
80
81    #[cfg(test)]
82    fn is_hashed(&self) -> bool {
83        matches!(self, Self::Hashed(_))
84    }
85}
86
87enum AdaptiveEndpointValues<'a, K, V> {
88    Ordered(std::collections::btree_map::Values<'a, K, V>),
89    Hashed(std::collections::hash_map::Values<'a, K, V>),
90}
91
92impl<'a, K, V> Iterator for AdaptiveEndpointValues<'a, K, V> {
93    type Item = &'a V;
94
95    fn next(&mut self) -> Option<Self::Item> {
96        match self {
97            Self::Ordered(values) => values.next(),
98            Self::Hashed(values) => values.next(),
99        }
100    }
101
102    fn size_hint(&self) -> (usize, Option<usize>) {
103        match self {
104            Self::Ordered(values) => values.size_hint(),
105            Self::Hashed(values) => values.size_hint(),
106        }
107    }
108}
109
110impl<K, V> ExactSizeIterator for AdaptiveEndpointValues<'_, K, V> {}
111
112#[derive(Clone, Debug)]
113enum BoundedLookupSet<K> {
114    Ordered(BTreeSet<K>),
115    Hashed(HashSet<K>),
116}
117
118impl<K: Copy + Eq + Hash + Ord> BoundedLookupSet<K> {
119    fn new(max_entries: usize) -> Self {
120        if max_entries >= HASHED_BOUNDED_SET_MIN_CAPACITY {
121            Self::Hashed(HashSet::new())
122        } else {
123            Self::Ordered(BTreeSet::new())
124        }
125    }
126
127    fn len(&self) -> usize {
128        match self {
129            Self::Ordered(entries) => entries.len(),
130            Self::Hashed(entries) => entries.len(),
131        }
132    }
133
134    fn is_empty(&self) -> bool {
135        match self {
136            Self::Ordered(entries) => entries.is_empty(),
137            Self::Hashed(entries) => entries.is_empty(),
138        }
139    }
140
141    fn contains(&self, key: &K) -> bool {
142        match self {
143            Self::Ordered(entries) => entries.contains(key),
144            Self::Hashed(entries) => entries.contains(key),
145        }
146    }
147
148    fn insert(&mut self, key: K) {
149        match self {
150            Self::Ordered(entries) => {
151                entries.insert(key);
152            }
153            Self::Hashed(entries) => {
154                entries.insert(key);
155            }
156        }
157    }
158
159    fn remove(&mut self, key: &K) {
160        match self {
161            Self::Ordered(entries) => {
162                entries.remove(key);
163            }
164            Self::Hashed(entries) => {
165                entries.remove(key);
166            }
167        }
168    }
169
170    #[cfg(test)]
171    fn is_hashed(&self) -> bool {
172        matches!(self, Self::Hashed(_))
173    }
174}
175
176/// Default maximum UDP datagram bytes read by `UdpTransport`.
177pub const DEFAULT_UDP_RECV_BUFFER_SIZE: usize = 16 * 1024;
178
179/// Outbound packet after wire encoding.
180#[derive(Clone, Debug, PartialEq, Eq)]
181pub struct OutboundPacket {
182    /// Target client.
183    pub client_id: ClientId,
184    /// Encoded bytes.
185    pub bytes: Vec<u8>,
186}
187
188/// Inbound packet before wire decoding.
189#[derive(Clone, Debug, PartialEq, Eq)]
190pub struct InboundPacket {
191    /// Known client id for `remote_addr`, if the transport has one registered.
192    pub client_id: Option<ClientId>,
193    /// Address the datagram came from.
194    pub remote_addr: SocketAddr,
195    /// Encoded bytes.
196    pub bytes: Vec<u8>,
197}
198
199/// Borrowed inbound packet backed by a transport-owned receive buffer.
200#[derive(Clone, Copy, Debug, PartialEq, Eq)]
201pub struct InboundPacketRef<'a> {
202    /// Known client id for `remote_addr`, if the transport has one registered.
203    pub client_id: Option<ClientId>,
204    /// Address the datagram came from.
205    pub remote_addr: SocketAddr,
206    /// Encoded bytes valid until the transport is mutably reused.
207    pub bytes: &'a [u8],
208}
209
210impl InboundPacketRef<'_> {
211    /// Materializes the compatible owned packet shape.
212    pub fn to_owned(self) -> InboundPacket {
213        InboundPacket {
214            client_id: self.client_id,
215            remote_addr: self.remote_addr,
216            bytes: self.bytes.to_vec(),
217        }
218    }
219}
220
221/// Outbound station-to-station packet after wire encoding.
222#[derive(Clone, Debug, PartialEq, Eq)]
223pub struct StationOutboundPacket {
224    /// Source station.
225    pub source_station: StationId,
226    /// Target station.
227    pub target_station: StationId,
228    /// Encoded bytes.
229    pub bytes: Vec<u8>,
230}
231
232/// Inbound station-to-station packet before wire decoding.
233#[derive(Clone, Debug, PartialEq, Eq)]
234pub struct StationInboundPacket {
235    /// Source station.
236    pub source_station: StationId,
237    /// Target station.
238    pub target_station: StationId,
239    /// Encoded bytes.
240    pub bytes: Vec<u8>,
241}
242
243/// Borrowed station packet backed by a transport-owned receive buffer.
244#[derive(Clone, Copy, Debug, PartialEq, Eq)]
245pub struct StationInboundPacketRef<'a> {
246    /// Source station resolved from the remote address.
247    pub source_station: StationId,
248    /// Target station owned by the receiving adapter.
249    pub target_station: StationId,
250    /// Encoded bytes valid until the transport is mutably reused.
251    pub bytes: &'a [u8],
252}
253
254impl StationInboundPacketRef<'_> {
255    /// Materializes the compatible owned packet shape.
256    pub fn to_owned(self) -> StationInboundPacket {
257        StationInboundPacket {
258            source_station: self.source_station,
259            target_station: self.target_station,
260            bytes: self.bytes.to_vec(),
261        }
262    }
263}
264
265/// Batch of outbound station-to-station packets.
266#[derive(Clone, Debug, Default, PartialEq, Eq)]
267pub struct StationPacketBatch {
268    /// Packets to send together.
269    pub packets: Vec<StationOutboundPacket>,
270}
271
272impl StationPacketBatch {
273    /// Creates an empty station packet batch.
274    pub const fn new() -> Self {
275        Self {
276            packets: Vec::new(),
277        }
278    }
279
280    /// Adds one packet to the batch.
281    pub fn push(&mut self, packet: StationOutboundPacket) {
282        self.packets.push(packet);
283    }
284
285    /// Returns packet count.
286    pub fn len(&self) -> usize {
287        self.packets.len()
288    }
289
290    /// Returns whether the batch is empty.
291    pub fn is_empty(&self) -> bool {
292        self.packets.is_empty()
293    }
294
295    /// Returns total byte count.
296    pub fn bytes_len(&self) -> usize {
297        self.packets.iter().map(|packet| packet.bytes.len()).sum()
298    }
299}
300
301/// Batch of outbound packets.
302#[derive(Clone, Debug, Default, PartialEq, Eq)]
303pub struct PacketBatch {
304    /// Packets to send together.
305    pub packets: Vec<OutboundPacket>,
306}
307
308impl PacketBatch {
309    /// Creates an empty batch.
310    pub const fn new() -> Self {
311        Self {
312            packets: Vec::new(),
313        }
314    }
315
316    /// Adds one packet to the batch.
317    pub fn push(&mut self, packet: OutboundPacket) {
318        self.packets.push(packet);
319    }
320
321    /// Returns packet count.
322    pub fn len(&self) -> usize {
323        self.packets.len()
324    }
325
326    /// Returns whether the batch is empty.
327    pub fn is_empty(&self) -> bool {
328        self.packets.is_empty()
329    }
330
331    /// Returns total byte count.
332    pub fn bytes_len(&self) -> usize {
333        self.packets.iter().map(|packet| packet.bytes.len()).sum()
334    }
335}
336
337/// Packet sink abstraction. Real network transports should implement this at
338/// batch boundaries rather than per-entity boundaries.
339pub trait TransportSink {
340    /// Transport error type.
341    type Error;
342
343    /// Sends one encoded packet.
344    fn send(&mut self, packet: OutboundPacket) -> Result<(), Self::Error>;
345
346    /// Sends a packet batch.
347    fn send_batch(&mut self, batch: PacketBatch) -> Result<(), Self::Error> {
348        for packet in batch.packets {
349            self.send(packet)?;
350        }
351        Ok(())
352    }
353}
354
355/// Non-blocking packet receive abstraction.
356pub trait TransportReceiver {
357    /// Transport error type.
358    type Error;
359
360    /// Attempts to receive one encoded packet.
361    ///
362    /// Implementations should return `Ok(None)` when no packet is currently
363    /// available instead of blocking the caller's station tick.
364    fn try_recv(&mut self) -> Result<Option<InboundPacket>, Self::Error>;
365}
366
367const PACKET_SECURITY_MAGIC: [u8; 4] = *b"SSEC";
368/// Packet security envelope header bytes before payload and tag.
369pub const PACKET_SECURITY_HEADER_BYTES: usize = 22;
370/// Default packet security payload budget aligned to the default packet budget
371/// after envelope overhead.
372pub const DEFAULT_PACKET_SECURITY_MAX_PAYLOAD_BYTES: usize =
373    (16 * 1024) - PACKET_SECURITY_HEADER_BYTES;
374/// Default maximum authentication tag bytes.
375pub const DEFAULT_PACKET_SECURITY_MAX_TAG_BYTES: usize = 128;
376/// Default replay history retained per security box.
377pub const DEFAULT_PACKET_SECURITY_REPLAY_HISTORY: usize = 4096;
378/// Default maximum packet keys tracked by one key ring.
379pub const DEFAULT_PACKET_KEY_RING_MAX_KEYS: usize = 32;
380
381/// Packet security framing configuration.
382#[derive(Clone, Copy, Debug, PartialEq, Eq)]
383pub struct PacketSecurityConfig {
384    /// Maximum encrypted/authenticated payload bytes.
385    pub max_payload_bytes: usize,
386    /// Maximum authentication tag bytes.
387    pub max_tag_bytes: usize,
388    /// Maximum accepted `(key_id, nonce)` pairs retained for replay checks.
389    pub max_replay_history: usize,
390}
391
392impl Default for PacketSecurityConfig {
393    fn default() -> Self {
394        Self {
395            max_payload_bytes: DEFAULT_PACKET_SECURITY_MAX_PAYLOAD_BYTES,
396            max_tag_bytes: DEFAULT_PACKET_SECURITY_MAX_TAG_BYTES,
397            max_replay_history: DEFAULT_PACKET_SECURITY_REPLAY_HISTORY,
398        }
399    }
400}
401
402/// Packet key lifecycle state tracked by `SectorSync` metadata.
403#[derive(Clone, Copy, Debug, PartialEq, Eq)]
404pub enum PacketKeyState {
405    /// Key can be used for sending and receiving packets.
406    Active,
407    /// Key is accepted for receiving old packets but is not selected for send.
408    Retiring,
409    /// Key is explicitly rejected.
410    Revoked,
411}
412
413impl PacketKeyState {
414    fn can_send(self) -> bool {
415        self == Self::Active
416    }
417
418    fn can_accept(self) -> bool {
419        matches!(self, Self::Active | Self::Retiring)
420    }
421}
422
423/// Metadata for one externally managed packet security key.
424///
425/// `SectorSync` never stores secret material. The descriptor only lets hot-path
426/// packet helpers choose send keys and reject stale receive keys deterministically.
427#[derive(Clone, Copy, Debug, PartialEq, Eq)]
428pub struct PacketKeyDescriptor {
429    /// External key identifier carried in packet security envelopes.
430    pub key_id: u32,
431    /// Current lifecycle state.
432    pub state: PacketKeyState,
433    /// Tick at which the embedding system created/imported the key metadata.
434    pub created_at: Tick,
435    /// First tick at which this key may be used or accepted.
436    pub activated_at: Tick,
437    /// Tick at which the key began retiring, if known.
438    pub retires_at: Option<Tick>,
439    /// First tick at which this key must be rejected and can be removed.
440    pub expires_at: Option<Tick>,
441    /// Higher values win when multiple active keys are eligible for send.
442    pub send_priority: u32,
443}
444
445impl PacketKeyDescriptor {
446    /// Creates active key metadata.
447    pub const fn active(key_id: u32, now: Tick, send_priority: u32) -> Self {
448        Self {
449            key_id,
450            state: PacketKeyState::Active,
451            created_at: now,
452            activated_at: now,
453            retires_at: None,
454            expires_at: None,
455            send_priority,
456        }
457    }
458
459    /// Returns a copy with an expiration tick.
460    #[must_use]
461    pub const fn with_expiry(mut self, expires_at: Tick) -> Self {
462        self.expires_at = Some(expires_at);
463        self
464    }
465
466    /// Returns whether the key is active for sending at `now`.
467    pub fn is_send_eligible(self, now: Tick) -> bool {
468        self.state.can_send() && self.is_activated_at(now) && !self.is_expired_at(now)
469    }
470
471    /// Returns whether the key is accepted for receiving at `now`.
472    pub fn is_accept_eligible(self, now: Tick) -> bool {
473        self.state.can_accept() && self.is_activated_at(now) && !self.is_expired_at(now)
474    }
475
476    fn is_activated_at(self, now: Tick) -> bool {
477        now >= self.activated_at
478    }
479
480    fn is_expired_at(self, now: Tick) -> bool {
481        self.expires_at.is_some_and(|expires_at| now >= expires_at)
482    }
483}
484
485/// Packet key ring configuration.
486#[derive(Clone, Copy, Debug, PartialEq, Eq)]
487pub struct PacketKeyRingConfig {
488    /// Maximum key metadata records retained in the ring.
489    pub max_keys: usize,
490}
491
492impl Default for PacketKeyRingConfig {
493    fn default() -> Self {
494        Self {
495            max_keys: DEFAULT_PACKET_KEY_RING_MAX_KEYS,
496        }
497    }
498}
499
500/// Packet key ring maintenance statistics.
501#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
502pub struct PacketKeyRingStats {
503    /// Key descriptors inserted.
504    pub keys_inserted: usize,
505    /// Key descriptors activated.
506    pub keys_activated: usize,
507    /// Key descriptors moved to receive-only retirement.
508    pub keys_retired: usize,
509    /// Key descriptors revoked.
510    pub keys_revoked: usize,
511    /// Expired key descriptors removed.
512    pub keys_expired_removed: usize,
513}
514
515/// Packet key ring policy error.
516#[derive(Clone, Copy, Debug, PartialEq, Eq)]
517pub enum PacketKeyRingError {
518    /// The configured key metadata capacity is full.
519    CapacityFull {
520        /// Configured capacity.
521        capacity: usize,
522    },
523    /// Key id already exists.
524    DuplicateKey(u32),
525    /// Key id is not known.
526    MissingKey(u32),
527    /// No active, non-expired key can be selected for send.
528    NoSendKey,
529    /// Key cannot be used for sending.
530    KeyNotSendable {
531        /// Key id.
532        key_id: u32,
533        /// Current lifecycle state.
534        state: PacketKeyState,
535    },
536    /// Key cannot be accepted for receiving.
537    KeyNotAccepted {
538        /// Key id.
539        key_id: u32,
540        /// Current lifecycle state.
541        state: PacketKeyState,
542    },
543}
544
545impl core::fmt::Display for PacketKeyRingError {
546    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
547        match self {
548            Self::CapacityFull { capacity } => {
549                write!(f, "packet key ring capacity full: capacity {capacity}")
550            }
551            Self::DuplicateKey(key_id) => write!(f, "packet key {key_id} already exists"),
552            Self::MissingKey(key_id) => write!(f, "packet key {key_id} is missing"),
553            Self::NoSendKey => f.write_str("no packet key is eligible for send"),
554            Self::KeyNotSendable { key_id, state } => {
555                write!(f, "packet key {key_id} is not sendable in state {state:?}")
556            }
557            Self::KeyNotAccepted { key_id, state } => {
558                write!(f, "packet key {key_id} is not accepted in state {state:?}")
559            }
560        }
561    }
562}
563
564impl std::error::Error for PacketKeyRingError {}
565
566/// Bounded packet key lifecycle metadata.
567#[derive(Clone, Debug)]
568pub struct PacketKeyRing {
569    config: PacketKeyRingConfig,
570    keys: BTreeMap<u32, PacketKeyDescriptor>,
571    stats: PacketKeyRingStats,
572}
573
574impl PacketKeyRing {
575    /// Creates an empty key ring.
576    pub fn new(config: PacketKeyRingConfig) -> Self {
577        Self {
578            config,
579            keys: BTreeMap::new(),
580            stats: PacketKeyRingStats::default(),
581        }
582    }
583
584    /// Creates an empty key ring with default limits.
585    pub fn with_defaults() -> Self {
586        Self::new(PacketKeyRingConfig::default())
587    }
588
589    /// Returns configuration.
590    pub const fn config(&self) -> PacketKeyRingConfig {
591        self.config
592    }
593
594    /// Returns key metadata count.
595    pub fn len(&self) -> usize {
596        self.keys.len()
597    }
598
599    /// Returns whether the ring has no key metadata.
600    pub fn is_empty(&self) -> bool {
601        self.keys.is_empty()
602    }
603
604    /// Returns statistics.
605    pub const fn stats(&self) -> PacketKeyRingStats {
606        self.stats
607    }
608
609    /// Returns one key descriptor.
610    pub fn get(&self, key_id: u32) -> Option<PacketKeyDescriptor> {
611        self.keys.get(&key_id).copied()
612    }
613
614    /// Iterates key descriptors in key-id order.
615    pub fn iter(&self) -> impl Iterator<Item = &PacketKeyDescriptor> {
616        self.keys.values()
617    }
618
619    /// Inserts a descriptor if capacity and uniqueness allow it.
620    pub fn insert(&mut self, descriptor: PacketKeyDescriptor) -> Result<(), PacketKeyRingError> {
621        if self.keys.contains_key(&descriptor.key_id) {
622            return Err(PacketKeyRingError::DuplicateKey(descriptor.key_id));
623        }
624        if self.keys.len() >= self.config.max_keys {
625            return Err(PacketKeyRingError::CapacityFull {
626                capacity: self.config.max_keys,
627            });
628        }
629        self.keys.insert(descriptor.key_id, descriptor);
630        self.stats.keys_inserted = self.stats.keys_inserted.saturating_add(1);
631        Ok(())
632    }
633
634    /// Inserts active key metadata.
635    pub fn insert_active(
636        &mut self,
637        key_id: u32,
638        now: Tick,
639        send_priority: u32,
640    ) -> Result<(), PacketKeyRingError> {
641        self.insert(PacketKeyDescriptor::active(key_id, now, send_priority))
642    }
643
644    /// Marks a key active and send-eligible from `activated_at`.
645    pub fn activate(&mut self, key_id: u32, activated_at: Tick) -> Result<(), PacketKeyRingError> {
646        let descriptor = self
647            .keys
648            .get_mut(&key_id)
649            .ok_or(PacketKeyRingError::MissingKey(key_id))?;
650        descriptor.state = PacketKeyState::Active;
651        descriptor.activated_at = activated_at;
652        descriptor.retires_at = None;
653        self.stats.keys_activated = self.stats.keys_activated.saturating_add(1);
654        Ok(())
655    }
656
657    /// Marks a key receive-only for retirement.
658    pub fn retire(&mut self, key_id: u32, retires_at: Tick) -> Result<(), PacketKeyRingError> {
659        let descriptor = self
660            .keys
661            .get_mut(&key_id)
662            .ok_or(PacketKeyRingError::MissingKey(key_id))?;
663        descriptor.state = PacketKeyState::Retiring;
664        descriptor.retires_at = Some(retires_at);
665        self.stats.keys_retired = self.stats.keys_retired.saturating_add(1);
666        Ok(())
667    }
668
669    /// Marks a key rejected.
670    pub fn revoke(&mut self, key_id: u32) -> Result<(), PacketKeyRingError> {
671        let descriptor = self
672            .keys
673            .get_mut(&key_id)
674            .ok_or(PacketKeyRingError::MissingKey(key_id))?;
675        descriptor.state = PacketKeyState::Revoked;
676        self.stats.keys_revoked = self.stats.keys_revoked.saturating_add(1);
677        Ok(())
678    }
679
680    /// Updates the expiration tick for one key.
681    pub fn set_expiry(
682        &mut self,
683        key_id: u32,
684        expires_at: Option<Tick>,
685    ) -> Result<(), PacketKeyRingError> {
686        let descriptor = self
687            .keys
688            .get_mut(&key_id)
689            .ok_or(PacketKeyRingError::MissingKey(key_id))?;
690        descriptor.expires_at = expires_at;
691        Ok(())
692    }
693
694    /// Removes expired descriptors and returns the removed count.
695    pub fn remove_expired(&mut self, now: Tick) -> usize {
696        let before = self.keys.len();
697        self.keys
698            .retain(|_, descriptor| !descriptor.is_expired_at(now));
699        let removed = before.saturating_sub(self.keys.len());
700        self.stats.keys_expired_removed = self.stats.keys_expired_removed.saturating_add(removed);
701        removed
702    }
703
704    /// Selects the best active key for sending at `now`.
705    pub fn select_send_key(&self, now: Tick) -> Result<PacketKeyDescriptor, PacketKeyRingError> {
706        self.keys
707            .values()
708            .copied()
709            .filter(|descriptor| descriptor.is_send_eligible(now))
710            .max_by_key(|descriptor| {
711                (
712                    descriptor.send_priority,
713                    descriptor.activated_at,
714                    descriptor.key_id,
715                )
716            })
717            .ok_or(PacketKeyRingError::NoSendKey)
718    }
719
720    /// Checks that `key_id` is accepted for receiving at `now`.
721    pub fn accept_key(
722        &self,
723        key_id: u32,
724        now: Tick,
725    ) -> Result<PacketKeyDescriptor, PacketKeyRingError> {
726        let descriptor = self
727            .keys
728            .get(&key_id)
729            .copied()
730            .ok_or(PacketKeyRingError::MissingKey(key_id))?;
731        if descriptor.is_accept_eligible(now) {
732            Ok(descriptor)
733        } else {
734            Err(PacketKeyRingError::KeyNotAccepted {
735                key_id,
736                state: descriptor.state,
737            })
738        }
739    }
740}
741
742impl Default for PacketKeyRing {
743    fn default() -> Self {
744        Self::with_defaults()
745    }
746}
747
748/// Borrowed packet security envelope decoded directly from wire bytes.
749#[derive(Clone, Copy, Debug, PartialEq, Eq)]
750pub struct PacketSecurityEnvelopeRef<'a> {
751    /// External key identifier chosen by the embedder.
752    pub key_id: u32,
753    /// Nonce scoped to `key_id`.
754    pub nonce: u64,
755    /// Borrowed ciphertext or plaintext payload.
756    pub payload: &'a [u8],
757    /// Borrowed authentication tag.
758    pub tag: &'a [u8],
759}
760
761impl<'a> PacketSecurityEnvelopeRef<'a> {
762    /// Decodes and validates an envelope without copying payload or tag bytes.
763    pub fn decode(
764        config: PacketSecurityConfig,
765        input: &'a [u8],
766    ) -> Result<Self, PacketSecurityDecodeError> {
767        let mut cursor = SecurityCursor::new(input);
768        let magic = cursor.read_array::<4>()?;
769        if magic != PACKET_SECURITY_MAGIC {
770            return Err(PacketSecurityDecodeError::BadMagic);
771        }
772        let key_id = cursor.read_u32()?;
773        let nonce = cursor.read_u64()?;
774        let payload_len = cursor.read_u32()? as usize;
775        let tag_len = cursor.read_u16()? as usize;
776        if payload_len > config.max_payload_bytes {
777            return Err(PacketSecurityDecodeError::PayloadTooLarge {
778                budget: config.max_payload_bytes,
779                actual: payload_len,
780            });
781        }
782        if tag_len > config.max_tag_bytes {
783            return Err(PacketSecurityDecodeError::TagTooLarge {
784                budget: config.max_tag_bytes,
785                actual: tag_len,
786            });
787        }
788        let payload = cursor.read_slice(payload_len)?;
789        let tag = cursor.read_slice(tag_len)?;
790        cursor.finish()?;
791        Ok(Self {
792            key_id,
793            nonce,
794            payload,
795            tag,
796        })
797    }
798}
799
800/// Owned packet security envelope.
801#[derive(Clone, Debug, PartialEq, Eq)]
802pub struct PacketSecurityEnvelope {
803    /// External key identifier chosen by the embedder.
804    pub key_id: u32,
805    /// Nonce scoped to `key_id`.
806    pub nonce: u64,
807    /// Ciphertext or plaintext payload, depending on the configured cipher.
808    pub payload: Vec<u8>,
809    /// Authentication tag produced by the configured authenticator.
810    pub tag: Vec<u8>,
811}
812
813impl PacketSecurityEnvelope {
814    /// Appends an envelope from borrowed payload and tag slices.
815    pub fn encode_parts(
816        config: PacketSecurityConfig,
817        key_id: u32,
818        nonce: u64,
819        payload: &[u8],
820        tag: &[u8],
821        out: &mut Vec<u8>,
822    ) -> Result<(), PacketSecurityEncodeError> {
823        if payload.len() > config.max_payload_bytes {
824            return Err(PacketSecurityEncodeError::PayloadTooLarge {
825                budget: config.max_payload_bytes,
826                actual: payload.len(),
827            });
828        }
829        if tag.len() > config.max_tag_bytes {
830            return Err(PacketSecurityEncodeError::TagTooLarge {
831                budget: config.max_tag_bytes,
832                actual: tag.len(),
833            });
834        }
835
836        let payload_len = u32::try_from(payload.len()).map_err(|_| {
837            PacketSecurityEncodeError::PayloadTooLarge {
838                budget: config.max_payload_bytes,
839                actual: payload.len(),
840            }
841        })?;
842        let tag_len =
843            u16::try_from(tag.len()).map_err(|_| PacketSecurityEncodeError::TagTooLarge {
844                budget: config.max_tag_bytes,
845                actual: tag.len(),
846            })?;
847        out.extend_from_slice(&PACKET_SECURITY_MAGIC);
848        out.extend_from_slice(&key_id.to_le_bytes());
849        out.extend_from_slice(&nonce.to_le_bytes());
850        out.extend_from_slice(&payload_len.to_le_bytes());
851        out.extend_from_slice(&tag_len.to_le_bytes());
852        out.extend_from_slice(payload);
853        out.extend_from_slice(tag);
854        Ok(())
855    }
856
857    /// Encodes a packet security envelope.
858    pub fn encode(
859        &self,
860        config: PacketSecurityConfig,
861        out: &mut Vec<u8>,
862    ) -> Result<(), PacketSecurityEncodeError> {
863        Self::encode_parts(
864            config,
865            self.key_id,
866            self.nonce,
867            &self.payload,
868            &self.tag,
869            out,
870        )
871    }
872
873    /// Decodes a packet security envelope.
874    pub fn decode(
875        config: PacketSecurityConfig,
876        input: &[u8],
877    ) -> Result<Self, PacketSecurityDecodeError> {
878        let envelope = PacketSecurityEnvelopeRef::decode(config, input)?;
879        Ok(Self {
880            key_id: envelope.key_id,
881            nonce: envelope.nonce,
882            payload: envelope.payload.to_vec(),
883            tag: envelope.tag.to_vec(),
884        })
885    }
886}
887
888/// Packet security encode error.
889#[derive(Clone, Copy, Debug, PartialEq, Eq)]
890pub enum PacketSecurityEncodeError {
891    /// Payload exceeded configured budget.
892    PayloadTooLarge {
893        /// Configured byte budget.
894        budget: usize,
895        /// Actual byte count.
896        actual: usize,
897    },
898    /// Authentication tag exceeded configured budget.
899    TagTooLarge {
900        /// Configured byte budget.
901        budget: usize,
902        /// Actual byte count.
903        actual: usize,
904    },
905}
906
907impl core::fmt::Display for PacketSecurityEncodeError {
908    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
909        match self {
910            Self::PayloadTooLarge { budget, actual } => write!(
911                f,
912                "packet security payload exceeded byte budget: budget {budget}, actual {actual}"
913            ),
914            Self::TagTooLarge { budget, actual } => write!(
915                f,
916                "packet security tag exceeded byte budget: budget {budget}, actual {actual}"
917            ),
918        }
919    }
920}
921
922impl std::error::Error for PacketSecurityEncodeError {}
923
924/// Packet security decode error.
925#[derive(Clone, Debug, PartialEq, Eq)]
926pub enum PacketSecurityDecodeError {
927    /// Frame magic did not match.
928    BadMagic,
929    /// Frame ended before all fields were available.
930    Truncated {
931        /// Required bytes.
932        needed: usize,
933        /// Available bytes.
934        available: usize,
935    },
936    /// Payload exceeded configured budget.
937    PayloadTooLarge {
938        /// Configured byte budget.
939        budget: usize,
940        /// Actual byte count.
941        actual: usize,
942    },
943    /// Authentication tag exceeded configured budget.
944    TagTooLarge {
945        /// Configured byte budget.
946        budget: usize,
947        /// Actual byte count.
948        actual: usize,
949    },
950    /// Frame had trailing bytes after a complete envelope.
951    TrailingBytes(usize),
952}
953
954impl core::fmt::Display for PacketSecurityDecodeError {
955    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
956        match self {
957            Self::BadMagic => f.write_str("bad packet security envelope magic"),
958            Self::Truncated { needed, available } => {
959                write!(
960                    f,
961                    "truncated packet security envelope: needed {needed}, available {available}"
962                )
963            }
964            Self::PayloadTooLarge { budget, actual } => write!(
965                f,
966                "packet security payload exceeded byte budget: budget {budget}, actual {actual}"
967            ),
968            Self::TagTooLarge { budget, actual } => write!(
969                f,
970                "packet security tag exceeded byte budget: budget {budget}, actual {actual}"
971            ),
972            Self::TrailingBytes(bytes) => {
973                write!(f, "packet security envelope has {bytes} trailing bytes")
974            }
975        }
976    }
977}
978
979impl std::error::Error for PacketSecurityDecodeError {}
980
981/// Packet authenticator hook. Embedders should provide a real MAC/signature
982/// implementation and key management outside `SectorSync`.
983pub trait PacketAuthenticator {
984    /// Authenticator error type.
985    type Error;
986
987    /// Produces an authentication tag over `key_id`, `nonce`, and `payload`.
988    fn sign(
989        &mut self,
990        key_id: u32,
991        nonce: u64,
992        payload: &[u8],
993        out: &mut Vec<u8>,
994    ) -> Result<(), Self::Error>;
995
996    /// Verifies an authentication tag over `key_id`, `nonce`, and `payload`.
997    fn verify(
998        &mut self,
999        key_id: u32,
1000        nonce: u64,
1001        payload: &[u8],
1002        tag: &[u8],
1003    ) -> Result<bool, Self::Error>;
1004}
1005
1006/// Packet cipher hook. Embedders should provide real encryption outside
1007/// `SectorSync` when confidentiality is needed.
1008pub trait PacketCipher {
1009    /// Cipher error type.
1010    type Error;
1011
1012    /// Seals a payload in place before authentication.
1013    fn seal(&mut self, key_id: u32, nonce: u64, payload: &mut Vec<u8>) -> Result<(), Self::Error>;
1014
1015    /// Opens a payload in place after authentication.
1016    fn open(&mut self, key_id: u32, nonce: u64, payload: &mut Vec<u8>) -> Result<(), Self::Error>;
1017}
1018
1019/// Explicit plaintext cipher for tests and integrations that only need
1020/// authentication framing.
1021#[derive(Clone, Copy, Debug, Default)]
1022pub struct PlaintextPacketCipher;
1023
1024impl PacketCipher for PlaintextPacketCipher {
1025    type Error = core::convert::Infallible;
1026
1027    fn seal(
1028        &mut self,
1029        _key_id: u32,
1030        _nonce: u64,
1031        _payload: &mut Vec<u8>,
1032    ) -> Result<(), Self::Error> {
1033        Ok(())
1034    }
1035
1036    fn open(
1037        &mut self,
1038        _key_id: u32,
1039        _nonce: u64,
1040        _payload: &mut Vec<u8>,
1041    ) -> Result<(), Self::Error> {
1042        Ok(())
1043    }
1044}
1045
1046/// Bounded replay window for security envelopes.
1047#[derive(Clone, Debug)]
1048pub struct PacketReplayWindow {
1049    max_seen: usize,
1050    seen: BoundedLookupSet<(u32, u64)>,
1051    order: VecDeque<(u32, u64)>,
1052}
1053
1054impl PacketReplayWindow {
1055    /// Creates an empty replay window.
1056    pub fn new(max_seen: usize) -> Self {
1057        Self {
1058            max_seen,
1059            seen: BoundedLookupSet::new(max_seen),
1060            order: VecDeque::new(),
1061        }
1062    }
1063
1064    /// Returns retained nonce count.
1065    pub fn len(&self) -> usize {
1066        self.seen.len()
1067    }
1068
1069    /// Returns whether no nonces are retained.
1070    pub fn is_empty(&self) -> bool {
1071        self.seen.is_empty()
1072    }
1073
1074    /// Returns whether a `(key_id, nonce)` pair was already accepted.
1075    pub fn contains(&self, key_id: u32, nonce: u64) -> bool {
1076        self.seen.contains(&(key_id, nonce))
1077    }
1078
1079    /// Records a `(key_id, nonce)` pair if it has not been seen.
1080    pub fn accept(&mut self, key_id: u32, nonce: u64) -> bool {
1081        if self.max_seen == 0 {
1082            return true;
1083        }
1084
1085        let key = (key_id, nonce);
1086        if self.seen.contains(&key) {
1087            return false;
1088        }
1089        self.seen.insert(key);
1090        self.order.push_back(key);
1091        while self.order.len() > self.max_seen {
1092            if let Some(old) = self.order.pop_front() {
1093                self.seen.remove(&old);
1094            }
1095        }
1096        true
1097    }
1098}
1099
1100impl Default for PacketReplayWindow {
1101    fn default() -> Self {
1102        Self::new(DEFAULT_PACKET_SECURITY_REPLAY_HISTORY)
1103    }
1104}
1105
1106/// Packet security box statistics.
1107#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
1108pub struct PacketSecurityStats {
1109    /// Packets sealed.
1110    pub sealed: usize,
1111    /// Packets opened.
1112    pub opened: usize,
1113    /// Packets rejected by key lifecycle policy.
1114    pub key_rejected: usize,
1115    /// Packets rejected because authentication failed.
1116    pub auth_failed: usize,
1117    /// Packets rejected because the nonce was replayed.
1118    pub replay_rejected: usize,
1119}
1120
1121/// Caller-owned reusable payload and tag storage for packet sealing.
1122#[derive(Clone, Debug, Default)]
1123pub struct PacketSecurityScratch {
1124    sealed_payload: Vec<u8>,
1125    tag: Vec<u8>,
1126}
1127
1128impl PacketSecurityScratch {
1129    /// Creates empty sealing storage.
1130    pub fn new() -> Self {
1131        Self::default()
1132    }
1133
1134    /// Creates sealing storage with explicit payload and tag capacities.
1135    pub fn with_capacity(payload_bytes: usize, tag_bytes: usize) -> Self {
1136        Self {
1137            sealed_payload: Vec::with_capacity(payload_bytes),
1138            tag: Vec::with_capacity(tag_bytes),
1139        }
1140    }
1141
1142    /// Encrypted-payload bytes retained without growing the scratch buffer.
1143    pub fn retained_payload_capacity(&self) -> usize {
1144        self.sealed_payload.capacity()
1145    }
1146
1147    /// Authentication-tag bytes retained without growing the scratch buffer.
1148    pub fn retained_tag_capacity(&self) -> usize {
1149        self.tag.capacity()
1150    }
1151}
1152
1153/// Caller-owned reusable payload storage for packet opening.
1154#[derive(Clone, Debug, Default)]
1155pub struct PacketSecurityOpenScratch {
1156    payload: Vec<u8>,
1157}
1158
1159impl PacketSecurityOpenScratch {
1160    /// Creates empty opening storage.
1161    pub fn new() -> Self {
1162        Self::default()
1163    }
1164
1165    /// Creates opening storage with explicit payload capacity.
1166    pub fn with_capacity(payload_bytes: usize) -> Self {
1167        Self {
1168            payload: Vec::with_capacity(payload_bytes),
1169        }
1170    }
1171
1172    /// Opened-payload bytes retained without growing the scratch buffer.
1173    pub fn retained_payload_capacity(&self) -> usize {
1174        self.payload.capacity()
1175    }
1176}
1177
1178/// Borrowed result of opening one packet into caller-owned scratch.
1179#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1180pub struct PacketSecurityOpenView<'a> {
1181    /// External key identifier carried by the envelope.
1182    pub key_id: u32,
1183    /// Accepted nonce carried by the envelope.
1184    pub nonce: u64,
1185    /// Opened payload, valid until the scratch is mutably reused.
1186    pub payload: &'a [u8],
1187}
1188
1189/// Error produced by packet security helpers.
1190#[derive(Debug)]
1191pub enum PacketSecurityError<A, C> {
1192    /// Envelope encode failed.
1193    Encode(PacketSecurityEncodeError),
1194    /// Envelope decode failed.
1195    Decode(PacketSecurityDecodeError),
1196    /// Authenticator failed.
1197    Authenticator(A),
1198    /// Cipher failed.
1199    Cipher(C),
1200    /// Key lifecycle policy rejected the packet or send attempt.
1201    Key(PacketKeyRingError),
1202    /// Authenticator returned false.
1203    AuthenticationFailed {
1204        /// Key id.
1205        key_id: u32,
1206        /// Nonce.
1207        nonce: u64,
1208    },
1209    /// Replay window rejected the nonce.
1210    Replay {
1211        /// Key id.
1212        key_id: u32,
1213        /// Nonce.
1214        nonce: u64,
1215    },
1216}
1217
1218impl<A: core::fmt::Display, C: core::fmt::Display> core::fmt::Display
1219    for PacketSecurityError<A, C>
1220{
1221    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1222        match self {
1223            Self::Encode(error) => write!(f, "{error}"),
1224            Self::Decode(error) => write!(f, "{error}"),
1225            Self::Authenticator(error) => write!(f, "{error}"),
1226            Self::Cipher(error) => write!(f, "{error}"),
1227            Self::Key(error) => write!(f, "{error}"),
1228            Self::AuthenticationFailed { key_id, nonce } => write!(
1229                f,
1230                "packet authentication failed for key {key_id} nonce {nonce}"
1231            ),
1232            Self::Replay { key_id, nonce } => {
1233                write!(f, "packet replay rejected for key {key_id} nonce {nonce}")
1234            }
1235        }
1236    }
1237}
1238
1239impl<A, C> std::error::Error for PacketSecurityError<A, C>
1240where
1241    A: std::error::Error + 'static,
1242    C: std::error::Error + 'static,
1243{
1244    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
1245        match self {
1246            Self::Encode(error) => Some(error),
1247            Self::Decode(error) => Some(error),
1248            Self::Authenticator(error) => Some(error),
1249            Self::Cipher(error) => Some(error),
1250            Self::Key(error) => Some(error),
1251            Self::AuthenticationFailed { .. } | Self::Replay { .. } => None,
1252        }
1253    }
1254}
1255
1256/// Bounded packet security helper combining nonce allocation, authentication,
1257/// optional encryption, and replay checks.
1258#[derive(Clone, Debug)]
1259pub struct PacketSecurityBox<A, C> {
1260    config: PacketSecurityConfig,
1261    authenticator: A,
1262    cipher: C,
1263    replay: PacketReplayWindow,
1264    next_nonce: BTreeMap<u32, u64>,
1265    stats: PacketSecurityStats,
1266}
1267
1268impl<A, C> PacketSecurityBox<A, C> {
1269    /// Creates a packet security box.
1270    pub fn new(config: PacketSecurityConfig, authenticator: A, cipher: C) -> Self {
1271        Self {
1272            config,
1273            authenticator,
1274            cipher,
1275            replay: PacketReplayWindow::new(config.max_replay_history),
1276            next_nonce: BTreeMap::new(),
1277            stats: PacketSecurityStats::default(),
1278        }
1279    }
1280
1281    /// Returns configuration.
1282    pub const fn config(&self) -> PacketSecurityConfig {
1283        self.config
1284    }
1285
1286    /// Returns statistics.
1287    pub const fn stats(&self) -> PacketSecurityStats {
1288        self.stats
1289    }
1290
1291    /// Borrows the replay window.
1292    pub const fn replay(&self) -> &PacketReplayWindow {
1293        &self.replay
1294    }
1295
1296    /// Consumes the box and returns its components.
1297    pub fn into_inner(self) -> (A, C, PacketReplayWindow) {
1298        (self.authenticator, self.cipher, self.replay)
1299    }
1300}
1301
1302impl<A, C> PacketSecurityBox<A, C>
1303where
1304    A: PacketAuthenticator,
1305    C: PacketCipher,
1306{
1307    /// Seals and encodes one payload.
1308    pub fn seal(
1309        &mut self,
1310        key_id: u32,
1311        payload: &[u8],
1312    ) -> Result<Vec<u8>, PacketSecurityError<A::Error, C::Error>> {
1313        let nonce = self.allocate_nonce(key_id);
1314        self.seal_with_nonce(key_id, nonce, payload)
1315    }
1316
1317    /// Selects a send key from `key_ring`, then seals and encodes one payload.
1318    pub fn seal_with_key_ring(
1319        &mut self,
1320        key_ring: &PacketKeyRing,
1321        payload: &[u8],
1322        now: Tick,
1323    ) -> Result<Vec<u8>, PacketSecurityError<A::Error, C::Error>> {
1324        let descriptor = key_ring
1325            .select_send_key(now)
1326            .map_err(PacketSecurityError::Key)?;
1327        self.seal(descriptor.key_id, payload)
1328    }
1329
1330    /// Seals and encodes one payload with an explicit nonce.
1331    pub fn seal_with_nonce(
1332        &mut self,
1333        key_id: u32,
1334        nonce: u64,
1335        payload: &[u8],
1336    ) -> Result<Vec<u8>, PacketSecurityError<A::Error, C::Error>> {
1337        let mut scratch = PacketSecurityScratch::with_capacity(
1338            payload.len().min(self.config.max_payload_bytes),
1339            0,
1340        );
1341        self.prepare_seal(key_id, nonce, payload, &mut scratch)?;
1342        let mut out = Vec::with_capacity(
1343            PACKET_SECURITY_HEADER_BYTES
1344                .saturating_add(scratch.sealed_payload.len())
1345                .saturating_add(scratch.tag.len()),
1346        );
1347        PacketSecurityEnvelope::encode_parts(
1348            self.config,
1349            key_id,
1350            nonce,
1351            &scratch.sealed_payload,
1352            &scratch.tag,
1353            &mut out,
1354        )
1355        .map_err(PacketSecurityError::Encode)?;
1356        self.stats.sealed = self.stats.sealed.saturating_add(1);
1357        Ok(out)
1358    }
1359
1360    /// Seals and appends one payload using an allocated nonce and reusable storage.
1361    pub fn seal_into(
1362        &mut self,
1363        key_id: u32,
1364        payload: &[u8],
1365        out: &mut Vec<u8>,
1366        scratch: &mut PacketSecurityScratch,
1367    ) -> Result<u64, PacketSecurityError<A::Error, C::Error>> {
1368        let nonce = self.allocate_nonce(key_id);
1369        self.seal_with_nonce_into(key_id, nonce, payload, out, scratch)?;
1370        Ok(nonce)
1371    }
1372
1373    /// Selects a key and appends one sealed payload using reusable storage.
1374    pub fn seal_with_key_ring_into(
1375        &mut self,
1376        key_ring: &PacketKeyRing,
1377        payload: &[u8],
1378        now: Tick,
1379        out: &mut Vec<u8>,
1380        scratch: &mut PacketSecurityScratch,
1381    ) -> Result<(u32, u64), PacketSecurityError<A::Error, C::Error>> {
1382        let descriptor = key_ring
1383            .select_send_key(now)
1384            .map_err(PacketSecurityError::Key)?;
1385        let nonce = self.seal_into(descriptor.key_id, payload, out, scratch)?;
1386        Ok((descriptor.key_id, nonce))
1387    }
1388
1389    /// Seals and appends one payload with an explicit nonce and reusable storage.
1390    pub fn seal_with_nonce_into(
1391        &mut self,
1392        key_id: u32,
1393        nonce: u64,
1394        payload: &[u8],
1395        out: &mut Vec<u8>,
1396        scratch: &mut PacketSecurityScratch,
1397    ) -> Result<(), PacketSecurityError<A::Error, C::Error>> {
1398        self.prepare_seal(key_id, nonce, payload, scratch)?;
1399        PacketSecurityEnvelope::encode_parts(
1400            self.config,
1401            key_id,
1402            nonce,
1403            &scratch.sealed_payload,
1404            &scratch.tag,
1405            out,
1406        )
1407        .map_err(PacketSecurityError::Encode)?;
1408        self.stats.sealed = self.stats.sealed.saturating_add(1);
1409        Ok(())
1410    }
1411
1412    fn prepare_seal(
1413        &mut self,
1414        key_id: u32,
1415        nonce: u64,
1416        payload: &[u8],
1417        scratch: &mut PacketSecurityScratch,
1418    ) -> Result<(), PacketSecurityError<A::Error, C::Error>> {
1419        if payload.len() > self.config.max_payload_bytes {
1420            return Err(PacketSecurityError::Encode(
1421                PacketSecurityEncodeError::PayloadTooLarge {
1422                    budget: self.config.max_payload_bytes,
1423                    actual: payload.len(),
1424                },
1425            ));
1426        }
1427        scratch.sealed_payload.clear();
1428        scratch.sealed_payload.extend_from_slice(payload);
1429        self.cipher
1430            .seal(key_id, nonce, &mut scratch.sealed_payload)
1431            .map_err(PacketSecurityError::Cipher)?;
1432        scratch.tag.clear();
1433        self.authenticator
1434            .sign(key_id, nonce, &scratch.sealed_payload, &mut scratch.tag)
1435            .map_err(PacketSecurityError::Authenticator)?;
1436        Ok(())
1437    }
1438
1439    /// Decodes, authenticates, replay-checks, and opens one payload.
1440    pub fn open(
1441        &mut self,
1442        input: &[u8],
1443    ) -> Result<Vec<u8>, PacketSecurityError<A::Error, C::Error>> {
1444        let envelope = PacketSecurityEnvelopeRef::decode(self.config, input)
1445            .map_err(PacketSecurityError::Decode)?;
1446        self.open_borrowed_owned(envelope)
1447    }
1448
1449    /// Decodes an envelope, validates its key against `key_ring`, then opens it.
1450    pub fn open_with_key_ring(
1451        &mut self,
1452        key_ring: &PacketKeyRing,
1453        input: &[u8],
1454        now: Tick,
1455    ) -> Result<Vec<u8>, PacketSecurityError<A::Error, C::Error>> {
1456        let envelope = PacketSecurityEnvelopeRef::decode(self.config, input)
1457            .map_err(PacketSecurityError::Decode)?;
1458        if let Err(error) = key_ring.accept_key(envelope.key_id, now) {
1459            self.stats.key_rejected = self.stats.key_rejected.saturating_add(1);
1460            return Err(PacketSecurityError::Key(error));
1461        }
1462        self.open_borrowed_owned(envelope)
1463    }
1464
1465    /// Opens one envelope into caller-owned reusable payload storage.
1466    pub fn open_with_scratch<'a>(
1467        &mut self,
1468        input: &[u8],
1469        scratch: &'a mut PacketSecurityOpenScratch,
1470    ) -> Result<PacketSecurityOpenView<'a>, PacketSecurityError<A::Error, C::Error>> {
1471        let envelope = PacketSecurityEnvelopeRef::decode(self.config, input)
1472            .map_err(PacketSecurityError::Decode)?;
1473        self.open_borrowed_with_scratch(envelope, scratch)
1474    }
1475
1476    /// Validates a key-ring entry and opens into caller-owned reusable storage.
1477    pub fn open_with_key_ring_and_scratch<'a>(
1478        &mut self,
1479        key_ring: &PacketKeyRing,
1480        input: &[u8],
1481        now: Tick,
1482        scratch: &'a mut PacketSecurityOpenScratch,
1483    ) -> Result<PacketSecurityOpenView<'a>, PacketSecurityError<A::Error, C::Error>> {
1484        let envelope = PacketSecurityEnvelopeRef::decode(self.config, input)
1485            .map_err(PacketSecurityError::Decode)?;
1486        if let Err(error) = key_ring.accept_key(envelope.key_id, now) {
1487            self.stats.key_rejected = self.stats.key_rejected.saturating_add(1);
1488            return Err(PacketSecurityError::Key(error));
1489        }
1490        self.open_borrowed_with_scratch(envelope, scratch)
1491    }
1492
1493    fn open_borrowed_owned(
1494        &mut self,
1495        envelope: PacketSecurityEnvelopeRef<'_>,
1496    ) -> Result<Vec<u8>, PacketSecurityError<A::Error, C::Error>> {
1497        self.verify_and_accept(envelope)?;
1498        let mut payload = envelope.payload.to_vec();
1499        self.cipher
1500            .open(envelope.key_id, envelope.nonce, &mut payload)
1501            .map_err(PacketSecurityError::Cipher)?;
1502        self.stats.opened = self.stats.opened.saturating_add(1);
1503        Ok(payload)
1504    }
1505
1506    fn open_borrowed_with_scratch<'a>(
1507        &mut self,
1508        envelope: PacketSecurityEnvelopeRef<'_>,
1509        scratch: &'a mut PacketSecurityOpenScratch,
1510    ) -> Result<PacketSecurityOpenView<'a>, PacketSecurityError<A::Error, C::Error>> {
1511        self.verify_and_accept(envelope)?;
1512        scratch.payload.clear();
1513        scratch.payload.extend_from_slice(envelope.payload);
1514        self.cipher
1515            .open(envelope.key_id, envelope.nonce, &mut scratch.payload)
1516            .map_err(PacketSecurityError::Cipher)?;
1517        self.stats.opened = self.stats.opened.saturating_add(1);
1518        Ok(PacketSecurityOpenView {
1519            key_id: envelope.key_id,
1520            nonce: envelope.nonce,
1521            payload: &scratch.payload,
1522        })
1523    }
1524
1525    fn verify_and_accept(
1526        &mut self,
1527        envelope: PacketSecurityEnvelopeRef<'_>,
1528    ) -> Result<(), PacketSecurityError<A::Error, C::Error>> {
1529        let verified = self
1530            .authenticator
1531            .verify(
1532                envelope.key_id,
1533                envelope.nonce,
1534                envelope.payload,
1535                envelope.tag,
1536            )
1537            .map_err(PacketSecurityError::Authenticator)?;
1538        if !verified {
1539            self.stats.auth_failed = self.stats.auth_failed.saturating_add(1);
1540            return Err(PacketSecurityError::AuthenticationFailed {
1541                key_id: envelope.key_id,
1542                nonce: envelope.nonce,
1543            });
1544        }
1545        if !self.replay.accept(envelope.key_id, envelope.nonce) {
1546            self.stats.replay_rejected = self.stats.replay_rejected.saturating_add(1);
1547            return Err(PacketSecurityError::Replay {
1548                key_id: envelope.key_id,
1549                nonce: envelope.nonce,
1550            });
1551        }
1552        Ok(())
1553    }
1554
1555    fn allocate_nonce(&mut self, key_id: u32) -> u64 {
1556        let next = self.next_nonce.entry(key_id).or_insert(1);
1557        let nonce = *next;
1558        *next = next.saturating_add(1);
1559        nonce
1560    }
1561}
1562
1563struct SecurityCursor<'a> {
1564    input: &'a [u8],
1565    offset: usize,
1566}
1567
1568impl<'a> SecurityCursor<'a> {
1569    const fn new(input: &'a [u8]) -> Self {
1570        Self { input, offset: 0 }
1571    }
1572
1573    fn read_u16(&mut self) -> Result<u16, PacketSecurityDecodeError> {
1574        let bytes = self.read_array::<2>()?;
1575        Ok(u16::from_le_bytes(bytes))
1576    }
1577
1578    fn read_u32(&mut self) -> Result<u32, PacketSecurityDecodeError> {
1579        let bytes = self.read_array::<4>()?;
1580        Ok(u32::from_le_bytes(bytes))
1581    }
1582
1583    fn read_u64(&mut self) -> Result<u64, PacketSecurityDecodeError> {
1584        let bytes = self.read_array::<8>()?;
1585        Ok(u64::from_le_bytes(bytes))
1586    }
1587
1588    fn read_array<const N: usize>(&mut self) -> Result<[u8; N], PacketSecurityDecodeError> {
1589        self.require(N)?;
1590        let mut out = [0_u8; N];
1591        out.copy_from_slice(&self.input[self.offset..self.offset + N]);
1592        self.offset += N;
1593        Ok(out)
1594    }
1595
1596    fn read_slice(&mut self, len: usize) -> Result<&'a [u8], PacketSecurityDecodeError> {
1597        self.require(len)?;
1598        let bytes = &self.input[self.offset..self.offset + len];
1599        self.offset += len;
1600        Ok(bytes)
1601    }
1602
1603    fn require(&self, count: usize) -> Result<(), PacketSecurityDecodeError> {
1604        let needed = self.offset.saturating_add(count);
1605        if needed > self.input.len() {
1606            Err(PacketSecurityDecodeError::Truncated {
1607                needed,
1608                available: self.input.len(),
1609            })
1610        } else {
1611            Ok(())
1612        }
1613    }
1614
1615    fn finish(&self) -> Result<(), PacketSecurityDecodeError> {
1616        if self.offset == self.input.len() {
1617            Ok(())
1618        } else {
1619            Err(PacketSecurityDecodeError::TrailingBytes(
1620                self.input.len().saturating_sub(self.offset),
1621            ))
1622        }
1623    }
1624}
1625
1626/// Bounded in-memory client transport limits.
1627#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1628pub struct ClientTransportLimits {
1629    /// Maximum queued packets per target client.
1630    pub max_queued_packets_per_client: usize,
1631    /// Maximum bytes accepted per packet.
1632    pub max_packet_bytes: usize,
1633}
1634
1635impl Default for ClientTransportLimits {
1636    fn default() -> Self {
1637        Self {
1638            max_queued_packets_per_client: 4096,
1639            max_packet_bytes: 16 * 1024,
1640        }
1641    }
1642}
1643
1644/// Statistics for the bounded in-memory client transport.
1645#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
1646pub struct InMemoryTransportStats {
1647    /// Packets accepted for delivery.
1648    pub packets_sent: usize,
1649    /// Packets received by local endpoints.
1650    pub packets_received: usize,
1651    /// Bytes accepted for delivery.
1652    pub bytes_sent: usize,
1653    /// Bytes received by local endpoints.
1654    pub bytes_received: usize,
1655    /// Packets rejected because the target queue was full.
1656    pub packets_rejected_full: usize,
1657    /// Packets rejected because they exceeded the packet byte budget.
1658    pub packets_rejected_bytes: usize,
1659}
1660
1661/// Error produced by bounded in-memory client transport.
1662#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1663pub enum InMemoryTransportError {
1664    /// Local endpoint has not been registered.
1665    MissingLocal(ClientId),
1666    /// Target client was not registered.
1667    MissingTarget(ClientId),
1668    /// Target client queue is full.
1669    QueueFull {
1670        /// Target client.
1671        client_id: ClientId,
1672        /// Configured queue capacity.
1673        capacity: usize,
1674    },
1675    /// Packet exceeded the byte budget.
1676    PacketTooLarge {
1677        /// Configured byte budget.
1678        budget: usize,
1679        /// Actual byte count.
1680        actual: usize,
1681    },
1682    /// Shared in-memory transport state was poisoned.
1683    Poisoned,
1684}
1685
1686impl core::fmt::Display for InMemoryTransportError {
1687    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1688        match self {
1689            Self::MissingLocal(client_id) => {
1690                write!(
1691                    f,
1692                    "in-memory transport local client {} is missing",
1693                    client_id.get()
1694                )
1695            }
1696            Self::MissingTarget(client_id) => {
1697                write!(
1698                    f,
1699                    "in-memory transport target client {} is missing",
1700                    client_id.get()
1701                )
1702            }
1703            Self::QueueFull {
1704                client_id,
1705                capacity,
1706            } => write!(
1707                f,
1708                "in-memory transport target client {} queue is full at capacity {capacity}",
1709                client_id.get()
1710            ),
1711            Self::PacketTooLarge { budget, actual } => {
1712                write!(
1713                    f,
1714                    "in-memory transport packet exceeded byte budget: budget {budget}, actual {actual}"
1715                )
1716            }
1717            Self::Poisoned => f.write_str("in-memory transport state is poisoned"),
1718        }
1719    }
1720}
1721
1722impl std::error::Error for InMemoryTransportError {}
1723
1724#[derive(Clone, Debug)]
1725struct InMemoryTransportClient {
1726    remote_addr: SocketAddr,
1727    registration_token: u64,
1728    queue: VecDeque<InboundPacket>,
1729}
1730
1731#[derive(Debug)]
1732struct InMemoryTransportInner {
1733    limits: ClientTransportLimits,
1734    clients: AdaptiveEndpointMap<ClientId, InMemoryTransportClient>,
1735    addr_to_client: HashMap<SocketAddr, ClientId>,
1736    stats: InMemoryTransportStats,
1737    next_registration_token: u64,
1738}
1739
1740/// Shared bounded in-memory client packet hub.
1741#[derive(Clone, Debug)]
1742pub struct InMemoryTransportHub {
1743    inner: Arc<Mutex<InMemoryTransportInner>>,
1744}
1745
1746/// Resources released when an in-memory client endpoint is unregistered.
1747#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1748pub struct InMemoryClientUnregister {
1749    /// Address previously associated with the client.
1750    pub remote_addr: SocketAddr,
1751    /// Packets discarded with the endpoint queue.
1752    pub queued_packets: usize,
1753    /// Queue slots released with the endpoint.
1754    pub retained_queue_capacity: usize,
1755}
1756
1757impl InMemoryTransportHub {
1758    /// Creates an empty in-memory transport hub.
1759    pub fn new(limits: ClientTransportLimits) -> Self {
1760        Self {
1761            inner: Arc::new(Mutex::new(InMemoryTransportInner {
1762                limits,
1763                clients: AdaptiveEndpointMap::new(),
1764                addr_to_client: HashMap::new(),
1765                stats: InMemoryTransportStats::default(),
1766                next_registration_token: 1,
1767            })),
1768        }
1769    }
1770
1771    /// Registers a client endpoint address.
1772    pub fn register_client(
1773        &self,
1774        client_id: ClientId,
1775        remote_addr: SocketAddr,
1776    ) -> Result<Option<SocketAddr>, InMemoryTransportError> {
1777        let mut inner = self.lock_inner()?;
1778        let registration_token = inner.next_registration_token;
1779        inner.next_registration_token = inner.next_registration_token.wrapping_add(1).max(1);
1780        let old_addr = inner.clients.insert(
1781            client_id,
1782            InMemoryTransportClient {
1783                remote_addr,
1784                registration_token,
1785                queue: VecDeque::new(),
1786            },
1787        );
1788        let previous_addr = old_addr.map(|client| client.remote_addr);
1789        if let Some(previous_addr) = previous_addr {
1790            inner.addr_to_client.remove(&previous_addr);
1791        }
1792        if let Some(old_client) = inner.addr_to_client.insert(remote_addr, client_id)
1793            && old_client != client_id
1794        {
1795            inner.clients.remove(&old_client);
1796        }
1797        Ok(previous_addr)
1798    }
1799
1800    /// Registers and returns a local endpoint for a client.
1801    pub fn endpoint(
1802        &self,
1803        client_id: ClientId,
1804        remote_addr: SocketAddr,
1805    ) -> Result<InMemoryTransportEndpoint, InMemoryTransportError> {
1806        self.register_client(client_id, remote_addr)?;
1807        Ok(self.endpoint_for_registered(client_id))
1808    }
1809
1810    /// Returns an endpoint handle for a client that should already be
1811    /// registered.
1812    pub fn endpoint_for_registered(&self, client_id: ClientId) -> InMemoryTransportEndpoint {
1813        let registration_token = self.lock_inner().ok().and_then(|inner| {
1814            inner
1815                .clients
1816                .get(&client_id)
1817                .map(|client| client.registration_token)
1818        });
1819        InMemoryTransportEndpoint {
1820            local_client_id: client_id,
1821            registration_token,
1822            hub: self.clone(),
1823        }
1824    }
1825
1826    /// Unregisters a client and releases its queued packet storage.
1827    pub fn unregister_client(
1828        &self,
1829        client_id: ClientId,
1830    ) -> Result<Option<InMemoryClientUnregister>, InMemoryTransportError> {
1831        let mut inner = self.lock_inner()?;
1832        let Some(client) = inner.clients.remove(&client_id) else {
1833            return Ok(None);
1834        };
1835        inner.addr_to_client.remove(&client.remote_addr);
1836        Ok(Some(InMemoryClientUnregister {
1837            remote_addr: client.remote_addr,
1838            queued_packets: client.queue.len(),
1839            retained_queue_capacity: client.queue.capacity(),
1840        }))
1841    }
1842
1843    /// Returns queued packet count for a client.
1844    pub fn queued_len(&self, client_id: ClientId) -> Result<Option<usize>, InMemoryTransportError> {
1845        let inner = self.lock_inner()?;
1846        Ok(inner
1847            .clients
1848            .get(&client_id)
1849            .map(|client| client.queue.len()))
1850    }
1851
1852    /// Returns packet slots retained by one client queue.
1853    pub fn queued_capacity(
1854        &self,
1855        client_id: ClientId,
1856    ) -> Result<Option<usize>, InMemoryTransportError> {
1857        let inner = self.lock_inner()?;
1858        Ok(inner
1859            .clients
1860            .get(&client_id)
1861            .map(|client| client.queue.capacity()))
1862    }
1863
1864    /// Returns packet slots retained across all registered client queues.
1865    pub fn retained_queue_capacity(&self) -> Result<usize, InMemoryTransportError> {
1866        let inner = self.lock_inner()?;
1867        Ok(inner
1868            .clients
1869            .values()
1870            .map(|client| client.queue.capacity())
1871            .sum())
1872    }
1873
1874    /// Returns configured limits.
1875    pub fn limits(&self) -> Result<ClientTransportLimits, InMemoryTransportError> {
1876        let inner = self.lock_inner()?;
1877        Ok(inner.limits)
1878    }
1879
1880    /// Returns transport statistics.
1881    pub fn stats(&self) -> Result<InMemoryTransportStats, InMemoryTransportError> {
1882        let inner = self.lock_inner()?;
1883        Ok(inner.stats)
1884    }
1885
1886    fn lock_inner(
1887        &self,
1888    ) -> Result<std::sync::MutexGuard<'_, InMemoryTransportInner>, InMemoryTransportError> {
1889        self.inner
1890            .lock()
1891            .map_err(|_| InMemoryTransportError::Poisoned)
1892    }
1893}
1894
1895impl Default for InMemoryTransportHub {
1896    fn default() -> Self {
1897        Self::new(ClientTransportLimits::default())
1898    }
1899}
1900
1901/// Local endpoint handle for a bounded in-memory client packet hub.
1902#[derive(Clone, Debug)]
1903pub struct InMemoryTransportEndpoint {
1904    local_client_id: ClientId,
1905    registration_token: Option<u64>,
1906    hub: InMemoryTransportHub,
1907}
1908
1909impl InMemoryTransportEndpoint {
1910    /// Returns the local client id.
1911    pub const fn local_client_id(&self) -> ClientId {
1912        self.local_client_id
1913    }
1914
1915    /// Returns the local endpoint address.
1916    pub fn local_addr(&self) -> Result<Option<SocketAddr>, InMemoryTransportError> {
1917        let inner = self.hub.lock_inner()?;
1918        let client = inner.clients.get(&self.local_client_id);
1919        if client.map(|client| client.registration_token) != self.registration_token {
1920            return Err(InMemoryTransportError::MissingLocal(self.local_client_id));
1921        }
1922        Ok(client.map(|client| client.remote_addr))
1923    }
1924
1925    fn send_locked(
1926        inner: &mut InMemoryTransportInner,
1927        local_client_id: ClientId,
1928        registration_token: Option<u64>,
1929        packet: OutboundPacket,
1930    ) -> Result<(), InMemoryTransportError> {
1931        let actual = packet.bytes.len();
1932        let limits = inner.limits;
1933        if actual > limits.max_packet_bytes {
1934            inner.stats.packets_rejected_bytes =
1935                inner.stats.packets_rejected_bytes.saturating_add(1);
1936            return Err(InMemoryTransportError::PacketTooLarge {
1937                budget: limits.max_packet_bytes,
1938                actual,
1939            });
1940        }
1941
1942        let source_addr = inner
1943            .clients
1944            .get(&local_client_id)
1945            .filter(|client| Some(client.registration_token) == registration_token)
1946            .ok_or(InMemoryTransportError::MissingLocal(local_client_id))?
1947            .remote_addr;
1948        let target = inner
1949            .clients
1950            .get_mut(&packet.client_id)
1951            .ok_or(InMemoryTransportError::MissingTarget(packet.client_id))?;
1952        if target.queue.len() >= limits.max_queued_packets_per_client {
1953            inner.stats.packets_rejected_full = inner.stats.packets_rejected_full.saturating_add(1);
1954            return Err(InMemoryTransportError::QueueFull {
1955                client_id: packet.client_id,
1956                capacity: limits.max_queued_packets_per_client,
1957            });
1958        }
1959        target.queue.push_back(InboundPacket {
1960            client_id: Some(local_client_id),
1961            remote_addr: source_addr,
1962            bytes: packet.bytes,
1963        });
1964        inner.stats.packets_sent = inner.stats.packets_sent.saturating_add(1);
1965        inner.stats.bytes_sent = inner.stats.bytes_sent.saturating_add(actual);
1966        Ok(())
1967    }
1968}
1969
1970impl TransportSink for InMemoryTransportEndpoint {
1971    type Error = InMemoryTransportError;
1972
1973    fn send(&mut self, packet: OutboundPacket) -> Result<(), Self::Error> {
1974        let mut inner = self.hub.lock_inner()?;
1975        Self::send_locked(
1976            &mut inner,
1977            self.local_client_id,
1978            self.registration_token,
1979            packet,
1980        )
1981    }
1982
1983    fn send_batch(&mut self, batch: PacketBatch) -> Result<(), Self::Error> {
1984        let mut packets = batch.packets.into_iter().peekable();
1985        while packets.peek().is_some() {
1986            let mut inner = self.hub.lock_inner()?;
1987            for _ in 0..IN_MEMORY_BATCH_LOCK_PACKETS {
1988                let Some(packet) = packets.next() else {
1989                    return Ok(());
1990                };
1991                Self::send_locked(
1992                    &mut inner,
1993                    self.local_client_id,
1994                    self.registration_token,
1995                    packet,
1996                )?;
1997            }
1998        }
1999        Ok(())
2000    }
2001}
2002
2003impl TransportReceiver for InMemoryTransportEndpoint {
2004    type Error = InMemoryTransportError;
2005
2006    fn try_recv(&mut self) -> Result<Option<InboundPacket>, Self::Error> {
2007        let mut inner = self.hub.lock_inner()?;
2008        let local = inner
2009            .clients
2010            .get_mut(&self.local_client_id)
2011            .filter(|client| Some(client.registration_token) == self.registration_token)
2012            .ok_or(InMemoryTransportError::MissingLocal(self.local_client_id))?;
2013        let Some(packet) = local.queue.pop_front() else {
2014            return Ok(None);
2015        };
2016        inner.stats.packets_received = inner.stats.packets_received.saturating_add(1);
2017        inner.stats.bytes_received = inner
2018            .stats
2019            .bytes_received
2020            .saturating_add(packet.bytes.len());
2021        Ok(Some(packet))
2022    }
2023}
2024
2025const RELIABLE_CLIENT_MAGIC: [u8; 4] = *b"SSCR";
2026const RELIABLE_CLIENT_KIND_DATA: u8 = 0;
2027const RELIABLE_CLIENT_KIND_ACK: u8 = 1;
2028/// Reliable client data frame header bytes before payload.
2029pub const RELIABLE_CLIENT_DATA_HEADER_BYTES: usize = 17;
2030/// Reliable client ACK frame bytes.
2031pub const RELIABLE_CLIENT_ACK_BYTES: usize = 13;
2032/// Default reliable client payload budget aligned to the default packet budget
2033/// after reliable header overhead.
2034pub const DEFAULT_RELIABLE_CLIENT_MAX_PAYLOAD_BYTES: usize =
2035    (16 * 1024) - RELIABLE_CLIENT_DATA_HEADER_BYTES;
2036/// Default duplicate-suppression history retained per reliable client endpoint.
2037pub const DEFAULT_RELIABLE_CLIENT_DELIVERED_HISTORY: usize = 4096;
2038
2039/// Bounded reliable client link configuration.
2040#[derive(Clone, Copy, Debug, PartialEq, Eq)]
2041pub struct ReliableClientConfig {
2042    /// Maximum in-flight reliable packets per peer client.
2043    pub max_in_flight_per_peer: usize,
2044    /// Number of ticks to wait before retrying an unacknowledged packet.
2045    pub retry_after_ticks: u64,
2046    /// Maximum send attempts before dropping an in-flight packet.
2047    pub max_attempts: u8,
2048    /// Maximum payload bytes before reliable envelope overhead.
2049    pub max_payload_bytes: usize,
2050    /// Maximum recently delivered packet ids retained for duplicate
2051    /// suppression. Set to zero to disable duplicate suppression history.
2052    pub max_delivered_history: usize,
2053}
2054
2055impl Default for ReliableClientConfig {
2056    fn default() -> Self {
2057        Self {
2058            max_in_flight_per_peer: 1024,
2059            retry_after_ticks: 2,
2060            max_attempts: 4,
2061            max_payload_bytes: DEFAULT_RELIABLE_CLIENT_MAX_PAYLOAD_BYTES,
2062            max_delivered_history: DEFAULT_RELIABLE_CLIENT_DELIVERED_HISTORY,
2063        }
2064    }
2065}
2066
2067/// Reliable client endpoint statistics.
2068#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
2069pub struct ReliableClientStats {
2070    /// New reliable data packets sent.
2071    pub data_sent: usize,
2072    /// Retry data packets sent.
2073    pub retries_sent: usize,
2074    /// ACK packets sent.
2075    pub acks_sent: usize,
2076    /// ACK packets received.
2077    pub acks_received: usize,
2078    /// Unique data packets delivered to the caller.
2079    pub data_delivered: usize,
2080    /// Duplicate data packets suppressed.
2081    pub duplicates_suppressed: usize,
2082    /// In-flight packets dropped after exhausting attempts.
2083    pub timed_out: usize,
2084}
2085
2086/// Encoded reliable client frame.
2087#[derive(Clone, Debug, PartialEq, Eq)]
2088pub enum ReliableClientFrame {
2089    /// Reliable data packet.
2090    Data {
2091        /// Sender-local sequence number scoped to the peer client.
2092        sequence: u64,
2093        /// Original packet payload.
2094        payload: Vec<u8>,
2095    },
2096    /// Acknowledgement for a reliable data packet.
2097    Ack {
2098        /// Acknowledged sequence number.
2099        sequence: u64,
2100    },
2101}
2102
2103/// Borrowed reliable client frame decoded from caller-owned bytes.
2104#[derive(Clone, Copy, Debug, PartialEq, Eq)]
2105pub enum ReliableClientFrameRef<'a> {
2106    /// Reliable data packet with borrowed payload bytes.
2107    Data {
2108        /// Sender-local sequence number scoped to the peer client.
2109        sequence: u64,
2110        /// Payload borrowed from the encoded frame.
2111        payload: &'a [u8],
2112    },
2113    /// Acknowledgement for a reliable data packet.
2114    Ack {
2115        /// Acknowledged sequence number.
2116        sequence: u64,
2117    },
2118}
2119
2120impl ReliableClientFrameRef<'_> {
2121    /// Materializes the compatible owned reliable client frame.
2122    pub fn to_owned(self) -> ReliableClientFrame {
2123        match self {
2124            Self::Data { sequence, payload } => ReliableClientFrame::Data {
2125                sequence,
2126                payload: payload.to_vec(),
2127            },
2128            Self::Ack { sequence } => ReliableClientFrame::Ack { sequence },
2129        }
2130    }
2131}
2132
2133impl ReliableClientFrame {
2134    /// Appends a data frame from a borrowed payload without materializing an owned frame.
2135    pub fn encode_data(
2136        sequence: u64,
2137        payload: &[u8],
2138        out: &mut Vec<u8>,
2139    ) -> Result<(), ReliableClientEncodeError> {
2140        out.extend_from_slice(&RELIABLE_CLIENT_MAGIC);
2141        out.push(RELIABLE_CLIENT_KIND_DATA);
2142        out.extend_from_slice(&sequence.to_le_bytes());
2143        let len = u32::try_from(payload.len()).map_err(|_| {
2144            ReliableClientEncodeError::PayloadTooLarge {
2145                actual: payload.len(),
2146            }
2147        })?;
2148        out.extend_from_slice(&len.to_le_bytes());
2149        out.extend_from_slice(payload);
2150        Ok(())
2151    }
2152
2153    /// Encodes a reliable client frame.
2154    pub fn encode(&self, out: &mut Vec<u8>) -> Result<(), ReliableClientEncodeError> {
2155        match self {
2156            Self::Data { sequence, payload } => Self::encode_data(*sequence, payload, out),
2157            Self::Ack { sequence } => {
2158                out.extend_from_slice(&RELIABLE_CLIENT_MAGIC);
2159                out.push(RELIABLE_CLIENT_KIND_ACK);
2160                out.extend_from_slice(&sequence.to_le_bytes());
2161                Ok(())
2162            }
2163        }
2164    }
2165
2166    /// Decodes a reliable client frame.
2167    pub fn decode(input: &[u8]) -> Result<Self, ReliableClientDecodeError> {
2168        Self::decode_ref(input).map(ReliableClientFrameRef::to_owned)
2169    }
2170
2171    /// Decodes a reliable client frame while borrowing data payload bytes.
2172    pub fn decode_ref(
2173        input: &[u8],
2174    ) -> Result<ReliableClientFrameRef<'_>, ReliableClientDecodeError> {
2175        let mut cursor = ReliableCursor::new(input);
2176        let magic = cursor
2177            .read_array::<4>()
2178            .map_err(ReliableClientDecodeError::from_station_decode)?;
2179        if magic != RELIABLE_CLIENT_MAGIC {
2180            return Err(ReliableClientDecodeError::BadMagic);
2181        }
2182        let kind = cursor
2183            .read_u8()
2184            .map_err(ReliableClientDecodeError::from_station_decode)?;
2185        let sequence = cursor
2186            .read_u64()
2187            .map_err(ReliableClientDecodeError::from_station_decode)?;
2188        let frame = match kind {
2189            RELIABLE_CLIENT_KIND_DATA => {
2190                let len = cursor
2191                    .read_u32()
2192                    .map_err(ReliableClientDecodeError::from_station_decode)?
2193                    as usize;
2194                let payload = cursor
2195                    .read_slice(len)
2196                    .map_err(ReliableClientDecodeError::from_station_decode)?;
2197                ReliableClientFrameRef::Data { sequence, payload }
2198            }
2199            RELIABLE_CLIENT_KIND_ACK => ReliableClientFrameRef::Ack { sequence },
2200            other => return Err(ReliableClientDecodeError::UnknownKind(other)),
2201        };
2202        cursor
2203            .finish()
2204            .map_err(ReliableClientDecodeError::from_station_decode)?;
2205        Ok(frame)
2206    }
2207}
2208
2209/// Reliable client frame encode error.
2210#[derive(Clone, Copy, Debug, PartialEq, Eq)]
2211pub enum ReliableClientEncodeError {
2212    /// Payload length exceeded `u32::MAX`.
2213    PayloadTooLarge {
2214        /// Actual byte count.
2215        actual: usize,
2216    },
2217}
2218
2219impl core::fmt::Display for ReliableClientEncodeError {
2220    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
2221        match self {
2222            Self::PayloadTooLarge { actual } => {
2223                write!(f, "reliable client payload too large: {actual} bytes")
2224            }
2225        }
2226    }
2227}
2228
2229impl std::error::Error for ReliableClientEncodeError {}
2230
2231/// Reliable client frame decode error.
2232#[derive(Clone, Copy, Debug, PartialEq, Eq)]
2233pub enum ReliableClientDecodeError {
2234    /// Frame magic did not match.
2235    BadMagic,
2236    /// Frame kind byte is unknown.
2237    UnknownKind(u8),
2238    /// Frame ended before all fields were available.
2239    Truncated {
2240        /// Required bytes.
2241        needed: usize,
2242        /// Available bytes.
2243        available: usize,
2244    },
2245    /// Frame had trailing bytes after a complete payload.
2246    TrailingBytes(usize),
2247}
2248
2249impl ReliableClientDecodeError {
2250    fn from_station_decode(error: ReliableStationDecodeError) -> Self {
2251        match error {
2252            ReliableStationDecodeError::BadMagic => Self::BadMagic,
2253            ReliableStationDecodeError::UnknownKind(kind) => Self::UnknownKind(kind),
2254            ReliableStationDecodeError::Truncated { needed, available } => {
2255                Self::Truncated { needed, available }
2256            }
2257            ReliableStationDecodeError::TrailingBytes(bytes) => Self::TrailingBytes(bytes),
2258        }
2259    }
2260}
2261
2262impl core::fmt::Display for ReliableClientDecodeError {
2263    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
2264        match self {
2265            Self::BadMagic => f.write_str("bad reliable client frame magic"),
2266            Self::UnknownKind(kind) => write!(f, "unknown reliable client frame kind {kind}"),
2267            Self::Truncated { needed, available } => {
2268                write!(
2269                    f,
2270                    "truncated reliable client frame: needed {needed}, available {available}"
2271                )
2272            }
2273            Self::TrailingBytes(bytes) => {
2274                write!(f, "reliable client frame has {bytes} trailing bytes")
2275            }
2276        }
2277    }
2278}
2279
2280impl std::error::Error for ReliableClientDecodeError {}
2281
2282/// Error produced by reliable client endpoints.
2283#[derive(Debug)]
2284pub enum ReliableClientError<E> {
2285    /// Underlying transport failed.
2286    Transport(E),
2287    /// Inbound packet did not contain a source client id.
2288    MissingSourceClient,
2289    /// Payload exceeded configured byte budget.
2290    PayloadTooLarge {
2291        /// Configured byte budget.
2292        budget: usize,
2293        /// Actual byte count.
2294        actual: usize,
2295    },
2296    /// Peer client in-flight window is full.
2297    WindowFull {
2298        /// Peer client.
2299        peer_client: ClientId,
2300        /// Configured in-flight capacity.
2301        capacity: usize,
2302    },
2303    /// Reliable frame encode failed.
2304    Encode(ReliableClientEncodeError),
2305    /// Reliable frame decode failed.
2306    Decode(ReliableClientDecodeError),
2307}
2308
2309impl<E: core::fmt::Display> core::fmt::Display for ReliableClientError<E> {
2310    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
2311        match self {
2312            Self::Transport(error) => write!(f, "{error}"),
2313            Self::MissingSourceClient => f.write_str("reliable client packet source is unknown"),
2314            Self::PayloadTooLarge { budget, actual } => {
2315                write!(
2316                    f,
2317                    "reliable client payload exceeded byte budget: budget {budget}, actual {actual}"
2318                )
2319            }
2320            Self::WindowFull {
2321                peer_client,
2322                capacity,
2323            } => write!(
2324                f,
2325                "reliable client peer {} window is full at capacity {capacity}",
2326                peer_client.get()
2327            ),
2328            Self::Encode(error) => write!(f, "{error}"),
2329            Self::Decode(error) => write!(f, "{error}"),
2330        }
2331    }
2332}
2333
2334impl<E> std::error::Error for ReliableClientError<E>
2335where
2336    E: std::error::Error + 'static,
2337{
2338    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
2339        match self {
2340            Self::Transport(error) => Some(error),
2341            Self::Encode(error) => Some(error),
2342            Self::Decode(error) => Some(error),
2343            Self::MissingSourceClient | Self::PayloadTooLarge { .. } | Self::WindowFull { .. } => {
2344                None
2345            }
2346        }
2347    }
2348}
2349
2350#[derive(Clone, Debug, PartialEq, Eq)]
2351struct InFlightReliableClientPacket {
2352    peer_client: ClientId,
2353    sequence: u64,
2354    payload: Vec<u8>,
2355    first_sent_tick: u64,
2356    last_sent_tick: u64,
2357    attempts: u8,
2358}
2359
2360/// Caller-owned reusable storage for reliable client retry scans.
2361#[derive(Clone, Debug, Default)]
2362pub struct ReliableClientRetryScratch {
2363    due_entries: Vec<(u64, ClientId, u64)>,
2364}
2365
2366impl ReliableClientRetryScratch {
2367    /// Creates empty retry storage.
2368    pub fn new() -> Self {
2369        Self::default()
2370    }
2371
2372    /// Due-key capacity retained across retry passes.
2373    pub fn retained_key_capacity(&self) -> usize {
2374        self.due_entries.capacity()
2375    }
2376}
2377
2378/// Bounded reliable client sender state.
2379#[derive(Clone, Debug)]
2380pub struct ReliableClientSender {
2381    config: ReliableClientConfig,
2382    next_sequence: BTreeMap<ClientId, u64>,
2383    in_flight: BTreeMap<(ClientId, u64), InFlightReliableClientPacket>,
2384    in_flight_by_peer: BTreeMap<ClientId, usize>,
2385    retry_deadlines: BinaryHeap<Reverse<(u64, ClientId, u64)>>,
2386    stats: ReliableClientStats,
2387}
2388
2389impl ReliableClientSender {
2390    /// Creates a reliable client sender.
2391    pub fn new(config: ReliableClientConfig) -> Self {
2392        Self {
2393            config,
2394            next_sequence: BTreeMap::new(),
2395            in_flight: BTreeMap::new(),
2396            in_flight_by_peer: BTreeMap::new(),
2397            retry_deadlines: BinaryHeap::new(),
2398            stats: ReliableClientStats::default(),
2399        }
2400    }
2401
2402    /// Returns sender configuration.
2403    pub const fn config(&self) -> ReliableClientConfig {
2404        self.config
2405    }
2406
2407    /// Returns sender statistics.
2408    pub const fn stats(&self) -> ReliableClientStats {
2409        self.stats
2410    }
2411
2412    /// Returns total in-flight reliable packets.
2413    pub fn in_flight_len(&self) -> usize {
2414        self.in_flight.len()
2415    }
2416
2417    /// Returns in-flight reliable packets for one peer client.
2418    pub fn in_flight_for(&self, peer_client: ClientId) -> usize {
2419        self.in_flight_by_peer
2420            .get(&peer_client)
2421            .copied()
2422            .unwrap_or(0)
2423    }
2424
2425    /// Sends a new reliable packet and stores it until acknowledged or timed
2426    /// out.
2427    pub fn send<T: TransportSink>(
2428        &mut self,
2429        transport: &mut T,
2430        packet: OutboundPacket,
2431        now_tick: u64,
2432    ) -> Result<u64, ReliableClientError<T::Error>> {
2433        self.validate_payload(packet.bytes.len())?;
2434        if self.in_flight_for(packet.client_id) >= self.config.max_in_flight_per_peer {
2435            return Err(ReliableClientError::WindowFull {
2436                peer_client: packet.client_id,
2437                capacity: self.config.max_in_flight_per_peer,
2438            });
2439        }
2440
2441        let sequence = self.allocate_sequence(packet.client_id);
2442        Self::send_data_frame(transport, packet.client_id, sequence, &packet.bytes)?;
2443        self.insert_in_flight(
2444            (packet.client_id, sequence),
2445            InFlightReliableClientPacket {
2446                peer_client: packet.client_id,
2447                sequence,
2448                payload: packet.bytes,
2449                first_sent_tick: now_tick,
2450                last_sent_tick: now_tick,
2451                attempts: 1,
2452            },
2453        );
2454        self.schedule_retry(packet.client_id, sequence, now_tick);
2455        self.stats.data_sent = self.stats.data_sent.saturating_add(1);
2456        Ok(sequence)
2457    }
2458
2459    /// Processes an ACK from `ack_source_client`.
2460    pub fn acknowledge(&mut self, ack_source_client: ClientId, sequence: u64) -> bool {
2461        let removed = self
2462            .remove_in_flight(&(ack_source_client, sequence))
2463            .is_some();
2464        if removed {
2465            self.stats.acks_received = self.stats.acks_received.saturating_add(1);
2466            self.compact_retry_deadlines_if_stale();
2467        }
2468        removed
2469    }
2470
2471    fn compact_retry_deadlines_if_stale(&mut self) {
2472        if self.retry_deadlines.len()
2473            > self
2474                .in_flight
2475                .len()
2476                .saturating_mul(RETRY_DEADLINE_COMPACTION_MULTIPLE)
2477        {
2478            self.retry_deadlines = self
2479                .in_flight
2480                .values()
2481                .map(|packet| {
2482                    Reverse((
2483                        packet
2484                            .last_sent_tick
2485                            .saturating_add(self.config.retry_after_ticks),
2486                        packet.peer_client,
2487                        packet.sequence,
2488                    ))
2489                })
2490                .collect();
2491        }
2492    }
2493
2494    /// Retries due packets using caller-owned scan storage.
2495    pub fn retry_due_into<T: TransportSink>(
2496        &mut self,
2497        transport: &mut T,
2498        now_tick: u64,
2499        scratch: &mut ReliableClientRetryScratch,
2500    ) -> Result<ReliableRetryReport, ReliableClientError<T::Error>> {
2501        scratch.due_entries.clear();
2502        while let Some(Reverse((deadline, peer, sequence))) = self.retry_deadlines.peek().copied() {
2503            if deadline > now_tick {
2504                break;
2505            }
2506            self.retry_deadlines.pop();
2507            scratch.due_entries.push((deadline, peer, sequence));
2508        }
2509        let mut report = ReliableRetryReport::default();
2510
2511        for (entry_index, (deadline, peer, sequence)) in scratch.due_entries.iter().enumerate() {
2512            let key = (*peer, *sequence);
2513            let Some(packet) = self.in_flight.get(&key) else {
2514                continue;
2515            };
2516            if packet
2517                .last_sent_tick
2518                .saturating_add(self.config.retry_after_ticks)
2519                != *deadline
2520            {
2521                continue;
2522            }
2523            report.examined = report.examined.saturating_add(1);
2524            if packet.attempts >= self.config.max_attempts {
2525                self.remove_in_flight(&key);
2526                self.stats.timed_out = self.stats.timed_out.saturating_add(1);
2527                report.timed_out = report.timed_out.saturating_add(1);
2528                continue;
2529            }
2530
2531            if let Err(error) = Self::send_data_frame(
2532                transport,
2533                packet.peer_client,
2534                packet.sequence,
2535                &packet.payload,
2536            ) {
2537                self.retry_deadlines.extend(
2538                    scratch.due_entries[entry_index..]
2539                        .iter()
2540                        .copied()
2541                        .map(Reverse),
2542                );
2543                return Err(error);
2544            }
2545            if let Some(stored) = self.in_flight.get_mut(&key) {
2546                stored.last_sent_tick = now_tick;
2547                stored.attempts = stored.attempts.saturating_add(1);
2548            }
2549            self.schedule_retry(*peer, *sequence, now_tick);
2550            self.stats.retries_sent = self.stats.retries_sent.saturating_add(1);
2551            report.retried = report.retried.saturating_add(1);
2552        }
2553
2554        Ok(report)
2555    }
2556
2557    fn validate_payload<E>(&self, bytes: usize) -> Result<(), ReliableClientError<E>> {
2558        if bytes > self.config.max_payload_bytes {
2559            Err(ReliableClientError::PayloadTooLarge {
2560                budget: self.config.max_payload_bytes,
2561                actual: bytes,
2562            })
2563        } else {
2564            Ok(())
2565        }
2566    }
2567
2568    fn allocate_sequence(&mut self, peer_client: ClientId) -> u64 {
2569        let next = self.next_sequence.entry(peer_client).or_insert(1);
2570        let sequence = *next;
2571        *next = next.saturating_add(1);
2572        sequence
2573    }
2574
2575    fn insert_in_flight(&mut self, key: (ClientId, u64), packet: InFlightReliableClientPacket) {
2576        if self.in_flight.insert(key, packet).is_none() {
2577            let count = self.in_flight_by_peer.entry(key.0).or_insert(0);
2578            *count = count.saturating_add(1);
2579        }
2580    }
2581
2582    fn schedule_retry(&mut self, peer: ClientId, sequence: u64, sent_tick: u64) {
2583        self.retry_deadlines.push(Reverse((
2584            sent_tick.saturating_add(self.config.retry_after_ticks),
2585            peer,
2586            sequence,
2587        )));
2588    }
2589
2590    fn remove_in_flight(&mut self, key: &(ClientId, u64)) -> Option<InFlightReliableClientPacket> {
2591        let removed = self.in_flight.remove(key)?;
2592        if let Some(count) = self.in_flight_by_peer.get_mut(&removed.peer_client) {
2593            *count = count.saturating_sub(1);
2594            if *count == 0 {
2595                self.in_flight_by_peer.remove(&removed.peer_client);
2596            }
2597        }
2598        Some(removed)
2599    }
2600
2601    fn send_data_frame<T: TransportSink>(
2602        transport: &mut T,
2603        peer_client: ClientId,
2604        sequence: u64,
2605        payload: &[u8],
2606    ) -> Result<(), ReliableClientError<T::Error>> {
2607        let mut bytes = Vec::with_capacity(
2608            payload
2609                .len()
2610                .saturating_add(RELIABLE_CLIENT_DATA_HEADER_BYTES),
2611        );
2612        ReliableClientFrame::encode_data(sequence, payload, &mut bytes)
2613            .map_err(ReliableClientError::Encode)?;
2614        transport
2615            .send(OutboundPacket {
2616                client_id: peer_client,
2617                bytes,
2618            })
2619            .map_err(ReliableClientError::Transport)
2620    }
2621}
2622
2623impl Default for ReliableClientSender {
2624    fn default() -> Self {
2625        Self::new(ReliableClientConfig::default())
2626    }
2627}
2628
2629/// Bounded reliable client receiver state.
2630#[derive(Clone, Debug)]
2631pub struct ReliableClientReceiver {
2632    config: ReliableClientConfig,
2633    delivered: BoundedLookupSet<(ClientId, u64)>,
2634    delivered_order: VecDeque<(ClientId, u64)>,
2635    stats: ReliableClientStats,
2636}
2637
2638impl ReliableClientReceiver {
2639    /// Creates a bounded reliable client receiver.
2640    pub fn new(config: ReliableClientConfig) -> Self {
2641        Self {
2642            config,
2643            delivered: BoundedLookupSet::new(config.max_delivered_history),
2644            delivered_order: VecDeque::new(),
2645            stats: ReliableClientStats::default(),
2646        }
2647    }
2648
2649    /// Returns receiver configuration.
2650    pub const fn config(&self) -> ReliableClientConfig {
2651        self.config
2652    }
2653
2654    /// Returns receiver statistics.
2655    pub const fn stats(&self) -> ReliableClientStats {
2656        self.stats
2657    }
2658
2659    /// Handles a reliable data packet, sends an ACK, and returns a payload only
2660    /// for first delivery.
2661    pub fn handle_data<T: TransportSink>(
2662        &mut self,
2663        transport: &mut T,
2664        packet: InboundPacket,
2665        source_client: ClientId,
2666        sequence: u64,
2667        payload: Vec<u8>,
2668    ) -> Result<Option<InboundPacket>, ReliableClientError<T::Error>> {
2669        let InboundPacket {
2670            remote_addr,
2671            bytes: wire_bytes,
2672            ..
2673        } = packet;
2674        drop(wire_bytes);
2675        self.send_ack(transport, source_client, sequence)?;
2676        if !self.record_unique(source_client, sequence) {
2677            self.stats.duplicates_suppressed = self.stats.duplicates_suppressed.saturating_add(1);
2678            return Ok(None);
2679        }
2680
2681        self.stats.data_delivered = self.stats.data_delivered.saturating_add(1);
2682        Ok(Some(InboundPacket {
2683            client_id: Some(source_client),
2684            remote_addr,
2685            bytes: payload,
2686        }))
2687    }
2688
2689    fn send_ack<T: TransportSink>(
2690        &mut self,
2691        transport: &mut T,
2692        target_client: ClientId,
2693        sequence: u64,
2694    ) -> Result<(), ReliableClientError<T::Error>> {
2695        let mut bytes = Vec::with_capacity(RELIABLE_CLIENT_ACK_BYTES);
2696        ReliableClientFrame::Ack { sequence }
2697            .encode(&mut bytes)
2698            .map_err(ReliableClientError::Encode)?;
2699        transport
2700            .send(OutboundPacket {
2701                client_id: target_client,
2702                bytes,
2703            })
2704            .map_err(ReliableClientError::Transport)?;
2705        self.stats.acks_sent = self.stats.acks_sent.saturating_add(1);
2706        Ok(())
2707    }
2708
2709    fn record_unique(&mut self, source_client: ClientId, sequence: u64) -> bool {
2710        if self.config.max_delivered_history == 0 {
2711            return true;
2712        }
2713
2714        let key = (source_client, sequence);
2715        if self.delivered.contains(&key) {
2716            return false;
2717        }
2718
2719        self.delivered.insert(key);
2720        self.delivered_order.push_back(key);
2721        while self.delivered_order.len() > self.config.max_delivered_history {
2722            if let Some(old) = self.delivered_order.pop_front() {
2723                self.delivered.remove(&old);
2724            }
2725        }
2726        true
2727    }
2728}
2729
2730impl Default for ReliableClientReceiver {
2731    fn default() -> Self {
2732        Self::new(ReliableClientConfig::default())
2733    }
2734}
2735
2736/// Reliable client endpoint combining sender and receiver state.
2737#[derive(Clone, Debug)]
2738pub struct ReliableClientEndpoint {
2739    /// Sender state.
2740    pub sender: ReliableClientSender,
2741    /// Receiver state.
2742    pub receiver: ReliableClientReceiver,
2743    retry_scratch: ReliableClientRetryScratch,
2744}
2745
2746impl ReliableClientEndpoint {
2747    /// Creates a reliable client endpoint.
2748    pub fn new(config: ReliableClientConfig) -> Self {
2749        Self {
2750            sender: ReliableClientSender::new(config),
2751            receiver: ReliableClientReceiver::new(config),
2752            retry_scratch: ReliableClientRetryScratch::default(),
2753        }
2754    }
2755
2756    /// Sends a new reliable packet to a peer client.
2757    pub fn send<T: TransportSink>(
2758        &mut self,
2759        transport: &mut T,
2760        packet: OutboundPacket,
2761        now_tick: u64,
2762    ) -> Result<u64, ReliableClientError<T::Error>> {
2763        self.sender.send(transport, packet, now_tick)
2764    }
2765
2766    /// Retries due reliable client packets.
2767    pub fn retry_due<T: TransportSink>(
2768        &mut self,
2769        transport: &mut T,
2770        now_tick: u64,
2771    ) -> Result<ReliableRetryReport, ReliableClientError<T::Error>> {
2772        self.sender
2773            .retry_due_into(transport, now_tick, &mut self.retry_scratch)
2774    }
2775
2776    /// Handles one inbound reliable client packet.
2777    pub fn handle_inbound<T: TransportSink>(
2778        &mut self,
2779        transport: &mut T,
2780        packet: InboundPacket,
2781    ) -> Result<Option<InboundPacket>, ReliableClientError<T::Error>> {
2782        let source_client = packet
2783            .client_id
2784            .ok_or(ReliableClientError::MissingSourceClient)?;
2785        match ReliableClientFrame::decode_ref(&packet.bytes).map_err(ReliableClientError::Decode)? {
2786            ReliableClientFrameRef::Data { sequence, payload } => {
2787                let payload_len = payload.len();
2788                let payload_offset = packet.bytes.len().saturating_sub(payload_len);
2789                let InboundPacket {
2790                    client_id,
2791                    remote_addr,
2792                    bytes,
2793                } = packet;
2794                let payload = reuse_reliable_payload(bytes, payload_offset, payload_len);
2795                self.receiver.handle_data(
2796                    transport,
2797                    InboundPacket {
2798                        client_id,
2799                        remote_addr,
2800                        bytes: Vec::new(),
2801                    },
2802                    source_client,
2803                    sequence,
2804                    payload,
2805                )
2806            }
2807            ReliableClientFrameRef::Ack { sequence } => {
2808                self.sender.acknowledge(source_client, sequence);
2809                Ok(None)
2810            }
2811        }
2812    }
2813}
2814
2815impl Default for ReliableClientEndpoint {
2816    fn default() -> Self {
2817        Self::new(ReliableClientConfig::default())
2818    }
2819}
2820
2821/// Station-to-station packet sink abstraction.
2822pub trait StationTransportSink {
2823    /// Transport error type.
2824    type Error;
2825
2826    /// Sends one encoded station packet.
2827    fn send_station(&mut self, packet: StationOutboundPacket) -> Result<(), Self::Error>;
2828
2829    /// Sends a station packet batch.
2830    fn send_station_batch(&mut self, batch: StationPacketBatch) -> Result<(), Self::Error> {
2831        for packet in batch.packets {
2832            self.send_station(packet)?;
2833        }
2834        Ok(())
2835    }
2836}
2837
2838/// Non-blocking station-to-station receive abstraction.
2839pub trait StationTransportReceiver {
2840    /// Transport error type.
2841    type Error;
2842
2843    /// Attempts to receive one encoded packet for `target_station`.
2844    fn try_recv_station(
2845        &mut self,
2846        target_station: StationId,
2847    ) -> Result<Option<StationInboundPacket>, Self::Error>;
2848}
2849
2850const RELIABLE_STATION_MAGIC: [u8; 4] = *b"SSRP";
2851const RELIABLE_KIND_DATA: u8 = 0;
2852const RELIABLE_KIND_ACK: u8 = 1;
2853/// Reliable data frame header bytes before payload.
2854pub const RELIABLE_STATION_DATA_HEADER_BYTES: usize = 17;
2855/// Reliable ACK frame bytes.
2856pub const RELIABLE_STATION_ACK_BYTES: usize = 13;
2857/// Default reliable station payload budget aligned to the default station
2858/// packet budget after reliable header overhead.
2859pub const DEFAULT_RELIABLE_STATION_MAX_PAYLOAD_BYTES: usize =
2860    (16 * 1024) - RELIABLE_STATION_DATA_HEADER_BYTES;
2861/// Default duplicate-suppression history retained per reliable endpoint.
2862pub const DEFAULT_RELIABLE_STATION_DELIVERED_HISTORY: usize = 4096;
2863
2864/// Bounded reliable station link configuration.
2865#[derive(Clone, Copy, Debug, PartialEq, Eq)]
2866pub struct ReliableStationConfig {
2867    /// Maximum in-flight reliable packets per target station.
2868    pub max_in_flight_per_target: usize,
2869    /// Number of ticks to wait before retrying an unacknowledged packet.
2870    pub retry_after_ticks: u64,
2871    /// Maximum send attempts before dropping an in-flight packet.
2872    pub max_attempts: u8,
2873    /// Maximum payload bytes before reliable envelope overhead.
2874    pub max_payload_bytes: usize,
2875    /// Maximum recently delivered packet ids retained for duplicate
2876    /// suppression. Set to zero to disable duplicate suppression history.
2877    pub max_delivered_history: usize,
2878}
2879
2880impl Default for ReliableStationConfig {
2881    fn default() -> Self {
2882        Self {
2883            max_in_flight_per_target: 1024,
2884            retry_after_ticks: 2,
2885            max_attempts: 4,
2886            max_payload_bytes: DEFAULT_RELIABLE_STATION_MAX_PAYLOAD_BYTES,
2887            max_delivered_history: DEFAULT_RELIABLE_STATION_DELIVERED_HISTORY,
2888        }
2889    }
2890}
2891
2892/// Reliable station endpoint statistics.
2893#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
2894pub struct ReliableStationStats {
2895    /// New reliable data packets sent.
2896    pub data_sent: usize,
2897    /// Retry data packets sent.
2898    pub retries_sent: usize,
2899    /// ACK packets sent.
2900    pub acks_sent: usize,
2901    /// ACK packets received.
2902    pub acks_received: usize,
2903    /// Unique data packets delivered to the caller.
2904    pub data_delivered: usize,
2905    /// Duplicate data packets suppressed.
2906    pub duplicates_suppressed: usize,
2907    /// In-flight packets dropped after exhausting attempts.
2908    pub timed_out: usize,
2909}
2910
2911/// Encoded reliable station frame.
2912#[derive(Clone, Debug, PartialEq, Eq)]
2913pub enum ReliableStationFrame {
2914    /// Reliable data packet.
2915    Data {
2916        /// Sender-local sequence number scoped to the target station.
2917        sequence: u64,
2918        /// Original station packet payload.
2919        payload: Vec<u8>,
2920    },
2921    /// Acknowledgement for a reliable data packet.
2922    Ack {
2923        /// Acknowledged sequence number.
2924        sequence: u64,
2925    },
2926}
2927
2928/// Borrowed reliable Station frame decoded from caller-owned bytes.
2929#[derive(Clone, Copy, Debug, PartialEq, Eq)]
2930pub enum ReliableStationFrameRef<'a> {
2931    /// Reliable data packet with borrowed payload bytes.
2932    Data {
2933        /// Sender-local sequence number scoped to the target Station.
2934        sequence: u64,
2935        /// Payload borrowed from the encoded frame.
2936        payload: &'a [u8],
2937    },
2938    /// Acknowledgement for a reliable data packet.
2939    Ack {
2940        /// Acknowledged sequence number.
2941        sequence: u64,
2942    },
2943}
2944
2945impl ReliableStationFrameRef<'_> {
2946    /// Materializes the compatible owned reliable Station frame.
2947    pub fn to_owned(self) -> ReliableStationFrame {
2948        match self {
2949            Self::Data { sequence, payload } => ReliableStationFrame::Data {
2950                sequence,
2951                payload: payload.to_vec(),
2952            },
2953            Self::Ack { sequence } => ReliableStationFrame::Ack { sequence },
2954        }
2955    }
2956}
2957
2958impl ReliableStationFrame {
2959    /// Appends a data frame from a borrowed payload without materializing an owned frame.
2960    pub fn encode_data(
2961        sequence: u64,
2962        payload: &[u8],
2963        out: &mut Vec<u8>,
2964    ) -> Result<(), ReliableStationEncodeError> {
2965        out.extend_from_slice(&RELIABLE_STATION_MAGIC);
2966        out.push(RELIABLE_KIND_DATA);
2967        out.extend_from_slice(&sequence.to_le_bytes());
2968        let len = u32::try_from(payload.len()).map_err(|_| {
2969            ReliableStationEncodeError::PayloadTooLarge {
2970                actual: payload.len(),
2971            }
2972        })?;
2973        out.extend_from_slice(&len.to_le_bytes());
2974        out.extend_from_slice(payload);
2975        Ok(())
2976    }
2977
2978    /// Encodes a reliable station frame.
2979    pub fn encode(&self, out: &mut Vec<u8>) -> Result<(), ReliableStationEncodeError> {
2980        match self {
2981            Self::Data { sequence, payload } => Self::encode_data(*sequence, payload, out),
2982            Self::Ack { sequence } => {
2983                out.extend_from_slice(&RELIABLE_STATION_MAGIC);
2984                out.push(RELIABLE_KIND_ACK);
2985                out.extend_from_slice(&sequence.to_le_bytes());
2986                Ok(())
2987            }
2988        }
2989    }
2990
2991    /// Decodes a reliable station frame.
2992    pub fn decode(input: &[u8]) -> Result<Self, ReliableStationDecodeError> {
2993        Self::decode_ref(input).map(ReliableStationFrameRef::to_owned)
2994    }
2995
2996    /// Decodes a reliable Station frame while borrowing data payload bytes.
2997    pub fn decode_ref(
2998        input: &[u8],
2999    ) -> Result<ReliableStationFrameRef<'_>, ReliableStationDecodeError> {
3000        let mut cursor = ReliableCursor::new(input);
3001        let magic = cursor.read_array::<4>()?;
3002        if magic != RELIABLE_STATION_MAGIC {
3003            return Err(ReliableStationDecodeError::BadMagic);
3004        }
3005        let kind = cursor.read_u8()?;
3006        let sequence = cursor.read_u64()?;
3007        let frame = match kind {
3008            RELIABLE_KIND_DATA => {
3009                let len = cursor.read_u32()? as usize;
3010                let payload = cursor.read_slice(len)?;
3011                ReliableStationFrameRef::Data { sequence, payload }
3012            }
3013            RELIABLE_KIND_ACK => ReliableStationFrameRef::Ack { sequence },
3014            other => return Err(ReliableStationDecodeError::UnknownKind(other)),
3015        };
3016        cursor.finish()?;
3017        Ok(frame)
3018    }
3019}
3020
3021/// Reliable station frame encode error.
3022#[derive(Clone, Copy, Debug, PartialEq, Eq)]
3023pub enum ReliableStationEncodeError {
3024    /// Payload length exceeded `u32::MAX`.
3025    PayloadTooLarge {
3026        /// Actual byte count.
3027        actual: usize,
3028    },
3029}
3030
3031impl core::fmt::Display for ReliableStationEncodeError {
3032    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
3033        match self {
3034            Self::PayloadTooLarge { actual } => {
3035                write!(f, "reliable station payload too large: {actual} bytes")
3036            }
3037        }
3038    }
3039}
3040
3041impl std::error::Error for ReliableStationEncodeError {}
3042
3043/// Reliable station frame decode error.
3044#[derive(Clone, Copy, Debug, PartialEq, Eq)]
3045pub enum ReliableStationDecodeError {
3046    /// Frame magic did not match.
3047    BadMagic,
3048    /// Frame kind byte is unknown.
3049    UnknownKind(u8),
3050    /// Frame ended before all fields were available.
3051    Truncated {
3052        /// Required bytes.
3053        needed: usize,
3054        /// Available bytes.
3055        available: usize,
3056    },
3057    /// Frame had trailing bytes after a complete payload.
3058    TrailingBytes(usize),
3059}
3060
3061impl core::fmt::Display for ReliableStationDecodeError {
3062    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
3063        match self {
3064            Self::BadMagic => f.write_str("bad reliable station frame magic"),
3065            Self::UnknownKind(kind) => write!(f, "unknown reliable station frame kind {kind}"),
3066            Self::Truncated { needed, available } => {
3067                write!(
3068                    f,
3069                    "truncated reliable station frame: needed {needed}, available {available}"
3070                )
3071            }
3072            Self::TrailingBytes(bytes) => {
3073                write!(f, "reliable station frame has {bytes} trailing bytes")
3074            }
3075        }
3076    }
3077}
3078
3079impl std::error::Error for ReliableStationDecodeError {}
3080
3081/// Error produced by reliable station endpoints.
3082#[derive(Debug)]
3083pub enum ReliableStationError<E> {
3084    /// Underlying station transport failed.
3085    Transport(E),
3086    /// Payload exceeded configured byte budget.
3087    PayloadTooLarge {
3088        /// Configured byte budget.
3089        budget: usize,
3090        /// Actual byte count.
3091        actual: usize,
3092    },
3093    /// Target station in-flight window is full.
3094    WindowFull {
3095        /// Target station.
3096        target_station: StationId,
3097        /// Configured in-flight capacity.
3098        capacity: usize,
3099    },
3100    /// Reliable frame encode failed.
3101    Encode(ReliableStationEncodeError),
3102    /// Reliable frame decode failed.
3103    Decode(ReliableStationDecodeError),
3104}
3105
3106impl<E: core::fmt::Display> core::fmt::Display for ReliableStationError<E> {
3107    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
3108        match self {
3109            Self::Transport(error) => write!(f, "{error}"),
3110            Self::PayloadTooLarge { budget, actual } => {
3111                write!(
3112                    f,
3113                    "reliable station payload exceeded byte budget: budget {budget}, actual {actual}"
3114                )
3115            }
3116            Self::WindowFull {
3117                target_station,
3118                capacity,
3119            } => write!(
3120                f,
3121                "reliable station target {} window is full at capacity {capacity}",
3122                target_station.get()
3123            ),
3124            Self::Encode(error) => write!(f, "{error}"),
3125            Self::Decode(error) => write!(f, "{error}"),
3126        }
3127    }
3128}
3129
3130impl<E> std::error::Error for ReliableStationError<E>
3131where
3132    E: std::error::Error + 'static,
3133{
3134    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
3135        match self {
3136            Self::Transport(error) => Some(error),
3137            Self::Encode(error) => Some(error),
3138            Self::Decode(error) => Some(error),
3139            Self::PayloadTooLarge { .. } | Self::WindowFull { .. } => None,
3140        }
3141    }
3142}
3143
3144#[derive(Clone, Debug, PartialEq, Eq)]
3145struct InFlightReliableStationPacket {
3146    source_station: StationId,
3147    target_station: StationId,
3148    sequence: u64,
3149    payload: Vec<u8>,
3150    first_sent_tick: u64,
3151    last_sent_tick: u64,
3152    attempts: u8,
3153}
3154
3155/// Caller-owned reusable storage for reliable station retry scans.
3156#[derive(Clone, Debug, Default)]
3157pub struct ReliableStationRetryScratch {
3158    due_entries: Vec<(u64, StationId, u64)>,
3159}
3160
3161impl ReliableStationRetryScratch {
3162    /// Creates empty retry storage.
3163    pub fn new() -> Self {
3164        Self::default()
3165    }
3166
3167    /// Due-key capacity retained across retry passes.
3168    pub fn retained_key_capacity(&self) -> usize {
3169        self.due_entries.capacity()
3170    }
3171}
3172
3173/// Retry pass report.
3174#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
3175pub struct ReliableRetryReport {
3176    /// Current deadline entries examined during this pass.
3177    pub examined: usize,
3178    /// Packets resent.
3179    pub retried: usize,
3180    /// Packets dropped after exhausting attempts.
3181    pub timed_out: usize,
3182}
3183
3184/// Bounded reliable station sender state.
3185#[derive(Clone, Debug)]
3186pub struct ReliableStationSender {
3187    config: ReliableStationConfig,
3188    next_sequence: BTreeMap<StationId, u64>,
3189    in_flight: BTreeMap<(StationId, u64), InFlightReliableStationPacket>,
3190    in_flight_by_target: BTreeMap<StationId, usize>,
3191    retry_deadlines: BinaryHeap<Reverse<(u64, StationId, u64)>>,
3192    stats: ReliableStationStats,
3193}
3194
3195impl ReliableStationSender {
3196    /// Creates a reliable station sender.
3197    pub fn new(config: ReliableStationConfig) -> Self {
3198        Self {
3199            config,
3200            next_sequence: BTreeMap::new(),
3201            in_flight: BTreeMap::new(),
3202            in_flight_by_target: BTreeMap::new(),
3203            retry_deadlines: BinaryHeap::new(),
3204            stats: ReliableStationStats::default(),
3205        }
3206    }
3207
3208    /// Returns sender configuration.
3209    pub const fn config(&self) -> ReliableStationConfig {
3210        self.config
3211    }
3212
3213    /// Returns sender statistics.
3214    pub const fn stats(&self) -> ReliableStationStats {
3215        self.stats
3216    }
3217
3218    /// Returns total in-flight reliable packets.
3219    pub fn in_flight_len(&self) -> usize {
3220        self.in_flight.len()
3221    }
3222
3223    /// Returns in-flight reliable packets for one target station.
3224    pub fn in_flight_for(&self, target_station: StationId) -> usize {
3225        self.in_flight_by_target
3226            .get(&target_station)
3227            .copied()
3228            .unwrap_or(0)
3229    }
3230
3231    /// Sends a new reliable packet and stores it until acknowledged or timed out.
3232    pub fn send<T: StationTransportSink>(
3233        &mut self,
3234        transport: &mut T,
3235        packet: StationOutboundPacket,
3236        now_tick: u64,
3237    ) -> Result<u64, ReliableStationError<T::Error>> {
3238        self.validate_payload(packet.bytes.len())?;
3239        if self.in_flight_for(packet.target_station) >= self.config.max_in_flight_per_target {
3240            return Err(ReliableStationError::WindowFull {
3241                target_station: packet.target_station,
3242                capacity: self.config.max_in_flight_per_target,
3243            });
3244        }
3245
3246        let sequence = self.allocate_sequence(packet.target_station);
3247        Self::send_data_frame(
3248            transport,
3249            packet.source_station,
3250            packet.target_station,
3251            sequence,
3252            &packet.bytes,
3253        )?;
3254        self.insert_in_flight(
3255            (packet.target_station, sequence),
3256            InFlightReliableStationPacket {
3257                source_station: packet.source_station,
3258                target_station: packet.target_station,
3259                sequence,
3260                payload: packet.bytes,
3261                first_sent_tick: now_tick,
3262                last_sent_tick: now_tick,
3263                attempts: 1,
3264            },
3265        );
3266        self.schedule_retry(packet.target_station, sequence, now_tick);
3267        self.stats.data_sent = self.stats.data_sent.saturating_add(1);
3268        Ok(sequence)
3269    }
3270
3271    /// Processes an ACK from `ack_source_station`.
3272    pub fn acknowledge(&mut self, ack_source_station: StationId, sequence: u64) -> bool {
3273        let removed = self
3274            .remove_in_flight(&(ack_source_station, sequence))
3275            .is_some();
3276        if removed {
3277            self.stats.acks_received = self.stats.acks_received.saturating_add(1);
3278            self.compact_retry_deadlines_if_stale();
3279        }
3280        removed
3281    }
3282
3283    fn compact_retry_deadlines_if_stale(&mut self) {
3284        if self.retry_deadlines.len()
3285            > self
3286                .in_flight
3287                .len()
3288                .saturating_mul(RETRY_DEADLINE_COMPACTION_MULTIPLE)
3289        {
3290            self.retry_deadlines = self
3291                .in_flight
3292                .values()
3293                .map(|packet| {
3294                    Reverse((
3295                        packet
3296                            .last_sent_tick
3297                            .saturating_add(self.config.retry_after_ticks),
3298                        packet.target_station,
3299                        packet.sequence,
3300                    ))
3301                })
3302                .collect();
3303        }
3304    }
3305
3306    /// Retries due packets using caller-owned scan storage.
3307    pub fn retry_due_into<T: StationTransportSink>(
3308        &mut self,
3309        transport: &mut T,
3310        now_tick: u64,
3311        scratch: &mut ReliableStationRetryScratch,
3312    ) -> Result<ReliableRetryReport, ReliableStationError<T::Error>> {
3313        scratch.due_entries.clear();
3314        while let Some(Reverse((deadline, target, sequence))) = self.retry_deadlines.peek().copied()
3315        {
3316            if deadline > now_tick {
3317                break;
3318            }
3319            self.retry_deadlines.pop();
3320            scratch.due_entries.push((deadline, target, sequence));
3321        }
3322        let mut report = ReliableRetryReport::default();
3323
3324        for (entry_index, (deadline, target, sequence)) in scratch.due_entries.iter().enumerate() {
3325            let key = (*target, *sequence);
3326            let Some(packet) = self.in_flight.get(&key) else {
3327                continue;
3328            };
3329            if packet
3330                .last_sent_tick
3331                .saturating_add(self.config.retry_after_ticks)
3332                != *deadline
3333            {
3334                continue;
3335            }
3336            report.examined = report.examined.saturating_add(1);
3337            if packet.attempts >= self.config.max_attempts {
3338                self.remove_in_flight(&key);
3339                self.stats.timed_out = self.stats.timed_out.saturating_add(1);
3340                report.timed_out = report.timed_out.saturating_add(1);
3341                continue;
3342            }
3343
3344            if let Err(error) = Self::send_data_frame(
3345                transport,
3346                packet.source_station,
3347                packet.target_station,
3348                packet.sequence,
3349                &packet.payload,
3350            ) {
3351                self.retry_deadlines.extend(
3352                    scratch.due_entries[entry_index..]
3353                        .iter()
3354                        .copied()
3355                        .map(Reverse),
3356                );
3357                return Err(error);
3358            }
3359            if let Some(stored) = self.in_flight.get_mut(&key) {
3360                stored.last_sent_tick = now_tick;
3361                stored.attempts = stored.attempts.saturating_add(1);
3362            }
3363            self.schedule_retry(*target, *sequence, now_tick);
3364            self.stats.retries_sent = self.stats.retries_sent.saturating_add(1);
3365            report.retried = report.retried.saturating_add(1);
3366        }
3367
3368        Ok(report)
3369    }
3370
3371    fn validate_payload<E>(&self, bytes: usize) -> Result<(), ReliableStationError<E>> {
3372        if bytes > self.config.max_payload_bytes {
3373            Err(ReliableStationError::PayloadTooLarge {
3374                budget: self.config.max_payload_bytes,
3375                actual: bytes,
3376            })
3377        } else {
3378            Ok(())
3379        }
3380    }
3381
3382    fn allocate_sequence(&mut self, target_station: StationId) -> u64 {
3383        let next = self.next_sequence.entry(target_station).or_insert(1);
3384        let sequence = *next;
3385        *next = next.saturating_add(1);
3386        sequence
3387    }
3388
3389    fn insert_in_flight(&mut self, key: (StationId, u64), packet: InFlightReliableStationPacket) {
3390        if self.in_flight.insert(key, packet).is_none() {
3391            let count = self.in_flight_by_target.entry(key.0).or_insert(0);
3392            *count = count.saturating_add(1);
3393        }
3394    }
3395
3396    fn schedule_retry(&mut self, target: StationId, sequence: u64, sent_tick: u64) {
3397        self.retry_deadlines.push(Reverse((
3398            sent_tick.saturating_add(self.config.retry_after_ticks),
3399            target,
3400            sequence,
3401        )));
3402    }
3403
3404    fn remove_in_flight(
3405        &mut self,
3406        key: &(StationId, u64),
3407    ) -> Option<InFlightReliableStationPacket> {
3408        let removed = self.in_flight.remove(key)?;
3409        if let Some(count) = self.in_flight_by_target.get_mut(&removed.target_station) {
3410            *count = count.saturating_sub(1);
3411            if *count == 0 {
3412                self.in_flight_by_target.remove(&removed.target_station);
3413            }
3414        }
3415        Some(removed)
3416    }
3417
3418    fn send_data_frame<T: StationTransportSink>(
3419        transport: &mut T,
3420        source_station: StationId,
3421        target_station: StationId,
3422        sequence: u64,
3423        payload: &[u8],
3424    ) -> Result<(), ReliableStationError<T::Error>> {
3425        let mut bytes = Vec::with_capacity(
3426            payload
3427                .len()
3428                .saturating_add(RELIABLE_STATION_DATA_HEADER_BYTES),
3429        );
3430        ReliableStationFrame::encode_data(sequence, payload, &mut bytes)
3431            .map_err(ReliableStationError::Encode)?;
3432        transport
3433            .send_station(StationOutboundPacket {
3434                source_station,
3435                target_station,
3436                bytes,
3437            })
3438            .map_err(ReliableStationError::Transport)
3439    }
3440}
3441
3442impl Default for ReliableStationSender {
3443    fn default() -> Self {
3444        Self::new(ReliableStationConfig::default())
3445    }
3446}
3447
3448/// Bounded reliable station receiver state.
3449#[derive(Clone, Debug)]
3450pub struct ReliableStationReceiver {
3451    config: ReliableStationConfig,
3452    delivered: BoundedLookupSet<(StationId, u64)>,
3453    delivered_order: VecDeque<(StationId, u64)>,
3454    stats: ReliableStationStats,
3455}
3456
3457impl ReliableStationReceiver {
3458    /// Creates a bounded reliable station receiver.
3459    pub fn new(config: ReliableStationConfig) -> Self {
3460        Self {
3461            config,
3462            delivered: BoundedLookupSet::new(config.max_delivered_history),
3463            delivered_order: VecDeque::new(),
3464            stats: ReliableStationStats::default(),
3465        }
3466    }
3467
3468    /// Returns receiver configuration.
3469    pub const fn config(&self) -> ReliableStationConfig {
3470        self.config
3471    }
3472
3473    /// Returns receiver statistics.
3474    pub const fn stats(&self) -> ReliableStationStats {
3475        self.stats
3476    }
3477
3478    /// Handles a reliable data packet, sends an ACK, and returns a payload only
3479    /// for first delivery.
3480    pub fn handle_data<T: StationTransportSink>(
3481        &mut self,
3482        transport: &mut T,
3483        packet: StationInboundPacket,
3484        sequence: u64,
3485        payload: Vec<u8>,
3486    ) -> Result<Option<StationInboundPacket>, ReliableStationError<T::Error>> {
3487        let StationInboundPacket {
3488            source_station,
3489            target_station,
3490            bytes: wire_bytes,
3491        } = packet;
3492        drop(wire_bytes);
3493        self.send_ack(transport, target_station, source_station, sequence)?;
3494        if !self.record_unique(source_station, sequence) {
3495            self.stats.duplicates_suppressed = self.stats.duplicates_suppressed.saturating_add(1);
3496            return Ok(None);
3497        }
3498
3499        self.stats.data_delivered = self.stats.data_delivered.saturating_add(1);
3500        Ok(Some(StationInboundPacket {
3501            source_station,
3502            target_station,
3503            bytes: payload,
3504        }))
3505    }
3506
3507    fn send_ack<T: StationTransportSink>(
3508        &mut self,
3509        transport: &mut T,
3510        source_station: StationId,
3511        target_station: StationId,
3512        sequence: u64,
3513    ) -> Result<(), ReliableStationError<T::Error>> {
3514        let mut bytes = Vec::with_capacity(RELIABLE_STATION_ACK_BYTES);
3515        ReliableStationFrame::Ack { sequence }
3516            .encode(&mut bytes)
3517            .map_err(ReliableStationError::Encode)?;
3518        transport
3519            .send_station(StationOutboundPacket {
3520                source_station,
3521                target_station,
3522                bytes,
3523            })
3524            .map_err(ReliableStationError::Transport)?;
3525        self.stats.acks_sent = self.stats.acks_sent.saturating_add(1);
3526        Ok(())
3527    }
3528
3529    fn record_unique(&mut self, source_station: StationId, sequence: u64) -> bool {
3530        if self.config.max_delivered_history == 0 {
3531            return true;
3532        }
3533
3534        let key = (source_station, sequence);
3535        if self.delivered.contains(&key) {
3536            return false;
3537        }
3538
3539        self.delivered.insert(key);
3540        self.delivered_order.push_back(key);
3541        while self.delivered_order.len() > self.config.max_delivered_history {
3542            if let Some(old) = self.delivered_order.pop_front() {
3543                self.delivered.remove(&old);
3544            }
3545        }
3546        true
3547    }
3548}
3549
3550impl Default for ReliableStationReceiver {
3551    fn default() -> Self {
3552        Self::new(ReliableStationConfig::default())
3553    }
3554}
3555
3556/// Reliable station endpoint combining sender and receiver state.
3557#[derive(Clone, Debug)]
3558pub struct ReliableStationEndpoint {
3559    /// Sender state.
3560    pub sender: ReliableStationSender,
3561    /// Receiver state.
3562    pub receiver: ReliableStationReceiver,
3563    retry_scratch: ReliableStationRetryScratch,
3564}
3565
3566impl ReliableStationEndpoint {
3567    /// Creates a reliable station endpoint.
3568    pub fn new(config: ReliableStationConfig) -> Self {
3569        Self {
3570            sender: ReliableStationSender::new(config),
3571            receiver: ReliableStationReceiver::new(config),
3572            retry_scratch: ReliableStationRetryScratch::default(),
3573        }
3574    }
3575
3576    /// Sends a new reliable station packet.
3577    pub fn send<T: StationTransportSink>(
3578        &mut self,
3579        transport: &mut T,
3580        packet: StationOutboundPacket,
3581        now_tick: u64,
3582    ) -> Result<u64, ReliableStationError<T::Error>> {
3583        self.sender.send(transport, packet, now_tick)
3584    }
3585
3586    /// Retries due reliable station packets.
3587    pub fn retry_due<T: StationTransportSink>(
3588        &mut self,
3589        transport: &mut T,
3590        now_tick: u64,
3591    ) -> Result<ReliableRetryReport, ReliableStationError<T::Error>> {
3592        self.sender
3593            .retry_due_into(transport, now_tick, &mut self.retry_scratch)
3594    }
3595
3596    /// Handles one inbound reliable station packet.
3597    pub fn handle_inbound<T: StationTransportSink>(
3598        &mut self,
3599        transport: &mut T,
3600        packet: StationInboundPacket,
3601    ) -> Result<Option<StationInboundPacket>, ReliableStationError<T::Error>> {
3602        match ReliableStationFrame::decode_ref(&packet.bytes)
3603            .map_err(ReliableStationError::Decode)?
3604        {
3605            ReliableStationFrameRef::Data { sequence, payload } => {
3606                let payload_len = payload.len();
3607                let payload_offset = packet.bytes.len().saturating_sub(payload_len);
3608                let StationInboundPacket {
3609                    source_station,
3610                    target_station,
3611                    bytes,
3612                } = packet;
3613                let payload = reuse_reliable_payload(bytes, payload_offset, payload_len);
3614                self.receiver.handle_data(
3615                    transport,
3616                    StationInboundPacket {
3617                        source_station,
3618                        target_station,
3619                        bytes: Vec::new(),
3620                    },
3621                    sequence,
3622                    payload,
3623                )
3624            }
3625            ReliableStationFrameRef::Ack { sequence } => {
3626                self.sender.acknowledge(packet.source_station, sequence);
3627                Ok(None)
3628            }
3629        }
3630    }
3631}
3632
3633impl Default for ReliableStationEndpoint {
3634    fn default() -> Self {
3635        Self::new(ReliableStationConfig::default())
3636    }
3637}
3638
3639struct ReliableCursor<'a> {
3640    input: &'a [u8],
3641    offset: usize,
3642}
3643
3644impl<'a> ReliableCursor<'a> {
3645    const fn new(input: &'a [u8]) -> Self {
3646        Self { input, offset: 0 }
3647    }
3648
3649    fn read_u8(&mut self) -> Result<u8, ReliableStationDecodeError> {
3650        self.require(1)?;
3651        let value = self.input[self.offset];
3652        self.offset += 1;
3653        Ok(value)
3654    }
3655
3656    fn read_u32(&mut self) -> Result<u32, ReliableStationDecodeError> {
3657        let bytes = self.read_array::<4>()?;
3658        Ok(u32::from_le_bytes(bytes))
3659    }
3660
3661    fn read_u64(&mut self) -> Result<u64, ReliableStationDecodeError> {
3662        let bytes = self.read_array::<8>()?;
3663        Ok(u64::from_le_bytes(bytes))
3664    }
3665
3666    fn read_array<const N: usize>(&mut self) -> Result<[u8; N], ReliableStationDecodeError> {
3667        self.require(N)?;
3668        let mut out = [0_u8; N];
3669        out.copy_from_slice(&self.input[self.offset..self.offset + N]);
3670        self.offset += N;
3671        Ok(out)
3672    }
3673
3674    fn read_slice(&mut self, len: usize) -> Result<&'a [u8], ReliableStationDecodeError> {
3675        self.require(len)?;
3676        let bytes = &self.input[self.offset..self.offset + len];
3677        self.offset += len;
3678        Ok(bytes)
3679    }
3680
3681    fn require(&self, count: usize) -> Result<(), ReliableStationDecodeError> {
3682        let needed = self.offset.saturating_add(count);
3683        if needed > self.input.len() {
3684            Err(ReliableStationDecodeError::Truncated {
3685                needed,
3686                available: self.input.len(),
3687            })
3688        } else {
3689            Ok(())
3690        }
3691    }
3692
3693    fn finish(&self) -> Result<(), ReliableStationDecodeError> {
3694        if self.offset == self.input.len() {
3695            Ok(())
3696        } else {
3697            Err(ReliableStationDecodeError::TrailingBytes(
3698                self.input.len().saturating_sub(self.offset),
3699            ))
3700        }
3701    }
3702}
3703
3704fn reuse_reliable_payload(mut wire_bytes: Vec<u8>, offset: usize, len: usize) -> Vec<u8> {
3705    wire_bytes.copy_within(offset..offset.saturating_add(len), 0);
3706    wire_bytes.truncate(len);
3707    wire_bytes
3708}
3709
3710/// Bounded in-memory station transport limits.
3711#[derive(Clone, Copy, Debug, PartialEq, Eq)]
3712pub struct StationTransportLimits {
3713    /// Maximum queued packets per target station.
3714    pub max_queued_packets_per_station: usize,
3715    /// Maximum bytes accepted per packet.
3716    pub max_packet_bytes: usize,
3717}
3718
3719impl Default for StationTransportLimits {
3720    fn default() -> Self {
3721        Self {
3722            max_queued_packets_per_station: 4096,
3723            max_packet_bytes: 16 * 1024,
3724        }
3725    }
3726}
3727
3728/// Statistics for the bounded in-memory station transport.
3729#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
3730pub struct InMemoryStationTransportStats {
3731    /// Packets accepted for delivery.
3732    pub packets_sent: usize,
3733    /// Packets received by target stations.
3734    pub packets_received: usize,
3735    /// Bytes accepted for delivery.
3736    pub bytes_sent: usize,
3737    /// Bytes received by target stations.
3738    pub bytes_received: usize,
3739    /// Packets rejected because the target station queue was full.
3740    pub packets_rejected_full: usize,
3741    /// Packets rejected because they exceeded the packet byte budget.
3742    pub packets_rejected_bytes: usize,
3743}
3744
3745/// Statistics for the UDP station transport adapter.
3746#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
3747pub struct UdpStationTransportStats {
3748    /// Packets sent by this local station transport.
3749    pub packets_sent: usize,
3750    /// Packets received by this local station transport.
3751    pub packets_received: usize,
3752    /// Bytes sent by this local station transport.
3753    pub bytes_sent: usize,
3754    /// Bytes received by this local station transport.
3755    pub bytes_received: usize,
3756}
3757
3758/// Error produced by bounded in-memory station transport.
3759#[derive(Clone, Copy, Debug, PartialEq, Eq)]
3760pub enum StationTransportError {
3761    /// Target station was not registered.
3762    MissingTarget(StationId),
3763    /// Target station queue is full.
3764    QueueFull {
3765        /// Target station.
3766        station_id: StationId,
3767        /// Configured queue capacity.
3768        capacity: usize,
3769    },
3770    /// Packet exceeded the byte budget.
3771    PacketTooLarge {
3772        /// Configured byte budget.
3773        budget: usize,
3774        /// Actual byte count.
3775        actual: usize,
3776    },
3777}
3778
3779impl core::fmt::Display for StationTransportError {
3780    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
3781        match self {
3782            Self::MissingTarget(station_id) => {
3783                write!(
3784                    f,
3785                    "station transport target {} is missing",
3786                    station_id.get()
3787                )
3788            }
3789            Self::QueueFull {
3790                station_id,
3791                capacity,
3792            } => write!(
3793                f,
3794                "station transport target {} queue is full at capacity {capacity}",
3795                station_id.get()
3796            ),
3797            Self::PacketTooLarge { budget, actual } => {
3798                write!(
3799                    f,
3800                    "station transport packet exceeded byte budget: budget {budget}, actual {actual}"
3801                )
3802            }
3803        }
3804    }
3805}
3806
3807impl std::error::Error for StationTransportError {}
3808
3809/// Bounded in-memory station-to-station packet transport.
3810#[derive(Clone, Debug)]
3811pub struct InMemoryStationTransport {
3812    limits: StationTransportLimits,
3813    queues: AdaptiveEndpointMap<StationId, VecDeque<StationInboundPacket>>,
3814    stats: InMemoryStationTransportStats,
3815}
3816
3817impl InMemoryStationTransport {
3818    /// Creates an empty bounded station transport.
3819    pub fn new(limits: StationTransportLimits) -> Self {
3820        Self {
3821            limits,
3822            queues: AdaptiveEndpointMap::new(),
3823            stats: InMemoryStationTransportStats::default(),
3824        }
3825    }
3826
3827    /// Registers a target station queue.
3828    pub fn register_station(&mut self, station_id: StationId) {
3829        if self.queues.get(&station_id).is_none() {
3830            self.queues.insert(station_id, VecDeque::new());
3831        }
3832    }
3833
3834    /// Unregisters a station and releases its queued packet storage.
3835    pub fn unregister_station(&mut self, station_id: StationId) -> Option<usize> {
3836        self.queues.remove(&station_id).map(|queue| queue.len())
3837    }
3838
3839    /// Returns queued packet count for a station.
3840    pub fn queued_len(&self, station_id: StationId) -> Option<usize> {
3841        self.queues.get(&station_id).map(VecDeque::len)
3842    }
3843
3844    /// Returns packet slots retained by one station queue.
3845    pub fn queued_capacity(&self, station_id: StationId) -> Option<usize> {
3846        self.queues.get(&station_id).map(VecDeque::capacity)
3847    }
3848
3849    /// Returns packet slots retained across all registered station queues.
3850    pub fn retained_queue_capacity(&self) -> usize {
3851        self.queues.values().map(VecDeque::capacity).sum()
3852    }
3853
3854    /// Returns configured limits.
3855    pub const fn limits(&self) -> StationTransportLimits {
3856        self.limits
3857    }
3858
3859    /// Returns transport statistics.
3860    pub const fn stats(&self) -> InMemoryStationTransportStats {
3861        self.stats
3862    }
3863}
3864
3865impl Default for InMemoryStationTransport {
3866    fn default() -> Self {
3867        Self::new(StationTransportLimits::default())
3868    }
3869}
3870
3871impl StationTransportSink for InMemoryStationTransport {
3872    type Error = StationTransportError;
3873
3874    fn send_station(&mut self, packet: StationOutboundPacket) -> Result<(), Self::Error> {
3875        let actual = packet.bytes.len();
3876        if actual > self.limits.max_packet_bytes {
3877            self.stats.packets_rejected_bytes = self.stats.packets_rejected_bytes.saturating_add(1);
3878            return Err(StationTransportError::PacketTooLarge {
3879                budget: self.limits.max_packet_bytes,
3880                actual,
3881            });
3882        }
3883
3884        let queue = self
3885            .queues
3886            .get_mut(&packet.target_station)
3887            .ok_or(StationTransportError::MissingTarget(packet.target_station))?;
3888        if queue.len() >= self.limits.max_queued_packets_per_station {
3889            self.stats.packets_rejected_full = self.stats.packets_rejected_full.saturating_add(1);
3890            return Err(StationTransportError::QueueFull {
3891                station_id: packet.target_station,
3892                capacity: self.limits.max_queued_packets_per_station,
3893            });
3894        }
3895
3896        self.stats.packets_sent = self.stats.packets_sent.saturating_add(1);
3897        self.stats.bytes_sent = self.stats.bytes_sent.saturating_add(actual);
3898        queue.push_back(StationInboundPacket {
3899            source_station: packet.source_station,
3900            target_station: packet.target_station,
3901            bytes: packet.bytes,
3902        });
3903        Ok(())
3904    }
3905}
3906
3907impl StationTransportReceiver for InMemoryStationTransport {
3908    type Error = StationTransportError;
3909
3910    fn try_recv_station(
3911        &mut self,
3912        target_station: StationId,
3913    ) -> Result<Option<StationInboundPacket>, Self::Error> {
3914        let queue = self
3915            .queues
3916            .get_mut(&target_station)
3917            .ok_or(StationTransportError::MissingTarget(target_station))?;
3918        let Some(packet) = queue.pop_front() else {
3919            return Ok(None);
3920        };
3921        self.stats.packets_received = self.stats.packets_received.saturating_add(1);
3922        self.stats.bytes_received = self.stats.bytes_received.saturating_add(packet.bytes.len());
3923        Ok(Some(packet))
3924    }
3925}
3926
3927/// Error produced by the UDP station transport adapter.
3928#[derive(Debug)]
3929pub enum UdpStationTransportError {
3930    /// No address has been registered for the target station.
3931    UnknownStation(StationId),
3932    /// A packet arrived from an unregistered remote address.
3933    UnknownRemote(SocketAddr),
3934    /// Outbound packet source did not match the local station.
3935    LocalStationMismatch {
3936        /// Local station owned by this transport.
3937        local_station: StationId,
3938        /// Packet source station.
3939        packet_source: StationId,
3940    },
3941    /// Receive was requested for a station not owned by this transport.
3942    TargetStationMismatch {
3943        /// Local station owned by this transport.
3944        local_station: StationId,
3945        /// Requested target station.
3946        requested_target: StationId,
3947    },
3948    /// Underlying socket error.
3949    Io(io::Error),
3950}
3951
3952impl core::fmt::Display for UdpStationTransportError {
3953    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
3954        match self {
3955            Self::UnknownStation(station_id) => {
3956                write!(
3957                    f,
3958                    "udp station target {} is not registered",
3959                    station_id.get()
3960                )
3961            }
3962            Self::UnknownRemote(addr) => {
3963                write!(f, "udp station remote address {addr} is not registered")
3964            }
3965            Self::LocalStationMismatch {
3966                local_station,
3967                packet_source,
3968            } => write!(
3969                f,
3970                "udp station local source mismatch: local {}, packet source {}",
3971                local_station.get(),
3972                packet_source.get()
3973            ),
3974            Self::TargetStationMismatch {
3975                local_station,
3976                requested_target,
3977            } => write!(
3978                f,
3979                "udp station receive target mismatch: local {}, requested {}",
3980                local_station.get(),
3981                requested_target.get()
3982            ),
3983            Self::Io(error) => write!(f, "{error}"),
3984        }
3985    }
3986}
3987
3988impl std::error::Error for UdpStationTransportError {
3989    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
3990        match self {
3991            Self::Io(error) => Some(error),
3992            Self::UnknownStation(_)
3993            | Self::UnknownRemote(_)
3994            | Self::LocalStationMismatch { .. }
3995            | Self::TargetStationMismatch { .. } => None,
3996        }
3997    }
3998}
3999
4000impl From<io::Error> for UdpStationTransportError {
4001    fn from(error: io::Error) -> Self {
4002        Self::Io(error)
4003    }
4004}
4005
4006/// Lightweight UDP station-to-station packet adapter.
4007///
4008/// Each instance represents one local station socket. Reliability,
4009/// authentication, encryption, and deployment routing remain outer-layer
4010/// concerns.
4011#[derive(Debug)]
4012pub struct UdpStationTransport {
4013    local_station: StationId,
4014    socket: UdpSocket,
4015    stations: HashMap<StationId, SocketAddr>,
4016    addr_to_station: HashMap<SocketAddr, StationId>,
4017    recv_buffer: Vec<u8>,
4018    stats: UdpStationTransportStats,
4019}
4020
4021impl UdpStationTransport {
4022    /// Binds a non-blocking UDP station transport.
4023    pub fn bind<A: ToSocketAddrs>(local_station: StationId, addr: A) -> io::Result<Self> {
4024        let socket = UdpSocket::bind(addr)?;
4025        Self::from_socket(local_station, socket)
4026    }
4027
4028    /// Wraps an existing UDP socket and configures it as non-blocking.
4029    pub fn from_socket(local_station: StationId, socket: UdpSocket) -> io::Result<Self> {
4030        socket.set_nonblocking(true)?;
4031        Ok(Self {
4032            local_station,
4033            socket,
4034            stations: HashMap::new(),
4035            addr_to_station: HashMap::new(),
4036            recv_buffer: vec![0; DEFAULT_UDP_RECV_BUFFER_SIZE],
4037            stats: UdpStationTransportStats::default(),
4038        })
4039    }
4040
4041    /// Returns the local station id.
4042    pub const fn local_station(&self) -> StationId {
4043        self.local_station
4044    }
4045
4046    /// Returns the local socket address.
4047    pub fn local_addr(&self) -> io::Result<SocketAddr> {
4048        self.socket.local_addr()
4049    }
4050
4051    /// Borrows the underlying socket.
4052    pub const fn socket(&self) -> &UdpSocket {
4053        &self.socket
4054    }
4055
4056    /// Mutably borrows the underlying socket.
4057    pub fn socket_mut(&mut self) -> &mut UdpSocket {
4058        &mut self.socket
4059    }
4060
4061    /// Registers or replaces the network address for a station.
4062    pub fn register_station(
4063        &mut self,
4064        station_id: StationId,
4065        addr: SocketAddr,
4066    ) -> Option<SocketAddr> {
4067        let old_addr = self.stations.insert(station_id, addr);
4068        if let Some(old_addr) = old_addr {
4069            self.addr_to_station.remove(&old_addr);
4070        }
4071        if let Some(old_station) = self.addr_to_station.insert(addr, station_id)
4072            && old_station != station_id
4073        {
4074            self.stations.remove(&old_station);
4075        }
4076        old_addr
4077    }
4078
4079    /// Removes a registered station address.
4080    pub fn unregister_station(&mut self, station_id: StationId) -> Option<SocketAddr> {
4081        let addr = self.stations.remove(&station_id)?;
4082        self.addr_to_station.remove(&addr);
4083        Some(addr)
4084    }
4085
4086    /// Returns a registered address for a station.
4087    pub fn station_addr(&self, station_id: StationId) -> Option<SocketAddr> {
4088        self.stations.get(&station_id).copied()
4089    }
4090
4091    /// Returns the registered station id for a remote address.
4092    pub fn station_for_addr(&self, addr: SocketAddr) -> Option<StationId> {
4093        self.addr_to_station.get(&addr).copied()
4094    }
4095
4096    /// Sets the reusable receive buffer size.
4097    pub fn set_recv_buffer_size(&mut self, bytes: usize) {
4098        self.recv_buffer.resize(bytes.max(1), 0);
4099    }
4100
4101    /// Returns the reusable receive buffer size.
4102    pub fn recv_buffer_size(&self) -> usize {
4103        self.recv_buffer.len()
4104    }
4105
4106    /// Returns transport statistics.
4107    pub const fn stats(&self) -> UdpStationTransportStats {
4108        self.stats
4109    }
4110
4111    /// Receives one station packet while borrowing the reusable UDP buffer.
4112    ///
4113    /// Consume or copy the returned bytes before the next mutable operation on
4114    /// this adapter. The call remains non-blocking and returns `Ok(None)` when
4115    /// no datagram is ready.
4116    pub fn try_recv_station_ref(
4117        &mut self,
4118        target_station: StationId,
4119    ) -> Result<Option<StationInboundPacketRef<'_>>, UdpStationTransportError> {
4120        if target_station != self.local_station {
4121            return Err(UdpStationTransportError::TargetStationMismatch {
4122                local_station: self.local_station,
4123                requested_target: target_station,
4124            });
4125        }
4126        match self.socket.recv_from(&mut self.recv_buffer) {
4127            Ok((len, remote_addr)) => {
4128                let source_station = self
4129                    .addr_to_station
4130                    .get(&remote_addr)
4131                    .copied()
4132                    .ok_or(UdpStationTransportError::UnknownRemote(remote_addr))?;
4133                self.stats.packets_received = self.stats.packets_received.saturating_add(1);
4134                self.stats.bytes_received = self.stats.bytes_received.saturating_add(len);
4135                Ok(Some(StationInboundPacketRef {
4136                    source_station,
4137                    target_station: self.local_station,
4138                    bytes: &self.recv_buffer[..len],
4139                }))
4140            }
4141            Err(error) if error.kind() == io::ErrorKind::WouldBlock => Ok(None),
4142            Err(error) => Err(error.into()),
4143        }
4144    }
4145}
4146
4147impl StationTransportSink for UdpStationTransport {
4148    type Error = UdpStationTransportError;
4149
4150    fn send_station(&mut self, packet: StationOutboundPacket) -> Result<(), Self::Error> {
4151        if packet.source_station != self.local_station {
4152            return Err(UdpStationTransportError::LocalStationMismatch {
4153                local_station: self.local_station,
4154                packet_source: packet.source_station,
4155            });
4156        }
4157        let addr = self.stations.get(&packet.target_station).copied().ok_or(
4158            UdpStationTransportError::UnknownStation(packet.target_station),
4159        )?;
4160        let sent = self.socket.send_to(&packet.bytes, addr)?;
4161        if sent != packet.bytes.len() {
4162            return Err(io::Error::new(
4163                io::ErrorKind::WriteZero,
4164                "udp station socket reported a partial datagram send",
4165            )
4166            .into());
4167        }
4168        self.stats.packets_sent = self.stats.packets_sent.saturating_add(1);
4169        self.stats.bytes_sent = self.stats.bytes_sent.saturating_add(packet.bytes.len());
4170        Ok(())
4171    }
4172}
4173
4174impl StationTransportReceiver for UdpStationTransport {
4175    type Error = UdpStationTransportError;
4176
4177    fn try_recv_station(
4178        &mut self,
4179        target_station: StationId,
4180    ) -> Result<Option<StationInboundPacket>, Self::Error> {
4181        Ok(self
4182            .try_recv_station_ref(target_station)?
4183            .map(StationInboundPacketRef::to_owned))
4184    }
4185}
4186
4187/// Error produced by the standard UDP transport adapter.
4188#[derive(Debug)]
4189pub enum UdpTransportError {
4190    /// No address has been registered for the target client.
4191    UnknownClient(ClientId),
4192    /// Underlying socket error.
4193    Io(io::Error),
4194}
4195
4196impl core::fmt::Display for UdpTransportError {
4197    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
4198        match self {
4199            Self::UnknownClient(client_id) => {
4200                write!(f, "udp target client {} is not registered", client_id.get())
4201            }
4202            Self::Io(error) => write!(f, "{error}"),
4203        }
4204    }
4205}
4206
4207impl std::error::Error for UdpTransportError {
4208    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
4209        match self {
4210            Self::UnknownClient(_) => None,
4211            Self::Io(error) => Some(error),
4212        }
4213    }
4214}
4215
4216impl From<io::Error> for UdpTransportError {
4217    fn from(error: io::Error) -> Self {
4218        Self::Io(error)
4219    }
4220}
4221
4222/// Lightweight `std::net::UdpSocket` transport adapter.
4223///
4224/// This adapter intentionally operates at packet boundaries. Reliability,
4225/// encryption, authentication, reconnects, and gateway/session semantics are
4226/// expected to live outside the core `SectorSync` hot path.
4227#[derive(Debug)]
4228pub struct UdpTransport {
4229    socket: UdpSocket,
4230    clients: HashMap<ClientId, SocketAddr>,
4231    addr_to_client: HashMap<SocketAddr, ClientId>,
4232    recv_buffer: Vec<u8>,
4233}
4234
4235impl UdpTransport {
4236    /// Binds a non-blocking UDP socket.
4237    pub fn bind<A: ToSocketAddrs>(addr: A) -> io::Result<Self> {
4238        let socket = UdpSocket::bind(addr)?;
4239        Self::from_socket(socket)
4240    }
4241
4242    /// Wraps an existing UDP socket and configures it as non-blocking.
4243    pub fn from_socket(socket: UdpSocket) -> io::Result<Self> {
4244        socket.set_nonblocking(true)?;
4245        Ok(Self {
4246            socket,
4247            clients: HashMap::new(),
4248            addr_to_client: HashMap::new(),
4249            recv_buffer: vec![0; DEFAULT_UDP_RECV_BUFFER_SIZE],
4250        })
4251    }
4252
4253    /// Returns the local socket address.
4254    pub fn local_addr(&self) -> io::Result<SocketAddr> {
4255        self.socket.local_addr()
4256    }
4257
4258    /// Borrows the underlying socket.
4259    pub const fn socket(&self) -> &UdpSocket {
4260        &self.socket
4261    }
4262
4263    /// Mutably borrows the underlying socket.
4264    pub fn socket_mut(&mut self) -> &mut UdpSocket {
4265        &mut self.socket
4266    }
4267
4268    /// Registers or replaces the address for a client.
4269    pub fn register_client(&mut self, client_id: ClientId, addr: SocketAddr) -> Option<SocketAddr> {
4270        let old_addr = self.clients.insert(client_id, addr);
4271        if let Some(old_addr) = old_addr {
4272            self.addr_to_client.remove(&old_addr);
4273        }
4274        if let Some(old_client) = self.addr_to_client.insert(addr, client_id)
4275            && old_client != client_id
4276        {
4277            self.clients.remove(&old_client);
4278        }
4279        old_addr
4280    }
4281
4282    /// Removes a registered client address.
4283    pub fn unregister_client(&mut self, client_id: ClientId) -> Option<SocketAddr> {
4284        let addr = self.clients.remove(&client_id)?;
4285        self.addr_to_client.remove(&addr);
4286        Some(addr)
4287    }
4288
4289    /// Returns a registered address for a client.
4290    pub fn client_addr(&self, client_id: ClientId) -> Option<SocketAddr> {
4291        self.clients.get(&client_id).copied()
4292    }
4293
4294    /// Returns the registered client id for a remote address.
4295    pub fn client_for_addr(&self, addr: SocketAddr) -> Option<ClientId> {
4296        self.addr_to_client.get(&addr).copied()
4297    }
4298
4299    /// Sets the reusable receive buffer size.
4300    ///
4301    /// Datagram payloads larger than this buffer may be truncated by the OS.
4302    /// Keep replication frames under the transport MTU/budget in normal use.
4303    pub fn set_recv_buffer_size(&mut self, bytes: usize) {
4304        self.recv_buffer.resize(bytes.max(1), 0);
4305    }
4306
4307    /// Returns the reusable receive buffer size.
4308    pub fn recv_buffer_size(&self) -> usize {
4309        self.recv_buffer.len()
4310    }
4311
4312    /// Receives one packet while borrowing the reusable UDP buffer.
4313    ///
4314    /// Consume or copy the returned bytes before the next mutable operation on
4315    /// this adapter. The call remains non-blocking and returns `Ok(None)` when
4316    /// no datagram is ready.
4317    pub fn try_recv_ref(&mut self) -> Result<Option<InboundPacketRef<'_>>, UdpTransportError> {
4318        match self.socket.recv_from(&mut self.recv_buffer) {
4319            Ok((len, remote_addr)) => Ok(Some(InboundPacketRef {
4320                client_id: self.addr_to_client.get(&remote_addr).copied(),
4321                remote_addr,
4322                bytes: &self.recv_buffer[..len],
4323            })),
4324            Err(error) if error.kind() == io::ErrorKind::WouldBlock => Ok(None),
4325            Err(error) => Err(error.into()),
4326        }
4327    }
4328}
4329
4330impl TransportSink for UdpTransport {
4331    type Error = UdpTransportError;
4332
4333    fn send(&mut self, packet: OutboundPacket) -> Result<(), Self::Error> {
4334        let addr = self
4335            .clients
4336            .get(&packet.client_id)
4337            .copied()
4338            .ok_or(UdpTransportError::UnknownClient(packet.client_id))?;
4339        let sent = self.socket.send_to(&packet.bytes, addr)?;
4340        if sent == packet.bytes.len() {
4341            Ok(())
4342        } else {
4343            Err(io::Error::new(
4344                io::ErrorKind::WriteZero,
4345                "udp socket reported a partial datagram send",
4346            )
4347            .into())
4348        }
4349    }
4350}
4351
4352impl TransportReceiver for UdpTransport {
4353    type Error = UdpTransportError;
4354
4355    fn try_recv(&mut self) -> Result<Option<InboundPacket>, Self::Error> {
4356        Ok(self.try_recv_ref()?.map(InboundPacketRef::to_owned))
4357    }
4358}
4359
4360/// Transport sink error produced by wrappers.
4361#[derive(Clone, Copy, Debug, PartialEq, Eq)]
4362pub enum TransportError<E> {
4363    /// Inner sink failed.
4364    Inner(E),
4365    /// A packet or batch exceeded byte budget.
4366    ByteBudgetExceeded {
4367        /// Configured budget.
4368        budget: usize,
4369        /// Actual bytes.
4370        actual: usize,
4371    },
4372}
4373
4374impl<E: core::fmt::Display> core::fmt::Display for TransportError<E> {
4375    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
4376        match self {
4377            Self::Inner(error) => write!(f, "{error}"),
4378            Self::ByteBudgetExceeded { budget, actual } => {
4379                write!(
4380                    f,
4381                    "transport byte budget exceeded: budget {budget}, actual {actual}"
4382                )
4383            }
4384        }
4385    }
4386}
4387
4388impl<E> std::error::Error for TransportError<E> where E: std::error::Error {}
4389
4390/// Byte-budget guard for any transport sink.
4391#[derive(Clone, Debug)]
4392pub struct BudgetedTransport<T> {
4393    inner: T,
4394    max_packet_bytes: usize,
4395    max_batch_bytes: usize,
4396}
4397
4398impl<T> BudgetedTransport<T> {
4399    /// Creates a budgeted transport wrapper.
4400    pub const fn new(inner: T, max_packet_bytes: usize, max_batch_bytes: usize) -> Self {
4401        Self {
4402            inner,
4403            max_packet_bytes,
4404            max_batch_bytes,
4405        }
4406    }
4407
4408    /// Returns the inner transport.
4409    pub fn into_inner(self) -> T {
4410        self.inner
4411    }
4412
4413    /// Borrows the inner transport.
4414    pub const fn inner(&self) -> &T {
4415        &self.inner
4416    }
4417}
4418
4419impl<T: TransportSink> TransportSink for BudgetedTransport<T> {
4420    type Error = TransportError<T::Error>;
4421
4422    fn send(&mut self, packet: OutboundPacket) -> Result<(), Self::Error> {
4423        let bytes = packet.bytes.len();
4424        if bytes > self.max_packet_bytes {
4425            return Err(TransportError::ByteBudgetExceeded {
4426                budget: self.max_packet_bytes,
4427                actual: bytes,
4428            });
4429        }
4430        self.inner.send(packet).map_err(TransportError::Inner)
4431    }
4432
4433    fn send_batch(&mut self, batch: PacketBatch) -> Result<(), Self::Error> {
4434        let mut bytes = 0_usize;
4435        let mut first_oversized_packet = None;
4436        for packet in &batch.packets {
4437            let packet_bytes = packet.bytes.len();
4438            bytes = bytes.saturating_add(packet_bytes);
4439            if first_oversized_packet.is_none() && packet_bytes > self.max_packet_bytes {
4440                first_oversized_packet = Some(packet_bytes);
4441            }
4442        }
4443        if bytes > self.max_batch_bytes {
4444            return Err(TransportError::ByteBudgetExceeded {
4445                budget: self.max_batch_bytes,
4446                actual: bytes,
4447            });
4448        }
4449        if let Some(actual) = first_oversized_packet {
4450            return Err(TransportError::ByteBudgetExceeded {
4451                budget: self.max_packet_bytes,
4452                actual,
4453            });
4454        }
4455        self.inner.send_batch(batch).map_err(TransportError::Inner)
4456    }
4457}
4458
4459/// Fake transport for benchmarks and tests.
4460#[derive(Clone, Debug, Default, PartialEq, Eq)]
4461pub struct FakeTransport {
4462    packets: usize,
4463    bytes: usize,
4464    batches: usize,
4465}
4466
4467impl FakeTransport {
4468    /// Returns sent packet count.
4469    pub const fn packets_sent(&self) -> usize {
4470        self.packets
4471    }
4472
4473    /// Returns sent byte count.
4474    pub const fn bytes_sent(&self) -> usize {
4475        self.bytes
4476    }
4477
4478    /// Returns sent batch count.
4479    pub const fn batches_sent(&self) -> usize {
4480        self.batches
4481    }
4482}
4483
4484impl TransportSink for FakeTransport {
4485    type Error = core::convert::Infallible;
4486
4487    fn send(&mut self, packet: OutboundPacket) -> Result<(), Self::Error> {
4488        self.packets += 1;
4489        self.bytes += packet.bytes.len();
4490        Ok(())
4491    }
4492
4493    fn send_batch(&mut self, batch: PacketBatch) -> Result<(), Self::Error> {
4494        self.batches += 1;
4495        self.packets += batch.packets.len();
4496        self.bytes += batch.bytes_len();
4497        Ok(())
4498    }
4499}
4500
4501#[cfg(test)]
4502mod tests {
4503    use super::*;
4504    use std::thread;
4505    use std::time::Duration;
4506
4507    fn packet(bytes: usize) -> OutboundPacket {
4508        OutboundPacket {
4509            client_id: ClientId::new(1),
4510            bytes: vec![0; bytes],
4511        }
4512    }
4513
4514    fn station_packet(bytes: usize) -> StationOutboundPacket {
4515        StationOutboundPacket {
4516            source_station: StationId::new(1),
4517            target_station: StationId::new(2),
4518            bytes: vec![1; bytes],
4519        }
4520    }
4521
4522    fn memory_addr(port: u16) -> SocketAddr {
4523        SocketAddr::from(([127, 0, 0, 1], port))
4524    }
4525
4526    fn recv_with_retry(transport: &mut UdpTransport) -> InboundPacket {
4527        for _ in 0..50 {
4528            if let Some(packet) = transport.try_recv().expect("udp receive should work") {
4529                return packet;
4530            }
4531            thread::sleep(Duration::from_millis(2));
4532        }
4533        panic!("udp packet was not received");
4534    }
4535
4536    fn recv_ref_with_retry(
4537        transport: &mut UdpTransport,
4538    ) -> (Option<ClientId>, SocketAddr, usize, u8, u8, usize) {
4539        for _ in 0..50 {
4540            if let Some(packet) = transport
4541                .try_recv_ref()
4542                .expect("borrowed udp receive should work")
4543            {
4544                return (
4545                    packet.client_id,
4546                    packet.remote_addr,
4547                    packet.bytes.len(),
4548                    packet.bytes.first().copied().unwrap_or(0),
4549                    packet.bytes.last().copied().unwrap_or(0),
4550                    packet.bytes.as_ptr() as usize,
4551                );
4552            }
4553            thread::sleep(Duration::from_millis(2));
4554        }
4555        panic!("borrowed udp packet was not received");
4556    }
4557
4558    fn recv_station_with_retry(
4559        transport: &mut UdpStationTransport,
4560        station_id: StationId,
4561    ) -> StationInboundPacket {
4562        for _ in 0..50 {
4563            if let Some(packet) = transport
4564                .try_recv_station(station_id)
4565                .expect("udp station receive should work")
4566            {
4567                return packet;
4568            }
4569            thread::sleep(Duration::from_millis(2));
4570        }
4571        panic!("udp station packet was not received");
4572    }
4573
4574    fn recv_station_ref_with_retry(
4575        transport: &mut UdpStationTransport,
4576        station_id: StationId,
4577    ) -> (StationId, StationId, usize, u8, u8, usize) {
4578        for _ in 0..50 {
4579            if let Some(packet) = transport
4580                .try_recv_station_ref(station_id)
4581                .expect("borrowed udp station receive should work")
4582            {
4583                return (
4584                    packet.source_station,
4585                    packet.target_station,
4586                    packet.bytes.len(),
4587                    packet.bytes.first().copied().unwrap_or(0),
4588                    packet.bytes.last().copied().unwrap_or(0),
4589                    packet.bytes.as_ptr() as usize,
4590                );
4591            }
4592            thread::sleep(Duration::from_millis(2));
4593        }
4594        panic!("borrowed udp station packet was not received");
4595    }
4596
4597    #[derive(Clone, Debug, Default)]
4598    struct TestAuthenticator;
4599
4600    impl PacketAuthenticator for TestAuthenticator {
4601        type Error = core::convert::Infallible;
4602
4603        fn sign(
4604            &mut self,
4605            key_id: u32,
4606            nonce: u64,
4607            payload: &[u8],
4608            out: &mut Vec<u8>,
4609        ) -> Result<(), Self::Error> {
4610            out.extend_from_slice(&test_tag(key_id, nonce, payload));
4611            Ok(())
4612        }
4613
4614        fn verify(
4615            &mut self,
4616            key_id: u32,
4617            nonce: u64,
4618            payload: &[u8],
4619            tag: &[u8],
4620        ) -> Result<bool, Self::Error> {
4621            Ok(tag == test_tag(key_id, nonce, payload))
4622        }
4623    }
4624
4625    fn test_tag(key_id: u32, nonce: u64, payload: &[u8]) -> [u8; 8] {
4626        let mut acc = u64::from(key_id)
4627            .wrapping_mul(0x9E37_79B9_7F4A_7C15)
4628            .wrapping_add(nonce.rotate_left(17));
4629        for (index, byte) in payload.iter().copied().enumerate() {
4630            acc = acc.rotate_left(5) ^ (u64::from(byte) << ((index % 8) * 8));
4631            acc = acc.wrapping_mul(0x1000_0000_01B3);
4632        }
4633        acc.to_le_bytes()
4634    }
4635
4636    #[test]
4637    fn fake_transport_counts_batches_without_storing_packets() {
4638        let mut batch = PacketBatch::new();
4639        batch.push(packet(3));
4640        batch.push(packet(5));
4641
4642        let mut transport = FakeTransport::default();
4643        transport
4644            .send_batch(batch)
4645            .expect("fake transport is infallible");
4646
4647        assert_eq!(transport.batches_sent(), 1);
4648        assert_eq!(transport.packets_sent(), 2);
4649        assert_eq!(transport.bytes_sent(), 8);
4650    }
4651
4652    #[test]
4653    fn budgeted_transport_rejects_large_batch() {
4654        let mut batch = PacketBatch::new();
4655        batch.push(packet(8));
4656        batch.push(packet(8));
4657
4658        let mut transport = BudgetedTransport::new(FakeTransport::default(), 16, 12);
4659        let error = transport
4660            .send_batch(batch)
4661            .expect_err("batch should exceed budget");
4662        assert_eq!(
4663            error,
4664            TransportError::ByteBudgetExceeded {
4665                budget: 12,
4666                actual: 16
4667            }
4668        );
4669        assert_eq!(transport.inner().packets_sent(), 0);
4670
4671        let mut both_limits = PacketBatch::new();
4672        both_limits.push(packet(20));
4673        let error = transport
4674            .send_batch(both_limits)
4675            .expect_err("aggregate budget should retain priority");
4676        assert_eq!(
4677            error,
4678            TransportError::ByteBudgetExceeded {
4679                budget: 12,
4680                actual: 20,
4681            }
4682        );
4683
4684        let mut packet_limit = PacketBatch::new();
4685        packet_limit.push(packet(8));
4686        packet_limit.push(packet(20));
4687        let mut packet_budget = BudgetedTransport::new(FakeTransport::default(), 16, 64);
4688        let error = packet_budget
4689            .send_batch(packet_limit)
4690            .expect_err("single packet should exceed budget");
4691        assert_eq!(
4692            error,
4693            TransportError::ByteBudgetExceeded {
4694                budget: 16,
4695                actual: 20,
4696            }
4697        );
4698        assert_eq!(packet_budget.inner().packets_sent(), 0);
4699
4700        let mut valid = PacketBatch::new();
4701        valid.push(packet(8));
4702        valid.push(packet(12));
4703        packet_budget
4704            .send_batch(valid)
4705            .expect("valid batch should forward once");
4706        assert_eq!(packet_budget.inner().batches_sent(), 1);
4707        assert_eq!(packet_budget.inner().packets_sent(), 2);
4708        assert_eq!(packet_budget.inner().bytes_sent(), 20);
4709    }
4710
4711    #[test]
4712    fn packet_security_envelope_roundtrips_and_enforces_limits() {
4713        let config = PacketSecurityConfig {
4714            max_payload_bytes: 4,
4715            max_tag_bytes: 8,
4716            max_replay_history: 4,
4717        };
4718        let envelope = PacketSecurityEnvelope {
4719            key_id: 9,
4720            nonce: 42,
4721            payload: b"move".to_vec(),
4722            tag: vec![1, 2, 3, 4],
4723        };
4724        let mut bytes = Vec::new();
4725        envelope
4726            .encode(config, &mut bytes)
4727            .expect("envelope should encode");
4728        let mut borrowed_bytes = Vec::new();
4729        PacketSecurityEnvelope::encode_parts(
4730            config,
4731            envelope.key_id,
4732            envelope.nonce,
4733            &envelope.payload,
4734            &envelope.tag,
4735            &mut borrowed_bytes,
4736        )
4737        .expect("borrowed envelope should encode");
4738        assert_eq!(borrowed_bytes, bytes);
4739        let borrowed = PacketSecurityEnvelopeRef::decode(config, &bytes)
4740            .expect("borrowed envelope should decode");
4741        assert_eq!(borrowed.key_id, envelope.key_id);
4742        assert_eq!(borrowed.nonce, envelope.nonce);
4743        assert_eq!(borrowed.payload, envelope.payload);
4744        assert_eq!(borrowed.tag, envelope.tag);
4745        assert!(borrowed.payload.as_ptr() >= bytes.as_ptr());
4746        assert!(borrowed.tag.as_ptr() >= borrowed.payload.as_ptr());
4747        assert_eq!(
4748            PacketSecurityEnvelope::decode(config, &bytes).expect("envelope should decode"),
4749            envelope
4750        );
4751
4752        let too_large = PacketSecurityEnvelope {
4753            key_id: 9,
4754            nonce: 43,
4755            payload: b"large".to_vec(),
4756            tag: Vec::new(),
4757        }
4758        .encode(config, &mut Vec::new())
4759        .expect_err("payload should exceed configured budget");
4760        assert_eq!(
4761            too_large,
4762            PacketSecurityEncodeError::PayloadTooLarge {
4763                budget: 4,
4764                actual: 5
4765            }
4766        );
4767
4768        let mut bad = bytes;
4769        bad[16..20].copy_from_slice(&5_u32.to_le_bytes());
4770        assert_eq!(
4771            PacketSecurityEnvelope::decode(config, &bad)
4772                .expect_err("decoded payload length should exceed budget"),
4773            PacketSecurityDecodeError::PayloadTooLarge {
4774                budget: 4,
4775                actual: 5
4776            }
4777        );
4778    }
4779
4780    #[test]
4781    fn packet_key_ring_selects_active_key_and_accepts_retiring_key() {
4782        let now = Tick::new(10);
4783        let mut ring = PacketKeyRing::new(PacketKeyRingConfig { max_keys: 4 });
4784        ring.insert_active(1, now, 1)
4785            .expect("first key should insert");
4786        ring.insert_active(2, Tick::new(11), 10)
4787            .expect("second key should insert");
4788
4789        assert_eq!(
4790            ring.select_send_key(now).expect("key 1 should send").key_id,
4791            1
4792        );
4793        assert_eq!(
4794            ring.select_send_key(Tick::new(11))
4795                .expect("key 2 should send")
4796                .key_id,
4797            2
4798        );
4799
4800        ring.retire(1, Tick::new(12)).expect("key 1 should retire");
4801        assert_eq!(
4802            ring.accept_key(1, Tick::new(12))
4803                .expect("retiring key should still receive")
4804                .state,
4805            PacketKeyState::Retiring
4806        );
4807        assert_eq!(
4808            ring.select_send_key(Tick::new(12))
4809                .expect("active key should win")
4810                .key_id,
4811            2
4812        );
4813        assert_eq!(ring.stats().keys_inserted, 2);
4814        assert_eq!(ring.stats().keys_retired, 1);
4815    }
4816
4817    #[test]
4818    fn packet_key_ring_rejects_revoked_expired_and_over_capacity_keys() {
4819        let mut ring = PacketKeyRing::new(PacketKeyRingConfig { max_keys: 2 });
4820        ring.insert(PacketKeyDescriptor::active(1, Tick::new(1), 1).with_expiry(Tick::new(5)))
4821            .expect("expiring key should insert");
4822        ring.insert_active(2, Tick::new(1), 2)
4823            .expect("second key should insert");
4824        assert_eq!(
4825            ring.insert_active(3, Tick::new(1), 3)
4826                .expect_err("ring should be full"),
4827            PacketKeyRingError::CapacityFull { capacity: 2 }
4828        );
4829        assert_eq!(
4830            ring.insert_active(2, Tick::new(1), 2)
4831                .expect_err("duplicate should reject"),
4832            PacketKeyRingError::DuplicateKey(2)
4833        );
4834
4835        assert!(ring.accept_key(1, Tick::new(4)).is_ok());
4836        assert_eq!(
4837            ring.accept_key(1, Tick::new(5))
4838                .expect_err("expired key should reject"),
4839            PacketKeyRingError::KeyNotAccepted {
4840                key_id: 1,
4841                state: PacketKeyState::Active
4842            }
4843        );
4844        ring.revoke(2).expect("key should revoke");
4845        assert_eq!(
4846            ring.accept_key(2, Tick::new(4))
4847                .expect_err("revoked key should reject"),
4848            PacketKeyRingError::KeyNotAccepted {
4849                key_id: 2,
4850                state: PacketKeyState::Revoked
4851            }
4852        );
4853        assert_eq!(ring.remove_expired(Tick::new(5)), 1);
4854        assert_eq!(ring.len(), 1);
4855        assert_eq!(ring.stats().keys_revoked, 1);
4856        assert_eq!(ring.stats().keys_expired_removed, 1);
4857    }
4858
4859    #[test]
4860    fn packet_security_box_seals_opens_and_rejects_replay() {
4861        let mut sender = PacketSecurityBox::new(
4862            PacketSecurityConfig::default(),
4863            TestAuthenticator,
4864            PlaintextPacketCipher,
4865        );
4866        let mut receiver = PacketSecurityBox::new(
4867            PacketSecurityConfig::default(),
4868            TestAuthenticator,
4869            PlaintextPacketCipher,
4870        );
4871        let sealed = sender.seal(7, b"command").expect("packet should seal");
4872        assert_eq!(sender.stats().sealed, 1);
4873        let opened = receiver.open(&sealed).expect("packet should open");
4874        assert_eq!(opened, b"command");
4875        assert_eq!(receiver.stats().opened, 1);
4876
4877        let replay = receiver.open(&sealed).expect_err("replay should reject");
4878        match replay {
4879            PacketSecurityError::Replay { key_id, nonce } => {
4880                assert_eq!(key_id, 7);
4881                assert_eq!(nonce, 1);
4882            }
4883            other => panic!("unexpected error: {other}"),
4884        }
4885        assert_eq!(receiver.stats().replay_rejected, 1);
4886    }
4887
4888    #[test]
4889    fn packet_security_seal_into_matches_owned_and_reuses_scratch_atomically() {
4890        let config = PacketSecurityConfig::default();
4891        let mut owned = PacketSecurityBox::new(config, TestAuthenticator, PlaintextPacketCipher);
4892        let mut reused = PacketSecurityBox::new(config, TestAuthenticator, PlaintextPacketCipher);
4893        let mut scratch = PacketSecurityScratch::with_capacity(32, 8);
4894        let mut out = Vec::with_capacity(64);
4895
4896        let expected = owned
4897            .seal_with_nonce(7, 10, b"command")
4898            .expect("owned packet should seal");
4899        reused
4900            .seal_with_nonce_into(7, 10, b"command", &mut out, &mut scratch)
4901            .expect("reused packet should seal");
4902        assert_eq!(out, expected);
4903        let payload_ptr = scratch.sealed_payload.as_ptr();
4904        let tag_ptr = scratch.tag.as_ptr();
4905        let payload_capacity = scratch.retained_payload_capacity();
4906        let tag_capacity = scratch.retained_tag_capacity();
4907
4908        out.clear();
4909        let nonce = reused
4910            .seal_into(7, b"ack", &mut out, &mut scratch)
4911            .expect("allocated nonce packet should seal");
4912        assert_eq!(nonce, 1);
4913        let envelope =
4914            PacketSecurityEnvelope::decode(config, &out).expect("reused packet should decode");
4915        assert_eq!(envelope.nonce, nonce);
4916        assert_eq!(scratch.sealed_payload.as_ptr(), payload_ptr);
4917        assert_eq!(scratch.tag.as_ptr(), tag_ptr);
4918        assert_eq!(scratch.retained_payload_capacity(), payload_capacity);
4919        assert_eq!(scratch.retained_tag_capacity(), tag_capacity);
4920        assert_eq!(reused.stats().sealed, 2);
4921
4922        let before_error = out.clone();
4923        let too_large = vec![0_u8; config.max_payload_bytes + 1];
4924        assert!(matches!(
4925            reused.seal_with_nonce_into(7, 11, &too_large, &mut out, &mut scratch),
4926            Err(PacketSecurityError::Encode(
4927                PacketSecurityEncodeError::PayloadTooLarge { .. }
4928            ))
4929        ));
4930        assert_eq!(out, before_error);
4931
4932        let small_tag_config = PacketSecurityConfig {
4933            max_tag_bytes: 4,
4934            ..config
4935        };
4936        let mut small_tag =
4937            PacketSecurityBox::new(small_tag_config, TestAuthenticator, PlaintextPacketCipher);
4938        assert!(matches!(
4939            small_tag.seal_with_nonce_into(7, 12, b"tag", &mut out, &mut scratch),
4940            Err(PacketSecurityError::Encode(
4941                PacketSecurityEncodeError::TagTooLarge { .. }
4942            ))
4943        ));
4944        assert_eq!(out, before_error);
4945    }
4946
4947    #[test]
4948    fn packet_security_open_scratch_matches_owned_reuses_and_preserves_failed_input() {
4949        let config = PacketSecurityConfig::default();
4950        let mut sender = PacketSecurityBox::new(config, TestAuthenticator, PlaintextPacketCipher);
4951        let first = sender
4952            .seal_with_nonce(7, 10, b"first-command")
4953            .expect("first packet should seal");
4954        let second = sender
4955            .seal_with_nonce(7, 11, b"second-command")
4956            .expect("second packet should seal");
4957        let mut owned_receiver =
4958            PacketSecurityBox::new(config, TestAuthenticator, PlaintextPacketCipher);
4959        let expected = owned_receiver
4960            .open(&first)
4961            .expect("owned packet should open");
4962
4963        let mut receiver = PacketSecurityBox::new(config, TestAuthenticator, PlaintextPacketCipher);
4964        let mut scratch = PacketSecurityOpenScratch::with_capacity(32);
4965        let first_ptr = {
4966            let opened = receiver
4967                .open_with_scratch(&first, &mut scratch)
4968                .expect("scratch packet should open");
4969            assert_eq!(opened.key_id, 7);
4970            assert_eq!(opened.nonce, 10);
4971            assert_eq!(opened.payload, expected);
4972            opened.payload.as_ptr()
4973        };
4974        let retained = scratch.retained_payload_capacity();
4975        let second_payload = {
4976            let opened = receiver
4977                .open_with_scratch(&second, &mut scratch)
4978                .expect("second scratch packet should open");
4979            assert_eq!(opened.payload.as_ptr(), first_ptr);
4980            opened.payload.to_vec()
4981        };
4982        assert_eq!(second_payload, b"second-command");
4983        assert_eq!(scratch.retained_payload_capacity(), retained);
4984        assert_eq!(receiver.stats().opened, 2);
4985
4986        let before_failure = scratch.payload.clone();
4987        let mut tampered = sender
4988            .seal_with_nonce(7, 12, b"tampered")
4989            .expect("tampered source should seal");
4990        tampered[PACKET_SECURITY_HEADER_BYTES] ^= 0x55;
4991        assert!(matches!(
4992            receiver.open_with_scratch(&tampered, &mut scratch),
4993            Err(PacketSecurityError::AuthenticationFailed { .. })
4994        ));
4995        assert_eq!(scratch.payload, before_failure);
4996    }
4997
4998    #[test]
4999    fn packet_security_box_uses_key_ring_for_rotation_policy() {
5000        let mut sender_ring = PacketKeyRing::default();
5001        let mut receiver_ring = PacketKeyRing::default();
5002        sender_ring
5003            .insert_active(7, Tick::new(10), 1)
5004            .expect("sender key should insert");
5005        receiver_ring
5006            .insert_active(7, Tick::new(10), 1)
5007            .expect("receiver key should insert");
5008
5009        let mut sender = PacketSecurityBox::new(
5010            PacketSecurityConfig::default(),
5011            TestAuthenticator,
5012            PlaintextPacketCipher,
5013        );
5014        let mut receiver = PacketSecurityBox::new(
5015            PacketSecurityConfig::default(),
5016            TestAuthenticator,
5017            PlaintextPacketCipher,
5018        );
5019        let mut open_scratch = PacketSecurityOpenScratch::with_capacity(16);
5020
5021        let sealed = sender
5022            .seal_with_key_ring(&sender_ring, b"command", Tick::new(10))
5023            .expect("packet should seal through selected key");
5024        let envelope = PacketSecurityEnvelope::decode(PacketSecurityConfig::default(), &sealed)
5025            .expect("packet should decode");
5026        assert_eq!(envelope.key_id, 7);
5027        assert_eq!(
5028            receiver
5029                .open_with_key_ring_and_scratch(
5030                    &receiver_ring,
5031                    &sealed,
5032                    Tick::new(10),
5033                    &mut open_scratch,
5034                )
5035                .expect("packet should open through accepted key")
5036                .payload,
5037            b"command"
5038        );
5039
5040        sender_ring
5041            .insert_active(8, Tick::new(11), 10)
5042            .expect("rotated sender key should insert");
5043        receiver_ring
5044            .insert_active(8, Tick::new(11), 10)
5045            .expect("rotated receiver key should insert");
5046        receiver_ring
5047            .retire(7, Tick::new(11))
5048            .expect("old key should retire");
5049        let rotated = sender
5050            .seal_with_key_ring(&sender_ring, b"ack", Tick::new(11))
5051            .expect("rotated packet should seal");
5052        let rotated_envelope =
5053            PacketSecurityEnvelope::decode(PacketSecurityConfig::default(), &rotated)
5054                .expect("rotated packet should decode");
5055        assert_eq!(rotated_envelope.key_id, 8);
5056        assert_eq!(
5057            receiver
5058                .open_with_key_ring_and_scratch(
5059                    &receiver_ring,
5060                    &rotated,
5061                    Tick::new(11),
5062                    &mut open_scratch,
5063                )
5064                .expect("rotated packet should open")
5065                .payload,
5066            b"ack"
5067        );
5068
5069        receiver_ring.revoke(7).expect("old key should revoke");
5070        let stale = sender
5071            .seal_with_nonce(7, 99, b"stale")
5072            .expect("explicit stale-key packet should seal");
5073        let error = receiver
5074            .open_with_key_ring(&receiver_ring, &stale, Tick::new(12))
5075            .expect_err("revoked key should reject before auth");
5076        match error {
5077            PacketSecurityError::Key(PacketKeyRingError::KeyNotAccepted { key_id, state }) => {
5078                assert_eq!(key_id, 7);
5079                assert_eq!(state, PacketKeyState::Revoked);
5080            }
5081            other => panic!("unexpected error: {other}"),
5082        }
5083        assert_eq!(receiver.stats().key_rejected, 1);
5084    }
5085
5086    #[test]
5087    fn packet_security_box_rejects_tampered_payload() {
5088        let mut sender = PacketSecurityBox::new(
5089            PacketSecurityConfig::default(),
5090            TestAuthenticator,
5091            PlaintextPacketCipher,
5092        );
5093        let mut receiver = PacketSecurityBox::new(
5094            PacketSecurityConfig::default(),
5095            TestAuthenticator,
5096            PlaintextPacketCipher,
5097        );
5098
5099        let mut sealed = sender
5100            .seal_with_nonce(7, 10, b"command")
5101            .expect("packet should seal");
5102        let payload_offset = PACKET_SECURITY_HEADER_BYTES;
5103        sealed[payload_offset] ^= 0x55;
5104        let error = receiver
5105            .open(&sealed)
5106            .expect_err("tampered payload should reject");
5107        match error {
5108            PacketSecurityError::AuthenticationFailed { key_id, nonce } => {
5109                assert_eq!(key_id, 7);
5110                assert_eq!(nonce, 10);
5111            }
5112            other => panic!("unexpected error: {other}"),
5113        }
5114        assert_eq!(receiver.stats().auth_failed, 1);
5115        assert_eq!(receiver.replay().len(), 0);
5116    }
5117
5118    #[test]
5119    fn packet_replay_window_bounds_history() {
5120        let mut replay = PacketReplayWindow::new(2);
5121        assert!(replay.accept(1, 1));
5122        assert!(replay.accept(1, 2));
5123        assert!(!replay.accept(1, 2));
5124        assert!(replay.accept(1, 3));
5125        assert_eq!(replay.len(), 2);
5126        assert!(!replay.contains(1, 1));
5127        assert!(replay.accept(1, 1));
5128    }
5129
5130    #[test]
5131    fn bounded_duplicate_indexes_adapt_to_configured_capacity() {
5132        let ordered = PacketReplayWindow::new(HASHED_BOUNDED_SET_MIN_CAPACITY - 1);
5133        let hashed = PacketReplayWindow::new(HASHED_BOUNDED_SET_MIN_CAPACITY);
5134        assert!(!ordered.seen.is_hashed());
5135        assert!(hashed.seen.is_hashed());
5136
5137        let client = ReliableClientReceiver::new(ReliableClientConfig {
5138            max_delivered_history: HASHED_BOUNDED_SET_MIN_CAPACITY,
5139            ..ReliableClientConfig::default()
5140        });
5141        let station = ReliableStationReceiver::new(ReliableStationConfig {
5142            max_delivered_history: HASHED_BOUNDED_SET_MIN_CAPACITY,
5143            ..ReliableStationConfig::default()
5144        });
5145        assert!(client.delivered.is_hashed());
5146        assert!(station.delivered.is_hashed());
5147    }
5148
5149    #[test]
5150    fn endpoint_map_promotes_once_without_changing_entries() {
5151        let mut endpoints = AdaptiveEndpointMap::new();
5152        for key in 0..HASHED_ENDPOINT_MAP_MIN_ENTRIES - 1 {
5153            assert_eq!(endpoints.insert(key, key), None);
5154        }
5155        assert!(!endpoints.is_hashed());
5156        assert_eq!(endpoints.insert(0, usize::MAX), Some(0));
5157        assert!(!endpoints.is_hashed());
5158
5159        let final_key = HASHED_ENDPOINT_MAP_MIN_ENTRIES - 1;
5160        assert_eq!(endpoints.insert(final_key, final_key), None);
5161        assert!(endpoints.is_hashed());
5162        assert_eq!(endpoints.get(&0), Some(&usize::MAX));
5163        *endpoints.get_mut(&1).expect("migrated entry should exist") = 17;
5164        assert_eq!(endpoints.remove(&2), Some(2));
5165        assert_eq!(
5166            endpoints.values().len(),
5167            HASHED_ENDPOINT_MAP_MIN_ENTRIES - 1
5168        );
5169        assert_eq!(endpoints.get(&1), Some(&17));
5170        assert_eq!(endpoints.get(&2), None);
5171    }
5172
5173    #[test]
5174    fn in_memory_transports_preserve_queued_packets_during_map_promotion() {
5175        let hub = InMemoryTransportHub::default();
5176        for key in 0..HASHED_ENDPOINT_MAP_MIN_ENTRIES - 1 {
5177            let client_id = ClientId::new(u64::try_from(key).expect("test id fits u64"));
5178            hub.register_client(
5179                client_id,
5180                memory_addr(10_000 + u16::try_from(key).expect("test port fits u16")),
5181            )
5182            .expect("client should register");
5183        }
5184        let mut source = hub.endpoint_for_registered(ClientId::new(0));
5185        source
5186            .send(OutboundPacket {
5187                client_id: ClientId::new(1),
5188                bytes: vec![7],
5189            })
5190            .expect("packet should queue before promotion");
5191        let final_client = HASHED_ENDPOINT_MAP_MIN_ENTRIES - 1;
5192        hub.register_client(
5193            ClientId::new(u64::try_from(final_client).expect("test id fits u64")),
5194            memory_addr(10_000 + u16::try_from(final_client).expect("test port fits u16")),
5195        )
5196        .expect("threshold client should register");
5197        assert!(
5198            hub.lock_inner()
5199                .expect("hub should lock")
5200                .clients
5201                .is_hashed()
5202        );
5203        let mut target = hub.endpoint_for_registered(ClientId::new(1));
5204        assert_eq!(
5205            target
5206                .try_recv()
5207                .expect("receive should work")
5208                .expect("queued packet should survive")
5209                .bytes,
5210            vec![7]
5211        );
5212
5213        let mut stations = InMemoryStationTransport::default();
5214        for key in 0..HASHED_ENDPOINT_MAP_MIN_ENTRIES - 1 {
5215            stations.register_station(StationId::new(
5216                u32::try_from(key).expect("test station id fits u32"),
5217            ));
5218        }
5219        stations
5220            .send_station(StationOutboundPacket {
5221                source_station: StationId::new(0),
5222                target_station: StationId::new(1),
5223                bytes: vec![9],
5224            })
5225            .expect("station packet should queue before promotion");
5226        stations.register_station(StationId::new(
5227            u32::try_from(HASHED_ENDPOINT_MAP_MIN_ENTRIES - 1).expect("test station id fits u32"),
5228        ));
5229        assert!(stations.queues.is_hashed());
5230        assert_eq!(
5231            stations
5232                .try_recv_station(StationId::new(1))
5233                .expect("receive should work")
5234                .expect("queued station packet should survive")
5235                .bytes,
5236            vec![9]
5237        );
5238    }
5239
5240    #[test]
5241    fn in_memory_transport_delivers_bounded_packets() {
5242        let client_id = ClientId::new(7);
5243        let server_id = ClientId::new(0);
5244        let hub = InMemoryTransportHub::new(ClientTransportLimits {
5245            max_queued_packets_per_client: 2,
5246            max_packet_bytes: 8,
5247        });
5248        let mut client = hub
5249            .endpoint(client_id, memory_addr(20007))
5250            .expect("client should register");
5251        let mut server = hub
5252            .endpoint(server_id, memory_addr(20000))
5253            .expect("server should register");
5254
5255        client
5256            .send(OutboundPacket {
5257                client_id: server_id,
5258                bytes: b"command".to_vec(),
5259            })
5260            .expect("client packet should send");
5261        assert_eq!(
5262            hub.queued_len(server_id).expect("queue should exist"),
5263            Some(1)
5264        );
5265
5266        let inbound = server
5267            .try_recv()
5268            .expect("server receive should work")
5269            .expect("packet should exist");
5270        assert_eq!(inbound.client_id, Some(client_id));
5271        assert_eq!(inbound.remote_addr, memory_addr(20007));
5272        assert_eq!(inbound.bytes, b"command");
5273
5274        let stats = hub.stats().expect("stats should read");
5275        assert_eq!(stats.packets_sent, 1);
5276        assert_eq!(stats.packets_received, 1);
5277        assert_eq!(stats.bytes_sent, 7);
5278        assert_eq!(stats.bytes_received, 7);
5279    }
5280
5281    #[test]
5282    fn in_memory_transport_queues_allocate_lazily_and_retain_peak_capacity() {
5283        let client_id = ClientId::new(7);
5284        let server_id = ClientId::new(0);
5285        let hub = InMemoryTransportHub::default();
5286        let mut client = hub
5287            .endpoint(client_id, memory_addr(20007))
5288            .expect("client should register");
5289        let mut server = hub
5290            .endpoint(server_id, memory_addr(20000))
5291            .expect("server should register");
5292        assert_eq!(
5293            hub.retained_queue_capacity().expect("capacity should read"),
5294            0
5295        );
5296
5297        for _ in 0..8 {
5298            client
5299                .send(OutboundPacket {
5300                    client_id: server_id,
5301                    bytes: vec![1],
5302                })
5303                .expect("burst packet should send");
5304        }
5305        let peak = hub
5306            .queued_capacity(server_id)
5307            .expect("capacity should read")
5308            .expect("server should remain registered");
5309        assert!(peak >= 8);
5310        for _ in 0..8 {
5311            assert!(server.try_recv().expect("receive should work").is_some());
5312        }
5313        assert_eq!(
5314            hub.queued_capacity(server_id)
5315                .expect("capacity should read"),
5316            Some(peak)
5317        );
5318
5319        client
5320            .send(OutboundPacket {
5321                client_id: server_id,
5322                bytes: vec![2],
5323            })
5324            .expect("packet should queue before teardown");
5325        let released = hub
5326            .unregister_client(server_id)
5327            .expect("unregister should lock")
5328            .expect("server should unregister");
5329        assert_eq!(released.remote_addr, memory_addr(20000));
5330        assert_eq!(released.queued_packets, 1);
5331        assert_eq!(released.retained_queue_capacity, peak);
5332        assert_eq!(hub.queued_len(server_id).expect("lookup should work"), None);
5333        assert_eq!(
5334            hub.retained_queue_capacity().expect("capacity should read"),
5335            0
5336        );
5337        assert!(
5338            hub.unregister_client(server_id)
5339                .expect("second unregister should lock")
5340                .is_none()
5341        );
5342    }
5343
5344    #[test]
5345    fn in_memory_transport_batch_preserves_ordered_partial_commit() {
5346        let source_id = ClientId::new(7);
5347        let target_id = ClientId::new(0);
5348        let hub = InMemoryTransportHub::new(ClientTransportLimits {
5349            max_queued_packets_per_client: 65,
5350            max_packet_bytes: 4,
5351        });
5352        let mut source = hub
5353            .endpoint(source_id, memory_addr(20007))
5354            .expect("source should register");
5355        hub.endpoint(target_id, memory_addr(20000))
5356            .expect("target should register");
5357        let mut batch = PacketBatch::new();
5358        for byte in 0..66_u8 {
5359            batch.push(OutboundPacket {
5360                client_id: target_id,
5361                bytes: vec![byte],
5362            });
5363        }
5364
5365        assert_eq!(
5366            source.send_batch(batch),
5367            Err(InMemoryTransportError::QueueFull {
5368                client_id: target_id,
5369                capacity: 65,
5370            })
5371        );
5372        let mut target = hub.endpoint_for_registered(target_id);
5373        for byte in 0..65_u8 {
5374            assert_eq!(
5375                target
5376                    .try_recv()
5377                    .expect("receive should work")
5378                    .expect("successful prefix should commit")
5379                    .bytes,
5380                vec![byte]
5381            );
5382        }
5383        assert!(target.try_recv().expect("receive should work").is_none());
5384        let stats = hub.stats().expect("stats should read");
5385        assert_eq!(stats.packets_sent, 65);
5386        assert_eq!(stats.bytes_sent, 65);
5387        assert_eq!(stats.packets_rejected_full, 1);
5388    }
5389
5390    #[test]
5391    fn in_memory_transport_rejects_full_queue_and_large_packet() {
5392        let client_id = ClientId::new(7);
5393        let server_id = ClientId::new(0);
5394        let hub = InMemoryTransportHub::new(ClientTransportLimits {
5395            max_queued_packets_per_client: 1,
5396            max_packet_bytes: 4,
5397        });
5398        let mut client = hub
5399            .endpoint(client_id, memory_addr(20007))
5400            .expect("client should register");
5401        hub.endpoint(server_id, memory_addr(20000))
5402            .expect("server should register");
5403
5404        client
5405            .send(OutboundPacket {
5406                client_id: server_id,
5407                bytes: vec![0; 4],
5408            })
5409            .expect("first packet should send");
5410
5411        let full = client
5412            .send(OutboundPacket {
5413                client_id: server_id,
5414                bytes: vec![0; 4],
5415            })
5416            .expect_err("queue should be full");
5417        assert_eq!(
5418            full,
5419            InMemoryTransportError::QueueFull {
5420                client_id: server_id,
5421                capacity: 1
5422            }
5423        );
5424
5425        let large = client
5426            .send(OutboundPacket {
5427                client_id: server_id,
5428                bytes: vec![0; 5],
5429            })
5430            .expect_err("packet should exceed budget");
5431        assert_eq!(
5432            large,
5433            InMemoryTransportError::PacketTooLarge {
5434                budget: 4,
5435                actual: 5
5436            }
5437        );
5438
5439        let missing_id = ClientId::new(99);
5440        let missing = client
5441            .send(OutboundPacket {
5442                client_id: missing_id,
5443                bytes: vec![0; 4],
5444            })
5445            .expect_err("missing target should reject");
5446        assert_eq!(missing, InMemoryTransportError::MissingTarget(missing_id));
5447
5448        let stats = hub.stats().expect("stats should read");
5449        assert_eq!(stats.packets_sent, 1);
5450        assert_eq!(stats.bytes_sent, 4);
5451        assert_eq!(stats.packets_rejected_full, 1);
5452        assert_eq!(stats.packets_rejected_bytes, 1);
5453    }
5454
5455    #[test]
5456    fn reliable_client_frame_roundtrips_data_and_ack() {
5457        let data = ReliableClientFrame::Data {
5458            sequence: 42,
5459            payload: b"command".to_vec(),
5460        };
5461        let mut bytes = Vec::new();
5462        data.encode(&mut bytes).expect("data frame should encode");
5463        let mut direct = Vec::new();
5464        ReliableClientFrame::encode_data(42, b"command", &mut direct)
5465            .expect("borrowed data frame should encode");
5466        assert_eq!(direct, bytes);
5467        assert_eq!(
5468            ReliableClientFrame::decode_ref(&bytes).expect("data frame view should decode"),
5469            ReliableClientFrameRef::Data {
5470                sequence: 42,
5471                payload: b"command"
5472            }
5473        );
5474        assert_eq!(
5475            ReliableClientFrame::decode(&bytes).expect("data frame should decode"),
5476            data
5477        );
5478        let truncated = &bytes[..bytes.len() - 1];
5479        assert_eq!(
5480            ReliableClientFrame::decode_ref(truncated).expect_err("view should reject truncation"),
5481            ReliableClientFrame::decode(truncated).expect_err("owned should reject truncation")
5482        );
5483        let mut trailing = bytes.clone();
5484        trailing.push(0);
5485        assert_eq!(
5486            ReliableClientFrame::decode_ref(&trailing).expect_err("view should reject trailing"),
5487            ReliableClientFrame::decode(&trailing).expect_err("owned should reject trailing")
5488        );
5489
5490        let ack = ReliableClientFrame::Ack { sequence: 42 };
5491        bytes.clear();
5492        ack.encode(&mut bytes).expect("ack frame should encode");
5493        assert_eq!(
5494            ReliableClientFrame::decode(&bytes).expect("ack frame should decode"),
5495            ack
5496        );
5497    }
5498
5499    #[test]
5500    fn reliable_client_endpoint_delivers_payload_and_acknowledges() {
5501        let client_id = ClientId::new(7);
5502        let server_id = ClientId::new(0);
5503        let hub = InMemoryTransportHub::default();
5504        let mut client_transport = hub
5505            .endpoint(client_id, memory_addr(20007))
5506            .expect("client should register");
5507        let mut server_transport = hub
5508            .endpoint(server_id, memory_addr(20000))
5509            .expect("server should register");
5510        let mut client = ReliableClientEndpoint::default();
5511        let mut server = ReliableClientEndpoint::default();
5512
5513        let sequence = client
5514            .send(
5515                &mut client_transport,
5516                OutboundPacket {
5517                    client_id: server_id,
5518                    bytes: b"command".to_vec(),
5519                },
5520                0,
5521            )
5522            .expect("reliable command should send");
5523        assert_eq!(sequence, 1);
5524        assert_eq!(client.sender.in_flight_len(), 1);
5525
5526        let raw = server_transport
5527            .try_recv()
5528            .expect("server receive should work")
5529            .expect("data packet should exist");
5530        let wire_pointer = raw.bytes.as_ptr();
5531        let delivered = server
5532            .handle_inbound(&mut server_transport, raw)
5533            .expect("data packet should handle")
5534            .expect("first data packet should deliver");
5535        assert_eq!(delivered.client_id, Some(client_id));
5536        assert_eq!(delivered.remote_addr, memory_addr(20007));
5537        assert_eq!(delivered.bytes, b"command");
5538        assert_eq!(delivered.bytes.as_ptr(), wire_pointer);
5539        assert_eq!(server.receiver.stats().data_delivered, 1);
5540        assert_eq!(server.receiver.stats().acks_sent, 1);
5541
5542        let ack = client_transport
5543            .try_recv()
5544            .expect("client ACK receive should work")
5545            .expect("ACK packet should exist");
5546        assert_eq!(
5547            client
5548                .handle_inbound(&mut client_transport, ack)
5549                .expect("ACK should handle"),
5550            None
5551        );
5552        assert_eq!(client.sender.in_flight_len(), 0);
5553        assert_eq!(client.sender.stats().acks_received, 1);
5554    }
5555
5556    #[test]
5557    fn reliable_client_endpoint_retries_and_suppresses_duplicate_delivery() {
5558        let client_id = ClientId::new(7);
5559        let server_id = ClientId::new(0);
5560        let hub = InMemoryTransportHub::default();
5561        let mut client_transport = hub
5562            .endpoint(client_id, memory_addr(20007))
5563            .expect("client should register");
5564        let mut server_transport = hub
5565            .endpoint(server_id, memory_addr(20000))
5566            .expect("server should register");
5567        let mut client = ReliableClientEndpoint::default();
5568        let mut server = ReliableClientEndpoint::default();
5569
5570        client
5571            .send(
5572                &mut client_transport,
5573                OutboundPacket {
5574                    client_id: server_id,
5575                    bytes: b"idempotent-command".to_vec(),
5576                },
5577                0,
5578            )
5579            .expect("reliable command should send");
5580        let retry = client
5581            .retry_due(&mut client_transport, 2)
5582            .expect("retry should send");
5583        assert_eq!(retry.examined, 1);
5584        assert_eq!(retry.retried, 1);
5585        assert_eq!(retry.timed_out, 0);
5586        assert_eq!(client.sender.stats().retries_sent, 1);
5587        let retained_keys = client.retry_scratch.retained_key_capacity();
5588        assert!(retained_keys >= 1);
5589        assert_eq!(
5590            client
5591                .retry_due(&mut client_transport, 2)
5592                .expect("non-due scan should succeed"),
5593            ReliableRetryReport::default()
5594        );
5595        assert_eq!(client.retry_scratch.retained_key_capacity(), retained_keys);
5596        assert_eq!(
5597            hub.queued_len(server_id).expect("queue should exist"),
5598            Some(2)
5599        );
5600
5601        let first_raw = server_transport
5602            .try_recv()
5603            .expect("server receive should work")
5604            .expect("first data packet should exist");
5605        let delivered = server
5606            .handle_inbound(&mut server_transport, first_raw)
5607            .expect("first data packet should handle")
5608            .expect("first data packet should deliver");
5609        assert_eq!(delivered.bytes, b"idempotent-command");
5610
5611        let duplicate_raw = server_transport
5612            .try_recv()
5613            .expect("server receive should work")
5614            .expect("duplicate data packet should exist");
5615        assert_eq!(
5616            server
5617                .handle_inbound(&mut server_transport, duplicate_raw)
5618                .expect("duplicate data packet should handle"),
5619            None
5620        );
5621        assert_eq!(server.receiver.stats().data_delivered, 1);
5622        assert_eq!(server.receiver.stats().duplicates_suppressed, 1);
5623        assert_eq!(server.receiver.stats().acks_sent, 2);
5624    }
5625
5626    #[test]
5627    fn reliable_client_failed_retry_preserves_attempt_before_timeout() {
5628        let client_id = ClientId::new(7);
5629        let server_id = ClientId::new(0);
5630        let hub = InMemoryTransportHub::new(ClientTransportLimits {
5631            max_queued_packets_per_client: 1,
5632            max_packet_bytes: 128,
5633        });
5634        let mut client_transport = hub
5635            .endpoint(client_id, memory_addr(20007))
5636            .expect("client should register");
5637        let mut server_transport = hub
5638            .endpoint(server_id, memory_addr(20000))
5639            .expect("server should register");
5640        let mut sender = ReliableClientSender::new(ReliableClientConfig {
5641            max_in_flight_per_peer: 1,
5642            retry_after_ticks: 1,
5643            max_attempts: 2,
5644            max_payload_bytes: 64,
5645            max_delivered_history: 0,
5646        });
5647        let mut scratch = ReliableClientRetryScratch::new();
5648        sender
5649            .send(
5650                &mut client_transport,
5651                OutboundPacket {
5652                    client_id: server_id,
5653                    bytes: b"retry".to_vec(),
5654                },
5655                0,
5656            )
5657            .expect("initial packet should fill queue");
5658
5659        assert!(matches!(
5660            sender.retry_due_into(&mut client_transport, 1, &mut scratch),
5661            Err(ReliableClientError::Transport(
5662                InMemoryTransportError::QueueFull { .. }
5663            ))
5664        ));
5665        server_transport
5666            .try_recv()
5667            .expect("queue should read")
5668            .expect("initial packet should remain");
5669        assert_eq!(
5670            sender
5671                .retry_due_into(&mut client_transport, 1, &mut scratch)
5672                .expect("failed attempt must remain retryable")
5673                .retried,
5674            1
5675        );
5676        let timeout = sender
5677            .retry_due_into(&mut client_transport, 2, &mut scratch)
5678            .expect("exhausted packet should time out");
5679        assert_eq!(timeout.retried, 0);
5680        assert_eq!(timeout.timed_out, 1);
5681        assert_eq!(sender.in_flight_len(), 0);
5682        assert_eq!(sender.in_flight_for(server_id), 0);
5683        assert!(!sender.in_flight_by_peer.contains_key(&server_id));
5684    }
5685
5686    #[test]
5687    fn reliable_client_receiver_bounds_duplicate_history() {
5688        let client_id = ClientId::new(7);
5689        let server_id = ClientId::new(0);
5690        let hub = InMemoryTransportHub::default();
5691        hub.endpoint(client_id, memory_addr(20007))
5692            .expect("client should register");
5693        let mut server_transport = hub
5694            .endpoint(server_id, memory_addr(20000))
5695            .expect("server should register");
5696        let config = ReliableClientConfig {
5697            max_in_flight_per_peer: 8,
5698            retry_after_ticks: 2,
5699            max_attempts: 4,
5700            max_payload_bytes: DEFAULT_RELIABLE_CLIENT_MAX_PAYLOAD_BYTES,
5701            max_delivered_history: 1,
5702        };
5703        let mut server = ReliableClientEndpoint::new(config);
5704
5705        let packet = |sequence: u64, payload: &[u8]| {
5706            let mut bytes = Vec::new();
5707            ReliableClientFrame::Data {
5708                sequence,
5709                payload: payload.to_vec(),
5710            }
5711            .encode(&mut bytes)
5712            .expect("data frame should encode");
5713            InboundPacket {
5714                client_id: Some(client_id),
5715                remote_addr: memory_addr(20007),
5716                bytes,
5717            }
5718        };
5719
5720        assert!(
5721            server
5722                .handle_inbound(&mut server_transport, packet(1, b"first"))
5723                .expect("first data packet should handle")
5724                .is_some()
5725        );
5726        assert_eq!(
5727            server
5728                .handle_inbound(&mut server_transport, packet(1, b"first-duplicate"))
5729                .expect("duplicate data packet should handle"),
5730            None
5731        );
5732        assert!(
5733            server
5734                .handle_inbound(&mut server_transport, packet(2, b"second"))
5735                .expect("second data packet should handle")
5736                .is_some()
5737        );
5738        assert!(
5739            server
5740                .handle_inbound(&mut server_transport, packet(1, b"first-after-eviction"))
5741                .expect("evicted data packet should handle")
5742                .is_some()
5743        );
5744        assert_eq!(server.receiver.stats().data_delivered, 3);
5745        assert_eq!(server.receiver.stats().duplicates_suppressed, 1);
5746    }
5747
5748    #[test]
5749    fn reliable_client_sender_enforces_payload_and_window_limits() {
5750        let client_id = ClientId::new(7);
5751        let server_id = ClientId::new(0);
5752        let hub = InMemoryTransportHub::default();
5753        let mut client_transport = hub
5754            .endpoint(client_id, memory_addr(20007))
5755            .expect("client should register");
5756        hub.endpoint(server_id, memory_addr(20000))
5757            .expect("server should register");
5758        let config = ReliableClientConfig {
5759            max_in_flight_per_peer: 1,
5760            retry_after_ticks: 2,
5761            max_attempts: 4,
5762            max_payload_bytes: 4,
5763            max_delivered_history: DEFAULT_RELIABLE_CLIENT_DELIVERED_HISTORY,
5764        };
5765        let mut client = ReliableClientEndpoint::new(config);
5766
5767        let too_large = client
5768            .send(
5769                &mut client_transport,
5770                OutboundPacket {
5771                    client_id: server_id,
5772                    bytes: vec![0; 5],
5773                },
5774                0,
5775            )
5776            .expect_err("payload should exceed configured budget");
5777        match too_large {
5778            ReliableClientError::PayloadTooLarge { budget, actual } => {
5779                assert_eq!(budget, 4);
5780                assert_eq!(actual, 5);
5781            }
5782            other => panic!("unexpected error: {other}"),
5783        }
5784
5785        client
5786            .send(
5787                &mut client_transport,
5788                OutboundPacket {
5789                    client_id: server_id,
5790                    bytes: vec![0; 4],
5791                },
5792                0,
5793            )
5794            .expect("first packet should fit");
5795        let full = client
5796            .send(
5797                &mut client_transport,
5798                OutboundPacket {
5799                    client_id: server_id,
5800                    bytes: vec![1; 4],
5801                },
5802                0,
5803            )
5804            .expect_err("in-flight window should be full");
5805        match full {
5806            ReliableClientError::WindowFull {
5807                peer_client,
5808                capacity,
5809            } => {
5810                assert_eq!(peer_client, server_id);
5811                assert_eq!(capacity, 1);
5812            }
5813            other => panic!("unexpected error: {other}"),
5814        }
5815    }
5816
5817    #[test]
5818    fn reliable_client_window_counts_track_peers_and_saturated_sequence_replacement() {
5819        let first_peer = ClientId::new(1);
5820        let second_peer = ClientId::new(2);
5821        let mut transport = FakeTransport::default();
5822        let mut sender = ReliableClientSender::new(ReliableClientConfig {
5823            max_in_flight_per_peer: 3,
5824            retry_after_ticks: 2,
5825            max_attempts: 4,
5826            max_payload_bytes: 16,
5827            max_delivered_history: 0,
5828        });
5829        let mut send = |sender: &mut ReliableClientSender, peer_client| {
5830            sender
5831                .send(
5832                    &mut transport,
5833                    OutboundPacket {
5834                        client_id: peer_client,
5835                        bytes: b"count".to_vec(),
5836                    },
5837                    0,
5838                )
5839                .expect("bounded packet should send")
5840        };
5841
5842        assert_eq!(send(&mut sender, first_peer), 1);
5843        assert_eq!(send(&mut sender, first_peer), 2);
5844        assert_eq!(send(&mut sender, second_peer), 1);
5845        assert_eq!(sender.in_flight_for(first_peer), 2);
5846        assert_eq!(sender.in_flight_for(second_peer), 1);
5847        assert!(sender.acknowledge(first_peer, 1));
5848        assert_eq!(sender.in_flight_for(first_peer), 1);
5849
5850        sender.next_sequence.insert(first_peer, u64::MAX);
5851        assert_eq!(send(&mut sender, first_peer), u64::MAX);
5852        assert_eq!(sender.in_flight_for(first_peer), 2);
5853        assert_eq!(send(&mut sender, first_peer), u64::MAX);
5854        assert_eq!(sender.in_flight_for(first_peer), 2);
5855
5856        assert!(sender.acknowledge(first_peer, u64::MAX));
5857        assert!(sender.acknowledge(first_peer, 2));
5858        assert_eq!(sender.in_flight_for(first_peer), 0);
5859        assert!(!sender.in_flight_by_peer.contains_key(&first_peer));
5860        assert_eq!(sender.in_flight_for(second_peer), 1);
5861    }
5862
5863    #[test]
5864    fn reliable_client_endpoint_rejects_unknown_packet_source() {
5865        let mut endpoint = ReliableClientEndpoint::default();
5866        let mut transport = FakeTransport::default();
5867        let mut bytes = Vec::new();
5868        ReliableClientFrame::Ack { sequence: 1 }
5869            .encode(&mut bytes)
5870            .expect("ACK should encode");
5871        let error = endpoint
5872            .handle_inbound(
5873                &mut transport,
5874                InboundPacket {
5875                    client_id: None,
5876                    remote_addr: memory_addr(20007),
5877                    bytes,
5878                },
5879            )
5880            .expect_err("unknown source should be rejected");
5881        match error {
5882            ReliableClientError::MissingSourceClient => {}
5883            other => panic!("unexpected error: {other}"),
5884        }
5885    }
5886
5887    #[test]
5888    fn in_memory_station_transport_delivers_bounded_packets() {
5889        let mut transport = InMemoryStationTransport::new(StationTransportLimits {
5890            max_queued_packets_per_station: 2,
5891            max_packet_bytes: 8,
5892        });
5893        transport.register_station(StationId::new(2));
5894
5895        transport
5896            .send_station(station_packet(4))
5897            .expect("station packet should send");
5898        assert_eq!(transport.queued_len(StationId::new(2)), Some(1));
5899
5900        let packet = transport
5901            .try_recv_station(StationId::new(2))
5902            .expect("receive should work")
5903            .expect("packet should exist");
5904        assert_eq!(packet.source_station, StationId::new(1));
5905        assert_eq!(packet.target_station, StationId::new(2));
5906        assert_eq!(packet.bytes, vec![1; 4]);
5907        assert_eq!(transport.stats().packets_sent, 1);
5908        assert_eq!(transport.stats().packets_received, 1);
5909    }
5910
5911    #[test]
5912    fn in_memory_station_queues_allocate_lazily_and_retain_peak_capacity() {
5913        let target = StationId::new(2);
5914        let mut transport = InMemoryStationTransport::default();
5915        transport.register_station(target);
5916        assert_eq!(transport.retained_queue_capacity(), 0);
5917
5918        for _ in 0..8 {
5919            transport
5920                .send_station(station_packet(1))
5921                .expect("burst packet should send");
5922        }
5923        let peak = transport
5924            .queued_capacity(target)
5925            .expect("station should remain registered");
5926        assert!(peak >= 8);
5927        for _ in 0..8 {
5928            assert!(
5929                transport
5930                    .try_recv_station(target)
5931                    .expect("receive should work")
5932                    .is_some()
5933            );
5934        }
5935        assert_eq!(transport.queued_capacity(target), Some(peak));
5936
5937        transport
5938            .send_station(station_packet(1))
5939            .expect("packet should queue before teardown");
5940        assert_eq!(transport.unregister_station(target), Some(1));
5941        assert_eq!(transport.queued_len(target), None);
5942        assert_eq!(transport.retained_queue_capacity(), 0);
5943        assert_eq!(transport.unregister_station(target), None);
5944    }
5945
5946    #[test]
5947    fn in_memory_station_transport_rejects_full_queue_and_large_packet() {
5948        let mut transport = InMemoryStationTransport::new(StationTransportLimits {
5949            max_queued_packets_per_station: 1,
5950            max_packet_bytes: 4,
5951        });
5952        transport.register_station(StationId::new(2));
5953        transport
5954            .send_station(station_packet(4))
5955            .expect("first packet should send");
5956
5957        let full = transport
5958            .send_station(station_packet(4))
5959            .expect_err("queue should be full");
5960        assert_eq!(
5961            full,
5962            StationTransportError::QueueFull {
5963                station_id: StationId::new(2),
5964                capacity: 1
5965            }
5966        );
5967
5968        let large = transport
5969            .send_station(station_packet(5))
5970            .expect_err("packet should exceed budget");
5971        assert_eq!(
5972            large,
5973            StationTransportError::PacketTooLarge {
5974                budget: 4,
5975                actual: 5
5976            }
5977        );
5978        assert_eq!(transport.stats().packets_rejected_full, 1);
5979        assert_eq!(transport.stats().packets_rejected_bytes, 1);
5980    }
5981
5982    #[test]
5983    fn reliable_station_frame_roundtrips_data_and_ack() {
5984        let data = ReliableStationFrame::Data {
5985            sequence: 42,
5986            payload: b"station-event".to_vec(),
5987        };
5988        let mut bytes = Vec::new();
5989        data.encode(&mut bytes).expect("data frame should encode");
5990        let mut direct = Vec::new();
5991        ReliableStationFrame::encode_data(42, b"station-event", &mut direct)
5992            .expect("borrowed data frame should encode");
5993        assert_eq!(direct, bytes);
5994        assert_eq!(
5995            ReliableStationFrame::decode_ref(&bytes).expect("data frame view should decode"),
5996            ReliableStationFrameRef::Data {
5997                sequence: 42,
5998                payload: b"station-event"
5999            }
6000        );
6001        assert_eq!(
6002            ReliableStationFrame::decode(&bytes).expect("data frame should decode"),
6003            data
6004        );
6005        let truncated = &bytes[..bytes.len() - 1];
6006        assert_eq!(
6007            ReliableStationFrame::decode_ref(truncated).expect_err("view should reject truncation"),
6008            ReliableStationFrame::decode(truncated).expect_err("owned should reject truncation")
6009        );
6010        let mut trailing = bytes.clone();
6011        trailing.push(0);
6012        assert_eq!(
6013            ReliableStationFrame::decode_ref(&trailing).expect_err("view should reject trailing"),
6014            ReliableStationFrame::decode(&trailing).expect_err("owned should reject trailing")
6015        );
6016
6017        let ack = ReliableStationFrame::Ack { sequence: 42 };
6018        bytes.clear();
6019        ack.encode(&mut bytes).expect("ack frame should encode");
6020        assert_eq!(
6021            ReliableStationFrame::decode(&bytes).expect("ack frame should decode"),
6022            ack
6023        );
6024    }
6025
6026    #[test]
6027    fn reliable_station_endpoint_delivers_payload_and_acknowledges() {
6028        let station_one = StationId::new(1);
6029        let station_two = StationId::new(2);
6030        let mut transport = InMemoryStationTransport::default();
6031        transport.register_station(station_one);
6032        transport.register_station(station_two);
6033        let mut first = ReliableStationEndpoint::default();
6034        let mut second = ReliableStationEndpoint::default();
6035
6036        let sequence = first
6037            .send(
6038                &mut transport,
6039                StationOutboundPacket {
6040                    source_station: station_one,
6041                    target_station: station_two,
6042                    bytes: b"event".to_vec(),
6043                },
6044                0,
6045            )
6046            .expect("reliable packet should send");
6047        assert_eq!(sequence, 1);
6048        assert_eq!(first.sender.in_flight_len(), 1);
6049
6050        let raw = transport
6051            .try_recv_station(station_two)
6052            .expect("receive should work")
6053            .expect("data packet should exist");
6054        let wire_pointer = raw.bytes.as_ptr();
6055        let delivered = second
6056            .handle_inbound(&mut transport, raw)
6057            .expect("data packet should handle")
6058            .expect("first data packet should deliver");
6059        assert_eq!(delivered.source_station, station_one);
6060        assert_eq!(delivered.target_station, station_two);
6061        assert_eq!(delivered.bytes, b"event");
6062        assert_eq!(delivered.bytes.as_ptr(), wire_pointer);
6063        assert_eq!(second.receiver.stats().data_delivered, 1);
6064        assert_eq!(second.receiver.stats().acks_sent, 1);
6065
6066        let ack = transport
6067            .try_recv_station(station_one)
6068            .expect("ack receive should work")
6069            .expect("ack packet should exist");
6070        assert_eq!(
6071            first
6072                .handle_inbound(&mut transport, ack)
6073                .expect("ack should handle"),
6074            None
6075        );
6076        assert_eq!(first.sender.in_flight_len(), 0);
6077        assert_eq!(first.sender.stats().acks_received, 1);
6078    }
6079
6080    #[test]
6081    fn reliable_station_endpoint_retries_and_suppresses_duplicate_delivery() {
6082        let station_one = StationId::new(1);
6083        let station_two = StationId::new(2);
6084        let mut transport = InMemoryStationTransport::default();
6085        transport.register_station(station_one);
6086        transport.register_station(station_two);
6087        let mut first = ReliableStationEndpoint::default();
6088        let mut second = ReliableStationEndpoint::default();
6089
6090        first
6091            .send(
6092                &mut transport,
6093                StationOutboundPacket {
6094                    source_station: station_one,
6095                    target_station: station_two,
6096                    bytes: b"idempotent-event".to_vec(),
6097                },
6098                0,
6099            )
6100            .expect("reliable packet should send");
6101        let retry = first
6102            .retry_due(&mut transport, 2)
6103            .expect("retry should send");
6104        assert_eq!(retry.examined, 1);
6105        assert_eq!(retry.retried, 1);
6106        assert_eq!(retry.timed_out, 0);
6107        assert_eq!(first.sender.stats().retries_sent, 1);
6108        let retained_keys = first.retry_scratch.retained_key_capacity();
6109        assert!(retained_keys >= 1);
6110        assert_eq!(
6111            first
6112                .retry_due(&mut transport, 2)
6113                .expect("non-due scan should succeed"),
6114            ReliableRetryReport::default()
6115        );
6116        assert_eq!(first.retry_scratch.retained_key_capacity(), retained_keys);
6117        assert_eq!(transport.queued_len(station_two), Some(2));
6118
6119        let first_raw = transport
6120            .try_recv_station(station_two)
6121            .expect("receive should work")
6122            .expect("first data packet should exist");
6123        let delivered = second
6124            .handle_inbound(&mut transport, first_raw)
6125            .expect("first data packet should handle")
6126            .expect("first data packet should deliver");
6127        assert_eq!(delivered.bytes, b"idempotent-event");
6128
6129        let duplicate_raw = transport
6130            .try_recv_station(station_two)
6131            .expect("receive should work")
6132            .expect("duplicate data packet should exist");
6133        assert_eq!(
6134            second
6135                .handle_inbound(&mut transport, duplicate_raw)
6136                .expect("duplicate data packet should handle"),
6137            None
6138        );
6139        assert_eq!(second.receiver.stats().data_delivered, 1);
6140        assert_eq!(second.receiver.stats().duplicates_suppressed, 1);
6141        assert_eq!(second.receiver.stats().acks_sent, 2);
6142    }
6143
6144    #[test]
6145    fn reliable_station_failed_retry_preserves_attempt_before_timeout() {
6146        let source = StationId::new(1);
6147        let target = StationId::new(2);
6148        let mut transport = InMemoryStationTransport::new(StationTransportLimits {
6149            max_queued_packets_per_station: 1,
6150            max_packet_bytes: 128,
6151        });
6152        transport.register_station(target);
6153        let mut sender = ReliableStationSender::new(ReliableStationConfig {
6154            max_in_flight_per_target: 1,
6155            retry_after_ticks: 1,
6156            max_attempts: 2,
6157            max_payload_bytes: 64,
6158            max_delivered_history: 0,
6159        });
6160        let mut scratch = ReliableStationRetryScratch::new();
6161        sender
6162            .send(
6163                &mut transport,
6164                StationOutboundPacket {
6165                    source_station: source,
6166                    target_station: target,
6167                    bytes: b"retry".to_vec(),
6168                },
6169                0,
6170            )
6171            .expect("initial packet should fill queue");
6172
6173        assert!(matches!(
6174            sender.retry_due_into(&mut transport, 1, &mut scratch),
6175            Err(ReliableStationError::Transport(
6176                StationTransportError::QueueFull { .. }
6177            ))
6178        ));
6179        transport
6180            .try_recv_station(target)
6181            .expect("queue should read")
6182            .expect("initial packet should remain");
6183        assert_eq!(
6184            sender
6185                .retry_due_into(&mut transport, 1, &mut scratch)
6186                .expect("failed attempt must remain retryable")
6187                .retried,
6188            1
6189        );
6190        let timeout = sender
6191            .retry_due_into(&mut transport, 2, &mut scratch)
6192            .expect("exhausted packet should time out");
6193        assert_eq!(timeout.retried, 0);
6194        assert_eq!(timeout.timed_out, 1);
6195        assert_eq!(sender.in_flight_len(), 0);
6196        assert_eq!(sender.in_flight_for(target), 0);
6197        assert!(!sender.in_flight_by_target.contains_key(&target));
6198    }
6199
6200    #[test]
6201    fn reliable_station_receiver_bounds_duplicate_history() {
6202        let station_one = StationId::new(1);
6203        let station_two = StationId::new(2);
6204        let mut transport = InMemoryStationTransport::default();
6205        transport.register_station(station_one);
6206        transport.register_station(station_two);
6207        let config = ReliableStationConfig {
6208            max_in_flight_per_target: 8,
6209            retry_after_ticks: 2,
6210            max_attempts: 4,
6211            max_payload_bytes: DEFAULT_RELIABLE_STATION_MAX_PAYLOAD_BYTES,
6212            max_delivered_history: 1,
6213        };
6214        let mut endpoint = ReliableStationEndpoint::new(config);
6215
6216        let packet = |sequence: u64, payload: &[u8]| {
6217            let mut bytes = Vec::new();
6218            ReliableStationFrame::Data {
6219                sequence,
6220                payload: payload.to_vec(),
6221            }
6222            .encode(&mut bytes)
6223            .expect("data frame should encode");
6224            StationInboundPacket {
6225                source_station: station_one,
6226                target_station: station_two,
6227                bytes,
6228            }
6229        };
6230
6231        assert!(
6232            endpoint
6233                .handle_inbound(&mut transport, packet(1, b"first"))
6234                .expect("first data packet should handle")
6235                .is_some()
6236        );
6237        assert_eq!(
6238            endpoint
6239                .handle_inbound(&mut transport, packet(1, b"first-duplicate"))
6240                .expect("duplicate data packet should handle"),
6241            None
6242        );
6243        assert!(
6244            endpoint
6245                .handle_inbound(&mut transport, packet(2, b"second"))
6246                .expect("second data packet should handle")
6247                .is_some()
6248        );
6249        assert!(
6250            endpoint
6251                .handle_inbound(&mut transport, packet(1, b"first-after-eviction"))
6252                .expect("evicted data packet should handle")
6253                .is_some()
6254        );
6255        assert_eq!(endpoint.receiver.stats().data_delivered, 3);
6256        assert_eq!(endpoint.receiver.stats().duplicates_suppressed, 1);
6257    }
6258
6259    #[test]
6260    fn reliable_station_sender_enforces_payload_and_window_limits() {
6261        let station_one = StationId::new(1);
6262        let station_two = StationId::new(2);
6263        let mut transport = InMemoryStationTransport::default();
6264        transport.register_station(station_one);
6265        transport.register_station(station_two);
6266        let config = ReliableStationConfig {
6267            max_in_flight_per_target: 1,
6268            retry_after_ticks: 2,
6269            max_attempts: 4,
6270            max_payload_bytes: 4,
6271            max_delivered_history: DEFAULT_RELIABLE_STATION_DELIVERED_HISTORY,
6272        };
6273        let mut endpoint = ReliableStationEndpoint::new(config);
6274
6275        let too_large = endpoint
6276            .send(
6277                &mut transport,
6278                StationOutboundPacket {
6279                    source_station: station_one,
6280                    target_station: station_two,
6281                    bytes: vec![0; 5],
6282                },
6283                0,
6284            )
6285            .expect_err("payload should exceed configured budget");
6286        match too_large {
6287            ReliableStationError::PayloadTooLarge { budget, actual } => {
6288                assert_eq!(budget, 4);
6289                assert_eq!(actual, 5);
6290            }
6291            other => panic!("unexpected error: {other}"),
6292        }
6293
6294        endpoint
6295            .send(
6296                &mut transport,
6297                StationOutboundPacket {
6298                    source_station: station_one,
6299                    target_station: station_two,
6300                    bytes: vec![0; 4],
6301                },
6302                0,
6303            )
6304            .expect("first packet should fit");
6305        let full = endpoint
6306            .send(
6307                &mut transport,
6308                StationOutboundPacket {
6309                    source_station: station_one,
6310                    target_station: station_two,
6311                    bytes: vec![1; 4],
6312                },
6313                0,
6314            )
6315            .expect_err("in-flight window should be full");
6316        match full {
6317            ReliableStationError::WindowFull {
6318                target_station,
6319                capacity,
6320            } => {
6321                assert_eq!(target_station, station_two);
6322                assert_eq!(capacity, 1);
6323            }
6324            other => panic!("unexpected error: {other}"),
6325        }
6326    }
6327
6328    #[test]
6329    fn reliable_station_window_counts_track_targets_and_saturated_sequence_replacement() {
6330        let source = StationId::new(1);
6331        let first_target = StationId::new(2);
6332        let second_target = StationId::new(3);
6333        let mut transport = InMemoryStationTransport::default();
6334        transport.register_station(first_target);
6335        transport.register_station(second_target);
6336        let mut sender = ReliableStationSender::new(ReliableStationConfig {
6337            max_in_flight_per_target: 3,
6338            retry_after_ticks: 2,
6339            max_attempts: 4,
6340            max_payload_bytes: 16,
6341            max_delivered_history: 0,
6342        });
6343        let mut send = |sender: &mut ReliableStationSender, target_station| {
6344            sender
6345                .send(
6346                    &mut transport,
6347                    StationOutboundPacket {
6348                        source_station: source,
6349                        target_station,
6350                        bytes: b"count".to_vec(),
6351                    },
6352                    0,
6353                )
6354                .expect("bounded packet should send")
6355        };
6356
6357        assert_eq!(send(&mut sender, first_target), 1);
6358        assert_eq!(send(&mut sender, first_target), 2);
6359        assert_eq!(send(&mut sender, second_target), 1);
6360        assert_eq!(sender.in_flight_for(first_target), 2);
6361        assert_eq!(sender.in_flight_for(second_target), 1);
6362        assert!(sender.acknowledge(first_target, 1));
6363        assert_eq!(sender.in_flight_for(first_target), 1);
6364
6365        sender.next_sequence.insert(first_target, u64::MAX);
6366        assert_eq!(send(&mut sender, first_target), u64::MAX);
6367        assert_eq!(sender.in_flight_for(first_target), 2);
6368        assert_eq!(send(&mut sender, first_target), u64::MAX);
6369        assert_eq!(sender.in_flight_for(first_target), 2);
6370
6371        assert!(sender.acknowledge(first_target, u64::MAX));
6372        assert!(sender.acknowledge(first_target, 2));
6373        assert_eq!(sender.in_flight_for(first_target), 0);
6374        assert!(!sender.in_flight_by_target.contains_key(&first_target));
6375        assert_eq!(sender.in_flight_for(second_target), 1);
6376    }
6377
6378    #[test]
6379    fn udp_transport_sends_and_receives_registered_client() {
6380        let client_id = ClientId::new(7);
6381        let server_id = ClientId::new(0);
6382        let mut server = UdpTransport::bind("127.0.0.1:0").expect("server should bind");
6383        let mut client = UdpTransport::bind("127.0.0.1:0").expect("client should bind");
6384        let server_addr = server.local_addr().expect("server addr should exist");
6385        let client_addr = client.local_addr().expect("client addr should exist");
6386
6387        server.register_client(client_id, client_addr);
6388        client.register_client(server_id, server_addr);
6389
6390        client
6391            .send(OutboundPacket {
6392                client_id: server_id,
6393                bytes: b"command".to_vec(),
6394            })
6395            .expect("client should send");
6396        let first = recv_ref_with_retry(&mut server);
6397        assert_eq!(first.0, Some(client_id));
6398        assert_eq!(first.1, client_addr);
6399        assert_eq!((first.2, first.3, first.4), (7, b'c', b'd'));
6400
6401        client
6402            .send(OutboundPacket {
6403                client_id: server_id,
6404                bytes: b"next".to_vec(),
6405            })
6406            .expect("client should send another packet");
6407        let second = recv_ref_with_retry(&mut server);
6408        assert_eq!((second.2, second.3, second.4), (4, b'n', b't'));
6409        assert_eq!(second.5, first.5);
6410
6411        server
6412            .send(OutboundPacket {
6413                client_id,
6414                bytes: b"replication".to_vec(),
6415            })
6416            .expect("server should send");
6417        let inbound = recv_with_retry(&mut client);
6418        assert_eq!(inbound.client_id, Some(server_id));
6419        assert_eq!(inbound.remote_addr, server_addr);
6420        assert_eq!(inbound.bytes, b"replication");
6421    }
6422
6423    #[test]
6424    fn udp_transport_rejects_unknown_client() {
6425        let mut transport = UdpTransport::bind("127.0.0.1:0").expect("transport should bind");
6426        let error = transport
6427            .send(OutboundPacket {
6428                client_id: ClientId::new(99),
6429                bytes: Vec::new(),
6430            })
6431            .expect_err("unknown client should fail");
6432
6433        match error {
6434            UdpTransportError::UnknownClient(client_id) => {
6435                assert_eq!(client_id, ClientId::new(99));
6436            }
6437            UdpTransportError::Io(error) => panic!("unexpected io error: {error}"),
6438        }
6439    }
6440
6441    #[test]
6442    fn udp_station_transport_sends_and_receives_registered_stations() {
6443        let station_one = StationId::new(1);
6444        let station_two = StationId::new(2);
6445        let mut first =
6446            UdpStationTransport::bind(station_one, "127.0.0.1:0").expect("first should bind");
6447        let mut second =
6448            UdpStationTransport::bind(station_two, "127.0.0.1:0").expect("second should bind");
6449        let first_addr = first.local_addr().expect("first addr should exist");
6450        let second_addr = second.local_addr().expect("second addr should exist");
6451
6452        first.register_station(station_two, second_addr);
6453        second.register_station(station_one, first_addr);
6454
6455        first
6456            .send_station(StationOutboundPacket {
6457                source_station: station_one,
6458                target_station: station_two,
6459                bytes: b"handoff-prepare".to_vec(),
6460            })
6461            .expect("first station should send");
6462        let prepare = recv_station_ref_with_retry(&mut second, station_two);
6463        assert_eq!(prepare.0, station_one);
6464        assert_eq!(prepare.1, station_two);
6465        assert_eq!((prepare.2, prepare.3, prepare.4), (15, b'h', b'e'));
6466
6467        first
6468            .send_station(StationOutboundPacket {
6469                source_station: station_one,
6470                target_station: station_two,
6471                bytes: b"next".to_vec(),
6472            })
6473            .expect("first station should send another packet");
6474        let next = recv_station_ref_with_retry(&mut second, station_two);
6475        assert_eq!((next.2, next.3, next.4), (4, b'n', b't'));
6476        assert_eq!(next.5, prepare.5);
6477
6478        second
6479            .send_station(StationOutboundPacket {
6480                source_station: station_two,
6481                target_station: station_one,
6482                bytes: b"handoff-commit".to_vec(),
6483            })
6484            .expect("second station should send");
6485        let inbound = recv_station_with_retry(&mut first, station_one);
6486        assert_eq!(inbound.source_station, station_two);
6487        assert_eq!(inbound.target_station, station_one);
6488        assert_eq!(inbound.bytes, b"handoff-commit");
6489        assert_eq!(first.stats().packets_sent, 2);
6490        assert_eq!(first.stats().packets_received, 1);
6491        assert_eq!(second.stats().packets_sent, 1);
6492        assert_eq!(second.stats().packets_received, 2);
6493    }
6494
6495    #[test]
6496    fn udp_station_transport_rejects_invalid_station_endpoints() {
6497        let local = StationId::new(1);
6498        let mut transport =
6499            UdpStationTransport::bind(local, "127.0.0.1:0").expect("transport should bind");
6500
6501        let source_mismatch = transport
6502            .send_station(StationOutboundPacket {
6503                source_station: StationId::new(9),
6504                target_station: StationId::new(2),
6505                bytes: Vec::new(),
6506            })
6507            .expect_err("source should match local station");
6508        match source_mismatch {
6509            UdpStationTransportError::LocalStationMismatch {
6510                local_station,
6511                packet_source,
6512            } => {
6513                assert_eq!(local_station, local);
6514                assert_eq!(packet_source, StationId::new(9));
6515            }
6516            other => panic!("unexpected error: {other}"),
6517        }
6518
6519        let unknown = transport
6520            .send_station(StationOutboundPacket {
6521                source_station: local,
6522                target_station: StationId::new(2),
6523                bytes: Vec::new(),
6524            })
6525            .expect_err("target station should be registered");
6526        match unknown {
6527            UdpStationTransportError::UnknownStation(station_id) => {
6528                assert_eq!(station_id, StationId::new(2));
6529            }
6530            other => panic!("unexpected error: {other}"),
6531        }
6532
6533        let target_mismatch = transport
6534            .try_recv_station(StationId::new(99))
6535            .expect_err("receive target should match local station");
6536        match target_mismatch {
6537            UdpStationTransportError::TargetStationMismatch {
6538                local_station,
6539                requested_target,
6540            } => {
6541                assert_eq!(local_station, local);
6542                assert_eq!(requested_target, StationId::new(99));
6543            }
6544            other => panic!("unexpected error: {other}"),
6545        }
6546    }
6547
6548    #[test]
6549    fn reliable_ack_churn_compacts_client_and_station_deadlines() {
6550        let peer = ClientId::new(2);
6551        let mut client_transport = FakeTransport::default();
6552        let mut client = ReliableClientSender::default();
6553        for tick in 0..100 {
6554            let sequence = client
6555                .send(
6556                    &mut client_transport,
6557                    OutboundPacket {
6558                        client_id: peer,
6559                        bytes: vec![1],
6560                    },
6561                    tick,
6562                )
6563                .expect("send succeeds");
6564            assert!(client.acknowledge(peer, sequence));
6565            assert!(client.retry_deadlines.len() <= RETRY_DEADLINE_COMPACTION_MULTIPLE);
6566        }
6567
6568        let source = StationId::new(1);
6569        let target = StationId::new(2);
6570        let mut station_transport = InMemoryStationTransport::default();
6571        station_transport.register_station(target);
6572        let mut station = ReliableStationSender::default();
6573        for tick in 0..100 {
6574            let sequence = station
6575                .send(
6576                    &mut station_transport,
6577                    StationOutboundPacket {
6578                        source_station: source,
6579                        target_station: target,
6580                        bytes: vec![1],
6581                    },
6582                    tick,
6583                )
6584                .expect("send succeeds");
6585            assert!(station.acknowledge(target, sequence));
6586            assert!(station.retry_deadlines.len() <= RETRY_DEADLINE_COMPACTION_MULTIPLE);
6587        }
6588    }
6589
6590    #[test]
6591    fn stale_in_memory_endpoint_does_not_revive_after_reregistration() {
6592        let hub = InMemoryTransportHub::default();
6593        let id = ClientId::new(1);
6594        let peer_id = ClientId::new(2);
6595        let mut stale = hub.endpoint(id, memory_addr(24001)).expect("register old");
6596        let mut peer = hub
6597            .endpoint(peer_id, memory_addr(24002))
6598            .expect("register peer");
6599        let stale_clone = stale.clone();
6600        hub.unregister_client(id).expect("unregister old");
6601        let mut current = hub.endpoint(id, memory_addr(24003)).expect("register new");
6602
6603        assert!(
6604            matches!(stale.local_addr(), Err(InMemoryTransportError::MissingLocal(found)) if found == id)
6605        );
6606        assert!(
6607            matches!(stale.send(OutboundPacket { client_id: peer_id, bytes: vec![1] }), Err(InMemoryTransportError::MissingLocal(found)) if found == id)
6608        );
6609        assert!(
6610            matches!(stale.try_recv(), Err(InMemoryTransportError::MissingLocal(found)) if found == id)
6611        );
6612        assert!(
6613            matches!(stale_clone.local_addr(), Err(InMemoryTransportError::MissingLocal(found)) if found == id)
6614        );
6615        peer.send(OutboundPacket {
6616            client_id: id,
6617            bytes: vec![7],
6618        })
6619        .expect("new endpoint receives");
6620        assert_eq!(
6621            current.local_addr().expect("current valid"),
6622            Some(memory_addr(24003))
6623        );
6624        assert_eq!(
6625            current
6626                .try_recv()
6627                .expect("receive current")
6628                .expect("packet")
6629                .bytes,
6630            vec![7]
6631        );
6632    }
6633}