Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/crypto/openssl/demos/http3/ossl-nghttp3.c
39536 views
1
/*
2
* Copyright 2023-2024 The OpenSSL Project Authors. All Rights Reserved.
3
*
4
* Licensed under the Apache License 2.0 (the "License"). You may not use
5
* this file except in compliance with the License. You can obtain a copy
6
* in the file LICENSE in the source distribution or at
7
* https://www.openssl.org/source/license.html
8
*/
9
#include "ossl-nghttp3.h"
10
#include <openssl/err.h>
11
#include <assert.h>
12
13
#define ARRAY_LEN(x) (sizeof(x)/sizeof((x)[0]))
14
15
enum {
16
OSSL_DEMO_H3_STREAM_TYPE_CTRL_SEND,
17
OSSL_DEMO_H3_STREAM_TYPE_QPACK_ENC_SEND,
18
OSSL_DEMO_H3_STREAM_TYPE_QPACK_DEC_SEND,
19
OSSL_DEMO_H3_STREAM_TYPE_REQ,
20
};
21
22
#define BUF_SIZE 4096
23
24
struct ossl_demo_h3_stream_st {
25
uint64_t id; /* QUIC stream ID */
26
SSL *s; /* QUIC stream SSL object */
27
int done_recv_fin; /* Received FIN */
28
void *user_data;
29
30
uint8_t buf[BUF_SIZE];
31
size_t buf_cur, buf_total;
32
};
33
34
DEFINE_LHASH_OF_EX(OSSL_DEMO_H3_STREAM);
35
36
static void h3_stream_free(OSSL_DEMO_H3_STREAM *s)
37
{
38
if (s == NULL)
39
return;
40
41
SSL_free(s->s);
42
OPENSSL_free(s);
43
}
44
45
static unsigned long h3_stream_hash(const OSSL_DEMO_H3_STREAM *s)
46
{
47
return (unsigned long)s->id;
48
}
49
50
static int h3_stream_eq(const OSSL_DEMO_H3_STREAM *a, const OSSL_DEMO_H3_STREAM *b)
51
{
52
if (a->id < b->id) return -1;
53
if (a->id > b->id) return 1;
54
return 0;
55
}
56
57
void *OSSL_DEMO_H3_STREAM_get_user_data(const OSSL_DEMO_H3_STREAM *s)
58
{
59
return s->user_data;
60
}
61
62
struct ossl_demo_h3_conn_st {
63
/* QUIC connection SSL object */
64
SSL *qconn;
65
/* BIO wrapping QCSO */
66
BIO *qconn_bio;
67
/* HTTP/3 connection object */
68
nghttp3_conn *h3conn;
69
/* map of stream IDs to OSSL_DEMO_H3_STREAMs */
70
LHASH_OF(OSSL_DEMO_H3_STREAM) *streams;
71
/* opaque user data pointer */
72
void *user_data;
73
74
int pump_res;
75
size_t consumed_app_data;
76
77
/* Forwarding callbacks */
78
nghttp3_recv_data recv_data_cb;
79
nghttp3_stream_close stream_close_cb;
80
nghttp3_stop_sending stop_sending_cb;
81
nghttp3_reset_stream reset_stream_cb;
82
nghttp3_deferred_consume deferred_consume_cb;
83
};
84
85
void OSSL_DEMO_H3_CONN_free(OSSL_DEMO_H3_CONN *conn)
86
{
87
if (conn == NULL)
88
return;
89
90
lh_OSSL_DEMO_H3_STREAM_doall(conn->streams, h3_stream_free);
91
92
nghttp3_conn_del(conn->h3conn);
93
BIO_free_all(conn->qconn_bio);
94
lh_OSSL_DEMO_H3_STREAM_free(conn->streams);
95
OPENSSL_free(conn);
96
}
97
98
static OSSL_DEMO_H3_STREAM *h3_conn_create_stream(OSSL_DEMO_H3_CONN *conn, int type)
99
{
100
OSSL_DEMO_H3_STREAM *s;
101
uint64_t flags = SSL_STREAM_FLAG_ADVANCE;
102
103
if ((s = OPENSSL_zalloc(sizeof(OSSL_DEMO_H3_STREAM))) == NULL)
104
return NULL;
105
106
if (type != OSSL_DEMO_H3_STREAM_TYPE_REQ)
107
flags |= SSL_STREAM_FLAG_UNI;
108
109
if ((s->s = SSL_new_stream(conn->qconn, flags)) == NULL) {
110
ERR_raise_data(ERR_LIB_USER, ERR_R_INTERNAL_ERROR,
111
"could not create QUIC stream object");
112
goto err;
113
}
114
115
s->id = SSL_get_stream_id(s->s);
116
lh_OSSL_DEMO_H3_STREAM_insert(conn->streams, s);
117
return s;
118
119
err:
120
OPENSSL_free(s);
121
return NULL;
122
}
123
124
static OSSL_DEMO_H3_STREAM *h3_conn_accept_stream(OSSL_DEMO_H3_CONN *conn, SSL *qstream)
125
{
126
OSSL_DEMO_H3_STREAM *s;
127
128
if ((s = OPENSSL_zalloc(sizeof(OSSL_DEMO_H3_STREAM))) == NULL)
129
return NULL;
130
131
s->id = SSL_get_stream_id(qstream);
132
s->s = qstream;
133
lh_OSSL_DEMO_H3_STREAM_insert(conn->streams, s);
134
return s;
135
}
136
137
static void h3_conn_remove_stream(OSSL_DEMO_H3_CONN *conn, OSSL_DEMO_H3_STREAM *s)
138
{
139
if (s == NULL)
140
return;
141
142
lh_OSSL_DEMO_H3_STREAM_delete(conn->streams, s);
143
h3_stream_free(s);
144
}
145
146
static int h3_conn_recv_data(nghttp3_conn *h3conn, int64_t stream_id,
147
const uint8_t *data, size_t datalen,
148
void *conn_user_data, void *stream_user_data)
149
{
150
OSSL_DEMO_H3_CONN *conn = conn_user_data;
151
152
conn->consumed_app_data += datalen;
153
if (conn->recv_data_cb == NULL)
154
return 0;
155
156
return conn->recv_data_cb(h3conn, stream_id, data, datalen,
157
conn_user_data, stream_user_data);
158
}
159
160
static int h3_conn_stream_close(nghttp3_conn *h3conn, int64_t stream_id,
161
uint64_t app_error_code,
162
void *conn_user_data, void *stream_user_data)
163
{
164
int ret = 0;
165
OSSL_DEMO_H3_CONN *conn = conn_user_data;
166
OSSL_DEMO_H3_STREAM *stream = stream_user_data;
167
168
if (conn->stream_close_cb != NULL)
169
ret = conn->stream_close_cb(h3conn, stream_id, app_error_code,
170
conn_user_data, stream_user_data);
171
172
h3_conn_remove_stream(conn, stream);
173
return ret;
174
}
175
176
static int h3_conn_stop_sending(nghttp3_conn *h3conn, int64_t stream_id,
177
uint64_t app_error_code,
178
void *conn_user_data, void *stream_user_data)
179
{
180
int ret = 0;
181
OSSL_DEMO_H3_CONN *conn = conn_user_data;
182
OSSL_DEMO_H3_STREAM *stream = stream_user_data;
183
184
if (conn->stop_sending_cb != NULL)
185
ret = conn->stop_sending_cb(h3conn, stream_id, app_error_code,
186
conn_user_data, stream_user_data);
187
188
SSL_free(stream->s);
189
stream->s = NULL;
190
return ret;
191
}
192
193
static int h3_conn_reset_stream(nghttp3_conn *h3conn, int64_t stream_id,
194
uint64_t app_error_code,
195
void *conn_user_data, void *stream_user_data)
196
{
197
int ret = 0;
198
OSSL_DEMO_H3_CONN *conn = conn_user_data;
199
OSSL_DEMO_H3_STREAM *stream = stream_user_data;
200
SSL_STREAM_RESET_ARGS args = {0};
201
202
if (conn->reset_stream_cb != NULL)
203
ret = conn->reset_stream_cb(h3conn, stream_id, app_error_code,
204
conn_user_data, stream_user_data);
205
206
if (stream->s != NULL) {
207
args.quic_error_code = app_error_code;
208
209
if (!SSL_stream_reset(stream->s, &args, sizeof(args)))
210
return 1;
211
}
212
213
return ret;
214
}
215
216
static int h3_conn_deferred_consume(nghttp3_conn *h3conn, int64_t stream_id,
217
size_t consumed,
218
void *conn_user_data, void *stream_user_data)
219
{
220
int ret = 0;
221
OSSL_DEMO_H3_CONN *conn = conn_user_data;
222
223
if (conn->deferred_consume_cb != NULL)
224
ret = conn->deferred_consume_cb(h3conn, stream_id, consumed,
225
conn_user_data, stream_user_data);
226
227
conn->consumed_app_data += consumed;
228
return ret;
229
}
230
231
OSSL_DEMO_H3_CONN *OSSL_DEMO_H3_CONN_new_for_conn(BIO *qconn_bio,
232
const nghttp3_callbacks *callbacks,
233
const nghttp3_settings *settings,
234
void *user_data)
235
{
236
int ec;
237
OSSL_DEMO_H3_CONN *conn;
238
OSSL_DEMO_H3_STREAM *s_ctl_send = NULL;
239
OSSL_DEMO_H3_STREAM *s_qpenc_send = NULL;
240
OSSL_DEMO_H3_STREAM *s_qpdec_send = NULL;
241
nghttp3_settings dsettings = {0};
242
nghttp3_callbacks intl_callbacks = {0};
243
static const unsigned char alpn[] = {2, 'h', '3'};
244
245
if (qconn_bio == NULL) {
246
ERR_raise_data(ERR_LIB_USER, ERR_R_PASSED_NULL_PARAMETER,
247
"QUIC connection BIO must be provided");
248
return NULL;
249
}
250
251
if ((conn = OPENSSL_zalloc(sizeof(OSSL_DEMO_H3_CONN))) == NULL)
252
return NULL;
253
254
conn->qconn_bio = qconn_bio;
255
conn->user_data = user_data;
256
257
if (BIO_get_ssl(qconn_bio, &conn->qconn) == 0) {
258
ERR_raise_data(ERR_LIB_USER, ERR_R_PASSED_INVALID_ARGUMENT,
259
"BIO must be an SSL BIO");
260
goto err;
261
}
262
263
/* Create the map of stream IDs to OSSL_DEMO_H3_STREAM structures. */
264
if ((conn->streams = lh_OSSL_DEMO_H3_STREAM_new(h3_stream_hash, h3_stream_eq)) == NULL)
265
goto err;
266
267
/*
268
* If the application has not started connecting yet, helpfully
269
* auto-configure ALPN. If the application wants to initiate the connection
270
* itself, it must take care of this itself.
271
*/
272
if (SSL_in_before(conn->qconn))
273
if (SSL_set_alpn_protos(conn->qconn, alpn, sizeof(alpn))) {
274
/* SSL_set_alpn_protos returns 1 on failure */
275
ERR_raise_data(ERR_LIB_USER, ERR_R_INTERNAL_ERROR,
276
"failed to configure ALPN");
277
goto err;
278
}
279
280
/*
281
* We use the QUIC stack in non-blocking mode so that we can react to
282
* incoming data on different streams, and e.g. incoming streams initiated
283
* by a server, as and when events occur.
284
*/
285
BIO_set_nbio(conn->qconn_bio, 1);
286
287
/*
288
* Disable default stream mode and create all streams explicitly. Each QUIC
289
* stream will be represented by its own QUIC stream SSL object (QSSO). This
290
* also automatically enables us to accept incoming streams (see
291
* SSL_set_incoming_stream_policy(3)).
292
*/
293
if (!SSL_set_default_stream_mode(conn->qconn, SSL_DEFAULT_STREAM_MODE_NONE)) {
294
ERR_raise_data(ERR_LIB_USER, ERR_R_INTERNAL_ERROR,
295
"failed to configure default stream mode");
296
goto err;
297
}
298
299
/*
300
* HTTP/3 requires a couple of unidirectional management streams: a control
301
* stream and some QPACK state management streams for each side of a
302
* connection. These are the instances on our side (with us sending); the
303
* server will also create its own equivalent unidirectional streams on its
304
* side, which we handle subsequently as they come in (see SSL_accept_stream
305
* in the event handling code below).
306
*/
307
if ((s_ctl_send
308
= h3_conn_create_stream(conn, OSSL_DEMO_H3_STREAM_TYPE_CTRL_SEND)) == NULL)
309
goto err;
310
311
if ((s_qpenc_send
312
= h3_conn_create_stream(conn, OSSL_DEMO_H3_STREAM_TYPE_QPACK_ENC_SEND)) == NULL)
313
goto err;
314
315
if ((s_qpdec_send
316
= h3_conn_create_stream(conn, OSSL_DEMO_H3_STREAM_TYPE_QPACK_DEC_SEND)) == NULL)
317
goto err;
318
319
if (settings == NULL) {
320
nghttp3_settings_default(&dsettings);
321
settings = &dsettings;
322
}
323
324
if (callbacks != NULL)
325
intl_callbacks = *callbacks;
326
327
/*
328
* We need to do some of our own processing when many of these events occur,
329
* so we note the original callback functions and forward appropriately.
330
*/
331
conn->recv_data_cb = intl_callbacks.recv_data;
332
conn->stream_close_cb = intl_callbacks.stream_close;
333
conn->stop_sending_cb = intl_callbacks.stop_sending;
334
conn->reset_stream_cb = intl_callbacks.reset_stream;
335
conn->deferred_consume_cb = intl_callbacks.deferred_consume;
336
337
intl_callbacks.recv_data = h3_conn_recv_data;
338
intl_callbacks.stream_close = h3_conn_stream_close;
339
intl_callbacks.stop_sending = h3_conn_stop_sending;
340
intl_callbacks.reset_stream = h3_conn_reset_stream;
341
intl_callbacks.deferred_consume = h3_conn_deferred_consume;
342
343
/* Create the HTTP/3 client state. */
344
ec = nghttp3_conn_client_new(&conn->h3conn, &intl_callbacks, settings,
345
NULL, conn);
346
if (ec < 0) {
347
ERR_raise_data(ERR_LIB_USER, ERR_R_INTERNAL_ERROR,
348
"cannot create nghttp3 connection: %s (%d)",
349
nghttp3_strerror(ec), ec);
350
goto err;
351
}
352
353
/*
354
* Tell the HTTP/3 stack which stream IDs are used for our outgoing control
355
* and QPACK streams. Note that we don't have to tell the HTTP/3 stack what
356
* IDs are used for incoming streams as this is inferred automatically from
357
* the stream type byte which starts every incoming unidirectional stream,
358
* so it will autodetect the correct stream IDs for the incoming control and
359
* QPACK streams initiated by the server.
360
*/
361
ec = nghttp3_conn_bind_control_stream(conn->h3conn, s_ctl_send->id);
362
if (ec < 0) {
363
ERR_raise_data(ERR_LIB_USER, ERR_R_INTERNAL_ERROR,
364
"cannot bind nghttp3 control stream: %s (%d)",
365
nghttp3_strerror(ec), ec);
366
goto err;
367
}
368
369
ec = nghttp3_conn_bind_qpack_streams(conn->h3conn,
370
s_qpenc_send->id,
371
s_qpdec_send->id);
372
if (ec < 0) {
373
ERR_raise_data(ERR_LIB_USER, ERR_R_INTERNAL_ERROR,
374
"cannot bind nghttp3 QPACK streams: %s (%d)",
375
nghttp3_strerror(ec), ec);
376
goto err;
377
}
378
379
return conn;
380
381
err:
382
nghttp3_conn_del(conn->h3conn);
383
h3_stream_free(s_ctl_send);
384
h3_stream_free(s_qpenc_send);
385
h3_stream_free(s_qpdec_send);
386
lh_OSSL_DEMO_H3_STREAM_free(conn->streams);
387
OPENSSL_free(conn);
388
return NULL;
389
}
390
391
OSSL_DEMO_H3_CONN *OSSL_DEMO_H3_CONN_new_for_addr(SSL_CTX *ctx, const char *addr,
392
const nghttp3_callbacks *callbacks,
393
const nghttp3_settings *settings,
394
void *user_data)
395
{
396
BIO *qconn_bio = NULL;
397
SSL *qconn = NULL;
398
OSSL_DEMO_H3_CONN *conn = NULL;
399
const char *bare_hostname;
400
401
/* QUIC connection setup */
402
if ((qconn_bio = BIO_new_ssl_connect(ctx)) == NULL)
403
goto err;
404
405
/* Pass the 'hostname:port' string into the ssl_connect BIO. */
406
if (BIO_set_conn_hostname(qconn_bio, addr) == 0)
407
goto err;
408
409
/*
410
* Get the 'bare' hostname out of the ssl_connect BIO. This is the hostname
411
* without the port.
412
*/
413
bare_hostname = BIO_get_conn_hostname(qconn_bio);
414
if (bare_hostname == NULL)
415
goto err;
416
417
if (BIO_get_ssl(qconn_bio, &qconn) == 0)
418
goto err;
419
420
/* Set the hostname we will validate the X.509 certificate against. */
421
if (SSL_set1_host(qconn, bare_hostname) <= 0)
422
goto err;
423
424
/* Configure SNI */
425
if (!SSL_set_tlsext_host_name(qconn, bare_hostname))
426
goto err;
427
428
conn = OSSL_DEMO_H3_CONN_new_for_conn(qconn_bio, callbacks,
429
settings, user_data);
430
if (conn == NULL)
431
goto err;
432
433
return conn;
434
435
err:
436
BIO_free_all(qconn_bio);
437
return NULL;
438
}
439
440
int OSSL_DEMO_H3_CONN_connect(OSSL_DEMO_H3_CONN *conn)
441
{
442
return SSL_connect(OSSL_DEMO_H3_CONN_get0_connection(conn));
443
}
444
445
void *OSSL_DEMO_H3_CONN_get_user_data(const OSSL_DEMO_H3_CONN *conn)
446
{
447
return conn->user_data;
448
}
449
450
SSL *OSSL_DEMO_H3_CONN_get0_connection(const OSSL_DEMO_H3_CONN *conn)
451
{
452
return conn->qconn;
453
}
454
455
/* Pumps received data to the HTTP/3 stack for a single stream. */
456
static void h3_conn_pump_stream(OSSL_DEMO_H3_STREAM *s, void *conn_)
457
{
458
int ec;
459
OSSL_DEMO_H3_CONN *conn = conn_;
460
size_t num_bytes, consumed;
461
uint64_t aec;
462
463
if (!conn->pump_res)
464
/*
465
* Handling of a previous stream in the iteration over all streams
466
* failed, so just do nothing.
467
*/
468
return;
469
470
for (;;) {
471
if (s->s == NULL /* If we already did STOP_SENDING, ignore this stream. */
472
/* If this is a write-only stream, there is no read data to check. */
473
|| SSL_get_stream_read_state(s->s) == SSL_STREAM_STATE_WRONG_DIR
474
/*
475
* If we already got a FIN for this stream, there is nothing more to
476
* do for it.
477
*/
478
|| s->done_recv_fin)
479
break;
480
481
/*
482
* Pump data from OpenSSL QUIC to the HTTP/3 stack by calling SSL_peek
483
* to get received data and passing it to nghttp3 using
484
* nghttp3_conn_read_stream. Note that this function is confusingly
485
* named and inputs data to the HTTP/3 stack.
486
*/
487
if (s->buf_cur == s->buf_total) {
488
/* Need more data. */
489
ec = SSL_read_ex(s->s, s->buf, sizeof(s->buf), &num_bytes);
490
if (ec <= 0) {
491
num_bytes = 0;
492
if (SSL_get_error(s->s, ec) == SSL_ERROR_ZERO_RETURN) {
493
/* Stream concluded normally. Pass FIN to HTTP/3 stack. */
494
ec = nghttp3_conn_read_stream(conn->h3conn, s->id, NULL, 0,
495
/*fin=*/1);
496
if (ec < 0) {
497
ERR_raise_data(ERR_LIB_USER, ERR_R_INTERNAL_ERROR,
498
"cannot pass FIN to nghttp3: %s (%d)",
499
nghttp3_strerror(ec), ec);
500
goto err;
501
}
502
503
s->done_recv_fin = 1;
504
} else if (SSL_get_stream_read_state(s->s)
505
== SSL_STREAM_STATE_RESET_REMOTE) {
506
/* Stream was reset by peer. */
507
if (!SSL_get_stream_read_error_code(s->s, &aec))
508
goto err;
509
510
ec = nghttp3_conn_close_stream(conn->h3conn, s->id, aec);
511
if (ec < 0) {
512
ERR_raise_data(ERR_LIB_USER, ERR_R_INTERNAL_ERROR,
513
"cannot mark stream as reset: %s (%d)",
514
nghttp3_strerror(ec), ec);
515
goto err;
516
}
517
518
s->done_recv_fin = 1;
519
} else {
520
/* Other error. */
521
goto err;
522
}
523
}
524
525
s->buf_cur = 0;
526
s->buf_total = num_bytes;
527
}
528
529
if (s->buf_cur == s->buf_total)
530
break;
531
532
/*
533
* This function is confusingly named as it is is named from nghttp3's
534
* 'perspective'; it is used to pass data *into* the HTTP/3 stack which
535
* has been received from the network.
536
*/
537
assert(conn->consumed_app_data == 0);
538
ec = nghttp3_conn_read_stream(conn->h3conn, s->id, s->buf + s->buf_cur,
539
s->buf_total - s->buf_cur, /*fin=*/0);
540
if (ec < 0) {
541
ERR_raise_data(ERR_LIB_USER, ERR_R_INTERNAL_ERROR,
542
"nghttp3 failed to process incoming data: %s (%d)",
543
nghttp3_strerror(ec), ec);
544
goto err;
545
}
546
547
/*
548
* read_stream reports the data it consumes from us in two different
549
* ways; the non-application data is returned as a number of bytes 'ec'
550
* above, but the number of bytes of application data has to be recorded
551
* by our callback. We sum the two to determine the total number of
552
* bytes which nghttp3 consumed.
553
*/
554
consumed = ec + conn->consumed_app_data;
555
assert(consumed <= s->buf_total - s->buf_cur);
556
s->buf_cur += consumed;
557
conn->consumed_app_data = 0;
558
}
559
560
return;
561
err:
562
conn->pump_res = 0;
563
}
564
565
int OSSL_DEMO_H3_CONN_handle_events(OSSL_DEMO_H3_CONN *conn)
566
{
567
int ec, fin;
568
size_t i, num_vecs, written, total_written, total_len;
569
int64_t stream_id;
570
uint64_t flags;
571
nghttp3_vec vecs[8] = {0};
572
OSSL_DEMO_H3_STREAM key, *s;
573
SSL *snew;
574
575
if (conn == NULL)
576
return 0;
577
578
/*
579
* We handle events by doing three things:
580
*
581
* 1. Handle new incoming streams
582
* 2. Pump outgoing data from the HTTP/3 stack to the QUIC engine
583
* 3. Pump incoming data from the QUIC engine to the HTTP/3 stack
584
*/
585
586
/* 1. Check for new incoming streams */
587
for (;;) {
588
if ((snew = SSL_accept_stream(conn->qconn, SSL_ACCEPT_STREAM_NO_BLOCK)) == NULL)
589
break;
590
591
/*
592
* Each new incoming stream gets wrapped into an OSSL_DEMO_H3_STREAM object and
593
* added into our stream ID map.
594
*/
595
if (h3_conn_accept_stream(conn, snew) == NULL) {
596
SSL_free(snew);
597
return 0;
598
}
599
}
600
601
/* 2. Pump outgoing data from HTTP/3 engine to QUIC. */
602
for (;;) {
603
/*
604
* Get a number of send vectors from the HTTP/3 engine.
605
*
606
* Note that this function is confusingly named as it is named from
607
* nghttp3's 'perspective': this outputs pointers to data which nghttp3
608
* wants to *write* to the network.
609
*/
610
ec = nghttp3_conn_writev_stream(conn->h3conn, &stream_id, &fin,
611
vecs, ARRAY_LEN(vecs));
612
if (ec < 0)
613
return 0;
614
if (ec == 0)
615
break;
616
617
/*
618
* we let SSL_write_ex2(3) to conclude the stream for us (send FIN)
619
* after all data are written.
620
*/
621
flags = (fin == 0) ? 0 : SSL_WRITE_FLAG_CONCLUDE;
622
623
/* For each of the vectors returned, pass it to OpenSSL QUIC. */
624
key.id = stream_id;
625
if ((s = lh_OSSL_DEMO_H3_STREAM_retrieve(conn->streams, &key)) == NULL) {
626
ERR_raise_data(ERR_LIB_USER, ERR_R_INTERNAL_ERROR,
627
"no stream for ID %zd", stream_id);
628
return 0;
629
}
630
631
num_vecs = ec;
632
total_len = nghttp3_vec_len(vecs, num_vecs);
633
total_written = 0;
634
for (i = 0; i < num_vecs; ++i) {
635
if (vecs[i].len == 0)
636
continue;
637
638
if (s->s == NULL) {
639
/* Already did STOP_SENDING and threw away stream, ignore */
640
written = vecs[i].len;
641
} else if (!SSL_write_ex2(s->s, vecs[i].base, vecs[i].len, flags, &written)) {
642
if (SSL_get_error(s->s, 0) == SSL_ERROR_WANT_WRITE) {
643
/*
644
* We have filled our send buffer so tell nghttp3 to stop
645
* generating more data; we have to do this explicitly.
646
*/
647
written = 0;
648
nghttp3_conn_block_stream(conn->h3conn, stream_id);
649
} else {
650
ERR_raise_data(ERR_LIB_USER, ERR_R_INTERNAL_ERROR,
651
"writing HTTP/3 data to network failed");
652
return 0;
653
}
654
} else {
655
/*
656
* Tell nghttp3 it can resume generating more data in case we
657
* previously called block_stream.
658
*/
659
nghttp3_conn_unblock_stream(conn->h3conn, stream_id);
660
}
661
662
total_written += written;
663
if (written > 0) {
664
/*
665
* Tell nghttp3 we have consumed the data it output when we
666
* called writev_stream, otherwise subsequent calls to
667
* writev_stream will output the same data.
668
*/
669
ec = nghttp3_conn_add_write_offset(conn->h3conn, stream_id, written);
670
if (ec < 0)
671
return 0;
672
673
/*
674
* Tell nghttp3 it can free the buffered data because we will
675
* not need it again. In our case we can always do this right
676
* away because we copy the data into our QUIC send buffers
677
* rather than simply storing a reference to it.
678
*/
679
ec = nghttp3_conn_add_ack_offset(conn->h3conn, stream_id, written);
680
if (ec < 0)
681
return 0;
682
}
683
}
684
685
if (fin && total_written == total_len) {
686
687
if (total_len == 0) {
688
/*
689
* As a special case, if nghttp3 requested to write a
690
* zero-length stream with a FIN, we have to tell it we did this
691
* by calling add_write_offset(0).
692
*/
693
ec = nghttp3_conn_add_write_offset(conn->h3conn, stream_id, 0);
694
if (ec < 0)
695
return 0;
696
}
697
}
698
}
699
700
/* 3. Pump incoming data from QUIC to HTTP/3 engine. */
701
conn->pump_res = 1; /* cleared in below call if an error occurs */
702
lh_OSSL_DEMO_H3_STREAM_doall_arg(conn->streams, h3_conn_pump_stream, conn);
703
if (!conn->pump_res)
704
return 0;
705
706
return 1;
707
}
708
709
int OSSL_DEMO_H3_CONN_submit_request(OSSL_DEMO_H3_CONN *conn,
710
const nghttp3_nv *nva, size_t nvlen,
711
const nghttp3_data_reader *dr,
712
void *user_data)
713
{
714
int ec;
715
OSSL_DEMO_H3_STREAM *s_req = NULL;
716
717
if (conn == NULL) {
718
ERR_raise_data(ERR_LIB_USER, ERR_R_PASSED_NULL_PARAMETER,
719
"connection must be specified");
720
return 0;
721
}
722
723
/* Each HTTP/3 request is represented by a stream. */
724
if ((s_req = h3_conn_create_stream(conn, OSSL_DEMO_H3_STREAM_TYPE_REQ)) == NULL)
725
goto err;
726
727
s_req->user_data = user_data;
728
729
ec = nghttp3_conn_submit_request(conn->h3conn, s_req->id, nva, nvlen,
730
dr, s_req);
731
if (ec < 0) {
732
ERR_raise_data(ERR_LIB_USER, ERR_R_INTERNAL_ERROR,
733
"cannot submit HTTP/3 request: %s (%d)",
734
nghttp3_strerror(ec), ec);
735
goto err;
736
}
737
738
return 1;
739
740
err:
741
h3_conn_remove_stream(conn, s_req);
742
return 0;
743
}
744
745