h2:   HTTP/  2 Frames and HPACK
1 Frames
frame
frame->bytes
bytes->frame
frames->bytes
connection-preface
data-frame
headers-frame*
priority-frame
rst-stream-frame
settings-frame
push-promise-frame
ping-frame
goaway-frame
window-update-frame
continuation-frame
no-error
protocol-error
internal-error
flow-control-error
make-settings-frame
settings-payload->list
make-ping-frame
make-goaway-frame
make-window-update-frame
make-rst-stream-frame
make-headers-frame
headers-block->bytes
2 HPACK
make-hpack-ctx
hpack-ctx?
hpack-ctx-size
hpack-decode-headers
hpack-encode-headers
hpack-encode-integer
hpack-decode-integer
hpack-encode-string
hpack-decode-string
3 Huffman
huffman-encode
huffman-decode
huffman-encoded-length-bits
4 Status and limits
9.3

h2: HTTP/2 Frames and HPACK🔗ℹ

 (require h2) package: h2

RFC 7540 frame codec and RFC 7541 HPACK for Racket — the protocol layer of HTTP/2, the sibling of the websocket package. Pure bytes in, structures out: connections, stream state machines, and flow control stay with the caller (and are the next milestone).

1 Frames🔗ℹ

Every frame is a frame? — length, type, flags, stream id, payload. The nine-byte header is decoded with bytes->frame; building helpers exist for the payloads that matter early: SETTINGS, PING, GOAWAY, WINDOW_UPDATE, RST_STREAM, and HEADERS with padding.

struct

(struct frame (length type flags stream-id payload))

  length : exact-nonnegative-integer?
  type : exact-nonnegative-integer?
  flags : exact-nonnegative-integer?
  stream-id : exact-nonnegative-integer?
  payload : bytes?

procedure

(frame->bytes f) → bytes?

  f : frame?

procedure

(bytes->frame bstr pos) → 
frame? exact-nonnegative-integer?
  bstr : bytes?
  pos : exact-nonnegative-integer?

Decode one frame at pos; the caller buffers at least length+9 bytes. Stream ids keep 31 bits (the reserved bit reads 0).

procedure

(frames->bytes fs) → bytes?

  fs : (listof frame?)

value

connection-preface : bytes?

The fixed client preface PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n (§3.5).

Frame type constants:

Error codes:

procedure

(make-settings-frame pairs) → frame?

  pairs : (listof (cons/c exact-nonnegative-integer? exact-nonnegative-integer?))

procedure

(settings-payload->list payload)

 → (listof (cons/c exact-nonnegative-integer? exact-nonnegative-integer?))
  payload : bytes?

procedure

(make-ping-frame ack? opaque) → frame?

  ack? : boolean?
  opaque : exact-nonnegative-integer?

procedure

(make-goaway-frame last-stream-id    
  error-code    
  debug-bytes) → frame?
  last-stream-id : exact-nonnegative-integer?
  error-code : exact-nonnegative-integer?
  debug-bytes : bytes?

GOAWAY frames are always stream 0; last-stream-id rides in the payload.

procedure

(make-window-update-frame stream-id    
  increment) → frame?
  stream-id : exact-nonnegative-integer?
  increment : exact-nonnegative-integer?

procedure

(make-rst-stream-frame stream-id    
  error-code) → frame?
  stream-id : exact-nonnegative-integer?
  error-code : exact-nonnegative-integer?

procedure

(make-headers-frame stream-id    
  block    
  [#:end-stream end-stream    
  #:pad-length pad-length]) → frame?
  stream-id : exact-nonnegative-integer?
  block : bytes?
  end-stream : boolean? = #t
  pad-length : (or/c exact-nonnegative-integer? #f) = #f

procedure

(headers-block->bytes payload) → bytes?

  payload : bytes?

Strips HPACK-unrelated padding from a PADDED HEADERS/DATA payload — call it only when the PADDED flag is set.

2 HPACK🔗ℹ

procedure

(make-hpack-ctx max-size) → hpack-ctx?

  max-size : exact-nonnegative-integer?

procedure

(hpack-ctx? v) → boolean?

  v : any/c

procedure

(hpack-ctx-size ctx) → exact-nonnegative-integer?

  ctx : hpack-ctx?

The dynamic-table context (in octets, per §4.1’s 32-byte entry overhead). Decoder contexts track inserts, evictions, and size updates; encoder contexts may stay fresh — the built-in encoder uses only static-table indexing and literals without indexing, which is always legal.

procedure

(hpack-decode-headers ctx bstr)

 → (listof (cons/c string? string?))
  ctx : hpack-ctx?
  bstr : bytes?

Full §6: indexed, literal with/without/never indexing, dynamic table size updates. Invalid data (including an indexed field with index 0) raises.

procedure

(hpack-encode-headers ctx headers) → bytes?

  ctx : hpack-ctx?
  headers : (listof (cons/c string? string?))

Exact static-table matches become one indexed byte (e.g. (:method . GET) → 130, same shape as the RFC’s own examples); everything else goes out as literal without indexing, Huffman-coded when the table actually makes it shorter (ASCII only).

procedure

(hpack-encode-integer value prefix-bits) → bytes?

  value : exact-nonnegative-integer?
  prefix-bits : exact-integer?

procedure

(hpack-decode-integer bstr pos prefix-bits)

 → 
exact-nonnegative-integer?
exact-nonnegative-integer?
  bstr : bytes?
  pos : exact-nonnegative-integer?
  prefix-bits : exact-integer?

§5.1; the prefix’s high bits are the caller’s opcode bits.

procedure

(hpack-encode-string s) → bytes?

  s : string?

procedure

(hpack-decode-string bstr pos)

 → 
string? exact-nonnegative-integer?
  bstr : bytes?
  pos : exact-nonnegative-integer?

§5.2 string literals with the Huffman flag.

3 Huffman🔗ℹ

procedure

(huffman-encode s) → bytes?

  s : string?

RFC 7541 Appendix B coding, padded with 1 bits (never a whole byte).

procedure

(huffman-decode bstr) → string?

  bstr : bytes?

Rejects padding that is not a prefix of EOS, mid-stream EOS, and invalid codes.

procedure

(huffman-encoded-length-bits s) → exact-nonnegative-integer?

  s : string?

For size decisions before encoding.

4 Status and limits🔗ℹ

Frames + HPACK are complete and golden-vector tested against RFC 7541’s appendix C. The connection layer — preface exchange, stream state machine, flow control, and a working h2c client — is the next milestone; until it lands, this package is the protocol core other code can build on, tested the same way the RFC tests itself.