hashing utility for register and node IDs

Signed-off-by: Zane Shelley <zshelle@us.ibm.com>
Change-Id: I69ac08e875982456fe81c7e65b1135411f25af81
diff --git a/src/hei_util.hpp b/src/hei_util.hpp
new file mode 100644
index 0000000..5db544c
--- /dev/null
+++ b/src/hei_util.hpp
@@ -0,0 +1,70 @@
+#pragma once
+
+#include <assert.h>
+
+#include <hei_types.hpp>
+
+#include <limits>
+#include <string>
+
+namespace libhei
+{
+
+//------------------------------------------------------------------------------
+
+/**
+ * @brief The string hashing algorithm used for register and isolation node
+ *        names.
+ */
+inline uint64_t hash(unsigned int i_bytes, const std::string& i_str)
+{
+    // This hash is a simple "n*s[0] + (n-1)*s[1] + ... + s[n-1]" algorithm,
+    // where s[i] is a chunk from the input string the length of i_bytes.
+
+    // Currently only supporting 1-8 byte hashes.
+    assert(1 <= i_bytes && i_bytes <= sizeof(uint64_t));
+
+    // Start hashing each chunk.
+    uint64_t sumA = 0;
+    uint64_t sumB = 0;
+
+    // Iterate one chunk at a time.
+    for (unsigned int i = 0; i < i_str.size(); i += i_bytes)
+    {
+        // Combine each chunk into a single integer value. If we reach the end
+        // of the string, pad with null characters.
+        uint64_t chunk = 0;
+        for (unsigned int j = 0; j < i_bytes; j++)
+        {
+            chunk <<= 8;
+            chunk |= (i + j < i_str.size()) ? i_str[i + j] : '\0';
+        }
+
+        // Apply the simple hash.
+        sumA += chunk;
+        sumB += sumA;
+    }
+
+    // Mask off everything except the target number of bytes.
+    auto mask = std::numeric_limits<uint64_t>::max();
+    sumB &= mask >> ((sizeof(uint64_t) - i_bytes) * 8);
+
+    return sumB;
+}
+
+template <class T>
+inline T hash(const std::string& i_str)
+{
+    return static_cast<T>(hash(sizeof(T), i_str));
+}
+
+// Specialization for RegisterId_t because they are only a 3-byte field.
+template <>
+inline RegisterId_t hash<RegisterId_t>(const std::string& i_str)
+{
+    return static_cast<RegisterId_t>(hash(3, i_str));
+}
+
+//------------------------------------------------------------------------------
+
+} // namespace libhei
diff --git a/test/meson.build b/test/meson.build
index cbe7fbc..2025c64 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -32,6 +32,7 @@
     'bit_string_test',
     'flyweight_test',
     'operator_register_test',
+    'test-util-hash',
 ]
 
 foreach g : gtests
diff --git a/test/test-util-hash.cpp b/test/test-util-hash.cpp
new file mode 100644
index 0000000..ba35625
--- /dev/null
+++ b/test/test-util-hash.cpp
@@ -0,0 +1,24 @@
+#include <hei_util.hpp>
+
+#include "gtest/gtest.h"
+
+using namespace libhei;
+
+TEST(UtilHash, TestSet1)
+{
+    std::string s{"SOME_RANDOM_STRING"};
+
+    uint8_t h1      = 0xBE;
+    uint16_t h2     = 0xF2DD;
+    uint32_t h3     = 0xDFB31440;
+    uint64_t h4     = 0xD0D3828Ec49F687C;
+    NodeId_t h5     = 0xF2DD;                              // 2-byte field
+    RegisterId_t h6 = static_cast<RegisterId_t>(0x31D080); // 3-byte field
+
+    EXPECT_EQ(h1, hash<uint8_t>(s));
+    EXPECT_EQ(h2, hash<uint16_t>(s));
+    EXPECT_EQ(h3, hash<uint32_t>(s));
+    EXPECT_EQ(h4, hash<uint64_t>(s));
+    EXPECT_EQ(h5, hash<NodeId_t>(s));
+    EXPECT_EQ(h6, hash<RegisterId_t>(s));
+}