Skip to content

Instantly share code, notes, and snippets.

@masakielastic
Last active February 28, 2026 02:39
Show Gist options
  • Select an option

  • Save masakielastic/aaed5acbe041da8bf50912f46f8cbbec to your computer and use it in GitHub Desktop.

Select an option

Save masakielastic/aaed5acbe041da8bf50912f46f8cbbec to your computer and use it in GitHub Desktop.
nghttp3 で QPACK

nghttp3 で QPACK

cc -O2 -Wall -Wextra qpack.c $(pkg-config --cflags --libs libnghttp3) -o qpack
./qpack
// qpack_roundtrip.c
#include <nghttp3/nghttp3.h>
#include <inttypes.h>
#include <stdio.h>
#include <string.h>
/* rcbuf から (ptr,len) を取り出して表示する */
static void print_qpack_nv(const nghttp3_qpack_nv *nv) {
nghttp3_vec name = nghttp3_rcbuf_get_buf(nv->name);
nghttp3_vec value = nghttp3_rcbuf_get_buf(nv->value);
printf(" %.*s: %.*s\n",
(int)name.len, (const char *)name.base,
(int)value.len, (const char *)value.base);
}
/* nghttp3_buf の中身 (begin..last) を (ptr,len) で返す */
static const uint8_t *buf_ptr(const nghttp3_buf *b) { return b->begin; }
static size_t buf_len(const nghttp3_buf *b) { return (size_t)(b->last - b->begin); }
int main(void) {
const nghttp3_mem *mem = nghttp3_mem_default();
/* --- QPACK encoder/decoder を作る --- */
nghttp3_qpack_encoder *enc = NULL;
nghttp3_qpack_decoder *dec = NULL;
/* hard_max_dtable_capacity は “上限”。実効値は set_max_dtable_capacity() で決める */
if (nghttp3_qpack_encoder_new(&enc, 4096, mem) != 0) {
fprintf(stderr, "nghttp3_qpack_encoder_new failed\n");
return 1;
}
if (nghttp3_qpack_decoder_new(&dec, 4096, 0 /* max_blocked_streams */, mem) != 0) {
fprintf(stderr, "nghttp3_qpack_decoder_new failed\n");
nghttp3_qpack_encoder_del(enc);
return 1;
}
/* 動的テーブルを使う(例として 4096) */
nghttp3_qpack_encoder_set_max_dtable_capacity(enc, 4096);
/* --- エンコード対象のヘッダ(HTTP/3 の疑似ヘッダ含む)--- */
const nghttp3_nv nva[] = {
{(const uint8_t *)":method", (const uint8_t *)"GET",
sizeof(":method") - 1, sizeof("GET") - 1, 0},
{(const uint8_t *)":scheme", (const uint8_t *)"https",
sizeof(":scheme") - 1, sizeof("https") - 1, 0},
{(const uint8_t *)":authority", (const uint8_t *)"example.com",
sizeof(":authority") - 1, sizeof("example.com") - 1, 0},
{(const uint8_t *)":path", (const uint8_t *)"/",
sizeof(":path") - 1, sizeof("/") - 1, 0},
{(const uint8_t *)"user-agent", (const uint8_t *)"qpack-roundtrip/0.1",
sizeof("user-agent") - 1, sizeof("qpack-roundtrip/0.1") - 1, 0},
};
const int64_t stream_id = 0;
/* --- エンコード:pbuf(prefix), rbuf(request stream), ebuf(encoder stream) を生成 --- */
nghttp3_buf pbuf, rbuf, ebuf;
nghttp3_buf_init(&pbuf);
nghttp3_buf_init(&rbuf);
nghttp3_buf_init(&ebuf);
int rv = nghttp3_qpack_encoder_encode(enc, &pbuf, &rbuf, &ebuf, stream_id,
nva, sizeof(nva) / sizeof(nva[0]));
if (rv != 0) {
fprintf(stderr, "nghttp3_qpack_encoder_encode failed: %d\n", rv);
nghttp3_buf_free(&pbuf, mem);
nghttp3_buf_free(&rbuf, mem);
nghttp3_buf_free(&ebuf, mem);
nghttp3_qpack_decoder_del(dec);
nghttp3_qpack_encoder_del(enc);
return 1;
}
printf("[encoded]\n");
printf(" prefix (pbuf): %zu bytes\n", buf_len(&pbuf));
printf(" request (rbuf): %zu bytes\n", buf_len(&rbuf));
printf(" encoder-stream (ebuf): %zu bytes\n", buf_len(&ebuf));
/* --- 受信側:encoder stream を先に食わせて dynamic table を更新 --- */
if (buf_len(&ebuf) > 0) {
nghttp3_ssize nread = nghttp3_qpack_decoder_read_encoder(dec, buf_ptr(&ebuf), buf_len(&ebuf));
if (nread < 0 || (size_t)nread != buf_len(&ebuf)) {
fprintf(stderr, "nghttp3_qpack_decoder_read_encoder failed: %zd\n", (ssize_t)nread);
goto cleanup;
}
}
/* --- 受信側:request stream(prefix→header block)をデコード --- */
nghttp3_qpack_stream_context *sctx = NULL;
if (nghttp3_qpack_stream_context_new(&sctx, stream_id, mem) != 0) {
fprintf(stderr, "nghttp3_qpack_stream_context_new failed\n");
goto cleanup;
}
/* prefix と request 本体は “この順で同一 request stream に流す” 前提なので、順に decode */
const struct {
const uint8_t *p;
size_t len;
} chunks[] = {
{buf_ptr(&pbuf), buf_len(&pbuf)},
{buf_ptr(&rbuf), buf_len(&rbuf)},
};
printf("\n[decoded headers]\n");
for (size_t ci = 0; ci < sizeof(chunks) / sizeof(chunks[0]); ++ci) {
const uint8_t *src = chunks[ci].p;
size_t srclen = chunks[ci].len;
while (srclen > 0) {
nghttp3_qpack_nv out = {0};
uint8_t flags = 0;
/* fin は「この呼び出しの src が header block の最後を含む」時だけ 1。
ここでは最後の chunk の最後の呼び出しで 1 にしたいので、ループ末尾で調整する。 */
int fin = 0;
if (ci == (sizeof(chunks) / sizeof(chunks[0]) - 1)) {
/* 最後の chunk の最後の呼び出し = 今回で srclen 全消費できるなら fin=1 */
fin = 1; /* “まとめて渡す”前提で簡略化(実運用は厳密に last-fragment で 1) */
}
nghttp3_ssize nread =
nghttp3_qpack_decoder_read_request(dec, sctx, &out, &flags, src, srclen, fin);
if (nread < 0) {
fprintf(stderr, "nghttp3_qpack_decoder_read_request failed: %zd\n", (ssize_t)nread);
nghttp3_qpack_stream_context_del(sctx);
goto cleanup;
}
src += (size_t)nread;
srclen -= (size_t)nread;
if (flags & NGHTTP3_QPACK_DECODE_FLAG_BLOCKED) {
fprintf(stderr, "decoder is BLOCKED (need more encoder stream)\n");
nghttp3_qpack_stream_context_del(sctx);
goto cleanup;
}
if (flags & NGHTTP3_QPACK_DECODE_FLAG_EMIT) {
print_qpack_nv(&out);
/* out.name/out.value は refcount 済みなので decref 必須 */
nghttp3_rcbuf_decref(out.name);
nghttp3_rcbuf_decref(out.value);
}
if (flags & NGHTTP3_QPACK_DECODE_FLAG_FINAL) {
/* この header block は完了。次の header block を同一 stream で読むなら reset して再利用可 */
break;
}
/* nread==0 で進まないケースは基本的に異常なので保険 */
if ((size_t)nread == 0) {
fprintf(stderr, "decoder made no progress\n");
nghttp3_qpack_stream_context_del(sctx);
goto cleanup;
}
}
}
nghttp3_qpack_stream_context_del(sctx);
/* --- 受信側:decoder stream を書き出し → 送信側 encoder が read_decoder() で取り込む --- */
size_t dlen = nghttp3_qpack_decoder_get_decoder_streamlen(dec);
if (dlen > 0) {
nghttp3_buf dbuf;
nghttp3_buf_init(&dbuf);
dbuf.begin = mem->malloc(dlen, mem->user_data);
if (!dbuf.begin) {
fprintf(stderr, "malloc failed\n");
goto cleanup;
}
dbuf.pos = dbuf.begin;
dbuf.last = dbuf.begin;
dbuf.end = dbuf.begin + dlen;
nghttp3_qpack_decoder_write_decoder(dec, &dbuf); /* dbuf.last が進む */
/* encoder 側に feed back */
nghttp3_ssize nread =
nghttp3_qpack_encoder_read_decoder(enc, dbuf.begin,
(size_t)(dbuf.last - dbuf.begin));
if (nread < 0) {
fprintf(stderr, "nghttp3_qpack_encoder_read_decoder failed: %zd\n", (ssize_t)nread);
}
mem->free(dbuf.begin, mem->user_data);
}
cleanup:
nghttp3_buf_free(&pbuf, mem);
nghttp3_buf_free(&rbuf, mem);
nghttp3_buf_free(&ebuf, mem);
nghttp3_qpack_decoder_del(dec);
nghttp3_qpack_encoder_del(enc);
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment