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
645impl Drop for PacketBatch {
646    fn drop(&mut self) {
647        let Some(pool) = self.pool.take() else {
648            return;
649        };
650        pool.put(mem::take(&mut self.packets));
651    }
652}
653
654impl PendingPackets {
655    fn new(
656        mut batch: PacketBatch,
657        rx_loop_owned_at: Option<crate::perf_profile::TraceStamp>,
658    ) -> Self {
659        batch.packets.reverse();
660        Self {
661            batch,
662            rx_loop_owned_at,
663        }
664    }
665
666    fn next(&mut self) -> Option<ReceivedPacket> {
667        let mut packet = self.batch.packets.pop()?;
668        if let Some(rx_loop_owned_at) = self.rx_loop_owned_at {
669            packet.trace_rx_loop_owned_at = Some(rx_loop_owned_at);
670        }
671        Some(packet)
672    }
673}
674
675impl PacketTx {
676    pub(crate) fn set_fast_ingress_sink(&mut self, sink: Arc<dyn PacketFastIngressSink>) {
677        self.fast_ingress = Some(sink);
678    }
679
680    pub(crate) fn try_fast_ingress_packet_batch(&self, batch: &mut PacketBatch) -> usize {
681        let Some(sink) = &self.fast_ingress else {
682            return 0;
683        };
684        sink.try_ingest_batch(&mut batch.packets)
685    }
686
687    pub(crate) fn packet_batch(&self, capacity: usize) -> PacketBatch {
688        self.batch_pool.take(capacity)
689    }
690
691    #[cfg(any(test, target_os = "linux", target_os = "macos"))]
692    pub(crate) fn recv_buffer(&self, capacity: usize) -> Vec<u8> {
693        self.buffer_pool.take(capacity)
694    }
695
696    #[cfg(any(test, target_os = "linux", target_os = "macos"))]
697    pub(crate) fn packet_buffer(&self, data: Vec<u8>) -> PacketBuffer {
698        PacketBuffer::pooled(data, self.buffer_pool.clone())
699    }
700
701    pub fn send(
702        &self,
703        packet: ReceivedPacket,
704    ) -> Result<(), tokio::sync::mpsc::error::SendError<ReceivedPacket>> {
705        let tx = if packet.is_transport_priority() {
706            PacketQueueTx::Priority
707        } else {
708            PacketQueueTx::Bulk
709        };
710        self.send_item(tx, PacketQueueItem::One(packet))
711            .map_err(|item| match item {
712                PacketQueueItem::One(packet) => tokio::sync::mpsc::error::SendError(packet),
713                PacketQueueItem::Batch(_) => {
714                    unreachable!("single packet send cannot fail with a batch item")
715                }
716            })
717    }
718
719    pub(crate) fn send_packet_batch(&self, mut batch: PacketBatch) -> Result<(), ()> {
720        if batch.is_empty() {
721            return Ok(());
722        }
723
724        let packet_count = batch.packets.len();
725        let priority_count = batch
726            .packets
727            .iter()
728            .filter(|packet| packet.is_transport_priority())
729            .count();
730        if priority_count == 0 || priority_count == packet_count {
731            let tx = if priority_count == 0 {
732                PacketQueueTx::Bulk
733            } else {
734                PacketQueueTx::Priority
735            };
736            return self.send_packet_items(tx, batch);
737        }
738
739        let mut priority_packets = self.packet_batch(priority_count);
740        let mut bulk_packets = self.packet_batch(packet_count - priority_count);
741        for packet in batch.packets.drain(..) {
742            if packet.is_transport_priority() {
743                priority_packets.push(packet);
744            } else {
745                bulk_packets.push(packet);
746            }
747        }
748
749        self.send_packet_items(PacketQueueTx::Priority, priority_packets)?;
750        self.send_packet_items(PacketQueueTx::Bulk, bulk_packets)?;
751        Ok(())
752    }
753
754    fn send_packet_items(&self, tx: PacketQueueTx, packets: PacketBatch) -> Result<(), ()> {
755        if matches!(tx, PacketQueueTx::Bulk) {
756            return self.send_bulk_packet_items(packets);
757        }
758
759        let item = match packets.packets.len() {
760            0 => return Ok(()),
761            _ => PacketQueueItem::Batch(packets),
762        };
763        self.send_item(tx, item).map_err(|_| ())
764    }
765
766    fn send_bulk_packet_items(&self, mut packets: PacketBatch) -> Result<(), ()> {
767        let packet_count = packets.packets.len();
768        if packet_count == 0 {
769            return Ok(());
770        }
771
772        let granted = self.try_reserve_bulk_packet_prefix(packet_count);
773        if granted == 0 {
774            crate::perf_profile::record_event_count(
775                crate::perf_profile::Event::TransportBulkDropped,
776                packet_count as u64,
777            );
778            return Ok(());
779        }
780
781        if granted < packet_count {
782            let dropped = packet_count - granted;
783            let _dropped_tail = packets.packets.split_off(granted);
784            crate::perf_profile::record_event_count(
785                crate::perf_profile::Event::TransportBulkDropped,
786                dropped as u64,
787            );
788        }
789
790        let item = match packets.packets.len() {
791            0 => return Ok(()),
792            _ => PacketQueueItem::Batch(packets),
793        };
794        self.send_reserved_item(PacketQueueTx::Bulk, item, Some(granted))
795            .map_err(|_| ())
796    }
797
798    fn send_item(&self, tx: PacketQueueTx, item: PacketQueueItem) -> Result<(), PacketQueueItem> {
799        let packet_count = item.packet_count();
800        let bulk_reserved = if matches!(tx, PacketQueueTx::Bulk) && packet_count > 0 {
801            if !self.try_reserve_bulk_packets(packet_count) {
802                crate::perf_profile::record_event_count(
803                    crate::perf_profile::Event::TransportBulkDropped,
804                    packet_count as u64,
805                );
806                return Ok(());
807            }
808            Some(packet_count)
809        } else {
810            None
811        };
812        self.send_reserved_item(tx, item, bulk_reserved)
813    }
814
815    fn send_reserved_item(
816        &self,
817        tx: PacketQueueTx,
818        item: PacketQueueItem,
819        bulk_reserved: Option<usize>,
820    ) -> Result<(), PacketQueueItem> {
821        let packet_count = item.packet_count();
822        debug_assert_eq!(
823            bulk_reserved,
824            matches!(tx, PacketQueueTx::Bulk)
825                .then_some(packet_count)
826                .filter(|count| *count > 0)
827        );
828        let priority_reserved = matches!(tx, PacketQueueTx::Priority)
829            .then_some(packet_count)
830            .filter(|count| *count > 0);
831        if let Some(count) = priority_reserved {
832            self.priority_queued_packets.fetch_add(count, Relaxed);
833        }
834
835        let tracked_count = if self.track_backlog {
836            Some(packet_count)
837        } else {
838            None
839        };
840        let previous = tracked_count.map(|count| self.queued_packets.fetch_add(count, Relaxed));
841        match tx.try_send(self, item) {
842            Ok(()) => {
843                if let (Some(count), Some(previous)) = (tracked_count, previous) {
844                    let queued = previous.saturating_add(count);
845                    if previous < TRANSPORT_CHANNEL_BACKLOG_HIGH_WATER
846                        && queued >= TRANSPORT_CHANNEL_BACKLOG_HIGH_WATER
847                    {
848                        crate::perf_profile::record_event(
849                            crate::perf_profile::Event::TransportChannelBacklogHigh,
850                        );
851                    }
852                }
853                Ok(())
854            }
855            Err(PacketSendFailure::Closed(item)) => {
856                if let Some(count) = tracked_count {
857                    self.queued_packets.fetch_sub(count, Relaxed);
858                }
859                if let Some(count) = priority_reserved {
860                    release_priority_packets(&self.priority_queued_packets, count);
861                }
862                if let Some(count) = bulk_reserved {
863                    self.release_bulk_packets(count);
864                }
865                Err(item)
866            }
867            Err(PacketSendFailure::DroppedBulk(dropped_count)) => {
868                if let Some(count) = tracked_count {
869                    self.queued_packets.fetch_sub(count, Relaxed);
870                }
871                if let Some(count) = priority_reserved {
872                    release_priority_packets(&self.priority_queued_packets, count);
873                }
874                if let Some(count) = bulk_reserved {
875                    self.release_bulk_packets(count);
876                }
877                crate::perf_profile::record_event_count(
878                    crate::perf_profile::Event::TransportBulkDropped,
879                    dropped_count as u64,
880                );
881                Ok(())
882            }
883        }
884    }
885
886    fn try_reserve_bulk_packets(&self, count: usize) -> bool {
887        self.bulk_queued_packets
888            .fetch_update(Relaxed, Relaxed, |current| {
889                current
890                    .checked_add(count)
891                    .filter(|next| *next <= self.bulk_packet_capacity)
892            })
893            .is_ok()
894    }
895
896    fn try_reserve_bulk_packet_prefix(&self, requested: usize) -> usize {
897        if requested == 0 {
898            return 0;
899        }
900
901        let mut current = self.bulk_queued_packets.load(Relaxed);
902        loop {
903            let available = self.bulk_packet_capacity.saturating_sub(current);
904            let granted = requested.min(available);
905            if granted == 0 {
906                return 0;
907            }
908            match self.bulk_queued_packets.compare_exchange_weak(
909                current,
910                current + granted,
911                Relaxed,
912                Relaxed,
913            ) {
914                Ok(_) => return granted,
915                Err(actual) => current = actual,
916            }
917        }
918    }
919
920    fn release_bulk_packets(&self, count: usize) {
921        release_reserved_bulk_packets(&self.bulk_queued_packets, count);
922    }
923}
924
925impl PacketRx {
926    pub(crate) fn priority_queued_packets(&self) -> usize {
927        self.priority_queued_packets.load(Relaxed)
928    }
929
930    pub(crate) fn priority_ready_packets(&self) -> usize {
931        self.pending_priority
932            .as_ref()
933            .map_or(0, |packets| packets.batch.packets.len())
934            .saturating_add(self.priority_queued_packets())
935    }
936
937    pub async fn recv(&mut self) -> Option<ReceivedPacket> {
938        loop {
939            match self.try_recv() {
940                Ok(packet) => return Some(packet),
941                Err(TryRecvError::Disconnected) => return None,
942                Err(TryRecvError::Empty) => {}
943            }
944
945            tokio::select! {
946                biased;
947                item = self.priority.recv(), if !self.priority_closed => {
948                    match item {
949                        Some(item) => {
950                            if let Some(packet) = self.packet_from_item(item, PacketLane::Priority) {
951                                return Some(packet);
952                            }
953                        }
954                        None => self.priority_closed = true,
955                    }
956                }
957                item = self.bulk.recv(), if !self.bulk_closed => {
958                    match item {
959                        Some(item) => {
960                            if let Some(packet) = self.packet_from_item(item, PacketLane::Bulk) {
961                                return Some(packet);
962                            }
963                        }
964                        None => self.bulk_closed = true,
965                    }
966                }
967            }
968        }
969    }
970
971    pub fn try_recv(&mut self) -> Result<ReceivedPacket, TryRecvError> {
972        if let Some(packet) = Self::take_pending(&mut self.pending_priority) {
973            return Ok(packet);
974        }
975
976        if self.should_probe_priority() {
977            match self.priority.try_recv() {
978                Ok(item) => {
979                    if let Some(packet) = self.packet_from_item(item, PacketLane::Priority) {
980                        return Ok(packet);
981                    }
982                }
983                Err(TryRecvError::Empty) => {}
984                Err(TryRecvError::Disconnected) => {
985                    self.priority_closed = true;
986                }
987            }
988        }
989
990        if let Some(packet) = Self::take_pending(&mut self.pending_bulk) {
991            return Ok(packet);
992        }
993
994        match self.bulk.try_recv() {
995            Ok(item) => self
996                .packet_from_item(item, PacketLane::Bulk)
997                .ok_or(TryRecvError::Empty),
998            Err(TryRecvError::Empty) => {
999                if self.priority_closed && self.bulk_closed {
1000                    Err(TryRecvError::Disconnected)
1001                } else {
1002                    Err(TryRecvError::Empty)
1003                }
1004            }
1005            Err(TryRecvError::Disconnected) => {
1006                self.bulk_closed = true;
1007                if self.priority_closed {
1008                    Err(TryRecvError::Disconnected)
1009                } else {
1010                    Err(TryRecvError::Empty)
1011                }
1012            }
1013        }
1014    }
1015
1016    pub(crate) fn drain_ready<F>(&mut self, limit: usize, mut consume: F) -> usize
1017    where
1018        F: FnMut(ReceivedPacket) -> bool,
1019    {
1020        let mut drained = 0usize;
1021        while drained < limit {
1022            if !self.drain_pending_priority(limit, &mut drained, &mut consume) {
1023                break;
1024            }
1025            if drained >= limit {
1026                break;
1027            }
1028
1029            if self.should_probe_priority() {
1030                match self.priority.try_recv() {
1031                    Ok(item) => {
1032                        if !self.drain_item(
1033                            item,
1034                            PacketLane::Priority,
1035                            limit,
1036                            &mut drained,
1037                            &mut consume,
1038                        ) {
1039                            break;
1040                        }
1041                        continue;
1042                    }
1043                    Err(TryRecvError::Empty) => {}
1044                    Err(TryRecvError::Disconnected) => {
1045                        self.priority_closed = true;
1046                    }
1047                }
1048            }
1049            if drained >= limit {
1050                break;
1051            }
1052
1053            if !self.drain_pending_bulk(limit, &mut drained, &mut consume) {
1054                break;
1055            }
1056            if drained >= limit {
1057                break;
1058            }
1059
1060            match self.bulk.try_recv() {
1061                Ok(item) => {
1062                    if !self.drain_item(item, PacketLane::Bulk, limit, &mut drained, &mut consume) {
1063                        break;
1064                    }
1065                }
1066                Err(TryRecvError::Empty) => break,
1067                Err(TryRecvError::Disconnected) => {
1068                    self.bulk_closed = true;
1069                    break;
1070                }
1071            }
1072        }
1073        drained
1074    }
1075
1076    fn packet_from_item(
1077        &mut self,
1078        item: PacketQueueItem,
1079        lane: PacketLane,
1080    ) -> Option<ReceivedPacket> {
1081        item.record_dequeue_wait(lane);
1082        let packet_count = item.packet_count();
1083        if self.track_backlog {
1084            self.queued_packets.fetch_sub(packet_count, Relaxed);
1085        }
1086        if matches!(lane, PacketLane::Priority) {
1087            release_priority_packets(&self.priority_queued_packets, packet_count);
1088        }
1089        if matches!(lane, PacketLane::Bulk) {
1090            release_reserved_bulk_packets(&self.bulk_queued_packets, packet_count);
1091        }
1092        let rx_loop_owned_at = crate::perf_profile::stamp();
1093        match item {
1094            PacketQueueItem::One(mut packet) => {
1095                packet.trace_rx_loop_owned_at = rx_loop_owned_at;
1096                Some(packet)
1097            }
1098            PacketQueueItem::Batch(packets) => {
1099                let mut pending = PendingPackets::new(packets, rx_loop_owned_at);
1100                let packet = pending.next()?;
1101                if !pending.batch.packets.is_empty() {
1102                    match lane {
1103                        PacketLane::Priority => self.pending_priority = Some(pending),
1104                        PacketLane::Bulk => self.pending_bulk = Some(pending),
1105                    }
1106                }
1107                Some(packet)
1108            }
1109        }
1110    }
1111
1112    fn drain_item<F>(
1113        &mut self,
1114        item: PacketQueueItem,
1115        lane: PacketLane,
1116        limit: usize,
1117        drained: &mut usize,
1118        consume: &mut F,
1119    ) -> bool
1120    where
1121        F: FnMut(ReceivedPacket) -> bool,
1122    {
1123        if let Some(packet) = self.packet_from_item(item, lane) {
1124            *drained += 1;
1125            if !consume(packet) {
1126                return false;
1127            }
1128        }
1129
1130        match lane {
1131            PacketLane::Priority => self.drain_pending_priority(limit, drained, consume),
1132            PacketLane::Bulk => self.drain_pending_bulk(limit, drained, consume),
1133        }
1134    }
1135
1136    fn drain_pending_priority<F>(
1137        &mut self,
1138        limit: usize,
1139        drained: &mut usize,
1140        consume: &mut F,
1141    ) -> bool
1142    where
1143        F: FnMut(ReceivedPacket) -> bool,
1144    {
1145        while *drained < limit {
1146            let Some(packet) = Self::take_pending(&mut self.pending_priority) else {
1147                return true;
1148            };
1149            *drained += 1;
1150            if !consume(packet) {
1151                return false;
1152            }
1153        }
1154        true
1155    }
1156
1157    fn drain_pending_bulk<F>(&mut self, limit: usize, drained: &mut usize, consume: &mut F) -> bool
1158    where
1159        F: FnMut(ReceivedPacket) -> bool,
1160    {
1161        while *drained < limit {
1162            if self.should_probe_priority() {
1163                return true;
1164            }
1165            let Some(packet) = Self::take_pending(&mut self.pending_bulk) else {
1166                return true;
1167            };
1168            *drained += 1;
1169            if !consume(packet) {
1170                return false;
1171            }
1172        }
1173        true
1174    }
1175
1176    fn should_probe_priority(&self) -> bool {
1177        !self.priority_closed
1178            && (self.priority_queued_packets.load(Relaxed) > 0 || self.bulk_closed)
1179    }
1180
1181    fn take_pending(pending: &mut Option<PendingPackets>) -> Option<ReceivedPacket> {
1182        let packets = pending.as_mut()?;
1183        let packet = packets.next();
1184        if packets.batch.packets.is_empty() {
1185            *pending = None;
1186        }
1187        packet
1188    }
1189}
1190
1191#[inline]
1192fn packet_channel_tracks_backlog() -> bool {
1193    cfg!(test) || crate::perf_profile::enabled()
1194}
1195
1196fn release_reserved_bulk_packets(counter: &AtomicUsize, count: usize) {
1197    if count == 0 {
1198        return;
1199    }
1200
1201    let previous = counter.fetch_sub(count, Relaxed);
1202    debug_assert!(
1203        previous >= count,
1204        "transport bulk queued packet accounting underflow"
1205    );
1206}
1207
1208fn release_priority_packets(counter: &AtomicUsize, count: usize) {
1209    if count == 0 {
1210        return;
1211    }
1212
1213    let previous = counter.fetch_sub(count, Relaxed);
1214    debug_assert!(
1215        previous >= count,
1216        "transport priority queued packet accounting underflow"
1217    );
1218}
1219
1220/// Create a packet channel.
1221///
1222/// The capacity applies to bulk packets. Priority traffic is intentionally
1223/// unbounded so small control-shaped packets can still wake the rx loop while a
1224/// bulk receiver is saturated.
1225pub fn packet_channel(buffer: usize) -> (PacketTx, PacketRx) {
1226    let (priority_tx, priority_rx) = tokio::sync::mpsc::unbounded_channel();
1227    let (bulk_tx, bulk_rx) = tokio::sync::mpsc::channel(buffer.max(1));
1228    let priority_queued_packets = Arc::new(AtomicUsize::new(0));
1229    let queued_packets = Arc::new(AtomicUsize::new(0));
1230    let bulk_queued_packets = Arc::new(AtomicUsize::new(0));
1231    let track_backlog = packet_channel_tracks_backlog();
1232    (
1233        PacketTx {
1234            priority: priority_tx,
1235            bulk: bulk_tx,
1236            fast_ingress: None,
1237            batch_pool: PacketBatchPool::new(),
1238            #[cfg(any(test, target_os = "linux", target_os = "macos"))]
1239            buffer_pool: PacketBufferPool::new(),
1240            priority_queued_packets: Arc::clone(&priority_queued_packets),
1241            queued_packets: Arc::clone(&queued_packets),
1242            bulk_queued_packets: Arc::clone(&bulk_queued_packets),
1243            bulk_packet_capacity: buffer.max(1),
1244            track_backlog,
1245        },
1246        PacketRx {
1247            priority: priority_rx,
1248            bulk: bulk_rx,
1249            priority_queued_packets,
1250            queued_packets,
1251            bulk_queued_packets,
1252            track_backlog,
1253            pending_priority: None,
1254            pending_bulk: None,
1255            priority_closed: false,
1256            bulk_closed: false,
1257        },
1258    )
1259}
1260
1261#[cfg(test)]
1262mod tests;