quic 0.0.2 → 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 +24 -0
- data/LICENSE-DEPENDENCIES.txt +2 -19
- data/README.md +27 -2
- data/Rakefile +7 -0
- data/examples/echo_demo.rb +5 -6
- data/examples/echo_server_demo.rb +127 -0
- data/ext/quic/connection_client.c +23 -880
- 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 +12 -5
- data/ext/quic/include/quic/ext.h +84 -0
- data/ext/quic/quic.c +29 -4
- data/ext/quic/quic.h +8 -8
- data/ext/quic/stream.c +81 -20
- data/ext/quic/stream.h +13 -7
- data/ext/quic/tls_context.c +260 -0
- data/ext/quic/tls_context.h +46 -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 +13 -1
data/ext/quic/endpoint.h
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
#ifndef QUIC_ENDPOINT_H
|
|
2
|
+
#define QUIC_ENDPOINT_H 1
|
|
3
|
+
|
|
4
|
+
#include "connection_common.h"
|
|
5
|
+
|
|
6
|
+
extern VALUE rb_cQUICEndpoint;
|
|
7
|
+
|
|
8
|
+
/* The CID table, for the get_new_connection_id2 and remove_connection_id
|
|
9
|
+
callbacks of a connection the Endpoint holds. conn is that connection's
|
|
10
|
+
object. Neither touches Ruby objects, so the callbacks need no
|
|
11
|
+
rb_protect. dissociate leaves an entry that maps to another connection
|
|
12
|
+
alone. */
|
|
13
|
+
void quic_endpoint_associate_cid(quic_endpoint_t *ep, const ngtcp2_cid *cid, VALUE conn);
|
|
14
|
+
void quic_endpoint_dissociate_cid(quic_endpoint_t *ep, const ngtcp2_cid *cid, VALUE conn);
|
|
15
|
+
|
|
16
|
+
/* The Stateless Reset token for cid, derived from the Endpoint's secret so
|
|
17
|
+
that it can be made again for a CID no longer in the table. token is
|
|
18
|
+
NGTCP2_STATELESS_RESET_TOKENLEN bytes. Returns 0, or -1 on failure. */
|
|
19
|
+
int quic_endpoint_generate_reset_token(const quic_endpoint_t *ep, uint8_t *token,
|
|
20
|
+
const ngtcp2_cid *cid);
|
|
21
|
+
|
|
22
|
+
/* Connection#close of a connection the Endpoint holds: build the
|
|
23
|
+
CONNECTION_CLOSE carrying ccerr, queue it for Endpoint#write_pkt and
|
|
24
|
+
start the closing period. When ngtcp2 cannot build the packet, the
|
|
25
|
+
connection leaves the table at once. */
|
|
26
|
+
void quic_endpoint_close_conn(quic_endpoint_t *ep, quic_conn_t *c, const ngtcp2_ccerr *ccerr);
|
|
27
|
+
|
|
28
|
+
#endif /* QUIC_ENDPOINT_H */
|
data/ext/quic/ext_api.c
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
#include "connection_common.h"
|
|
2
|
+
|
|
3
|
+
/* The quic_ext_api table of quic/ext.h. A quic_ext_conn * is a quic_conn_t *
|
|
4
|
+
behind the opaque type. None of these functions raises a Ruby exception
|
|
5
|
+
except quic_ext_conn_get; they return 0 or a QUIC_EXT_ERR_* code. */
|
|
6
|
+
|
|
7
|
+
static quic_conn_t *
|
|
8
|
+
quic_ext_conn_struct(quic_ext_conn *ec)
|
|
9
|
+
{
|
|
10
|
+
return (quic_conn_t *)ec;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/* Raises TypeError unless conn is a QUIC::Connection::Client or Server. */
|
|
14
|
+
static quic_ext_conn *
|
|
15
|
+
quic_ext_conn_get(VALUE conn)
|
|
16
|
+
{
|
|
17
|
+
return (quic_ext_conn *)rb_check_typeddata(conn, &quic_conn_data_type);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/* Hand every stream of the connection to h. Allowed once, and only before
|
|
21
|
+
the connection has a QUIC::Stream: the two modes cannot share streams.
|
|
22
|
+
Every member of the ABI 1.0 handler is called, so h must be at least that
|
|
23
|
+
large. @stream_handler marks the connection for the Ruby side, which then
|
|
24
|
+
refuses #accept_stream. */
|
|
25
|
+
static int
|
|
26
|
+
quic_ext_set_stream_handler(quic_ext_conn *ec, const quic_ext_stream_handler *h,
|
|
27
|
+
void *ud, VALUE owner)
|
|
28
|
+
{
|
|
29
|
+
quic_conn_t *c = quic_ext_conn_struct(ec);
|
|
30
|
+
if (h == NULL || h->size < sizeof(quic_ext_stream_handler)) return QUIC_EXT_ERR_OTHER;
|
|
31
|
+
if (c->handler != NULL) return QUIC_EXT_ERR_INVALID_STATE;
|
|
32
|
+
VALUE streams = rb_ivar_get(c->owner, rb_intern("@streams"));
|
|
33
|
+
if (RB_TYPE_P(streams, T_HASH) && RHASH_SIZE(streams) > 0) return QUIC_EXT_ERR_INVALID_STATE;
|
|
34
|
+
|
|
35
|
+
c->handler = h;
|
|
36
|
+
c->handler_ud = ud;
|
|
37
|
+
c->handler_owner = owner;
|
|
38
|
+
rb_ivar_set(c->owner, rb_intern("@stream_handler"), Qtrue);
|
|
39
|
+
return 0;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
static int
|
|
43
|
+
quic_ext_open_uni_stream(quic_ext_conn *ec, int64_t *id)
|
|
44
|
+
{
|
|
45
|
+
quic_conn_t *c = quic_ext_conn_struct(ec);
|
|
46
|
+
int rv = ngtcp2_conn_open_uni_stream(c->conn, id, NULL);
|
|
47
|
+
if (rv == NGTCP2_ERR_STREAM_ID_BLOCKED) return QUIC_EXT_ERR_STREAM_ID_BLOCKED;
|
|
48
|
+
return rv == 0 ? 0 : QUIC_EXT_ERR_OTHER;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/* Give the peer n more bytes of credit on the stream and the connection,
|
|
52
|
+
for data the handler has now consumed (for HTTP/3, request body handed
|
|
53
|
+
to the application). */
|
|
54
|
+
static int
|
|
55
|
+
quic_ext_consume(quic_ext_conn *ec, int64_t id, uint64_t n)
|
|
56
|
+
{
|
|
57
|
+
quic_conn_t *c = quic_ext_conn_struct(ec);
|
|
58
|
+
if (ngtcp2_conn_extend_max_stream_offset(c->conn, id, n) != 0) return QUIC_EXT_ERR_OTHER;
|
|
59
|
+
ngtcp2_conn_extend_max_offset(c->conn, n);
|
|
60
|
+
return 0;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/* Both return 0 for a stream ngtcp2 no longer knows. */
|
|
64
|
+
static int
|
|
65
|
+
quic_ext_shutdown_stream_read(quic_ext_conn *ec, int64_t id, uint64_t app_error_code)
|
|
66
|
+
{
|
|
67
|
+
quic_conn_t *c = quic_ext_conn_struct(ec);
|
|
68
|
+
return ngtcp2_conn_shutdown_stream_read(c->conn, 0, id, app_error_code) == 0
|
|
69
|
+
? 0 : QUIC_EXT_ERR_OTHER;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
static int
|
|
73
|
+
quic_ext_shutdown_stream_write(quic_ext_conn *ec, int64_t id, uint64_t app_error_code)
|
|
74
|
+
{
|
|
75
|
+
quic_conn_t *c = quic_ext_conn_struct(ec);
|
|
76
|
+
return ngtcp2_conn_shutdown_stream_write(c->conn, 0, id, app_error_code) == 0
|
|
77
|
+
? 0 : QUIC_EXT_ERR_OTHER;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/* This endpoint's initial_max_streams_bidi. nghttp3 needs it at setup;
|
|
81
|
+
later raises arrive through the extend_max_remote_streams_bidi handler. */
|
|
82
|
+
static uint64_t
|
|
83
|
+
quic_ext_local_max_streams_bidi(quic_ext_conn *ec)
|
|
84
|
+
{
|
|
85
|
+
quic_conn_t *c = quic_ext_conn_struct(ec);
|
|
86
|
+
return ngtcp2_conn_get_local_transport_params2(c->conn)->initial_max_streams_bidi;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/* Remember the application error to close the connection with after the
|
|
90
|
+
handler reports a failure (for HTTP/3, the H3_* code nghttp3 gave). */
|
|
91
|
+
static int
|
|
92
|
+
quic_ext_set_application_error(quic_ext_conn *ec, uint64_t app_error_code)
|
|
93
|
+
{
|
|
94
|
+
quic_conn_t *c = quic_ext_conn_struct(ec);
|
|
95
|
+
ngtcp2_ccerr_set_application_error(&c->close_error, app_error_code, NULL, 0);
|
|
96
|
+
c->close_error_set = true;
|
|
97
|
+
return 0;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const quic_ext_api quic_ext_api_table = {
|
|
101
|
+
.abi_major = QUIC_EXT_ABI_MAJOR,
|
|
102
|
+
.abi_minor = 0,
|
|
103
|
+
.size = sizeof(quic_ext_api),
|
|
104
|
+
.conn_get = quic_ext_conn_get,
|
|
105
|
+
.set_stream_handler = quic_ext_set_stream_handler,
|
|
106
|
+
.open_uni_stream = quic_ext_open_uni_stream,
|
|
107
|
+
.consume = quic_ext_consume,
|
|
108
|
+
.shutdown_stream_read = quic_ext_shutdown_stream_read,
|
|
109
|
+
.shutdown_stream_write = quic_ext_shutdown_stream_write,
|
|
110
|
+
.local_max_streams_bidi = quic_ext_local_max_streams_bidi,
|
|
111
|
+
.set_application_error = quic_ext_set_application_error,
|
|
112
|
+
};
|
data/ext/quic/extconf.rb
CHANGED
|
@@ -10,8 +10,9 @@ NGTCP2_SHA256 = "1c0843076528a87b65e9a9d455100941f4cb65d44f96c5da6ae56df14604395
|
|
|
10
10
|
# The picotls revision that ngtcp2 1.25.0 is tested against (see ngtcp2's README).
|
|
11
11
|
PICOTLS_COMMIT = "f07f1c8c68b237f1468bc1f1fe1b68aba3ff23b4"
|
|
12
12
|
PICOTLS_SHA256 = "6ecb4f8d9946c2b9342300b29627dc34e746a551d438d35af325a4495d62a6e3"
|
|
13
|
-
# Bump when ext/quic/patches/picotls/ changes;
|
|
14
|
-
|
|
13
|
+
# Bump when ext/quic/patches/picotls/ or PicotlsRecipe::SOURCES changes;
|
|
14
|
+
# installed? only checks for the archive.
|
|
15
|
+
PICOTLS_PATCH_LEVEL = "p2"
|
|
15
16
|
|
|
16
17
|
# Locate the host's libcrypto. ngtcp2 and picotls are vendored and statically
|
|
17
18
|
# linked, but the crypto primitives and X.509 come from whatever OpenSSL (or
|
|
@@ -39,10 +40,12 @@ end
|
|
|
39
40
|
openssl_cflags = pkg_config("openssl", "cflags-only-I").to_s.strip
|
|
40
41
|
|
|
41
42
|
# picotls has no release tarballs, and its CMake build needs the picotest
|
|
42
|
-
# submodule, which GitHub archive tarballs do not contain. Build only
|
|
43
|
-
#
|
|
43
|
+
# submodule, which GitHub archive tarballs do not contain. Build only what
|
|
44
|
+
# quic.so uses into a single libpicotls.a: the core and the OpenSSL-API
|
|
45
|
+
# backend. The core's lib/pembase64.c, a PEM loader, is left out; the server
|
|
46
|
+
# reads its certificates with libcrypto instead.
|
|
44
47
|
class PicotlsRecipe < MiniPortile
|
|
45
|
-
SOURCES = %w[lib/picotls.c lib/hpke.c lib/
|
|
48
|
+
SOURCES = %w[lib/picotls.c lib/hpke.c lib/openssl.c].freeze
|
|
46
49
|
|
|
47
50
|
attr_accessor :openssl_cflags
|
|
48
51
|
|
|
@@ -161,4 +164,8 @@ append_cflags("-DQUIC_PICOTLS_COMMIT=\\\"#{PICOTLS_COMMIT}\\\"")
|
|
|
161
164
|
append_cflags("-fvisibility=hidden")
|
|
162
165
|
append_ldflags("-Wl,--exclude-libs,ALL") # Unexport symbols from statically linked libraries
|
|
163
166
|
|
|
167
|
+
# quic/ext.h, the C function table other extensions (the http3 gem) build
|
|
168
|
+
# against. It ships in the gem; QUIC::EXT_INCLUDE_DIR points them here.
|
|
169
|
+
append_cppflags("-I#{File.expand_path("include", __dir__)}")
|
|
170
|
+
|
|
164
171
|
create_makefile("quic/quic")
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/* quic/ext.h — the C function table quic.so offers to other extensions.
|
|
2
|
+
*
|
|
3
|
+
* Another extension (the http3 gem) registers a stream handler on a
|
|
4
|
+
* connection and drives it through this table instead of QUIC::Stream.
|
|
5
|
+
* Obtain the table after rb_require("quic") with
|
|
6
|
+
*
|
|
7
|
+
* quic_ext_get_api_fn get =
|
|
8
|
+
* (quic_ext_get_api_fn)rb_ext_resolve_symbol("quic/quic", "quic_ext_get_api");
|
|
9
|
+
* const quic_ext_api *api = get ? get(QUIC_EXT_ABI_MAJOR) : NULL;
|
|
10
|
+
*
|
|
11
|
+
* Rules:
|
|
12
|
+
*
|
|
13
|
+
* - Version: quic_ext_get_api(major) returns NULL when major differs from
|
|
14
|
+
* the table's. The structs below grow only by appending, which bumps
|
|
15
|
+
* abi_minor; reordering, removing or retyping a member bumps abi_major.
|
|
16
|
+
* Check `size` before calling a member added after the version you were
|
|
17
|
+
* built against. The handler struct follows the same rules.
|
|
18
|
+
* - Exceptions: no function in the table and no handler function raises a
|
|
19
|
+
* Ruby exception; they return 0 or a negative error code. The one
|
|
20
|
+
* exception is conn_get, which raises TypeError for a wrong object and is
|
|
21
|
+
* meant for initialization called from a Ruby method. A handler function
|
|
22
|
+
* returning a negative value makes the ngtcp2 callback fail with
|
|
23
|
+
* NGTCP2_ERR_CALLBACK_FAILURE.
|
|
24
|
+
* - Lifetime: `owner` passed to set_stream_handler is marked by the
|
|
25
|
+
* connection while it lives. `ud` must not move (allocate it with
|
|
26
|
+
* xmalloc, not as a Ruby object), and `h` must outlive the connection
|
|
27
|
+
* (a static const struct). quic.so never calls a handler from its dfree,
|
|
28
|
+
* and the owner's dfree must not touch the quic_ext_conn.
|
|
29
|
+
* - Where: table functions may be called from inside handler functions.
|
|
30
|
+
* Everything runs with the GVL held.
|
|
31
|
+
*/
|
|
32
|
+
#ifndef QUIC_EXT_H
|
|
33
|
+
#define QUIC_EXT_H 1
|
|
34
|
+
|
|
35
|
+
#include <stddef.h>
|
|
36
|
+
#include <stdint.h>
|
|
37
|
+
#include <ruby.h>
|
|
38
|
+
|
|
39
|
+
#define QUIC_EXT_ABI_MAJOR 1
|
|
40
|
+
|
|
41
|
+
#define QUIC_EXT_ERR_INVALID_STATE (-1)
|
|
42
|
+
#define QUIC_EXT_ERR_STREAM_ID_BLOCKED (-2)
|
|
43
|
+
#define QUIC_EXT_ERR_STREAM_NOT_FOUND (-3)
|
|
44
|
+
#define QUIC_EXT_ERR_OTHER (-99)
|
|
45
|
+
|
|
46
|
+
typedef struct { const uint8_t *base; size_t len; } quic_ext_vec; /* same layout as ngtcp2_vec */
|
|
47
|
+
typedef struct quic_ext_conn quic_ext_conn; /* opaque */
|
|
48
|
+
|
|
49
|
+
typedef struct {
|
|
50
|
+
size_t size; /* sizeof(quic_ext_stream_handler); tells which members exist */
|
|
51
|
+
int (*recv_stream_data)(void *ud, int64_t id, const uint8_t *data, size_t len,
|
|
52
|
+
int fin, uint64_t ts, size_t *consumed);
|
|
53
|
+
int (*next_stream_data)(void *ud, int64_t *id, int *fin,
|
|
54
|
+
quic_ext_vec *vecs, size_t cap, size_t *nvecs);
|
|
55
|
+
int (*stream_data_written)(void *ud, int64_t id, size_t n);
|
|
56
|
+
int (*stream_blocked)(void *ud, int64_t id);
|
|
57
|
+
int (*stream_shut_write)(void *ud, int64_t id);
|
|
58
|
+
int (*acked_stream_data)(void *ud, int64_t id, uint64_t n);
|
|
59
|
+
int (*extend_max_stream_data)(void *ud, int64_t id);
|
|
60
|
+
int (*extend_max_remote_streams_bidi)(void *ud, uint64_t max);
|
|
61
|
+
int (*stream_stop_sending)(void *ud, int64_t id, uint64_t app_error_code);
|
|
62
|
+
int (*stream_reset)(void *ud, int64_t id, uint64_t final_size, uint64_t app_error_code);
|
|
63
|
+
int (*stream_close)(void *ud, int64_t id, int has_code, uint64_t app_error_code);
|
|
64
|
+
int (*handshake_completed)(void *ud);
|
|
65
|
+
} quic_ext_stream_handler;
|
|
66
|
+
|
|
67
|
+
typedef struct {
|
|
68
|
+
uint32_t abi_major; /* bumped only to break compatibility */
|
|
69
|
+
uint32_t abi_minor; /* bumped whenever a member is appended */
|
|
70
|
+
size_t size; /* sizeof(quic_ext_api) */
|
|
71
|
+
quic_ext_conn *(*conn_get)(VALUE conn); /* TypeError for a wrong object */
|
|
72
|
+
int (*set_stream_handler)(quic_ext_conn *c, const quic_ext_stream_handler *h,
|
|
73
|
+
void *ud, VALUE owner);
|
|
74
|
+
int (*open_uni_stream)(quic_ext_conn *c, int64_t *id);
|
|
75
|
+
int (*consume)(quic_ext_conn *c, int64_t id, uint64_t n);
|
|
76
|
+
int (*shutdown_stream_read)(quic_ext_conn *c, int64_t id, uint64_t app_error_code);
|
|
77
|
+
int (*shutdown_stream_write)(quic_ext_conn *c, int64_t id, uint64_t app_error_code);
|
|
78
|
+
uint64_t (*local_max_streams_bidi)(quic_ext_conn *c);
|
|
79
|
+
int (*set_application_error)(quic_ext_conn *c, uint64_t app_error_code);
|
|
80
|
+
} quic_ext_api;
|
|
81
|
+
|
|
82
|
+
typedef const quic_ext_api *(*quic_ext_get_api_fn)(uint32_t abi_major);
|
|
83
|
+
|
|
84
|
+
#endif /* QUIC_EXT_H */
|
data/ext/quic/quic.c
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
#include "quic.h"
|
|
2
2
|
#include "stream.h"
|
|
3
|
+
#include "connection_common.h"
|
|
3
4
|
|
|
4
5
|
VALUE rb_mQUIC;
|
|
5
6
|
VALUE rb_mQUICConnection;
|
|
6
7
|
VALUE rb_cQUICConnectionClient;
|
|
8
|
+
VALUE rb_cQUICConnectionServer;
|
|
7
9
|
|
|
8
10
|
VALUE rb_eQUICError;
|
|
9
11
|
VALUE rb_eQUICErrorProto;
|
|
@@ -20,6 +22,17 @@ VALUE rb_eQUICErrorWaitWritable;
|
|
|
20
22
|
VALUE rb_eQUICErrorStreamClosed;
|
|
21
23
|
VALUE rb_eQUICErrorStreamReset;
|
|
22
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
|
+
}
|
|
23
36
|
|
|
24
37
|
static VALUE
|
|
25
38
|
quic_library_versions(VALUE self)
|
|
@@ -37,8 +50,8 @@ quic_library_versions(VALUE self)
|
|
|
37
50
|
return h;
|
|
38
51
|
}
|
|
39
52
|
|
|
40
|
-
|
|
41
|
-
|
|
53
|
+
VALUE
|
|
54
|
+
quic_ngtcp2_error_new(int rv)
|
|
42
55
|
{
|
|
43
56
|
VALUE cls;
|
|
44
57
|
switch (rv) {
|
|
@@ -77,7 +90,13 @@ quic_raise_ngtcp2_error(int rv)
|
|
|
77
90
|
|
|
78
91
|
VALUE exc = rb_exc_new_cstr(cls, ngtcp2_strerror(rv));
|
|
79
92
|
rb_ivar_set(exc, rb_intern("@code"), INT2NUM(rv));
|
|
80
|
-
|
|
93
|
+
return exc;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
void
|
|
97
|
+
quic_raise_ngtcp2_error(int rv)
|
|
98
|
+
{
|
|
99
|
+
rb_exc_raise(quic_ngtcp2_error_new(rv));
|
|
81
100
|
}
|
|
82
101
|
|
|
83
102
|
RUBY_FUNC_EXPORTED void
|
|
@@ -96,7 +115,7 @@ Init_quic(void)
|
|
|
96
115
|
rb_eQUICErrorRetry = rb_define_class_under(rb_eQUICError, "Retry", rb_eQUICError);
|
|
97
116
|
rb_eQUICErrorClosed = rb_define_class_under(rb_eQUICError, "Closed", rb_eQUICError);
|
|
98
117
|
rb_eQUICErrorCryptoError = rb_define_class_under(rb_eQUICError, "CryptoError", rb_eQUICError);
|
|
99
|
-
/* The TLS alert this
|
|
118
|
+
/* The TLS alert this endpoint sent, when read_pkt fails in the handshake.
|
|
100
119
|
Alerts received from the peer are not recorded by ngtcp2. */
|
|
101
120
|
rb_define_attr(rb_eQUICErrorCryptoError, "tls_alert", 1, 0);
|
|
102
121
|
rb_eQUICErrorCertificateVerifyFailed =
|
|
@@ -120,7 +139,13 @@ Init_quic(void)
|
|
|
120
139
|
rb_eQUICErrorStreamClosed = rb_define_class_under(rb_eQUICError, "StreamClosed", rb_eQUICError);
|
|
121
140
|
rb_eQUICErrorStreamReset = rb_define_class_under(rb_eQUICError, "StreamReset", rb_eQUICError);
|
|
122
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);
|
|
123
145
|
|
|
146
|
+
Init_quic_tls_context(rb_mQUIC);
|
|
124
147
|
Init_quic_connection_client(rb_mQUICConnection);
|
|
148
|
+
Init_quic_connection_server(rb_mQUICConnection);
|
|
149
|
+
Init_quic_endpoint(rb_mQUIC);
|
|
125
150
|
Init_quic_stream(rb_mQUIC);
|
|
126
151
|
}
|
data/ext/quic/quic.h
CHANGED
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
extern VALUE rb_mQUIC;
|
|
22
22
|
extern VALUE rb_mQUICConnection;
|
|
23
23
|
extern VALUE rb_cQUICConnectionClient;
|
|
24
|
+
extern VALUE rb_cQUICConnectionServer;
|
|
24
25
|
|
|
25
26
|
extern VALUE rb_eQUICError;
|
|
26
27
|
extern VALUE rb_eQUICErrorProto;
|
|
@@ -37,18 +38,17 @@ extern VALUE rb_eQUICErrorWaitWritable;
|
|
|
37
38
|
extern VALUE rb_eQUICErrorStreamClosed;
|
|
38
39
|
extern VALUE rb_eQUICErrorStreamReset;
|
|
39
40
|
extern VALUE rb_eQUICErrorNotBound;
|
|
41
|
+
extern VALUE rb_eQUICErrorInvalidState;
|
|
40
42
|
|
|
41
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);
|
|
42
46
|
|
|
43
|
-
/*
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
ngtcp2_conn pointer; the caller is responsible for ensuring the Ruby
|
|
47
|
-
Client object is alive for the duration of the call. */
|
|
48
|
-
ngtcp2_conn *quic_client_conn(VALUE client);
|
|
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);
|
|
49
50
|
|
|
50
|
-
/* Raise
|
|
51
|
-
Never returns. */
|
|
51
|
+
/* Raise quic_ngtcp2_error_new(rv). Never returns. */
|
|
52
52
|
NORETURN(void quic_raise_ngtcp2_error(int rv));
|
|
53
53
|
|
|
54
54
|
#endif /* QUIC_H */
|
data/ext/quic/stream.c
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#include "stream.h"
|
|
2
|
+
#include "connection_common.h"
|
|
2
3
|
|
|
3
4
|
#include <string.h>
|
|
4
5
|
|
|
@@ -33,7 +34,7 @@ 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
39
|
VALUE self = quic_stream_alloc(rb_cQUICStream);
|
|
39
40
|
quic_stream_t *s;
|
|
@@ -41,7 +42,7 @@ quic_stream_new(int64_t stream_id, VALUE client)
|
|
|
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));
|
|
@@ -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 QUIC::Stream.allocate for unit tests,
|
|
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 QUIC::Error::NotBound if
|
|
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
|
}
|
|
@@ -169,7 +183,7 @@ 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
189
|
rb_raise(rb_eQUICErrorWaitWritable, "stream send window is full");
|
|
@@ -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,7 +290,7 @@ 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
296
|
since no QUIC-level error API is exposed yet. */
|
|
@@ -296,13 +336,32 @@ quic_stream_reset_m(int argc, VALUE *argv, VALUE self)
|
|
|
296
336
|
return Qnil;
|
|
297
337
|
}
|
|
298
338
|
|
|
299
|
-
ngtcp2_conn *conn =
|
|
339
|
+
ngtcp2_conn *conn = quic_conn_ptr(client_v);
|
|
300
340
|
int rv = ngtcp2_conn_shutdown_stream_write(conn, 0, s->stream_id, error_code);
|
|
301
341
|
if (rv != 0) quic_raise_ngtcp2_error(rv);
|
|
302
342
|
s->reset = true;
|
|
303
343
|
return Qnil;
|
|
304
344
|
}
|
|
305
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
|
+
|
|
306
365
|
void
|
|
307
366
|
Init_quic_stream(VALUE rb_mQUICArg)
|
|
308
367
|
{
|
|
@@ -313,8 +372,10 @@ Init_quic_stream(VALUE rb_mQUICArg)
|
|
|
313
372
|
rb_define_method(rb_cQUICStream, "close_write", quic_stream_close_write_m, 0);
|
|
314
373
|
rb_define_method(rb_cQUICStream, "read_nonblock", quic_stream_read_nonblock_m, 1);
|
|
315
374
|
rb_define_method(rb_cQUICStream, "eof?", quic_stream_eof_p, 0);
|
|
375
|
+
rb_define_method(rb_cQUICStream, "closed?", quic_stream_closed_p, 0);
|
|
316
376
|
rb_define_method(rb_cQUICStream, "close_read", quic_stream_close_read_m, 0);
|
|
317
377
|
rb_define_method(rb_cQUICStream, "close", quic_stream_close_m, 0);
|
|
318
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);
|
|
319
380
|
/* #read (blocking) and #initiator are defined in lib/quic/stream.rb. */
|
|
320
381
|
}
|
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 {
|
|
@@ -29,17 +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
|
|
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 */
|