Commit 8ba9860b55 for openssl.org

commit 8ba9860b555e35f6bdfaf80eeaa087e65fdcca11
Author: Nikola Pajkovsky <nikolap@openssl.org>
Date:   Mon Oct 26 08:50:37 2026 +0100

    perf(ssl): make DTLS queue size queries constant time

    Maintain the previously unused queue count on successful insertion
    and nonempty pop, and return it directly from pqueue_size().  Use size_t
    to match the size API and avoid signed counter overflow.

    Add a test that covers heap and embedded queues, insertion positions,
    duplicate rejection, empty pops, ordered draining, transfers, and reuse.

    Assisted-by: pi:gpt-6.1-sol
    Reviewed-by: Saša NedvÄ›dický <sashan@openssl.org>
    Reviewed-by: Eugene Syromiatnikov <esyr@openssl.org>
    Reviewed-by: Norbert Pocs <norbertp@openssl.org>
    Merge-date: Thu Nov  5 17:54:37 2026
    Merged-from: https://github.com/openssl/openssl/pull/33397

diff --git a/ssl/pqueue.c b/ssl/pqueue.c
index e413d8c5ae..301accfece 100644
--- a/ssl/pqueue.c
+++ b/ssl/pqueue.c
@@ -64,6 +64,8 @@ pitem *pqueue_insert(pqueue *pq, pitem *item)
     if (pq->items == NULL) {
         item->next = NULL;
         pq->items = item;
+        pq->count++;
+
         return item;
     }

@@ -80,6 +82,7 @@ pitem *pqueue_insert(pqueue *pq, pitem *item)
                 pq->items = item;
             else
                 curr->next = item;
+            pq->count++;

             return item;
         }
@@ -90,6 +93,7 @@ pitem *pqueue_insert(pqueue *pq, pitem *item)

     item->next = NULL;
     curr->next = item;
+    pq->count++;

     return item;
 }
@@ -103,8 +107,10 @@ pitem *pqueue_pop(pqueue *pq)
 {
     pitem *item = pq->items;

-    if (pq->items != NULL)
+    if (pq->items != NULL) {
         pq->items = pq->items->next;
+        pq->count--;
+    }

     return item;
 }
@@ -164,12 +170,5 @@ pitem *pqueue_next(piterator *item)

 size_t pqueue_size(pqueue *pq)
 {
-    pitem *item = pq->items;
-    size_t count = 0;
-
-    while (item != NULL) {
-        count++;
-        item = item->next;
-    }
-    return count;
+    return pq->count;
 }
diff --git a/ssl/ssl_local.h b/ssl/ssl_local.h
index 274b3cd521..28bd4a6df5 100644
--- a/ssl/ssl_local.h
+++ b/ssl/ssl_local.h
@@ -2140,7 +2140,7 @@ typedef struct pitem_st pitem;

 struct pqueue_st {
     pitem *items;
-    int count;
+    size_t count;
 };

 struct pitem_st {
diff --git a/test/dtls13_internal_test.c b/test/dtls13_internal_test.c
index ce5d152b7f..718d30c39a 100644
--- a/test/dtls13_internal_test.c
+++ b/test/dtls13_internal_test.c
@@ -21,6 +21,89 @@
 static char *cert = NULL;
 static char *privkey = NULL;

+static int test_pqueue_size(int idx)
+{
+    const uint64_t priorities[] = { 3, 1, 2, 4 };
+    const size_t order[] = { 1, 2, 0, 3 };
+    pqueue embedded = { 0 }, other = { 0 };
+    pqueue *queue = idx == 0 ? pqueue_new() : &embedded;
+    pitem *items[OSSL_NELEM(priorities)] = { NULL };
+    pitem *duplicate = NULL;
+    size_t i;
+    int ret = 0;
+
+    /* An empty queue: size 0 and popping from it yields nothing. */
+    if (!TEST_ptr(queue)
+        || !TEST_size_t_eq(pqueue_size(queue), 0)
+        || !TEST_ptr_null(pqueue_pop(queue))
+        || !TEST_size_t_eq(pqueue_size(queue), 0))
+        goto end;
+
+    /*
+     * Inserting items succeeds, returns the item back, and grows the size
+     * by exactly one each time (regardless of the priority's value).
+     */
+    for (i = 0; i < OSSL_NELEM(priorities); i++) {
+        if (!TEST_ptr(items[i] = pitem_new_u64(priorities[i], NULL))
+            || !TEST_ptr_eq(pqueue_insert(queue, items[i]), items[i])
+            || !TEST_size_t_eq(pqueue_size(queue), i + 1))
+            goto end;
+    }
+
+    /*
+     * A second item with an already-present priority is rejected:
+     * pqueue_insert() returns NULL and the size is unchanged.
+     */
+    if (!TEST_ptr(duplicate = pitem_new_u64(2, NULL))
+        || !TEST_ptr_null(pqueue_insert(queue, duplicate))
+        || !TEST_size_t_eq(pqueue_size(queue), OSSL_NELEM(priorities)))
+        goto end;
+
+    /*
+     * Peek then pop, in ascending priority order. peek() must not remove
+     * anything (size drops only after pop()), and popping must shrink the
+     * size by exactly one.
+     */
+    for (i = 0; i < OSSL_NELEM(order); i++) {
+        if (!TEST_ptr_eq(pqueue_peek(queue), items[order[i]])
+            || !TEST_size_t_eq(pqueue_size(queue), OSSL_NELEM(order) - i)
+            || !TEST_ptr_eq(pqueue_pop(queue), items[order[i]])
+            || !TEST_size_t_eq(pqueue_size(queue), OSSL_NELEM(order) - i - 1))
+            goto end;
+
+        /* Move a popped item into an empty queue, then reuse that queue. */
+        if (!TEST_ptr_eq(pqueue_insert(&other, items[order[i]]), items[order[i]])
+            || !TEST_size_t_eq(pqueue_size(&other), 1)
+            || !TEST_size_t_eq(pqueue_size(queue), OSSL_NELEM(order) - i - 1)
+            || !TEST_ptr_eq(pqueue_pop(&other), items[order[i]])
+            || !TEST_size_t_eq(pqueue_size(&other), 0))
+            goto end;
+    }
+
+    /* Popping an already-empty queue is still a harmless NULL/no-op. */
+    for (i = 0; i < 2; i++) {
+        if (!TEST_ptr_null(pqueue_pop(queue))
+            || !TEST_size_t_eq(pqueue_size(queue), 0))
+            goto end;
+    }
+
+    /* The now-empty queue is still usable: insert and pop once more. */
+    if (!TEST_ptr_eq(pqueue_insert(queue, items[0]), items[0])
+        || !TEST_size_t_eq(pqueue_size(queue), 1)
+        || !TEST_ptr_eq(pqueue_pop(queue), items[0])
+        || !TEST_size_t_eq(pqueue_size(queue), 0))
+        goto end;
+
+    ret = 1;
+end:
+    for (i = 0; i < OSSL_NELEM(items); i++)
+        pitem_free(items[i]);
+    pitem_free(duplicate);
+    if (idx == 0)
+        pqueue_free(queue);
+    return ret;
+}
+
 static const char *cipher_names[] = {
     "aes-128-ecb",
     "aes-256-ecb",
@@ -3564,6 +3647,7 @@ int setup_tests(void)
         || !TEST_ptr(privkey = test_get_argument(1)))
         return 0;

+    ADD_ALL_TESTS(test_pqueue_size, 2);
     ADD_ALL_TESTS(test_dtls_crypt_sequence_number, OSSL_NELEM(cipher_names));
     ADD_ALL_TESTS(test_seq_num_reconstruction, OSSL_NELEM(seq_num_tests));
 #ifndef OPENSSL_NO_DTLS1_3