1pub const HTTP2_PREFACE: &[u8] = b"PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n";
24
25pub const HTTP2_FRAME_HEADER_LEN: usize = 9;
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
37pub enum Http2FrameType {
38 Data,
40 Headers,
42 Priority,
44 RstStream,
46 Settings,
48 PushPromise,
50 Ping,
52 GoAway,
54 WindowUpdate,
56 Continuation,
58 Unknown(u8),
60}
61
62impl Http2FrameType {
63 #[must_use]
65 pub fn from_u8(t: u8) -> Self {
66 match t {
67 0 => Self::Data,
68 1 => Self::Headers,
69 2 => Self::Priority,
70 3 => Self::RstStream,
71 4 => Self::Settings,
72 5 => Self::PushPromise,
73 6 => Self::Ping,
74 7 => Self::GoAway,
75 8 => Self::WindowUpdate,
76 9 => Self::Continuation,
77 _ => Self::Unknown(t),
78 }
79 }
80
81 #[must_use]
83 pub fn name(&self) -> &'static str {
84 match self {
85 Self::Data => "DATA",
86 Self::Headers => "HEADERS",
87 Self::Priority => "PRIORITY",
88 Self::RstStream => "RST_STREAM",
89 Self::Settings => "SETTINGS",
90 Self::PushPromise => "PUSH_PROMISE",
91 Self::Ping => "PING",
92 Self::GoAway => "GOAWAY",
93 Self::WindowUpdate => "WINDOW_UPDATE",
94 Self::Continuation => "CONTINUATION",
95 Self::Unknown(_) => "UNKNOWN",
96 }
97 }
98
99 #[must_use]
101 pub fn as_u8(&self) -> u8 {
102 match self {
103 Self::Data => 0,
104 Self::Headers => 1,
105 Self::Priority => 2,
106 Self::RstStream => 3,
107 Self::Settings => 4,
108 Self::PushPromise => 5,
109 Self::Ping => 6,
110 Self::GoAway => 7,
111 Self::WindowUpdate => 8,
112 Self::Continuation => 9,
113 Self::Unknown(t) => *t,
114 }
115 }
116}
117
118impl std::fmt::Display for Http2FrameType {
119 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
120 write!(f, "{}", self.name())
121 }
122}
123
124pub mod flags {
130 pub const DATA_END_STREAM: u8 = 0x01;
132 pub const DATA_PADDED: u8 = 0x08;
134
135 pub const HEADERS_END_STREAM: u8 = 0x01;
137 pub const HEADERS_END_HEADERS: u8 = 0x04;
139 pub const HEADERS_PADDED: u8 = 0x08;
141 pub const HEADERS_PRIORITY: u8 = 0x20;
143
144 pub const SETTINGS_ACK: u8 = 0x01;
146
147 pub const PING_ACK: u8 = 0x01;
149
150 pub const PUSH_PROMISE_END_HEADERS: u8 = 0x04;
152 pub const PUSH_PROMISE_PADDED: u8 = 0x08;
154
155 pub const CONTINUATION_END_HEADERS: u8 = 0x04;
157}
158
159pub mod settings_id {
165 pub const HEADER_TABLE_SIZE: u16 = 0x0001;
167 pub const ENABLE_PUSH: u16 = 0x0002;
169 pub const MAX_CONCURRENT_STREAMS: u16 = 0x0003;
171 pub const INITIAL_WINDOW_SIZE: u16 = 0x0004;
173 pub const MAX_FRAME_SIZE: u16 = 0x0005;
175 pub const MAX_HEADER_LIST_SIZE: u16 = 0x0006;
177
178 #[must_use]
180 pub fn name(id: u16) -> &'static str {
181 match id {
182 HEADER_TABLE_SIZE => "HEADER_TABLE_SIZE",
183 ENABLE_PUSH => "ENABLE_PUSH",
184 MAX_CONCURRENT_STREAMS => "MAX_CONCURRENT_STREAMS",
185 INITIAL_WINDOW_SIZE => "INITIAL_WINDOW_SIZE",
186 MAX_FRAME_SIZE => "MAX_FRAME_SIZE",
187 MAX_HEADER_LIST_SIZE => "MAX_HEADER_LIST_SIZE",
188 _ => "UNKNOWN",
189 }
190 }
191}
192
193pub mod error_codes {
199 pub const NO_ERROR: u32 = 0x0;
200 pub const PROTOCOL_ERROR: u32 = 0x1;
201 pub const INTERNAL_ERROR: u32 = 0x2;
202 pub const FLOW_CONTROL_ERROR: u32 = 0x3;
203 pub const SETTINGS_TIMEOUT: u32 = 0x4;
204 pub const STREAM_CLOSED: u32 = 0x5;
205 pub const FRAME_SIZE_ERROR: u32 = 0x6;
206 pub const REFUSED_STREAM: u32 = 0x7;
207 pub const CANCEL: u32 = 0x8;
208 pub const COMPRESSION_ERROR: u32 = 0x9;
209 pub const CONNECT_ERROR: u32 = 0xa;
210 pub const ENHANCE_YOUR_CALM: u32 = 0xb;
211 pub const INADEQUATE_SECURITY: u32 = 0xc;
212 pub const HTTP_1_1_REQUIRED: u32 = 0xd;
213
214 #[must_use]
216 pub fn name(code: u32) -> &'static str {
217 match code {
218 NO_ERROR => "NO_ERROR",
219 PROTOCOL_ERROR => "PROTOCOL_ERROR",
220 INTERNAL_ERROR => "INTERNAL_ERROR",
221 FLOW_CONTROL_ERROR => "FLOW_CONTROL_ERROR",
222 SETTINGS_TIMEOUT => "SETTINGS_TIMEOUT",
223 STREAM_CLOSED => "STREAM_CLOSED",
224 FRAME_SIZE_ERROR => "FRAME_SIZE_ERROR",
225 REFUSED_STREAM => "REFUSED_STREAM",
226 CANCEL => "CANCEL",
227 COMPRESSION_ERROR => "COMPRESSION_ERROR",
228 CONNECT_ERROR => "CONNECT_ERROR",
229 ENHANCE_YOUR_CALM => "ENHANCE_YOUR_CALM",
230 INADEQUATE_SECURITY => "INADEQUATE_SECURITY",
231 HTTP_1_1_REQUIRED => "HTTP_1_1_REQUIRED",
232 _ => "UNKNOWN",
233 }
234 }
235}
236
237#[derive(Debug, Clone)]
244pub struct Http2Frame {
245 pub length: u32,
247 pub frame_type: Http2FrameType,
249 pub flags: u8,
251 pub stream_id: u32,
253 pub payload_offset: usize,
255 pub total_size: usize,
257}
258
259impl Http2Frame {
260 #[must_use]
265 pub fn parse_at(buf: &[u8], offset: usize) -> Option<Self> {
266 if offset + HTTP2_FRAME_HEADER_LEN > buf.len() {
267 return None;
268 }
269
270 let header = &buf[offset..offset + HTTP2_FRAME_HEADER_LEN];
271
272 let length =
274 (u32::from(header[0]) << 16) | (u32::from(header[1]) << 8) | u32::from(header[2]);
275
276 let frame_type = Http2FrameType::from_u8(header[3]);
277 let flags = header[4];
278 let stream_id =
280 u32::from_be_bytes([header[5], header[6], header[7], header[8]]) & 0x7FFFFFFF;
281
282 let payload_offset = offset + HTTP2_FRAME_HEADER_LEN;
283 let total_size = HTTP2_FRAME_HEADER_LEN + length as usize;
284
285 if payload_offset + length as usize > buf.len() {
287 return None;
288 }
289
290 Some(Http2Frame {
291 length,
292 frame_type,
293 flags,
294 stream_id,
295 payload_offset,
296 total_size,
297 })
298 }
299
300 #[must_use]
302 pub fn payload<'a>(&self, buf: &'a [u8]) -> &'a [u8] {
303 let end = self.payload_offset + self.length as usize;
304 if end <= buf.len() {
305 &buf[self.payload_offset..end]
306 } else {
307 &[]
308 }
309 }
310
311 #[must_use]
315 pub fn is_end_stream(&self) -> bool {
316 (self.flags & 0x01) != 0
317 }
318
319 #[must_use]
323 pub fn is_end_headers(&self) -> bool {
324 (self.flags & 0x04) != 0
325 }
326
327 #[must_use]
331 pub fn is_ack(&self) -> bool {
332 (self.flags & 0x01) != 0
333 }
334
335 #[must_use]
339 pub fn is_padded(&self) -> bool {
340 (self.flags & 0x08) != 0
341 }
342
343 #[must_use]
347 pub fn has_priority(&self) -> bool {
348 (self.flags & 0x20) != 0
349 }
350
351 #[must_use]
353 pub fn summary(&self) -> String {
354 format!(
355 "{} stream={} length={} flags={:#04x}",
356 self.frame_type.name(),
357 self.stream_id,
358 self.length,
359 self.flags
360 )
361 }
362}
363
364#[must_use]
376pub fn parse_all_frames(buf: &[u8]) -> Vec<Http2Frame> {
377 let mut frames = Vec::new();
378 let start = if buf.starts_with(HTTP2_PREFACE) {
379 HTTP2_PREFACE.len()
380 } else {
381 0
382 };
383
384 let mut offset = start;
385
386 loop {
387 match Http2Frame::parse_at(buf, offset) {
388 Some(frame) => {
389 let total = frame.total_size;
390 frames.push(frame);
391 offset += total;
392 },
393 None => break,
394 }
395 }
396
397 frames
398}
399
400#[must_use]
408pub fn parse_settings(payload: &[u8]) -> Vec<(u16, u32)> {
409 let mut settings = Vec::new();
410 let mut pos = 0;
411
412 while pos + 6 <= payload.len() {
413 let id = u16::from_be_bytes([payload[pos], payload[pos + 1]]);
414 let value = u32::from_be_bytes([
415 payload[pos + 2],
416 payload[pos + 3],
417 payload[pos + 4],
418 payload[pos + 5],
419 ]);
420 settings.push((id, value));
421 pos += 6;
422 }
423
424 settings
425}
426
427#[must_use]
431pub fn parse_goaway(payload: &[u8]) -> Option<(u32, u32)> {
432 if payload.len() < 8 {
433 return None;
434 }
435 let last_stream_id =
436 u32::from_be_bytes([payload[0], payload[1], payload[2], payload[3]]) & 0x7FFFFFFF;
437 let error_code = u32::from_be_bytes([payload[4], payload[5], payload[6], payload[7]]);
438 Some((last_stream_id, error_code))
439}
440
441#[must_use]
445pub fn parse_window_update(payload: &[u8]) -> Option<u32> {
446 if payload.len() < 4 {
447 return None;
448 }
449 let increment =
451 u32::from_be_bytes([payload[0], payload[1], payload[2], payload[3]]) & 0x7FFFFFFF;
452 Some(increment)
453}
454
455#[must_use]
459pub fn parse_rst_stream(payload: &[u8]) -> Option<u32> {
460 if payload.len() < 4 {
461 return None;
462 }
463 let error_code = u32::from_be_bytes([payload[0], payload[1], payload[2], payload[3]]);
464 Some(error_code)
465}
466
467#[must_use]
471pub fn parse_priority(payload: &[u8]) -> Option<(bool, u32, u8)> {
472 if payload.len() < 5 {
473 return None;
474 }
475 let word = u32::from_be_bytes([payload[0], payload[1], payload[2], payload[3]]);
476 let exclusive = (word & 0x80000000) != 0;
477 let stream_dependency = word & 0x7FFFFFFF;
478 let weight = payload[4];
479 Some((exclusive, stream_dependency, weight))
480}
481
482#[must_use]
487pub fn headers_fragment<'a>(frame: &Http2Frame, buf: &'a [u8]) -> Option<&'a [u8]> {
488 if frame.frame_type != Http2FrameType::Headers {
489 return None;
490 }
491
492 let payload = frame.payload(buf);
493 let mut start = 0;
494
495 let pad_length = if frame.is_padded() {
496 if payload.is_empty() {
497 return None;
498 }
499 let pl = payload[0] as usize;
500 start += 1;
501 pl
502 } else {
503 0
504 };
505
506 if frame.has_priority() {
507 start += 5; }
509
510 let end = payload.len().saturating_sub(pad_length);
511 if start > end {
512 return None;
513 }
514
515 Some(&payload[start..end])
516}
517
518#[cfg(test)]
523mod tests {
524 use super::*;
525
526 fn make_frame(frame_type: u8, flags: u8, stream_id: u32, payload: &[u8]) -> Vec<u8> {
527 let len = payload.len() as u32;
528 let mut out = Vec::new();
529 out.push(((len >> 16) & 0xFF) as u8);
530 out.push(((len >> 8) & 0xFF) as u8);
531 out.push((len & 0xFF) as u8);
532 out.push(frame_type);
533 out.push(flags);
534 out.extend_from_slice(&(stream_id & 0x7FFFFFFF).to_be_bytes());
535 out.extend_from_slice(payload);
536 out
537 }
538
539 #[test]
540 fn test_parse_settings_frame() {
541 let frame_bytes = make_frame(4, 0, 0, &[]);
543 let frame = Http2Frame::parse_at(&frame_bytes, 0).unwrap();
544
545 assert_eq!(frame.frame_type, Http2FrameType::Settings);
546 assert_eq!(frame.flags, 0);
547 assert_eq!(frame.stream_id, 0);
548 assert_eq!(frame.length, 0);
549 }
550
551 #[test]
552 fn test_parse_settings_ack() {
553 let frame_bytes = make_frame(4, flags::SETTINGS_ACK, 0, &[]);
554 let frame = Http2Frame::parse_at(&frame_bytes, 0).unwrap();
555
556 assert_eq!(frame.frame_type, Http2FrameType::Settings);
557 assert!(frame.is_ack());
558 }
559
560 #[test]
561 fn test_parse_data_frame() {
562 let payload = b"Hello, HTTP/2!";
563 let frame_bytes = make_frame(0, flags::DATA_END_STREAM, 1, payload);
564 let frame = Http2Frame::parse_at(&frame_bytes, 0).unwrap();
565
566 assert_eq!(frame.frame_type, Http2FrameType::Data);
567 assert!(frame.is_end_stream());
568 assert_eq!(frame.stream_id, 1);
569 assert_eq!(frame.payload(&frame_bytes), payload);
570 }
571
572 #[test]
573 fn test_parse_headers_frame() {
574 let hpack_data = vec![0x82u8]; let frame_bytes = make_frame(
576 1,
577 flags::HEADERS_END_HEADERS | flags::HEADERS_END_STREAM,
578 1,
579 &hpack_data,
580 );
581 let frame = Http2Frame::parse_at(&frame_bytes, 0).unwrap();
582
583 assert_eq!(frame.frame_type, Http2FrameType::Headers);
584 assert!(frame.is_end_headers());
585 assert!(frame.is_end_stream());
586 assert_eq!(frame.stream_id, 1);
587 }
588
589 #[test]
590 fn test_parse_all_frames_with_preface() {
591 let mut buf = Vec::new();
592 buf.extend_from_slice(HTTP2_PREFACE);
593 buf.extend_from_slice(&make_frame(4, 0, 0, &[]));
595 buf.extend_from_slice(&make_frame(4, flags::SETTINGS_ACK, 0, &[]));
597
598 let frames = parse_all_frames(&buf);
599 assert_eq!(frames.len(), 2);
600 assert_eq!(frames[0].frame_type, Http2FrameType::Settings);
601 assert_eq!(frames[1].frame_type, Http2FrameType::Settings);
602 assert!(frames[1].is_ack());
603 }
604
605 #[test]
606 fn test_parse_settings_payload() {
607 let mut payload = Vec::new();
609 payload.extend_from_slice(&settings_id::INITIAL_WINDOW_SIZE.to_be_bytes());
610 payload.extend_from_slice(&65535u32.to_be_bytes());
611 payload.extend_from_slice(&settings_id::MAX_FRAME_SIZE.to_be_bytes());
612 payload.extend_from_slice(&16384u32.to_be_bytes());
613
614 let settings = parse_settings(&payload);
615 assert_eq!(settings.len(), 2);
616 assert_eq!(settings[0], (settings_id::INITIAL_WINDOW_SIZE, 65535));
617 assert_eq!(settings[1], (settings_id::MAX_FRAME_SIZE, 16384));
618 }
619
620 #[test]
621 fn test_parse_goaway_frame() {
622 let mut payload = Vec::new();
623 payload.extend_from_slice(&1u32.to_be_bytes()); payload.extend_from_slice(&error_codes::NO_ERROR.to_be_bytes());
625
626 let (last_id, error) = parse_goaway(&payload).unwrap();
627 assert_eq!(last_id, 1);
628 assert_eq!(error, error_codes::NO_ERROR);
629 }
630
631 #[test]
632 fn test_parse_window_update() {
633 let mut payload = Vec::new();
634 payload.extend_from_slice(&65535u32.to_be_bytes());
635 let increment = parse_window_update(&payload).unwrap();
636 assert_eq!(increment, 65535);
637 }
638
639 #[test]
640 fn test_parse_rst_stream() {
641 let mut payload = Vec::new();
642 payload.extend_from_slice(&error_codes::CANCEL.to_be_bytes());
643 let error = parse_rst_stream(&payload).unwrap();
644 assert_eq!(error, error_codes::CANCEL);
645 }
646
647 #[test]
648 fn test_frame_type_names() {
649 assert_eq!(Http2FrameType::Data.name(), "DATA");
650 assert_eq!(Http2FrameType::Headers.name(), "HEADERS");
651 assert_eq!(Http2FrameType::Settings.name(), "SETTINGS");
652 assert_eq!(Http2FrameType::GoAway.name(), "GOAWAY");
653 assert_eq!(Http2FrameType::Unknown(0xff).name(), "UNKNOWN");
654 }
655
656 #[test]
657 fn test_frame_type_roundtrip() {
658 for t in 0..=9u8 {
659 let ft = Http2FrameType::from_u8(t);
660 assert_eq!(ft.as_u8(), t);
661 }
662 }
663
664 #[test]
665 fn test_parse_multiple_frames() {
666 let data_payload = b"test data";
667 let mut buf = Vec::new();
668 buf.extend_from_slice(&make_frame(0, 0x01, 1, data_payload)); buf.extend_from_slice(&make_frame(4, 0x01, 0, &[])); let frames = parse_all_frames(&buf);
672 assert_eq!(frames.len(), 2);
673 assert_eq!(frames[0].frame_type, Http2FrameType::Data);
674 assert_eq!(frames[1].frame_type, Http2FrameType::Settings);
675 }
676
677 #[test]
678 fn test_frame_too_short() {
679 let buf = [0u8; 8];
681 let result = Http2Frame::parse_at(&buf, 0);
682 assert!(result.is_none());
683 }
684
685 #[test]
686 fn test_frame_payload_truncated() {
687 let buf = [
689 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, ];
694 let result = Http2Frame::parse_at(&buf, 0);
695 assert!(result.is_none());
696 }
697
698 #[test]
699 fn test_http2_preface_const() {
700 assert_eq!(HTTP2_PREFACE.len(), 24);
701 assert_eq!(HTTP2_PREFACE, b"PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n");
702 }
703
704 #[test]
705 fn test_ping_frame() {
706 let ping_data = [0u8; 8];
707 let frame_bytes = make_frame(6, 0, 0, &ping_data);
708 let frame = Http2Frame::parse_at(&frame_bytes, 0).unwrap();
709
710 assert_eq!(frame.frame_type, Http2FrameType::Ping);
711 assert!(!frame.is_ack());
712 assert_eq!(frame.payload(&frame_bytes).len(), 8);
713 }
714
715 #[test]
716 fn test_settings_id_names() {
717 assert_eq!(
718 settings_id::name(settings_id::HEADER_TABLE_SIZE),
719 "HEADER_TABLE_SIZE"
720 );
721 assert_eq!(settings_id::name(settings_id::ENABLE_PUSH), "ENABLE_PUSH");
722 assert_eq!(settings_id::name(0x99), "UNKNOWN");
723 }
724
725 #[test]
726 fn test_error_code_names() {
727 assert_eq!(error_codes::name(error_codes::NO_ERROR), "NO_ERROR");
728 assert_eq!(
729 error_codes::name(error_codes::PROTOCOL_ERROR),
730 "PROTOCOL_ERROR"
731 );
732 assert_eq!(error_codes::name(0x99), "UNKNOWN");
733 }
734}