quic 0.0.3 → 0.0.5
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 +15 -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 +423 -377
- 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 +80 -80
- data/ext/quic/stream.h +11 -5
- data/ext/quic/tls_context.c +79 -70
- data/ext/quic/tls_context.h +2 -1
- data/lib/quic/version.rb +1 -1
- data/sig/quic.rbs +8 -4
- 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);
|
|
@@ -49,21 +45,30 @@ quic_stream_new(int64_t stream_id, VALUE owner)
|
|
|
49
45
|
return self;
|
|
50
46
|
}
|
|
51
47
|
|
|
48
|
+
/* Raise QUIC::Error::StreamClosed if nothing more can be written to the
|
|
49
|
+
stream: it was reset or has closed, or the peer sent STOP_SENDING. */
|
|
50
|
+
static void quic_stream_check_writable(quic_stream_t *s) {
|
|
51
|
+
if (s->reset || s->closed) {
|
|
52
|
+
rb_raise(rb_eQUICErrorStreamClosed, "stream is closed");
|
|
53
|
+
}
|
|
54
|
+
if (s->stopped) {
|
|
55
|
+
rb_raise(rb_eQUICErrorStreamClosed,
|
|
56
|
+
"the peer asked to stop sending (error code %llu)",
|
|
57
|
+
(unsigned long long)s->stop_app_error_code);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
52
61
|
/* Append a binary copy of |data| to @pending_chunks and optionally set the
|
|
53
62
|
fin_sent flag. Flow control is the caller's concern (#write blocks in
|
|
54
63
|
quic_stream_write_m and #write_nonblock returns a partial byte count in
|
|
55
64
|
quic_stream_write_nonblock_m). This helper is reached only after the
|
|
56
65
|
caller has confirmed the bytes fit in the current send window, so it
|
|
57
66
|
never raises QUIC::Error::WaitWritable on its own. */
|
|
58
|
-
static VALUE
|
|
59
|
-
quic_stream_enqueue(VALUE self, VALUE data, bool fin)
|
|
60
|
-
{
|
|
67
|
+
static VALUE quic_stream_enqueue(VALUE self, VALUE data, bool fin) {
|
|
61
68
|
quic_stream_t *s;
|
|
62
69
|
TypedData_Get_Struct(self, quic_stream_t, &quic_stream_data_type, s);
|
|
63
70
|
|
|
64
|
-
|
|
65
|
-
rb_raise(rb_eQUICErrorStreamClosed, "stream is closed");
|
|
66
|
-
}
|
|
71
|
+
quic_stream_check_writable(s);
|
|
67
72
|
if (s->fin_sent) {
|
|
68
73
|
rb_raise(rb_eQUICErrorStreamClosed, "stream FIN already sent");
|
|
69
74
|
}
|
|
@@ -87,12 +92,11 @@ quic_stream_enqueue(VALUE self, VALUE data, bool fin)
|
|
|
87
92
|
return LONG2NUM(len);
|
|
88
93
|
}
|
|
89
94
|
|
|
90
|
-
static bool
|
|
91
|
-
quic_stream_fin_kwarg(int argc, VALUE *argv, VALUE *data)
|
|
92
|
-
{
|
|
95
|
+
static bool quic_stream_fin_kwarg(int argc, VALUE *argv, VALUE *data) {
|
|
93
96
|
VALUE opts = Qnil;
|
|
94
97
|
rb_scan_args(argc, argv, "1:", data, &opts);
|
|
95
|
-
if (NIL_P(opts))
|
|
98
|
+
if (NIL_P(opts))
|
|
99
|
+
return false;
|
|
96
100
|
VALUE fin = rb_hash_lookup(opts, ID2SYM(rb_intern("fin")));
|
|
97
101
|
return RTEST(fin);
|
|
98
102
|
}
|
|
@@ -101,12 +105,14 @@ quic_stream_fin_kwarg(int argc, VALUE *argv, VALUE *data)
|
|
|
101
105
|
per-stream and the per-connection flow control limits ngtcp2 tracks, less
|
|
102
106
|
the bytes already queued in @pending_chunks but not yet handed to ngtcp2.
|
|
103
107
|
ngtcp2 only counts what it has sent, so without the subtraction a run of
|
|
104
|
-
writes could queue far more than the peer allows.
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
+
writes could queue far more than the peer allows. Callers check the
|
|
109
|
+
stream is writable first: a stopped stream's @pending_chunks no longer
|
|
110
|
+
matches its offsets. */
|
|
111
|
+
static uint64_t quic_stream_window_left(VALUE self, VALUE client_v,
|
|
112
|
+
quic_stream_t *s) {
|
|
108
113
|
ngtcp2_conn *conn = quic_conn_ptr(client_v);
|
|
109
|
-
uint64_t stream_left =
|
|
114
|
+
uint64_t stream_left =
|
|
115
|
+
ngtcp2_conn_get_max_stream_data_left2(conn, s->stream_id);
|
|
110
116
|
uint64_t conn_left = ngtcp2_conn_get_max_data_left2(conn);
|
|
111
117
|
uint64_t left = stream_left < conn_left ? stream_left : conn_left;
|
|
112
118
|
|
|
@@ -124,17 +130,17 @@ quic_stream_window_left(VALUE self, VALUE client_v, quic_stream_t *s)
|
|
|
124
130
|
/* Blocking write: block by repeatedly invoking #pump_once on the owning
|
|
125
131
|
connection (@client) until the peer's flow control window has enough room
|
|
126
132
|
for the full payload, then enqueue. IO#write-compatible: always queues all
|
|
127
|
-
of `data`.
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
{
|
|
133
|
+
of `data`. A window that can no longer open, because the peer sent
|
|
134
|
+
STOP_SENDING or the stream ended meanwhile, raises
|
|
135
|
+
QUIC::Error::StreamClosed. Bare Streams (built via QUIC::Stream.allocate
|
|
136
|
+
for unit tests, with @client = nil) skip the window check entirely. */
|
|
137
|
+
static VALUE quic_stream_write_m(int argc, VALUE *argv, VALUE self) {
|
|
132
138
|
VALUE data;
|
|
133
139
|
bool fin = quic_stream_fin_kwarg(argc, argv, &data);
|
|
134
140
|
|
|
135
141
|
VALUE client_v = rb_ivar_get(self, rb_intern("@client"));
|
|
136
142
|
if (NIL_P(client_v)) {
|
|
137
|
-
return quic_stream_enqueue(self, data, fin);
|
|
143
|
+
return quic_stream_enqueue(self, data, fin); /* bare-Stream test escape */
|
|
138
144
|
}
|
|
139
145
|
|
|
140
146
|
/* These will be re-validated by quic_stream_enqueue, but we need the
|
|
@@ -148,8 +154,10 @@ quic_stream_write_m(int argc, VALUE *argv, VALUE self)
|
|
|
148
154
|
/* Loop until the full payload would fit in the current window. pump_once
|
|
149
155
|
raises QUIC::Error::NotBound if the connection has not been #bind'ed;
|
|
150
156
|
that error surfaces verbatim. */
|
|
157
|
+
quic_stream_check_writable(s);
|
|
151
158
|
while ((uint64_t)needed > quic_stream_window_left(self, client_v, s)) {
|
|
152
159
|
rb_funcall(client_v, rb_intern("pump_once"), 0);
|
|
160
|
+
quic_stream_check_writable(s);
|
|
153
161
|
}
|
|
154
162
|
}
|
|
155
163
|
|
|
@@ -162,15 +170,13 @@ quic_stream_write_m(int argc, VALUE *argv, VALUE self)
|
|
|
162
170
|
prefix fits, FIN is NOT set on this call (caller re-issues with
|
|
163
171
|
`fin: true` once the remainder is accepted) so we don't half-commit FIN.
|
|
164
172
|
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
|
-
{
|
|
173
|
+
static VALUE quic_stream_write_nonblock_m(int argc, VALUE *argv, VALUE self) {
|
|
168
174
|
VALUE data;
|
|
169
175
|
bool fin = quic_stream_fin_kwarg(argc, argv, &data);
|
|
170
176
|
|
|
171
177
|
VALUE client_v = rb_ivar_get(self, rb_intern("@client"));
|
|
172
178
|
if (NIL_P(client_v)) {
|
|
173
|
-
return quic_stream_enqueue(self, data, fin);
|
|
179
|
+
return quic_stream_enqueue(self, data, fin); /* bare-Stream test escape */
|
|
174
180
|
}
|
|
175
181
|
|
|
176
182
|
Check_Type(data, T_STRING);
|
|
@@ -183,6 +189,8 @@ quic_stream_write_nonblock_m(int argc, VALUE *argv, VALUE self)
|
|
|
183
189
|
|
|
184
190
|
quic_stream_t *s;
|
|
185
191
|
TypedData_Get_Struct(self, quic_stream_t, &quic_stream_data_type, s);
|
|
192
|
+
/* Before the window: a stream that cannot be written to never gets one. */
|
|
193
|
+
quic_stream_check_writable(s);
|
|
186
194
|
uint64_t avail = quic_stream_window_left(self, client_v, s);
|
|
187
195
|
|
|
188
196
|
if (avail == 0) {
|
|
@@ -195,12 +203,11 @@ quic_stream_write_nonblock_m(int argc, VALUE *argv, VALUE self)
|
|
|
195
203
|
return quic_stream_enqueue(self, slice, effective_fin);
|
|
196
204
|
}
|
|
197
205
|
|
|
198
|
-
static VALUE
|
|
199
|
-
quic_stream_close_write_m(VALUE self)
|
|
200
|
-
{
|
|
206
|
+
static VALUE quic_stream_close_write_m(VALUE self) {
|
|
201
207
|
quic_stream_t *s;
|
|
202
208
|
TypedData_Get_Struct(self, quic_stream_t, &quic_stream_data_type, s);
|
|
203
|
-
if (s->fin_sent)
|
|
209
|
+
if (s->fin_sent)
|
|
210
|
+
return Qnil; /* idempotent */
|
|
204
211
|
VALUE empty = rb_str_new("", 0);
|
|
205
212
|
rb_enc_associate_index(empty, rb_ascii8bit_encindex());
|
|
206
213
|
quic_stream_enqueue(self, empty, true);
|
|
@@ -215,9 +222,7 @@ quic_stream_close_write_m(VALUE self)
|
|
|
215
222
|
- buffer empty + !fin_received: raise QUIC::Error::WaitReadable
|
|
216
223
|
- buffer non-empty: return min(length, buffer.bytesize) bytes
|
|
217
224
|
*/
|
|
218
|
-
static VALUE
|
|
219
|
-
quic_stream_read_nonblock_m(VALUE self, VALUE length_v)
|
|
220
|
-
{
|
|
225
|
+
static VALUE quic_stream_read_nonblock_m(VALUE self, VALUE length_v) {
|
|
221
226
|
quic_stream_t *s;
|
|
222
227
|
TypedData_Get_Struct(self, quic_stream_t, &quic_stream_data_type, s);
|
|
223
228
|
|
|
@@ -241,7 +246,8 @@ quic_stream_read_nonblock_m(VALUE self, VALUE length_v)
|
|
|
241
246
|
|
|
242
247
|
/* Drop the first |take| bytes from buffer by memmove + truncate. */
|
|
243
248
|
if (take < have) {
|
|
244
|
-
memmove(RSTRING_PTR(buffer), RSTRING_PTR(buffer) + take,
|
|
249
|
+
memmove(RSTRING_PTR(buffer), RSTRING_PTR(buffer) + take,
|
|
250
|
+
(size_t)(have - take));
|
|
245
251
|
}
|
|
246
252
|
rb_str_set_len(buffer, have - take);
|
|
247
253
|
|
|
@@ -253,17 +259,17 @@ quic_stream_read_nonblock_m(VALUE self, VALUE length_v)
|
|
|
253
259
|
VALUE client_v = rb_ivar_get(self, rb_intern("@client"));
|
|
254
260
|
if (!NIL_P(client_v)) {
|
|
255
261
|
ngtcp2_conn *conn = quic_conn_ptr(client_v);
|
|
256
|
-
int rv = ngtcp2_conn_extend_max_stream_offset(conn, s->stream_id,
|
|
257
|
-
|
|
262
|
+
int rv = ngtcp2_conn_extend_max_stream_offset(conn, s->stream_id,
|
|
263
|
+
(uint64_t)take);
|
|
264
|
+
if (rv != 0)
|
|
265
|
+
quic_raise_ngtcp2_error(rv);
|
|
258
266
|
ngtcp2_conn_extend_max_offset(conn, (uint64_t)take);
|
|
259
267
|
}
|
|
260
268
|
|
|
261
269
|
return out;
|
|
262
270
|
}
|
|
263
271
|
|
|
264
|
-
static VALUE
|
|
265
|
-
quic_stream_eof_p(VALUE self)
|
|
266
|
-
{
|
|
272
|
+
static VALUE quic_stream_eof_p(VALUE self) {
|
|
267
273
|
quic_stream_t *s;
|
|
268
274
|
TypedData_Get_Struct(self, quic_stream_t, &quic_stream_data_type, s);
|
|
269
275
|
VALUE buffer = rb_ivar_get(self, rb_intern("@recv_buffer"));
|
|
@@ -275,17 +281,13 @@ quic_stream_eof_p(VALUE self)
|
|
|
275
281
|
connection closes first, since ngtcp2 does not report stream closure then.
|
|
276
282
|
Keeps returning true after the stream has left the connection's @streams.
|
|
277
283
|
A bare Stream is never closed. */
|
|
278
|
-
static VALUE
|
|
279
|
-
quic_stream_closed_p(VALUE self)
|
|
280
|
-
{
|
|
284
|
+
static VALUE quic_stream_closed_p(VALUE self) {
|
|
281
285
|
quic_stream_t *s;
|
|
282
286
|
TypedData_Get_Struct(self, quic_stream_t, &quic_stream_data_type, s);
|
|
283
287
|
return s->closed ? Qtrue : Qfalse;
|
|
284
288
|
}
|
|
285
289
|
|
|
286
|
-
static VALUE
|
|
287
|
-
quic_stream_close_read_m(VALUE self)
|
|
288
|
-
{
|
|
290
|
+
static VALUE quic_stream_close_read_m(VALUE self) {
|
|
289
291
|
quic_stream_t *s;
|
|
290
292
|
TypedData_Get_Struct(self, quic_stream_t, &quic_stream_data_type, s);
|
|
291
293
|
|
|
@@ -295,13 +297,12 @@ quic_stream_close_read_m(VALUE self)
|
|
|
295
297
|
/* ngtcp2_conn_shutdown_stream_read sends STOP_SENDING. app_error_code 0
|
|
296
298
|
since no QUIC-level error API is exposed yet. */
|
|
297
299
|
int rv = ngtcp2_conn_shutdown_stream_read(conn, 0, s->stream_id, 0);
|
|
298
|
-
if (rv != 0)
|
|
300
|
+
if (rv != 0)
|
|
301
|
+
quic_raise_ngtcp2_error(rv);
|
|
299
302
|
return Qnil;
|
|
300
303
|
}
|
|
301
304
|
|
|
302
|
-
static VALUE
|
|
303
|
-
quic_stream_close_m(VALUE self)
|
|
304
|
-
{
|
|
305
|
+
static VALUE quic_stream_close_m(VALUE self) {
|
|
305
306
|
/* Send FIN (if not yet) then STOP_SENDING. Idempotent on each leg. */
|
|
306
307
|
quic_stream_close_write_m(self);
|
|
307
308
|
quic_stream_close_read_m(self);
|
|
@@ -318,9 +319,7 @@ quic_stream_close_m(VALUE self)
|
|
|
318
319
|
A bare Stream (@client == nil, used by unit-test fixtures) skips the
|
|
319
320
|
ngtcp2 call and only flips s->reset, mirroring the #write / #close_write
|
|
320
321
|
bare-Stream escapes. */
|
|
321
|
-
static VALUE
|
|
322
|
-
quic_stream_reset_m(int argc, VALUE *argv, VALUE self)
|
|
323
|
-
{
|
|
322
|
+
static VALUE quic_stream_reset_m(int argc, VALUE *argv, VALUE self) {
|
|
324
323
|
VALUE error_code_v = Qnil;
|
|
325
324
|
rb_scan_args(argc, argv, "01", &error_code_v);
|
|
326
325
|
uint64_t error_code = NIL_P(error_code_v) ? 0 : NUM2ULL(error_code_v);
|
|
@@ -328,17 +327,19 @@ quic_stream_reset_m(int argc, VALUE *argv, VALUE self)
|
|
|
328
327
|
quic_stream_t *s;
|
|
329
328
|
TypedData_Get_Struct(self, quic_stream_t, &quic_stream_data_type, s);
|
|
330
329
|
|
|
331
|
-
if (s->reset)
|
|
330
|
+
if (s->reset)
|
|
331
|
+
return Qnil; /* idempotent */
|
|
332
332
|
|
|
333
333
|
VALUE client_v = rb_ivar_get(self, rb_intern("@client"));
|
|
334
334
|
if (NIL_P(client_v)) {
|
|
335
|
-
s->reset = true;
|
|
335
|
+
s->reset = true; /* bare Stream escape */
|
|
336
336
|
return Qnil;
|
|
337
337
|
}
|
|
338
338
|
|
|
339
339
|
ngtcp2_conn *conn = quic_conn_ptr(client_v);
|
|
340
340
|
int rv = ngtcp2_conn_shutdown_stream_write(conn, 0, s->stream_id, error_code);
|
|
341
|
-
if (rv != 0)
|
|
341
|
+
if (rv != 0)
|
|
342
|
+
quic_raise_ngtcp2_error(rv);
|
|
342
343
|
s->reset = true;
|
|
343
344
|
return Qnil;
|
|
344
345
|
}
|
|
@@ -346,9 +347,7 @@ quic_stream_reset_m(int argc, VALUE *argv, VALUE self)
|
|
|
346
347
|
/* Called by #accept_stream / #accept_stream_nonblock for the stream they
|
|
347
348
|
take off @accept_queue. A stream that closed while it waited there gives
|
|
348
349
|
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
|
-
{
|
|
350
|
+
static VALUE quic_stream_accepted_m(VALUE self) {
|
|
352
351
|
quic_stream_t *s;
|
|
353
352
|
TypedData_Get_Struct(self, quic_stream_t, &quic_stream_data_type, s);
|
|
354
353
|
s->accepted = true;
|
|
@@ -362,20 +361,21 @@ quic_stream_accepted_m(VALUE self)
|
|
|
362
361
|
return Qnil;
|
|
363
362
|
}
|
|
364
363
|
|
|
365
|
-
void
|
|
366
|
-
Init_quic_stream(VALUE rb_mQUICArg)
|
|
367
|
-
{
|
|
364
|
+
void Init_quic_stream(VALUE rb_mQUICArg) {
|
|
368
365
|
rb_cQUICStream = rb_define_class_under(rb_mQUICArg, "Stream", rb_cObject);
|
|
369
366
|
rb_define_alloc_func(rb_cQUICStream, quic_stream_alloc);
|
|
370
367
|
rb_define_method(rb_cQUICStream, "write", quic_stream_write_m, -1);
|
|
371
|
-
rb_define_method(rb_cQUICStream, "write_nonblock",
|
|
368
|
+
rb_define_method(rb_cQUICStream, "write_nonblock",
|
|
369
|
+
quic_stream_write_nonblock_m, -1);
|
|
372
370
|
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,
|
|
371
|
+
rb_define_method(rb_cQUICStream, "read_nonblock", quic_stream_read_nonblock_m,
|
|
372
|
+
1);
|
|
374
373
|
rb_define_method(rb_cQUICStream, "eof?", quic_stream_eof_p, 0);
|
|
375
374
|
rb_define_method(rb_cQUICStream, "closed?", quic_stream_closed_p, 0);
|
|
376
375
|
rb_define_method(rb_cQUICStream, "close_read", quic_stream_close_read_m, 0);
|
|
377
376
|
rb_define_method(rb_cQUICStream, "close", quic_stream_close_m, 0);
|
|
378
377
|
rb_define_method(rb_cQUICStream, "reset", quic_stream_reset_m, -1);
|
|
379
|
-
rb_define_private_method(rb_cQUICStream, "_accepted", quic_stream_accepted_m,
|
|
378
|
+
rb_define_private_method(rb_cQUICStream, "_accepted", quic_stream_accepted_m,
|
|
379
|
+
0);
|
|
380
380
|
/* #read (blocking) and #initiator are defined in lib/quic/stream.rb. */
|
|
381
381
|
}
|
data/ext/quic/stream.h
CHANGED
|
@@ -22,13 +22,19 @@ 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
|
+
/* The send side is shut: the peer's STOP_SENDING arrived, or ngtcp2
|
|
33
|
+
reported the send side shut while writing. @pending_chunks has been
|
|
34
|
+
emptied, so the offsets above no longer describe it, and #write_pkt
|
|
35
|
+
skips the stream. */
|
|
36
|
+
bool stopped;
|
|
37
|
+
uint64_t stop_app_error_code; /* the STOP_SENDING's, 0 if none arrived */
|
|
32
38
|
/* Set once a peer-initiated stream has been pushed onto the owning
|
|
33
39
|
connection's @accept_queue, so recv_stream_data does not enqueue it
|
|
34
40
|
again on subsequent data arrivals. */
|
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