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