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.
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 rb_mQuic;
5
- VALUE rb_mQuicConnection;
6
- VALUE rb_cQuicConnectionClient;
5
+ VALUE rb_mQUIC;
6
+ VALUE rb_mQUICConnection;
7
+ VALUE rb_cQUICConnectionClient;
8
+ VALUE rb_cQUICConnectionServer;
7
9
 
8
- VALUE rb_eQuicError;
9
- VALUE rb_eQuicErrorProto;
10
- VALUE rb_eQuicErrorDropConn;
11
- VALUE rb_eQuicErrorRetry;
12
- VALUE rb_eQuicErrorClosed;
13
- VALUE rb_eQuicErrorCryptoError;
14
- VALUE rb_eQuicErrorHandshakeTimeout;
15
- VALUE rb_eQuicErrorFlowControl;
16
- VALUE rb_eQuicErrorUnknown;
17
- VALUE rb_eQuicErrorWaitReadable;
18
- VALUE rb_eQuicErrorWaitWritable;
19
- VALUE rb_eQuicErrorStreamClosed;
20
- VALUE rb_eQuicErrorStreamReset;
21
- VALUE rb_eQuicErrorNotBound;
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
- void
36
- quic_raise_ngtcp2_error(int rv)
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 = rb_eQuicErrorProto;
59
+ cls = rb_eQUICErrorProto;
42
60
  break;
43
61
  case NGTCP2_ERR_DROP_CONN:
44
- cls = rb_eQuicErrorDropConn;
62
+ cls = rb_eQUICErrorDropConn;
45
63
  break;
46
64
  case NGTCP2_ERR_RETRY:
47
- cls = rb_eQuicErrorRetry;
65
+ cls = rb_eQUICErrorRetry;
48
66
  break;
49
67
  case NGTCP2_ERR_CLOSING:
50
68
  case NGTCP2_ERR_DRAINING:
51
- cls = rb_eQuicErrorClosed;
69
+ cls = rb_eQUICErrorClosed;
52
70
  break;
53
71
  case NGTCP2_ERR_IDLE_CLOSE:
54
- cls = rb_eQuicErrorClosed;
72
+ cls = rb_eQUICErrorClosed;
55
73
  break;
56
74
  case NGTCP2_ERR_CRYPTO:
57
- cls = rb_eQuicErrorCryptoError;
75
+ cls = rb_eQUICErrorCryptoError;
58
76
  break;
59
77
  case NGTCP2_ERR_HANDSHAKE_TIMEOUT:
60
- cls = rb_eQuicErrorHandshakeTimeout;
78
+ cls = rb_eQUICErrorHandshakeTimeout;
61
79
  break;
62
80
  case NGTCP2_ERR_STREAM_DATA_BLOCKED:
63
- cls = rb_eQuicErrorFlowControl;
81
+ cls = rb_eQUICErrorFlowControl;
64
82
  break;
65
83
  case NGTCP2_ERR_STREAM_SHUT_WR:
66
- cls = rb_eQuicErrorStreamClosed;
84
+ cls = rb_eQUICErrorStreamClosed;
67
85
  break;
68
86
  default:
69
- cls = rb_eQuicErrorUnknown;
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
- rb_exc_raise(exc);
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
- rb_mQuic = rb_define_module("Quic");
82
- rb_mQuicConnection = rb_define_module_under(rb_mQuic, "Connection");
83
- rb_define_singleton_method(rb_mQuic, "library_versions", quic_library_versions, 0);
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
- /* Quic::Error is defined in Ruby (lib/quic.rb) before this Init_quic runs,
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 Quic::Error::<Name>. */
88
- rb_eQuicError = rb_const_get(rb_mQuic, rb_intern("Error"));
89
- rb_eQuicErrorProto = rb_define_class_under(rb_eQuicError, "Proto", rb_eQuicError);
90
- rb_eQuicErrorDropConn = rb_define_class_under(rb_eQuicError, "DropConn", rb_eQuicError);
91
- rb_eQuicErrorRetry = rb_define_class_under(rb_eQuicError, "Retry", rb_eQuicError);
92
- rb_eQuicErrorClosed = rb_define_class_under(rb_eQuicError, "Closed", rb_eQuicError);
93
- rb_eQuicErrorCryptoError = rb_define_class_under(rb_eQuicError, "CryptoError", rb_eQuicError);
94
- rb_eQuicErrorHandshakeTimeout = rb_define_class_under(rb_eQuicError, "HandshakeTimeout", rb_eQuicError);
95
- rb_eQuicErrorFlowControl = rb_define_class_under(rb_eQuicError, "FlowControl", rb_eQuicError);
96
- rb_eQuicErrorUnknown = rb_define_class_under(rb_eQuicError, "Unknown", rb_eQuicError);
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 Quic subclass)
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
- rb_eQuicErrorWaitReadable = rb_define_class_under(rb_eQuicError, "WaitReadable", rb_eQuicError);
104
- rb_include_module(rb_eQuicErrorWaitReadable, io_wait_readable);
105
- rb_eQuicErrorWaitWritable = rb_define_class_under(rb_eQuicError, "WaitWritable", rb_eQuicError);
106
- rb_include_module(rb_eQuicErrorWaitWritable, io_wait_writable);
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
- rb_eQuicErrorStreamClosed = rb_define_class_under(rb_eQuicError, "StreamClosed", rb_eQuicError);
109
- rb_eQuicErrorStreamReset = rb_define_class_under(rb_eQuicError, "StreamReset", rb_eQuicError);
110
- rb_eQuicErrorNotBound = rb_define_class_under(rb_eQuicError, "NotBound", rb_eQuicError);
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
- Init_quic_connection_client(rb_mQuicConnection);
113
- Init_quic_stream(rb_mQuic);
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 <ngtcp2/ngtcp2_crypto_quictls.h>
10
- #include <openssl/ssl.h>
11
-
12
- extern VALUE rb_mQuic;
13
- extern VALUE rb_mQuicConnection;
14
- extern VALUE rb_cQuicConnectionClient;
15
-
16
- extern VALUE rb_eQuicError;
17
- extern VALUE rb_eQuicErrorProto;
18
- extern VALUE rb_eQuicErrorDropConn;
19
- extern VALUE rb_eQuicErrorRetry;
20
- extern VALUE rb_eQuicErrorClosed;
21
- extern VALUE rb_eQuicErrorCryptoError;
22
- extern VALUE rb_eQuicErrorHandshakeTimeout;
23
- extern VALUE rb_eQuicErrorFlowControl;
24
- extern VALUE rb_eQuicErrorUnknown;
25
- extern VALUE rb_eQuicErrorWaitReadable;
26
- extern VALUE rb_eQuicErrorWaitWritable;
27
- extern VALUE rb_eQuicErrorStreamClosed;
28
- extern VALUE rb_eQuicErrorStreamReset;
29
- extern VALUE rb_eQuicErrorNotBound;
30
-
31
- void Init_quic_connection_client(VALUE rb_mQuicConnection);
32
-
33
- /* Accessor used by stream.c to issue ngtcp2 calls (e.g.
34
- ngtcp2_conn_shutdown_stream_read) for streams that hold a back-reference
35
- to their parent Quic::Connection::Client via @client. Returns the raw
36
- ngtcp2_conn pointer; the caller is responsible for ensuring the Ruby
37
- Client object is alive for the duration of the call. */
38
- ngtcp2_conn *quic_client_conn(VALUE client);
39
-
40
- /* Raise a Quic::Error subclass mapped from an ngtcp2 negative error code.
41
- Never returns. */
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 rb_cQuicStream;
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
- "Quic::Stream",
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 client)
37
+ quic_stream_new(int64_t stream_id, VALUE owner)
37
38
  {
38
- VALUE self = quic_stream_alloc(rb_cQuicStream);
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"), 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 Quic::Error::WaitWritable on its own. */
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(rb_eQuicErrorStreamClosed, "stream is closed");
65
+ rb_raise(rb_eQUICErrorStreamClosed, "stream is closed");
65
66
  }
66
67
  if (s->fin_sent) {
67
- rb_raise(rb_eQuicErrorStreamClosed, "stream FIN already sent");
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, capped by both the
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 = quic_client_conn(client_v);
105
- uint64_t stream_left = ngtcp2_conn_get_max_stream_data_left(conn, s->stream_id);
106
- uint64_t conn_left = ngtcp2_conn_get_max_data_left(conn);
107
- return stream_left < conn_left ? stream_left : conn_left;
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 Client#pump_once until the
111
- peer's flow control window has enough room for the full payload, then
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
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 @client has no socket bound; that
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 Quic::Error::WaitWritable if the
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(rb_eQuicErrorWaitWritable, "stream send window is full");
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 Quic::Error::WaitReadable
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(rb_eQuicErrorWaitReadable, "no data available");
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 = quic_client_conn(client_v);
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 Quic-level error API is exposed yet. */
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 rb_mQuicArg)
366
+ Init_quic_stream(VALUE rb_mQUICArg)
273
367
  {
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);
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 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
- /* Internal helpers used by connection_client.c stream callbacks and
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 Quic::Stream Ruby object with a zero-initialized
37
- quic_stream_t. Caller fills stream_id and ivars (@id / @client /
38
- @pending_chunks / @recv_buffer). */
39
- VALUE quic_stream_new(int64_t stream_id, VALUE client);
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 */