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