Commit 57e9054380 for openssl.org

commit 57e9054380a4f24df57a0bb8104750f6226a3601
Author: Alexandr Nedvedicky <sashan@openssl.org>
Date:   Mon Aug 10 12:34:32 2026 +0200

    Add explicit tests to check some typical stream reassembly situation

    the list of tests is as follows:s
       - partial overlap between newly arriving chunk and existing range
         (there is intersection between existing range and newly arriving chunk)
       - full overlap: existing range is subset of newly arriving stream chunk
       - newly arriving chunk is superset of two existing ranges.
       - reassembly of 1-byte chunks which are kept in stream buffers
       - we need to also test handling of short chunks in directo storage

    Co-authored-by: Jakub Zelenka <bukka@php.net>

    Assisted-by: Claude:claude-fable-5
    Reviewed-by: Tomas Mraz <tomas@openssl.foundation>
    Reviewed-by: Mounir Idrassi <mounir.idrassi@idrix.fr>
    Merge-date: Sat Sep 26 11:35:34 2026
    Merged-from: https://github.com/openssl/openssl/pull/32038

diff --git a/test/quic_stream_test.c b/test/quic_stream_test.c
index 1d75f0611b..8a10b64db6 100644
--- a/test/quic_stream_test.c
+++ b/test/quic_stream_test.c
@@ -407,6 +407,128 @@ static int test_single_copy_read(QUIC_RSTREAM *qrs,
     return 1;
 }

+static const unsigned char simple_data[] = "Hello world! And thank you for all the fish!";
+
+/*
+ * Walks the basic sequential contract of the receive stream: a gap at
+ * the head reads zero bytes without signalling the end of stream even
+ * when FIN is already known, duplicate FIN frames with a matching final
+ * size are accepted, available() reports the final size together with
+ * FIN, partial reads flip the FIN flag exactly with the last byte and
+ * reading at the end of stream stays successful. Runs once with the
+ * plain read and once with the single copy record path.
+ */
+static int test_rstream_simple(int idx)
+{
+    QUIC_RSTREAM *rstream = NULL;
+    QUIC_CHANNEL *ch = NULL;
+    QUIC_RSTREAM_QPARM *rsqp = NULL;
+    OSSL_QRX_PKT *pkt[8] = { NULL };
+    int ret = 0;
+    unsigned char buf[sizeof(simple_data)];
+    size_t readbytes = 0, avail = 0, i;
+    int fin = 0;
+    int use_sc = (idx & 1) != 0;
+    int (*read_fn)(QUIC_RSTREAM *, unsigned char *, size_t, size_t *,
+        int *)
+        = use_sc ? test_single_copy_read
+                 : ossl_quic_rstream_read;
+
+    /* every frame arrives in a packet, as it does in production */
+    for (i = 0; i < OSSL_NELEM(pkt); ++i)
+        if (!TEST_ptr(pkt[i] = pkt_test_new(1200)))
+            goto err;
+
+    if (!TEST_ptr(ch = OPENSSL_zalloc(sizeof(QUIC_CHANNEL)))
+        || !TEST_ptr(rsqp = ossl_quic_rstream_qparm_new(ch))
+        || !TEST_ptr(rstream = ossl_quic_rstream_new(NULL, NULL, rsqp)))
+        goto err;
+
+    if (!TEST_true(ossl_quic_rstream_queue_data(rstream, pkt[0], 5,
+            simple_data + 5, 10, 0))
+        || !TEST_true(ossl_quic_rstream_queue_data(rstream, pkt[1],
+            sizeof(simple_data) - 1,
+            simple_data + sizeof(simple_data) - 1,
+            1, 1))
+        || !TEST_true(ossl_quic_rstream_peek(rstream, buf, sizeof(buf),
+            &readbytes, &fin))
+        || !TEST_false(fin)
+        || !TEST_size_t_eq(readbytes, 0)
+        || !TEST_true(ossl_quic_rstream_queue_data(rstream, pkt[2],
+            sizeof(simple_data) - 10,
+            simple_data + sizeof(simple_data) - 10,
+            10, 1))
+        || !TEST_true(ossl_quic_rstream_queue_data(rstream, pkt[3], 0,
+            simple_data, 1, 0))
+        || !TEST_true(ossl_quic_rstream_peek(rstream, buf, sizeof(buf),
+            &readbytes, &fin))
+        || !TEST_false(fin)
+        || !TEST_size_t_eq(readbytes, 1)
+        || !TEST_mem_eq(buf, 1, simple_data, 1)
+        || !TEST_true(ossl_quic_rstream_queue_data(rstream, pkt[4],
+            0, simple_data,
+            10, 0))
+        || !TEST_true(ossl_quic_rstream_queue_data(rstream, pkt[5],
+            sizeof(simple_data),
+            NULL,
+            0, 1))
+        || !TEST_true(ossl_quic_rstream_peek(rstream, buf, sizeof(buf),
+            &readbytes, &fin))
+        || !TEST_false(fin)
+        || !TEST_size_t_eq(readbytes, 15)
+        || !TEST_mem_eq(buf, 15, simple_data, 15)
+        || !TEST_true(ossl_quic_rstream_queue_data(rstream, pkt[6],
+            15,
+            simple_data + 15,
+            sizeof(simple_data) - 15, 1))
+        || !TEST_true(ossl_quic_rstream_available(rstream, &avail, &fin))
+        || !TEST_true(fin)
+        || !TEST_size_t_eq(avail, sizeof(simple_data))
+        || !TEST_true(read_fn(rstream, buf, 2, &readbytes, &fin))
+        || !TEST_false(fin)
+        || !TEST_size_t_eq(readbytes, 2)
+        || !TEST_mem_eq(buf, 2, simple_data, 2)
+        || !TEST_true(read_fn(rstream, buf + 2, 12, &readbytes, &fin))
+        || !TEST_false(fin)
+        || !TEST_size_t_eq(readbytes, 12)
+        || !TEST_mem_eq(buf + 2, 12, simple_data + 2, 12)
+        || !TEST_true(ossl_quic_rstream_queue_data(rstream, pkt[7],
+            sizeof(simple_data),
+            NULL,
+            0, 1))
+        || !TEST_true(read_fn(rstream, buf + 14, 5, &readbytes, &fin))
+        || !TEST_false(fin)
+        || !TEST_size_t_eq(readbytes, 5)
+        || !TEST_mem_eq(buf, 14 + 5, simple_data, 14 + 5)
+        || !TEST_true(read_fn(rstream, buf + 14 + 5, sizeof(buf) - 14 - 5,
+            &readbytes, &fin))
+        || !TEST_true(fin)
+        || !TEST_size_t_eq(readbytes, sizeof(buf) - 14 - 5)
+        || !TEST_mem_eq(buf, sizeof(buf), simple_data, sizeof(simple_data))
+        || !TEST_true(read_fn(rstream, buf, sizeof(buf), &readbytes, &fin))
+        || !TEST_true(fin)
+        || !TEST_size_t_eq(readbytes, 0))
+        goto err;
+
+    if (!TEST_int_eq(ch->protocol_error, 0))
+        goto err;
+
+    ret = 1;
+
+err:
+    ossl_quic_rstream_free(rstream);
+    /* All the references held by the stream must have been released */
+    for (i = 0; i < OSSL_NELEM(pkt); ++i) {
+        if (pkt[i] != NULL
+            && !TEST_size_t_eq(pkt_test_refcount(pkt[i]), 1))
+            ret = 0;
+        pkt_test_free(pkt[i]);
+    }
+    ossl_quic_rstream_qparm_destroy(rsqp);
+    ossl_quic_channel_free(ch);
+    return ret;
+}
+
 static int test_rstream_random(int idx)
 {
     unsigned char *bulk_data = NULL;
@@ -608,7 +730,7 @@ static int test_rstream_pkt(void)
      */
     if (!TEST_true(ossl_quic_rstream_queue_data(rstream, pkt_c, 0,
             pdata, 15, 0))
-        || !TEST_size_t_eq(pkt_test_refcount(pkt_a), 2)
+        || !TEST_size_t_eq(pkt_test_refcount(pkt_a), 3)
         || !TEST_size_t_eq(pkt_test_refcount(pkt_c), 2))
         goto err;

@@ -627,10 +749,7 @@ static int test_rstream_pkt(void)
     if (!TEST_true(ossl_quic_rstream_queue_data(rstream, pkt_b, 15,
             pdata + 15, 5, 0))
         || !TEST_size_t_eq(pkt_test_refcount(pkt_b), 2)
-        || !TEST_true(ossl_quic_rstream_resize_rbuf(rstream, sizeof(pdata)))
-        || !TEST_true(ossl_quic_rstream_move_to_rbuf(rstream))
-        || !TEST_size_t_eq(pkt_test_refcount(pkt_a), 1)
-        || !TEST_size_t_eq(pkt_test_refcount(pkt_b), 1))
+        || !TEST_size_t_eq(pkt_test_refcount(pkt_a), 2))
         goto err;

     /* The moved data is still readable from the ring buffer */
@@ -1195,10 +1314,11 @@ static int test_rstream_chunk_partial_overlap(void)

     ok = 1;
 err:
+    ossl_quic_rstream_free(rstream);
+
     for (i = 0; i < OSSL_NELEM(tsc_buf); i++)
         pkt_test_free(pkt[i]);

-    ossl_quic_rstream_free(rstream);
     ossl_quic_rstream_qparm_destroy(rsqp);
     ossl_quic_channel_free(ch);

@@ -1339,10 +1459,11 @@ static int test_rstream_chunk_full_overlap(void)

     ok = 1;
 err:
+    ossl_quic_rstream_free(rstream);
+
     for (i = 0; i < OSSL_NELEM(tsc_buf); i++)
         pkt_test_free(pkt[i]);

-    ossl_quic_rstream_free(rstream);
     ossl_quic_rstream_qparm_destroy(rsqp);
     ossl_quic_channel_free(ch);

@@ -1538,10 +1659,11 @@ static int test_rstream_range_overlap(void)

     ok = 1;
 err:
+    ossl_quic_rstream_free(rstream);
+
     for (i = 0; i < OSSL_NELEM(tsc_buf); i++)
         pkt_test_free(pkt[i]);

-    ossl_quic_rstream_free(rstream);
     ossl_quic_rstream_qparm_destroy(rsqp);
     ossl_quic_channel_free(ch);

@@ -1692,10 +1814,11 @@ static int test_rstream_prepend_byte_chunks(void)

     ok = 1;
 err:
+    ossl_quic_rstream_free(rstream);
+
     for (i = 0; i < OSSL_NELEM(tsc_buf); i++)
         pkt_test_free(pkt[i]);

-    ossl_quic_rstream_free(rstream);
     ossl_quic_rstream_qparm_destroy(rsqp);
     ossl_quic_channel_free(ch);

@@ -1846,10 +1969,11 @@ static int test_rstream_append_byte_chunks(void)

     ok = 1;
 err:
+    ossl_quic_rstream_free(rstream);
+
     for (i = 0; i < OSSL_NELEM(tsc_buf); i++)
         pkt_test_free(pkt[i]);

-    ossl_quic_rstream_free(rstream);
     ossl_quic_rstream_qparm_destroy(rsqp);
     ossl_quic_channel_free(ch);

@@ -2026,10 +2150,11 @@ static int test_rstream_mix_chunks(void)

     ok = 1;
 err:
+    ossl_quic_rstream_free(rstream);
+
     for (i = 0; i < OSSL_NELEM(tsc_buf); i++)
         pkt_test_free(pkt[i]);

-    ossl_quic_rstream_free(rstream);
     ossl_quic_rstream_qparm_destroy(rsqp);
     ossl_quic_channel_free(ch);

@@ -2153,12 +2278,6 @@ static int test_final_size_violation_fin_first(void)

     ok = 1;
 err:
-    /*
-     * the data from rstream has not been consumed,
-     * references to packets are still retained there.
-     * therefore we need to free rstream before freeing
-     * pkckets.
-     */
     ossl_quic_rstream_free(rstream);

     for (i = 0; i < OSSL_NELEM(tsc_buf); i++)
@@ -2272,12 +2391,6 @@ static int test_final_size_violation_data_first(void)

     ok = 1;
 err:
-    /*
-     * the data from rstream has not been consumed,
-     * references to packets are still retained there.
-     * therefore we need to free rstream before freeing
-     * pkckets.
-     */
     ossl_quic_rstream_free(rstream);

     for (i = 0; i < OSSL_NELEM(tsc_buf); i++)
@@ -2293,6 +2406,7 @@ int setup_tests(void)
 {
     ADD_TEST(test_sstream_simple);
     ADD_ALL_TESTS(test_sstream_bulk, 100);
+    ADD_ALL_TESTS(test_rstream_simple, 2);
     ADD_ALL_TESTS(test_rstream_random, 100);
     ADD_TEST(test_rstream_pkt);
     ADD_TEST(test_rstream_pkt_overhead);