http3 0.0.3 → 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.
@@ -102,6 +102,12 @@ static VALUE connection_alloc(VALUE klass) {
102
102
  obj->pending_exc = Qnil;
103
103
  obj->is_closed = 0;
104
104
  obj->is_server = 0;
105
+ obj->entered = CONN_IDLE;
106
+ obj->failed = 0;
107
+ obj->ctrl_bound = 0;
108
+ obj->qpack_bound = 0;
109
+ obj->goaway = 0;
110
+ obj->max_client_streams = 0;
105
111
  obj->pending = st_init_numtable();
106
112
  obj->stream_data_readers = rb_hash_new();
107
113
  obj->stream_user_data = rb_hash_new();
@@ -134,6 +140,92 @@ static void connection_raise_pending(ConnectionObj *obj) {
134
140
  rb_exc_raise(exc);
135
141
  }
136
142
 
143
+ /* The connection of self, for a method about to call into nghttp3. Raises
144
+ * InvalidStateError if the connection is closed or has failed, or if this
145
+ * is a callback of a call into nghttp3 that does not allow the method:
146
+ * inside read_stream only the methods allowed_in_read, and inside any other
147
+ * call none. Called once the arguments are converted, since to_int and
148
+ * to_str may run Ruby code that closes the connection.
149
+ *
150
+ * Around the call into nghttp3, the method sets obj->entered to the kind of
151
+ * call (CONN_IN_READ for read_stream, CONN_IN_OTHER otherwise) and puts the
152
+ * value before back as soon as nghttp3 returns. */
153
+ /* Note that the connection has failed if rv, which a call into nghttp3
154
+ * other than read_stream or writev_stream returned, is a fatal error. */
155
+ static void connection_check_fatal(ConnectionObj *obj, int rv) {
156
+ if (nghttp3_err_is_fatal(rv)) {
157
+ obj->failed = 1;
158
+ }
159
+ }
160
+
161
+ /* The name of the method being run, for messages. */
162
+ static VALUE this_method(void) { return rb_id2str(rb_frame_this_func()); }
163
+
164
+ static ConnectionObj *connection_enter(VALUE self, int allowed_in_read) {
165
+ ConnectionObj *obj;
166
+ TypedData_Get_Struct(self, ConnectionObj, &connection_data_type, obj);
167
+ if (obj->conn == NULL || obj->is_closed) {
168
+ rb_raise(rb_eNghttp3InvalidStateError, "Connection is closed");
169
+ }
170
+ if (obj->failed) {
171
+ rb_raise(rb_eNghttp3InvalidStateError,
172
+ "the connection has failed; only #close is allowed");
173
+ }
174
+ if (obj->entered == CONN_IN_OTHER ||
175
+ (obj->entered == CONN_IN_READ && !allowed_in_read)) {
176
+ rb_raise(rb_eNghttp3InvalidStateError,
177
+ "#%" PRIsVALUE
178
+ " cannot be called from inside a callback of this connection",
179
+ this_method());
180
+ }
181
+ return obj;
182
+ }
183
+
184
+ /* What nghttp3 asserts of its callers, which would abort the process,
185
+ * checked here first: the kind of a stream id (its range is checked by
186
+ * nghttp3_rb_stream_id), the role of the connection and the calls that must
187
+ * or must not have come before. Each raises for the method being run. */
188
+
189
+ NORETURN(static void raise_stream_kind(VALUE rb_id, const char *kind));
190
+ static void raise_stream_kind(VALUE rb_id, const char *kind) {
191
+ rb_raise(rb_eArgError, "stream_id %" PRIsVALUE " is not a %s stream", rb_id,
192
+ kind);
193
+ }
194
+
195
+ /* A unidirectional stream opened by this end, for the bind_* methods. */
196
+ static void check_own_uni_stream(ConnectionObj *obj, VALUE rb_id, int64_t id) {
197
+ if (obj->is_server && (id & 3) != 3) {
198
+ raise_stream_kind(rb_id, "server-initiated unidirectional");
199
+ }
200
+ if (!obj->is_server && (id & 3) != 2) {
201
+ raise_stream_kind(rb_id, "client-initiated unidirectional");
202
+ }
203
+ }
204
+
205
+ static void check_server(ConnectionObj *obj) {
206
+ if (!obj->is_server) {
207
+ rb_raise(rb_eNghttp3InvalidStateError,
208
+ "#%" PRIsVALUE " is for a server connection", this_method());
209
+ }
210
+ }
211
+
212
+ static void check_done(int done, const char *first) {
213
+ if (!done) {
214
+ rb_raise(rb_eNghttp3InvalidStateError, "#%" PRIsVALUE " needs #%s first",
215
+ this_method(), first);
216
+ }
217
+ }
218
+
219
+ /* nghttp3 asserts that the id of each GOAWAY is no larger than the one
220
+ * before. A server's shutdown sends one past the largest stream it has
221
+ * seen, which may have grown since, and a shutdown notice sends the largest
222
+ * id there is, so neither may follow a shutdown. */
223
+ static void check_no_shutdown(ConnectionObj *obj) {
224
+ if (obj->goaway == 2) {
225
+ rb_raise(rb_eNghttp3InvalidStateError, "#shutdown has been called already");
226
+ }
227
+ }
228
+
137
229
  static PendingStream *pending_lookup(ConnectionObj *obj, int64_t stream_id) {
138
230
  st_data_t value;
139
231
  if (!st_lookup(obj->pending, (st_data_t)stream_id, &value)) {
@@ -294,21 +386,23 @@ static VALUE rb_nghttp3_connection_bind_control_stream(VALUE self,
294
386
  VALUE rb_stream_id) {
295
387
  ConnectionObj *obj;
296
388
  int64_t stream_id;
297
- int rv;
298
-
299
- TypedData_Get_Struct(self, ConnectionObj, &connection_data_type, obj);
389
+ int rv, entered;
300
390
 
301
- if (obj->conn == NULL || obj->is_closed) {
302
- rb_raise(rb_eNghttp3InvalidStateError, "Connection is closed");
303
- }
391
+ stream_id = nghttp3_rb_stream_id(rb_stream_id);
392
+ obj = connection_enter(self, 0);
393
+ check_own_uni_stream(obj, rb_stream_id, stream_id);
304
394
 
305
- stream_id = NUM2LL(rb_stream_id);
395
+ entered = obj->entered;
396
+ obj->entered = CONN_IN_OTHER;
306
397
  rv = nghttp3_conn_bind_control_stream(obj->conn, stream_id);
398
+ obj->entered = entered;
399
+ connection_check_fatal(obj, rv);
307
400
 
308
401
  if (rv != 0) {
309
402
  nghttp3_rb_raise(rv, "Failed to bind control stream");
310
403
  }
311
404
 
405
+ obj->ctrl_bound = 1;
312
406
  return self;
313
407
  }
314
408
 
@@ -324,23 +418,25 @@ static VALUE rb_nghttp3_connection_bind_qpack_streams(VALUE self,
324
418
  VALUE rb_dec_stream_id) {
325
419
  ConnectionObj *obj;
326
420
  int64_t enc_stream_id, dec_stream_id;
327
- int rv;
421
+ int rv, entered;
328
422
 
329
- TypedData_Get_Struct(self, ConnectionObj, &connection_data_type, obj);
330
-
331
- if (obj->conn == NULL || obj->is_closed) {
332
- rb_raise(rb_eNghttp3InvalidStateError, "Connection is closed");
333
- }
334
-
335
- enc_stream_id = NUM2LL(rb_enc_stream_id);
336
- dec_stream_id = NUM2LL(rb_dec_stream_id);
423
+ enc_stream_id = nghttp3_rb_stream_id(rb_enc_stream_id);
424
+ dec_stream_id = nghttp3_rb_stream_id(rb_dec_stream_id);
425
+ obj = connection_enter(self, 0);
426
+ check_own_uni_stream(obj, rb_enc_stream_id, enc_stream_id);
427
+ check_own_uni_stream(obj, rb_dec_stream_id, dec_stream_id);
337
428
 
429
+ entered = obj->entered;
430
+ obj->entered = CONN_IN_OTHER;
338
431
  rv = nghttp3_conn_bind_qpack_streams(obj->conn, enc_stream_id, dec_stream_id);
432
+ obj->entered = entered;
433
+ connection_check_fatal(obj, rv);
339
434
 
340
435
  if (rv != 0) {
341
436
  nghttp3_rb_raise(rv, "Failed to bind QPACK streams");
342
437
  }
343
438
 
439
+ obj->qpack_bound = 1;
344
440
  return self;
345
441
  }
346
442
 
@@ -349,13 +445,20 @@ static VALUE rb_nghttp3_connection_bind_qpack_streams(VALUE self,
349
445
  * connection.close -> nil
350
446
  *
351
447
  * Explicitly closes the connection and frees resources.
352
- * After calling this, the connection object cannot be used.
448
+ * After calling this, the connection object cannot be used. Closing a
449
+ * closed connection does nothing; closing it from inside one of its
450
+ * callbacks, while nghttp3 still uses it, raises InvalidStateError.
353
451
  */
354
452
  static VALUE rb_nghttp3_connection_close(VALUE self) {
355
453
  ConnectionObj *obj;
356
454
 
357
455
  TypedData_Get_Struct(self, ConnectionObj, &connection_data_type, obj);
358
456
 
457
+ if (obj->entered != CONN_IDLE) {
458
+ rb_raise(rb_eNghttp3InvalidStateError,
459
+ "#close cannot be called from inside a callback of this "
460
+ "connection");
461
+ }
359
462
  if (obj->conn != NULL && !obj->is_closed) {
360
463
  nghttp3_conn_del(obj->conn);
361
464
  obj->conn = NULL;
@@ -413,15 +516,22 @@ struct read_stream_args {
413
516
  ConnectionObj *obj;
414
517
  int64_t stream_id;
415
518
  VALUE data;
519
+ int locked; /* read_stream locked data, and read_stream_unlock unlocks it */
416
520
  int fin;
417
521
  nghttp3_tstamp ts;
418
522
  };
419
523
 
420
524
  static VALUE read_stream_body(VALUE arg) {
421
525
  struct read_stream_args *a = (struct read_stream_args *)arg;
526
+ int entered = a->obj->entered;
527
+ a->obj->entered = CONN_IN_READ;
422
528
  nghttp3_ssize rv = nghttp3_conn_read_stream2(
423
529
  a->obj->conn, a->stream_id, (const uint8_t *)RSTRING_PTR(a->data),
424
530
  (size_t)RSTRING_LEN(a->data), a->fin, a->ts);
531
+ a->obj->entered = entered;
532
+ if (rv < 0) {
533
+ a->obj->failed = 1;
534
+ }
425
535
  connection_raise_pending(a->obj);
426
536
  if (rv < 0) {
427
537
  nghttp3_rb_raise((int)rv, "Failed to read stream");
@@ -430,7 +540,10 @@ static VALUE read_stream_body(VALUE arg) {
430
540
  }
431
541
 
432
542
  static VALUE read_stream_unlock(VALUE arg) {
433
- rb_str_unlocktmp(((struct read_stream_args *)arg)->data);
543
+ struct read_stream_args *a = (struct read_stream_args *)arg;
544
+ if (a->locked) {
545
+ rb_str_unlocktmp(a->data);
546
+ }
434
547
  return Qnil;
435
548
  }
436
549
 
@@ -449,7 +562,6 @@ static VALUE rb_nghttp3_connection_read_stream(int argc, VALUE *argv,
449
562
  VALUE rb_stream_id, rb_data, rb_opts;
450
563
  VALUE rb_fin = Qfalse;
451
564
  VALUE rb_ts = Qnil;
452
- ConnectionObj *obj;
453
565
 
454
566
  rb_scan_args(argc, argv, "2:", &rb_stream_id, &rb_data, &rb_opts);
455
567
 
@@ -461,23 +573,32 @@ static VALUE rb_nghttp3_connection_read_stream(int argc, VALUE *argv,
461
573
  rb_ts = rb_hash_aref(rb_opts, ID2SYM(rb_intern("ts")));
462
574
  }
463
575
 
464
- TypedData_Get_Struct(self, ConnectionObj, &connection_data_type, obj);
465
-
466
- if (obj->conn == NULL || obj->is_closed) {
467
- rb_raise(rb_eNghttp3InvalidStateError, "Connection is closed");
468
- }
469
-
470
576
  Check_Type(rb_data, T_STRING);
471
577
 
472
578
  struct read_stream_args args;
473
- args.obj = obj;
474
- args.stream_id = NUM2LL(rb_stream_id);
579
+ args.stream_id = nghttp3_rb_stream_id(rb_stream_id);
475
580
  args.data = rb_data;
476
581
  args.fin = RTEST(rb_fin) ? 1 : 0;
477
582
  args.ts = NIL_P(rb_ts) ? monotonic_now() : (nghttp3_tstamp)NUM2ULL(rb_ts);
583
+ args.obj = connection_enter(self, 0);
584
+ /* Data comes in on the streams of the other end and the request streams;
585
+ * nghttp3 asserts that a stream it knows is one of those. */
586
+ if (args.obj->is_server && (args.stream_id & 1) != 0) {
587
+ raise_stream_kind(rb_stream_id, "client-initiated");
588
+ }
589
+ if (!args.obj->is_server && (args.stream_id & 3) != 0 &&
590
+ (args.stream_id & 3) != 3) {
591
+ raise_stream_kind(
592
+ rb_stream_id,
593
+ "client-initiated bidirectional or server-initiated unidirectional");
594
+ }
478
595
 
479
- /* Callbacks run Ruby code, which must not change data under nghttp3. */
480
- rb_str_locktmp(rb_data);
596
+ /* Callbacks run Ruby code, which must not change data under nghttp3. A
597
+ * frozen String cannot be changed, and rb_str_locktmp refuses one. */
598
+ args.locked = !OBJ_FROZEN(rb_data);
599
+ if (args.locked) {
600
+ rb_str_locktmp(rb_data);
601
+ }
481
602
  return rb_ensure(read_stream_body, (VALUE)&args, read_stream_unlock,
482
603
  (VALUE)&args);
483
604
  }
@@ -494,17 +615,19 @@ static VALUE rb_nghttp3_connection_writev_stream(VALUE self) {
494
615
  nghttp3_vec vec[16];
495
616
  nghttp3_ssize rv;
496
617
  int64_t stream_id;
497
- int fin;
618
+ int fin, entered;
498
619
  VALUE rb_result, rb_data;
499
620
  size_t i, total_len;
500
621
 
501
- TypedData_Get_Struct(self, ConnectionObj, &connection_data_type, obj);
502
-
503
- if (obj->conn == NULL || obj->is_closed) {
504
- rb_raise(rb_eNghttp3InvalidStateError, "Connection is closed");
505
- }
622
+ obj = connection_enter(self, 0);
506
623
 
624
+ entered = obj->entered;
625
+ obj->entered = CONN_IN_OTHER;
507
626
  rv = nghttp3_conn_writev_stream(obj->conn, &stream_id, &fin, vec, 16);
627
+ obj->entered = entered;
628
+ if (rv < 0) {
629
+ obj->failed = 1;
630
+ }
508
631
  connection_raise_pending(obj);
509
632
 
510
633
  if (rv < 0) {
@@ -544,20 +667,19 @@ static VALUE rb_nghttp3_connection_add_write_offset(VALUE self,
544
667
  VALUE rb_stream_id,
545
668
  VALUE rb_n) {
546
669
  ConnectionObj *obj;
547
- int rv;
670
+ int rv, entered;
548
671
  int64_t stream_id;
549
672
  size_t n;
550
673
 
551
- TypedData_Get_Struct(self, ConnectionObj, &connection_data_type, obj);
552
-
553
- if (obj->conn == NULL || obj->is_closed) {
554
- rb_raise(rb_eNghttp3InvalidStateError, "Connection is closed");
555
- }
556
-
557
674
  stream_id = NUM2LL(rb_stream_id);
558
675
  n = NUM2SIZET(rb_n);
676
+ obj = connection_enter(self, 0);
559
677
 
678
+ entered = obj->entered;
679
+ obj->entered = CONN_IN_OTHER;
560
680
  rv = nghttp3_conn_add_write_offset(obj->conn, stream_id, n);
681
+ obj->entered = entered;
682
+ connection_check_fatal(obj, rv);
561
683
  connection_raise_pending(obj);
562
684
 
563
685
  if (rv != 0) {
@@ -577,20 +699,19 @@ static VALUE rb_nghttp3_connection_add_ack_offset(VALUE self,
577
699
  VALUE rb_stream_id,
578
700
  VALUE rb_n) {
579
701
  ConnectionObj *obj;
580
- int rv;
702
+ int rv, entered;
581
703
  int64_t stream_id;
582
704
  uint64_t n;
583
705
 
584
- TypedData_Get_Struct(self, ConnectionObj, &connection_data_type, obj);
585
-
586
- if (obj->conn == NULL || obj->is_closed) {
587
- rb_raise(rb_eNghttp3InvalidStateError, "Connection is closed");
588
- }
589
-
590
706
  stream_id = NUM2LL(rb_stream_id);
591
707
  n = NUM2ULL(rb_n);
708
+ obj = connection_enter(self, 0);
592
709
 
710
+ entered = obj->entered;
711
+ obj->entered = CONN_IN_OTHER;
593
712
  rv = nghttp3_conn_add_ack_offset(obj->conn, stream_id, n);
713
+ obj->entered = entered;
714
+ connection_check_fatal(obj, rv);
594
715
  connection_raise_pending(obj);
595
716
 
596
717
  if (rv != 0) {
@@ -610,15 +731,15 @@ static VALUE rb_nghttp3_connection_block_stream(VALUE self,
610
731
  VALUE rb_stream_id) {
611
732
  ConnectionObj *obj;
612
733
  int64_t stream_id;
613
-
614
- TypedData_Get_Struct(self, ConnectionObj, &connection_data_type, obj);
615
-
616
- if (obj->conn == NULL || obj->is_closed) {
617
- rb_raise(rb_eNghttp3InvalidStateError, "Connection is closed");
618
- }
734
+ int entered;
619
735
 
620
736
  stream_id = NUM2LL(rb_stream_id);
737
+ obj = connection_enter(self, 1);
738
+
739
+ entered = obj->entered;
740
+ obj->entered = CONN_IN_OTHER;
621
741
  nghttp3_conn_block_stream(obj->conn, stream_id);
742
+ obj->entered = entered;
622
743
 
623
744
  return self;
624
745
  }
@@ -632,17 +753,17 @@ static VALUE rb_nghttp3_connection_block_stream(VALUE self,
632
753
  static VALUE rb_nghttp3_connection_unblock_stream(VALUE self,
633
754
  VALUE rb_stream_id) {
634
755
  ConnectionObj *obj;
635
- int rv;
756
+ int rv, entered;
636
757
  int64_t stream_id;
637
758
 
638
- TypedData_Get_Struct(self, ConnectionObj, &connection_data_type, obj);
639
-
640
- if (obj->conn == NULL || obj->is_closed) {
641
- rb_raise(rb_eNghttp3InvalidStateError, "Connection is closed");
642
- }
643
-
644
759
  stream_id = NUM2LL(rb_stream_id);
760
+ obj = connection_enter(self, 1);
761
+
762
+ entered = obj->entered;
763
+ obj->entered = CONN_IN_OTHER;
645
764
  rv = nghttp3_conn_unblock_stream(obj->conn, stream_id);
765
+ obj->entered = entered;
766
+ connection_check_fatal(obj, rv);
646
767
  connection_raise_pending(obj);
647
768
 
648
769
  if (rv != 0) {
@@ -662,16 +783,15 @@ static VALUE rb_nghttp3_connection_stream_writable_p(VALUE self,
662
783
  VALUE rb_stream_id) {
663
784
  ConnectionObj *obj;
664
785
  int64_t stream_id;
665
- int rv;
666
-
667
- TypedData_Get_Struct(self, ConnectionObj, &connection_data_type, obj);
668
-
669
- if (obj->conn == NULL || obj->is_closed) {
670
- rb_raise(rb_eNghttp3InvalidStateError, "Connection is closed");
671
- }
786
+ int rv, entered;
672
787
 
673
788
  stream_id = NUM2LL(rb_stream_id);
789
+ obj = connection_enter(self, 1);
790
+
791
+ entered = obj->entered;
792
+ obj->entered = CONN_IN_OTHER;
674
793
  rv = nghttp3_conn_is_stream_writable(obj->conn, stream_id);
794
+ obj->entered = entered;
675
795
 
676
796
  return rv ? Qtrue : Qfalse;
677
797
  }
@@ -685,20 +805,19 @@ static VALUE rb_nghttp3_connection_stream_writable_p(VALUE self,
685
805
  static VALUE rb_nghttp3_connection_close_stream(VALUE self, VALUE rb_stream_id,
686
806
  VALUE rb_error_code) {
687
807
  ConnectionObj *obj;
688
- int rv;
808
+ int rv, entered;
689
809
  int64_t stream_id;
690
810
  uint64_t app_error_code;
691
811
 
692
- TypedData_Get_Struct(self, ConnectionObj, &connection_data_type, obj);
693
-
694
- if (obj->conn == NULL || obj->is_closed) {
695
- rb_raise(rb_eNghttp3InvalidStateError, "Connection is closed");
696
- }
697
-
698
812
  stream_id = NUM2LL(rb_stream_id);
699
813
  app_error_code = NUM2ULL(rb_error_code);
814
+ obj = connection_enter(self, 0);
700
815
 
816
+ entered = obj->entered;
817
+ obj->entered = CONN_IN_OTHER;
701
818
  rv = nghttp3_conn_close_stream(obj->conn, stream_id, app_error_code);
819
+ obj->entered = entered;
820
+ connection_check_fatal(obj, rv);
702
821
  connection_raise_pending(obj);
703
822
 
704
823
  if (rv != 0) {
@@ -718,15 +837,15 @@ static VALUE rb_nghttp3_connection_shutdown_stream_write(VALUE self,
718
837
  VALUE rb_stream_id) {
719
838
  ConnectionObj *obj;
720
839
  int64_t stream_id;
721
-
722
- TypedData_Get_Struct(self, ConnectionObj, &connection_data_type, obj);
723
-
724
- if (obj->conn == NULL || obj->is_closed) {
725
- rb_raise(rb_eNghttp3InvalidStateError, "Connection is closed");
726
- }
840
+ int entered;
727
841
 
728
842
  stream_id = NUM2LL(rb_stream_id);
843
+ obj = connection_enter(self, 1);
844
+
845
+ entered = obj->entered;
846
+ obj->entered = CONN_IN_OTHER;
729
847
  nghttp3_conn_shutdown_stream_write(obj->conn, stream_id);
848
+ obj->entered = entered;
730
849
  connection_raise_pending(obj);
731
850
 
732
851
  return self;
@@ -741,17 +860,17 @@ static VALUE rb_nghttp3_connection_shutdown_stream_write(VALUE self,
741
860
  static VALUE rb_nghttp3_connection_resume_stream(VALUE self,
742
861
  VALUE rb_stream_id) {
743
862
  ConnectionObj *obj;
744
- int rv;
863
+ int rv, entered;
745
864
  int64_t stream_id;
746
865
 
747
- TypedData_Get_Struct(self, ConnectionObj, &connection_data_type, obj);
748
-
749
- if (obj->conn == NULL || obj->is_closed) {
750
- rb_raise(rb_eNghttp3InvalidStateError, "Connection is closed");
751
- }
752
-
753
866
  stream_id = NUM2LL(rb_stream_id);
867
+ obj = connection_enter(self, 1);
868
+
869
+ entered = obj->entered;
870
+ obj->entered = CONN_IN_OTHER;
754
871
  rv = nghttp3_conn_resume_stream(obj->conn, stream_id);
872
+ obj->entered = entered;
873
+ connection_check_fatal(obj, rv);
755
874
  connection_raise_pending(obj);
756
875
 
757
876
  if (rv != 0) {
@@ -835,6 +954,31 @@ static nghttp3_ssize read_data_callback(nghttp3_conn *conn, int64_t stream_id,
835
954
  static const nghttp3_data_reader data_reader = {.read_data =
836
955
  read_data_callback};
837
956
 
957
+ /* The body of a submit_request or submit_response: a frozen copy of body:,
958
+ * so that the application cannot change the bytes nghttp3 will read, or the
959
+ * block, or Qnil for none. StringValue may run Ruby code (to_str). */
960
+ static VALUE submit_body_reader(VALUE rb_opts) {
961
+ VALUE rb_body = Qnil;
962
+ if (!NIL_P(rb_opts)) {
963
+ rb_body = rb_hash_aref(rb_opts, ID2SYM(rb_intern("body")));
964
+ }
965
+ if (!NIL_P(rb_body)) {
966
+ StringValue(rb_body);
967
+ return rb_str_new_frozen(rb_body);
968
+ }
969
+ return rb_block_given_p() ? rb_block_proc() : Qnil;
970
+ }
971
+
972
+ /*
973
+ * The submit_* methods take the pointers into the header Strings only once
974
+ * nothing more can run Ruby code or allocate before nghttp3 copies them: Ruby
975
+ * code could change or drop the Strings, and an allocation could start a GC
976
+ * that moves them. So they first convert their arguments, which may call
977
+ * to_int and to_str, then check the connection, which that Ruby code may
978
+ * have closed, then allocate (the header array, and the reader entry), and
979
+ * only then fill the array and call nghttp3.
980
+ */
981
+
838
982
  /*
839
983
  * call-seq:
840
984
  * connection.submit_request(stream_id, headers, body: nil) -> self
@@ -845,59 +989,57 @@ static const nghttp3_data_reader data_reader = {.read_data =
845
989
  */
846
990
  static VALUE rb_nghttp3_connection_submit_request(int argc, VALUE *argv,
847
991
  VALUE self) {
848
- VALUE rb_stream_id, rb_headers, rb_opts, rb_body;
992
+ VALUE rb_stream_id, rb_headers, rb_opts, reader;
849
993
  ConnectionObj *obj;
850
994
  int64_t stream_id;
851
995
  nghttp3_nv *nva;
852
- size_t nvlen, i;
853
- int rv;
854
- int has_body = 0;
996
+ VALUE nva_buf;
997
+ long nvlen, bad;
998
+ int rv, entered;
855
999
 
856
1000
  rb_scan_args(argc, argv, "2:", &rb_stream_id, &rb_headers, &rb_opts);
857
1001
 
858
1002
  TypedData_Get_Struct(self, ConnectionObj, &connection_data_type, obj);
859
1003
 
860
- if (obj->conn == NULL || obj->is_closed) {
861
- rb_raise(rb_eNghttp3InvalidStateError, "Connection is closed");
862
- }
863
-
864
1004
  if (obj->is_server) {
865
1005
  rb_raise(rb_eNghttp3InvalidStateError,
866
1006
  "submit_request can only be called on client connections");
867
1007
  }
868
1008
 
869
- stream_id = NUM2LL(rb_stream_id);
1009
+ stream_id = nghttp3_rb_stream_id(rb_stream_id);
870
1010
  Check_Type(rb_headers, T_ARRAY);
871
- nvlen = RARRAY_LEN(rb_headers);
1011
+ reader = submit_body_reader(rb_opts);
872
1012
 
873
- /* Allocate and convert headers */
874
- nva = ALLOCA_N(nghttp3_nv, nvlen);
875
- for (i = 0; i < nvlen; i++) {
876
- VALUE rb_nv = rb_ary_entry(rb_headers, i);
877
- nva[i] = nghttp3_rb_nv_to_c(rb_nv);
1013
+ connection_enter(self, 1);
1014
+ if ((stream_id & 3) != 0) {
1015
+ raise_stream_kind(rb_stream_id, "client-initiated bidirectional");
878
1016
  }
1017
+ check_done(obj->qpack_bound, "bind_qpack_streams");
879
1018
 
880
- /* Check for body */
881
- rb_body = Qnil;
882
- if (!NIL_P(rb_opts)) {
883
- rb_body = rb_hash_aref(rb_opts, ID2SYM(rb_intern("body")));
1019
+ /* On the heap: a Ruby thread's machine stack is too small for a long
1020
+ * list. */
1021
+ nvlen = RARRAY_LEN(rb_headers);
1022
+ nva = ALLOCV_N(nghttp3_nv, nva_buf, nvlen);
1023
+ if (!NIL_P(reader)) {
1024
+ rb_hash_aset(obj->stream_data_readers, LL2NUM(stream_id), reader);
884
1025
  }
885
1026
 
886
- if (!NIL_P(rb_body)) {
887
- StringValue(rb_body);
888
- /* A frozen copy, so that the application cannot change the bytes
889
- * nghttp3 will read. */
890
- rb_hash_aset(obj->stream_data_readers, LL2NUM(stream_id),
891
- rb_str_new_frozen(rb_body));
892
- has_body = 1;
893
- } else if (rb_block_given_p()) {
894
- VALUE block = rb_block_proc();
895
- rb_hash_aset(obj->stream_data_readers, LL2NUM(stream_id), block);
896
- has_body = 1;
897
- }
898
-
899
- rv = nghttp3_conn_submit_request(obj->conn, stream_id, nva, nvlen,
900
- has_body ? &data_reader : NULL, NULL);
1027
+ bad = nghttp3_rb_nva_fill(rb_headers, nva, nvlen);
1028
+ if (bad >= 0) {
1029
+ ALLOCV_END(nva_buf);
1030
+ if (!NIL_P(reader)) {
1031
+ rb_hash_delete(obj->stream_data_readers, LL2NUM(stream_id));
1032
+ }
1033
+ nghttp3_rb_raise_bad_nv(bad);
1034
+ }
1035
+ entered = obj->entered;
1036
+ obj->entered = CONN_IN_OTHER;
1037
+ rv = nghttp3_conn_submit_request(obj->conn, stream_id, nva, (size_t)nvlen,
1038
+ NIL_P(reader) ? NULL : &data_reader, NULL);
1039
+ obj->entered = entered;
1040
+ connection_check_fatal(obj, rv);
1041
+ ALLOCV_END(nva_buf);
1042
+ RB_GC_GUARD(rb_headers);
901
1043
  connection_raise_pending(obj);
902
1044
 
903
1045
  if (rv != 0) {
@@ -918,22 +1060,18 @@ static VALUE rb_nghttp3_connection_submit_request(int argc, VALUE *argv,
918
1060
  */
919
1061
  static VALUE rb_nghttp3_connection_submit_response(int argc, VALUE *argv,
920
1062
  VALUE self) {
921
- VALUE rb_stream_id, rb_headers, rb_opts, rb_body;
1063
+ VALUE rb_stream_id, rb_headers, rb_opts, reader;
922
1064
  ConnectionObj *obj;
923
1065
  int64_t stream_id;
924
1066
  nghttp3_nv *nva;
925
- size_t nvlen, i;
926
- int rv;
927
- int has_body = 0;
1067
+ VALUE nva_buf;
1068
+ long nvlen, bad;
1069
+ int rv, entered;
928
1070
 
929
1071
  rb_scan_args(argc, argv, "2:", &rb_stream_id, &rb_headers, &rb_opts);
930
1072
 
931
1073
  TypedData_Get_Struct(self, ConnectionObj, &connection_data_type, obj);
932
1074
 
933
- if (obj->conn == NULL || obj->is_closed) {
934
- rb_raise(rb_eNghttp3InvalidStateError, "Connection is closed");
935
- }
936
-
937
1075
  if (!obj->is_server) {
938
1076
  rb_raise(rb_eNghttp3InvalidStateError,
939
1077
  "submit_response can only be called on server connections");
@@ -941,36 +1079,33 @@ static VALUE rb_nghttp3_connection_submit_response(int argc, VALUE *argv,
941
1079
 
942
1080
  stream_id = NUM2LL(rb_stream_id);
943
1081
  Check_Type(rb_headers, T_ARRAY);
944
- nvlen = RARRAY_LEN(rb_headers);
1082
+ reader = submit_body_reader(rb_opts);
945
1083
 
946
- /* Allocate and convert headers */
947
- nva = ALLOCA_N(nghttp3_nv, nvlen);
948
- for (i = 0; i < nvlen; i++) {
949
- VALUE rb_nv = rb_ary_entry(rb_headers, i);
950
- nva[i] = nghttp3_rb_nv_to_c(rb_nv);
951
- }
1084
+ connection_enter(self, 1);
1085
+ check_done(obj->qpack_bound, "bind_qpack_streams");
952
1086
 
953
- /* Check for body */
954
- rb_body = Qnil;
955
- if (!NIL_P(rb_opts)) {
956
- rb_body = rb_hash_aref(rb_opts, ID2SYM(rb_intern("body")));
1087
+ nvlen = RARRAY_LEN(rb_headers);
1088
+ nva = ALLOCV_N(nghttp3_nv, nva_buf, nvlen);
1089
+ if (!NIL_P(reader)) {
1090
+ rb_hash_aset(obj->stream_data_readers, LL2NUM(stream_id), reader);
957
1091
  }
958
1092
 
959
- if (!NIL_P(rb_body)) {
960
- StringValue(rb_body);
961
- /* A frozen copy, so that the application cannot change the bytes
962
- * nghttp3 will read. */
963
- rb_hash_aset(obj->stream_data_readers, LL2NUM(stream_id),
964
- rb_str_new_frozen(rb_body));
965
- has_body = 1;
966
- } else if (rb_block_given_p()) {
967
- VALUE block = rb_block_proc();
968
- rb_hash_aset(obj->stream_data_readers, LL2NUM(stream_id), block);
969
- has_body = 1;
970
- }
971
-
972
- rv = nghttp3_conn_submit_response(obj->conn, stream_id, nva, nvlen,
973
- has_body ? &data_reader : NULL);
1093
+ bad = nghttp3_rb_nva_fill(rb_headers, nva, nvlen);
1094
+ if (bad >= 0) {
1095
+ ALLOCV_END(nva_buf);
1096
+ if (!NIL_P(reader)) {
1097
+ rb_hash_delete(obj->stream_data_readers, LL2NUM(stream_id));
1098
+ }
1099
+ nghttp3_rb_raise_bad_nv(bad);
1100
+ }
1101
+ entered = obj->entered;
1102
+ obj->entered = CONN_IN_OTHER;
1103
+ rv = nghttp3_conn_submit_response(obj->conn, stream_id, nva, (size_t)nvlen,
1104
+ NIL_P(reader) ? NULL : &data_reader);
1105
+ obj->entered = entered;
1106
+ connection_check_fatal(obj, rv);
1107
+ ALLOCV_END(nva_buf);
1108
+ RB_GC_GUARD(rb_headers);
974
1109
  connection_raise_pending(obj);
975
1110
 
976
1111
  if (rv != 0) {
@@ -993,26 +1128,35 @@ static VALUE rb_nghttp3_connection_submit_info(VALUE self, VALUE rb_stream_id,
993
1128
  ConnectionObj *obj;
994
1129
  int64_t stream_id;
995
1130
  nghttp3_nv *nva;
996
- size_t nvlen, i;
997
- int rv;
1131
+ VALUE nva_buf;
1132
+ long nvlen, bad;
1133
+ int rv, entered;
998
1134
 
999
1135
  TypedData_Get_Struct(self, ConnectionObj, &connection_data_type, obj);
1000
1136
 
1001
- if (obj->conn == NULL || obj->is_closed) {
1002
- rb_raise(rb_eNghttp3InvalidStateError, "Connection is closed");
1003
- }
1137
+ check_server(obj);
1004
1138
 
1005
1139
  stream_id = NUM2LL(rb_stream_id);
1006
1140
  Check_Type(rb_headers, T_ARRAY);
1007
- nvlen = RARRAY_LEN(rb_headers);
1008
1141
 
1009
- nva = ALLOCA_N(nghttp3_nv, nvlen);
1010
- for (i = 0; i < nvlen; i++) {
1011
- VALUE rb_nv = rb_ary_entry(rb_headers, i);
1012
- nva[i] = nghttp3_rb_nv_to_c(rb_nv);
1013
- }
1142
+ connection_enter(self, 1);
1143
+ check_done(obj->qpack_bound, "bind_qpack_streams");
1014
1144
 
1015
- rv = nghttp3_conn_submit_info(obj->conn, stream_id, nva, nvlen);
1145
+ nvlen = RARRAY_LEN(rb_headers);
1146
+ nva = ALLOCV_N(nghttp3_nv, nva_buf, nvlen);
1147
+
1148
+ bad = nghttp3_rb_nva_fill(rb_headers, nva, nvlen);
1149
+ if (bad >= 0) {
1150
+ ALLOCV_END(nva_buf);
1151
+ nghttp3_rb_raise_bad_nv(bad);
1152
+ }
1153
+ entered = obj->entered;
1154
+ obj->entered = CONN_IN_OTHER;
1155
+ rv = nghttp3_conn_submit_info(obj->conn, stream_id, nva, (size_t)nvlen);
1156
+ obj->entered = entered;
1157
+ connection_check_fatal(obj, rv);
1158
+ ALLOCV_END(nva_buf);
1159
+ RB_GC_GUARD(rb_headers);
1016
1160
  connection_raise_pending(obj);
1017
1161
 
1018
1162
  if (rv != 0) {
@@ -1036,26 +1180,33 @@ static VALUE rb_nghttp3_connection_submit_trailers(VALUE self,
1036
1180
  ConnectionObj *obj;
1037
1181
  int64_t stream_id;
1038
1182
  nghttp3_nv *nva;
1039
- size_t nvlen, i;
1040
- int rv;
1183
+ VALUE nva_buf;
1184
+ long nvlen, bad;
1185
+ int rv, entered;
1041
1186
 
1042
1187
  TypedData_Get_Struct(self, ConnectionObj, &connection_data_type, obj);
1043
1188
 
1044
- if (obj->conn == NULL || obj->is_closed) {
1045
- rb_raise(rb_eNghttp3InvalidStateError, "Connection is closed");
1046
- }
1047
-
1048
1189
  stream_id = NUM2LL(rb_stream_id);
1049
1190
  Check_Type(rb_trailers, T_ARRAY);
1050
- nvlen = RARRAY_LEN(rb_trailers);
1051
1191
 
1052
- nva = ALLOCA_N(nghttp3_nv, nvlen);
1053
- for (i = 0; i < nvlen; i++) {
1054
- VALUE rb_nv = rb_ary_entry(rb_trailers, i);
1055
- nva[i] = nghttp3_rb_nv_to_c(rb_nv);
1056
- }
1192
+ connection_enter(self, 1);
1193
+ check_done(obj->qpack_bound, "bind_qpack_streams");
1057
1194
 
1058
- rv = nghttp3_conn_submit_trailers(obj->conn, stream_id, nva, nvlen);
1195
+ nvlen = RARRAY_LEN(rb_trailers);
1196
+ nva = ALLOCV_N(nghttp3_nv, nva_buf, nvlen);
1197
+
1198
+ bad = nghttp3_rb_nva_fill(rb_trailers, nva, nvlen);
1199
+ if (bad >= 0) {
1200
+ ALLOCV_END(nva_buf);
1201
+ nghttp3_rb_raise_bad_nv(bad);
1202
+ }
1203
+ entered = obj->entered;
1204
+ obj->entered = CONN_IN_OTHER;
1205
+ rv = nghttp3_conn_submit_trailers(obj->conn, stream_id, nva, (size_t)nvlen);
1206
+ obj->entered = entered;
1207
+ connection_check_fatal(obj, rv);
1208
+ ALLOCV_END(nva_buf);
1209
+ RB_GC_GUARD(rb_trailers);
1059
1210
  connection_raise_pending(obj);
1060
1211
 
1061
1212
  if (rv != 0) {
@@ -1074,21 +1225,24 @@ static VALUE rb_nghttp3_connection_submit_trailers(VALUE self,
1074
1225
  */
1075
1226
  static VALUE rb_nghttp3_connection_submit_shutdown_notice(VALUE self) {
1076
1227
  ConnectionObj *obj;
1077
- int rv;
1228
+ int rv, entered;
1078
1229
 
1079
- TypedData_Get_Struct(self, ConnectionObj, &connection_data_type, obj);
1080
-
1081
- if (obj->conn == NULL || obj->is_closed) {
1082
- rb_raise(rb_eNghttp3InvalidStateError, "Connection is closed");
1083
- }
1230
+ obj = connection_enter(self, 1);
1231
+ check_done(obj->ctrl_bound, "bind_control_stream");
1232
+ check_no_shutdown(obj);
1084
1233
 
1234
+ entered = obj->entered;
1235
+ obj->entered = CONN_IN_OTHER;
1085
1236
  rv = nghttp3_conn_submit_shutdown_notice(obj->conn);
1237
+ obj->entered = entered;
1238
+ connection_check_fatal(obj, rv);
1086
1239
  connection_raise_pending(obj);
1087
1240
 
1088
1241
  if (rv != 0) {
1089
1242
  nghttp3_rb_raise(rv, "Failed to submit shutdown notice");
1090
1243
  }
1091
1244
 
1245
+ obj->goaway = 1;
1092
1246
  return self;
1093
1247
  }
1094
1248
 
@@ -1100,21 +1254,24 @@ static VALUE rb_nghttp3_connection_submit_shutdown_notice(VALUE self) {
1100
1254
  */
1101
1255
  static VALUE rb_nghttp3_connection_shutdown(VALUE self) {
1102
1256
  ConnectionObj *obj;
1103
- int rv;
1257
+ int rv, entered;
1104
1258
 
1105
- TypedData_Get_Struct(self, ConnectionObj, &connection_data_type, obj);
1106
-
1107
- if (obj->conn == NULL || obj->is_closed) {
1108
- rb_raise(rb_eNghttp3InvalidStateError, "Connection is closed");
1109
- }
1259
+ obj = connection_enter(self, 1);
1260
+ check_done(obj->ctrl_bound, "bind_control_stream");
1261
+ check_no_shutdown(obj);
1110
1262
 
1263
+ entered = obj->entered;
1264
+ obj->entered = CONN_IN_OTHER;
1111
1265
  rv = nghttp3_conn_shutdown(obj->conn);
1266
+ obj->entered = entered;
1267
+ connection_check_fatal(obj, rv);
1112
1268
  connection_raise_pending(obj);
1113
1269
 
1114
1270
  if (rv != 0) {
1115
1271
  nghttp3_rb_raise(rv, "Failed to shutdown connection");
1116
1272
  }
1117
1273
 
1274
+ obj->goaway = 2;
1118
1275
  return self;
1119
1276
  }
1120
1277
 
@@ -1128,15 +1285,17 @@ static VALUE rb_nghttp3_connection_shutdown(VALUE self) {
1128
1285
  static VALUE rb_nghttp3_connection_shutdown_stream_read(VALUE self,
1129
1286
  VALUE rb_stream_id) {
1130
1287
  ConnectionObj *obj;
1131
- int rv;
1132
-
1133
- TypedData_Get_Struct(self, ConnectionObj, &connection_data_type, obj);
1288
+ int64_t stream_id;
1289
+ int rv, entered;
1134
1290
 
1135
- if (obj->conn == NULL || obj->is_closed) {
1136
- rb_raise(rb_eNghttp3InvalidStateError, "Connection is closed");
1137
- }
1291
+ stream_id = nghttp3_rb_stream_id(rb_stream_id);
1292
+ obj = connection_enter(self, 1);
1138
1293
 
1139
- rv = nghttp3_conn_shutdown_stream_read(obj->conn, NUM2LL(rb_stream_id));
1294
+ entered = obj->entered;
1295
+ obj->entered = CONN_IN_OTHER;
1296
+ rv = nghttp3_conn_shutdown_stream_read(obj->conn, stream_id);
1297
+ obj->entered = entered;
1298
+ connection_check_fatal(obj, rv);
1140
1299
  connection_raise_pending(obj);
1141
1300
 
1142
1301
  if (rv != 0) {
@@ -1152,21 +1311,34 @@ static VALUE rb_nghttp3_connection_shutdown_stream_read(VALUE self,
1152
1311
  *
1153
1312
  * Tells a server connection the cumulative number of bidirectional streams
1154
1313
  * the client may open, as QUIC's MAX_STREAMS allows. nghttp3 uses it for
1155
- * graceful shutdown.
1314
+ * graceful shutdown. Like MAX_STREAMS, it never goes down.
1156
1315
  */
1157
1316
  static VALUE
1158
1317
  rb_nghttp3_connection_set_max_client_streams_bidi(VALUE self,
1159
1318
  VALUE rb_max_streams) {
1160
1319
  ConnectionObj *obj;
1320
+ uint64_t max_streams;
1321
+ int entered;
1161
1322
 
1162
1323
  TypedData_Get_Struct(self, ConnectionObj, &connection_data_type, obj);
1163
1324
 
1164
- if (obj->conn == NULL || obj->is_closed) {
1165
- rb_raise(rb_eNghttp3InvalidStateError, "Connection is closed");
1325
+ check_server(obj);
1326
+
1327
+ max_streams = NUM2ULL(rb_max_streams);
1328
+ connection_enter(self, 1);
1329
+ if (max_streams < obj->max_client_streams) {
1330
+ rb_raise(rb_eArgError,
1331
+ "max_client_streams_bidi cannot go down (from %" PRIsVALUE
1332
+ " to %" PRIsVALUE ")",
1333
+ ULL2NUM(obj->max_client_streams), rb_max_streams);
1166
1334
  }
1167
1335
 
1168
- nghttp3_conn_set_max_client_streams_bidi(obj->conn, NUM2ULL(rb_max_streams));
1336
+ entered = obj->entered;
1337
+ obj->entered = CONN_IN_OTHER;
1338
+ nghttp3_conn_set_max_client_streams_bidi(obj->conn, max_streams);
1339
+ obj->entered = entered;
1169
1340
 
1341
+ obj->max_client_streams = max_streams;
1170
1342
  return self;
1171
1343
  }
1172
1344