|
// 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; |
|
} |