Skip to main content

noq_proto/connection/
datagrams.rs

1use std::collections::VecDeque;
2
3use bytes::Bytes;
4use thiserror::Error;
5use tracing::{debug, trace};
6
7use super::Connection;
8use crate::{
9    FrameStats, TransportError,
10    connection::PacketBuilder,
11    frame::{Datagram, FrameStruct},
12};
13
14/// API to control datagram traffic
15pub struct Datagrams<'a> {
16    pub(super) conn: &'a mut Connection,
17}
18
19impl Datagrams<'_> {
20    /// Queue an unreliable, unordered datagram for immediate transmission
21    ///
22    /// If `drop` is true, previously queued datagrams which are still unsent may be discarded to
23    /// make space for this datagram, in order of oldest to newest. If `drop` is false, and there
24    /// isn't enough space due to previously queued datagrams, this function will return
25    /// `SendDatagramError::Blocked`. `Event::DatagramsUnblocked` will be emitted once datagrams
26    /// have been sent.
27    ///
28    /// Returns `Err` iff a `len`-byte datagram cannot currently be sent.
29    pub fn send(&mut self, data: Bytes, drop: bool) -> Result<(), SendDatagramError> {
30        if self.conn.config.datagram_receive_buffer_size.is_none() {
31            return Err(SendDatagramError::Disabled);
32        }
33        let max = self
34            .max_size()
35            .ok_or(SendDatagramError::UnsupportedByPeer)?;
36        if data.len() > max {
37            return Err(SendDatagramError::TooLarge);
38        }
39        if drop {
40            while self.conn.datagrams.outgoing_total > self.conn.config.datagram_send_buffer_size {
41                let prev = self
42                    .conn
43                    .datagrams
44                    .outgoing
45                    .pop_front()
46                    .expect("datagrams.outgoing_total desynchronized");
47                trace!(len = prev.data.len(), "dropping outgoing datagram");
48                self.conn.datagrams.outgoing_total -= prev.data.len();
49            }
50        } else if self.conn.datagrams.outgoing_total + data.len()
51            > self.conn.config.datagram_send_buffer_size
52        {
53            self.conn.datagrams.send_blocked = true;
54            return Err(SendDatagramError::Blocked(data));
55        }
56        self.conn.datagrams.outgoing_total += data.len();
57        self.conn.datagrams.outgoing.push_back(Datagram { data });
58        Ok(())
59    }
60
61    /// Compute the maximum size of datagrams that may be passed to `send_datagram`
62    ///
63    /// Returns `None` if datagrams are unsupported by the peer or disabled locally.
64    ///
65    /// This may change over the lifetime of a connection according to variation in the path MTU
66    /// estimate. The peer can also enforce an arbitrarily small fixed limit, but if the peer's
67    /// limit is large this is guaranteed to be a little over a kilobyte at minimum.
68    ///
69    /// Not necessarily the maximum size of received datagrams.
70    ///
71    /// When multipath is enabled, this is calculated using the smallest MTU across all
72    /// available paths.
73    pub fn max_size(&self) -> Option<usize> {
74        // We use the conservative overhead bound for any packet number, reducing the budget by at
75        // most 3 bytes, so that PN size fluctuations don't cause users sending maximum-size
76        // datagrams to suffer avoidable packet loss.
77        let max_size = self.conn.current_mtu() as usize
78            - self.conn.predict_1rtt_overhead_no_pn()
79            - Datagram::SIZE_BOUND;
80        let limit = self
81            .conn
82            .peer_params
83            .max_datagram_frame_size?
84            .into_inner()
85            .saturating_sub(Datagram::SIZE_BOUND as u64);
86        Some(limit.min(max_size as u64) as usize)
87    }
88
89    /// Receive an unreliable, unordered datagram
90    pub fn recv(&mut self) -> Option<Bytes> {
91        self.conn.datagrams.recv()
92    }
93
94    /// Bytes available in the outgoing datagram buffer
95    ///
96    /// When greater than zero, [`send`](Self::send)ing a datagram of at most this size is
97    /// guaranteed not to cause older datagrams to be dropped.
98    pub fn send_buffer_space(&self) -> usize {
99        self.conn
100            .config
101            .datagram_send_buffer_size
102            .saturating_sub(self.conn.datagrams.outgoing_total)
103    }
104}
105
106#[derive(Default)]
107pub(super) struct DatagramState {
108    /// Number of bytes of datagrams that have been received by the local transport but not
109    /// delivered to the application
110    pub(super) recv_buffered: usize,
111    pub(super) incoming: VecDeque<Datagram>,
112    pub(super) outgoing: VecDeque<Datagram>,
113    pub(super) outgoing_total: usize,
114    pub(super) send_blocked: bool,
115}
116
117impl DatagramState {
118    pub(super) fn received(
119        &mut self,
120        datagram: Datagram,
121        window: &Option<usize>,
122    ) -> Result<bool, TransportError> {
123        let window = match window {
124            None => {
125                return Err(TransportError::PROTOCOL_VIOLATION(
126                    "unexpected DATAGRAM frame",
127                ));
128            }
129            Some(x) => *x,
130        };
131
132        if datagram.data.len() > window {
133            return Err(TransportError::PROTOCOL_VIOLATION("oversized datagram"));
134        }
135
136        let was_empty = self.recv_buffered == 0;
137        while datagram.data.len() + self.recv_buffered > window {
138            debug!("dropping stale datagram");
139            self.recv();
140        }
141
142        self.recv_buffered += datagram.data.len();
143        self.incoming.push_back(datagram);
144        Ok(was_empty)
145    }
146
147    /// Discard outgoing datagrams with a payload larger than `max_payload` bytes
148    ///
149    /// Returns whether any datagrams were dropped.
150    ///
151    /// Used to ensure that reductions in MTU don't get us stuck in a state where we have a datagram
152    /// queued but can't send it.
153    pub(super) fn drop_oversized(&mut self, max_payload: usize) -> bool {
154        let mut dropped_any = false;
155        self.outgoing.retain(|datagram| {
156            let result = datagram.data.len() < max_payload;
157            if !result {
158                trace!(
159                    "dropping {} byte datagram violating {} byte limit",
160                    datagram.data.len(),
161                    max_payload
162                );
163                self.outgoing_total -= datagram.data.len();
164                dropped_any = true;
165            }
166            result
167        });
168        dropped_any
169    }
170
171    /// Attempt to write a datagram frame into `buf`, consuming it from `self.outgoing`
172    ///
173    /// Returns whether a frame was written. At most `max_size` bytes will be written, including
174    /// framing.
175    pub(super) fn write<'a, 'b>(
176        &mut self,
177        buf: &mut PacketBuilder<'a, 'b>,
178        stat: &mut FrameStats,
179    ) -> bool {
180        let Some(datagram) = self.outgoing.pop_front() else {
181            return false;
182        };
183
184        if buf.frame_space_remaining() < datagram.size(true) {
185            // Future work: we could be more clever about cramming small datagrams into
186            // mostly-full packets when a larger one is queued first
187            self.outgoing.push_front(datagram);
188            return false;
189        }
190
191        self.outgoing_total -= datagram.data.len();
192        buf.write_frame(datagram, stat);
193        true
194    }
195
196    pub(super) fn recv(&mut self) -> Option<Bytes> {
197        let x = self.incoming.pop_front()?.data;
198        self.recv_buffered -= x.len();
199        Some(x)
200    }
201}
202
203/// Errors that can arise when sending a datagram
204#[derive(Debug, Error, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
205pub enum SendDatagramError {
206    /// The peer does not support receiving datagram frames
207    #[error("datagrams not supported by peer")]
208    UnsupportedByPeer,
209    /// Datagram support is disabled locally
210    #[error("datagram support disabled")]
211    Disabled,
212    /// The datagram is larger than the connection can currently accommodate
213    ///
214    /// Indicates that the path MTU minus overhead or the limit advertised by the peer has been
215    /// exceeded.
216    #[error("datagram too large")]
217    TooLarge,
218    /// Send would block
219    #[error("datagram send blocked")]
220    Blocked(Bytes),
221}