quic 0.0.2 → 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.
data/ext/quic/stream.c CHANGED
@@ -1,47 +1,44 @@
1
1
  #include "stream.h"
2
+ #include "connection_common.h"
2
3
 
3
4
  #include <string.h>
4
5
 
5
6
  VALUE rb_cQUICStream;
6
7
 
7
- static void
8
- quic_stream_free(void *ptr)
9
- {
10
- xfree(ptr);
11
- }
8
+ static void quic_stream_free(void *ptr) { xfree(ptr); }
12
9
 
13
- static size_t
14
- quic_stream_size(const void *ptr)
15
- {
10
+ static size_t quic_stream_size(const void *ptr) {
16
11
  (void)ptr;
17
12
  return sizeof(quic_stream_t);
18
13
  }
19
14
 
20
15
  const rb_data_type_t quic_stream_data_type = {
21
- "QUIC::Stream",
22
- {NULL, quic_stream_free, quic_stream_size,},
23
- NULL, NULL,
24
- RUBY_TYPED_FREE_IMMEDIATELY,
16
+ "QUIC::Stream",
17
+ {
18
+ NULL,
19
+ quic_stream_free,
20
+ quic_stream_size,
21
+ },
22
+ NULL,
23
+ NULL,
24
+ RUBY_TYPED_FREE_IMMEDIATELY,
25
25
  };
26
26
 
27
- static VALUE
28
- quic_stream_alloc(VALUE klass)
29
- {
27
+ static VALUE quic_stream_alloc(VALUE klass) {
30
28
  quic_stream_t *s = ALLOC(quic_stream_t);
31
29
  memset(s, 0, sizeof(*s));
32
30
  return TypedData_Wrap_Struct(klass, &quic_stream_data_type, s);
33
31
  }
34
32
 
35
33
  VALUE
36
- quic_stream_new(int64_t stream_id, VALUE client)
37
- {
34
+ quic_stream_new(int64_t stream_id, VALUE owner) {
38
35
  VALUE self = quic_stream_alloc(rb_cQUICStream);
39
36
  quic_stream_t *s;
40
37
  TypedData_Get_Struct(self, quic_stream_t, &quic_stream_data_type, s);
41
38
  s->stream_id = stream_id;
42
39
 
43
40
  rb_ivar_set(self, rb_intern("@id"), LL2NUM(stream_id));
44
- rb_ivar_set(self, rb_intern("@client"), client);
41
+ rb_ivar_set(self, rb_intern("@client"), owner);
45
42
  rb_ivar_set(self, rb_intern("@pending_chunks"), rb_ary_new());
46
43
  /* recv_buffer is a binary String; rb_str_buf_new returns ASCII-8BIT. */
47
44
  rb_ivar_set(self, rb_intern("@recv_buffer"), rb_str_buf_new(0));
@@ -54,9 +51,7 @@ quic_stream_new(int64_t stream_id, VALUE client)
54
51
  quic_stream_write_nonblock_m). This helper is reached only after the
55
52
  caller has confirmed the bytes fit in the current send window, so it
56
53
  never raises QUIC::Error::WaitWritable on its own. */
57
- static VALUE
58
- quic_stream_enqueue(VALUE self, VALUE data, bool fin)
59
- {
54
+ static VALUE quic_stream_enqueue(VALUE self, VALUE data, bool fin) {
60
55
  quic_stream_t *s;
61
56
  TypedData_Get_Struct(self, quic_stream_t, &quic_stream_data_type, s);
62
57
 
@@ -86,41 +81,51 @@ quic_stream_enqueue(VALUE self, VALUE data, bool fin)
86
81
  return LONG2NUM(len);
87
82
  }
88
83
 
89
- static bool
90
- quic_stream_fin_kwarg(int argc, VALUE *argv, VALUE *data)
91
- {
84
+ static bool quic_stream_fin_kwarg(int argc, VALUE *argv, VALUE *data) {
92
85
  VALUE opts = Qnil;
93
86
  rb_scan_args(argc, argv, "1:", data, &opts);
94
- if (NIL_P(opts)) return false;
87
+ if (NIL_P(opts))
88
+ return false;
95
89
  VALUE fin = rb_hash_lookup(opts, ID2SYM(rb_intern("fin")));
96
90
  return RTEST(fin);
97
91
  }
98
92
 
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. */
101
- static uint64_t
102
- quic_stream_window_left(VALUE client_v, quic_stream_t *s)
103
- {
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;
93
+ /* Return the effective send window for this stream: the smaller of the
94
+ per-stream and the per-connection flow control limits ngtcp2 tracks, less
95
+ the bytes already queued in @pending_chunks but not yet handed to ngtcp2.
96
+ ngtcp2 only counts what it has sent, so without the subtraction a run of
97
+ writes could queue far more than the peer allows. */
98
+ static uint64_t quic_stream_window_left(VALUE self, VALUE client_v,
99
+ quic_stream_t *s) {
100
+ ngtcp2_conn *conn = quic_conn_ptr(client_v);
101
+ uint64_t stream_left =
102
+ ngtcp2_conn_get_max_stream_data_left2(conn, s->stream_id);
103
+ uint64_t conn_left = ngtcp2_conn_get_max_data_left2(conn);
104
+ uint64_t left = stream_left < conn_left ? stream_left : conn_left;
105
+
106
+ /* @pending_chunks starts at stream offset pending_shifted, and the bytes
107
+ up to sent_offset have been sent. */
108
+ VALUE pending = rb_ivar_get(self, rb_intern("@pending_chunks"));
109
+ uint64_t queued_end = s->pending_shifted;
110
+ for (long i = 0; i < RARRAY_LEN(pending); i++) {
111
+ queued_end += (uint64_t)RSTRING_LEN(RARRAY_AREF(pending, i));
112
+ }
113
+ uint64_t unsent = queued_end - s->sent_offset;
114
+ return left > unsent ? left - unsent : 0;
108
115
  }
109
116
 
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. */
115
- static VALUE
116
- quic_stream_write_m(int argc, VALUE *argv, VALUE self)
117
- {
117
+ /* Blocking write: block by repeatedly invoking #pump_once on the owning
118
+ connection (@client) until the peer's flow control window has enough room
119
+ 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. */
122
+ static VALUE quic_stream_write_m(int argc, VALUE *argv, VALUE self) {
118
123
  VALUE data;
119
124
  bool fin = quic_stream_fin_kwarg(argc, argv, &data);
120
125
 
121
126
  VALUE client_v = rb_ivar_get(self, rb_intern("@client"));
122
127
  if (NIL_P(client_v)) {
123
- return quic_stream_enqueue(self, data, fin); /* bare-Stream test escape */
128
+ return quic_stream_enqueue(self, data, fin); /* bare-Stream test escape */
124
129
  }
125
130
 
126
131
  /* These will be re-validated by quic_stream_enqueue, but we need the
@@ -132,9 +137,9 @@ quic_stream_write_m(int argc, VALUE *argv, VALUE self)
132
137
  quic_stream_t *s;
133
138
  TypedData_Get_Struct(self, quic_stream_t, &quic_stream_data_type, s);
134
139
  /* 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)) {
140
+ raises QUIC::Error::NotBound if the connection has not been #bind'ed;
141
+ that error surfaces verbatim. */
142
+ while ((uint64_t)needed > quic_stream_window_left(self, client_v, s)) {
138
143
  rb_funcall(client_v, rb_intern("pump_once"), 0);
139
144
  }
140
145
  }
@@ -148,15 +153,13 @@ quic_stream_write_m(int argc, VALUE *argv, VALUE self)
148
153
  prefix fits, FIN is NOT set on this call (caller re-issues with
149
154
  `fin: true` once the remainder is accepted) so we don't half-commit FIN.
150
155
  Bare Streams with @client = nil skip the window check entirely. */
151
- static VALUE
152
- quic_stream_write_nonblock_m(int argc, VALUE *argv, VALUE self)
153
- {
156
+ static VALUE quic_stream_write_nonblock_m(int argc, VALUE *argv, VALUE self) {
154
157
  VALUE data;
155
158
  bool fin = quic_stream_fin_kwarg(argc, argv, &data);
156
159
 
157
160
  VALUE client_v = rb_ivar_get(self, rb_intern("@client"));
158
161
  if (NIL_P(client_v)) {
159
- return quic_stream_enqueue(self, data, fin); /* bare-Stream test escape */
162
+ return quic_stream_enqueue(self, data, fin); /* bare-Stream test escape */
160
163
  }
161
164
 
162
165
  Check_Type(data, T_STRING);
@@ -169,7 +172,7 @@ quic_stream_write_nonblock_m(int argc, VALUE *argv, VALUE self)
169
172
 
170
173
  quic_stream_t *s;
171
174
  TypedData_Get_Struct(self, quic_stream_t, &quic_stream_data_type, s);
172
- uint64_t avail = quic_stream_window_left(client_v, s);
175
+ uint64_t avail = quic_stream_window_left(self, client_v, s);
173
176
 
174
177
  if (avail == 0) {
175
178
  rb_raise(rb_eQUICErrorWaitWritable, "stream send window is full");
@@ -181,12 +184,11 @@ quic_stream_write_nonblock_m(int argc, VALUE *argv, VALUE self)
181
184
  return quic_stream_enqueue(self, slice, effective_fin);
182
185
  }
183
186
 
184
- static VALUE
185
- quic_stream_close_write_m(VALUE self)
186
- {
187
+ static VALUE quic_stream_close_write_m(VALUE self) {
187
188
  quic_stream_t *s;
188
189
  TypedData_Get_Struct(self, quic_stream_t, &quic_stream_data_type, s);
189
- if (s->fin_sent) return Qnil; /* idempotent */
190
+ if (s->fin_sent)
191
+ return Qnil; /* idempotent */
190
192
  VALUE empty = rb_str_new("", 0);
191
193
  rb_enc_associate_index(empty, rb_ascii8bit_encindex());
192
194
  quic_stream_enqueue(self, empty, true);
@@ -201,9 +203,7 @@ quic_stream_close_write_m(VALUE self)
201
203
  - buffer empty + !fin_received: raise QUIC::Error::WaitReadable
202
204
  - buffer non-empty: return min(length, buffer.bytesize) bytes
203
205
  */
204
- static VALUE
205
- quic_stream_read_nonblock_m(VALUE self, VALUE length_v)
206
- {
206
+ static VALUE quic_stream_read_nonblock_m(VALUE self, VALUE length_v) {
207
207
  quic_stream_t *s;
208
208
  TypedData_Get_Struct(self, quic_stream_t, &quic_stream_data_type, s);
209
209
 
@@ -227,41 +227,63 @@ quic_stream_read_nonblock_m(VALUE self, VALUE length_v)
227
227
 
228
228
  /* Drop the first |take| bytes from buffer by memmove + truncate. */
229
229
  if (take < have) {
230
- memmove(RSTRING_PTR(buffer), RSTRING_PTR(buffer) + take, (size_t)(have - take));
230
+ memmove(RSTRING_PTR(buffer), RSTRING_PTR(buffer) + take,
231
+ (size_t)(have - take));
231
232
  }
232
233
  rb_str_set_len(buffer, have - take);
233
234
 
235
+ /* The application has consumed |take| bytes, so give the peer that much
236
+ more stream and connection credit. Without this the peer stops sending
237
+ once initial_max_data / initial_max_stream_data are used up. A stream
238
+ ngtcp2 has already forgotten is ignored (the call returns 0), but the
239
+ connection window still grows. Bare Streams have no connection. */
240
+ VALUE client_v = rb_ivar_get(self, rb_intern("@client"));
241
+ if (!NIL_P(client_v)) {
242
+ ngtcp2_conn *conn = quic_conn_ptr(client_v);
243
+ int rv = ngtcp2_conn_extend_max_stream_offset(conn, s->stream_id,
244
+ (uint64_t)take);
245
+ if (rv != 0)
246
+ quic_raise_ngtcp2_error(rv);
247
+ ngtcp2_conn_extend_max_offset(conn, (uint64_t)take);
248
+ }
249
+
234
250
  return out;
235
251
  }
236
252
 
237
- static VALUE
238
- quic_stream_eof_p(VALUE self)
239
- {
253
+ static VALUE quic_stream_eof_p(VALUE self) {
240
254
  quic_stream_t *s;
241
255
  TypedData_Get_Struct(self, quic_stream_t, &quic_stream_data_type, s);
242
256
  VALUE buffer = rb_ivar_get(self, rb_intern("@recv_buffer"));
243
257
  return (RSTRING_LEN(buffer) == 0 && s->fin_received) ? Qtrue : Qfalse;
244
258
  }
245
259
 
246
- static VALUE
247
- quic_stream_close_read_m(VALUE self)
248
- {
260
+ /* True once the stream is fully closed: both directions have finished (FIN
261
+ or reset) and everything sent has been acknowledged. Stays false if the
262
+ connection closes first, since ngtcp2 does not report stream closure then.
263
+ Keeps returning true after the stream has left the connection's @streams.
264
+ A bare Stream is never closed. */
265
+ static VALUE quic_stream_closed_p(VALUE self) {
266
+ quic_stream_t *s;
267
+ TypedData_Get_Struct(self, quic_stream_t, &quic_stream_data_type, s);
268
+ return s->closed ? Qtrue : Qfalse;
269
+ }
270
+
271
+ static VALUE quic_stream_close_read_m(VALUE self) {
249
272
  quic_stream_t *s;
250
273
  TypedData_Get_Struct(self, quic_stream_t, &quic_stream_data_type, s);
251
274
 
252
275
  VALUE client_v = rb_ivar_get(self, rb_intern("@client"));
253
- ngtcp2_conn *conn = quic_client_conn(client_v);
276
+ ngtcp2_conn *conn = quic_conn_ptr(client_v);
254
277
 
255
278
  /* ngtcp2_conn_shutdown_stream_read sends STOP_SENDING. app_error_code 0
256
279
  since no QUIC-level error API is exposed yet. */
257
280
  int rv = ngtcp2_conn_shutdown_stream_read(conn, 0, s->stream_id, 0);
258
- if (rv != 0) quic_raise_ngtcp2_error(rv);
281
+ if (rv != 0)
282
+ quic_raise_ngtcp2_error(rv);
259
283
  return Qnil;
260
284
  }
261
285
 
262
- static VALUE
263
- quic_stream_close_m(VALUE self)
264
- {
286
+ static VALUE quic_stream_close_m(VALUE self) {
265
287
  /* Send FIN (if not yet) then STOP_SENDING. Idempotent on each leg. */
266
288
  quic_stream_close_write_m(self);
267
289
  quic_stream_close_read_m(self);
@@ -278,9 +300,7 @@ quic_stream_close_m(VALUE self)
278
300
  A bare Stream (@client == nil, used by unit-test fixtures) skips the
279
301
  ngtcp2 call and only flips s->reset, mirroring the #write / #close_write
280
302
  bare-Stream escapes. */
281
- static VALUE
282
- quic_stream_reset_m(int argc, VALUE *argv, VALUE self)
283
- {
303
+ static VALUE quic_stream_reset_m(int argc, VALUE *argv, VALUE self) {
284
304
  VALUE error_code_v = Qnil;
285
305
  rb_scan_args(argc, argv, "01", &error_code_v);
286
306
  uint64_t error_code = NIL_P(error_code_v) ? 0 : NUM2ULL(error_code_v);
@@ -288,33 +308,55 @@ quic_stream_reset_m(int argc, VALUE *argv, VALUE self)
288
308
  quic_stream_t *s;
289
309
  TypedData_Get_Struct(self, quic_stream_t, &quic_stream_data_type, s);
290
310
 
291
- if (s->reset) return Qnil; /* idempotent */
311
+ if (s->reset)
312
+ return Qnil; /* idempotent */
292
313
 
293
314
  VALUE client_v = rb_ivar_get(self, rb_intern("@client"));
294
315
  if (NIL_P(client_v)) {
295
- s->reset = true; /* bare Stream escape */
316
+ s->reset = true; /* bare Stream escape */
296
317
  return Qnil;
297
318
  }
298
319
 
299
- ngtcp2_conn *conn = quic_client_conn(client_v);
320
+ ngtcp2_conn *conn = quic_conn_ptr(client_v);
300
321
  int rv = ngtcp2_conn_shutdown_stream_write(conn, 0, s->stream_id, error_code);
301
- if (rv != 0) quic_raise_ngtcp2_error(rv);
322
+ if (rv != 0)
323
+ quic_raise_ngtcp2_error(rv);
302
324
  s->reset = true;
303
325
  return Qnil;
304
326
  }
305
327
 
306
- void
307
- Init_quic_stream(VALUE rb_mQUICArg)
308
- {
328
+ /* Called by #accept_stream / #accept_stream_nonblock for the stream they
329
+ take off @accept_queue. A stream that closed while it waited there gives
330
+ the peer back the stream it used up now (see quic_stream_close_body). */
331
+ static VALUE quic_stream_accepted_m(VALUE self) {
332
+ quic_stream_t *s;
333
+ TypedData_Get_Struct(self, quic_stream_t, &quic_stream_data_type, s);
334
+ s->accepted = true;
335
+ if (s->peer_stream_held) {
336
+ s->peer_stream_held = false;
337
+ VALUE client_v = rb_ivar_get(self, rb_intern("@client"));
338
+ if (!NIL_P(client_v)) {
339
+ quic_conn_give_back_peer_stream(quic_conn_ptr(client_v), s->stream_id);
340
+ }
341
+ }
342
+ return Qnil;
343
+ }
344
+
345
+ void Init_quic_stream(VALUE rb_mQUICArg) {
309
346
  rb_cQUICStream = rb_define_class_under(rb_mQUICArg, "Stream", rb_cObject);
310
347
  rb_define_alloc_func(rb_cQUICStream, quic_stream_alloc);
311
348
  rb_define_method(rb_cQUICStream, "write", quic_stream_write_m, -1);
312
- rb_define_method(rb_cQUICStream, "write_nonblock", quic_stream_write_nonblock_m, -1);
349
+ rb_define_method(rb_cQUICStream, "write_nonblock",
350
+ quic_stream_write_nonblock_m, -1);
313
351
  rb_define_method(rb_cQUICStream, "close_write", quic_stream_close_write_m, 0);
314
- rb_define_method(rb_cQUICStream, "read_nonblock", quic_stream_read_nonblock_m, 1);
352
+ rb_define_method(rb_cQUICStream, "read_nonblock", quic_stream_read_nonblock_m,
353
+ 1);
315
354
  rb_define_method(rb_cQUICStream, "eof?", quic_stream_eof_p, 0);
355
+ rb_define_method(rb_cQUICStream, "closed?", quic_stream_closed_p, 0);
316
356
  rb_define_method(rb_cQUICStream, "close_read", quic_stream_close_read_m, 0);
317
357
  rb_define_method(rb_cQUICStream, "close", quic_stream_close_m, 0);
318
358
  rb_define_method(rb_cQUICStream, "reset", quic_stream_reset_m, -1);
359
+ rb_define_private_method(rb_cQUICStream, "_accepted", quic_stream_accepted_m,
360
+ 0);
319
361
  /* #read (blocking) and #initiator are defined in lib/quic/stream.rb. */
320
362
  }
data/ext/quic/stream.h CHANGED
@@ -7,7 +7,7 @@ extern VALUE rb_cQUICStream;
7
7
 
8
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 {
@@ -22,24 +22,30 @@ typedef struct {
22
22
  /* Cumulative bytes shifted out of @pending_chunks. Always
23
23
  <= acked_offset (we only shift a Chunk after its end is acked). */
24
24
  uint64_t pending_shifted;
25
- bool fin_received; /* recv_stream_data delivered FIN */
26
- bool fin_sent; /* #write(fin: true) queued FIN */
27
- bool fin_flushed; /* write_pkt actually wrote the FIN frame */
28
- bool reset; /* stream_reset callback fired */
29
- bool closed; /* stream_close callback fired */
25
+ bool fin_received; /* recv_stream_data delivered FIN */
26
+ bool fin_sent; /* #write(fin: true) queued FIN */
27
+ bool fin_flushed; /* write_pkt actually wrote the FIN frame */
28
+ bool reset; /* stream_reset callback fired */
29
+ bool closed; /* stream_close callback fired */
30
30
  uint64_t close_app_error_code;
31
31
  bool close_has_app_error_code;
32
- /* Set once a peer-initiated (server) stream has been pushed onto the
33
- owner Client's @accept_queue, so recv_stream_data does not enqueue it
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
34
  again on subsequent data arrivals. */
35
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;
36
41
  } quic_stream_t;
37
42
 
38
43
  extern const rb_data_type_t quic_stream_data_type;
39
44
 
40
- /* Allocate a new QUIC::Stream Ruby object with a zero-initialized
41
- quic_stream_t. Caller fills stream_id and ivars (@id / @client /
42
- @pending_chunks / @recv_buffer). */
43
- 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);
44
50
 
45
51
  #endif /* QUIC_STREAM_H */
@@ -0,0 +1,269 @@
1
+ #include "tls_context.h"
2
+ #include "connection_common.h"
3
+
4
+ #include <stddef.h>
5
+ #include <string.h>
6
+
7
+ #include <openssl/bio.h>
8
+ #include <openssl/err.h>
9
+ #include <openssl/pem.h>
10
+ #include <openssl/x509.h>
11
+
12
+ VALUE rb_cQUICTLSContext;
13
+
14
+ void quic_tls_ctx_ref(quic_tls_ctx_t *t) { t->refcnt++; }
15
+
16
+ void quic_tls_ctx_unref(quic_tls_ctx_t *t) {
17
+ if (--t->refcnt > 0)
18
+ return;
19
+ if (t->sign_cert_initialized)
20
+ ptls_openssl_dispose_sign_certificate(&t->sign_cert);
21
+ /* picotls malloc()ed the list and each entry, so free(), not xfree(). */
22
+ if (t->ctx.certificates.list != NULL) {
23
+ for (size_t i = 0; i < t->ctx.certificates.count; i++)
24
+ free(t->ctx.certificates.list[i].base);
25
+ free(t->ctx.certificates.list);
26
+ }
27
+ xfree(t->alpn);
28
+ xfree(t->alpn_buf);
29
+ xfree(t);
30
+ }
31
+
32
+ /* The QUIC::TLSContext object holds one reference. A context it never
33
+ finished building (an exception mid-way) is released the same way. */
34
+ static void quic_tls_context_free(void *ptr) {
35
+ if (ptr != NULL)
36
+ quic_tls_ctx_unref((quic_tls_ctx_t *)ptr);
37
+ }
38
+
39
+ static size_t quic_tls_context_size(const void *ptr) {
40
+ (void)ptr;
41
+ return sizeof(quic_tls_ctx_t);
42
+ }
43
+
44
+ static const rb_data_type_t quic_tls_context_type = {
45
+ "QUIC::TLSContext",
46
+ {
47
+ NULL,
48
+ quic_tls_context_free,
49
+ quic_tls_context_size,
50
+ },
51
+ NULL,
52
+ NULL,
53
+ RUBY_TYPED_FREE_IMMEDIATELY,
54
+ };
55
+
56
+ quic_tls_ctx_t *quic_tls_context_get(VALUE tls_context) {
57
+ return (quic_tls_ctx_t *)rb_check_typeddata(tls_context,
58
+ &quic_tls_context_type);
59
+ }
60
+
61
+ /* Pick the first of the server's ALPN protocols, in the server's order of
62
+ preference, that the client offered. Runs inside read_pkt with the GVL
63
+ held and never touches Ruby objects. */
64
+ static int
65
+ quic_tls_on_client_hello_cb(ptls_on_client_hello_t *self, ptls_t *tls,
66
+ ptls_on_client_hello_parameters_t *params) {
67
+ quic_tls_ctx_t *t =
68
+ (quic_tls_ctx_t *)((char *)self -
69
+ offsetof(quic_tls_ctx_t, on_client_hello));
70
+ for (size_t i = 0; i < t->alpn_count; i++) {
71
+ for (size_t j = 0; j < params->negotiated_protocols.count; j++) {
72
+ ptls_iovec_t offered = params->negotiated_protocols.list[j];
73
+ if (offered.len == t->alpn[i].len &&
74
+ memcmp(offered.base, t->alpn[i].base, offered.len) == 0) {
75
+ return ptls_set_negotiated_protocol(tls, (const char *)offered.base,
76
+ offered.len) == 0
77
+ ? 0
78
+ : PTLS_ERROR_NO_MEMORY;
79
+ }
80
+ }
81
+ }
82
+ return PTLS_ALERT_NO_APPLICATION_PROTOCOL;
83
+ }
84
+
85
+ /* Never supply a passphrase. Without a callback, PEM_read_bio_PrivateKey
86
+ falls back to PEM_def_callback, which prompts on the controlling terminal;
87
+ this way an encrypted key fails to load like any other unreadable one. */
88
+ static int quic_tls_no_passphrase_cb(char *buf, int size, int rwflag,
89
+ void *userdata) {
90
+ (void)buf;
91
+ (void)size;
92
+ (void)rwflag;
93
+ (void)userdata;
94
+ return -1;
95
+ }
96
+
97
+ /* Load the certificate chain and private key into t->ctx and install the
98
+ signer. The certificate file is PEM, the leaf first and then any
99
+ intermediates; there is no limit on their number. A misconfiguration
100
+ raises ArgumentError naming the step that failed. Everything allocated
101
+ here is released before raising, and libcrypto's error queue, which is
102
+ shared with Ruby's openssl extension, is left empty on every return. */
103
+ static void quic_tls_ctx_load_certificate_and_key(quic_tls_ctx_t *t,
104
+ VALUE certificate_path,
105
+ VALUE private_key_path) {
106
+ VALUE failure_class = rb_eArgError;
107
+ const char *failure = NULL;
108
+ VALUE failed_path = Qnil;
109
+ BIO *cert_bio = NULL;
110
+ BIO *key_bio = NULL;
111
+ X509 *leaf = NULL;
112
+ X509 *x;
113
+ STACK_OF(X509) *chain = NULL;
114
+ EVP_PKEY *pkey = NULL;
115
+ unsigned long err;
116
+
117
+ ERR_clear_error();
118
+
119
+ cert_bio = BIO_new_file(RSTRING_PTR(certificate_path), "r");
120
+ if (cert_bio == NULL ||
121
+ (leaf = PEM_read_bio_X509(cert_bio, NULL, NULL, NULL)) == NULL) {
122
+ failure = "failed to load certificate";
123
+ failed_path = certificate_path;
124
+ goto done;
125
+ }
126
+
127
+ if ((chain = sk_X509_new_null()) == NULL) {
128
+ failure_class = rb_eRuntimeError;
129
+ failure = "sk_X509_new_null failed";
130
+ goto done;
131
+ }
132
+ while ((x = PEM_read_bio_X509(cert_bio, NULL, NULL, NULL)) != NULL) {
133
+ if (!sk_X509_push(chain, x)) {
134
+ X509_free(x);
135
+ failure_class = rb_eRuntimeError;
136
+ failure = "sk_X509_push failed";
137
+ goto done;
138
+ }
139
+ }
140
+ /* The loop ends with PEM_R_NO_START_LINE once no PEM block is left. Any
141
+ other error means a broken block, as in SSL_CTX_use_certificate_chain_file.
142
+ */
143
+ err = ERR_peek_last_error();
144
+ if (ERR_GET_LIB(err) != ERR_LIB_PEM ||
145
+ ERR_GET_REASON(err) != PEM_R_NO_START_LINE) {
146
+ failure = "failed to load certificate";
147
+ failed_path = certificate_path;
148
+ goto done;
149
+ }
150
+
151
+ key_bio = BIO_new_file(RSTRING_PTR(private_key_path), "r");
152
+ if (key_bio == NULL ||
153
+ (pkey = PEM_read_bio_PrivateKey(key_bio, NULL, quic_tls_no_passphrase_cb,
154
+ NULL)) == NULL) {
155
+ failure = "failed to load private key";
156
+ failed_path = private_key_path;
157
+ goto done;
158
+ }
159
+
160
+ /* picotls has no counterpart of SSL_CTX_check_private_key. */
161
+ if (X509_check_private_key(leaf, pkey) != 1) {
162
+ failure = "private key does not match certificate";
163
+ failed_path = private_key_path;
164
+ goto done;
165
+ }
166
+
167
+ /* Takes its own reference on pkey. */
168
+ if (ptls_openssl_init_sign_certificate(&t->sign_cert, pkey) != 0) {
169
+ failure = "unsupported private key type";
170
+ failed_path = private_key_path;
171
+ goto done;
172
+ }
173
+ t->sign_cert_initialized = true;
174
+ t->ctx.sign_certificate = &t->sign_cert.super;
175
+
176
+ /* Copies the DER of each certificate into a list quic_tls_ctx_unref
177
+ releases. It fails only on allocation, and frees what it allocated then. */
178
+ if (ptls_openssl_load_certificates(&t->ctx, leaf, chain) != 0) {
179
+ failure_class = rb_eRuntimeError;
180
+ failure = "ptls_openssl_load_certificates failed";
181
+ goto done;
182
+ }
183
+
184
+ done:
185
+ sk_X509_pop_free(chain, X509_free);
186
+ X509_free(leaf);
187
+ EVP_PKEY_free(pkey);
188
+ BIO_free(cert_bio);
189
+ BIO_free(key_bio);
190
+ ERR_clear_error();
191
+
192
+ if (failure == NULL)
193
+ return;
194
+ if (NIL_P(failed_path))
195
+ rb_raise(failure_class, "%s", failure);
196
+ rb_raise(failure_class, "%s: %" PRIsVALUE, failure, failed_path);
197
+ }
198
+
199
+ VALUE
200
+ quic_tls_context_server_new(VALUE certificate_path, VALUE private_key_path,
201
+ VALUE alpn_ary) {
202
+ certificate_path = quic_path_value(certificate_path, "certificate_path");
203
+ private_key_path = quic_path_value(private_key_path, "private_key_path");
204
+ if (NIL_P(certificate_path))
205
+ rb_raise(rb_eArgError, "certificate_path must not be nil");
206
+ if (NIL_P(private_key_path))
207
+ rb_raise(rb_eArgError, "private_key_path must not be nil");
208
+
209
+ /* The object owns the context from the start, so that an exception below
210
+ releases whatever was built so far. */
211
+ VALUE self =
212
+ TypedData_Wrap_Struct(rb_cQUICTLSContext, &quic_tls_context_type, NULL);
213
+ quic_tls_ctx_t *t = ZALLOC(quic_tls_ctx_t);
214
+ t->refcnt = 1;
215
+ DATA_PTR(self) = t;
216
+
217
+ /* Without an ALPN to pick, every handshake would end in
218
+ no_application_protocol. */
219
+ quic_copy_alpn(alpn_ary, &t->alpn, &t->alpn_count, &t->alpn_buf);
220
+ if (t->alpn_count == 0)
221
+ rb_raise(rb_eArgError, "alpn must not be empty for a server");
222
+
223
+ t->ctx = (ptls_context_t){
224
+ .random_bytes = ptls_openssl_random_bytes,
225
+ .get_time = &ptls_get_time,
226
+ .key_exchanges = quic_key_exchanges,
227
+ .cipher_suites = quic_cipher_suites,
228
+ /* No session resumption: encrypt_ticket stays NULL, so no ticket is
229
+ ever issued. Keep this so that PSK-only resumption is refused once
230
+ resumption is added. */
231
+ .require_dhe_on_psk = 1,
232
+ .server_cipher_preference = 1,
233
+ };
234
+ if (ngtcp2_crypto_picotls_configure_server_context(&t->ctx) != 0) {
235
+ rb_raise(rb_eRuntimeError,
236
+ "ngtcp2_crypto_picotls_configure_server_context failed");
237
+ }
238
+ /* verify_certificate stays NULL: no client certificate is requested. */
239
+ quic_tls_ctx_load_certificate_and_key(t, certificate_path, private_key_path);
240
+ t->on_client_hello.cb = quic_tls_on_client_hello_cb;
241
+ t->ctx.on_client_hello = &t->on_client_hello;
242
+ return self;
243
+ }
244
+
245
+ /* QUIC::TLSContext.server(certificate_path:, private_key_path:, alpn:). A
246
+ missing or unknown keyword raises ArgumentError as a Ruby method would. */
247
+ static VALUE quic_tls_context_s_server(int argc, VALUE *argv, VALUE klass) {
248
+ (void)klass;
249
+ VALUE opts = Qnil;
250
+ rb_scan_args(argc, argv, "0:", &opts);
251
+ if (NIL_P(opts))
252
+ opts = rb_hash_new();
253
+
254
+ ID keywords[3];
255
+ VALUE values[3];
256
+ keywords[0] = rb_intern("certificate_path");
257
+ keywords[1] = rb_intern("private_key_path");
258
+ keywords[2] = rb_intern("alpn");
259
+ rb_get_kwargs(opts, keywords, 3, 0, values);
260
+ return quic_tls_context_server_new(values[0], values[1], values[2]);
261
+ }
262
+
263
+ void Init_quic_tls_context(VALUE rb_mQUICArg) {
264
+ rb_cQUICTLSContext =
265
+ rb_define_class_under(rb_mQUICArg, "TLSContext", rb_cObject);
266
+ rb_undef_alloc_func(rb_cQUICTLSContext);
267
+ rb_define_singleton_method(rb_cQUICTLSContext, "server",
268
+ quic_tls_context_s_server, -1);
269
+ }