Uuid: add default ctor and Parse() (#5935)
* Uuid: add default ctor and Parse() * Fix merge, integrate better * nano-optimization for IsNil() * Changelog * Pedantism * GCC fix * GCC fix (include <stdexcept>) * PR feedback * Update sdk/core/azure-core/test/ut/uuid_test.cpp Co-authored-by: Rick Winter <rick.winter@microsoft.com> * Comments for bad tests * Add doxygen comments * One more constexpr, and clang-format --------- Co-authored-by: Anton Kolesnyk <antkmsft@users.noreply.github.com> Co-authored-by: Rick Winter <rick.winter@microsoft.com>
This commit is contained in:
Родитель
227ae0ea0b
Коммит
ee0c7565df
|
@ -5,11 +5,14 @@
|
|||
### Features Added
|
||||
|
||||
- Request logs to now include the `accept-range`, `content-range`, `range`, `WWW-Authenticate`, `x-ms-date`, `x-ms-error-code`, `x-ms-range`, and `x-ms-version` headers.
|
||||
- Added default constructor, `Parse()`, and equality comparison operators to `Azure::Core::Uuid`.
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
### Bugs Fixed
|
||||
|
||||
- `Azure::Core::Uuid::ToString()` is now `const`.
|
||||
|
||||
### Other Changes
|
||||
|
||||
## 1.14.0-beta.1 (2024-08-01)
|
||||
|
|
|
@ -8,12 +8,8 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include "azure/core/platform.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <cstdint> // defines std::uint8_t
|
||||
#include <cstring>
|
||||
#include <stdint.h> // deprecated, defines uint8_t in global namespace. TODO: Remove in the future when references to uint8_t and friends are removed.
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
namespace Azure { namespace Core {
|
||||
|
@ -21,19 +17,31 @@ namespace Azure { namespace Core {
|
|||
* @brief Universally unique identifier.
|
||||
*/
|
||||
class Uuid final {
|
||||
public:
|
||||
/**
|
||||
* @brief Represents a byte array where the UUID value can be stored.
|
||||
*
|
||||
*/
|
||||
using ValueArray = std::array<std::uint8_t, 16>;
|
||||
|
||||
private:
|
||||
static constexpr size_t UuidSize = 16;
|
||||
|
||||
std::array<uint8_t, UuidSize> m_uuid{};
|
||||
ValueArray m_uuid{};
|
||||
|
||||
private:
|
||||
Uuid(uint8_t const uuid[UuidSize]) { std::memcpy(m_uuid.data(), uuid, UuidSize); }
|
||||
constexpr Uuid(ValueArray const& uuid) : m_uuid(uuid) {}
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Constructs a Nil UUID (`00000000-0000-0000-0000-000000000000`).
|
||||
*
|
||||
*/
|
||||
// Nil UUID, per RFC9562, consists of all zeros:
|
||||
// https://www.rfc-editor.org/rfc/rfc9562.html#name-nil-uuid
|
||||
constexpr explicit Uuid() : m_uuid{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} {}
|
||||
|
||||
/**
|
||||
* @brief Gets Uuid as a string.
|
||||
* @details A string is in canonical format (8-4-4-4-12 lowercase hex and dashes only).
|
||||
* @details A string is in canonical format (`8-4-4-4-12` lowercase hex and dashes only).
|
||||
*/
|
||||
std::string ToString() const;
|
||||
|
||||
|
@ -42,7 +50,7 @@ namespace Azure { namespace Core {
|
|||
* representation of the Uuid.
|
||||
* @returns An array with the binary representation of the Uuid.
|
||||
*/
|
||||
std::array<uint8_t, UuidSize> const& AsArray() const { return m_uuid; }
|
||||
constexpr ValueArray const& AsArray() const { return m_uuid; }
|
||||
|
||||
/**
|
||||
* @brief Creates a new random UUID.
|
||||
|
@ -54,6 +62,60 @@ namespace Azure { namespace Core {
|
|||
* @brief Construct a Uuid from an existing UUID represented as an array of bytes.
|
||||
* @details Creates a Uuid from a UUID created in an external scope.
|
||||
*/
|
||||
static Uuid CreateFromArray(std::array<uint8_t, UuidSize> const& uuid);
|
||||
static constexpr Uuid CreateFromArray(ValueArray const& uuid) { return Uuid{uuid}; }
|
||||
|
||||
/**
|
||||
* @brief Construct a Uuid by parsing its representation.
|
||||
* @param s a string in `8-4-4-4-12` hex characters format.
|
||||
* @throw `std::invalid_argument` if \p s cannot be parsed.
|
||||
*/
|
||||
static Uuid Parse(std::string const& s);
|
||||
|
||||
/**
|
||||
* @brief Compares with another instance of Uuid for equality.
|
||||
* @param other another instance of Uuid.
|
||||
* @return `true` if values of two Uuids are equal, `false` otherwise.
|
||||
*
|
||||
*/
|
||||
constexpr bool operator==(Uuid const& other) const
|
||||
{
|
||||
// std::array::operator==() is not a constexpr until C++20
|
||||
for (size_t i = 0; i < m_uuid.size(); ++i)
|
||||
{
|
||||
if (m_uuid[i] != other.m_uuid[i])
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Compares with another instance of Uuid for inequality.
|
||||
* @param other another instance of Uuid.
|
||||
* @return `true` if values of two Uuids are not equal, `false` otherwise.
|
||||
*
|
||||
*/
|
||||
constexpr bool operator!=(Uuid const& other) const { return !(*this == other); }
|
||||
|
||||
/**
|
||||
* @brief Checks if the value represents a Nil UUID (`00000000-0000-0000-0000-000000000000`).
|
||||
*
|
||||
*/
|
||||
constexpr bool IsNil() const
|
||||
{
|
||||
// Nil UUID, per RFC9562, consists of all zeros:
|
||||
// https://www.rfc-editor.org/rfc/rfc9562.html#name-nil-uuid
|
||||
for (size_t i = 0; i < m_uuid.size(); ++i)
|
||||
{
|
||||
if (m_uuid[i] != 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}} // namespace Azure::Core
|
||||
|
|
|
@ -3,10 +3,15 @@
|
|||
|
||||
#include "azure/core/uuid.hpp"
|
||||
|
||||
#include "azure/core/azure_assert.hpp"
|
||||
#include "azure/core/internal/strings.hpp"
|
||||
#include "azure/core/platform.hpp"
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <random>
|
||||
#include <stdexcept>
|
||||
#include <type_traits>
|
||||
|
||||
#if defined(AZ_PLATFORM_POSIX)
|
||||
#include <thread>
|
||||
|
@ -18,84 +23,158 @@ static thread_local std::mt19937_64 randomGenerator(std::random_device{}());
|
|||
} // namespace
|
||||
#endif
|
||||
|
||||
using Azure::Core::_internal::StringExtensions;
|
||||
|
||||
namespace {
|
||||
static char ByteToHexChar(uint8_t byte)
|
||||
/*
|
||||
"00000000-0000-0000-0000-000000000000"
|
||||
^ ^ ^ ^
|
||||
000000000011111111112222222222333333
|
||||
012345678901234567890123456789012345
|
||||
\______________ = 36 ______________/
|
||||
*/
|
||||
constexpr size_t UuidStringLength = 36;
|
||||
constexpr bool IsDashIndex(size_t i) { return i == 8 || i == 13 || i == 18 || i == 23; }
|
||||
|
||||
constexpr std::uint8_t HexToNibble(char c) // does not check for errors
|
||||
{
|
||||
if (byte <= 9)
|
||||
if (c >= 'a')
|
||||
{
|
||||
return '0' + byte;
|
||||
return 10 + (c - 'a');
|
||||
}
|
||||
|
||||
AZURE_ASSERT_MSG(
|
||||
byte >= 10 && byte <= 15,
|
||||
"It is expected, for a valid Uuid, to have byte values, where each of the two nibbles fit "
|
||||
"into a hexadecimal character");
|
||||
if (c >= 'A')
|
||||
{
|
||||
return 10 + (c - 'A');
|
||||
}
|
||||
|
||||
return 'a' + (byte - 10);
|
||||
return c - '0';
|
||||
}
|
||||
|
||||
constexpr char NibbleToHex(std::uint8_t nibble) // does not check for errors
|
||||
{
|
||||
if (nibble <= 9)
|
||||
{
|
||||
return '0' + nibble;
|
||||
}
|
||||
|
||||
return 'a' + (nibble - 10);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace Azure { namespace Core {
|
||||
std::string Uuid::ToString() const
|
||||
{
|
||||
std::string s(36, '-');
|
||||
std::string s(UuidStringLength, '-');
|
||||
|
||||
for (size_t i = 0, j = 0; j < s.size() && i < UuidSize; i++)
|
||||
for (size_t bi = 0, si = 0; bi < m_uuid.size(); ++bi)
|
||||
{
|
||||
if (i == 4 || i == 6 || i == 8 || i == 10)
|
||||
if (IsDashIndex(si))
|
||||
{
|
||||
j++; // Add hyphens at the appropriate places
|
||||
++si;
|
||||
}
|
||||
|
||||
uint8_t highNibble = (m_uuid[i] >> 4) & 0x0F;
|
||||
uint8_t lowNibble = m_uuid[i] & 0x0F;
|
||||
s[j++] = ByteToHexChar(highNibble);
|
||||
s[j++] = ByteToHexChar(lowNibble);
|
||||
assert((si < UuidStringLength) && (si + 1 < UuidStringLength));
|
||||
|
||||
const std::uint8_t b = m_uuid[bi];
|
||||
s[si] = NibbleToHex((b >> 4) & 0x0F);
|
||||
s[si + 1] = NibbleToHex(b & 0x0F);
|
||||
si += 2;
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
Uuid Uuid::Parse(std::string const& s)
|
||||
{
|
||||
bool parseError = false;
|
||||
Uuid result;
|
||||
if (s.size() != UuidStringLength)
|
||||
{
|
||||
parseError = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
for (size_t si = 0, bi = 0; si < UuidStringLength; ++si)
|
||||
{
|
||||
const auto c = s[si];
|
||||
if (IsDashIndex(si))
|
||||
{
|
||||
if (c != '-')
|
||||
{
|
||||
parseError = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
assert(si + 1 < UuidStringLength && bi < result.m_uuid.size());
|
||||
|
||||
const auto c2 = s[si + 1];
|
||||
if (!StringExtensions::IsHexDigit(c) || !StringExtensions::IsHexDigit(c2))
|
||||
{
|
||||
parseError = true;
|
||||
break;
|
||||
}
|
||||
|
||||
result.m_uuid[bi] = (HexToNibble(c) << 4) | HexToNibble(c2);
|
||||
++si;
|
||||
++bi;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return parseError ? throw std::invalid_argument(
|
||||
"Error parsing Uuid: '" + s
|
||||
+ "' is not in the '00112233-4455-6677-8899-aAbBcCdDeEfF' format.")
|
||||
: result;
|
||||
}
|
||||
|
||||
Uuid Uuid::CreateUuid()
|
||||
{
|
||||
uint8_t uuid[UuidSize] = {};
|
||||
Uuid result{};
|
||||
|
||||
// Using RngResultType and RngResultSize to highlight the places where the same type is used.
|
||||
using RngResultType = std::uint32_t;
|
||||
static_assert(sizeof(RngResultType) == 4, "sizeof(RngResultType) must be 4.");
|
||||
constexpr size_t RngResultSize = 4;
|
||||
|
||||
#if defined(AZ_PLATFORM_WINDOWS)
|
||||
std::random_device rd;
|
||||
|
||||
static_assert(
|
||||
std::is_same<RngResultType, decltype(rd())>::value,
|
||||
"random_device::result_type must be of RngResultType.");
|
||||
#else
|
||||
std::uniform_int_distribution<uint32_t> distribution;
|
||||
std::uniform_int_distribution<RngResultType> distribution;
|
||||
#endif
|
||||
|
||||
for (size_t i = 0; i < UuidSize; i += 4)
|
||||
for (size_t i = 0; i < result.m_uuid.size(); i += RngResultSize)
|
||||
{
|
||||
#if defined(AZ_PLATFORM_WINDOWS)
|
||||
const uint32_t x = rd();
|
||||
const RngResultType x = rd();
|
||||
#else
|
||||
const uint32_t x = distribution(randomGenerator);
|
||||
const RngResultType x = distribution(randomGenerator);
|
||||
#endif
|
||||
std::memcpy(uuid + i, &x, 4);
|
||||
std::memcpy(result.m_uuid.data() + i, &x, RngResultSize);
|
||||
}
|
||||
|
||||
// The variant field consists of a variable number of the most significant bits of octet 8 of
|
||||
// the UUID.
|
||||
// https://www.rfc-editor.org/rfc/rfc4122.html#section-4.1.1
|
||||
// For setting the variant to conform to RFC4122, the high bits need to be of the form 10xx,
|
||||
// https://www.rfc-editor.org/rfc/rfc9562.html#name-variant-field
|
||||
// For setting the variant to conform to RFC9562, the high bits need to be of the form 10xx,
|
||||
// which means the hex value of the first 4 bits can only be either 8, 9, A|a, B|b. The 0-7
|
||||
// values are reserved for backward compatibility. The C|c, D|d values are reserved for
|
||||
// Microsoft, and the E|e, F|f values are reserved for future use.
|
||||
// Therefore, we have to zero out the two high bits, and then set the highest bit to 1.
|
||||
uuid[8] = (uuid[8] & 0x3F) | 0x80;
|
||||
result.m_uuid.data()[8] = (result.m_uuid.data()[8] & 0x3F) | 0x80;
|
||||
|
||||
constexpr uint8_t version = 4; // Version 4: Pseudo-random number
|
||||
{
|
||||
// https://www.rfc-editor.org/rfc/rfc9562.html#name-version-field
|
||||
constexpr std::uint8_t Version = 4; // Version 4: Pseudo-random number
|
||||
result.m_uuid.data()[6] = (result.m_uuid.data()[6] & 0xF) | (Version << 4);
|
||||
}
|
||||
|
||||
uuid[6] = (uuid[6] & 0xF) | (version << 4);
|
||||
|
||||
return Uuid(uuid);
|
||||
return result;
|
||||
}
|
||||
|
||||
Uuid Uuid::CreateFromArray(std::array<uint8_t, UuidSize> const& uuid)
|
||||
{
|
||||
Uuid rv{uuid.data()};
|
||||
return rv;
|
||||
}
|
||||
|
||||
}} // namespace Azure::Core
|
||||
|
|
|
@ -127,3 +127,81 @@ TEST(Uuid, validChars)
|
|||
uuidKey,
|
||||
4);
|
||||
}
|
||||
|
||||
TEST(Uuid, nilAndDefault)
|
||||
{
|
||||
Uuid uuid;
|
||||
ASSERT_TRUE(uuid.IsNil());
|
||||
ASSERT_EQ(uuid.ToString(), "00000000-0000-0000-0000-000000000000");
|
||||
ASSERT_EQ(uuid, Uuid{});
|
||||
ASSERT_EQ(uuid.AsArray(), Uuid::ValueArray({0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}));
|
||||
}
|
||||
|
||||
TEST(Uuid, parse)
|
||||
{
|
||||
Uuid uuid1 = Uuid::Parse("00112233-4455-6677-8899-aAbBcCdDeEfF");
|
||||
|
||||
ASSERT_FALSE(uuid1.IsNil());
|
||||
ASSERT_EQ(uuid1.ToString(), "00112233-4455-6677-8899-aabbccddeeff");
|
||||
ASSERT_NE(uuid1, Uuid{});
|
||||
ASSERT_EQ(
|
||||
uuid1.AsArray(),
|
||||
Uuid::ValueArray(
|
||||
{0x00,
|
||||
0x11,
|
||||
0x22,
|
||||
0x33,
|
||||
0x44,
|
||||
0x55,
|
||||
0x66,
|
||||
0x77,
|
||||
0x88,
|
||||
0x99,
|
||||
0xAA,
|
||||
0xBB,
|
||||
0xCC,
|
||||
0xDD,
|
||||
0xEE,
|
||||
0xFF}));
|
||||
|
||||
// Empty string
|
||||
ASSERT_THROW(Uuid::Parse(""), std::invalid_argument);
|
||||
|
||||
// Special characters - make sure we're not treating them as byte array
|
||||
ASSERT_THROW(Uuid::Parse("\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a\a"), std::invalid_argument);
|
||||
|
||||
// Spaces before, after, and both.
|
||||
ASSERT_THROW(Uuid::Parse("00000000-0000-0000-0000-000000000000 "), std::invalid_argument);
|
||||
ASSERT_THROW(Uuid::Parse(" 00000000-0000-0000-0000-000000000000"), std::invalid_argument);
|
||||
ASSERT_THROW(Uuid::Parse("00000000-0000-0000-0000-00000000000"), std::invalid_argument);
|
||||
|
||||
// Valid characters, but in places where dashes should be
|
||||
ASSERT_THROW(Uuid::Parse("00000000a0000-0000-0000-000000000000"), std::invalid_argument);
|
||||
ASSERT_THROW(Uuid::Parse("00000000-0000a0000-0000-000000000000"), std::invalid_argument);
|
||||
ASSERT_THROW(Uuid::Parse("00000000-0000-0000a0000-000000000000"), std::invalid_argument);
|
||||
ASSERT_THROW(Uuid::Parse("00000000-0000-0000-0000a000000000000"), std::invalid_argument);
|
||||
|
||||
// Another ToString() formats
|
||||
// (https://learn.microsoft.com/dotnet/api/system.guid.tostring?view=net-8.0)
|
||||
ASSERT_THROW(Uuid::Parse("00000000000000000000000000000000"), std::invalid_argument);
|
||||
ASSERT_THROW(Uuid::Parse("{00000000-0000-0000-0000-000000000000}"), std::invalid_argument);
|
||||
ASSERT_THROW(Uuid::Parse("(00000000-0000-0000-0000-000000000000)"), std::invalid_argument);
|
||||
ASSERT_THROW(
|
||||
Uuid::Parse("{0x00000000,0x0000,0x0000,{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}}"),
|
||||
std::invalid_argument);
|
||||
|
||||
// Correct length, invalid characters
|
||||
ASSERT_THROW(Uuid::Parse("o000000000-0000-0000-0000-000000000000"), std::invalid_argument);
|
||||
ASSERT_THROW(Uuid::Parse("0000000000-0000-0000-0000-00000000000o"), std::invalid_argument);
|
||||
|
||||
// Incorrect length, incorrect caracters
|
||||
ASSERT_THROW(Uuid::Parse("00000000-0000-0000-0000-0000000000G"), std::invalid_argument);
|
||||
|
||||
// Less dashes
|
||||
ASSERT_THROW(Uuid::Parse("00000000-000000000000000000000000"), std::invalid_argument);
|
||||
ASSERT_THROW(Uuid::Parse("00000000-0000-00000000000000000000"), std::invalid_argument);
|
||||
ASSERT_THROW(Uuid::Parse("00000000-0000-0000-0000000000000000"), std::invalid_argument);
|
||||
|
||||
// Just a string of text
|
||||
ASSERT_THROW(Uuid::Parse("The quick brown fox jumps over the lazy dog."), std::invalid_argument);
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче