quic 0.0.2 → 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.clang-format +1 -0
- data/.git-blame-ignore-revs +2 -0
- data/CHANGELOG.md +33 -0
- data/LICENSE-DEPENDENCIES.txt +2 -19
- data/README.md +101 -2
- data/Rakefile +7 -0
- data/examples/demo_certificates.rb +67 -0
- data/examples/echo_demo.rb +5 -6
- data/examples/echo_endpoint_demo.rb +170 -0
- data/examples/echo_server_demo.rb +71 -0
- data/ext/quic/connection_client.c +95 -934
- data/ext/quic/connection_common.c +1255 -0
- data/ext/quic/connection_common.h +226 -0
- data/ext/quic/connection_server.c +205 -0
- data/ext/quic/endpoint.c +829 -0
- data/ext/quic/endpoint.h +31 -0
- data/ext/quic/ext_api.c +106 -0
- data/ext/quic/extconf.rb +12 -5
- data/ext/quic/include/quic/ext.h +91 -0
- data/ext/quic/quic.c +95 -62
- data/ext/quic/quic.h +12 -10
- data/ext/quic/stream.c +124 -82
- data/ext/quic/stream.h +18 -12
- data/ext/quic/tls_context.c +269 -0
- data/ext/quic/tls_context.h +47 -0
- data/lib/quic/connection.rb +142 -57
- data/lib/quic/endpoint.rb +57 -0
- data/lib/quic/ext_include.rb +12 -0
- data/lib/quic/settings.rb +26 -3
- data/lib/quic/stream.rb +4 -3
- data/lib/quic/version.rb +1 -1
- data/lib/quic.rb +2 -0
- data/sig/quic.rbs +203 -22
- metadata +17 -1
data/ext/quic/stream.c
CHANGED
|
@@ -1,47 +1,44 @@
|
|
|
1
1
|
#include "stream.h"
|
|
2
|
+
#include "connection_common.h"
|
|
2
3
|
|
|
3
4
|
#include <string.h>
|
|
4
5
|
|
|
5
6
|
VALUE rb_cQUICStream;
|
|
6
7
|
|
|
7
|
-
static void
|
|
8
|
-
quic_stream_free(void *ptr)
|
|
9
|
-
{
|
|
10
|
-
xfree(ptr);
|
|
11
|
-
}
|
|
8
|
+
static void quic_stream_free(void *ptr) { xfree(ptr); }
|
|
12
9
|
|
|
13
|
-
static size_t
|
|
14
|
-
quic_stream_size(const void *ptr)
|
|
15
|
-
{
|
|
10
|
+
static size_t quic_stream_size(const void *ptr) {
|
|
16
11
|
(void)ptr;
|
|
17
12
|
return sizeof(quic_stream_t);
|
|
18
13
|
}
|
|
19
14
|
|
|
20
15
|
const rb_data_type_t quic_stream_data_type = {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
16
|
+
"QUIC::Stream",
|
|
17
|
+
{
|
|
18
|
+
NULL,
|
|
19
|
+
quic_stream_free,
|
|
20
|
+
quic_stream_size,
|
|
21
|
+
},
|
|
22
|
+
NULL,
|
|
23
|
+
NULL,
|
|
24
|
+
RUBY_TYPED_FREE_IMMEDIATELY,
|
|
25
25
|
};
|
|
26
26
|
|
|
27
|
-
static VALUE
|
|
28
|
-
quic_stream_alloc(VALUE klass)
|
|
29
|
-
{
|
|
27
|
+
static VALUE quic_stream_alloc(VALUE klass) {
|
|
30
28
|
quic_stream_t *s = ALLOC(quic_stream_t);
|
|
31
29
|
memset(s, 0, sizeof(*s));
|
|
32
30
|
return TypedData_Wrap_Struct(klass, &quic_stream_data_type, s);
|
|
33
31
|
}
|
|
34
32
|
|
|
35
33
|
VALUE
|
|
36
|
-
quic_stream_new(int64_t stream_id, VALUE
|
|
37
|
-
{
|
|
34
|
+
quic_stream_new(int64_t stream_id, VALUE owner) {
|
|
38
35
|
VALUE self = quic_stream_alloc(rb_cQUICStream);
|
|
39
36
|
quic_stream_t *s;
|
|
40
37
|
TypedData_Get_Struct(self, quic_stream_t, &quic_stream_data_type, s);
|
|
41
38
|
s->stream_id = stream_id;
|
|
42
39
|
|
|
43
40
|
rb_ivar_set(self, rb_intern("@id"), LL2NUM(stream_id));
|
|
44
|
-
rb_ivar_set(self, rb_intern("@client"),
|
|
41
|
+
rb_ivar_set(self, rb_intern("@client"), owner);
|
|
45
42
|
rb_ivar_set(self, rb_intern("@pending_chunks"), rb_ary_new());
|
|
46
43
|
/* recv_buffer is a binary String; rb_str_buf_new returns ASCII-8BIT. */
|
|
47
44
|
rb_ivar_set(self, rb_intern("@recv_buffer"), rb_str_buf_new(0));
|
|
@@ -54,9 +51,7 @@ quic_stream_new(int64_t stream_id, VALUE client)
|
|
|
54
51
|
quic_stream_write_nonblock_m). This helper is reached only after the
|
|
55
52
|
caller has confirmed the bytes fit in the current send window, so it
|
|
56
53
|
never raises QUIC::Error::WaitWritable on its own. */
|
|
57
|
-
static VALUE
|
|
58
|
-
quic_stream_enqueue(VALUE self, VALUE data, bool fin)
|
|
59
|
-
{
|
|
54
|
+
static VALUE quic_stream_enqueue(VALUE self, VALUE data, bool fin) {
|
|
60
55
|
quic_stream_t *s;
|
|
61
56
|
TypedData_Get_Struct(self, quic_stream_t, &quic_stream_data_type, s);
|
|
62
57
|
|
|
@@ -86,41 +81,51 @@ quic_stream_enqueue(VALUE self, VALUE data, bool fin)
|
|
|
86
81
|
return LONG2NUM(len);
|
|
87
82
|
}
|
|
88
83
|
|
|
89
|
-
static bool
|
|
90
|
-
quic_stream_fin_kwarg(int argc, VALUE *argv, VALUE *data)
|
|
91
|
-
{
|
|
84
|
+
static bool quic_stream_fin_kwarg(int argc, VALUE *argv, VALUE *data) {
|
|
92
85
|
VALUE opts = Qnil;
|
|
93
86
|
rb_scan_args(argc, argv, "1:", data, &opts);
|
|
94
|
-
if (NIL_P(opts))
|
|
87
|
+
if (NIL_P(opts))
|
|
88
|
+
return false;
|
|
95
89
|
VALUE fin = rb_hash_lookup(opts, ID2SYM(rb_intern("fin")));
|
|
96
90
|
return RTEST(fin);
|
|
97
91
|
}
|
|
98
92
|
|
|
99
|
-
/* Return the effective send window for this stream
|
|
100
|
-
per-stream and the per-connection flow control limits ngtcp2 tracks
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
93
|
+
/* Return the effective send window for this stream: the smaller of the
|
|
94
|
+
per-stream and the per-connection flow control limits ngtcp2 tracks, less
|
|
95
|
+
the bytes already queued in @pending_chunks but not yet handed to ngtcp2.
|
|
96
|
+
ngtcp2 only counts what it has sent, so without the subtraction a run of
|
|
97
|
+
writes could queue far more than the peer allows. */
|
|
98
|
+
static uint64_t quic_stream_window_left(VALUE self, VALUE client_v,
|
|
99
|
+
quic_stream_t *s) {
|
|
100
|
+
ngtcp2_conn *conn = quic_conn_ptr(client_v);
|
|
101
|
+
uint64_t stream_left =
|
|
102
|
+
ngtcp2_conn_get_max_stream_data_left2(conn, s->stream_id);
|
|
103
|
+
uint64_t conn_left = ngtcp2_conn_get_max_data_left2(conn);
|
|
104
|
+
uint64_t left = stream_left < conn_left ? stream_left : conn_left;
|
|
105
|
+
|
|
106
|
+
/* @pending_chunks starts at stream offset pending_shifted, and the bytes
|
|
107
|
+
up to sent_offset have been sent. */
|
|
108
|
+
VALUE pending = rb_ivar_get(self, rb_intern("@pending_chunks"));
|
|
109
|
+
uint64_t queued_end = s->pending_shifted;
|
|
110
|
+
for (long i = 0; i < RARRAY_LEN(pending); i++) {
|
|
111
|
+
queued_end += (uint64_t)RSTRING_LEN(RARRAY_AREF(pending, i));
|
|
112
|
+
}
|
|
113
|
+
uint64_t unsent = queued_end - s->sent_offset;
|
|
114
|
+
return left > unsent ? left - unsent : 0;
|
|
108
115
|
}
|
|
109
116
|
|
|
110
|
-
/* Blocking write: block by repeatedly invoking
|
|
111
|
-
peer's flow control window has enough room
|
|
112
|
-
enqueue. IO#write-compatible: always queues all
|
|
113
|
-
(built via QUIC::Stream.allocate for unit tests,
|
|
114
|
-
the window check entirely. */
|
|
115
|
-
static VALUE
|
|
116
|
-
quic_stream_write_m(int argc, VALUE *argv, VALUE self)
|
|
117
|
-
{
|
|
117
|
+
/* Blocking write: block by repeatedly invoking #pump_once on the owning
|
|
118
|
+
connection (@client) until the peer's flow control window has enough room
|
|
119
|
+
for the full payload, then enqueue. IO#write-compatible: always queues all
|
|
120
|
+
of `data`. Bare Streams (built via QUIC::Stream.allocate for unit tests,
|
|
121
|
+
with @client = nil) skip the window check entirely. */
|
|
122
|
+
static VALUE quic_stream_write_m(int argc, VALUE *argv, VALUE self) {
|
|
118
123
|
VALUE data;
|
|
119
124
|
bool fin = quic_stream_fin_kwarg(argc, argv, &data);
|
|
120
125
|
|
|
121
126
|
VALUE client_v = rb_ivar_get(self, rb_intern("@client"));
|
|
122
127
|
if (NIL_P(client_v)) {
|
|
123
|
-
return quic_stream_enqueue(self, data, fin);
|
|
128
|
+
return quic_stream_enqueue(self, data, fin); /* bare-Stream test escape */
|
|
124
129
|
}
|
|
125
130
|
|
|
126
131
|
/* These will be re-validated by quic_stream_enqueue, but we need the
|
|
@@ -132,9 +137,9 @@ quic_stream_write_m(int argc, VALUE *argv, VALUE self)
|
|
|
132
137
|
quic_stream_t *s;
|
|
133
138
|
TypedData_Get_Struct(self, quic_stream_t, &quic_stream_data_type, s);
|
|
134
139
|
/* Loop until the full payload would fit in the current window. pump_once
|
|
135
|
-
raises QUIC::Error::NotBound if
|
|
136
|
-
error surfaces verbatim. */
|
|
137
|
-
while ((uint64_t)needed > quic_stream_window_left(client_v, s)) {
|
|
140
|
+
raises QUIC::Error::NotBound if the connection has not been #bind'ed;
|
|
141
|
+
that error surfaces verbatim. */
|
|
142
|
+
while ((uint64_t)needed > quic_stream_window_left(self, client_v, s)) {
|
|
138
143
|
rb_funcall(client_v, rb_intern("pump_once"), 0);
|
|
139
144
|
}
|
|
140
145
|
}
|
|
@@ -148,15 +153,13 @@ quic_stream_write_m(int argc, VALUE *argv, VALUE self)
|
|
|
148
153
|
prefix fits, FIN is NOT set on this call (caller re-issues with
|
|
149
154
|
`fin: true` once the remainder is accepted) so we don't half-commit FIN.
|
|
150
155
|
Bare Streams with @client = nil skip the window check entirely. */
|
|
151
|
-
static VALUE
|
|
152
|
-
quic_stream_write_nonblock_m(int argc, VALUE *argv, VALUE self)
|
|
153
|
-
{
|
|
156
|
+
static VALUE quic_stream_write_nonblock_m(int argc, VALUE *argv, VALUE self) {
|
|
154
157
|
VALUE data;
|
|
155
158
|
bool fin = quic_stream_fin_kwarg(argc, argv, &data);
|
|
156
159
|
|
|
157
160
|
VALUE client_v = rb_ivar_get(self, rb_intern("@client"));
|
|
158
161
|
if (NIL_P(client_v)) {
|
|
159
|
-
return quic_stream_enqueue(self, data, fin);
|
|
162
|
+
return quic_stream_enqueue(self, data, fin); /* bare-Stream test escape */
|
|
160
163
|
}
|
|
161
164
|
|
|
162
165
|
Check_Type(data, T_STRING);
|
|
@@ -169,7 +172,7 @@ quic_stream_write_nonblock_m(int argc, VALUE *argv, VALUE self)
|
|
|
169
172
|
|
|
170
173
|
quic_stream_t *s;
|
|
171
174
|
TypedData_Get_Struct(self, quic_stream_t, &quic_stream_data_type, s);
|
|
172
|
-
uint64_t avail = quic_stream_window_left(client_v, s);
|
|
175
|
+
uint64_t avail = quic_stream_window_left(self, client_v, s);
|
|
173
176
|
|
|
174
177
|
if (avail == 0) {
|
|
175
178
|
rb_raise(rb_eQUICErrorWaitWritable, "stream send window is full");
|
|
@@ -181,12 +184,11 @@ quic_stream_write_nonblock_m(int argc, VALUE *argv, VALUE self)
|
|
|
181
184
|
return quic_stream_enqueue(self, slice, effective_fin);
|
|
182
185
|
}
|
|
183
186
|
|
|
184
|
-
static VALUE
|
|
185
|
-
quic_stream_close_write_m(VALUE self)
|
|
186
|
-
{
|
|
187
|
+
static VALUE quic_stream_close_write_m(VALUE self) {
|
|
187
188
|
quic_stream_t *s;
|
|
188
189
|
TypedData_Get_Struct(self, quic_stream_t, &quic_stream_data_type, s);
|
|
189
|
-
if (s->fin_sent)
|
|
190
|
+
if (s->fin_sent)
|
|
191
|
+
return Qnil; /* idempotent */
|
|
190
192
|
VALUE empty = rb_str_new("", 0);
|
|
191
193
|
rb_enc_associate_index(empty, rb_ascii8bit_encindex());
|
|
192
194
|
quic_stream_enqueue(self, empty, true);
|
|
@@ -201,9 +203,7 @@ quic_stream_close_write_m(VALUE self)
|
|
|
201
203
|
- buffer empty + !fin_received: raise QUIC::Error::WaitReadable
|
|
202
204
|
- buffer non-empty: return min(length, buffer.bytesize) bytes
|
|
203
205
|
*/
|
|
204
|
-
static VALUE
|
|
205
|
-
quic_stream_read_nonblock_m(VALUE self, VALUE length_v)
|
|
206
|
-
{
|
|
206
|
+
static VALUE quic_stream_read_nonblock_m(VALUE self, VALUE length_v) {
|
|
207
207
|
quic_stream_t *s;
|
|
208
208
|
TypedData_Get_Struct(self, quic_stream_t, &quic_stream_data_type, s);
|
|
209
209
|
|
|
@@ -227,41 +227,63 @@ quic_stream_read_nonblock_m(VALUE self, VALUE length_v)
|
|
|
227
227
|
|
|
228
228
|
/* Drop the first |take| bytes from buffer by memmove + truncate. */
|
|
229
229
|
if (take < have) {
|
|
230
|
-
memmove(RSTRING_PTR(buffer), RSTRING_PTR(buffer) + take,
|
|
230
|
+
memmove(RSTRING_PTR(buffer), RSTRING_PTR(buffer) + take,
|
|
231
|
+
(size_t)(have - take));
|
|
231
232
|
}
|
|
232
233
|
rb_str_set_len(buffer, have - take);
|
|
233
234
|
|
|
235
|
+
/* The application has consumed |take| bytes, so give the peer that much
|
|
236
|
+
more stream and connection credit. Without this the peer stops sending
|
|
237
|
+
once initial_max_data / initial_max_stream_data are used up. A stream
|
|
238
|
+
ngtcp2 has already forgotten is ignored (the call returns 0), but the
|
|
239
|
+
connection window still grows. Bare Streams have no connection. */
|
|
240
|
+
VALUE client_v = rb_ivar_get(self, rb_intern("@client"));
|
|
241
|
+
if (!NIL_P(client_v)) {
|
|
242
|
+
ngtcp2_conn *conn = quic_conn_ptr(client_v);
|
|
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);
|
|
247
|
+
ngtcp2_conn_extend_max_offset(conn, (uint64_t)take);
|
|
248
|
+
}
|
|
249
|
+
|
|
234
250
|
return out;
|
|
235
251
|
}
|
|
236
252
|
|
|
237
|
-
static VALUE
|
|
238
|
-
quic_stream_eof_p(VALUE self)
|
|
239
|
-
{
|
|
253
|
+
static VALUE quic_stream_eof_p(VALUE self) {
|
|
240
254
|
quic_stream_t *s;
|
|
241
255
|
TypedData_Get_Struct(self, quic_stream_t, &quic_stream_data_type, s);
|
|
242
256
|
VALUE buffer = rb_ivar_get(self, rb_intern("@recv_buffer"));
|
|
243
257
|
return (RSTRING_LEN(buffer) == 0 && s->fin_received) ? Qtrue : Qfalse;
|
|
244
258
|
}
|
|
245
259
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
260
|
+
/* True once the stream is fully closed: both directions have finished (FIN
|
|
261
|
+
or reset) and everything sent has been acknowledged. Stays false if the
|
|
262
|
+
connection closes first, since ngtcp2 does not report stream closure then.
|
|
263
|
+
Keeps returning true after the stream has left the connection's @streams.
|
|
264
|
+
A bare Stream is never closed. */
|
|
265
|
+
static VALUE quic_stream_closed_p(VALUE self) {
|
|
266
|
+
quic_stream_t *s;
|
|
267
|
+
TypedData_Get_Struct(self, quic_stream_t, &quic_stream_data_type, s);
|
|
268
|
+
return s->closed ? Qtrue : Qfalse;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
static VALUE quic_stream_close_read_m(VALUE self) {
|
|
249
272
|
quic_stream_t *s;
|
|
250
273
|
TypedData_Get_Struct(self, quic_stream_t, &quic_stream_data_type, s);
|
|
251
274
|
|
|
252
275
|
VALUE client_v = rb_ivar_get(self, rb_intern("@client"));
|
|
253
|
-
ngtcp2_conn *conn =
|
|
276
|
+
ngtcp2_conn *conn = quic_conn_ptr(client_v);
|
|
254
277
|
|
|
255
278
|
/* ngtcp2_conn_shutdown_stream_read sends STOP_SENDING. app_error_code 0
|
|
256
279
|
since no QUIC-level error API is exposed yet. */
|
|
257
280
|
int rv = ngtcp2_conn_shutdown_stream_read(conn, 0, s->stream_id, 0);
|
|
258
|
-
if (rv != 0)
|
|
281
|
+
if (rv != 0)
|
|
282
|
+
quic_raise_ngtcp2_error(rv);
|
|
259
283
|
return Qnil;
|
|
260
284
|
}
|
|
261
285
|
|
|
262
|
-
static VALUE
|
|
263
|
-
quic_stream_close_m(VALUE self)
|
|
264
|
-
{
|
|
286
|
+
static VALUE quic_stream_close_m(VALUE self) {
|
|
265
287
|
/* Send FIN (if not yet) then STOP_SENDING. Idempotent on each leg. */
|
|
266
288
|
quic_stream_close_write_m(self);
|
|
267
289
|
quic_stream_close_read_m(self);
|
|
@@ -278,9 +300,7 @@ quic_stream_close_m(VALUE self)
|
|
|
278
300
|
A bare Stream (@client == nil, used by unit-test fixtures) skips the
|
|
279
301
|
ngtcp2 call and only flips s->reset, mirroring the #write / #close_write
|
|
280
302
|
bare-Stream escapes. */
|
|
281
|
-
static VALUE
|
|
282
|
-
quic_stream_reset_m(int argc, VALUE *argv, VALUE self)
|
|
283
|
-
{
|
|
303
|
+
static VALUE quic_stream_reset_m(int argc, VALUE *argv, VALUE self) {
|
|
284
304
|
VALUE error_code_v = Qnil;
|
|
285
305
|
rb_scan_args(argc, argv, "01", &error_code_v);
|
|
286
306
|
uint64_t error_code = NIL_P(error_code_v) ? 0 : NUM2ULL(error_code_v);
|
|
@@ -288,33 +308,55 @@ quic_stream_reset_m(int argc, VALUE *argv, VALUE self)
|
|
|
288
308
|
quic_stream_t *s;
|
|
289
309
|
TypedData_Get_Struct(self, quic_stream_t, &quic_stream_data_type, s);
|
|
290
310
|
|
|
291
|
-
if (s->reset)
|
|
311
|
+
if (s->reset)
|
|
312
|
+
return Qnil; /* idempotent */
|
|
292
313
|
|
|
293
314
|
VALUE client_v = rb_ivar_get(self, rb_intern("@client"));
|
|
294
315
|
if (NIL_P(client_v)) {
|
|
295
|
-
s->reset = true;
|
|
316
|
+
s->reset = true; /* bare Stream escape */
|
|
296
317
|
return Qnil;
|
|
297
318
|
}
|
|
298
319
|
|
|
299
|
-
ngtcp2_conn *conn =
|
|
320
|
+
ngtcp2_conn *conn = quic_conn_ptr(client_v);
|
|
300
321
|
int rv = ngtcp2_conn_shutdown_stream_write(conn, 0, s->stream_id, error_code);
|
|
301
|
-
if (rv != 0)
|
|
322
|
+
if (rv != 0)
|
|
323
|
+
quic_raise_ngtcp2_error(rv);
|
|
302
324
|
s->reset = true;
|
|
303
325
|
return Qnil;
|
|
304
326
|
}
|
|
305
327
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
328
|
+
/* Called by #accept_stream / #accept_stream_nonblock for the stream they
|
|
329
|
+
take off @accept_queue. A stream that closed while it waited there gives
|
|
330
|
+
the peer back the stream it used up now (see quic_stream_close_body). */
|
|
331
|
+
static VALUE quic_stream_accepted_m(VALUE self) {
|
|
332
|
+
quic_stream_t *s;
|
|
333
|
+
TypedData_Get_Struct(self, quic_stream_t, &quic_stream_data_type, s);
|
|
334
|
+
s->accepted = true;
|
|
335
|
+
if (s->peer_stream_held) {
|
|
336
|
+
s->peer_stream_held = false;
|
|
337
|
+
VALUE client_v = rb_ivar_get(self, rb_intern("@client"));
|
|
338
|
+
if (!NIL_P(client_v)) {
|
|
339
|
+
quic_conn_give_back_peer_stream(quic_conn_ptr(client_v), s->stream_id);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
return Qnil;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
void Init_quic_stream(VALUE rb_mQUICArg) {
|
|
309
346
|
rb_cQUICStream = rb_define_class_under(rb_mQUICArg, "Stream", rb_cObject);
|
|
310
347
|
rb_define_alloc_func(rb_cQUICStream, quic_stream_alloc);
|
|
311
348
|
rb_define_method(rb_cQUICStream, "write", quic_stream_write_m, -1);
|
|
312
|
-
rb_define_method(rb_cQUICStream, "write_nonblock",
|
|
349
|
+
rb_define_method(rb_cQUICStream, "write_nonblock",
|
|
350
|
+
quic_stream_write_nonblock_m, -1);
|
|
313
351
|
rb_define_method(rb_cQUICStream, "close_write", quic_stream_close_write_m, 0);
|
|
314
|
-
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);
|
|
315
354
|
rb_define_method(rb_cQUICStream, "eof?", quic_stream_eof_p, 0);
|
|
355
|
+
rb_define_method(rb_cQUICStream, "closed?", quic_stream_closed_p, 0);
|
|
316
356
|
rb_define_method(rb_cQUICStream, "close_read", quic_stream_close_read_m, 0);
|
|
317
357
|
rb_define_method(rb_cQUICStream, "close", quic_stream_close_m, 0);
|
|
318
358
|
rb_define_method(rb_cQUICStream, "reset", quic_stream_reset_m, -1);
|
|
359
|
+
rb_define_private_method(rb_cQUICStream, "_accepted", quic_stream_accepted_m,
|
|
360
|
+
0);
|
|
319
361
|
/* #read (blocking) and #initiator are defined in lib/quic/stream.rb. */
|
|
320
362
|
}
|
data/ext/quic/stream.h
CHANGED
|
@@ -7,7 +7,7 @@ extern VALUE rb_cQUICStream;
|
|
|
7
7
|
|
|
8
8
|
void Init_quic_stream(VALUE rb_mQUICArg);
|
|
9
9
|
|
|
10
|
-
/* Internal helpers used by
|
|
10
|
+
/* Internal helpers used by connection_common.c stream callbacks and
|
|
11
11
|
write_pkt's stream-aware path. */
|
|
12
12
|
|
|
13
13
|
typedef struct {
|
|
@@ -22,24 +22,30 @@ 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
|
-
/* Set once a peer-initiated
|
|
33
|
-
|
|
32
|
+
/* Set once a peer-initiated stream has been pushed onto the owning
|
|
33
|
+
connection's @accept_queue, so recv_stream_data does not enqueue it
|
|
34
34
|
again on subsequent data arrivals. */
|
|
35
35
|
bool accept_queued;
|
|
36
|
+
/* Set once #accept_stream has handed the stream to the application. */
|
|
37
|
+
bool accepted;
|
|
38
|
+
/* The stream closed while it waited in @accept_queue: the peer gets the
|
|
39
|
+
stream it used up back when the stream is accepted. */
|
|
40
|
+
bool peer_stream_held;
|
|
36
41
|
} quic_stream_t;
|
|
37
42
|
|
|
38
43
|
extern const rb_data_type_t quic_stream_data_type;
|
|
39
44
|
|
|
40
|
-
/* Allocate a new QUIC::Stream Ruby object with a
|
|
41
|
-
quic_stream_t
|
|
42
|
-
@pending_chunks / @recv_buffer).
|
|
43
|
-
|
|
45
|
+
/* Allocate a new QUIC::Stream Ruby object for stream_id with a
|
|
46
|
+
zero-initialized quic_stream_t, and set its ivars (@id / @client /
|
|
47
|
+
@pending_chunks / @recv_buffer). @client is owner, the Client or Server
|
|
48
|
+
the stream belongs to; the ivar keeps its name for both. */
|
|
49
|
+
VALUE quic_stream_new(int64_t stream_id, VALUE owner);
|
|
44
50
|
|
|
45
51
|
#endif /* QUIC_STREAM_H */
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
#include "tls_context.h"
|
|
2
|
+
#include "connection_common.h"
|
|
3
|
+
|
|
4
|
+
#include <stddef.h>
|
|
5
|
+
#include <string.h>
|
|
6
|
+
|
|
7
|
+
#include <openssl/bio.h>
|
|
8
|
+
#include <openssl/err.h>
|
|
9
|
+
#include <openssl/pem.h>
|
|
10
|
+
#include <openssl/x509.h>
|
|
11
|
+
|
|
12
|
+
VALUE rb_cQUICTLSContext;
|
|
13
|
+
|
|
14
|
+
void quic_tls_ctx_ref(quic_tls_ctx_t *t) { t->refcnt++; }
|
|
15
|
+
|
|
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);
|
|
21
|
+
/* picotls malloc()ed the list and each entry, so free(), not xfree(). */
|
|
22
|
+
if (t->ctx.certificates.list != NULL) {
|
|
23
|
+
for (size_t i = 0; i < t->ctx.certificates.count; i++)
|
|
24
|
+
free(t->ctx.certificates.list[i].base);
|
|
25
|
+
free(t->ctx.certificates.list);
|
|
26
|
+
}
|
|
27
|
+
xfree(t->alpn);
|
|
28
|
+
xfree(t->alpn_buf);
|
|
29
|
+
xfree(t);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/* The QUIC::TLSContext object holds one reference. A context it never
|
|
33
|
+
finished building (an exception mid-way) is released the same way. */
|
|
34
|
+
static void quic_tls_context_free(void *ptr) {
|
|
35
|
+
if (ptr != NULL)
|
|
36
|
+
quic_tls_ctx_unref((quic_tls_ctx_t *)ptr);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
static size_t quic_tls_context_size(const void *ptr) {
|
|
40
|
+
(void)ptr;
|
|
41
|
+
return sizeof(quic_tls_ctx_t);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
static const rb_data_type_t quic_tls_context_type = {
|
|
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,
|
|
54
|
+
};
|
|
55
|
+
|
|
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);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/* Pick the first of the server's ALPN protocols, in the server's order of
|
|
62
|
+
preference, that the client offered. Runs inside read_pkt with the GVL
|
|
63
|
+
held and never touches Ruby objects. */
|
|
64
|
+
static int
|
|
65
|
+
quic_tls_on_client_hello_cb(ptls_on_client_hello_t *self, ptls_t *tls,
|
|
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));
|
|
70
|
+
for (size_t i = 0; i < t->alpn_count; i++) {
|
|
71
|
+
for (size_t j = 0; j < params->negotiated_protocols.count; j++) {
|
|
72
|
+
ptls_iovec_t offered = params->negotiated_protocols.list[j];
|
|
73
|
+
if (offered.len == t->alpn[i].len &&
|
|
74
|
+
memcmp(offered.base, t->alpn[i].base, offered.len) == 0) {
|
|
75
|
+
return ptls_set_negotiated_protocol(tls, (const char *)offered.base,
|
|
76
|
+
offered.len) == 0
|
|
77
|
+
? 0
|
|
78
|
+
: PTLS_ERROR_NO_MEMORY;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return PTLS_ALERT_NO_APPLICATION_PROTOCOL;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/* Never supply a passphrase. Without a callback, PEM_read_bio_PrivateKey
|
|
86
|
+
falls back to PEM_def_callback, which prompts on the controlling terminal;
|
|
87
|
+
this way an encrypted key fails to load like any other unreadable one. */
|
|
88
|
+
static int quic_tls_no_passphrase_cb(char *buf, int size, int rwflag,
|
|
89
|
+
void *userdata) {
|
|
90
|
+
(void)buf;
|
|
91
|
+
(void)size;
|
|
92
|
+
(void)rwflag;
|
|
93
|
+
(void)userdata;
|
|
94
|
+
return -1;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/* Load the certificate chain and private key into t->ctx and install the
|
|
98
|
+
signer. The certificate file is PEM, the leaf first and then any
|
|
99
|
+
intermediates; there is no limit on their number. A misconfiguration
|
|
100
|
+
raises ArgumentError naming the step that failed. Everything allocated
|
|
101
|
+
here is released before raising, and libcrypto's error queue, which is
|
|
102
|
+
shared with Ruby's openssl extension, is left empty on every return. */
|
|
103
|
+
static void quic_tls_ctx_load_certificate_and_key(quic_tls_ctx_t *t,
|
|
104
|
+
VALUE certificate_path,
|
|
105
|
+
VALUE private_key_path) {
|
|
106
|
+
VALUE failure_class = rb_eArgError;
|
|
107
|
+
const char *failure = NULL;
|
|
108
|
+
VALUE failed_path = Qnil;
|
|
109
|
+
BIO *cert_bio = NULL;
|
|
110
|
+
BIO *key_bio = NULL;
|
|
111
|
+
X509 *leaf = NULL;
|
|
112
|
+
X509 *x;
|
|
113
|
+
STACK_OF(X509) *chain = NULL;
|
|
114
|
+
EVP_PKEY *pkey = NULL;
|
|
115
|
+
unsigned long err;
|
|
116
|
+
|
|
117
|
+
ERR_clear_error();
|
|
118
|
+
|
|
119
|
+
cert_bio = BIO_new_file(RSTRING_PTR(certificate_path), "r");
|
|
120
|
+
if (cert_bio == NULL ||
|
|
121
|
+
(leaf = PEM_read_bio_X509(cert_bio, NULL, NULL, NULL)) == NULL) {
|
|
122
|
+
failure = "failed to load certificate";
|
|
123
|
+
failed_path = certificate_path;
|
|
124
|
+
goto done;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if ((chain = sk_X509_new_null()) == NULL) {
|
|
128
|
+
failure_class = rb_eRuntimeError;
|
|
129
|
+
failure = "sk_X509_new_null failed";
|
|
130
|
+
goto done;
|
|
131
|
+
}
|
|
132
|
+
while ((x = PEM_read_bio_X509(cert_bio, NULL, NULL, NULL)) != NULL) {
|
|
133
|
+
if (!sk_X509_push(chain, x)) {
|
|
134
|
+
X509_free(x);
|
|
135
|
+
failure_class = rb_eRuntimeError;
|
|
136
|
+
failure = "sk_X509_push failed";
|
|
137
|
+
goto done;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
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.
|
|
142
|
+
*/
|
|
143
|
+
err = ERR_peek_last_error();
|
|
144
|
+
if (ERR_GET_LIB(err) != ERR_LIB_PEM ||
|
|
145
|
+
ERR_GET_REASON(err) != PEM_R_NO_START_LINE) {
|
|
146
|
+
failure = "failed to load certificate";
|
|
147
|
+
failed_path = certificate_path;
|
|
148
|
+
goto done;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
key_bio = BIO_new_file(RSTRING_PTR(private_key_path), "r");
|
|
152
|
+
if (key_bio == NULL ||
|
|
153
|
+
(pkey = PEM_read_bio_PrivateKey(key_bio, NULL, quic_tls_no_passphrase_cb,
|
|
154
|
+
NULL)) == NULL) {
|
|
155
|
+
failure = "failed to load private key";
|
|
156
|
+
failed_path = private_key_path;
|
|
157
|
+
goto done;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/* picotls has no counterpart of SSL_CTX_check_private_key. */
|
|
161
|
+
if (X509_check_private_key(leaf, pkey) != 1) {
|
|
162
|
+
failure = "private key does not match certificate";
|
|
163
|
+
failed_path = private_key_path;
|
|
164
|
+
goto done;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/* Takes its own reference on pkey. */
|
|
168
|
+
if (ptls_openssl_init_sign_certificate(&t->sign_cert, pkey) != 0) {
|
|
169
|
+
failure = "unsupported private key type";
|
|
170
|
+
failed_path = private_key_path;
|
|
171
|
+
goto done;
|
|
172
|
+
}
|
|
173
|
+
t->sign_cert_initialized = true;
|
|
174
|
+
t->ctx.sign_certificate = &t->sign_cert.super;
|
|
175
|
+
|
|
176
|
+
/* Copies the DER of each certificate into a list quic_tls_ctx_unref
|
|
177
|
+
releases. It fails only on allocation, and frees what it allocated then. */
|
|
178
|
+
if (ptls_openssl_load_certificates(&t->ctx, leaf, chain) != 0) {
|
|
179
|
+
failure_class = rb_eRuntimeError;
|
|
180
|
+
failure = "ptls_openssl_load_certificates failed";
|
|
181
|
+
goto done;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
done:
|
|
185
|
+
sk_X509_pop_free(chain, X509_free);
|
|
186
|
+
X509_free(leaf);
|
|
187
|
+
EVP_PKEY_free(pkey);
|
|
188
|
+
BIO_free(cert_bio);
|
|
189
|
+
BIO_free(key_bio);
|
|
190
|
+
ERR_clear_error();
|
|
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);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
VALUE
|
|
200
|
+
quic_tls_context_server_new(VALUE certificate_path, VALUE private_key_path,
|
|
201
|
+
VALUE alpn_ary) {
|
|
202
|
+
certificate_path = quic_path_value(certificate_path, "certificate_path");
|
|
203
|
+
private_key_path = quic_path_value(private_key_path, "private_key_path");
|
|
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");
|
|
208
|
+
|
|
209
|
+
/* The object owns the context from the start, so that an exception below
|
|
210
|
+
releases whatever was built so far. */
|
|
211
|
+
VALUE self =
|
|
212
|
+
TypedData_Wrap_Struct(rb_cQUICTLSContext, &quic_tls_context_type, NULL);
|
|
213
|
+
quic_tls_ctx_t *t = ZALLOC(quic_tls_ctx_t);
|
|
214
|
+
t->refcnt = 1;
|
|
215
|
+
DATA_PTR(self) = t;
|
|
216
|
+
|
|
217
|
+
/* Without an ALPN to pick, every handshake would end in
|
|
218
|
+
no_application_protocol. */
|
|
219
|
+
quic_copy_alpn(alpn_ary, &t->alpn, &t->alpn_count, &t->alpn_buf);
|
|
220
|
+
if (t->alpn_count == 0)
|
|
221
|
+
rb_raise(rb_eArgError, "alpn must not be empty for a server");
|
|
222
|
+
|
|
223
|
+
t->ctx = (ptls_context_t){
|
|
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,
|
|
233
|
+
};
|
|
234
|
+
if (ngtcp2_crypto_picotls_configure_server_context(&t->ctx) != 0) {
|
|
235
|
+
rb_raise(rb_eRuntimeError,
|
|
236
|
+
"ngtcp2_crypto_picotls_configure_server_context failed");
|
|
237
|
+
}
|
|
238
|
+
/* verify_certificate stays NULL: no client certificate is requested. */
|
|
239
|
+
quic_tls_ctx_load_certificate_and_key(t, certificate_path, private_key_path);
|
|
240
|
+
t->on_client_hello.cb = quic_tls_on_client_hello_cb;
|
|
241
|
+
t->ctx.on_client_hello = &t->on_client_hello;
|
|
242
|
+
return self;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/* QUIC::TLSContext.server(certificate_path:, private_key_path:, alpn:). A
|
|
246
|
+
missing or unknown keyword raises ArgumentError as a Ruby method would. */
|
|
247
|
+
static VALUE quic_tls_context_s_server(int argc, VALUE *argv, VALUE klass) {
|
|
248
|
+
(void)klass;
|
|
249
|
+
VALUE opts = Qnil;
|
|
250
|
+
rb_scan_args(argc, argv, "0:", &opts);
|
|
251
|
+
if (NIL_P(opts))
|
|
252
|
+
opts = rb_hash_new();
|
|
253
|
+
|
|
254
|
+
ID keywords[3];
|
|
255
|
+
VALUE values[3];
|
|
256
|
+
keywords[0] = rb_intern("certificate_path");
|
|
257
|
+
keywords[1] = rb_intern("private_key_path");
|
|
258
|
+
keywords[2] = rb_intern("alpn");
|
|
259
|
+
rb_get_kwargs(opts, keywords, 3, 0, values);
|
|
260
|
+
return quic_tls_context_server_new(values[0], values[1], values[2]);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
void Init_quic_tls_context(VALUE rb_mQUICArg) {
|
|
264
|
+
rb_cQUICTLSContext =
|
|
265
|
+
rb_define_class_under(rb_mQUICArg, "TLSContext", rb_cObject);
|
|
266
|
+
rb_undef_alloc_func(rb_cQUICTLSContext);
|
|
267
|
+
rb_define_singleton_method(rb_cQUICTLSContext, "server",
|
|
268
|
+
quic_tls_context_s_server, -1);
|
|
269
|
+
}
|