Skip to main content

stackforge_core/layer/http2/
frames.rs

1//! HTTP/2 frame parsing (RFC 7540 Section 4).
2//!
3//! HTTP/2 frames have a fixed 9-byte header:
4//! ```text
5//! +-----------------------------------------------+
6//! |                 Length (24)                   |
7//! +---------------+---------------+---------------+
8//! |   Type (8)    |   Flags (8)   |
9//! +-+-------------+---------------+-------------------------------+
10//! |R|                 Stream Identifier (31)                      |
11//! +=+=============================================================+
12//! |                   Frame Payload (0...)                      ...
13//! +---------------------------------------------------------------+
14//! ```
15//!
16//! This module provides types and functions for parsing all HTTP/2 frame types.
17
18// ============================================================================
19// Constants
20// ============================================================================
21
22/// The HTTP/2 client connection preface (RFC 7540 Section 3.5).
23pub const HTTP2_PREFACE: &[u8] = b"PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n";
24
25/// Length of the HTTP/2 frame header in bytes.
26pub const HTTP2_FRAME_HEADER_LEN: usize = 9;
27
28// ============================================================================
29// Frame Type
30// ============================================================================
31
32/// HTTP/2 frame type identifiers (RFC 7540 Section 6).
33///
34/// Note: Explicit discriminants are not used because the enum has a non-unit
35/// variant (`Unknown`). Use `as_u8()` to get the numeric frame type value.
36#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
37pub enum Http2FrameType {
38    /// DATA frame (type=0x0).
39    Data,
40    /// HEADERS frame (type=0x1).
41    Headers,
42    /// PRIORITY frame (type=0x2).
43    Priority,
44    /// `RST_STREAM` frame (type=0x3).
45    RstStream,
46    /// SETTINGS frame (type=0x4).
47    Settings,
48    /// `PUSH_PROMISE` frame (type=0x5).
49    PushPromise,
50    /// PING frame (type=0x6).
51    Ping,
52    /// GOAWAY frame (type=0x7).
53    GoAway,
54    /// `WINDOW_UPDATE` frame (type=0x8).
55    WindowUpdate,
56    /// CONTINUATION frame (type=0x9).
57    Continuation,
58    /// Unknown frame type with raw byte value.
59    Unknown(u8),
60}
61
62impl Http2FrameType {
63    /// Convert a raw u8 byte to an `Http2FrameType`.
64    #[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    /// Get the human-readable name of this frame type.
82    #[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    /// Convert to raw u8 byte value.
100    #[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
124// ============================================================================
125// Frame Flags
126// ============================================================================
127
128/// Flag constants for HTTP/2 frame types (RFC 7540 Section 6).
129pub mod flags {
130    /// DATA frame: `END_STREAM` flag (0x1) - indicates the last DATA frame for a stream.
131    pub const DATA_END_STREAM: u8 = 0x01;
132    /// DATA frame: PADDED flag (0x8) - indicates padding is present.
133    pub const DATA_PADDED: u8 = 0x08;
134
135    /// HEADERS frame: `END_STREAM` flag (0x1) - last HEADERS/CONTINUATION for this stream.
136    pub const HEADERS_END_STREAM: u8 = 0x01;
137    /// HEADERS frame: `END_HEADERS` flag (0x4) - the frame contains a complete header block.
138    pub const HEADERS_END_HEADERS: u8 = 0x04;
139    /// HEADERS frame: PADDED flag (0x8) - padding is present.
140    pub const HEADERS_PADDED: u8 = 0x08;
141    /// HEADERS frame: PRIORITY flag (0x20) - priority fields are present.
142    pub const HEADERS_PRIORITY: u8 = 0x20;
143
144    /// SETTINGS frame: ACK flag (0x1) - this is an acknowledgment of a SETTINGS frame.
145    pub const SETTINGS_ACK: u8 = 0x01;
146
147    /// PING frame: ACK flag (0x1) - this is a PING response.
148    pub const PING_ACK: u8 = 0x01;
149
150    /// `PUSH_PROMISE` frame: `END_HEADERS` flag (0x4) - complete header block.
151    pub const PUSH_PROMISE_END_HEADERS: u8 = 0x04;
152    /// `PUSH_PROMISE` frame: PADDED flag (0x8) - padding is present.
153    pub const PUSH_PROMISE_PADDED: u8 = 0x08;
154
155    /// CONTINUATION frame: `END_HEADERS` flag (0x4) - complete header block.
156    pub const CONTINUATION_END_HEADERS: u8 = 0x04;
157}
158
159// ============================================================================
160// HTTP/2 Settings identifiers
161// ============================================================================
162
163/// SETTINGS parameter identifiers (RFC 7540 Section 6.5.2).
164pub mod settings_id {
165    /// `SETTINGS_HEADER_TABLE_SIZE` (0x1): initial value 4096.
166    pub const HEADER_TABLE_SIZE: u16 = 0x0001;
167    /// `SETTINGS_ENABLE_PUSH` (0x2): initial value 1.
168    pub const ENABLE_PUSH: u16 = 0x0002;
169    /// `SETTINGS_MAX_CONCURRENT_STREAMS` (0x3): initially unlimited.
170    pub const MAX_CONCURRENT_STREAMS: u16 = 0x0003;
171    /// `SETTINGS_INITIAL_WINDOW_SIZE` (0x4): initial value 65535.
172    pub const INITIAL_WINDOW_SIZE: u16 = 0x0004;
173    /// `SETTINGS_MAX_FRAME_SIZE` (0x5): initial value 16384.
174    pub const MAX_FRAME_SIZE: u16 = 0x0005;
175    /// `SETTINGS_MAX_HEADER_LIST_SIZE` (0x6): initially unlimited.
176    pub const MAX_HEADER_LIST_SIZE: u16 = 0x0006;
177
178    /// Get the human-readable name of a settings ID.
179    #[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
193// ============================================================================
194// HTTP/2 Error Codes
195// ============================================================================
196
197/// HTTP/2 error codes (RFC 7540 Section 7).
198pub 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    /// Get the human-readable name for an error code.
215    #[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// ============================================================================
238// Http2Frame
239// ============================================================================
240
241/// A parsed HTTP/2 frame, holding the frame header fields and a reference
242/// to where the payload resides in the original buffer.
243#[derive(Debug, Clone)]
244pub struct Http2Frame {
245    /// 24-bit payload length.
246    pub length: u32,
247    /// Frame type.
248    pub frame_type: Http2FrameType,
249    /// Flags byte.
250    pub flags: u8,
251    /// 31-bit stream identifier (R bit masked out).
252    pub stream_id: u32,
253    /// Byte offset in the original buffer where the payload starts.
254    pub payload_offset: usize,
255    /// Total frame size in bytes: 9 (header) + payload length.
256    pub total_size: usize,
257}
258
259impl Http2Frame {
260    /// Parse one HTTP/2 frame from `buf` starting at `offset`.
261    ///
262    /// Returns `None` if there are not enough bytes for the frame header
263    /// or if the payload extends beyond the buffer.
264    #[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        // Length is 3 bytes big-endian
273        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        // Stream ID: 4 bytes, R bit (MSB) masked out
279        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        // Verify the payload is within the buffer
286        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    /// Get the payload bytes from the original buffer.
301    #[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    /// Returns true if the `END_STREAM` flag is set.
312    ///
313    /// Valid for DATA and HEADERS frames.
314    #[must_use]
315    pub fn is_end_stream(&self) -> bool {
316        (self.flags & 0x01) != 0
317    }
318
319    /// Returns true if the `END_HEADERS` flag is set.
320    ///
321    /// Valid for HEADERS, `PUSH_PROMISE`, and CONTINUATION frames.
322    #[must_use]
323    pub fn is_end_headers(&self) -> bool {
324        (self.flags & 0x04) != 0
325    }
326
327    /// Returns true if the ACK flag is set.
328    ///
329    /// Valid for SETTINGS and PING frames.
330    #[must_use]
331    pub fn is_ack(&self) -> bool {
332        (self.flags & 0x01) != 0
333    }
334
335    /// Returns true if the PADDED flag is set.
336    ///
337    /// Valid for DATA, HEADERS, and `PUSH_PROMISE` frames.
338    #[must_use]
339    pub fn is_padded(&self) -> bool {
340        (self.flags & 0x08) != 0
341    }
342
343    /// Returns true if the PRIORITY flag is set.
344    ///
345    /// Valid for HEADERS frames.
346    #[must_use]
347    pub fn has_priority(&self) -> bool {
348        (self.flags & 0x20) != 0
349    }
350
351    /// Get a human-readable summary of this frame.
352    #[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// ============================================================================
365// Multi-frame parsing
366// ============================================================================
367
368/// Parse all HTTP/2 frames from a buffer.
369///
370/// If the buffer starts with the HTTP/2 connection preface (24 bytes), it is
371/// skipped before parsing frames.
372///
373/// Returns a list of successfully parsed frames. Stops at the first truncated
374/// or malformed frame.
375#[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// ============================================================================
401// Frame-specific payload parsers
402// ============================================================================
403
404/// Parse a SETTINGS frame payload into a list of (id, value) pairs.
405///
406/// Each settings entry is 6 bytes: 2-byte identifier + 4-byte value.
407#[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/// Parse a GOAWAY frame payload.
428///
429/// Returns `Some((last_stream_id, error_code))` or `None` if the payload is too short.
430#[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/// Parse a `WINDOW_UPDATE` frame payload.
442///
443/// Returns `Some(window_size_increment)` or `None` if the payload is too short.
444#[must_use]
445pub fn parse_window_update(payload: &[u8]) -> Option<u32> {
446    if payload.len() < 4 {
447        return None;
448    }
449    // R bit (MSB) must be masked out
450    let increment =
451        u32::from_be_bytes([payload[0], payload[1], payload[2], payload[3]]) & 0x7FFFFFFF;
452    Some(increment)
453}
454
455/// Parse a `RST_STREAM` frame payload.
456///
457/// Returns `Some(error_code)` or `None` if the payload is too short.
458#[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/// Parse a PRIORITY frame payload.
468///
469/// Returns `Some((exclusive, stream_dependency, weight))` or `None` if too short.
470#[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/// Extract the header block fragment from a HEADERS frame payload.
483///
484/// Strips padding and priority fields if the corresponding flags are set.
485/// Returns the HPACK-encoded header block fragment slice.
486#[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; // 4 bytes stream dependency + 1 byte weight
508    }
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// ============================================================================
519// Tests
520// ============================================================================
521
522#[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        // SETTINGS frame with no settings (empty payload), no flags, stream=0
542        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]; // ":method: GET"
575        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        // SETTINGS frame
594        buf.extend_from_slice(&make_frame(4, 0, 0, &[]));
595        // SETTINGS ACK
596        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        // SETTINGS with two entries: INITIAL_WINDOW_SIZE=65535, MAX_FRAME_SIZE=16384
608        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()); // last_stream_id = 1
624        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)); // DATA END_STREAM
669        buf.extend_from_slice(&make_frame(4, 0x01, 0, &[])); // SETTINGS ACK
670
671        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        // 8 bytes: not enough for a 9-byte header
680        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        // 9-byte header saying payload length=100, but no payload bytes
688        let buf = [
689            0x00, 0x00, 0x64, // length = 100
690            0x00, // type = DATA
691            0x00, // flags = 0
692            0x00, 0x00, 0x00, 0x01, // stream_id = 1
693        ];
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}