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 pub fn from_u8(t: u8) -> Self {
65 match t {
66 0 => Self::Data,
67 1 => Self::Headers,
68 2 => Self::Priority,
69 3 => Self::RstStream,
70 4 => Self::Settings,
71 5 => Self::PushPromise,
72 6 => Self::Ping,
73 7 => Self::GoAway,
74 8 => Self::WindowUpdate,
75 9 => Self::Continuation,
76 _ => Self::Unknown(t),
77 }
78 }
79
80 pub fn name(&self) -> &'static str {
82 match self {
83 Self::Data => "DATA",
84 Self::Headers => "HEADERS",
85 Self::Priority => "PRIORITY",
86 Self::RstStream => "RST_STREAM",
87 Self::Settings => "SETTINGS",
88 Self::PushPromise => "PUSH_PROMISE",
89 Self::Ping => "PING",
90 Self::GoAway => "GOAWAY",
91 Self::WindowUpdate => "WINDOW_UPDATE",
92 Self::Continuation => "CONTINUATION",
93 Self::Unknown(_) => "UNKNOWN",
94 }
95 }
96
97 pub fn as_u8(&self) -> u8 {
99 match self {
100 Self::Data => 0,
101 Self::Headers => 1,
102 Self::Priority => 2,
103 Self::RstStream => 3,
104 Self::Settings => 4,
105 Self::PushPromise => 5,
106 Self::Ping => 6,
107 Self::GoAway => 7,
108 Self::WindowUpdate => 8,
109 Self::Continuation => 9,
110 Self::Unknown(t) => *t,
111 }
112 }
113}
114
115impl std::fmt::Display for Http2FrameType {
116 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
117 write!(f, "{}", self.name())
118 }
119}
120
121pub mod flags {
127 pub const DATA_END_STREAM: u8 = 0x01;
129 pub const DATA_PADDED: u8 = 0x08;
131
132 pub const HEADERS_END_STREAM: u8 = 0x01;
134 pub const HEADERS_END_HEADERS: u8 = 0x04;
136 pub const HEADERS_PADDED: u8 = 0x08;
138 pub const HEADERS_PRIORITY: u8 = 0x20;
140
141 pub const SETTINGS_ACK: u8 = 0x01;
143
144 pub const PING_ACK: u8 = 0x01;
146
147 pub const PUSH_PROMISE_END_HEADERS: u8 = 0x04;
149 pub const PUSH_PROMISE_PADDED: u8 = 0x08;
151
152 pub const CONTINUATION_END_HEADERS: u8 = 0x04;
154}
155
156pub mod settings_id {
162 pub const HEADER_TABLE_SIZE: u16 = 0x0001;
164 pub const ENABLE_PUSH: u16 = 0x0002;
166 pub const MAX_CONCURRENT_STREAMS: u16 = 0x0003;
168 pub const INITIAL_WINDOW_SIZE: u16 = 0x0004;
170 pub const MAX_FRAME_SIZE: u16 = 0x0005;
172 pub const MAX_HEADER_LIST_SIZE: u16 = 0x0006;
174
175 pub fn name(id: u16) -> &'static str {
177 match id {
178 HEADER_TABLE_SIZE => "HEADER_TABLE_SIZE",
179 ENABLE_PUSH => "ENABLE_PUSH",
180 MAX_CONCURRENT_STREAMS => "MAX_CONCURRENT_STREAMS",
181 INITIAL_WINDOW_SIZE => "INITIAL_WINDOW_SIZE",
182 MAX_FRAME_SIZE => "MAX_FRAME_SIZE",
183 MAX_HEADER_LIST_SIZE => "MAX_HEADER_LIST_SIZE",
184 _ => "UNKNOWN",
185 }
186 }
187}
188
189pub mod error_codes {
195 pub const NO_ERROR: u32 = 0x0;
196 pub const PROTOCOL_ERROR: u32 = 0x1;
197 pub const INTERNAL_ERROR: u32 = 0x2;
198 pub const FLOW_CONTROL_ERROR: u32 = 0x3;
199 pub const SETTINGS_TIMEOUT: u32 = 0x4;
200 pub const STREAM_CLOSED: u32 = 0x5;
201 pub const FRAME_SIZE_ERROR: u32 = 0x6;
202 pub const REFUSED_STREAM: u32 = 0x7;
203 pub const CANCEL: u32 = 0x8;
204 pub const COMPRESSION_ERROR: u32 = 0x9;
205 pub const CONNECT_ERROR: u32 = 0xa;
206 pub const ENHANCE_YOUR_CALM: u32 = 0xb;
207 pub const INADEQUATE_SECURITY: u32 = 0xc;
208 pub const HTTP_1_1_REQUIRED: u32 = 0xd;
209
210 pub fn name(code: u32) -> &'static str {
212 match code {
213 NO_ERROR => "NO_ERROR",
214 PROTOCOL_ERROR => "PROTOCOL_ERROR",
215 INTERNAL_ERROR => "INTERNAL_ERROR",
216 FLOW_CONTROL_ERROR => "FLOW_CONTROL_ERROR",
217 SETTINGS_TIMEOUT => "SETTINGS_TIMEOUT",
218 STREAM_CLOSED => "STREAM_CLOSED",
219 FRAME_SIZE_ERROR => "FRAME_SIZE_ERROR",
220 REFUSED_STREAM => "REFUSED_STREAM",
221 CANCEL => "CANCEL",
222 COMPRESSION_ERROR => "COMPRESSION_ERROR",
223 CONNECT_ERROR => "CONNECT_ERROR",
224 ENHANCE_YOUR_CALM => "ENHANCE_YOUR_CALM",
225 INADEQUATE_SECURITY => "INADEQUATE_SECURITY",
226 HTTP_1_1_REQUIRED => "HTTP_1_1_REQUIRED",
227 _ => "UNKNOWN",
228 }
229 }
230}
231
232#[derive(Debug, Clone)]
239pub struct Http2Frame {
240 pub length: u32,
242 pub frame_type: Http2FrameType,
244 pub flags: u8,
246 pub stream_id: u32,
248 pub payload_offset: usize,
250 pub total_size: usize,
252}
253
254impl Http2Frame {
255 pub fn parse_at(buf: &[u8], offset: usize) -> Option<Self> {
260 if offset + HTTP2_FRAME_HEADER_LEN > buf.len() {
261 return None;
262 }
263
264 let header = &buf[offset..offset + HTTP2_FRAME_HEADER_LEN];
265
266 let length = ((header[0] as u32) << 16) | ((header[1] as u32) << 8) | (header[2] as u32);
268
269 let frame_type = Http2FrameType::from_u8(header[3]);
270 let flags = header[4];
271 let stream_id =
273 u32::from_be_bytes([header[5], header[6], header[7], header[8]]) & 0x7FFFFFFF;
274
275 let payload_offset = offset + HTTP2_FRAME_HEADER_LEN;
276 let total_size = HTTP2_FRAME_HEADER_LEN + length as usize;
277
278 if payload_offset + length as usize > buf.len() {
280 return None;
281 }
282
283 Some(Http2Frame {
284 length,
285 frame_type,
286 flags,
287 stream_id,
288 payload_offset,
289 total_size,
290 })
291 }
292
293 pub fn payload<'a>(&self, buf: &'a [u8]) -> &'a [u8] {
295 let end = self.payload_offset + self.length as usize;
296 if end <= buf.len() {
297 &buf[self.payload_offset..end]
298 } else {
299 &[]
300 }
301 }
302
303 pub fn is_end_stream(&self) -> bool {
307 (self.flags & 0x01) != 0
308 }
309
310 pub fn is_end_headers(&self) -> bool {
314 (self.flags & 0x04) != 0
315 }
316
317 pub fn is_ack(&self) -> bool {
321 (self.flags & 0x01) != 0
322 }
323
324 pub fn is_padded(&self) -> bool {
328 (self.flags & 0x08) != 0
329 }
330
331 pub fn has_priority(&self) -> bool {
335 (self.flags & 0x20) != 0
336 }
337
338 pub fn summary(&self) -> String {
340 format!(
341 "{} stream={} length={} flags={:#04x}",
342 self.frame_type.name(),
343 self.stream_id,
344 self.length,
345 self.flags
346 )
347 }
348}
349
350pub fn parse_all_frames(buf: &[u8]) -> Vec<Http2Frame> {
362 let mut frames = Vec::new();
363 let start = if buf.starts_with(HTTP2_PREFACE) {
364 HTTP2_PREFACE.len()
365 } else {
366 0
367 };
368
369 let mut offset = start;
370
371 loop {
372 match Http2Frame::parse_at(buf, offset) {
373 Some(frame) => {
374 let total = frame.total_size;
375 frames.push(frame);
376 offset += total;
377 },
378 None => break,
379 }
380 }
381
382 frames
383}
384
385pub fn parse_settings(payload: &[u8]) -> Vec<(u16, u32)> {
393 let mut settings = Vec::new();
394 let mut pos = 0;
395
396 while pos + 6 <= payload.len() {
397 let id = u16::from_be_bytes([payload[pos], payload[pos + 1]]);
398 let value = u32::from_be_bytes([
399 payload[pos + 2],
400 payload[pos + 3],
401 payload[pos + 4],
402 payload[pos + 5],
403 ]);
404 settings.push((id, value));
405 pos += 6;
406 }
407
408 settings
409}
410
411pub fn parse_goaway(payload: &[u8]) -> Option<(u32, u32)> {
415 if payload.len() < 8 {
416 return None;
417 }
418 let last_stream_id =
419 u32::from_be_bytes([payload[0], payload[1], payload[2], payload[3]]) & 0x7FFFFFFF;
420 let error_code = u32::from_be_bytes([payload[4], payload[5], payload[6], payload[7]]);
421 Some((last_stream_id, error_code))
422}
423
424pub fn parse_window_update(payload: &[u8]) -> Option<u32> {
428 if payload.len() < 4 {
429 return None;
430 }
431 let increment =
433 u32::from_be_bytes([payload[0], payload[1], payload[2], payload[3]]) & 0x7FFFFFFF;
434 Some(increment)
435}
436
437pub fn parse_rst_stream(payload: &[u8]) -> Option<u32> {
441 if payload.len() < 4 {
442 return None;
443 }
444 let error_code = u32::from_be_bytes([payload[0], payload[1], payload[2], payload[3]]);
445 Some(error_code)
446}
447
448pub fn parse_priority(payload: &[u8]) -> Option<(bool, u32, u8)> {
452 if payload.len() < 5 {
453 return None;
454 }
455 let word = u32::from_be_bytes([payload[0], payload[1], payload[2], payload[3]]);
456 let exclusive = (word & 0x80000000) != 0;
457 let stream_dependency = word & 0x7FFFFFFF;
458 let weight = payload[4];
459 Some((exclusive, stream_dependency, weight))
460}
461
462pub fn headers_fragment<'a>(frame: &Http2Frame, buf: &'a [u8]) -> Option<&'a [u8]> {
467 if frame.frame_type != Http2FrameType::Headers {
468 return None;
469 }
470
471 let payload = frame.payload(buf);
472 let mut start = 0;
473
474 let pad_length = if frame.is_padded() {
475 if payload.is_empty() {
476 return None;
477 }
478 let pl = payload[0] as usize;
479 start += 1;
480 pl
481 } else {
482 0
483 };
484
485 if frame.has_priority() {
486 start += 5; }
488
489 let end = payload.len().saturating_sub(pad_length);
490 if start > end {
491 return None;
492 }
493
494 Some(&payload[start..end])
495}
496
497#[cfg(test)]
502mod tests {
503 use super::*;
504
505 fn make_frame(frame_type: u8, flags: u8, stream_id: u32, payload: &[u8]) -> Vec<u8> {
506 let len = payload.len() as u32;
507 let mut out = Vec::new();
508 out.push(((len >> 16) & 0xFF) as u8);
509 out.push(((len >> 8) & 0xFF) as u8);
510 out.push((len & 0xFF) as u8);
511 out.push(frame_type);
512 out.push(flags);
513 out.extend_from_slice(&(stream_id & 0x7FFFFFFF).to_be_bytes());
514 out.extend_from_slice(payload);
515 out
516 }
517
518 #[test]
519 fn test_parse_settings_frame() {
520 let frame_bytes = make_frame(4, 0, 0, &[]);
522 let frame = Http2Frame::parse_at(&frame_bytes, 0).unwrap();
523
524 assert_eq!(frame.frame_type, Http2FrameType::Settings);
525 assert_eq!(frame.flags, 0);
526 assert_eq!(frame.stream_id, 0);
527 assert_eq!(frame.length, 0);
528 }
529
530 #[test]
531 fn test_parse_settings_ack() {
532 let frame_bytes = make_frame(4, flags::SETTINGS_ACK, 0, &[]);
533 let frame = Http2Frame::parse_at(&frame_bytes, 0).unwrap();
534
535 assert_eq!(frame.frame_type, Http2FrameType::Settings);
536 assert!(frame.is_ack());
537 }
538
539 #[test]
540 fn test_parse_data_frame() {
541 let payload = b"Hello, HTTP/2!";
542 let frame_bytes = make_frame(0, flags::DATA_END_STREAM, 1, payload);
543 let frame = Http2Frame::parse_at(&frame_bytes, 0).unwrap();
544
545 assert_eq!(frame.frame_type, Http2FrameType::Data);
546 assert!(frame.is_end_stream());
547 assert_eq!(frame.stream_id, 1);
548 assert_eq!(frame.payload(&frame_bytes), payload);
549 }
550
551 #[test]
552 fn test_parse_headers_frame() {
553 let hpack_data = vec![0x82u8]; let frame_bytes = make_frame(
555 1,
556 flags::HEADERS_END_HEADERS | flags::HEADERS_END_STREAM,
557 1,
558 &hpack_data,
559 );
560 let frame = Http2Frame::parse_at(&frame_bytes, 0).unwrap();
561
562 assert_eq!(frame.frame_type, Http2FrameType::Headers);
563 assert!(frame.is_end_headers());
564 assert!(frame.is_end_stream());
565 assert_eq!(frame.stream_id, 1);
566 }
567
568 #[test]
569 fn test_parse_all_frames_with_preface() {
570 let mut buf = Vec::new();
571 buf.extend_from_slice(HTTP2_PREFACE);
572 buf.extend_from_slice(&make_frame(4, 0, 0, &[]));
574 buf.extend_from_slice(&make_frame(4, flags::SETTINGS_ACK, 0, &[]));
576
577 let frames = parse_all_frames(&buf);
578 assert_eq!(frames.len(), 2);
579 assert_eq!(frames[0].frame_type, Http2FrameType::Settings);
580 assert_eq!(frames[1].frame_type, Http2FrameType::Settings);
581 assert!(frames[1].is_ack());
582 }
583
584 #[test]
585 fn test_parse_settings_payload() {
586 let mut payload = Vec::new();
588 payload.extend_from_slice(&settings_id::INITIAL_WINDOW_SIZE.to_be_bytes());
589 payload.extend_from_slice(&65535u32.to_be_bytes());
590 payload.extend_from_slice(&settings_id::MAX_FRAME_SIZE.to_be_bytes());
591 payload.extend_from_slice(&16384u32.to_be_bytes());
592
593 let settings = parse_settings(&payload);
594 assert_eq!(settings.len(), 2);
595 assert_eq!(settings[0], (settings_id::INITIAL_WINDOW_SIZE, 65535));
596 assert_eq!(settings[1], (settings_id::MAX_FRAME_SIZE, 16384));
597 }
598
599 #[test]
600 fn test_parse_goaway_frame() {
601 let mut payload = Vec::new();
602 payload.extend_from_slice(&1u32.to_be_bytes()); payload.extend_from_slice(&error_codes::NO_ERROR.to_be_bytes());
604
605 let (last_id, error) = parse_goaway(&payload).unwrap();
606 assert_eq!(last_id, 1);
607 assert_eq!(error, error_codes::NO_ERROR);
608 }
609
610 #[test]
611 fn test_parse_window_update() {
612 let mut payload = Vec::new();
613 payload.extend_from_slice(&65535u32.to_be_bytes());
614 let increment = parse_window_update(&payload).unwrap();
615 assert_eq!(increment, 65535);
616 }
617
618 #[test]
619 fn test_parse_rst_stream() {
620 let mut payload = Vec::new();
621 payload.extend_from_slice(&error_codes::CANCEL.to_be_bytes());
622 let error = parse_rst_stream(&payload).unwrap();
623 assert_eq!(error, error_codes::CANCEL);
624 }
625
626 #[test]
627 fn test_frame_type_names() {
628 assert_eq!(Http2FrameType::Data.name(), "DATA");
629 assert_eq!(Http2FrameType::Headers.name(), "HEADERS");
630 assert_eq!(Http2FrameType::Settings.name(), "SETTINGS");
631 assert_eq!(Http2FrameType::GoAway.name(), "GOAWAY");
632 assert_eq!(Http2FrameType::Unknown(0xff).name(), "UNKNOWN");
633 }
634
635 #[test]
636 fn test_frame_type_roundtrip() {
637 for t in 0..=9u8 {
638 let ft = Http2FrameType::from_u8(t);
639 assert_eq!(ft.as_u8(), t);
640 }
641 }
642
643 #[test]
644 fn test_parse_multiple_frames() {
645 let data_payload = b"test data";
646 let mut buf = Vec::new();
647 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);
651 assert_eq!(frames.len(), 2);
652 assert_eq!(frames[0].frame_type, Http2FrameType::Data);
653 assert_eq!(frames[1].frame_type, Http2FrameType::Settings);
654 }
655
656 #[test]
657 fn test_frame_too_short() {
658 let buf = [0u8; 8];
660 let result = Http2Frame::parse_at(&buf, 0);
661 assert!(result.is_none());
662 }
663
664 #[test]
665 fn test_frame_payload_truncated() {
666 let buf = [
668 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, ];
673 let result = Http2Frame::parse_at(&buf, 0);
674 assert!(result.is_none());
675 }
676
677 #[test]
678 fn test_http2_preface_const() {
679 assert_eq!(HTTP2_PREFACE.len(), 24);
680 assert_eq!(HTTP2_PREFACE, b"PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n");
681 }
682
683 #[test]
684 fn test_ping_frame() {
685 let ping_data = [0u8; 8];
686 let frame_bytes = make_frame(6, 0, 0, &ping_data);
687 let frame = Http2Frame::parse_at(&frame_bytes, 0).unwrap();
688
689 assert_eq!(frame.frame_type, Http2FrameType::Ping);
690 assert!(!frame.is_ack());
691 assert_eq!(frame.payload(&frame_bytes).len(), 8);
692 }
693
694 #[test]
695 fn test_settings_id_names() {
696 assert_eq!(
697 settings_id::name(settings_id::HEADER_TABLE_SIZE),
698 "HEADER_TABLE_SIZE"
699 );
700 assert_eq!(settings_id::name(settings_id::ENABLE_PUSH), "ENABLE_PUSH");
701 assert_eq!(settings_id::name(0x99), "UNKNOWN");
702 }
703
704 #[test]
705 fn test_error_code_names() {
706 assert_eq!(error_codes::name(error_codes::NO_ERROR), "NO_ERROR");
707 assert_eq!(
708 error_codes::name(error_codes::PROTOCOL_ERROR),
709 "PROTOCOL_ERROR"
710 );
711 assert_eq!(error_codes::name(0x99), "UNKNOWN");
712 }
713}