22static_assert(PC_H3_STREAM_BUF >= 256 + 16,
23 "PC_H3_STREAM_BUF must hold a whole response HEADERS frame: the 256-byte QPACK field section "
24 "pc_h3_conn_respond builds plus the H3 frame type and length varints");
28H3Stream *pc_h3_stream_get(H3Conn *h3, uint64_t
id,
bool create)
30 H3Stream *free_slot =
nullptr;
33 if (h3->streams[i].role != H3StreamRole::H3_ROLE_FREE && h3->streams[i].id ==
id)
35 return &h3->streams[i];
37 if (!free_slot && h3->streams[i].role == H3StreamRole::H3_ROLE_FREE)
39 free_slot = &h3->streams[i];
42 if (!create || !free_slot)
46 memset(free_slot, 0,
sizeof(*free_slot));
52void set_field(
char *dst,
size_t cap,
const char *src,
size_t len)
58 memcpy(dst, src, len);
67bool req_emit(
void *ctx,
const char *name,
size_t nlen,
const char *value,
size_t vlen)
69 H3Stream *st = ((ReqEmit *)ctx)->st;
70 if (nlen == 7 && memcmp(name,
":method", 7) == 0)
72 set_field(st->method,
sizeof(st->method), value, vlen);
74 else if (nlen == 5 && memcmp(name,
":path", 5) == 0)
76 set_field(st->path,
sizeof(st->path), value, vlen);
78 else if (nlen == 10 && memcmp(name,
":authority", 10) == 0)
80 set_field(st->authority,
sizeof(st->authority), value, vlen);
86void dispatch_request(H3Conn *h3, H3Stream *st)
88 static uint8_t body[PC_H3_STREAM_BUF];
89 static char scratch[PC_H3_PATH_LEN + PC_H3_AUTHORITY_LEN + 64];
93 while (off < st->buf_len)
96 if (!pc_h3_frame_parse(st->buf + off, st->buf_len - off, &fr))
100 size_t payload = off + fr.header_len;
101 if (payload + fr.length > st->buf_len)
105 const uint8_t *fp = st->buf + payload;
106 if (fr.type == H3FrameType::H3_HEADERS)
109 pc_qpack_decode(fp, (
size_t)fr.length, scratch,
sizeof(scratch), req_emit, &e);
110 st->have_headers =
true;
112 else if (fr.type == H3FrameType::H3_DATA)
119 size_t room = (body_len <
sizeof(body)) ?
sizeof(body) - body_len : 0;
120 size_t take = (size_t)fr.length;
127 memcpy(body + body_len, fp, take);
131 off = payload + (size_t)fr.length;
134 if (st->have_headers && h3->on_request)
136 h3->on_request(h3->app, h3, st->id, st->method, st->path, st->authority, body, body_len);
140void append(H3Stream *st,
const uint8_t *data,
size_t len)
142 if (len >
sizeof(st->buf) - st->buf_len)
144 len =
sizeof(st->buf) - st->buf_len;
146 memcpy(st->buf + st->buf_len, data, len);
152bool pc_h3_classify_uni_stream(H3Stream *st)
156 if (!pc_quic_varint_decode(st->buf, st->buf_len, &type, &c))
160 st->type_read =
true;
163 st->role = H3StreamRole::H3_ROLE_CONTROL;
165 else if (type == 0x02)
167 st->role = H3StreamRole::H3_ROLE_QPACK_ENC;
169 else if (type == 0x03)
171 st->role = H3StreamRole::H3_ROLE_QPACK_DEC;
175 st->role = H3StreamRole::H3_ROLE_OTHER_UNI;
177 memmove(st->buf, st->buf + c, st->buf_len - c);
183void pc_h3_consume_control(H3Conn *h3, H3Stream *st)
186 while (off < st->buf_len)
189 if (!pc_h3_frame_parse(st->buf + off, st->buf_len - off, &fr))
193 if (off + fr.header_len + fr.length > st->buf_len)
197 if (fr.type == H3FrameType::H3_SETTINGS)
199 pc_h3_settings_defaults(&h3->peer_settings);
200 pc_h3_parse_settings(st->buf + off + fr.header_len, (
size_t)fr.length, &h3->peer_settings);
202 off += fr.header_len + (size_t)fr.length;
204 memmove(st->buf, st->buf + off, st->buf_len - off);
208void on_stream_data(
void *app, QuicConn *, uint64_t stream_id,
const uint8_t *data,
size_t len,
bool fin)
210 H3Conn *h3 = (H3Conn *)app;
211 H3Stream *st = pc_h3_stream_get(h3, stream_id,
true);
217 if (st->role == H3StreamRole::H3_ROLE_FREE)
219 st->role = (stream_id & 0x03) == 0x00 ? H3StreamRole::H3_ROLE_REQUEST : H3StreamRole::H3_ROLE_OTHER_UNI;
222 append(st, data, len);
225 if (st->role != H3StreamRole::H3_ROLE_REQUEST && !st->type_read && st->buf_len >= 1 &&
226 !pc_h3_classify_uni_stream(st))
231 if (st->role == H3StreamRole::H3_ROLE_CONTROL)
233 pc_h3_consume_control(h3, st);
236 if (st->role != H3StreamRole::H3_ROLE_REQUEST)
244 dispatch_request(h3, st);
248void on_handshake_done(
void *app, QuicConn *qc)
250 H3Conn *h3 = (H3Conn *)app;
251 if (h3->control_opened)
255 h3->control_opened =
true;
259 size_t p = pc_quic_varint_encode(buf,
sizeof(buf), 0x00);
260 static const uint64_t ids[] = {H3Setting::H3_SETTINGS_QPACK_MAX_TABLE_CAPACITY,
261 H3Setting::H3_SETTINGS_QPACK_BLOCKED_STREAMS};
262 static const uint64_t vals[] = {0, 0};
263 p += pc_h3_build_settings(buf + p,
sizeof(buf) - p, ids, vals, 2);
264 pc_quic_conn_stream_send(qc, 3, buf, p,
false);
268 size_t n = pc_quic_varint_encode(&t, 1, 0x02);
269 pc_quic_conn_stream_send(qc, 7, &t, n,
false);
270 n = pc_quic_varint_encode(&t, 1, 0x03);
271 pc_quic_conn_stream_send(qc, 11, &t, n,
false);
272 h3->next_uni_id = 15;
276void pc_h3_conn_init(H3Conn *h3, QuicConn *qc, H3RequestFn on_request,
void *app)
278 memset(h3, 0,
sizeof(*h3));
280 h3->on_request = on_request;
285 h3->streams[i].id = UINT64_MAX;
287 pc_h3_settings_defaults(&h3->peer_settings);
289 QuicConnCallbacks cb = {on_stream_data, on_handshake_done, h3};
293bool pc_h3_conn_respond(H3Conn *h3, uint64_t stream_id,
int status,
const char *content_type,
const uint8_t *body,
296 H3Stream *st = pc_h3_stream_get(h3, stream_id,
false);
299 st->responded =
true;
304 size_t bp = pc_qpack_encode_prefix(block,
sizeof(block));
306 st3[0] = (char)(
'0' + (status / 100) % 10);
307 st3[1] = (char)(
'0' + (status / 10) % 10);
308 st3[2] = (char)(
'0' + status % 10);
310 bp += pc_qpack_encode_header(block + bp,
sizeof(block) - bp,
":status", 7, st3, 3);
316 bp += pc_qpack_encode_header(block + bp,
sizeof(block) - bp,
"content-type", 12, content_type,
317 strnlen(content_type,
sizeof(block) * 2));
328 tmp[n++] = (char)(
'0' + v % 10);
333 clen[cl++] = tmp[--n];
336 bp += pc_qpack_encode_header(block + bp,
sizeof(block) - bp,
"content-length", 14, clen, cl);
339 uint8_t out[PC_H3_STREAM_BUF];
340 size_t op = pc_h3_build_headers(out,
sizeof(out), block, bp);
347 size_t dn = pc_h3_build_data(out + op,
sizeof(out) - op, body, body_len);
354 return pc_quic_conn_stream_send(h3->qc, stream_id, out, op,
true) == op;
#define PC_H3_MAX_STREAMS
QPACK field-section compression for HTTP/3 (RFC 9204).