From: Eduard Bagdasaryan Date: Fri, 13 May 2022 11:31:09 +0000 (+0000) Subject: Add Universally Unique IDentifier (UUID) support (#1015) X-Git-Tag: SQUID_6_0_1~186 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=b253a88cf3e5dd72a4c6ffe500d56bb9a0661a11;p=thirdparty%2Fsquid.git Add Universally Unique IDentifier (UUID) support (#1015) These 128-bit UUIDs have a very high chance of being unique across SMP Squid kids and even across independent Squid instances. Our UUID creation algorithm uses random number generation as described in RFC 4122 Section 4.4 (UUID version 4 variant 1). --- diff --git a/src/Makefile.am b/src/Makefile.am index 153064aa14..a6d5bd7e5d 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -793,6 +793,23 @@ tests_testMath_LDADD = \ $(XTRA_LIBS) tests_testMath_LDFLAGS = $(LIBADD_DL) +## Tests of RandomUuid.h +check_PROGRAMS += tests/testRandomUuid +tests_testRandomUuid_SOURCES = \ + tests/testRandomUuid.cc +nodist_tests_testRandomUuid_SOURCES = \ + RandomUuid.h \ + tests/stub_debug.cc \ + tests/stub_libmem.cc +tests_testRandomUuid_LDADD = \ + libsquid.la \ + sbuf/libsbuf.la \ + base/libbase.la \ + $(LIBCPPUNIT_LIBS) \ + $(COMPAT_LIB) \ + $(XTRA_LIBS) +tests_testRandomUuid_LDFLAGS = $(LIBADD_DL) + ## Tests of mem/* check_PROGRAMS += tests/testMem diff --git a/src/base/Makefile.am b/src/base/Makefile.am index 9f7591d6c2..0c8ae4d861 100644 --- a/src/base/Makefile.am +++ b/src/base/Makefile.am @@ -51,6 +51,8 @@ libbase_la_SOURCES = \ Optional.h \ Packable.h \ PackableStream.h \ + RandomUuid.cc \ + RandomUuid.h \ Range.h \ Raw.cc \ Raw.h \ diff --git a/src/base/RandomUuid.cc b/src/base/RandomUuid.cc new file mode 100644 index 0000000000..66628f4c8d --- /dev/null +++ b/src/base/RandomUuid.cc @@ -0,0 +1,119 @@ +/* + * Copyright (C) 1996-2022 The Squid Software Foundation and contributors + * + * Squid software is distributed under GPLv2+ license and includes + * contributions from numerous individuals and organizations. + * Please see the COPYING and CONTRIBUTORS files for details. + */ + +#include "squid.h" +#include "base/IoManip.h" +#include "base/RandomUuid.h" +#include "base/TextException.h" +#include "defines.h" + +#include +#include + +static_assert(sizeof(RandomUuid) == 128/8, "RandomUuid has RFC 4122-prescribed 128-bit size"); + +RandomUuid::RandomUuid() +{ + // Generate random bits for populating our UUID. + // STL implementation bugs notwithstanding (e.g., MinGW bug #338), this is + // our best chance of getting a non-deterministic seed value for the r.n.g. + static std::mt19937_64 rng(std::random_device {}()); // produces 64-bit sized values + const auto rnd1 = rng(); + const auto rnd2 = rng(); + + // No real r.n.g. is perfect, but we assume that std::mt19937_64 quality is + // high enough to make any imperfections irrelevant to this specific code. + + // bullet 3 of RFC 4122 Section 4.4 algorithm but setting _all_ bits (KISS) + static_assert(sizeof(rnd1) + sizeof(rnd2) == sizeof(*this), "random bits fill a UUID"); + memcpy(raw(), &rnd1, sizeof(rnd1)); + memcpy(raw() + sizeof(rnd1), &rnd2, sizeof(rnd2)); + + // bullet 2 of RFC 4122 Section 4.4 algorithm + EBIT_CLR(timeHiAndVersion, 12); + EBIT_CLR(timeHiAndVersion, 13); + EBIT_SET(timeHiAndVersion, 14); + EBIT_CLR(timeHiAndVersion, 15); + + // bullet 1 of RFC 4122 Section 4.4 algorithm + EBIT_CLR(clockSeqHiAndReserved, 6); + EBIT_SET(clockSeqHiAndReserved, 7); + + assert(sane()); +} + +RandomUuid::RandomUuid(const Serialized &bytes) +{ + static_assert(sizeof(*this) == sizeof(Serialized), "RandomUuid is deserialized with 128/8 bytes"); + memcpy(raw(), bytes.data(), sizeof(*this)); + timeLow = ntohl(timeLow); + timeMid = ntohs(timeMid); + timeHiAndVersion = ntohs(timeHiAndVersion); + if (!sane()) + throw TextException("malformed version 4 variant 1 UUID", Here()); +} + +/// whether this (being constructed) object follows UUID version 4 variant 1 format +bool +RandomUuid::sane() const +{ + return (!EBIT_TEST(clockSeqHiAndReserved, 6) && + EBIT_TEST(clockSeqHiAndReserved, 7) && + !EBIT_TEST(timeHiAndVersion, 12) && + !EBIT_TEST(timeHiAndVersion, 13) && + EBIT_TEST(timeHiAndVersion, 14) && + !EBIT_TEST(timeHiAndVersion, 15)); +} + +RandomUuid::Serialized +RandomUuid::serialize() const +{ + assert(sane()); + auto toNetwork = clone(); + // Convert all multi-byte fields to network byte order so that the recipient + // will consider our ID sane() and print() the same text representation. + toNetwork.timeLow = htonl(timeLow); + toNetwork.timeMid = htons(timeMid); + toNetwork.timeHiAndVersion = htons(timeHiAndVersion); + return *reinterpret_cast(toNetwork.raw()); +} + +void +RandomUuid::print(std::ostream &os) const +{ + const auto savedFlags = os.flags(); + const auto savedFill = os.fill('0'); + + os << std::hex; + + os << + std::setw(8) << timeLow << '-' << + std::setw(4) << timeMid << '-' << + std::setw(4) << timeHiAndVersion << '-' << + std::setw(2) << +clockSeqHiAndReserved << std::setw(2) << +clockSeqLow << '-'; + + for (size_t i = 0; i < sizeof(node); ++i) + os << std::setw(2) << +node[i]; + + os.fill(savedFill); + os.flags(savedFlags); +} + +bool +RandomUuid::operator ==(const RandomUuid &other) const +{ + return memcmp(raw(), other.raw(), sizeof(*this)) == 0; +} + +std::ostream & +operator<<(std::ostream &os, const RandomUuid &uuid) +{ + uuid.print(os); + return os; +} + diff --git a/src/base/RandomUuid.h b/src/base/RandomUuid.h new file mode 100644 index 0000000000..10b3bc0691 --- /dev/null +++ b/src/base/RandomUuid.h @@ -0,0 +1,76 @@ +/* + * Copyright (C) 1996-2022 The Squid Software Foundation and contributors + * + * Squid software is distributed under GPLv2+ license and includes + * contributions from numerous individuals and organizations. + * Please see the COPYING and CONTRIBUTORS files for details. + */ + +#ifndef SQUID_SRC_BASE_RANDOMUUID_H +#define SQUID_SRC_BASE_RANDOMUUID_H + +#include +#include + +/// 128-bit Universally Unique IDentifier (UUID), version 4 (variant 1) as +/// defined by RFC 4122. These UUIDs are generated from pseudo-random numbers. +class RandomUuid +{ +public: + /// UUID representation independent of machine byte-order architecture + using Serialized = std::array; + + /// creates a new unique ID (i.e. not a "nil UUID" in RFC 4122 terminology) + RandomUuid(); + + /// imports a UUID value that was exported using the serialize() API + explicit RandomUuid(const Serialized &); + + RandomUuid(RandomUuid &&) = default; + RandomUuid &operator=(RandomUuid &&) = default; + + // (Implicit) public copying is prohibited to prevent accidental duplication + // of supposed-to-be-unique values. Use clone() when duplication is needed. + RandomUuid &operator=(const RandomUuid &) = delete; + + /// exports UUID value; suitable for long-term storage + Serialized serialize() const; + + bool operator ==(const RandomUuid &) const; + bool operator !=(const RandomUuid &other) const { return !(*this == other); } + + /// creates a UUID object with the same value as this UUID + RandomUuid clone() const { return *this; } + + /// writes a human-readable representation + void print(std::ostream &os) const; + +private: + RandomUuid(const RandomUuid &) = default; + + /// read/write access to storage bytes + char *raw() { return reinterpret_cast(this); } + + /// read-only access to storage bytes + const char *raw() const { return reinterpret_cast(this); } + + /// whether this (being constructed) object follows UUID version 4 variant 1 format + bool sane() const; + + /* + * These field sizes and names come from RFC 4122 Section 4.1.2. They do not + * accurately represent the actual UUID version 4 structure which, the six + * version/variant bits aside, contains just random bits. + */ + uint32_t timeLow; + uint16_t timeMid; + uint16_t timeHiAndVersion; + uint8_t clockSeqHiAndReserved; + uint8_t clockSeqLow; + uint8_t node[6]; +}; + +std::ostream &operator<<(std::ostream &os, const RandomUuid &uuid); + +#endif /* SQUID_SRC_BASE_RANDOMUUID_H */ + diff --git a/src/tests/testRandomUuid.cc b/src/tests/testRandomUuid.cc new file mode 100644 index 0000000000..5efa31775d --- /dev/null +++ b/src/tests/testRandomUuid.cc @@ -0,0 +1,92 @@ +/* + * Copyright (C) 1996-2022 The Squid Software Foundation and contributors + * + * Squid software is distributed under GPLv2+ license and includes + * contributions from numerous individuals and organizations. + * Please see the COPYING and CONTRIBUTORS files for details. + */ + +#include "squid.h" +#include "base/RandomUuid.h" +#include "compat/cppunit.h" +#include "sbuf/SBuf.h" +#include "sbuf/Stream.h" +#include "unitTestMain.h" + +#include +#include + +class TestRandomUuid: public CPPUNIT_NS::TestFixture +{ + CPPUNIT_TEST_SUITE( TestRandomUuid ); + CPPUNIT_TEST( testUniqueness ); + CPPUNIT_TEST( testSerialization ); + CPPUNIT_TEST( testTextRepresentation ); + CPPUNIT_TEST( testInvalidIds ); + CPPUNIT_TEST_SUITE_END(); + +protected: + void testUniqueness(); + void testSerialization(); + void testTextRepresentation(); + void testInvalidIds(); +}; + +CPPUNIT_TEST_SUITE_REGISTRATION( TestRandomUuid ); + +typedef std::map RandomIds; + +// Generated by https://www.uuidgenerator.net/version4 +// binary representation of the generated UUID in network byte order +static const RandomIds ExternalIds { + { SBuf("bd1b1c07-f7fa-428a-b019-7e390133b0e5"), { 0xbd, 0x1b, 0x1c, 0x07, 0xf7, 0xfa, 0x42, 0x8a, 0xb0, 0x19, 0x7e, 0x39, 0x01, 0x33, 0xb0, 0xe5 } }, + { SBuf("f63ccd5a-9d25-41a5-a36c-a7b0c6b5c678"), { 0xf6, 0x3c, 0xcd, 0x5a, 0x9d, 0x25, 0x41, 0xa5, 0xa3, 0x6c, 0xa7, 0xb0, 0xc6, 0xb5, 0xc6, 0x78 } }, + { SBuf("9c8363ac-9c62-44e9-941f-86b7edc25dc7"), { 0x9c, 0x83, 0x63, 0xac, 0x9c, 0x62, 0x44, 0xe9, 0x94, 0x1f, 0x86, 0xb7, 0xed, 0xc2, 0x5d, 0xc7 } } +}; + +static const RandomIds InvalidIds { + { SBuf("00000000-0000-0000-0000-000000000000"), { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } }, + { SBuf("ffffffff-ffff-ffff-ffff-ffffffffffff"), { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff } } +}; + +void +TestRandomUuid::testUniqueness() +{ + std::set uniqueIds; + while (uniqueIds.size() < 1000) { + const auto inserted = uniqueIds.insert(ToSBuf(RandomUuid())).second; + CPPUNIT_ASSERT_MESSAGE("few generated UUIDs are unique", inserted); + } +} + +void +TestRandomUuid::testSerialization() +{ + RandomUuid uuid; + CPPUNIT_ASSERT_MESSAGE("original and deserialized UUIDs are equal", uuid == RandomUuid(uuid.serialize())); +} + +void +TestRandomUuid::testTextRepresentation() +{ + for (const auto &id: ExternalIds) { + CPPUNIT_ASSERT_MESSAGE("UUID text representation matches the expected one", ToSBuf(RandomUuid(id.second)) == id.first); + } +} + +void +TestRandomUuid::testInvalidIds() +{ + for (const auto &id: InvalidIds) { + try { + RandomUuid uuid(id.second); + std::cerr << std::endl + << "FAIL: " << id.first + << Debug::Extra << "error: should be rejected" << std::endl; + } catch (const TextException &e) { + continue; // success, caught a malformed UUID + } + CPPUNIT_FAIL("failed to reject an invalid UUID"); + } +} +