ProtoCore v1.0.16
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
hpack.h
Go to the documentation of this file.
1// ProtoCore v1.0.16 - Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file hpack.h
6 * @brief HPACK header compression for HTTP/2 (RFC 7541).
7 *
8 * HPACK encodes an HTTP header list into a compact byte block and back. It combines a fixed
9 * static table (61 common header entries), a per-connection FIFO dynamic table, prefix-integer
10 * and length-prefixed string coding, and a canonical Huffman code (RFC 7541 Appendix B) for
11 * string literals. All tables are generated verbatim from the RFC.
12 *
13 * This codec is pure and host-tested (against the RFC 7541 Appendix C worked examples). The
14 * decoder resolves indexed fields, literals (with / without / never indexed), and dynamic-table
15 * size updates, maintaining the dynamic table with FIFO eviction. The encoder (server side) uses
16 * static-table indexing and literal-without-indexing with Huffman-coded strings, so it needs no
17 * dynamic-table state of its own. Zero heap; the dynamic table is a fixed byte ring.
18 *
19 * @author Douglas Quigg (dstroy0)
20 * @date 2026
21 */
22
23#ifndef PROTOCORE_HPACK_H
24#define PROTOCORE_HPACK_H
25
26#include "protocore_config.h" // the entry point: the enable gate below, and the widths
27
28#if PROTOCORE_ENABLE_HTTP2
29
31
32/** @brief Callback invoked for each decoded header; return false to abort the decode. */
33typedef proto_bool (*HpackEmitFn)(void *ctx, const char *name, size_t name_len, const char *value, size_t value_len);
34
35/** @brief RFC 7541 sec 4.2: the size a table is initialised to. */
36typedef struct
37{
38 uint32_t max_bytes; ///< the negotiated maximum, 0 = PROTOCORE_HPACK_TABLE_BYTES
39} HpackInitArgs;
40
41/** @brief RFC 7541 sec 3: the block a decode walks, and where each field is handed on. */
42typedef struct
43{
44 const uint8_t *block; ///< the header block
45 size_t len; ///< how many bytes
46 char *scratch; ///< holds one header's name+value during each emit
47 size_t scratch_cap; ///< how much room it has
48 HpackEmitFn emit; ///< run for each decoded field
49 void *ctx; ///< passed to emit
50} HpackDecodeArgs;
51
52/** @brief RFC 7541 sec 6: the field an encode emits. */
53typedef struct
54{
55 uint8_t *out; ///< where the field is written
56 size_t cap; ///< how much room it has
57 const char *name; ///< the field name
58 size_t name_len; ///< how many bytes
59 const char *value; ///< the field value
60 size_t value_len; ///< how many bytes
61} HpackEncodeArgs;
62
63/**
64 * @brief HPACK (RFC 7541): the peer encoder's dynamic table, and one field at a time.
65 *
66 * A caller sets the members a call takes, invokes it through ::Hpack, and reads the outcome off the
67 * same handle.
68 *
69 * @var HpackNs::init_args the size a table is initialised to
70 * @var HpackNs::decode_args the block a decode walks
71 * @var HpackNs::encode_args the field an encode emits
72 * @var HpackNs::ok whether the whole block decoded cleanly
73 * @var HpackNs::n bytes an encode wrote, or 0 on overflow
74 * @var HpackNs::dyn_init empty the table and set its maximum
75 * @var HpackNs::decode decode a block, emitting each field
76 * @var HpackNs::encode_header encode one field
77 *
78 * Every entry takes the borrow the table lives in; ::PROTOCORE_HPACK_BORROW is how many bytes that
79 * is. The encode reads no table, so it takes the borrow and ignores it.
80 */
81typedef struct
82{
83 HpackInitArgs init_args; ///< the members ::HpackNs::dyn_init takes
84 HpackDecodeArgs decode_args; ///< the members ::HpackNs::decode takes
85 HpackEncodeArgs encode_args; ///< the members ::HpackNs::encode_header takes
86 proto_bool ok; ///< whether the whole block decoded cleanly
87 size_t n; ///< bytes an encode wrote, or 0 on overflow
88} HpackVars;
89
90/** @brief The operands and the outcome. */
91extern HpackVars HpackV;
92
93/** @brief The entries. */
94typedef struct
95{
96 void (*const dyn_init)(uint8_t *work);
97 void (*const decode)(uint8_t *work);
98 void (*const encode_header)(uint8_t *work);
99} HpackNs;
100
101// What the table binds, defined once in the .c and taking one parameter each: everything
102// else an entry needs is an operand in HpackV or a region of the borrow at a fixed offset.
103void protocore_hpack_dyn_init(uint8_t *work);
104void protocore_hpack_decode(uint8_t *work);
105void protocore_hpack_encode_header(uint8_t *work);
106
107// `static const`, initialised HERE rather than `extern` against a definition in the .c: a
108// const object whose initializer every translation unit can see is a COMPILE-TIME FACT, so
109// `Hpack.dyn_init(work)` resolves to a named function and becomes a DIRECT call. An extern table
110// leaves the call indirect and the symbol live at every level, -O2 -flto included.
111static const HpackNs Hpack __attribute__((unused)) = {
112 .dyn_init = protocore_hpack_dyn_init,
113 .decode = protocore_hpack_decode,
114 .encode_header = protocore_hpack_encode_header,
115};
116
117// The prefix-integer and Huffman primitives moved to protocore_hpack_prim.h (shared with QPACK).
118
120
121#endif // PROTOCORE_ENABLE_HTTP2
122
123#endif // PROTOCORE_HPACK_H
#define PROTOCORE_BEGIN_DECLS
Give a header's declarations C linkage, so their symbol names carry no parameter types.
Definition types.h:96
_Bool proto_bool
The truth value.
Definition types.h:64
#define PROTOCORE_END_DECLS
Definition types.h:97