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.
@@ -1,311 +1,115 @@
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
 
5
+ #include <openssl/err.h>
9
6
  #include <openssl/rand.h>
10
7
 
11
- /* Buffer size for #write_pkt. NGTCP2_MAX_UDP_PAYLOAD_SIZE (1200) is the
12
- minimum destlen ngtcp2 accepts. Revisit when PMTUD is enabled (Phase 4+). */
13
- #define QUIC_WRITE_PKT_BUFLEN NGTCP2_MAX_UDP_PAYLOAD_SIZE
14
-
15
- static int quic_crypto_initialized = 0;
16
-
17
- typedef struct {
18
- ngtcp2_conn *conn;
19
- SSL_CTX *ssl_ctx;
20
- SSL *ssl;
21
- ngtcp2_cid scid;
22
- ngtcp2_cid dcid;
23
- /* ngtcp2 1.x requires applications to associate an ngtcp2_conn with the
24
- TLS object via SSL_set_app_data so that crypto callbacks (e.g.
25
- add_handshake_data) can recover the conn. The ref must outlive ssl. */
26
- ngtcp2_crypto_conn_ref conn_ref;
27
- /* Back-reference to the Quic::Connection::Client Ruby object that owns
28
- this struct. ngtcp2 stream callbacks receive a void* user_data equal
29
- to this struct, and they look up @streams via owner. We are stored
30
- INSIDE owner via TypedData_Wrap_Struct, so owner is guaranteed alive
31
- while we exist (no dmark needed). GC.compact may relocate owner and
32
- leave this field stale; a dcompact slot is a Phase 5 follow-up. */
33
- VALUE owner;
34
- } quic_client_t;
8
+ /* The system default store is loaded once per process: reading the CA bundle
9
+ takes several milliseconds. X509_STORE is reference counted and safe to
10
+ share across verifications; creation runs under the GVL. Never freed. */
11
+ static X509_STORE *quic_default_store;
35
12
 
36
- static void
37
- quic_client_free(void *ptr)
38
- {
39
- quic_client_t *c = (quic_client_t *)ptr;
40
- if (c->conn) ngtcp2_conn_del(c->conn);
41
- if (c->ssl) SSL_free(c->ssl);
42
- if (c->ssl_ctx) SSL_CTX_free(c->ssl_ctx);
43
- xfree(c);
44
- }
45
-
46
- static size_t
47
- quic_client_size(const void *ptr)
48
- {
49
- (void)ptr;
50
- return sizeof(quic_client_t);
51
- }
52
-
53
- static const rb_data_type_t quic_client_data_type = {
54
- "Quic::Connection::Client",
55
- {NULL, quic_client_free, quic_client_size,},
56
- NULL, NULL,
57
- RUBY_TYPED_FREE_IMMEDIATELY,
58
- };
59
-
60
- static VALUE
61
- quic_client_alloc(VALUE klass)
62
- {
63
- quic_client_t *c = ALLOC(quic_client_t);
64
- memset(c, 0, sizeof(*c));
65
- return TypedData_Wrap_Struct(klass, &quic_client_data_type, c);
66
- }
67
-
68
- ngtcp2_conn *
69
- quic_client_conn(VALUE client_v)
70
- {
71
- quic_client_t *c;
72
- TypedData_Get_Struct(client_v, quic_client_t, &quic_client_data_type, c);
73
- return c->conn;
74
- }
75
-
76
- static ngtcp2_tstamp
77
- quic_now(void)
13
+ static X509_STORE *
14
+ quic_get_default_store(void)
78
15
  {
79
- struct timespec ts;
80
- clock_gettime(CLOCK_MONOTONIC, &ts);
81
- return (ngtcp2_tstamp)ts.tv_sec * NGTCP2_SECONDS + (ngtcp2_tstamp)ts.tv_nsec;
82
- }
83
-
84
- static void
85
- quic_rand_cb(uint8_t *dest, size_t destlen, const ngtcp2_rand_ctx *rand_ctx)
86
- {
87
- (void)rand_ctx;
88
- RAND_bytes(dest, (int)destlen);
16
+ if (quic_default_store == NULL) {
17
+ X509_STORE *store = X509_STORE_new();
18
+ if (store == NULL || X509_STORE_set_default_paths(store) != 1) {
19
+ X509_STORE_free(store);
20
+ rb_raise(rb_eRuntimeError, "failed to load the default certificate store");
21
+ }
22
+ quic_default_store = store;
23
+ }
24
+ return quic_default_store;
89
25
  }
90
26
 
27
+ /* Record the X509 verification result so read_pkt can raise a descriptive
28
+ error. The verdict itself is left to picotls: ret is returned unchanged.
29
+ Runs synchronously inside read_pkt with the GVL held; it only writes to the
30
+ struct and never touches Ruby objects. */
91
31
  static int
92
- quic_get_new_connection_id_cb(ngtcp2_conn *conn, ngtcp2_cid *cid,
93
- uint8_t *token, size_t cidlen, void *user_data)
94
- {
95
- (void)conn;
96
- (void)user_data;
97
- if (RAND_bytes(cid->data, (int)cidlen) != 1) {
98
- return NGTCP2_ERR_CALLBACK_FAILURE;
99
- }
100
- cid->datalen = cidlen;
101
- if (RAND_bytes(token, NGTCP2_STATELESS_RESET_TOKENLEN) != 1) {
102
- return NGTCP2_ERR_CALLBACK_FAILURE;
32
+ quic_override_verify_cb(ptls_openssl_override_verify_certificate_t *self,
33
+ ptls_t *tls, int ret, int ossl_ret, X509 *cert,
34
+ STACK_OF(X509) *chain)
35
+ {
36
+ (void)self;
37
+ (void)cert;
38
+ (void)chain;
39
+ ngtcp2_crypto_conn_ref *ref = *ptls_get_data_ptr(tls);
40
+ quic_conn_t *c = (quic_conn_t *)ref->user_data;
41
+ if (ret != 0) {
42
+ c->verify_failed = true;
43
+ c->verify_result = ossl_ret;
103
44
  }
104
- return 0;
45
+ return ret;
105
46
  }
106
47
 
107
- static ngtcp2_conn *
108
- quic_client_get_conn(ngtcp2_crypto_conn_ref *conn_ref)
109
- {
110
- return ((quic_client_t *)conn_ref->user_data)->conn;
111
- }
48
+ static ptls_openssl_override_verify_certificate_t quic_override_verify = {
49
+ quic_override_verify_cb,
50
+ };
112
51
 
52
+ /* Install picotls's certificate verifier on c->tls_ctx according to the
53
+ verify_mode / ca_file / ca_path settings. Leaves verify_certificate NULL
54
+ for verify_mode :none, which skips verification altogether. */
113
55
  static void
114
- quic_require_binary(VALUE str, const char *name)
56
+ quic_client_setup_verify(quic_conn_t *c, VALUE settings_v, VALUE server_name)
115
57
  {
116
- Check_Type(str, T_STRING);
117
- if (rb_enc_get_index(str) != rb_ascii8bit_encindex()) {
118
- rb_raise(rb_eArgError, "%s must be ASCII-8BIT (binary) encoding", name);
58
+ VALUE mode = rb_funcall(settings_v, rb_intern("verify_mode"), 0);
59
+ /* ca_file and ca_path are not even type-checked under :none. */
60
+ if (mode == ID2SYM(rb_intern("none"))) return;
61
+ if (mode != ID2SYM(rb_intern("peer"))) {
62
+ rb_raise(rb_eArgError, "verify_mode must be :peer or :none (got %+"PRIsVALUE")", mode);
119
63
  }
120
- }
121
64
 
122
- static uint64_t
123
- quic_get_uint64_field(VALUE obj, const char *name)
124
- {
125
- VALUE v = rb_funcall(obj, rb_intern(name), 0);
126
- return (uint64_t)NUM2ULL(v);
127
- }
128
-
129
- static ngtcp2_cc_algo
130
- quic_cc_algo_from_sym(VALUE sym)
131
- {
132
- Check_Type(sym, T_SYMBOL);
133
- ID id = SYM2ID(sym);
134
- if (id == rb_intern("reno")) return NGTCP2_CC_ALGO_RENO;
135
- if (id == rb_intern("cubic")) return NGTCP2_CC_ALGO_CUBIC;
136
- if (id == rb_intern("bbr")) return NGTCP2_CC_ALGO_BBR;
137
- rb_raise(rb_eArgError, "unknown cc_algo: %"PRIsVALUE" (expected :reno, :cubic, or :bbr)", sym);
138
- }
65
+ VALUE ca_file = quic_path_value(rb_funcall(settings_v, rb_intern("ca_file"), 0), "ca_file");
66
+ VALUE ca_path = quic_path_value(rb_funcall(settings_v, rb_intern("ca_path"), 0), "ca_path");
139
67
 
140
- static void
141
- quic_fill_transport_params(ngtcp2_transport_params *params, VALUE tp)
142
- {
143
- ngtcp2_transport_params_default(params);
144
- params->initial_max_stream_data_bidi_local =
145
- quic_get_uint64_field(tp, "initial_max_stream_data_bidi_local");
146
- params->initial_max_stream_data_bidi_remote =
147
- quic_get_uint64_field(tp, "initial_max_stream_data_bidi_remote");
148
- params->initial_max_stream_data_uni =
149
- quic_get_uint64_field(tp, "initial_max_stream_data_uni");
150
- params->initial_max_data =
151
- quic_get_uint64_field(tp, "initial_max_data");
152
- params->initial_max_streams_bidi =
153
- quic_get_uint64_field(tp, "initial_max_streams_bidi");
154
- params->initial_max_streams_uni =
155
- quic_get_uint64_field(tp, "initial_max_streams_uni");
156
- params->max_idle_timeout =
157
- (ngtcp2_duration)quic_get_uint64_field(tp, "max_idle_timeout");
158
- params->active_connection_id_limit =
159
- quic_get_uint64_field(tp, "active_connection_id_limit");
160
- }
161
-
162
- static void
163
- quic_fill_settings(ngtcp2_settings *settings, VALUE st)
164
- {
165
- ngtcp2_settings_default(settings);
166
- settings->initial_ts = quic_now();
167
- settings->cc_algo = quic_cc_algo_from_sym(rb_funcall(st, rb_intern("cc_algo"), 0));
168
- settings->initial_rtt =
169
- (ngtcp2_duration)quic_get_uint64_field(st, "initial_rtt");
170
- settings->max_window =
171
- quic_get_uint64_field(st, "max_window");
172
- settings->max_stream_window =
173
- quic_get_uint64_field(st, "max_stream_window");
174
- settings->handshake_timeout =
175
- (ngtcp2_duration)quic_get_uint64_field(st, "handshake_timeout");
176
- VALUE no_pmtud = rb_funcall(st, rb_intern("no_pmtud"), 0);
177
- settings->no_pmtud = RTEST(no_pmtud) ? 1 : 0;
178
- }
179
-
180
- /* Shared helper: look up the Quic::Stream registered for stream_id in the
181
- owner Client's @streams Hash. Returns Qnil if not found (which can happen
182
- if the stream was already removed by a previous stream_close callback). */
183
- static VALUE
184
- quic_client_lookup_stream(quic_client_t *c, int64_t stream_id)
185
- {
186
- VALUE streams = rb_ivar_get(c->owner, rb_intern("@streams"));
187
- return rb_hash_aref(streams, LL2NUM(stream_id));
188
- }
189
-
190
- /* ngtcp2 stream callbacks. Each returns 0 on success or
191
- NGTCP2_ERR_CALLBACK_FAILURE on Ruby-side errors. We assume rb_str_buf_cat /
192
- rb_ary_* won't raise in normal operation (allocation failures aside, which
193
- would abort the process anyway), so we don't wrap in rb_protect for now. */
194
-
195
- static int
196
- quic_stream_open_cb(ngtcp2_conn *conn, int64_t stream_id, void *user_data)
197
- {
198
- (void)conn;
199
- quic_client_t *c = (quic_client_t *)user_data;
200
- VALUE streams = rb_ivar_get(c->owner, rb_intern("@streams"));
201
- if (!NIL_P(rb_hash_aref(streams, LL2NUM(stream_id)))) {
202
- return 0; /* already known (client-initiated) */
203
- }
204
- VALUE stream = quic_stream_new(stream_id, c->owner);
205
- rb_hash_aset(streams, LL2NUM(stream_id), stream);
206
- return 0;
207
- }
208
-
209
- static int
210
- quic_recv_stream_data_cb(ngtcp2_conn *conn, uint32_t flags, int64_t stream_id,
211
- uint64_t offset, const uint8_t *data, size_t datalen,
212
- void *user_data, void *stream_user_data)
213
- {
214
- (void)conn;
215
- (void)offset;
216
- (void)stream_user_data;
217
- quic_client_t *c = (quic_client_t *)user_data;
218
- VALUE stream = quic_client_lookup_stream(c, stream_id);
219
- if (NIL_P(stream)) return 0;
220
-
221
- quic_stream_t *s;
222
- TypedData_Get_Struct(stream, quic_stream_t, &quic_stream_data_type, s);
223
-
224
- if (datalen > 0) {
225
- VALUE recv_buffer = rb_ivar_get(stream, rb_intern("@recv_buffer"));
226
- rb_str_buf_cat(recv_buffer, (const char *)data, (long)datalen);
68
+ /* An empty name makes OpenSSL clear its host list and skip the name check,
69
+ and both SNI and the check would silently stop at an embedded NUL. */
70
+ if (RSTRING_LEN(server_name) == 0 ||
71
+ memchr(RSTRING_PTR(server_name), '\0', (size_t)RSTRING_LEN(server_name)) != NULL) {
72
+ rb_raise(rb_eArgError,
73
+ "server_name must be a non-empty string without NUL bytes when verify_mode is :peer");
227
74
  }
228
- if (flags & NGTCP2_STREAM_DATA_FLAG_FIN) {
229
- s->fin_received = true;
75
+ /* X509_LOOKUP_hash_dir does not check that the directory exists. */
76
+ if (!NIL_P(ca_path) && !RTEST(rb_funcall(rb_cFile, rb_intern("directory?"), 1, ca_path))) {
77
+ rb_raise(rb_eArgError, "ca_path is not a directory: %"PRIsVALUE, ca_path);
230
78
  }
231
- return 0;
232
- }
233
79
 
234
- static int
235
- quic_acked_stream_data_offset_cb(ngtcp2_conn *conn, int64_t stream_id,
236
- uint64_t offset, uint64_t datalen,
237
- void *user_data, void *stream_user_data)
238
- {
239
- (void)conn;
240
- (void)offset;
241
- (void)stream_user_data;
242
- quic_client_t *c = (quic_client_t *)user_data;
243
- VALUE stream = quic_client_lookup_stream(c, stream_id);
244
- if (NIL_P(stream)) return 0;
245
-
246
- quic_stream_t *s;
247
- TypedData_Get_Struct(stream, quic_stream_t, &quic_stream_data_type, s);
248
- s->acked_offset += datalen;
249
-
250
- /* Shift any head Chunks of @pending_chunks whose end-offset is fully
251
- covered by acked_offset. Each Chunk's start-offset in the stream is
252
- pending_shifted (after previous shifts); its end-offset is
253
- pending_shifted + RSTRING_LEN(head). */
254
- VALUE pending = rb_ivar_get(stream, rb_intern("@pending_chunks"));
255
- while (RARRAY_LEN(pending) > 0) {
256
- VALUE head = RARRAY_AREF(pending, 0);
257
- uint64_t head_end = s->pending_shifted + (uint64_t)RSTRING_LEN(head);
258
- if (head_end <= s->acked_offset) {
259
- rb_ary_shift(pending);
260
- s->pending_shifted = head_end;
261
- } else {
262
- break;
80
+ X509_STORE *store;
81
+ bool own_store = !NIL_P(ca_file) || !NIL_P(ca_path);
82
+ if (own_store) {
83
+ /* Trust only what was given; the system store is not consulted. */
84
+ store = X509_STORE_new();
85
+ if (store == NULL) {
86
+ rb_raise(rb_eRuntimeError, "X509_STORE_new failed");
87
+ }
88
+ if (X509_STORE_load_locations(store,
89
+ NIL_P(ca_file) ? NULL : RSTRING_PTR(ca_file),
90
+ NIL_P(ca_path) ? NULL : RSTRING_PTR(ca_path)) != 1) {
91
+ X509_STORE_free(store);
92
+ /* libcrypto is shared with Ruby's openssl extension; do not leave our
93
+ failure on its error queue. */
94
+ ERR_clear_error();
95
+ if (NIL_P(ca_file)) {
96
+ rb_raise(rb_eArgError, "failed to load ca_path: %"PRIsVALUE, ca_path);
97
+ }
98
+ rb_raise(rb_eArgError, "failed to load ca_file: %"PRIsVALUE, ca_file);
263
99
  }
100
+ } else {
101
+ store = quic_get_default_store();
264
102
  }
265
- return 0;
266
- }
267
103
 
268
- static int
269
- quic_stream_close_cb(ngtcp2_conn *conn, uint32_t flags, int64_t stream_id,
270
- uint64_t app_error_code, void *user_data,
271
- void *stream_user_data)
272
- {
273
- (void)conn;
274
- (void)stream_user_data;
275
- quic_client_t *c = (quic_client_t *)user_data;
276
- VALUE streams = rb_ivar_get(c->owner, rb_intern("@streams"));
277
- VALUE stream = rb_hash_aref(streams, LL2NUM(stream_id));
278
- if (NIL_P(stream)) return 0;
279
-
280
- quic_stream_t *s;
281
- TypedData_Get_Struct(stream, quic_stream_t, &quic_stream_data_type, s);
282
- s->closed = true;
283
- if (flags & NGTCP2_STREAM_CLOSE_FLAG_APP_ERROR_CODE_SET) {
284
- s->close_has_app_error_code = true;
285
- s->close_app_error_code = app_error_code;
104
+ /* init takes its own reference on the store, so ours can go right away. */
105
+ int rv = ptls_openssl_init_verify_certificate(&c->verify_cert, store);
106
+ if (own_store) X509_STORE_free(store);
107
+ if (rv != 0) {
108
+ rb_raise(rb_eRuntimeError, "ptls_openssl_init_verify_certificate failed");
286
109
  }
287
- rb_hash_delete(streams, LL2NUM(stream_id));
288
- return 0;
289
- }
290
-
291
- static int
292
- quic_stream_reset_cb(ngtcp2_conn *conn, int64_t stream_id, uint64_t final_size,
293
- uint64_t app_error_code, void *user_data,
294
- void *stream_user_data)
295
- {
296
- (void)conn;
297
- (void)final_size;
298
- (void)stream_user_data;
299
- quic_client_t *c = (quic_client_t *)user_data;
300
- VALUE stream = quic_client_lookup_stream(c, stream_id);
301
- if (NIL_P(stream)) return 0;
302
-
303
- quic_stream_t *s;
304
- TypedData_Get_Struct(stream, quic_stream_t, &quic_stream_data_type, s);
305
- s->reset = true;
306
- s->close_app_error_code = app_error_code;
307
- s->close_has_app_error_code = true;
308
- return 0;
110
+ c->verify_cert_initialized = true;
111
+ c->verify_cert.override_callback = &quic_override_verify;
112
+ c->tls_ctx.verify_certificate = &c->verify_cert.super;
309
113
  }
310
114
 
311
115
  static VALUE
@@ -330,79 +134,47 @@ quic_client_open(int argc, VALUE *argv, VALUE klass)
330
134
  "all keywords required: local_sockaddr, remote_sockaddr, server_name, transport_params, settings");
331
135
  }
332
136
 
333
- quic_require_binary(local_sockaddr, "local_sockaddr");
334
- quic_require_binary(remote_sockaddr, "remote_sockaddr");
137
+ quic_require_sockaddr(local_sockaddr, "local_sockaddr");
138
+ quic_require_sockaddr(remote_sockaddr, "remote_sockaddr");
335
139
  Check_Type(server_name, T_STRING);
336
140
 
337
- if (!quic_crypto_initialized) {
338
- if (ngtcp2_crypto_quictls_init() != 0) {
339
- rb_raise(rb_eRuntimeError, "ngtcp2_crypto_quictls_init failed");
340
- }
341
- quic_crypto_initialized = 1;
342
- }
343
-
344
- VALUE self = quic_client_alloc(klass);
141
+ VALUE self = quic_conn_alloc(klass);
345
142
  rb_ivar_set(self, rb_intern("@server_name"), server_name);
346
143
 
347
- quic_client_t *c;
348
- TypedData_Get_Struct(self, quic_client_t, &quic_client_data_type, c);
144
+ quic_conn_t *c = quic_conn_get(self);
349
145
  c->owner = self;
350
-
351
- c->ssl_ctx = SSL_CTX_new(TLS_client_method());
352
- if (!c->ssl_ctx) {
353
- rb_raise(rb_eRuntimeError, "SSL_CTX_new failed");
354
- }
355
- if (ngtcp2_crypto_quictls_configure_client_context(c->ssl_ctx) != 0) {
356
- rb_raise(rb_eRuntimeError, "ngtcp2_crypto_quictls_configure_client_context failed");
146
+ c->is_server = false;
147
+
148
+ /* An empty list means no ALPN extension, matching the old
149
+ skip-SSL_set_alpn_protos path. */
150
+ quic_conn_copy_alpn(c, settings_v);
151
+
152
+ /* quic_conn_alloc already zeroed the struct, but calling ctx_init keeps
153
+ the ngtcp2-side initialization explicit and the TLS setup in one place. */
154
+ ngtcp2_crypto_picotls_ctx_init(&c->cptls);
155
+ c->tls_ctx = (ptls_context_t){
156
+ .random_bytes = ptls_openssl_random_bytes,
157
+ .get_time = &ptls_get_time,
158
+ .key_exchanges = quic_key_exchanges,
159
+ .cipher_suites = quic_cipher_suites,
160
+ /* No session resumption yet, so this has no effect today. Keep it so that
161
+ when resumption is added, PSK-only (non-forward-secret) resumption is
162
+ refused rather than silently accepted. */
163
+ .require_dhe_on_psk = 1,
164
+ };
165
+ if (ngtcp2_crypto_picotls_configure_client_context(&c->tls_ctx) != 0) {
166
+ rb_raise(rb_eRuntimeError, "ngtcp2_crypto_picotls_configure_client_context failed");
357
167
  }
358
168
 
359
- c->ssl = SSL_new(c->ssl_ctx);
360
- if (!c->ssl) {
361
- rb_raise(rb_eRuntimeError, "SSL_new failed");
362
- }
363
- SSL_set_connect_state(c->ssl);
364
- SSL_set_tlsext_host_name(c->ssl, RSTRING_PTR(server_name));
365
-
366
- VALUE alpn_ary = rb_funcall(settings_v, rb_intern("alpn"), 0);
367
- Check_Type(alpn_ary, T_ARRAY);
368
- long n_alpn = RARRAY_LEN(alpn_ary);
369
- if (n_alpn > 0) {
370
- /* RFC 7301 wire format: 1-byte length prefix + bytes, concatenated. */
371
- size_t total = 0;
372
- for (long i = 0; i < n_alpn; i++) {
373
- VALUE entry = RARRAY_AREF(alpn_ary, i);
374
- Check_Type(entry, T_STRING);
375
- long len = RSTRING_LEN(entry);
376
- if (len < 1 || len > 255) {
377
- rb_raise(rb_eArgError, "alpn entry must be 1-255 bytes (got %ld)", len);
378
- }
379
- total += 1 + (size_t)len;
380
- }
381
- /* total <= 256 * 256 = 65536; ALLOCA_N is safe at this size. */
382
- unsigned char *wire = ALLOCA_N(unsigned char, total);
383
- unsigned char *p = wire;
384
- for (long i = 0; i < n_alpn; i++) {
385
- VALUE entry = RARRAY_AREF(alpn_ary, i);
386
- long len = RSTRING_LEN(entry);
387
- *p++ = (unsigned char)len;
388
- memcpy(p, RSTRING_PTR(entry), (size_t)len);
389
- p += len;
390
- }
391
- /* SSL_set_alpn_protos returns 0 on success (counter-intuitive). */
392
- if (SSL_set_alpn_protos(c->ssl, wire, (unsigned int)total) != 0) {
393
- rb_raise(rb_eRuntimeError, "SSL_set_alpn_protos failed");
394
- }
395
- }
169
+ /* ptls_client_new keeps a pointer to tls_ctx, so the verifier has to be in
170
+ place before it runs. */
171
+ quic_client_setup_verify(c, settings_v, server_name);
396
172
 
397
- /* Link the SSL handle back to this client so ngtcp2's crypto callbacks
398
- (add_handshake_data, set_encryption_secrets, ...) can resolve the
399
- ngtcp2_conn via SSL_get_app_data -> ngtcp2_crypto_conn_ref. */
400
- c->conn_ref.get_conn = quic_client_get_conn;
173
+ c->conn_ref.get_conn = quic_conn_get_conn;
401
174
  c->conn_ref.user_data = c;
402
- SSL_set_app_data(c->ssl, &c->conn_ref);
403
175
 
404
- c->scid.datalen = 8;
405
- if (RAND_bytes(c->scid.data, 8) != 1) {
176
+ c->scid.datalen = QUIC_SCID_LEN;
177
+ if (RAND_bytes(c->scid.data, QUIC_SCID_LEN) != 1) {
406
178
  rb_raise(rb_eRuntimeError, "RAND_bytes(scid) failed");
407
179
  }
408
180
  c->dcid.datalen = 8;
@@ -411,28 +183,13 @@ quic_client_open(int argc, VALUE *argv, VALUE klass)
411
183
  }
412
184
 
413
185
  ngtcp2_callbacks callbacks;
414
- memset(&callbacks, 0, sizeof(callbacks));
186
+ quic_conn_fill_common_callbacks(&callbacks);
415
187
  callbacks.client_initial = ngtcp2_crypto_client_initial_cb;
416
- callbacks.recv_crypto_data = ngtcp2_crypto_recv_crypto_data_cb;
417
- callbacks.encrypt = ngtcp2_crypto_encrypt_cb;
418
- callbacks.decrypt = ngtcp2_crypto_decrypt_cb;
419
- callbacks.hp_mask = ngtcp2_crypto_hp_mask_cb;
420
188
  callbacks.recv_retry = ngtcp2_crypto_recv_retry_cb;
421
- callbacks.update_key = ngtcp2_crypto_update_key_cb;
422
- callbacks.delete_crypto_aead_ctx = ngtcp2_crypto_delete_crypto_aead_ctx_cb;
423
- callbacks.delete_crypto_cipher_ctx = ngtcp2_crypto_delete_crypto_cipher_ctx_cb;
424
- callbacks.get_path_challenge_data = ngtcp2_crypto_get_path_challenge_data_cb;
425
- callbacks.version_negotiation = ngtcp2_crypto_version_negotiation_cb;
426
- callbacks.rand = quic_rand_cb;
427
- callbacks.get_new_connection_id = quic_get_new_connection_id_cb;
428
- callbacks.stream_open = quic_stream_open_cb;
429
- callbacks.recv_stream_data = quic_recv_stream_data_cb;
430
- callbacks.acked_stream_data_offset = quic_acked_stream_data_offset_cb;
431
- callbacks.stream_close = quic_stream_close_cb;
432
- callbacks.stream_reset = quic_stream_reset_cb;
433
189
 
434
190
  ngtcp2_settings settings;
435
191
  quic_fill_settings(&settings, settings_v);
192
+ quic_conn_open_qlog(c, settings_v, &settings);
436
193
 
437
194
  ngtcp2_transport_params params;
438
195
  quic_fill_transport_params(&params, transport_params);
@@ -450,329 +207,50 @@ quic_client_open(int argc, VALUE *argv, VALUE klass)
450
207
  quic_raise_ngtcp2_error(rv);
451
208
  }
452
209
 
453
- ngtcp2_conn_set_tls_native_handle(c->conn, c->ssl);
454
-
455
- /* Stream registry: stream_id (Integer) -> Quic::Stream.
456
- Populated by #open_bidi_stream / #open_uni_stream and the stream_open
457
- callback. Entries are removed in the stream_close callback. */
458
- rb_ivar_set(self, rb_intern("@streams"), rb_hash_new());
459
-
460
- return self;
461
- }
462
-
463
- /* Find the first stream in @streams that has either pending bytes or a
464
- pending FIN to flush. Phase 4 minimum: linear scan, take the first match
465
- (no round-robin). Sets *out_datav to the byte slice to send, *out_datavcnt
466
- to 0 or 1, *out_flags to NGTCP2_WRITE_STREAM_FLAG_FIN when applicable.
467
- Returns the stream's Ruby VALUE (or Qnil if no candidate). */
468
- static VALUE
469
- quic_client_pick_send_stream(VALUE self, ngtcp2_vec *out_datav,
470
- size_t *out_datavcnt, uint32_t *out_flags,
471
- int64_t *out_stream_id)
472
- {
473
- VALUE streams = rb_ivar_get(self, rb_intern("@streams"));
474
- VALUE stream_values = rb_funcall(streams, rb_intern("values"), 0);
475
-
476
- for (long i = 0; i < RARRAY_LEN(stream_values); i++) {
477
- VALUE st = RARRAY_AREF(stream_values, i);
478
- quic_stream_t *s;
479
- TypedData_Get_Struct(st, quic_stream_t, &quic_stream_data_type, s);
480
- if (s->fin_flushed) continue;
481
-
482
- VALUE pending = rb_ivar_get(st, rb_intern("@pending_chunks"));
483
- uint64_t position = s->sent_offset - s->pending_shifted;
484
- long chunks_count = RARRAY_LEN(pending);
485
-
486
- /* Walk pending Chunks to find the one containing the next unsent byte. */
487
- long chunk_idx = -1;
488
- long offset_in_chunk = 0;
489
- for (long j = 0; j < chunks_count; j++) {
490
- VALUE chunk = RARRAY_AREF(pending, j);
491
- long clen = RSTRING_LEN(chunk);
492
- if (position < (uint64_t)clen) {
493
- chunk_idx = j;
494
- offset_in_chunk = (long)position;
495
- break;
496
- }
497
- position -= (uint64_t)clen;
498
- }
499
-
500
- if (chunk_idx >= 0) {
501
- VALUE chunk = RARRAY_AREF(pending, chunk_idx);
502
- out_datav->base = (uint8_t *)RSTRING_PTR(chunk) + offset_in_chunk;
503
- out_datav->len = (size_t)(RSTRING_LEN(chunk) - offset_in_chunk);
504
- *out_datavcnt = 1;
505
- *out_stream_id = s->stream_id;
506
- /* Attach FIN if this is the LAST chunk and we're going to send all
507
- remaining bytes of it (ngtcp2 may encode less, in which case FIN
508
- won't actually be flushed and we'll retry next call). */
509
- *out_flags = (s->fin_sent && chunk_idx == chunks_count - 1)
510
- ? NGTCP2_WRITE_STREAM_FLAG_FIN
511
- : 0;
512
- return st;
513
- }
514
-
515
- if (s->fin_sent && !s->fin_flushed) {
516
- /* No data to send but a pending FIN-only frame. */
517
- out_datav->base = NULL;
518
- out_datav->len = 0;
519
- *out_datavcnt = 0;
520
- *out_stream_id = s->stream_id;
521
- *out_flags = NGTCP2_WRITE_STREAM_FLAG_FIN;
522
- return st;
523
- }
524
- }
525
-
526
- return Qnil;
527
- }
528
-
529
- static VALUE
530
- quic_client_write_pkt(int argc, VALUE *argv, VALUE self)
531
- {
532
- VALUE buffer = Qnil;
533
- rb_scan_args(argc, argv, "01", &buffer);
534
-
535
- quic_client_t *c;
536
- TypedData_Get_Struct(self, quic_client_t, &quic_client_data_type, c);
537
-
538
- if (NIL_P(buffer)) {
539
- /* rb_str_buf_new returns an ASCII-8BIT (binary) String per CRuby spec,
540
- so no explicit rb_enc_associate is required. */
541
- buffer = rb_str_buf_new(QUIC_WRITE_PKT_BUFLEN);
542
- } else {
543
- Check_Type(buffer, T_STRING);
544
- if (rb_enc_get_index(buffer) != rb_ascii8bit_encindex()) {
545
- rb_raise(rb_eArgError, "buffer must be ASCII-8BIT (binary) encoding");
546
- }
547
- if (RSTRING_LEN(buffer) < (long)QUIC_WRITE_PKT_BUFLEN) {
548
- rb_str_modify_expand(buffer, (long)QUIC_WRITE_PKT_BUFLEN - RSTRING_LEN(buffer));
549
- }
550
- }
551
-
552
- ngtcp2_path_storage path_storage;
553
- ngtcp2_path_storage_zero(&path_storage);
554
-
555
- ngtcp2_pkt_info pi;
556
- memset(&pi, 0, sizeof(pi));
557
-
558
- uint8_t *dest = (uint8_t *)RSTRING_PTR(buffer);
559
- size_t destlen = (size_t)QUIC_WRITE_PKT_BUFLEN;
560
-
561
- /* Stream-aware path: if any stream has pending bytes or a pending FIN,
562
- route the packet through ngtcp2_conn_writev_stream so the stream data
563
- gets framed alongside connection-level frames. Falls back to conn-only
564
- when no stream is queued. */
565
- ngtcp2_vec datav;
566
- size_t datavcnt = 0;
567
- uint32_t writev_flags = 0;
568
- int64_t send_stream_id = -1;
569
- VALUE selected_stream = quic_client_pick_send_stream(
570
- self, &datav, &datavcnt, &writev_flags, &send_stream_id);
571
-
572
- ngtcp2_ssize pdatalen = -1;
573
- ngtcp2_ssize n = ngtcp2_conn_writev_stream(
574
- c->conn, &path_storage.path, &pi, dest, destlen, &pdatalen,
575
- writev_flags, send_stream_id, &datav, datavcnt, quic_now());
576
-
577
- if (n < 0) {
578
- /* ngtcp2 does not partially write on failure, but truncate explicitly so
579
- that a rescued caller cannot accidentally transmit stale bytes. */
580
- rb_str_set_len(buffer, 0);
581
- quic_raise_ngtcp2_error((int)n);
582
- }
583
-
584
- /* Update the selected stream's accounting only when we actually sent
585
- bytes. pdatalen >= 0 means ngtcp2 framed that many data bytes into the
586
- packet; if 0, no data was framed (but the packet may still contain ACK
587
- or other frames). */
588
- if (!NIL_P(selected_stream) && pdatalen > 0) {
589
- quic_stream_t *s;
590
- TypedData_Get_Struct(selected_stream, quic_stream_t,
591
- &quic_stream_data_type, s);
592
- s->sent_offset += (uint64_t)pdatalen;
593
- /* FIN is only actually written when all requested data fits in the
594
- frame (see ngtcp2 docs on NGTCP2_WRITE_STREAM_FLAG_FIN). */
595
- if ((writev_flags & NGTCP2_WRITE_STREAM_FLAG_FIN) &&
596
- (size_t)pdatalen == datav.len) {
597
- s->fin_flushed = true;
598
- }
599
- } else if (!NIL_P(selected_stream) && datavcnt == 0 && n > 0 &&
600
- (writev_flags & NGTCP2_WRITE_STREAM_FLAG_FIN)) {
601
- /* FIN-only packet (no data) was successfully written. */
602
- quic_stream_t *s;
603
- TypedData_Get_Struct(selected_stream, quic_stream_t,
604
- &quic_stream_data_type, s);
605
- s->fin_flushed = true;
210
+ /* The TLS session is built last: configure_client_session derives the QUIC
211
+ transport parameters from the conn, so it needs the conn to exist. */
212
+ c->cptls.ptls = ptls_client_new(&c->tls_ctx);
213
+ if (c->cptls.ptls == NULL) {
214
+ rb_raise(rb_eRuntimeError, "ptls_client_new failed");
606
215
  }
607
-
608
- if (n == 0) {
609
- rb_str_set_len(buffer, 0);
610
- return Qnil;
611
- }
612
- rb_str_set_len(buffer, n);
613
- return buffer;
614
- }
615
-
616
- struct quic_read_pkt_args {
617
- quic_client_t *c;
618
- VALUE packet;
619
- ngtcp2_path path;
620
- ngtcp2_pkt_info pi;
621
- };
622
-
623
- static VALUE
624
- quic_read_pkt_body(VALUE arg)
625
- {
626
- struct quic_read_pkt_args *a = (struct quic_read_pkt_args *)arg;
627
- int rv = ngtcp2_conn_read_pkt(a->c->conn, &a->path, &a->pi,
628
- (const uint8_t *)RSTRING_PTR(a->packet),
629
- (size_t)RSTRING_LEN(a->packet),
630
- quic_now());
631
- if (rv != 0) {
632
- /* NORETURN: longjmp passes through rb_ensure so unlock still fires. */
633
- quic_raise_ngtcp2_error(rv);
634
- }
635
- return Qnil;
636
- }
637
-
638
- static VALUE
639
- quic_read_pkt_unlock(VALUE arg)
640
- {
641
- rb_str_unlocktmp(((struct quic_read_pkt_args *)arg)->packet);
642
- return Qnil;
643
- }
644
-
645
- static VALUE
646
- quic_client_read_pkt(int argc, VALUE *argv, VALUE self)
647
- {
648
- VALUE packet = Qnil;
649
- VALUE opts = Qnil;
650
- rb_scan_args(argc, argv, "1:", &packet, &opts);
651
-
652
- if (NIL_P(opts)) {
653
- rb_raise(rb_eArgError, "missing keywords: local_sockaddr, remote_sockaddr");
216
+ /* Let ngtcp2's crypto callbacks (add_handshake_data,
217
+ set_encryption_secrets, ...) resolve the ngtcp2_conn from the ptls_t. */
218
+ *ptls_get_data_ptr(c->cptls.ptls) = &c->conn_ref;
219
+ /* [0] is filled in by configure_client_session with the QUIC transport
220
+ parameters; [1] terminates the list. */
221
+ c->tls_exts[0].type = UINT16_MAX;
222
+ c->tls_exts[1].type = UINT16_MAX;
223
+ c->cptls.handshake_properties.additional_extensions = c->tls_exts;
224
+ if (ngtcp2_crypto_picotls_configure_client_session(&c->cptls, c->conn) != 0) {
225
+ rb_raise(rb_eRuntimeError, "ngtcp2_crypto_picotls_configure_client_session failed");
654
226
  }
655
-
656
- VALUE local_sockaddr = rb_hash_aref(opts, ID2SYM(rb_intern("local_sockaddr")));
657
- VALUE remote_sockaddr = rb_hash_aref(opts, ID2SYM(rb_intern("remote_sockaddr")));
658
-
659
- if (NIL_P(local_sockaddr) || NIL_P(remote_sockaddr)) {
660
- rb_raise(rb_eArgError, "missing keywords: local_sockaddr, remote_sockaddr");
227
+ c->cptls.handshake_properties.client.negotiated_protocols.list = c->alpn;
228
+ c->cptls.handshake_properties.client.negotiated_protocols.count = c->alpn_count;
229
+ /* ptls_set_server_name copies the name. */
230
+ if (ptls_set_server_name(c->cptls.ptls, RSTRING_PTR(server_name),
231
+ (size_t)RSTRING_LEN(server_name)) != 0) {
232
+ rb_raise(rb_eRuntimeError, "ptls_set_server_name failed");
661
233
  }
662
234
 
663
- quic_require_binary(packet, "packet");
664
- quic_require_binary(local_sockaddr, "local_sockaddr");
665
- quic_require_binary(remote_sockaddr, "remote_sockaddr");
666
-
667
- quic_client_t *c;
668
- TypedData_Get_Struct(self, quic_client_t, &quic_client_data_type, c);
669
-
670
- struct quic_read_pkt_args args;
671
- args.c = c;
672
- args.packet = packet;
673
- args.path.local.addr = (struct sockaddr *)RSTRING_PTR(local_sockaddr);
674
- args.path.local.addrlen = (ngtcp2_socklen)RSTRING_LEN(local_sockaddr);
675
- args.path.remote.addr = (struct sockaddr *)RSTRING_PTR(remote_sockaddr);
676
- args.path.remote.addrlen = (ngtcp2_socklen)RSTRING_LEN(remote_sockaddr);
677
- args.path.user_data = NULL;
678
- memset(&args.pi, 0, sizeof(args.pi));
679
-
680
- rb_str_locktmp(packet);
681
- return rb_ensure(quic_read_pkt_body, (VALUE)&args,
682
- quic_read_pkt_unlock, (VALUE)&args);
683
- }
684
-
685
- static VALUE
686
- quic_client_handshake_completed_p(VALUE self)
687
- {
688
- quic_client_t *c;
689
- TypedData_Get_Struct(self, quic_client_t, &quic_client_data_type, c);
690
- return ngtcp2_conn_get_handshake_completed(c->conn) ? Qtrue : Qfalse;
691
- }
692
-
693
- static VALUE
694
- quic_client_in_closing_period_p(VALUE self)
695
- {
696
- quic_client_t *c;
697
- TypedData_Get_Struct(self, quic_client_t, &quic_client_data_type, c);
698
- return ngtcp2_conn_in_closing_period(c->conn) ? Qtrue : Qfalse;
699
- }
700
-
701
- static VALUE
702
- quic_client_in_draining_period_p(VALUE self)
703
- {
704
- quic_client_t *c;
705
- TypedData_Get_Struct(self, quic_client_t, &quic_client_data_type, c);
706
- return ngtcp2_conn_in_draining_period(c->conn) ? Qtrue : Qfalse;
707
- }
708
-
709
- static VALUE
710
- quic_client_expiry(VALUE self)
711
- {
712
- quic_client_t *c;
713
- TypedData_Get_Struct(self, quic_client_t, &quic_client_data_type, c);
714
- ngtcp2_tstamp t = ngtcp2_conn_get_expiry(c->conn);
715
- if (t == UINT64_MAX) return Qnil;
716
- return ULL2NUM((unsigned long long)t);
717
- }
235
+ ngtcp2_conn_set_tls_native_handle(c->conn, &c->cptls);
718
236
 
719
- /* Caller is responsible for ordering with #read_pkt / #write_pkt:
720
- read_pkt or handle_expiry to advance ngtcp2 state, then write_pkt to flush. */
721
- static VALUE
722
- quic_client_handle_expiry(VALUE self)
723
- {
724
- quic_client_t *c;
725
- TypedData_Get_Struct(self, quic_client_t, &quic_client_data_type, c);
726
- int rv = ngtcp2_conn_handle_expiry(c->conn, quic_now());
727
- if (rv != 0) quic_raise_ngtcp2_error(rv);
728
- return Qnil;
729
- }
730
-
731
- static VALUE
732
- quic_client_open_bidi_stream(VALUE self)
733
- {
734
- quic_client_t *c;
735
- TypedData_Get_Struct(self, quic_client_t, &quic_client_data_type, c);
736
-
737
- int64_t stream_id;
738
- int rv = ngtcp2_conn_open_bidi_stream(c->conn, &stream_id, NULL);
739
- if (rv != 0) quic_raise_ngtcp2_error(rv);
740
-
741
- VALUE stream = quic_stream_new(stream_id, self);
742
- VALUE streams = rb_ivar_get(self, rb_intern("@streams"));
743
- rb_hash_aset(streams, LL2NUM(stream_id), stream);
744
- return stream;
745
- }
746
-
747
- static VALUE
748
- quic_client_open_uni_stream(VALUE self)
749
- {
750
- quic_client_t *c;
751
- TypedData_Get_Struct(self, quic_client_t, &quic_client_data_type, c);
237
+ /* Stream registry: stream_id (Integer) -> QUIC::Stream.
238
+ Populated by #open_bidi_stream / #open_uni_stream and the stream_open
239
+ callback. Entries are removed in the stream_close callback. */
240
+ rb_ivar_set(self, rb_intern("@streams"), rb_hash_new());
752
241
 
753
- int64_t stream_id;
754
- int rv = ngtcp2_conn_open_uni_stream(c->conn, &stream_id, NULL);
755
- if (rv != 0) quic_raise_ngtcp2_error(rv);
242
+ /* FIFO of peer-initiated streams awaiting #accept_stream. The
243
+ recv_stream_data callback pushes each new peer stream here once. */
244
+ rb_ivar_set(self, rb_intern("@accept_queue"), rb_ary_new());
756
245
 
757
- VALUE stream = quic_stream_new(stream_id, self);
758
- VALUE streams = rb_ivar_get(self, rb_intern("@streams"));
759
- rb_hash_aset(streams, LL2NUM(stream_id), stream);
760
- return stream;
246
+ return self;
761
247
  }
762
248
 
763
249
  void
764
- Init_quic_connection_client(VALUE rb_mQuicConnectionArg)
250
+ Init_quic_connection_client(VALUE rb_mQUICConnectionArg)
765
251
  {
766
- rb_cQuicConnectionClient = rb_define_class_under(rb_mQuicConnectionArg, "Client", rb_cObject);
767
- rb_define_alloc_func(rb_cQuicConnectionClient, quic_client_alloc);
768
- rb_define_singleton_method(rb_cQuicConnectionClient, "_open", quic_client_open, -1);
769
- rb_define_method(rb_cQuicConnectionClient, "write_pkt", quic_client_write_pkt, -1);
770
- rb_define_method(rb_cQuicConnectionClient, "read_pkt", quic_client_read_pkt, -1);
771
- rb_define_method(rb_cQuicConnectionClient, "expiry", quic_client_expiry, 0);
772
- rb_define_method(rb_cQuicConnectionClient, "handle_expiry", quic_client_handle_expiry, 0);
773
- rb_define_method(rb_cQuicConnectionClient, "handshake_completed?", quic_client_handshake_completed_p, 0);
774
- rb_define_method(rb_cQuicConnectionClient, "in_closing_period?", quic_client_in_closing_period_p, 0);
775
- rb_define_method(rb_cQuicConnectionClient, "in_draining_period?", quic_client_in_draining_period_p, 0);
776
- rb_define_method(rb_cQuicConnectionClient, "open_bidi_stream", quic_client_open_bidi_stream, 0);
777
- rb_define_method(rb_cQuicConnectionClient, "open_uni_stream", quic_client_open_uni_stream, 0);
252
+ rb_cQUICConnectionClient = rb_define_class_under(rb_mQUICConnectionArg, "Client", rb_cObject);
253
+ rb_define_alloc_func(rb_cQUICConnectionClient, quic_conn_alloc);
254
+ rb_define_singleton_method(rb_cQUICConnectionClient, "_open", quic_client_open, -1);
255
+ quic_conn_define_shared_methods(rb_cQUICConnectionClient);
778
256
  }