summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/corelib/CMakeLists.txt2
-rw-r--r--src/corelib/plugin/quuid.cpp28
-rw-r--r--src/corelib/plugin/quuid.h25
-rw-r--r--src/corelib/plugin/quuid_p.h61
-rw-r--r--tests/auto/corelib/plugin/quuid/tst_quuid.cpp56
5 files changed, 164 insertions, 8 deletions
diff --git a/src/corelib/CMakeLists.txt b/src/corelib/CMakeLists.txt
index 063bb859b48..7adbc7014fa 100644
--- a/src/corelib/CMakeLists.txt
+++ b/src/corelib/CMakeLists.txt
@@ -194,7 +194,7 @@ qt_internal_add_module(Core
plugin/qfactoryloader.cpp plugin/qfactoryloader_p.h
plugin/qplugin.h plugin/qplugin_p.h
plugin/qpluginloader.cpp plugin/qpluginloader.h
- plugin/quuid.cpp plugin/quuid.h
+ plugin/quuid.cpp plugin/quuid.h plugin/quuid_p.h
serialization/qcborarray.h
serialization/qcborcommon.cpp serialization/qcborcommon.h serialization/qcborcommon_p.h
serialization/qcbordiagnostic.cpp
diff --git a/src/corelib/plugin/quuid.cpp b/src/corelib/plugin/quuid.cpp
index 0b1615c6499..e0d621dec80 100644
--- a/src/corelib/plugin/quuid.cpp
+++ b/src/corelib/plugin/quuid.cpp
@@ -3,6 +3,7 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "quuid.h"
+#include "quuid_p.h"
#include "qcryptographichash.h"
#include "qdatastream.h"
@@ -543,7 +544,7 @@ QUuid QUuid::fromString(QAnyStringView text) noexcept
\note In Qt versions prior to 6.8, this function took QByteArray, not
QByteArrayView.
- \sa variant(), version(), createUuidV5()
+ \sa variant(), version(), createUuidV5(), createUuidV7()
*/
/*!
@@ -553,7 +554,7 @@ QUuid QUuid::fromString(QAnyStringView text) noexcept
This function returns a new UUID with variant QUuid::DCE and version QUuid::Md5.
\a ns is the namespace and \a baseData is the basic data as described by RFC 4122.
- \sa variant(), version(), createUuidV5()
+ \sa variant(), version(), createUuidV5(), createUuidV7()
*/
/*!
@@ -591,6 +592,25 @@ QUuid QUuid::createUuidV5(QUuid ns, QByteArrayView baseData) noexcept
}
/*!
+ \since 6.9
+
+ This function returns a new UUID with variant QUuid::DCE and version
+ QUuid::UnixEpoch.
+
+ It uses a time-ordered value field derived from the number of milliseconds
+ since the UNIX Epoch as described by
+ \l {https://datatracker.ietf.org/doc/html/rfc9562#name-uuid-version-7}{RFC9562}.
+
+ \sa variant(), version(), createUuidV3(), createUuidV5()
+*/
+#ifndef QT_BOOTSTRAPPED
+QUuid QUuid::createUuidV7()
+{
+ return createUuidV7_internal(std::chrono::system_clock::now());
+}
+#endif // !defined(QT_BOOTSTRAPPED)
+
+/*!
Creates a QUuid object from the binary representation of the UUID, as
specified by RFC 4122 section 4.1.2. See toRfc4122() for a further
explanation of the order of \a bytes required.
@@ -861,7 +881,9 @@ QDataStream &operator>>(QDataStream &s, QUuid &id)
\value Name Name-based, by using values from a name for all sections
\value Md5 Alias for Name
\value Random Random-based, by using random numbers for all sections
- \value Sha1
+ \value Sha1 Name-based version that uses SHA-1 hashing
+ \value UnixEpoch Time-based UUID using the number of milliseconds since
+ the UNIX epoch
*/
/*!
diff --git a/src/corelib/plugin/quuid.h b/src/corelib/plugin/quuid.h
index 34a14cbc641..e19a4b7c52e 100644
--- a/src/corelib/plugin/quuid.h
+++ b/src/corelib/plugin/quuid.h
@@ -48,7 +48,8 @@ public:
Md5 = 3, // 0 0 1 1
Name = Md5,
Random = 4, // 0 1 0 0
- Sha1 = 5 // 0 1 0 1
+ Sha1 = 5, // 0 1 0 1
+ UnixEpoch = 7, // 0 1 1 1
};
enum StringFormat {
@@ -277,6 +278,26 @@ public:
return QUuid::createUuidV5(ns, qToByteArrayViewIgnoringNull(baseData.toUtf8()));
}
+ static QUuid createUuidV7();
+
+private:
+ static constexpr bool isKnownVersion(Version v) noexcept
+ {
+ switch (v) {
+ case VerUnknown:
+ return false;
+ case Time:
+ case EmbeddedPOSIX:
+ case Md5:
+ case Random:
+ case Sha1:
+ case UnixEpoch:
+ return true;
+ }
+ return false;
+ }
+
+public:
#if QT_CORE_REMOVED_SINCE(6, 9)
QUuid::Variant variant() const noexcept;
QUuid::Version version() const noexcept;
@@ -296,7 +317,7 @@ public:
// Check the 4 MSB of data3
const Version ver = Version(data3 >> 12);
// Check that variant() == DCE and version is in a valid range
- if (ver >= Time && ver <= Sha1 && (data4[0] & 0xC0) == 0x80)
+ if (isKnownVersion(ver) && (data4[0] & 0xC0) == 0x80)
return ver;
return VerUnknown;
}
diff --git a/src/corelib/plugin/quuid_p.h b/src/corelib/plugin/quuid_p.h
new file mode 100644
index 00000000000..871fc57195d
--- /dev/null
+++ b/src/corelib/plugin/quuid_p.h
@@ -0,0 +1,61 @@
+// Copyright (C) 2020 The Qt Company Ltd.
+// Copyright (C) 2021 Intel Corporation.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QUUID_P_H
+#define QUUID_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists for the convenience
+// of the QLibrary class. This header file may change from
+// version to version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include "quuid.h"
+
+#include <QtCore/qrandom.h>
+
+#include <chrono>
+
+QT_BEGIN_NAMESPACE
+
+#ifndef QT_BOOTSTRAPPED
+static inline QUuid createUuidV7_internal(
+ std::chrono::time_point<std::chrono::system_clock, std::chrono::nanoseconds> tp)
+{
+ QUuid result;
+
+ using namespace std::chrono;
+ const nanoseconds nsecSinceEpoch = tp.time_since_epoch();
+ const auto msecSinceEpoch = floor<milliseconds>(nsecSinceEpoch);
+ const quint64 frac = (nsecSinceEpoch - msecSinceEpoch).count();
+ // Lower 48 bits of the timestamp
+ const quint64 msecs = quint64(msecSinceEpoch.count()) & 0xffffffffffff;
+ result.data1 = uint(msecs >> 16);
+ result.data2 = ushort(msecs);
+ // rand_a: use a 12-bit sub-millisecond timestamp for additional monotonicity
+ // https://datatracker.ietf.org/doc/html/rfc9562#monotonicity_counters (Method 3)
+
+ // "frac" is a number between 0 and 999,999, so the lowest 20 bits
+ // should be roughly random. Use the high 12 of those for additional
+ // monotonicity.
+ result.data3 = frac >> 8;
+ result.data3 &= 0x0FFF;
+ result.data3 |= ushort(7) << 12;
+
+ // rand_b: 62 bits of random data (64 - 2 bits for the variant)
+ const quint64 random = QRandomGenerator::system()->generate64();
+ memcpy(result.data4, &random, sizeof(quint64));
+ result.data4[0] = (result.data4[0] & 0x3F) | 0x80; // UV_DCE
+ return result;
+}
+#endif
+
+QT_END_NAMESPACE
+
+#endif // QUUID_P_H
diff --git a/tests/auto/corelib/plugin/quuid/tst_quuid.cpp b/tests/auto/corelib/plugin/quuid/tst_quuid.cpp
index 655c0965785..531200ed041 100644
--- a/tests/auto/corelib/plugin/quuid/tst_quuid.cpp
+++ b/tests/auto/corelib/plugin/quuid/tst_quuid.cpp
@@ -11,11 +11,15 @@
#include <qcoreapplication.h>
#include <quuid.h>
+#include <QtCore/private/quuid_p.h>
#ifdef Q_OS_ANDROID
#include <QStandardPaths>
#endif
+using namespace std::chrono_literals;
+using namespace Qt::StringLiterals;
+
class tst_QUuid : public QObject
{
Q_OBJECT
@@ -35,6 +39,9 @@ private slots:
void id128();
void uint128();
void createUuidV3OrV5();
+ void createUuidV7_unique();
+ void createUuidV7_data();
+ void createUuidV7();
void check_QDataStream();
void isNull();
void equal();
@@ -323,6 +330,51 @@ void tst_QUuid::createUuidV3OrV5()
QT_TEST_EQUALITY_OPS(uuidD, QUuid::createUuidV5(uuidNS, QString("www.widgets.com")), true);
}
+void tst_QUuid::createUuidV7_unique()
+{
+ const int count = 1000;
+ std::vector<QUuid> vec;
+ vec.reserve(count);
+ for (int i = 0; i < count; ++i) {
+ auto id = QUuid::createUuidV7();
+ QCOMPARE(id.version(), QUuid::UnixEpoch);
+ QCOMPARE(id.variant(), QUuid::DCE);
+ vec.push_back(id);
+ }
+
+ QVERIFY(std::unique(vec.begin(), vec.end()) == vec.end());
+}
+
+void tst_QUuid::createUuidV7_data()
+{
+ QTest::addColumn<QDateTime>("dt");
+ QTest::addColumn<QUuid>("expected");
+
+ // February 22, 2022 2:22:22.00 PM GMT-05:00, example from:
+ // https://datatracker.ietf.org/doc/html/rfc9562#name-example-of-a-uuidv7-value
+ QTest::newRow("feb2022")
+ << QDateTime::fromString("2022-02-22T14:22:22.00-05:00"_L1, Qt::ISODateWithMs)
+ << QUuid::fromString("017F22E2-79B0-7CC3-98C4-DC0C0C07398F"_L1);
+
+ QTest::newRow("jan2000")
+ << QDateTime::fromString("2000-01-02T14:22:22.00-05:00"_L1, Qt::ISODateWithMs)
+ << QUuid("00dc741e-35b0-7643-947d-0380e108ce80"_L1);
+}
+
+void tst_QUuid::createUuidV7()
+{
+ QFETCH(QDateTime, dt);
+ QFETCH(QUuid, expected);
+
+ QVERIFY(dt.isValid());
+
+ using namespace std::chrono;
+ auto extractTimestamp = [](const QUuid &id) { return (quint64(id.data1) << 16) | id.data2; };
+ const auto result =
+ createUuidV7_internal(time_point<system_clock, milliseconds>(dt.toMSecsSinceEpoch() * 1ms));
+ QCOMPARE_EQ(extractTimestamp(result), extractTimestamp(expected));
+}
+
void tst_QUuid::check_QDataStream()
{
QUuid tmp;
@@ -618,8 +670,8 @@ void tst_QUuid::versions_data()
QTest::newRow("DCE-inv-less-than-Time->unknown")
<< QUuid(0, 0, 0b0000'1101'0101'1011, 0b1000'0000, 0, 0, 0, 0, 0, 0, 0)
<< QUuid::VerUnknown;
- QTest::newRow("DCE-inv-greater-than-Sha1->unknown")
- << QUuid(0, 0, 0b0111'1101'0101'1011, 0b1000'0000, 0, 0, 0, 0, 0, 0, 0)
+ QTest::newRow("DCE-inv-greater-than-UnixEpoch->unknown")
+ << QUuid(0, 0, 0b1000'1101'0101'1011, 0b1000'0000, 0, 0, 0, 0, 0, 0, 0)
<< QUuid::VerUnknown;
QTest::newRow("NCS-Time->unknown")
<< QUuid(0, 0, 0b0001'0000'0000'0000, 0b0100'0000, 0, 0, 0, 0, 0, 0, 0)