Hive is written in C11. No C++, no scripting languages, no code generation.
Standard: C11 (-std=c11)
Why C11 over C99:
_Static_assertfor compile-time checks on struct sizes, protocol constants, and buffer dimensions - for example, verifying thatsizeof(hive_stream_t) == 64does not silently change_Alignas/_Alignoffor aligned iovec and arena declarations- Anonymous structs and unions for frame header parsing
- Designated initialisers for the static HPACK table and Huffman decode table
Feature test macros (defined in Makefile, not in source files):
_POSIX_C_SOURCE=200809L /* POSIX.1-2008 */
_XOPEN_SOURCE=700 /* XSI extensions */Do not define _GNU_SOURCE - it pulls in non-portable extensions and
breaks OpenBSD builds.
Hive has zero external library dependencies. This is a hard requirement, not a preference. Everything is implemented from the authoritative specifications with no third-party code.
The only dependency is the C standard library (libc), which is available on every supported platform without installation.
There are no vendored libraries, no submodules, no package manager files.
Runtime dependencies for embedders:
- Linux: none beyond libc
- OpenBSD: none beyond libc
These modules are written from scratch as part of Hive. They have no external dependencies and together form the complete library.
Purpose: Public API surface and session lifecycle. Creates and tears
down hive_session_t, orchestrates hive_session_recv() and
hive_session_send(), dispatches to internal modules.
Responsibilities:
- Session struct allocation and pre-allocation of all sub-buffers
- Session creation for server role, client role, and h2c Upgrade
hive_session_recv()entry point - drives the receive state machinehive_session_send()entry point - drives the DATA flush and send callbackhive_buf_retain()andhive_buf_free()hive_session_want_read()andhive_session_want_write()- Introspection functions
See ARCHITECTURE.md §1 and §9 for full session lifecycle and public API.
Purpose: Incremental HTTP/2 frame parsing from the caller's input buffer
and frame header serialisation into send_buf.
Responsibilities:
- All 18 receive states of the state machine (ARCHITECTURE.md §3.1)
- 9-byte frame header staging and parsing (ARCHITECTURE.md §3.2)
- Frame validation: length vs
SETTINGS_MAX_FRAME_SIZE, stream_id rules - CONTINUATION lockout enforcement (ARCHITECTURE.md §3.5)
- New stream validation: ID parity, monotonicity, concurrent limit (ARCHITECTURE.md §3.4)
ctrl_stagingaccumulation for small control frame payloadsframe_hdr_write()andframe_hdr_write_at()for outgoing frames
Purpose: Full RFC 7541 HPACK encoder and decoder. Also implements the standalone HPACK public API.
Responsibilities:
- Dynamic table management: ring buffer, entry allocation, eviction (ARCHITECTURE.md §4.1, §4.2)
- Static table: compile-time array, index 1–61 (ARCHITECTURE.md §4.3)
- Huffman decode: 256-entry lookup table (ARCHITECTURE.md §4.4)
- Huffman encode: RFC 7541 Appendix B code table, compile-time
hpack_decode_block(): decode a full HEADERS block with bomb protection (ARCHITECTURE.md §4.5)- String decode: Huffman path via
hpack_scratch, literal path via direct pointer (ARCHITECTURE.md §4.6) - Integer varint encode/decode (ARCHITECTURE.md §4.7)
hpack_encode_block(): encode header list intosend_buf(ARCHITECTURE.md §4.8)- Hash index for large tables (ARCHITECTURE.md §4.9)
- Standalone
hive_hpack_encoder_tandhive_hpack_decoder_tobjects
Purpose: Two-layer stream state store: open-addressed hash table for O(1) lookup and pre-allocated slot array for state storage.
Note: originally planned as a separate hive_stream.c/hive_stream.h
pair; merged into hive.c during Phase 4 implementation. All stream table
symbols remain internal and are declared in hive_internal.h.
Responsibilities:
stream_open(),stream_lookup(),stream_close()- Knuth multiplicative hash function (ARCHITECTURE.md §5.4)
- Tombstone management and compaction (ARCHITECTURE.md §5.5, §5.6)
- Free stack management (O(1) slot alloc and free)
- Stream state transitions (ARCHITECTURE.md §5.3)
Purpose: Connection-level and stream-level flow control windows, and coalesced WINDOW_UPDATE emission.
Note: originally planned as a separate hive_flow.c/hive_flow.h
pair; merged into hive_frame.c during Phase 5 implementation alongside
the receive state machine that consumes DATA frames.
Responsibilities:
- Receive window tracking:
recv_consumedper stream and per connection - WINDOW_UPDATE coalescing: queue only when consumed > window/2 (ARCHITECTURE.md §7.7)
- Send window enforcement: block DATA frames when
send_window == 0 - Retroactive stream window adjustment on
SETTINGS_INITIAL_WINDOW_SIZEchange - Connection-level WINDOW_UPDATE handling
Purpose: Scatter-gather iovec accumulation and drain.
Responsibilities:
frame_hdr_write()- write 9-byte frame header intosend_buf- Control frame queuing: SETTINGS, SETTINGS ACK, PING ACK, RST_STREAM, WINDOW_UPDATE, GOAWAY (ARCHITECTURE.md §6.3)
- HEADERS frame queuing with HPACK encode and CONTINUATION splitting (ARCHITECTURE.md §6.4)
- DATA frame queuing with
NO_COPYzero-copy path (ARCHITECTURE.md §6.5) send_queue_flush_data(): drive pending data_sources within flow control limits athive_session_send()time- iovec overflow handling (ARCHITECTURE.md §6.7)
Purpose: All flood detection and exhaustion protection logic.
Note: originally planned as a separate hive_security.c/hive_security.h
pair; implemented directly in hive_frame.c (frame-level checks) and
hive_hpack.c (HPACK bomb protection and HTTP messaging validation) during
Phase 7. There is no separate security module.
Responsibilities:
- HPACK bomb: decoded size and count limits (ARCHITECTURE.md §8.2)
- CONTINUATION flood: compressed reassembly cap (ARCHITECTURE.md §8.3)
- SETTINGS flood: inbound counter and GOAWAY (ARCHITECTURE.md §8.4)
- RST_STREAM flood: rolling rate counter and callback (ARCHITECTURE.md §8.5)
- Stream ID exhaustion: auto-GOAWAY threshold (ARCHITECTURE.md §8.6)
- HTTP messaging validation: RFC 9113 §8 rules (ARCHITECTURE.md §8)
hive_buf_tASan poisoning: ephemeral region marking (ARCHITECTURE.md §8.8)
Note: the HPACK always-copy rule (ARCHITECTURE.md §8.1) is enforced inside
hive_hpack.c at every dynamic table insertion. It is not a security module
function - it is a property of the table implementation itself.
These are part of the C standard library or POSIX and require no installation.
#include <stdint.h> /* uint8_t, uint32_t, int32_t, size_t */
#include <stddef.h> /* offsetof, NULL, size_t */
#include <string.h> /* memcpy, memset, memcmp */
#include <stdlib.h> /* malloc, free, calloc (system allocator path only) */
#include <time.h> /* clock_gettime - RST_STREAM flood window timing */
#include <sys/uio.h> /* struct iovec - send callback and scatter-gather */
#include <errno.h> /* EINVAL, ENOMEM - error returns */malloc, free, calloc, and realloc are called only in one place: the
NULL-allocator fallback path that wraps them into the hive_mem_t interface.
All other code in the library goes through session->mem.*. This is the
direct enforcement mechanism for the no-direct-malloc coding standard.
#include <time.h>clock_gettime(CLOCK_MONOTONIC, ...) is used exclusively for the RST_STREAM
flood rate window (ARCHITECTURE.md §8.5). CLOCK_MONOTONIC is correct here
because we need elapsed seconds, not wall-clock time. No persistence, no
cross-process coordination - monotonicity is the only requirement.
No other clock usage exists in the library. Hive does not implement timeouts - those are the caller's responsibility.
OpenBSD: strlcpy and strlcat are in libc.
Linux: strlcpy and strlcat are not in glibc. Hive provides
src/compat_str.c and src/compat_str.h with portable implementations
compiled on Linux only. On OpenBSD, these files are not compiled.
Do not use strcpy, strcat, sprintf, or gets anywhere in the library.
One top-level Makefile. No per-directory Makefiles.
hive/
├── Makefile
├── src/ ← all library source files
├── tests/ ← unit tests and conformance helpers
├── include/ ← public header (hive.h)
└── tools/ ← optional developer tools (frame decoder, fuzz driver)
The output of make is a static library: libhive.a. Embedders link
against libhive.a and include include/hive.h.
Primary: Clang (preferred on both platforms).
OpenBSD ships Clang as the system compiler. On Linux:
# Debian/Ubuntu
apt install clang
# RHEL/Fedora
dnf install clang
# Arch
pacman -S clangMinimum supported version: Clang 11.0. Older versions lack full C11 support
and _Static_assert in all required positions.
CC = clang
CSTD = -std=c11
# Warnings - all treated as errors in CI
CWARN = -Wall -Wextra -Wpedantic \
-Wshadow \
-Wformat=2 \
-Wconversion \
-Wnull-dereference \
-Wstrict-prototypes \
-Wmissing-prototypes \
-Wold-style-definition
# Development build - sanitizers, debug symbols, no optimisation
CFLAGS_DEV = $(CSTD) $(CWARN) \
-g -O0 \
-fsanitize=address,undefined \
-fno-omit-frame-pointer \
-DDEBUG \
-DHIVE_DEBUG=1
# Release build - optimised, hardened
CFLAGS_REL = $(CSTD) $(CWARN) \
-O2 \
-DNDEBUG \
-fstack-protector-strong \
-fPIC \
-D_FORTIFY_SOURCE=2
# Feature test macros (all builds)
CFLAGS_FT = -D_POSIX_C_SOURCE=200809L \
-D_XOPEN_SOURCE=700
# Platform detection
UNAME := $(shell uname)
ifeq ($(UNAME), OpenBSD)
CFLAGS_OS = -DOPENBSD
LDFLAGS_OS =
COMPAT_SRC =
else ifeq ($(UNAME), Linux)
CFLAGS_OS = -DLINUX
LDFLAGS_OS =
COMPAT_SRC = src/compat_str.c
else
$(error Unsupported platform: $(UNAME))
endif-fPIC in release builds produces position-independent object code,
required for embedders who link Hive into a shared library.
-DHIVE_DEBUG=1 in development builds enables:
HIVE_BUF_VALIDclearing and ASan poisoning on callback return_Static_assertexpansion for struct size verification- Additional assertion paths throughout the receive state machine
make # same as make release
make dev # debug build with ASan/UBSan
make release # optimised hardened build
make test # build and run unit tests
make test-asan # run tests under ASan/UBSan (same as make dev + make test)
make test-tsan # run tests under ThreadSanitizer (Linux only)
make valgrind # run tests under Valgrind (Linux only)
make h2spec # run h2spec conformance suite against test server
make lint # clang-tidy + cppcheck
make clean # remove build artifacts
make install # install libhive.a and include/hive.h to PREFIXmake install installs two files:
PREFIX = /usr/local
LIBDIR = $(PREFIX)/lib
INCLUDEDIR = $(PREFIX)/include
install:
install -m 644 libhive.a $(LIBDIR)/libhive.a
install -m 644 include/hive.h $(INCLUDEDIR)/hive.hNo other files are installed. No pkg-config file is generated in v1.
The Makefile builds all .c files in src/ plus the platform-specific
compat file. Platform-specific compilation is handled via $(COMPAT_SRC):
LIB_SRC = src/hive.c \
src/hive_frame_bare.c \
src/hive_frame.c \
src/hive_hpack.c \
src/hive_send.c \
$(COMPAT_SRC)
LIB_OBJ = $(LIB_SRC:.c=.o)
libhive.a: $(LIB_OBJ)
$(AR) rcs $@ $^Stream table (§3.4), flow control (§3.5), and security (§3.7) logic are
implemented directly in hive.c and hive_frame.c - there are no separate
hive_stream.c, hive_flow.c, or hive_security.c files.
TEST_SRC = tests/test_hpack.c \
tests/test_frame.c \
tests/test_flow.c \
tests/test_session.c \
tests/test_security.c \
tests/run_tests.c
TEST_BIN = build/tests/run_tests
$(TEST_BIN): $(TEST_SRC) libhive.a
$(CC) $(CFLAGS) $(CFLAGS_FT) $(CFLAGS_OS) \
-I include/ $(TEST_SRC) libhive.a -o $@tests/test_harness.h is a header included by each test file - it is not
a compiled source file and must not appear in TEST_SRC.
Stream table tests are in tests/test_session.c (not a separate
test_stream.c), co-located with session creation and send-queue tests.
No external test framework. Plain C with a minimal assertion macro, identical in structure to the kvd test harness.
/* tests/test_harness.h */
static int tests_run = 0;
static int tests_passed = 0;
#define RUN(name) \
do { \
tests_run++; \
if (test_##name()) { \
tests_passed++; \
printf("PASS: " #name "\n"); \
} else { \
printf("FAIL: " #name "\n"); \
} \
} while (0)
/* In each test file's main(): */
RUN(hpack_static_table_lookup);
RUN(hpack_huffman_decode_hello);
/* ... */
printf("%d/%d tests passed\n", tests_passed, tests_run);
return tests_passed == tests_run ? 0 : 1;Every test binary:
- Returns 0 if all tests pass
- Returns 1 if any test fails
- Prints
PASS: test_nameorFAIL: test_nameper test case - Prints
N/M tests passedas the final line
make test runs tests/run_tests.sh which runs the test binary and fails
if it exits non-zero.
Hive's zero-I/O design makes unit testing straightforward - test code
constructs raw HTTP/2 byte sequences and feeds them directly to
hive_session_recv(). No sockets, no threads, no timing.
/* helper used throughout test files */
static void
feed_bytes(hive_session_t *s, const uint8_t *data, size_t len)
{
ssize_t consumed;
size_t pos = 0;
while (pos < len) {
consumed = hive_session_recv(s, data + pos, len - pos);
if (consumed < 0)
return; /* session error - test will fail on next assert */
pos += (size_t)consumed;
}
}Tests feed bytes in varying chunk sizes (1 byte at a time, full frames, split across frame boundaries) to exercise all code paths in the state machine. This is the primary technique for testing partial delivery.
Tests intercept callbacks via a capture struct populated by the test's callback implementations:
typedef struct {
uint32_t last_stream_id;
int headers_complete_fired;
int end_stream;
char last_method[16];
char last_path[256];
int data_chunk_count;
size_t data_total_bytes;
uint32_t stream_close_error;
int rst_flood_fired;
uint32_t rst_flood_rate;
} test_capture_t;
static int
test_on_header(hive_session_t *s, uint32_t sid,
hive_buf_t *name, hive_buf_t *value, uint8_t flags, void *ud)
{
test_capture_t *cap = (test_capture_t *)ud;
/* copy name/value into cap fields for assertion after callback */
return 0;
}Purpose: Memory error detection - leaks, use-after-free, uninitialised reads.
Installation: apt install valgrind
Usage:
make dev
valgrind --leak-check=full \
--show-leak-kinds=all \
--track-origins=yes \
--error-exitcode=1 \
./tests/run_testsHive has no external library dependencies, so no suppression file is needed. All code must pass Valgrind clean. Run on Linux before every commit. Valgrind is not available on OpenBSD - use the ASan build there.
Purpose: Runtime memory and undefined behaviour detection.
Compiler flags: -fsanitize=address,undefined (included in make dev)
Available on both Linux and OpenBSD with Clang. Use for all development builds. Do not ship sanitizer builds to embedders.
The HIVE_DEBUG=1 macro activates additional ASan behaviour within the
library: hive_buf_t handles are poisoned via __asan_poison_memory_region
on callback return, and retained buffers are unpoisoned via
__asan_unpoison_memory_region when retained. These macros are no-ops in
non-ASan builds.
#if defined(HIVE_DEBUG) && defined(__SANITIZE_ADDRESS__)
# include <sanitizer/asan_interface.h>
# define HIVE_ASAN_POISON(ptr, size) __asan_poison_memory_region((ptr), (size))
# define HIVE_ASAN_UNPOISON(ptr, size) __asan_unpoison_memory_region((ptr), (size))
#else
# define HIVE_ASAN_POISON(ptr, size) ((void)0)
# define HIVE_ASAN_UNPOISON(ptr, size) ((void)0)
#endifPurpose: Data race detection. TSAN and ASan are mutually exclusive - TSAN runs as a separate target.
Usage:
make test-tsanHive itself is not thread-safe by design (one session, one thread). However, TSAN is useful during development of the test server used for h2spec runs, which handles multiple connections and uses a simple multi-connection model.
TSAN is not a per-commit gate. Run at phase boundaries and before release.
Purpose: Static analysis.
Installation: apt install clang-tidy (Linux) - included with Clang on
OpenBSD.
Usage:
make lint
# or directly:
clang-tidy src/*.c -- $(CFLAGS_DEV) $(CFLAGS_FT) $(CFLAGS_OS) -I include/Purpose: Additional static analysis, complements clang-tidy.
Installation: apt install cppcheck / pkg_add cppcheck
cppcheck --enable=all --error-exitcode=1 \
--suppress=missingIncludeSystem \
src/Purpose: Consistent code formatting.
Configuration: .clang-format in repository root, OpenBSD KNF style.
clang-format -i src/*.c src/*.h include/hive.hCI rejects commits that are not clang-format clean.
Purpose: HTTP/2 protocol conformance suite. Fires RFC-conformant frame sequences at a running HTTP/2 server and verifies correct responses.
Repository: https://github.com/summerwind/h2spec
Installation:
# Download the binary for your platform
curl -L https://github.com/summerwind/h2spec/releases/latest/download/h2spec_linux_amd64.tar.gz \
| tar xz
chmod +x h2spec
mv h2spec /usr/local/bin/
# OpenBSD: download the openbsd/amd64 build or build from sourceUsage:
# Start the test server (built from tests/h2spec_server.c)
make h2spec-server
./tests/h2spec_server --port 8443 &
# Run the full conformance suite
make h2spec
# equivalent to:
h2spec -h 127.0.0.1 -p 8443 --tls --insecuretests/h2spec_server.c is a minimal HTTP/2 server built on top of
libhive.a. It handles all h2spec test cases and returns appropriate
responses. It uses libtls from LibreSSL for TLS, because h2spec requires
TLS by default (ALPN h2 negotiation). This is the only development
dependency beyond the C toolchain.
libtls installation (for the test server only - not linked into
libhive.a):
# OpenBSD - included in base system
# No installation required
# Debian/Ubuntu
apt install libssl-dev # libtls is part of LibreSSL on newer Debian
# or for portbsd/LibreSSL:
apt install libtls-dev
# RHEL/Fedora
dnf install libressl-develAll h2spec tests must pass before Hive v1 is considered complete. There are no documented planned exceptions. If a test case requires a behaviour that Hive implements differently from h2spec's expectation, the discrepancy must be diagnosed against the RFC before concluding that Hive is correct and h2spec is wrong - that conclusion requires explicit documentation and is not the default assumption.
See TESTING.md §4 for the full conformance test strategy.
Purpose: Offline HTTP/2 frame decoder for protocol debugging. Reads raw HTTP/2 bytes from a file or stdin and pretty-prints each frame. Useful when diagnosing unexpected frames from real clients or servers.
Built from: tools/hive_decode.c and src/hive_frame_bare.c only - no
dependency on the session, HPACK, stream, or send modules.
Usage:
make tools
# Capture raw bytes (after TLS decryption) and decode:
./tools/hive_decode /tmp/raw_h2_frames.bin
# Pipe from stdin:
cat /tmp/raw_h2_frames.bin | ./tools/hive_decodeOutput:
--- frame 1 ---
type: 0x1 HEADERS
flags: 0x25 END_STREAM|END_HEADERS|PRIORITY
stream: 1
length: 23 bytes
--- frame 2 ---
type: 0x4 SETTINGS
flags: 0x00
stream: 0 (connection)
length: 18 bytes
HEADER_TABLE_SIZE = 4096
INITIAL_WINDOW_SIZE = 1048576
MAX_FRAME_SIZE = 16384
--- frame 3 ---
type: 0x0 DATA
flags: 0x01 END_STREAM
stream: 1
length: 8192 bytes
hive_decode does not decode HPACK - it shows raw compressed bytes for
HEADERS frames. It is an offline protocol debugging tool, not a full
HTTP/2 analyser.
| Dependency | Version | Type | Purpose | Platforms |
|---|---|---|---|---|
| Clang | ≥ 11.0 | Build tool | Compilation | Linux, OpenBSD |
| libc | system | System lib | Standard C | Linux, OpenBSD |
| Valgrind | latest | Dev tool | Memory checking | Linux only |
| clang-tidy | ≥ 11.0 | Dev tool | Static analysis | Linux, OpenBSD |
| cppcheck | latest | Dev tool | Static analysis | Linux, OpenBSD |
| h2spec | latest | Conformance | HTTP/2 spec test | Linux, OpenBSD |
| libtls (LibreSSL) | ≥ 3.3 | Dev tool | TLS for test server | Linux, OpenBSD |
Runtime dependencies for embedders:
- Linux: none (libc only)
- OpenBSD: none (libc only)
Development dependencies (not required to embed the library):
- Valgrind (Linux only)
- clang-tidy, cppcheck
- h2spec binary
- libtls (LibreSSL) - for the h2spec test server only
libtls is not linked into libhive.a. It is used only in
tests/h2spec_server.c. Embedders who do not run h2spec do not need
libtls installed.