quic 0.0.1 → 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.
@@ -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
+ }