quic 0.0.5 → 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 +18 -0
- data/LICENSE-DEPENDENCIES.txt +2 -2
- data/README.md +1 -1
- data/ext/quic/connection_common.c +116 -425
- data/ext/quic/connection_common.h +31 -5
- data/ext/quic/ext_api.c +7 -5
- data/ext/quic/extconf.rb +5 -5
- data/ext/quic/include/quic/ext.h +22 -1
- 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
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);
|