quic 0.0.4 → 0.0.6

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e019da100b72501ae5f3f097aa00344e763de5483e301b03e619c695e7ed269d
4
- data.tar.gz: 922faa6c8b6b96e121849d4f17823e31cbfdeb05da3d93681603c3fb55840127
3
+ metadata.gz: fd02d782aebbc1128a0fca8bda34bc2651b5d677e682d126ab0575b332b4f556
4
+ data.tar.gz: 9a5155a1b4f75a342d7476895641e5b2e06a6f7d5e99b4bd6f1830c90e747cda
5
5
  SHA512:
6
- metadata.gz: c1eb1754a7e98f652825f641d0a1bfb2c131d54fcaceffe2867b8000107752d39c7fcc9e739125085c84bb76272306213e5774730e408b4d6cfc2bb6ce61b5c9
7
- data.tar.gz: 365881fb77b46679c549f6e19dc5988cd94f62a3ab13ee937e0514d12202a45a63c17501c6bf492b6363b2b33c81894651965f74425e44af37cc2e24f37c0aa5
6
+ metadata.gz: f94809c98904f20281edbc702f57db0c0014e53243fb2d478e02c1c54870fb205bad185aadf3ab319d4f2294484aca46cf58b72f4d5a486138584426cd513808
7
+ data.tar.gz: d1f7925f594bc307201a295aed815084000da0e0c2a057dc6ad403d587888b42ae4a478a5d4949603a5d5d70f3c51b9cc2fa1224d1783956c9b2330743e2e467
data/CHANGELOG.md CHANGED
@@ -1,3 +1,17 @@
1
+ ## [0.0.6] - 2026-11-03
2
+
3
+ ### Added
4
+ - `quic/ext.h` is ABI 1.1: a stream handler's new `recv_stop_sending` is called as soon as the peer's STOP_SENDING arrives, instead of the handler learning of it when the stream closes. Handlers built for ABI 1.0 keep working. ([#23](https://github.com/unasuke/quic-ruby/pull/23))
5
+
6
+ ### Changed
7
+ - ngtcp2 is now built from 1.26.0 (was 1.25.0), and picotls from `a2d95de`, the revision ngtcp2 1.26.0 is tested against. ([#24](https://github.com/unasuke/quic-ruby/pull/24))
8
+
9
+ ## [0.0.5] - 2026-10-24
10
+
11
+ ### Fixed
12
+ - When the peer sends STOP_SENDING on a stream, what was queued on it but not yet sent is dropped, and `QUIC::Stream#write` and `#write_nonblock` raise `QUIC::Error::StreamClosed` carrying the STOP_SENDING's error code, a `#write` waiting for the peer's window included. Before, every `#write_pkt` after it raised `QUIC::Error::StreamClosed` while the stream had bytes left to send, and a waiting `#write` returned only at the idle timeout. ([#21](https://github.com/unasuke/quic-ruby/pull/21))
13
+ - `#write_pkt` no longer raises `QUIC::Error::StreamClosed` after `QUIC::Stream#reset` on a stream with bytes queued; those bytes are dropped. ([#21](https://github.com/unasuke/quic-ruby/pull/21))
14
+
1
15
  ## [0.0.4] - 2026-10-22
2
16
 
3
17
  ### Added
@@ -9,7 +9,7 @@ bundled nor redistributed. The quic gem itself is MIT licensed; see
9
9
  LICENSE.txt.
10
10
 
11
11
 
12
- ngtcp2 1.25.0 -- https://github.com/ngtcp2/ngtcp2
12
+ ngtcp2 1.26.0 -- https://github.com/ngtcp2/ngtcp2
13
13
  -------------------------------------------------
14
14
 
15
15
  The MIT License
@@ -53,7 +53,7 @@ the statically linked libngtcp2 and are covered by their own terms:
53
53
  COPYING text above.
54
54
 
55
55
 
56
- picotls f07f1c8 -- https://github.com/h2o/picotls
56
+ picotls a2d95de -- https://github.com/h2o/picotls
57
57
  -------------------------------------------------
58
58
 
59
59
  picotls has no top-level license file; the terms are stated in the header of
data/README.md CHANGED
@@ -147,7 +147,7 @@ It applies to clients, `Connection::Server.accept` and every connection of an En
147
147
 
148
148
  ### From other native extensions
149
149
 
150
- Another native extension can drive a connection's streams from C instead of through `QUIC::Stream`, as the [http3](https://github.com/unasuke/http3-ruby) gem does. `quic/ext.h` declares the function table (ABI 1.0), and `QUIC::EXT_INCLUDE_DIR` tells an `extconf.rb` where it is:
150
+ Another native extension can drive a connection's streams from C instead of through `QUIC::Stream`, as the [http3](https://github.com/unasuke/http3-ruby) gem does. `quic/ext.h` declares the function table (ABI 1.1; a stream handler built against ABI 1.0 keeps working), and `QUIC::EXT_INCLUDE_DIR` tells an `extconf.rb` where it is:
151
151
 
152
152
  ```ruby
153
153
  require "mkmf"
@@ -508,7 +508,9 @@ static VALUE quic_acked_stream_data_offset_body(VALUE arg) {
508
508
  /* Shift any head Chunks of @pending_chunks whose end-offset is fully
509
509
  covered by acked_offset. Each Chunk's start-offset in the stream is
510
510
  pending_shifted (after previous shifts); its end-offset is
511
- pending_shifted + RSTRING_LEN(head). */
511
+ pending_shifted + RSTRING_LEN(head). A stopped stream's
512
+ @pending_chunks is empty, and acks for what it sent before shift
513
+ nothing. */
512
514
  VALUE pending = rb_ivar_get(stream, rb_intern("@pending_chunks"));
513
515
  while (RARRAY_LEN(pending) > 0) {
514
516
  VALUE head = RARRAY_AREF(pending, 0);
@@ -626,9 +628,59 @@ static int quic_stream_reset_cb(ngtcp2_conn *conn, int64_t stream_id,
626
628
  return quic_conn_protect(c, quic_stream_reset_body, &a);
627
629
  }
628
630
 
631
+ /* Nothing queued on stream can go out any more: drop it all, and have
632
+ #write_pkt skip the stream from now on. ngtcp2 retransmits no STREAM
633
+ frame of a stream it has reset, so it no longer reads the bytes it was
634
+ given either. */
635
+ static void quic_stream_stop(VALUE stream) {
636
+ quic_stream_t *s;
637
+ TypedData_Get_Struct(stream, quic_stream_t, &quic_stream_data_type, s);
638
+ s->stopped = true;
639
+ rb_ary_clear(rb_ivar_get(stream, rb_intern("@pending_chunks")));
640
+ }
641
+
642
+ static VALUE quic_recv_stop_sending_body(VALUE arg) {
643
+ struct quic_stream_cb_args *a = (struct quic_stream_cb_args *)arg;
644
+ VALUE stream = quic_conn_lookup_stream(a->c, a->stream_id);
645
+ if (NIL_P(stream))
646
+ return Qnil;
647
+
648
+ quic_stream_t *s;
649
+ TypedData_Get_Struct(stream, quic_stream_t, &quic_stream_data_type, s);
650
+ s->stop_app_error_code = a->app_error_code;
651
+ quic_stream_stop(stream);
652
+ return Qnil;
653
+ }
654
+
655
+ /* The peer sent STOP_SENDING. ngtcp2 shuts the send side and sends
656
+ RESET_STREAM itself once this returns. A stream handler hears of it here
657
+ (recv_stop_sending). One built against ABI 1.0 lacks that member, and
658
+ learns of it only when #write_pkt finds the send side shut
659
+ (stream_shut_write) or the stream closes. */
660
+ static int quic_recv_stop_sending_cb(ngtcp2_conn *conn, int64_t stream_id,
661
+ uint64_t app_error_code, void *user_data,
662
+ void *stream_user_data) {
663
+ (void)conn;
664
+ (void)stream_user_data;
665
+ quic_conn_t *c = (quic_conn_t *)user_data;
666
+ if (c->handler) {
667
+ if (!QUIC_EXT_HANDLER_HAS(c->handler, recv_stop_sending))
668
+ return 0;
669
+ return c->handler->recv_stop_sending(c->handler_ud, stream_id,
670
+ app_error_code) < 0
671
+ ? NGTCP2_ERR_CALLBACK_FAILURE
672
+ : 0;
673
+ }
674
+ struct quic_stream_cb_args a = {
675
+ .c = c, .stream_id = stream_id, .app_error_code = app_error_code};
676
+ return quic_conn_protect(c, quic_recv_stop_sending_body, &a);
677
+ }
678
+
629
679
  /* The callbacks below matter only to a stream handler. Without one, the
630
680
  QUIC::Stream mode needs none of them. */
631
681
 
682
+ /* This endpoint stopped reading the stream, and STOP_SENDING is going out.
683
+ Not to be confused with recv_stop_sending above. */
632
684
  static int quic_stream_stop_sending_cb(ngtcp2_conn *conn, int64_t stream_id,
633
685
  uint64_t app_error_code, void *user_data,
634
686
  void *stream_user_data) {
@@ -701,6 +753,7 @@ void quic_conn_fill_common_callbacks(ngtcp2_callbacks *callbacks) {
701
753
  callbacks->acked_stream_data_offset = quic_acked_stream_data_offset_cb;
702
754
  callbacks->stream_close = quic_stream_close_cb;
703
755
  callbacks->stream_reset = quic_stream_reset_cb;
756
+ callbacks->recv_stop_sending = quic_recv_stop_sending_cb;
704
757
  callbacks->stream_stop_sending = quic_stream_stop_sending_cb;
705
758
  callbacks->extend_max_stream_data = quic_extend_max_stream_data_cb;
706
759
  callbacks->extend_max_remote_streams_bidi =
@@ -708,11 +761,12 @@ void quic_conn_fill_common_callbacks(ngtcp2_callbacks *callbacks) {
708
761
  callbacks->handshake_completed = quic_handshake_completed_cb;
709
762
  }
710
763
 
711
- /* Find the first stream in @streams that has either pending bytes or a
712
- pending FIN to flush. Phase 4 minimum: linear scan, take the first match
713
- (no round-robin). Sets *out_datav to the byte slice to send, *out_datavcnt
714
- to 0 or 1, *out_flags to NGTCP2_WRITE_STREAM_FLAG_FIN when applicable.
715
- Returns the stream's Ruby VALUE (or Qnil if no candidate). */
764
+ /* Find the first stream in @streams, stopped ones aside, that has either
765
+ pending bytes or a pending FIN to flush. Phase 4 minimum: linear scan,
766
+ take the first match (no round-robin). Sets *out_datav to the byte slice
767
+ to send, *out_datavcnt to 0 or 1, *out_flags to
768
+ NGTCP2_WRITE_STREAM_FLAG_FIN when applicable. Returns the stream's Ruby
769
+ VALUE (or Qnil if no candidate). */
716
770
  static VALUE quic_conn_pick_send_stream(VALUE self, ngtcp2_vec *out_datav,
717
771
  size_t *out_datavcnt,
718
772
  uint32_t *out_flags,
@@ -724,7 +778,7 @@ static VALUE quic_conn_pick_send_stream(VALUE self, ngtcp2_vec *out_datav,
724
778
  VALUE st = RARRAY_AREF(stream_values, i);
725
779
  quic_stream_t *s;
726
780
  TypedData_Get_Struct(st, quic_stream_t, &quic_stream_data_type, s);
727
- if (s->fin_flushed)
781
+ if (s->fin_flushed || s->stopped)
728
782
  continue;
729
783
 
730
784
  VALUE pending = rb_ivar_get(st, rb_intern("@pending_chunks"));
@@ -794,11 +848,18 @@ static ngtcp2_ssize quic_conn_write_pkt_streams(VALUE self, quic_conn_t *c,
794
848
  c->conn, path, pi, dest, destlen, &pdatalen, writev_flags, send_stream_id,
795
849
  &datav, datavcnt, c->now);
796
850
 
797
- if (n == NGTCP2_ERR_STREAM_DATA_BLOCKED && NIL_P(c->pending_exc)) {
798
- /* The stream's queued data is beyond what the peer's flow control lets
799
- through right now. Keep it queued and build the packet without it, so
800
- that ACKs and other frames still go out; the data follows once the
801
- peer extends its window (MAX_STREAM_DATA / MAX_DATA). */
851
+ if ((n == NGTCP2_ERR_STREAM_DATA_BLOCKED || n == NGTCP2_ERR_STREAM_SHUT_WR) &&
852
+ NIL_P(c->pending_exc)) {
853
+ /* DATA_BLOCKED: the stream's queued data is beyond what the peer's flow
854
+ control lets through right now. Keep it queued; it follows once the
855
+ peer extends its window (MAX_STREAM_DATA / MAX_DATA).
856
+ SHUT_WR: ngtcp2 shut the stream's send side without recv_stop_sending
857
+ marking the stream, as #reset does. What is queued can never go out,
858
+ so drop it.
859
+ Either way, build the packet without the stream's data, so that ACKs
860
+ and other frames still go out. */
861
+ if (n == NGTCP2_ERR_STREAM_SHUT_WR)
862
+ quic_stream_stop(selected_stream);
802
863
  selected_stream = Qnil;
803
864
  datavcnt = 0;
804
865
  writev_flags = 0;
@@ -14,6 +14,14 @@
14
14
  minimum destlen ngtcp2 accepts. Revisit when PMTUD is enabled (Phase 4+). */
15
15
  #define QUIC_WRITE_PKT_BUFLEN NGTCP2_MAX_UDP_PAYLOAD_SIZE
16
16
 
17
+ /* Whether the stream handler h has member, judged by the size it was built
18
+ with: a handler built against an older quic/ext.h is shorter, and its
19
+ memory ends before the members added since. Compared with the end of the
20
+ member, not the start of the next one, so trailing padding does not
21
+ count. */
22
+ #define QUIC_EXT_HANDLER_HAS(h, member) \
23
+ ((h)->size >= offsetof(quic_ext_stream_handler, member) + sizeof((h)->member))
24
+
17
25
  /* A QUIC::Endpoint (endpoint.c). */
18
26
  typedef struct quic_endpoint quic_endpoint_t;
19
27
 
data/ext/quic/ext_api.c CHANGED
@@ -15,14 +15,16 @@ static quic_ext_conn *quic_ext_conn_get(VALUE conn) {
15
15
 
16
16
  /* Hand every stream of the connection to h. Allowed once, and only before
17
17
  the connection has a QUIC::Stream: the two modes cannot share streams.
18
- Every member of the ABI 1.0 handler is called, so h must be at least that
19
- large. @stream_handler marks the connection for the Ruby side, which then
20
- refuses #accept_stream. */
18
+ Every member of the ABI 1.0 handler is called, so h must hold them all
19
+ (handshake_completed is the last). A handler built against an older
20
+ quic/ext.h than this one is shorter than sizeof(quic_ext_stream_handler),
21
+ and is called only for the members its size covers. @stream_handler marks
22
+ the connection for the Ruby side, which then refuses #accept_stream. */
21
23
  static int quic_ext_set_stream_handler(quic_ext_conn *ec,
22
24
  const quic_ext_stream_handler *h,
23
25
  void *ud, VALUE owner) {
24
26
  quic_conn_t *c = quic_ext_conn_struct(ec);
25
- if (h == NULL || h->size < sizeof(quic_ext_stream_handler))
27
+ if (h == NULL || !QUIC_EXT_HANDLER_HAS(h, handshake_completed))
26
28
  return QUIC_EXT_ERR_OTHER;
27
29
  if (c->handler != NULL)
28
30
  return QUIC_EXT_ERR_INVALID_STATE;
@@ -93,7 +95,7 @@ static int quic_ext_set_application_error(quic_ext_conn *ec,
93
95
 
94
96
  const quic_ext_api quic_ext_api_table = {
95
97
  .abi_major = QUIC_EXT_ABI_MAJOR,
96
- .abi_minor = 0,
98
+ .abi_minor = QUIC_EXT_ABI_MINOR,
97
99
  .size = sizeof(quic_ext_api),
98
100
  .conn_get = quic_ext_conn_get,
99
101
  .set_stream_handler = quic_ext_set_stream_handler,
data/ext/quic/extconf.rb CHANGED
@@ -5,11 +5,11 @@ require "mini_portile2"
5
5
  require "shellwords"
6
6
  require "fileutils"
7
7
 
8
- NGTCP2_VERSION = "1.25.0"
9
- NGTCP2_SHA256 = "1c0843076528a87b65e9a9d455100941f4cb65d44f96c5da6ae56df146043955"
10
- # The picotls revision that ngtcp2 1.25.0 is tested against (see ngtcp2's README).
11
- PICOTLS_COMMIT = "f07f1c8c68b237f1468bc1f1fe1b68aba3ff23b4"
12
- PICOTLS_SHA256 = "6ecb4f8d9946c2b9342300b29627dc34e746a551d438d35af325a4495d62a6e3"
8
+ NGTCP2_VERSION = "1.26.0"
9
+ NGTCP2_SHA256 = "85174d674e7f8fd911a827c6e483fd417b21b1f54d7853dac464cee446c8d35f"
10
+ # The picotls revision that ngtcp2 1.26.0 is tested against (see ngtcp2's README).
11
+ PICOTLS_COMMIT = "a2d95de4c875d6dcb27658e476e3b624f4381bf0"
12
+ PICOTLS_SHA256 = "d54263601c4652ef28451533bb2e2fae3f5f44aff54338cf5110b9c12c4bab3a"
13
13
  # Bump when ext/quic/patches/picotls/ or PicotlsRecipe::SOURCES changes;
14
14
  # installed? only checks for the archive.
15
15
  PICOTLS_PATCH_LEVEL = "p2"
@@ -14,7 +14,13 @@
14
14
  * the table's. The structs below grow only by appending, which bumps
15
15
  * abi_minor; reordering, removing or retyping a member bumps abi_major.
16
16
  * Check `size` before calling a member added after the version you were
17
- * built against. The handler struct follows the same rules.
17
+ * built against. The handler struct follows the same rules:
18
+ * set_stream_handler takes any handler at least as large as the ABI 1.0
19
+ * one, and quic.so calls a member added later only when the handler's
20
+ * `size` covers it. Every member `size` covers must be non-NULL.
21
+ * A member added after ABI 1.0 is marked with the minor that brought it
22
+ * (abi_minor N), and quic.so calls a handler member only when the table's
23
+ * abi_minor is at least that N: an older quic.so leaves it uncalled.
18
24
  * - Exceptions: no function in the table and no handler function raises a
19
25
  * Ruby exception; they return 0 or a negative error code. The one
20
26
  * exception is conn_get, which raises TypeError for a wrong object and is
@@ -37,6 +43,8 @@
37
43
  #include <stdint.h>
38
44
 
39
45
  #define QUIC_EXT_ABI_MAJOR 1
46
+ /* The abi_minor of the structs as this header declares them. */
47
+ #define QUIC_EXT_ABI_MINOR 1
40
48
 
41
49
  #define QUIC_EXT_ERR_INVALID_STATE (-1)
42
50
  #define QUIC_EXT_ERR_STREAM_ID_BLOCKED (-2)
@@ -67,6 +75,19 @@ typedef struct {
67
75
  int (*stream_close)(void *ud, int64_t id, int has_code,
68
76
  uint64_t app_error_code);
69
77
  int (*handshake_completed)(void *ud);
78
+ /* abi_minor 1 */
79
+ /* The peer sent STOP_SENDING on stream id, which this endpoint sends on
80
+ (stream_stop_sending is the other way: this endpoint stopped reading).
81
+ Called once per stream. Once it returns 0, ngtcp2 shuts the send side
82
+ and sends RESET_STREAM carrying app_error_code, unless all that was
83
+ sent, FIN included, has been acknowledged. next_stream_data should no
84
+ longer offer the stream; if it does, stream_shut_write follows. It can
85
+ come before any recv_stream_data, for a stream the peer has just
86
+ opened, and also comes for a unidirectional stream this endpoint
87
+ opened. stream_close may follow within the same #read_pkt. Calling
88
+ shutdown_stream_write here sends RESET_STREAM with that code instead,
89
+ but stream_close still reports app_error_code. */
90
+ int (*recv_stop_sending)(void *ud, int64_t id, uint64_t app_error_code);
70
91
  } quic_ext_stream_handler;
71
92
 
72
93
  typedef struct {
data/ext/quic/stream.c CHANGED
@@ -45,6 +45,19 @@ quic_stream_new(int64_t stream_id, VALUE owner) {
45
45
  return self;
46
46
  }
47
47
 
48
+ /* Raise QUIC::Error::StreamClosed if nothing more can be written to the
49
+ stream: it was reset or has closed, or the peer sent STOP_SENDING. */
50
+ static void quic_stream_check_writable(quic_stream_t *s) {
51
+ if (s->reset || s->closed) {
52
+ rb_raise(rb_eQUICErrorStreamClosed, "stream is closed");
53
+ }
54
+ if (s->stopped) {
55
+ rb_raise(rb_eQUICErrorStreamClosed,
56
+ "the peer asked to stop sending (error code %llu)",
57
+ (unsigned long long)s->stop_app_error_code);
58
+ }
59
+ }
60
+
48
61
  /* Append a binary copy of |data| to @pending_chunks and optionally set the
49
62
  fin_sent flag. Flow control is the caller's concern (#write blocks in
50
63
  quic_stream_write_m and #write_nonblock returns a partial byte count in
@@ -55,9 +68,7 @@ static VALUE quic_stream_enqueue(VALUE self, VALUE data, bool fin) {
55
68
  quic_stream_t *s;
56
69
  TypedData_Get_Struct(self, quic_stream_t, &quic_stream_data_type, s);
57
70
 
58
- if (s->reset || s->closed) {
59
- rb_raise(rb_eQUICErrorStreamClosed, "stream is closed");
60
- }
71
+ quic_stream_check_writable(s);
61
72
  if (s->fin_sent) {
62
73
  rb_raise(rb_eQUICErrorStreamClosed, "stream FIN already sent");
63
74
  }
@@ -94,7 +105,9 @@ static bool quic_stream_fin_kwarg(int argc, VALUE *argv, VALUE *data) {
94
105
  per-stream and the per-connection flow control limits ngtcp2 tracks, less
95
106
  the bytes already queued in @pending_chunks but not yet handed to ngtcp2.
96
107
  ngtcp2 only counts what it has sent, so without the subtraction a run of
97
- writes could queue far more than the peer allows. */
108
+ writes could queue far more than the peer allows. Callers check the
109
+ stream is writable first: a stopped stream's @pending_chunks no longer
110
+ matches its offsets. */
98
111
  static uint64_t quic_stream_window_left(VALUE self, VALUE client_v,
99
112
  quic_stream_t *s) {
100
113
  ngtcp2_conn *conn = quic_conn_ptr(client_v);
@@ -117,8 +130,10 @@ static uint64_t quic_stream_window_left(VALUE self, VALUE client_v,
117
130
  /* Blocking write: block by repeatedly invoking #pump_once on the owning
118
131
  connection (@client) until the peer's flow control window has enough room
119
132
  for the full payload, then enqueue. IO#write-compatible: always queues all
120
- of `data`. Bare Streams (built via QUIC::Stream.allocate for unit tests,
121
- with @client = nil) skip the window check entirely. */
133
+ of `data`. A window that can no longer open, because the peer sent
134
+ STOP_SENDING or the stream ended meanwhile, raises
135
+ QUIC::Error::StreamClosed. Bare Streams (built via QUIC::Stream.allocate
136
+ for unit tests, with @client = nil) skip the window check entirely. */
122
137
  static VALUE quic_stream_write_m(int argc, VALUE *argv, VALUE self) {
123
138
  VALUE data;
124
139
  bool fin = quic_stream_fin_kwarg(argc, argv, &data);
@@ -139,8 +154,10 @@ static VALUE quic_stream_write_m(int argc, VALUE *argv, VALUE self) {
139
154
  /* Loop until the full payload would fit in the current window. pump_once
140
155
  raises QUIC::Error::NotBound if the connection has not been #bind'ed;
141
156
  that error surfaces verbatim. */
157
+ quic_stream_check_writable(s);
142
158
  while ((uint64_t)needed > quic_stream_window_left(self, client_v, s)) {
143
159
  rb_funcall(client_v, rb_intern("pump_once"), 0);
160
+ quic_stream_check_writable(s);
144
161
  }
145
162
  }
146
163
 
@@ -172,6 +189,8 @@ static VALUE quic_stream_write_nonblock_m(int argc, VALUE *argv, VALUE self) {
172
189
 
173
190
  quic_stream_t *s;
174
191
  TypedData_Get_Struct(self, quic_stream_t, &quic_stream_data_type, s);
192
+ /* Before the window: a stream that cannot be written to never gets one. */
193
+ quic_stream_check_writable(s);
175
194
  uint64_t avail = quic_stream_window_left(self, client_v, s);
176
195
 
177
196
  if (avail == 0) {
data/ext/quic/stream.h CHANGED
@@ -29,6 +29,12 @@ 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
+ /* The send side is shut: the peer's STOP_SENDING arrived, or ngtcp2
33
+ reported the send side shut while writing. @pending_chunks has been
34
+ emptied, so the offsets above no longer describe it, and #write_pkt
35
+ skips the stream. */
36
+ bool stopped;
37
+ uint64_t stop_app_error_code; /* the STOP_SENDING's, 0 if none arrived */
32
38
  /* Set once a peer-initiated stream has been pushed onto the owning
33
39
  connection's @accept_queue, so recv_stream_data does not enqueue it
34
40
  again on subsequent data arrivals. */
data/lib/quic/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module QUIC
4
- VERSION = "0.0.4"
4
+ VERSION = "0.0.6"
5
5
  end
data/sig/quic.rbs CHANGED
@@ -323,15 +323,19 @@ module QUIC
323
323
  # Server) until the peer's flow control window has room for the full
324
324
  # payload, then queues all bytes and returns data.bytesize. Requires the
325
325
  # owning connection to be bound to a socket via #bind; raises
326
- # QUIC::Error::NotBound otherwise.
326
+ # QUIC::Error::NotBound otherwise. Raises QUIC::Error::StreamClosed,
327
+ # while waiting too, once the stream has been reset or closed, or the
328
+ # peer has sent STOP_SENDING. STOP_SENDING also drops what was queued
329
+ # but not yet sent.
327
330
  def write: (String data, ?fin: bool) -> Integer
328
331
 
329
332
  # Non-blocking variant. Queues as much of `data` as the current flow
330
333
  # control window allows and returns the byte count actually queued.
331
334
  # Raises QUIC::Error::WaitWritable (mixed in with IO::WaitWritable) when
332
- # the window is zero and the payload is non-empty. If only a partial
333
- # prefix is accepted, `fin: true` is NOT propagated; the caller must
334
- # re-issue with `fin: true` on the trailing chunk.
335
+ # the window is zero and the payload is non-empty, and
336
+ # QUIC::Error::StreamClosed as #write does. If only a partial prefix is
337
+ # accepted, `fin: true` is NOT propagated; the caller must re-issue with
338
+ # `fin: true` on the trailing chunk.
335
339
  def write_nonblock: (String data, ?fin: bool) -> Integer
336
340
  def eof?: () -> bool
337
341
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: quic
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.0.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yusuke Nakamura