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.
- checksums.yaml +4 -4
- data/.clang-format +1 -0
- data/.git-blame-ignore-revs +2 -0
- data/CHANGELOG.md +33 -0
- data/LICENSE-DEPENDENCIES.txt +2 -19
- data/README.md +101 -2
- data/Rakefile +7 -0
- data/examples/demo_certificates.rb +67 -0
- data/examples/echo_demo.rb +5 -6
- data/examples/echo_endpoint_demo.rb +170 -0
- data/examples/echo_server_demo.rb +71 -0
- data/ext/quic/connection_client.c +95 -934
- data/ext/quic/connection_common.c +1255 -0
- data/ext/quic/connection_common.h +226 -0
- data/ext/quic/connection_server.c +205 -0
- data/ext/quic/endpoint.c +829 -0
- data/ext/quic/endpoint.h +31 -0
- data/ext/quic/ext_api.c +106 -0
- data/ext/quic/extconf.rb +12 -5
- data/ext/quic/include/quic/ext.h +91 -0
- data/ext/quic/quic.c +95 -62
- data/ext/quic/quic.h +12 -10
- data/ext/quic/stream.c +124 -82
- data/ext/quic/stream.h +18 -12
- data/ext/quic/tls_context.c +269 -0
- data/ext/quic/tls_context.h +47 -0
- data/lib/quic/connection.rb +142 -57
- data/lib/quic/endpoint.rb +57 -0
- data/lib/quic/ext_include.rb +12 -0
- data/lib/quic/settings.rb +26 -3
- data/lib/quic/stream.rb +4 -3
- data/lib/quic/version.rb +1 -1
- data/lib/quic.rb +2 -0
- data/sig/quic.rbs +203 -22
- metadata +17 -1
|
@@ -1,399 +1,28 @@
|
|
|
1
|
-
#include "
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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")))
|
|
57
|
+
if (mode == ID2SYM(rb_intern("none")))
|
|
58
|
+
return;
|
|
454
59
|
if (mode != ID2SYM(rb_intern("peer"))) {
|
|
455
|
-
rb_raise(rb_eArgError,
|
|
60
|
+
rb_raise(rb_eArgError,
|
|
61
|
+
"verify_mode must be :peer or :none (got %+" PRIsVALUE ")", mode);
|
|
456
62
|
}
|
|
457
63
|
|
|
458
|
-
VALUE ca_file =
|
|
459
|
-
|
|
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',
|
|
465
|
-
|
|
466
|
-
|
|
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) &&
|
|
470
|
-
|
|
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(
|
|
482
|
-
|
|
483
|
-
|
|
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)
|
|
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
|
-
|
|
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
|
|
519
|
-
|
|
520
|
-
VALUE
|
|
521
|
-
|
|
522
|
-
VALUE
|
|
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,
|
|
139
|
+
"all keywords required: local_sockaddr, remote_sockaddr, "
|
|
140
|
+
"server_name, transport_params, settings");
|
|
528
141
|
}
|
|
529
142
|
|
|
530
|
-
|
|
531
|
-
|
|
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 =
|
|
147
|
+
VALUE self = quic_conn_alloc(klass);
|
|
535
148
|
rb_ivar_set(self, rb_intern("@server_name"), server_name);
|
|
536
149
|
|
|
537
|
-
|
|
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
|
-
/*
|
|
542
|
-
|
|
543
|
-
|
|
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
|
-
/*
|
|
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
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
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,
|
|
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 =
|
|
180
|
+
c->conn_ref.get_conn = quic_conn_get_conn;
|
|
593
181
|
c->conn_ref.user_data = c;
|
|
594
182
|
|
|
595
|
-
c->scid.datalen =
|
|
596
|
-
if (RAND_bytes(c->scid.data,
|
|
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
|
-
|
|
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(¶ms, 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
|
-
|
|
633
|
-
|
|
634
|
-
|
|
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
|
-
&
|
|
216
|
+
NGTCP2_PROTO_VER_V1, &callbacks, &settings,
|
|
217
|
+
¶ms, 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
|
-
|
|
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 =
|
|
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
|
|
677
|
-
recv_stream_data callback pushes each new
|
|
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
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
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
|
}
|