v4 / vlib / net / http / h2_conn.v
687 lines · 659 sloc · 24.55 KB · 89607c731290ee2c148c11541f73c40b23b00481
Raw
1// Copyright (c) 2019-2024 Alexander Medvednikov. All rights reserved.
2// Use of this source code is governed by an MIT license
3// that can be found in the LICENSE file.
4module http
5
6// --- HTTP/2 (RFC 7540 / 7541) file map -----------------------------------------
7// The h2 implementation is split by layer; h2_conn.v (this file) is the lead.
8// h2_frame.v binary framing layer (RFC 7540 §4, §6): parse/encode
9// h2_hpack.v HPACK header compression (RFC 7541)
10// h2_hpack_static.v HPACK static table data (RFC 7541 App. A)
11// h2_hpack_huffman*.v HPACK Huffman code + table (generated)
12// h2_error.v H2ErrorCode for RST_STREAM / GOAWAY
13// h2_conn.v synchronous single-stream client connection (this file)
14// h2_mux_conn.v multiplexed client: one connection, many concurrent streams
15// h2_server.v server-side connection driver
16// h2_client.v glue: net.http Request/Response <-> the h2 layer
17// vschannel_h2_windows.c.v HTTP/2 over the Windows SChannel TLS transport
18// Each *.v has a sibling *_test.v with hermetic in-memory-transport tests.
19
20// This file implements a minimal HTTP/2 client connection (RFC 7540) on top of
21// the framing and HPACK layers. It is intentionally synchronous and handles a
22// single request at a time: it sends one request, then reads frames until that
23// stream completes, servicing connection-level frames (SETTINGS, PING,
24// WINDOW_UPDATE, GOAWAY) inline. Concurrent multiplexing over one connection is
25// provided separately by h2_mux_conn.v; this remains the smallest synchronous client.
26
27// h2_client_preface is the fixed sequence a client sends to start an HTTP/2
28// connection, immediately followed by a SETTINGS frame (RFC 7540 Section 3.5).
29pub const h2_client_preface = 'PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n'
30
31// h2_default_initial_window is the initial flow-control window for both the
32// connection and new streams (RFC 7540 Section 6.9.2).
33pub const h2_default_initial_window = u32(65535)
34
35// h2_conn_read_chunk is the size of each transport read.
36const h2_conn_read_chunk = 16 * 1024
37
38// H2Transport is the byte transport an H2Conn runs over: typically an
39// ALPN-negotiated `h2` TLS connection, but any reader/writer works (which makes
40// the connection testable without a socket). Its method signatures match
41// net.ssl.SSLConn, so an SSLConn satisfies it directly.
42pub interface H2Transport {
43mut:
44 read(mut buf []u8) !int
45 write(buf []u8) !int
46}
47
48// H2PeerSettings holds the peer's SETTINGS, with HTTP/2 defaults.
49struct H2PeerSettings {
50mut:
51 header_table_size u32 = 4096
52 enable_push bool = true
53 max_concurrent_streams u32 = max_u32
54 initial_window_size u32 = 65535
55 max_frame_size u32 = 16384
56 max_header_list_size u32 = max_u32
57}
58
59// H2DataFn is called for each DATA frame received on the response stream, with
60// the chunk's bytes, the cumulative body bytes received (including this chunk),
61// the body length from Content-Length if known (else 0), and the response
62// status code.
63pub type H2DataFn = fn (chunk []u8, body_so_far u64, body_expected u64, status int) !
64
65// H2ClientRequest describes a single HTTP/2 request. Header names in `headers`
66// must be lowercase (RFC 7540 Section 8.1.2); the pseudo-headers are filled in
67// from the other fields.
68pub struct H2ClientRequest {
69pub:
70 method string = 'GET'
71 scheme string = 'https'
72 authority string
73 path string = '/'
74 headers []H2HeaderField
75 body []u8
76 // Optional response chunk callback, called after each DATA frame's payload
77 // is received. The arguments are the chunk bytes (not yet copied into the
78 // response body), the cumulative body bytes received so far (including this
79 // chunk), the body length from Content-Length (0 when not present), and the
80 // response status code.
81 on_data H2DataFn = unsafe { nil }
82 // stop_copying_limit, when >= 0, caps the cumulative body bytes copied into
83 // the response body; further DATA chunks are dropped but the callback keeps
84 // firing and the stream is drained to completion.
85 stop_copying_limit i64 = -1
86 // stop_receiving_limit, when >= 0, causes the response read loop to break
87 // once that many body bytes have been received. The callback fires for the
88 // final chunk; no further callbacks fire after that.
89 stop_receiving_limit i64 = -1
90}
91
92// H2ClientResponse is the result of an HTTP/2 request.
93pub struct H2ClientResponse {
94pub mut:
95 status int
96 headers []H2HeaderField
97 body []u8
98}
99
100// H2Conn is a client-side HTTP/2 connection.
101pub struct H2Conn {
102mut:
103 transport H2Transport
104 encoder H2HpackEncoder
105 decoder H2HpackDecoder
106 peer H2PeerSettings
107 rbuf []u8 // buffered bytes read from the transport, not yet consumed
108 pending []H2Frame // stream frames read early (while sending), to replay
109 next_stream_id u32 = 1 // clients use odd stream ids
110 cur_stream_id u32 // the stream currently being driven by do()
111 send_window i64 = 65535 // connection-level send flow-control window
112 stream_send_window i64 // send window for cur_stream_id
113 handshaked bool
114 goaway bool
115 // aborted is set when this connection terminated a stream early
116 // (RST_STREAM sent without draining the remaining DATA). Subsequent
117 // requests on the same connection must fail rather than risk being starved
118 // by leftover DATA frames the peer had already sent for the cancelled
119 // stream.
120 aborted bool
121}
122
123// new_h2_conn creates a client connection over `transport`. The HTTP/2
124// connection preface is sent lazily on the first request.
125pub fn new_h2_conn(transport H2Transport) &H2Conn {
126 return &H2Conn{
127 transport: transport
128 }
129}
130
131// do sends `req` and returns the response, after the request's stream closes.
132pub fn (mut c H2Conn) do(req H2ClientRequest) !H2ClientResponse {
133 c.handshake()!
134 if c.goaway {
135 return error('h2: connection is shutting down (GOAWAY)')
136 }
137 if c.aborted {
138 return error('h2: connection is no longer usable after an early stream termination')
139 }
140 stream_id := c.next_stream_id
141 c.next_stream_id += 2
142 c.cur_stream_id = stream_id
143 c.stream_send_window = i64(c.peer.initial_window_size)
144
145 mut fields := [
146 H2HeaderField{':method', req.method},
147 H2HeaderField{':scheme', req.scheme},
148 H2HeaderField{':authority', req.authority},
149 H2HeaderField{':path', req.path},
150 ]
151 for h in req.headers {
152 fields << h
153 }
154 // RFC 9113 §6.5.2: honor the peer's advisory SETTINGS_MAX_HEADER_LIST_SIZE —
155 // refuse an over-limit request locally instead of having the server reject it
156 // after the round trip.
157 if c.peer.max_header_list_size != max_u32 {
158 size := h2_header_list_size(fields)
159 if size > u64(c.peer.max_header_list_size) {
160 return error('h2: request header list (${size} bytes) exceeds peer SETTINGS_MAX_HEADER_LIST_SIZE (${c.peer.max_header_list_size})')
161 }
162 }
163 block := c.encoder.encode(fields)
164 has_body := req.body.len > 0
165 c.send_header_block(stream_id, block, !has_body)!
166 if has_body {
167 c.send_body(stream_id, req.body)!
168 }
169 return c.read_response(stream_id, req)!
170}
171
172fn (mut c H2Conn) handshake() ! {
173 if c.handshaked {
174 return
175 }
176 c.write_all(h2_client_preface.bytes())!
177 // Advertise our SETTINGS: refuse server push, and use the protocol defaults
178 // otherwise. (Our decoder uses the default 4096-byte HPACK table.)
179 c.send_frame(H2SettingsFrame{
180 settings: [
181 H2Setting{h2_settings_enable_push, 0},
182 H2Setting{h2_settings_initial_window_size, h2_default_initial_window},
183 H2Setting{h2_settings_max_frame_size, h2_default_max_frame_size},
184 ]
185 })!
186 c.handshaked = true
187}
188
189// read_response reads frames until `stream_id` is closed, returning its
190// response and servicing connection-level frames along the way. The streaming
191// options on `req` (on_data callback and the two stop limits) are honored
192// while reading DATA frames, matching the HTTP/1.1 streaming semantics.
193fn (mut c H2Conn) read_response(stream_id u32, req H2ClientRequest) !H2ClientResponse {
194 mut resp := H2ClientResponse{}
195 mut got_headers := false
196 mut body_so_far := u64(0)
197 mut body_expected := u64(0)
198 mut has_content_length := false
199 for {
200 frame := c.next_frame()!
201 if c.handle_conn_frame(frame)! {
202 continue
203 }
204 match frame {
205 H2HeadersFrame {
206 if frame.stream_id != stream_id {
207 continue
208 }
209 fragment := c.collect_header_block(frame.fragment, frame.end_headers, stream_id)!
210 // Decode once — HPACK table state is advanced here.
211 decoded := c.decoder.decode(fragment)!
212 if got_headers {
213 // A second HEADERS block carries trailers, which MUST end the
214 // stream (RFC 9113 §8.1). Without END_STREAM read_response would
215 // loop forever waiting for a stream end that never comes; the mux
216 // path resets such a stream, so the synchronous client fails too.
217 if !frame.end_stream {
218 return error('h2: trailers HEADERS frame must carry END_STREAM')
219 }
220 for f in decoded {
221 // RFC 9113 §8.1: trailers MUST NOT contain pseudo-header fields;
222 // the §8.2 field-name rules apply. A malformed field makes the
223 // response malformed — fail the request (mirrors the mux reset).
224 if f.name.starts_with(':') {
225 return error('h2: malformed trailers: pseudo-header ${f.name}')
226 }
227 reason := h2_response_field_error(f.name)
228 if reason != '' {
229 return error('h2: malformed trailers: ${reason}')
230 }
231 resp.headers << f
232 }
233 break
234 }
235 // First (interim or final) response HEADERS. Find :status. A
236 // response MUST carry a valid :status (RFC 9113 §8.3.1), and 101 is
237 // forbidden in HTTP/2 (§8.1.1).
238 mut status := 0
239 mut status_seen := false
240 for f in decoded {
241 if f.name == ':status' {
242 if f.value.len == 3 && all_digits(f.value) {
243 status = f.value.int()
244 } else {
245 return error('h2: malformed :status value: ${f.value}')
246 }
247 status_seen = true
248 break
249 }
250 }
251 if !status_seen || status < 100 || status > 599 || status == 101 {
252 // Missing / out-of-range / 101 status: fail rather than latch a
253 // bogus status or (for 101 or a sub-200 interim) loop forever
254 // waiting for a "final" HEADERS. Mirrors the mux path, which
255 // resets the stream with PROTOCOL_ERROR.
256 return error('h2: response with a missing or invalid :status: ${status}')
257 }
258 if status >= 100 && status < 200 {
259 // 1xx informational: discard and continue waiting for the
260 // final HEADERS block. Do not set got_headers here.
261 // A 1xx is not a final response and must not end the stream
262 // (RFC 9113 §8.1); END_STREAM here is malformed. Fail rather
263 // than loop forever waiting for a final response the stream can
264 // no longer send. (The mux path rejects this as a stream-level
265 // PROTOCOL_ERROR; the synchronous client has no other stream to
266 // keep alive, so a connection-level error is appropriate here.)
267 if frame.end_stream {
268 return error('h2: server set END_STREAM on a 1xx informational response')
269 }
270 continue
271 }
272 // Final response (status >= 200): populate, skipping pseudo-headers.
273 resp.status = status
274 mut seen_regular := false
275 mut seen_status := false
276 for f in decoded {
277 if f.name.starts_with(':') {
278 // RFC 9113 §8.3: in a response only :status is valid; pseudo-
279 // headers MUST precede regular fields and MUST NOT be duplicated.
280 // An undefined pseudo, :status after a regular field, or a
281 // second :status is malformed.
282 if f.name != ':status' || seen_regular || seen_status {
283 return error('h2: malformed response: invalid pseudo-header ${f.name}')
284 }
285 seen_status = true
286 continue
287 }
288 seen_regular = true
289 // RFC 9113 §8.2: reject uppercase/empty names and connection-specific
290 // fields rather than delivering a malformed response to the caller.
291 reason := h2_response_field_error(f.name)
292 if reason != '' {
293 return error('h2: malformed response: ${reason}')
294 }
295 resp.headers << f
296 if f.name == 'content-length' {
297 if all_digits(f.value) {
298 body_expected = f.value.u64()
299 has_content_length = true
300 } else {
301 return error('h2: malformed Content-Length: ${f.value}')
302 }
303 }
304 }
305 got_headers = true
306 if frame.end_stream {
307 break
308 }
309 }
310 H2DataFrame {
311 if frame.stream_id != stream_id {
312 continue
313 }
314 if !got_headers {
315 // DATA before the response HEADERS is a protocol error
316 // (RFC 9113 §8.1); the mux path rejects it. Fail rather than
317 // deliver body bytes for a response that has no status yet.
318 return error('h2: DATA frame before response HEADERS')
319 }
320 if frame.data.len > 0 {
321 body_so_far += u64(frame.data.len)
322 // Append the chunk to the response body unless the copy
323 // limit has been reached; the callback still fires.
324 if req.stop_copying_limit < 0
325 || i64(body_so_far) - i64(frame.data.len) < req.stop_copying_limit {
326 if req.stop_copying_limit >= 0 && i64(body_so_far) > req.stop_copying_limit {
327 remaining := req.stop_copying_limit - (i64(body_so_far) - i64(frame.data.len))
328 if remaining > 0 {
329 resp.body << frame.data[..int(remaining)]
330 }
331 } else {
332 resp.body << frame.data
333 }
334 }
335 if req.on_data != unsafe { nil } {
336 req.on_data(frame.data, body_so_far, body_expected, resp.status)!
337 }
338 }
339 // Replenish flow control using flow_size (full wire payload including
340 // padding), per RFC 7540 §6.9.1. Credit unconditionally when flow_size>0:
341 // a padding-only DATA frame (data.len==0, flow_size>0) still consumes the
342 // peer's send window and must be credited back. (Formerly inside the
343 // data.len>0 block — padding-only frames leaked window silently.)
344 if frame.flow_size > 0 {
345 c.send_window_update(0, u32(frame.flow_size))!
346 c.send_window_update(stream_id, u32(frame.flow_size))!
347 }
348 if frame.end_stream {
349 break
350 }
351 if req.stop_receiving_limit >= 0 && i64(body_so_far) >= req.stop_receiving_limit {
352 // Cancel the stream (RFC 7540 Section 8.1.4 / 5.4.2) so the
353 // peer stops sending more DATA, and mark the connection
354 // unusable: in-flight DATA frames that the peer has already
355 // sent for this stream would otherwise consume the
356 // connection-level receive window and block subsequent
357 // requests on the same H2Conn.
358 c.send_frame(H2RstStreamFrame{
359 stream_id: stream_id
360 error_code: u32(H2ErrorCode.cancel)
361 })!
362 c.aborted = true
363 break
364 }
365 }
366 H2RstStreamFrame {
367 if frame.stream_id == stream_id {
368 return error('h2: stream reset by peer (${h2_error_code_name(frame.error_code)})')
369 }
370 }
371 H2PushPromiseFrame {
372 // We sent SETTINGS_ENABLE_PUSH=0 in the preface; receiving
373 // PUSH_PROMISE is a connection error (RFC 7540 §8.2 PROTOCOL_ERROR).
374 // Ignoring it without decoding the embedded HPACK block would also
375 // desync the dynamic table.
376 return error('h2: unexpected PUSH_PROMISE (server push was disabled)')
377 }
378 else {
379 // PRIORITY / stray CONTINUATION / unknown: ignore.
380 }
381 }
382 }
383 if !got_headers {
384 return error('h2: stream closed without a response')
385 }
386 // Verify body completeness when Content-Length was advertised (RFC 7230 §3.3.2).
387 // Skip HEAD responses and 204/304 which carry no body by definition.
388 // Skip early-cancelled streams where we sent RST_STREAM ourselves.
389 body_allowed := req.method != 'HEAD' && resp.status != 204 && resp.status != 304
390 if has_content_length && body_allowed && !c.aborted && body_so_far != body_expected {
391 return error('h2: response body ${body_so_far} bytes does not match Content-Length ${body_expected}')
392 }
393 return resp
394}
395
396// collect_header_block returns the full HPACK block for a HEADERS frame,
397// reading and concatenating CONTINUATION frames until END_HEADERS.
398fn (mut c H2Conn) collect_header_block(first []u8, end_headers bool, stream_id u32) ![]u8 {
399 if end_headers {
400 return first
401 }
402 mut fragment := first.clone()
403 for {
404 frame := c.read_frame()!
405 if frame is H2ContinuationFrame {
406 if frame.stream_id != stream_id {
407 return error('h2: CONTINUATION on the wrong stream')
408 }
409 fragment << frame.fragment
410 if fragment.len > h2_max_recv_header_block {
411 return error('h2: response header block exceeds ${h2_max_recv_header_block} bytes')
412 }
413 if frame.end_headers {
414 break
415 }
416 } else {
417 return error('h2: expected a CONTINUATION frame')
418 }
419 }
420 return fragment
421}
422
423// handle_conn_frame services a connection-level frame, returning true if the
424// frame was fully handled here (so the caller should skip it).
425fn (mut c H2Conn) handle_conn_frame(frame H2Frame) !bool {
426 match frame {
427 H2SettingsFrame {
428 if !frame.ack {
429 c.apply_settings(frame.settings)!
430 c.send_frame(H2SettingsFrame{
431 ack: true
432 })!
433 }
434 return true
435 }
436 H2PingFrame {
437 if !frame.ack {
438 c.send_frame(H2PingFrame{
439 ack: true
440 data: frame.data
441 })!
442 }
443 return true
444 }
445 H2WindowUpdateFrame {
446 inc := frame.window_size_increment
447 if frame.stream_id == 0 {
448 if inc == 0 {
449 // RFC 7540 §6.9: connection-level zero increment is a connection
450 // error (PROTOCOL_ERROR).
451 return error('h2: connection WINDOW_UPDATE with zero increment (RFC 7540 §6.9 PROTOCOL_ERROR)')
452 }
453 new_window := c.send_window + i64(inc)
454 if new_window > i64(0x7fff_ffff) {
455 // RFC 7540 §6.9.1: exceeding 2^31-1 is a FLOW_CONTROL_ERROR.
456 return error('h2: connection flow-control window exceeded 2^31-1 (RFC 7540 §6.9.1)')
457 }
458 c.send_window = new_window
459 } else if frame.stream_id == c.cur_stream_id {
460 if inc == 0 {
461 // RFC 7540 §6.9: stream-level zero increment is a stream error
462 // (RST_STREAM). H2Conn has no multi-stream tracking so we treat
463 // it as connection-fatal to avoid accepting a broken stream state.
464 return error('h2: stream WINDOW_UPDATE with zero increment (RFC 7540 §6.9 PROTOCOL_ERROR)')
465 }
466 new_window := c.stream_send_window + i64(inc)
467 if new_window > i64(0x7fff_ffff) {
468 return error('h2: stream flow-control window exceeded 2^31-1 (RFC 7540 §6.9.1)')
469 }
470 c.stream_send_window = new_window
471 }
472 // Zero-increment on an untracked stream: silently ignore — H2Conn
473 // cannot send RST_STREAM for a stream it does not own.
474 return true
475 }
476 H2GoawayFrame {
477 c.goaway = true
478 return error('h2: GOAWAY received (${h2_error_code_name(frame.error_code)})')
479 }
480 else {
481 return false
482 }
483 }
484}
485
486fn (mut c H2Conn) apply_settings(settings []H2Setting) ! {
487 for s in settings {
488 match s.id {
489 h2_settings_header_table_size {
490 c.peer.header_table_size = s.value
491 // RFC 7541 §6.3: SETTINGS_HEADER_TABLE_SIZE from the server
492 // constrains our ENCODER's dynamic table (outgoing request headers).
493 // Evict entries exceeding the new limit immediately so we never
494 // reference indices the peer has already evicted; the pending flag
495 // causes encode() to emit the required Dynamic Table Size Update
496 // prefix at the start of the next header block.
497 c.encoder.dyn_table.set_max_size(int(s.value))
498 c.encoder.pending_max_table_size = int(s.value)
499 }
500 h2_settings_enable_push {
501 c.peer.enable_push = s.value != 0
502 }
503 h2_settings_max_concurrent_streams {
504 c.peer.max_concurrent_streams = s.value
505 }
506 h2_settings_initial_window_size {
507 if s.value > u32(0x7fff_ffff) {
508 // RFC 7540 §6.5.3: values above 2^31-1 are a FLOW_CONTROL_ERROR.
509 return error('h2: peer SETTINGS_INITIAL_WINDOW_SIZE ${s.value} exceeds 2^31-1')
510 }
511 // RFC 7540 §6.9.2: a change to the initial window size
512 // retroactively adjusts the active stream's send window by the delta.
513 // Validate before mutating so a rejected SETTINGS leaves state consistent.
514 delta := i64(s.value) - i64(c.peer.initial_window_size)
515 new_window := c.stream_send_window + delta
516 if new_window > i64(0x7fff_ffff) {
517 // RFC 7540 §6.9.2: if applying the delta pushes the stream
518 // window above 2^31-1 it is a FLOW_CONTROL_ERROR.
519 return error('h2: SETTINGS_INITIAL_WINDOW_SIZE delta overflows stream send window')
520 }
521 c.peer.initial_window_size = s.value
522 c.stream_send_window = new_window
523 }
524 h2_settings_max_frame_size {
525 if s.value < h2_default_max_frame_size || s.value > u32(0x00ff_ffff) {
526 // RFC 7540 §6.5.2: valid range is 2^14..2^24-1. Values outside
527 // this range are a connection SETTINGS_ERROR; a zero value would
528 // also make our HEADERS/DATA chunk step zero and hang senders.
529 return error('h2: peer SETTINGS_MAX_FRAME_SIZE ${s.value} out of range [16384, 16777215]')
530 }
531 c.peer.max_frame_size = s.value
532 }
533 h2_settings_max_header_list_size {
534 c.peer.max_header_list_size = s.value
535 }
536 else {} // unknown settings are ignored (RFC 7540 §6.5.2)
537 }
538 }
539}
540
541// send_header_block sends an HPACK header block as a HEADERS frame, splitting
542// it across CONTINUATION frames when it exceeds the peer's max frame size
543// (RFC 7540 Section 4.3). END_STREAM, when set, goes on the HEADERS frame.
544fn (mut c H2Conn) send_header_block(stream_id u32, block []u8, end_stream bool) ! {
545 max := int(c.peer.max_frame_size)
546 if block.len <= max {
547 c.send_frame(H2HeadersFrame{
548 stream_id: stream_id
549 fragment: block
550 end_headers: true
551 end_stream: end_stream
552 })!
553 return
554 }
555 c.send_frame(H2HeadersFrame{
556 stream_id: stream_id
557 fragment: block[..max]
558 end_headers: false
559 end_stream: end_stream
560 })!
561 mut off := max
562 for off < block.len {
563 mut next := off + max
564 if next > block.len {
565 next = block.len
566 }
567 c.send_frame(H2ContinuationFrame{
568 stream_id: stream_id
569 fragment: block[off..next]
570 end_headers: next == block.len
571 })!
572 off = next
573 }
574}
575
576// send_body writes the request body as DATA frames, chunked to the peer's max
577// frame size and bounded by both the connection and the per-stream send
578// flow-control windows (RFC 7540 Section 6.9).
579fn (mut c H2Conn) send_body(stream_id u32, body []u8) ! {
580 max := int(c.peer.max_frame_size)
581 mut off := 0
582 for off < body.len {
583 for c.send_window <= 0 || c.stream_send_window <= 0 {
584 // Wait for the peer to grow a window. Connection-level frames are
585 // handled; any stream frames are stashed for read_response.
586 frame := c.next_frame()!
587 if !c.handle_conn_frame(frame)! {
588 c.pending << frame
589 }
590 }
591 avail := if c.send_window < c.stream_send_window {
592 c.send_window
593 } else {
594 c.stream_send_window
595 }
596 mut chunk := body.len - off
597 if chunk > max {
598 chunk = max
599 }
600 if i64(chunk) > avail {
601 chunk = int(avail)
602 }
603 next := off + chunk
604 c.send_frame(H2DataFrame{
605 stream_id: stream_id
606 data: body[off..next]
607 end_stream: next == body.len
608 })!
609 c.send_window -= i64(chunk)
610 c.stream_send_window -= i64(chunk)
611 off = next
612 }
613}
614
615fn (mut c H2Conn) send_window_update(stream_id u32, inc u32) ! {
616 if inc == 0 {
617 return
618 }
619 c.send_frame(H2WindowUpdateFrame{
620 stream_id: stream_id
621 window_size_increment: inc
622 })!
623}
624
625// next_frame returns the next frame, replaying any stashed stream frames first.
626fn (mut c H2Conn) next_frame() !H2Frame {
627 if c.pending.len > 0 {
628 f := c.pending[0]
629 c.pending.delete(0)
630 return f
631 }
632 return c.read_frame()!
633}
634
635// read_frame reads and decodes one frame from the transport, enforcing the
636// receive limit we advertised to the peer in our own SETTINGS. This is
637// h2_default_max_frame_size, which H2Conn always sends and never renegotiates.
638// (c.peer.max_frame_size is the peer's receive limit — our outbound cap —
639// and must not be used here.)
640fn (mut c H2Conn) read_frame() !H2Frame {
641 c.fill_at_least(h2_frame_header_len)!
642 header := h2_parse_frame_header(c.rbuf)!
643 if header.length > h2_default_max_frame_size {
644 return error('h2: frame larger than SETTINGS_MAX_FRAME_SIZE (${header.length})')
645 }
646 total := h2_frame_header_len + int(header.length)
647 c.fill_at_least(total)!
648 frame := h2_parse_frame(header, c.rbuf[h2_frame_header_len..total])!
649 c.rbuf = c.rbuf[total..].clone()
650 return frame
651}
652
653// fill_at_least reads from the transport until rbuf holds at least n bytes.
654fn (mut c H2Conn) fill_at_least(n int) ! {
655 for c.rbuf.len < n {
656 mut tmp := []u8{len: h2_conn_read_chunk}
657 got := c.transport.read(mut tmp)!
658 if got <= 0 {
659 return error('h2: connection closed by peer')
660 }
661 c.rbuf << tmp[..got]
662 }
663}
664
665fn (mut c H2Conn) send_frame(f H2Frame) ! {
666 c.write_all(f.encode())!
667}
668
669fn (mut c H2Conn) write_all(data []u8) ! {
670 mut sent := 0
671 for sent < data.len {
672 n := c.transport.write(data[sent..])!
673 if n <= 0 {
674 return error('h2: transport write returned ${n}')
675 }
676 sent += n
677 }
678}
679
680// h2_error_code_name renders an HTTP/2 error code, falling back to hex for
681// codes outside the defined range.
682fn h2_error_code_name(code u32) string {
683 if code <= u32(H2ErrorCode.http_1_1_required) {
684 return unsafe { H2ErrorCode(code) }.str()
685 }
686 return 'unknown(0x${code.hex()})'
687}
688