quic 0.0.6 → 0.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +10 -0
- data/ext/quic/connection_common.c +117 -434
- data/ext/quic/connection_common.h +23 -5
- data/ext/quic/stream.c +434 -45
- data/ext/quic/stream.h +68 -22
- data/lib/quic/connection.rb +11 -0
- data/lib/quic/version.rb +1 -1
- data/sig/quic.rbs +14 -12
- metadata +3 -3
|
@@ -65,11 +65,18 @@ typedef struct {
|
|
|
65
65
|
ptls_get_data_ptr. The ref must outlive the ptls_t. */
|
|
66
66
|
ngtcp2_crypto_conn_ref conn_ref;
|
|
67
67
|
/* Back-reference to the Client or Server Ruby object that owns this
|
|
68
|
-
struct. ngtcp2
|
|
69
|
-
struct, and
|
|
70
|
-
via TypedData_Wrap_Struct, so owner is guaranteed alive
|
|
71
|
-
(no dmark needed). GC.compact may relocate owner, so
|
|
72
|
-
updates this field via rb_gc_location.
|
|
68
|
+
struct. ngtcp2 callbacks receive a void* user_data equal to this
|
|
69
|
+
struct, and reach @streams and @accept_queue via owner. We are stored
|
|
70
|
+
INSIDE owner via TypedData_Wrap_Struct, so owner is guaranteed alive
|
|
71
|
+
while we exist (no dmark needed). GC.compact may relocate owner, so
|
|
72
|
+
quic_conn_compact updates this field via rb_gc_location.
|
|
73
|
+
|
|
74
|
+
A stream's callbacks get its QUIC::Stream's quic_stream_t from
|
|
75
|
+
ngtcp2's stream_user_data, which is only safe while @streams keeps
|
|
76
|
+
that Stream alive. So a Stream goes into @streams before ngtcp2 is
|
|
77
|
+
given its struct, and leaves only at the end of the stream_close
|
|
78
|
+
callback, which must not fail after that: a failing stream_close
|
|
79
|
+
leaves the stream in ngtcp2, pointing at a struct GC may free. */
|
|
73
80
|
VALUE owner;
|
|
74
81
|
/* An exception raised by Ruby code inside an ngtcp2 callback, caught with
|
|
75
82
|
rb_protect so that it does not unwind through ngtcp2's frames. #read_pkt,
|
|
@@ -94,6 +101,11 @@ typedef struct {
|
|
|
94
101
|
/* Set by the constructor. Decides which peer-initiated streams
|
|
95
102
|
recv_stream_data queues for #accept_stream. */
|
|
96
103
|
bool is_server;
|
|
104
|
+
/* The bytes the QUIC::Streams of this connection have queued and not yet
|
|
105
|
+
handed to ngtcp2, stopped streams excepted. ngtcp2 takes off the
|
|
106
|
+
connection's flow control window only what it has sent, so #write and
|
|
107
|
+
#write_nonblock take this off as well. */
|
|
108
|
+
uint64_t stream_unsent;
|
|
97
109
|
/* <qlog_dir>/<SCID>.sqlog while ngtcp2 writes qlog to it, else NULL.
|
|
98
110
|
quic_conn_close_qlog closes it as soon as the connection ends;
|
|
99
111
|
NGTCP2_QLOG_WRITE_FLAG_FIN, sent by ngtcp2_conn_del, closes one still
|
|
@@ -134,6 +146,12 @@ VALUE quic_conn_alloc(VALUE klass);
|
|
|
134
146
|
/* CLOCK_MONOTONIC in ngtcp2's units (nanoseconds). */
|
|
135
147
|
ngtcp2_tstamp quic_now(void);
|
|
136
148
|
|
|
149
|
+
/* Run body(arg), Ruby code an ngtcp2 callback of c calls, under rb_protect.
|
|
150
|
+
An exception is kept in c->pending_exc (the first one wins; ngtcp2 stops
|
|
151
|
+
at the first failing callback anyway) and turned into
|
|
152
|
+
NGTCP2_ERR_CALLBACK_FAILURE, so that it never unwinds through ngtcp2. */
|
|
153
|
+
int quic_conn_protect(quic_conn_t *c, VALUE (*body)(VALUE), void *arg);
|
|
154
|
+
|
|
137
155
|
/* Build the next packet of the connection self (wrapping c) into dest of
|
|
138
156
|
QUIC_WRITE_PKT_BUFLEN bytes, as #write_pkt does, and store its path in
|
|
139
157
|
*path. Returns the packet's length, 0 when there is nothing to send, or a
|
data/ext/quic/stream.c
CHANGED
|
@@ -5,19 +5,44 @@
|
|
|
5
5
|
|
|
6
6
|
VALUE rb_cQUICStream;
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
/* Free the whole send buffer. Safe to call more than once. */
|
|
9
|
+
static void quic_stream_free_send_buffer(quic_stream_t *s) {
|
|
10
|
+
quic_send_chunk *chunk = s->send_head;
|
|
11
|
+
while (chunk != NULL) {
|
|
12
|
+
quic_send_chunk *next = chunk->next;
|
|
13
|
+
xfree(chunk);
|
|
14
|
+
chunk = next;
|
|
15
|
+
}
|
|
16
|
+
s->send_head = NULL;
|
|
17
|
+
s->send_tail = NULL;
|
|
18
|
+
s->send_next = NULL;
|
|
19
|
+
s->send_next_off = 0;
|
|
20
|
+
s->buffered = 0;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/* Touches neither the connection nor any Ruby object: the connection may
|
|
24
|
+
have been freed first, in the same GC. */
|
|
25
|
+
static void quic_stream_free(void *ptr) {
|
|
26
|
+
quic_stream_free_send_buffer((quic_stream_t *)ptr);
|
|
27
|
+
xfree(ptr);
|
|
28
|
+
}
|
|
9
29
|
|
|
10
30
|
static size_t quic_stream_size(const void *ptr) {
|
|
11
|
-
(
|
|
12
|
-
|
|
31
|
+
return sizeof(quic_stream_t) + ((const quic_stream_t *)ptr)->buffered;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
static void quic_stream_compact(void *ptr) {
|
|
35
|
+
quic_stream_t *s = (quic_stream_t *)ptr;
|
|
36
|
+
s->self = rb_gc_location(s->self);
|
|
13
37
|
}
|
|
14
38
|
|
|
15
|
-
const rb_data_type_t quic_stream_data_type = {
|
|
39
|
+
static const rb_data_type_t quic_stream_data_type = {
|
|
16
40
|
"QUIC::Stream",
|
|
17
41
|
{
|
|
18
42
|
NULL,
|
|
19
43
|
quic_stream_free,
|
|
20
44
|
quic_stream_size,
|
|
45
|
+
quic_stream_compact,
|
|
21
46
|
},
|
|
22
47
|
NULL,
|
|
23
48
|
NULL,
|
|
@@ -27,11 +52,16 @@ const rb_data_type_t quic_stream_data_type = {
|
|
|
27
52
|
static VALUE quic_stream_alloc(VALUE klass) {
|
|
28
53
|
quic_stream_t *s = ALLOC(quic_stream_t);
|
|
29
54
|
memset(s, 0, sizeof(*s));
|
|
30
|
-
|
|
55
|
+
VALUE self = TypedData_Wrap_Struct(klass, &quic_stream_data_type, s);
|
|
56
|
+
s->self = self;
|
|
57
|
+
return self;
|
|
31
58
|
}
|
|
32
59
|
|
|
33
|
-
|
|
34
|
-
|
|
60
|
+
/* Allocate a new QUIC::Stream Ruby object for stream_id with a
|
|
61
|
+
zero-initialized quic_stream_t, and set its ivars (@id / @client /
|
|
62
|
+
@recv_buffer). @client is owner, the Client or Server the stream belongs
|
|
63
|
+
to; the ivar keeps its name for both. */
|
|
64
|
+
static VALUE quic_stream_new(int64_t stream_id, VALUE owner) {
|
|
35
65
|
VALUE self = quic_stream_alloc(rb_cQUICStream);
|
|
36
66
|
quic_stream_t *s;
|
|
37
67
|
TypedData_Get_Struct(self, quic_stream_t, &quic_stream_data_type, s);
|
|
@@ -39,7 +69,6 @@ quic_stream_new(int64_t stream_id, VALUE owner) {
|
|
|
39
69
|
|
|
40
70
|
rb_ivar_set(self, rb_intern("@id"), LL2NUM(stream_id));
|
|
41
71
|
rb_ivar_set(self, rb_intern("@client"), owner);
|
|
42
|
-
rb_ivar_set(self, rb_intern("@pending_chunks"), rb_ary_new());
|
|
43
72
|
/* recv_buffer is a binary String; rb_str_buf_new returns ASCII-8BIT. */
|
|
44
73
|
rb_ivar_set(self, rb_intern("@recv_buffer"), rb_str_buf_new(0));
|
|
45
74
|
return self;
|
|
@@ -58,11 +87,11 @@ static void quic_stream_check_writable(quic_stream_t *s) {
|
|
|
58
87
|
}
|
|
59
88
|
}
|
|
60
89
|
|
|
61
|
-
/* Append a
|
|
62
|
-
fin_sent flag. Flow control is the caller's concern (#write
|
|
63
|
-
quic_stream_write_m and #write_nonblock returns a partial byte
|
|
64
|
-
quic_stream_write_nonblock_m). This helper is reached only after
|
|
65
|
-
caller has confirmed the bytes fit in the current send window, so it
|
|
90
|
+
/* Append a copy of |data| to the send buffer as one chunk, and optionally
|
|
91
|
+
set the fin_sent flag. Flow control is the caller's concern (#write
|
|
92
|
+
blocks in quic_stream_write_m and #write_nonblock returns a partial byte
|
|
93
|
+
count in quic_stream_write_nonblock_m). This helper is reached only after
|
|
94
|
+
the caller has confirmed the bytes fit in the current send window, so it
|
|
66
95
|
never raises QUIC::Error::WaitWritable on its own. */
|
|
67
96
|
static VALUE quic_stream_enqueue(VALUE self, VALUE data, bool fin) {
|
|
68
97
|
quic_stream_t *s;
|
|
@@ -77,12 +106,31 @@ static VALUE quic_stream_enqueue(VALUE self, VALUE data, bool fin) {
|
|
|
77
106
|
long len = RSTRING_LEN(data);
|
|
78
107
|
|
|
79
108
|
if (len > 0) {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
109
|
+
/* A bare Stream has no connection to count its bytes against. */
|
|
110
|
+
VALUE client_v = rb_ivar_get(self, rb_intern("@client"));
|
|
111
|
+
quic_conn_t *c = NIL_P(client_v) ? NULL : quic_conn_get(client_v);
|
|
112
|
+
|
|
113
|
+
/* len is a long, so the header cannot overflow size_t. */
|
|
114
|
+
size_t size = offsetof(quic_send_chunk, data) + (size_t)len;
|
|
115
|
+
quic_send_chunk *chunk = (quic_send_chunk *)xmalloc(size);
|
|
116
|
+
chunk->next = NULL;
|
|
117
|
+
chunk->len = (size_t)len;
|
|
118
|
+
memcpy(chunk->data, RSTRING_PTR(data), (size_t)len);
|
|
119
|
+
if (s->send_tail != NULL) {
|
|
120
|
+
s->send_tail->next = chunk;
|
|
121
|
+
} else {
|
|
122
|
+
s->send_head = chunk;
|
|
123
|
+
s->head_offset = s->queued_offset;
|
|
124
|
+
}
|
|
125
|
+
s->send_tail = chunk;
|
|
126
|
+
if (s->send_next == NULL) {
|
|
127
|
+
s->send_next = chunk;
|
|
128
|
+
s->send_next_off = 0;
|
|
129
|
+
}
|
|
130
|
+
s->queued_offset += (uint64_t)len;
|
|
131
|
+
s->buffered += size;
|
|
132
|
+
if (c != NULL)
|
|
133
|
+
c->stream_unsent += (uint64_t)len;
|
|
86
134
|
}
|
|
87
135
|
|
|
88
136
|
if (fin) {
|
|
@@ -102,29 +150,365 @@ static bool quic_stream_fin_kwarg(int argc, VALUE *argv, VALUE *data) {
|
|
|
102
150
|
}
|
|
103
151
|
|
|
104
152
|
/* Return the effective send window for this stream: the smaller of the
|
|
105
|
-
per-stream and the per-connection flow control limits ngtcp2 tracks,
|
|
106
|
-
the bytes already queued
|
|
107
|
-
ngtcp2
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
static uint64_t quic_stream_window_left(VALUE
|
|
112
|
-
|
|
113
|
-
ngtcp2_conn *conn = quic_conn_ptr(client_v);
|
|
153
|
+
per-stream and the per-connection flow control limits ngtcp2 tracks,
|
|
154
|
+
each less the bytes already queued against it but not yet handed to
|
|
155
|
+
ngtcp2 (this stream's, and every stream's of the connection). ngtcp2 only
|
|
156
|
+
counts what it has sent, so without the subtraction a run of writes could
|
|
157
|
+
queue far more than the peer allows. Callers check the stream is
|
|
158
|
+
writable first. */
|
|
159
|
+
static uint64_t quic_stream_window_left(VALUE client_v, quic_stream_t *s) {
|
|
160
|
+
quic_conn_t *c = quic_conn_get(client_v);
|
|
114
161
|
uint64_t stream_left =
|
|
115
|
-
ngtcp2_conn_get_max_stream_data_left2(conn, s->stream_id);
|
|
116
|
-
uint64_t conn_left = ngtcp2_conn_get_max_data_left2(conn);
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
162
|
+
ngtcp2_conn_get_max_stream_data_left2(c->conn, s->stream_id);
|
|
163
|
+
uint64_t conn_left = ngtcp2_conn_get_max_data_left2(c->conn);
|
|
164
|
+
|
|
165
|
+
uint64_t unsent = s->queued_offset - s->sent_offset;
|
|
166
|
+
stream_left = stream_left > unsent ? stream_left - unsent : 0;
|
|
167
|
+
conn_left = conn_left > c->stream_unsent ? conn_left - c->stream_unsent : 0;
|
|
168
|
+
return stream_left < conn_left ? stream_left : conn_left;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/* What s has to send next: the unsent part of the chunk at send_next in
|
|
172
|
+
*datav (*datavcnt = 1), or nothing but a FIN (*datavcnt = 0). *flags gets
|
|
173
|
+
NGTCP2_WRITE_STREAM_FLAG_FIN when the FIN goes with it, that is when the
|
|
174
|
+
chunk is the last one queued. Returns false when s has nothing to send,
|
|
175
|
+
which includes a stopped stream. */
|
|
176
|
+
static bool quic_stream_next_send(const quic_stream_t *s, ngtcp2_vec *datav,
|
|
177
|
+
size_t *datavcnt, uint32_t *flags) {
|
|
178
|
+
if (s->stopped || s->fin_flushed)
|
|
179
|
+
return false;
|
|
180
|
+
|
|
181
|
+
if (s->send_next != NULL) {
|
|
182
|
+
datav->base = s->send_next->data + s->send_next_off;
|
|
183
|
+
datav->len = s->send_next->len - s->send_next_off;
|
|
184
|
+
*datavcnt = 1;
|
|
185
|
+
/* FIN goes with the last chunk only if ngtcp2 frames all of it; when
|
|
186
|
+
it encodes less, FIN is not flushed and the next call retries. */
|
|
187
|
+
*flags = (s->fin_sent && s->send_next == s->send_tail)
|
|
188
|
+
? NGTCP2_WRITE_STREAM_FLAG_FIN
|
|
189
|
+
: 0;
|
|
190
|
+
return true;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (s->fin_sent) {
|
|
194
|
+
/* No data to send but a pending FIN-only frame. */
|
|
195
|
+
datav->base = NULL;
|
|
196
|
+
datav->len = 0;
|
|
197
|
+
*datavcnt = 0;
|
|
198
|
+
*flags = NGTCP2_WRITE_STREAM_FLAG_FIN;
|
|
199
|
+
return true;
|
|
200
|
+
}
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/* Account for a packet ngtcp2_conn_writev_stream built for c from what
|
|
205
|
+
quic_stream_next_send offered: datalen bytes (0 for a FIN alone) with
|
|
206
|
+
flags, of which ngtcp2 framed pdatalen, in a packet of n bytes. */
|
|
207
|
+
static void quic_stream_sent(quic_conn_t *c, quic_stream_t *s, size_t datalen,
|
|
208
|
+
uint32_t flags, ngtcp2_ssize pdatalen,
|
|
209
|
+
ngtcp2_ssize n) {
|
|
210
|
+
if (pdatalen > 0) {
|
|
211
|
+
s->sent_offset += (uint64_t)pdatalen;
|
|
212
|
+
c->stream_unsent -= (uint64_t)pdatalen;
|
|
213
|
+
s->send_next_off += (size_t)pdatalen;
|
|
214
|
+
/* Leave send_next on no fully handed over chunk: an acknowledgement
|
|
215
|
+
of all that was sent would free it under us. */
|
|
216
|
+
if (s->send_next_off == s->send_next->len) {
|
|
217
|
+
s->send_next = s->send_next->next;
|
|
218
|
+
s->send_next_off = 0;
|
|
219
|
+
}
|
|
220
|
+
/* FIN is only actually written when all requested data fits in the
|
|
221
|
+
frame (see ngtcp2 docs on NGTCP2_WRITE_STREAM_FLAG_FIN). */
|
|
222
|
+
if ((flags & NGTCP2_WRITE_STREAM_FLAG_FIN) && (size_t)pdatalen == datalen)
|
|
223
|
+
s->fin_flushed = true;
|
|
224
|
+
} else if (datalen == 0 && n > 0 && (flags & NGTCP2_WRITE_STREAM_FLAG_FIN)) {
|
|
225
|
+
/* FIN-only packet (no data) was successfully written. */
|
|
226
|
+
s->fin_flushed = true;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/* Shut the send side of s, a stream of c (NULL for a bare Stream): nothing
|
|
231
|
+
it has queued goes out any more. Does nothing the second time. The send
|
|
232
|
+
buffer stays until the stream closes. On STOP_SENDING ngtcp2 calls
|
|
233
|
+
recv_stop_sending before it drops the frames it queued for the stream,
|
|
234
|
+
which still point into the buffer, and should it fail in between it keeps
|
|
235
|
+
them. */
|
|
236
|
+
static void quic_stream_stop(quic_conn_t *c, quic_stream_t *s) {
|
|
237
|
+
if (s->stopped)
|
|
238
|
+
return;
|
|
239
|
+
s->stopped = true;
|
|
240
|
+
if (c != NULL)
|
|
241
|
+
c->stream_unsent -= s->queued_offset - s->sent_offset;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/* Make a QUIC::Stream for stream_id, a stream ngtcp2 has just opened on the
|
|
245
|
+
connection owner (wrapping c), put it in @streams, and only then hand its
|
|
246
|
+
quic_stream_t to ngtcp2 as the stream's stream_user_data: ngtcp2 must
|
|
247
|
+
never point at a Stream that @streams does not keep alive (see
|
|
248
|
+
quic_conn_t.owner). Returns the Stream. */
|
|
249
|
+
static VALUE quic_stream_add(VALUE owner, quic_conn_t *c, int64_t stream_id) {
|
|
250
|
+
VALUE stream = quic_stream_new(stream_id, owner);
|
|
251
|
+
VALUE streams = rb_ivar_get(owner, rb_intern("@streams"));
|
|
252
|
+
rb_hash_aset(streams, LL2NUM(stream_id), stream);
|
|
253
|
+
|
|
254
|
+
quic_stream_t *s;
|
|
255
|
+
TypedData_Get_Struct(stream, quic_stream_t, &quic_stream_data_type, s);
|
|
256
|
+
int rv = ngtcp2_conn_set_stream_user_data(c->conn, stream_id, s);
|
|
257
|
+
if (rv != 0)
|
|
258
|
+
quic_raise_ngtcp2_error(rv);
|
|
259
|
+
return stream;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
VALUE quic_stream_open_local(VALUE self, quic_conn_t *c, int64_t stream_id) {
|
|
263
|
+
return quic_stream_add(self, c, stream_id);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/* The callbacks that touch Ruby objects (on_open, on_recv_data and
|
|
267
|
+
on_close) do that in a *_body function run under rb_protect by
|
|
268
|
+
quic_conn_protect, so that an exception (a frozen @streams, an allocation
|
|
269
|
+
failure, ...) never unwinds through ngtcp2. The callback then returns
|
|
270
|
+
NGTCP2_ERR_CALLBACK_FAILURE and the exception is re-raised once ngtcp2
|
|
271
|
+
has returned. */
|
|
272
|
+
|
|
273
|
+
/* The arguments of a stream callback, handed to its body through
|
|
274
|
+
rb_protect's single VALUE. Each body reads only the fields it needs. */
|
|
275
|
+
struct quic_stream_cb_args {
|
|
276
|
+
quic_conn_t *c;
|
|
277
|
+
quic_stream_t *s;
|
|
278
|
+
int64_t stream_id;
|
|
279
|
+
uint32_t flags;
|
|
280
|
+
const uint8_t *data;
|
|
281
|
+
size_t datalen;
|
|
282
|
+
uint64_t app_error_code;
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
static VALUE quic_stream_on_open_body(VALUE arg) {
|
|
286
|
+
struct quic_stream_cb_args *a = (struct quic_stream_cb_args *)arg;
|
|
287
|
+
quic_stream_add(a->c->owner, a->c, a->stream_id);
|
|
288
|
+
return Qnil;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/* Called only for a stream the peer opened. */
|
|
292
|
+
int quic_stream_on_open(quic_conn_t *c, int64_t stream_id) {
|
|
293
|
+
struct quic_stream_cb_args a = {.c = c, .stream_id = stream_id};
|
|
294
|
+
return quic_conn_protect(c, quic_stream_on_open_body, &a);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
static VALUE quic_stream_on_recv_data_body(VALUE arg) {
|
|
298
|
+
struct quic_stream_cb_args *a = (struct quic_stream_cb_args *)arg;
|
|
299
|
+
quic_conn_t *c = a->c;
|
|
300
|
+
quic_stream_t *s = a->s;
|
|
301
|
+
VALUE stream = s->self;
|
|
302
|
+
|
|
303
|
+
/* Surface peer-initiated streams to #accept_stream. The low stream-id bit
|
|
304
|
+
marks the initiator (0 == client, 1 == server), so a client queues the
|
|
305
|
+
odd ids and a server the even ones. Push once, on first data arrival,
|
|
306
|
+
then mark so later data does not re-enqueue. */
|
|
307
|
+
bool peer_initiated =
|
|
308
|
+
c->is_server ? !(s->stream_id & 0x01) : (s->stream_id & 0x01);
|
|
309
|
+
if (!s->accept_queued && peer_initiated) {
|
|
310
|
+
VALUE accept_queue = rb_ivar_get(c->owner, rb_intern("@accept_queue"));
|
|
311
|
+
rb_ary_push(accept_queue, stream);
|
|
312
|
+
s->accept_queued = true;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
if (a->datalen > 0) {
|
|
316
|
+
VALUE recv_buffer = rb_ivar_get(stream, rb_intern("@recv_buffer"));
|
|
317
|
+
rb_str_buf_cat(recv_buffer, (const char *)a->data, (long)a->datalen);
|
|
318
|
+
}
|
|
319
|
+
if (a->flags & NGTCP2_STREAM_DATA_FLAG_FIN) {
|
|
320
|
+
s->fin_received = true;
|
|
321
|
+
}
|
|
322
|
+
return Qnil;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
int quic_stream_on_recv_data(quic_conn_t *c, quic_stream_t *s, uint32_t flags,
|
|
326
|
+
const uint8_t *data, size_t datalen) {
|
|
327
|
+
if (s == NULL)
|
|
328
|
+
return 0;
|
|
329
|
+
struct quic_stream_cb_args a = {
|
|
330
|
+
.c = c,
|
|
331
|
+
.s = s,
|
|
332
|
+
.flags = flags,
|
|
333
|
+
.data = data,
|
|
334
|
+
.datalen = datalen,
|
|
335
|
+
};
|
|
336
|
+
return quic_conn_protect(c, quic_stream_on_recv_data_body, &a);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/* The peer acknowledged [offset, offset + datalen): free the chunks that
|
|
340
|
+
end at or before offset + datalen. */
|
|
341
|
+
void quic_stream_on_acked(quic_stream_t *s, uint64_t offset, uint64_t datalen) {
|
|
342
|
+
if (s == NULL)
|
|
343
|
+
return;
|
|
344
|
+
/* ngtcp2 reports the acknowledged ranges of a stream in order and
|
|
345
|
+
without overlap; datalen is 0 only for a FIN alone. */
|
|
346
|
+
s->acked_offset = offset + datalen;
|
|
347
|
+
while (s->send_head != NULL &&
|
|
348
|
+
s->head_offset + s->send_head->len <= s->acked_offset) {
|
|
349
|
+
quic_send_chunk *chunk = s->send_head;
|
|
350
|
+
s->head_offset += chunk->len;
|
|
351
|
+
s->send_head = chunk->next;
|
|
352
|
+
if (s->send_head == NULL)
|
|
353
|
+
s->send_tail = NULL;
|
|
354
|
+
s->buffered -= offsetof(quic_send_chunk, data) + chunk->len;
|
|
355
|
+
xfree(chunk);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
static VALUE quic_stream_on_close_body(VALUE arg) {
|
|
360
|
+
struct quic_stream_cb_args *a = (struct quic_stream_cb_args *)arg;
|
|
361
|
+
quic_stream_t *s = a->s;
|
|
362
|
+
s->closed = true;
|
|
363
|
+
if (a->flags & NGTCP2_STREAM_CLOSE_FLAG_APP_ERROR_CODE_SET) {
|
|
364
|
+
s->close_has_app_error_code = true;
|
|
365
|
+
s->close_app_error_code = a->app_error_code;
|
|
366
|
+
}
|
|
367
|
+
/* Usually stopped already, or with nothing left unsent. */
|
|
368
|
+
quic_stream_stop(a->c, s);
|
|
369
|
+
/* ngtcp2 never reads the stream's data again. */
|
|
370
|
+
quic_stream_free_send_buffer(s);
|
|
371
|
+
/* A stream still waiting in @accept_queue holds on to what it received.
|
|
372
|
+
Were the peer given its stream back now, it could open and close
|
|
373
|
+
streams until the queue had used up our memory, so it gets the stream
|
|
374
|
+
back only once the application has accepted this one
|
|
375
|
+
(QUIC::Stream#_accepted). */
|
|
376
|
+
if (s->accept_queued && !s->accepted) {
|
|
377
|
+
s->peer_stream_held = true;
|
|
378
|
+
} else {
|
|
379
|
+
quic_conn_give_back_peer_stream(a->c->conn, a->stream_id);
|
|
125
380
|
}
|
|
126
|
-
|
|
127
|
-
|
|
381
|
+
/* Last, and nothing that can fail after it: should this callback fail,
|
|
382
|
+
ngtcp2 keeps the stream, and the Stream must stay in @streams. */
|
|
383
|
+
VALUE streams = rb_ivar_get(a->c->owner, rb_intern("@streams"));
|
|
384
|
+
rb_hash_delete(streams, LL2NUM(a->stream_id));
|
|
385
|
+
return Qnil;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
int quic_stream_on_close(quic_conn_t *c, quic_stream_t *s, int64_t stream_id,
|
|
389
|
+
uint32_t flags, uint64_t app_error_code) {
|
|
390
|
+
if (s == NULL) {
|
|
391
|
+
quic_conn_give_back_peer_stream(c->conn, stream_id);
|
|
392
|
+
return 0;
|
|
393
|
+
}
|
|
394
|
+
struct quic_stream_cb_args a = {
|
|
395
|
+
.c = c,
|
|
396
|
+
.s = s,
|
|
397
|
+
.stream_id = stream_id,
|
|
398
|
+
.flags = flags,
|
|
399
|
+
.app_error_code = app_error_code,
|
|
400
|
+
};
|
|
401
|
+
return quic_conn_protect(c, quic_stream_on_close_body, &a);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
void quic_stream_on_reset(quic_stream_t *s, uint64_t app_error_code) {
|
|
405
|
+
if (s == NULL)
|
|
406
|
+
return;
|
|
407
|
+
s->reset = true;
|
|
408
|
+
s->close_app_error_code = app_error_code;
|
|
409
|
+
s->close_has_app_error_code = true;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
void quic_stream_on_recv_stop_sending(quic_conn_t *c, quic_stream_t *s,
|
|
413
|
+
uint64_t app_error_code) {
|
|
414
|
+
if (s == NULL)
|
|
415
|
+
return;
|
|
416
|
+
s->stop_app_error_code = app_error_code;
|
|
417
|
+
quic_stream_stop(c, s);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
struct quic_pick_send_args {
|
|
421
|
+
quic_stream_t *picked;
|
|
422
|
+
ngtcp2_vec *datav;
|
|
423
|
+
size_t *datavcnt;
|
|
424
|
+
uint32_t *flags;
|
|
425
|
+
};
|
|
426
|
+
|
|
427
|
+
static int quic_streams_pick_send_i(VALUE key, VALUE value, VALUE arg) {
|
|
428
|
+
(void)key;
|
|
429
|
+
struct quic_pick_send_args *a = (struct quic_pick_send_args *)arg;
|
|
430
|
+
if (!rb_typeddata_is_kind_of(value, &quic_stream_data_type))
|
|
431
|
+
return ST_CONTINUE;
|
|
432
|
+
quic_stream_t *s = (quic_stream_t *)RTYPEDDATA_DATA(value);
|
|
433
|
+
if (!quic_stream_next_send(s, a->datav, a->datavcnt, a->flags))
|
|
434
|
+
return ST_CONTINUE;
|
|
435
|
+
a->picked = s;
|
|
436
|
+
return ST_STOP;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/* Find the first stream in @streams that has either pending bytes or a
|
|
440
|
+
pending FIN to flush (linear scan, first match, no round-robin), and
|
|
441
|
+
what it has to send (quic_stream_next_send). Returns NULL if no stream
|
|
442
|
+
has anything. Raises nothing, so that #write_pkt fails only for what
|
|
443
|
+
ngtcp2 reports: a value that is not a QUIC::Stream is skipped, and an
|
|
444
|
+
@streams that is not a Hash holds no stream (only a test puts either
|
|
445
|
+
there). rb_hash_foreach raises only when the Hash changes while it
|
|
446
|
+
iterates, which the callback never does. */
|
|
447
|
+
static quic_stream_t *quic_streams_pick_send(VALUE self, ngtcp2_vec *out_datav,
|
|
448
|
+
size_t *out_datavcnt,
|
|
449
|
+
uint32_t *out_flags) {
|
|
450
|
+
VALUE streams = rb_ivar_get(self, rb_intern("@streams"));
|
|
451
|
+
if (!RB_TYPE_P(streams, T_HASH))
|
|
452
|
+
return NULL;
|
|
453
|
+
|
|
454
|
+
struct quic_pick_send_args a = {
|
|
455
|
+
.picked = NULL,
|
|
456
|
+
.datav = out_datav,
|
|
457
|
+
.datavcnt = out_datavcnt,
|
|
458
|
+
.flags = out_flags,
|
|
459
|
+
};
|
|
460
|
+
rb_hash_foreach(streams, quic_streams_pick_send_i, (VALUE)&a);
|
|
461
|
+
return a.picked;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/* If any stream has pending bytes or a pending FIN, route the packet
|
|
465
|
+
through ngtcp2_conn_writev_stream so the stream data gets framed
|
|
466
|
+
alongside connection-level frames. Falls back to conn-only when no stream
|
|
467
|
+
is queued. */
|
|
468
|
+
ngtcp2_ssize quic_streams_write_pkt(VALUE self, quic_conn_t *c,
|
|
469
|
+
ngtcp2_path *path, ngtcp2_pkt_info *pi,
|
|
470
|
+
uint8_t *dest, size_t destlen) {
|
|
471
|
+
ngtcp2_vec datav;
|
|
472
|
+
size_t datavcnt = 0;
|
|
473
|
+
uint32_t writev_flags = 0;
|
|
474
|
+
quic_stream_t *selected =
|
|
475
|
+
quic_streams_pick_send(self, &datav, &datavcnt, &writev_flags);
|
|
476
|
+
|
|
477
|
+
ngtcp2_ssize pdatalen = -1;
|
|
478
|
+
ngtcp2_ssize n = ngtcp2_conn_writev_stream(
|
|
479
|
+
c->conn, path, pi, dest, destlen, &pdatalen, writev_flags,
|
|
480
|
+
selected != NULL ? selected->stream_id : -1, &datav, datavcnt, c->now);
|
|
481
|
+
|
|
482
|
+
if ((n == NGTCP2_ERR_STREAM_DATA_BLOCKED || n == NGTCP2_ERR_STREAM_SHUT_WR) &&
|
|
483
|
+
NIL_P(c->pending_exc)) {
|
|
484
|
+
/* DATA_BLOCKED: the stream's queued data is beyond what the peer's flow
|
|
485
|
+
control lets through right now. Keep it queued; it follows once the
|
|
486
|
+
peer extends its window (MAX_STREAM_DATA / MAX_DATA).
|
|
487
|
+
SHUT_WR: ngtcp2 shut the stream's send side, and neither
|
|
488
|
+
recv_stop_sending nor #reset stopped the stream. What is queued can
|
|
489
|
+
never go out, so stop it now.
|
|
490
|
+
Either way, build the packet without the stream's data, so that ACKs
|
|
491
|
+
and other frames still go out. */
|
|
492
|
+
if (n == NGTCP2_ERR_STREAM_SHUT_WR)
|
|
493
|
+
quic_stream_stop(c, selected);
|
|
494
|
+
selected = NULL;
|
|
495
|
+
datavcnt = 0;
|
|
496
|
+
writev_flags = 0;
|
|
497
|
+
pdatalen = -1;
|
|
498
|
+
n = ngtcp2_conn_writev_stream(c->conn, path, pi, dest, destlen, &pdatalen,
|
|
499
|
+
0, -1, NULL, 0, c->now);
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
if (n < 0 || !NIL_P(c->pending_exc))
|
|
503
|
+
return n;
|
|
504
|
+
|
|
505
|
+
/* pdatalen >= 0 means ngtcp2 framed that many data bytes into the packet;
|
|
506
|
+
if 0, no data was framed (but the packet may still contain ACK or other
|
|
507
|
+
frames). */
|
|
508
|
+
if (selected != NULL)
|
|
509
|
+
quic_stream_sent(c, selected, datavcnt > 0 ? datav.len : 0, writev_flags,
|
|
510
|
+
pdatalen, n);
|
|
511
|
+
return n;
|
|
128
512
|
}
|
|
129
513
|
|
|
130
514
|
/* Blocking write: block by repeatedly invoking #pump_once on the owning
|
|
@@ -155,7 +539,7 @@ static VALUE quic_stream_write_m(int argc, VALUE *argv, VALUE self) {
|
|
|
155
539
|
raises QUIC::Error::NotBound if the connection has not been #bind'ed;
|
|
156
540
|
that error surfaces verbatim. */
|
|
157
541
|
quic_stream_check_writable(s);
|
|
158
|
-
while ((uint64_t)needed > quic_stream_window_left(
|
|
542
|
+
while ((uint64_t)needed > quic_stream_window_left(client_v, s)) {
|
|
159
543
|
rb_funcall(client_v, rb_intern("pump_once"), 0);
|
|
160
544
|
quic_stream_check_writable(s);
|
|
161
545
|
}
|
|
@@ -191,7 +575,7 @@ static VALUE quic_stream_write_nonblock_m(int argc, VALUE *argv, VALUE self) {
|
|
|
191
575
|
TypedData_Get_Struct(self, quic_stream_t, &quic_stream_data_type, s);
|
|
192
576
|
/* Before the window: a stream that cannot be written to never gets one. */
|
|
193
577
|
quic_stream_check_writable(s);
|
|
194
|
-
uint64_t avail = quic_stream_window_left(
|
|
578
|
+
uint64_t avail = quic_stream_window_left(client_v, s);
|
|
195
579
|
|
|
196
580
|
if (avail == 0) {
|
|
197
581
|
rb_raise(rb_eQUICErrorWaitWritable, "stream send window is full");
|
|
@@ -336,17 +720,22 @@ static VALUE quic_stream_reset_m(int argc, VALUE *argv, VALUE self) {
|
|
|
336
720
|
return Qnil;
|
|
337
721
|
}
|
|
338
722
|
|
|
339
|
-
|
|
340
|
-
int rv =
|
|
723
|
+
quic_conn_t *c = quic_conn_get(client_v);
|
|
724
|
+
int rv =
|
|
725
|
+
ngtcp2_conn_shutdown_stream_write(c->conn, 0, s->stream_id, error_code);
|
|
341
726
|
if (rv != 0)
|
|
342
727
|
quic_raise_ngtcp2_error(rv);
|
|
343
728
|
s->reset = true;
|
|
729
|
+
/* Now, not at the next #write_pkt: until then, what it had queued would
|
|
730
|
+
keep the other streams' #write_nonblock out of the window. */
|
|
731
|
+
quic_stream_stop(c, s);
|
|
344
732
|
return Qnil;
|
|
345
733
|
}
|
|
346
734
|
|
|
347
735
|
/* Called by #accept_stream / #accept_stream_nonblock for the stream they
|
|
348
736
|
take off @accept_queue. A stream that closed while it waited there gives
|
|
349
|
-
the peer back the stream it used up now (see
|
|
737
|
+
the peer back the stream it used up now (see
|
|
738
|
+
quic_stream_on_close_body). */
|
|
350
739
|
static VALUE quic_stream_accepted_m(VALUE self) {
|
|
351
740
|
quic_stream_t *s;
|
|
352
741
|
TypedData_Get_Struct(self, quic_stream_t, &quic_stream_data_type, s);
|