Skip to content

Commit 4b0dcbf

Browse files
committed
Only allow access to the current datagram
The TransmitBuilder now only allows access to the current datagram instead of to the entire buffer. This buffer is implemented as the DatagramBuffer struct. It is defined by the BufSlice trait however: code paths outside of poll_transmit also encode QUIC packets into buffers and they use a simple Vec buffer. This trait could potentially later be useful for other buffers as well, e.g. a packet buffer. Most importantly the logic of the minimum and maximum datagra size in the PacketBuilder had to be adjusted to track offsets relative to the datagram instead of relative to the entire buffer.
1 parent 17d7dbc commit 4b0dcbf

File tree

4 files changed

+157
-38
lines changed

4 files changed

+157
-38
lines changed

quinn-proto/src/connection/mod.rs

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ mod timer;
8989
use timer::{Timer, TimerTable};
9090

9191
mod transmit_builder;
92+
pub(crate) use transmit_builder::BufSlice;
9293
use transmit_builder::TransmitBuilder;
9394

9495
/// Protocol state and logic for a single QUIC connection
@@ -537,13 +538,13 @@ impl Connection {
537538
ack_eliciting |= self.can_send_1rtt(frame_space_1rtt);
538539
}
539540

540-
// Can we append more data into the current buffer?
541-
// It is not safe to assume that `buf.len()` is the end of the data,
542-
// since the last packet might not have been finished.
541+
// Can we append more data into the current datagram?
542+
// It is not safe to assume that `transmits.datagram().len()` is the end of the
543+
// data, since the last packet might not have been finished.
543544
let buf_end = if let Some(builder) = &builder_storage {
544-
transmits.len().max(builder.min_size) + builder.tag_len
545+
transmits.datagram().len().max(builder.min_size) + builder.tag_len
545546
} else {
546-
transmits.len()
547+
transmits.datagram().len()
547548
};
548549

549550
let tag_len = if let Some(ref crypto) = self.spaces[space_id].crypto {
@@ -559,7 +560,9 @@ impl Connection {
559560
// We are NOT coalescing (the default is we are, so this was turned off in an
560561
// earlier iteration) OR there is not enough space for another *packet* in this
561562
// datagram (buf_capacity - buf_end == unused space in datagram).
562-
if !coalesce || transmits.datagram_max_offset() - buf_end < MIN_PACKET_SPACE + tag_len {
563+
if !coalesce
564+
|| transmits.datagram_mut().capacity() - buf_end < MIN_PACKET_SPACE + tag_len
565+
{
563566
// We need to send 1 more datagram and extend the buffer for that.
564567

565568
// Is 1 more datagram allowed?
@@ -586,7 +589,7 @@ impl Connection {
586589
if ack_eliciting && self.spaces[space_id].loss_probes == 0 {
587590
// Assume the current packet will get padded to fill the segment
588591
let untracked_bytes = if let Some(builder) = &builder_storage {
589-
transmits.datagram_max_offset() - builder.partial_encode.start
592+
transmits.datagram().len() - builder.partial_encode.start
590593
} else {
591594
0
592595
} as u64;
@@ -640,9 +643,10 @@ impl Connection {
640643
// MTU. Loss probes are the only packets for which we might grow
641644
// `buf_capacity` by less than `segment_size`.
642645
const MAX_PADDING: usize = 16;
643-
let packet_len_unpadded = cmp::max(builder.min_size, transmits.len())
644-
- transmits.datagram_start_offset()
645-
+ builder.tag_len;
646+
let packet_len_unpadded =
647+
cmp::max(builder.min_size, transmits.datagram().len())
648+
- builder.partial_encode.start
649+
+ builder.tag_len;
646650
if packet_len_unpadded + MAX_PADDING < transmits.segment_size()
647651
|| transmits.datagram_start_offset() + transmits.segment_size()
648652
> transmits.datagram_max_offset()
@@ -767,11 +771,14 @@ impl Connection {
767771
// to encode the ConnectionClose frame too. However we still have the
768772
// check here to prevent crashes if something changes.
769773
debug_assert!(
770-
transmits.len() + frame::ConnectionClose::SIZE_BOUND < builder.max_size,
771-
"ACKs should leave space for ConnectionClose"
774+
transmits.datagram().len() + frame::ConnectionClose::SIZE_BOUND
775+
< builder.max_size,
776+
"ACKS should leave space for ConnectionClose"
772777
);
773-
if transmits.len() + frame::ConnectionClose::SIZE_BOUND < builder.max_size {
774-
let max_frame_size = builder.max_size - transmits.len();
778+
if transmits.datagram().len() + frame::ConnectionClose::SIZE_BOUND
779+
< builder.max_size
780+
{
781+
let max_frame_size = builder.max_size - transmits.datagram().len();
775782
match self.state {
776783
State::Closed(state::Closed { ref reason }) => {
777784
if space_id == SpaceId::Data || reason.is_transport_layer() {

quinn-proto/src/connection/packet_builder.rs

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use tracing::{trace, trace_span};
55
use super::{Connection, SentFrames, TransmitBuilder, spaces::SentPacket};
66
use crate::{
77
ConnectionId, Instant, TransportError, TransportErrorCode,
8-
connection::ConnectionSide,
8+
connection::{BufSlice, ConnectionSide},
99
frame::{self, Close},
1010
packet::{FIXED_BIT, Header, InitialHeader, LongType, PacketNumber, PartialEncode, SpaceId},
1111
};
@@ -17,11 +17,12 @@ pub(super) struct PacketBuilder {
1717
pub(super) ack_eliciting: bool,
1818
pub(super) exact_number: u64,
1919
pub(super) short_header: bool,
20-
/// Smallest absolute position in the associated buffer that must be occupied by this packet's
21-
/// frames
20+
/// The smallest datagram offset that must be occupied by this packet's frames
21+
///
22+
/// This is the smallest offset into the datagram this packet is being written into,
23+
/// that must contain frames for this packet.
2224
pub(super) min_size: usize,
23-
/// Largest absolute position in the associated buffer that may be occupied by this packet's
24-
/// frames
25+
/// The largest datagram offset that may be occupied by this packet's frames
2526
pub(super) max_size: usize,
2627
pub(super) tag_len: usize,
2728
pub(super) _span: tracing::span::EnteredSpan,
@@ -122,7 +123,7 @@ impl PacketBuilder {
122123
};
123124
let partial_encode = header.encode(&mut transmits.datagram_mut());
124125
if conn.peer_params.grease_quic_bit && conn.rng.random() {
125-
transmits.as_mut_slice()[partial_encode.start] ^= FIXED_BIT;
126+
transmits.datagram_mut()[partial_encode.start] ^= FIXED_BIT;
126127
}
127128

128129
let (sample_size, tag_len) = if let Some(ref crypto) = space.crypto {
@@ -146,10 +147,10 @@ impl PacketBuilder {
146147
// pn_len + payload_len + tag_len >= sample_size + 4
147148
// payload_len >= sample_size + 4 - pn_len - tag_len
148149
let min_size = Ord::max(
149-
transmits.len() + (sample_size + 4).saturating_sub(number.len() + tag_len),
150+
transmits.datagram().len() + (sample_size + 4).saturating_sub(number.len() + tag_len),
150151
partial_encode.start + dst_cid.len() + 6,
151152
);
152-
let max_size = transmits.datagram_max_offset() - tag_len;
153+
let max_size = transmits.datagram_mut().capacity() - tag_len;
153154
debug_assert!(max_size >= min_size);
154155

155156
Some(Self {
@@ -172,10 +173,7 @@ impl PacketBuilder {
172173
// The datagram might already have a larger minimum size than the caller is requesting, if
173174
// e.g. we're coalescing packets and have populated more than `min_size` bytes with packets
174175
// already.
175-
self.min_size = Ord::max(
176-
self.min_size,
177-
self.datagram_start + (min_size as usize) - self.tag_len,
178-
);
176+
self.min_size = Ord::max(self.min_size, (min_size as usize) - self.tag_len);
179177
}
180178

181179
pub(super) fn finish_and_track(
@@ -231,9 +229,9 @@ impl PacketBuilder {
231229
conn: &mut Connection,
232230
transmits: &mut TransmitBuilder<'_>,
233231
) -> (usize, bool) {
234-
let pad = transmits.len() < self.min_size;
232+
let pad = self.min_size > transmits.datagram().len();
235233
if pad {
236-
let padding_bytes = self.min_size - transmits.len();
234+
let padding_bytes = self.min_size - transmits.datagram().len();
237235
trace!("PADDING * {padding_bytes}");
238236
transmits.datagram_mut().put_bytes(0, padding_bytes);
239237
}
@@ -258,13 +256,14 @@ impl PacketBuilder {
258256
.datagram_mut()
259257
.put_bytes(0, packet_crypto.tag_len());
260258
let encode_start = self.partial_encode.start;
261-
let packet_buf = &mut transmits.as_mut_slice()[encode_start..];
259+
let mut datagram_buf = transmits.datagram_mut();
260+
let packet_buf = &mut datagram_buf[encode_start..];
262261
self.partial_encode.finish(
263262
packet_buf,
264263
header_crypto,
265264
Some((self.exact_number, packet_crypto)),
266265
);
267266

268-
(transmits.len() - encode_start, pad)
267+
(transmits.datagram().len() - encode_start, pad)
269268
}
270269
}

quinn-proto/src/connection/transmit_builder.rs

Lines changed: 115 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
1+
use std::ops::{Deref, DerefMut};
2+
13
use bytes::BufMut;
24

5+
use super::BufLen;
6+
37
/// The buffer in which to write datagrams for [`Connection::poll_transmit`]
48
///
59
/// The `poll_transmit` function writes zero or more datagrams to a buffer. Multiple
@@ -140,14 +144,24 @@ impl<'a> TransmitBuilder<'a> {
140144
self.buf_capacity = self.buf.len();
141145
}
142146

143-
/// Returns a buffer into which the current datagram can be written
144-
pub(super) fn datagram_mut(&mut self) -> bytes::buf::Limit<&mut Vec<u8>> {
145-
self.buf.limit(self.buf_capacity)
147+
/// Returns a mutable buffer for the current datagram
148+
///
149+
/// This buffer implements [`BufSlice`] and thus allows writing into the buffer using
150+
/// [`BufMut`] and both reading and modifying the already written data in the buffer
151+
/// using [`Deref`] and [`DerefMut`]. The buffer also enforces a maximum size.
152+
// This returns a concrete type since `impl BufSlice` would need a `+ use<'_>` lifetime
153+
// bound, which is only supported since Rust 1.82.
154+
pub(super) fn datagram_mut(&mut self) -> DatagramBuffer<'_> {
155+
DatagramBuffer::new(
156+
self.buf,
157+
self.datagram_start,
158+
self.buf_capacity - self.datagram_start,
159+
)
146160
}
147161

148-
/// Returns the already written bytes in the buffer
149-
pub(super) fn as_mut_slice(&mut self) -> &mut [u8] {
150-
self.buf.as_mut_slice()
162+
/// Returns the bytes written into the current datagram
163+
pub(super) fn datagram(&self) -> &[u8] {
164+
&self.buf[self.datagram_start..]
151165
}
152166

153167
/// Returns the GSO segment size
@@ -196,3 +210,98 @@ impl<'a> TransmitBuilder<'a> {
196210
self.buf.len()
197211
}
198212
}
213+
214+
/// A writable buffer with a capacity and access to the already written bytes
215+
///
216+
/// This is a writable buffer, using the [`BufMut`] trait, which also has a maximum capacity.
217+
/// If more bytes are written than the buffer can contain a panic will occur.
218+
///
219+
/// The [`Deref`] impl must return the already written bytes as a slice. [`DerefMut`] must
220+
/// allow modification of these already written bytes.
221+
pub(crate) trait BufSlice: BufMut + Deref<Target = [u8]> + DerefMut {
222+
/// Returns the number of already written bytes in the buffer
223+
fn len(&self) -> usize;
224+
225+
/// Returns the maximum size of the buffer
226+
fn capacity(&self) -> usize;
227+
}
228+
229+
impl BufSlice for Vec<u8> {
230+
fn len(&self) -> usize {
231+
self.len()
232+
}
233+
234+
/// For `Vec<u8>` the capacity is the same as the maximum vec size
235+
fn capacity(&self) -> usize {
236+
isize::MAX as usize
237+
}
238+
}
239+
240+
/// A [`BufSlice`] implementation for a datagram
241+
#[derive(Debug)]
242+
pub(crate) struct DatagramBuffer<'a> {
243+
/// The underlying storage the datagram buffer exists in
244+
buf: &'a mut Vec<u8>,
245+
/// The start offset of the datagram in the underlying buffer
246+
start_offset: usize,
247+
/// The maximum write offset in the underlying buffer for this datagram
248+
max_offset: usize,
249+
}
250+
251+
impl<'a> DatagramBuffer<'a> {
252+
pub(crate) fn new(buf: &'a mut Vec<u8>, start_offset: usize, max_size: usize) -> Self {
253+
let max_offset = start_offset + max_size;
254+
DatagramBuffer {
255+
buf,
256+
start_offset,
257+
max_offset,
258+
}
259+
}
260+
}
261+
262+
unsafe impl BufMut for DatagramBuffer<'_> {
263+
fn remaining_mut(&self) -> usize {
264+
self.max_offset - self.buf.len()
265+
}
266+
267+
unsafe fn advance_mut(&mut self, cnt: usize) {
268+
self.buf.advance_mut(cnt);
269+
}
270+
271+
fn chunk_mut(&mut self) -> &mut bytes::buf::UninitSlice {
272+
self.buf.chunk_mut()
273+
}
274+
}
275+
276+
impl Deref for DatagramBuffer<'_> {
277+
type Target = [u8];
278+
279+
fn deref(&self) -> &Self::Target {
280+
&self.buf[self.start_offset..]
281+
}
282+
}
283+
284+
impl DerefMut for DatagramBuffer<'_> {
285+
fn deref_mut(&mut self) -> &mut Self::Target {
286+
&mut self.buf[self.start_offset..]
287+
}
288+
}
289+
290+
impl BufSlice for DatagramBuffer<'_> {
291+
/// Returns the number of already written bytes in the buffer
292+
fn len(&self) -> usize {
293+
self.buf.len() - self.start_offset
294+
}
295+
296+
/// Returns the maximum size of the buffer
297+
fn capacity(&self) -> usize {
298+
self.max_offset - self.start_offset
299+
}
300+
}
301+
302+
// Temporary compatibility with the BufLen trait. To be removed in follow-up commits.
303+
impl BufLen for DatagramBuffer<'_> {
304+
fn len(&self) -> usize {
305+
<Self as BufSlice>::len(self)
306+
}
307+
}

quinn-proto/src/packet.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use thiserror::Error;
66
use crate::{
77
ConnectionId,
88
coding::{self, BufExt, BufMutExt},
9-
connection::BufLen,
9+
connection::BufSlice,
1010
crypto,
1111
};
1212

@@ -282,7 +282,11 @@ pub(crate) enum Header {
282282
}
283283

284284
impl Header {
285-
pub(crate) fn encode(&self, w: &mut (impl BufMut + BufLen)) -> PartialEncode {
285+
/// Encodes the QUIC packet header into the buffer
286+
///
287+
/// The current position of the buffer is stored in the [`PartialEncode`] as the start
288+
/// of the packet in the buffer.
289+
pub(crate) fn encode(&self, w: &mut impl BufSlice) -> PartialEncode {
286290
use Header::*;
287291
let start = w.len();
288292
match *self {

0 commit comments

Comments
 (0)