wtransport_proto/
settings.rs

1use crate::bytes::BufferReader;
2use crate::bytes::BufferWriter;
3use crate::bytes::BytesReader;
4use crate::bytes::BytesWriter;
5use crate::bytes::EndOfBuffer;
6use crate::error::ErrorCode;
7use crate::frame::Frame;
8use crate::frame::FrameKind;
9use crate::varint::VarInt;
10use std::borrow::Cow;
11use std::collections::hash_map;
12use std::collections::HashMap;
13
14enum ParseError {
15    ReservedSetting,
16    UnknownSetting,
17}
18
19/// Settings IDs for an HTTP3 connection.
20#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
21pub enum SettingId {
22    /// `SETTINGS_QPACK_MAX_TABLE_CAPACITY`.
23    QPackMaxTableCapacity,
24
25    /// `SETTINGS_MAX_FIELD_SECTION_SIZE`.
26    MaxFieldSectionSize,
27
28    /// `SETTINGS_QPACK_BLOCKED_STREAMS`.
29    QPackBlockedStreams,
30
31    /// `SETTINGS_ENABLE_CONNECT_PROTOCOL`.
32    EnableConnectProtocol,
33
34    /// `SETTINGS_H3_DATAGRAM`.
35    H3Datagram,
36
37    /// `SETTINGS_ENABLE_WEBTRANSPORT`.
38    EnableWebTransport,
39
40    /// `WEBTRANSPORT_MAX_SESSIONS`.
41    WebTransportMaxSessions,
42
43    /// Exercise setting.
44    Exercise(VarInt),
45}
46
47impl SettingId {
48    fn parse(id: VarInt) -> Result<Self, ParseError> {
49        if Self::is_reserved(id) {
50            return Err(ParseError::ReservedSetting);
51        }
52
53        if Self::is_exercise(id) {
54            Ok(Self::Exercise(id))
55        } else {
56            match id {
57                setting_ids::SETTINGS_QPACK_MAX_TABLE_CAPACITY => Ok(Self::QPackMaxTableCapacity),
58                setting_ids::SETTINGS_MAX_FIELD_SECTION_SIZE => Ok(Self::MaxFieldSectionSize),
59                setting_ids::SETTINGS_QPACK_BLOCKED_STREAMS => Ok(Self::QPackBlockedStreams),
60                setting_ids::SETTINGS_ENABLE_CONNECT_PROTOCOL => Ok(Self::EnableConnectProtocol),
61                setting_ids::SETTINGS_H3_DATAGRAM => Ok(Self::H3Datagram),
62                setting_ids::SETTINGS_ENABLE_WEBTRANSPORT => Ok(Self::EnableWebTransport),
63                setting_ids::SETTINGS_WEBTRANSPORT_MAX_SESSIONS => {
64                    Ok(Self::WebTransportMaxSessions)
65                }
66                _ => Err(ParseError::UnknownSetting),
67            }
68        }
69    }
70
71    const fn id(self) -> VarInt {
72        match self {
73            Self::QPackMaxTableCapacity => setting_ids::SETTINGS_QPACK_MAX_TABLE_CAPACITY,
74            Self::MaxFieldSectionSize => setting_ids::SETTINGS_MAX_FIELD_SECTION_SIZE,
75            Self::QPackBlockedStreams => setting_ids::SETTINGS_QPACK_BLOCKED_STREAMS,
76            Self::EnableConnectProtocol => setting_ids::SETTINGS_ENABLE_CONNECT_PROTOCOL,
77            Self::H3Datagram => setting_ids::SETTINGS_H3_DATAGRAM,
78            Self::EnableWebTransport => setting_ids::SETTINGS_ENABLE_WEBTRANSPORT,
79            Self::WebTransportMaxSessions => setting_ids::SETTINGS_WEBTRANSPORT_MAX_SESSIONS,
80            Self::Exercise(id) => id,
81        }
82    }
83
84    #[inline(always)]
85    const fn is_reserved(id: VarInt) -> bool {
86        matches!(id.into_inner(), 0x0 | 0x2 | 0x3 | 0x4 | 0x5)
87    }
88
89    #[inline(always)]
90    const fn is_exercise(id: VarInt) -> bool {
91        id.into_inner() >= 0x21 && ((id.into_inner() - 0x21) % 0x1f == 0)
92    }
93}
94
95/// Collection of settings for an HTTP3 connection.
96#[derive(Clone, Debug)]
97pub struct Settings(HashMap<SettingId, VarInt>);
98
99impl Settings {
100    /// Produces a new [`SettingsBuilder`] for new [`Settings`] construction.
101    pub fn builder() -> SettingsBuilder {
102        SettingsBuilder(Settings::new())
103    }
104
105    /// Constructs [`Settings`] parsing payload of a [`Frame`].
106    ///
107    /// Returns an [`Err`] in case of invalid setting or incomplete payload.
108    ///
109    /// Unknown settings-ids are ignored.
110    ///
111    /// # Panics
112    ///
113    /// Panics if `frame` is not type [`FrameKind::Settings`].
114    pub fn with_frame(frame: &Frame) -> Result<Self, ErrorCode> {
115        assert!(matches!(frame.kind(), FrameKind::Settings));
116
117        let mut settings = Settings::new();
118        let mut buffer_reader = BufferReader::new(frame.payload());
119
120        while buffer_reader.capacity() > 0 {
121            let id = buffer_reader.get_varint().ok_or(ErrorCode::Frame)?;
122            let value = buffer_reader.get_varint().ok_or(ErrorCode::Frame)?;
123
124            // TODO(bfesta): do we need to validate value?
125
126            match SettingId::parse(id) {
127                Ok(setting_id) => match settings.0.entry(setting_id) {
128                    hash_map::Entry::Vacant(slot) => {
129                        slot.insert(value);
130                    }
131                    hash_map::Entry::Occupied(_) => {
132                        return Err(ErrorCode::Settings);
133                    }
134                },
135                Err(ParseError::UnknownSetting) => {}
136                Err(ParseError::ReservedSetting) => return Err(ErrorCode::Settings),
137            }
138        }
139
140        Ok(settings)
141    }
142
143    /// Generates a [`Frame`] with these settings.
144    ///
145    /// This function allocates heap-memory, producing a [`Frame`] with owned payload.
146    /// See [`Self::generate_frame_ref`] for a version without inner memory allocation.
147    pub fn generate_frame(&self) -> Frame {
148        let mut payload = Vec::new();
149
150        for (id, value) in &self.0 {
151            payload.put_varint(id.id()).expect("Vec does not have EOF");
152
153            payload.put_varint(*value).expect("Vec does not have EOF");
154        }
155
156        payload.shrink_to_fit();
157
158        Frame::new_settings(Cow::Owned(payload))
159    }
160
161    /// Generates a [`Frame`] with these settings.
162    ///
163    /// This function does *not* allocates memory. It uses `buffer` for frame-payload
164    /// serialization.
165    /// See [`Self::generate_frame`] for a version with inner memory allocation.
166    pub fn generate_frame_ref<'a>(&self, buffer: &'a mut [u8]) -> Result<Frame<'a>, EndOfBuffer> {
167        let mut bytes_writer = BufferWriter::new(buffer);
168
169        for (id, value) in &self.0 {
170            bytes_writer.put_varint(id.id())?;
171            bytes_writer.put_varint(*value)?;
172        }
173
174        let offset = bytes_writer.offset();
175
176        Ok(Frame::new_settings(Cow::Borrowed(&buffer[..offset])))
177    }
178
179    /// Returns the value of a setting.
180    pub fn get(&self, id: SettingId) -> Option<VarInt> {
181        self.0.get(&id).copied()
182    }
183
184    fn new() -> Self {
185        Self(HashMap::new())
186    }
187}
188
189/// Allows building [`Settings`].
190pub struct SettingsBuilder(Settings);
191
192impl SettingsBuilder {
193    /// Sets the QPACK max dynamic table capacity.
194    pub fn qpack_max_table_capacity(mut self, value: VarInt) -> Self {
195        self.0 .0.insert(SettingId::QPackMaxTableCapacity, value);
196        self
197    }
198
199    /// Sets the upper bound on the number of streams that can be blocked.
200    pub fn qpack_blocked_streams(mut self, value: VarInt) -> Self {
201        self.0 .0.insert(SettingId::QPackBlockedStreams, value);
202        self
203    }
204
205    /// Enables `CONNECT` method.
206    pub fn enable_connect_protocol(mut self) -> Self {
207        self.0
208             .0
209            .insert(SettingId::EnableConnectProtocol, VarInt::from_u32(1));
210        self
211    }
212
213    /// Enables *WebTransport* support.
214    pub fn enable_webtransport(mut self) -> Self {
215        self.0
216             .0
217            .insert(SettingId::EnableWebTransport, VarInt::from_u32(1));
218        self
219    }
220
221    /// Enables HTTP3 datagrams support.
222    pub fn enable_h3_datagrams(mut self) -> Self {
223        self.0 .0.insert(SettingId::H3Datagram, VarInt::from_u32(1));
224        self
225    }
226
227    /// Sets the max number of webtransport sessions server accepts over single HTTP/3 connection.
228    pub fn webtransport_max_sessions(mut self, value: VarInt) -> Self {
229        self.0 .0.insert(SettingId::WebTransportMaxSessions, value);
230        self
231    }
232
233    /// Builds [`Settings`].
234    pub fn build(self) -> Settings {
235        self.0
236    }
237}
238
239mod setting_ids {
240    use crate::varint::VarInt;
241
242    pub const SETTINGS_QPACK_MAX_TABLE_CAPACITY: VarInt = VarInt::from_u32(0x01);
243    pub const SETTINGS_MAX_FIELD_SECTION_SIZE: VarInt = VarInt::from_u32(0x06);
244    pub const SETTINGS_QPACK_BLOCKED_STREAMS: VarInt = VarInt::from_u32(0x07);
245    pub const SETTINGS_ENABLE_CONNECT_PROTOCOL: VarInt = VarInt::from_u32(0x08);
246    pub const SETTINGS_H3_DATAGRAM: VarInt = VarInt::from_u32(0x33);
247    pub const SETTINGS_ENABLE_WEBTRANSPORT: VarInt = VarInt::from_u32(0x2b60_3742);
248    pub const SETTINGS_WEBTRANSPORT_MAX_SESSIONS: VarInt = VarInt::from_u32(0xc671_706a);
249}