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:
parent
84a5f50c77
commit
d89cef439f
@ -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
|
||||
|
@ -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()
|
||||
*/
|
||||
|
||||
/*!
|
||||
@ -590,6 +591,25 @@ QUuid QUuid::createUuidV5(QUuid ns, QByteArrayView baseData) noexcept
|
||||
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
|
||||
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 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
|
||||
*/
|
||||
|
||||
/*!
|
||||
|
@ -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;
|
||||
}
|
||||
|
61
src/corelib/plugin/quuid_p.h
Normal file
61
src/corelib/plugin/quuid_p.h
Normal 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
|
@ -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)
|
||||
|
Loading…
x
Reference in New Issue
Block a user