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.
@@ -1,399 +1,28 @@
1
- #include "quic.h"
2
- #include "stream.h"
1
+ #include "connection_common.h"
3
2
 
4
- #include <netinet/in.h>
5
3
  #include <string.h>
6
- #include <sys/socket.h>
7
- #include <time.h>
8
4
 
9
5
  #include <openssl/err.h>
10
6
  #include <openssl/rand.h>
11
7
 
12
- /* Buffer size for #write_pkt. NGTCP2_MAX_UDP_PAYLOAD_SIZE (1200) is the
13
- minimum destlen ngtcp2 accepts. Revisit when PMTUD is enabled (Phase 4+). */
14
- #define QUIC_WRITE_PKT_BUFLEN NGTCP2_MAX_UDP_PAYLOAD_SIZE
15
-
16
- /* picotls sends a key share only for the first entry; the others are offered
17
- in supported_groups and reached via HelloRetryRequest. X25519 is first so
18
- the common case stays 1-RTT. */
19
- static ptls_key_exchange_algorithm_t *quic_key_exchanges[] = {
20
- &ptls_openssl_x25519,
21
- &ptls_openssl_secp256r1,
22
- #if PTLS_OPENSSL_HAVE_SECP384R1
23
- &ptls_openssl_secp384r1,
24
- #endif
25
- NULL,
26
- };
27
-
28
- static ptls_cipher_suite_t *quic_cipher_suites[] = {
29
- &ptls_openssl_aes128gcmsha256,
30
- &ptls_openssl_aes256gcmsha384,
31
- #if PTLS_OPENSSL_HAVE_CHACHA20_POLY1305
32
- &ptls_openssl_chacha20poly1305sha256,
33
- #endif
34
- NULL,
35
- };
36
-
37
8
  /* The system default store is loaded once per process: reading the CA bundle
38
9
  takes several milliseconds. X509_STORE is reference counted and safe to
39
10
  share across verifications; creation runs under the GVL. Never freed. */
40
11
  static X509_STORE *quic_default_store;
41
12
 
42
- static X509_STORE *
43
- quic_get_default_store(void)
44
- {
13
+ static X509_STORE *quic_get_default_store(void) {
45
14
  if (quic_default_store == NULL) {
46
15
  X509_STORE *store = X509_STORE_new();
47
16
  if (store == NULL || X509_STORE_set_default_paths(store) != 1) {
48
17
  X509_STORE_free(store);
49
- rb_raise(rb_eRuntimeError, "failed to load the default certificate store");
18
+ rb_raise(rb_eRuntimeError,
19
+ "failed to load the default certificate store");
50
20
  }
51
21
  quic_default_store = store;
52
22
  }
53
23
  return quic_default_store;
54
24
  }
55
25
 
56
- typedef struct {
57
- ngtcp2_conn *conn;
58
- /* Referenced by cptls.ptls, which only keeps the pointer, so this has to
59
- outlive the ptls_t. */
60
- ptls_context_t tls_ctx;
61
- /* Referenced by tls_ctx.verify_certificate when verify_mode is :peer. Holds
62
- a reference to an X509_STORE, released in quic_client_free. */
63
- ptls_openssl_verify_certificate_t verify_cert;
64
- bool verify_cert_initialized;
65
- /* Filled by quic_override_verify_cb during the handshake so that a failed
66
- read_pkt can raise CertificateVerifyFailed with the X509 error. */
67
- bool verify_failed;
68
- int verify_result; /* X509_V_ERR_*, 0 when the server sent no certificate */
69
- /* The TLS native handle handed to ngtcp2 (&cptls, not cptls.ptls). */
70
- ngtcp2_crypto_picotls_ctx cptls;
71
- /* [0]: QUIC transport params, filled in by ngtcp2; [1]: terminator. */
72
- ptls_raw_extension_t tls_exts[2];
73
- /* ALPN list handed to picotls, which keeps the pointers for the whole
74
- handshake (unlike SSL_set_alpn_protos, which copies). The entries point
75
- into alpn_buf, and both are owned by this struct. */
76
- ptls_iovec_t *alpn;
77
- size_t alpn_count;
78
- uint8_t *alpn_buf;
79
- ngtcp2_cid scid;
80
- ngtcp2_cid dcid;
81
- /* ngtcp2 1.x requires applications to associate an ngtcp2_conn with the
82
- TLS object so that crypto callbacks (e.g. add_handshake_data) can recover
83
- the conn. picotls carries it in the ptls_t's data pointer, set via
84
- ptls_get_data_ptr. The ref must outlive the ptls_t. */
85
- ngtcp2_crypto_conn_ref conn_ref;
86
- /* Back-reference to the QUIC::Connection::Client Ruby object that owns
87
- this struct. ngtcp2 stream callbacks receive a void* user_data equal
88
- to this struct, and they look up @streams via owner. We are stored
89
- INSIDE owner via TypedData_Wrap_Struct, so owner is guaranteed alive
90
- while we exist (no dmark needed). GC.compact may relocate owner, so
91
- quic_client_compact updates this field via rb_gc_location. */
92
- VALUE owner;
93
- } quic_client_t;
94
-
95
- static void
96
- quic_client_free(void *ptr)
97
- {
98
- quic_client_t *c = (quic_client_t *)ptr;
99
- if (c->conn) ngtcp2_conn_del(c->conn);
100
- if (c->cptls.ptls) {
101
- /* Safe even when configure_client_session never ran or failed midway. */
102
- ngtcp2_crypto_picotls_deconfigure_session(&c->cptls);
103
- ptls_free(c->cptls.ptls);
104
- }
105
- /* After ptls_free: the ptls_t references verify_cert through tls_ctx. */
106
- if (c->verify_cert_initialized) ptls_openssl_dispose_verify_certificate(&c->verify_cert);
107
- xfree(c->alpn);
108
- xfree(c->alpn_buf);
109
- xfree(c);
110
- }
111
-
112
- static size_t
113
- quic_client_size(const void *ptr)
114
- {
115
- (void)ptr;
116
- return sizeof(quic_client_t);
117
- }
118
-
119
- /* GC.compact may relocate the owner Client object. Follow it so the
120
- void* user_data ngtcp2 callbacks receive (which equals this struct,
121
- stored inside owner) keeps resolving @streams / @accept_queue. The
122
- raw ngtcp2/picotls pointers are outside Ruby's heap and are left alone. */
123
- static void
124
- quic_client_compact(void *ptr)
125
- {
126
- quic_client_t *c = (quic_client_t *)ptr;
127
- c->owner = rb_gc_location(c->owner);
128
- }
129
-
130
- static const rb_data_type_t quic_client_data_type = {
131
- "QUIC::Connection::Client",
132
- {NULL, quic_client_free, quic_client_size, quic_client_compact,},
133
- NULL, NULL,
134
- RUBY_TYPED_FREE_IMMEDIATELY,
135
- };
136
-
137
- static VALUE
138
- quic_client_alloc(VALUE klass)
139
- {
140
- quic_client_t *c = ALLOC(quic_client_t);
141
- memset(c, 0, sizeof(*c));
142
- return TypedData_Wrap_Struct(klass, &quic_client_data_type, c);
143
- }
144
-
145
- ngtcp2_conn *
146
- quic_client_conn(VALUE client_v)
147
- {
148
- quic_client_t *c;
149
- TypedData_Get_Struct(client_v, quic_client_t, &quic_client_data_type, c);
150
- return c->conn;
151
- }
152
-
153
- static 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
- static int
169
- quic_get_new_connection_id_cb(ngtcp2_conn *conn, ngtcp2_cid *cid,
170
- uint8_t *token, size_t cidlen, void *user_data)
171
- {
172
- (void)conn;
173
- (void)user_data;
174
- if (RAND_bytes(cid->data, (int)cidlen) != 1) {
175
- return NGTCP2_ERR_CALLBACK_FAILURE;
176
- }
177
- cid->datalen = cidlen;
178
- if (RAND_bytes(token, NGTCP2_STATELESS_RESET_TOKENLEN) != 1) {
179
- return NGTCP2_ERR_CALLBACK_FAILURE;
180
- }
181
- return 0;
182
- }
183
-
184
- static ngtcp2_conn *
185
- quic_client_get_conn(ngtcp2_crypto_conn_ref *conn_ref)
186
- {
187
- return ((quic_client_t *)conn_ref->user_data)->conn;
188
- }
189
-
190
- static void
191
- quic_require_binary(VALUE str, const char *name)
192
- {
193
- Check_Type(str, T_STRING);
194
- if (rb_enc_get_index(str) != rb_ascii8bit_encindex()) {
195
- rb_raise(rb_eArgError, "%s must be ASCII-8BIT (binary) encoding", name);
196
- }
197
- }
198
-
199
- static uint64_t
200
- quic_get_uint64_field(VALUE obj, const char *name)
201
- {
202
- VALUE v = rb_funcall(obj, rb_intern(name), 0);
203
- return (uint64_t)NUM2ULL(v);
204
- }
205
-
206
- static ngtcp2_cc_algo
207
- quic_cc_algo_from_sym(VALUE sym)
208
- {
209
- Check_Type(sym, T_SYMBOL);
210
- ID id = SYM2ID(sym);
211
- if (id == rb_intern("reno")) return NGTCP2_CC_ALGO_RENO;
212
- if (id == rb_intern("cubic")) return NGTCP2_CC_ALGO_CUBIC;
213
- if (id == rb_intern("bbr")) return NGTCP2_CC_ALGO_BBR;
214
- rb_raise(rb_eArgError, "unknown cc_algo: %"PRIsVALUE" (expected :reno, :cubic, or :bbr)", sym);
215
- }
216
-
217
- static void
218
- quic_fill_transport_params(ngtcp2_transport_params *params, VALUE tp)
219
- {
220
- ngtcp2_transport_params_default(params);
221
- params->initial_max_stream_data_bidi_local =
222
- quic_get_uint64_field(tp, "initial_max_stream_data_bidi_local");
223
- params->initial_max_stream_data_bidi_remote =
224
- quic_get_uint64_field(tp, "initial_max_stream_data_bidi_remote");
225
- params->initial_max_stream_data_uni =
226
- quic_get_uint64_field(tp, "initial_max_stream_data_uni");
227
- params->initial_max_data =
228
- quic_get_uint64_field(tp, "initial_max_data");
229
- params->initial_max_streams_bidi =
230
- quic_get_uint64_field(tp, "initial_max_streams_bidi");
231
- params->initial_max_streams_uni =
232
- quic_get_uint64_field(tp, "initial_max_streams_uni");
233
- params->max_idle_timeout =
234
- (ngtcp2_duration)quic_get_uint64_field(tp, "max_idle_timeout");
235
- params->active_connection_id_limit =
236
- quic_get_uint64_field(tp, "active_connection_id_limit");
237
- }
238
-
239
- static void
240
- quic_fill_settings(ngtcp2_settings *settings, VALUE st)
241
- {
242
- ngtcp2_settings_default(settings);
243
- settings->initial_ts = quic_now();
244
- settings->cc_algo = quic_cc_algo_from_sym(rb_funcall(st, rb_intern("cc_algo"), 0));
245
- settings->initial_rtt =
246
- (ngtcp2_duration)quic_get_uint64_field(st, "initial_rtt");
247
- settings->max_window =
248
- quic_get_uint64_field(st, "max_window");
249
- settings->max_stream_window =
250
- quic_get_uint64_field(st, "max_stream_window");
251
- settings->handshake_timeout =
252
- (ngtcp2_duration)quic_get_uint64_field(st, "handshake_timeout");
253
- VALUE no_pmtud = rb_funcall(st, rb_intern("no_pmtud"), 0);
254
- settings->no_pmtud = RTEST(no_pmtud) ? 1 : 0;
255
- }
256
-
257
- /* Shared helper: look up the QUIC::Stream registered for stream_id in the
258
- owner Client's @streams Hash. Returns Qnil if not found (which can happen
259
- if the stream was already removed by a previous stream_close callback). */
260
- static VALUE
261
- quic_client_lookup_stream(quic_client_t *c, int64_t stream_id)
262
- {
263
- VALUE streams = rb_ivar_get(c->owner, rb_intern("@streams"));
264
- return rb_hash_aref(streams, LL2NUM(stream_id));
265
- }
266
-
267
- /* ngtcp2 stream callbacks. Each returns 0 on success or
268
- NGTCP2_ERR_CALLBACK_FAILURE on Ruby-side errors. We assume rb_str_buf_cat /
269
- rb_ary_* won't raise in normal operation (allocation failures aside, which
270
- would abort the process anyway), so we don't wrap in rb_protect for now. */
271
-
272
- static int
273
- quic_stream_open_cb(ngtcp2_conn *conn, int64_t stream_id, void *user_data)
274
- {
275
- (void)conn;
276
- quic_client_t *c = (quic_client_t *)user_data;
277
- VALUE streams = rb_ivar_get(c->owner, rb_intern("@streams"));
278
- if (!NIL_P(rb_hash_aref(streams, LL2NUM(stream_id)))) {
279
- return 0; /* already known (client-initiated) */
280
- }
281
- VALUE stream = quic_stream_new(stream_id, c->owner);
282
- rb_hash_aset(streams, LL2NUM(stream_id), stream);
283
- return 0;
284
- }
285
-
286
- static int
287
- quic_recv_stream_data_cb(ngtcp2_conn *conn, uint32_t flags, int64_t stream_id,
288
- uint64_t offset, const uint8_t *data, size_t datalen,
289
- void *user_data, void *stream_user_data)
290
- {
291
- (void)conn;
292
- (void)offset;
293
- (void)stream_user_data;
294
- quic_client_t *c = (quic_client_t *)user_data;
295
- VALUE stream = quic_client_lookup_stream(c, stream_id);
296
- if (NIL_P(stream)) return 0;
297
-
298
- quic_stream_t *s;
299
- TypedData_Get_Struct(stream, quic_stream_t, &quic_stream_data_type, s);
300
-
301
- /* Surface peer-initiated (server) streams to #accept_stream. For a client
302
- connection the low stream-id bit marks the initiator: 1 == server. Push
303
- once, on first data arrival, then mark so later data does not re-enqueue. */
304
- if (!s->accept_queued && (stream_id & 0x01)) {
305
- VALUE accept_queue = rb_ivar_get(c->owner, rb_intern("@accept_queue"));
306
- rb_ary_push(accept_queue, stream);
307
- s->accept_queued = true;
308
- }
309
-
310
- if (datalen > 0) {
311
- VALUE recv_buffer = rb_ivar_get(stream, rb_intern("@recv_buffer"));
312
- rb_str_buf_cat(recv_buffer, (const char *)data, (long)datalen);
313
- }
314
- if (flags & NGTCP2_STREAM_DATA_FLAG_FIN) {
315
- s->fin_received = true;
316
- }
317
- return 0;
318
- }
319
-
320
- static int
321
- quic_acked_stream_data_offset_cb(ngtcp2_conn *conn, int64_t stream_id,
322
- uint64_t offset, uint64_t datalen,
323
- void *user_data, void *stream_user_data)
324
- {
325
- (void)conn;
326
- (void)offset;
327
- (void)stream_user_data;
328
- quic_client_t *c = (quic_client_t *)user_data;
329
- VALUE stream = quic_client_lookup_stream(c, stream_id);
330
- if (NIL_P(stream)) return 0;
331
-
332
- quic_stream_t *s;
333
- TypedData_Get_Struct(stream, quic_stream_t, &quic_stream_data_type, s);
334
- s->acked_offset += datalen;
335
-
336
- /* Shift any head Chunks of @pending_chunks whose end-offset is fully
337
- covered by acked_offset. Each Chunk's start-offset in the stream is
338
- pending_shifted (after previous shifts); its end-offset is
339
- pending_shifted + RSTRING_LEN(head). */
340
- VALUE pending = rb_ivar_get(stream, rb_intern("@pending_chunks"));
341
- while (RARRAY_LEN(pending) > 0) {
342
- VALUE head = RARRAY_AREF(pending, 0);
343
- uint64_t head_end = s->pending_shifted + (uint64_t)RSTRING_LEN(head);
344
- if (head_end <= s->acked_offset) {
345
- rb_ary_shift(pending);
346
- s->pending_shifted = head_end;
347
- } else {
348
- break;
349
- }
350
- }
351
- return 0;
352
- }
353
-
354
- static int
355
- quic_stream_close_cb(ngtcp2_conn *conn, uint32_t flags, int64_t stream_id,
356
- uint64_t app_error_code, void *user_data,
357
- void *stream_user_data)
358
- {
359
- (void)conn;
360
- (void)stream_user_data;
361
- quic_client_t *c = (quic_client_t *)user_data;
362
- VALUE streams = rb_ivar_get(c->owner, rb_intern("@streams"));
363
- VALUE stream = rb_hash_aref(streams, LL2NUM(stream_id));
364
- if (NIL_P(stream)) return 0;
365
-
366
- quic_stream_t *s;
367
- TypedData_Get_Struct(stream, quic_stream_t, &quic_stream_data_type, s);
368
- s->closed = true;
369
- if (flags & NGTCP2_STREAM_CLOSE_FLAG_APP_ERROR_CODE_SET) {
370
- s->close_has_app_error_code = true;
371
- s->close_app_error_code = app_error_code;
372
- }
373
- rb_hash_delete(streams, LL2NUM(stream_id));
374
- return 0;
375
- }
376
-
377
- static int
378
- quic_stream_reset_cb(ngtcp2_conn *conn, int64_t stream_id, uint64_t final_size,
379
- uint64_t app_error_code, void *user_data,
380
- void *stream_user_data)
381
- {
382
- (void)conn;
383
- (void)final_size;
384
- (void)stream_user_data;
385
- quic_client_t *c = (quic_client_t *)user_data;
386
- VALUE stream = quic_client_lookup_stream(c, stream_id);
387
- if (NIL_P(stream)) return 0;
388
-
389
- quic_stream_t *s;
390
- TypedData_Get_Struct(stream, quic_stream_t, &quic_stream_data_type, s);
391
- s->reset = true;
392
- s->close_app_error_code = app_error_code;
393
- s->close_has_app_error_code = true;
394
- return 0;
395
- }
396
-
397
26
  /* Record the X509 verification result so read_pkt can raise a descriptive
398
27
  error. The verdict itself is left to picotls: ret is returned unchanged.
399
28
  Runs synchronously inside read_pkt with the GVL held; it only writes to the
@@ -401,13 +30,12 @@ quic_stream_reset_cb(ngtcp2_conn *conn, int64_t stream_id, uint64_t final_size,
401
30
  static int
402
31
  quic_override_verify_cb(ptls_openssl_override_verify_certificate_t *self,
403
32
  ptls_t *tls, int ret, int ossl_ret, X509 *cert,
404
- STACK_OF(X509) *chain)
405
- {
33
+ STACK_OF(X509) * chain) {
406
34
  (void)self;
407
35
  (void)cert;
408
36
  (void)chain;
409
37
  ngtcp2_crypto_conn_ref *ref = *ptls_get_data_ptr(tls);
410
- quic_client_t *c = (quic_client_t *)ref->user_data;
38
+ quic_conn_t *c = (quic_conn_t *)ref->user_data;
411
39
  if (ret != 0) {
412
40
  c->verify_failed = true;
413
41
  c->verify_result = ossl_ret;
@@ -416,58 +44,40 @@ quic_override_verify_cb(ptls_openssl_override_verify_certificate_t *self,
416
44
  }
417
45
 
418
46
  static ptls_openssl_override_verify_certificate_t quic_override_verify = {
419
- quic_override_verify_cb,
47
+ quic_override_verify_cb,
420
48
  };
421
49
 
422
- /* Validate a ca_file / ca_path setting and return it as a String, or Qnil.
423
- Accepts a String or anything responding to #to_path. Every misconfiguration
424
- of the verification settings raises ArgumentError, including a wrong type. */
425
- static VALUE
426
- quic_verify_path_value(VALUE value, const char *name)
427
- {
428
- if (NIL_P(value)) return Qnil;
429
-
430
- VALUE path = value;
431
- if (!RB_TYPE_P(path, T_STRING)) {
432
- if (rb_respond_to(path, rb_intern("to_path"))) {
433
- path = rb_funcall(path, rb_intern("to_path"), 0);
434
- }
435
- if (!RB_TYPE_P(path, T_STRING)) {
436
- rb_raise(rb_eArgError, "%s must be a String or Pathname (got %"PRIsVALUE")",
437
- name, rb_obj_class(value));
438
- }
439
- }
440
- /* Raises ArgumentError when the path contains a NUL byte. */
441
- StringValueCStr(path);
442
- return path;
443
- }
444
-
445
50
  /* Install picotls's certificate verifier on c->tls_ctx according to the
446
51
  verify_mode / ca_file / ca_path settings. Leaves verify_certificate NULL
447
52
  for verify_mode :none, which skips verification altogether. */
448
- static void
449
- quic_client_setup_verify(quic_client_t *c, VALUE settings_v, VALUE server_name)
450
- {
53
+ static void quic_client_setup_verify(quic_conn_t *c, VALUE settings_v,
54
+ VALUE server_name) {
451
55
  VALUE mode = rb_funcall(settings_v, rb_intern("verify_mode"), 0);
452
56
  /* ca_file and ca_path are not even type-checked under :none. */
453
- if (mode == ID2SYM(rb_intern("none"))) return;
57
+ if (mode == ID2SYM(rb_intern("none")))
58
+ return;
454
59
  if (mode != ID2SYM(rb_intern("peer"))) {
455
- rb_raise(rb_eArgError, "verify_mode must be :peer or :none (got %+"PRIsVALUE")", mode);
60
+ rb_raise(rb_eArgError,
61
+ "verify_mode must be :peer or :none (got %+" PRIsVALUE ")", mode);
456
62
  }
457
63
 
458
- VALUE ca_file = quic_verify_path_value(rb_funcall(settings_v, rb_intern("ca_file"), 0), "ca_file");
459
- VALUE ca_path = quic_verify_path_value(rb_funcall(settings_v, rb_intern("ca_path"), 0), "ca_path");
64
+ VALUE ca_file = quic_path_value(
65
+ rb_funcall(settings_v, rb_intern("ca_file"), 0), "ca_file");
66
+ VALUE ca_path = quic_path_value(
67
+ rb_funcall(settings_v, rb_intern("ca_path"), 0), "ca_path");
460
68
 
461
69
  /* An empty name makes OpenSSL clear its host list and skip the name check,
462
70
  and both SNI and the check would silently stop at an embedded NUL. */
463
71
  if (RSTRING_LEN(server_name) == 0 ||
464
- memchr(RSTRING_PTR(server_name), '\0', (size_t)RSTRING_LEN(server_name)) != NULL) {
465
- rb_raise(rb_eArgError,
466
- "server_name must be a non-empty string without NUL bytes when verify_mode is :peer");
72
+ memchr(RSTRING_PTR(server_name), '\0',
73
+ (size_t)RSTRING_LEN(server_name)) != NULL) {
74
+ rb_raise(rb_eArgError, "server_name must be a non-empty string without NUL "
75
+ "bytes when verify_mode is :peer");
467
76
  }
468
77
  /* X509_LOOKUP_hash_dir does not check that the directory exists. */
469
- if (!NIL_P(ca_path) && !RTEST(rb_funcall(rb_cFile, rb_intern("directory?"), 1, ca_path))) {
470
- rb_raise(rb_eArgError, "ca_path is not a directory: %"PRIsVALUE, ca_path);
78
+ if (!NIL_P(ca_path) &&
79
+ !RTEST(rb_funcall(rb_cFile, rb_intern("directory?"), 1, ca_path))) {
80
+ rb_raise(rb_eArgError, "ca_path is not a directory: %" PRIsVALUE, ca_path);
471
81
  }
472
82
 
473
83
  X509_STORE *store;
@@ -478,17 +88,17 @@ quic_client_setup_verify(quic_client_t *c, VALUE settings_v, VALUE server_name)
478
88
  if (store == NULL) {
479
89
  rb_raise(rb_eRuntimeError, "X509_STORE_new failed");
480
90
  }
481
- if (X509_STORE_load_locations(store,
482
- NIL_P(ca_file) ? NULL : RSTRING_PTR(ca_file),
483
- NIL_P(ca_path) ? NULL : RSTRING_PTR(ca_path)) != 1) {
91
+ if (X509_STORE_load_locations(
92
+ store, NIL_P(ca_file) ? NULL : RSTRING_PTR(ca_file),
93
+ NIL_P(ca_path) ? NULL : RSTRING_PTR(ca_path)) != 1) {
484
94
  X509_STORE_free(store);
485
95
  /* libcrypto is shared with Ruby's openssl extension; do not leave our
486
96
  failure on its error queue. */
487
97
  ERR_clear_error();
488
98
  if (NIL_P(ca_file)) {
489
- rb_raise(rb_eArgError, "failed to load ca_path: %"PRIsVALUE, ca_path);
99
+ rb_raise(rb_eArgError, "failed to load ca_path: %" PRIsVALUE, ca_path);
490
100
  }
491
- rb_raise(rb_eArgError, "failed to load ca_file: %"PRIsVALUE, ca_file);
101
+ rb_raise(rb_eArgError, "failed to load ca_file: %" PRIsVALUE, ca_file);
492
102
  }
493
103
  } else {
494
104
  store = quic_get_default_store();
@@ -496,7 +106,8 @@ quic_client_setup_verify(quic_client_t *c, VALUE settings_v, VALUE server_name)
496
106
 
497
107
  /* init takes its own reference on the store, so ours can go right away. */
498
108
  int rv = ptls_openssl_init_verify_certificate(&c->verify_cert, store);
499
- if (own_store) X509_STORE_free(store);
109
+ if (own_store)
110
+ X509_STORE_free(store);
500
111
  if (rv != 0) {
501
112
  rb_raise(rb_eRuntimeError, "ptls_openssl_init_verify_certificate failed");
502
113
  }
@@ -505,95 +116,72 @@ quic_client_setup_verify(quic_client_t *c, VALUE settings_v, VALUE server_name)
505
116
  c->tls_ctx.verify_certificate = &c->verify_cert.super;
506
117
  }
507
118
 
508
- static VALUE
509
- quic_client_open(int argc, VALUE *argv, VALUE klass)
510
- {
119
+ static VALUE quic_client_open(int argc, VALUE *argv, VALUE klass) {
511
120
  VALUE opts = Qnil;
512
121
  rb_scan_args(argc, argv, "0:", &opts);
513
122
  if (NIL_P(opts)) {
514
- rb_raise(rb_eArgError,
515
- "missing keywords: local_sockaddr, remote_sockaddr, server_name, transport_params, settings");
123
+ rb_raise(rb_eArgError, "missing keywords: local_sockaddr, remote_sockaddr, "
124
+ "server_name, transport_params, settings");
516
125
  }
517
126
 
518
- VALUE local_sockaddr = rb_hash_aref(opts, ID2SYM(rb_intern("local_sockaddr")));
519
- VALUE remote_sockaddr = rb_hash_aref(opts, ID2SYM(rb_intern("remote_sockaddr")));
520
- VALUE server_name = rb_hash_aref(opts, ID2SYM(rb_intern("server_name")));
521
- VALUE transport_params = rb_hash_aref(opts, ID2SYM(rb_intern("transport_params")));
522
- VALUE settings_v = rb_hash_aref(opts, ID2SYM(rb_intern("settings")));
127
+ VALUE local_sockaddr =
128
+ rb_hash_aref(opts, ID2SYM(rb_intern("local_sockaddr")));
129
+ VALUE remote_sockaddr =
130
+ rb_hash_aref(opts, ID2SYM(rb_intern("remote_sockaddr")));
131
+ VALUE server_name = rb_hash_aref(opts, ID2SYM(rb_intern("server_name")));
132
+ VALUE transport_params =
133
+ rb_hash_aref(opts, ID2SYM(rb_intern("transport_params")));
134
+ VALUE settings_v = rb_hash_aref(opts, ID2SYM(rb_intern("settings")));
523
135
 
524
136
  if (NIL_P(local_sockaddr) || NIL_P(remote_sockaddr) || NIL_P(server_name) ||
525
137
  NIL_P(transport_params) || NIL_P(settings_v)) {
526
138
  rb_raise(rb_eArgError,
527
- "all keywords required: local_sockaddr, remote_sockaddr, server_name, transport_params, settings");
139
+ "all keywords required: local_sockaddr, remote_sockaddr, "
140
+ "server_name, transport_params, settings");
528
141
  }
529
142
 
530
- quic_require_binary(local_sockaddr, "local_sockaddr");
531
- quic_require_binary(remote_sockaddr, "remote_sockaddr");
143
+ quic_require_sockaddr(local_sockaddr, "local_sockaddr");
144
+ quic_require_sockaddr(remote_sockaddr, "remote_sockaddr");
532
145
  Check_Type(server_name, T_STRING);
533
146
 
534
- VALUE self = quic_client_alloc(klass);
147
+ VALUE self = quic_conn_alloc(klass);
535
148
  rb_ivar_set(self, rb_intern("@server_name"), server_name);
536
149
 
537
- quic_client_t *c;
538
- TypedData_Get_Struct(self, quic_client_t, &quic_client_data_type, c);
150
+ quic_conn_t *c = quic_conn_get(self);
539
151
  c->owner = self;
152
+ c->is_server = false;
540
153
 
541
- /* Copy the ALPN list into memory owned by c: picotls keeps the pointers
542
- for the whole handshake, so ALLOCA_N would not survive. An empty list
543
- means no ALPN extension, matching the old skip-SSL_set_alpn_protos path. */
544
- VALUE alpn_ary = rb_funcall(settings_v, rb_intern("alpn"), 0);
545
- Check_Type(alpn_ary, T_ARRAY);
546
- long n_alpn = RARRAY_LEN(alpn_ary);
547
- if (n_alpn > 0) {
548
- size_t total = 0;
549
- for (long i = 0; i < n_alpn; i++) {
550
- VALUE entry = RARRAY_AREF(alpn_ary, i);
551
- Check_Type(entry, T_STRING);
552
- long len = RSTRING_LEN(entry);
553
- if (len < 1 || len > 255) {
554
- rb_raise(rb_eArgError, "alpn entry must be 1-255 bytes (got %ld)", len);
555
- }
556
- total += (size_t)len;
557
- }
558
- c->alpn_buf = ALLOC_N(uint8_t, total);
559
- c->alpn = ALLOC_N(ptls_iovec_t, n_alpn);
560
- uint8_t *p = c->alpn_buf;
561
- for (long i = 0; i < n_alpn; i++) {
562
- VALUE entry = RARRAY_AREF(alpn_ary, i);
563
- size_t len = (size_t)RSTRING_LEN(entry);
564
- memcpy(p, RSTRING_PTR(entry), len);
565
- c->alpn[i] = ptls_iovec_init(p, len);
566
- p += len;
567
- }
568
- c->alpn_count = (size_t)n_alpn;
569
- }
154
+ /* An empty list means no ALPN extension, matching the old
155
+ skip-SSL_set_alpn_protos path. */
156
+ quic_conn_copy_alpn(c, settings_v);
570
157
 
571
- /* quic_client_alloc already zeroed the struct, but calling ctx_init keeps
158
+ /* quic_conn_alloc already zeroed the struct, but calling ctx_init keeps
572
159
  the ngtcp2-side initialization explicit and the TLS setup in one place. */
573
160
  ngtcp2_crypto_picotls_ctx_init(&c->cptls);
574
161
  c->tls_ctx = (ptls_context_t){
575
- .random_bytes = ptls_openssl_random_bytes,
576
- .get_time = &ptls_get_time,
577
- .key_exchanges = quic_key_exchanges,
578
- .cipher_suites = quic_cipher_suites,
579
- /* No session resumption yet, so this has no effect today. Keep it so that
580
- when resumption is added, PSK-only (non-forward-secret) resumption is
581
- refused rather than silently accepted. */
582
- .require_dhe_on_psk = 1,
162
+ .random_bytes = ptls_openssl_random_bytes,
163
+ .get_time = &ptls_get_time,
164
+ .key_exchanges = quic_key_exchanges,
165
+ .cipher_suites = quic_cipher_suites,
166
+ /* No session resumption yet, so this has no effect today. Keep it so that
167
+ when resumption is added, PSK-only (non-forward-secret) resumption is
168
+ refused rather than silently accepted. */
169
+ .require_dhe_on_psk = 1,
583
170
  };
584
171
  if (ngtcp2_crypto_picotls_configure_client_context(&c->tls_ctx) != 0) {
585
- rb_raise(rb_eRuntimeError, "ngtcp2_crypto_picotls_configure_client_context failed");
172
+ rb_raise(rb_eRuntimeError,
173
+ "ngtcp2_crypto_picotls_configure_client_context failed");
586
174
  }
587
175
 
588
176
  /* ptls_client_new keeps a pointer to tls_ctx, so the verifier has to be in
589
177
  place before it runs. */
590
178
  quic_client_setup_verify(c, settings_v, server_name);
591
179
 
592
- c->conn_ref.get_conn = quic_client_get_conn;
180
+ c->conn_ref.get_conn = quic_conn_get_conn;
593
181
  c->conn_ref.user_data = c;
594
182
 
595
- c->scid.datalen = 8;
596
- if (RAND_bytes(c->scid.data, 8) != 1) {
183
+ c->scid.datalen = QUIC_SCID_LEN;
184
+ if (RAND_bytes(c->scid.data, QUIC_SCID_LEN) != 1) {
597
185
  rb_raise(rb_eRuntimeError, "RAND_bytes(scid) failed");
598
186
  }
599
187
  c->dcid.datalen = 8;
@@ -602,25 +190,9 @@ quic_client_open(int argc, VALUE *argv, VALUE klass)
602
190
  }
603
191
 
604
192
  ngtcp2_callbacks callbacks;
605
- memset(&callbacks, 0, sizeof(callbacks));
193
+ quic_conn_fill_common_callbacks(&callbacks);
606
194
  callbacks.client_initial = ngtcp2_crypto_client_initial_cb;
607
- callbacks.recv_crypto_data = ngtcp2_crypto_recv_crypto_data_cb;
608
- callbacks.encrypt = ngtcp2_crypto_encrypt_cb;
609
- callbacks.decrypt = ngtcp2_crypto_decrypt_cb;
610
- callbacks.hp_mask = ngtcp2_crypto_hp_mask_cb;
611
195
  callbacks.recv_retry = ngtcp2_crypto_recv_retry_cb;
612
- callbacks.update_key = ngtcp2_crypto_update_key_cb;
613
- callbacks.delete_crypto_aead_ctx = ngtcp2_crypto_delete_crypto_aead_ctx_cb;
614
- callbacks.delete_crypto_cipher_ctx = ngtcp2_crypto_delete_crypto_cipher_ctx_cb;
615
- callbacks.get_path_challenge_data = ngtcp2_crypto_get_path_challenge_data_cb;
616
- callbacks.version_negotiation = ngtcp2_crypto_version_negotiation_cb;
617
- callbacks.rand = quic_rand_cb;
618
- callbacks.get_new_connection_id = quic_get_new_connection_id_cb;
619
- callbacks.stream_open = quic_stream_open_cb;
620
- callbacks.recv_stream_data = quic_recv_stream_data_cb;
621
- callbacks.acked_stream_data_offset = quic_acked_stream_data_offset_cb;
622
- callbacks.stream_close = quic_stream_close_cb;
623
- callbacks.stream_reset = quic_stream_reset_cb;
624
196
 
625
197
  ngtcp2_settings settings;
626
198
  quic_fill_settings(&settings, settings_v);
@@ -628,16 +200,23 @@ quic_client_open(int argc, VALUE *argv, VALUE klass)
628
200
  ngtcp2_transport_params params;
629
201
  quic_fill_transport_params(&params, transport_params);
630
202
 
203
+ /* Opened once the caller's values have all been read, so that a bad one
204
+ leaves no file behind. Every raise below closes it first. */
205
+ quic_conn_open_qlog(c, settings_v, &settings);
206
+
631
207
  ngtcp2_path path = {
632
- {(struct sockaddr *)RSTRING_PTR(local_sockaddr), (ngtcp2_socklen)RSTRING_LEN(local_sockaddr)},
633
- {(struct sockaddr *)RSTRING_PTR(remote_sockaddr), (ngtcp2_socklen)RSTRING_LEN(remote_sockaddr)},
634
- NULL,
208
+ {(struct sockaddr *)RSTRING_PTR(local_sockaddr),
209
+ (ngtcp2_socklen)RSTRING_LEN(local_sockaddr)},
210
+ {(struct sockaddr *)RSTRING_PTR(remote_sockaddr),
211
+ (ngtcp2_socklen)RSTRING_LEN(remote_sockaddr)},
212
+ NULL,
635
213
  };
636
214
 
637
215
  int rv = ngtcp2_conn_client_new(&c->conn, &c->dcid, &c->scid, &path,
638
- NGTCP2_PROTO_VER_V1, &callbacks,
639
- &settings, &params, NULL, c);
216
+ NGTCP2_PROTO_VER_V1, &callbacks, &settings,
217
+ &params, NULL, c);
640
218
  if (rv != 0) {
219
+ quic_conn_close_qlog(c);
641
220
  quic_raise_ngtcp2_error(rv);
642
221
  }
643
222
 
@@ -645,6 +224,7 @@ quic_client_open(int argc, VALUE *argv, VALUE klass)
645
224
  transport parameters from the conn, so it needs the conn to exist. */
646
225
  c->cptls.ptls = ptls_client_new(&c->tls_ctx);
647
226
  if (c->cptls.ptls == NULL) {
227
+ quic_conn_close_qlog(c);
648
228
  rb_raise(rb_eRuntimeError, "ptls_client_new failed");
649
229
  }
650
230
  /* Let ngtcp2's crypto callbacks (add_handshake_data,
@@ -656,13 +236,17 @@ quic_client_open(int argc, VALUE *argv, VALUE klass)
656
236
  c->tls_exts[1].type = UINT16_MAX;
657
237
  c->cptls.handshake_properties.additional_extensions = c->tls_exts;
658
238
  if (ngtcp2_crypto_picotls_configure_client_session(&c->cptls, c->conn) != 0) {
659
- rb_raise(rb_eRuntimeError, "ngtcp2_crypto_picotls_configure_client_session failed");
239
+ quic_conn_close_qlog(c);
240
+ rb_raise(rb_eRuntimeError,
241
+ "ngtcp2_crypto_picotls_configure_client_session failed");
660
242
  }
661
243
  c->cptls.handshake_properties.client.negotiated_protocols.list = c->alpn;
662
- c->cptls.handshake_properties.client.negotiated_protocols.count = c->alpn_count;
244
+ c->cptls.handshake_properties.client.negotiated_protocols.count =
245
+ c->alpn_count;
663
246
  /* ptls_set_server_name copies the name. */
664
247
  if (ptls_set_server_name(c->cptls.ptls, RSTRING_PTR(server_name),
665
248
  (size_t)RSTRING_LEN(server_name)) != 0) {
249
+ quic_conn_close_qlog(c);
666
250
  rb_raise(rb_eRuntimeError, "ptls_set_server_name failed");
667
251
  }
668
252
 
@@ -673,441 +257,18 @@ quic_client_open(int argc, VALUE *argv, VALUE klass)
673
257
  callback. Entries are removed in the stream_close callback. */
674
258
  rb_ivar_set(self, rb_intern("@streams"), rb_hash_new());
675
259
 
676
- /* FIFO of peer-initiated (server) streams awaiting #accept_stream. The
677
- recv_stream_data callback pushes each new server stream here once. */
260
+ /* FIFO of peer-initiated streams awaiting #accept_stream. The
261
+ recv_stream_data callback pushes each new peer stream here once. */
678
262
  rb_ivar_set(self, rb_intern("@accept_queue"), rb_ary_new());
679
263
 
680
264
  return self;
681
265
  }
682
266
 
683
- /* Find the first stream in @streams that has either pending bytes or a
684
- pending FIN to flush. Phase 4 minimum: linear scan, take the first match
685
- (no round-robin). Sets *out_datav to the byte slice to send, *out_datavcnt
686
- to 0 or 1, *out_flags to NGTCP2_WRITE_STREAM_FLAG_FIN when applicable.
687
- Returns the stream's Ruby VALUE (or Qnil if no candidate). */
688
- static VALUE
689
- quic_client_pick_send_stream(VALUE self, ngtcp2_vec *out_datav,
690
- size_t *out_datavcnt, uint32_t *out_flags,
691
- int64_t *out_stream_id)
692
- {
693
- VALUE streams = rb_ivar_get(self, rb_intern("@streams"));
694
- VALUE stream_values = rb_funcall(streams, rb_intern("values"), 0);
695
-
696
- for (long i = 0; i < RARRAY_LEN(stream_values); i++) {
697
- VALUE st = RARRAY_AREF(stream_values, i);
698
- quic_stream_t *s;
699
- TypedData_Get_Struct(st, quic_stream_t, &quic_stream_data_type, s);
700
- if (s->fin_flushed) continue;
701
-
702
- VALUE pending = rb_ivar_get(st, rb_intern("@pending_chunks"));
703
- uint64_t position = s->sent_offset - s->pending_shifted;
704
- long chunks_count = RARRAY_LEN(pending);
705
-
706
- /* Walk pending Chunks to find the one containing the next unsent byte. */
707
- long chunk_idx = -1;
708
- long offset_in_chunk = 0;
709
- for (long j = 0; j < chunks_count; j++) {
710
- VALUE chunk = RARRAY_AREF(pending, j);
711
- long clen = RSTRING_LEN(chunk);
712
- if (position < (uint64_t)clen) {
713
- chunk_idx = j;
714
- offset_in_chunk = (long)position;
715
- break;
716
- }
717
- position -= (uint64_t)clen;
718
- }
719
-
720
- if (chunk_idx >= 0) {
721
- VALUE chunk = RARRAY_AREF(pending, chunk_idx);
722
- out_datav->base = (uint8_t *)RSTRING_PTR(chunk) + offset_in_chunk;
723
- out_datav->len = (size_t)(RSTRING_LEN(chunk) - offset_in_chunk);
724
- *out_datavcnt = 1;
725
- *out_stream_id = s->stream_id;
726
- /* Attach FIN if this is the LAST chunk and we're going to send all
727
- remaining bytes of it (ngtcp2 may encode less, in which case FIN
728
- won't actually be flushed and we'll retry next call). */
729
- *out_flags = (s->fin_sent && chunk_idx == chunks_count - 1)
730
- ? NGTCP2_WRITE_STREAM_FLAG_FIN
731
- : 0;
732
- return st;
733
- }
734
-
735
- if (s->fin_sent && !s->fin_flushed) {
736
- /* No data to send but a pending FIN-only frame. */
737
- out_datav->base = NULL;
738
- out_datav->len = 0;
739
- *out_datavcnt = 0;
740
- *out_stream_id = s->stream_id;
741
- *out_flags = NGTCP2_WRITE_STREAM_FLAG_FIN;
742
- return st;
743
- }
744
- }
745
-
746
- return Qnil;
747
- }
748
-
749
- static VALUE
750
- quic_client_write_pkt(int argc, VALUE *argv, VALUE self)
751
- {
752
- VALUE buffer = Qnil;
753
- rb_scan_args(argc, argv, "01", &buffer);
754
-
755
- quic_client_t *c;
756
- TypedData_Get_Struct(self, quic_client_t, &quic_client_data_type, c);
757
-
758
- if (NIL_P(buffer)) {
759
- /* rb_str_buf_new returns an ASCII-8BIT (binary) String per CRuby spec,
760
- so no explicit rb_enc_associate is required. */
761
- buffer = rb_str_buf_new(QUIC_WRITE_PKT_BUFLEN);
762
- } else {
763
- Check_Type(buffer, T_STRING);
764
- if (rb_enc_get_index(buffer) != rb_ascii8bit_encindex()) {
765
- rb_raise(rb_eArgError, "buffer must be ASCII-8BIT (binary) encoding");
766
- }
767
- if (RSTRING_LEN(buffer) < (long)QUIC_WRITE_PKT_BUFLEN) {
768
- rb_str_modify_expand(buffer, (long)QUIC_WRITE_PKT_BUFLEN - RSTRING_LEN(buffer));
769
- }
770
- }
771
-
772
- ngtcp2_path_storage path_storage;
773
- ngtcp2_path_storage_zero(&path_storage);
774
-
775
- ngtcp2_pkt_info pi;
776
- memset(&pi, 0, sizeof(pi));
777
-
778
- uint8_t *dest = (uint8_t *)RSTRING_PTR(buffer);
779
- size_t destlen = (size_t)QUIC_WRITE_PKT_BUFLEN;
780
-
781
- /* Stream-aware path: if any stream has pending bytes or a pending FIN,
782
- route the packet through ngtcp2_conn_writev_stream so the stream data
783
- gets framed alongside connection-level frames. Falls back to conn-only
784
- when no stream is queued. */
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_client_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_storage.path, &pi, dest, destlen, &pdatalen,
795
- writev_flags, send_stream_id, &datav, datavcnt, quic_now());
796
-
797
- if (n < 0) {
798
- /* ngtcp2 does not partially write on failure, but truncate explicitly so
799
- that a rescued caller cannot accidentally transmit stale bytes. */
800
- rb_str_set_len(buffer, 0);
801
- quic_raise_ngtcp2_error((int)n);
802
- }
803
-
804
- /* Update the selected stream's accounting only when we actually sent
805
- bytes. pdatalen >= 0 means ngtcp2 framed that many data bytes into the
806
- packet; if 0, no data was framed (but the packet may still contain ACK
807
- or other frames). */
808
- if (!NIL_P(selected_stream) && pdatalen > 0) {
809
- quic_stream_t *s;
810
- TypedData_Get_Struct(selected_stream, quic_stream_t,
811
- &quic_stream_data_type, s);
812
- s->sent_offset += (uint64_t)pdatalen;
813
- /* FIN is only actually written when all requested data fits in the
814
- frame (see ngtcp2 docs on NGTCP2_WRITE_STREAM_FLAG_FIN). */
815
- if ((writev_flags & NGTCP2_WRITE_STREAM_FLAG_FIN) &&
816
- (size_t)pdatalen == datav.len) {
817
- s->fin_flushed = true;
818
- }
819
- } else if (!NIL_P(selected_stream) && datavcnt == 0 && n > 0 &&
820
- (writev_flags & NGTCP2_WRITE_STREAM_FLAG_FIN)) {
821
- /* FIN-only packet (no data) was successfully written. */
822
- quic_stream_t *s;
823
- TypedData_Get_Struct(selected_stream, quic_stream_t,
824
- &quic_stream_data_type, s);
825
- s->fin_flushed = true;
826
- }
827
-
828
- if (n == 0) {
829
- rb_str_set_len(buffer, 0);
830
- return Qnil;
831
- }
832
- rb_str_set_len(buffer, n);
833
- return buffer;
834
- }
835
-
836
- NORETURN(static void quic_client_raise_error(quic_client_t *c, int rv));
837
-
838
- /* Like quic_raise_ngtcp2_error, but for errors from read_pkt, where the TLS
839
- handshake runs: NGTCP2_ERR_CRYPTO carries the TLS alert this client sent,
840
- and becomes CertificateVerifyFailed when the verifier rejected the peer. */
841
- static void
842
- quic_client_raise_error(quic_client_t *c, int rv)
843
- {
844
- if (rv != NGTCP2_ERR_CRYPTO) quic_raise_ngtcp2_error(rv);
845
-
846
- /* 0 means "not set" to ngtcp2; close_notify (0) is never sent for a
847
- verification failure, so report it as nil. */
848
- uint8_t alert = ngtcp2_conn_get_tls_alert(c->conn);
849
- VALUE exc;
850
- if (c->verify_failed) {
851
- VALUE msg = c->verify_result
852
- ? rb_sprintf("certificate verify failed (%s)",
853
- X509_verify_cert_error_string(c->verify_result))
854
- : rb_str_new_cstr("certificate verify failed (server sent no certificate)");
855
- exc = rb_exc_new_str(rb_eQUICErrorCertificateVerifyFailed, msg);
856
- rb_ivar_set(exc, rb_intern("@verify_result"),
857
- c->verify_result ? INT2NUM(c->verify_result) : Qnil);
858
- } else {
859
- exc = rb_exc_new_cstr(rb_eQUICErrorCryptoError, ngtcp2_strerror(rv));
860
- }
861
- rb_ivar_set(exc, rb_intern("@code"), INT2NUM(rv));
862
- rb_ivar_set(exc, rb_intern("@tls_alert"), alert ? INT2NUM(alert) : Qnil);
863
- rb_exc_raise(exc);
864
- }
865
-
866
- struct quic_read_pkt_args {
867
- quic_client_t *c;
868
- VALUE packet;
869
- ngtcp2_path path;
870
- ngtcp2_pkt_info pi;
871
- };
872
-
873
- static VALUE
874
- quic_read_pkt_body(VALUE arg)
875
- {
876
- struct quic_read_pkt_args *a = (struct quic_read_pkt_args *)arg;
877
- int rv = ngtcp2_conn_read_pkt(a->c->conn, &a->path, &a->pi,
878
- (const uint8_t *)RSTRING_PTR(a->packet),
879
- (size_t)RSTRING_LEN(a->packet),
880
- quic_now());
881
- if (rv != 0) {
882
- /* NORETURN: longjmp passes through rb_ensure so unlock still fires. */
883
- quic_client_raise_error(a->c, rv);
884
- }
885
- return Qnil;
886
- }
887
-
888
- static VALUE
889
- quic_read_pkt_unlock(VALUE arg)
890
- {
891
- rb_str_unlocktmp(((struct quic_read_pkt_args *)arg)->packet);
892
- return Qnil;
893
- }
894
-
895
- static VALUE
896
- quic_client_read_pkt(int argc, VALUE *argv, VALUE self)
897
- {
898
- VALUE packet = Qnil;
899
- VALUE opts = Qnil;
900
- rb_scan_args(argc, argv, "1:", &packet, &opts);
901
-
902
- if (NIL_P(opts)) {
903
- rb_raise(rb_eArgError, "missing keywords: local_sockaddr, remote_sockaddr");
904
- }
905
-
906
- VALUE local_sockaddr = rb_hash_aref(opts, ID2SYM(rb_intern("local_sockaddr")));
907
- VALUE remote_sockaddr = rb_hash_aref(opts, ID2SYM(rb_intern("remote_sockaddr")));
908
-
909
- if (NIL_P(local_sockaddr) || NIL_P(remote_sockaddr)) {
910
- rb_raise(rb_eArgError, "missing keywords: local_sockaddr, remote_sockaddr");
911
- }
912
-
913
- quic_require_binary(packet, "packet");
914
- quic_require_binary(local_sockaddr, "local_sockaddr");
915
- quic_require_binary(remote_sockaddr, "remote_sockaddr");
916
-
917
- quic_client_t *c;
918
- TypedData_Get_Struct(self, quic_client_t, &quic_client_data_type, c);
919
-
920
- struct quic_read_pkt_args args;
921
- args.c = c;
922
- args.packet = packet;
923
- args.path.local.addr = (struct sockaddr *)RSTRING_PTR(local_sockaddr);
924
- args.path.local.addrlen = (ngtcp2_socklen)RSTRING_LEN(local_sockaddr);
925
- args.path.remote.addr = (struct sockaddr *)RSTRING_PTR(remote_sockaddr);
926
- args.path.remote.addrlen = (ngtcp2_socklen)RSTRING_LEN(remote_sockaddr);
927
- args.path.user_data = NULL;
928
- memset(&args.pi, 0, sizeof(args.pi));
929
-
930
- rb_str_locktmp(packet);
931
- return rb_ensure(quic_read_pkt_body, (VALUE)&args,
932
- quic_read_pkt_unlock, (VALUE)&args);
933
- }
934
-
935
- static VALUE
936
- quic_client_handshake_completed_p(VALUE self)
937
- {
938
- quic_client_t *c;
939
- TypedData_Get_Struct(self, quic_client_t, &quic_client_data_type, c);
940
- return ngtcp2_conn_get_handshake_completed(c->conn) ? Qtrue : Qfalse;
941
- }
942
-
943
- static VALUE
944
- quic_client_in_closing_period_p(VALUE self)
945
- {
946
- quic_client_t *c;
947
- TypedData_Get_Struct(self, quic_client_t, &quic_client_data_type, c);
948
- return ngtcp2_conn_in_closing_period(c->conn) ? Qtrue : Qfalse;
949
- }
950
-
951
- static VALUE
952
- quic_client_in_draining_period_p(VALUE self)
953
- {
954
- quic_client_t *c;
955
- TypedData_Get_Struct(self, quic_client_t, &quic_client_data_type, c);
956
- return ngtcp2_conn_in_draining_period(c->conn) ? Qtrue : Qfalse;
957
- }
958
-
959
- static VALUE
960
- quic_client_expiry(VALUE self)
961
- {
962
- quic_client_t *c;
963
- TypedData_Get_Struct(self, quic_client_t, &quic_client_data_type, c);
964
- ngtcp2_tstamp t = ngtcp2_conn_get_expiry(c->conn);
965
- if (t == UINT64_MAX) return Qnil;
966
- return ULL2NUM((unsigned long long)t);
967
- }
968
-
969
- /* Caller is responsible for ordering with #read_pkt / #write_pkt:
970
- read_pkt or handle_expiry to advance ngtcp2 state, then write_pkt to flush. */
971
- static VALUE
972
- quic_client_handle_expiry(VALUE self)
973
- {
974
- quic_client_t *c;
975
- TypedData_Get_Struct(self, quic_client_t, &quic_client_data_type, c);
976
- int rv = ngtcp2_conn_handle_expiry(c->conn, quic_now());
977
- if (rv != 0) quic_raise_ngtcp2_error(rv);
978
- return Qnil;
979
- }
980
-
981
- static VALUE
982
- quic_client_open_bidi_stream(VALUE self)
983
- {
984
- quic_client_t *c;
985
- TypedData_Get_Struct(self, quic_client_t, &quic_client_data_type, c);
986
-
987
- int64_t stream_id;
988
- int rv = ngtcp2_conn_open_bidi_stream(c->conn, &stream_id, NULL);
989
- if (rv != 0) quic_raise_ngtcp2_error(rv);
990
-
991
- VALUE stream = quic_stream_new(stream_id, self);
992
- VALUE streams = rb_ivar_get(self, rb_intern("@streams"));
993
- rb_hash_aset(streams, LL2NUM(stream_id), stream);
994
- return stream;
995
- }
996
-
997
- static VALUE
998
- quic_client_open_uni_stream(VALUE self)
999
- {
1000
- quic_client_t *c;
1001
- TypedData_Get_Struct(self, quic_client_t, &quic_client_data_type, c);
1002
-
1003
- int64_t stream_id;
1004
- int rv = ngtcp2_conn_open_uni_stream(c->conn, &stream_id, NULL);
1005
- if (rv != 0) quic_raise_ngtcp2_error(rv);
1006
-
1007
- VALUE stream = quic_stream_new(stream_id, self);
1008
- VALUE streams = rb_ivar_get(self, rb_intern("@streams"));
1009
- rb_hash_aset(streams, LL2NUM(stream_id), stream);
1010
- return stream;
1011
- }
1012
-
1013
- /* Send an application CONNECTION_CLOSE (frame type 0x1d) to the peer and
1014
- transition the underlying ngtcp2_conn to the closing period. One UDP
1015
- datagram is written to the bound socket; ngtcp2 handles closing-period
1016
- retransmits via the caller's pump loop. Calling #close on a connection
1017
- that is already in the closing or draining period is a no-op (= returns
1018
- nil without doing any I/O). */
1019
- static VALUE
1020
- quic_client_close_m(int argc, VALUE *argv, VALUE self)
1021
- {
1022
- VALUE opts = Qnil;
1023
- rb_scan_args(argc, argv, "0:", &opts);
1024
-
1025
- uint64_t error_code = 0;
1026
- const uint8_t *reason_ptr = (const uint8_t *)"";
1027
- size_t reason_len = 0;
1028
-
1029
- if (!NIL_P(opts)) {
1030
- VALUE error_code_v = rb_hash_aref(opts, ID2SYM(rb_intern("error_code")));
1031
- VALUE reason_v = rb_hash_aref(opts, ID2SYM(rb_intern("reason")));
1032
- if (!NIL_P(error_code_v)) {
1033
- error_code = NUM2ULL(error_code_v);
1034
- }
1035
- if (!NIL_P(reason_v)) {
1036
- Check_Type(reason_v, T_STRING);
1037
- reason_ptr = (const uint8_t *)RSTRING_PTR(reason_v);
1038
- reason_len = (size_t)RSTRING_LEN(reason_v);
1039
- }
1040
- }
1041
-
1042
- quic_client_t *c;
1043
- TypedData_Get_Struct(self, quic_client_t, &quic_client_data_type, c);
1044
-
1045
- /* Idempotent: a subsequent #close after the conn already entered the
1046
- closing/draining period does not emit another CONNECTION_CLOSE. */
1047
- if (ngtcp2_conn_in_closing_period(c->conn) ||
1048
- ngtcp2_conn_in_draining_period(c->conn)) {
1049
- return Qnil;
1050
- }
1051
-
1052
- VALUE sock = rb_ivar_get(self, rb_intern("@sock"));
1053
- if (NIL_P(sock)) {
1054
- rb_raise(rb_eQUICErrorNotBound,
1055
- "QUIC::Connection::Client#bind(sock) has not been called");
1056
- }
1057
-
1058
- ngtcp2_ccerr ccerr;
1059
- ngtcp2_ccerr_set_application_error(&ccerr, error_code,
1060
- reason_len > 0 ? reason_ptr : NULL,
1061
- reason_len);
1062
-
1063
- ngtcp2_path_storage path_storage;
1064
- ngtcp2_path_storage_zero(&path_storage);
1065
- ngtcp2_pkt_info pi;
1066
- memset(&pi, 0, sizeof(pi));
1067
-
1068
- VALUE buffer = rb_str_buf_new(QUIC_WRITE_PKT_BUFLEN);
1069
- uint8_t *dest = (uint8_t *)RSTRING_PTR(buffer);
1070
-
1071
- ngtcp2_ssize n = ngtcp2_conn_write_connection_close(
1072
- c->conn, &path_storage.path, &pi, dest,
1073
- (size_t)QUIC_WRITE_PKT_BUFLEN, &ccerr, quic_now());
1074
-
1075
- /* Pre-handshake or otherwise not in a state where CONNECTION_CLOSE can be
1076
- emitted (ngtcp2 returns NGTCP2_ERR_INVALID_STATE). Spec treats this as
1077
- best-effort: silently no-op rather than surface an error. The Ruby
1078
- Client is effectively dead from here. */
1079
- if (n == NGTCP2_ERR_INVALID_STATE) {
1080
- return Qnil;
1081
- }
1082
-
1083
- if (n < 0) {
1084
- rb_str_set_len(buffer, 0);
1085
- quic_raise_ngtcp2_error((int)n);
1086
- }
1087
-
1088
- if (n == 0) {
1089
- return Qnil;
1090
- }
1091
-
1092
- rb_str_set_len(buffer, n);
1093
- rb_funcall(sock, rb_intern("send"), 2, buffer, INT2NUM(0));
1094
- return Qnil;
1095
- }
1096
-
1097
- void
1098
- Init_quic_connection_client(VALUE rb_mQUICConnectionArg)
1099
- {
1100
- rb_cQUICConnectionClient = rb_define_class_under(rb_mQUICConnectionArg, "Client", rb_cObject);
1101
- rb_define_alloc_func(rb_cQUICConnectionClient, quic_client_alloc);
1102
- rb_define_singleton_method(rb_cQUICConnectionClient, "_open", quic_client_open, -1);
1103
- rb_define_method(rb_cQUICConnectionClient, "write_pkt", quic_client_write_pkt, -1);
1104
- rb_define_method(rb_cQUICConnectionClient, "read_pkt", quic_client_read_pkt, -1);
1105
- rb_define_method(rb_cQUICConnectionClient, "expiry", quic_client_expiry, 0);
1106
- rb_define_method(rb_cQUICConnectionClient, "handle_expiry", quic_client_handle_expiry, 0);
1107
- rb_define_method(rb_cQUICConnectionClient, "handshake_completed?", quic_client_handshake_completed_p, 0);
1108
- rb_define_method(rb_cQUICConnectionClient, "in_closing_period?", quic_client_in_closing_period_p, 0);
1109
- rb_define_method(rb_cQUICConnectionClient, "in_draining_period?", quic_client_in_draining_period_p, 0);
1110
- rb_define_method(rb_cQUICConnectionClient, "open_bidi_stream", quic_client_open_bidi_stream, 0);
1111
- rb_define_method(rb_cQUICConnectionClient, "open_uni_stream", quic_client_open_uni_stream, 0);
1112
- rb_define_method(rb_cQUICConnectionClient, "close", quic_client_close_m, -1);
267
+ void Init_quic_connection_client(VALUE rb_mQUICConnectionArg) {
268
+ rb_cQUICConnectionClient =
269
+ rb_define_class_under(rb_mQUICConnectionArg, "Client", rb_cObject);
270
+ rb_define_alloc_func(rb_cQUICConnectionClient, quic_conn_alloc);
271
+ rb_define_singleton_method(rb_cQUICConnectionClient, "_open",
272
+ quic_client_open, -1);
273
+ quic_conn_define_shared_methods(rb_cQUICConnectionClient);
1113
274
  }