quic 0.0.3 → 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 +9 -0
- data/README.md +76 -2
- data/examples/demo_certificates.rb +67 -0
- data/examples/echo_endpoint_demo.rb +170 -0
- data/examples/echo_server_demo.rb +2 -58
- data/ext/quic/connection_client.c +77 -59
- data/ext/quic/connection_common.c +358 -365
- data/ext/quic/connection_common.h +20 -11
- data/ext/quic/connection_server.c +67 -48
- data/ext/quic/endpoint.c +206 -200
- data/ext/quic/endpoint.h +8 -5
- data/ext/quic/ext_api.c +41 -47
- data/ext/quic/include/quic/ext.h +26 -19
- data/ext/quic/quic.c +75 -67
- data/ext/quic/quic.h +4 -2
- data/ext/quic/stream.c +55 -74
- data/ext/quic/stream.h +5 -5
- data/ext/quic/tls_context.c +79 -70
- data/ext/quic/tls_context.h +2 -1
- data/lib/quic/version.rb +1 -1
- metadata +5 -1
data/ext/quic/stream.c
CHANGED
|
@@ -5,37 +5,33 @@
|
|
|
5
5
|
|
|
6
6
|
VALUE rb_cQUICStream;
|
|
7
7
|
|
|
8
|
-
static void
|
|
9
|
-
quic_stream_free(void *ptr)
|
|
10
|
-
{
|
|
11
|
-
xfree(ptr);
|
|
12
|
-
}
|
|
8
|
+
static void quic_stream_free(void *ptr) { xfree(ptr); }
|
|
13
9
|
|
|
14
|
-
static size_t
|
|
15
|
-
quic_stream_size(const void *ptr)
|
|
16
|
-
{
|
|
10
|
+
static size_t quic_stream_size(const void *ptr) {
|
|
17
11
|
(void)ptr;
|
|
18
12
|
return sizeof(quic_stream_t);
|
|
19
13
|
}
|
|
20
14
|
|
|
21
15
|
const rb_data_type_t quic_stream_data_type = {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
16
|
+
"QUIC::Stream",
|
|
17
|
+
{
|
|
18
|
+
NULL,
|
|
19
|
+
quic_stream_free,
|
|
20
|
+
quic_stream_size,
|
|
21
|
+
},
|
|
22
|
+
NULL,
|
|
23
|
+
NULL,
|
|
24
|
+
RUBY_TYPED_FREE_IMMEDIATELY,
|
|
26
25
|
};
|
|
27
26
|
|
|
28
|
-
static VALUE
|
|
29
|
-
quic_stream_alloc(VALUE klass)
|
|
30
|
-
{
|
|
27
|
+
static VALUE quic_stream_alloc(VALUE klass) {
|
|
31
28
|
quic_stream_t *s = ALLOC(quic_stream_t);
|
|
32
29
|
memset(s, 0, sizeof(*s));
|
|
33
30
|
return TypedData_Wrap_Struct(klass, &quic_stream_data_type, s);
|
|
34
31
|
}
|
|
35
32
|
|
|
36
33
|
VALUE
|
|
37
|
-
quic_stream_new(int64_t stream_id, VALUE owner)
|
|
38
|
-
{
|
|
34
|
+
quic_stream_new(int64_t stream_id, VALUE owner) {
|
|
39
35
|
VALUE self = quic_stream_alloc(rb_cQUICStream);
|
|
40
36
|
quic_stream_t *s;
|
|
41
37
|
TypedData_Get_Struct(self, quic_stream_t, &quic_stream_data_type, s);
|
|
@@ -55,9 +51,7 @@ quic_stream_new(int64_t stream_id, VALUE owner)
|
|
|
55
51
|
quic_stream_write_nonblock_m). This helper is reached only after the
|
|
56
52
|
caller has confirmed the bytes fit in the current send window, so it
|
|
57
53
|
never raises QUIC::Error::WaitWritable on its own. */
|
|
58
|
-
static VALUE
|
|
59
|
-
quic_stream_enqueue(VALUE self, VALUE data, bool fin)
|
|
60
|
-
{
|
|
54
|
+
static VALUE quic_stream_enqueue(VALUE self, VALUE data, bool fin) {
|
|
61
55
|
quic_stream_t *s;
|
|
62
56
|
TypedData_Get_Struct(self, quic_stream_t, &quic_stream_data_type, s);
|
|
63
57
|
|
|
@@ -87,12 +81,11 @@ quic_stream_enqueue(VALUE self, VALUE data, bool fin)
|
|
|
87
81
|
return LONG2NUM(len);
|
|
88
82
|
}
|
|
89
83
|
|
|
90
|
-
static bool
|
|
91
|
-
quic_stream_fin_kwarg(int argc, VALUE *argv, VALUE *data)
|
|
92
|
-
{
|
|
84
|
+
static bool quic_stream_fin_kwarg(int argc, VALUE *argv, VALUE *data) {
|
|
93
85
|
VALUE opts = Qnil;
|
|
94
86
|
rb_scan_args(argc, argv, "1:", data, &opts);
|
|
95
|
-
if (NIL_P(opts))
|
|
87
|
+
if (NIL_P(opts))
|
|
88
|
+
return false;
|
|
96
89
|
VALUE fin = rb_hash_lookup(opts, ID2SYM(rb_intern("fin")));
|
|
97
90
|
return RTEST(fin);
|
|
98
91
|
}
|
|
@@ -102,11 +95,11 @@ quic_stream_fin_kwarg(int argc, VALUE *argv, VALUE *data)
|
|
|
102
95
|
the bytes already queued in @pending_chunks but not yet handed to ngtcp2.
|
|
103
96
|
ngtcp2 only counts what it has sent, so without the subtraction a run of
|
|
104
97
|
writes could queue far more than the peer allows. */
|
|
105
|
-
static uint64_t
|
|
106
|
-
|
|
107
|
-
{
|
|
98
|
+
static uint64_t quic_stream_window_left(VALUE self, VALUE client_v,
|
|
99
|
+
quic_stream_t *s) {
|
|
108
100
|
ngtcp2_conn *conn = quic_conn_ptr(client_v);
|
|
109
|
-
uint64_t stream_left =
|
|
101
|
+
uint64_t stream_left =
|
|
102
|
+
ngtcp2_conn_get_max_stream_data_left2(conn, s->stream_id);
|
|
110
103
|
uint64_t conn_left = ngtcp2_conn_get_max_data_left2(conn);
|
|
111
104
|
uint64_t left = stream_left < conn_left ? stream_left : conn_left;
|
|
112
105
|
|
|
@@ -126,15 +119,13 @@ quic_stream_window_left(VALUE self, VALUE client_v, quic_stream_t *s)
|
|
|
126
119
|
for the full payload, then enqueue. IO#write-compatible: always queues all
|
|
127
120
|
of `data`. Bare Streams (built via QUIC::Stream.allocate for unit tests,
|
|
128
121
|
with @client = nil) skip the window check entirely. */
|
|
129
|
-
static VALUE
|
|
130
|
-
quic_stream_write_m(int argc, VALUE *argv, VALUE self)
|
|
131
|
-
{
|
|
122
|
+
static VALUE quic_stream_write_m(int argc, VALUE *argv, VALUE self) {
|
|
132
123
|
VALUE data;
|
|
133
124
|
bool fin = quic_stream_fin_kwarg(argc, argv, &data);
|
|
134
125
|
|
|
135
126
|
VALUE client_v = rb_ivar_get(self, rb_intern("@client"));
|
|
136
127
|
if (NIL_P(client_v)) {
|
|
137
|
-
return quic_stream_enqueue(self, data, fin);
|
|
128
|
+
return quic_stream_enqueue(self, data, fin); /* bare-Stream test escape */
|
|
138
129
|
}
|
|
139
130
|
|
|
140
131
|
/* These will be re-validated by quic_stream_enqueue, but we need the
|
|
@@ -162,15 +153,13 @@ quic_stream_write_m(int argc, VALUE *argv, VALUE self)
|
|
|
162
153
|
prefix fits, FIN is NOT set on this call (caller re-issues with
|
|
163
154
|
`fin: true` once the remainder is accepted) so we don't half-commit FIN.
|
|
164
155
|
Bare Streams with @client = nil skip the window check entirely. */
|
|
165
|
-
static VALUE
|
|
166
|
-
quic_stream_write_nonblock_m(int argc, VALUE *argv, VALUE self)
|
|
167
|
-
{
|
|
156
|
+
static VALUE quic_stream_write_nonblock_m(int argc, VALUE *argv, VALUE self) {
|
|
168
157
|
VALUE data;
|
|
169
158
|
bool fin = quic_stream_fin_kwarg(argc, argv, &data);
|
|
170
159
|
|
|
171
160
|
VALUE client_v = rb_ivar_get(self, rb_intern("@client"));
|
|
172
161
|
if (NIL_P(client_v)) {
|
|
173
|
-
return quic_stream_enqueue(self, data, fin);
|
|
162
|
+
return quic_stream_enqueue(self, data, fin); /* bare-Stream test escape */
|
|
174
163
|
}
|
|
175
164
|
|
|
176
165
|
Check_Type(data, T_STRING);
|
|
@@ -195,12 +184,11 @@ quic_stream_write_nonblock_m(int argc, VALUE *argv, VALUE self)
|
|
|
195
184
|
return quic_stream_enqueue(self, slice, effective_fin);
|
|
196
185
|
}
|
|
197
186
|
|
|
198
|
-
static VALUE
|
|
199
|
-
quic_stream_close_write_m(VALUE self)
|
|
200
|
-
{
|
|
187
|
+
static VALUE quic_stream_close_write_m(VALUE self) {
|
|
201
188
|
quic_stream_t *s;
|
|
202
189
|
TypedData_Get_Struct(self, quic_stream_t, &quic_stream_data_type, s);
|
|
203
|
-
if (s->fin_sent)
|
|
190
|
+
if (s->fin_sent)
|
|
191
|
+
return Qnil; /* idempotent */
|
|
204
192
|
VALUE empty = rb_str_new("", 0);
|
|
205
193
|
rb_enc_associate_index(empty, rb_ascii8bit_encindex());
|
|
206
194
|
quic_stream_enqueue(self, empty, true);
|
|
@@ -215,9 +203,7 @@ quic_stream_close_write_m(VALUE self)
|
|
|
215
203
|
- buffer empty + !fin_received: raise QUIC::Error::WaitReadable
|
|
216
204
|
- buffer non-empty: return min(length, buffer.bytesize) bytes
|
|
217
205
|
*/
|
|
218
|
-
static VALUE
|
|
219
|
-
quic_stream_read_nonblock_m(VALUE self, VALUE length_v)
|
|
220
|
-
{
|
|
206
|
+
static VALUE quic_stream_read_nonblock_m(VALUE self, VALUE length_v) {
|
|
221
207
|
quic_stream_t *s;
|
|
222
208
|
TypedData_Get_Struct(self, quic_stream_t, &quic_stream_data_type, s);
|
|
223
209
|
|
|
@@ -241,7 +227,8 @@ quic_stream_read_nonblock_m(VALUE self, VALUE length_v)
|
|
|
241
227
|
|
|
242
228
|
/* Drop the first |take| bytes from buffer by memmove + truncate. */
|
|
243
229
|
if (take < have) {
|
|
244
|
-
memmove(RSTRING_PTR(buffer), RSTRING_PTR(buffer) + take,
|
|
230
|
+
memmove(RSTRING_PTR(buffer), RSTRING_PTR(buffer) + take,
|
|
231
|
+
(size_t)(have - take));
|
|
245
232
|
}
|
|
246
233
|
rb_str_set_len(buffer, have - take);
|
|
247
234
|
|
|
@@ -253,17 +240,17 @@ quic_stream_read_nonblock_m(VALUE self, VALUE length_v)
|
|
|
253
240
|
VALUE client_v = rb_ivar_get(self, rb_intern("@client"));
|
|
254
241
|
if (!NIL_P(client_v)) {
|
|
255
242
|
ngtcp2_conn *conn = quic_conn_ptr(client_v);
|
|
256
|
-
int rv = ngtcp2_conn_extend_max_stream_offset(conn, s->stream_id,
|
|
257
|
-
|
|
243
|
+
int rv = ngtcp2_conn_extend_max_stream_offset(conn, s->stream_id,
|
|
244
|
+
(uint64_t)take);
|
|
245
|
+
if (rv != 0)
|
|
246
|
+
quic_raise_ngtcp2_error(rv);
|
|
258
247
|
ngtcp2_conn_extend_max_offset(conn, (uint64_t)take);
|
|
259
248
|
}
|
|
260
249
|
|
|
261
250
|
return out;
|
|
262
251
|
}
|
|
263
252
|
|
|
264
|
-
static VALUE
|
|
265
|
-
quic_stream_eof_p(VALUE self)
|
|
266
|
-
{
|
|
253
|
+
static VALUE quic_stream_eof_p(VALUE self) {
|
|
267
254
|
quic_stream_t *s;
|
|
268
255
|
TypedData_Get_Struct(self, quic_stream_t, &quic_stream_data_type, s);
|
|
269
256
|
VALUE buffer = rb_ivar_get(self, rb_intern("@recv_buffer"));
|
|
@@ -275,17 +262,13 @@ quic_stream_eof_p(VALUE self)
|
|
|
275
262
|
connection closes first, since ngtcp2 does not report stream closure then.
|
|
276
263
|
Keeps returning true after the stream has left the connection's @streams.
|
|
277
264
|
A bare Stream is never closed. */
|
|
278
|
-
static VALUE
|
|
279
|
-
quic_stream_closed_p(VALUE self)
|
|
280
|
-
{
|
|
265
|
+
static VALUE quic_stream_closed_p(VALUE self) {
|
|
281
266
|
quic_stream_t *s;
|
|
282
267
|
TypedData_Get_Struct(self, quic_stream_t, &quic_stream_data_type, s);
|
|
283
268
|
return s->closed ? Qtrue : Qfalse;
|
|
284
269
|
}
|
|
285
270
|
|
|
286
|
-
static VALUE
|
|
287
|
-
quic_stream_close_read_m(VALUE self)
|
|
288
|
-
{
|
|
271
|
+
static VALUE quic_stream_close_read_m(VALUE self) {
|
|
289
272
|
quic_stream_t *s;
|
|
290
273
|
TypedData_Get_Struct(self, quic_stream_t, &quic_stream_data_type, s);
|
|
291
274
|
|
|
@@ -295,13 +278,12 @@ quic_stream_close_read_m(VALUE self)
|
|
|
295
278
|
/* ngtcp2_conn_shutdown_stream_read sends STOP_SENDING. app_error_code 0
|
|
296
279
|
since no QUIC-level error API is exposed yet. */
|
|
297
280
|
int rv = ngtcp2_conn_shutdown_stream_read(conn, 0, s->stream_id, 0);
|
|
298
|
-
if (rv != 0)
|
|
281
|
+
if (rv != 0)
|
|
282
|
+
quic_raise_ngtcp2_error(rv);
|
|
299
283
|
return Qnil;
|
|
300
284
|
}
|
|
301
285
|
|
|
302
|
-
static VALUE
|
|
303
|
-
quic_stream_close_m(VALUE self)
|
|
304
|
-
{
|
|
286
|
+
static VALUE quic_stream_close_m(VALUE self) {
|
|
305
287
|
/* Send FIN (if not yet) then STOP_SENDING. Idempotent on each leg. */
|
|
306
288
|
quic_stream_close_write_m(self);
|
|
307
289
|
quic_stream_close_read_m(self);
|
|
@@ -318,9 +300,7 @@ quic_stream_close_m(VALUE self)
|
|
|
318
300
|
A bare Stream (@client == nil, used by unit-test fixtures) skips the
|
|
319
301
|
ngtcp2 call and only flips s->reset, mirroring the #write / #close_write
|
|
320
302
|
bare-Stream escapes. */
|
|
321
|
-
static VALUE
|
|
322
|
-
quic_stream_reset_m(int argc, VALUE *argv, VALUE self)
|
|
323
|
-
{
|
|
303
|
+
static VALUE quic_stream_reset_m(int argc, VALUE *argv, VALUE self) {
|
|
324
304
|
VALUE error_code_v = Qnil;
|
|
325
305
|
rb_scan_args(argc, argv, "01", &error_code_v);
|
|
326
306
|
uint64_t error_code = NIL_P(error_code_v) ? 0 : NUM2ULL(error_code_v);
|
|
@@ -328,17 +308,19 @@ quic_stream_reset_m(int argc, VALUE *argv, VALUE self)
|
|
|
328
308
|
quic_stream_t *s;
|
|
329
309
|
TypedData_Get_Struct(self, quic_stream_t, &quic_stream_data_type, s);
|
|
330
310
|
|
|
331
|
-
if (s->reset)
|
|
311
|
+
if (s->reset)
|
|
312
|
+
return Qnil; /* idempotent */
|
|
332
313
|
|
|
333
314
|
VALUE client_v = rb_ivar_get(self, rb_intern("@client"));
|
|
334
315
|
if (NIL_P(client_v)) {
|
|
335
|
-
s->reset = true;
|
|
316
|
+
s->reset = true; /* bare Stream escape */
|
|
336
317
|
return Qnil;
|
|
337
318
|
}
|
|
338
319
|
|
|
339
320
|
ngtcp2_conn *conn = quic_conn_ptr(client_v);
|
|
340
321
|
int rv = ngtcp2_conn_shutdown_stream_write(conn, 0, s->stream_id, error_code);
|
|
341
|
-
if (rv != 0)
|
|
322
|
+
if (rv != 0)
|
|
323
|
+
quic_raise_ngtcp2_error(rv);
|
|
342
324
|
s->reset = true;
|
|
343
325
|
return Qnil;
|
|
344
326
|
}
|
|
@@ -346,9 +328,7 @@ quic_stream_reset_m(int argc, VALUE *argv, VALUE self)
|
|
|
346
328
|
/* Called by #accept_stream / #accept_stream_nonblock for the stream they
|
|
347
329
|
take off @accept_queue. A stream that closed while it waited there gives
|
|
348
330
|
the peer back the stream it used up now (see quic_stream_close_body). */
|
|
349
|
-
static VALUE
|
|
350
|
-
quic_stream_accepted_m(VALUE self)
|
|
351
|
-
{
|
|
331
|
+
static VALUE quic_stream_accepted_m(VALUE self) {
|
|
352
332
|
quic_stream_t *s;
|
|
353
333
|
TypedData_Get_Struct(self, quic_stream_t, &quic_stream_data_type, s);
|
|
354
334
|
s->accepted = true;
|
|
@@ -362,20 +342,21 @@ quic_stream_accepted_m(VALUE self)
|
|
|
362
342
|
return Qnil;
|
|
363
343
|
}
|
|
364
344
|
|
|
365
|
-
void
|
|
366
|
-
Init_quic_stream(VALUE rb_mQUICArg)
|
|
367
|
-
{
|
|
345
|
+
void Init_quic_stream(VALUE rb_mQUICArg) {
|
|
368
346
|
rb_cQUICStream = rb_define_class_under(rb_mQUICArg, "Stream", rb_cObject);
|
|
369
347
|
rb_define_alloc_func(rb_cQUICStream, quic_stream_alloc);
|
|
370
348
|
rb_define_method(rb_cQUICStream, "write", quic_stream_write_m, -1);
|
|
371
|
-
rb_define_method(rb_cQUICStream, "write_nonblock",
|
|
349
|
+
rb_define_method(rb_cQUICStream, "write_nonblock",
|
|
350
|
+
quic_stream_write_nonblock_m, -1);
|
|
372
351
|
rb_define_method(rb_cQUICStream, "close_write", quic_stream_close_write_m, 0);
|
|
373
|
-
rb_define_method(rb_cQUICStream, "read_nonblock", quic_stream_read_nonblock_m,
|
|
352
|
+
rb_define_method(rb_cQUICStream, "read_nonblock", quic_stream_read_nonblock_m,
|
|
353
|
+
1);
|
|
374
354
|
rb_define_method(rb_cQUICStream, "eof?", quic_stream_eof_p, 0);
|
|
375
355
|
rb_define_method(rb_cQUICStream, "closed?", quic_stream_closed_p, 0);
|
|
376
356
|
rb_define_method(rb_cQUICStream, "close_read", quic_stream_close_read_m, 0);
|
|
377
357
|
rb_define_method(rb_cQUICStream, "close", quic_stream_close_m, 0);
|
|
378
358
|
rb_define_method(rb_cQUICStream, "reset", quic_stream_reset_m, -1);
|
|
379
|
-
rb_define_private_method(rb_cQUICStream, "_accepted", quic_stream_accepted_m,
|
|
359
|
+
rb_define_private_method(rb_cQUICStream, "_accepted", quic_stream_accepted_m,
|
|
360
|
+
0);
|
|
380
361
|
/* #read (blocking) and #initiator are defined in lib/quic/stream.rb. */
|
|
381
362
|
}
|
data/ext/quic/stream.h
CHANGED
|
@@ -22,11 +22,11 @@ typedef struct {
|
|
|
22
22
|
/* Cumulative bytes shifted out of @pending_chunks. Always
|
|
23
23
|
<= acked_offset (we only shift a Chunk after its end is acked). */
|
|
24
24
|
uint64_t pending_shifted;
|
|
25
|
-
bool fin_received;
|
|
26
|
-
bool fin_sent;
|
|
27
|
-
bool fin_flushed;
|
|
28
|
-
bool reset;
|
|
29
|
-
bool closed;
|
|
25
|
+
bool fin_received; /* recv_stream_data delivered FIN */
|
|
26
|
+
bool fin_sent; /* #write(fin: true) queued FIN */
|
|
27
|
+
bool fin_flushed; /* write_pkt actually wrote the FIN frame */
|
|
28
|
+
bool reset; /* stream_reset callback fired */
|
|
29
|
+
bool closed; /* stream_close callback fired */
|
|
30
30
|
uint64_t close_app_error_code;
|
|
31
31
|
bool close_has_app_error_code;
|
|
32
32
|
/* Set once a peer-initiated stream has been pushed onto the owning
|
data/ext/quic/tls_context.c
CHANGED
|
@@ -11,20 +11,17 @@
|
|
|
11
11
|
|
|
12
12
|
VALUE rb_cQUICTLSContext;
|
|
13
13
|
|
|
14
|
-
void
|
|
15
|
-
quic_tls_ctx_ref(quic_tls_ctx_t *t)
|
|
16
|
-
{
|
|
17
|
-
t->refcnt++;
|
|
18
|
-
}
|
|
14
|
+
void quic_tls_ctx_ref(quic_tls_ctx_t *t) { t->refcnt++; }
|
|
19
15
|
|
|
20
|
-
void
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
if (
|
|
24
|
-
|
|
16
|
+
void quic_tls_ctx_unref(quic_tls_ctx_t *t) {
|
|
17
|
+
if (--t->refcnt > 0)
|
|
18
|
+
return;
|
|
19
|
+
if (t->sign_cert_initialized)
|
|
20
|
+
ptls_openssl_dispose_sign_certificate(&t->sign_cert);
|
|
25
21
|
/* picotls malloc()ed the list and each entry, so free(), not xfree(). */
|
|
26
22
|
if (t->ctx.certificates.list != NULL) {
|
|
27
|
-
for (size_t i = 0; i < t->ctx.certificates.count; i++)
|
|
23
|
+
for (size_t i = 0; i < t->ctx.certificates.count; i++)
|
|
24
|
+
free(t->ctx.certificates.list[i].base);
|
|
28
25
|
free(t->ctx.certificates.list);
|
|
29
26
|
}
|
|
30
27
|
xfree(t->alpn);
|
|
@@ -34,30 +31,31 @@ quic_tls_ctx_unref(quic_tls_ctx_t *t)
|
|
|
34
31
|
|
|
35
32
|
/* The QUIC::TLSContext object holds one reference. A context it never
|
|
36
33
|
finished building (an exception mid-way) is released the same way. */
|
|
37
|
-
static void
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
if (ptr != NULL) quic_tls_ctx_unref((quic_tls_ctx_t *)ptr);
|
|
34
|
+
static void quic_tls_context_free(void *ptr) {
|
|
35
|
+
if (ptr != NULL)
|
|
36
|
+
quic_tls_ctx_unref((quic_tls_ctx_t *)ptr);
|
|
41
37
|
}
|
|
42
38
|
|
|
43
|
-
static size_t
|
|
44
|
-
quic_tls_context_size(const void *ptr)
|
|
45
|
-
{
|
|
39
|
+
static size_t quic_tls_context_size(const void *ptr) {
|
|
46
40
|
(void)ptr;
|
|
47
41
|
return sizeof(quic_tls_ctx_t);
|
|
48
42
|
}
|
|
49
43
|
|
|
50
44
|
static const rb_data_type_t quic_tls_context_type = {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
45
|
+
"QUIC::TLSContext",
|
|
46
|
+
{
|
|
47
|
+
NULL,
|
|
48
|
+
quic_tls_context_free,
|
|
49
|
+
quic_tls_context_size,
|
|
50
|
+
},
|
|
51
|
+
NULL,
|
|
52
|
+
NULL,
|
|
53
|
+
RUBY_TYPED_FREE_IMMEDIATELY,
|
|
55
54
|
};
|
|
56
55
|
|
|
57
|
-
quic_tls_ctx_t *
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
return (quic_tls_ctx_t *)rb_check_typeddata(tls_context, &quic_tls_context_type);
|
|
56
|
+
quic_tls_ctx_t *quic_tls_context_get(VALUE tls_context) {
|
|
57
|
+
return (quic_tls_ctx_t *)rb_check_typeddata(tls_context,
|
|
58
|
+
&quic_tls_context_type);
|
|
61
59
|
}
|
|
62
60
|
|
|
63
61
|
/* Pick the first of the server's ALPN protocols, in the server's order of
|
|
@@ -65,16 +63,19 @@ quic_tls_context_get(VALUE tls_context)
|
|
|
65
63
|
held and never touches Ruby objects. */
|
|
66
64
|
static int
|
|
67
65
|
quic_tls_on_client_hello_cb(ptls_on_client_hello_t *self, ptls_t *tls,
|
|
68
|
-
ptls_on_client_hello_parameters_t *params)
|
|
69
|
-
|
|
70
|
-
|
|
66
|
+
ptls_on_client_hello_parameters_t *params) {
|
|
67
|
+
quic_tls_ctx_t *t =
|
|
68
|
+
(quic_tls_ctx_t *)((char *)self -
|
|
69
|
+
offsetof(quic_tls_ctx_t, on_client_hello));
|
|
71
70
|
for (size_t i = 0; i < t->alpn_count; i++) {
|
|
72
71
|
for (size_t j = 0; j < params->negotiated_protocols.count; j++) {
|
|
73
72
|
ptls_iovec_t offered = params->negotiated_protocols.list[j];
|
|
74
73
|
if (offered.len == t->alpn[i].len &&
|
|
75
74
|
memcmp(offered.base, t->alpn[i].base, offered.len) == 0) {
|
|
76
|
-
return ptls_set_negotiated_protocol(tls, (const char *)offered.base,
|
|
77
|
-
|
|
75
|
+
return ptls_set_negotiated_protocol(tls, (const char *)offered.base,
|
|
76
|
+
offered.len) == 0
|
|
77
|
+
? 0
|
|
78
|
+
: PTLS_ERROR_NO_MEMORY;
|
|
78
79
|
}
|
|
79
80
|
}
|
|
80
81
|
}
|
|
@@ -84,9 +85,8 @@ quic_tls_on_client_hello_cb(ptls_on_client_hello_t *self, ptls_t *tls,
|
|
|
84
85
|
/* Never supply a passphrase. Without a callback, PEM_read_bio_PrivateKey
|
|
85
86
|
falls back to PEM_def_callback, which prompts on the controlling terminal;
|
|
86
87
|
this way an encrypted key fails to load like any other unreadable one. */
|
|
87
|
-
static int
|
|
88
|
-
|
|
89
|
-
{
|
|
88
|
+
static int quic_tls_no_passphrase_cb(char *buf, int size, int rwflag,
|
|
89
|
+
void *userdata) {
|
|
90
90
|
(void)buf;
|
|
91
91
|
(void)size;
|
|
92
92
|
(void)rwflag;
|
|
@@ -100,10 +100,9 @@ quic_tls_no_passphrase_cb(char *buf, int size, int rwflag, void *userdata)
|
|
|
100
100
|
raises ArgumentError naming the step that failed. Everything allocated
|
|
101
101
|
here is released before raising, and libcrypto's error queue, which is
|
|
102
102
|
shared with Ruby's openssl extension, is left empty on every return. */
|
|
103
|
-
static void
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
{
|
|
103
|
+
static void quic_tls_ctx_load_certificate_and_key(quic_tls_ctx_t *t,
|
|
104
|
+
VALUE certificate_path,
|
|
105
|
+
VALUE private_key_path) {
|
|
107
106
|
VALUE failure_class = rb_eArgError;
|
|
108
107
|
const char *failure = NULL;
|
|
109
108
|
VALUE failed_path = Qnil;
|
|
@@ -118,7 +117,8 @@ quic_tls_ctx_load_certificate_and_key(quic_tls_ctx_t *t, VALUE certificate_path,
|
|
|
118
117
|
ERR_clear_error();
|
|
119
118
|
|
|
120
119
|
cert_bio = BIO_new_file(RSTRING_PTR(certificate_path), "r");
|
|
121
|
-
if (cert_bio == NULL ||
|
|
120
|
+
if (cert_bio == NULL ||
|
|
121
|
+
(leaf = PEM_read_bio_X509(cert_bio, NULL, NULL, NULL)) == NULL) {
|
|
122
122
|
failure = "failed to load certificate";
|
|
123
123
|
failed_path = certificate_path;
|
|
124
124
|
goto done;
|
|
@@ -138,9 +138,11 @@ quic_tls_ctx_load_certificate_and_key(quic_tls_ctx_t *t, VALUE certificate_path,
|
|
|
138
138
|
}
|
|
139
139
|
}
|
|
140
140
|
/* The loop ends with PEM_R_NO_START_LINE once no PEM block is left. Any
|
|
141
|
-
other error means a broken block, as in SSL_CTX_use_certificate_chain_file.
|
|
141
|
+
other error means a broken block, as in SSL_CTX_use_certificate_chain_file.
|
|
142
|
+
*/
|
|
142
143
|
err = ERR_peek_last_error();
|
|
143
|
-
if (ERR_GET_LIB(err) != ERR_LIB_PEM ||
|
|
144
|
+
if (ERR_GET_LIB(err) != ERR_LIB_PEM ||
|
|
145
|
+
ERR_GET_REASON(err) != PEM_R_NO_START_LINE) {
|
|
144
146
|
failure = "failed to load certificate";
|
|
145
147
|
failed_path = certificate_path;
|
|
146
148
|
goto done;
|
|
@@ -148,7 +150,8 @@ quic_tls_ctx_load_certificate_and_key(quic_tls_ctx_t *t, VALUE certificate_path,
|
|
|
148
150
|
|
|
149
151
|
key_bio = BIO_new_file(RSTRING_PTR(private_key_path), "r");
|
|
150
152
|
if (key_bio == NULL ||
|
|
151
|
-
(pkey = PEM_read_bio_PrivateKey(key_bio, NULL, quic_tls_no_passphrase_cb,
|
|
153
|
+
(pkey = PEM_read_bio_PrivateKey(key_bio, NULL, quic_tls_no_passphrase_cb,
|
|
154
|
+
NULL)) == NULL) {
|
|
152
155
|
failure = "failed to load private key";
|
|
153
156
|
failed_path = private_key_path;
|
|
154
157
|
goto done;
|
|
@@ -186,22 +189,27 @@ done:
|
|
|
186
189
|
BIO_free(key_bio);
|
|
187
190
|
ERR_clear_error();
|
|
188
191
|
|
|
189
|
-
if (failure == NULL)
|
|
190
|
-
|
|
191
|
-
|
|
192
|
+
if (failure == NULL)
|
|
193
|
+
return;
|
|
194
|
+
if (NIL_P(failed_path))
|
|
195
|
+
rb_raise(failure_class, "%s", failure);
|
|
196
|
+
rb_raise(failure_class, "%s: %" PRIsVALUE, failure, failed_path);
|
|
192
197
|
}
|
|
193
198
|
|
|
194
199
|
VALUE
|
|
195
|
-
quic_tls_context_server_new(VALUE certificate_path, VALUE private_key_path,
|
|
196
|
-
{
|
|
200
|
+
quic_tls_context_server_new(VALUE certificate_path, VALUE private_key_path,
|
|
201
|
+
VALUE alpn_ary) {
|
|
197
202
|
certificate_path = quic_path_value(certificate_path, "certificate_path");
|
|
198
203
|
private_key_path = quic_path_value(private_key_path, "private_key_path");
|
|
199
|
-
if (NIL_P(certificate_path))
|
|
200
|
-
|
|
204
|
+
if (NIL_P(certificate_path))
|
|
205
|
+
rb_raise(rb_eArgError, "certificate_path must not be nil");
|
|
206
|
+
if (NIL_P(private_key_path))
|
|
207
|
+
rb_raise(rb_eArgError, "private_key_path must not be nil");
|
|
201
208
|
|
|
202
209
|
/* The object owns the context from the start, so that an exception below
|
|
203
210
|
releases whatever was built so far. */
|
|
204
|
-
VALUE self =
|
|
211
|
+
VALUE self =
|
|
212
|
+
TypedData_Wrap_Struct(rb_cQUICTLSContext, &quic_tls_context_type, NULL);
|
|
205
213
|
quic_tls_ctx_t *t = ZALLOC(quic_tls_ctx_t);
|
|
206
214
|
t->refcnt = 1;
|
|
207
215
|
DATA_PTR(self) = t;
|
|
@@ -209,21 +217,23 @@ quic_tls_context_server_new(VALUE certificate_path, VALUE private_key_path, VALU
|
|
|
209
217
|
/* Without an ALPN to pick, every handshake would end in
|
|
210
218
|
no_application_protocol. */
|
|
211
219
|
quic_copy_alpn(alpn_ary, &t->alpn, &t->alpn_count, &t->alpn_buf);
|
|
212
|
-
if (t->alpn_count == 0)
|
|
220
|
+
if (t->alpn_count == 0)
|
|
221
|
+
rb_raise(rb_eArgError, "alpn must not be empty for a server");
|
|
213
222
|
|
|
214
223
|
t->ctx = (ptls_context_t){
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
+
.random_bytes = ptls_openssl_random_bytes,
|
|
225
|
+
.get_time = &ptls_get_time,
|
|
226
|
+
.key_exchanges = quic_key_exchanges,
|
|
227
|
+
.cipher_suites = quic_cipher_suites,
|
|
228
|
+
/* No session resumption: encrypt_ticket stays NULL, so no ticket is
|
|
229
|
+
ever issued. Keep this so that PSK-only resumption is refused once
|
|
230
|
+
resumption is added. */
|
|
231
|
+
.require_dhe_on_psk = 1,
|
|
232
|
+
.server_cipher_preference = 1,
|
|
224
233
|
};
|
|
225
234
|
if (ngtcp2_crypto_picotls_configure_server_context(&t->ctx) != 0) {
|
|
226
|
-
rb_raise(rb_eRuntimeError,
|
|
235
|
+
rb_raise(rb_eRuntimeError,
|
|
236
|
+
"ngtcp2_crypto_picotls_configure_server_context failed");
|
|
227
237
|
}
|
|
228
238
|
/* verify_certificate stays NULL: no client certificate is requested. */
|
|
229
239
|
quic_tls_ctx_load_certificate_and_key(t, certificate_path, private_key_path);
|
|
@@ -234,13 +244,12 @@ quic_tls_context_server_new(VALUE certificate_path, VALUE private_key_path, VALU
|
|
|
234
244
|
|
|
235
245
|
/* QUIC::TLSContext.server(certificate_path:, private_key_path:, alpn:). A
|
|
236
246
|
missing or unknown keyword raises ArgumentError as a Ruby method would. */
|
|
237
|
-
static VALUE
|
|
238
|
-
quic_tls_context_s_server(int argc, VALUE *argv, VALUE klass)
|
|
239
|
-
{
|
|
247
|
+
static VALUE quic_tls_context_s_server(int argc, VALUE *argv, VALUE klass) {
|
|
240
248
|
(void)klass;
|
|
241
249
|
VALUE opts = Qnil;
|
|
242
250
|
rb_scan_args(argc, argv, "0:", &opts);
|
|
243
|
-
if (NIL_P(opts))
|
|
251
|
+
if (NIL_P(opts))
|
|
252
|
+
opts = rb_hash_new();
|
|
244
253
|
|
|
245
254
|
ID keywords[3];
|
|
246
255
|
VALUE values[3];
|
|
@@ -251,10 +260,10 @@ quic_tls_context_s_server(int argc, VALUE *argv, VALUE klass)
|
|
|
251
260
|
return quic_tls_context_server_new(values[0], values[1], values[2]);
|
|
252
261
|
}
|
|
253
262
|
|
|
254
|
-
void
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
rb_cQUICTLSContext = rb_define_class_under(rb_mQUICArg, "TLSContext", rb_cObject);
|
|
263
|
+
void Init_quic_tls_context(VALUE rb_mQUICArg) {
|
|
264
|
+
rb_cQUICTLSContext =
|
|
265
|
+
rb_define_class_under(rb_mQUICArg, "TLSContext", rb_cObject);
|
|
258
266
|
rb_undef_alloc_func(rb_cQUICTLSContext);
|
|
259
|
-
rb_define_singleton_method(rb_cQUICTLSContext, "server",
|
|
267
|
+
rb_define_singleton_method(rb_cQUICTLSContext, "server",
|
|
268
|
+
quic_tls_context_s_server, -1);
|
|
260
269
|
}
|
data/ext/quic/tls_context.h
CHANGED
|
@@ -34,7 +34,8 @@ void Init_quic_tls_context(VALUE rb_mQUICArg);
|
|
|
34
34
|
holding the one reference to a new context. alpn_ary must be a non-empty
|
|
35
35
|
Array of 1-255 byte Strings. Raises ArgumentError naming what could not
|
|
36
36
|
be loaded or used. */
|
|
37
|
-
VALUE quic_tls_context_server_new(VALUE certificate_path,
|
|
37
|
+
VALUE quic_tls_context_server_new(VALUE certificate_path,
|
|
38
|
+
VALUE private_key_path, VALUE alpn_ary);
|
|
38
39
|
|
|
39
40
|
/* The context behind a QUIC::TLSContext object. Raises TypeError for any
|
|
40
41
|
other object. */
|
data/lib/quic/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: quic
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.0.
|
|
4
|
+
version: 0.0.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Yusuke Nakamura
|
|
@@ -36,14 +36,18 @@ extensions:
|
|
|
36
36
|
- ext/quic/extconf.rb
|
|
37
37
|
extra_rdoc_files: []
|
|
38
38
|
files:
|
|
39
|
+
- ".clang-format"
|
|
40
|
+
- ".git-blame-ignore-revs"
|
|
39
41
|
- CHANGELOG.md
|
|
40
42
|
- CODE_OF_CONDUCT.md
|
|
41
43
|
- LICENSE-DEPENDENCIES.txt
|
|
42
44
|
- LICENSE.txt
|
|
43
45
|
- README.md
|
|
44
46
|
- Rakefile
|
|
47
|
+
- examples/demo_certificates.rb
|
|
45
48
|
- examples/doq_demo.rb
|
|
46
49
|
- examples/echo_demo.rb
|
|
50
|
+
- examples/echo_endpoint_demo.rb
|
|
47
51
|
- examples/echo_server_demo.rb
|
|
48
52
|
- examples/handshake_demo.rb
|
|
49
53
|
- examples/io_loop_demo.rb
|