From a1d28be11fa94f3932f150485ce56315410db60b Mon Sep 17 00:00:00 2001 From: Ulrik Lindahl Date: Sat, 10 Feb 2024 15:09:33 +0100 Subject: [PATCH] * Added hash template class for 256 & 512 bit hashes * Added optional picosha2 dependency for calculating hashes --- CMakeLists.txt | 11 +++ ctle/ctle.h | 1 + ctle/hash.h | 200 +++++++++++++++++++++++++++++++++++++++ ctle/uuid.h | 12 +-- unit_tests/test_hash.cpp | 164 ++++++++++++++++++++++++++++++++ unit_tests/unit_tests.h | 17 ++++ 6 files changed, 396 insertions(+), 9 deletions(-) create mode 100644 ctle/hash.h create mode 100644 unit_tests/test_hash.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index a036516..0255a29 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -35,9 +35,17 @@ if(CTLE_BUILD_TESTS) GIT_TAG 6fdeff4d67f3db493d47c44da20aa1efaa6574ef # (2020 Aug 06) ) + # picosha2 - used by hash.h to calculate sha256 hashes + FetchContent_Declare( + picosha2 + GIT_REPOSITORY https://github.com/okdshin/PicoSHA2.git + GIT_TAG 27fcf6979298949e8a462e16d09a0351c18fcaf2 # (2022 Aug 08) + ) + FetchContent_MakeAvailable( googletest glm + picosha2 ) # lots of warnings and all warnings as errors @@ -71,6 +79,7 @@ if(CTLE_BUILD_TESTS) ./ctle/thread_safe_map.h ./ctle/types.h ./ctle/uuid.h + ./ctle/hash.h ./ctle/util.h ./ctle/_macros.inl ./ctle/_undef_macros.inl @@ -94,6 +103,7 @@ if(CTLE_BUILD_TESTS) ./unit_tests/test_types.cpp ./unit_tests/test_util.cpp ./unit_tests/test_uuid.cpp + ./unit_tests/test_hash.cpp ./unit_tests/test_macros.cpp ./ctle.natvis @@ -102,6 +112,7 @@ if(CTLE_BUILD_TESTS) target_include_directories( unit_tests PUBLIC ${glm_SOURCE_DIR} + PUBLIC ${picosha2_SOURCE_DIR} ) target_link_libraries( diff --git a/ctle/ctle.h b/ctle/ctle.h index 1f2e895..4f59f2a 100644 --- a/ctle/ctle.h +++ b/ctle/ctle.h @@ -22,3 +22,4 @@ #include "../ctle/types.h" #include "../ctle/util.h" #include "../ctle/uuid.h" +#include "../ctle/hash.h" \ No newline at end of file diff --git a/ctle/hash.h b/ctle/hash.h new file mode 100644 index 0000000..9389cd0 --- /dev/null +++ b/ctle/hash.h @@ -0,0 +1,200 @@ +// ctle Copyright (c) 2021 Ulrik Lindahl +// Licensed under the MIT license https://github.com/Cooolrik/ctle/blob/main/LICENSE +#pragma once + +#include +#include +#include + +#include "status.h" + +namespace ctle +{ + +// define hash for message digests, either 256 or 512 bits in size (32 or 64 bytes) +template +struct hash +{ + static_assert( _Size == 256 || _Size == 512 , "Hash size must be 256 or 512"); + static constexpr const size_t hash_size = _Size; + + union + { + uint64_t _data_q[_Size/64] = {}; + uint8_t data[_Size/8]; + }; + + // compare operators + bool operator<( const hash &other ) const noexcept; + bool operator==( const hash &other ) const noexcept; + bool operator!=( const hash &other ) const noexcept; +}; + +template +inline bool hash<_Size>::operator<( const hash &right ) const noexcept +{ + const uint8_t *u1 = this->data; + const uint8_t *u2 = right.data; + + // hash values are stored big-endian, so MSB is first byte (index 0), LSB is last byte (index 31 or 63) + size_t n = _Size/8; + do + { + if( *u1 != *u2 ) // not equal, early exit, check if more or less than + { + if( *u1 < *u2 ) + return true; // less than + else + return false; // more than + } + ++u1; + ++u2; + --n; + } while( n>0 ); + + return false; // equal, so not less +}; + +template +inline bool hash<_Size>::operator==( const hash &right ) const noexcept +{ + const uint64_t *u1 = this->_data_q; + const uint64_t *u2 = right._data_q; + + // hash values are stored big-endian, so MSB is first byte (index 0), LSB is last byte (index 31 or 63) + size_t n = _Size/64; + do + { + if( *u1 != *u2 ) // not equal, return false + { + return false; + } + ++u1; + ++u2; + --n; + } while( n>0 ); + + return true; // equal +}; + +template +inline bool hash<_Size>::operator!=( const hash &right ) const noexcept +{ + return !this->operator==( right ); +}; + +template +inline size_t calculate_size_hash( const hash<_Size> &value ) +{ + static_assert( sizeof( std::size_t ) == sizeof( std::uint64_t ), "The hashing code only works for 64bit size_t" ); + size_t hval = value._data_q[0]; + for( size_t inx=1; inx<(_Size/64); ++inx ) + { + hval ^= value._data_q[inx]; + } + return hval; +} + +status calculate_sha256_hash( uint8_t destDigest[32], const uint8_t *srcData, size_t srcDataLength ); +status calculate_sha256_hash( hash<256> &destHash, const uint8_t *srcData, size_t srcDataLength ); + +} +//namespace ctle + +template <> +struct std::hash> +{ + std::size_t operator()( const ctle::hash<256> &val ) const noexcept + { + return ctle::calculate_size_hash<256>( val ); + } +}; + +template <> +struct std::hash> +{ + std::size_t operator()( const ctle::hash<512> &val ) const noexcept + { + return ctle::calculate_size_hash<512>( val ); + } +}; + +std::ostream &operator<<( std::ostream &os, const ctle::hash<256> &_hash ); +std::ostream &operator<<( std::ostream &os, const ctle::hash<512> &_hash ); + +#ifdef CTLE_IMPLEMENTATION + +#include +#include +#include +#include + +#include "string_funcs.h" + +namespace ctle +{ + +template <> std::string value_to_hex_string>( const hash<256> &value ) +{ + static_assert( sizeof( value ) == 32, "Error: hash<256> is assumed to be of size 32." ); + return bytes_to_hex_string( &value, 32 ); +} + +template <> std::string value_to_hex_string>( const hash<512> &value ) +{ + static_assert( sizeof( value ) == 64, "Error: hash<512> is assumed to be of size 64." ); + return bytes_to_hex_string( &value, 64 ); +} + +template <> hash<256> hex_string_to_value>( const char *hex_string ) +{ + hash<256> value; + static_assert( sizeof( value ) == 32, "Error: hash<256> is assumed to be of size 32." ); + hex_string_to_bytes( &value, hex_string, 32 ); + return value; +} + +template <> hash<512> hex_string_to_value>( const char *hex_string ) +{ + hash<512> value; + static_assert( sizeof( value ) == 64, "Error: hash<512> is assumed to be of size 64." ); + hex_string_to_bytes( &value, hex_string, 64 ); + return value; +} + +// if picosha-2 is included, implement the hash generation function for hash<256> +#ifdef PICOSHA2_H + +status calculate_sha256_hash( uint8_t destDigest[32], const uint8_t *srcData, size_t srcDataLength ) +{ + picosha2::hash256_one_by_one hasher; + + hasher.process( srcData, srcData + srcDataLength ); + hasher.finish(); + hasher.get_hash_bytes( destDigest, destDigest + 32 ); + + return status::ok; +} + +status calculate_sha256_hash( hash<256> &destHash, const uint8_t *srcData, size_t srcDataLength ) +{ + return calculate_sha256_hash( destHash.data, srcData, srcDataLength ); +} +#endif + +} +//namespace ctle + +std::ostream &operator<<( std::ostream &os, const ctle::hash<256> &_hash ) +{ + os << ctle::value_to_hex_string( _hash ); + return os; +} + +std::ostream &operator<<( std::ostream &os, const ctle::hash<512> &_hash ) +{ + os << ctle::value_to_hex_string( _hash ); + return os; +} + +#endif diff --git a/ctle/uuid.h b/ctle/uuid.h index 517ff3e..944d0fc 100644 --- a/ctle/uuid.h +++ b/ctle/uuid.h @@ -70,10 +70,8 @@ inline bool uuid::operator!=( const ctle::uuid &right ) const noexcept } //namespace ctle -namespace std -{ template <> -struct hash +struct std::hash { std::size_t operator()( ctle::uuid const &val ) const noexcept { @@ -84,7 +82,7 @@ struct hash }; std::ostream &operator<<( std::ostream &os, const ctle::uuid &_uuid ); -} + //namespace std #ifdef CTLE_IMPLEMENTATION @@ -100,7 +98,7 @@ namespace ctle { const uuid uuid::nil; -template <> inline std::string value_to_hex_string( const uuid &value ) +template <> std::string value_to_hex_string( const uuid &value ) { std::string ret; @@ -182,14 +180,10 @@ uuid uuid::generate() } //namespace ctle -namespace std -{ std::ostream &operator<<( std::ostream &os, const ctle::uuid &_uuid ) { os << ctle::value_to_hex_string( _uuid ); return os; } -} -//namespace std #endif \ No newline at end of file diff --git a/unit_tests/test_hash.cpp b/unit_tests/test_hash.cpp new file mode 100644 index 0000000..6593233 --- /dev/null +++ b/unit_tests/test_hash.cpp @@ -0,0 +1,164 @@ +// ctle Copyright (c) 2023 Ulrik Lindahl +// Licensed under the MIT license https://github.com/Cooolrik/ctle/blob/main/LICENSE + +#include "../ctle/hash.h" +#include "../ctle/string_funcs.h" + +#include "unit_tests.h" + +using namespace ctle; + +template +static hash<_Size> random_hash() +{ + hash<_Size> val; + for( size_t inx=0; inx<_Size/64; ++inx ) + { + val._data_q[inx] = random_value(); + } + return val; +} + +TEST( hash, basic_test ) +{ + using hash = ctle::hash<256>; + + const hash hsh0 = hex_string_to_value( "0000000000000000000000000000000000000000000000000000000000000000" ); // lowest + const hash hsh1 = hex_string_to_value( "0000000000000000ffffffffffffffffffffffffffffffffffffffffffffffff" ); + const hash hsh2 = hex_string_to_value( "00000200ffffffffffffffffffffffffffffffffffffffffffffffffffffffff" ); + const hash hsh3 = hex_string_to_value( "ffffffffffffffffffffffffffffffffffff0000000000000000000000000000" ); // highest + + std::string hshstr = value_to_hex_string( hsh1 ); + auto hshval2 = hex_string_to_value( hshstr.c_str() ); + EXPECT_TRUE( hsh1 == hshval2 ); + + std::stringstream str; + str << hsh2; + auto hshval3 = hex_string_to_value( str.str().c_str() ); + EXPECT_TRUE( hsh2 == hshval3 ); + + auto h2 = hsh3; + EXPECT_TRUE( h2 == hsh3 ); + + std::vector a = { random_hash() , random_hash() }; + std::vector b; + EXPECT_FALSE( a == b ); + EXPECT_TRUE( a != b ); + EXPECT_TRUE( b.empty() ); + + b = std::move( a ); + EXPECT_FALSE( a == b ); + EXPECT_TRUE( a != b ); + EXPECT_TRUE( a.empty() ); + + a = b; + EXPECT_TRUE( a == b ); + EXPECT_FALSE( a != b ); + + // test ordering + if( true ) + { + EXPECT_TRUE( hsh0._data_q[0] == 0 && hsh0._data_q[1] == 0 ); + EXPECT_TRUE( hsh1._data_q[0] == 0 && hsh1._data_q[1] != 0 ); + EXPECT_TRUE( hsh2._data_q[0] != 0 && hsh2._data_q[1] != 0 ); + EXPECT_TRUE( hsh3._data_q[0] == 0xffffffffffffffff && hsh3._data_q[3] == 0 ); + + EXPECT_TRUE( hsh0 != hsh1 ); + EXPECT_TRUE( hsh1 != hsh0 ); + EXPECT_TRUE( hsh0 < hsh1 ); + EXPECT_FALSE( hsh1 < hsh0 ); + EXPECT_FALSE( hsh0 == hsh1 ); + EXPECT_FALSE( hsh1 == hsh0 ); + + EXPECT_TRUE( std::hash{}( hsh0 ) != std::hash{}( hsh1 ) ); + EXPECT_TRUE( std::hash{}( hsh1 ) != std::hash{}( hsh0 ) ); + } + + // test lookup in map + if( true ) + { + std::map idstrmap; + idstrmap.insert( std::pair( hsh0, "hsh0" ) ); + idstrmap.insert( std::pair( hsh1, "hsh1" ) ); + idstrmap.insert( std::pair( hsh2, "hsh2" ) ); + idstrmap.insert( std::pair( hsh3, "hsh3" ) ); + EXPECT_EQ( idstrmap.size(), 4 ); + + EXPECT_EQ( idstrmap.find( hsh0 )->second, "hsh0" ); + EXPECT_EQ( idstrmap.find( hsh1 )->second, "hsh1" ); + EXPECT_EQ( idstrmap.find( hsh2 )->second, "hsh2" ); + EXPECT_EQ( idstrmap.find( hsh3 )->second, "hsh3" ); + } + + // test lookup in unordered_map + if( true ) + { + std::unordered_map idstrmap; + idstrmap.insert( std::pair( hsh0, "hsh0" ) ); + idstrmap.insert( std::pair( hsh1, "hsh1" ) ); + idstrmap.insert( std::pair( hsh2, "hsh2" ) ); + idstrmap.insert( std::pair( hsh3, "hsh3" ) ); + EXPECT_EQ( idstrmap.size(), 4 ); + + EXPECT_EQ( idstrmap.find( hsh0 )->second, "hsh0" ); + EXPECT_EQ( idstrmap.find( hsh1 )->second, "hsh1" ); + EXPECT_EQ( idstrmap.find( hsh2 )->second, "hsh2" ); + EXPECT_EQ( idstrmap.find( hsh3 )->second, "hsh3" ); + } + + // insert a number of generated values. + if( true ) + { + std::map idmap; + for( size_t inx = 0; inx < 1000; ++inx ) + { + hash myid = random_hash(); + idmap[myid] = myid; + } + EXPECT_EQ( idmap.size(), 1000 ); + } +} + +TEST( hash, sha256_hashing ) +{ + using hash = ctle::hash<256>; + + if( true ) + { + hash sha; + + u8 testdata[] = { + 0x34,0x2b,0x1f,0x3e,0x61, + 0x4b,0x03,0x4b,0x02,0x36, + 0x05,0x5c,0x17,0x29,0x3d, + 0x53,0x0e,0x5e,0x5b,0x4d, + 0x52,0x5f,0x12,0x20,0x0a, + 0x56,0x31,0x3b,0x2c,0x06, + 0x51,0x28,0x28,0x5d,0x05, + 0x59,0x2b,0x41,0x0d,0x1f, + 0x01,0x01,0x1b,0x1f,0x09, + 0x2c,0x13,0x01,0x46,0x19, + 0x05,0x3e,0x3c,0x2d,0x58, + 0x16,0x5f,0x19,0x0f,0x07, + 0x39,0x48,0x46,0x4b,0x23, + 0x06,0x15,0x0b,0x44,0x18, + 0x0e,0x38,0x56,0x0e,0x0a, + 0x0e,0x54,0x43,0x0a,0x31, + 0x2d,0x51,0x0d,0x2a,0x5a, + 0x09,0x06,0x10,0x23,0x24, + 0x23,0x33,0x2e,0x1d,0x56, + 0x48,0x2f,0x4a,0x33,0x06 + }; + + ctle::calculate_sha256_hash( sha, testdata, sizeof( testdata ) ); + + u8 expected_hash[32] = { + 0xf6,0x48,0x54,0x2d,0xf8,0xcc,0xf2,0x1f, + 0xd3,0x4e,0x95,0xf6,0x7d,0xf5,0xf2,0xb4, + 0xf2,0x72,0x72,0xaa,0x14,0xf5,0x03,0x09, + 0x0c,0xc4,0x76,0x6f,0xe2,0x78,0xc4,0xb5 + }; + + EXPECT_EQ( memcmp( sha.data, expected_hash, 32 ), 0 ); + } +} diff --git a/unit_tests/unit_tests.h b/unit_tests/unit_tests.h index e1ffdd2..4e4533c 100644 --- a/unit_tests/unit_tests.h +++ b/unit_tests/unit_tests.h @@ -15,7 +15,24 @@ #include #include +// include external headers +// silence warning we can't control +#ifdef _MSC_VER +#pragma warning( push ) +#pragma warning( disable : 4456 ) +#elif defined(__GNUC__) +#pragma GCC diagnostic push +#endif + #include +#include + +// re-enable warnings again +#ifdef _MSC_VER +#pragma warning( pop ) +#elif defined(__GNUC__) +#pragma GCC diagnostic pop +#endif typedef uint8_t u8; typedef uint16_t u16;