]> git.ipfire.org Git - thirdparty/squid.git/commitdiff
Add Universally Unique IDentifier (UUID) support (#1015)
authorEduard Bagdasaryan <eduard.bagdasaryan@measurement-factory.com>
Fri, 13 May 2022 11:31:09 +0000 (11:31 +0000)
committerSquid Anubis <squid-anubis@squid-cache.org>
Fri, 13 May 2022 11:31:18 +0000 (11:31 +0000)
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).

src/Makefile.am
src/base/Makefile.am
src/base/RandomUuid.cc [new file with mode: 0644]
src/base/RandomUuid.h [new file with mode: 0644]
src/tests/testRandomUuid.cc [new file with mode: 0644]

index 153064aa1492d073ad092ea0b9a4293fc4657eb1..a6d5bd7e5d1706e0e4e9f13bbdbfa01205363dc6 100644 (file)
@@ -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
index 9f7591d6c2184dc573cfcc28d6d3f189e413f7c1..0c8ae4d8615082ea45bb7db3eb07d129b41813bd 100644 (file)
@@ -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 (file)
index 0000000..66628f4
--- /dev/null
@@ -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 <iostream>
+#include <random>
+
+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<const Serialized *>(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 (file)
index 0000000..10b3bc0
--- /dev/null
@@ -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 <array>
+#include <iosfwd>
+
+/// 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<uint8_t, 128/8>;
+
+    /// 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<char*>(this); }
+
+    /// read-only access to storage bytes
+    const char *raw() const { return reinterpret_cast<const char*>(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 (file)
index 0000000..5efa317
--- /dev/null
@@ -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 <map>
+#include <set>
+
+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<SBuf, RandomUuid::Serialized> 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<SBuf> 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");
+    }
+}
+