http3 0.0.3 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7342e51b5150f53092806631b85d5d1053d16f7f757c26730227fd0a9d27619d
4
- data.tar.gz: 7c7cee88475696117fcfe14e42333fc1267ffc0942c40360f6b6d0fcc9b487cf
3
+ metadata.gz: 327825057f3faad23ce063df236945bb927803d537b9a9df853352ad30515fb4
4
+ data.tar.gz: 19051a40291f678fce8a6a44fce125d40706e6ad3cf4b9b800875910dfd5a11b
5
5
  SHA512:
6
- metadata.gz: 802c51e3f9040ac9d51ff0f6afee6e1541f7f479f94336283d49c346c127bf6740fca313ce4badb5627ba03e4793be9cd1536bafbfec24a180a3658a2a0c3cd7
7
- data.tar.gz: fe45066ac7c54c622ff9c5fdab9d1fbc474867ce2076db6fd28f1c78ca4f65db8c5fbf1059b2dead1234e414902f8d43aa53c9912a08602fd0849425a5bfc480
6
+ metadata.gz: 6003280f0c3dbcd4d62d4a63f7c70a79b31230c93dcc2ad7d95bfd83b3ae98a9814fa7e076e1ccb6c8e476b8b8822a94a7daefffbab7d0db19752752c5b3a929
7
+ data.tar.gz: b10b118decf20b8326cabd055890d35a7c0b8e17aa1853e61e726761f679b98a35c0cc178a1250fd43e1311a616e1b32b47cbf58e3ff239bd87f55cc1d0ac24a
data/CHANGELOG.md CHANGED
@@ -1,3 +1,49 @@
1
+ ## [0.0.4] - 2026-10-27
2
+
3
+ ### Added
4
+ - quic-interop-runner's http3 test case runs against http3-ruby with `test/interop/run_interop_runner.sh`. The runner's ns-3 network simulator passes no packets under rootless podman, so the script runs the runner on `test/interop/quic-interop-runner.yml`, its compose with a stand-in (`test/interop/sim/`) that forwards with plain Linux routing; it makes the networks afresh before and after each run. CI runs it against the clients of ngtcp2, quic-go, quiche, neqo, aioquic, picoquic, lsquic, s2n-quic and quinn. ([#10](https://github.com/unasuke/http3-ruby/pull/10))
5
+ - `HTTP3::Server` takes `max_connection_request_buffer` (4 MiB) and `max_total_request_buffer` (64 MiB): the bytes that requests not yet passed to the application may hold on one connection and over all connections, header fields counted as RFC 9114 §4.2.2 counts them. A request that would pass either is answered with 503, with neither headers nor body. `HTTP3::Session.attach` takes the same limits as `max_connection_request_buffer:` and `shared_buffer:`, made with `HTTP3::Session.shared_buffer(limit)`. ([#12](https://github.com/unasuke/http3-ruby/pull/12))
6
+ - `test/interop/hold_requests.rb`, a check run by hand that requests held open cannot exhaust the server's memory: 20 connections of 100 requests, none of them ever finished, then a GET on a new connection. ([#12](https://github.com/unasuke/http3-ruby/pull/12))
7
+ - `HTTP3::Server` takes `max_connection_send_buffer` (1 MiB) and `max_total_send_buffer` (64 MiB): the bytes of array and `each` response bodies that may be queued but unsent on one connection and over all connections. A body waits for room, and while the total is used up, the bodies on every connection wait. `HTTP3::Session#fill_bodies` and `#wants_fill?` take the room left as `connection_limit:` and `room:`, and `#unsent_total` tells what a Session has unsent. ([#17](https://github.com/unasuke/http3-ruby/pull/17))
8
+ - `HTTP3::Server` takes `request_timeout` (30 seconds): the time a request may take to come in full, counted from the start of its headers. One that takes longer is answered with 408, with neither headers nor body, and the rest of it is not read. `nil` sets no limit; anything else but a positive number makes `HTTP3::Server.new` raise `ArgumentError`. `HTTP3::Session.attach` takes it as `request_timeout:`, `HTTP3::Session#expire_requests(now)` refuses the requests past their time, and `#next_request_deadline` tells when the next one runs out. 30 seconds fit the default `max_request_body` of 1 MiB at about 280 kbps; an application that raises `max_request_body` should lengthen `request_timeout` to match or set it to `nil`, or large uploads over slow links get 408 (100 MiB in 30 seconds takes about 28 Mbps). ([#18](https://github.com/unasuke/http3-ruby/pull/18))
9
+ - `HTTP3::Server` logs how many requests it answered without the application (400, 408, 413, 431, 501 and 503), which are not logged one by one: `refusal_log_interval` seconds (60) after the first of them, and when `#run` ends, a warn line per status such as `requests_refused status=503 count=1234 seconds=60`, with the seconds since the first. `HTTP3::Server.new` raises `ArgumentError` for a `refusal_log_interval` that is not a positive number. `HTTP3::RackAdapter#take_refusals` returns the 400s and 501s it answered since the last call, as `{status => count}`. ([#19](https://github.com/unasuke/http3-ruby/pull/19))
10
+
11
+ ### Changed
12
+ - The interop checks in CI, curl and quic-interop-runner, run on every push to main as well as weekly. ([#10](https://github.com/unasuke/http3-ruby/pull/10))
13
+ - http3 depends on quic `~> 0.0.5`. A client on that quic keeps its connection when the server stops reading a request it is still sending, and closes the qlog file of a connection once the connection ends. ([#11](https://github.com/unasuke/http3-ruby/pull/11))
14
+ - 413 and 431 are answered as soon as the request passes the limit, before the rest of it arrives. The server does not read the rest: `HTTP3::Session#stop_reading` sends STOP_SENDING with H3_NO_ERROR, as RFC 9114 §4.1 allows. ([#12](https://github.com/unasuke/http3-ruby/pull/12))
15
+ - `HTTP3::Session#take_requests` hands out a request it refuses (413, 431 or 503) as soon as it is refused, with neither headers nor body. ([#12](https://github.com/unasuke/http3-ruby/pull/12))
16
+ - `HTTP3::Session#respond` raises `HTTP3::Nghttp3::InvalidStateError` for a request already answered, instead of sending a second response. ([#12](https://github.com/unasuke/http3-ruby/pull/12))
17
+ - The request that `HTTP3::Session#fill_bodies` passes to its block has only the pseudo-header fields, and no body: a response being sent no longer keeps the body of its request. ([#12](https://github.com/unasuke/http3-ruby/pull/12))
18
+ - `HTTP3::Server.new` raises `ArgumentError` for request limits that are not Integers in range, and for a `max_connection_request_buffer` smaller than `max_request_body + max_field_section_size` or a `max_total_request_buffer` smaller than `max_connection_request_buffer`, which would answer a request within both limits with 503 on an idle server. ([#12](https://github.com/unasuke/http3-ruby/pull/12))
19
+ - `HTTP3::Server.new` raises `ArgumentError` unless `send_buffer_size`, `max_connection_send_buffer` and `max_total_send_buffer` are positive Integers, each at least the one before. ([#17](https://github.com/unasuke/http3-ruby/pull/17))
20
+ - `HTTP3::Nghttp3::NV_FLAG_NO_COPY_NAME` and `NV_FLAG_NO_COPY_VALUE` are ignored: nghttp3 always copies the names and values, and lowercases the names it copies. `HTTP3::Nghttp3::NV.new` raises `ArgumentError` for flags that are not an Integer of 0..255. ([#13](https://github.com/unasuke/http3-ruby/pull/13))
21
+ - A callback may call back into its `HTTP3::Nghttp3::Connection` only as far as nghttp3 allows: inside the callbacks of `#read_stream`, the `#submit_*` methods, `#resume_stream`, `#block_stream`, `#unblock_stream`, `#shutdown_stream_read`, `#shutdown_stream_write`, `#submit_shutdown_notice`, `#shutdown`, `#set_max_client_streams_bidi` and `#stream_writable?`; inside any other callback, nothing that calls into nghttp3. The rest, `#close` among them, raise `InvalidStateError`, as do `HTTP3::Session`'s methods called from inside its callbacks. Once nghttp3 reports a connection error, only `#close` is allowed. ([#13](https://github.com/unasuke/http3-ruby/pull/13))
22
+ - What nghttp3 asserts of its callers raises instead. `ArgumentError`: a stream id out of 0..2**62-1 or of the wrong kind for the call, a smaller `#set_max_client_streams_bidi` than before, and a header that is not an `HTTP3::Nghttp3::NV` with String name and value, whose `to_str` is no longer called. `InvalidStateError`: `#submit_info` or `#set_max_client_streams_bidi` on a client, a `#submit_*` before `#bind_qpack_streams`, and a GOAWAY before `#bind_control_stream` or after `#shutdown`. ([#13](https://github.com/unasuke/http3-ruby/pull/13))
23
+ - `HTTP3::RackAdapter` answers 400, without calling the application or logging, a request whose scheme is not http or https, whose path has a fragment, whose authority is not `host` or `host:port`, or whose `:authority` and `host` field differ (RFC 9114 §4.3.1). It answers CONNECT with 501, since the server opens no tunnels. ([#14](https://github.com/unasuke/http3-ruby/pull/14))
24
+ - A response gets 500 for a status outside 200..599, a `content-length` given more than once or not a number, or one that a file or array body does not have. A body read with `each` is counted as it is sent, and its stream is aborted once it passes `content-length` or ends short of it. ([#14](https://github.com/unasuke/http3-ruby/pull/14))
25
+ - The `content-length` of a 204 response is dropped, as RFC 9110 §8.6 forbids one. ([#14](https://github.com/unasuke/http3-ruby/pull/14))
26
+
27
+ ### Fixed
28
+ - The browser steps of `test/interop/README.md` work as written. Chrome opens 127.0.0.1, since it looks localhost up as ::1 first. Firefox gets Alt-Svc over TCP from `openssl s_server` on the same port, since `network.http.http3.alt-svc-mapping-for-testing` alone still left it connecting over TCP, where nothing listened. ([#9](https://github.com/unasuke/http3-ruby/pull/9))
29
+ - A client that never finished its requests could make the server hold `max_request_body` on every stream of every connection: about 100 MiB per connection, and 100 GiB in all, with the defaults. The new buffer limits bound it. ([#12](https://github.com/unasuke/http3-ruby/pull/12))
30
+ - A request the client resets before sending it in full gets the server's side of its stream reset with H3_REQUEST_INCOMPLETE (RFC 9114 §4.1), so that the stream closes. Before, it stayed open, holding what had come of the request, until the connection ended. ([#12](https://github.com/unasuke/http3-ruby/pull/12))
31
+ - A client that allows fewer than three unidirectional streams no longer aborts the server process on nghttp3's assertions: its connection is closed with H3_GENERAL_PROTOCOL_ERROR. ([#12](https://github.com/unasuke/http3-ruby/pull/12))
32
+ - Once nghttp3 reports an error on a connection, `HTTP3::Session` deletes the nghttp3 connection and the QUIC connection closes. Before, calls made later in the same turn still went into the failed nghttp3 connection. ([#12](https://github.com/unasuke/http3-ruby/pull/12))
33
+ - An exception from `HTTP3::Session.attach` no longer stops `HTTP3::Server#run`. It is logged as `session_error` and closes that connection only. ([#12](https://github.com/unasuke/http3-ruby/pull/12))
34
+ - Two use-after-frees in `HTTP3::Nghttp3`: a header submitted with the no-copy flags went out as freed memory once its NV was dropped, and `Connection#close` inside a callback freed the nghttp3 connection nghttp3 was still running on. ([#13](https://github.com/unasuke/http3-ruby/pull/13))
35
+ - `Connection#submit_*` and `QPACK::Encoder#encode` put the header list on the heap. On the machine stack, a long list overflowed a thread's stack. ([#13](https://github.com/unasuke/http3-ruby/pull/13))
36
+ - `Connection#submit_*`, `QPACK::Encoder#encode` and `HTTP3::Session#submit_response` take the pointers into the header Strings only once no Ruby code can run before nghttp3 copies them. A GC that compacted the heap inside a later field's `to_str` made them send the bytes left behind, and the body's `to_str` could close the connection before nghttp3 was called with it. `Session#push_chunk` and `#attach_file` likewise convert their arguments before they look up the stream. ([#13](https://github.com/unasuke/http3-ruby/pull/13))
37
+ - A stream id, role or order of calls that nghttp3 asserts against no longer aborts the process. ([#13](https://github.com/unasuke/http3-ruby/pull/13))
38
+ - After nghttp3 reports a connection error, `HTTP3::Nghttp3::Connection` no longer lets calls go into the failed nghttp3 connection. ([#13](https://github.com/unasuke/http3-ruby/pull/13))
39
+ - `HTTP3::Nghttp3::QPACK::Decoder` decodes at most `MAX_STREAMS` (2000) streams at once, and raises `InvalidStateError` for one more. Before, a peer that began field sections and never finished them made it hold any number of stream contexts. ([#13](https://github.com/unasuke/http3-ruby/pull/13))
40
+ - Calling `QPACK::Encoder#initialize` or `QPACK::Decoder#initialize` again frees the old encoder or decoder, which leaked, and a decoder's stream contexts, from which it went on decoding a stream in the old state. ([#13](https://github.com/unasuke/http3-ruby/pull/13))
41
+ - The Rack env no longer carries what the client sent unchecked: the scheme goes in lowercase, and an empty port is the scheme's default. Before, a scheme such as file or ftp became `rack.url_scheme`, a fragment stayed in `PATH_INFO`, an authority such as `example.com:abc` was split into `SERVER_NAME` and `SERVER_PORT` however it fell apart, and `:authority` won over a `host` field that said otherwise. ([#14](https://github.com/unasuke/http3-ruby/pull/14))
42
+ - An array body with a chunk that is not a String is answered with 500 before anything is sent. Before, its headers went out and then its stream was aborted. ([#14](https://github.com/unasuke/http3-ruby/pull/14))
43
+ - A client that never finished its requests could keep what they held of the request buffers for as long as it kept them open: with the defaults, 16 such connections filled `max_total_request_buffer` and left every other request with 503. Such a request now gets 408 once `request_timeout` has passed. ([#18](https://github.com/unasuke/http3-ruby/pull/18))
44
+ - `HTTP3::Nghttp3::Connection#read_stream` takes frozen data, such as a String literal under `frozen_string_literal`. Before, it raised `FrozenError`. ([#16](https://github.com/unasuke/http3-ruby/pull/16))
45
+ - A client that stops reading its responses could make the server hold `send_buffer_size` on every stream of every connection: about 6.4 GiB with the defaults. The new send buffer limits bound it. ([#17](https://github.com/unasuke/http3-ruby/pull/17))
46
+
1
47
  ## [0.0.3] - 2026-10-13
2
48
 
3
49
  ### Added
data/README.md CHANGED
@@ -1,17 +1,23 @@
1
1
  # http3-ruby
2
2
 
3
- > [!CAUTION]
4
- > This gem is for experimental purposes only. Do not use it in production environments.
5
-
6
- > [!WARNING]
7
- > The code in this repository is almost entirely generated by Claude Code and reviewed by a human.
8
-
9
3
  `http3` serves Rack 3 applications over HTTP/3. It runs QUIC through the
10
4
  [quic gem](https://github.com/unasuke/quic-ruby) and HTTP/3 through
11
5
  [nghttp3](https://github.com/ngtcp2/nghttp3), which it builds from source and
12
6
  links in statically. The nghttp3 bindings are also there to use directly, as
13
7
  `HTTP3::Nghttp3`.
14
8
 
9
+ It is not suited for production yet:
10
+
11
+ - The application is called on the thread that drives every connection, one
12
+ request at a time, so a slow request holds up all the others.
13
+ - A request body is read in full, up to `max_request_body:` (1 MiB), before
14
+ the application is called, and streaming response bodies are not
15
+ supported.
16
+ - CI runs it on Ruby 4.0 alone, not on 3.3, the oldest it installs on. It is
17
+ checked against curl and the clients of quic-interop-runner, over links
18
+ with no loss or delay, and has not been run under load or over lossy
19
+ networks.
20
+
15
21
  ## Installation
16
22
 
17
23
  Linux only, Ruby 3.3 or later. Like the quic gem, installing builds C code, so
@@ -58,17 +64,44 @@ Other options, with their defaults:
58
64
  | `max_connections:` | `1024` | Connections at once; more are refused with CONNECTION_REFUSED |
59
65
  | `max_request_body:` | `1_048_576` | Bytes of request body; more gets 413 |
60
66
  | `max_field_section_size:` | `65_536` | Bytes of request headers (RFC 9114 §4.2.2); more gets 431 |
67
+ | `max_connection_request_buffer:` | `4_194_304` | Bytes a connection's requests may hold before they reach the application (headers counted as RFC 9114 §4.2.2 does, and bodies); more gets 503 |
68
+ | `max_total_request_buffer:` | `67_108_864` | The same over all connections; more gets 503 |
61
69
  | `send_buffer_size:` | `65_536` | Bytes of a response body queued but unsent per stream |
70
+ | `max_connection_send_buffer:` | `1_048_576` | Bytes of array and `each` response bodies a connection may have queued but unsent; more waits |
71
+ | `max_total_send_buffer:` | `67_108_864` | The same over all connections |
72
+ | `request_timeout:` | `30` | Seconds a request may take to come in full, counted from its headers; later gets 408. `nil` for no limit |
73
+ | `refusal_log_interval:` | `60` | Seconds between the `requests_refused` log lines, which count the requests answered without the application (400, 408, 413, 431, 501, 503) |
62
74
  | `shutdown_timeout:` | `30` | Seconds a graceful stop waits for the requests under way |
63
75
  | `qlog_dir:` | `nil` | Directory for one qlog file per connection |
64
76
  | `transport_params:` | `QUIC::TransportParams.default` | QUIC transport parameters |
65
77
  | `settings:` | `QUIC::Settings.default` | QUIC settings (the ALPN is always `h3`) |
66
78
  | `logger:` | `Logger.new($stderr)` | Where failures are logged |
67
79
 
80
+ `max_connection_request_buffer:` has to be at least `max_request_body:` plus
81
+ `max_field_section_size:`, and `max_total_request_buffer:` at least
82
+ `max_connection_request_buffer:`, so that a request within the limits never
83
+ gets 503 from an idle server; otherwise `HTTP3::Server.new` raises
84
+ `ArgumentError`.
85
+
86
+ A request gets 413, 431 or 503 as soon as it passes a limit, without waiting
87
+ for the rest of it, and the server reads no more of it (STOP_SENDING with
88
+ H3_NO_ERROR). The 503 has neither headers nor body.
89
+
90
+ A request gets 400, without the application seeing it, when its scheme is
91
+ not `http` or `https`, its path has a fragment, its authority is not `host`
92
+ or `host:port`, or its `:authority` and `host` field differ. `CONNECT` gets
93
+ 501: the server opens no tunnels.
94
+
68
95
  Response bodies may be files (`to_path`, sent 16 KiB at a time as the
69
- connection takes them), arrays or anything with `each`. Streaming bodies
70
- (`call`) are not supported and get 500, as do response headers whose names
71
- are not lowercase tokens or whose values contain NUL, CR or LF.
96
+ connection takes them), arrays or anything with `each`. These get 500:
97
+ streaming bodies (`call`), which are not supported; a status outside
98
+ 200..599; response headers whose names are not lowercase tokens or whose
99
+ values contain NUL, CR or LF; a `content-length` given more than once, not a
100
+ number, or not the length of a file or array body; and an array body with a
101
+ chunk that is not a String. A body read with `each` is counted as it is
102
+ sent, and its stream is aborted (H3_INTERNAL_ERROR) once it goes past
103
+ `content-length` or ends short of it. The `content-length` of a 204 is
104
+ dropped.
72
105
 
73
106
  ### Signals
74
107
 
@@ -105,6 +138,30 @@ alt-svc: h3=":443"
105
138
 
106
139
  That is configured in that server (nginx, Puma, ...), not in this gem.
107
140
 
141
+ ### HTTP3::Nghttp3
142
+
143
+ The bindings keep nghttp3's rules for its callers, so that a wrong call
144
+ raises instead of crashing the process or reaching freed memory:
145
+
146
+ - A callback (a `Callbacks` block or a body block) runs while nghttp3 is in
147
+ the middle of a call. Inside the callbacks of `read_stream`, it may call
148
+ `submit_*`, `resume_stream`, `block_stream`, `unblock_stream`,
149
+ `shutdown_stream_read`, `shutdown_stream_write`, `submit_shutdown_notice`,
150
+ `shutdown`, `set_max_client_streams_bidi` and `stream_writable?` on the
151
+ same connection. `close`, `read_stream`, `writev_stream` and the rest
152
+ raise `InvalidStateError` there, and inside any other callback every call
153
+ into nghttp3 does.
154
+ - Once nghttp3 reports a connection error, only `#close` can be called; the
155
+ rest raise `InvalidStateError`.
156
+ - `NV_FLAG_NO_COPY_NAME` and `NV_FLAG_NO_COPY_VALUE` are ignored: names and
157
+ values are always copied.
158
+ - Stream ids are checked for their range and kind, and calls for the role
159
+ and state of the connection, raising `ArgumentError` and
160
+ `InvalidStateError` instead of tripping nghttp3's assertions.
161
+ - A `QPACK::Decoder` decodes at most `MAX_STREAMS` (2000) streams at once.
162
+
163
+ The signatures in `sig/` describe each method.
164
+
108
165
  ## Development
109
166
 
110
167
  After checking out the repo, run `bin/setup` to install dependencies. Then,
data/ext/http3/nghttp3.c CHANGED
@@ -136,6 +136,25 @@ void nghttp3_rb_raise(int error_code, const char *fmt, ...) {
136
136
  }
137
137
  }
138
138
 
139
+ /* The largest stream id, the largest value of a QUIC variable-length
140
+ * integer. */
141
+ #define NGHTTP3_RB_MAX_STREAM_ID ((1ULL << 62) - 1)
142
+
143
+ int64_t nghttp3_rb_stream_id(VALUE rb_id) {
144
+ VALUE id = rb_to_int(rb_id);
145
+ uint64_t value;
146
+ /* 0 for zero, 1 for a positive value that fits, -1 or -2 for a negative
147
+ * one and 2 for one too large. */
148
+ int sign = rb_integer_pack(id, &value, 1, sizeof(value), 0,
149
+ INTEGER_PACK_MSWORD_FIRST |
150
+ INTEGER_PACK_NATIVE_BYTE_ORDER);
151
+ if ((sign == 0 || sign == 1) && value <= NGHTTP3_RB_MAX_STREAM_ID) {
152
+ return (int64_t)value;
153
+ }
154
+ rb_raise(rb_eArgError, "stream_id must be 0..2**62-1 (got %" PRIsVALUE ")",
155
+ rb_id);
156
+ }
157
+
139
158
  /*
140
159
  * call-seq:
141
160
  * Nghttp3.err_is_fatal?(error_code) -> true or false
@@ -174,7 +193,7 @@ static const quic_ext_api *http3_load_quic_api(uint32_t major) {
174
193
  VALUE version = rb_const_get(quic, rb_intern("VERSION"));
175
194
  rb_raise(rb_eLoadError,
176
195
  "http3 needs the quic gem's C function table for quic/ext.h "
177
- "ABI major version %u (quic ~> 0.0.3), which quic %" PRIsVALUE
196
+ "ABI major version %u (quic ~> 0.0.5), which quic %" PRIsVALUE
178
197
  " does not provide",
179
198
  (unsigned int)major, version);
180
199
  }
data/ext/http3/nghttp3.h CHANGED
@@ -46,6 +46,13 @@ extern const rb_data_type_t settings_data_type;
46
46
  /* TypedData type for Callbacks */
47
47
  extern const rb_data_type_t callbacks_data_type;
48
48
 
49
+ /* Which call into nghttp3 a connection is in, for ConnectionObj.entered.
50
+ * nghttp3 runs callbacks only inside such a call, and lets a callback call
51
+ * back into the connection only as far as that call allows: from inside
52
+ * read_stream, ngtcp2's examples submit a response, but nghttp3 is in no
53
+ * state to read, write or be deleted. */
54
+ enum { CONN_IDLE, CONN_IN_READ, CONN_IN_OTHER };
55
+
49
56
  /* The C side of an Nghttp3::Connection. nghttp3 gets a pointer to this
50
57
  * struct as conn_user_data, never the Ruby object: GC.compact may move the
51
58
  * object, and dcompact keeps self pointing at it. */
@@ -66,6 +73,19 @@ typedef struct {
66
73
  VALUE pending_exc;
67
74
  int is_closed;
68
75
  int is_server;
76
+ int entered; /* CONN_IDLE, or the call into nghttp3 under way */
77
+ /* nghttp3 returned an error after which it allows nothing but
78
+ * nghttp3_conn_del: any from read_stream or writev_stream, or a fatal one
79
+ * from another call. */
80
+ int failed;
81
+ /* What has been done to the connection, for the calls nghttp3 asserts
82
+ * come after it or not after it. goaway is 0 before any GOAWAY, 1 after
83
+ * submit_shutdown_notice and 2 after shutdown. max_client_streams is the
84
+ * last value given to set_max_client_streams_bidi. */
85
+ int ctrl_bound;
86
+ int qpack_bound;
87
+ int goaway;
88
+ uint64_t max_client_streams;
69
89
  } ConnectionObj;
70
90
 
71
91
  /* Run body(arg) under rb_protect on behalf of a callback of obj. An
@@ -84,11 +104,24 @@ void nghttp3_rb_conn_stream_closed(ConnectionObj *obj, int64_t stream_id);
84
104
  VALUE nghttp3_rb_error_class_for_code(int error_code);
85
105
  void nghttp3_rb_raise(int error_code, const char *fmt, ...);
86
106
 
107
+ /* rb_id (to_int is called) as a stream id for nghttp3, which asserts the
108
+ * range of the ids it is given: ArgumentError unless it is 0..2**62-1. */
109
+ int64_t nghttp3_rb_stream_id(VALUE rb_id);
110
+
87
111
  /* Settings helper */
88
112
  nghttp3_settings *nghttp3_rb_get_settings(VALUE rb_settings);
89
113
 
90
- /* NV helper */
91
- nghttp3_nv nghttp3_rb_nv_to_c(VALUE rb_nv);
114
+ /* NV helpers. nghttp3_rb_nva_fill fills nva with the first nvlen elements
115
+ * of headers, an Array of NVs, pointing into their Strings. It never raises,
116
+ * allocates nothing and runs no Ruby code, so that from the moment it takes
117
+ * those pointers until nghttp3 has copied the Strings, nothing can change,
118
+ * free or move them; so call it after anything that allocates or may run
119
+ * Ruby code, and call nghttp3 right after it. It returns the index of the
120
+ * first element that is not an NV with String name and value and flags of
121
+ * 0..255, or -1 if there is none. nghttp3_rb_raise_bad_nv raises
122
+ * ArgumentError for that index. */
123
+ long nghttp3_rb_nva_fill(VALUE headers, nghttp3_nv *nva, long nvlen);
124
+ NORETURN(void nghttp3_rb_raise_bad_nv(long index));
92
125
 
93
126
  /* Callbacks helper: install the C callbacks, which do their own
94
127
  * bookkeeping and then call the Ruby blocks of conn's Callbacks, if any. */