quic 0.0.2 → 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.clang-format +1 -0
- data/.git-blame-ignore-revs +2 -0
- data/CHANGELOG.md +33 -0
- data/LICENSE-DEPENDENCIES.txt +2 -19
- data/README.md +101 -2
- data/Rakefile +7 -0
- data/examples/demo_certificates.rb +67 -0
- data/examples/echo_demo.rb +5 -6
- data/examples/echo_endpoint_demo.rb +170 -0
- data/examples/echo_server_demo.rb +71 -0
- data/ext/quic/connection_client.c +95 -934
- data/ext/quic/connection_common.c +1255 -0
- data/ext/quic/connection_common.h +226 -0
- data/ext/quic/connection_server.c +205 -0
- data/ext/quic/endpoint.c +829 -0
- data/ext/quic/endpoint.h +31 -0
- data/ext/quic/ext_api.c +106 -0
- data/ext/quic/extconf.rb +12 -5
- data/ext/quic/include/quic/ext.h +91 -0
- data/ext/quic/quic.c +95 -62
- data/ext/quic/quic.h +12 -10
- data/ext/quic/stream.c +124 -82
- data/ext/quic/stream.h +18 -12
- data/ext/quic/tls_context.c +269 -0
- data/ext/quic/tls_context.h +47 -0
- data/lib/quic/connection.rb +142 -57
- data/lib/quic/endpoint.rb +57 -0
- data/lib/quic/ext_include.rb +12 -0
- data/lib/quic/settings.rb +26 -3
- data/lib/quic/stream.rb +4 -3
- data/lib/quic/version.rb +1 -1
- data/lib/quic.rb +2 -0
- data/sig/quic.rbs +203 -22
- metadata +17 -1
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
#ifndef QUIC_TLS_CONTEXT_H
|
|
2
|
+
#define QUIC_TLS_CONTEXT_H 1
|
|
3
|
+
|
|
4
|
+
#include "quic.h"
|
|
5
|
+
|
|
6
|
+
/* A server's TLS context: the certificate chain, the signer and the ALPN
|
|
7
|
+
list, read once and shared by every connection made from it. Reference
|
|
8
|
+
counted: the QUIC::TLSContext object holds one reference and each server
|
|
9
|
+
connection another, so a context replaced on an Endpoint lives on until
|
|
10
|
+
the last connection that uses it is freed. */
|
|
11
|
+
typedef struct {
|
|
12
|
+
/* Handed to ptls_server_new. certificates.list is malloc()ed by picotls
|
|
13
|
+
and freed with the context. */
|
|
14
|
+
ptls_context_t ctx;
|
|
15
|
+
/* Referenced by ctx.sign_certificate; holds a reference to the key. */
|
|
16
|
+
ptls_openssl_sign_certificate_t sign_cert;
|
|
17
|
+
bool sign_cert_initialized;
|
|
18
|
+
/* ctx.on_client_hello points here; the callback recovers this struct
|
|
19
|
+
from its self argument with offsetof, so it needs no connection. */
|
|
20
|
+
ptls_on_client_hello_t on_client_hello;
|
|
21
|
+
/* The ALPN protocols the server accepts, in its order of preference. The
|
|
22
|
+
entries point into alpn_buf. */
|
|
23
|
+
ptls_iovec_t *alpn;
|
|
24
|
+
size_t alpn_count;
|
|
25
|
+
uint8_t *alpn_buf;
|
|
26
|
+
long refcnt;
|
|
27
|
+
} quic_tls_ctx_t;
|
|
28
|
+
|
|
29
|
+
extern VALUE rb_cQUICTLSContext;
|
|
30
|
+
|
|
31
|
+
void Init_quic_tls_context(VALUE rb_mQUICArg);
|
|
32
|
+
|
|
33
|
+
/* QUIC::TLSContext.server, callable from C: a new QUIC::TLSContext object
|
|
34
|
+
holding the one reference to a new context. alpn_ary must be a non-empty
|
|
35
|
+
Array of 1-255 byte Strings. Raises ArgumentError naming what could not
|
|
36
|
+
be loaded or used. */
|
|
37
|
+
VALUE quic_tls_context_server_new(VALUE certificate_path,
|
|
38
|
+
VALUE private_key_path, VALUE alpn_ary);
|
|
39
|
+
|
|
40
|
+
/* The context behind a QUIC::TLSContext object. Raises TypeError for any
|
|
41
|
+
other object. */
|
|
42
|
+
quic_tls_ctx_t *quic_tls_context_get(VALUE tls_context);
|
|
43
|
+
|
|
44
|
+
void quic_tls_ctx_ref(quic_tls_ctx_t *t);
|
|
45
|
+
void quic_tls_ctx_unref(quic_tls_ctx_t *t);
|
|
46
|
+
|
|
47
|
+
#endif /* QUIC_TLS_CONTEXT_H */
|
data/lib/quic/connection.rb
CHANGED
|
@@ -4,46 +4,11 @@ require "socket"
|
|
|
4
4
|
|
|
5
5
|
module QUIC
|
|
6
6
|
module Connection
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
# selection to Addrinfo.udp's implicit resolution. Pin the resolved
|
|
13
|
-
# Addrinfo into @remote_address so callers can match their UDPSocket's
|
|
14
|
-
# connected sockaddr (otherwise DNS round-robin between Client.new and
|
|
15
|
-
# sock.connect can produce a path that ngtcp2 silently drops).
|
|
16
|
-
def self.new(host:, port:, address_family: nil, transport_params: nil, settings: nil)
|
|
17
|
-
family = case address_family
|
|
18
|
-
when nil then nil
|
|
19
|
-
when :inet then Socket::AF_INET
|
|
20
|
-
when :inet6 then Socket::AF_INET6
|
|
21
|
-
else
|
|
22
|
-
raise ArgumentError, "unknown address_family: #{address_family.inspect}"
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
remote_address = if family
|
|
26
|
-
Addrinfo.getaddrinfo(host, port, family, :DGRAM, Socket::IPPROTO_UDP).first
|
|
27
|
-
else
|
|
28
|
-
Addrinfo.udp(host, port)
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
remote_sockaddr = remote_address.to_sockaddr
|
|
32
|
-
local_sockaddr = Addrinfo.udp("0.0.0.0", 0).to_sockaddr
|
|
33
|
-
|
|
34
|
-
client = _open(
|
|
35
|
-
local_sockaddr: local_sockaddr,
|
|
36
|
-
remote_sockaddr: remote_sockaddr,
|
|
37
|
-
server_name: host,
|
|
38
|
-
transport_params: transport_params || QUIC::TransportParams.default,
|
|
39
|
-
settings: settings || QUIC::Settings.default
|
|
40
|
-
)
|
|
41
|
-
client.instance_variable_set(:@host, host)
|
|
42
|
-
client.instance_variable_set(:@port, port)
|
|
43
|
-
client.instance_variable_set(:@remote_address, remote_address)
|
|
44
|
-
client
|
|
45
|
-
end
|
|
46
|
-
|
|
7
|
+
# The Ruby side of the I/O loop, shared by Client and Server. It is an
|
|
8
|
+
# internal implementation detail: not meant to be included in other
|
|
9
|
+
# classes, and the ivars and methods it relies on are not a public
|
|
10
|
+
# contract.
|
|
11
|
+
module Pump
|
|
47
12
|
# Attach an IO.select / #send / #recvfrom-compatible UDP socket so that
|
|
48
13
|
# #run and Stream blocking I/O can drive the wire transparently. Caller
|
|
49
14
|
# is responsible for socket lifetime; we do not close it. The socket
|
|
@@ -61,7 +26,7 @@ module QUIC
|
|
|
61
26
|
# QUIC::Error::NotBound if #bind has not been called. Propagates any
|
|
62
27
|
# QUIC::Error subclass raised by #read_pkt / #handle_expiry.
|
|
63
28
|
def run
|
|
64
|
-
raise QUIC::Error::NotBound, "
|
|
29
|
+
raise QUIC::Error::NotBound, "#{self.class}#bind(sock) has not been called" if @sock.nil?
|
|
65
30
|
|
|
66
31
|
pump_until { handshake_completed? }
|
|
67
32
|
end
|
|
@@ -72,7 +37,7 @@ module QUIC
|
|
|
72
37
|
# fire. Public-ish so Stream blocking ops can share it; documented as
|
|
73
38
|
# "internal" for users.
|
|
74
39
|
def pump_once(timeout: nil)
|
|
75
|
-
raise QUIC::Error::NotBound, "
|
|
40
|
+
raise QUIC::Error::NotBound, "#{self.class}#bind(sock) has not been called" if @sock.nil?
|
|
76
41
|
|
|
77
42
|
while (pkt = write_pkt)
|
|
78
43
|
@sock.send(pkt, 0)
|
|
@@ -82,10 +47,11 @@ module QUIC
|
|
|
82
47
|
ready = IO.select([@sock], nil, nil, wait_for)
|
|
83
48
|
if ready
|
|
84
49
|
data, _addr = @sock.recvfrom(2048)
|
|
85
|
-
# Reuse the same path tuple
|
|
86
|
-
#
|
|
87
|
-
#
|
|
88
|
-
# remote comes from the connected socket (assumes
|
|
50
|
+
# Reuse the same path tuple the connection was built with so the
|
|
51
|
+
# read_pkt path matches the stored connection path. A Client leaves
|
|
52
|
+
# @local_sockaddr unset, so local is 0.0.0.0:0 (Client.new
|
|
53
|
+
# convention); remote comes from the connected socket (assumes
|
|
54
|
+
# #connect was used).
|
|
89
55
|
read_pkt(
|
|
90
56
|
data,
|
|
91
57
|
local_sockaddr: pump_local_sockaddr,
|
|
@@ -102,9 +68,9 @@ module QUIC
|
|
|
102
68
|
pump_once until pred.call
|
|
103
69
|
end
|
|
104
70
|
|
|
105
|
-
# Pop the next peer-initiated
|
|
106
|
-
#
|
|
107
|
-
#
|
|
71
|
+
# Pop the next peer-initiated stream off the accept queue, driving the
|
|
72
|
+
# I/O loop until one arrives. The queue is fed by the recv_stream_data
|
|
73
|
+
# callback the first time a peer-initiated stream carries data.
|
|
108
74
|
#
|
|
109
75
|
# timeout: nil block until a stream is available (requires #bind)
|
|
110
76
|
# timeout: 0 return immediately; nil if the queue is empty
|
|
@@ -112,7 +78,8 @@ module QUIC
|
|
|
112
78
|
#
|
|
113
79
|
# Returns a QUIC::Stream, or nil on timeout.
|
|
114
80
|
def accept_stream(timeout: nil)
|
|
115
|
-
|
|
81
|
+
require_stream_mode
|
|
82
|
+
stream = take_accepted_stream
|
|
116
83
|
return stream unless stream.nil?
|
|
117
84
|
return nil if timeout == 0
|
|
118
85
|
|
|
@@ -125,22 +92,40 @@ module QUIC
|
|
|
125
92
|
else
|
|
126
93
|
pump_once
|
|
127
94
|
end
|
|
128
|
-
stream =
|
|
95
|
+
stream = take_accepted_stream
|
|
129
96
|
return stream unless stream.nil?
|
|
130
97
|
end
|
|
131
98
|
end
|
|
132
99
|
|
|
133
100
|
# Non-blocking variant of #accept_stream. Returns the next queued
|
|
134
|
-
#
|
|
135
|
-
# mixin) when the queue is empty. Does not require
|
|
101
|
+
# peer-initiated stream, or raises QUIC::Error::WaitReadable
|
|
102
|
+
# (IO::WaitReadable mixin) when the queue is empty. Does not require
|
|
103
|
+
# #bind.
|
|
136
104
|
def accept_stream_nonblock
|
|
137
|
-
|
|
138
|
-
|
|
105
|
+
require_stream_mode
|
|
106
|
+
stream = take_accepted_stream
|
|
107
|
+
raise QUIC::Error::WaitReadable, "no peer-initiated stream available" if stream.nil?
|
|
139
108
|
stream
|
|
140
109
|
end
|
|
141
110
|
|
|
142
111
|
private
|
|
143
112
|
|
|
113
|
+
# A C stream handler (quic/ext.h) registered on the connection owns
|
|
114
|
+
# every stream, so no QUIC::Stream is ever queued for #accept_stream.
|
|
115
|
+
def require_stream_mode
|
|
116
|
+
return unless @stream_handler
|
|
117
|
+
raise QUIC::Error::InvalidState, "the connection's streams belong to a C stream handler"
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# The next stream off the accept queue, or nil. Telling the stream it
|
|
121
|
+
# has been accepted lets the peer open another one in place of a
|
|
122
|
+
# stream that closed while it waited in the queue.
|
|
123
|
+
def take_accepted_stream
|
|
124
|
+
stream = @accept_queue.shift
|
|
125
|
+
stream&.__send__(:_accepted)
|
|
126
|
+
stream
|
|
127
|
+
end
|
|
128
|
+
|
|
144
129
|
def pump_timeout(caller_timeout)
|
|
145
130
|
ngtcp2_expiry_ns = expiry
|
|
146
131
|
if ngtcp2_expiry_ns.nil?
|
|
@@ -153,11 +138,111 @@ module QUIC
|
|
|
153
138
|
end
|
|
154
139
|
|
|
155
140
|
def pump_local_sockaddr
|
|
156
|
-
@
|
|
141
|
+
@local_sockaddr ||= Addrinfo.udp("0.0.0.0", 0).to_sockaddr
|
|
157
142
|
end
|
|
158
143
|
|
|
159
144
|
def pump_remote_sockaddr
|
|
160
|
-
@
|
|
145
|
+
@remote_sockaddr ||= @sock.remote_address.to_sockaddr
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
class Client
|
|
150
|
+
include Pump
|
|
151
|
+
|
|
152
|
+
attr_reader :remote_address
|
|
153
|
+
|
|
154
|
+
# Build a Client and pin a single peer address. address_family takes a
|
|
155
|
+
# Symbol (:inet for IPv4, :inet6 for IPv6) or nil to defer family
|
|
156
|
+
# selection to Addrinfo.udp's implicit resolution. Pin the resolved
|
|
157
|
+
# Addrinfo into @remote_address so callers can match their UDPSocket's
|
|
158
|
+
# connected sockaddr (otherwise DNS round-robin between Client.new and
|
|
159
|
+
# sock.connect can produce a path that ngtcp2 silently drops).
|
|
160
|
+
def self.new(host:, port:, address_family: nil, transport_params: nil, settings: nil)
|
|
161
|
+
family = case address_family
|
|
162
|
+
when nil then nil
|
|
163
|
+
when :inet then Socket::AF_INET
|
|
164
|
+
when :inet6 then Socket::AF_INET6
|
|
165
|
+
else
|
|
166
|
+
raise ArgumentError, "unknown address_family: #{address_family.inspect}"
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
remote_address = if family
|
|
170
|
+
Addrinfo.getaddrinfo(host, port, family, :DGRAM, Socket::IPPROTO_UDP).first
|
|
171
|
+
else
|
|
172
|
+
Addrinfo.udp(host, port)
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
remote_sockaddr = remote_address.to_sockaddr
|
|
176
|
+
local_sockaddr = Addrinfo.udp("0.0.0.0", 0).to_sockaddr
|
|
177
|
+
settings ||= QUIC::Settings.default
|
|
178
|
+
settings.check_qlog_dir
|
|
179
|
+
|
|
180
|
+
client = _open(
|
|
181
|
+
local_sockaddr: local_sockaddr,
|
|
182
|
+
remote_sockaddr: remote_sockaddr,
|
|
183
|
+
server_name: host,
|
|
184
|
+
transport_params: transport_params || QUIC::TransportParams.default,
|
|
185
|
+
settings: settings
|
|
186
|
+
)
|
|
187
|
+
client.instance_variable_set(:@host, host)
|
|
188
|
+
client.instance_variable_set(:@port, port)
|
|
189
|
+
client.instance_variable_set(:@remote_address, remote_address)
|
|
190
|
+
client
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
# One server-side connection. Built by Server.accept (or Server._accept)
|
|
195
|
+
# from the first datagram a client sent; there is no public Server.new,
|
|
196
|
+
# since an object without an ngtcp2 connection behind it would be
|
|
197
|
+
# unusable.
|
|
198
|
+
class Server
|
|
199
|
+
include Pump
|
|
200
|
+
|
|
201
|
+
attr_reader :remote_address
|
|
202
|
+
|
|
203
|
+
private_class_method :new
|
|
204
|
+
|
|
205
|
+
# Wait for the first datagram on sock and build a connection for its
|
|
206
|
+
# sender. sock must be bound to the listen address and not connected.
|
|
207
|
+
# It is connected to the first sender, so this Server serves exactly
|
|
208
|
+
# that one client and shares the I/O loop with Client as is. The
|
|
209
|
+
# handshake is left to #run.
|
|
210
|
+
#
|
|
211
|
+
# The wait for the first datagram has no timeout; close sock from
|
|
212
|
+
# another thread to give up. If anything fails after that datagram
|
|
213
|
+
# arrived, sock stays connected to its sender: discard it and accept on
|
|
214
|
+
# a new socket. accept never closes sock; its lifetime is the caller's.
|
|
215
|
+
#
|
|
216
|
+
# settings must name at least one ALPN protocol, so the default
|
|
217
|
+
# (QUIC::Settings.default) raises ArgumentError. A qlog_dir that is not
|
|
218
|
+
# a writable directory raises ArgumentError before the wait.
|
|
219
|
+
def self.accept(sock:, certificate_path:, private_key_path:, transport_params: nil, settings: nil)
|
|
220
|
+
settings ||= QUIC::Settings.default
|
|
221
|
+
settings.check_qlog_dir
|
|
222
|
+
|
|
223
|
+
data, sender = sock.recvfrom(2048)
|
|
224
|
+
sock.connect(sender[3], sender[1])
|
|
225
|
+
# Read both addresses back from the connected socket so that the path
|
|
226
|
+
# given to ngtcp2 here and every later read_pkt path come from one
|
|
227
|
+
# source and always match.
|
|
228
|
+
local_sockaddr = sock.local_address.to_sockaddr
|
|
229
|
+
remote_sockaddr = sock.remote_address.to_sockaddr
|
|
230
|
+
|
|
231
|
+
server = _accept(
|
|
232
|
+
initial_packet: data,
|
|
233
|
+
local_sockaddr: local_sockaddr,
|
|
234
|
+
remote_sockaddr: remote_sockaddr,
|
|
235
|
+
certificate_path: certificate_path,
|
|
236
|
+
private_key_path: private_key_path,
|
|
237
|
+
transport_params: transport_params || QUIC::TransportParams.default,
|
|
238
|
+
settings: settings
|
|
239
|
+
)
|
|
240
|
+
server.instance_variable_set(:@local_sockaddr, local_sockaddr)
|
|
241
|
+
server.instance_variable_set(:@remote_sockaddr, remote_sockaddr)
|
|
242
|
+
server.instance_variable_set(:@remote_address, sock.remote_address)
|
|
243
|
+
server.bind(sock)
|
|
244
|
+
server.read_pkt(data, local_sockaddr: local_sockaddr, remote_sockaddr: remote_sockaddr)
|
|
245
|
+
server
|
|
161
246
|
end
|
|
162
247
|
end
|
|
163
248
|
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "socket"
|
|
4
|
+
|
|
5
|
+
module QUIC
|
|
6
|
+
# Many server connections on one UDP socket. The Endpoint routes each
|
|
7
|
+
# datagram to its connection by connection ID, makes connections for new
|
|
8
|
+
# clients and answers what no connection can (Version Negotiation,
|
|
9
|
+
# Stateless Reset, CONNECTION_REFUSED). The socket and the I/O loop stay
|
|
10
|
+
# with the caller:
|
|
11
|
+
#
|
|
12
|
+
# loop do
|
|
13
|
+
# data, addr = sock.recvfrom(65_535) # or recvmsg_nonblock
|
|
14
|
+
# endpoint.read_pkt(data, remote: addr.to_sockaddr)
|
|
15
|
+
# endpoint.handle_expiry # when endpoint.expiry is due
|
|
16
|
+
# while (pkt, to = endpoint.write_pkt)
|
|
17
|
+
# sock.send(pkt, 0, to)
|
|
18
|
+
# end
|
|
19
|
+
# endpoint.take_closed.each { |conn| ... }
|
|
20
|
+
# end
|
|
21
|
+
#
|
|
22
|
+
# The socket must not be connected, and must be bound to the address it
|
|
23
|
+
# serves (not 0.0.0.0): every path's local address is local_address.
|
|
24
|
+
#
|
|
25
|
+
# The Endpoint's own answers wait for #write_pkt in a queue of at most
|
|
26
|
+
# 1024; any beyond are dropped, so call #write_pkt often under load.
|
|
27
|
+
#
|
|
28
|
+
# A connection's failure never raises from the Endpoint's methods. It
|
|
29
|
+
# closes that connection alone, and Connection#error tells why. The
|
|
30
|
+
# connections have no socket, so only their non-blocking methods work;
|
|
31
|
+
# #close queues its CONNECTION_CLOSE for #write_pkt.
|
|
32
|
+
class Endpoint
|
|
33
|
+
attr_reader :local_address
|
|
34
|
+
|
|
35
|
+
# tls_context is a QUIC::TLSContext; its ALPN list is the server's, and
|
|
36
|
+
# settings.alpn is not read. max_connections counts connections in their
|
|
37
|
+
# closing or draining period too.
|
|
38
|
+
def self.new(local_address:, tls_context:, transport_params: nil, settings: nil, max_connections: 1024)
|
|
39
|
+
raise TypeError, "local_address must be an Addrinfo (got #{local_address.class})" unless local_address.is_a?(Addrinfo)
|
|
40
|
+
unless max_connections.is_a?(Integer)
|
|
41
|
+
raise TypeError, "max_connections must be an Integer (got #{max_connections.class})"
|
|
42
|
+
end
|
|
43
|
+
settings ||= QUIC::Settings.default
|
|
44
|
+
settings.check_qlog_dir
|
|
45
|
+
|
|
46
|
+
endpoint = _new(
|
|
47
|
+
local_sockaddr: local_address.to_sockaddr,
|
|
48
|
+
tls_context: tls_context,
|
|
49
|
+
transport_params: transport_params || QUIC::TransportParams.default,
|
|
50
|
+
settings: settings,
|
|
51
|
+
max_connections: max_connections
|
|
52
|
+
)
|
|
53
|
+
endpoint.instance_variable_set(:@local_address, local_address)
|
|
54
|
+
endpoint
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module QUIC
|
|
4
|
+
# The directory holding quic/ext.h, the C function table other native
|
|
5
|
+
# extensions build against. An extconf.rb adds it to its include path:
|
|
6
|
+
#
|
|
7
|
+
# require "quic/ext_include"
|
|
8
|
+
# append_cppflags("-I#{QUIC::EXT_INCLUDE_DIR}")
|
|
9
|
+
#
|
|
10
|
+
# This file does not load the extension, so extconf.rb can require it.
|
|
11
|
+
EXT_INCLUDE_DIR = File.expand_path("../../ext/quic/include", __dir__)
|
|
12
|
+
end
|
data/lib/quic/settings.rb
CHANGED
|
@@ -11,7 +11,8 @@ module QUIC
|
|
|
11
11
|
:alpn,
|
|
12
12
|
:verify_mode,
|
|
13
13
|
:ca_file,
|
|
14
|
-
:ca_path
|
|
14
|
+
:ca_path,
|
|
15
|
+
:qlog_dir
|
|
15
16
|
)
|
|
16
17
|
|
|
17
18
|
class Settings
|
|
@@ -25,7 +26,15 @@ module QUIC
|
|
|
25
26
|
# it; later changes to those variables or to the CA bundle are not picked
|
|
26
27
|
# up. Setting ca_file or ca_path trusts only the certificates found there.
|
|
27
28
|
# With verify_mode :none, ca_file and ca_path are ignored.
|
|
28
|
-
|
|
29
|
+
#
|
|
30
|
+
# Only a client uses verify_mode, ca_file and ca_path. A server ignores
|
|
31
|
+
# them, since it does not ask for a client certificate.
|
|
32
|
+
#
|
|
33
|
+
# qlog_dir (a String or Pathname) makes every connection write its qlog
|
|
34
|
+
# to <qlog_dir>/<its first source connection ID in hex>.sqlog. A file
|
|
35
|
+
# that cannot be opened when a connection is made only costs that
|
|
36
|
+
# connection's qlog.
|
|
37
|
+
def initialize(verify_mode: :peer, ca_file: nil, ca_path: nil, qlog_dir: nil, **rest)
|
|
29
38
|
super
|
|
30
39
|
end
|
|
31
40
|
|
|
@@ -40,8 +49,22 @@ module QUIC
|
|
|
40
49
|
alpn: [],
|
|
41
50
|
verify_mode: :peer,
|
|
42
51
|
ca_file: nil,
|
|
43
|
-
ca_path: nil
|
|
52
|
+
ca_path: nil,
|
|
53
|
+
qlog_dir: nil
|
|
44
54
|
)
|
|
45
55
|
end
|
|
56
|
+
|
|
57
|
+
# Raises ArgumentError unless qlog_dir is nil or a directory this
|
|
58
|
+
# process can write to. Client.new, Connection::Server.accept and
|
|
59
|
+
# Endpoint.new call it, so that a wrong qlog_dir shows up when they are
|
|
60
|
+
# set up rather than as qlog files that never appear.
|
|
61
|
+
def check_qlog_dir
|
|
62
|
+
return if qlog_dir.nil?
|
|
63
|
+
|
|
64
|
+
path = qlog_dir.respond_to?(:to_path) ? qlog_dir.to_path : qlog_dir
|
|
65
|
+
return if path.is_a?(String) && File.directory?(path) && File.writable?(path)
|
|
66
|
+
|
|
67
|
+
raise ArgumentError, "qlog_dir is not a writable directory: #{path}"
|
|
68
|
+
end
|
|
46
69
|
end
|
|
47
70
|
end
|
data/lib/quic/stream.rb
CHANGED
|
@@ -4,7 +4,8 @@ module QUIC
|
|
|
4
4
|
# QUIC::Stream is defined in the C extension (ext/quic/stream.c); this file
|
|
5
5
|
# reopens it to add the bits that are easier to express in Ruby:
|
|
6
6
|
# #initiator (a tiny lookup over @id) and the blocking #read which wraps
|
|
7
|
-
# the C #read_nonblock with an internal
|
|
7
|
+
# the C #read_nonblock with an internal #pump_once loop on the owning
|
|
8
|
+
# connection (@client, a Client or Server).
|
|
8
9
|
class Stream
|
|
9
10
|
INITIATORS = %i[client_bidi server_bidi client_uni server_uni].freeze
|
|
10
11
|
|
|
@@ -17,8 +18,8 @@ module QUIC
|
|
|
17
18
|
INITIATORS[@id & 0b11]
|
|
18
19
|
end
|
|
19
20
|
|
|
20
|
-
# IO#read-compatible blocking read. Requires the
|
|
21
|
-
# been #bind'ed to a socket (raised via
|
|
21
|
+
# IO#read-compatible blocking read. Requires the owning connection to
|
|
22
|
+
# have been #bind'ed to a socket (raised via its #pump_once otherwise).
|
|
22
23
|
#
|
|
23
24
|
# read(length): block until at least 1 byte is available or EOF,
|
|
24
25
|
# return up to `length` bytes, return nil at EOF.
|
data/lib/quic/version.rb
CHANGED
data/lib/quic.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "quic/version"
|
|
4
|
+
require_relative "quic/ext_include"
|
|
4
5
|
|
|
5
6
|
module QUIC
|
|
6
7
|
class Error < StandardError
|
|
@@ -12,4 +13,5 @@ require "quic/quic"
|
|
|
12
13
|
require_relative "quic/transport_params"
|
|
13
14
|
require_relative "quic/settings"
|
|
14
15
|
require_relative "quic/connection"
|
|
16
|
+
require_relative "quic/endpoint"
|
|
15
17
|
require_relative "quic/stream"
|