Skip to main content

webtrans_proto/
settings.rs

1//! HTTP/3 SETTINGS frame helpers for WebTransport.
2
3use std::{
4    collections::HashMap,
5    fmt::Debug,
6    ops::{Deref, DerefMut},
7    sync::Arc,
8};
9
10use bytes::{Buf, BufMut, BytesMut};
11
12use thiserror::Error;
13use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
14
15use super::{Frame, UniStream, VarInt, VarIntUnexpectedEnd};
16use crate::grease::is_grease_value;
17const MAX_SETTINGS_FRAME_SIZE: usize = 16 * 1024;
18
19#[derive(Clone, Copy, PartialEq, Eq, Hash)]
20/// HTTP/3 SETTINGS identifier.
21pub struct Setting(pub VarInt);
22
23impl Setting {
24    /// Decode a settings identifier.
25    pub fn decode<B: Buf>(buf: &mut B) -> Result<Self, VarIntUnexpectedEnd> {
26        Ok(Setting(VarInt::decode(buf)?))
27    }
28
29    /// Encode a settings identifier.
30    pub fn encode<B: BufMut>(&self, buf: &mut B) {
31        self.0.encode(buf)
32    }
33
34    /// Return the encoded size of this identifier.
35    pub fn size(&self) -> usize {
36        self.0.size()
37    }
38
39    // Reference: https://datatracker.ietf.org/doc/html/rfc9114#section-7.2.4.1
40    /// Return `true` when the setting uses RFC 9114 GREASE spacing.
41    pub fn is_grease(&self) -> bool {
42        is_grease_value(self.0.into_inner())
43    }
44}
45
46impl Debug for Setting {
47    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
48        match *self {
49            Setting::QPACK_MAX_TABLE_CAPACITY => write!(f, "QPACK_MAX_TABLE_CAPACITY"),
50            Setting::MAX_FIELD_SECTION_SIZE => write!(f, "MAX_FIELD_SECTION_SIZE"),
51            Setting::QPACK_BLOCKED_STREAMS => write!(f, "QPACK_BLOCKED_STREAMS"),
52            Setting::ENABLE_CONNECT_PROTOCOL => write!(f, "ENABLE_CONNECT_PROTOCOL"),
53            Setting::ENABLE_DATAGRAM => write!(f, "ENABLE_DATAGRAM"),
54            Setting::ENABLE_DATAGRAM_DEPRECATED => write!(f, "ENABLE_DATAGRAM_DEPRECATED"),
55            Setting::WEBTRANSPORT_ENABLE_DEPRECATED => write!(f, "WEBTRANSPORT_ENABLE_DEPRECATED"),
56            Setting::WEBTRANSPORT_MAX_SESSIONS_DEPRECATED => {
57                write!(f, "WEBTRANSPORT_MAX_SESSIONS_DEPRECATED")
58            }
59            Setting::WEBTRANSPORT_MAX_SESSIONS => write!(f, "WEBTRANSPORT_MAX_SESSIONS"),
60            Setting::WT_ENABLED => write!(f, "WT_ENABLED"),
61            x if x.is_grease() => write!(f, "GREASE SETTING [{:x?}]", x.0.into_inner()),
62            x => write!(f, "UNKNOWN_SETTING [{:x?}]", x.0.into_inner()),
63        }
64    }
65}
66
67impl Setting {
68    /// Build a settings identifier from a known `u32` value.
69    pub const fn from_u32(value: u32) -> Self {
70        Self(VarInt::from_u32(value))
71    }
72
73    // HTTP/3 settings that WebTransport ignores.
74    /// HTTP/3 QPACK dynamic table capacity setting.
75    pub const QPACK_MAX_TABLE_CAPACITY: Setting = Setting::from_u32(0x1); // Default is 0, which disables the dynamic table.
76    /// HTTP/3 maximum header field section size setting.
77    pub const MAX_FIELD_SECTION_SIZE: Setting = Setting::from_u32(0x6);
78    /// HTTP/3 QPACK blocked streams setting.
79    pub const QPACK_BLOCKED_STREAMS: Setting = Setting::from_u32(0x7);
80
81    // Both values are required for WebTransport.
82    /// HTTP/3 extended CONNECT enable flag.
83    pub const ENABLE_CONNECT_PROTOCOL: Setting = Setting::from_u32(0x8);
84    /// HTTP/3 datagram support flag (current).
85    pub const ENABLE_DATAGRAM: Setting = Setting::from_u32(0x33);
86    /// HTTP/3 datagram support flag (deprecated draft value).
87    pub const ENABLE_DATAGRAM_DEPRECATED: Setting = Setting::from_u32(0xFFD277); // Still used by some Chrome versions.
88
89    // Removed in draft-06.
90    /// Draft WebTransport enable flag (deprecated).
91    pub const WEBTRANSPORT_ENABLE_DEPRECATED: Setting = Setting::from_u32(0x2b603742);
92    /// Draft maximum WebTransport sessions setting (deprecated).
93    pub const WEBTRANSPORT_MAX_SESSIONS_DEPRECATED: Setting = Setting::from_u32(0x2b603743);
94
95    // Current way to enable WebTransport.
96    /// Current WebTransport maximum sessions setting.
97    pub const WEBTRANSPORT_MAX_SESSIONS: Setting = Setting::from_u32(0xc671706a);
98    /// WebTransport over HTTP/3 draft-16 enable flag.
99    pub const WT_ENABLED: Setting = Setting::from_u32(0x2c7cf000);
100}
101
102#[derive(Error, Debug, Clone)]
103/// Errors returned while encoding or decoding SETTINGS exchanges.
104pub enum SettingsError {
105    /// Input ended before a full SETTINGS payload was available.
106    #[error("unexpected end of input")]
107    UnexpectedEnd,
108
109    /// First unidirectional stream type was not the expected control stream.
110    #[error("unexpected stream type {0:?}")]
111    UnexpectedStreamType(UniStream),
112
113    /// Frame type was not SETTINGS.
114    #[error("unexpected frame {0:?}")]
115    UnexpectedFrame(Frame),
116
117    /// Invalid varint or truncated settings pair inside a frame payload.
118    #[error("invalid size")]
119    InvalidSize,
120
121    /// A SETTINGS identifier occurred more than once in the same frame.
122    #[error("duplicate setting {0:?}")]
123    DuplicateSetting(Setting),
124
125    /// A boolean SETTINGS value was neither zero nor one.
126    #[error("invalid value {1} for setting {0:?}")]
127    InvalidValue(Setting, VarInt),
128
129    /// SETTINGS frame exceeded the implementation's bounded decode limit.
130    #[error("SETTINGS frame exceeds the 16 KiB implementation limit")]
131    MessageTooLong,
132
133    /// I/O error while reading or writing the SETTINGS exchange.
134    #[error("io error: {0}")]
135    Io(Arc<std::io::Error>),
136}
137
138impl From<std::io::Error> for SettingsError {
139    fn from(err: std::io::Error) -> Self {
140        SettingsError::Io(Arc::new(err))
141    }
142}
143
144// A map of SETTINGS identifiers to values.
145#[derive(Default, Debug)]
146/// Parsed HTTP/3 settings map keyed by [`Setting`].
147pub struct Settings(HashMap<Setting, VarInt>);
148
149impl Settings {
150    /// Decode a control stream prefix and SETTINGS frame from an in-memory buffer.
151    pub fn decode<B: Buf>(buf: &mut B) -> Result<Self, SettingsError> {
152        let typ = UniStream::decode(buf).map_err(|_| SettingsError::UnexpectedEnd)?;
153        if typ != UniStream::CONTROL {
154            return Err(SettingsError::UnexpectedStreamType(typ));
155        }
156
157        let (typ, mut data) = Frame::read(buf).map_err(|_| SettingsError::UnexpectedEnd)?;
158        if typ != Frame::SETTINGS {
159            return Err(SettingsError::UnexpectedFrame(typ));
160        }
161
162        let mut settings = Settings::default();
163        while data.has_remaining() {
164            // Use InvalidSize because retrying will not help.
165            let id = Setting::decode(&mut data).map_err(|_| SettingsError::InvalidSize)?;
166            let value = VarInt::decode(&mut data).map_err(|_| SettingsError::InvalidSize)?;
167            // Only retain non-GREASE entries.
168            if !id.is_grease() {
169                if settings.0.contains_key(&id) {
170                    return Err(SettingsError::DuplicateSetting(id));
171                }
172                if matches!(
173                    id,
174                    Setting::ENABLE_CONNECT_PROTOCOL
175                        | Setting::ENABLE_DATAGRAM
176                        | Setting::ENABLE_DATAGRAM_DEPRECATED
177                        | Setting::WEBTRANSPORT_ENABLE_DEPRECATED
178                        | Setting::WT_ENABLED
179                ) && value.into_inner() > 1
180                {
181                    return Err(SettingsError::InvalidValue(id, value));
182                }
183                settings.0.insert(id, value);
184            }
185        }
186
187        Ok(settings)
188    }
189
190    /// Read and decode one SETTINGS exchange from an async stream.
191    pub async fn read<S: AsyncRead + Unpin>(stream: &mut S) -> Result<Self, SettingsError> {
192        let stream_type = VarInt::read(stream)
193            .await
194            .map_err(|_| SettingsError::UnexpectedEnd)?;
195        let stream_type = UniStream(stream_type);
196        if stream_type != UniStream::CONTROL {
197            return Err(SettingsError::UnexpectedStreamType(stream_type));
198        }
199
200        loop {
201            let typ = VarInt::read(stream)
202                .await
203                .map_err(|_| SettingsError::UnexpectedEnd)?;
204            let length = VarInt::read(stream)
205                .await
206                .map_err(|_| SettingsError::UnexpectedEnd)?;
207            let length =
208                usize::try_from(length.into_inner()).map_err(|_| SettingsError::MessageTooLong)?;
209            if length > MAX_SETTINGS_FRAME_SIZE {
210                return Err(SettingsError::MessageTooLong);
211            }
212
213            let mut payload = vec![0; length];
214            stream.read_exact(&mut payload).await?;
215            let typ = Frame(typ);
216            if typ.is_grease() {
217                continue;
218            }
219
220            let mut frame =
221                Vec::with_capacity(stream_type.0.size() + typ.0.size() + VarInt::MAX_SIZE + length);
222            stream_type.encode(&mut frame);
223            typ.encode(&mut frame);
224            VarInt::try_from(length)
225                .map_err(|_| SettingsError::MessageTooLong)?
226                .encode(&mut frame);
227            frame.extend_from_slice(&payload);
228            return Self::decode(&mut frame.as_slice());
229        }
230    }
231
232    /// Encode this settings map as a control stream prefix followed by a SETTINGS frame.
233    pub fn encode<B: BufMut>(&self, buf: &mut B) {
234        UniStream::CONTROL.encode(buf);
235        Frame::SETTINGS.encode(buf);
236
237        let payload_len = self.payload_len();
238        VarInt::try_from(payload_len as u64)
239            .expect("settings payload length exceeds VarInt bounds")
240            .encode(buf);
241
242        for (id, value) in &self.0 {
243            id.encode(buf);
244            value.encode(buf);
245        }
246    }
247
248    /// Encode and write this settings map to an async stream.
249    pub async fn write<S: AsyncWrite + Unpin>(&self, stream: &mut S) -> Result<(), SettingsError> {
250        let mut buf = BytesMut::with_capacity(self.encoded_len());
251        self.encode(&mut buf);
252        stream.write_all_buf(&mut buf).await?;
253        Ok(())
254    }
255
256    /// Enable WebTransport settings, including deprecated parameters for compatibility.
257    pub fn enable_webtransport(&mut self, max_sessions: u32) {
258        self.enable_webtransport_internal(max_sessions, true);
259    }
260
261    /// Enable WebTransport settings without deprecated draft parameters.
262    pub fn enable_webtransport_latest(&mut self, max_sessions: u32) {
263        self.enable_webtransport_internal(max_sessions, false);
264    }
265
266    fn enable_webtransport_internal(&mut self, max_sessions: u32, include_deprecated: bool) {
267        let max = VarInt::from_u32(max_sessions);
268
269        self.insert(Setting::ENABLE_CONNECT_PROTOCOL, VarInt::from_u32(1));
270        self.insert(Setting::ENABLE_DATAGRAM, VarInt::from_u32(1));
271        self.insert(Setting::ENABLE_DATAGRAM_DEPRECATED, VarInt::from_u32(1));
272        self.insert(Setting::WT_ENABLED, VarInt::from_u32(1));
273        self.insert(Setting::WEBTRANSPORT_MAX_SESSIONS, max);
274
275        if include_deprecated {
276            self.insert(Setting::WEBTRANSPORT_MAX_SESSIONS_DEPRECATED, max);
277            self.insert(Setting::WEBTRANSPORT_ENABLE_DEPRECATED, VarInt::from_u32(1));
278        } else {
279            self.0
280                .remove(&Setting::WEBTRANSPORT_MAX_SESSIONS_DEPRECATED);
281            self.0.remove(&Setting::WEBTRANSPORT_ENABLE_DEPRECATED);
282        }
283    }
284
285    // Return the maximum number of sessions supported.
286    /// Return the peer-advertised maximum number of WebTransport sessions, or `0` when unsupported.
287    pub fn supports_webtransport(&self) -> u64 {
288        let enabled = self
289            .get(&Setting::WT_ENABLED)
290            .map(|value| value.into_inner());
291        if enabled == Some(1)
292            && self.get(&Setting::ENABLE_DATAGRAM).map(|v| v.into_inner()) == Some(1)
293        {
294            return 1;
295        }
296
297        // Observed from Chrome 114.0.5735.198 (July 19, 2023).
298        // Setting(1): 65536,              // qpack_max_table_capacity
299        // Setting(6): 16384,              // max_field_section_size
300        // Setting(7): 100,                // qpack_blocked_streams
301        // Setting(51): 1,                 // enable_datagram
302        // Setting(16765559): 1            // enable_datagram_deprecated
303        // Setting(727725890): 1,          // webtransport_max_sessions_deprecated
304        // Setting(4445614305): 454654587, // grease
305
306        // NOTE: The presence of ENABLE_WEBTRANSPORT implies ENABLE_CONNECT is supported.
307
308        let datagram = self
309            .get(&Setting::ENABLE_DATAGRAM)
310            .or(self.get(&Setting::ENABLE_DATAGRAM_DEPRECATED))
311            .map(|v| v.into_inner());
312
313        if datagram != Some(1) {
314            return 0;
315        }
316
317        // Before draft-07, enabling WebTransport used two parameters: ENABLE=1 and MAX_SESSIONS=N.
318        // The modern approach uses MAX_SESSIONS alone, where non-zero means enabled.
319
320        if let Some(max) = self.get(&Setting::WEBTRANSPORT_MAX_SESSIONS) {
321            return max.into_inner();
322        }
323
324        let enabled = self
325            .get(&Setting::WEBTRANSPORT_ENABLE_DEPRECATED)
326            .map(|v| v.into_inner());
327        if enabled != Some(1) {
328            return 0;
329        }
330
331        // Only the server may set this value; default to 1 if absent.
332        self.get(&Setting::WEBTRANSPORT_MAX_SESSIONS_DEPRECATED)
333            .map(|v| v.into_inner())
334            .unwrap_or(1)
335    }
336
337    /// Return whether settings received from a server satisfy current
338    /// WebTransport negotiation requirements.
339    pub fn supports_webtransport_server(&self) -> bool {
340        (self.get(&Setting::WT_ENABLED).map(|v| v.into_inner()) == Some(1)
341            && self
342                .get(&Setting::ENABLE_CONNECT_PROTOCOL)
343                .map(|v| v.into_inner())
344                == Some(1)
345            && self.get(&Setting::ENABLE_DATAGRAM).map(|v| v.into_inner()) == Some(1))
346            || (self.supports_webtransport() > 0 && self.get(&Setting::WT_ENABLED).is_none())
347    }
348
349    /// Return whether settings received from a client satisfy current
350    /// WebTransport draft negotiation requirements.
351    pub fn supports_webtransport_client(&self) -> bool {
352        (self.get(&Setting::WT_ENABLED).map(|v| v.into_inner()) == Some(1)
353            && self.get(&Setting::ENABLE_DATAGRAM).map(|v| v.into_inner()) == Some(1))
354            || (self.supports_webtransport() > 0 && self.get(&Setting::WT_ENABLED).is_none())
355    }
356
357    fn payload_len(&self) -> usize {
358        self.0
359            .iter()
360            .map(|(id, value)| id.size() + value.size())
361            .sum()
362    }
363
364    fn encoded_len(&self) -> usize {
365        let payload_len = self.payload_len();
366        UniStream::CONTROL.0.size()
367            + Frame::SETTINGS.0.size()
368            + VarInt::try_from(payload_len as u64)
369                .expect("settings payload length exceeds VarInt bounds")
370                .size()
371            + payload_len
372    }
373}
374
375impl Deref for Settings {
376    type Target = HashMap<Setting, VarInt>;
377
378    fn deref(&self) -> &Self::Target {
379        &self.0
380    }
381}
382
383impl DerefMut for Settings {
384    fn deref_mut(&mut self) -> &mut Self::Target {
385        &mut self.0
386    }
387}
388
389#[cfg(test)]
390mod tests {
391    use super::*;
392
393    #[test]
394    fn latest_settings_enable_webtransport() {
395        let mut settings = Settings::default();
396        settings.enable_webtransport_latest(1);
397
398        assert_eq!(settings.supports_webtransport(), 1);
399        assert_eq!(
400            settings.get(&Setting::WT_ENABLED),
401            Some(&VarInt::from_u32(1))
402        );
403    }
404
405    #[test]
406    fn rejects_duplicate_settings() {
407        let mut data = Vec::new();
408        UniStream::CONTROL.encode(&mut data);
409        Frame::SETTINGS.encode(&mut data);
410        VarInt::from_u32(4).encode(&mut data);
411        Setting::ENABLE_DATAGRAM.encode(&mut data);
412        VarInt::from_u32(1).encode(&mut data);
413        Setting::ENABLE_DATAGRAM.encode(&mut data);
414        VarInt::from_u32(1).encode(&mut data);
415
416        let error = Settings::decode(&mut data.as_slice()).unwrap_err();
417        assert!(matches!(
418            error,
419            SettingsError::DuplicateSetting(Setting::ENABLE_DATAGRAM)
420        ));
421    }
422
423    #[test]
424    fn rejects_non_boolean_enabled_value() {
425        let mut data = Vec::new();
426        UniStream::CONTROL.encode(&mut data);
427        Frame::SETTINGS.encode(&mut data);
428        VarInt::from_u32(5).encode(&mut data);
429        Setting::WT_ENABLED.encode(&mut data);
430        VarInt::from_u32(2).encode(&mut data);
431
432        let error = Settings::decode(&mut data.as_slice()).unwrap_err();
433        assert!(matches!(
434            error,
435            SettingsError::InvalidValue(Setting::WT_ENABLED, _)
436        ));
437    }
438
439    #[test]
440    fn server_settings_require_extended_connect() {
441        let mut settings = Settings::default();
442        settings.insert(Setting::WT_ENABLED, VarInt::from_u32(1));
443        settings.insert(Setting::ENABLE_DATAGRAM, VarInt::from_u32(1));
444
445        assert!(settings.supports_webtransport_client());
446        assert!(!settings.supports_webtransport_server());
447
448        settings.insert(Setting::ENABLE_CONNECT_PROTOCOL, VarInt::from_u32(1));
449        assert!(settings.supports_webtransport_server());
450    }
451}