Skip to main content

ringline_h2/
settings.rs

1//! HTTP/2 SETTINGS parameters (RFC 7540 Section 6.5.1).
2
3use crate::error::H2Error;
4
5// Settings identifiers.
6const SETTINGS_HEADER_TABLE_SIZE: u16 = 0x1;
7const SETTINGS_ENABLE_PUSH: u16 = 0x2;
8const SETTINGS_MAX_CONCURRENT_STREAMS: u16 = 0x3;
9const SETTINGS_INITIAL_WINDOW_SIZE: u16 = 0x4;
10const SETTINGS_MAX_FRAME_SIZE: u16 = 0x5;
11const SETTINGS_MAX_HEADER_LIST_SIZE: u16 = 0x6;
12
13/// Hard ceiling on `header_table_size` we accept from a peer's SETTINGS,
14/// even if the peer advertises a larger value. The HPACK dynamic table
15/// uses ~32 bytes of accounting per entry plus the entry bytes themselves,
16/// so unbounded growth here is a DoS vector. 64 KiB lets `4096`-sized
17/// tables (the default) keep working while bounding worst case.
18pub const MAX_PEER_HEADER_TABLE_SIZE: u32 = 65536;
19
20/// Default value for `max_header_list_size` (RFC 7540 ยง6.5.2 leaves this
21/// unbounded by default, but advertising a finite value is the only way to
22/// stop a peer from amplifying small encoded frames into multi-megabyte
23/// decoded header lists). 256 KiB matches the H3 default in ringline-h3.
24pub const DEFAULT_MAX_HEADER_LIST_SIZE: u32 = 262_144;
25
26/// HTTP/2 SETTINGS parameters.
27#[derive(Debug, Clone)]
28pub struct Settings {
29    /// SETTINGS_HEADER_TABLE_SIZE (0x1). Default 4096.
30    pub header_table_size: u32,
31    /// SETTINGS_ENABLE_PUSH (0x2). Default 1 (enabled).
32    pub enable_push: bool,
33    /// SETTINGS_MAX_CONCURRENT_STREAMS (0x3). Default unlimited.
34    pub max_concurrent_streams: Option<u32>,
35    /// SETTINGS_INITIAL_WINDOW_SIZE (0x4). Default 65535.
36    pub initial_window_size: u32,
37    /// SETTINGS_MAX_FRAME_SIZE (0x5). Default 16384.
38    pub max_frame_size: u32,
39    /// SETTINGS_MAX_HEADER_LIST_SIZE (0x6). Default unlimited.
40    pub max_header_list_size: Option<u32>,
41}
42
43impl Default for Settings {
44    fn default() -> Self {
45        Self {
46            header_table_size: 4096,
47            enable_push: true,
48            max_concurrent_streams: None,
49            initial_window_size: 65535,
50            max_frame_size: 16384,
51            max_header_list_size: Some(DEFAULT_MAX_HEADER_LIST_SIZE),
52        }
53    }
54}
55
56impl Settings {
57    /// Client defaults: push disabled (ENABLE_PUSH=0).
58    pub fn client_default() -> Self {
59        Self {
60            enable_push: false,
61            ..Default::default()
62        }
63    }
64
65    /// Encode settings as a sequence of 6-byte (id: u16, value: u32) pairs.
66    pub fn encode_to_vec(&self) -> Vec<u8> {
67        let mut buf = Vec::new();
68        self.encode(&mut buf);
69        buf
70    }
71
72    /// Encode settings into `buf`.
73    pub fn encode(&self, buf: &mut Vec<u8>) {
74        encode_setting(buf, SETTINGS_HEADER_TABLE_SIZE, self.header_table_size);
75        encode_setting(
76            buf,
77            SETTINGS_ENABLE_PUSH,
78            if self.enable_push { 1 } else { 0 },
79        );
80        if let Some(v) = self.max_concurrent_streams {
81            encode_setting(buf, SETTINGS_MAX_CONCURRENT_STREAMS, v);
82        }
83        encode_setting(buf, SETTINGS_INITIAL_WINDOW_SIZE, self.initial_window_size);
84        encode_setting(buf, SETTINGS_MAX_FRAME_SIZE, self.max_frame_size);
85        if let Some(v) = self.max_header_list_size {
86            encode_setting(buf, SETTINGS_MAX_HEADER_LIST_SIZE, v);
87        }
88    }
89
90    /// Decode settings from a byte buffer of 6-byte pairs.
91    pub fn decode(buf: &[u8]) -> Result<Self, H2Error> {
92        if !buf.len().is_multiple_of(6) {
93            return Err(H2Error::FrameSizeError);
94        }
95        let mut settings = Settings::default();
96        let mut pos = 0;
97        while pos + 6 <= buf.len() {
98            let id = (u16::from(buf[pos]) << 8) | u16::from(buf[pos + 1]);
99            let value = (u32::from(buf[pos + 2]) << 24)
100                | (u32::from(buf[pos + 3]) << 16)
101                | (u32::from(buf[pos + 4]) << 8)
102                | u32::from(buf[pos + 5]);
103            pos += 6;
104            match id {
105                SETTINGS_HEADER_TABLE_SIZE => {
106                    // Cap what we actually honour at MAX_PEER_HEADER_TABLE_SIZE,
107                    // regardless of what the peer advertised. The peer can't
108                    // force us to allocate an arbitrarily large HPACK table.
109                    settings.header_table_size = value.min(MAX_PEER_HEADER_TABLE_SIZE);
110                }
111                SETTINGS_ENABLE_PUSH => {
112                    if value > 1 {
113                        return Err(H2Error::ProtocolError("ENABLE_PUSH must be 0 or 1".into()));
114                    }
115                    settings.enable_push = value == 1;
116                }
117                SETTINGS_MAX_CONCURRENT_STREAMS => {
118                    settings.max_concurrent_streams = Some(value);
119                }
120                SETTINGS_INITIAL_WINDOW_SIZE => {
121                    if value > 0x7fff_ffff {
122                        return Err(H2Error::FlowControlError);
123                    }
124                    settings.initial_window_size = value;
125                }
126                SETTINGS_MAX_FRAME_SIZE => {
127                    if !(16384..=16_777_215).contains(&value) {
128                        return Err(H2Error::ProtocolError("MAX_FRAME_SIZE out of range".into()));
129                    }
130                    settings.max_frame_size = value;
131                }
132                SETTINGS_MAX_HEADER_LIST_SIZE => {
133                    settings.max_header_list_size = Some(value);
134                }
135                // Unknown settings MUST be ignored (RFC 7540 Section 6.5.2).
136                _ => {}
137            }
138        }
139        Ok(settings)
140    }
141}
142
143fn encode_setting(buf: &mut Vec<u8>, id: u16, value: u32) {
144    buf.push((id >> 8) as u8);
145    buf.push(id as u8);
146    buf.push((value >> 24) as u8);
147    buf.push((value >> 16) as u8);
148    buf.push((value >> 8) as u8);
149    buf.push(value as u8);
150}
151
152#[cfg(test)]
153mod tests {
154    use super::*;
155
156    #[test]
157    fn default_settings_round_trip() {
158        let settings = Settings::default();
159        let encoded = settings.encode_to_vec();
160        let decoded = Settings::decode(&encoded).unwrap();
161        assert_eq!(decoded.header_table_size, 4096);
162        assert!(decoded.enable_push);
163        assert_eq!(decoded.initial_window_size, 65535);
164        assert_eq!(decoded.max_frame_size, 16384);
165    }
166
167    #[test]
168    fn client_settings_round_trip() {
169        let settings = Settings::client_default();
170        let encoded = settings.encode_to_vec();
171        let decoded = Settings::decode(&encoded).unwrap();
172        assert!(!decoded.enable_push);
173    }
174
175    #[test]
176    fn custom_settings_round_trip() {
177        let settings = Settings {
178            header_table_size: 8192,
179            enable_push: false,
180            max_concurrent_streams: Some(100),
181            initial_window_size: 1048576,
182            max_frame_size: 32768,
183            max_header_list_size: Some(65536),
184        };
185        let encoded = settings.encode_to_vec();
186        let decoded = Settings::decode(&encoded).unwrap();
187        assert_eq!(decoded.header_table_size, 8192);
188        assert!(!decoded.enable_push);
189        assert_eq!(decoded.max_concurrent_streams, Some(100));
190        assert_eq!(decoded.initial_window_size, 1048576);
191        assert_eq!(decoded.max_frame_size, 32768);
192        assert_eq!(decoded.max_header_list_size, Some(65536));
193    }
194
195    #[test]
196    fn invalid_enable_push_rejected() {
197        let mut buf = Vec::new();
198        encode_setting(&mut buf, 0x2, 2); // ENABLE_PUSH = 2 is invalid
199        assert!(Settings::decode(&buf).is_err());
200    }
201
202    #[test]
203    fn invalid_window_size_rejected() {
204        let mut buf = Vec::new();
205        encode_setting(&mut buf, 0x4, 0x8000_0000); // > 2^31 - 1
206        assert!(Settings::decode(&buf).is_err());
207    }
208
209    #[test]
210    fn invalid_max_frame_size_rejected() {
211        let mut buf = Vec::new();
212        encode_setting(&mut buf, 0x5, 100); // < 16384
213        assert!(Settings::decode(&buf).is_err());
214    }
215
216    #[test]
217    fn unknown_setting_ignored() {
218        let mut buf = Vec::new();
219        encode_setting(&mut buf, 0xff, 42);
220        let decoded = Settings::decode(&buf).unwrap();
221        assert_eq!(decoded.header_table_size, 4096); // still default
222    }
223}