diff --git a/include/uuid.h b/include/uuid.h index cf75155..8fac04c 100644 --- a/include/uuid.h +++ b/include/uuid.h @@ -9,6 +9,7 @@ #include #include #include +#include #include #ifdef _WIN32 @@ -49,6 +50,186 @@ namespace uuids { return (hex2char(a) << 4) | hex2char(b); } + + class sha1 + { + public: + typedef uint32_t digest32_t[5]; + typedef uint8_t digest8_t[20]; + + static constexpr unsigned int block_bytes = 64; + + inline static uint32_t left_rotate(uint32_t value, size_t const count) + { + return (value << count) ^ (value >> (32 - count)); + } + + sha1() { reset(); } + + void reset() + { + m_digest[0] = 0x67452301; + m_digest[1] = 0xEFCDAB89; + m_digest[2] = 0x98BADCFE; + m_digest[3] = 0x10325476; + m_digest[4] = 0xC3D2E1F0; + m_blockByteIndex = 0; + m_byteCount = 0; + } + + void process_byte(uint8_t octet) + { + this->m_block[this->m_blockByteIndex++] = octet; + ++this->m_byteCount; + if (m_blockByteIndex == block_bytes) + { + this->m_blockByteIndex = 0; + process_block(); + } + } + + void process_block(void const * const start, void const * const end) + { + const uint8_t* begin = static_cast(start); + const uint8_t* finish = static_cast(end); + while (begin != finish) + { + process_byte(*begin); + begin++; + } + } + + void process_bytes(void const * const data, size_t const len) + { + const uint8_t* block = static_cast(data); + process_block(block, block + len); + } + + uint32_t const * get_digest(digest32_t digest) + { + size_t const bitCount = this->m_byteCount * 8; + process_byte(0x80); + if (this->m_blockByteIndex > 56) { + while (m_blockByteIndex != 0) { + process_byte(0); + } + while (m_blockByteIndex < 56) { + process_byte(0); + } + } + else { + while (m_blockByteIndex < 56) { + process_byte(0); + } + } + process_byte(0); + process_byte(0); + process_byte(0); + process_byte(0); + process_byte(static_cast((bitCount >> 24) & 0xFF)); + process_byte(static_cast((bitCount >> 16) & 0xFF)); + process_byte(static_cast((bitCount >> 8) & 0xFF)); + process_byte(static_cast((bitCount) & 0xFF)); + + memcpy(digest, m_digest, 5 * sizeof(uint32_t)); + return digest; + } + + uint8_t const * get_digest_bytes(digest8_t digest) + { + digest32_t d32; + get_digest(d32); + size_t di = 0; + digest[di++] = ((d32[0] >> 24) & 0xFF); + digest[di++] = ((d32[0] >> 16) & 0xFF); + digest[di++] = ((d32[0] >> 8) & 0xFF); + digest[di++] = ((d32[0]) & 0xFF); + + digest[di++] = ((d32[1] >> 24) & 0xFF); + digest[di++] = ((d32[1] >> 16) & 0xFF); + digest[di++] = ((d32[1] >> 8) & 0xFF); + digest[di++] = ((d32[1]) & 0xFF); + + digest[di++] = ((d32[2] >> 24) & 0xFF); + digest[di++] = ((d32[2] >> 16) & 0xFF); + digest[di++] = ((d32[2] >> 8) & 0xFF); + digest[di++] = ((d32[2]) & 0xFF); + + digest[di++] = ((d32[3] >> 24) & 0xFF); + digest[di++] = ((d32[3] >> 16) & 0xFF); + digest[di++] = ((d32[3] >> 8) & 0xFF); + digest[di++] = ((d32[3]) & 0xFF); + + digest[di++] = ((d32[4] >> 24) & 0xFF); + digest[di++] = ((d32[4] >> 16) & 0xFF); + digest[di++] = ((d32[4] >> 8) & 0xFF); + digest[di++] = ((d32[4]) & 0xFF); + + return digest; + } + + private: + void process_block() + { + uint32_t w[80]; + for (size_t i = 0; i < 16; i++) { + w[i] = (m_block[i * 4 + 0] << 24); + w[i] |= (m_block[i * 4 + 1] << 16); + w[i] |= (m_block[i * 4 + 2] << 8); + w[i] |= (m_block[i * 4 + 3]); + } + for (size_t i = 16; i < 80; i++) { + w[i] = left_rotate((w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16]), 1); + } + + uint32_t a = m_digest[0]; + uint32_t b = m_digest[1]; + uint32_t c = m_digest[2]; + uint32_t d = m_digest[3]; + uint32_t e = m_digest[4]; + + for (std::size_t i = 0; i < 80; ++i) + { + uint32_t f = 0; + uint32_t k = 0; + + if (i < 20) { + f = (b & c) | (~b & d); + k = 0x5A827999; + } + else if (i < 40) { + f = b ^ c ^ d; + k = 0x6ED9EBA1; + } + else if (i < 60) { + f = (b & c) | (b & d) | (c & d); + k = 0x8F1BBCDC; + } + else { + f = b ^ c ^ d; + k = 0xCA62C1D6; + } + uint32_t temp = left_rotate(a, 5) + f + e + k + w[i]; + e = d; + d = c; + c = left_rotate(b, 30); + b = a; + a = temp; + } + + m_digest[0] += a; + m_digest[1] += b; + m_digest[2] += c; + m_digest[3] += d; + m_digest[4] += e; + } + + private: + digest32_t m_digest; + uint8_t m_block[64]; + size_t m_blockByteIndex; + size_t m_byteCount; + }; } // UUID format https://tools.ietf.org/html/rfc4122 @@ -724,6 +905,78 @@ namespace uuids }; using uuid_random_generator = basic_uuid_random_generator; + + class uuid_name_generator + { + public: + typedef uuid result_type; + + explicit uuid_name_generator(uuid const& namespace_uuid) noexcept + : nsuuid(namespace_uuid) + {} + + uuid operator()(std::string_view name) + { + reset(); + process_characters(name.data(), name.size()); + return make_uuid(); + } + + uuid operator()(std::wstring_view name) + { + reset(); + process_characters(name.data(), name.size()); + return make_uuid(); + } + + private: + void reset() + { + hasher.reset(); + uint8_t bytes[uuid::state_size]; + std::copy(std::begin(nsuuid), std::end(nsuuid), bytes); + hasher.process_bytes(bytes, uuid::state_size); + } + + template ::value>> + void process_characters(char_type const * const characters, size_t const count) + { + for (size_t i = 0; i < count; i++) + { + uint32_t c = characters[i]; + hasher.process_byte(static_cast((c >> 0) & 0xFF)); + hasher.process_byte(static_cast((c >> 8) & 0xFF)); + hasher.process_byte(static_cast((c >> 16) & 0xFF)); + hasher.process_byte(static_cast((c >> 24) & 0xFF)); + } + } + + void process_characters(const char * const characters, size_t const count) + { + hasher.process_bytes(characters, count); + } + + uuid make_uuid() + { + detail::sha1::digest8_t digest; + hasher.get_digest_bytes(digest); + + // variant must be 0b10xxxxxx + digest[8] &= 0xBF; + digest[8] |= 0x80; + + // version must be 0b0101xxxx + digest[6] &= 0x5F; + digest[6] |= 0x50; + + return uuid{ digest, digest + uuid::state_size }; + } + + private: + uuid nsuuid; + detail::sha1 hasher; + }; } namespace std diff --git a/test/test.cpp b/test/test.cpp index d8e455c..d51f2dd 100644 --- a/test/test.cpp +++ b/test/test.cpp @@ -231,10 +231,10 @@ int main() assert(id1.variant() == uuids::uuid_variant::rfc); auto id2 = dgen(); - assert(!id1.nil()); - assert(id1.size() == 16); - assert(id1.version() == uuids::uuid_version::random_number_based); - assert(id1.variant() == uuids::uuid_variant::rfc); + assert(!id2.nil()); + assert(id2.size() == 16); + assert(id2.version() == uuids::uuid_version::random_number_based); + assert(id2.variant() == uuids::uuid_variant::rfc); assert(id1 != id2); } @@ -256,10 +256,10 @@ int main() assert(id1.variant() == uuids::uuid_variant::rfc); auto id2 = dgen(); - assert(!id1.nil()); - assert(id1.size() == 16); - assert(id1.version() == uuids::uuid_version::random_number_based); - assert(id1.variant() == uuids::uuid_variant::rfc); + assert(!id2.nil()); + assert(id2.size() == 16); + assert(id2.version() == uuids::uuid_version::random_number_based); + assert(id2.variant() == uuids::uuid_variant::rfc); assert(id1 != id2); } @@ -282,9 +282,9 @@ int main() auto id2 = dgen(); assert(!id1.nil()); - assert(id1.size() == 16); - assert(id1.version() == uuids::uuid_version::random_number_based); - assert(id1.variant() == uuids::uuid_variant::rfc); + assert(id2.size() == 16); + assert(id2.version() == uuids::uuid_version::random_number_based); + assert(id2.variant() == uuids::uuid_variant::rfc); assert(id1 != id2); } @@ -306,10 +306,10 @@ int main() assert(id1.variant() == uuids::uuid_variant::rfc); auto id2 = dgen(); - assert(!id1.nil()); - assert(id1.size() == 16); - assert(id1.version() == uuids::uuid_version::random_number_based); - assert(id1.variant() == uuids::uuid_variant::rfc); + assert(!id2.nil()); + assert(id2.size() == 16); + assert(id2.version() == uuids::uuid_version::random_number_based); + assert(id2.variant() == uuids::uuid_variant::rfc); assert(id1 != id2); } @@ -326,9 +326,9 @@ int main() auto id2 = dgen(); assert(!id1.nil()); - assert(id1.size() == 16); - assert(id1.version() == uuids::uuid_version::random_number_based); - assert(id1.variant() == uuids::uuid_variant::rfc); + assert(id2.size() == 16); + assert(id2.version() == uuids::uuid_version::random_number_based); + assert(id2.variant() == uuids::uuid_variant::rfc); assert(id1 != id2); } @@ -347,10 +347,10 @@ int main() assert(id1.variant() == uuids::uuid_variant::rfc); auto id2 = dgen(); - assert(!id1.nil()); - assert(id1.size() == 16); - assert(id1.version() == uuids::uuid_version::random_number_based); - assert(id1.variant() == uuids::uuid_variant::rfc); + assert(!id2.nil()); + assert(id2.size() == 16); + assert(id2.version() == uuids::uuid_version::random_number_based); + assert(id2.variant() == uuids::uuid_variant::rfc); assert(id1 != id2); } @@ -369,10 +369,10 @@ int main() assert(id1.variant() == uuids::uuid_variant::rfc); auto id2 = dgen(); - assert(!id1.nil()); - assert(id1.size() == 16); - assert(id1.version() == uuids::uuid_version::random_number_based); - assert(id1.variant() == uuids::uuid_variant::rfc); + assert(!id2.nil()); + assert(id2.size() == 16); + assert(id2.version() == uuids::uuid_version::random_number_based); + assert(id2.variant() == uuids::uuid_variant::rfc); assert(id1 != id2); } @@ -391,14 +391,47 @@ int main() assert(id1.variant() == uuids::uuid_variant::rfc); auto id2 = dgen(); - assert(!id1.nil()); - assert(id1.size() == 16); - assert(id1.version() == uuids::uuid_version::random_number_based); - assert(id1.variant() == uuids::uuid_variant::rfc); + assert(!id2.nil()); + assert(id2.size() == 16); + assert(id2.version() == uuids::uuid_version::random_number_based); + assert(id2.variant() == uuids::uuid_variant::rfc); assert(id1 != id2); } + { + std::cout << "Test name generator" << std::endl; + + uuids::uuid_name_generator dgen(uuids::uuid_default_generator{}()); + auto id1 = dgen("john"); + assert(!id1.nil()); + assert(id1.size() == 16); + assert(id1.version() == uuids::uuid_version::name_based_sha1); + assert(id1.variant() == uuids::uuid_variant::rfc); + + auto id2 = dgen("jane"); + assert(!id2.nil()); + assert(id2.size() == 16); + assert(id2.version() == uuids::uuid_version::name_based_sha1); + assert(id2.variant() == uuids::uuid_variant::rfc); + + auto id3 = dgen("jane"); + assert(!id3.nil()); + assert(id3.size() == 16); + assert(id3.version() == uuids::uuid_version::name_based_sha1); + assert(id3.variant() == uuids::uuid_variant::rfc); + + auto id4 = dgen(L"jane"); + assert(!id4.nil()); + assert(id4.size() == 16); + assert(id4.version() == uuids::uuid_version::name_based_sha1); + assert(id4.variant() == uuids::uuid_variant::rfc); + + assert(id1 != id2); + assert(id2 == id3); + assert(id3 != id4); + } + std::cout << "ALL PASSED" << std::endl; return 0;