quic 0.0.1 → 0.0.2

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.
data/ext/quic/stream.c CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  #include <string.h>
4
4
 
5
- VALUE rb_cQuicStream;
5
+ VALUE rb_cQUICStream;
6
6
 
7
7
  static void
8
8
  quic_stream_free(void *ptr)
@@ -18,7 +18,7 @@ quic_stream_size(const void *ptr)
18
18
  }
19
19
 
20
20
  const rb_data_type_t quic_stream_data_type = {
21
- "Quic::Stream",
21
+ "QUIC::Stream",
22
22
  {NULL, quic_stream_free, quic_stream_size,},
23
23
  NULL, NULL,
24
24
  RUBY_TYPED_FREE_IMMEDIATELY,
@@ -35,7 +35,7 @@ quic_stream_alloc(VALUE klass)
35
35
  VALUE
36
36
  quic_stream_new(int64_t stream_id, VALUE client)
37
37
  {
38
- VALUE self = quic_stream_alloc(rb_cQuicStream);
38
+ VALUE self = quic_stream_alloc(rb_cQUICStream);
39
39
  quic_stream_t *s;
40
40
  TypedData_Get_Struct(self, quic_stream_t, &quic_stream_data_type, s);
41
41
  s->stream_id = stream_id;
@@ -53,7 +53,7 @@ quic_stream_new(int64_t stream_id, VALUE client)
53
53
  quic_stream_write_m and #write_nonblock returns a partial byte count in
54
54
  quic_stream_write_nonblock_m). This helper is reached only after the
55
55
  caller has confirmed the bytes fit in the current send window, so it
56
- never raises Quic::Error::WaitWritable on its own. */
56
+ never raises QUIC::Error::WaitWritable on its own. */
57
57
  static VALUE
58
58
  quic_stream_enqueue(VALUE self, VALUE data, bool fin)
59
59
  {
@@ -61,10 +61,10 @@ quic_stream_enqueue(VALUE self, VALUE data, bool fin)
61
61
  TypedData_Get_Struct(self, quic_stream_t, &quic_stream_data_type, s);
62
62
 
63
63
  if (s->reset || s->closed) {
64
- rb_raise(rb_eQuicErrorStreamClosed, "stream is closed");
64
+ rb_raise(rb_eQUICErrorStreamClosed, "stream is closed");
65
65
  }
66
66
  if (s->fin_sent) {
67
- rb_raise(rb_eQuicErrorStreamClosed, "stream FIN already sent");
67
+ rb_raise(rb_eQUICErrorStreamClosed, "stream FIN already sent");
68
68
  }
69
69
 
70
70
  Check_Type(data, T_STRING);
@@ -110,7 +110,7 @@ quic_stream_window_left(VALUE client_v, quic_stream_t *s)
110
110
  /* Blocking write: block by repeatedly invoking Client#pump_once until the
111
111
  peer's flow control window has enough room for the full payload, then
112
112
  enqueue. IO#write-compatible: always queues all of `data`. Bare Streams
113
- (built via Quic::Stream.allocate for unit tests, with @client = nil) skip
113
+ (built via QUIC::Stream.allocate for unit tests, with @client = nil) skip
114
114
  the window check entirely. */
115
115
  static VALUE
116
116
  quic_stream_write_m(int argc, VALUE *argv, VALUE self)
@@ -132,7 +132,7 @@ quic_stream_write_m(int argc, VALUE *argv, VALUE self)
132
132
  quic_stream_t *s;
133
133
  TypedData_Get_Struct(self, quic_stream_t, &quic_stream_data_type, s);
134
134
  /* Loop until the full payload would fit in the current window. pump_once
135
- raises Quic::Error::NotBound if @client has no socket bound; that
135
+ raises QUIC::Error::NotBound if @client has no socket bound; that
136
136
  error surfaces verbatim. */
137
137
  while ((uint64_t)needed > quic_stream_window_left(client_v, s)) {
138
138
  rb_funcall(client_v, rb_intern("pump_once"), 0);
@@ -143,7 +143,7 @@ quic_stream_write_m(int argc, VALUE *argv, VALUE self)
143
143
  }
144
144
 
145
145
  /* Non-blocking write: enqueue at most `window_left` bytes from `data` and
146
- return the count actually queued. Raises Quic::Error::WaitWritable if the
146
+ return the count actually queued. Raises QUIC::Error::WaitWritable if the
147
147
  window is zero and we have a non-empty payload to send. If only a partial
148
148
  prefix fits, FIN is NOT set on this call (caller re-issues with
149
149
  `fin: true` once the remainder is accepted) so we don't half-commit FIN.
@@ -172,7 +172,7 @@ quic_stream_write_nonblock_m(int argc, VALUE *argv, VALUE self)
172
172
  uint64_t avail = quic_stream_window_left(client_v, s);
173
173
 
174
174
  if (avail == 0) {
175
- rb_raise(rb_eQuicErrorWaitWritable, "stream send window is full");
175
+ rb_raise(rb_eQUICErrorWaitWritable, "stream send window is full");
176
176
  }
177
177
 
178
178
  long take = ((uint64_t)needed <= avail) ? needed : (long)avail;
@@ -198,7 +198,7 @@ quic_stream_close_write_m(VALUE self)
198
198
  to drop the returned prefix.
199
199
 
200
200
  - buffer empty + fin_received: raise EOFError (IO#read_nonblock semantics)
201
- - buffer empty + !fin_received: raise Quic::Error::WaitReadable
201
+ - buffer empty + !fin_received: raise QUIC::Error::WaitReadable
202
202
  - buffer non-empty: return min(length, buffer.bytesize) bytes
203
203
  */
204
204
  static VALUE
@@ -218,7 +218,7 @@ quic_stream_read_nonblock_m(VALUE self, VALUE length_v)
218
218
  if (s->fin_received) {
219
219
  rb_raise(rb_eEOFError, "end of file reached");
220
220
  }
221
- rb_raise(rb_eQuicErrorWaitReadable, "no data available");
221
+ rb_raise(rb_eQUICErrorWaitReadable, "no data available");
222
222
  }
223
223
 
224
224
  long take = (length < have) ? length : have;
@@ -253,7 +253,7 @@ quic_stream_close_read_m(VALUE self)
253
253
  ngtcp2_conn *conn = quic_client_conn(client_v);
254
254
 
255
255
  /* ngtcp2_conn_shutdown_stream_read sends STOP_SENDING. app_error_code 0
256
- since no Quic-level error API is exposed yet. */
256
+ since no QUIC-level error API is exposed yet. */
257
257
  int rv = ngtcp2_conn_shutdown_stream_read(conn, 0, s->stream_id, 0);
258
258
  if (rv != 0) quic_raise_ngtcp2_error(rv);
259
259
  return Qnil;
@@ -268,17 +268,53 @@ quic_stream_close_m(VALUE self)
268
268
  return Qnil;
269
269
  }
270
270
 
271
+ /* Abort the send side of the stream with RESET_STREAM
272
+ (ngtcp2_conn_shutdown_stream_write), carrying the given application error
273
+ code (default 0). After #reset, #write / #write_nonblock raise
274
+ QUIC::Error::StreamClosed because quic_stream_enqueue rejects a stream
275
+ with s->reset set. The read side is untouched (use #close_read /
276
+ #close for STOP_SENDING). Idempotent: a second #reset is a no-op.
277
+
278
+ A bare Stream (@client == nil, used by unit-test fixtures) skips the
279
+ ngtcp2 call and only flips s->reset, mirroring the #write / #close_write
280
+ bare-Stream escapes. */
281
+ static VALUE
282
+ quic_stream_reset_m(int argc, VALUE *argv, VALUE self)
283
+ {
284
+ VALUE error_code_v = Qnil;
285
+ rb_scan_args(argc, argv, "01", &error_code_v);
286
+ uint64_t error_code = NIL_P(error_code_v) ? 0 : NUM2ULL(error_code_v);
287
+
288
+ quic_stream_t *s;
289
+ TypedData_Get_Struct(self, quic_stream_t, &quic_stream_data_type, s);
290
+
291
+ if (s->reset) return Qnil; /* idempotent */
292
+
293
+ VALUE client_v = rb_ivar_get(self, rb_intern("@client"));
294
+ if (NIL_P(client_v)) {
295
+ s->reset = true; /* bare Stream escape */
296
+ return Qnil;
297
+ }
298
+
299
+ ngtcp2_conn *conn = quic_client_conn(client_v);
300
+ int rv = ngtcp2_conn_shutdown_stream_write(conn, 0, s->stream_id, error_code);
301
+ if (rv != 0) quic_raise_ngtcp2_error(rv);
302
+ s->reset = true;
303
+ return Qnil;
304
+ }
305
+
271
306
  void
272
- Init_quic_stream(VALUE rb_mQuicArg)
307
+ Init_quic_stream(VALUE rb_mQUICArg)
273
308
  {
274
- rb_cQuicStream = rb_define_class_under(rb_mQuicArg, "Stream", rb_cObject);
275
- rb_define_alloc_func(rb_cQuicStream, quic_stream_alloc);
276
- rb_define_method(rb_cQuicStream, "write", quic_stream_write_m, -1);
277
- rb_define_method(rb_cQuicStream, "write_nonblock", quic_stream_write_nonblock_m, -1);
278
- rb_define_method(rb_cQuicStream, "close_write", quic_stream_close_write_m, 0);
279
- rb_define_method(rb_cQuicStream, "read_nonblock", quic_stream_read_nonblock_m, 1);
280
- rb_define_method(rb_cQuicStream, "eof?", quic_stream_eof_p, 0);
281
- rb_define_method(rb_cQuicStream, "close_read", quic_stream_close_read_m, 0);
282
- rb_define_method(rb_cQuicStream, "close", quic_stream_close_m, 0);
309
+ rb_cQUICStream = rb_define_class_under(rb_mQUICArg, "Stream", rb_cObject);
310
+ rb_define_alloc_func(rb_cQUICStream, quic_stream_alloc);
311
+ rb_define_method(rb_cQUICStream, "write", quic_stream_write_m, -1);
312
+ rb_define_method(rb_cQUICStream, "write_nonblock", quic_stream_write_nonblock_m, -1);
313
+ rb_define_method(rb_cQUICStream, "close_write", quic_stream_close_write_m, 0);
314
+ rb_define_method(rb_cQUICStream, "read_nonblock", quic_stream_read_nonblock_m, 1);
315
+ rb_define_method(rb_cQUICStream, "eof?", quic_stream_eof_p, 0);
316
+ rb_define_method(rb_cQUICStream, "close_read", quic_stream_close_read_m, 0);
317
+ rb_define_method(rb_cQUICStream, "close", quic_stream_close_m, 0);
318
+ rb_define_method(rb_cQUICStream, "reset", quic_stream_reset_m, -1);
283
319
  /* #read (blocking) and #initiator are defined in lib/quic/stream.rb. */
284
320
  }
data/ext/quic/stream.h CHANGED
@@ -3,9 +3,9 @@
3
3
 
4
4
  #include "quic.h"
5
5
 
6
- extern VALUE rb_cQuicStream;
6
+ extern VALUE rb_cQUICStream;
7
7
 
8
- void Init_quic_stream(VALUE rb_mQuicArg);
8
+ void Init_quic_stream(VALUE rb_mQUICArg);
9
9
 
10
10
  /* Internal helpers used by connection_client.c stream callbacks and
11
11
  write_pkt's stream-aware path. */
@@ -29,11 +29,15 @@ 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 (server) stream has been pushed onto the
33
+ owner Client's @accept_queue, so recv_stream_data does not enqueue it
34
+ again on subsequent data arrivals. */
35
+ bool accept_queued;
32
36
  } quic_stream_t;
33
37
 
34
38
  extern const rb_data_type_t quic_stream_data_type;
35
39
 
36
- /* Allocate a new Quic::Stream Ruby object with a zero-initialized
40
+ /* Allocate a new QUIC::Stream Ruby object with a zero-initialized
37
41
  quic_stream_t. Caller fills stream_id and ivars (@id / @client /
38
42
  @pending_chunks / @recv_buffer). */
39
43
  VALUE quic_stream_new(int64_t stream_id, VALUE client);
@@ -2,22 +2,45 @@
2
2
 
3
3
  require "socket"
4
4
 
5
- module Quic
5
+ module QUIC
6
6
  module Connection
7
7
  class Client
8
- def self.new(host:, port:, transport_params: nil, settings: nil)
9
- remote_sockaddr = Addrinfo.udp(host, port).to_sockaddr
8
+ attr_reader :remote_address
9
+
10
+ # Build a Client and pin a single peer address. address_family takes a
11
+ # Symbol (:inet for IPv4, :inet6 for IPv6) or nil to defer family
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
10
32
  local_sockaddr = Addrinfo.udp("0.0.0.0", 0).to_sockaddr
11
33
 
12
34
  client = _open(
13
35
  local_sockaddr: local_sockaddr,
14
36
  remote_sockaddr: remote_sockaddr,
15
37
  server_name: host,
16
- transport_params: transport_params || Quic::TransportParams.default,
17
- settings: settings || Quic::Settings.default
38
+ transport_params: transport_params || QUIC::TransportParams.default,
39
+ settings: settings || QUIC::Settings.default
18
40
  )
19
41
  client.instance_variable_set(:@host, host)
20
42
  client.instance_variable_set(:@port, port)
43
+ client.instance_variable_set(:@remote_address, remote_address)
21
44
  client
22
45
  end
23
46
 
@@ -35,10 +58,10 @@ module Quic
35
58
  end
36
59
 
37
60
  # Drive the handshake to completion using the bound socket. Raises
38
- # Quic::Error::NotBound if #bind has not been called. Propagates any
39
- # Quic::Error subclass raised by #read_pkt / #handle_expiry.
61
+ # QUIC::Error::NotBound if #bind has not been called. Propagates any
62
+ # QUIC::Error subclass raised by #read_pkt / #handle_expiry.
40
63
  def run
41
- raise Quic::Error::NotBound, "Quic::Connection::Client#bind(sock) has not been called" if @sock.nil?
64
+ raise QUIC::Error::NotBound, "QUIC::Connection::Client#bind(sock) has not been called" if @sock.nil?
42
65
 
43
66
  pump_until { handshake_completed? }
44
67
  end
@@ -49,7 +72,7 @@ module Quic
49
72
  # fire. Public-ish so Stream blocking ops can share it; documented as
50
73
  # "internal" for users.
51
74
  def pump_once(timeout: nil)
52
- raise Quic::Error::NotBound, "Quic::Connection::Client#bind(sock) has not been called" if @sock.nil?
75
+ raise QUIC::Error::NotBound, "QUIC::Connection::Client#bind(sock) has not been called" if @sock.nil?
53
76
 
54
77
  while (pkt = write_pkt)
55
78
  @sock.send(pkt, 0)
@@ -79,6 +102,43 @@ module Quic
79
102
  pump_once until pred.call
80
103
  end
81
104
 
105
+ # Pop the next peer-initiated (server) stream off the accept queue,
106
+ # driving the I/O loop until one arrives. The queue is fed by the
107
+ # recv_stream_data callback the first time a server stream carries data.
108
+ #
109
+ # timeout: nil block until a stream is available (requires #bind)
110
+ # timeout: 0 return immediately; nil if the queue is empty
111
+ # timeout: Numeric block up to that many seconds, then return nil
112
+ #
113
+ # Returns a QUIC::Stream, or nil on timeout.
114
+ def accept_stream(timeout: nil)
115
+ stream = @accept_queue.shift
116
+ return stream unless stream.nil?
117
+ return nil if timeout == 0
118
+
119
+ deadline = timeout && Process.clock_gettime(Process::CLOCK_MONOTONIC) + timeout
120
+ loop do
121
+ if deadline
122
+ remaining = deadline - Process.clock_gettime(Process::CLOCK_MONOTONIC)
123
+ return nil if remaining <= 0.0
124
+ pump_once(timeout: remaining)
125
+ else
126
+ pump_once
127
+ end
128
+ stream = @accept_queue.shift
129
+ return stream unless stream.nil?
130
+ end
131
+ end
132
+
133
+ # Non-blocking variant of #accept_stream. Returns the next queued
134
+ # server stream, or raises QUIC::Error::WaitReadable (IO::WaitReadable
135
+ # mixin) when the queue is empty. Does not require #bind.
136
+ def accept_stream_nonblock
137
+ stream = @accept_queue.shift
138
+ raise QUIC::Error::WaitReadable, "no server-initiated stream available" if stream.nil?
139
+ stream
140
+ end
141
+
82
142
  private
83
143
 
84
144
  def pump_timeout(caller_timeout)
data/lib/quic/settings.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module Quic
3
+ module QUIC
4
4
  Settings = Data.define(
5
5
  :cc_algo,
6
6
  :initial_rtt,
@@ -8,10 +8,27 @@ module Quic
8
8
  :max_stream_window,
9
9
  :handshake_timeout,
10
10
  :no_pmtud,
11
- :alpn
11
+ :alpn,
12
+ :verify_mode,
13
+ :ca_file,
14
+ :ca_path
12
15
  )
13
16
 
14
17
  class Settings
18
+ # verify_mode (:peer or :none), ca_file and ca_path control server
19
+ # certificate verification. They default here so that callers building a
20
+ # Settings without them keep working.
21
+ #
22
+ # With verify_mode :peer and both ca_file and ca_path nil, the system
23
+ # default store is used, and SSL_CERT_FILE / SSL_CERT_DIR are honoured.
24
+ # That store is loaded once per process, the first time a connection needs
25
+ # it; later changes to those variables or to the CA bundle are not picked
26
+ # up. Setting ca_file or ca_path trusts only the certificates found there.
27
+ # With verify_mode :none, ca_file and ca_path are ignored.
28
+ def initialize(verify_mode: :peer, ca_file: nil, ca_path: nil, **rest)
29
+ super
30
+ end
31
+
15
32
  def self.default
16
33
  new(
17
34
  cc_algo: :cubic,
@@ -20,7 +37,10 @@ module Quic
20
37
  max_stream_window: 0,
21
38
  handshake_timeout: 10_000_000_000,
22
39
  no_pmtud: false,
23
- alpn: []
40
+ alpn: [],
41
+ verify_mode: :peer,
42
+ ca_file: nil,
43
+ ca_path: nil
24
44
  )
25
45
  end
26
46
  end
data/lib/quic/stream.rb CHANGED
@@ -1,10 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module Quic
4
- # Quic::Stream is defined in the C extension (ext/quic/stream.c); this file
3
+ module QUIC
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 Client#pump_until loop.
7
+ # the C #read_nonblock with an internal Client#pump_once loop.
8
8
  class Stream
9
9
  INITIATORS = %i[client_bidi server_bidi client_uni server_uni].freeze
10
10
 
@@ -24,28 +24,31 @@ module Quic
24
24
  # return up to `length` bytes, return nil at EOF.
25
25
  # read(nil): block until EOF, return everything that was received
26
26
  # (an empty String if nothing arrived before FIN).
27
+ #
28
+ # Both branches drain first and pump only when there is nothing to drain.
29
+ # #read_nonblock already distinguishes the two states we care about --
30
+ # QUIC::Error::WaitReadable for "nothing buffered yet" and EOFError for
31
+ # "nothing buffered and FIN seen" -- so the loop needs no separate
32
+ # predicate over @recv_buffer / #eof?.
27
33
  def read(length = nil)
28
34
  if length.nil?
29
- @client.pump_until { eof? }
30
35
  out = +""
31
36
  out.force_encoding(Encoding::BINARY)
32
- until eof?
33
- # Should not happen given pump_until's predicate, but defensive.
34
- out << read_nonblock(4096)
35
- end
36
- # Drain anything that arrived after the predicate was last evaluated.
37
37
  loop do
38
38
  out << read_nonblock(4096)
39
- rescue Quic::Error::WaitReadable, EOFError
39
+ rescue QUIC::Error::WaitReadable
40
+ @client.pump_once
41
+ rescue EOFError
40
42
  break
41
43
  end
42
44
  out
43
45
  else
44
- @client.pump_until { @recv_buffer.bytesize > 0 || @fin_received }
45
- begin
46
- read_nonblock(length)
46
+ loop do
47
+ return read_nonblock(length)
48
+ rescue QUIC::Error::WaitReadable
49
+ @client.pump_once
47
50
  rescue EOFError
48
- nil
51
+ return nil
49
52
  end
50
53
  end
51
54
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module Quic
3
+ module QUIC
4
4
  TransportParams = Data.define(
5
5
  :initial_max_stream_data_bidi_local,
6
6
  :initial_max_stream_data_bidi_remote,
data/lib/quic/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module Quic
4
- VERSION = "0.0.1"
3
+ module QUIC
4
+ VERSION = "0.0.2"
5
5
  end
data/lib/quic.rb CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  require_relative "quic/version"
4
4
 
5
- module Quic
5
+ module QUIC
6
6
  class Error < StandardError
7
7
  attr_reader :code
8
8
  end
data/sig/quic.rbs CHANGED
@@ -1,7 +1,7 @@
1
- module Quic
1
+ module QUIC
2
2
  VERSION: String
3
3
 
4
- def self.library_versions: () -> { ngtcp2: String, openssl: String }
4
+ def self.library_versions: () -> { ngtcp2: String, openssl: String, picotls: String }
5
5
 
6
6
  class Error < StandardError
7
7
  attr_reader code: Integer?
@@ -19,6 +19,15 @@ module Quic
19
19
  end
20
20
 
21
21
  class CryptoError < Error
22
+ # The TLS alert this client sent when the handshake failed in
23
+ # #read_pkt, or nil. Alerts received from the peer are not recorded.
24
+ attr_reader tls_alert: Integer?
25
+ end
26
+
27
+ class CertificateVerifyFailed < CryptoError
28
+ # X509_V_ERR_* (comparable with OpenSSL::X509::V_ERR_*), or nil when
29
+ # the server sent no certificate.
30
+ attr_reader verify_result: Integer?
22
31
  end
23
32
 
24
33
  class HandshakeTimeout < Error
@@ -69,13 +78,18 @@ module Quic
69
78
  attr_reader handshake_timeout: Integer
70
79
  attr_reader no_pmtud: bool
71
80
  attr_reader alpn: Array[String]
81
+ attr_reader verify_mode: :peer | :none
82
+ attr_reader ca_file: (String | _ToPath)?
83
+ attr_reader ca_path: (String | _ToPath)?
72
84
 
73
85
  def self.default: () -> Settings
74
86
  end
75
87
 
76
88
  module Connection
77
89
  class Client
78
- def self.new: (host: String, port: Integer, ?transport_params: TransportParams?, ?settings: Settings?) -> Client
90
+ attr_reader remote_address: Addrinfo
91
+
92
+ def self.new: (host: String, port: Integer, ?address_family: Symbol?, ?transport_params: TransportParams?, ?settings: Settings?) -> Client
79
93
 
80
94
  def self._open: (
81
95
  local_sockaddr: String,
@@ -102,6 +116,24 @@ module Quic
102
116
  def run: () -> void
103
117
  def pump_once: (?timeout: Float?) -> void
104
118
  def pump_until: () { () -> boolish } -> void
119
+
120
+ # Pops the next peer-initiated (server) stream off the accept queue,
121
+ # driving the I/O loop until one arrives. timeout: nil blocks forever
122
+ # (requires #bind), timeout: 0 returns immediately, timeout: Numeric
123
+ # blocks up to that many seconds. Returns nil on timeout.
124
+ def accept_stream: (?timeout: Numeric?) -> Stream?
125
+
126
+ # Non-blocking #accept_stream. Returns the next queued server stream,
127
+ # or raises QUIC::Error::WaitReadable when the queue is empty.
128
+ def accept_stream_nonblock: () -> Stream
129
+
130
+ # Sends an application CONNECTION_CLOSE (frame type 0x1d) to the peer
131
+ # carrying error_code and reason, then transitions the underlying
132
+ # ngtcp2_conn to the closing period. Requires Client#bind to have been
133
+ # called; raises QUIC::Error::NotBound otherwise. A second #close call
134
+ # after the connection has already entered the closing or draining
135
+ # period is a no-op.
136
+ def close: (?error_code: Integer, ?reason: String) -> nil
105
137
  end
106
138
  end
107
139
 
@@ -118,12 +150,12 @@ module Quic
118
150
  # Blocks (by internally calling Client#pump_once) until the peer's flow
119
151
  # control window has room for the full payload, then queues all bytes
120
152
  # and returns data.bytesize. Requires the parent Client to be bound to
121
- # a socket via Client#bind; raises Quic::Error::NotBound otherwise.
153
+ # a socket via Client#bind; raises QUIC::Error::NotBound otherwise.
122
154
  def write: (String data, ?fin: bool) -> Integer
123
155
 
124
156
  # Non-blocking variant. Queues as much of `data` as the current flow
125
157
  # control window allows and returns the byte count actually queued.
126
- # Raises Quic::Error::WaitWritable (mixed in with IO::WaitWritable) when
158
+ # Raises QUIC::Error::WaitWritable (mixed in with IO::WaitWritable) when
127
159
  # the window is zero and the payload is non-empty. If only a partial
128
160
  # prefix is accepted, `fin: true` is NOT propagated; the caller must
129
161
  # re-issue with `fin: true` on the trailing chunk.
@@ -132,5 +164,10 @@ module Quic
132
164
  def close_write: () -> nil
133
165
  def close_read: () -> nil
134
166
  def close: () -> nil
167
+
168
+ # Aborts the send side with RESET_STREAM carrying error_code (default 0).
169
+ # After #reset, #write / #write_nonblock raise QUIC::Error::StreamClosed.
170
+ # The read side is unaffected. Idempotent: a second #reset is a no-op.
171
+ def reset: (?Integer error_code) -> nil
135
172
  end
136
173
  end
metadata CHANGED
@@ -1,18 +1,34 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: quic
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yusuke Nakamura
8
8
  bindir: exe
9
9
  cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
11
- dependencies: []
12
- description: A thin Ruby binding around ngtcp2 for the QUIC transport protocol, with
13
- LibreSSL as the TLS backend. Both are vendored via mini_portile2 at install time;
14
- no system libraries required. The API is intentionally optimized for synchronous
15
- I/O and String-based buffers. Public API is not yet stable.
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: mini_portile2
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '0'
26
+ description: A thin Ruby binding around ngtcp2 for the QUIC transport protocol, using
27
+ picotls for TLS 1.3. ngtcp2 and picotls are vendored via mini_portile2 at install
28
+ time and linked statically; the crypto primitives and X.509 come from the host's
29
+ OpenSSL (or LibreSSL), linked dynamically, so the process shares one libcrypto with
30
+ Ruby's openssl extension. The API is intentionally optimized for synchronous I/O
31
+ and String-based buffers. Public API is not yet stable.
16
32
  email:
17
33
  - yusuke1994525@gmail.com
18
34
  executables: []
@@ -22,13 +38,18 @@ extra_rdoc_files: []
22
38
  files:
23
39
  - CHANGELOG.md
24
40
  - CODE_OF_CONDUCT.md
41
+ - LICENSE-DEPENDENCIES.txt
25
42
  - LICENSE.txt
26
43
  - README.md
27
44
  - Rakefile
45
+ - examples/doq_demo.rb
28
46
  - examples/echo_demo.rb
29
47
  - examples/handshake_demo.rb
48
+ - examples/io_loop_demo.rb
49
+ - examples/verify_demo.rb
30
50
  - ext/quic/connection_client.c
31
51
  - ext/quic/extconf.rb
52
+ - ext/quic/patches/picotls/0001-openssl-use-raw-public-key-api-for-X25519.patch
32
53
  - ext/quic/quic.c
33
54
  - ext/quic/quic.h
34
55
  - ext/quic/stream.c
@@ -48,6 +69,7 @@ metadata:
48
69
  homepage_uri: https://github.com/unasuke/quic-ruby
49
70
  source_code_uri: https://github.com/unasuke/quic-ruby
50
71
  changelog_uri: https://github.com/unasuke/quic-ruby/blob/main/CHANGELOG.md
72
+ rubygems_mfa_required: 'true'
51
73
  rdoc_options: []
52
74
  require_paths:
53
75
  - lib
@@ -62,8 +84,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
62
84
  - !ruby/object:Gem::Version
63
85
  version: '0'
64
86
  requirements: []
65
- rubygems_version: 4.0.10
87
+ rubygems_version: 4.0.16
66
88
  specification_version: 4
67
- summary: Thin Ruby binding around ngtcp2 for the QUIC transport protocol, with LibreSSL
68
- as the TLS backend.
89
+ summary: Thin Ruby binding around ngtcp2 for the QUIC transport protocol, using picotls
90
+ for TLS 1.3 over the system OpenSSL.
69
91
  test_files: []