Skip to content

Commit 15f631d

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 c572d85 commit 15f631d

File tree

4 files changed

+156
-38
lines changed

4 files changed

+156
-38
lines changed

quinn-proto/src/connection/mod.rs

Lines changed: 20 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
@@ -536,13 +537,13 @@ impl Connection {
536537
ack_eliciting |= self.can_send_1rtt(frame_space_1rtt);
537538
}
538539

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

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

564567
// Is 1 more datagram allowed?
@@ -585,7 +588,7 @@ impl Connection {
585588
if ack_eliciting && self.spaces[space_id].loss_probes == 0 {
586589
// Assume the current packet will get padded to fill the segment
587590
let untracked_bytes = if let Some(builder) = &builder_storage {
588-
transmit.datagram_max_offset() - builder.partial_encode.start
591+
transmit.datagram().len() - builder.partial_encode.start
589592
} else {
590593
0
591594
} as u64;
@@ -639,9 +642,10 @@ impl Connection {
639642
// MTU. Loss probes are the only packets for which we might grow
640643
// `buf_capacity` by less than `segment_size`.
641644
const MAX_PADDING: usize = 16;
642-
let packet_len_unpadded = cmp::max(builder.min_size, transmit.len())
643-
- transmit.datagram_start_offset()
644-
+ builder.tag_len;
645+
let packet_len_unpadded =
646+
cmp::max(builder.min_size, transmit.datagram().len())
647+
- builder.partial_encode.start
648+
+ builder.tag_len;
645649
if packet_len_unpadded + MAX_PADDING < transmit.segment_size()
646650
|| transmit.datagram_start_offset() + transmit.segment_size()
647651
> transmit.datagram_max_offset()
@@ -766,11 +770,13 @@ impl Connection {
766770
// to encode the ConnectionClose frame too. However we still have the
767771
// check here to prevent crashes if something changes.
768772
debug_assert!(
769-
transmit.len() + frame::ConnectionClose::SIZE_BOUND < builder.max_size,
770-
"ACKs should leave space for ConnectionClose"
773+
transmit.datagram().len() + frame::ConnectionClose::SIZE_BOUND
774+
< builder.max_size,
775+
"ACKS should leave space for ConnectionClose"
771776
);
772-
if transmit.len() + frame::ConnectionClose::SIZE_BOUND < builder.max_size {
773-
let max_frame_size = builder.max_size - transmit.len();
777+
if transmit.datagram().len() + frame::ConnectionClose::SIZE_BOUND < builder.max_size
778+
{
779+
let max_frame_size = builder.max_size - transmit.datagram().len();
774780
match self.state {
775781
State::Closed(state::Closed { ref reason }) => {
776782
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 buffer.datagram_mut());
124125
if conn.peer_params.grease_quic_bit && conn.rng.random() {
125-
buffer.as_mut_slice()[partial_encode.start] ^= FIXED_BIT;
126+
buffer.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-
buffer.len() + (sample_size + 4).saturating_sub(number.len() + tag_len),
150+
buffer.datagram().len() + (sample_size + 4).saturating_sub(number.len() + tag_len),
150151
partial_encode.start + dst_cid.len() + 6,
151152
);
152-
let max_size = buffer.datagram_max_offset() - tag_len;
153+
let max_size = buffer.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
buffer: &mut TransmitBuilder<'_>,
233231
) -> (usize, bool) {
234-
let pad = buffer.len() < self.min_size;
232+
let pad = self.min_size > buffer.datagram().len();
235233
if pad {
236-
let padding_bytes = self.min_size - buffer.len();
234+
let padding_bytes = self.min_size - buffer.datagram().len();
237235
trace!("PADDING * {padding_bytes}");
238236
buffer.datagram_mut().put_bytes(0, padding_bytes);
239237
}
@@ -256,13 +254,14 @@ impl PacketBuilder {
256254

257255
buffer.datagram_mut().put_bytes(0, packet_crypto.tag_len());
258256
let encode_start = self.partial_encode.start;
259-
let packet_buf = &mut buffer.as_mut_slice()[encode_start..];
257+
let mut datagram_buf = buffer.datagram_mut();
258+
let packet_buf = &mut datagram_buf[encode_start..];
260259
self.partial_encode.finish(
261260
packet_buf,
262261
header_crypto,
263262
Some((self.exact_number, packet_crypto)),
264263
);
265264

266-
(buffer.len() - encode_start, pad)
265+
(buffer.datagram().len() - encode_start, pad)
267266
}
268267
}

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 the already written bytes in the buffer
144-
pub(super) fn as_mut_slice(&mut self) -> &mut [u8] {
145-
self.buf.as_mut_slice()
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 a buffer into which the current datagram can be written
149-
pub(super) fn datagram_mut(&mut self) -> bytes::buf::Limit<&mut Vec<u8>> {
150-
self.buf.limit(self.buf_capacity)
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)