Skip to main content

fips_core/transport/
packet_channel.rs

1//! Priority-aware packet channel for transport receive paths.
2
3use super::{TransportAddr, TransportId};
4use std::mem;
5use std::sync::{
6    Arc, Mutex,
7    atomic::{AtomicUsize, Ordering::Relaxed},
8};
9use tokio::sync::mpsc::{
10    Sender, UnboundedReceiver, UnboundedSender,
11    error::{TryRecvError, TrySendError},
12};
13
14pub(crate) trait PacketFastIngressSink: std::fmt::Debug + Send + Sync {
15    fn try_ingest_batch(&self, packets: &mut Vec<ReceivedPacket>) -> usize;
16}
17
18/// A packet received from a transport.
19#[derive(Clone, Debug)]
20pub struct ReceivedPacket {
21    /// Which transport received this packet.
22    pub transport_id: TransportId,
23    /// Remote peer address.
24    pub remote_addr: TransportAddr,
25    /// Packet data.
26    pub data: PacketBuffer,
27    /// Receipt timestamp (Unix milliseconds).
28    pub timestamp_ms: u64,
29    /// Monotonic timestamp for optional pipeline queue-wait profiling.
30    #[doc(hidden)]
31    pub trace_enqueued_at: Option<crate::perf_profile::TraceStamp>,
32    /// Monotonic timestamp captured when `PacketRx` takes ownership of a
33    /// channel item. Distinguishes mpsc/channel residence from rx-loop-owned
34    /// batch-tail residence in perf traces.
35    #[doc(hidden)]
36    pub trace_rx_loop_owned_at: Option<crate::perf_profile::TraceStamp>,
37}
38
39impl ReceivedPacket {
40    /// Create a received packet with explicit timestamp.
41    pub fn with_timestamp(
42        transport_id: TransportId,
43        remote_addr: TransportAddr,
44        data: PacketBuffer,
45        timestamp_ms: u64,
46    ) -> Self {
47        Self::with_trace_timestamp(
48            transport_id,
49            remote_addr,
50            data,
51            timestamp_ms,
52            crate::perf_profile::stamp(),
53        )
54    }
55
56    /// Create a received packet with explicit wall-clock and queue timestamps.
57    ///
58    /// UDP receive paths can drain several datagrams per kernel batch. Those
59    /// datagrams arrived close together, so sharing one wall-clock sample and
60    /// one queue trace stamp across the batch avoids per-packet clock reads
61    /// while preserving arrival order and queue residence visibility.
62    pub(crate) fn with_trace_timestamp(
63        transport_id: TransportId,
64        remote_addr: TransportAddr,
65        data: PacketBuffer,
66        timestamp_ms: u64,
67        trace_enqueued_at: Option<crate::perf_profile::TraceStamp>,
68    ) -> Self {
69        Self {
70            transport_id,
71            remote_addr,
72            data,
73            timestamp_ms,
74            trace_enqueued_at,
75            trace_rx_loop_owned_at: None,
76        }
77    }
78
79    pub(crate) fn is_transport_priority(&self) -> bool {
80        is_transport_priority_packet(self.data.as_slice())
81    }
82}
83
84/// Byte storage for a received transport packet.
85///
86/// Receive/decrypt/drop paths carry this owner so pressure drops and endpoint
87/// delivery can recycle kernel receive buffers without an extra packet copy.
88#[derive(Debug, Default)]
89pub struct PacketBuffer {
90    data: Vec<u8>,
91    start: usize,
92    pool: Option<PacketBufferPool>,
93}
94
95impl PacketBuffer {
96    #[cfg(any(test, target_os = "linux", target_os = "macos"))]
97    fn pooled(data: Vec<u8>, pool: PacketBufferPool) -> Self {
98        Self {
99            data,
100            start: 0,
101            pool: Some(pool),
102        }
103    }
104
105    pub fn new(data: Vec<u8>) -> Self {
106        Self {
107            data,
108            start: 0,
109            pool: None,
110        }
111    }
112
113    pub fn as_slice(&self) -> &[u8] {
114        &self.data[self.start..]
115    }
116
117    pub fn as_mut_slice(&mut self) -> &mut [u8] {
118        &mut self.data[self.start..]
119    }
120
121    pub fn len(&self) -> usize {
122        self.data.len().saturating_sub(self.start)
123    }
124
125    pub fn is_empty(&self) -> bool {
126        self.len() == 0
127    }
128
129    pub fn into_vec(mut self) -> Vec<u8> {
130        self.pool = None;
131        if self.start > 0 {
132            self.data.drain(..self.start);
133            self.start = 0;
134        }
135        mem::take(&mut self.data)
136    }
137
138    pub(crate) fn trim_front(&mut self, len: usize) -> bool {
139        if len > self.len() {
140            return false;
141        }
142        self.start += len;
143        true
144    }
145
146    pub(crate) fn truncate(&mut self, len: usize) {
147        if len < self.len() {
148            self.data.truncate(self.start + len);
149        }
150    }
151
152    pub(crate) fn extend_from_slice(&mut self, bytes: &[u8]) {
153        self.data.extend_from_slice(bytes);
154    }
155
156    pub(crate) fn try_prepend_slices(&mut self, parts: &[&[u8]], reserve_tail: usize) -> bool {
157        let prefix_len = parts
158            .iter()
159            .fold(0usize, |total, part| total.saturating_add(part.len()));
160        if prefix_len == 0 {
161            return self.data.capacity().saturating_sub(self.data.len()) >= reserve_tail;
162        }
163
164        let len = self.data.len();
165        if self.start >= prefix_len && self.data.capacity().saturating_sub(len) >= reserve_tail {
166            let new_start = self.start - prefix_len;
167            let mut offset = new_start;
168            for part in parts {
169                self.data[offset..offset + part.len()].copy_from_slice(part);
170                offset += part.len();
171            }
172            self.start = new_start;
173            return true;
174        }
175
176        if self.data.capacity().saturating_sub(len) < prefix_len.saturating_add(reserve_tail) {
177            return false;
178        }
179
180        // Move the packet body right inside the existing allocation, then fill
181        // the newly opened header space. This is the Vec equivalent of the
182        // fixed headroom WireGuard-go keeps in its message buffers.
183        unsafe {
184            let ptr = self.data.as_mut_ptr();
185            std::ptr::copy(
186                ptr.add(self.start),
187                ptr.add(self.start + prefix_len),
188                self.len(),
189            );
190            let mut offset = self.start;
191            for part in parts {
192                std::ptr::copy_nonoverlapping(part.as_ptr(), ptr.add(offset), part.len());
193                offset += part.len();
194            }
195            self.data.set_len(len + prefix_len);
196        }
197        true
198    }
199
200    pub(crate) fn replace_visible_prefix(&mut self, remove_len: usize, prefix: &[u8]) -> bool {
201        if remove_len > self.len() {
202            return false;
203        }
204
205        let prefix_len = prefix.len();
206        let tail_len = self.len() - remove_len;
207        if prefix_len >= remove_len {
208            let grow = prefix_len - remove_len;
209            if grow > 0 && self.start >= grow {
210                let new_start = self.start - grow;
211                self.data[new_start..new_start + prefix_len].copy_from_slice(prefix);
212                self.start = new_start;
213                return true;
214            }
215
216            let len = self.data.len();
217            if grow > 0 {
218                self.data.reserve(grow);
219                unsafe {
220                    let ptr = self.data.as_mut_ptr();
221                    std::ptr::copy(
222                        ptr.add(self.start + remove_len),
223                        ptr.add(self.start + prefix_len),
224                        tail_len,
225                    );
226                    self.data.set_len(len + grow);
227                }
228            }
229            self.data[self.start..self.start + prefix_len].copy_from_slice(prefix);
230            return true;
231        }
232
233        let shrink = remove_len - prefix_len;
234        if tail_len > 0 {
235            self.data.copy_within(
236                self.start + remove_len..self.start + remove_len + tail_len,
237                self.start + prefix_len,
238            );
239        }
240        self.data.truncate(self.data.len() - shrink);
241        self.data[self.start..self.start + prefix_len].copy_from_slice(prefix);
242        true
243    }
244}
245
246impl Clone for PacketBuffer {
247    fn clone(&self) -> Self {
248        Self {
249            data: self.as_slice().to_vec(),
250            start: 0,
251            pool: None,
252        }
253    }
254}
255
256impl AsRef<[u8]> for PacketBuffer {
257    fn as_ref(&self) -> &[u8] {
258        self.as_slice()
259    }
260}
261
262impl Drop for PacketBuffer {
263    fn drop(&mut self) {
264        if let Some(pool) = self.pool.take() {
265            pool.put(mem::take(&mut self.data));
266        }
267    }
268}
269
270impl PartialEq for PacketBuffer {
271    fn eq(&self, other: &Self) -> bool {
272        self.as_slice() == other.as_slice()
273    }
274}
275
276impl Eq for PacketBuffer {}
277
278/// FMP packet shape that is visible before dataplane authenticates established data.
279///
280/// Bulk app data is opaque phase-0 data here, so the transport channel only
281/// promotes exact control-sized frames that can be identified from public wire
282/// length: handshakes, link heartbeats, and fixed-size link MMP reports.
283const FMP_VERSION: u8 = crate::node::wire::FMP_VERSION;
284const FMP_PHASE_ESTABLISHED: u8 = crate::node::wire::PHASE_ESTABLISHED;
285const FMP_PHASE_MSG1: u8 = crate::node::wire::PHASE_MSG1;
286const FMP_PHASE_MSG2: u8 = crate::node::wire::PHASE_MSG2;
287const FMP_COMMON_PREFIX_SIZE: usize = crate::node::wire::COMMON_PREFIX_SIZE;
288const FMP_ESTABLISHED_HEADER_SIZE: usize = crate::node::wire::ESTABLISHED_HEADER_SIZE;
289const FMP_MSG1_WIRE_SIZE: usize = crate::node::wire::MSG1_WIRE_SIZE;
290const FMP_MSG2_WIRE_SIZE: usize = crate::node::wire::MSG2_WIRE_SIZE;
291const AEAD_TAG_SIZE: usize = crate::noise::TAG_SIZE;
292const FMP_HEARTBEAT_PLAINTEXT_SIZE: usize = 4 + 1;
293const FMP_MMP_SENDER_REPORT_PLAINTEXT_SIZE: usize = crate::mmp::SENDER_REPORT_WIRE_SIZE;
294const FMP_MMP_RECEIVER_REPORT_PLAINTEXT_SIZE: usize = crate::mmp::RECEIVER_REPORT_WIRE_SIZE;
295
296fn is_transport_priority_packet(data: &[u8]) -> bool {
297    if data.len() < FMP_COMMON_PREFIX_SIZE {
298        return false;
299    }
300
301    let version = data[0] >> 4;
302    let phase = data[0] & 0x0F;
303    if version != FMP_VERSION {
304        return false;
305    }
306
307    match phase {
308        FMP_PHASE_MSG1 => data.len() == FMP_MSG1_WIRE_SIZE,
309        FMP_PHASE_MSG2 => data.len() == FMP_MSG2_WIRE_SIZE,
310        FMP_PHASE_ESTABLISHED => is_fmp_established_priority_packet(data),
311        _ => false,
312    }
313}
314
315fn is_fmp_established_priority_packet(data: &[u8]) -> bool {
316    if data.len() < FMP_ESTABLISHED_HEADER_SIZE.saturating_add(AEAD_TAG_SIZE) {
317        return false;
318    }
319
320    let payload_len = usize::from(u16::from_le_bytes([data[2], data[3]]));
321    let expected_len = FMP_ESTABLISHED_HEADER_SIZE
322        .saturating_add(payload_len)
323        .saturating_add(AEAD_TAG_SIZE);
324    if data.len() != expected_len {
325        return false;
326    }
327
328    matches!(
329        payload_len,
330        FMP_HEARTBEAT_PLAINTEXT_SIZE
331            | FMP_MMP_SENDER_REPORT_PLAINTEXT_SIZE
332            | FMP_MMP_RECEIVER_REPORT_PLAINTEXT_SIZE
333    )
334}
335
336/// Number of receive-batch Vec containers retained for reuse.
337const PACKET_BATCH_POOL_LIMIT: usize = 256;
338/// Avoid pinning unusually large test/control batches in the hot-path pool.
339const PACKET_BATCH_MAX_RETAINED_CAPACITY: usize = 256;
340/// Number of packet byte buffers retained after pressure drops.
341const PACKET_BUFFER_POOL_LIMIT: usize = 4096;
342/// Avoid pinning oversized receive buffers in the hot-path pool.
343const PACKET_BUFFER_MAX_RETAINED_CAPACITY: usize = 16 * 1024;
344
345/// Packet count at which the transport receive channel is visibly backlogged.
346///
347/// This tracks packets still owned by the priority/bulk mpsc channels. Once a
348/// batched item is dequeued into `PacketRx`'s pending iterator, it no longer
349/// contributes to this counter; those packets are already inside the rx-loop
350/// owner's drain budget rather than waiting behind the transport channel.
351const TRANSPORT_CHANNEL_BACKLOG_HIGH_WATER: usize = 16_384;
352
353/// Channel sender for received packets.
354///
355/// The priority lane stays unbounded because control-shaped datagrams must keep
356/// making progress even when bulk is saturated. The bulk lane is bounded by the
357/// configured packet-channel capacity in packets, not receive-batch items, and
358/// uses nonblocking `try_send`: overload sheds bulk explicitly instead of
359/// hiding unbounded latency behind the rx loop.
360#[derive(Clone, Debug)]
361pub struct PacketTx {
362    priority: UnboundedSender<PacketQueueItem>,
363    bulk: Sender<PacketQueueItem>,
364    fast_ingress: Option<Arc<dyn PacketFastIngressSink>>,
365    batch_pool: PacketBatchPool,
366    #[cfg(any(test, target_os = "linux", target_os = "macos"))]
367    buffer_pool: PacketBufferPool,
368    /// Packet-count ready hint for priority lane probes. Bulk batch tails check
369    /// this instead of touching an empty priority mpsc once per data packet.
370    priority_queued_packets: Arc<AtomicUsize>,
371    queued_packets: Arc<AtomicUsize>,
372    bulk_queued_packets: Arc<AtomicUsize>,
373    bulk_packet_capacity: usize,
374    track_backlog: bool,
375}
376
377/// Channel receiver for received packets.
378pub struct PacketRx {
379    priority: UnboundedReceiver<PacketQueueItem>,
380    bulk: tokio::sync::mpsc::Receiver<PacketQueueItem>,
381    priority_queued_packets: Arc<AtomicUsize>,
382    queued_packets: Arc<AtomicUsize>,
383    bulk_queued_packets: Arc<AtomicUsize>,
384    track_backlog: bool,
385    pending_priority: Option<PendingPackets>,
386    pending_bulk: Option<PendingPackets>,
387    priority_closed: bool,
388    bulk_closed: bool,
389}
390
391#[derive(Clone, Debug)]
392struct PacketBatchPool {
393    inner: Arc<Mutex<Vec<Vec<ReceivedPacket>>>>,
394}
395
396#[derive(Clone, Debug)]
397struct PacketBufferPool {
398    inner: Arc<Mutex<Vec<Vec<u8>>>>,
399    available: Arc<AtomicUsize>,
400}
401
402#[derive(Debug)]
403pub(crate) struct PacketBatch {
404    packets: Vec<ReceivedPacket>,
405    pool: Option<PacketBatchPool>,
406}
407
408#[derive(Debug)]
409enum PacketQueueItem {
410    One(ReceivedPacket),
411    Batch(PacketBatch),
412}
413
414#[derive(Clone, Copy)]
415enum PacketLane {
416    Priority,
417    Bulk,
418}
419
420#[derive(Clone, Copy)]
421enum PacketQueueTx {
422    Priority,
423    Bulk,
424}
425
426enum PacketSendFailure {
427    Closed(PacketQueueItem),
428    DroppedBulk(usize),
429}
430
431struct PendingPackets {
432    batch: PacketBatch,
433    rx_loop_owned_at: Option<crate::perf_profile::TraceStamp>,
434}
435
436#[derive(Debug, PartialEq, Eq)]
437struct PacketQueueDequeueCounts {
438    total: usize,
439    priority: usize,
440    bulk: usize,
441}
442
443impl PacketQueueTx {
444    fn try_send(self, owner: &PacketTx, item: PacketQueueItem) -> Result<(), PacketSendFailure> {
445        match self {
446            PacketQueueTx::Priority => owner
447                .priority
448                .send(item)
449                .map_err(|error| PacketSendFailure::Closed(error.0)),
450            PacketQueueTx::Bulk => {
451                let packet_count = item.packet_count();
452                match owner.bulk.try_send(item) {
453                    Ok(()) => Ok(()),
454                    Err(TrySendError::Full(_item)) => {
455                        Err(PacketSendFailure::DroppedBulk(packet_count))
456                    }
457                    Err(TrySendError::Closed(item)) => Err(PacketSendFailure::Closed(item)),
458                }
459            }
460        }
461    }
462}
463
464impl PacketQueueItem {
465    fn packet_count(&self) -> usize {
466        match self {
467            PacketQueueItem::One(_) => 1,
468            PacketQueueItem::Batch(packets) => packets.packets.len(),
469        }
470    }
471
472    fn dequeue_counts(&self, lane: PacketLane) -> PacketQueueDequeueCounts {
473        let total = self.packet_count();
474        match lane {
475            PacketLane::Priority => PacketQueueDequeueCounts {
476                total,
477                priority: total,
478                bulk: 0,
479            },
480            PacketLane::Bulk => PacketQueueDequeueCounts {
481                total,
482                priority: 0,
483                bulk: total,
484            },
485        }
486    }
487
488    fn queued_at(&self) -> Option<crate::perf_profile::TraceStamp> {
489        match self {
490            PacketQueueItem::One(packet) => packet.trace_enqueued_at,
491            PacketQueueItem::Batch(packets) => packets
492                .packets
493                .first()
494                .and_then(|packet| packet.trace_enqueued_at),
495        }
496    }
497
498    fn record_dequeue_wait(&self, lane: PacketLane) {
499        let queued_at = self.queued_at();
500        if queued_at.is_none() {
501            return;
502        }
503        let counts = self.dequeue_counts(lane);
504        crate::perf_profile::record_since_split_count(
505            crate::perf_profile::Stage::TransportChannelWait,
506            crate::perf_profile::Stage::TransportPriorityChannelWait,
507            crate::perf_profile::Stage::TransportBulkChannelWait,
508            queued_at,
509            counts.total as u64,
510            counts.priority as u64,
511            counts.bulk as u64,
512        );
513    }
514}
515
516impl PacketBatchPool {
517    fn new() -> Self {
518        Self {
519            inner: Arc::new(Mutex::new(Vec::new())),
520        }
521    }
522
523    fn take(&self, capacity: usize) -> PacketBatch {
524        let packets = {
525            let mut guard = self.inner.lock().unwrap_or_else(|error| error.into_inner());
526            guard.pop()
527        };
528        if let Some(mut packets) = packets {
529            crate::perf_profile::record_event(crate::perf_profile::Event::PacketBatchPoolReuse);
530            packets.clear();
531            if packets.capacity() >= capacity {
532                return PacketBatch::pooled(packets, self.clone());
533            }
534            packets.reserve(capacity.saturating_sub(packets.capacity()));
535            return PacketBatch::pooled(packets, self.clone());
536        }
537        crate::perf_profile::record_event(crate::perf_profile::Event::PacketBatchPoolFresh);
538        PacketBatch::pooled(Vec::with_capacity(capacity), self.clone())
539    }
540
541    fn put(&self, mut packets: Vec<ReceivedPacket>) {
542        packets.clear();
543        if packets.capacity() > PACKET_BATCH_MAX_RETAINED_CAPACITY {
544            crate::perf_profile::record_event(crate::perf_profile::Event::PacketBatchPoolDiscard);
545            return;
546        }
547        let mut guard = self.inner.lock().unwrap_or_else(|error| error.into_inner());
548        if guard.len() < PACKET_BATCH_POOL_LIMIT {
549            guard.push(packets);
550            crate::perf_profile::record_event(crate::perf_profile::Event::PacketBatchPoolReturn);
551        } else {
552            crate::perf_profile::record_event(crate::perf_profile::Event::PacketBatchPoolDiscard);
553        }
554    }
555}
556
557impl PacketBufferPool {
558    #[cfg(any(test, target_os = "linux", target_os = "macos"))]
559    fn new() -> Self {
560        Self {
561            inner: Arc::new(Mutex::new(Vec::new())),
562            available: Arc::new(AtomicUsize::new(0)),
563        }
564    }
565
566    #[cfg(any(test, target_os = "linux", target_os = "macos"))]
567    fn take(&self, capacity: usize) -> Vec<u8> {
568        if self.available.load(Relaxed) > 0 {
569            let buffer = {
570                let mut guard = self.inner.lock().unwrap_or_else(|error| error.into_inner());
571                guard.pop()
572            };
573            if let Some(mut buffer) = buffer {
574                self.available.fetch_sub(1, Relaxed);
575                crate::perf_profile::record_event(
576                    crate::perf_profile::Event::PacketBufferPoolReuse,
577                );
578                prepare_recv_buffer(&mut buffer, capacity);
579                return buffer;
580            }
581        }
582
583        crate::perf_profile::record_event(crate::perf_profile::Event::PacketBufferPoolFresh);
584        fresh_recv_buffer(capacity)
585    }
586
587    fn put(&self, mut buffer: Vec<u8>) {
588        buffer.clear();
589        if buffer.capacity() > PACKET_BUFFER_MAX_RETAINED_CAPACITY {
590            crate::perf_profile::record_event(crate::perf_profile::Event::PacketBufferPoolDiscard);
591            return;
592        }
593
594        let mut guard = self.inner.lock().unwrap_or_else(|error| error.into_inner());
595        if guard.len() < PACKET_BUFFER_POOL_LIMIT {
596            guard.push(buffer);
597            self.available.fetch_add(1, Relaxed);
598            crate::perf_profile::record_event(crate::perf_profile::Event::PacketBufferPoolReturn);
599        } else {
600            crate::perf_profile::record_event(crate::perf_profile::Event::PacketBufferPoolDiscard);
601        }
602    }
603}
604
605#[cfg(target_os = "macos")]
606fn fresh_recv_buffer(size: usize) -> Vec<u8> {
607    vec![0u8; size]
608}
609
610#[cfg(all(any(test, target_os = "linux"), not(target_os = "macos")))]
611fn fresh_recv_buffer(size: usize) -> Vec<u8> {
612    Vec::with_capacity(size)
613}
614
615#[cfg(target_os = "macos")]
616fn prepare_recv_buffer(buffer: &mut Vec<u8>, size: usize) {
617    buffer.resize(size, 0);
618}
619
620#[cfg(all(any(test, target_os = "linux"), not(target_os = "macos")))]
621fn prepare_recv_buffer(buffer: &mut Vec<u8>, size: usize) {
622    buffer.clear();
623    if buffer.capacity() < size {
624        buffer.reserve(size.saturating_sub(buffer.capacity()));
625    }
626}
627
628impl PacketBatch {
629    fn pooled(packets: Vec<ReceivedPacket>, pool: PacketBatchPool) -> Self {
630        Self {
631            packets,
632            pool: Some(pool),
633        }
634    }
635
636    pub(crate) fn push(&mut self, packet: ReceivedPacket) {
637        self.packets.push(packet);
638    }
639
640    pub(crate) fn is_empty(&self) -> bool {
641        self.packets.is_empty()
642    }
643
644    pub(crate) fn as_slice(&self) -> &[ReceivedPacket] {
645        &self.packets
646    }
647}
648
649impl Drop for PacketBatch {
650    fn drop(&mut self) {
651        let Some(pool) = self.pool.take() else {
652            return;
653        };
654        pool.put(mem::take(&mut self.packets));
655    }
656}
657
658impl PendingPackets {
659    fn new(
660        mut batch: PacketBatch,
661        rx_loop_owned_at: Option<crate::perf_profile::TraceStamp>,
662    ) -> Self {
663        batch.packets.reverse();
664        Self {
665            batch,
666            rx_loop_owned_at,
667        }
668    }
669
670    fn next(&mut self) -> Option<ReceivedPacket> {
671        let mut packet = self.batch.packets.pop()?;
672        if let Some(rx_loop_owned_at) = self.rx_loop_owned_at {
673            packet.trace_rx_loop_owned_at = Some(rx_loop_owned_at);
674        }
675        Some(packet)
676    }
677}
678
679impl PacketTx {
680    pub(crate) fn set_fast_ingress_sink(&mut self, sink: Arc<dyn PacketFastIngressSink>) {
681        self.fast_ingress = Some(sink);
682    }
683
684    pub(crate) fn try_fast_ingress_packet_batch(&self, batch: &mut PacketBatch) -> usize {
685        let Some(sink) = &self.fast_ingress else {
686            return 0;
687        };
688        sink.try_ingest_batch(&mut batch.packets)
689    }
690
691    pub(crate) fn packet_batch(&self, capacity: usize) -> PacketBatch {
692        self.batch_pool.take(capacity)
693    }
694
695    #[cfg(any(test, target_os = "linux", target_os = "macos"))]
696    pub(crate) fn recv_buffer(&self, capacity: usize) -> Vec<u8> {
697        self.buffer_pool.take(capacity)
698    }
699
700    #[cfg(any(test, target_os = "linux", target_os = "macos"))]
701    pub(crate) fn packet_buffer(&self, data: Vec<u8>) -> PacketBuffer {
702        PacketBuffer::pooled(data, self.buffer_pool.clone())
703    }
704
705    pub fn send(
706        &self,
707        packet: ReceivedPacket,
708    ) -> Result<(), tokio::sync::mpsc::error::SendError<ReceivedPacket>> {
709        let tx = if packet.is_transport_priority() {
710            PacketQueueTx::Priority
711        } else {
712            PacketQueueTx::Bulk
713        };
714        self.send_item(tx, PacketQueueItem::One(packet))
715            .map_err(|item| match item {
716                PacketQueueItem::One(packet) => tokio::sync::mpsc::error::SendError(packet),
717                PacketQueueItem::Batch(_) => {
718                    unreachable!("single packet send cannot fail with a batch item")
719                }
720            })
721    }
722
723    pub(crate) fn send_packet_batch(&self, mut batch: PacketBatch) -> Result<(), ()> {
724        if batch.is_empty() {
725            return Ok(());
726        }
727
728        let packet_count = batch.packets.len();
729        let priority_count = batch
730            .packets
731            .iter()
732            .filter(|packet| packet.is_transport_priority())
733            .count();
734        if priority_count == 0 || priority_count == packet_count {
735            let tx = if priority_count == 0 {
736                PacketQueueTx::Bulk
737            } else {
738                PacketQueueTx::Priority
739            };
740            return self.send_packet_items(tx, batch);
741        }
742
743        let mut priority_packets = self.packet_batch(priority_count);
744        let mut bulk_packets = self.packet_batch(packet_count - priority_count);
745        for packet in batch.packets.drain(..) {
746            if packet.is_transport_priority() {
747                priority_packets.push(packet);
748            } else {
749                bulk_packets.push(packet);
750            }
751        }
752
753        self.send_packet_items(PacketQueueTx::Priority, priority_packets)?;
754        self.send_packet_items(PacketQueueTx::Bulk, bulk_packets)?;
755        Ok(())
756    }
757
758    fn send_packet_items(&self, tx: PacketQueueTx, packets: PacketBatch) -> Result<(), ()> {
759        if matches!(tx, PacketQueueTx::Bulk) {
760            return self.send_bulk_packet_items(packets);
761        }
762
763        let item = match packets.packets.len() {
764            0 => return Ok(()),
765            _ => PacketQueueItem::Batch(packets),
766        };
767        self.send_item(tx, item).map_err(|_| ())
768    }
769
770    fn send_bulk_packet_items(&self, mut packets: PacketBatch) -> Result<(), ()> {
771        let packet_count = packets.packets.len();
772        if packet_count == 0 {
773            return Ok(());
774        }
775
776        let granted = self.try_reserve_bulk_packet_prefix(packet_count);
777        if granted == 0 {
778            crate::perf_profile::record_event_count(
779                crate::perf_profile::Event::TransportBulkDropped,
780                packet_count as u64,
781            );
782            return Ok(());
783        }
784
785        if granted < packet_count {
786            let dropped = packet_count - granted;
787            let _dropped_tail = packets.packets.split_off(granted);
788            crate::perf_profile::record_event_count(
789                crate::perf_profile::Event::TransportBulkDropped,
790                dropped as u64,
791            );
792        }
793
794        let item = match packets.packets.len() {
795            0 => return Ok(()),
796            _ => PacketQueueItem::Batch(packets),
797        };
798        self.send_reserved_item(PacketQueueTx::Bulk, item, Some(granted))
799            .map_err(|_| ())
800    }
801
802    fn send_item(&self, tx: PacketQueueTx, item: PacketQueueItem) -> Result<(), PacketQueueItem> {
803        let packet_count = item.packet_count();
804        let bulk_reserved = if matches!(tx, PacketQueueTx::Bulk) && packet_count > 0 {
805            if !self.try_reserve_bulk_packets(packet_count) {
806                crate::perf_profile::record_event_count(
807                    crate::perf_profile::Event::TransportBulkDropped,
808                    packet_count as u64,
809                );
810                return Ok(());
811            }
812            Some(packet_count)
813        } else {
814            None
815        };
816        self.send_reserved_item(tx, item, bulk_reserved)
817    }
818
819    fn send_reserved_item(
820        &self,
821        tx: PacketQueueTx,
822        item: PacketQueueItem,
823        bulk_reserved: Option<usize>,
824    ) -> Result<(), PacketQueueItem> {
825        let packet_count = item.packet_count();
826        debug_assert_eq!(
827            bulk_reserved,
828            matches!(tx, PacketQueueTx::Bulk)
829                .then_some(packet_count)
830                .filter(|count| *count > 0)
831        );
832        let priority_reserved = matches!(tx, PacketQueueTx::Priority)
833            .then_some(packet_count)
834            .filter(|count| *count > 0);
835        if let Some(count) = priority_reserved {
836            self.priority_queued_packets.fetch_add(count, Relaxed);
837        }
838
839        let tracked_count = if self.track_backlog {
840            Some(packet_count)
841        } else {
842            None
843        };
844        let previous = tracked_count.map(|count| self.queued_packets.fetch_add(count, Relaxed));
845        match tx.try_send(self, item) {
846            Ok(()) => {
847                if let (Some(count), Some(previous)) = (tracked_count, previous) {
848                    let queued = previous.saturating_add(count);
849                    if previous < TRANSPORT_CHANNEL_BACKLOG_HIGH_WATER
850                        && queued >= TRANSPORT_CHANNEL_BACKLOG_HIGH_WATER
851                    {
852                        crate::perf_profile::record_event(
853                            crate::perf_profile::Event::TransportChannelBacklogHigh,
854                        );
855                    }
856                }
857                Ok(())
858            }
859            Err(PacketSendFailure::Closed(item)) => {
860                if let Some(count) = tracked_count {
861                    self.queued_packets.fetch_sub(count, Relaxed);
862                }
863                if let Some(count) = priority_reserved {
864                    release_priority_packets(&self.priority_queued_packets, count);
865                }
866                if let Some(count) = bulk_reserved {
867                    self.release_bulk_packets(count);
868                }
869                Err(item)
870            }
871            Err(PacketSendFailure::DroppedBulk(dropped_count)) => {
872                if let Some(count) = tracked_count {
873                    self.queued_packets.fetch_sub(count, Relaxed);
874                }
875                if let Some(count) = priority_reserved {
876                    release_priority_packets(&self.priority_queued_packets, count);
877                }
878                if let Some(count) = bulk_reserved {
879                    self.release_bulk_packets(count);
880                }
881                crate::perf_profile::record_event_count(
882                    crate::perf_profile::Event::TransportBulkDropped,
883                    dropped_count as u64,
884                );
885                Ok(())
886            }
887        }
888    }
889
890    fn try_reserve_bulk_packets(&self, count: usize) -> bool {
891        self.bulk_queued_packets
892            .fetch_update(Relaxed, Relaxed, |current| {
893                current
894                    .checked_add(count)
895                    .filter(|next| *next <= self.bulk_packet_capacity)
896            })
897            .is_ok()
898    }
899
900    fn try_reserve_bulk_packet_prefix(&self, requested: usize) -> usize {
901        if requested == 0 {
902            return 0;
903        }
904
905        let mut current = self.bulk_queued_packets.load(Relaxed);
906        loop {
907            let available = self.bulk_packet_capacity.saturating_sub(current);
908            let granted = requested.min(available);
909            if granted == 0 {
910                return 0;
911            }
912            match self.bulk_queued_packets.compare_exchange_weak(
913                current,
914                current + granted,
915                Relaxed,
916                Relaxed,
917            ) {
918                Ok(_) => return granted,
919                Err(actual) => current = actual,
920            }
921        }
922    }
923
924    fn release_bulk_packets(&self, count: usize) {
925        release_reserved_bulk_packets(&self.bulk_queued_packets, count);
926    }
927}
928
929impl PacketRx {
930    pub(crate) fn priority_queued_packets(&self) -> usize {
931        self.priority_queued_packets.load(Relaxed)
932    }
933
934    pub(crate) fn priority_ready_packets(&self) -> usize {
935        self.pending_priority
936            .as_ref()
937            .map_or(0, |packets| packets.batch.packets.len())
938            .saturating_add(self.priority_queued_packets())
939    }
940
941    pub async fn recv(&mut self) -> Option<ReceivedPacket> {
942        loop {
943            match self.try_recv() {
944                Ok(packet) => return Some(packet),
945                Err(TryRecvError::Disconnected) => return None,
946                Err(TryRecvError::Empty) => {}
947            }
948
949            tokio::select! {
950                biased;
951                item = self.priority.recv(), if !self.priority_closed => {
952                    match item {
953                        Some(item) => {
954                            if let Some(packet) = self.packet_from_item(item, PacketLane::Priority) {
955                                return Some(packet);
956                            }
957                        }
958                        None => self.priority_closed = true,
959                    }
960                }
961                item = self.bulk.recv(), if !self.bulk_closed => {
962                    match item {
963                        Some(item) => {
964                            if let Some(packet) = self.packet_from_item(item, PacketLane::Bulk) {
965                                return Some(packet);
966                            }
967                        }
968                        None => self.bulk_closed = true,
969                    }
970                }
971            }
972        }
973    }
974
975    pub fn try_recv(&mut self) -> Result<ReceivedPacket, TryRecvError> {
976        if let Some(packet) = Self::take_pending(&mut self.pending_priority) {
977            return Ok(packet);
978        }
979
980        if self.should_probe_priority() {
981            match self.priority.try_recv() {
982                Ok(item) => {
983                    if let Some(packet) = self.packet_from_item(item, PacketLane::Priority) {
984                        return Ok(packet);
985                    }
986                }
987                Err(TryRecvError::Empty) => {}
988                Err(TryRecvError::Disconnected) => {
989                    self.priority_closed = true;
990                }
991            }
992        }
993
994        if let Some(packet) = Self::take_pending(&mut self.pending_bulk) {
995            return Ok(packet);
996        }
997
998        match self.bulk.try_recv() {
999            Ok(item) => self
1000                .packet_from_item(item, PacketLane::Bulk)
1001                .ok_or(TryRecvError::Empty),
1002            Err(TryRecvError::Empty) => {
1003                if self.priority_closed && self.bulk_closed {
1004                    Err(TryRecvError::Disconnected)
1005                } else {
1006                    Err(TryRecvError::Empty)
1007                }
1008            }
1009            Err(TryRecvError::Disconnected) => {
1010                self.bulk_closed = true;
1011                if self.priority_closed {
1012                    Err(TryRecvError::Disconnected)
1013                } else {
1014                    Err(TryRecvError::Empty)
1015                }
1016            }
1017        }
1018    }
1019
1020    pub(crate) fn drain_ready<F>(&mut self, limit: usize, mut consume: F) -> usize
1021    where
1022        F: FnMut(ReceivedPacket) -> bool,
1023    {
1024        let mut drained = 0usize;
1025        while drained < limit {
1026            if !self.drain_pending_priority(limit, &mut drained, &mut consume) {
1027                break;
1028            }
1029            if drained >= limit {
1030                break;
1031            }
1032
1033            if self.should_probe_priority() {
1034                match self.priority.try_recv() {
1035                    Ok(item) => {
1036                        if !self.drain_item(
1037                            item,
1038                            PacketLane::Priority,
1039                            limit,
1040                            &mut drained,
1041                            &mut consume,
1042                        ) {
1043                            break;
1044                        }
1045                        continue;
1046                    }
1047                    Err(TryRecvError::Empty) => {}
1048                    Err(TryRecvError::Disconnected) => {
1049                        self.priority_closed = true;
1050                    }
1051                }
1052            }
1053            if drained >= limit {
1054                break;
1055            }
1056
1057            if !self.drain_pending_bulk(limit, &mut drained, &mut consume) {
1058                break;
1059            }
1060            if drained >= limit {
1061                break;
1062            }
1063
1064            match self.bulk.try_recv() {
1065                Ok(item) => {
1066                    if !self.drain_item(item, PacketLane::Bulk, limit, &mut drained, &mut consume) {
1067                        break;
1068                    }
1069                }
1070                Err(TryRecvError::Empty) => break,
1071                Err(TryRecvError::Disconnected) => {
1072                    self.bulk_closed = true;
1073                    break;
1074                }
1075            }
1076        }
1077        drained
1078    }
1079
1080    fn packet_from_item(
1081        &mut self,
1082        item: PacketQueueItem,
1083        lane: PacketLane,
1084    ) -> Option<ReceivedPacket> {
1085        item.record_dequeue_wait(lane);
1086        let packet_count = item.packet_count();
1087        if self.track_backlog {
1088            self.queued_packets.fetch_sub(packet_count, Relaxed);
1089        }
1090        if matches!(lane, PacketLane::Priority) {
1091            release_priority_packets(&self.priority_queued_packets, packet_count);
1092        }
1093        if matches!(lane, PacketLane::Bulk) {
1094            release_reserved_bulk_packets(&self.bulk_queued_packets, packet_count);
1095        }
1096        let rx_loop_owned_at = crate::perf_profile::stamp();
1097        match item {
1098            PacketQueueItem::One(mut packet) => {
1099                packet.trace_rx_loop_owned_at = rx_loop_owned_at;
1100                Some(packet)
1101            }
1102            PacketQueueItem::Batch(packets) => {
1103                let mut pending = PendingPackets::new(packets, rx_loop_owned_at);
1104                let packet = pending.next()?;
1105                if !pending.batch.packets.is_empty() {
1106                    match lane {
1107                        PacketLane::Priority => self.pending_priority = Some(pending),
1108                        PacketLane::Bulk => self.pending_bulk = Some(pending),
1109                    }
1110                }
1111                Some(packet)
1112            }
1113        }
1114    }
1115
1116    fn drain_item<F>(
1117        &mut self,
1118        item: PacketQueueItem,
1119        lane: PacketLane,
1120        limit: usize,
1121        drained: &mut usize,
1122        consume: &mut F,
1123    ) -> bool
1124    where
1125        F: FnMut(ReceivedPacket) -> bool,
1126    {
1127        if let Some(packet) = self.packet_from_item(item, lane) {
1128            *drained += 1;
1129            if !consume(packet) {
1130                return false;
1131            }
1132        }
1133
1134        match lane {
1135            PacketLane::Priority => self.drain_pending_priority(limit, drained, consume),
1136            PacketLane::Bulk => self.drain_pending_bulk(limit, drained, consume),
1137        }
1138    }
1139
1140    fn drain_pending_priority<F>(
1141        &mut self,
1142        limit: usize,
1143        drained: &mut usize,
1144        consume: &mut F,
1145    ) -> bool
1146    where
1147        F: FnMut(ReceivedPacket) -> bool,
1148    {
1149        while *drained < limit {
1150            let Some(packet) = Self::take_pending(&mut self.pending_priority) else {
1151                return true;
1152            };
1153            *drained += 1;
1154            if !consume(packet) {
1155                return false;
1156            }
1157        }
1158        true
1159    }
1160
1161    fn drain_pending_bulk<F>(&mut self, limit: usize, drained: &mut usize, consume: &mut F) -> bool
1162    where
1163        F: FnMut(ReceivedPacket) -> bool,
1164    {
1165        while *drained < limit {
1166            if self.should_probe_priority() {
1167                return true;
1168            }
1169            let Some(packet) = Self::take_pending(&mut self.pending_bulk) else {
1170                return true;
1171            };
1172            *drained += 1;
1173            if !consume(packet) {
1174                return false;
1175            }
1176        }
1177        true
1178    }
1179
1180    fn should_probe_priority(&self) -> bool {
1181        !self.priority_closed
1182            && (self.priority_queued_packets.load(Relaxed) > 0 || self.bulk_closed)
1183    }
1184
1185    fn take_pending(pending: &mut Option<PendingPackets>) -> Option<ReceivedPacket> {
1186        let packets = pending.as_mut()?;
1187        let packet = packets.next();
1188        if packets.batch.packets.is_empty() {
1189            *pending = None;
1190        }
1191        packet
1192    }
1193}
1194
1195#[inline]
1196fn packet_channel_tracks_backlog() -> bool {
1197    cfg!(test) || crate::perf_profile::enabled()
1198}
1199
1200fn release_reserved_bulk_packets(counter: &AtomicUsize, count: usize) {
1201    if count == 0 {
1202        return;
1203    }
1204
1205    let previous = counter.fetch_sub(count, Relaxed);
1206    debug_assert!(
1207        previous >= count,
1208        "transport bulk queued packet accounting underflow"
1209    );
1210}
1211
1212fn release_priority_packets(counter: &AtomicUsize, count: usize) {
1213    if count == 0 {
1214        return;
1215    }
1216
1217    let previous = counter.fetch_sub(count, Relaxed);
1218    debug_assert!(
1219        previous >= count,
1220        "transport priority queued packet accounting underflow"
1221    );
1222}
1223
1224/// Create a packet channel.
1225///
1226/// The capacity applies to bulk packets. Priority traffic is intentionally
1227/// unbounded so small control-shaped packets can still wake the rx loop while a
1228/// bulk receiver is saturated.
1229pub fn packet_channel(buffer: usize) -> (PacketTx, PacketRx) {
1230    let (priority_tx, priority_rx) = tokio::sync::mpsc::unbounded_channel();
1231    let (bulk_tx, bulk_rx) = tokio::sync::mpsc::channel(buffer.max(1));
1232    let priority_queued_packets = Arc::new(AtomicUsize::new(0));
1233    let queued_packets = Arc::new(AtomicUsize::new(0));
1234    let bulk_queued_packets = Arc::new(AtomicUsize::new(0));
1235    let track_backlog = packet_channel_tracks_backlog();
1236    (
1237        PacketTx {
1238            priority: priority_tx,
1239            bulk: bulk_tx,
1240            fast_ingress: None,
1241            batch_pool: PacketBatchPool::new(),
1242            #[cfg(any(test, target_os = "linux", target_os = "macos"))]
1243            buffer_pool: PacketBufferPool::new(),
1244            priority_queued_packets: Arc::clone(&priority_queued_packets),
1245            queued_packets: Arc::clone(&queued_packets),
1246            bulk_queued_packets: Arc::clone(&bulk_queued_packets),
1247            bulk_packet_capacity: buffer.max(1),
1248            track_backlog,
1249        },
1250        PacketRx {
1251            priority: priority_rx,
1252            bulk: bulk_rx,
1253            priority_queued_packets,
1254            queued_packets,
1255            bulk_queued_packets,
1256            track_backlog,
1257            pending_priority: None,
1258            pending_bulk: None,
1259            priority_closed: false,
1260            bulk_closed: false,
1261        },
1262    )
1263}
1264
1265#[cfg(test)]
1266mod tests;