zerodds_http2/
settings.rs1use alloc::vec::Vec;
10
11use crate::error::Http2Error;
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
15#[repr(u16)]
16pub enum SettingId {
17 HeaderTableSize = 0x1,
19 EnablePush = 0x2,
21 MaxConcurrentStreams = 0x3,
23 InitialWindowSize = 0x4,
25 MaxFrameSize = 0x5,
27 MaxHeaderListSize = 0x6,
29}
30
31impl SettingId {
32 #[must_use]
36 pub fn from_u16(v: u16) -> Option<Self> {
37 match v {
38 0x1 => Some(Self::HeaderTableSize),
39 0x2 => Some(Self::EnablePush),
40 0x3 => Some(Self::MaxConcurrentStreams),
41 0x4 => Some(Self::InitialWindowSize),
42 0x5 => Some(Self::MaxFrameSize),
43 0x6 => Some(Self::MaxHeaderListSize),
44 _ => None,
45 }
46 }
47}
48
49#[derive(Debug, Clone, Copy, PartialEq, Eq)]
51pub struct Setting {
52 pub id: SettingId,
54 pub value: u32,
56}
57
58#[derive(Debug, Clone, PartialEq, Eq)]
60pub struct Settings {
61 pub header_table_size: u32,
63 pub enable_push: u32,
65 pub max_concurrent_streams: u32,
68 pub initial_window_size: u32,
70 pub max_frame_size: u32,
72 pub max_header_list_size: u32,
74}
75
76impl Default for Settings {
77 fn default() -> Self {
78 Self {
80 header_table_size: 4096,
81 enable_push: 1,
82 max_concurrent_streams: u32::MAX,
83 initial_window_size: 65_535,
84 max_frame_size: 16_384,
85 max_header_list_size: u32::MAX,
86 }
87 }
88}
89
90impl Settings {
91 pub fn apply(&mut self, s: Setting) -> Result<(), Http2Error> {
99 use crate::error::ErrorCode;
100 match s.id {
101 SettingId::HeaderTableSize => self.header_table_size = s.value,
102 SettingId::EnablePush => {
103 if s.value > 1 {
104 return Err(Http2Error::Protocol(ErrorCode::ProtocolError));
105 }
106 self.enable_push = s.value;
107 }
108 SettingId::MaxConcurrentStreams => self.max_concurrent_streams = s.value,
109 SettingId::InitialWindowSize => {
110 if s.value > 0x7fff_ffff {
111 return Err(Http2Error::Protocol(ErrorCode::FlowControlError));
112 }
113 self.initial_window_size = s.value;
114 }
115 SettingId::MaxFrameSize => {
116 if !(16_384..=16_777_215).contains(&s.value) {
117 return Err(Http2Error::Protocol(ErrorCode::ProtocolError));
118 }
119 self.max_frame_size = s.value;
120 }
121 SettingId::MaxHeaderListSize => self.max_header_list_size = s.value,
122 }
123 Ok(())
124 }
125}
126
127pub fn decode_settings(payload: &[u8]) -> Result<Vec<Setting>, Http2Error> {
134 use crate::error::ErrorCode;
135 if payload.len() % 6 != 0 {
136 return Err(Http2Error::Protocol(ErrorCode::FrameSizeError));
137 }
138 let mut out = Vec::with_capacity(payload.len() / 6);
139 let mut i = 0;
140 while i + 6 <= payload.len() {
141 let id_u = (u16::from(payload[i]) << 8) | u16::from(payload[i + 1]);
142 let value = (u32::from(payload[i + 2]) << 24)
143 | (u32::from(payload[i + 3]) << 16)
144 | (u32::from(payload[i + 4]) << 8)
145 | u32::from(payload[i + 5]);
146 if let Some(id) = SettingId::from_u16(id_u) {
147 out.push(Setting { id, value });
148 }
149 i += 6;
151 }
152 Ok(out)
153}
154
155#[must_use]
157pub fn encode_settings(settings: &[Setting]) -> Vec<u8> {
158 let mut out = Vec::with_capacity(settings.len() * 6);
159 for s in settings {
160 let id = s.id as u16;
161 out.push((id >> 8) as u8);
162 out.push((id & 0xff) as u8);
163 out.push(((s.value >> 24) & 0xff) as u8);
164 out.push(((s.value >> 16) & 0xff) as u8);
165 out.push(((s.value >> 8) & 0xff) as u8);
166 out.push((s.value & 0xff) as u8);
167 }
168 out
169}
170
171#[cfg(test)]
172#[allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)]
173mod tests {
174 use super::*;
175
176 #[test]
177 fn defaults_match_spec() {
178 let s = Settings::default();
179 assert_eq!(s.header_table_size, 4096);
180 assert_eq!(s.enable_push, 1);
181 assert_eq!(s.initial_window_size, 65_535);
182 assert_eq!(s.max_frame_size, 16_384);
183 }
184
185 #[test]
186 fn apply_valid_settings() {
187 let mut s = Settings::default();
188 s.apply(Setting {
189 id: SettingId::MaxFrameSize,
190 value: 32_768,
191 })
192 .unwrap();
193 assert_eq!(s.max_frame_size, 32_768);
194 }
195
196 #[test]
197 fn invalid_enable_push_rejected() {
198 let mut s = Settings::default();
199 assert!(
200 s.apply(Setting {
201 id: SettingId::EnablePush,
202 value: 2,
203 })
204 .is_err()
205 );
206 }
207
208 #[test]
209 fn invalid_initial_window_size_rejected() {
210 let mut s = Settings::default();
211 assert!(
212 s.apply(Setting {
213 id: SettingId::InitialWindowSize,
214 value: 0x8000_0000,
215 })
216 .is_err()
217 );
218 }
219
220 #[test]
221 fn invalid_max_frame_size_rejected() {
222 let mut s = Settings::default();
223 assert!(
224 s.apply(Setting {
225 id: SettingId::MaxFrameSize,
226 value: 1,
227 })
228 .is_err()
229 );
230 assert!(
231 s.apply(Setting {
232 id: SettingId::MaxFrameSize,
233 value: 16_777_216,
234 })
235 .is_err()
236 );
237 }
238
239 #[test]
240 fn round_trip_encode_decode() {
241 let settings = alloc::vec![
242 Setting {
243 id: SettingId::MaxConcurrentStreams,
244 value: 100
245 },
246 Setting {
247 id: SettingId::InitialWindowSize,
248 value: 1_048_576,
249 },
250 ];
251 let payload = encode_settings(&settings);
252 let decoded = decode_settings(&payload).unwrap();
253 assert_eq!(decoded, settings);
254 }
255
256 #[test]
257 fn unknown_setting_id_ignored() {
258 let payload = alloc::vec![0x00, 0xff, 0x00, 0x00, 0x00, 0x01];
259 let decoded = decode_settings(&payload).unwrap();
260 assert!(decoded.is_empty());
261 }
262
263 #[test]
264 fn odd_length_payload_rejected() {
265 let payload = alloc::vec![0; 5];
266 assert!(decode_settings(&payload).is_err());
267 }
268}