xref: /openssl/demos/http3/ossl-nghttp3.c (revision b6461792)
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 
h3_stream_free(OSSL_DEMO_H3_STREAM * s)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 
h3_stream_hash(const OSSL_DEMO_H3_STREAM * s)45 static unsigned long h3_stream_hash(const OSSL_DEMO_H3_STREAM *s)
46 {
47     return (unsigned long)s->id;
48 }
49 
h3_stream_eq(const OSSL_DEMO_H3_STREAM * a,const OSSL_DEMO_H3_STREAM * b)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 
OSSL_DEMO_H3_STREAM_get_user_data(const OSSL_DEMO_H3_STREAM * s)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 
OSSL_DEMO_H3_CONN_free(OSSL_DEMO_H3_CONN * conn)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 
h3_conn_create_stream(OSSL_DEMO_H3_CONN * conn,int type)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 
h3_conn_accept_stream(OSSL_DEMO_H3_CONN * conn,SSL * qstream)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 
h3_conn_remove_stream(OSSL_DEMO_H3_CONN * conn,OSSL_DEMO_H3_STREAM * s)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 
h3_conn_recv_data(nghttp3_conn * h3conn,int64_t stream_id,const uint8_t * data,size_t datalen,void * conn_user_data,void * stream_user_data)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 
h3_conn_stream_close(nghttp3_conn * h3conn,int64_t stream_id,uint64_t app_error_code,void * conn_user_data,void * stream_user_data)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 
h3_conn_stop_sending(nghttp3_conn * h3conn,int64_t stream_id,uint64_t app_error_code,void * conn_user_data,void * stream_user_data)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 
h3_conn_reset_stream(nghttp3_conn * h3conn,int64_t stream_id,uint64_t app_error_code,void * conn_user_data,void * stream_user_data)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 
h3_conn_deferred_consume(nghttp3_conn * h3conn,int64_t stream_id,size_t consumed,void * conn_user_data,void * stream_user_data)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 
OSSL_DEMO_H3_CONN_new_for_conn(BIO * qconn_bio,const nghttp3_callbacks * callbacks,const nghttp3_settings * settings,void * user_data)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 
OSSL_DEMO_H3_CONN_new_for_addr(SSL_CTX * ctx,const char * addr,const nghttp3_callbacks * callbacks,const nghttp3_settings * settings,void * user_data)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 
OSSL_DEMO_H3_CONN_connect(OSSL_DEMO_H3_CONN * conn)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 
OSSL_DEMO_H3_CONN_get_user_data(const OSSL_DEMO_H3_CONN * conn)445 void *OSSL_DEMO_H3_CONN_get_user_data(const OSSL_DEMO_H3_CONN *conn)
446 {
447     return conn->user_data;
448 }
449 
OSSL_DEMO_H3_CONN_get0_connection(const OSSL_DEMO_H3_CONN * conn)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. */
h3_conn_pump_stream(OSSL_DEMO_H3_STREAM * s,void * conn_)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 
OSSL_DEMO_H3_CONN_handle_events(OSSL_DEMO_H3_CONN * conn)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 
OSSL_DEMO_H3_CONN_submit_request(OSSL_DEMO_H3_CONN * conn,const nghttp3_nv * nva,size_t nvlen,const nghttp3_data_reader * dr,void * user_data)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