in quic/s2n-quic-transport/src/connection/transmission.rs [106:441]
fn write_payload(
&mut self,
buffer: tx::PayloadBuffer,
gso_offset: usize,
) -> Result<usize, tx::Error> {
let space_manager = &mut self.space_manager;
let buffer = unsafe {
// the WriterContext has its own checks for buffer capacity so convert `buffer` into a
// `&mut [u8]`
buffer.into_mut_slice()
};
//= https://www.rfc-editor.org/rfc/rfc9002#section-7
//# An endpoint MUST NOT send a packet if it would cause bytes_in_flight
//# (see Appendix B.2) to be larger than the congestion window, unless
//# the packet is sent on a PTO timer expiration (see Section 6.2) or
//# when entering recovery (see Section 7.3.2).
let transmission_constraint =
if space_manager.requires_probe() && self.context.transmission_mode.is_normal() {
//= https://www.rfc-editor.org/rfc/rfc9002#section-6.2.4
//# When a PTO timer expires, a sender MUST send at least one ack-
//# eliciting packet in the packet number space as a probe.
//= https://www.rfc-editor.org/rfc/rfc9002#section-6.2.4
//# An endpoint SHOULD include new data in packets that are sent on PTO
//# expiration. Previously sent data MAY be sent if no new data can be
//# sent.
//= https://www.rfc-editor.org/rfc/rfc9002#section-7.5
//# Probe packets MUST NOT be blocked by the congestion controller.
self.context.transmission_mode = transmission::Mode::LossRecoveryProbing;
transmission::Constraint::None
} else {
self.context.path().transmission_constraint()
};
let max_datagram_size = self
.context
.path()
.clamp_datagram_size(buffer.len(), self.context.transmission_mode);
debug_assert_ne!(
max_datagram_size, 0,
"the amplification limit should be checked before trying to transmit"
);
// limit the number of retries to the MAX_BURST_PACKETS
for _ in 0..MAX_BURST_PACKETS {
let encoder = EncoderBuffer::new(&mut buffer[..max_datagram_size]);
let initial_capacity = encoder.capacity();
//= https://www.rfc-editor.org/rfc/rfc9002#section-6.2.4
//# In addition to sending data in the packet number space for which the
//# timer expired, the sender SHOULD send ack-eliciting packets from
//# other packet number spaces with in-flight data, coalescing packets if
//# possible.
//= https://www.rfc-editor.org/rfc/rfc9000#section-14.1
//# A client MUST expand the payload of all UDP datagrams carrying
//# Initial packets to at least the smallest allowed maximum datagram
//# size of 1200 bytes by adding PADDING frames to the Initial packet or
//# by coalescing the Initial packet; see Section 12.2.
//= https://www.rfc-editor.org/rfc/rfc9000#section-14.1
//# Similarly, a
//# server MUST expand the payload of all UDP datagrams carrying ack-
//# eliciting Initial packets to at least the smallest allowed maximum
//# datagram size of 1200 bytes.
// If the transmission contains an Initial packet, it must be padded. However, we should
// add padding only if necessary, after packets from all packet number spaces have been
// coalesced. Therefore, after confirming there will be an Initial packet, we first check
// if there will be an ApplicationData packet, since those packets come at the end of the
// datagram. If there is no ApplicationData packet, the Handshake packet will come at the
// end, so we check that next. Finally, if there is no ApplicationData or Handshake packet
// to transmit, the Initial packet itself will be padded.
let mut pn_space_to_pad = {
let needs_padding =
has_transmission(space_manager.initial(), transmission_constraint);
if !needs_padding {
// There is no Initial packet, so no padding is needed
None
} else if has_transmission(space_manager.application(), transmission_constraint) {
Some(PacketNumberSpace::ApplicationData)
} else if has_transmission(space_manager.handshake(), transmission_constraint) {
Some(PacketNumberSpace::Handshake)
} else {
//= https://www.rfc-editor.org/rfc/rfc9001#section-4.9
//# These packets MAY also include PADDING frames.
Some(PacketNumberSpace::Initial)
}
};
//= https://www.rfc-editor.org/rfc/rfc9001#section-4
//# When packets of different types need to be sent,
//# endpoints SHOULD use coalesced packets to send them in the same UDP
//# datagram.
// here we query all of the spaces to try and fill the current datagram
let is_mtu_probing = self.context.transmission_mode.is_mtu_probing();
let encoder = if let Some((space, handshake_status)) = space_manager
.initial_mut()
// MTU probes are only sent in the Application Space
.filter(|_| !is_mtu_probing)
{
self.context.min_packet_len = pn_space_to_pad
.filter(|pn_space| pn_space.is_initial())
.map(|_| encoder.capacity());
match space.on_transmit(
&mut self.context,
transmission_constraint,
handshake_status,
encoder,
) {
Ok((outcome, encoder)) => {
if Config::ENDPOINT_TYPE.is_server()
&& !outcome.ack_elicitation().is_ack_eliciting()
{
//= https://www.rfc-editor.org/rfc/rfc9000#section-14.1
//# Similarly, a
//# server MUST expand the payload of all UDP datagrams carrying ack-
//# eliciting Initial packets to at least the smallest allowed maximum
//# datagram size of 1200 bytes.
// The Initial packet was not ack eliciting so there is no need to pad
pn_space_to_pad = None;
}
*self.context.outcome += outcome;
encoder
}
Err(PacketEncodingError::PacketNumberTruncationError(encoder)) => {
// TODO handle this
encoder
}
Err(PacketEncodingError::InsufficientSpace(encoder)) => {
// move to the next packet space
encoder
}
Err(PacketEncodingError::EmptyPayload(encoder)) => {
// move to the next packet space
encoder
}
Err(PacketEncodingError::AeadLimitReached(encoder)) => {
// move to the next packet space
encoder
}
}
} else {
encoder
};
let encoder = if let Some((space, handshake_status)) = space_manager
.handshake_mut()
// MTU probes are only sent in the Application Space
.filter(|_| !is_mtu_probing)
{
self.context.min_packet_len = pn_space_to_pad
.filter(|pn_space| pn_space.is_handshake())
.map(|_| encoder.capacity());
let encoder = match space.on_transmit(
&mut self.context,
transmission_constraint,
handshake_status,
encoder,
) {
Ok((outcome, encoder)) => {
*self.context.outcome += outcome;
encoder
}
Err(PacketEncodingError::PacketNumberTruncationError(encoder)) => {
// TODO handle this
encoder
}
Err(PacketEncodingError::InsufficientSpace(encoder)) => {
// move to the next packet space
encoder
}
Err(PacketEncodingError::EmptyPayload(encoder)) => {
// move to the next packet space
encoder
}
Err(PacketEncodingError::AeadLimitReached(encoder)) => {
// move to the next packet space
encoder
}
};
//= https://www.rfc-editor.org/rfc/rfc9001#section-4.9.1
//# a client MUST discard Initial keys when it first sends a
//# Handshake packet
if Config::ENDPOINT_TYPE.is_client() {
space_manager.discard_initial(
self.context.path_manager,
self.context.timestamp,
self.context.publisher,
);
}
//= https://www.rfc-editor.org/rfc/rfc9001#section-4.9.2
//# An endpoint MUST discard its handshake keys when the TLS handshake is
//# confirmed (Section 4.1.2).
debug_assert!(!space_manager.is_handshake_confirmed());
encoder
} else {
encoder
};
//= https://www.rfc-editor.org/rfc/rfc9001#section-4.9
//# Though an endpoint might retain older keys, new data MUST be sent at
//# the highest currently-available encryption level.
// This requirement is automatically support with s2n-quic's implementation. Each space
// acts mostly independent from another and will buffer its own CRYPTO and ACK state. Other
// frames are only allowed in the ApplicationData space, which will always be the highest
// current-available encryption level.
let encoder = if let Some((space, handshake_status)) = space_manager.application_mut() {
self.context.min_packet_len = pn_space_to_pad
.filter(|pn_space| pn_space.is_application_data())
.map(|_| encoder.capacity());
// Pad the packet when sending path validation frames so that MTU is also validated.
let path = &self.context.path_manager[self.context.path_id];
//= https://www.rfc-editor.org/rfc/rfc9000#section-8.2.1
//# An endpoint MUST expand datagrams that contain a PATH_CHALLENGE frame
//# to at least the smallest allowed maximum datagram size of 1200 bytes.
//
//= https://www.rfc-editor.org/rfc/rfc9000#section-8.2.2
//# An endpoint MUST expand datagrams that contain a PATH_RESPONSE frame
//# to at least the smallest allowed maximum datagram size of 1200 bytes.
// Pad the packet when sending path validation frames so that MTU is also validated.
//
// The path's transmission_interest indicates if a PATH_CHALLENGE or PATH_RESPONSE
// frame is to be written.
//
// We need to check is_validated because it is possible to receive a PATH_CHALLENGE on
// an active path for Off-Path Packet Forwarding prevention. However, we would only
// like to pad when validating the MTU.
if !path.is_validated() && path.has_transmission_interest() {
self.context.min_packet_len = Some(encoder.capacity());
}
match space.on_transmit(
&mut self.context,
transmission_constraint,
handshake_status,
encoder,
) {
Ok((outcome, encoder)) => {
*self.context.outcome += outcome;
encoder
}
Err(PacketEncodingError::PacketNumberTruncationError(encoder)) => {
// TODO handle this
encoder
}
Err(PacketEncodingError::InsufficientSpace(encoder)) => {
// move to the next packet space
encoder
}
Err(PacketEncodingError::EmptyPayload(encoder)) => {
// move to the next packet space
encoder
}
Err(PacketEncodingError::AeadLimitReached(encoder)) => {
// move to the next packet space
encoder
}
}
} else {
encoder
};
let datagram_len = initial_capacity - encoder.capacity();
// the spaces didn't write anything so we're done
if datagram_len == 0 {
return Err(tx::Error::EmptyPayload);
}
// Emit the transmission event
//
// Even though the interceptor could alter the outgoing bytes, we're going to pretend
// that it doesn't so it's closer to on-path datagram corruption.
self.context.path_mut().on_bytes_transmitted(datagram_len);
self.context
.publisher
.on_datagram_sent(event::builder::DatagramSent {
len: datagram_len as u16,
gso_offset,
});
let datagram_len = {
use s2n_quic_core::{
event::IntoEvent,
packet::interceptor::{Datagram, Interceptor},
};
let mut encoder = EncoderBuffer::new(buffer);
encoder.set_position(datagram_len);
let subject = self.context.publisher.subject();
let remote_address = self.context.path().remote_address();
let local_address = self.context.path().local_address();
let datagram = Datagram {
remote_address: remote_address.into_event(),
local_address: local_address.into_event(),
timestamp: self.context.timestamp,
};
self.context.packet_interceptor.intercept_tx_datagram(
&subject,
&datagram,
&mut encoder,
);
// if the packet interceptor cleared the encoder buffer, then try again
if encoder.is_empty() {
continue;
}
encoder.len()
};
return Ok(datagram_len);
}
Err(tx::Error::EmptyPayload)
}