aboutsummaryrefslogtreecommitdiff
path: root/crypto/openssl/ssl/quic/quic_sstream.c
diff options
context:
space:
mode:
Diffstat (limited to 'crypto/openssl/ssl/quic/quic_sstream.c')
-rw-r--r--crypto/openssl/ssl/quic/quic_sstream.c424
1 files changed, 424 insertions, 0 deletions
diff --git a/crypto/openssl/ssl/quic/quic_sstream.c b/crypto/openssl/ssl/quic/quic_sstream.c
new file mode 100644
index 000000000000..1f0b5497fce5
--- /dev/null
+++ b/crypto/openssl/ssl/quic/quic_sstream.c
@@ -0,0 +1,424 @@
+/*
+ * Copyright 2022-2023 The OpenSSL Project Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License 2.0 (the "License"). You may not use
+ * this file except in compliance with the License. You can obtain a copy
+ * in the file LICENSE in the source distribution or at
+ * https://www.openssl.org/source/license.html
+ */
+
+#include "internal/quic_stream.h"
+#include "internal/uint_set.h"
+#include "internal/common.h"
+#include "internal/ring_buf.h"
+
+/*
+ * ==================================================================
+ * QUIC Send Stream
+ */
+struct quic_sstream_st {
+ struct ring_buf ring_buf;
+
+ /*
+ * Any logical byte in the stream is in one of these states:
+ *
+ * - NEW: The byte has not yet been transmitted, or has been lost and is
+ * in need of retransmission.
+ *
+ * - IN_FLIGHT: The byte has been transmitted but is awaiting
+ * acknowledgement. We continue to store the data in case we return
+ * to the NEW state.
+ *
+ * - ACKED: The byte has been acknowledged and we can cease storing it.
+ * We do not necessarily cull it immediately, so there may be a delay
+ * between reaching the ACKED state and the buffer space actually being
+ * recycled.
+ *
+ * A logical byte in the stream is
+ *
+ * - in the NEW state if it is in new_set;
+ * - is in the ACKED state if it is in acked_set
+ * (and may or may not have been culled);
+ * - is in the IN_FLIGHT state otherwise.
+ *
+ * Invariant: No logical byte is ever in both new_set and acked_set.
+ */
+ UINT_SET new_set, acked_set;
+
+ /*
+ * The current size of the stream is ring_buf.head_offset. If
+ * have_final_size is true, this is also the final size of the stream.
+ */
+ unsigned int have_final_size : 1;
+ unsigned int sent_final_size : 1;
+ unsigned int acked_final_size : 1;
+ unsigned int cleanse : 1;
+};
+
+static void qss_cull(QUIC_SSTREAM *qss);
+
+QUIC_SSTREAM *ossl_quic_sstream_new(size_t init_buf_size)
+{
+ QUIC_SSTREAM *qss;
+
+ qss = OPENSSL_zalloc(sizeof(QUIC_SSTREAM));
+ if (qss == NULL)
+ return NULL;
+
+ ring_buf_init(&qss->ring_buf);
+ if (!ring_buf_resize(&qss->ring_buf, init_buf_size, 0)) {
+ ring_buf_destroy(&qss->ring_buf, 0);
+ OPENSSL_free(qss);
+ return NULL;
+ }
+
+ ossl_uint_set_init(&qss->new_set);
+ ossl_uint_set_init(&qss->acked_set);
+ return qss;
+}
+
+void ossl_quic_sstream_free(QUIC_SSTREAM *qss)
+{
+ if (qss == NULL)
+ return;
+
+ ossl_uint_set_destroy(&qss->new_set);
+ ossl_uint_set_destroy(&qss->acked_set);
+ ring_buf_destroy(&qss->ring_buf, qss->cleanse);
+ OPENSSL_free(qss);
+}
+
+int ossl_quic_sstream_get_stream_frame(QUIC_SSTREAM *qss,
+ size_t skip,
+ OSSL_QUIC_FRAME_STREAM *hdr,
+ OSSL_QTX_IOVEC *iov,
+ size_t *num_iov)
+{
+ size_t num_iov_ = 0, src_len = 0, total_len = 0, i;
+ uint64_t max_len;
+ const unsigned char *src = NULL;
+ UINT_SET_ITEM *range = ossl_list_uint_set_head(&qss->new_set);
+
+ if (*num_iov < 2)
+ return 0;
+
+ for (i = 0; i < skip && range != NULL; ++i)
+ range = ossl_list_uint_set_next(range);
+
+ if (range == NULL) {
+ if (i < skip)
+ /* Don't return FIN for infinitely increasing skip */
+ return 0;
+
+ /* No new bytes to send, but we might have a FIN */
+ if (!qss->have_final_size || qss->sent_final_size)
+ return 0;
+
+ hdr->offset = qss->ring_buf.head_offset;
+ hdr->len = 0;
+ hdr->is_fin = 1;
+ *num_iov = 0;
+ return 1;
+ }
+
+ /*
+ * We can only send a contiguous range of logical bytes in a single
+ * stream frame, so limit ourselves to the range of the first set entry.
+ *
+ * Set entries never have 'adjacent' entries so we don't have to worry
+ * about them here.
+ */
+ max_len = range->range.end - range->range.start + 1;
+
+ for (i = 0;; ++i) {
+ if (total_len >= max_len)
+ break;
+
+ if (!ring_buf_get_buf_at(&qss->ring_buf,
+ range->range.start + total_len,
+ &src, &src_len))
+ return 0;
+
+ if (src_len == 0)
+ break;
+
+ assert(i < 2);
+
+ if (total_len + src_len > max_len)
+ src_len = (size_t)(max_len - total_len);
+
+ iov[num_iov_].buf = src;
+ iov[num_iov_].buf_len = src_len;
+
+ total_len += src_len;
+ ++num_iov_;
+ }
+
+ hdr->offset = range->range.start;
+ hdr->len = total_len;
+ hdr->is_fin = qss->have_final_size
+ && hdr->offset + hdr->len == qss->ring_buf.head_offset;
+
+ *num_iov = num_iov_;
+ return 1;
+}
+
+int ossl_quic_sstream_has_pending(QUIC_SSTREAM *qss)
+{
+ OSSL_QUIC_FRAME_STREAM shdr;
+ OSSL_QTX_IOVEC iov[2];
+ size_t num_iov = OSSL_NELEM(iov);
+
+ return ossl_quic_sstream_get_stream_frame(qss, 0, &shdr, iov, &num_iov);
+}
+
+uint64_t ossl_quic_sstream_get_cur_size(QUIC_SSTREAM *qss)
+{
+ return qss->ring_buf.head_offset;
+}
+
+int ossl_quic_sstream_mark_transmitted(QUIC_SSTREAM *qss,
+ uint64_t start,
+ uint64_t end)
+{
+ UINT_RANGE r;
+
+ r.start = start;
+ r.end = end;
+
+ if (!ossl_uint_set_remove(&qss->new_set, &r))
+ return 0;
+
+ return 1;
+}
+
+int ossl_quic_sstream_mark_transmitted_fin(QUIC_SSTREAM *qss,
+ uint64_t final_size)
+{
+ /*
+ * We do not really need final_size since we already know the size of the
+ * stream, but this serves as a sanity check.
+ */
+ if (!qss->have_final_size || final_size != qss->ring_buf.head_offset)
+ return 0;
+
+ qss->sent_final_size = 1;
+ return 1;
+}
+
+int ossl_quic_sstream_mark_lost(QUIC_SSTREAM *qss,
+ uint64_t start,
+ uint64_t end)
+{
+ UINT_RANGE r;
+ r.start = start;
+ r.end = end;
+
+ /*
+ * We lost a range of stream data bytes, so reinsert them into the new set,
+ * so that they are returned once more by ossl_quic_sstream_get_stream_frame.
+ */
+ if (!ossl_uint_set_insert(&qss->new_set, &r))
+ return 0;
+
+ return 1;
+}
+
+int ossl_quic_sstream_mark_lost_fin(QUIC_SSTREAM *qss)
+{
+ if (qss->acked_final_size)
+ /* Does not make sense to lose a FIN after it has been ACKed */
+ return 0;
+
+ /* FIN was lost, so we need to transmit it again. */
+ qss->sent_final_size = 0;
+ return 1;
+}
+
+int ossl_quic_sstream_mark_acked(QUIC_SSTREAM *qss,
+ uint64_t start,
+ uint64_t end)
+{
+ UINT_RANGE r;
+ r.start = start;
+ r.end = end;
+
+ if (!ossl_uint_set_insert(&qss->acked_set, &r))
+ return 0;
+
+ qss_cull(qss);
+ return 1;
+}
+
+int ossl_quic_sstream_mark_acked_fin(QUIC_SSTREAM *qss)
+{
+ if (!qss->have_final_size)
+ /* Cannot ack final size before we have a final size */
+ return 0;
+
+ qss->acked_final_size = 1;
+ return 1;
+}
+
+void ossl_quic_sstream_fin(QUIC_SSTREAM *qss)
+{
+ if (qss->have_final_size)
+ return;
+
+ 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,
+ size_t *consumed)
+{
+ size_t l, consumed_ = 0;
+ UINT_RANGE r;
+ struct ring_buf old_ring_buf = qss->ring_buf;
+
+ if (qss->have_final_size) {
+ *consumed = 0;
+ return 0;
+ }
+
+ /*
+ * Note: It is assumed that ossl_quic_sstream_append will be called during a
+ * call to e.g. SSL_write and this function is therefore designed to support
+ * such semantics. In particular, the buffer pointed to by buf is only
+ * assumed to be valid for the duration of this call, therefore we must copy
+ * the data here. We will later copy-and-encrypt the data during packet
+ * encryption, so this is a two-copy design. Supporting a one-copy design in
+ * the future will require applications to use a different kind of API.
+ * Supporting such changes in future will require corresponding enhancements
+ * to this code.
+ */
+ while (buf_len > 0) {
+ l = ring_buf_push(&qss->ring_buf, buf, buf_len);
+ if (l == 0)
+ break;
+
+ buf += l;
+ buf_len -= l;
+ consumed_ += l;
+ }
+
+ if (consumed_ > 0) {
+ r.start = old_ring_buf.head_offset;
+ r.end = r.start + consumed_ - 1;
+ assert(r.end + 1 == qss->ring_buf.head_offset);
+ if (!ossl_uint_set_insert(&qss->new_set, &r)) {
+ qss->ring_buf = old_ring_buf;
+ *consumed = 0;
+ return 0;
+ }
+ }
+
+ *consumed = consumed_;
+ return 1;
+}
+
+static void qss_cull(QUIC_SSTREAM *qss)
+{
+ UINT_SET_ITEM *h = ossl_list_uint_set_head(&qss->acked_set);
+
+ /*
+ * Potentially cull data from our ring buffer. This can happen once data has
+ * been ACKed and we know we are never going to have to transmit it again.
+ *
+ * Since we use a ring buffer design for simplicity, we cannot cull byte n +
+ * k (for k > 0) from the ring buffer until byte n has also been culled.
+ * This means if parts of the stream get acknowledged out of order we might
+ * keep around some data we technically don't need to for a while. The
+ * impact of this is likely to be small and limited to quite a short
+ * duration, and doesn't justify the use of a more complex design.
+ */
+
+ /*
+ * We only need to check the first range entry in the integer set because we
+ * can only cull contiguous areas at the start of the ring buffer anyway.
+ */
+ if (h != NULL)
+ ring_buf_cpop_range(&qss->ring_buf, h->range.start, h->range.end,
+ qss->cleanse);
+}
+
+int ossl_quic_sstream_set_buffer_size(QUIC_SSTREAM *qss, size_t num_bytes)
+{
+ return ring_buf_resize(&qss->ring_buf, num_bytes, qss->cleanse);
+}
+
+size_t ossl_quic_sstream_get_buffer_size(QUIC_SSTREAM *qss)
+{
+ return qss->ring_buf.alloc;
+}
+
+size_t ossl_quic_sstream_get_buffer_used(QUIC_SSTREAM *qss)
+{
+ return ring_buf_used(&qss->ring_buf);
+}
+
+size_t ossl_quic_sstream_get_buffer_avail(QUIC_SSTREAM *qss)
+{
+ return ring_buf_avail(&qss->ring_buf);
+}
+
+int ossl_quic_sstream_is_totally_acked(QUIC_SSTREAM *qss)
+{
+ UINT_RANGE r;
+ uint64_t cur_size;
+
+ if (qss->have_final_size && !qss->acked_final_size)
+ return 0;
+
+ if (ossl_quic_sstream_get_cur_size(qss) == 0)
+ return 1;
+
+ if (ossl_list_uint_set_num(&qss->acked_set) != 1)
+ return 0;
+
+ r = ossl_list_uint_set_head(&qss->acked_set)->range;
+ cur_size = qss->ring_buf.head_offset;
+
+ /*
+ * The invariants of UINT_SET guarantee a single list element if we have a
+ * single contiguous range, which is what we should have if everything has
+ * been acked.
+ */
+ assert(r.end + 1 <= cur_size);
+ return r.start == 0 && r.end + 1 == cur_size;
+}
+
+void ossl_quic_sstream_adjust_iov(size_t len,
+ OSSL_QTX_IOVEC *iov,
+ size_t num_iov)
+{
+ size_t running = 0, i, iovlen;
+
+ for (i = 0, running = 0; i < num_iov; ++i) {
+ iovlen = iov[i].buf_len;
+
+ if (running >= len)
+ iov[i].buf_len = 0;
+ else if (running + iovlen > len)
+ iov[i].buf_len = len - running;
+
+ running += iovlen;
+ }
+}
+
+void ossl_quic_sstream_set_cleanse(QUIC_SSTREAM *qss, int cleanse)
+{
+ qss->cleanse = cleanse;
+}