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