Skip to main content

net_wire/http3/
settings.rs

1//! HTTP/3 SETTINGS wire views, construction, and semantic interpretation.
2
3use core::fmt;
4use core::iter::FusedIterator;
5
6use crate::quic::varint::{QuicVarInt, QuicVarIntBuildError};
7
8use super::codepoints::{Http3FrameType, Http3SettingId};
9use super::frame::{
10    Http3Frame, Http3FrameBuildError, Http3FrameBuilder, Http3FramePayloadField,
11    Http3FramePayloadParseError, encode_varint, parse_payload_varint, validate_type,
12};
13
14/// Failure to build an outbound HTTP/3 SETTINGS frame.
15#[derive(Clone, Copy, Debug, Eq, PartialEq)]
16pub enum Http3SettingsBuildError {
17    /// Building the raw HTTP/3 frame envelope failed.
18    Frame(Http3FrameBuildError),
19    /// A SETTINGS field cannot be canonically encoded as a QUIC variable-length integer.
20    SettingVarInt {
21        /// Field whose value could not be encoded.
22        field: Http3FramePayloadField,
23        /// Underlying QUIC variable-length integer failure.
24        error: QuicVarIntBuildError,
25    },
26    /// Adding a SETTINGS entry component overflowed `usize`.
27    PayloadLengthOverflow {
28        /// Index of the SETTINGS entry being accumulated.
29        index: usize,
30        /// Accumulated payload length before the addition.
31        current: usize,
32        /// Component length being added.
33        addition: usize,
34    },
35    /// The caller-provided SETTINGS workspace is too short.
36    ScratchTooShort {
37        /// Bytes required for the canonical SETTINGS payload.
38        required: usize,
39        /// Bytes available in the caller workspace.
40        available: usize,
41    },
42    /// The outgoing SETTINGS list contains the same identifier more than once.
43    DuplicateSetting {
44        /// Repeated raw setting identifier.
45        id: Http3SettingId,
46    },
47    /// The outgoing SETTINGS list contains an HTTP/2-only reserved identifier.
48    ProhibitedSetting {
49        /// Reserved raw setting identifier.
50        id: Http3SettingId,
51    },
52}
53
54impl fmt::Display for Http3SettingsBuildError {
55    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
56        match self {
57            Self::Frame(error) => write!(f, "HTTP/3 frame: {error}"),
58            Self::SettingVarInt { field, error } => {
59                write!(f, "HTTP/3 {field} variable-length integer: {error}")
60            }
61            Self::PayloadLengthOverflow {
62                index,
63                current,
64                addition,
65            } => write!(
66                f,
67                "HTTP/3 SETTINGS payload length overflows at entry {index}: {current} plus {addition} bytes"
68            ),
69            Self::ScratchTooShort {
70                required,
71                available,
72            } => write!(
73                f,
74                "HTTP/3 SETTINGS workspace is too short: need {required} bytes, have {available}"
75            ),
76            Self::DuplicateSetting { id } => {
77                write!(
78                    f,
79                    "HTTP/3 SETTINGS contains duplicate identifier {}",
80                    id.value()
81                )
82            }
83            Self::ProhibitedSetting { id } => write!(
84                f,
85                "HTTP/3 SETTINGS identifier {} is reserved for HTTP/2",
86                id.value()
87            ),
88        }
89    }
90}
91
92impl core::error::Error for Http3SettingsBuildError {
93    fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
94        match self {
95            Self::Frame(error) => Some(error),
96            Self::SettingVarInt { .. }
97            | Self::PayloadLengthOverflow { .. }
98            | Self::ScratchTooShort { .. }
99            | Self::DuplicateSetting { .. }
100            | Self::ProhibitedSetting { .. } => None,
101        }
102    }
103}
104/// A raw-preserving HTTP/3 SETTINGS parameter.
105#[derive(Clone, Copy, Debug, Eq, PartialEq)]
106pub struct Http3Setting<'a> {
107    bytes: &'a [u8],
108    id: QuicVarInt<'a>,
109    value: QuicVarInt<'a>,
110}
111
112impl<'a> Http3Setting<'a> {
113    /// Returns the exact encoded setting bytes.
114    pub const fn as_bytes(&self) -> &'a [u8] {
115        self.bytes
116    }
117
118    /// Returns the raw-preserving setting identifier.
119    pub const fn id(&self) -> Http3SettingId {
120        Http3SettingId::new(self.id.value())
121    }
122
123    /// Returns the exact setting identifier variable-length integer.
124    pub const fn id_varint(&self) -> QuicVarInt<'a> {
125        self.id
126    }
127
128    /// Returns the decoded setting value.
129    pub const fn value(&self) -> u64 {
130        self.value.value()
131    }
132
133    /// Returns the exact setting value variable-length integer.
134    pub const fn value_varint(&self) -> QuicVarInt<'a> {
135        self.value
136    }
137}
138
139/// A validated borrowed HTTP/3 SETTINGS frame.
140#[derive(Clone, Copy, Debug, Eq, PartialEq)]
141pub struct Http3Settings<'a> {
142    frame: Http3Frame<'a>,
143}
144
145impl<'a> Http3Settings<'a> {
146    /// Assembles a SETTINGS view from a builder-validated frame.
147    const fn from_validated(frame: Http3Frame<'a>) -> Self {
148        Self { frame }
149    }
150
151    /// Parses the first complete SETTINGS frame, enforcing the caller-provided payload maximum.
152    pub fn parse(
153        bytes: &'a [u8],
154        maximum_payload: usize,
155    ) -> Result<Self, Http3FramePayloadParseError> {
156        Self::from_frame(
157            Http3Frame::parse(bytes, maximum_payload)
158                .map_err(Http3FramePayloadParseError::Frame)?,
159        )
160    }
161
162    /// Validates every SETTINGS identifier and value in wire order.
163    pub fn from_frame(frame: Http3Frame<'a>) -> Result<Self, Http3FramePayloadParseError> {
164        validate_type(frame, Http3FrameType::SETTINGS)?;
165        validate_settings(frame.payload())?;
166        Ok(Self { frame })
167    }
168
169    /// Returns the validated raw frame.
170    pub const fn frame(&self) -> Http3Frame<'a> {
171        self.frame
172    }
173
174    /// Returns the exact represented frame bytes.
175    pub const fn as_bytes(&self) -> &'a [u8] {
176        self.frame.as_bytes()
177    }
178
179    /// Iterates validated SETTINGS parameters in wire order without combining duplicates.
180    pub fn settings(&self) -> Http3SettingsIter<'a> {
181        Http3SettingsIter {
182            bytes: self.frame.payload(),
183        }
184    }
185
186    /// Strictly validates SETTINGS semantics and returns effective known peer settings.
187    ///
188    /// Unknown extension settings are ignored in the returned snapshot. This rejects duplicate
189    /// identifiers even though HTTP/3 permits receivers to choose that policy.
190    pub fn validate_semantics(self) -> Result<Http3PeerSettings, Http3SettingsSemanticError> {
191        validate(self)
192    }
193}
194
195/// Iterator over eagerly validated HTTP/3 SETTINGS parameters.
196#[derive(Clone, Debug)]
197pub struct Http3SettingsIter<'a> {
198    bytes: &'a [u8],
199}
200
201impl<'a> Iterator for Http3SettingsIter<'a> {
202    type Item = Http3Setting<'a>;
203
204    fn next(&mut self) -> Option<Self::Item> {
205        let (setting, remaining) = parse_setting(self.bytes)?;
206        self.bytes = remaining;
207        Some(setting)
208    }
209}
210
211impl FusedIterator for Http3SettingsIter<'_> {}
212
213fn validate_settings(payload: &[u8]) -> Result<(), Http3FramePayloadParseError> {
214    let mut remaining = payload;
215    while !remaining.is_empty() {
216        let identifier_offset = payload.len() - remaining.len();
217        let identifier = parse_payload_varint(
218            remaining,
219            Http3FramePayloadField::SettingIdentifier,
220            identifier_offset,
221        )?;
222        remaining = &remaining[identifier.byte_len()..];
223
224        let value_offset = payload.len() - remaining.len();
225        let value = parse_payload_varint(
226            remaining,
227            Http3FramePayloadField::SettingValue,
228            value_offset,
229        )?;
230        remaining = &remaining[value.byte_len()..];
231    }
232    Ok(())
233}
234
235fn parse_setting<'a>(bytes: &'a [u8]) -> Option<(Http3Setting<'a>, &'a [u8])> {
236    let id = QuicVarInt::parse(bytes).ok()?;
237    let after_id = &bytes[id.byte_len()..];
238    let value = QuicVarInt::parse(after_id).ok()?;
239    let remaining = &after_id[value.byte_len()..];
240    let setting = Http3Setting {
241        bytes: &bytes[..bytes.len() - remaining.len()],
242        id,
243        value,
244    };
245    Some((setting, remaining))
246}
247
248/// A raw-preserving outbound HTTP/3 SETTINGS identifier and value.
249#[derive(Clone, Copy, Debug, Eq, PartialEq)]
250pub struct Http3SettingValue {
251    id: Http3SettingId,
252    value: u64,
253}
254
255impl Http3SettingValue {
256    /// Creates an outbound SETTINGS entry without restricting extension identifiers.
257    pub const fn new(id: Http3SettingId, value: u64) -> Self {
258        Self { id, value }
259    }
260
261    /// Returns the raw SETTINGS identifier.
262    pub const fn id(self) -> Http3SettingId {
263        self.id
264    }
265
266    /// Returns the decoded SETTINGS value.
267    pub const fn value(self) -> u64 {
268        self.value
269    }
270}
271
272/// Builds a typed HTTP/3 SETTINGS frame in caller-owned storage.
273pub struct Http3SettingsBuilder<'output, 'scratch, 'settings> {
274    destination: &'output mut [u8],
275    payload_scratch: &'scratch mut [u8],
276    settings: &'settings [Http3SettingValue],
277}
278
279impl<'output, 'scratch, 'settings> Http3SettingsBuilder<'output, 'scratch, 'settings> {
280    /// Creates a SETTINGS builder using caller-owned payload workspace.
281    ///
282    /// Workspace contents are unspecified after an error that occurs after its capacity has been
283    /// validated. Destination bytes remain unchanged on every error.
284    pub fn new(
285        destination: &'output mut [u8],
286        payload_scratch: &'scratch mut [u8],
287        settings: &'settings [Http3SettingValue],
288    ) -> Self {
289        Self {
290            destination,
291            payload_scratch,
292            settings,
293        }
294    }
295
296    /// Canonically encodes semantically valid SETTINGS and atomically writes their frame.
297    pub fn build(self) -> Result<Http3Settings<'output>, Http3SettingsBuildError> {
298        let payload_length = validate_outbound_settings(self.settings)?;
299        if self.payload_scratch.len() < payload_length {
300            return Err(Http3SettingsBuildError::ScratchTooShort {
301                required: payload_length,
302                available: self.payload_scratch.len(),
303            });
304        }
305
306        let mut offset = 0;
307        for setting in self.settings {
308            let id = encode_varint(setting.id.value()).map_err(|error| {
309                Http3SettingsBuildError::SettingVarInt {
310                    field: Http3FramePayloadField::SettingIdentifier,
311                    error,
312                }
313            })?;
314            let value = encode_varint(setting.value).map_err(|error| {
315                Http3SettingsBuildError::SettingVarInt {
316                    field: Http3FramePayloadField::SettingValue,
317                    error,
318                }
319            })?;
320            let id_end = offset + id.byte_len();
321            self.payload_scratch[offset..id_end].copy_from_slice(id.bytes());
322            let value_end = id_end + value.byte_len();
323            self.payload_scratch[id_end..value_end].copy_from_slice(value.bytes());
324            offset = value_end;
325        }
326
327        let frame = Http3FrameBuilder::new(
328            self.destination,
329            Http3FrameType::SETTINGS,
330            &self.payload_scratch[..payload_length],
331        )
332        .build()
333        .map_err(Http3SettingsBuildError::Frame)?;
334        Ok(Http3Settings::from_validated(frame))
335    }
336}
337
338fn validate_outbound_settings(
339    settings: &[Http3SettingValue],
340) -> Result<usize, Http3SettingsBuildError> {
341    let mut payload_length = 0;
342    for (index, setting) in settings.iter().copied().enumerate() {
343        if prohibited_setting(setting.id) {
344            return Err(Http3SettingsBuildError::ProhibitedSetting { id: setting.id });
345        }
346        for previous in &settings[..index] {
347            if previous.id == setting.id {
348                return Err(Http3SettingsBuildError::DuplicateSetting { id: setting.id });
349            }
350        }
351        let id = encode_varint(setting.id.value()).map_err(|error| {
352            Http3SettingsBuildError::SettingVarInt {
353                field: Http3FramePayloadField::SettingIdentifier,
354                error,
355            }
356        })?;
357        let value = encode_varint(setting.value).map_err(|error| {
358            Http3SettingsBuildError::SettingVarInt {
359                field: Http3FramePayloadField::SettingValue,
360                error,
361            }
362        })?;
363        payload_length = add_setting_payload(index, payload_length, id.byte_len())?;
364        payload_length = add_setting_payload(index, payload_length, value.byte_len())?;
365    }
366    Ok(payload_length)
367}
368
369fn add_setting_payload(
370    index: usize,
371    current: usize,
372    addition: usize,
373) -> Result<usize, Http3SettingsBuildError> {
374    current
375        .checked_add(addition)
376        .ok_or(Http3SettingsBuildError::PayloadLengthOverflow {
377            index,
378            current,
379            addition,
380        })
381}
382
383/// Failure to strictly validate HTTP/3 SETTINGS semantics.
384///
385/// Offsets are byte offsets of setting identifiers from the start of the SETTINGS payload.
386#[derive(Clone, Copy, Debug, Eq, PartialEq)]
387pub enum Http3SettingsSemanticError {
388    /// A SETTINGS identifier repeats an earlier identifier.
389    DuplicateSetting {
390        /// Repeated raw setting identifier.
391        id: Http3SettingId,
392        /// Byte offset of this setting identifier within the SETTINGS payload.
393        offset: usize,
394    },
395    /// A SETTINGS identifier is reserved for HTTP/2.
396    ProhibitedSetting {
397        /// Reserved raw setting identifier.
398        id: Http3SettingId,
399        /// Byte offset of this setting identifier within the SETTINGS payload.
400        offset: usize,
401    },
402}
403
404impl fmt::Display for Http3SettingsSemanticError {
405    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
406        match self {
407            Self::DuplicateSetting { id, offset } => write!(
408                f,
409                "HTTP/3 SETTINGS identifier {} at payload offset {offset} duplicates an earlier identifier",
410                id.value()
411            ),
412            Self::ProhibitedSetting { id, offset } => write!(
413                f,
414                "HTTP/3 SETTINGS identifier {} at payload offset {offset} is reserved for HTTP/2",
415                id.value()
416            ),
417        }
418    }
419}
420
421impl core::error::Error for Http3SettingsSemanticError {}
422
423/// Validates SETTINGS semantics and returns the effective known peer settings.
424pub(super) fn validate(
425    settings: Http3Settings<'_>,
426) -> Result<Http3PeerSettings, Http3SettingsSemanticError> {
427    let mut peer_settings = Http3PeerSettings::default();
428    let mut offset = 0;
429
430    for (index, setting) in settings.settings().enumerate() {
431        let id = setting.id();
432        if prohibited_setting(id) {
433            return Err(Http3SettingsSemanticError::ProhibitedSetting { id, offset });
434        }
435        for previous in settings.settings().take(index) {
436            if previous.id() == id {
437                return Err(Http3SettingsSemanticError::DuplicateSetting { id, offset });
438            }
439        }
440
441        match id {
442            Http3SettingId::QPACK_MAX_TABLE_CAPACITY => {
443                peer_settings.qpack_max_table_capacity = setting.value();
444            }
445            Http3SettingId::MAX_FIELD_SECTION_SIZE => {
446                peer_settings.maximum_field_section_size = Some(setting.value());
447            }
448            Http3SettingId::QPACK_BLOCKED_STREAMS => {
449                peer_settings.qpack_blocked_streams = setting.value();
450            }
451            _ => {}
452        }
453
454        // Settings are disjoint subslices of one validated payload, so this cannot overflow.
455        if let Some(next_offset) = offset.checked_add(setting.as_bytes().len()) {
456            offset = next_offset;
457        }
458    }
459
460    Ok(peer_settings)
461}
462
463/// Returns whether an identifier is reserved for HTTP/2 and prohibited in HTTP/3 SETTINGS.
464pub(super) const fn prohibited_setting(id: Http3SettingId) -> bool {
465    matches!(id.value(), 0x00 | 0x02 | 0x03 | 0x04 | 0x05)
466}
467
468/// Effective known SETTINGS received from an HTTP/3 peer.
469#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
470pub struct Http3PeerSettings {
471    qpack_max_table_capacity: u64,
472    maximum_field_section_size: Option<u64>,
473    qpack_blocked_streams: u64,
474}
475
476impl Http3PeerSettings {
477    /// Returns SETTINGS_QPACK_MAX_TABLE_CAPACITY, defaulting to zero.
478    pub const fn qpack_max_table_capacity(self) -> u64 {
479        self.qpack_max_table_capacity
480    }
481
482    /// Returns SETTINGS_MAX_FIELD_SECTION_SIZE, or `None` for the unlimited default.
483    pub const fn maximum_field_section_size(self) -> Option<u64> {
484        self.maximum_field_section_size
485    }
486
487    /// Returns SETTINGS_QPACK_BLOCKED_STREAMS, defaulting to zero.
488    pub const fn qpack_blocked_streams(self) -> u64 {
489        self.qpack_blocked_streams
490    }
491}
492
493#[cfg(test)]
494mod tests {
495    use super::*;
496
497    #[test]
498    fn setting_payload_length_overflow_preserves_entry_index() {
499        assert_eq!(
500            add_setting_payload(7, usize::MAX, 1),
501            Err(Http3SettingsBuildError::PayloadLengthOverflow {
502                index: 7,
503                current: usize::MAX,
504                addition: 1,
505            })
506        );
507    }
508}