quic 0.0.2 → 0.0.3
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 +4 -4
- data/CHANGELOG.md +24 -0
- data/LICENSE-DEPENDENCIES.txt +2 -19
- data/README.md +27 -2
- data/Rakefile +7 -0
- data/examples/echo_demo.rb +5 -6
- data/examples/echo_server_demo.rb +127 -0
- data/ext/quic/connection_client.c +23 -880
- data/ext/quic/connection_common.c +1262 -0
- data/ext/quic/connection_common.h +217 -0
- data/ext/quic/connection_server.c +186 -0
- data/ext/quic/endpoint.c +823 -0
- data/ext/quic/endpoint.h +28 -0
- data/ext/quic/ext_api.c +112 -0
- data/ext/quic/extconf.rb +12 -5
- data/ext/quic/include/quic/ext.h +84 -0
- data/ext/quic/quic.c +29 -4
- data/ext/quic/quic.h +8 -8
- data/ext/quic/stream.c +81 -20
- data/ext/quic/stream.h +13 -7
- data/ext/quic/tls_context.c +260 -0
- data/ext/quic/tls_context.h +46 -0
- data/lib/quic/connection.rb +142 -57
- data/lib/quic/endpoint.rb +57 -0
- data/lib/quic/ext_include.rb +12 -0
- data/lib/quic/settings.rb +26 -3
- data/lib/quic/stream.rb +4 -3
- data/lib/quic/version.rb +1 -1
- data/lib/quic.rb +2 -0
- data/sig/quic.rbs +203 -22
- metadata +13 -1
|
@@ -0,0 +1,1262 @@
|
|
|
1
|
+
#include "connection_common.h"
|
|
2
|
+
#include "endpoint.h"
|
|
3
|
+
#include "stream.h"
|
|
4
|
+
|
|
5
|
+
#include <netinet/in.h>
|
|
6
|
+
#include <string.h>
|
|
7
|
+
#include <sys/socket.h>
|
|
8
|
+
#include <time.h>
|
|
9
|
+
|
|
10
|
+
#include <openssl/rand.h>
|
|
11
|
+
|
|
12
|
+
/* picotls sends a key share only for the first entry; the others are offered
|
|
13
|
+
in supported_groups and reached via HelloRetryRequest. X25519 is first so
|
|
14
|
+
the common case stays 1-RTT. */
|
|
15
|
+
ptls_key_exchange_algorithm_t *quic_key_exchanges[] = {
|
|
16
|
+
&ptls_openssl_x25519,
|
|
17
|
+
&ptls_openssl_secp256r1,
|
|
18
|
+
#if PTLS_OPENSSL_HAVE_SECP384R1
|
|
19
|
+
&ptls_openssl_secp384r1,
|
|
20
|
+
#endif
|
|
21
|
+
NULL,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
ptls_cipher_suite_t *quic_cipher_suites[] = {
|
|
25
|
+
&ptls_openssl_aes128gcmsha256,
|
|
26
|
+
&ptls_openssl_aes256gcmsha384,
|
|
27
|
+
#if PTLS_OPENSSL_HAVE_CHACHA20_POLY1305
|
|
28
|
+
&ptls_openssl_chacha20poly1305sha256,
|
|
29
|
+
#endif
|
|
30
|
+
NULL,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
static void
|
|
34
|
+
quic_conn_free(void *ptr)
|
|
35
|
+
{
|
|
36
|
+
quic_conn_t *c = (quic_conn_t *)ptr;
|
|
37
|
+
/* The Endpoint may be swept in the same GC cycle and freed first; nothing
|
|
38
|
+
from here on may reach its CID table. */
|
|
39
|
+
c->endpoint = NULL;
|
|
40
|
+
if (c->conn) ngtcp2_conn_del(c->conn);
|
|
41
|
+
if (c->cptls.ptls) {
|
|
42
|
+
/* Safe even when configure_client_session never ran or failed midway. */
|
|
43
|
+
ngtcp2_crypto_picotls_deconfigure_session(&c->cptls);
|
|
44
|
+
ptls_free(c->cptls.ptls);
|
|
45
|
+
}
|
|
46
|
+
/* After ptls_free: the ptls_t references verify_cert through tls_ctx, or
|
|
47
|
+
the shared context on a server. */
|
|
48
|
+
if (c->verify_cert_initialized) ptls_openssl_dispose_verify_certificate(&c->verify_cert);
|
|
49
|
+
if (c->tls != NULL) quic_tls_ctx_unref(c->tls);
|
|
50
|
+
/* Still open only when no ngtcp2_conn was made to finish it. */
|
|
51
|
+
if (c->qlog != NULL) fclose(c->qlog);
|
|
52
|
+
xfree(c->alpn);
|
|
53
|
+
xfree(c->alpn_buf);
|
|
54
|
+
xfree(c);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
static size_t
|
|
58
|
+
quic_conn_size(const void *ptr)
|
|
59
|
+
{
|
|
60
|
+
(void)ptr;
|
|
61
|
+
return sizeof(quic_conn_t);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/* owner is the object wrapping this struct, so it needs no mark. */
|
|
65
|
+
static void
|
|
66
|
+
quic_conn_mark(void *ptr)
|
|
67
|
+
{
|
|
68
|
+
quic_conn_t *c = (quic_conn_t *)ptr;
|
|
69
|
+
rb_gc_mark_movable(c->pending_exc);
|
|
70
|
+
rb_gc_mark_movable(c->handler_owner);
|
|
71
|
+
rb_gc_mark_movable(c->endpoint_owner);
|
|
72
|
+
rb_gc_mark_movable(c->error);
|
|
73
|
+
rb_gc_mark_movable(c->closebuf);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/* GC.compact may relocate the owner connection object. Follow it so the
|
|
77
|
+
void* user_data ngtcp2 callbacks receive (which equals this struct,
|
|
78
|
+
stored inside owner) keeps resolving @streams / @accept_queue. The
|
|
79
|
+
raw ngtcp2/picotls pointers are outside Ruby's heap and are left alone. */
|
|
80
|
+
static void
|
|
81
|
+
quic_conn_compact(void *ptr)
|
|
82
|
+
{
|
|
83
|
+
quic_conn_t *c = (quic_conn_t *)ptr;
|
|
84
|
+
c->owner = rb_gc_location(c->owner);
|
|
85
|
+
c->pending_exc = rb_gc_location(c->pending_exc);
|
|
86
|
+
c->handler_owner = rb_gc_location(c->handler_owner);
|
|
87
|
+
c->endpoint_owner = rb_gc_location(c->endpoint_owner);
|
|
88
|
+
c->error = rb_gc_location(c->error);
|
|
89
|
+
c->closebuf = rb_gc_location(c->closebuf);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const rb_data_type_t quic_conn_data_type = {
|
|
93
|
+
"QUIC::Connection",
|
|
94
|
+
{quic_conn_mark, quic_conn_free, quic_conn_size, quic_conn_compact,},
|
|
95
|
+
NULL, NULL,
|
|
96
|
+
RUBY_TYPED_FREE_IMMEDIATELY,
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
VALUE
|
|
100
|
+
quic_conn_alloc(VALUE klass)
|
|
101
|
+
{
|
|
102
|
+
quic_conn_t *c = ALLOC(quic_conn_t);
|
|
103
|
+
memset(c, 0, sizeof(*c));
|
|
104
|
+
c->pending_exc = Qnil;
|
|
105
|
+
c->handler_owner = Qnil;
|
|
106
|
+
c->endpoint_owner = Qnil;
|
|
107
|
+
c->error = Qnil;
|
|
108
|
+
c->closebuf = Qnil;
|
|
109
|
+
return TypedData_Wrap_Struct(klass, &quic_conn_data_type, c);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/* Re-raise the exception a callback left in c->pending_exc, if any. Called
|
|
113
|
+
right after ngtcp2 returns, before its return value is looked at: the
|
|
114
|
+
NGTCP2_ERR_CALLBACK_FAILURE it reports is the result of that exception. */
|
|
115
|
+
static void
|
|
116
|
+
quic_conn_raise_pending(quic_conn_t *c)
|
|
117
|
+
{
|
|
118
|
+
if (NIL_P(c->pending_exc)) return;
|
|
119
|
+
VALUE exc = c->pending_exc;
|
|
120
|
+
c->pending_exc = Qnil;
|
|
121
|
+
rb_exc_raise(exc);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/* Run body(arg) under rb_protect. An exception is kept in c->pending_exc
|
|
125
|
+
(the first one wins; ngtcp2 stops at the first failing callback anyway)
|
|
126
|
+
and turned into NGTCP2_ERR_CALLBACK_FAILURE, so that it never unwinds
|
|
127
|
+
through ngtcp2. */
|
|
128
|
+
static int
|
|
129
|
+
quic_conn_protect(quic_conn_t *c, VALUE (*body)(VALUE), void *arg)
|
|
130
|
+
{
|
|
131
|
+
int state = 0;
|
|
132
|
+
rb_protect(body, (VALUE)arg, &state);
|
|
133
|
+
if (state == 0) return 0;
|
|
134
|
+
if (NIL_P(c->pending_exc)) c->pending_exc = rb_errinfo();
|
|
135
|
+
rb_set_errinfo(Qnil);
|
|
136
|
+
return NGTCP2_ERR_CALLBACK_FAILURE;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
quic_conn_t *
|
|
140
|
+
quic_conn_get(VALUE self)
|
|
141
|
+
{
|
|
142
|
+
quic_conn_t *c;
|
|
143
|
+
TypedData_Get_Struct(self, quic_conn_t, &quic_conn_data_type, c);
|
|
144
|
+
return c;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
ngtcp2_conn *
|
|
148
|
+
quic_conn_ptr(VALUE owner)
|
|
149
|
+
{
|
|
150
|
+
return quic_conn_get(owner)->conn;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
ngtcp2_tstamp
|
|
154
|
+
quic_now(void)
|
|
155
|
+
{
|
|
156
|
+
struct timespec ts;
|
|
157
|
+
clock_gettime(CLOCK_MONOTONIC, &ts);
|
|
158
|
+
return (ngtcp2_tstamp)ts.tv_sec * NGTCP2_SECONDS + (ngtcp2_tstamp)ts.tv_nsec;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
static void
|
|
162
|
+
quic_rand_cb(uint8_t *dest, size_t destlen, const ngtcp2_rand_ctx *rand_ctx)
|
|
163
|
+
{
|
|
164
|
+
(void)rand_ctx;
|
|
165
|
+
RAND_bytes(dest, (int)destlen);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/* A connection an Endpoint holds derives the token from the Endpoint's
|
|
169
|
+
secret and enters the new CID in its table. Any other connection never
|
|
170
|
+
sends a Stateless Reset, so a random token does. */
|
|
171
|
+
static int
|
|
172
|
+
quic_get_new_connection_id_cb(ngtcp2_conn *conn, ngtcp2_cid *cid,
|
|
173
|
+
ngtcp2_stateless_reset_token *token, size_t cidlen,
|
|
174
|
+
void *user_data)
|
|
175
|
+
{
|
|
176
|
+
(void)conn;
|
|
177
|
+
quic_conn_t *c = (quic_conn_t *)user_data;
|
|
178
|
+
if (RAND_bytes(cid->data, (int)cidlen) != 1) {
|
|
179
|
+
return NGTCP2_ERR_CALLBACK_FAILURE;
|
|
180
|
+
}
|
|
181
|
+
cid->datalen = cidlen;
|
|
182
|
+
if (c->endpoint == NULL) {
|
|
183
|
+
return RAND_bytes(token->data, NGTCP2_STATELESS_RESET_TOKENLEN) == 1
|
|
184
|
+
? 0 : NGTCP2_ERR_CALLBACK_FAILURE;
|
|
185
|
+
}
|
|
186
|
+
if (quic_endpoint_generate_reset_token(c->endpoint, token->data, cid) != 0) {
|
|
187
|
+
return NGTCP2_ERR_CALLBACK_FAILURE;
|
|
188
|
+
}
|
|
189
|
+
quic_endpoint_associate_cid(c->endpoint, cid, c->owner);
|
|
190
|
+
return 0;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
static int
|
|
194
|
+
quic_remove_connection_id_cb(ngtcp2_conn *conn, const ngtcp2_cid *cid, void *user_data)
|
|
195
|
+
{
|
|
196
|
+
(void)conn;
|
|
197
|
+
quic_conn_t *c = (quic_conn_t *)user_data;
|
|
198
|
+
if (c->endpoint != NULL) quic_endpoint_dissociate_cid(c->endpoint, cid, c->owner);
|
|
199
|
+
return 0;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
ngtcp2_conn *
|
|
203
|
+
quic_conn_get_conn(ngtcp2_crypto_conn_ref *conn_ref)
|
|
204
|
+
{
|
|
205
|
+
return ((quic_conn_t *)conn_ref->user_data)->conn;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
void
|
|
209
|
+
quic_require_binary(VALUE str, const char *name)
|
|
210
|
+
{
|
|
211
|
+
Check_Type(str, T_STRING);
|
|
212
|
+
if (rb_enc_get_index(str) != rb_ascii8bit_encindex()) {
|
|
213
|
+
rb_raise(rb_eArgError, "%s must be ASCII-8BIT (binary) encoding", name);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
void
|
|
218
|
+
quic_require_sockaddr(VALUE str, const char *name)
|
|
219
|
+
{
|
|
220
|
+
quic_require_binary(str, name);
|
|
221
|
+
|
|
222
|
+
long len = RSTRING_LEN(str);
|
|
223
|
+
sa_family_t family = AF_UNSPEC;
|
|
224
|
+
if (len >= (long)(offsetof(struct sockaddr, sa_family) + sizeof(family))) {
|
|
225
|
+
memcpy(&family, RSTRING_PTR(str) + offsetof(struct sockaddr, sa_family), sizeof(family));
|
|
226
|
+
}
|
|
227
|
+
if ((family == AF_INET && len == (long)sizeof(struct sockaddr_in)) ||
|
|
228
|
+
(family == AF_INET6 && len == (long)sizeof(struct sockaddr_in6))) {
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
rb_raise(rb_eArgError, "%s must be an AF_INET or AF_INET6 sockaddr (Addrinfo#to_sockaddr)", name);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
static uint64_t
|
|
235
|
+
quic_get_uint64_field(VALUE obj, const char *name)
|
|
236
|
+
{
|
|
237
|
+
VALUE v = rb_funcall(obj, rb_intern(name), 0);
|
|
238
|
+
return (uint64_t)NUM2ULL(v);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
static ngtcp2_cc_algo
|
|
242
|
+
quic_cc_algo_from_sym(VALUE sym)
|
|
243
|
+
{
|
|
244
|
+
Check_Type(sym, T_SYMBOL);
|
|
245
|
+
ID id = SYM2ID(sym);
|
|
246
|
+
if (id == rb_intern("reno")) return NGTCP2_CC_ALGO_RENO;
|
|
247
|
+
if (id == rb_intern("cubic")) return NGTCP2_CC_ALGO_CUBIC;
|
|
248
|
+
if (id == rb_intern("bbr")) return NGTCP2_CC_ALGO_BBR;
|
|
249
|
+
rb_raise(rb_eArgError, "unknown cc_algo: %"PRIsVALUE" (expected :reno, :cubic, or :bbr)", sym);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
void
|
|
253
|
+
quic_fill_transport_params(ngtcp2_transport_params *params, VALUE tp)
|
|
254
|
+
{
|
|
255
|
+
ngtcp2_transport_params_default(params);
|
|
256
|
+
params->initial_max_stream_data_bidi_local =
|
|
257
|
+
quic_get_uint64_field(tp, "initial_max_stream_data_bidi_local");
|
|
258
|
+
params->initial_max_stream_data_bidi_remote =
|
|
259
|
+
quic_get_uint64_field(tp, "initial_max_stream_data_bidi_remote");
|
|
260
|
+
params->initial_max_stream_data_uni =
|
|
261
|
+
quic_get_uint64_field(tp, "initial_max_stream_data_uni");
|
|
262
|
+
params->initial_max_data =
|
|
263
|
+
quic_get_uint64_field(tp, "initial_max_data");
|
|
264
|
+
params->initial_max_streams_bidi =
|
|
265
|
+
quic_get_uint64_field(tp, "initial_max_streams_bidi");
|
|
266
|
+
params->initial_max_streams_uni =
|
|
267
|
+
quic_get_uint64_field(tp, "initial_max_streams_uni");
|
|
268
|
+
params->max_idle_timeout =
|
|
269
|
+
(ngtcp2_duration)quic_get_uint64_field(tp, "max_idle_timeout");
|
|
270
|
+
params->active_connection_id_limit =
|
|
271
|
+
quic_get_uint64_field(tp, "active_connection_id_limit");
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
void
|
|
275
|
+
quic_fill_settings(ngtcp2_settings *settings, VALUE st)
|
|
276
|
+
{
|
|
277
|
+
ngtcp2_settings_default(settings);
|
|
278
|
+
settings->initial_ts = quic_now();
|
|
279
|
+
settings->cc_algo = quic_cc_algo_from_sym(rb_funcall(st, rb_intern("cc_algo"), 0));
|
|
280
|
+
settings->initial_rtt =
|
|
281
|
+
(ngtcp2_duration)quic_get_uint64_field(st, "initial_rtt");
|
|
282
|
+
settings->max_window =
|
|
283
|
+
quic_get_uint64_field(st, "max_window");
|
|
284
|
+
settings->max_stream_window =
|
|
285
|
+
quic_get_uint64_field(st, "max_stream_window");
|
|
286
|
+
settings->handshake_timeout =
|
|
287
|
+
(ngtcp2_duration)quic_get_uint64_field(st, "handshake_timeout");
|
|
288
|
+
VALUE no_pmtud = rb_funcall(st, rb_intern("no_pmtud"), 0);
|
|
289
|
+
settings->no_pmtud = RTEST(no_pmtud) ? 1 : 0;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/* ngtcp2_settings.qlog_write. Touches no Ruby object, so it needs no
|
|
293
|
+
rb_protect. A write error only costs qlog. */
|
|
294
|
+
static void
|
|
295
|
+
quic_qlog_write_cb(void *user_data, uint32_t flags, const void *data, size_t datalen)
|
|
296
|
+
{
|
|
297
|
+
quic_conn_t *c = (quic_conn_t *)user_data;
|
|
298
|
+
if (c->qlog == NULL) return;
|
|
299
|
+
fwrite(data, 1, datalen, c->qlog);
|
|
300
|
+
if (flags & NGTCP2_QLOG_WRITE_FLAG_FIN) {
|
|
301
|
+
fclose(c->qlog);
|
|
302
|
+
c->qlog = NULL;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
void
|
|
307
|
+
quic_conn_open_qlog(quic_conn_t *c, VALUE settings_v, ngtcp2_settings *settings)
|
|
308
|
+
{
|
|
309
|
+
VALUE dir = quic_path_value(rb_funcall(settings_v, rb_intern("qlog_dir"), 0), "qlog_dir");
|
|
310
|
+
if (NIL_P(dir)) return;
|
|
311
|
+
|
|
312
|
+
char hex[NGTCP2_MAX_CIDLEN * 2 + 1];
|
|
313
|
+
for (size_t i = 0; i < c->scid.datalen; i++) {
|
|
314
|
+
snprintf(hex + i * 2, 3, "%02x", c->scid.data[i]);
|
|
315
|
+
}
|
|
316
|
+
hex[c->scid.datalen * 2] = '\0';
|
|
317
|
+
VALUE path = rb_sprintf("%"PRIsVALUE"/%s.sqlog", dir, hex);
|
|
318
|
+
c->qlog = fopen(StringValueCStr(path), "w");
|
|
319
|
+
if (c->qlog != NULL) settings->qlog_write = quic_qlog_write_cb;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/* Validate a path setting (ca_file, ca_path, ...) and return it as a String,
|
|
323
|
+
or Qnil. Accepts a String or anything responding to #to_path. Every
|
|
324
|
+
misconfiguration raises ArgumentError, including a wrong type. */
|
|
325
|
+
VALUE
|
|
326
|
+
quic_path_value(VALUE value, const char *name)
|
|
327
|
+
{
|
|
328
|
+
if (NIL_P(value)) return Qnil;
|
|
329
|
+
|
|
330
|
+
VALUE path = value;
|
|
331
|
+
if (!RB_TYPE_P(path, T_STRING)) {
|
|
332
|
+
if (rb_respond_to(path, rb_intern("to_path"))) {
|
|
333
|
+
path = rb_funcall(path, rb_intern("to_path"), 0);
|
|
334
|
+
}
|
|
335
|
+
if (!RB_TYPE_P(path, T_STRING)) {
|
|
336
|
+
rb_raise(rb_eArgError, "%s must be a String or Pathname (got %"PRIsVALUE")",
|
|
337
|
+
name, rb_obj_class(value));
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
/* Raises ArgumentError when the path contains a NUL byte. */
|
|
341
|
+
StringValueCStr(path);
|
|
342
|
+
return path;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/* picotls keeps the pointers of an ALPN list for the whole handshake, so
|
|
346
|
+
ALLOCA_N would not survive. */
|
|
347
|
+
void
|
|
348
|
+
quic_copy_alpn(VALUE alpn_ary, ptls_iovec_t **list, size_t *count, uint8_t **buf)
|
|
349
|
+
{
|
|
350
|
+
Check_Type(alpn_ary, T_ARRAY);
|
|
351
|
+
long n_alpn = RARRAY_LEN(alpn_ary);
|
|
352
|
+
*count = 0;
|
|
353
|
+
if (n_alpn <= 0) return;
|
|
354
|
+
|
|
355
|
+
size_t total = 0;
|
|
356
|
+
for (long i = 0; i < n_alpn; i++) {
|
|
357
|
+
VALUE entry = RARRAY_AREF(alpn_ary, i);
|
|
358
|
+
Check_Type(entry, T_STRING);
|
|
359
|
+
long len = RSTRING_LEN(entry);
|
|
360
|
+
if (len < 1 || len > 255) {
|
|
361
|
+
rb_raise(rb_eArgError, "alpn entry must be 1-255 bytes (got %ld)", len);
|
|
362
|
+
}
|
|
363
|
+
total += (size_t)len;
|
|
364
|
+
}
|
|
365
|
+
*buf = ALLOC_N(uint8_t, total);
|
|
366
|
+
*list = ALLOC_N(ptls_iovec_t, n_alpn);
|
|
367
|
+
uint8_t *p = *buf;
|
|
368
|
+
for (long i = 0; i < n_alpn; i++) {
|
|
369
|
+
VALUE entry = RARRAY_AREF(alpn_ary, i);
|
|
370
|
+
size_t len = (size_t)RSTRING_LEN(entry);
|
|
371
|
+
memcpy(p, RSTRING_PTR(entry), len);
|
|
372
|
+
(*list)[i] = ptls_iovec_init(p, len);
|
|
373
|
+
p += len;
|
|
374
|
+
}
|
|
375
|
+
*count = (size_t)n_alpn;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/* An empty list leaves c->alpn NULL, which a client sends as no ALPN
|
|
379
|
+
extension at all. */
|
|
380
|
+
void
|
|
381
|
+
quic_conn_copy_alpn(quic_conn_t *c, VALUE settings_v)
|
|
382
|
+
{
|
|
383
|
+
quic_copy_alpn(rb_funcall(settings_v, rb_intern("alpn"), 0), &c->alpn, &c->alpn_count,
|
|
384
|
+
&c->alpn_buf);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/* Shared helper: look up the QUIC::Stream registered for stream_id in the
|
|
388
|
+
owner connection's @streams Hash. Returns Qnil if not found (which can
|
|
389
|
+
happen if the stream was already removed by a previous stream_close
|
|
390
|
+
callback). */
|
|
391
|
+
static VALUE
|
|
392
|
+
quic_conn_lookup_stream(quic_conn_t *c, int64_t stream_id)
|
|
393
|
+
{
|
|
394
|
+
VALUE streams = rb_ivar_get(c->owner, rb_intern("@streams"));
|
|
395
|
+
return rb_hash_aref(streams, LL2NUM(stream_id));
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/* ngtcp2 stream callbacks. The Ruby-touching part of each is a *_body
|
|
399
|
+
function run under rb_protect by quic_conn_protect, so that an exception
|
|
400
|
+
(a frozen @streams, an allocation failure, ...) never unwinds through
|
|
401
|
+
ngtcp2. The callback then returns NGTCP2_ERR_CALLBACK_FAILURE and the
|
|
402
|
+
exception is re-raised once ngtcp2 has returned. */
|
|
403
|
+
|
|
404
|
+
/* The arguments of a stream callback, handed to its body through
|
|
405
|
+
rb_protect's single VALUE. Each body reads only the fields it needs. */
|
|
406
|
+
struct quic_stream_cb_args {
|
|
407
|
+
quic_conn_t *c;
|
|
408
|
+
int64_t stream_id;
|
|
409
|
+
uint32_t flags;
|
|
410
|
+
const uint8_t *data;
|
|
411
|
+
size_t datalen;
|
|
412
|
+
uint64_t acked;
|
|
413
|
+
uint64_t app_error_code;
|
|
414
|
+
};
|
|
415
|
+
|
|
416
|
+
static VALUE
|
|
417
|
+
quic_stream_open_body(VALUE arg)
|
|
418
|
+
{
|
|
419
|
+
struct quic_stream_cb_args *a = (struct quic_stream_cb_args *)arg;
|
|
420
|
+
VALUE streams = rb_ivar_get(a->c->owner, rb_intern("@streams"));
|
|
421
|
+
if (!NIL_P(rb_hash_aref(streams, LL2NUM(a->stream_id)))) {
|
|
422
|
+
return Qnil; /* already known (opened by this endpoint) */
|
|
423
|
+
}
|
|
424
|
+
VALUE stream = quic_stream_new(a->stream_id, a->c->owner);
|
|
425
|
+
rb_hash_aset(streams, LL2NUM(a->stream_id), stream);
|
|
426
|
+
return Qnil;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
void
|
|
430
|
+
quic_conn_give_back_peer_stream(ngtcp2_conn *conn, int64_t stream_id)
|
|
431
|
+
{
|
|
432
|
+
if (!ngtcp2_conn_is_local_stream2(conn, stream_id) && ngtcp2_is_bidi_stream(stream_id)) {
|
|
433
|
+
ngtcp2_conn_extend_max_streams_bidi(conn, 1);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
static int
|
|
438
|
+
quic_stream_open_cb(ngtcp2_conn *conn, int64_t stream_id, void *user_data)
|
|
439
|
+
{
|
|
440
|
+
(void)conn;
|
|
441
|
+
quic_conn_t *c = (quic_conn_t *)user_data;
|
|
442
|
+
if (c->handler) return 0; /* the handler keeps its own stream state */
|
|
443
|
+
struct quic_stream_cb_args a = {.c = c, .stream_id = stream_id};
|
|
444
|
+
return quic_conn_protect(c, quic_stream_open_body, &a);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
static VALUE
|
|
448
|
+
quic_recv_stream_data_body(VALUE arg)
|
|
449
|
+
{
|
|
450
|
+
struct quic_stream_cb_args *a = (struct quic_stream_cb_args *)arg;
|
|
451
|
+
quic_conn_t *c = a->c;
|
|
452
|
+
VALUE stream = quic_conn_lookup_stream(c, a->stream_id);
|
|
453
|
+
if (NIL_P(stream)) return Qnil;
|
|
454
|
+
|
|
455
|
+
quic_stream_t *s;
|
|
456
|
+
TypedData_Get_Struct(stream, quic_stream_t, &quic_stream_data_type, s);
|
|
457
|
+
|
|
458
|
+
/* Surface peer-initiated streams to #accept_stream. The low stream-id bit
|
|
459
|
+
marks the initiator (0 == client, 1 == server), so a client queues the
|
|
460
|
+
odd ids and a server the even ones. Push once, on first data arrival,
|
|
461
|
+
then mark so later data does not re-enqueue. */
|
|
462
|
+
bool peer_initiated = c->is_server ? !(a->stream_id & 0x01) : (a->stream_id & 0x01);
|
|
463
|
+
if (!s->accept_queued && peer_initiated) {
|
|
464
|
+
VALUE accept_queue = rb_ivar_get(c->owner, rb_intern("@accept_queue"));
|
|
465
|
+
rb_ary_push(accept_queue, stream);
|
|
466
|
+
s->accept_queued = true;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
if (a->datalen > 0) {
|
|
470
|
+
VALUE recv_buffer = rb_ivar_get(stream, rb_intern("@recv_buffer"));
|
|
471
|
+
rb_str_buf_cat(recv_buffer, (const char *)a->data, (long)a->datalen);
|
|
472
|
+
}
|
|
473
|
+
if (a->flags & NGTCP2_STREAM_DATA_FLAG_FIN) {
|
|
474
|
+
s->fin_received = true;
|
|
475
|
+
}
|
|
476
|
+
return Qnil;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
static int
|
|
480
|
+
quic_recv_stream_data_cb(ngtcp2_conn *conn, uint32_t flags, int64_t stream_id,
|
|
481
|
+
uint64_t offset, const uint8_t *data, size_t datalen,
|
|
482
|
+
void *user_data, void *stream_user_data)
|
|
483
|
+
{
|
|
484
|
+
(void)offset;
|
|
485
|
+
(void)stream_user_data;
|
|
486
|
+
quic_conn_t *c = (quic_conn_t *)user_data;
|
|
487
|
+
if (c->handler) {
|
|
488
|
+
size_t consumed = 0;
|
|
489
|
+
int fin = (flags & NGTCP2_STREAM_DATA_FLAG_FIN) != 0;
|
|
490
|
+
if (c->handler->recv_stream_data(c->handler_ud, stream_id, data, datalen, fin,
|
|
491
|
+
c->now, &consumed) < 0) {
|
|
492
|
+
return NGTCP2_ERR_CALLBACK_FAILURE;
|
|
493
|
+
}
|
|
494
|
+
/* What the handler consumed outright (for HTTP/3, everything but the
|
|
495
|
+
request body) goes straight back to the peer as credit. */
|
|
496
|
+
if (consumed > 0) {
|
|
497
|
+
if (ngtcp2_conn_extend_max_stream_offset(conn, stream_id, consumed) != 0) {
|
|
498
|
+
return NGTCP2_ERR_CALLBACK_FAILURE;
|
|
499
|
+
}
|
|
500
|
+
ngtcp2_conn_extend_max_offset(conn, consumed);
|
|
501
|
+
}
|
|
502
|
+
return 0;
|
|
503
|
+
}
|
|
504
|
+
struct quic_stream_cb_args a = {
|
|
505
|
+
.c = c, .stream_id = stream_id, .flags = flags, .data = data, .datalen = datalen,
|
|
506
|
+
};
|
|
507
|
+
return quic_conn_protect(c, quic_recv_stream_data_body, &a);
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
static VALUE
|
|
511
|
+
quic_acked_stream_data_offset_body(VALUE arg)
|
|
512
|
+
{
|
|
513
|
+
struct quic_stream_cb_args *a = (struct quic_stream_cb_args *)arg;
|
|
514
|
+
VALUE stream = quic_conn_lookup_stream(a->c, a->stream_id);
|
|
515
|
+
if (NIL_P(stream)) return Qnil;
|
|
516
|
+
|
|
517
|
+
quic_stream_t *s;
|
|
518
|
+
TypedData_Get_Struct(stream, quic_stream_t, &quic_stream_data_type, s);
|
|
519
|
+
s->acked_offset += a->acked;
|
|
520
|
+
|
|
521
|
+
/* Shift any head Chunks of @pending_chunks whose end-offset is fully
|
|
522
|
+
covered by acked_offset. Each Chunk's start-offset in the stream is
|
|
523
|
+
pending_shifted (after previous shifts); its end-offset is
|
|
524
|
+
pending_shifted + RSTRING_LEN(head). */
|
|
525
|
+
VALUE pending = rb_ivar_get(stream, rb_intern("@pending_chunks"));
|
|
526
|
+
while (RARRAY_LEN(pending) > 0) {
|
|
527
|
+
VALUE head = RARRAY_AREF(pending, 0);
|
|
528
|
+
uint64_t head_end = s->pending_shifted + (uint64_t)RSTRING_LEN(head);
|
|
529
|
+
if (head_end <= s->acked_offset) {
|
|
530
|
+
rb_ary_shift(pending);
|
|
531
|
+
s->pending_shifted = head_end;
|
|
532
|
+
} else {
|
|
533
|
+
break;
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
return Qnil;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
static int
|
|
540
|
+
quic_acked_stream_data_offset_cb(ngtcp2_conn *conn, int64_t stream_id,
|
|
541
|
+
uint64_t offset, uint64_t datalen,
|
|
542
|
+
void *user_data, void *stream_user_data)
|
|
543
|
+
{
|
|
544
|
+
(void)conn;
|
|
545
|
+
(void)offset;
|
|
546
|
+
(void)stream_user_data;
|
|
547
|
+
quic_conn_t *c = (quic_conn_t *)user_data;
|
|
548
|
+
if (c->handler) {
|
|
549
|
+
return c->handler->acked_stream_data(c->handler_ud, stream_id, datalen) < 0
|
|
550
|
+
? NGTCP2_ERR_CALLBACK_FAILURE : 0;
|
|
551
|
+
}
|
|
552
|
+
struct quic_stream_cb_args a = {.c = c, .stream_id = stream_id, .acked = datalen};
|
|
553
|
+
return quic_conn_protect(c, quic_acked_stream_data_offset_body, &a);
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
static VALUE
|
|
557
|
+
quic_stream_close_body(VALUE arg)
|
|
558
|
+
{
|
|
559
|
+
struct quic_stream_cb_args *a = (struct quic_stream_cb_args *)arg;
|
|
560
|
+
VALUE streams = rb_ivar_get(a->c->owner, rb_intern("@streams"));
|
|
561
|
+
VALUE stream = rb_hash_aref(streams, LL2NUM(a->stream_id));
|
|
562
|
+
if (NIL_P(stream)) {
|
|
563
|
+
quic_conn_give_back_peer_stream(a->c->conn, a->stream_id);
|
|
564
|
+
return Qnil;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
quic_stream_t *s;
|
|
568
|
+
TypedData_Get_Struct(stream, quic_stream_t, &quic_stream_data_type, s);
|
|
569
|
+
s->closed = true;
|
|
570
|
+
if (a->flags & NGTCP2_STREAM_CLOSE_FLAG_APP_ERROR_CODE_SET) {
|
|
571
|
+
s->close_has_app_error_code = true;
|
|
572
|
+
s->close_app_error_code = a->app_error_code;
|
|
573
|
+
}
|
|
574
|
+
/* A stream still waiting in @accept_queue holds on to what it received.
|
|
575
|
+
Were the peer given its stream back now, it could open and close
|
|
576
|
+
streams until the queue had used up our memory, so it gets the stream
|
|
577
|
+
back only once the application has accepted this one
|
|
578
|
+
(QUIC::Stream#_accepted). */
|
|
579
|
+
if (s->accept_queued && !s->accepted) {
|
|
580
|
+
s->peer_stream_held = true;
|
|
581
|
+
} else {
|
|
582
|
+
quic_conn_give_back_peer_stream(a->c->conn, a->stream_id);
|
|
583
|
+
}
|
|
584
|
+
rb_hash_delete(streams, LL2NUM(a->stream_id));
|
|
585
|
+
return Qnil;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
static int
|
|
589
|
+
quic_stream_close_cb(ngtcp2_conn *conn, uint32_t flags, int64_t stream_id,
|
|
590
|
+
uint64_t app_error_code, void *user_data,
|
|
591
|
+
void *stream_user_data)
|
|
592
|
+
{
|
|
593
|
+
(void)stream_user_data;
|
|
594
|
+
quic_conn_t *c = (quic_conn_t *)user_data;
|
|
595
|
+
if (c->handler) {
|
|
596
|
+
/* The handler keeps no stream waiting for the application, so the peer
|
|
597
|
+
gets its stream back at once. */
|
|
598
|
+
quic_conn_give_back_peer_stream(conn, stream_id);
|
|
599
|
+
int has_code = (flags & NGTCP2_STREAM_CLOSE_FLAG_APP_ERROR_CODE_SET) != 0;
|
|
600
|
+
return c->handler->stream_close(c->handler_ud, stream_id, has_code, app_error_code) < 0
|
|
601
|
+
? NGTCP2_ERR_CALLBACK_FAILURE : 0;
|
|
602
|
+
}
|
|
603
|
+
struct quic_stream_cb_args a = {
|
|
604
|
+
.c = c, .stream_id = stream_id, .flags = flags, .app_error_code = app_error_code,
|
|
605
|
+
};
|
|
606
|
+
return quic_conn_protect(c, quic_stream_close_body, &a);
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
static VALUE
|
|
610
|
+
quic_stream_reset_body(VALUE arg)
|
|
611
|
+
{
|
|
612
|
+
struct quic_stream_cb_args *a = (struct quic_stream_cb_args *)arg;
|
|
613
|
+
VALUE stream = quic_conn_lookup_stream(a->c, a->stream_id);
|
|
614
|
+
if (NIL_P(stream)) return Qnil;
|
|
615
|
+
|
|
616
|
+
quic_stream_t *s;
|
|
617
|
+
TypedData_Get_Struct(stream, quic_stream_t, &quic_stream_data_type, s);
|
|
618
|
+
s->reset = true;
|
|
619
|
+
s->close_app_error_code = a->app_error_code;
|
|
620
|
+
s->close_has_app_error_code = true;
|
|
621
|
+
return Qnil;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
static int
|
|
625
|
+
quic_stream_reset_cb(ngtcp2_conn *conn, int64_t stream_id, uint64_t final_size,
|
|
626
|
+
uint64_t app_error_code, void *user_data,
|
|
627
|
+
void *stream_user_data)
|
|
628
|
+
{
|
|
629
|
+
(void)conn;
|
|
630
|
+
(void)stream_user_data;
|
|
631
|
+
quic_conn_t *c = (quic_conn_t *)user_data;
|
|
632
|
+
if (c->handler) {
|
|
633
|
+
return c->handler->stream_reset(c->handler_ud, stream_id, final_size, app_error_code) < 0
|
|
634
|
+
? NGTCP2_ERR_CALLBACK_FAILURE : 0;
|
|
635
|
+
}
|
|
636
|
+
struct quic_stream_cb_args a = {.c = c, .stream_id = stream_id, .app_error_code = app_error_code};
|
|
637
|
+
return quic_conn_protect(c, quic_stream_reset_body, &a);
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
/* The callbacks below matter only to a stream handler. Without one, the
|
|
641
|
+
QUIC::Stream mode needs none of them. */
|
|
642
|
+
|
|
643
|
+
static int
|
|
644
|
+
quic_stream_stop_sending_cb(ngtcp2_conn *conn, int64_t stream_id,
|
|
645
|
+
uint64_t app_error_code, void *user_data,
|
|
646
|
+
void *stream_user_data)
|
|
647
|
+
{
|
|
648
|
+
(void)conn;
|
|
649
|
+
(void)stream_user_data;
|
|
650
|
+
quic_conn_t *c = (quic_conn_t *)user_data;
|
|
651
|
+
if (!c->handler) return 0;
|
|
652
|
+
return c->handler->stream_stop_sending(c->handler_ud, stream_id, app_error_code) < 0
|
|
653
|
+
? NGTCP2_ERR_CALLBACK_FAILURE : 0;
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
static int
|
|
657
|
+
quic_extend_max_stream_data_cb(ngtcp2_conn *conn, int64_t stream_id,
|
|
658
|
+
uint64_t max_data, void *user_data,
|
|
659
|
+
void *stream_user_data)
|
|
660
|
+
{
|
|
661
|
+
(void)conn;
|
|
662
|
+
(void)max_data;
|
|
663
|
+
(void)stream_user_data;
|
|
664
|
+
quic_conn_t *c = (quic_conn_t *)user_data;
|
|
665
|
+
if (!c->handler) return 0;
|
|
666
|
+
return c->handler->extend_max_stream_data(c->handler_ud, stream_id) < 0
|
|
667
|
+
? NGTCP2_ERR_CALLBACK_FAILURE : 0;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
static int
|
|
671
|
+
quic_extend_max_remote_streams_bidi_cb(ngtcp2_conn *conn, uint64_t max_streams,
|
|
672
|
+
void *user_data)
|
|
673
|
+
{
|
|
674
|
+
(void)conn;
|
|
675
|
+
quic_conn_t *c = (quic_conn_t *)user_data;
|
|
676
|
+
if (!c->handler) return 0;
|
|
677
|
+
return c->handler->extend_max_remote_streams_bidi(c->handler_ud, max_streams) < 0
|
|
678
|
+
? NGTCP2_ERR_CALLBACK_FAILURE : 0;
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
static int
|
|
682
|
+
quic_handshake_completed_cb(ngtcp2_conn *conn, void *user_data)
|
|
683
|
+
{
|
|
684
|
+
(void)conn;
|
|
685
|
+
quic_conn_t *c = (quic_conn_t *)user_data;
|
|
686
|
+
if (!c->handler) return 0;
|
|
687
|
+
return c->handler->handshake_completed(c->handler_ud) < 0
|
|
688
|
+
? NGTCP2_ERR_CALLBACK_FAILURE : 0;
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
void
|
|
692
|
+
quic_conn_fill_common_callbacks(ngtcp2_callbacks *callbacks)
|
|
693
|
+
{
|
|
694
|
+
memset(callbacks, 0, sizeof(*callbacks));
|
|
695
|
+
callbacks->recv_crypto_data = ngtcp2_crypto_recv_crypto_data_cb;
|
|
696
|
+
callbacks->encrypt = ngtcp2_crypto_encrypt_cb;
|
|
697
|
+
callbacks->decrypt = ngtcp2_crypto_decrypt_cb;
|
|
698
|
+
callbacks->hp_mask = ngtcp2_crypto_hp_mask_cb;
|
|
699
|
+
callbacks->update_key = ngtcp2_crypto_update_key_cb;
|
|
700
|
+
callbacks->delete_crypto_aead_ctx = ngtcp2_crypto_delete_crypto_aead_ctx_cb;
|
|
701
|
+
callbacks->delete_crypto_cipher_ctx = ngtcp2_crypto_delete_crypto_cipher_ctx_cb;
|
|
702
|
+
callbacks->get_path_challenge_data2 = ngtcp2_crypto_get_path_challenge_data2_cb;
|
|
703
|
+
callbacks->version_negotiation = ngtcp2_crypto_version_negotiation_cb;
|
|
704
|
+
callbacks->rand = quic_rand_cb;
|
|
705
|
+
callbacks->get_new_connection_id2 = quic_get_new_connection_id_cb;
|
|
706
|
+
callbacks->remove_connection_id = quic_remove_connection_id_cb;
|
|
707
|
+
callbacks->stream_open = quic_stream_open_cb;
|
|
708
|
+
callbacks->recv_stream_data = quic_recv_stream_data_cb;
|
|
709
|
+
callbacks->acked_stream_data_offset = quic_acked_stream_data_offset_cb;
|
|
710
|
+
callbacks->stream_close = quic_stream_close_cb;
|
|
711
|
+
callbacks->stream_reset = quic_stream_reset_cb;
|
|
712
|
+
callbacks->stream_stop_sending = quic_stream_stop_sending_cb;
|
|
713
|
+
callbacks->extend_max_stream_data = quic_extend_max_stream_data_cb;
|
|
714
|
+
callbacks->extend_max_remote_streams_bidi = quic_extend_max_remote_streams_bidi_cb;
|
|
715
|
+
callbacks->handshake_completed = quic_handshake_completed_cb;
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
/* Find the first stream in @streams that has either pending bytes or a
|
|
719
|
+
pending FIN to flush. Phase 4 minimum: linear scan, take the first match
|
|
720
|
+
(no round-robin). Sets *out_datav to the byte slice to send, *out_datavcnt
|
|
721
|
+
to 0 or 1, *out_flags to NGTCP2_WRITE_STREAM_FLAG_FIN when applicable.
|
|
722
|
+
Returns the stream's Ruby VALUE (or Qnil if no candidate). */
|
|
723
|
+
static VALUE
|
|
724
|
+
quic_conn_pick_send_stream(VALUE self, ngtcp2_vec *out_datav,
|
|
725
|
+
size_t *out_datavcnt, uint32_t *out_flags,
|
|
726
|
+
int64_t *out_stream_id)
|
|
727
|
+
{
|
|
728
|
+
VALUE streams = rb_ivar_get(self, rb_intern("@streams"));
|
|
729
|
+
VALUE stream_values = rb_funcall(streams, rb_intern("values"), 0);
|
|
730
|
+
|
|
731
|
+
for (long i = 0; i < RARRAY_LEN(stream_values); i++) {
|
|
732
|
+
VALUE st = RARRAY_AREF(stream_values, i);
|
|
733
|
+
quic_stream_t *s;
|
|
734
|
+
TypedData_Get_Struct(st, quic_stream_t, &quic_stream_data_type, s);
|
|
735
|
+
if (s->fin_flushed) continue;
|
|
736
|
+
|
|
737
|
+
VALUE pending = rb_ivar_get(st, rb_intern("@pending_chunks"));
|
|
738
|
+
uint64_t position = s->sent_offset - s->pending_shifted;
|
|
739
|
+
long chunks_count = RARRAY_LEN(pending);
|
|
740
|
+
|
|
741
|
+
/* Walk pending Chunks to find the one containing the next unsent byte. */
|
|
742
|
+
long chunk_idx = -1;
|
|
743
|
+
long offset_in_chunk = 0;
|
|
744
|
+
for (long j = 0; j < chunks_count; j++) {
|
|
745
|
+
VALUE chunk = RARRAY_AREF(pending, j);
|
|
746
|
+
long clen = RSTRING_LEN(chunk);
|
|
747
|
+
if (position < (uint64_t)clen) {
|
|
748
|
+
chunk_idx = j;
|
|
749
|
+
offset_in_chunk = (long)position;
|
|
750
|
+
break;
|
|
751
|
+
}
|
|
752
|
+
position -= (uint64_t)clen;
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
if (chunk_idx >= 0) {
|
|
756
|
+
VALUE chunk = RARRAY_AREF(pending, chunk_idx);
|
|
757
|
+
out_datav->base = (uint8_t *)RSTRING_PTR(chunk) + offset_in_chunk;
|
|
758
|
+
out_datav->len = (size_t)(RSTRING_LEN(chunk) - offset_in_chunk);
|
|
759
|
+
*out_datavcnt = 1;
|
|
760
|
+
*out_stream_id = s->stream_id;
|
|
761
|
+
/* Attach FIN if this is the LAST chunk and we're going to send all
|
|
762
|
+
remaining bytes of it (ngtcp2 may encode less, in which case FIN
|
|
763
|
+
won't actually be flushed and we'll retry next call). */
|
|
764
|
+
*out_flags = (s->fin_sent && chunk_idx == chunks_count - 1)
|
|
765
|
+
? NGTCP2_WRITE_STREAM_FLAG_FIN
|
|
766
|
+
: 0;
|
|
767
|
+
return st;
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
if (s->fin_sent && !s->fin_flushed) {
|
|
771
|
+
/* No data to send but a pending FIN-only frame. */
|
|
772
|
+
out_datav->base = NULL;
|
|
773
|
+
out_datav->len = 0;
|
|
774
|
+
*out_datavcnt = 0;
|
|
775
|
+
*out_stream_id = s->stream_id;
|
|
776
|
+
*out_flags = NGTCP2_WRITE_STREAM_FLAG_FIN;
|
|
777
|
+
return st;
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
return Qnil;
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
/* #write_pkt for the QUIC::Stream mode. If any stream has pending bytes or
|
|
785
|
+
a pending FIN, route the packet through ngtcp2_conn_writev_stream so the
|
|
786
|
+
stream data gets framed alongside connection-level frames. Falls back to
|
|
787
|
+
conn-only when no stream is queued. Returns what ngtcp2 returned. */
|
|
788
|
+
static ngtcp2_ssize
|
|
789
|
+
quic_conn_write_pkt_streams(VALUE self, quic_conn_t *c, ngtcp2_path *path,
|
|
790
|
+
ngtcp2_pkt_info *pi, uint8_t *dest, size_t destlen)
|
|
791
|
+
{
|
|
792
|
+
ngtcp2_vec datav;
|
|
793
|
+
size_t datavcnt = 0;
|
|
794
|
+
uint32_t writev_flags = 0;
|
|
795
|
+
int64_t send_stream_id = -1;
|
|
796
|
+
VALUE selected_stream = quic_conn_pick_send_stream(
|
|
797
|
+
self, &datav, &datavcnt, &writev_flags, &send_stream_id);
|
|
798
|
+
|
|
799
|
+
ngtcp2_ssize pdatalen = -1;
|
|
800
|
+
ngtcp2_ssize n = ngtcp2_conn_writev_stream(
|
|
801
|
+
c->conn, path, pi, dest, destlen, &pdatalen,
|
|
802
|
+
writev_flags, send_stream_id, &datav, datavcnt, c->now);
|
|
803
|
+
|
|
804
|
+
if (n == NGTCP2_ERR_STREAM_DATA_BLOCKED && NIL_P(c->pending_exc)) {
|
|
805
|
+
/* The stream's queued data is beyond what the peer's flow control lets
|
|
806
|
+
through right now. Keep it queued and build the packet without it, so
|
|
807
|
+
that ACKs and other frames still go out; the data follows once the
|
|
808
|
+
peer extends its window (MAX_STREAM_DATA / MAX_DATA). */
|
|
809
|
+
selected_stream = Qnil;
|
|
810
|
+
datavcnt = 0;
|
|
811
|
+
writev_flags = 0;
|
|
812
|
+
pdatalen = -1;
|
|
813
|
+
n = ngtcp2_conn_writev_stream(c->conn, path, pi, dest, destlen, &pdatalen,
|
|
814
|
+
0, -1, NULL, 0, c->now);
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
if (n < 0 || !NIL_P(c->pending_exc)) return n;
|
|
818
|
+
|
|
819
|
+
/* Update the selected stream's accounting only when we actually sent
|
|
820
|
+
bytes. pdatalen >= 0 means ngtcp2 framed that many data bytes into the
|
|
821
|
+
packet; if 0, no data was framed (but the packet may still contain ACK
|
|
822
|
+
or other frames). */
|
|
823
|
+
if (!NIL_P(selected_stream) && pdatalen > 0) {
|
|
824
|
+
quic_stream_t *s;
|
|
825
|
+
TypedData_Get_Struct(selected_stream, quic_stream_t,
|
|
826
|
+
&quic_stream_data_type, s);
|
|
827
|
+
s->sent_offset += (uint64_t)pdatalen;
|
|
828
|
+
/* FIN is only actually written when all requested data fits in the
|
|
829
|
+
frame (see ngtcp2 docs on NGTCP2_WRITE_STREAM_FLAG_FIN). */
|
|
830
|
+
if ((writev_flags & NGTCP2_WRITE_STREAM_FLAG_FIN) &&
|
|
831
|
+
(size_t)pdatalen == datav.len) {
|
|
832
|
+
s->fin_flushed = true;
|
|
833
|
+
}
|
|
834
|
+
} else if (!NIL_P(selected_stream) && datavcnt == 0 && n > 0 &&
|
|
835
|
+
(writev_flags & NGTCP2_WRITE_STREAM_FLAG_FIN)) {
|
|
836
|
+
/* FIN-only packet (no data) was successfully written. */
|
|
837
|
+
quic_stream_t *s;
|
|
838
|
+
TypedData_Get_Struct(selected_stream, quic_stream_t,
|
|
839
|
+
&quic_stream_data_type, s);
|
|
840
|
+
s->fin_flushed = true;
|
|
841
|
+
}
|
|
842
|
+
return n;
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
/* The number of vectors #write_pkt asks a stream handler for per packet. */
|
|
846
|
+
#define QUIC_HANDLER_VECCNT 16
|
|
847
|
+
|
|
848
|
+
_Static_assert(sizeof(quic_ext_vec) == sizeof(ngtcp2_vec),
|
|
849
|
+
"quic_ext_vec must have the layout of ngtcp2_vec");
|
|
850
|
+
_Static_assert(offsetof(quic_ext_vec, base) == offsetof(ngtcp2_vec, base),
|
|
851
|
+
"quic_ext_vec.base must line up with ngtcp2_vec.base");
|
|
852
|
+
_Static_assert(offsetof(quic_ext_vec, len) == offsetof(ngtcp2_vec, len),
|
|
853
|
+
"quic_ext_vec.len must line up with ngtcp2_vec.len");
|
|
854
|
+
|
|
855
|
+
/* #write_pkt with a stream handler: pull the next stream data from the
|
|
856
|
+
handler for every packet, as ngtcp2's examples/server.cc does with
|
|
857
|
+
nghttp3. NGTCP2_WRITE_STREAM_FLAG_MORE is not used, so a packet carries
|
|
858
|
+
data of one stream at most. A handler failure is reported as
|
|
859
|
+
NGTCP2_ERR_CALLBACK_FAILURE. */
|
|
860
|
+
static ngtcp2_ssize
|
|
861
|
+
quic_conn_write_pkt_handler(quic_conn_t *c, ngtcp2_path *path, ngtcp2_pkt_info *pi,
|
|
862
|
+
uint8_t *dest, size_t destlen)
|
|
863
|
+
{
|
|
864
|
+
const quic_ext_stream_handler *h = c->handler;
|
|
865
|
+
for (;;) {
|
|
866
|
+
int64_t sid = -1;
|
|
867
|
+
int fin = 0;
|
|
868
|
+
quic_ext_vec vec[QUIC_HANDLER_VECCNT];
|
|
869
|
+
size_t nvec = 0;
|
|
870
|
+
if (ngtcp2_conn_get_max_data_left2(c->conn) &&
|
|
871
|
+
h->next_stream_data(c->handler_ud, &sid, &fin, vec, QUIC_HANDLER_VECCNT, &nvec) < 0) {
|
|
872
|
+
return NGTCP2_ERR_CALLBACK_FAILURE;
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
ngtcp2_ssize ndatalen = -1;
|
|
876
|
+
ngtcp2_ssize n = ngtcp2_conn_writev_stream(
|
|
877
|
+
c->conn, path, pi, dest, destlen, &ndatalen,
|
|
878
|
+
fin ? NGTCP2_WRITE_STREAM_FLAG_FIN : 0, sid,
|
|
879
|
+
(const ngtcp2_vec *)vec, nvec, c->now);
|
|
880
|
+
if (n == NGTCP2_ERR_STREAM_DATA_BLOCKED) {
|
|
881
|
+
if (h->stream_blocked(c->handler_ud, sid) < 0) return NGTCP2_ERR_CALLBACK_FAILURE;
|
|
882
|
+
continue;
|
|
883
|
+
}
|
|
884
|
+
if (n == NGTCP2_ERR_STREAM_SHUT_WR) {
|
|
885
|
+
if (h->stream_shut_write(c->handler_ud, sid) < 0) return NGTCP2_ERR_CALLBACK_FAILURE;
|
|
886
|
+
continue;
|
|
887
|
+
}
|
|
888
|
+
if (n < 0) return n;
|
|
889
|
+
if (ndatalen >= 0 && h->stream_data_written(c->handler_ud, sid, (size_t)ndatalen) < 0) {
|
|
890
|
+
return NGTCP2_ERR_CALLBACK_FAILURE;
|
|
891
|
+
}
|
|
892
|
+
return n; /* 0: nothing to send */
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
ngtcp2_ssize
|
|
897
|
+
quic_conn_write_pkt_raw(VALUE self, quic_conn_t *c, ngtcp2_path *path, uint8_t *dest)
|
|
898
|
+
{
|
|
899
|
+
ngtcp2_pkt_info pi;
|
|
900
|
+
memset(&pi, 0, sizeof(pi));
|
|
901
|
+
|
|
902
|
+
c->now = quic_now();
|
|
903
|
+
return c->handler
|
|
904
|
+
? quic_conn_write_pkt_handler(c, path, &pi, dest, QUIC_WRITE_PKT_BUFLEN)
|
|
905
|
+
: quic_conn_write_pkt_streams(self, c, path, &pi, dest, QUIC_WRITE_PKT_BUFLEN);
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
static VALUE
|
|
909
|
+
quic_conn_write_pkt(int argc, VALUE *argv, VALUE self)
|
|
910
|
+
{
|
|
911
|
+
VALUE buffer = Qnil;
|
|
912
|
+
rb_scan_args(argc, argv, "01", &buffer);
|
|
913
|
+
|
|
914
|
+
quic_conn_t *c = quic_conn_get(self);
|
|
915
|
+
|
|
916
|
+
if (NIL_P(buffer)) {
|
|
917
|
+
/* rb_str_buf_new returns an ASCII-8BIT (binary) String per CRuby spec,
|
|
918
|
+
so no explicit rb_enc_associate is required. */
|
|
919
|
+
buffer = rb_str_buf_new(QUIC_WRITE_PKT_BUFLEN);
|
|
920
|
+
} else {
|
|
921
|
+
Check_Type(buffer, T_STRING);
|
|
922
|
+
if (rb_enc_get_index(buffer) != rb_ascii8bit_encindex()) {
|
|
923
|
+
rb_raise(rb_eArgError, "buffer must be ASCII-8BIT (binary) encoding");
|
|
924
|
+
}
|
|
925
|
+
if (RSTRING_LEN(buffer) < (long)QUIC_WRITE_PKT_BUFLEN) {
|
|
926
|
+
rb_str_modify_expand(buffer, (long)QUIC_WRITE_PKT_BUFLEN - RSTRING_LEN(buffer));
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
ngtcp2_path_storage path_storage;
|
|
931
|
+
ngtcp2_path_storage_zero(&path_storage);
|
|
932
|
+
|
|
933
|
+
ngtcp2_ssize n =
|
|
934
|
+
quic_conn_write_pkt_raw(self, c, &path_storage.path, (uint8_t *)RSTRING_PTR(buffer));
|
|
935
|
+
|
|
936
|
+
if (!NIL_P(c->pending_exc)) {
|
|
937
|
+
rb_str_set_len(buffer, 0);
|
|
938
|
+
quic_conn_raise_pending(c);
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
if (n < 0) {
|
|
942
|
+
/* ngtcp2 does not partially write on failure, but truncate explicitly so
|
|
943
|
+
that a rescued caller cannot accidentally transmit stale bytes. */
|
|
944
|
+
rb_str_set_len(buffer, 0);
|
|
945
|
+
quic_raise_ngtcp2_error((int)n);
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
if (n == 0) {
|
|
949
|
+
rb_str_set_len(buffer, 0);
|
|
950
|
+
return Qnil;
|
|
951
|
+
}
|
|
952
|
+
rb_str_set_len(buffer, n);
|
|
953
|
+
return buffer;
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
/* Like quic_ngtcp2_error_new, but for errors from read_pkt, where the TLS
|
|
957
|
+
handshake runs: NGTCP2_ERR_CRYPTO carries the TLS alert this endpoint
|
|
958
|
+
sent, and becomes CertificateVerifyFailed when the verifier rejected the
|
|
959
|
+
peer. */
|
|
960
|
+
VALUE
|
|
961
|
+
quic_conn_error_new(quic_conn_t *c, int rv)
|
|
962
|
+
{
|
|
963
|
+
if (rv != NGTCP2_ERR_CRYPTO) return quic_ngtcp2_error_new(rv);
|
|
964
|
+
|
|
965
|
+
/* 0 means "not set" to ngtcp2; close_notify (0) is never sent for a
|
|
966
|
+
verification failure, so report it as nil. */
|
|
967
|
+
uint8_t alert = ngtcp2_conn_get_tls_alert2(c->conn);
|
|
968
|
+
VALUE exc;
|
|
969
|
+
if (c->verify_failed) {
|
|
970
|
+
VALUE msg = c->verify_result
|
|
971
|
+
? rb_sprintf("certificate verify failed (%s)",
|
|
972
|
+
X509_verify_cert_error_string(c->verify_result))
|
|
973
|
+
: rb_str_new_cstr("certificate verify failed (server sent no certificate)");
|
|
974
|
+
exc = rb_exc_new_str(rb_eQUICErrorCertificateVerifyFailed, msg);
|
|
975
|
+
rb_ivar_set(exc, rb_intern("@verify_result"),
|
|
976
|
+
c->verify_result ? INT2NUM(c->verify_result) : Qnil);
|
|
977
|
+
} else {
|
|
978
|
+
exc = rb_exc_new_cstr(rb_eQUICErrorCryptoError, ngtcp2_strerror(rv));
|
|
979
|
+
}
|
|
980
|
+
rb_ivar_set(exc, rb_intern("@code"), INT2NUM(rv));
|
|
981
|
+
rb_ivar_set(exc, rb_intern("@tls_alert"), alert ? INT2NUM(alert) : Qnil);
|
|
982
|
+
return exc;
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
int
|
|
986
|
+
quic_conn_read_pkt_raw(quic_conn_t *c, const ngtcp2_path *path, const uint8_t *data,
|
|
987
|
+
size_t datalen)
|
|
988
|
+
{
|
|
989
|
+
ngtcp2_pkt_info pi;
|
|
990
|
+
memset(&pi, 0, sizeof(pi));
|
|
991
|
+
|
|
992
|
+
c->now = quic_now();
|
|
993
|
+
return ngtcp2_conn_read_pkt(c->conn, path, &pi, data, datalen, c->now);
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
struct quic_read_pkt_args {
|
|
997
|
+
quic_conn_t *c;
|
|
998
|
+
VALUE packet;
|
|
999
|
+
ngtcp2_path path;
|
|
1000
|
+
};
|
|
1001
|
+
|
|
1002
|
+
static VALUE
|
|
1003
|
+
quic_read_pkt_body(VALUE arg)
|
|
1004
|
+
{
|
|
1005
|
+
struct quic_read_pkt_args *a = (struct quic_read_pkt_args *)arg;
|
|
1006
|
+
int rv = quic_conn_read_pkt_raw(a->c, &a->path, (const uint8_t *)RSTRING_PTR(a->packet),
|
|
1007
|
+
(size_t)RSTRING_LEN(a->packet));
|
|
1008
|
+
quic_conn_raise_pending(a->c);
|
|
1009
|
+
if (rv != 0) {
|
|
1010
|
+
/* longjmp passes through rb_ensure so unlock still fires. */
|
|
1011
|
+
rb_exc_raise(quic_conn_error_new(a->c, rv));
|
|
1012
|
+
}
|
|
1013
|
+
return Qnil;
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
static VALUE
|
|
1017
|
+
quic_read_pkt_unlock(VALUE arg)
|
|
1018
|
+
{
|
|
1019
|
+
rb_str_unlocktmp(((struct quic_read_pkt_args *)arg)->packet);
|
|
1020
|
+
return Qnil;
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
static VALUE
|
|
1024
|
+
quic_conn_read_pkt(int argc, VALUE *argv, VALUE self)
|
|
1025
|
+
{
|
|
1026
|
+
VALUE packet = Qnil;
|
|
1027
|
+
VALUE opts = Qnil;
|
|
1028
|
+
rb_scan_args(argc, argv, "1:", &packet, &opts);
|
|
1029
|
+
|
|
1030
|
+
if (NIL_P(opts)) {
|
|
1031
|
+
rb_raise(rb_eArgError, "missing keywords: local_sockaddr, remote_sockaddr");
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
VALUE local_sockaddr = rb_hash_aref(opts, ID2SYM(rb_intern("local_sockaddr")));
|
|
1035
|
+
VALUE remote_sockaddr = rb_hash_aref(opts, ID2SYM(rb_intern("remote_sockaddr")));
|
|
1036
|
+
|
|
1037
|
+
if (NIL_P(local_sockaddr) || NIL_P(remote_sockaddr)) {
|
|
1038
|
+
rb_raise(rb_eArgError, "missing keywords: local_sockaddr, remote_sockaddr");
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
quic_require_binary(packet, "packet");
|
|
1042
|
+
quic_require_sockaddr(local_sockaddr, "local_sockaddr");
|
|
1043
|
+
quic_require_sockaddr(remote_sockaddr, "remote_sockaddr");
|
|
1044
|
+
|
|
1045
|
+
quic_conn_t *c = quic_conn_get(self);
|
|
1046
|
+
|
|
1047
|
+
struct quic_read_pkt_args args;
|
|
1048
|
+
args.c = c;
|
|
1049
|
+
args.packet = packet;
|
|
1050
|
+
args.path.local.addr = (struct sockaddr *)RSTRING_PTR(local_sockaddr);
|
|
1051
|
+
args.path.local.addrlen = (ngtcp2_socklen)RSTRING_LEN(local_sockaddr);
|
|
1052
|
+
args.path.remote.addr = (struct sockaddr *)RSTRING_PTR(remote_sockaddr);
|
|
1053
|
+
args.path.remote.addrlen = (ngtcp2_socklen)RSTRING_LEN(remote_sockaddr);
|
|
1054
|
+
args.path.user_data = NULL;
|
|
1055
|
+
|
|
1056
|
+
rb_str_locktmp(packet);
|
|
1057
|
+
return rb_ensure(quic_read_pkt_body, (VALUE)&args,
|
|
1058
|
+
quic_read_pkt_unlock, (VALUE)&args);
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
static VALUE
|
|
1062
|
+
quic_conn_handshake_completed_p(VALUE self)
|
|
1063
|
+
{
|
|
1064
|
+
quic_conn_t *c = quic_conn_get(self);
|
|
1065
|
+
return ngtcp2_conn_get_handshake_completed2(c->conn) ? Qtrue : Qfalse;
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
static VALUE
|
|
1069
|
+
quic_conn_in_closing_period_p(VALUE self)
|
|
1070
|
+
{
|
|
1071
|
+
quic_conn_t *c = quic_conn_get(self);
|
|
1072
|
+
return ngtcp2_conn_in_closing_period2(c->conn) ? Qtrue : Qfalse;
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
static VALUE
|
|
1076
|
+
quic_conn_in_draining_period_p(VALUE self)
|
|
1077
|
+
{
|
|
1078
|
+
quic_conn_t *c = quic_conn_get(self);
|
|
1079
|
+
return ngtcp2_conn_in_draining_period2(c->conn) ? Qtrue : Qfalse;
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
static VALUE
|
|
1083
|
+
quic_conn_expiry(VALUE self)
|
|
1084
|
+
{
|
|
1085
|
+
quic_conn_t *c = quic_conn_get(self);
|
|
1086
|
+
ngtcp2_tstamp t = ngtcp2_conn_get_expiry2(c->conn);
|
|
1087
|
+
if (t == UINT64_MAX) return Qnil;
|
|
1088
|
+
return ULL2NUM((unsigned long long)t);
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
/* Caller is responsible for ordering with #read_pkt / #write_pkt:
|
|
1092
|
+
read_pkt or handle_expiry to advance ngtcp2 state, then write_pkt to flush. */
|
|
1093
|
+
static VALUE
|
|
1094
|
+
quic_conn_handle_expiry(VALUE self)
|
|
1095
|
+
{
|
|
1096
|
+
quic_conn_t *c = quic_conn_get(self);
|
|
1097
|
+
c->now = quic_now();
|
|
1098
|
+
int rv = ngtcp2_conn_handle_expiry(c->conn, c->now);
|
|
1099
|
+
quic_conn_raise_pending(c);
|
|
1100
|
+
if (rv != 0) quic_raise_ngtcp2_error(rv);
|
|
1101
|
+
return Qnil;
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
/* QUIC::Stream objects exist only without a stream handler. */
|
|
1105
|
+
static void
|
|
1106
|
+
quic_conn_require_stream_mode(quic_conn_t *c)
|
|
1107
|
+
{
|
|
1108
|
+
if (c->handler) {
|
|
1109
|
+
rb_raise(rb_eQUICErrorInvalidState, "the connection's streams belong to a C stream handler");
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
static VALUE
|
|
1114
|
+
quic_conn_open_bidi_stream(VALUE self)
|
|
1115
|
+
{
|
|
1116
|
+
quic_conn_t *c = quic_conn_get(self);
|
|
1117
|
+
quic_conn_require_stream_mode(c);
|
|
1118
|
+
|
|
1119
|
+
int64_t stream_id;
|
|
1120
|
+
int rv = ngtcp2_conn_open_bidi_stream(c->conn, &stream_id, NULL);
|
|
1121
|
+
if (rv != 0) quic_raise_ngtcp2_error(rv);
|
|
1122
|
+
|
|
1123
|
+
VALUE stream = quic_stream_new(stream_id, self);
|
|
1124
|
+
VALUE streams = rb_ivar_get(self, rb_intern("@streams"));
|
|
1125
|
+
rb_hash_aset(streams, LL2NUM(stream_id), stream);
|
|
1126
|
+
return stream;
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
static VALUE
|
|
1130
|
+
quic_conn_open_uni_stream(VALUE self)
|
|
1131
|
+
{
|
|
1132
|
+
quic_conn_t *c = quic_conn_get(self);
|
|
1133
|
+
quic_conn_require_stream_mode(c);
|
|
1134
|
+
|
|
1135
|
+
int64_t stream_id;
|
|
1136
|
+
int rv = ngtcp2_conn_open_uni_stream(c->conn, &stream_id, NULL);
|
|
1137
|
+
if (rv != 0) quic_raise_ngtcp2_error(rv);
|
|
1138
|
+
|
|
1139
|
+
VALUE stream = quic_stream_new(stream_id, self);
|
|
1140
|
+
VALUE streams = rb_ivar_get(self, rb_intern("@streams"));
|
|
1141
|
+
rb_hash_aset(streams, LL2NUM(stream_id), stream);
|
|
1142
|
+
return stream;
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
/* Send an application CONNECTION_CLOSE (frame type 0x1d) to the peer and
|
|
1146
|
+
transition the underlying ngtcp2_conn to the closing period. One UDP
|
|
1147
|
+
datagram is written to the bound socket; ngtcp2 handles closing-period
|
|
1148
|
+
retransmits via the caller's pump loop. Calling #close on a connection
|
|
1149
|
+
that is already in the closing or draining period is a no-op (= returns
|
|
1150
|
+
nil without doing any I/O). */
|
|
1151
|
+
static VALUE
|
|
1152
|
+
quic_conn_close_m(int argc, VALUE *argv, VALUE self)
|
|
1153
|
+
{
|
|
1154
|
+
VALUE opts = Qnil;
|
|
1155
|
+
rb_scan_args(argc, argv, "0:", &opts);
|
|
1156
|
+
|
|
1157
|
+
uint64_t error_code = 0;
|
|
1158
|
+
const uint8_t *reason_ptr = (const uint8_t *)"";
|
|
1159
|
+
size_t reason_len = 0;
|
|
1160
|
+
|
|
1161
|
+
if (!NIL_P(opts)) {
|
|
1162
|
+
VALUE error_code_v = rb_hash_aref(opts, ID2SYM(rb_intern("error_code")));
|
|
1163
|
+
VALUE reason_v = rb_hash_aref(opts, ID2SYM(rb_intern("reason")));
|
|
1164
|
+
if (!NIL_P(error_code_v)) {
|
|
1165
|
+
error_code = NUM2ULL(error_code_v);
|
|
1166
|
+
}
|
|
1167
|
+
if (!NIL_P(reason_v)) {
|
|
1168
|
+
Check_Type(reason_v, T_STRING);
|
|
1169
|
+
reason_ptr = (const uint8_t *)RSTRING_PTR(reason_v);
|
|
1170
|
+
reason_len = (size_t)RSTRING_LEN(reason_v);
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
quic_conn_t *c = quic_conn_get(self);
|
|
1175
|
+
|
|
1176
|
+
/* Idempotent: a subsequent #close after the conn already entered the
|
|
1177
|
+
closing/draining period does not emit another CONNECTION_CLOSE. */
|
|
1178
|
+
if (ngtcp2_conn_in_closing_period2(c->conn) ||
|
|
1179
|
+
ngtcp2_conn_in_draining_period2(c->conn)) {
|
|
1180
|
+
return Qnil;
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
/* A connection an Endpoint made has no socket: the CONNECTION_CLOSE goes
|
|
1184
|
+
out through Endpoint#write_pkt. Once it has left the Endpoint's table
|
|
1185
|
+
it is finished, and #close does nothing. */
|
|
1186
|
+
if (c->from_endpoint) {
|
|
1187
|
+
if (c->endpoint == NULL) return Qnil;
|
|
1188
|
+
ngtcp2_ccerr ccerr;
|
|
1189
|
+
ngtcp2_ccerr_set_application_error(&ccerr, error_code, reason_len > 0 ? reason_ptr : NULL,
|
|
1190
|
+
reason_len);
|
|
1191
|
+
quic_endpoint_close_conn(c->endpoint, c, &ccerr);
|
|
1192
|
+
return Qnil;
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
VALUE sock = rb_ivar_get(self, rb_intern("@sock"));
|
|
1196
|
+
if (NIL_P(sock)) {
|
|
1197
|
+
rb_raise(rb_eQUICErrorNotBound, "%"PRIsVALUE"#bind(sock) has not been called",
|
|
1198
|
+
rb_obj_class(self));
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
ngtcp2_ccerr ccerr;
|
|
1202
|
+
ngtcp2_ccerr_set_application_error(&ccerr, error_code,
|
|
1203
|
+
reason_len > 0 ? reason_ptr : NULL,
|
|
1204
|
+
reason_len);
|
|
1205
|
+
|
|
1206
|
+
ngtcp2_path_storage path_storage;
|
|
1207
|
+
ngtcp2_path_storage_zero(&path_storage);
|
|
1208
|
+
ngtcp2_pkt_info pi;
|
|
1209
|
+
memset(&pi, 0, sizeof(pi));
|
|
1210
|
+
|
|
1211
|
+
VALUE buffer = rb_str_buf_new(QUIC_WRITE_PKT_BUFLEN);
|
|
1212
|
+
uint8_t *dest = (uint8_t *)RSTRING_PTR(buffer);
|
|
1213
|
+
|
|
1214
|
+
ngtcp2_ssize n = ngtcp2_conn_write_connection_close(
|
|
1215
|
+
c->conn, &path_storage.path, &pi, dest,
|
|
1216
|
+
(size_t)QUIC_WRITE_PKT_BUFLEN, &ccerr, quic_now());
|
|
1217
|
+
|
|
1218
|
+
/* Pre-handshake or otherwise not in a state where CONNECTION_CLOSE can be
|
|
1219
|
+
emitted (ngtcp2 returns NGTCP2_ERR_INVALID_STATE). Spec treats this as
|
|
1220
|
+
best-effort: silently no-op rather than surface an error. The Ruby
|
|
1221
|
+
connection object is effectively dead from here. */
|
|
1222
|
+
if (n == NGTCP2_ERR_INVALID_STATE) {
|
|
1223
|
+
return Qnil;
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
if (n < 0) {
|
|
1227
|
+
rb_str_set_len(buffer, 0);
|
|
1228
|
+
quic_raise_ngtcp2_error((int)n);
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
if (n == 0) {
|
|
1232
|
+
return Qnil;
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
rb_str_set_len(buffer, n);
|
|
1236
|
+
rb_funcall(sock, rb_intern("send"), 2, buffer, INT2NUM(0));
|
|
1237
|
+
return Qnil;
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
/* Why an Endpoint closed this connection; see quic_conn_t.error. Always nil
|
|
1241
|
+
for a connection no Endpoint made. */
|
|
1242
|
+
static VALUE
|
|
1243
|
+
quic_conn_error_m(VALUE self)
|
|
1244
|
+
{
|
|
1245
|
+
return quic_conn_get(self)->error;
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
void
|
|
1249
|
+
quic_conn_define_shared_methods(VALUE klass)
|
|
1250
|
+
{
|
|
1251
|
+
rb_define_method(klass, "write_pkt", quic_conn_write_pkt, -1);
|
|
1252
|
+
rb_define_method(klass, "read_pkt", quic_conn_read_pkt, -1);
|
|
1253
|
+
rb_define_method(klass, "expiry", quic_conn_expiry, 0);
|
|
1254
|
+
rb_define_method(klass, "handle_expiry", quic_conn_handle_expiry, 0);
|
|
1255
|
+
rb_define_method(klass, "handshake_completed?", quic_conn_handshake_completed_p, 0);
|
|
1256
|
+
rb_define_method(klass, "in_closing_period?", quic_conn_in_closing_period_p, 0);
|
|
1257
|
+
rb_define_method(klass, "in_draining_period?", quic_conn_in_draining_period_p, 0);
|
|
1258
|
+
rb_define_method(klass, "open_bidi_stream", quic_conn_open_bidi_stream, 0);
|
|
1259
|
+
rb_define_method(klass, "open_uni_stream", quic_conn_open_uni_stream, 0);
|
|
1260
|
+
rb_define_method(klass, "close", quic_conn_close_m, -1);
|
|
1261
|
+
rb_define_method(klass, "error", quic_conn_error_m, 0);
|
|
1262
|
+
}
|