quic 0.0.2 → 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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +24 -0
- data/LICENSE-DEPENDENCIES.txt +2 -19
- data/README.md +27 -2
- data/Rakefile +7 -0
- data/examples/echo_demo.rb +5 -6
- data/examples/echo_server_demo.rb +127 -0
- data/ext/quic/connection_client.c +23 -880
- data/ext/quic/connection_common.c +1262 -0
- data/ext/quic/connection_common.h +217 -0
- data/ext/quic/connection_server.c +186 -0
- data/ext/quic/endpoint.c +823 -0
- data/ext/quic/endpoint.h +28 -0
- data/ext/quic/ext_api.c +112 -0
- data/ext/quic/extconf.rb +12 -5
- data/ext/quic/include/quic/ext.h +84 -0
- data/ext/quic/quic.c +29 -4
- data/ext/quic/quic.h +8 -8
- data/ext/quic/stream.c +81 -20
- data/ext/quic/stream.h +13 -7
- data/ext/quic/tls_context.c +260 -0
- data/ext/quic/tls_context.h +46 -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 +13 -1
|
@@ -1,39 +1,10 @@
|
|
|
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. */
|
|
@@ -53,347 +24,6 @@ quic_get_default_store(void)
|
|
|
53
24
|
return quic_default_store;
|
|
54
25
|
}
|
|
55
26
|
|
|
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
27
|
/* Record the X509 verification result so read_pkt can raise a descriptive
|
|
398
28
|
error. The verdict itself is left to picotls: ret is returned unchanged.
|
|
399
29
|
Runs synchronously inside read_pkt with the GVL held; it only writes to the
|
|
@@ -407,7 +37,7 @@ quic_override_verify_cb(ptls_openssl_override_verify_certificate_t *self,
|
|
|
407
37
|
(void)cert;
|
|
408
38
|
(void)chain;
|
|
409
39
|
ngtcp2_crypto_conn_ref *ref = *ptls_get_data_ptr(tls);
|
|
410
|
-
|
|
40
|
+
quic_conn_t *c = (quic_conn_t *)ref->user_data;
|
|
411
41
|
if (ret != 0) {
|
|
412
42
|
c->verify_failed = true;
|
|
413
43
|
c->verify_result = ossl_ret;
|
|
@@ -419,34 +49,11 @@ static ptls_openssl_override_verify_certificate_t quic_override_verify = {
|
|
|
419
49
|
quic_override_verify_cb,
|
|
420
50
|
};
|
|
421
51
|
|
|
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
52
|
/* Install picotls's certificate verifier on c->tls_ctx according to the
|
|
446
53
|
verify_mode / ca_file / ca_path settings. Leaves verify_certificate NULL
|
|
447
54
|
for verify_mode :none, which skips verification altogether. */
|
|
448
55
|
static void
|
|
449
|
-
quic_client_setup_verify(
|
|
56
|
+
quic_client_setup_verify(quic_conn_t *c, VALUE settings_v, VALUE server_name)
|
|
450
57
|
{
|
|
451
58
|
VALUE mode = rb_funcall(settings_v, rb_intern("verify_mode"), 0);
|
|
452
59
|
/* ca_file and ca_path are not even type-checked under :none. */
|
|
@@ -455,8 +62,8 @@ quic_client_setup_verify(quic_client_t *c, VALUE settings_v, VALUE server_name)
|
|
|
455
62
|
rb_raise(rb_eArgError, "verify_mode must be :peer or :none (got %+"PRIsVALUE")", mode);
|
|
456
63
|
}
|
|
457
64
|
|
|
458
|
-
VALUE ca_file =
|
|
459
|
-
VALUE ca_path =
|
|
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");
|
|
460
67
|
|
|
461
68
|
/* An empty name makes OpenSSL clear its host list and skip the name check,
|
|
462
69
|
and both SNI and the check would silently stop at an embedded NUL. */
|
|
@@ -527,48 +134,22 @@ quic_client_open(int argc, VALUE *argv, VALUE klass)
|
|
|
527
134
|
"all keywords required: local_sockaddr, remote_sockaddr, server_name, transport_params, settings");
|
|
528
135
|
}
|
|
529
136
|
|
|
530
|
-
|
|
531
|
-
|
|
137
|
+
quic_require_sockaddr(local_sockaddr, "local_sockaddr");
|
|
138
|
+
quic_require_sockaddr(remote_sockaddr, "remote_sockaddr");
|
|
532
139
|
Check_Type(server_name, T_STRING);
|
|
533
140
|
|
|
534
|
-
VALUE self =
|
|
141
|
+
VALUE self = quic_conn_alloc(klass);
|
|
535
142
|
rb_ivar_set(self, rb_intern("@server_name"), server_name);
|
|
536
143
|
|
|
537
|
-
|
|
538
|
-
TypedData_Get_Struct(self, quic_client_t, &quic_client_data_type, c);
|
|
144
|
+
quic_conn_t *c = quic_conn_get(self);
|
|
539
145
|
c->owner = self;
|
|
146
|
+
c->is_server = false;
|
|
540
147
|
|
|
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
|
-
}
|
|
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);
|
|
570
151
|
|
|
571
|
-
/*
|
|
152
|
+
/* quic_conn_alloc already zeroed the struct, but calling ctx_init keeps
|
|
572
153
|
the ngtcp2-side initialization explicit and the TLS setup in one place. */
|
|
573
154
|
ngtcp2_crypto_picotls_ctx_init(&c->cptls);
|
|
574
155
|
c->tls_ctx = (ptls_context_t){
|
|
@@ -589,11 +170,11 @@ quic_client_open(int argc, VALUE *argv, VALUE klass)
|
|
|
589
170
|
place before it runs. */
|
|
590
171
|
quic_client_setup_verify(c, settings_v, server_name);
|
|
591
172
|
|
|
592
|
-
c->conn_ref.get_conn =
|
|
173
|
+
c->conn_ref.get_conn = quic_conn_get_conn;
|
|
593
174
|
c->conn_ref.user_data = c;
|
|
594
175
|
|
|
595
|
-
c->scid.datalen =
|
|
596
|
-
if (RAND_bytes(c->scid.data,
|
|
176
|
+
c->scid.datalen = QUIC_SCID_LEN;
|
|
177
|
+
if (RAND_bytes(c->scid.data, QUIC_SCID_LEN) != 1) {
|
|
597
178
|
rb_raise(rb_eRuntimeError, "RAND_bytes(scid) failed");
|
|
598
179
|
}
|
|
599
180
|
c->dcid.datalen = 8;
|
|
@@ -602,28 +183,13 @@ quic_client_open(int argc, VALUE *argv, VALUE klass)
|
|
|
602
183
|
}
|
|
603
184
|
|
|
604
185
|
ngtcp2_callbacks callbacks;
|
|
605
|
-
|
|
186
|
+
quic_conn_fill_common_callbacks(&callbacks);
|
|
606
187
|
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
188
|
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
189
|
|
|
625
190
|
ngtcp2_settings settings;
|
|
626
191
|
quic_fill_settings(&settings, settings_v);
|
|
192
|
+
quic_conn_open_qlog(c, settings_v, &settings);
|
|
627
193
|
|
|
628
194
|
ngtcp2_transport_params params;
|
|
629
195
|
quic_fill_transport_params(¶ms, transport_params);
|
|
@@ -673,441 +239,18 @@ quic_client_open(int argc, VALUE *argv, VALUE klass)
|
|
|
673
239
|
callback. Entries are removed in the stream_close callback. */
|
|
674
240
|
rb_ivar_set(self, rb_intern("@streams"), rb_hash_new());
|
|
675
241
|
|
|
676
|
-
/* FIFO of peer-initiated
|
|
677
|
-
recv_stream_data callback pushes each new
|
|
242
|
+
/* FIFO of peer-initiated streams awaiting #accept_stream. The
|
|
243
|
+
recv_stream_data callback pushes each new peer stream here once. */
|
|
678
244
|
rb_ivar_set(self, rb_intern("@accept_queue"), rb_ary_new());
|
|
679
245
|
|
|
680
246
|
return self;
|
|
681
247
|
}
|
|
682
248
|
|
|
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
249
|
void
|
|
1098
250
|
Init_quic_connection_client(VALUE rb_mQUICConnectionArg)
|
|
1099
251
|
{
|
|
1100
252
|
rb_cQUICConnectionClient = rb_define_class_under(rb_mQUICConnectionArg, "Client", rb_cObject);
|
|
1101
|
-
rb_define_alloc_func(rb_cQUICConnectionClient,
|
|
253
|
+
rb_define_alloc_func(rb_cQUICConnectionClient, quic_conn_alloc);
|
|
1102
254
|
rb_define_singleton_method(rb_cQUICConnectionClient, "_open", quic_client_open, -1);
|
|
1103
|
-
|
|
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);
|
|
255
|
+
quic_conn_define_shared_methods(rb_cQUICConnectionClient);
|
|
1113
256
|
}
|