use crate::stream::TransportFeatures;
use arrayvec::ArrayVec;
use core::ops::Deref;
use s2n_quic_core::{ensure, inet::ExplicitCongestionNotification};
use s2n_quic_platform::features;
use std::io::IoSlice;
const IPV4_HEADER_LEN: u16 = 20;
const IPV6_HEADER_LEN: u16 = 40;
const UDP_HEADER_LEN: u16 = 8;
const fn min_u16(a: u16, b: u16) -> u16 {
if a < b {
a
} else {
b
}
}
pub const MAX_COUNT: usize = if features::gso::IS_SUPPORTED {
let max_datagram_size = 1500 - min_u16(IPV4_HEADER_LEN, IPV6_HEADER_LEN) - UDP_HEADER_LEN;
(MAX_TOTAL / max_datagram_size) as _
} else {
1
};
const MAX_TOTAL_IPV4: u16 = if cfg!(target_os = "linux") {
u16::MAX - IPV4_HEADER_LEN - UDP_HEADER_LEN
} else {
9001 - IPV4_HEADER_LEN - UDP_HEADER_LEN
};
const MAX_TOTAL_IPV6: u16 = if cfg!(target_os = "linux") {
u16::MAX - IPV6_HEADER_LEN - UDP_HEADER_LEN
} else {
9001 - IPV6_HEADER_LEN - UDP_HEADER_LEN
};
pub const MAX_TOTAL: u16 = min_u16(MAX_TOTAL_IPV4, MAX_TOTAL_IPV6);
#[test]
fn max_total_test() {
let tests = [("127.0.0.1:0", MAX_TOTAL_IPV4), ("[::1]:0", MAX_TOTAL_IPV6)];
for (addr, total) in tests {
let socket = std::net::UdpSocket::bind(addr).unwrap();
let addr = socket.local_addr().unwrap();
let mut buffer = vec![0u8; total as usize + 1];
let _ = socket.send_to(&buffer, addr);
buffer.pop().unwrap();
socket
.send_to(&buffer, addr)
.expect("send should succeed when limited to MAX_TOTAL");
}
}
type Segments<'a> = ArrayVec<IoSlice<'a>, MAX_COUNT>;
pub struct Batch<'a> {
segments: Segments<'a>,
ecn: ExplicitCongestionNotification,
}
impl<'a> Deref for Batch<'a> {
type Target = [IoSlice<'a>];
#[inline]
fn deref(&self) -> &Self::Target {
&self.segments
}
}
impl<'a> Batch<'a> {
#[inline]
pub fn new<Q>(queue: Q, features: &TransportFeatures) -> Self
where
Q: IntoIterator<Item = (ExplicitCongestionNotification, &'a [u8])>,
{
let mut ecn = ExplicitCongestionNotification::Ect0;
let mut total_len = 0u32;
let mut segments = Segments::new();
for segment in queue {
let packet_len = segment.1.len();
debug_assert!(
packet_len <= u16::MAX as usize,
"segments should not exceed the maximum datagram size"
);
let packet_len = packet_len as u16;
let new_total_len = total_len + packet_len as u32;
if !features.is_stream() {
ensure!(new_total_len < MAX_TOTAL as u32, break);
}
let mut undersized_segment = false;
if let Some(first_segment) = segments.first() {
ensure!(first_segment.len() >= packet_len as usize, break);
undersized_segment = first_segment.len() > packet_len as usize;
ensure!(ecn == segment.0, break);
} else {
ecn = segment.0;
}
total_len = new_total_len;
let iovec = std::io::IoSlice::new(segment.1);
segments.push(iovec);
ensure!(!undersized_segment, break);
ensure!(!segments.is_full(), break);
}
Self { segments, ecn }
}
#[inline]
pub fn ecn(&self) -> ExplicitCongestionNotification {
self.ecn
}
}