Module H2_frame

HTTP/2 Frame Layer.

This module implements HTTP/2 frame parsing and serialization as specified in RFC 9113 Section 4 and RFC 9113 Section 6.

Frame Structure

All HTTP/2 frames share a common 9-octet header:

+-----------------------------------------------+
|                 Length (24)                   |
+---------------+---------------+---------------+
|   Type (8)    |   Flags (8)   |
+-+-------------+---------------+-------------------------------+
|R|                 Stream Identifier (31)                      |
+=+=============================================================+
|                   Frame Payload (0...)                      ...
+---------------------------------------------------------------+

Per RFC 9113 Section 4.1:

Stream Identifier

type stream_id = int32

Stream identifier. Per RFC 9113 Section 5.1.1:

  • Client-initiated streams use odd numbers
  • Server-initiated streams use even numbers
  • Stream 0 is reserved for connection-level frames
val stream_id_is_client_initiated : stream_id -> bool

stream_id_is_client_initiated id returns true for odd stream IDs.

val stream_id_is_server_initiated : stream_id -> bool

stream_id_is_server_initiated id returns true for even non-zero stream IDs.

Frame Types

Per RFC 9113 Section 6.

type frame_type =
  1. | Data
    (*

    0x00 - RFC 9113 Section 6.1

    *)
  2. | Headers
    (*

    0x01 - RFC 9113 Section 6.2

    *)
  3. | Priority
    (*

    0x02 - RFC 9113 Section 6.3 (deprecated)

    *)
  4. | Rst_stream
    (*

    0x03 - RFC 9113 Section 6.4

    *)
  5. | Settings
    (*

    0x04 - RFC 9113 Section 6.5

    *)
  6. | Push_promise
    (*

    0x05 - RFC 9113 Section 6.6

    *)
  7. | Ping
    (*

    0x06 - RFC 9113 Section 6.7

    *)
  8. | Goaway
    (*

    0x07 - RFC 9113 Section 6.8

    *)
  9. | Window_update
    (*

    0x08 - RFC 9113 Section 6.9

    *)
  10. | Continuation
    (*

    0x09 - RFC 9113 Section 6.10

    *)
  11. | Unknown of int
    (*

    Unknown frame type - MUST be ignored per RFC 9113 Section 5.5

    *)
val frame_type_to_int : frame_type -> int

Convert frame type to its numeric value.

val frame_type_of_int : int -> frame_type

Convert numeric value to frame type.

val pp_frame_type : Format.formatter -> frame_type -> unit

Pretty printer for frame types.

Frame Flags

module Flags : sig ... end

Frame Header

type frame_header = {
  1. length : int;
    (*

    Payload length (24-bit unsigned). MUST NOT exceed SETTINGS_MAX_FRAME_SIZE.

    *)
  2. frame_type : frame_type;
    (*

    Frame type (8-bit).

    *)
  3. flags : Flags.t;
    (*

    Frame-specific flags (8-bit).

    *)
  4. stream_id : stream_id;
    (*

    Stream identifier (31-bit). 0 for connection-level frames.

    *)
}
val frame_header_length : int

Frame header is always 9 octets.

val pp_frame_header : Format.formatter -> frame_header -> unit

Pretty printer for frame headers.

Error Codes

Per RFC 9113 Section 7.

type error_code =
  1. | No_error
    (*

    0x00 - Graceful shutdown

    *)
  2. | Protocol_error
    (*

    0x01 - Protocol error detected

    *)
  3. | Internal_error
    (*

    0x02 - Implementation fault

    *)
  4. | Flow_control_error
    (*

    0x03 - Flow control limits exceeded

    *)
  5. | Settings_timeout
    (*

    0x04 - Settings not acknowledged in time

    *)
  6. | Stream_closed
    (*

    0x05 - Frame received for closed stream

    *)
  7. | Frame_size_error
    (*

    0x06 - Frame size incorrect

    *)
  8. | Refused_stream
    (*

    0x07 - Stream not processed

    *)
  9. | Cancel
    (*

    0x08 - Stream cancelled

    *)
  10. | Compression_error
    (*

    0x09 - Compression state not updated

    *)
  11. | Connect_error
    (*

    0x0a - TCP connection error for CONNECT

    *)
  12. | Enhance_your_calm
    (*

    0x0b - Processing capacity exceeded

    *)
  13. | Inadequate_security
    (*

    0x0c - Negotiated TLS parameters not acceptable

    *)
  14. | Http_1_1_required
    (*

    0x0d - Use HTTP/1.1 for this request

    *)
  15. | Unknown_error of int32
    (*

    Unknown error code

    *)
val error_code_to_int32 : error_code -> int32

Convert error code to its numeric value.

val error_code_of_int32 : int32 -> error_code

Convert numeric value to error code.

val error_code_to_string : error_code -> string

Convert error code to its string representation.

val pp_error_code : Format.formatter -> error_code -> unit

Pretty printer for error codes.

Settings

Per RFC 9113 Section 6.5.2.

type setting =
  1. | Header_table_size of int
    (*

    0x01 - HPACK dynamic table size

    *)
  2. | Enable_push of bool
    (*

    0x02 - Server push enabled

    *)
  3. | Max_concurrent_streams of int32
    (*

    0x03 - Maximum concurrent streams

    *)
  4. | Initial_window_size of int32
    (*

    0x04 - Initial flow control window

    *)
  5. | Max_frame_size of int
    (*

    0x05 - Maximum frame payload size

    *)
  6. | Max_header_list_size of int
    (*

    0x06 - Maximum header list size

    *)
  7. | No_rfc7540_priorities of bool
    (*

    0x09 - RFC 9113: Deprecate RFC 7540 priorities

    *)
  8. | Unknown_setting of int * int32
    (*

    Unknown setting (id, value)

    *)
val setting_to_pair : setting -> int * int32

Convert setting to (identifier, value) pair.

val setting_of_pair : int -> int32 -> setting

Convert (identifier, value) pair to setting.

val pp_setting : Format.formatter -> setting -> unit

Pretty printer for settings.

Priority

Per RFC 9113 Section 6.3.

Note: Stream prioritization is deprecated in RFC 9113 but MUST still be parsed.

type priority = {
  1. exclusive : bool;
    (*

    Exclusive dependency flag.

    *)
  2. stream_dependency : stream_id;
    (*

    Stream this one depends on.

    *)
  3. weight : int;
    (*

    Weight 1-256 (stored as 1-256, not 0-255).

    *)
}
val default_priority : priority

Default priority: non-exclusive, depends on 0, weight 16.

val pp_priority : Format.formatter -> priority -> unit

Pretty printer for priority.

Frame Payloads

type frame_payload =
  1. | Data_payload of {
    1. data : Cstruct.t;
      (*

      The actual data being transferred.

      *)
    }
  2. | Headers_payload of {
    1. priority : priority option;
      (*

      Priority if PRIORITY flag set.

      *)
    2. header_block : Cstruct.t;
      (*

      Encoded header block fragment (HPACK).

      *)
    }
  3. | Priority_payload of priority
    (*

    Priority specification (deprecated).

    *)
  4. | Rst_stream_payload of error_code
    (*

    Error code for stream termination.

    *)
  5. | Settings_payload of setting list
    (*

    List of settings. Empty list for ACK.

    *)
  6. | Push_promise_payload of {
    1. promised_stream_id : stream_id;
      (*

      Stream ID being reserved.

      *)
    2. header_block : Cstruct.t;
      (*

      Encoded header block fragment.

      *)
    }
  7. | Ping_payload of Cstruct.t
    (*

    8 bytes of opaque data.

    *)
  8. | Goaway_payload of {
    1. last_stream_id : stream_id;
      (*

      Highest processed stream ID.

      *)
    2. error_code : error_code;
      (*

      Reason for closing connection.

      *)
    3. debug_data : Cstruct.t;
      (*

      Optional diagnostic data.

      *)
    }
  9. | Window_update_payload of int32
    (*

    Window size increment (1 to 2^31-1).

    *)
  10. | Continuation_payload of {
    1. header_block : Cstruct.t;
      (*

      Continuation of header block.

      *)
    }
  11. | Unknown_payload of Cstruct.t
    (*

    Payload for unknown frame types.

    *)

Complete Frame

type frame = {
  1. header : frame_header;
    (*

    Frame header.

    *)
  2. payload : frame_payload;
    (*

    Frame payload.

    *)
}
val pp_frame : Format.formatter -> frame -> unit

Pretty printer for frames.

Frame Parsing

Parse frames from Eio buffered input.

type parse_error =
  1. | Incomplete
    (*

    Not enough data available.

    *)
  2. | Frame_size_error of string
    (*

    Frame size exceeds limits.

    *)
  3. | Protocol_error of string
    (*

    Protocol violation.

    *)
val pp_parse_error : Format.formatter -> parse_error -> unit

Pretty printer for parse errors.

val parse_frame_header : Cstruct.t -> (frame_header, parse_error) result

parse_frame_header buf parses a 9-byte frame header. Returns Error Incomplete if buffer is too small.

val parse_frame_payload : frame_header -> Cstruct.t -> (frame_payload, parse_error) result

parse_frame_payload header buf parses frame payload based on type. The buffer should contain exactly header.length bytes.

val parse_frame : Cstruct.t -> max_frame_size:int -> (frame * int, parse_error) result

parse_frame buf ~max_frame_size parses a complete frame. Returns the frame and number of bytes consumed. max_frame_size is the current SETTINGS_MAX_FRAME_SIZE value. Returns Error Frame_size_error if payload exceeds limit.

Frame Serialization

Serialize frames to Eio buffered output.

val serialize_frame_header : frame_header -> Cstruct.t

serialize_frame_header header serializes a frame header to 9 bytes.

val serialize_frame : frame -> Cstruct.t

serialize_frame frame serializes a complete frame.

val write_frame : Eio.Buf_write.t -> frame -> unit

write_frame writer frame writes a frame to the buffer.

Frame Construction Helpers

val make_data : stream_id:stream_id -> ?end_stream:bool -> Cstruct.t -> frame

make_data ~stream_id ?end_stream data creates a DATA frame.

val make_headers : stream_id:stream_id -> ?end_stream:bool -> ?end_headers:bool -> ?priority:priority -> Cstruct.t -> frame

make_headers ~stream_id ?end_stream ?end_headers ?priority block creates a HEADERS frame.

val make_rst_stream : stream_id:stream_id -> error_code -> frame

make_rst_stream ~stream_id code creates a RST_STREAM frame.

val make_settings : ?ack:bool -> setting list -> frame

make_settings ?ack settings creates a SETTINGS frame.

val make_ping : ?ack:bool -> Cstruct.t -> frame

make_ping ?ack data creates a PING frame. data must be exactly 8 bytes.

val make_goaway : last_stream_id:stream_id -> error_code -> ?debug:string -> unit -> frame

make_goaway ~last_stream_id code ?debug () creates a GOAWAY frame.

val make_window_update : stream_id:stream_id -> int32 -> frame

make_window_update ~stream_id increment creates a WINDOW_UPDATE frame.

val make_continuation : stream_id:stream_id -> ?end_headers:bool -> Cstruct.t -> frame

make_continuation ~stream_id ?end_headers block creates a CONTINUATION frame.

Constants

val default_max_frame_size : int

Default SETTINGS_MAX_FRAME_SIZE: 16384 (2^14).

val max_max_frame_size : int

Maximum SETTINGS_MAX_FRAME_SIZE: 16777215 (2^24 - 1).

val default_initial_window_size : int32

Default initial flow control window: 65535 (2^16 - 1).

val max_window_size : int32

Maximum flow control window: 2147483647 (2^31 - 1).

val connection_preface : string

HTTP/2 connection preface (magic string): "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"

val connection_preface_length : int

Length of connection preface: 24 bytes.