quic 0.0.1 → 0.0.3
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 +46 -1
- data/LICENSE-DEPENDENCIES.txt +84 -0
- data/README.md +94 -6
- data/Rakefile +7 -0
- data/examples/doq_demo.rb +122 -0
- data/examples/echo_demo.rb +24 -14
- data/examples/echo_server_demo.rb +127 -0
- data/examples/handshake_demo.rb +18 -20
- data/examples/io_loop_demo.rb +144 -0
- data/examples/verify_demo.rb +105 -0
- data/ext/quic/connection_client.c +154 -676
- data/ext/quic/connection_common.c +1262 -0
- data/ext/quic/connection_common.h +217 -0
- data/ext/quic/connection_server.c +186 -0
- data/ext/quic/endpoint.c +823 -0
- data/ext/quic/endpoint.h +28 -0
- data/ext/quic/ext_api.c +112 -0
- data/ext/quic/extconf.rb +145 -28
- data/ext/quic/include/quic/ext.h +84 -0
- data/ext/quic/patches/picotls/0001-openssl-use-raw-public-key-api-for-X25519.patch +74 -0
- data/ext/quic/quic.c +91 -54
- data/ext/quic/quic.h +43 -33
- data/ext/quic/stream.c +137 -40
- data/ext/quic/stream.h +17 -7
- data/ext/quic/tls_context.c +260 -0
- data/ext/quic/tls_context.h +46 -0
- data/lib/quic/connection.rb +173 -28
- data/lib/quic/endpoint.rb +57 -0
- data/lib/quic/ext_include.rb +12 -0
- data/lib/quic/settings.rb +46 -3
- data/lib/quic/stream.rb +20 -16
- data/lib/quic/transport_params.rb +1 -1
- data/lib/quic/version.rb +2 -2
- data/lib/quic.rb +3 -1
- data/sig/quic.rbs +231 -13
- metadata +43 -9
data/ext/quic/quic.c
CHANGED
|
@@ -1,24 +1,38 @@
|
|
|
1
1
|
#include "quic.h"
|
|
2
2
|
#include "stream.h"
|
|
3
|
+
#include "connection_common.h"
|
|
3
4
|
|
|
4
|
-
VALUE
|
|
5
|
-
VALUE
|
|
6
|
-
VALUE
|
|
5
|
+
VALUE rb_mQUIC;
|
|
6
|
+
VALUE rb_mQUICConnection;
|
|
7
|
+
VALUE rb_cQUICConnectionClient;
|
|
8
|
+
VALUE rb_cQUICConnectionServer;
|
|
7
9
|
|
|
8
|
-
VALUE
|
|
9
|
-
VALUE
|
|
10
|
-
VALUE
|
|
11
|
-
VALUE
|
|
12
|
-
VALUE
|
|
13
|
-
VALUE
|
|
14
|
-
VALUE
|
|
15
|
-
VALUE
|
|
16
|
-
VALUE
|
|
17
|
-
VALUE
|
|
18
|
-
VALUE
|
|
19
|
-
VALUE
|
|
20
|
-
VALUE
|
|
21
|
-
VALUE
|
|
10
|
+
VALUE rb_eQUICError;
|
|
11
|
+
VALUE rb_eQUICErrorProto;
|
|
12
|
+
VALUE rb_eQUICErrorDropConn;
|
|
13
|
+
VALUE rb_eQUICErrorRetry;
|
|
14
|
+
VALUE rb_eQUICErrorClosed;
|
|
15
|
+
VALUE rb_eQUICErrorCryptoError;
|
|
16
|
+
VALUE rb_eQUICErrorCertificateVerifyFailed;
|
|
17
|
+
VALUE rb_eQUICErrorHandshakeTimeout;
|
|
18
|
+
VALUE rb_eQUICErrorFlowControl;
|
|
19
|
+
VALUE rb_eQUICErrorUnknown;
|
|
20
|
+
VALUE rb_eQUICErrorWaitReadable;
|
|
21
|
+
VALUE rb_eQUICErrorWaitWritable;
|
|
22
|
+
VALUE rb_eQUICErrorStreamClosed;
|
|
23
|
+
VALUE rb_eQUICErrorStreamReset;
|
|
24
|
+
VALUE rb_eQUICErrorNotBound;
|
|
25
|
+
VALUE rb_eQUICErrorInvalidState;
|
|
26
|
+
|
|
27
|
+
/* The one symbol besides Init_quic that quic.so exports: other extensions
|
|
28
|
+
look it up with rb_ext_resolve_symbol("quic/quic", "quic_ext_get_api")
|
|
29
|
+
and get the C function table declared in quic/ext.h. NULL means the
|
|
30
|
+
caller was built against an incompatible major version. */
|
|
31
|
+
RUBY_FUNC_EXPORTED const quic_ext_api *
|
|
32
|
+
quic_ext_get_api(uint32_t abi_major)
|
|
33
|
+
{
|
|
34
|
+
return abi_major == QUIC_EXT_ABI_MAJOR ? &quic_ext_api_table : NULL;
|
|
35
|
+
}
|
|
22
36
|
|
|
23
37
|
static VALUE
|
|
24
38
|
quic_library_versions(VALUE self)
|
|
@@ -29,86 +43,109 @@ quic_library_versions(VALUE self)
|
|
|
29
43
|
rb_str_new_cstr(ngtcp2_version(0)->version_str));
|
|
30
44
|
rb_hash_aset(h, ID2SYM(rb_intern("openssl")),
|
|
31
45
|
rb_str_new_cstr(OpenSSL_version(OPENSSL_VERSION)));
|
|
46
|
+
/* picotls has neither a version macro nor releases, so report the commit
|
|
47
|
+
extconf.rb built against (passed in as -DQUIC_PICOTLS_COMMIT). */
|
|
48
|
+
rb_hash_aset(h, ID2SYM(rb_intern("picotls")),
|
|
49
|
+
rb_str_new_cstr(QUIC_PICOTLS_COMMIT));
|
|
32
50
|
return h;
|
|
33
51
|
}
|
|
34
52
|
|
|
35
|
-
|
|
36
|
-
|
|
53
|
+
VALUE
|
|
54
|
+
quic_ngtcp2_error_new(int rv)
|
|
37
55
|
{
|
|
38
56
|
VALUE cls;
|
|
39
57
|
switch (rv) {
|
|
40
58
|
case NGTCP2_ERR_PROTO:
|
|
41
|
-
cls =
|
|
59
|
+
cls = rb_eQUICErrorProto;
|
|
42
60
|
break;
|
|
43
61
|
case NGTCP2_ERR_DROP_CONN:
|
|
44
|
-
cls =
|
|
62
|
+
cls = rb_eQUICErrorDropConn;
|
|
45
63
|
break;
|
|
46
64
|
case NGTCP2_ERR_RETRY:
|
|
47
|
-
cls =
|
|
65
|
+
cls = rb_eQUICErrorRetry;
|
|
48
66
|
break;
|
|
49
67
|
case NGTCP2_ERR_CLOSING:
|
|
50
68
|
case NGTCP2_ERR_DRAINING:
|
|
51
|
-
cls =
|
|
69
|
+
cls = rb_eQUICErrorClosed;
|
|
52
70
|
break;
|
|
53
71
|
case NGTCP2_ERR_IDLE_CLOSE:
|
|
54
|
-
cls =
|
|
72
|
+
cls = rb_eQUICErrorClosed;
|
|
55
73
|
break;
|
|
56
74
|
case NGTCP2_ERR_CRYPTO:
|
|
57
|
-
cls =
|
|
75
|
+
cls = rb_eQUICErrorCryptoError;
|
|
58
76
|
break;
|
|
59
77
|
case NGTCP2_ERR_HANDSHAKE_TIMEOUT:
|
|
60
|
-
cls =
|
|
78
|
+
cls = rb_eQUICErrorHandshakeTimeout;
|
|
61
79
|
break;
|
|
62
80
|
case NGTCP2_ERR_STREAM_DATA_BLOCKED:
|
|
63
|
-
cls =
|
|
81
|
+
cls = rb_eQUICErrorFlowControl;
|
|
64
82
|
break;
|
|
65
83
|
case NGTCP2_ERR_STREAM_SHUT_WR:
|
|
66
|
-
cls =
|
|
84
|
+
cls = rb_eQUICErrorStreamClosed;
|
|
67
85
|
break;
|
|
68
86
|
default:
|
|
69
|
-
cls =
|
|
87
|
+
cls = rb_eQUICErrorUnknown;
|
|
70
88
|
break;
|
|
71
89
|
}
|
|
72
90
|
|
|
73
91
|
VALUE exc = rb_exc_new_cstr(cls, ngtcp2_strerror(rv));
|
|
74
92
|
rb_ivar_set(exc, rb_intern("@code"), INT2NUM(rv));
|
|
75
|
-
|
|
93
|
+
return exc;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
void
|
|
97
|
+
quic_raise_ngtcp2_error(int rv)
|
|
98
|
+
{
|
|
99
|
+
rb_exc_raise(quic_ngtcp2_error_new(rv));
|
|
76
100
|
}
|
|
77
101
|
|
|
78
102
|
RUBY_FUNC_EXPORTED void
|
|
79
103
|
Init_quic(void)
|
|
80
104
|
{
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
rb_define_singleton_method(
|
|
105
|
+
rb_mQUIC = rb_define_module("QUIC");
|
|
106
|
+
rb_mQUICConnection = rb_define_module_under(rb_mQUIC, "Connection");
|
|
107
|
+
rb_define_singleton_method(rb_mQUIC, "library_versions", quic_library_versions, 0);
|
|
84
108
|
|
|
85
|
-
/*
|
|
109
|
+
/* QUIC::Error is defined in Ruby (lib/quic.rb) before this Init_quic runs,
|
|
86
110
|
so rb_const_get retrieves the already-defined base class. Subclasses live
|
|
87
|
-
under
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
111
|
+
under QUIC::Error::<Name>. */
|
|
112
|
+
rb_eQUICError = rb_const_get(rb_mQUIC, rb_intern("Error"));
|
|
113
|
+
rb_eQUICErrorProto = rb_define_class_under(rb_eQUICError, "Proto", rb_eQUICError);
|
|
114
|
+
rb_eQUICErrorDropConn = rb_define_class_under(rb_eQUICError, "DropConn", rb_eQUICError);
|
|
115
|
+
rb_eQUICErrorRetry = rb_define_class_under(rb_eQUICError, "Retry", rb_eQUICError);
|
|
116
|
+
rb_eQUICErrorClosed = rb_define_class_under(rb_eQUICError, "Closed", rb_eQUICError);
|
|
117
|
+
rb_eQUICErrorCryptoError = rb_define_class_under(rb_eQUICError, "CryptoError", rb_eQUICError);
|
|
118
|
+
/* The TLS alert this endpoint sent, when read_pkt fails in the handshake.
|
|
119
|
+
Alerts received from the peer are not recorded by ngtcp2. */
|
|
120
|
+
rb_define_attr(rb_eQUICErrorCryptoError, "tls_alert", 1, 0);
|
|
121
|
+
rb_eQUICErrorCertificateVerifyFailed =
|
|
122
|
+
rb_define_class_under(rb_eQUICError, "CertificateVerifyFailed", rb_eQUICErrorCryptoError);
|
|
123
|
+
/* X509_V_ERR_* from the chain / name check, or nil when no certificate came. */
|
|
124
|
+
rb_define_attr(rb_eQUICErrorCertificateVerifyFailed, "verify_result", 1, 0);
|
|
125
|
+
rb_eQUICErrorHandshakeTimeout = rb_define_class_under(rb_eQUICError, "HandshakeTimeout", rb_eQUICError);
|
|
126
|
+
rb_eQUICErrorFlowControl = rb_define_class_under(rb_eQUICError, "FlowControl", rb_eQUICError);
|
|
127
|
+
rb_eQUICErrorUnknown = rb_define_class_under(rb_eQUICError, "Unknown", rb_eQUICError);
|
|
97
128
|
|
|
98
129
|
/* IO-style non-blocking errors. Mix in the standard IO::Wait{Readable,Writable}
|
|
99
|
-
modules so callers can `rescue IO::WaitReadable` (or the
|
|
130
|
+
modules so callers can `rescue IO::WaitReadable` (or the QUIC subclass)
|
|
100
131
|
interchangeably with stdlib IO objects. */
|
|
101
132
|
VALUE io_wait_readable = rb_const_get(rb_cIO, rb_intern("WaitReadable"));
|
|
102
133
|
VALUE io_wait_writable = rb_const_get(rb_cIO, rb_intern("WaitWritable"));
|
|
103
|
-
|
|
104
|
-
rb_include_module(
|
|
105
|
-
|
|
106
|
-
rb_include_module(
|
|
134
|
+
rb_eQUICErrorWaitReadable = rb_define_class_under(rb_eQUICError, "WaitReadable", rb_eQUICError);
|
|
135
|
+
rb_include_module(rb_eQUICErrorWaitReadable, io_wait_readable);
|
|
136
|
+
rb_eQUICErrorWaitWritable = rb_define_class_under(rb_eQUICError, "WaitWritable", rb_eQUICError);
|
|
137
|
+
rb_include_module(rb_eQUICErrorWaitWritable, io_wait_writable);
|
|
107
138
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
139
|
+
rb_eQUICErrorStreamClosed = rb_define_class_under(rb_eQUICError, "StreamClosed", rb_eQUICError);
|
|
140
|
+
rb_eQUICErrorStreamReset = rb_define_class_under(rb_eQUICError, "StreamReset", rb_eQUICError);
|
|
141
|
+
rb_eQUICErrorNotBound = rb_define_class_under(rb_eQUICError, "NotBound", rb_eQUICError);
|
|
142
|
+
/* Raised by the QUIC::Stream methods of a connection whose streams a C
|
|
143
|
+
stream handler (quic/ext.h) owns. */
|
|
144
|
+
rb_eQUICErrorInvalidState = rb_define_class_under(rb_eQUICError, "InvalidState", rb_eQUICError);
|
|
111
145
|
|
|
112
|
-
|
|
113
|
-
|
|
146
|
+
Init_quic_tls_context(rb_mQUIC);
|
|
147
|
+
Init_quic_connection_client(rb_mQUICConnection);
|
|
148
|
+
Init_quic_connection_server(rb_mQUICConnection);
|
|
149
|
+
Init_quic_endpoint(rb_mQUIC);
|
|
150
|
+
Init_quic_stream(rb_mQUIC);
|
|
114
151
|
}
|
data/ext/quic/quic.h
CHANGED
|
@@ -6,39 +6,49 @@
|
|
|
6
6
|
|
|
7
7
|
#include <ngtcp2/ngtcp2.h>
|
|
8
8
|
#include <ngtcp2/ngtcp2_crypto.h>
|
|
9
|
-
#include <
|
|
10
|
-
#include <openssl
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
extern VALUE
|
|
22
|
-
extern VALUE
|
|
23
|
-
extern VALUE
|
|
24
|
-
extern VALUE
|
|
25
|
-
|
|
26
|
-
extern VALUE
|
|
27
|
-
extern VALUE
|
|
28
|
-
extern VALUE
|
|
29
|
-
extern VALUE
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
9
|
+
#include <picotls.h>
|
|
10
|
+
#include <picotls/openssl.h>
|
|
11
|
+
#include <ngtcp2/ngtcp2_crypto_picotls.h>
|
|
12
|
+
#include <openssl/crypto.h>
|
|
13
|
+
|
|
14
|
+
/* picotls disables ptls_openssl_x25519 unless it is built with the patch in
|
|
15
|
+
ext/quic/patches/picotls/. extconf.rb checks the same condition at configure
|
|
16
|
+
time; this catches a prebuilt ports/ tree whose patch was swapped out. */
|
|
17
|
+
#if !PTLS_OPENSSL_HAVE_X25519
|
|
18
|
+
#error "picotls was built without X25519; is ext/quic/patches/picotls/*.patch applied?"
|
|
19
|
+
#endif
|
|
20
|
+
|
|
21
|
+
extern VALUE rb_mQUIC;
|
|
22
|
+
extern VALUE rb_mQUICConnection;
|
|
23
|
+
extern VALUE rb_cQUICConnectionClient;
|
|
24
|
+
extern VALUE rb_cQUICConnectionServer;
|
|
25
|
+
|
|
26
|
+
extern VALUE rb_eQUICError;
|
|
27
|
+
extern VALUE rb_eQUICErrorProto;
|
|
28
|
+
extern VALUE rb_eQUICErrorDropConn;
|
|
29
|
+
extern VALUE rb_eQUICErrorRetry;
|
|
30
|
+
extern VALUE rb_eQUICErrorClosed;
|
|
31
|
+
extern VALUE rb_eQUICErrorCryptoError;
|
|
32
|
+
extern VALUE rb_eQUICErrorCertificateVerifyFailed;
|
|
33
|
+
extern VALUE rb_eQUICErrorHandshakeTimeout;
|
|
34
|
+
extern VALUE rb_eQUICErrorFlowControl;
|
|
35
|
+
extern VALUE rb_eQUICErrorUnknown;
|
|
36
|
+
extern VALUE rb_eQUICErrorWaitReadable;
|
|
37
|
+
extern VALUE rb_eQUICErrorWaitWritable;
|
|
38
|
+
extern VALUE rb_eQUICErrorStreamClosed;
|
|
39
|
+
extern VALUE rb_eQUICErrorStreamReset;
|
|
40
|
+
extern VALUE rb_eQUICErrorNotBound;
|
|
41
|
+
extern VALUE rb_eQUICErrorInvalidState;
|
|
42
|
+
|
|
43
|
+
void Init_quic_connection_client(VALUE rb_mQUICConnection);
|
|
44
|
+
void Init_quic_connection_server(VALUE rb_mQUICConnection);
|
|
45
|
+
void Init_quic_endpoint(VALUE rb_mQUIC);
|
|
46
|
+
|
|
47
|
+
/* A QUIC::Error subclass instance mapped from an ngtcp2 negative error code,
|
|
48
|
+
with the code in #code. Made, not raised. */
|
|
49
|
+
VALUE quic_ngtcp2_error_new(int rv);
|
|
50
|
+
|
|
51
|
+
/* Raise quic_ngtcp2_error_new(rv). Never returns. */
|
|
42
52
|
NORETURN(void quic_raise_ngtcp2_error(int rv));
|
|
43
53
|
|
|
44
54
|
#endif /* QUIC_H */
|
data/ext/quic/stream.c
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
#include "stream.h"
|
|
2
|
+
#include "connection_common.h"
|
|
2
3
|
|
|
3
4
|
#include <string.h>
|
|
4
5
|
|
|
5
|
-
VALUE
|
|
6
|
+
VALUE rb_cQUICStream;
|
|
6
7
|
|
|
7
8
|
static void
|
|
8
9
|
quic_stream_free(void *ptr)
|
|
@@ -18,7 +19,7 @@ quic_stream_size(const void *ptr)
|
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
const rb_data_type_t quic_stream_data_type = {
|
|
21
|
-
"
|
|
22
|
+
"QUIC::Stream",
|
|
22
23
|
{NULL, quic_stream_free, quic_stream_size,},
|
|
23
24
|
NULL, NULL,
|
|
24
25
|
RUBY_TYPED_FREE_IMMEDIATELY,
|
|
@@ -33,15 +34,15 @@ quic_stream_alloc(VALUE klass)
|
|
|
33
34
|
}
|
|
34
35
|
|
|
35
36
|
VALUE
|
|
36
|
-
quic_stream_new(int64_t stream_id, VALUE
|
|
37
|
+
quic_stream_new(int64_t stream_id, VALUE owner)
|
|
37
38
|
{
|
|
38
|
-
VALUE self = quic_stream_alloc(
|
|
39
|
+
VALUE self = quic_stream_alloc(rb_cQUICStream);
|
|
39
40
|
quic_stream_t *s;
|
|
40
41
|
TypedData_Get_Struct(self, quic_stream_t, &quic_stream_data_type, s);
|
|
41
42
|
s->stream_id = stream_id;
|
|
42
43
|
|
|
43
44
|
rb_ivar_set(self, rb_intern("@id"), LL2NUM(stream_id));
|
|
44
|
-
rb_ivar_set(self, rb_intern("@client"),
|
|
45
|
+
rb_ivar_set(self, rb_intern("@client"), owner);
|
|
45
46
|
rb_ivar_set(self, rb_intern("@pending_chunks"), rb_ary_new());
|
|
46
47
|
/* recv_buffer is a binary String; rb_str_buf_new returns ASCII-8BIT. */
|
|
47
48
|
rb_ivar_set(self, rb_intern("@recv_buffer"), rb_str_buf_new(0));
|
|
@@ -53,7 +54,7 @@ quic_stream_new(int64_t stream_id, VALUE client)
|
|
|
53
54
|
quic_stream_write_m and #write_nonblock returns a partial byte count in
|
|
54
55
|
quic_stream_write_nonblock_m). This helper is reached only after the
|
|
55
56
|
caller has confirmed the bytes fit in the current send window, so it
|
|
56
|
-
never raises
|
|
57
|
+
never raises QUIC::Error::WaitWritable on its own. */
|
|
57
58
|
static VALUE
|
|
58
59
|
quic_stream_enqueue(VALUE self, VALUE data, bool fin)
|
|
59
60
|
{
|
|
@@ -61,10 +62,10 @@ quic_stream_enqueue(VALUE self, VALUE data, bool fin)
|
|
|
61
62
|
TypedData_Get_Struct(self, quic_stream_t, &quic_stream_data_type, s);
|
|
62
63
|
|
|
63
64
|
if (s->reset || s->closed) {
|
|
64
|
-
rb_raise(
|
|
65
|
+
rb_raise(rb_eQUICErrorStreamClosed, "stream is closed");
|
|
65
66
|
}
|
|
66
67
|
if (s->fin_sent) {
|
|
67
|
-
rb_raise(
|
|
68
|
+
rb_raise(rb_eQUICErrorStreamClosed, "stream FIN already sent");
|
|
68
69
|
}
|
|
69
70
|
|
|
70
71
|
Check_Type(data, T_STRING);
|
|
@@ -96,22 +97,35 @@ quic_stream_fin_kwarg(int argc, VALUE *argv, VALUE *data)
|
|
|
96
97
|
return RTEST(fin);
|
|
97
98
|
}
|
|
98
99
|
|
|
99
|
-
/* Return the effective send window for this stream
|
|
100
|
-
per-stream and the per-connection flow control limits ngtcp2 tracks
|
|
100
|
+
/* Return the effective send window for this stream: the smaller of the
|
|
101
|
+
per-stream and the per-connection flow control limits ngtcp2 tracks, less
|
|
102
|
+
the bytes already queued in @pending_chunks but not yet handed to ngtcp2.
|
|
103
|
+
ngtcp2 only counts what it has sent, so without the subtraction a run of
|
|
104
|
+
writes could queue far more than the peer allows. */
|
|
101
105
|
static uint64_t
|
|
102
|
-
quic_stream_window_left(VALUE client_v, quic_stream_t *s)
|
|
106
|
+
quic_stream_window_left(VALUE self, VALUE client_v, quic_stream_t *s)
|
|
103
107
|
{
|
|
104
|
-
ngtcp2_conn *conn =
|
|
105
|
-
uint64_t stream_left =
|
|
106
|
-
uint64_t conn_left =
|
|
107
|
-
|
|
108
|
+
ngtcp2_conn *conn = quic_conn_ptr(client_v);
|
|
109
|
+
uint64_t stream_left = ngtcp2_conn_get_max_stream_data_left2(conn, s->stream_id);
|
|
110
|
+
uint64_t conn_left = ngtcp2_conn_get_max_data_left2(conn);
|
|
111
|
+
uint64_t left = stream_left < conn_left ? stream_left : conn_left;
|
|
112
|
+
|
|
113
|
+
/* @pending_chunks starts at stream offset pending_shifted, and the bytes
|
|
114
|
+
up to sent_offset have been sent. */
|
|
115
|
+
VALUE pending = rb_ivar_get(self, rb_intern("@pending_chunks"));
|
|
116
|
+
uint64_t queued_end = s->pending_shifted;
|
|
117
|
+
for (long i = 0; i < RARRAY_LEN(pending); i++) {
|
|
118
|
+
queued_end += (uint64_t)RSTRING_LEN(RARRAY_AREF(pending, i));
|
|
119
|
+
}
|
|
120
|
+
uint64_t unsent = queued_end - s->sent_offset;
|
|
121
|
+
return left > unsent ? left - unsent : 0;
|
|
108
122
|
}
|
|
109
123
|
|
|
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
|
|
114
|
-
the window check entirely. */
|
|
124
|
+
/* Blocking write: block by repeatedly invoking #pump_once on the owning
|
|
125
|
+
connection (@client) until the peer's flow control window has enough room
|
|
126
|
+
for the full payload, then enqueue. IO#write-compatible: always queues all
|
|
127
|
+
of `data`. Bare Streams (built via QUIC::Stream.allocate for unit tests,
|
|
128
|
+
with @client = nil) skip the window check entirely. */
|
|
115
129
|
static VALUE
|
|
116
130
|
quic_stream_write_m(int argc, VALUE *argv, VALUE self)
|
|
117
131
|
{
|
|
@@ -132,9 +146,9 @@ quic_stream_write_m(int argc, VALUE *argv, VALUE self)
|
|
|
132
146
|
quic_stream_t *s;
|
|
133
147
|
TypedData_Get_Struct(self, quic_stream_t, &quic_stream_data_type, s);
|
|
134
148
|
/* Loop until the full payload would fit in the current window. pump_once
|
|
135
|
-
raises
|
|
136
|
-
error surfaces verbatim. */
|
|
137
|
-
while ((uint64_t)needed > quic_stream_window_left(client_v, s)) {
|
|
149
|
+
raises QUIC::Error::NotBound if the connection has not been #bind'ed;
|
|
150
|
+
that error surfaces verbatim. */
|
|
151
|
+
while ((uint64_t)needed > quic_stream_window_left(self, client_v, s)) {
|
|
138
152
|
rb_funcall(client_v, rb_intern("pump_once"), 0);
|
|
139
153
|
}
|
|
140
154
|
}
|
|
@@ -143,7 +157,7 @@ quic_stream_write_m(int argc, VALUE *argv, VALUE self)
|
|
|
143
157
|
}
|
|
144
158
|
|
|
145
159
|
/* Non-blocking write: enqueue at most `window_left` bytes from `data` and
|
|
146
|
-
return the count actually queued. Raises
|
|
160
|
+
return the count actually queued. Raises QUIC::Error::WaitWritable if the
|
|
147
161
|
window is zero and we have a non-empty payload to send. If only a partial
|
|
148
162
|
prefix fits, FIN is NOT set on this call (caller re-issues with
|
|
149
163
|
`fin: true` once the remainder is accepted) so we don't half-commit FIN.
|
|
@@ -169,10 +183,10 @@ quic_stream_write_nonblock_m(int argc, VALUE *argv, VALUE self)
|
|
|
169
183
|
|
|
170
184
|
quic_stream_t *s;
|
|
171
185
|
TypedData_Get_Struct(self, quic_stream_t, &quic_stream_data_type, s);
|
|
172
|
-
uint64_t avail = quic_stream_window_left(client_v, s);
|
|
186
|
+
uint64_t avail = quic_stream_window_left(self, client_v, s);
|
|
173
187
|
|
|
174
188
|
if (avail == 0) {
|
|
175
|
-
rb_raise(
|
|
189
|
+
rb_raise(rb_eQUICErrorWaitWritable, "stream send window is full");
|
|
176
190
|
}
|
|
177
191
|
|
|
178
192
|
long take = ((uint64_t)needed <= avail) ? needed : (long)avail;
|
|
@@ -198,7 +212,7 @@ quic_stream_close_write_m(VALUE self)
|
|
|
198
212
|
to drop the returned prefix.
|
|
199
213
|
|
|
200
214
|
- buffer empty + fin_received: raise EOFError (IO#read_nonblock semantics)
|
|
201
|
-
- buffer empty + !fin_received: raise
|
|
215
|
+
- buffer empty + !fin_received: raise QUIC::Error::WaitReadable
|
|
202
216
|
- buffer non-empty: return min(length, buffer.bytesize) bytes
|
|
203
217
|
*/
|
|
204
218
|
static VALUE
|
|
@@ -218,7 +232,7 @@ quic_stream_read_nonblock_m(VALUE self, VALUE length_v)
|
|
|
218
232
|
if (s->fin_received) {
|
|
219
233
|
rb_raise(rb_eEOFError, "end of file reached");
|
|
220
234
|
}
|
|
221
|
-
rb_raise(
|
|
235
|
+
rb_raise(rb_eQUICErrorWaitReadable, "no data available");
|
|
222
236
|
}
|
|
223
237
|
|
|
224
238
|
long take = (length < have) ? length : have;
|
|
@@ -231,6 +245,19 @@ quic_stream_read_nonblock_m(VALUE self, VALUE length_v)
|
|
|
231
245
|
}
|
|
232
246
|
rb_str_set_len(buffer, have - take);
|
|
233
247
|
|
|
248
|
+
/* The application has consumed |take| bytes, so give the peer that much
|
|
249
|
+
more stream and connection credit. Without this the peer stops sending
|
|
250
|
+
once initial_max_data / initial_max_stream_data are used up. A stream
|
|
251
|
+
ngtcp2 has already forgotten is ignored (the call returns 0), but the
|
|
252
|
+
connection window still grows. Bare Streams have no connection. */
|
|
253
|
+
VALUE client_v = rb_ivar_get(self, rb_intern("@client"));
|
|
254
|
+
if (!NIL_P(client_v)) {
|
|
255
|
+
ngtcp2_conn *conn = quic_conn_ptr(client_v);
|
|
256
|
+
int rv = ngtcp2_conn_extend_max_stream_offset(conn, s->stream_id, (uint64_t)take);
|
|
257
|
+
if (rv != 0) quic_raise_ngtcp2_error(rv);
|
|
258
|
+
ngtcp2_conn_extend_max_offset(conn, (uint64_t)take);
|
|
259
|
+
}
|
|
260
|
+
|
|
234
261
|
return out;
|
|
235
262
|
}
|
|
236
263
|
|
|
@@ -243,6 +270,19 @@ quic_stream_eof_p(VALUE self)
|
|
|
243
270
|
return (RSTRING_LEN(buffer) == 0 && s->fin_received) ? Qtrue : Qfalse;
|
|
244
271
|
}
|
|
245
272
|
|
|
273
|
+
/* True once the stream is fully closed: both directions have finished (FIN
|
|
274
|
+
or reset) and everything sent has been acknowledged. Stays false if the
|
|
275
|
+
connection closes first, since ngtcp2 does not report stream closure then.
|
|
276
|
+
Keeps returning true after the stream has left the connection's @streams.
|
|
277
|
+
A bare Stream is never closed. */
|
|
278
|
+
static VALUE
|
|
279
|
+
quic_stream_closed_p(VALUE self)
|
|
280
|
+
{
|
|
281
|
+
quic_stream_t *s;
|
|
282
|
+
TypedData_Get_Struct(self, quic_stream_t, &quic_stream_data_type, s);
|
|
283
|
+
return s->closed ? Qtrue : Qfalse;
|
|
284
|
+
}
|
|
285
|
+
|
|
246
286
|
static VALUE
|
|
247
287
|
quic_stream_close_read_m(VALUE self)
|
|
248
288
|
{
|
|
@@ -250,10 +290,10 @@ quic_stream_close_read_m(VALUE self)
|
|
|
250
290
|
TypedData_Get_Struct(self, quic_stream_t, &quic_stream_data_type, s);
|
|
251
291
|
|
|
252
292
|
VALUE client_v = rb_ivar_get(self, rb_intern("@client"));
|
|
253
|
-
ngtcp2_conn *conn =
|
|
293
|
+
ngtcp2_conn *conn = quic_conn_ptr(client_v);
|
|
254
294
|
|
|
255
295
|
/* ngtcp2_conn_shutdown_stream_read sends STOP_SENDING. app_error_code 0
|
|
256
|
-
since no
|
|
296
|
+
since no QUIC-level error API is exposed yet. */
|
|
257
297
|
int rv = ngtcp2_conn_shutdown_stream_read(conn, 0, s->stream_id, 0);
|
|
258
298
|
if (rv != 0) quic_raise_ngtcp2_error(rv);
|
|
259
299
|
return Qnil;
|
|
@@ -268,17 +308,74 @@ quic_stream_close_m(VALUE self)
|
|
|
268
308
|
return Qnil;
|
|
269
309
|
}
|
|
270
310
|
|
|
311
|
+
/* Abort the send side of the stream with RESET_STREAM
|
|
312
|
+
(ngtcp2_conn_shutdown_stream_write), carrying the given application error
|
|
313
|
+
code (default 0). After #reset, #write / #write_nonblock raise
|
|
314
|
+
QUIC::Error::StreamClosed because quic_stream_enqueue rejects a stream
|
|
315
|
+
with s->reset set. The read side is untouched (use #close_read /
|
|
316
|
+
#close for STOP_SENDING). Idempotent: a second #reset is a no-op.
|
|
317
|
+
|
|
318
|
+
A bare Stream (@client == nil, used by unit-test fixtures) skips the
|
|
319
|
+
ngtcp2 call and only flips s->reset, mirroring the #write / #close_write
|
|
320
|
+
bare-Stream escapes. */
|
|
321
|
+
static VALUE
|
|
322
|
+
quic_stream_reset_m(int argc, VALUE *argv, VALUE self)
|
|
323
|
+
{
|
|
324
|
+
VALUE error_code_v = Qnil;
|
|
325
|
+
rb_scan_args(argc, argv, "01", &error_code_v);
|
|
326
|
+
uint64_t error_code = NIL_P(error_code_v) ? 0 : NUM2ULL(error_code_v);
|
|
327
|
+
|
|
328
|
+
quic_stream_t *s;
|
|
329
|
+
TypedData_Get_Struct(self, quic_stream_t, &quic_stream_data_type, s);
|
|
330
|
+
|
|
331
|
+
if (s->reset) return Qnil; /* idempotent */
|
|
332
|
+
|
|
333
|
+
VALUE client_v = rb_ivar_get(self, rb_intern("@client"));
|
|
334
|
+
if (NIL_P(client_v)) {
|
|
335
|
+
s->reset = true; /* bare Stream escape */
|
|
336
|
+
return Qnil;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
ngtcp2_conn *conn = quic_conn_ptr(client_v);
|
|
340
|
+
int rv = ngtcp2_conn_shutdown_stream_write(conn, 0, s->stream_id, error_code);
|
|
341
|
+
if (rv != 0) quic_raise_ngtcp2_error(rv);
|
|
342
|
+
s->reset = true;
|
|
343
|
+
return Qnil;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/* Called by #accept_stream / #accept_stream_nonblock for the stream they
|
|
347
|
+
take off @accept_queue. A stream that closed while it waited there gives
|
|
348
|
+
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
|
+
{
|
|
352
|
+
quic_stream_t *s;
|
|
353
|
+
TypedData_Get_Struct(self, quic_stream_t, &quic_stream_data_type, s);
|
|
354
|
+
s->accepted = true;
|
|
355
|
+
if (s->peer_stream_held) {
|
|
356
|
+
s->peer_stream_held = false;
|
|
357
|
+
VALUE client_v = rb_ivar_get(self, rb_intern("@client"));
|
|
358
|
+
if (!NIL_P(client_v)) {
|
|
359
|
+
quic_conn_give_back_peer_stream(quic_conn_ptr(client_v), s->stream_id);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
return Qnil;
|
|
363
|
+
}
|
|
364
|
+
|
|
271
365
|
void
|
|
272
|
-
Init_quic_stream(VALUE
|
|
366
|
+
Init_quic_stream(VALUE rb_mQUICArg)
|
|
273
367
|
{
|
|
274
|
-
|
|
275
|
-
rb_define_alloc_func(
|
|
276
|
-
rb_define_method(
|
|
277
|
-
rb_define_method(
|
|
278
|
-
rb_define_method(
|
|
279
|
-
rb_define_method(
|
|
280
|
-
rb_define_method(
|
|
281
|
-
rb_define_method(
|
|
282
|
-
rb_define_method(
|
|
368
|
+
rb_cQUICStream = rb_define_class_under(rb_mQUICArg, "Stream", rb_cObject);
|
|
369
|
+
rb_define_alloc_func(rb_cQUICStream, quic_stream_alloc);
|
|
370
|
+
rb_define_method(rb_cQUICStream, "write", quic_stream_write_m, -1);
|
|
371
|
+
rb_define_method(rb_cQUICStream, "write_nonblock", quic_stream_write_nonblock_m, -1);
|
|
372
|
+
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, 1);
|
|
374
|
+
rb_define_method(rb_cQUICStream, "eof?", quic_stream_eof_p, 0);
|
|
375
|
+
rb_define_method(rb_cQUICStream, "closed?", quic_stream_closed_p, 0);
|
|
376
|
+
rb_define_method(rb_cQUICStream, "close_read", quic_stream_close_read_m, 0);
|
|
377
|
+
rb_define_method(rb_cQUICStream, "close", quic_stream_close_m, 0);
|
|
378
|
+
rb_define_method(rb_cQUICStream, "reset", quic_stream_reset_m, -1);
|
|
379
|
+
rb_define_private_method(rb_cQUICStream, "_accepted", quic_stream_accepted_m, 0);
|
|
283
380
|
/* #read (blocking) and #initiator are defined in lib/quic/stream.rb. */
|
|
284
381
|
}
|
data/ext/quic/stream.h
CHANGED
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
|
|
4
4
|
#include "quic.h"
|
|
5
5
|
|
|
6
|
-
extern VALUE
|
|
6
|
+
extern VALUE rb_cQUICStream;
|
|
7
7
|
|
|
8
|
-
void Init_quic_stream(VALUE
|
|
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 {
|
|
@@ -29,13 +29,23 @@ typedef struct {
|
|
|
29
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 stream has been pushed onto the owning
|
|
33
|
+
connection's @accept_queue, so recv_stream_data does not enqueue it
|
|
34
|
+
again on subsequent data arrivals. */
|
|
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;
|
|
32
41
|
} quic_stream_t;
|
|
33
42
|
|
|
34
43
|
extern const rb_data_type_t quic_stream_data_type;
|
|
35
44
|
|
|
36
|
-
/* Allocate a new
|
|
37
|
-
quic_stream_t
|
|
38
|
-
@pending_chunks / @recv_buffer).
|
|
39
|
-
|
|
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);
|
|
40
50
|
|
|
41
51
|
#endif /* QUIC_STREAM_H */
|