[ovs-dev] [PATCH] sha1: Use implementation from openssl if available.

Ilya Maximets i.maximets at ovn.org
Tue Aug 24 20:57:41 UTC 2021


Implementation of SHA1 in OpenSSL library is much faster and optimized
for all available CPU architectures and instruction sets.  OVS should
use it instead of internal implementation if possible.

Depending on compiler options OpenSSL's version finishes our sha1
unit tests from 3 to 12 times faster.  Performance of OpenSSL's
version is constant, but OVS's implementation highly depends on
compiler.  Interestingly, default build with  '-g -O2' works faster
than optimized '-march=native -Ofast'.

Tests with ovsdb-server on big databases shows ~5-10% improvement of
the time needed for database compaction (sha1 is only a part of this
operation), depending on compiler options.

We still need internal implementation, because OpenSSL can be not
available on some platforms.  Tests enhanced to check both versions
of API.

Signed-off-by: Ilya Maximets <i.maximets at ovn.org>
---
 lib/sha1.c        | 143 ++++++++++++++++++++++++++++++++++------------
 lib/sha1.h        |  27 +++++++--
 tests/library.at  |   2 +-
 tests/test-sha1.c |  60 +++++++++++++------
 4 files changed, 172 insertions(+), 60 deletions(-)

diff --git a/lib/sha1.c b/lib/sha1.c
index 87360d9cd..d90780a6d 100644
--- a/lib/sha1.c
+++ b/lib/sha1.c
@@ -31,12 +31,114 @@
 
 #include <config.h>
 #include "sha1.h"
+
+#ifdef HAVE_OPENSSL
+#include <openssl/sha.h>
+#endif
+
 #include <ctype.h>
 #include <string.h>
 #include "compiler.h"
+#include "openvswitch/vlog.h"
 #include "util.h"
 
-/* a bit faster & bigger, if defined */
+VLOG_DEFINE_THIS_MODULE(sha1);
+
+/*
+ * Initialize the SHA digest.
+ * context: The SHA context to initialize
+ */
+void
+sha1_init(struct sha1_ctx *sha_info)
+{
+#ifdef HAVE_OPENSSL
+    if (!SHA1_Init(&sha_info->ctx)) {
+        VLOG_FATAL("SHA1_Init failed.");
+    }
+#else
+    ovs_sha1_init(sha_info);
+#endif
+}
+
+/*
+ * Update the SHA digest.
+ * context: The SHA1 context to update.
+ * input: The buffer to add to the SHA digest.
+ * inputLen: The length of the input buffer.
+ */
+void
+sha1_update(struct sha1_ctx *ctx, const void *buffer_, uint32_t count)
+{
+#ifdef HAVE_OPENSSL
+    if (!SHA1_Update(&ctx->ctx, buffer_, count)) {
+        VLOG_FATAL("SHA1_Update failed.");
+    }
+#else
+    ovs_sha1_update(ctx, buffer_, count);
+#endif
+}
+
+/*
+ * Finish computing the SHA digest.
+ * digest: the output buffer in which to store the digest.
+ * context: The context to finalize.
+ */
+void
+sha1_final(struct sha1_ctx *ctx, uint8_t digest[SHA1_DIGEST_SIZE])
+{
+#ifdef HAVE_OPENSSL
+    if (!SHA1_Final(digest, &ctx->ctx)) {
+        VLOG_FATAL("SHA1_Final failed.");
+    }
+#else
+    ovs_sha1_final(ctx, digest);
+#endif
+}
+
+/* Computes the hash of 'n' bytes in 'data' into 'digest'. */
+void
+sha1_bytes(const void *data, uint32_t n, uint8_t digest[SHA1_DIGEST_SIZE])
+{
+#ifdef HAVE_OPENSSL
+    SHA1(data, n, digest);
+#else
+    ovs_sha1_bytes(data, n, digest);
+#endif
+}
+
+void
+sha1_to_hex(const uint8_t digest[SHA1_DIGEST_SIZE],
+            char hex[SHA1_HEX_DIGEST_LEN + 1])
+{
+    int i;
+
+    for (i = 0; i < SHA1_DIGEST_SIZE; i++) {
+        *hex++ = "0123456789abcdef"[digest[i] >> 4];
+        *hex++ = "0123456789abcdef"[digest[i] & 15];
+    }
+    *hex = '\0';
+}
+
+bool
+sha1_from_hex(uint8_t digest[SHA1_DIGEST_SIZE], const char *hex)
+{
+    int i;
+
+    for (i = 0; i < SHA1_DIGEST_SIZE; i++) {
+        bool ok;
+
+        digest[i] = hexits_value(hex, 2, &ok);
+        if (!ok) {
+            return false;
+        }
+        hex += 2;
+    }
+    return true;
+}
+
+/* Generic implementation for a case where OpenSSL is not available. */
+
+/* A bit faster & bigger, if defined */
 #define UNROLL_LOOPS
 
 /* SHA f()-functions */
@@ -178,7 +280,7 @@ maybe_byte_reverse(uint32_t *buffer OVS_UNUSED, int count OVS_UNUSED)
  * context: The SHA context to initialize
  */
 void
-sha1_init(struct sha1_ctx *sha_info)
+ovs_sha1_init(struct sha1_ctx *sha_info)
 {
     sha_info->digest[0] = 0x67452301L;
     sha_info->digest[1] = 0xefcdab89L;
@@ -197,7 +299,7 @@ sha1_init(struct sha1_ctx *sha_info)
  * inputLen: The length of the input buffer.
  */
 void
-sha1_update(struct sha1_ctx *ctx, const void *buffer_, uint32_t count)
+ovs_sha1_update(struct sha1_ctx *ctx, const void *buffer_, uint32_t count)
 {
     const uint8_t *buffer = buffer_;
     unsigned int i;
@@ -240,7 +342,7 @@ sha1_update(struct sha1_ctx *ctx, const void *buffer_, uint32_t count)
  * context: The context to finalize.
  */
 void
-sha1_final(struct sha1_ctx *ctx, uint8_t digest[SHA1_DIGEST_SIZE])
+ovs_sha1_final(struct sha1_ctx *ctx, uint8_t digest[SHA1_DIGEST_SIZE])
 {
     int count, i, j;
     uint32_t lo_bit_count, hi_bit_count, k;
@@ -274,7 +376,7 @@ sha1_final(struct sha1_ctx *ctx, uint8_t digest[SHA1_DIGEST_SIZE])
 
 /* Computes the hash of 'n' bytes in 'data' into 'digest'. */
 void
-sha1_bytes(const void *data, uint32_t n, uint8_t digest[SHA1_DIGEST_SIZE])
+ovs_sha1_bytes(const void *data, uint32_t n, uint8_t digest[SHA1_DIGEST_SIZE])
 {
     struct sha1_ctx ctx;
 
@@ -282,34 +384,3 @@ sha1_bytes(const void *data, uint32_t n, uint8_t digest[SHA1_DIGEST_SIZE])
     sha1_update(&ctx, data, n);
     sha1_final(&ctx, digest);
 }
-
-void
-sha1_to_hex(const uint8_t digest[SHA1_DIGEST_SIZE],
-            char hex[SHA1_HEX_DIGEST_LEN + 1])
-{
-    int i;
-
-    for (i = 0; i < SHA1_DIGEST_SIZE; i++) {
-        *hex++ = "0123456789abcdef"[digest[i] >> 4];
-        *hex++ = "0123456789abcdef"[digest[i] & 15];
-    }
-    *hex = '\0';
-}
-
-bool
-sha1_from_hex(uint8_t digest[SHA1_DIGEST_SIZE], const char *hex)
-{
-    int i;
-
-    for (i = 0; i < SHA1_DIGEST_SIZE; i++) {
-        bool ok;
-
-        digest[i] = hexits_value(hex, 2, &ok);
-        if (!ok) {
-            return false;
-        }
-        hex += 2;
-    }
-    return true;
-}
-
diff --git a/lib/sha1.h b/lib/sha1.h
index a635ff768..8f3199a4a 100644
--- a/lib/sha1.h
+++ b/lib/sha1.h
@@ -33,15 +33,26 @@
 #include <stddef.h>
 #include <stdint.h>
 
+#ifdef HAVE_OPENSSL
+#include <openssl/sha.h>
+#endif
+
 #define SHA1_DIGEST_SIZE 20     /* Size of the SHA1 digest. */
 #define SHA1_HEX_DIGEST_LEN 40  /* Length of SHA1 digest as hex in ASCII. */
 
 /* SHA1 context structure. */
 struct sha1_ctx {
-    uint32_t digest[5];          /* Message digest. */
-    uint32_t count_lo, count_hi; /* 64-bit bit counts. */
-    uint32_t data[16];           /* SHA data buffer */
-    int local;                   /* Unprocessed amount in data. */
+    union {
+#ifdef HAVE_OPENSSL
+        SHA_CTX ctx;                     /* OpenSSL context for SHA1. */
+#endif
+        struct {
+            uint32_t digest[5];          /* Message digest. */
+            uint32_t count_lo, count_hi; /* 64-bit bit counts. */
+            uint32_t data[16];           /* SHA data buffer */
+            int local;                   /* Unprocessed amount in data. */
+        };
+    };
 };
 
 void sha1_init(struct sha1_ctx *);
@@ -63,4 +74,12 @@ void sha1_to_hex(const uint8_t digest[SHA1_DIGEST_SIZE],
                  char hex[SHA1_HEX_DIGEST_LEN + 1]);
 bool sha1_from_hex(uint8_t digest[SHA1_DIGEST_SIZE], const char *hex);
 
+/* Generic implementation for the case where OpenSSL is not available.
+ * This API should not be used directly.  Exposed for unit testing. */
+void ovs_sha1_init(struct sha1_ctx *);
+void ovs_sha1_update(struct sha1_ctx *, const void *, uint32_t size);
+void ovs_sha1_final(struct sha1_ctx *, uint8_t digest[SHA1_DIGEST_SIZE]);
+void ovs_sha1_bytes(const void *, uint32_t size,
+                    uint8_t digest[SHA1_DIGEST_SIZE]);
+
 #endif  /* sha1.h */
diff --git a/tests/library.at b/tests/library.at
index 1ed2ad3e9..42fbde418 100644
--- a/tests/library.at
+++ b/tests/library.at
@@ -54,7 +54,7 @@ AT_CLEANUP
 
 AT_SETUP([SHA-1])
 AT_KEYWORDS([sha1])
-AT_CHECK([ovstest test-sha1], [0], [..........
+AT_CHECK([ovstest test-sha1], [0], [....................
 ])
 AT_CLEANUP
 
diff --git a/tests/test-sha1.c b/tests/test-sha1.c
index cc80888a7..f5a310bc9 100644
--- a/tests/test-sha1.c
+++ b/tests/test-sha1.c
@@ -32,6 +32,14 @@ struct test_vector {
     const uint8_t output[20];
 };
 
+struct test_api {
+    void (*sha1_init)(struct sha1_ctx *);
+    void (*sha1_update)(struct sha1_ctx *, const void *, uint32_t size);
+    void (*sha1_final)(struct sha1_ctx *, uint8_t digest[SHA1_DIGEST_SIZE]);
+    void (*sha1_bytes)(const void *, uint32_t size,
+                       uint8_t digest[SHA1_DIGEST_SIZE]);
+};
+
 static const struct test_vector vectors[] = {
     /* FIPS 180-1. */
     {
@@ -92,13 +100,13 @@ static const struct test_vector vectors[] = {
 };
 
 static void
-test_one(const struct test_vector *vec)
+test_one(const struct test_api *api, const struct test_vector *vec)
 {
     uint8_t md[SHA1_DIGEST_SIZE];
     int i;
 
     /* All at once. */
-    sha1_bytes(vec->data, vec->size, md);
+    api->sha1_bytes(vec->data, vec->size, md);
     assert(!memcmp(md, vec->output, SHA1_DIGEST_SIZE));
 
     /* In two pieces. */
@@ -107,10 +115,10 @@ test_one(const struct test_vector *vec)
         int n1 = vec->size - n0;
         struct sha1_ctx sha1;
 
-        sha1_init(&sha1);
-        sha1_update(&sha1, vec->data, n0);
-        sha1_update(&sha1, vec->data + n0, n1);
-        sha1_final(&sha1, md);
+        api->sha1_init(&sha1);
+        api->sha1_update(&sha1, vec->data, n0);
+        api->sha1_update(&sha1, vec->data + n0, n1);
+        api->sha1_final(&sha1, md);
         assert(!memcmp(md, vec->output, SHA1_DIGEST_SIZE));
     }
 
@@ -119,7 +127,7 @@ test_one(const struct test_vector *vec)
 }
 
 static void
-test_big_vector(void)
+test_big_vector(const struct test_api *api)
 {
     enum { SIZE = 1000000 };
     struct test_vector vec = {
@@ -133,12 +141,12 @@ test_big_vector(void)
     for (i = 0; i < SIZE; i++) {
         vec.data[i] = 'a';
     }
-    test_one(&vec);
+    test_one(api, &vec);
     free(vec.data);
 }
 
 static void
-test_huge_vector(void)
+test_huge_vector(const struct test_api *api)
 {
     enum { SIZE = 1000000000 };
     struct test_vector vec = {
@@ -159,13 +167,13 @@ test_huge_vector(void)
         vec.data[i] = 'a';
     }
 
-    sha1_init(&sha1);
+    api->sha1_init(&sha1);
     for (sz = 0; sz < SIZE; sz += chunk) {
         int n = sz + chunk < SIZE ? chunk : SIZE - sz;
 
-        sha1_update(&sha1, vec.data, n);
+        api->sha1_update(&sha1, vec.data, n);
     }
-    sha1_final(&sha1, md);
+    api->sha1_final(&sha1, md);
     ovs_assert(!memcmp(md, vec.output, SHA1_DIGEST_SIZE));
 
     free(vec.data);
@@ -176,15 +184,29 @@ test_huge_vector(void)
 static void
 test_shar1_main(int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
 {
-    int i;
-
-    for (i = 0; i < ARRAY_SIZE(vectors); i++) {
-        test_one(&vectors[i]);
-    }
+    struct test_api api[] = {
+        {
+            .sha1_init   = sha1_init,
+            .sha1_update = sha1_update,
+            .sha1_final  = sha1_final,
+            .sha1_bytes  = sha1_bytes,
+        },
+        {
+            .sha1_init   = ovs_sha1_init,
+            .sha1_update = ovs_sha1_update,
+            .sha1_final  = ovs_sha1_final,
+            .sha1_bytes  = ovs_sha1_bytes,
+        },
+    };
 
-    test_big_vector();
-    test_huge_vector();
+    for (int i = 0; i < ARRAY_SIZE(api); i++) {
+        for (int j = 0; j < ARRAY_SIZE(vectors); j++) {
+            test_one(&api[i], &vectors[j]);
+        }
 
+        test_big_vector(&api[i]);
+        test_huge_vector(&api[i]);
+    }
     putchar('\n');
 }
 
-- 
2.31.1



More information about the dev mailing list