1pub const FRAME_HEADER_LEN: usize = 9;
22
23pub const DEFAULT_MAX_FRAME_SIZE: u32 = 16_384;
27pub const MAX_ALLOWED_FRAME_SIZE: u32 = 16_777_215;
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32pub enum FrameKind {
33 Data,
35 Headers,
37 Priority,
39 RstStream,
41 Settings,
43 PushPromise,
45 Ping,
47 GoAway,
49 WindowUpdate,
51 Continuation,
53 Unknown(u8),
55}
56
57impl FrameKind {
58 fn from_u8(b: u8) -> Self {
59 match b {
60 0x0 => Self::Data,
61 0x1 => Self::Headers,
62 0x2 => Self::Priority,
63 0x3 => Self::RstStream,
64 0x4 => Self::Settings,
65 0x5 => Self::PushPromise,
66 0x6 => Self::Ping,
67 0x7 => Self::GoAway,
68 0x8 => Self::WindowUpdate,
69 0x9 => Self::Continuation,
70 other => Self::Unknown(other),
71 }
72 }
73
74 #[must_use]
76 pub fn as_u8(self) -> u8 {
77 match self {
78 Self::Data => 0x0,
79 Self::Headers => 0x1,
80 Self::Priority => 0x2,
81 Self::RstStream => 0x3,
82 Self::Settings => 0x4,
83 Self::PushPromise => 0x5,
84 Self::Ping => 0x6,
85 Self::GoAway => 0x7,
86 Self::WindowUpdate => 0x8,
87 Self::Continuation => 0x9,
88 Self::Unknown(b) => b,
89 }
90 }
91}
92
93#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
95pub struct FrameFlags(u8);
96
97impl FrameFlags {
98 pub const EMPTY: Self = Self(0);
100
101 #[must_use]
103 pub fn end_stream(self) -> bool {
104 self.0 & 0x01 != 0
105 }
106 #[must_use]
108 pub fn end_headers(self) -> bool {
109 self.0 & 0x04 != 0
110 }
111 #[must_use]
113 pub fn padded(self) -> bool {
114 self.0 & 0x08 != 0
115 }
116 #[must_use]
118 pub fn ack(self) -> bool {
119 self.0 & 0x01 != 0
120 }
121 #[must_use]
123 pub fn priority(self) -> bool {
124 self.0 & 0x20 != 0
125 }
126
127 #[must_use]
129 pub fn from_u8(b: u8) -> Self {
130 Self(b)
131 }
132
133 #[must_use]
135 pub fn as_u8(self) -> u8 {
136 self.0
137 }
138}
139
140#[derive(Debug, Clone, Copy, PartialEq, Eq)]
142pub struct FrameHeader {
143 pub length: u32,
145 pub kind: FrameKind,
147 pub flags: FrameFlags,
149 pub stream_id: u32,
151}
152
153#[derive(Debug, Clone, Copy, PartialEq, Eq)]
155pub enum FrameError {
156 Truncated,
158 InvalidStreamId,
162 InvalidPayloadLength,
165 InvalidPadding,
167 InvalidSettings,
170 InvalidWindowIncrement,
172}
173
174pub fn parse_header(data: &[u8]) -> Result<FrameHeader, FrameError> {
182 let hdr = data.get(..FRAME_HEADER_LEN).ok_or(FrameError::Truncated)?;
183 let length = u32::from_be_bytes([0, hdr[0], hdr[1], hdr[2]]);
184 let kind = FrameKind::from_u8(hdr[3]);
185 let flags = FrameFlags::from_u8(hdr[4]);
186 let stream_id = u32::from_be_bytes([hdr[5] & 0x7f, hdr[6], hdr[7], hdr[8]]);
188 Ok(FrameHeader {
189 length,
190 kind,
191 flags,
192 stream_id,
193 })
194}
195
196pub fn write_header(
198 out: &mut Vec<u8>,
199 length: u32,
200 kind: FrameKind,
201 flags: FrameFlags,
202 stream_id: u32,
203) {
204 debug_assert!(length <= MAX_ALLOWED_FRAME_SIZE);
205 debug_assert!(stream_id <= 0x7fff_ffff);
206 let b = length.to_be_bytes();
207 out.extend_from_slice(&b[1..4]);
208 out.push(kind.as_u8());
209 out.push(flags.as_u8());
210 out.extend_from_slice(&stream_id.to_be_bytes());
211}
212
213pub fn validate_payload(
227 header: &FrameHeader,
228 data: &[u8],
229 peer_max_frame: u32,
230) -> Result<PayloadSplit, FrameError> {
231 if data.len() as u32 != header.length {
232 return Err(FrameError::Truncated);
233 }
234 if header.length > peer_max_frame.max(MAX_ALLOWED_FRAME_SIZE) {
236 return Err(FrameError::InvalidPayloadLength);
237 }
238
239 let padded = match header.kind {
240 FrameKind::Data => header.flags.padded(),
241 FrameKind::Headers => header.flags.padded(),
242 FrameKind::PushPromise => header.flags.padded(),
243 _ => false,
244 };
245 if padded {
246 let Some(&pad_len) = data.first() else {
247 return Err(FrameError::Truncated);
248 };
249 let content_len = (data.len() as u32)
251 .checked_sub(1 + u32::from(pad_len))
252 .ok_or(FrameError::InvalidPadding)?;
253 return Ok(PayloadSplit {
254 pad_len: Some(pad_len),
255 content_start: 1,
256 content_end: 1 + content_len as usize,
257 });
258 }
259
260 match header.kind {
261 FrameKind::Data | FrameKind::Headers | FrameKind::Continuation => {}
262 FrameKind::RstStream => {
263 if header.length != 4 {
264 return Err(FrameError::InvalidPayloadLength);
265 }
266 }
267 FrameKind::Settings => {
268 if header.flags.ack() {
269 if header.length != 0 {
270 return Err(FrameError::InvalidSettings);
271 }
272 } else if header.length % 6 != 0 {
273 return Err(FrameError::InvalidSettings);
274 }
275 }
276 FrameKind::Ping => {
277 if header.length != 8 {
278 return Err(FrameError::InvalidPayloadLength);
279 }
280 }
281 FrameKind::GoAway => {
282 if header.length < 8 {
283 return Err(FrameError::InvalidPayloadLength);
284 }
285 }
286 FrameKind::WindowUpdate => {
287 if header.length != 4 {
288 return Err(FrameError::InvalidPayloadLength);
289 }
290 if data.len() == 4 {
291 let inc = u32::from_be_bytes([data[0] & 0x7f, data[1], data[2], data[3]]);
292 if inc == 0 {
293 return Err(FrameError::InvalidWindowIncrement);
294 }
295 }
296 }
297 FrameKind::Priority => {
298 if header.length != 5 {
299 return Err(FrameError::InvalidPayloadLength);
300 }
301 }
302 FrameKind::PushPromise | FrameKind::Unknown(_) => {}
303 }
304
305 Ok(PayloadSplit {
306 pad_len: None,
307 content_start: 0,
308 content_end: data.len(),
309 })
310}
311
312#[derive(Debug, Clone, Copy, PartialEq, Eq)]
314pub struct PayloadSplit {
315 pub pad_len: Option<u8>,
317 pub content_start: usize,
319 pub content_end: usize,
321}
322
323#[derive(Debug, Clone, Copy, PartialEq, Eq)]
325pub enum Setting {
326 HeaderTableSize(u32),
328 EnablePush(u32),
331 MaxConcurrentStreams(u32),
333 InitialWindowSize(u32),
335 MaxFrameSize(u32),
337 MaxHeaderListSize(u32),
339 Unknown(u16, u32),
341}
342
343pub fn parse_settings(data: &[u8]) -> Result<Vec<Setting>, FrameError> {
350 if data.len() % 6 != 0 {
351 return Err(FrameError::InvalidSettings);
352 }
353 let mut out = Vec::with_capacity(data.len() / 6);
354 for pair in data.chunks_exact(6) {
355 let id = u16::from_be_bytes([pair[0], pair[1]]);
356 let value = u32::from_be_bytes([pair[2], pair[3], pair[4], pair[5]]);
357 out.push(match id {
358 0x1 => Setting::HeaderTableSize(value),
359 0x2 => Setting::EnablePush(value),
360 0x3 => Setting::MaxConcurrentStreams(value),
361 0x4 => Setting::InitialWindowSize(value),
362 0x5 => Setting::MaxFrameSize(value),
363 0x6 => Setting::MaxHeaderListSize(value),
364 other => Setting::Unknown(other, value),
365 });
366 }
367 Ok(out)
368}
369
370pub fn write_setting(out: &mut Vec<u8>, id: u16, value: u32) {
372 out.extend_from_slice(&id.to_be_bytes());
373 out.extend_from_slice(&value.to_be_bytes());
374}
375
376pub fn parse_window_update(data: &[u8]) -> Result<u32, FrameError> {
382 if data.len() != 4 {
383 return Err(FrameError::InvalidPayloadLength);
384 }
385 Ok(u32::from_be_bytes([
386 data[0] & 0x7f,
387 data[1],
388 data[2],
389 data[3],
390 ]))
391}
392
393pub fn parse_rst_stream(data: &[u8]) -> Result<u32, FrameError> {
398 if data.len() != 4 {
399 return Err(FrameError::InvalidPayloadLength);
400 }
401 Ok(u32::from_be_bytes([data[0], data[1], data[2], data[3]]))
402}
403
404#[cfg(test)]
405mod tests {
406 use super::*;
407
408 fn header_bytes(length: u32, kind: FrameKind, flags: FrameFlags, stream_id: u32) -> Vec<u8> {
409 let mut out = Vec::with_capacity(FRAME_HEADER_LEN);
410 write_header(&mut out, length, kind, flags, stream_id);
411 out
412 }
413
414 #[test]
415 fn header_roundtrip() {
416 let raw = header_bytes(
417 0x000102,
418 FrameKind::Headers,
419 FrameFlags::from_u8(0xff),
420 0x7fff_ffff,
421 );
422 let hdr = parse_header(&raw).expect("parse");
423 assert_eq!(hdr.length, 0x000102);
424 assert_eq!(hdr.kind, FrameKind::Headers);
425 assert_eq!(hdr.flags.as_u8(), 0xff);
426 assert_eq!(hdr.stream_id, 0x7fff_ffff, "reserved bit masked");
427 }
428
429 #[test]
430 fn header_truncated() {
431 assert_eq!(parse_header(&[0; 8]), Err(FrameError::Truncated));
432 }
433
434 #[test]
435 fn settings_roundtrip() {
436 let mut payload = Vec::new();
437 write_setting(&mut payload, 0x1, 4096);
438 write_setting(&mut payload, 0x4, 1_048_576);
439 write_setting(&mut payload, 0xff, 7); let raw = header_bytes(
441 payload.len() as u32,
442 FrameKind::Settings,
443 FrameFlags::EMPTY,
444 0,
445 );
446 let hdr = parse_header(&raw).expect("parse");
447 assert!(validate_payload(&hdr, &payload, DEFAULT_MAX_FRAME_SIZE).is_ok());
448 let settings = parse_settings(&payload).expect("parse settings");
449 assert_eq!(settings.len(), 3);
450 assert_eq!(settings[0], Setting::HeaderTableSize(4096));
451 assert_eq!(settings[1], Setting::InitialWindowSize(1_048_576));
452 assert_eq!(settings[2], Setting::Unknown(0xff, 7));
453 }
454
455 #[test]
456 fn settings_bad_length() {
457 let raw = header_bytes(7, FrameKind::Settings, FrameFlags::EMPTY, 0);
458 let hdr = parse_header(&raw).expect("parse");
459 assert!(matches!(
460 validate_payload(&hdr, &[0u8; 7], DEFAULT_MAX_FRAME_SIZE),
461 Err(FrameError::InvalidSettings)
462 ));
463 let raw = header_bytes(6, FrameKind::Settings, FrameFlags::from_u8(0x01), 0);
465 let hdr = parse_header(&raw).expect("parse");
466 assert!(matches!(
467 validate_payload(&hdr, &[0u8; 6], DEFAULT_MAX_FRAME_SIZE),
468 Err(FrameError::InvalidSettings)
469 ));
470 }
471
472 #[test]
473 fn data_padding_split() {
474 let payload = [3, b'h', b'i', 0, 0, 0];
476 let raw = header_bytes(
477 payload.len() as u32,
478 FrameKind::Data,
479 FrameFlags::from_u8(0x08),
480 1,
481 );
482 let hdr = parse_header(&raw).expect("parse");
483 let split = validate_payload(&hdr, &payload, DEFAULT_MAX_FRAME_SIZE).expect("valid");
484 assert_eq!(&payload[split.content_start..split.content_end], b"hi");
485 }
486
487 #[test]
488 fn data_pad_overflow_is_invalid() {
489 let payload = [9, b'h', b'i', 0, 0];
490 let raw = header_bytes(
491 payload.len() as u32,
492 FrameKind::Data,
493 FrameFlags::from_u8(0x08),
494 1,
495 );
496 let hdr = parse_header(&raw).expect("parse");
497 assert!(matches!(
498 validate_payload(&hdr, &payload, DEFAULT_MAX_FRAME_SIZE),
499 Err(FrameError::InvalidPadding)
500 ));
501 }
502
503 #[test]
504 fn window_update_zero_increment_invalid() {
505 let payload = [0, 0, 0, 0];
506 let raw = header_bytes(4, FrameKind::WindowUpdate, FrameFlags::EMPTY, 0);
507 let hdr = parse_header(&raw).expect("parse");
508 assert!(matches!(
509 validate_payload(&hdr, &payload, DEFAULT_MAX_FRAME_SIZE),
510 Err(FrameError::InvalidWindowIncrement)
511 ));
512 }
513
514 #[test]
515 fn window_update_stream_zero_ok() {
516 let payload = [0, 0, 0, 16];
517 let raw = header_bytes(4, FrameKind::WindowUpdate, FrameFlags::EMPTY, 0);
518 let hdr = parse_header(&raw).expect("parse");
519 assert!(validate_payload(&hdr, &payload, DEFAULT_MAX_FRAME_SIZE).is_ok());
520 }
521
522 #[test]
523 fn oversize_frame_rejected() {
524 let raw = header_bytes(
525 DEFAULT_MAX_FRAME_SIZE + 1,
526 FrameKind::Data,
527 FrameFlags::EMPTY,
528 1,
529 );
530 let hdr = parse_header(&raw).expect("parse");
531 assert!(matches!(
534 validate_payload(&hdr, &[], DEFAULT_MAX_FRAME_SIZE),
535 Err(FrameError::InvalidPayloadLength) | Err(FrameError::Truncated)
536 ));
537 }
538
539 #[test]
540 fn rst_stream_requires_4_bytes() {
541 let raw = header_bytes(3, FrameKind::RstStream, FrameFlags::EMPTY, 1);
542 let hdr = parse_header(&raw).expect("parse");
543 assert!(matches!(
544 validate_payload(&hdr, &[0u8; 3], DEFAULT_MAX_FRAME_SIZE),
545 Err(FrameError::InvalidPayloadLength)
546 ));
547 let raw = header_bytes(4, FrameKind::RstStream, FrameFlags::EMPTY, 1);
548 let hdr = parse_header(&raw).expect("parse");
549 let payload = [0, 0, 0, 8]; assert!(validate_payload(&hdr, &payload, DEFAULT_MAX_FRAME_SIZE).is_ok());
551 assert_eq!(parse_rst_stream(&payload), Ok(8));
552 }
553
554 #[test]
555 fn ping_requires_8_bytes() {
556 let raw = header_bytes(9, FrameKind::Ping, FrameFlags::EMPTY, 0);
557 let hdr = parse_header(&raw).expect("parse");
558 assert!(matches!(
559 validate_payload(&hdr, &[0u8; 9], DEFAULT_MAX_FRAME_SIZE),
560 Err(FrameError::InvalidPayloadLength)
561 ));
562 }
563
564 #[test]
565 fn unknown_frame_type_skipped() {
566 let payload = [1, 2, 3];
567 let raw = header_bytes(3, FrameKind::Unknown(0xab), FrameFlags::EMPTY, 4);
568 let hdr = parse_header(&raw).expect("parse");
569 assert!(validate_payload(&hdr, &payload, DEFAULT_MAX_FRAME_SIZE).is_ok());
570 }
571
572 #[test]
573 fn goaway_requires_at_least_8_bytes() {
574 let raw = header_bytes(4, FrameKind::GoAway, FrameFlags::EMPTY, 0);
575 let hdr = parse_header(&raw).expect("parse");
576 assert!(matches!(
577 validate_payload(&hdr, &[0u8; 4], DEFAULT_MAX_FRAME_SIZE),
578 Err(FrameError::InvalidPayloadLength)
579 ));
580 let raw = header_bytes(8, FrameKind::GoAway, FrameFlags::EMPTY, 0);
581 let hdr = parse_header(&raw).expect("parse");
582 assert!(validate_payload(&hdr, &[0u8; 8], DEFAULT_MAX_FRAME_SIZE).is_ok());
583 }
584
585 #[test]
586 fn window_update_requires_4_bytes() {
587 let raw = header_bytes(5, FrameKind::WindowUpdate, FrameFlags::EMPTY, 1);
588 let hdr = parse_header(&raw).expect("parse");
589 assert!(matches!(
590 validate_payload(&hdr, &[0u8; 5], DEFAULT_MAX_FRAME_SIZE),
591 Err(FrameError::InvalidPayloadLength)
592 ));
593 }
594
595 #[test]
596 fn priority_requires_5_bytes() {
597 let raw = header_bytes(4, FrameKind::Priority, FrameFlags::EMPTY, 1);
598 let hdr = parse_header(&raw).expect("parse");
599 assert!(matches!(
600 validate_payload(&hdr, &[0u8; 4], DEFAULT_MAX_FRAME_SIZE),
601 Err(FrameError::InvalidPayloadLength)
602 ));
603 let raw = header_bytes(5, FrameKind::Priority, FrameFlags::EMPTY, 1);
604 let hdr = parse_header(&raw).expect("parse");
605 assert!(validate_payload(&hdr, &[0u8; 5], DEFAULT_MAX_FRAME_SIZE).is_ok());
606 }
607
608 #[test]
609 fn push_promise_padding_splits_like_data() {
610 let payload = [1, b'x', 0];
612 let raw = header_bytes(
613 payload.len() as u32,
614 FrameKind::PushPromise,
615 FrameFlags::from_u8(0x08),
616 3,
617 );
618 let hdr = parse_header(&raw).expect("parse");
619 let split = validate_payload(&hdr, &payload, DEFAULT_MAX_FRAME_SIZE).expect("valid");
620 assert_eq!(split.pad_len, Some(1));
621 assert_eq!(&payload[split.content_start..split.content_end], b"x");
622 }
623
624 #[test]
625 fn padded_frame_with_empty_payload_truncated() {
626 let raw = header_bytes(0, FrameKind::Data, FrameFlags::from_u8(0x08), 1);
627 let hdr = parse_header(&raw).expect("parse");
628 assert!(matches!(
629 validate_payload(&hdr, &[], DEFAULT_MAX_FRAME_SIZE),
630 Err(FrameError::Truncated)
631 ));
632 }
633
634 #[test]
635 fn spec_hard_frame_cap_rejected_even_with_matching_payload() {
636 let len = MAX_ALLOWED_FRAME_SIZE + 1;
642 let hdr = FrameHeader {
643 length: len,
644 kind: FrameKind::Data,
645 flags: FrameFlags::EMPTY,
646 stream_id: 1,
647 };
648 let payload = vec![0u8; len as usize];
649 assert!(matches!(
650 validate_payload(&hdr, &payload, DEFAULT_MAX_FRAME_SIZE),
651 Err(FrameError::InvalidPayloadLength)
652 ));
653 }
654
655 #[test]
656 fn parse_settings_rejects_non_multiple_of_six() {
657 assert_eq!(parse_settings(&[0u8; 7]), Err(FrameError::InvalidSettings));
658 }
659
660 #[test]
661 fn parse_settings_maps_all_known_ids() {
662 let mut payload = Vec::new();
663 write_setting(&mut payload, 0x2, 1);
664 write_setting(&mut payload, 0x5, 16_384);
665 write_setting(&mut payload, 0x6, 4096);
666 let settings = parse_settings(&payload).expect("parse settings");
667 assert_eq!(settings[0], Setting::EnablePush(1));
668 assert_eq!(settings[1], Setting::MaxFrameSize(16_384));
669 assert_eq!(settings[2], Setting::MaxHeaderListSize(4096));
670 }
671
672 #[test]
673 fn parse_window_update_and_rst_reject_bad_lengths() {
674 assert_eq!(
675 parse_window_update(&[0u8; 3]),
676 Err(FrameError::InvalidPayloadLength)
677 );
678 assert_eq!(parse_window_update(&[0, 0, 0, 9]), Ok(9));
679 assert_eq!(
680 parse_rst_stream(&[0u8; 5]),
681 Err(FrameError::InvalidPayloadLength)
682 );
683 }
684
685 #[test]
686 fn garbage_never_panics() {
687 let mut x: u64 = 0x9e37_79b9_7f4a_7c15;
688 for len in 0..400u64 {
689 let mut data = Vec::with_capacity(len as usize);
690 for _ in 0..len {
691 x ^= x << 13;
692 x ^= x >> 7;
693 x ^= x << 17;
694 data.push(x as u8);
695 }
696 if let Ok(hdr) = parse_header(&data) {
697 let _ = validate_payload(
698 &hdr,
699 data.get(FRAME_HEADER_LEN..).unwrap_or(&[]),
700 DEFAULT_MAX_FRAME_SIZE,
701 );
702 let _ = FrameKind::from_u8(hdr.kind.as_u8());
703 }
704 }
705 }
706}