1use crate::error::Http2Error;
20
21pub const FRAME_HEADER_LEN: usize = 9;
23
24pub const DEFAULT_MAX_FRAME_SIZE: u32 = 16_384;
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30#[repr(u8)]
31pub enum FrameType {
32 Data = 0x0,
34 Headers = 0x1,
36 Priority = 0x2,
38 RstStream = 0x3,
40 Settings = 0x4,
42 PushPromise = 0x5,
44 Ping = 0x6,
46 GoAway = 0x7,
48 WindowUpdate = 0x8,
50 Continuation = 0x9,
52}
53
54impl FrameType {
55 pub fn from_u8(v: u8) -> Result<Self, Http2Error> {
60 match v {
61 0x0 => Ok(Self::Data),
62 0x1 => Ok(Self::Headers),
63 0x2 => Ok(Self::Priority),
64 0x3 => Ok(Self::RstStream),
65 0x4 => Ok(Self::Settings),
66 0x5 => Ok(Self::PushPromise),
67 0x6 => Ok(Self::Ping),
68 0x7 => Ok(Self::GoAway),
69 0x8 => Ok(Self::WindowUpdate),
70 0x9 => Ok(Self::Continuation),
71 other => Err(Http2Error::UnknownFrameType(other)),
72 }
73 }
74}
75
76#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
78pub struct Flags(pub u8);
79
80impl Flags {
81 pub const END_STREAM: u8 = 0x1;
83 pub const END_HEADERS: u8 = 0x4;
85 pub const PADDED: u8 = 0x8;
87 pub const PRIORITY: u8 = 0x20;
89 pub const ACK: u8 = 0x1;
91
92 #[must_use]
94 pub fn has(self, bit: u8) -> bool {
95 (self.0 & bit) == bit
96 }
97}
98
99#[derive(Debug, Clone, Copy, PartialEq, Eq)]
101pub struct FrameHeader {
102 pub length: u32,
104 pub frame_type: FrameType,
106 pub flags: Flags,
108 pub stream_id: u32,
110}
111
112#[derive(Debug, Clone, PartialEq, Eq)]
114pub struct Frame<'a> {
115 pub header: FrameHeader,
117 pub payload: &'a [u8],
119}
120
121pub fn decode_frame(input: &[u8], max_frame_size: u32) -> Result<(Frame<'_>, usize), Http2Error> {
132 if input.len() < FRAME_HEADER_LEN {
133 return Err(Http2Error::ShortFrameHeader);
134 }
135 let length = (u32::from(input[0]) << 16) | (u32::from(input[1]) << 8) | u32::from(input[2]);
136 if length > max_frame_size {
137 return Err(Http2Error::FrameTooLarge {
138 got: length,
139 max: max_frame_size,
140 });
141 }
142 let frame_type = FrameType::from_u8(input[3])?;
143 let flags = Flags(input[4]);
144 let stream_id = ((u32::from(input[5]) & 0x7f) << 24)
146 | (u32::from(input[6]) << 16)
147 | (u32::from(input[7]) << 8)
148 | u32::from(input[8]);
149 let total = FRAME_HEADER_LEN + length as usize;
150 if input.len() < total {
151 return Err(Http2Error::ShortPayload);
152 }
153 let payload = &input[FRAME_HEADER_LEN..total];
154 Ok((
155 Frame {
156 header: FrameHeader {
157 length,
158 frame_type,
159 flags,
160 stream_id,
161 },
162 payload,
163 },
164 total,
165 ))
166}
167
168pub fn encode_frame(
174 header: &FrameHeader,
175 payload: &[u8],
176 out: &mut [u8],
177 max_frame_size: u32,
178) -> Result<usize, Http2Error> {
179 let len = payload.len();
180 if len > max_frame_size as usize {
181 return Err(Http2Error::FrameTooLarge {
182 got: len as u32,
183 max: max_frame_size,
184 });
185 }
186 let total = FRAME_HEADER_LEN + len;
187 if out.len() < total {
188 return Err(Http2Error::ShortPayload);
189 }
190 let l = len as u32;
191 out[0] = ((l >> 16) & 0xff) as u8;
192 out[1] = ((l >> 8) & 0xff) as u8;
193 out[2] = (l & 0xff) as u8;
194 out[3] = header.frame_type as u8;
195 out[4] = header.flags.0;
196 let sid = header.stream_id & 0x7fff_ffff;
197 out[5] = ((sid >> 24) & 0xff) as u8;
198 out[6] = ((sid >> 16) & 0xff) as u8;
199 out[7] = ((sid >> 8) & 0xff) as u8;
200 out[8] = (sid & 0xff) as u8;
201 out[FRAME_HEADER_LEN..total].copy_from_slice(payload);
202 Ok(total)
203}
204
205#[cfg(test)]
206#[allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)]
207mod tests {
208 use super::*;
209 use alloc::vec;
210
211 #[test]
212 fn from_u8_round_trip() {
213 for ft in [
214 FrameType::Data,
215 FrameType::Headers,
216 FrameType::Priority,
217 FrameType::RstStream,
218 FrameType::Settings,
219 FrameType::PushPromise,
220 FrameType::Ping,
221 FrameType::GoAway,
222 FrameType::WindowUpdate,
223 FrameType::Continuation,
224 ] {
225 assert_eq!(FrameType::from_u8(ft as u8).unwrap(), ft);
226 }
227 }
228
229 #[test]
230 fn unknown_frame_type_rejected() {
231 assert!(matches!(
232 FrameType::from_u8(0xff),
233 Err(Http2Error::UnknownFrameType(0xff))
234 ));
235 }
236
237 #[test]
238 fn flags_has_detects_bits() {
239 let f = Flags(Flags::END_STREAM | Flags::END_HEADERS);
240 assert!(f.has(Flags::END_STREAM));
241 assert!(f.has(Flags::END_HEADERS));
242 assert!(!f.has(Flags::PADDED));
243 }
244
245 #[test]
246 fn encode_decode_round_trip() {
247 let h = FrameHeader {
248 length: 5,
249 frame_type: FrameType::Data,
250 flags: Flags(Flags::END_STREAM),
251 stream_id: 7,
252 };
253 let payload = vec![1, 2, 3, 4, 5];
254 let mut buf = vec![0u8; 32];
255 let n = encode_frame(&h, &payload, &mut buf, DEFAULT_MAX_FRAME_SIZE).unwrap();
256 assert_eq!(n, FRAME_HEADER_LEN + 5);
257 let (frame, consumed) = decode_frame(&buf[..n], DEFAULT_MAX_FRAME_SIZE).unwrap();
258 assert_eq!(consumed, n);
259 assert_eq!(frame.header.length, 5);
260 assert_eq!(frame.header.stream_id, 7);
261 assert_eq!(frame.header.frame_type, FrameType::Data);
262 assert!(frame.header.flags.has(Flags::END_STREAM));
263 assert_eq!(frame.payload, &payload[..]);
264 }
265
266 #[test]
267 fn r_bit_stripped_from_stream_id() {
268 let mut buf = vec![0u8; 9];
270 buf[3] = FrameType::Settings as u8;
271 buf[5] = 0x80; let (frame, _) = decode_frame(&buf, DEFAULT_MAX_FRAME_SIZE).unwrap();
273 assert_eq!(frame.header.stream_id, 0, "R-bit must be stripped");
274 }
275
276 #[test]
277 fn frame_too_large_rejected() {
278 let mut buf = vec![0u8; FRAME_HEADER_LEN + 100];
279 buf[2] = 100;
281 let r = decode_frame(&buf, 50);
282 assert!(matches!(
283 r,
284 Err(Http2Error::FrameTooLarge { got: 100, max: 50 })
285 ));
286 }
287
288 #[test]
289 fn short_header_rejected() {
290 let buf = vec![0u8; 5];
291 assert!(matches!(
292 decode_frame(&buf, DEFAULT_MAX_FRAME_SIZE),
293 Err(Http2Error::ShortFrameHeader)
294 ));
295 }
296
297 #[test]
298 fn short_payload_rejected() {
299 let mut buf = vec![0u8; FRAME_HEADER_LEN + 50];
301 buf[2] = 100;
302 assert!(matches!(
303 decode_frame(&buf, DEFAULT_MAX_FRAME_SIZE),
304 Err(Http2Error::ShortPayload)
305 ));
306 }
307
308 #[test]
309 fn encode_into_too_small_buffer_rejected() {
310 let h = FrameHeader {
311 length: 0,
312 frame_type: FrameType::Ping,
313 flags: Flags(0),
314 stream_id: 0,
315 };
316 let mut buf = vec![0u8; 5];
317 assert!(encode_frame(&h, b"PINGPING", &mut buf, DEFAULT_MAX_FRAME_SIZE).is_err());
318 }
319}