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.
@@ -2,27 +2,49 @@
2
2
 
3
3
  VALUE rb_cNghttp3NV;
4
4
 
5
- /*
6
- * Converts a Ruby NV object to a C nghttp3_nv structure.
7
- * Note: The returned structure contains pointers to Ruby string data,
8
- * so the Ruby strings must remain valid while the structure is in use.
9
- */
10
- nghttp3_nv nghttp3_rb_nv_to_c(VALUE rb_nv) {
11
- nghttp3_nv nv;
12
- VALUE name = rb_iv_get(rb_nv, "@name");
13
- VALUE value = rb_iv_get(rb_nv, "@value");
14
- VALUE flags = rb_iv_get(rb_nv, "@flags");
15
-
16
- StringValue(name);
17
- StringValue(value);
18
-
19
- nv.name = (uint8_t *)RSTRING_PTR(name);
20
- nv.namelen = RSTRING_LEN(name);
21
- nv.value = (uint8_t *)RSTRING_PTR(value);
22
- nv.valuelen = RSTRING_LEN(value);
23
- nv.flags = NIL_P(flags) ? 0 : NUM2UINT(flags);
5
+ /* nghttp3 keeps the name and value of an NV it is told not to copy until
6
+ * writev_stream has sent them, and nothing keeps a Ruby String alive, or in
7
+ * place, that long: the NV may be dropped and its Strings freed, or moved by
8
+ * GC.compact. So these two flags are dropped, and nghttp3 always copies.
9
+ * They only spare a copy; the bytes sent stay the same, except that nghttp3
10
+ * lowercases a name it copies. */
11
+ #define NV_FLAGS_NO_COPY \
12
+ (NGHTTP3_NV_FLAG_NO_COPY_NAME | NGHTTP3_NV_FLAG_NO_COPY_VALUE)
13
+
14
+ static ID id_iv_name, id_iv_value, id_iv_flags;
15
+
16
+ /* The instance variables are read as they are, and to_str is not called:
17
+ * Ruby code could change the Strings already taken, or start a GC that
18
+ * moves them. rb_obj_is_kind_of and rb_ivar_get call no methods. */
19
+ long nghttp3_rb_nva_fill(VALUE headers, nghttp3_nv *nva, long nvlen) {
20
+ for (long i = 0; i < nvlen; i++) {
21
+ VALUE nv = RARRAY_AREF(headers, i);
22
+ if (!RTEST(rb_obj_is_kind_of(nv, rb_cNghttp3NV))) {
23
+ return i;
24
+ }
25
+ VALUE name = rb_ivar_get(nv, id_iv_name);
26
+ VALUE value = rb_ivar_get(nv, id_iv_value);
27
+ VALUE flags = rb_ivar_get(nv, id_iv_flags);
28
+ if (!RB_TYPE_P(name, T_STRING) || !RB_TYPE_P(value, T_STRING) ||
29
+ !FIXNUM_P(flags) || FIX2LONG(flags) < 0 || FIX2LONG(flags) > 255) {
30
+ return i;
31
+ }
32
+ nva[i] = (nghttp3_nv){
33
+ .name = (const uint8_t *)RSTRING_PTR(name),
34
+ .namelen = (size_t)RSTRING_LEN(name),
35
+ .value = (const uint8_t *)RSTRING_PTR(value),
36
+ .valuelen = (size_t)RSTRING_LEN(value),
37
+ .flags = (uint8_t)(FIX2LONG(flags) & ~NV_FLAGS_NO_COPY),
38
+ };
39
+ }
40
+ return -1;
41
+ }
24
42
 
25
- return nv;
43
+ void nghttp3_rb_raise_bad_nv(long index) {
44
+ rb_raise(rb_eArgError,
45
+ "headers[%ld] is not an HTTP3::Nghttp3::NV with String name and "
46
+ "value",
47
+ index);
26
48
  }
27
49
 
28
50
  /*
@@ -30,6 +52,8 @@ nghttp3_nv nghttp3_rb_nv_to_c(VALUE rb_nv) {
30
52
  * NV.new(name, value, flags: 0) -> NV
31
53
  *
32
54
  * Creates a new NV (name-value pair) object representing an HTTP header.
55
+ * flags is an Integer of 0..255, the NV_FLAG_* constants or-ed together;
56
+ * NV_FLAG_NO_COPY_NAME and NV_FLAG_NO_COPY_VALUE are ignored.
33
57
  */
34
58
  static VALUE rb_nghttp3_nv_initialize(int argc, VALUE *argv, VALUE self) {
35
59
  VALUE name, value, opts;
@@ -46,6 +70,11 @@ static VALUE rb_nghttp3_nv_initialize(int argc, VALUE *argv, VALUE self) {
46
70
  flags = rb_flags;
47
71
  }
48
72
  }
73
+ /* nghttp3_nv.flags is a uint8_t. */
74
+ if (!FIXNUM_P(flags) || FIX2LONG(flags) < 0 || FIX2LONG(flags) > 255) {
75
+ rb_raise(rb_eArgError,
76
+ "flags must be an Integer of 0..255 (got %+" PRIsVALUE ")", flags);
77
+ }
49
78
 
50
79
  rb_iv_set(self, "@name", rb_str_freeze(rb_str_dup(name)));
51
80
  rb_iv_set(self, "@value", rb_str_freeze(rb_str_dup(value)));
@@ -67,6 +96,10 @@ static VALUE rb_nghttp3_nv_get_flags(VALUE self) {
67
96
  }
68
97
 
69
98
  void Init_nghttp3_nv(void) {
99
+ id_iv_name = rb_intern("@name");
100
+ id_iv_value = rb_intern("@value");
101
+ id_iv_flags = rb_intern("@flags");
102
+
70
103
  /* Define NV flag constants */
71
104
  rb_define_const(rb_mNghttp3, "NV_FLAG_NONE", UINT2NUM(NGHTTP3_NV_FLAG_NONE));
72
105
  rb_define_const(rb_mNghttp3, "NV_FLAG_NEVER_INDEX",
@@ -46,26 +46,33 @@ static VALUE encoder_alloc(VALUE klass) {
46
46
  * call-seq:
47
47
  * Encoder.new(max_dtable_capacity) -> Encoder
48
48
  *
49
- * Creates a new QPACK encoder.
49
+ * Creates a new QPACK encoder. Called again, it starts over with a new
50
+ * encoder and frees the old one; if the new one cannot be made, it raises
51
+ * and the old one stays.
50
52
  */
51
53
  static VALUE rb_nghttp3_qpack_encoder_initialize(VALUE self,
52
54
  VALUE rb_max_capacity) {
53
55
  EncoderObj *obj;
56
+ nghttp3_qpack_encoder *encoder;
54
57
  size_t max_capacity;
55
58
  int rv;
56
59
 
57
60
  TypedData_Get_Struct(self, EncoderObj, &encoder_data_type, obj);
58
61
 
59
62
  max_capacity = NUM2SIZET(rb_max_capacity);
60
- obj->hard_max_dtable_capacity = max_capacity;
61
63
 
62
- rv = nghttp3_qpack_encoder_new(&obj->encoder, max_capacity,
63
- nghttp3_mem_default());
64
+ rv = nghttp3_qpack_encoder_new(&encoder, max_capacity, nghttp3_mem_default());
64
65
 
65
66
  if (rv != 0) {
66
67
  nghttp3_rb_raise(rv, "Failed to create QPACK encoder");
67
68
  }
68
69
 
70
+ if (obj->encoder != NULL) {
71
+ nghttp3_qpack_encoder_del(obj->encoder);
72
+ }
73
+ obj->encoder = encoder;
74
+ obj->hard_max_dtable_capacity = max_capacity;
75
+
69
76
  return self;
70
77
  }
71
78
 
@@ -91,26 +98,33 @@ static VALUE rb_nghttp3_qpack_encoder_encode(VALUE self, VALUE rb_stream_id,
91
98
  EncoderObj *obj;
92
99
  int64_t stream_id;
93
100
  nghttp3_nv *nva;
94
- size_t nvlen, i;
101
+ VALUE nva_buf;
102
+ long nvlen, bad;
95
103
  nghttp3_buf pbuf, rbuf, ebuf;
96
104
  int rv;
97
105
  VALUE result;
98
106
 
99
107
  TypedData_Get_Struct(self, EncoderObj, &encoder_data_type, obj);
100
108
 
109
+ /* As Connection#submit_request does: the arguments first, since to_int
110
+ * may run Ruby code, then every allocation, and only then the pointers
111
+ * into the header Strings. */
112
+ stream_id = nghttp3_rb_stream_id(rb_stream_id);
113
+ Check_Type(rb_headers, T_ARRAY);
114
+
101
115
  if (obj->encoder == NULL) {
102
116
  rb_raise(rb_eNghttp3InvalidStateError, "Encoder is not initialized");
103
117
  }
104
118
 
105
- stream_id = NUM2LL(rb_stream_id);
106
- Check_Type(rb_headers, T_ARRAY);
119
+ /* On the heap: a Ruby thread's machine stack is too small for a long
120
+ * list. */
107
121
  nvlen = RARRAY_LEN(rb_headers);
122
+ nva = ALLOCV_N(nghttp3_nv, nva_buf, nvlen);
108
123
 
109
- /* Allocate and convert headers */
110
- nva = ALLOCA_N(nghttp3_nv, nvlen);
111
- for (i = 0; i < nvlen; i++) {
112
- VALUE rb_nv = rb_ary_entry(rb_headers, i);
113
- nva[i] = nghttp3_rb_nv_to_c(rb_nv);
124
+ bad = nghttp3_rb_nva_fill(rb_headers, nva, nvlen);
125
+ if (bad >= 0) {
126
+ ALLOCV_END(nva_buf);
127
+ nghttp3_rb_raise_bad_nv(bad);
114
128
  }
115
129
 
116
130
  /* Initialize buffers */
@@ -119,7 +133,9 @@ static VALUE rb_nghttp3_qpack_encoder_encode(VALUE self, VALUE rb_stream_id,
119
133
  nghttp3_buf_init(&ebuf);
120
134
 
121
135
  rv = nghttp3_qpack_encoder_encode(obj->encoder, &pbuf, &rbuf, &ebuf,
122
- stream_id, nva, nvlen);
136
+ stream_id, nva, (size_t)nvlen);
137
+ ALLOCV_END(nva_buf);
138
+ RB_GC_GUARD(rb_headers);
123
139
 
124
140
  if (rv != 0) {
125
141
  nghttp3_buf_free(&pbuf, nghttp3_mem_default());
@@ -239,6 +255,12 @@ static VALUE rb_nghttp3_qpack_encoder_get_num_blocked_streams(VALUE self) {
239
255
 
240
256
  /* ============== Decoder ============== */
241
257
 
258
+ /* The most streams a Decoder decodes at once, Decoder::MAX_STREAMS: each
259
+ * one not finished holds a stream context, and the peer chooses how many
260
+ * it leaves unfinished. nghttp3's encoder keeps the same number of streams
261
+ * (NGHTTP3_QPACK_MAX_QPACK_STREAMS). */
262
+ #define DECODER_MAX_STREAMS 2000
263
+
242
264
  typedef struct {
243
265
  nghttp3_qpack_decoder *decoder;
244
266
  /* stream_id => nghttp3_qpack_stream_context *, in a C st_table: dfree
@@ -301,12 +323,15 @@ static VALUE decoder_alloc(VALUE klass) {
301
323
  * call-seq:
302
324
  * Decoder.new(max_dtable_capacity, max_blocked_streams) -> Decoder
303
325
  *
304
- * Creates a new QPACK decoder.
326
+ * Creates a new QPACK decoder. Called again, it starts over with a new
327
+ * decoder and frees the old one with the streams it was decoding; if the
328
+ * new one cannot be made, it raises and the old one stays.
305
329
  */
306
330
  static VALUE rb_nghttp3_qpack_decoder_initialize(VALUE self,
307
331
  VALUE rb_max_capacity,
308
332
  VALUE rb_max_blocked) {
309
333
  DecoderObj *obj;
334
+ nghttp3_qpack_decoder *decoder;
310
335
  size_t max_capacity, max_blocked;
311
336
  int rv;
312
337
 
@@ -314,21 +339,31 @@ static VALUE rb_nghttp3_qpack_decoder_initialize(VALUE self,
314
339
 
315
340
  max_capacity = NUM2SIZET(rb_max_capacity);
316
341
  max_blocked = NUM2SIZET(rb_max_blocked);
317
- obj->hard_max_dtable_capacity = max_capacity;
318
- obj->max_blocked_streams = max_blocked;
319
342
 
320
- rv = nghttp3_qpack_decoder_new(&obj->decoder, max_capacity, max_blocked,
343
+ rv = nghttp3_qpack_decoder_new(&decoder, max_capacity, max_blocked,
321
344
  nghttp3_mem_default());
322
345
 
323
346
  if (rv != 0) {
324
347
  nghttp3_rb_raise(rv, "Failed to create QPACK decoder");
325
348
  }
326
349
 
350
+ /* The stream contexts are of the old decoder's field sections. */
351
+ st_foreach(obj->stream_contexts, free_stream_context_i, 0);
352
+ st_clear(obj->stream_contexts);
353
+ if (obj->decoder != NULL) {
354
+ nghttp3_qpack_decoder_del(obj->decoder);
355
+ }
356
+ obj->decoder = decoder;
357
+ obj->hard_max_dtable_capacity = max_capacity;
358
+ obj->max_blocked_streams = max_blocked;
359
+
327
360
  return self;
328
361
  }
329
362
 
330
363
  /*
331
- * Get or create a stream context for the given stream_id
364
+ * Get or create a stream context for the given stream_id. Raises
365
+ * InvalidStateError when a new one would be one more than
366
+ * DECODER_MAX_STREAMS.
332
367
  */
333
368
  static nghttp3_qpack_stream_context *
334
369
  get_or_create_stream_context(DecoderObj *obj, int64_t stream_id) {
@@ -338,6 +373,13 @@ get_or_create_stream_context(DecoderObj *obj, int64_t stream_id) {
338
373
  return (nghttp3_qpack_stream_context *)existing;
339
374
  }
340
375
 
376
+ if (obj->stream_contexts->num_entries >= DECODER_MAX_STREAMS) {
377
+ rb_raise(rb_eNghttp3InvalidStateError,
378
+ "a decoder takes at most %d streams at once; finish or "
379
+ "cancel_stream one first",
380
+ DECODER_MAX_STREAMS);
381
+ }
382
+
341
383
  /* Create new context */
342
384
  nghttp3_qpack_stream_context *sctx;
343
385
  int rv =
@@ -407,7 +449,7 @@ static VALUE rb_nghttp3_qpack_decoder_decode(int argc, VALUE *argv,
407
449
  rb_raise(rb_eNghttp3InvalidStateError, "Decoder is not initialized");
408
450
  }
409
451
 
410
- stream_id = NUM2LL(rb_stream_id);
452
+ stream_id = nghttp3_rb_stream_id(rb_stream_id);
411
453
  Check_Type(rb_data, T_STRING);
412
454
  src = (const uint8_t *)RSTRING_PTR(rb_data);
413
455
  srclen = RSTRING_LEN(rb_data);
@@ -552,7 +594,7 @@ static VALUE rb_nghttp3_qpack_decoder_cancel_stream(VALUE self,
552
594
  rb_raise(rb_eNghttp3InvalidStateError, "Decoder is not initialized");
553
595
  }
554
596
 
555
- stream_id = NUM2LL(rb_stream_id);
597
+ stream_id = nghttp3_rb_stream_id(rb_stream_id);
556
598
 
557
599
  delete_stream_context(obj, stream_id);
558
600
 
@@ -638,6 +680,8 @@ void Init_nghttp3_qpack(void) {
638
680
  /* Define Decoder class */
639
681
  rb_cNghttp3QPACKDecoder =
640
682
  rb_define_class_under(rb_mNghttp3QPACK, "Decoder", rb_cObject);
683
+ rb_define_const(rb_cNghttp3QPACKDecoder, "MAX_STREAMS",
684
+ INT2NUM(DECODER_MAX_STREAMS));
641
685
  rb_define_alloc_func(rb_cNghttp3QPACKDecoder, decoder_alloc);
642
686
  rb_define_method(rb_cNghttp3QPACKDecoder, "initialize",
643
687
  rb_nghttp3_qpack_decoder_initialize, 2);