noq_proto/connection/
transmit_buf.rs

1use std::num::NonZeroUsize;
2
3use bytes::BufMut;
4use tracing::trace;
5
6use crate::packet::BufLen;
7
8/// The buffer in which to write datagrams for [`Connection::poll_transmit`]
9///
10/// The `poll_transmit` function writes zero or more datagrams to a buffer. Multiple
11/// datagrams are possible in case GSO (Generic Segmentation Offload) is supported.
12///
13/// This buffer tracks datagrams being written to it. There is always a "current" datagram,
14/// which is started by calling [`TransmitBuf::start_new_datagram`]. Writing to the buffer
15/// is done through the [`BufMut`] interface.
16///
17/// Usually a datagram contains one QUIC packet, though QUIC-TRANSPORT 12.2 Coalescing
18/// Packets allows for placing multiple packets into a single datagram provided all but the
19/// last packet uses long headers. This is normally used during connection setup where often
20/// the initial, handshake and sometimes even a 1-RTT packet can be coalesced into a single
21/// datagram.
22///
23/// Inside a single packet multiple QUIC frames are written.
24///
25/// The buffer managed here is passed straight to the OS' `sendmsg` call (or variant) once
26/// `poll_transmit` returns.  So needs to contain the datagrams as they are sent on the
27/// wire.
28///
29/// [`Connection::poll_transmit`]: super::Connection::poll_transmit
30#[derive(Debug)]
31pub(super) struct TransmitBuf<'a> {
32    /// The buffer itself, packets are written to this buffer
33    buf: &'a mut Vec<u8>,
34    /// Offset into the buffer at which the current datagram starts
35    ///
36    /// Note that when coalescing packets this might be before the start of the current
37    /// packet.
38    datagram_start: usize,
39    /// The maximum offset allowed to be used for the current datagram in the buffer
40    ///
41    /// The first and last datagram in a batch are allowed to be smaller then the maximum
42    /// size. All datagrams in between need to be exactly this size.
43    buf_capacity: usize,
44    /// The maximum number of datagrams allowed to write into [`TransmitBuf::buf`]
45    max_datagrams: NonZeroUsize,
46    /// The number of datagrams already (partially) written into the buffer
47    ///
48    /// Incremented by a call to [`TransmitBuf::start_new_datagram`].
49    pub(super) num_datagrams: usize,
50    /// The segment size of this GSO batch
51    ///
52    /// The segment size is the size of each datagram in the GSO batch, only the last
53    /// datagram in the batch may be smaller.
54    ///
55    /// For the first datagram this is set to the maximum size a datagram is allowed to be:
56    /// the current path MTU. After the first datagram is finished this is reduced to the
57    /// size of the first datagram and can no longer change.
58    segment_size: usize,
59}
60
61impl<'a> TransmitBuf<'a> {
62    pub(super) fn new(buf: &'a mut Vec<u8>, max_datagrams: NonZeroUsize, mtu: usize) -> Self {
63        buf.clear();
64        Self {
65            buf,
66            datagram_start: 0,
67            buf_capacity: 0,
68            max_datagrams,
69            num_datagrams: 0,
70            segment_size: mtu,
71        }
72    }
73
74    /// Starts a datagram with a custom datagram size
75    ///
76    /// This is a specialized version of [`TransmitBuf::start_new_datagram`] which sets the
77    /// datagram size. Useful for e.g. PATH_CHALLENGE, tail-loss probes or MTU probes.
78    ///
79    /// After the first datagram you can never increase the segment size. If you decrease
80    /// the size of a datagram in a batch, it must be the last datagram of the batch.
81    pub(super) fn start_new_datagram_with_size(&mut self, datagram_size: usize) {
82        // Only reserve space for this datagram, usually it is the last one in the batch.
83        let max_capacity_hint = datagram_size;
84        self.new_datagram_inner(datagram_size, max_capacity_hint)
85    }
86
87    /// Starts a new datagram in the transmit buffer
88    ///
89    /// If this starts the second datagram the segment size will be set to the size of the
90    /// first datagram.
91    ///
92    /// If the underlying buffer does not have enough capacity yet this will allocate enough
93    /// capacity for all the datagrams allowed in a single batch. Use
94    /// [`TransmitBuf::start_new_datagram_with_size`] if you know you will need less.
95    pub(super) fn start_new_datagram(&mut self) {
96        // We reserve the maximum space for sending `max_datagrams` upfront to avoid any
97        // reallocations if more datagrams have to be appended later on.  Benchmarks have
98        // shown a 5-10% throughput improvement compared to continuously resizing the
99        // datagram buffer. While this will lead to over-allocation for small transmits
100        // (e.g. purely containing ACKs), modern memory allocators (e.g. mimalloc and
101        // jemalloc) will pool certain allocation sizes and therefore this is still rather
102        // efficient.
103        let max_capacity_hint = self.max_datagrams.get() * self.segment_size;
104        self.new_datagram_inner(self.segment_size, max_capacity_hint)
105    }
106
107    fn new_datagram_inner(&mut self, datagram_size: usize, max_capacity_hint: usize) {
108        debug_assert!(self.num_datagrams < self.max_datagrams.into());
109        if self.num_datagrams == 1 {
110            // Set the segment size to the size of the first datagram.
111            self.segment_size = self.buf.len();
112        }
113        if self.num_datagrams >= 1 {
114            debug_assert!(datagram_size <= self.segment_size);
115            if datagram_size < self.segment_size {
116                // If this is a GSO batch and this datagram is smaller than the segment
117                // size, this must be the last datagram in the batch.
118                self.max_datagrams = NonZeroUsize::MIN.saturating_add(self.num_datagrams);
119            }
120        }
121        self.datagram_start = self.buf.len();
122        debug_assert_eq!(
123            self.datagram_start % self.segment_size,
124            0,
125            "datagrams in a GSO batch must be aligned to the segment size"
126        );
127        self.buf_capacity = self.datagram_start + datagram_size;
128        if self.buf_capacity > self.buf.capacity() {
129            self.buf
130                .reserve_exact(max_capacity_hint.saturating_sub(self.buf.capacity()));
131        }
132        self.num_datagrams += 1;
133    }
134
135    /// Clips the segment size to the current size
136    ///
137    /// Only valid for the first datagram, when the datagram might be smaller than the
138    /// segment size. Needed before estimating the available space in the next datagram
139    /// based on [`TransmitBuf::segment_size`].
140    ///
141    /// Use [`TransmitBuf::start_new_datagram_with_size`] if you need to reduce the size of
142    /// the last datagram in a batch.
143    pub(super) fn clip_segment_size(&mut self) {
144        debug_assert_eq!(self.num_datagrams, 1);
145        if self.buf.len() < self.segment_size {
146            trace!(
147                segment_size = self.buf.len(),
148                prev_segment_size = self.segment_size,
149                "clipped datagram size"
150            );
151        }
152        self.segment_size = self.buf.len();
153        self.buf_capacity = self.buf.len();
154    }
155
156    /// Returns the GSO segment size
157    ///
158    /// This is also the maximum size datagrams are allowed to be. The first and last
159    /// datagram in a batch are allowed to be smaller however. After the first datagram the
160    /// segment size is clipped to the size of the first datagram.
161    ///
162    /// If the last datagram was created using [`TransmitBuf::start_new_datagram_with_size`]
163    /// the the segment size will be greater than the current datagram is allowed to be.
164    /// Thus [`TransmitBuf::datagram_remaining_mut`] should be used if you need to know the
165    /// amount of data that can be written into the datagram.
166    pub(super) fn segment_size(&self) -> usize {
167        self.segment_size
168    }
169
170    /// Returns the number of datagrams written into the buffer
171    ///
172    /// The last datagram is not necessarily finished yet.
173    pub(super) fn num_datagrams(&self) -> usize {
174        self.num_datagrams
175    }
176
177    /// Returns the maximum number of datagrams allowed to be written into the buffer
178    pub(super) fn max_datagrams(&self) -> NonZeroUsize {
179        self.max_datagrams
180    }
181
182    /// Returns the start offset of the current datagram in the buffer
183    ///
184    /// In other words, this offset contains the first byte of the current datagram.
185    pub(super) fn datagram_start_offset(&self) -> usize {
186        self.datagram_start
187    }
188
189    /// Returns the maximum offset in the buffer allowed for the current datagram
190    ///
191    /// The first and last datagram in a batch are allowed to be smaller then the maximum
192    /// size. All datagrams in between need to be exactly this size.
193    pub(super) fn datagram_max_offset(&self) -> usize {
194        self.buf_capacity
195    }
196
197    /// Returns the number of bytes that may still be written into this datagram
198    pub(super) fn datagram_remaining_mut(&self) -> usize {
199        self.buf_capacity.saturating_sub(self.buf.len())
200    }
201
202    /// Returns `true` if the buffer did not have anything written into it
203    pub(super) fn is_empty(&self) -> bool {
204        self.len() == 0
205    }
206
207    /// The number of bytes written into the buffer so far
208    pub(super) fn len(&self) -> usize {
209        self.buf.len()
210    }
211
212    /// Returns the already written bytes in the buffer
213    pub(super) fn as_mut_slice(&mut self) -> &mut [u8] {
214        self.buf.as_mut_slice()
215    }
216}
217
218unsafe impl BufMut for TransmitBuf<'_> {
219    fn remaining_mut(&self) -> usize {
220        self.buf.remaining_mut()
221    }
222
223    unsafe fn advance_mut(&mut self, cnt: usize) {
224        unsafe { self.buf.advance_mut(cnt) };
225    }
226
227    fn chunk_mut(&mut self) -> &mut bytes::buf::UninitSlice {
228        self.buf.chunk_mut()
229    }
230}
231
232impl BufLen for TransmitBuf<'_> {
233    fn len(&self) -> usize {
234        self.len()
235    }
236}