ssl: implement an event queue
authorPauli <pauli@openssl.org>
Tue, 10 May 2022 02:05:11 +0000 (12:05 +1000)
committerPauli <pauli@openssl.org>
Wed, 6 Jul 2022 00:33:11 +0000 (10:33 +1000)
Reviewed-by: Tomas Mraz <tomas@openssl.org>
Reviewed-by: Matt Caswell <matt@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/18345)

include/internal/event_queue.h [new file with mode: 0644]
ssl/build.info
ssl/event_queue.c [new file with mode: 0644]

diff --git a/include/internal/event_queue.h b/include/internal/event_queue.h
new file mode 100644 (file)
index 0000000..bc77e5a
--- /dev/null
@@ -0,0 +1,163 @@
+/*
+ * Copyright 2022 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
+ */
+
+#ifndef OSSL_INTERNAL_EVENT_QUEUE_H
+# define OSSL_INTERNAL_EVENT_QUEUE_H
+# pragma once
+
+# include "internal/priority_queue.h"
+# include "internal/time.h"
+
+/*
+ * Opaque type holding an event.
+ */
+typedef struct ossl_event_st OSSL_EVENT;
+
+DEFINE_PRIORITY_QUEUE_OF(OSSL_EVENT);
+
+/*
+ * Public type representing an event queue, the underlying structure being
+ * opaque.
+ */
+typedef struct ossl_event_queue_st OSSL_EVENT_QUEUE;
+
+/*
+ * Public type representing a event queue entry.
+ * It is (internally) public so that it can be embedded into other structures,
+ * it should otherwise be treated as opaque.
+ */
+struct ossl_event_st {
+    uint32_t type;              /* What type of event this is               */
+    uint32_t priority;          /* What priority this event has             */
+    OSSL_TIME when;             /* When the event is scheduled to happen    */
+    void *ctx;                  /* User argument passed to call backs       */
+    void *payload;              /* Event specific data of unknown kind      */
+    size_t payload_size;        /* Length (in bytes) of event specific data */
+
+    /* These fields are for internal use only */
+    PRIORITY_QUEUE_OF(OSSL_EVENT) *queue;    /* Queue containing this event */
+    size_t ref;                 /* ID for this event                        */
+    unsigned int flag_dynamic : 1;  /* Malloced or not?                     */
+};
+
+/*
+ * Utility function to populate an event structure and add it to the queue
+ */
+int ossl_event_queue_add(OSSL_EVENT_QUEUE *queue, OSSL_EVENT *event,
+                         uint32_t type, uint32_t priority,
+                         OSSL_TIME when, void *ctx,
+                         void *payload, size_t payload_size);
+
+/*
+ * Utility functions to extract event fields
+ */
+static ossl_unused ossl_inline
+uint32_t ossl_event_get_type(const OSSL_EVENT *event)
+{
+    return event->type;
+}
+
+static ossl_unused ossl_inline
+uint32_t ossl_event_get_priority(const OSSL_EVENT *event)
+{
+    return event->priority;
+}
+
+static ossl_unused ossl_inline
+OSSL_TIME ossl_event_get_when(const OSSL_EVENT *event)
+{
+    return event->when;
+}
+
+static ossl_unused ossl_inline
+void *ossl_event_get0_ctx(const OSSL_EVENT *event)
+{
+    return event->ctx;
+}
+
+static ossl_unused ossl_inline
+void *ossl_event_get0_payload(const OSSL_EVENT *event, size_t *length)
+{
+    if (length != NULL)
+        *length = event->payload_size;
+    return event->payload;
+}
+
+/*
+ * Create and free a queue.
+ */
+OSSL_EVENT_QUEUE *ossl_event_queue_new(void);
+void ossl_event_queue_free(OSSL_EVENT_QUEUE *queue);
+
+/*
+ * Schedule a new event into an event queue.
+ *
+ * The event parameters are taken from the function arguments.
+ *
+ * The function reutrns NULL on failure and the added event on success.
+ */
+OSSL_EVENT *ossl_event_queue_add_new(OSSL_EVENT_QUEUE *queue, 
+                                     uint32_t type, uint32_t priority,
+                                     OSSL_TIME when, void *ctx,
+                                     void *payload, size_t payload_size)
+;
+
+/*
+ * Schedule an event into an event queue.
+ *
+ * The event parameters are taken from the function arguments.
+ *
+ * The function reutrns 0 on failure and 1 on success.
+ */
+int ossl_event_queue_add(OSSL_EVENT_QUEUE *queue, OSSL_EVENT *event,
+                         uint32_t type, uint32_t priority,
+                         OSSL_TIME when, void *ctx,
+                         void *payload, size_t payload_size);
+
+/*
+ * Delete an event from the queue.
+ * This will cause the early deletion function to be called if it is non-NULL.
+ * A pointer to the event structure is returned.
+ */
+int ossl_event_queue_remove(OSSL_EVENT_QUEUE *queue, OSSL_EVENT *event);
+
+/*
+ * Free a dynamic event.
+ * Is a NOP for a static event.
+ */
+void ossl_event_free(OSSL_EVENT *event);
+
+/*
+ * Return the time until the next event for the specified event, if the event's
+ * time is past, zero is returned.  Once activated, the event reference becomes
+ * invalid and this function becomes undefined.
+ */
+OSSL_TIME ossl_event_time_until(const OSSL_EVENT *event);
+
+/*
+ * Return the time until the next event in the queue.
+ * If the next event is in the past, zero is returned.
+ */
+OSSL_TIME ossl_event_queue_time_until_next(const OSSL_EVENT_QUEUE *queue);
+
+/*
+ * Postpone an event to trigger at the specified time.
+ * If the event has triggered, this function's behaviour is undefined.
+ */
+int ossl_event_queue_postpone_until(OSSL_EVENT_QUEUE *queue,
+                                    OSSL_EVENT *event,
+                                    OSSL_TIME when);
+
+/*
+ * Return the next event to process.
+ */
+int ossl_event_queue_get1_next_event(OSSL_EVENT_QUEUE *queue,
+                                     OSSL_EVENT **event);
+
+#endif
index 6b6692de2fce2dfbf21190c04ae1b68a58827986..2ecf5ccd225a414d2ea82a2e656673a6f2f38890 100644 (file)
@@ -43,7 +43,7 @@ IF[{- !$disabled{'deprecated-3.0'} -}]
 ENDIF
 
 IF[{- !$disabled{quic} -}]
-  SOURCE[../libssl]=priority_queue.c
+  SOURCE[../libssl]=priority_queue.c event_queue.c
 ENDIF
 
 DEFINE[../libssl]=$AESDEF
diff --git a/ssl/event_queue.c b/ssl/event_queue.c
new file mode 100644 (file)
index 0000000..f828d52
--- /dev/null
@@ -0,0 +1,194 @@
+/*
+ * Copyright 2022 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 <stdlib.h>
+#include "internal/event_queue.h"
+#include "crypto/sparse_array.h"
+#include "ssl_local.h"
+
+struct ossl_event_queue_st {
+    PRIORITY_QUEUE_OF(OSSL_EVENT) *timed_events;
+    PRIORITY_QUEUE_OF(OSSL_EVENT) *now_events;
+};
+
+static int event_compare_times(const OSSL_EVENT *a, const OSSL_EVENT *b)
+{
+    return ossl_time_compare(a->when, b->when);
+}
+
+static int event_compare_priority(const OSSL_EVENT *a, const OSSL_EVENT *b)
+{
+    if (a->priority > b->priority)
+        return -1;
+    if (a->priority < b->priority)
+        return 1;
+    return 0;
+}
+
+OSSL_EVENT_QUEUE *ossl_event_queue_new(void)
+{
+    OSSL_EVENT_QUEUE *r = OPENSSL_malloc(sizeof(*r));
+
+    if (r != NULL) {
+        r->timed_events = ossl_pqueue_OSSL_EVENT_new(&event_compare_times);
+        r->now_events = ossl_pqueue_OSSL_EVENT_new(&event_compare_priority);
+        if (r->timed_events == NULL || r->now_events == NULL) {
+            ossl_event_queue_free(r);
+            return NULL;
+        }
+    }
+    return r;
+}
+
+void ossl_event_free(OSSL_EVENT *event)
+{
+    if (event != NULL) {
+        if (event->flag_dynamic)
+            OPENSSL_free(event);
+        else
+            event->queue = NULL;
+    }
+}
+
+static void event_queue_free(PRIORITY_QUEUE_OF(OSSL_EVENT) *queue)
+{
+    OSSL_EVENT *e;
+
+    if (queue != NULL) {
+        while ((e = ossl_pqueue_OSSL_EVENT_pop(queue)) != NULL)
+            ossl_event_free(e);
+        ossl_pqueue_OSSL_EVENT_free(queue);
+    }
+}
+
+void ossl_event_queue_free(OSSL_EVENT_QUEUE *queue)
+{
+    if (queue != NULL) {
+        event_queue_free(queue->now_events);
+        event_queue_free(queue->timed_events);
+        OPENSSL_free(queue);
+    }
+}
+
+static ossl_inline
+int event_queue_add(OSSL_EVENT_QUEUE *queue, OSSL_EVENT *event)
+{
+    PRIORITY_QUEUE_OF(OSSL_EVENT) *pq =
+            ossl_time_compare(event->when, ossl_time_now()) <= 0
+            ? queue->now_events
+            : queue->timed_events;
+
+    if (ossl_pqueue_OSSL_EVENT_push(pq, event, &event->ref)) {
+        event->queue = pq;
+        return 1;
+    }
+    return 0;
+}
+
+static ossl_inline
+void ossl_event_set(OSSL_EVENT *event, uint32_t type, uint32_t priority,
+                    OSSL_TIME when, void *ctx,
+                    void *payload, size_t payload_size)
+{
+    event->type = type;
+    event->priority = priority;
+    event->when = when;
+    event->ctx = ctx;
+    event->payload = payload;
+    event->payload_size = payload_size;
+}
+
+OSSL_EVENT *ossl_event_queue_add_new(OSSL_EVENT_QUEUE *queue, 
+                                     uint32_t type, uint32_t priority,
+                                     OSSL_TIME when, void *ctx,
+                                     void *payload, size_t payload_size)
+{
+    OSSL_EVENT *e = OPENSSL_malloc(sizeof(*e));
+
+    if (e == NULL || queue == NULL)
+        return NULL;
+    ossl_event_set(e, type, priority, when, ctx, payload, payload_size);
+    e->flag_dynamic = 1;
+    if (event_queue_add(queue, e))
+        return e;
+    OPENSSL_free(e);
+    return NULL;
+}
+
+int ossl_event_queue_add(OSSL_EVENT_QUEUE *queue, OSSL_EVENT *event,
+                         uint32_t type, uint32_t priority,
+                         OSSL_TIME when, void *ctx,
+                         void *payload, size_t payload_size)
+{
+    if (event == NULL || queue == NULL)
+        return 0;
+    ossl_event_set(event, type, priority, when, ctx, payload, payload_size);
+    event->flag_dynamic = 0;
+    return event_queue_add(queue, event);
+}
+
+int ossl_event_queue_remove(OSSL_EVENT_QUEUE *queue, OSSL_EVENT *event)
+{
+    if (event != NULL && event->queue != NULL) {
+        ossl_pqueue_OSSL_EVENT_remove(event->queue, event->ref);
+        event->queue = NULL;
+    }
+    return 1;
+}
+
+OSSL_TIME ossl_event_time_until(const OSSL_EVENT *event)
+{
+    if (event == NULL)
+        return OSSL_TIME_INFINITY;
+    return ossl_time_subtract(event->when, ossl_time_now());
+}
+
+OSSL_TIME ossl_event_queue_time_until_next(const OSSL_EVENT_QUEUE *queue)
+{
+    if (queue == NULL)
+        return OSSL_TIME_INFINITY;
+    if (ossl_pqueue_OSSL_EVENT_num(queue->now_events) > 0)
+        return OSSL_TIME_IMMEDIATE;
+    return ossl_event_time_until(ossl_pqueue_OSSL_EVENT_peek(queue->timed_events));
+}
+
+int ossl_event_queue_postpone_until(OSSL_EVENT_QUEUE *queue,
+                                    OSSL_EVENT *event,
+                                    OSSL_TIME when)
+{
+    if (ossl_event_queue_remove(queue, event)) {
+        event->when = when;
+        return event_queue_add(queue, event);
+    }
+    return 0;
+}
+
+int ossl_event_queue_get1_next_event(OSSL_EVENT_QUEUE *queue,
+                                     OSSL_EVENT **event)
+{
+    OSSL_TIME now = ossl_time_now();
+    OSSL_EVENT *e;
+
+    /* Check for expired timer based events and convert them to now events */
+    while ((e = ossl_pqueue_OSSL_EVENT_peek(queue->timed_events)) != NULL
+           && ossl_time_compare(e->when, now) <= 0) {
+        e = ossl_pqueue_OSSL_EVENT_pop(queue->timed_events);
+        if (!ossl_pqueue_OSSL_EVENT_push(queue->now_events, e, &e->ref)) {
+            e->queue = NULL;
+            return 0;
+        }
+    }
+
+    /*
+     * Get next event from the now queue.
+     * The pop returns NULL when there is none.
+     */
+    *event = ossl_pqueue_OSSL_EVENT_pop(queue->now_events);
+    return 1;
+}