19uint32_t rd32(
const uint8_t *p)
21 return ((uint32_t)p[0] << 24) | ((uint32_t)p[1] << 16) | ((uint32_t)p[2] << 8) | p[3];
24void wr(H2Conn *c,
const uint8_t *data,
size_t len)
28 c->cb.write(c->cb.io, data, len);
32H2Stream *find_stream(H2Conn *c, uint32_t
id)
36 if (c->streams[i].id ==
id &&
id != 0)
38 return &c->streams[i];
44H2Stream *alloc_stream(H2Conn *c, uint32_t
id)
48 if (c->streams[i].id == 0)
50 c->streams[i].id = id;
51 c->streams[i].state = H2StreamState::H2_ST_OPEN;
52 c->streams[i].send_window = (int32_t)c->peer.initial_window_size;
53 return &c->streams[i];
59void send_our_settings(H2Conn *c)
61 static const uint16_t ids[4] = {H2Setting::H2_SETTINGS_ENABLE_PUSH, H2Setting::H2_SETTINGS_MAX_CONCURRENT_STREAMS,
62 H2Setting::H2_SETTINGS_INITIAL_WINDOW_SIZE, H2Setting::H2_SETTINGS_MAX_FRAME_SIZE};
64 uint8_t buf[H2_FRAME_HEADER_LEN + 4 * 6];
65 size_t n = pc_h2_build_settings(buf,
sizeof buf, ids, vals, 4);
69void send_control(H2Conn *c,
size_t (*build)(uint8_t *,
size_t))
71 uint8_t buf[H2_FRAME_HEADER_LEN + 16];
72 size_t n = build(buf,
sizeof buf);
87bool emit_header(
void *ctx,
const char *name,
size_t nl,
const char *val,
size_t vl)
89 EmitCtx *e = (EmitCtx *)ctx;
90 if (e->c->cb.on_header)
92 e->c->cb.on_header(e->c->cb.app, e->stream_id, name, nl, val, vl);
98bool decode_block(H2Conn *c, uint32_t stream_id,
const uint8_t *block,
size_t len,
bool end_stream)
100 EmitCtx e = {c, stream_id};
101 if (!pc_hpack_decode(&c->hdec, block, len, c->hscratch,
sizeof c->hscratch, emit_header, &e))
105 if (c->cb.on_headers_end)
107 c->cb.on_headers_end(c->cb.app, stream_id, end_stream);
109 H2Stream *s = find_stream(c, stream_id);
112 s->state = end_stream ? H2StreamState::H2_ST_HALF_CLOSED : H2StreamState::H2_ST_OPEN;
117bool handle_headers(H2Conn *c,
const H2FrameHeader *h,
const uint8_t *payload)
119 if (h->stream_id == 0 || (h->stream_id & 1) == 0)
123 const uint8_t *p = payload;
124 size_t plen = h->length;
126 if (h->flags & H2_FLAG_PADDED)
136 if (h->flags & H2_FLAG_PRIORITY)
151 bool end_stream = (h->flags & H2_FLAG_END_STREAM) != 0;
152 if (h->stream_id <= c->last_peer_stream)
156 c->last_peer_stream = h->stream_id;
157 if (!alloc_stream(c, h->stream_id))
159 send_control(c, [](uint8_t *b,
size_t cap) {
return pc_h2_build_rst_stream(b, cap, 0, 0); });
163 if (h->flags & H2_FLAG_END_HEADERS)
165 return decode_block(c, h->stream_id, p, plen, end_stream);
168 if (plen >
sizeof c->hblock)
172 memcpy(c->hblock, p, plen);
173 c->hblock_len = plen;
174 c->hblock_stream = h->stream_id;
175 c->hblock_end_stream = end_stream;
176 c->in_header_block =
true;
180bool handle_continuation(H2Conn *c,
const H2FrameHeader *h,
const uint8_t *payload)
182 if (!c->in_header_block || h->stream_id != c->hblock_stream)
186 if (c->hblock_len + h->length >
sizeof c->hblock)
190 memcpy(c->hblock + c->hblock_len, payload, h->length);
191 c->hblock_len += h->length;
192 if (h->flags & H2_FLAG_END_HEADERS)
194 c->in_header_block =
false;
195 return decode_block(c, c->hblock_stream, c->hblock, c->hblock_len, c->hblock_end_stream);
200bool handle_data(H2Conn *c,
const H2FrameHeader *h,
const uint8_t *payload)
202 if (h->stream_id == 0)
206 const uint8_t *p = payload;
207 size_t plen = h->length;
209 if (h->flags & H2_FLAG_PADDED)
225 bool end_stream = (h->flags & H2_FLAG_END_STREAM) != 0;
228 c->cb.on_data(c->cb.app, h->stream_id, p, plen, end_stream);
230 H2Stream *s = find_stream(c, h->stream_id);
233 s->state = H2StreamState::H2_ST_HALF_CLOSED;
238 uint8_t wu[H2_FRAME_HEADER_LEN + 4];
239 size_t n = pc_h2_build_window_update(wu,
sizeof wu, 0, h->length);
241 n = pc_h2_build_window_update(wu,
sizeof wu, h->stream_id, h->length);
247bool process_frame(H2Conn *c)
250 pc_h2_parse_header(c->fbuf, H2_FRAME_HEADER_LEN, &h);
251 const uint8_t *payload = c->fbuf + H2_FRAME_HEADER_LEN;
254 if (c->in_header_block && h.type != H2FrameType::H2_CONTINUATION)
261 case H2FrameType::H2_SETTINGS:
262 if (h.flags & H2_FLAG_ACK)
264 return h.length == 0;
266 if (!pc_h2_parse_settings(payload, h.length, &c->peer))
270 send_control(c, pc_h2_build_settings_ack);
272 case H2FrameType::H2_PING:
273 if (h.flags & H2_FLAG_ACK)
282 uint8_t pg[H2_FRAME_HEADER_LEN + 8];
283 size_t n = pc_h2_build_ping_ack(pg,
sizeof pg, payload);
287 case H2FrameType::H2_WINDOW_UPDATE: {
292 uint32_t inc = rd32(payload) & 0x7FFFFFFF;
293 if (h.stream_id == 0)
295 c->conn_send_window += (int32_t)inc;
299 H2Stream *s = find_stream(c, h.stream_id);
302 s->send_window += (int32_t)inc;
307 case H2FrameType::H2_HEADERS:
308 return handle_headers(c, &h, payload);
309 case H2FrameType::H2_CONTINUATION:
310 return handle_continuation(c, &h, payload);
311 case H2FrameType::H2_DATA:
312 return handle_data(c, &h, payload);
313 case H2FrameType::H2_RST_STREAM: {
314 H2Stream *s = find_stream(c, h.stream_id);
321 case H2FrameType::H2_PRIORITY:
323 case H2FrameType::H2_GOAWAY:
326 case H2FrameType::H2_PUSH_PROMISE:
334void pc_h2_conn_init(H2Conn *c,
const H2Callbacks *cb)
336 memset(c, 0,
sizeof(*c));
339 pc_h2_settings_defaults(&c->peer);
340 c->conn_send_window = 65535;
342 send_our_settings(c);
345bool pc_h2_conn_recv(H2Conn *c,
const uint8_t *data,
size_t len)
350 while (off < len && c->pre < H2_PREFACE_LEN)
352 if (data[off] != (uint8_t)H2_PREFACE[c->pre])
359 if (c->pre < H2_PREFACE_LEN)
372 if (c->fhave < H2_FRAME_HEADER_LEN)
374 size_t take = H2_FRAME_HEADER_LEN - c->fhave;
375 if (take > len - off)
379 memcpy(c->fbuf + c->fhave, data + off, take);
382 if (c->fhave < H2_FRAME_HEADER_LEN)
387 uint32_t plen = ((uint32_t)c->fbuf[0] << 16) | ((uint32_t)c->fbuf[1] << 8) | c->fbuf[2];
392 size_t total = H2_FRAME_HEADER_LEN + plen;
393 size_t take = total - c->fhave;
394 if (take > len - off)
398 memcpy(c->fbuf + c->fhave, data + off, take);
401 if (c->fhave < total)
405 if (!process_frame(c))
414bool pc_h2_conn_respond(H2Conn *c, uint32_t stream_id,
int status,
const char *content_type,
const char *body,
417 H2Stream *s = find_stream(c, stream_id);
427 pc_sb sb_num = {num,
sizeof num, 0,
true};
430 size_t w = pc_hpack_encode_header(block + bo,
sizeof block - bo,
":status", 7, num, (
size_t)nl);
443 w = pc_hpack_encode_header(block + bo,
sizeof block - bo,
"content-type", 12, content_type,
444 strnlen(content_type,
sizeof block * 2));
451 pc_sb sb_num2 = {num,
sizeof num, 0,
true};
452 pc_sb_u32(&sb_num2, (uint32_t)((
unsigned)body_len));
454 w = pc_hpack_encode_header(block + bo,
sizeof block - bo,
"content-length", 14, num, (
size_t)cl);
463 uint8_t frame[H2_FRAME_HEADER_LEN +
sizeof block];
464 size_t n = pc_h2_build_headers(frame,
sizeof frame, stream_id, block, bo, body_len == 0);
475 uint32_t chunk_max = c->peer.max_frame_size ? c->peer.max_frame_size : 16384;
476 while (sent < body_len)
478 size_t chunk = body_len - sent;
479 if (chunk > chunk_max)
483 bool last = (sent + chunk == body_len);
484 uint8_t dh[H2_FRAME_HEADER_LEN];
485 size_t hn = pc_h2_write_header(dh,
sizeof dh, (uint32_t)chunk, H2FrameType::H2_DATA,
486 last ? H2_FLAG_END_STREAM : 0, stream_id);
494 wr(c, (
const uint8_t *)(body + sent), chunk);
495 c->conn_send_window -= (int32_t)chunk;
496 s->send_window -= (int32_t)chunk;
503void pc_h2_conn_goaway(H2Conn *c, uint32_t error)
505 uint8_t buf[H2_FRAME_HEADER_LEN + 8];
506 size_t n = pc_h2_build_goaway(buf,
sizeof buf, c->last_peer_stream, error);
#define PC_H2_MAX_STREAMS
#define PC_H2_MAX_FRAME
Largest HTTP/2 frame we accept, in bytes (advertised as SETTINGS_MAX_FRAME_SIZE). RFC 9113 requires a...
#define PC_HPACK_TABLE_BYTES
Per-connection HPACK dynamic-table size in bytes (our decoder; advertised to the peer as SETTINGS_HEA...
Bounded no-heap string builder that fails closed on overflow (one shared copy).
size_t pc_sb_finish(pc_sb *b)
NUL-terminate and return the built length, or 0 if the build overflowed.
void pc_sb_i64(pc_sb *b, int64_t v)
Append v as signed decimal (64-bit), with a leading '-' when negative.
void pc_sb_u32(pc_sb *b, uint32_t v)
Append v as decimal (no leading zeros; "0" for zero).
Bump-append target; ok latches false once an append would overflow cap.