QUuid: add support for creating UUID v7

Thanks to Thiago for the more efficient way of using 12 bits of
the sub-milliseconds part of the timestamp. Previously I used the method
described in the RFC (value of type double multiplied by 4096).

Add a private helper to check the version since now there is a gap in
the Version enum values (UUID v6 isn't supported).

Drive-by, document Version::Sha1 enumerator.

[ChangeLog][QtCore][QUuid] Added support for creating UUID v7 as
described in
https://datatracker.ietf.org/doc/html/rfc9562#name-uuid-version-7

Fixes: QTBUG-130672
Change-Id: Idfea9fbb12a7f28e25b27b56a3b402799afb4864
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
This commit is contained in:
Ahmad Samir 2024-11-29 18:29:16 +02:00
parent 84a5f50c77
commit d89cef439f
5 changed files with 164 additions and 8 deletions

View File

@ -194,7 +194,7 @@ qt_internal_add_module(Core
plugin/qfactoryloader.cpp plugin/qfactoryloader_p.h plugin/qfactoryloader.cpp plugin/qfactoryloader_p.h
plugin/qplugin.h plugin/qplugin_p.h plugin/qplugin.h plugin/qplugin_p.h
plugin/qpluginloader.cpp plugin/qpluginloader.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/qcborarray.h
serialization/qcborcommon.cpp serialization/qcborcommon.h serialization/qcborcommon_p.h serialization/qcborcommon.cpp serialization/qcborcommon.h serialization/qcborcommon_p.h
serialization/qcbordiagnostic.cpp serialization/qcbordiagnostic.cpp

View File

@ -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 // 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.h"
#include "quuid_p.h"
#include "qcryptographichash.h" #include "qcryptographichash.h"
#include "qdatastream.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 \note In Qt versions prior to 6.8, this function took QByteArray, not
QByteArrayView. 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. 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. \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()
*/ */
/*! /*!
@ -590,6 +591,25 @@ QUuid QUuid::createUuidV5(QUuid ns, QByteArrayView baseData) noexcept
return createFromName(ns, baseData, QCryptographicHash::Sha1, 5); return createFromName(ns, baseData, QCryptographicHash::Sha1, 5);
} }
/*!
\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 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 specified by RFC 4122 section 4.1.2. See toRfc4122() for a further
@ -861,7 +881,9 @@ QDataStream &operator>>(QDataStream &s, QUuid &id)
\value Name Name-based, by using values from a name for all sections \value Name Name-based, by using values from a name for all sections
\value Md5 Alias for Name \value Md5 Alias for Name
\value Random Random-based, by using random numbers for all sections \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
*/ */
/*! /*!

View File

@ -48,7 +48,8 @@ public:
Md5 = 3, // 0 0 1 1 Md5 = 3, // 0 0 1 1
Name = Md5, Name = Md5,
Random = 4, // 0 1 0 0 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 { enum StringFormat {
@ -277,6 +278,26 @@ public:
return QUuid::createUuidV5(ns, qToByteArrayViewIgnoringNull(baseData.toUtf8())); 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) #if QT_CORE_REMOVED_SINCE(6, 9)
QUuid::Variant variant() const noexcept; QUuid::Variant variant() const noexcept;
QUuid::Version version() const noexcept; QUuid::Version version() const noexcept;
@ -296,7 +317,7 @@ public:
// Check the 4 MSB of data3 // Check the 4 MSB of data3
const Version ver = Version(data3 >> 12); const Version ver = Version(data3 >> 12);
// Check that variant() == DCE and version is in a valid range // 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 ver;
return VerUnknown; return VerUnknown;
} }

View File

@ -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

View File

@ -11,11 +11,15 @@
#include <qcoreapplication.h> #include <qcoreapplication.h>
#include <quuid.h> #include <quuid.h>
#include <QtCore/private/quuid_p.h>
#ifdef Q_OS_ANDROID #ifdef Q_OS_ANDROID
#include <QStandardPaths> #include <QStandardPaths>
#endif #endif
using namespace std::chrono_literals;
using namespace Qt::StringLiterals;
class tst_QUuid : public QObject class tst_QUuid : public QObject
{ {
Q_OBJECT Q_OBJECT
@ -35,6 +39,9 @@ private slots:
void id128(); void id128();
void uint128(); void uint128();
void createUuidV3OrV5(); void createUuidV3OrV5();
void createUuidV7_unique();
void createUuidV7_data();
void createUuidV7();
void check_QDataStream(); void check_QDataStream();
void isNull(); void isNull();
void equal(); void equal();
@ -323,6 +330,51 @@ void tst_QUuid::createUuidV3OrV5()
QT_TEST_EQUALITY_OPS(uuidD, QUuid::createUuidV5(uuidNS, QString("www.widgets.com")), true); 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() void tst_QUuid::check_QDataStream()
{ {
QUuid tmp; QUuid tmp;
@ -618,8 +670,8 @@ void tst_QUuid::versions_data()
QTest::newRow("DCE-inv-less-than-Time->unknown") 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(0, 0, 0b0000'1101'0101'1011, 0b1000'0000, 0, 0, 0, 0, 0, 0, 0)
<< QUuid::VerUnknown; << QUuid::VerUnknown;
QTest::newRow("DCE-inv-greater-than-Sha1->unknown") QTest::newRow("DCE-inv-greater-than-UnixEpoch->unknown")
<< QUuid(0, 0, 0b0111'1101'0101'1011, 0b1000'0000, 0, 0, 0, 0, 0, 0, 0) << QUuid(0, 0, 0b1000'1101'0101'1011, 0b1000'0000, 0, 0, 0, 0, 0, 0, 0)
<< QUuid::VerUnknown; << QUuid::VerUnknown;
QTest::newRow("NCS-Time->unknown") QTest::newRow("NCS-Time->unknown")
<< QUuid(0, 0, 0b0001'0000'0000'0000, 0b0100'0000, 0, 0, 0, 0, 0, 0, 0) << QUuid(0, 0, 0b0001'0000'0000'0000, 0b0100'0000, 0, 0, 0, 0, 0, 0, 0)