Browse Source

QUIC Front End I/O API: Add support for signalling and detecting end-of-stream

Reviewed-by: Tomas Mraz <tomas@openssl.org>
Reviewed-by: Matt Caswell <matt@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/19897)
Hugo Landau 1 year ago
parent
commit
a9979965bf

+ 6 - 0
include/internal/quic_stream.h

@@ -247,6 +247,12 @@ int ossl_quic_sstream_append(QUIC_SSTREAM *qss,
  */
 void ossl_quic_sstream_fin(QUIC_SSTREAM *qss);
 
+/*
+ * If the stream has had ossl_quic_sstream_fin() called, returns 1 and writes
+ * the final size to *final_size. Otherwise, returns 0.
+ */
+int ossl_quic_sstream_get_final_size(QUIC_SSTREAM *qss, uint64_t *final_size);
+
 /*
  * Resizes the internal ring buffer. All stream data is preserved safely.
  *

+ 13 - 1
include/internal/quic_tserver.h

@@ -52,13 +52,20 @@ int ossl_quic_tserver_is_connected(QUIC_TSERVER *srv);
  * *bytes_read and returns 1 on success. If no bytes are available, 0 is written
  * to *bytes_read and 1 is returned (this is considered a success case).
  *
- * Returns 0 if connection is not currently active.
+ * Returns 0 if connection is not currently active. If the receive part of
+ * the stream has reached the end of stream condition, returns 0; call
+ * ossl_quic_tserver_has_read_ended() to identify this condition.
  */
 int ossl_quic_tserver_read(QUIC_TSERVER *srv,
                            unsigned char *buf,
                            size_t buf_len,
                            size_t *bytes_read);
 
+/*
+ * Returns 1 if the read part of the stream has ended normally.
+ */
+int ossl_quic_tserver_has_read_ended(QUIC_TSERVER *srv);
+
 /*
  * Attempts to write to stream 0. Writes the number of bytes consumed to
  * *bytes_written and returns 1 on success. If there is no space currently
@@ -75,6 +82,11 @@ int ossl_quic_tserver_write(QUIC_TSERVER *srv,
                             size_t buf_len,
                             size_t *bytes_written);
 
+/*
+ * Signals normal end of the stream.
+ */
+int ossl_quic_tserver_conclude(QUIC_TSERVER *srv);
+
 # endif
 
 #endif

+ 2 - 0
include/openssl/ssl.h.in

@@ -2270,6 +2270,8 @@ __owur int SSL_shutdown_ex(SSL *ssl, uint64_t flags,
                            const SSL_SHUTDOWN_EX_ARGS *args,
                            size_t args_len);
 
+__owur int SSL_stream_conclude(SSL *ssl, uint64_t flags);
+
 # ifndef OPENSSL_NO_DEPRECATED_1_1_0
 #  define SSL_cache_hit(s) SSL_session_reused(s)
 # endif

+ 36 - 10
ssl/quic/quic_impl.c

@@ -756,6 +756,8 @@ int ossl_quic_accept(SSL *s)
  *   (BIO/)SSL_read             => ossl_quic_read
  *   (BIO/)SSL_write            => ossl_quic_write
  *         SSL_pending          => ossl_quic_pending
+ *         SSL_stream_conclude  => ossl_quic_conn_stream_conclude
+ *
  */
 
 /* SSL_get_error */
@@ -1053,6 +1055,10 @@ static int quic_read_actual(QUIC_CONNECTION *qc,
 {
     int is_fin = 0;
 
+    /* If the receive part of the stream is over, issue EOF. */
+    if (stream->recv_fin_retired)
+        return QUIC_RAISE_NORMAL_ERROR(qc, SSL_ERROR_ZERO_RETURN);
+
     if (stream->rstream == NULL)
         return QUIC_RAISE_NON_NORMAL_ERROR(qc, ERR_R_INTERNAL_ERROR, NULL);
 
@@ -1099,9 +1105,11 @@ static int quic_read_again(void *arg)
 {
     struct quic_read_again_args *args = arg;
 
-    if (!ossl_quic_channel_is_active(args->qc->ch))
+    if (!ossl_quic_channel_is_active(args->qc->ch)) {
         /* If connection is torn down due to an error while blocking, stop. */
+        QUIC_RAISE_NON_NORMAL_ERROR(args->qc, SSL_R_PROTOCOL_IS_SHUTDOWN, NULL);
         return -1;
+    }
 
     if (!quic_read_actual(args->qc, args->stream,
                           args->buf, args->len, args->bytes_read,
@@ -1129,15 +1137,15 @@ static int quic_read(SSL *s, void *buf, size_t len, size_t *bytes_read, int peek
     if (qc->ch != NULL && ossl_quic_channel_is_term_any(qc->ch))
         return QUIC_RAISE_NON_NORMAL_ERROR(qc, SSL_R_PROTOCOL_IS_SHUTDOWN, NULL);
 
-    /* If we haven't finished the handshake, try to advance it.*/
+    /* If we haven't finished the handshake, try to advance it. */
     if (ossl_quic_do_handshake(qc) < 1)
-        return 0;
+        return 0; /* ossl_quic_do_handshake raised error here */
 
     if (qc->stream0 == NULL)
         return QUIC_RAISE_NON_NORMAL_ERROR(qc, ERR_R_INTERNAL_ERROR, NULL);
 
     if (!quic_read_actual(qc, qc->stream0, buf, len, bytes_read, peek))
-        return 0;
+        return 0; /* quic_read_actual raised error here */
 
     if (*bytes_read > 0) {
         /*
@@ -1160,12 +1168,10 @@ static int quic_read(SSL *s, void *buf, size_t len, size_t *bytes_read, int peek
         args.peek       = peek;
 
         res = block_until_pred(qc, quic_read_again, &args, 0);
-        if (res <= 0) {
-            if (!ossl_quic_channel_is_active(qc->ch))
-                return QUIC_RAISE_NON_NORMAL_ERROR(qc, SSL_R_PROTOCOL_IS_SHUTDOWN, NULL);
-            else
-                return QUIC_RAISE_NON_NORMAL_ERROR(qc, ERR_R_INTERNAL_ERROR, NULL);
-        }
+        if (res == 0)
+            return QUIC_RAISE_NON_NORMAL_ERROR(qc, ERR_R_INTERNAL_ERROR, NULL);
+        else if (res < 0)
+            return 0; /* quic_read_again raised error here */
 
         return 1;
     } else {
@@ -1207,6 +1213,26 @@ size_t ossl_quic_pending(const SSL *s)
     return avail;
 }
 
+/*
+ * SSL_stream_conclude
+ * -------------------
+ */
+int ossl_quic_conn_stream_conclude(QUIC_CONNECTION *qc)
+{
+    QUIC_STREAM *qs = qc->stream0;
+
+    if (qs == NULL || qs->sstream == NULL)
+        return 0;
+
+    if (!ossl_quic_channel_is_active(qc->ch)
+        || ossl_quic_sstream_get_final_size(qs->sstream, NULL))
+        return 1;
+
+    ossl_quic_sstream_fin(qs->sstream);
+    quic_post_write(qc, 1, 1);
+    return 1;
+}
+
 /*
  * QUIC Front-End I/O API: SSL_CTX Management
  * ==========================================

+ 11 - 0
ssl/quic/quic_sstream.c

@@ -442,6 +442,17 @@ void ossl_quic_sstream_fin(QUIC_SSTREAM *qss)
     qss->have_final_size = 1;
 }
 
+int ossl_quic_sstream_get_final_size(QUIC_SSTREAM *qss, uint64_t *final_size)
+{
+    if (!qss->have_final_size)
+        return 0;
+
+    if (final_size != NULL)
+        *final_size = qss->ring_buf.head_offset;
+
+    return 1;
+}
+
 int ossl_quic_sstream_append(QUIC_SSTREAM *qss,
                              const unsigned char *buf,
                              size_t buf_len,

+ 24 - 1
ssl/quic/quic_tserver.c

@@ -142,11 +142,14 @@ int ossl_quic_tserver_read(QUIC_TSERVER *srv,
                            size_t buf_len,
                            size_t *bytes_read)
 {
-    int is_fin = 0; /* TODO(QUIC): Handle FIN in API */
+    int is_fin = 0;
 
     if (!ossl_quic_channel_is_active(srv->ch))
         return 0;
 
+    if (srv->stream0->recv_fin_retired)
+        return 0;
+
     if (!ossl_quic_rstream_read(srv->stream0->rstream, buf, buf_len,
                                 bytes_read, &is_fin))
         return 0;
@@ -177,6 +180,11 @@ int ossl_quic_tserver_read(QUIC_TSERVER *srv,
     return 1;
 }
 
+int ossl_quic_tserver_has_read_ended(QUIC_TSERVER *srv)
+{
+    return srv->stream0->recv_fin_retired;
+}
+
 int ossl_quic_tserver_write(QUIC_TSERVER *srv,
                             const unsigned char *buf,
                             size_t buf_len,
@@ -201,3 +209,18 @@ int ossl_quic_tserver_write(QUIC_TSERVER *srv,
     ossl_quic_tserver_tick(srv);
     return 1;
 }
+
+int ossl_quic_tserver_conclude(QUIC_TSERVER *srv)
+{
+    if (!ossl_quic_channel_is_active(srv->ch))
+        return 0;
+
+    if (!ossl_quic_sstream_get_final_size(srv->stream0->sstream, NULL)) {
+        ossl_quic_sstream_fin(srv->stream0->sstream);
+        ossl_quic_stream_map_update_state(ossl_quic_channel_get_qsm(srv->ch),
+                                          srv->stream0);
+    }
+
+    ossl_quic_tserver_tick(srv);
+    return 1;
+}

+ 14 - 0
ssl/ssl_lib.c

@@ -7197,3 +7197,17 @@ int SSL_shutdown_ex(SSL *ssl, uint64_t flags,
     return SSL_shutdown(ssl);
 #endif
 }
+
+int SSL_stream_conclude(SSL *ssl, uint64_t flags)
+{
+#ifndef OPENSSL_NO_QUIC
+    QUIC_CONNECTION *qc = QUIC_CONNECTION_FROM_SSL(ssl);
+
+    if (qc == NULL)
+        return 0;
+
+    return ossl_quic_conn_stream_conclude(qc);
+#else
+    return 0;
+#endif
+}

+ 1 - 0
util/libssl.num

@@ -542,3 +542,4 @@ SSL_set_initial_peer_addr               ?	3_2_0	EXIST::FUNCTION:
 SSL_net_read_desired                    ?	3_2_0	EXIST::FUNCTION:
 SSL_net_write_desired                   ?	3_2_0	EXIST::FUNCTION:
 SSL_shutdown_ex                         ?	3_2_0	EXIST::FUNCTION:
+SSL_stream_conclude                     ?	3_2_0	EXIST::FUNCTION: