use std::collections::VecDeque;
use bytes::Bytes;
use thiserror::Error;
use tracing::{debug, trace};
use super::{Connection, Event};
use crate::{
TransportError,
frame::{Datagram, FrameStruct},
};
pub struct Datagrams<'a> {
pub(super) conn: &'a mut Connection,
}
impl Datagrams<'_> {
pub fn send(&mut self, data: Bytes, drop: bool) -> Result<(), SendDatagramError> {
if self.conn.config.datagram_receive_buffer_size.is_none() {
return Err(SendDatagramError::Disabled);
}
let max = self
.max_size()
.ok_or(SendDatagramError::UnsupportedByPeer)?;
let send_buffer_size = self.conn.config.datagram_send_buffer_size;
if data.len() > Ord::min(max, send_buffer_size) {
return Err(SendDatagramError::TooLarge);
}
if drop {
self.conn
.datagrams
.make_space_for(data.len(), send_buffer_size);
} else if !self
.conn
.datagrams
.has_send_buffer_space(data.len(), send_buffer_size)
{
self.conn.datagrams.send_blocked = true;
return Err(SendDatagramError::Blocked(data));
}
self.conn.datagrams.outgoing.push_back(Datagram { data });
Ok(())
}
pub(super) fn drop_oversized(&mut self) {
let Some(max_datagram_size) = self.max_size() else {
return;
};
if !self.conn.datagrams.drop_oversized(max_datagram_size)
|| !self.conn.datagrams.send_blocked
{
return;
}
self.conn.datagrams.send_blocked = false;
self.conn.events.push_back(Event::DatagramsUnblocked);
}
pub fn max_size(&self) -> Option<usize> {
let max_size = self.conn.path.current_mtu() as usize
- self.conn.predict_1rtt_overhead(None)
- Datagram::SIZE_BOUND;
let limit = self
.conn
.peer_params
.max_datagram_frame_size?
.into_inner()
.saturating_sub(Datagram::SIZE_BOUND as u64);
Some(limit.min(max_size as u64) as usize)
}
pub fn recv(&mut self) -> Option<Bytes> {
self.conn.datagrams.recv()
}
pub fn send_buffer_space(&self) -> usize {
self.conn
.config
.datagram_send_buffer_size
.saturating_sub(self.conn.datagrams.outgoing.payload_bytes)
.saturating_sub(size_of::<Datagram>())
}
}
#[derive(Default)]
pub(super) struct DatagramState {
pub(super) incoming: DatagramBuffer,
pub(super) outgoing: DatagramBuffer,
pub(super) send_blocked: bool,
}
impl DatagramState {
pub(super) fn received(
&mut self,
datagram: Datagram,
window: &Option<usize>,
) -> Result<bool, TransportError> {
let window = match window {
None => {
return Err(TransportError::PROTOCOL_VIOLATION(
"unexpected DATAGRAM frame",
));
}
Some(x) => *x,
};
let size_with_overhead = datagram.data.len() + size_of::<Datagram>();
if size_with_overhead > window {
return Err(TransportError::PROTOCOL_VIOLATION("oversized datagram"));
}
let was_empty = self.incoming.is_empty();
while self.incoming.memory_used() + size_with_overhead > window {
debug!("dropping stale datagram");
self.recv();
}
self.incoming.push_back(datagram);
Ok(was_empty)
}
fn make_space_for(&mut self, datagram_len: usize, send_buffer_size: usize) {
while !self.has_send_buffer_space(datagram_len, send_buffer_size) {
let Some(prev) = self.outgoing.pop_front() else {
break;
};
trace!(len = prev.data.len(), "dropping outgoing datagram");
}
}
fn has_send_buffer_space(&self, datagram_len: usize, send_buffer_size: usize) -> bool {
let Some(total) = self.outgoing.memory_used().checked_add(datagram_len) else {
return false;
};
total <= send_buffer_size
}
pub(super) fn drop_oversized(&mut self, max_payload: usize) -> bool {
let mut dropped_any = false;
self.outgoing.queue.retain(|datagram| {
let result = datagram.data.len() <= max_payload;
if !result {
trace!(
"dropping {} byte datagram violating {} byte limit",
datagram.data.len(),
max_payload
);
self.outgoing.payload_bytes -= datagram.data.len();
dropped_any = true;
}
result
});
dropped_any
}
pub(super) fn write(&mut self, buf: &mut Vec<u8>, max_size: usize) -> bool {
let Some(datagram) = self.outgoing.pop_front() else {
return false;
};
if buf.len() + datagram.size(true) > max_size {
self.outgoing.push_front(datagram);
return false;
}
trace!(len = datagram.data.len(), "DATAGRAM");
datagram.encode(true, buf);
true
}
pub(super) fn recv(&mut self) -> Option<Bytes> {
let x = self.incoming.pop_front()?.data;
Some(x)
}
}
#[derive(Default)]
pub(super) struct DatagramBuffer {
queue: VecDeque<Datagram>,
payload_bytes: usize,
}
impl DatagramBuffer {
fn push_back(&mut self, datagram: Datagram) {
self.payload_bytes += datagram.data.len();
self.queue.push_back(datagram);
}
fn pop_front(&mut self) -> Option<Datagram> {
let datagram = self.queue.pop_front()?;
self.payload_bytes -= datagram.data.len();
Some(datagram)
}
fn push_front(&mut self, datagram: Datagram) {
self.payload_bytes += datagram.data.len();
self.queue.push_front(datagram);
}
fn memory_used(&self) -> usize {
self.payload_bytes
.saturating_add(self.queue.len() * size_of::<Datagram>())
}
pub(super) fn can_send_1rtt(&self, max_size: usize) -> bool {
self.queue.front().is_some_and(|x| x.size(true) <= max_size)
}
pub(super) fn is_empty(&self) -> bool {
self.queue.is_empty()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn make_space_for_accounts_for_new_datagram() {
let mut state = DatagramState::default();
state.outgoing.push_back(Datagram {
data: Bytes::from_static(&[0; 7]),
});
state.outgoing.push_back(Datagram {
data: Bytes::from_static(&[0; 2]),
});
state.make_space_for(4, 10 + 2 * size_of::<Datagram>());
assert_eq!(state.outgoing.queue.len(), 1);
assert_eq!(state.outgoing.queue[0].data.len(), 2);
assert_eq!(state.outgoing.payload_bytes, 2);
}
#[test]
fn make_space_for_handles_overflowing_capacity_check() {
let mut state = DatagramState::default();
state.outgoing.queue.push_back(Datagram {
data: Bytes::from_static(&[0]),
});
state.outgoing.payload_bytes = usize::MAX - 1;
state.make_space_for(2, usize::MAX);
assert!(state.outgoing.is_empty());
assert_eq!(state.outgoing.payload_bytes, usize::MAX - 2);
}
#[test]
fn empty_frame_flood_limit() {
let mut state = DatagramState::default();
let datagram = Datagram { data: Bytes::new() };
let window = 100;
loop {
let initial_count = state.incoming.queue.len();
state.received(datagram.clone(), &Some(window)).unwrap();
assert!(state.incoming.queue.len() * size_of::<Datagram>() <= window);
if state.incoming.queue.len() == initial_count {
break;
}
}
}
#[test]
fn drop_oversized_keeps_datagrams_at_limit() {
let mut state = DatagramState::default();
state.outgoing.push_back(Datagram {
data: Bytes::from_static(&[0; 10]),
});
state.outgoing.push_back(Datagram {
data: Bytes::from_static(&[0; 11]),
});
assert!(state.drop_oversized(10));
assert_eq!(state.outgoing.queue.len(), 1);
assert_eq!(state.outgoing.queue[0].data.len(), 10);
assert_eq!(state.outgoing.payload_bytes, 10);
}
}
#[derive(Debug, Error, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum SendDatagramError {
#[error("datagrams not supported by peer")]
UnsupportedByPeer,
#[error("datagram support disabled")]
Disabled,
#[error("datagram too large")]
TooLarge,
#[error("datagram send blocked")]
Blocked(Bytes),
}