Commit 938c59fa2c for openssl.org

commit 938c59fa2cfcd4dd019e74dc65514f5fc6dabb20
Author: Neil Horman <nhorman@openssl.org>
Date:   Fri Feb 27 11:56:42 2026 -0500

    Add support for dynamic key sizing in hashtable

    Currently our internal hashtable suffers from a performance issue, as
    discussed here:
    https://github.com/openssl/openssl/pull/30188

    The hashtable requires that keys be defined at build time, and moreover,
    be defined to support the maximum possible key length you might try to
    insert to a given hash table, even if they actual key you are using is
    shorter.

    As a result, that hashtable hash function (typically ossl_fnv1a_hash,
    but any hash function really) receives a buffer that is specified as the
    maximal length of the build-time defined key, which often means hashing
    of many 0 bytes for byte elements in the key that may never have been
    used.  This causes performance problems as we are always hashing the
    maximum number of elements, even if they key is truly only a few bytes
    long.

    Lets give users an opportunity to improve on that.

    Keys are defined to be a struct, so that users can access individual
    field names within the key, but under the covers its all just one
    contiguous uint8_t buf.  We can implement macros that allow users to,
    instead of setting individual field names, just copy needed data into
    the raw buffer, keeping track of how many bytes have been used as we go.

    The result of using these macros is that the hash function, while it
    will receive a buffer that is still maximally sized for that particular
    key, gets a length value that only represents the number of bytes used
    while writing the key value.

    This results in the hash function having to do much less work, giving us
    a significant opportunistic speedup.

    Reviewed-by: Saša NedvÄ›dický <sashan@openssl.org>
    Reviewed-by: Paul Dale <paul.dale@oracle.com>
    Reviewed-by: Nikola Pajkovsky <nikolap@openssl.org>
    MergeDate: Tue Mar  3 13:23:00 2026
    (Merged from https://github.com/openssl/openssl/pull/30211)

diff --git a/include/internal/hashtable.h b/include/internal/hashtable.h
index 92d1254eac..5147aa374e 100644
--- a/include/internal/hashtable.h
+++ b/include/internal/hashtable.h
@@ -24,6 +24,7 @@ typedef struct ht_internal_st HT;
  */
 typedef struct ht_key_header_st {
     size_t keysize;
+    size_t bufsize;
     uint8_t *keybuf;
 } HT_KEY;

@@ -107,11 +108,62 @@ typedef struct ht_config_st {
 /*
  * Initializes a key
  */
-#define HT_INIT_KEY(key)                                                \
-    do {                                                                \
-        memset((key), 0, sizeof(*(key)));                               \
-        (key)->key_header.keysize = (sizeof(*(key)) - sizeof(HT_KEY));  \
-        (key)->key_header.keybuf = (((uint8_t *)key) + sizeof(HT_KEY)); \
+#define HT_INIT_KEY(key)                                                                           \
+    do {                                                                                           \
+        memset((key), 0, sizeof(*(key)));                                                          \
+        (key)->key_header.keysize = (key)->key_header.bufsize = (sizeof(*(key)) - sizeof(HT_KEY)); \
+        (key)->key_header.keybuf = (((uint8_t *)key) + sizeof(HT_KEY));                            \
+    } while (0)
+
+/*
+ * Initializes a key as a raw buffer
+ * This operates identically to HT_INIT_KEY
+ * but it treats the provided key as a raw buffer
+ * and iteratively accounts the running amount of
+ * data copied into the key from the caller.
+ *
+ * This MUST be used with the RAW macros below:
+ * HT_COPY_RAW_KEY
+ * HT_COPY_RAW_KEY_CASE
+ */
+#define HT_INIT_RAW_KEY(key)           \
+    do {                               \
+        HT_INIT_KEY((key));            \
+        (key)->key_header.keysize = 0; \
+    } while (0)
+
+/*
+ * Helper function to copy raw data into a key
+ * This should not be called independently
+ * use the HT_COPY_RAW_KEY macro instead
+ */
+static ossl_inline ossl_unused int ossl_key_raw_copy(HT_KEY *key, const uint8_t *buf, size_t len)
+{
+    if (key->keysize + len > key->bufsize)
+        return 0;
+    memcpy(&key->keybuf[key->keysize], buf, len);
+    key->keysize += len;
+    return 1;
+}
+
+/*
+ * Copy data directly into a key
+ * When initialized with HT_INIT_RAW_KEY, this macro
+ * can be used to copy packed data into a key for hashtable usage
+ * It is advantageous as it limits the amount of data that needs to
+ * be hashed when doing inserts/lookups/deletes, as it tracks how much
+ * key data is actually valid
+ */
+#define HT_COPY_RAW_KEY(key, buf, len) ossl_key_raw_copy(key, buf, len)
+
+/*
+ * Similar to HT_COPY_RAW_KEY but accepts a character buffer, and copies
+ * data while converting case for case insensitive matches
+ */
+#define HT_COPY_RAW_KEY_CASE(key, buf, len)                                         \
+    do {                                                                            \
+        ossl_ht_strcase((key), (char *)&((key)->keybuf[(key)->keysize]), buf, len); \
+        (key)->keysize += len;                                                      \
     } while (0)

 /*
@@ -140,9 +192,9 @@ typedef struct ht_config_st {
  * This is useful for instances in which we want upper and lower case
  * key value to hash to the same entry
  */
-#define HT_SET_KEY_STRING_CASE(key, member, value)                                            \
-    do {                                                                                      \
-        ossl_ht_strcase((key)->keyfields.member, value, sizeof((key)->keyfields.member) - 1); \
+#define HT_SET_KEY_STRING_CASE(key, member, value)                                                  \
+    do {                                                                                            \
+        ossl_ht_strcase(NULL, (key)->keyfields.member, value, sizeof((key)->keyfields.member) - 1); \
     } while (0)

 /*
@@ -159,12 +211,12 @@ typedef struct ht_config_st {
     } while (0)

 /* Same as HT_SET_KEY_STRING_CASE but also takes length of the string. */
-#define HT_SET_KEY_STRING_CASE_N(key, member, value, len)                                         \
-    do {                                                                                          \
-        if ((size_t)len < sizeof((key)->keyfields.member))                                        \
-            ossl_ht_strcase((key)->keyfields.member, value, len);                                 \
-        else                                                                                      \
-            ossl_ht_strcase((key)->keyfields.member, value, sizeof((key)->keyfields.member) - 1); \
+#define HT_SET_KEY_STRING_CASE_N(key, member, value, len)                                               \
+    do {                                                                                                \
+        if ((size_t)len < sizeof((key)->keyfields.member))                                              \
+            ossl_ht_strcase(NULL, (key)->keyfields.member, value, len);                                 \
+        else                                                                                            \
+            ossl_ht_strcase(NULL, (key)->keyfields.member, value, sizeof((key)->keyfields.member) - 1); \
     } while (0)

 /*
@@ -261,7 +313,7 @@ typedef struct ht_config_st {
 /*
  * Helper function to construct case insensitive keys
  */
-static void ossl_unused ossl_ht_strcase(char *tgt, const char *src, int len)
+static ossl_inline ossl_unused void ossl_ht_strcase(HT_KEY *key, char *tgt, const char *src, int len)
 {
     int i;
 #if defined(CHARSET_EBCDIC) && !defined(CHARSET_EBCDIC_TEST)
@@ -273,6 +325,14 @@ static void ossl_unused ossl_ht_strcase(char *tgt, const char *src, int len)
     if (src == NULL)
         return;

+    /*
+     * If we're passed a key, we're doing raw key copies
+     * so check that we don't overflow here, and truncate if
+     * we copy more space than we have available
+     */
+    if (key != NULL && key->keysize + len > key->bufsize)
+        len = (int)(key->bufsize - key->keysize);
+
     for (i = 0; src[i] != '\0' && i < len; i++)
         tgt[i] = case_adjust & src[i];
 }