Commit 0e4198bb authored by Dominik Charousset's avatar Dominik Charousset

Add UUID class according to RFC 4122

parent 311d73f0
......@@ -158,6 +158,7 @@ set(CAF_CORE_SOURCES
src/uniform_type_info_map.cpp
src/uri.cpp
src/uri_builder.cpp
src/uuid.cpp
)
# -- list cpp files for caf-core-test ------------------------------------------
......@@ -279,6 +280,7 @@ set(CAF_CORE_TEST_SOURCES
test/typed_spawn.cpp
test/unit.cpp
test/uri.cpp
test/uuid.cpp
test/variant.cpp
)
......
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2020 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#pragma once
#include <array>
#include <cstdint>
#include <string>
#include "caf/byte.hpp"
#include "caf/detail/core_export.hpp"
#include "caf/fwd.hpp"
#include "caf/string_view.hpp"
namespace caf {
/// A universally unique identifier according to
/// [RFC 4122](https://tools.ietf.org/html/rfc4122). While this implementation
/// can read all UUID versions, it can only create random-generated ones.
class CAF_CORE_EXPORT uuid {
public:
using array_type = std::array<byte, 16>;
/// Creates the nil UUID with all 128 bits set to zero.
uuid() noexcept;
uuid(const uuid&) noexcept = default;
uuid& operator=(const uuid&) noexcept = default;
explicit uuid(const array_type& bytes) noexcept : bytes_(bytes) {
// nop
}
const array_type& bytes() const noexcept {
return bytes_;
}
array_type& bytes() noexcept {
return bytes_;
}
/// Returns `true` if this UUID is *not* `nil`, `false` otherwise.
/// A UUID is `nil` if all bits are 0.
operator bool() const noexcept;
/// Returns `true` if this UUID is `nil`, `false` otherwise.
bool operator!() const noexcept;
/// Denotes the variant (type) that determines the layout of the UUID. The
/// interpretation of all other bits in a UUID depend on this field.
enum variant_field {
reserved,
rfc4122,
microsoft,
};
/// Returns the variant (type) that determines the layout of the UUID.
/// @pre `not nil()`
variant_field variant() const noexcept;
/// Denotes the version, i.e., which algorithm was used to create this UUID.
enum version_field {
/// Time-based algorithm.
time_based = 1,
/// DCE security version with embedded POSIX UIDs.
dce_compatible = 2,
/// Name-based algorithm using MD5 hashing.
md5_based = 3,
/// Random or pseudo-random algorithm.
randomized = 4,
/// Name-based algorithm using SHA-1 hashing.
sha1_based = 5,
};
/// Returns the version (sub type) that identifies the algorithm used to
/// generate this UUID. The algorithms defined in RFC 4122 are:
///
/// 1. Time-based
/// 2. DCE security with embedded POSIX UIDs
/// 3. Name-based, using MD5 hashing
/// 4. Random or pseudo-random
/// 5. Name-based, using SHA-1 hashing
/// @pre `not nil()`
version_field version() const noexcept;
/// The 60-bit timestamp of a time-based UUID. Usually represents a count of
/// 100- nanosecond intervals since 00:00:00.00, 15 October 1582 in UTC.
/// @pre `version() == time_based`
uint64_t timestamp() const noexcept;
/// The 14-bit unsigned integer helps to avoid duplicates that could arise
/// when the clock is set backwards in time or if the node ID changes.
/// @pre `version() == time_based`
uint16_t clock_sequence() const noexcept;
/// 48-bit value, representing a network address (`time_based` UUIDs), a hash
/// (`md5_based` and `sha1_based` UUIDs), or a random bit sequence
/// (`randomized` UUIDs).
uint64_t node() const noexcept;
/// Creates a random UUID.
static uuid random() noexcept;
/// Creates a random UUID with a predefined seed.
static uuid random(unsigned seed) noexcept;
/// Convenience function for creating an UUID with all 128 bits set to zero.
static uuid nil() noexcept {
return uuid{};
}
/// Returns whether `parse` would produce a valid UUID.
static bool can_parse(string_view str) noexcept;
private:
/// Stores the fields, encoded as 16 octets:
///
/// ```
/// 0 1 2 3
/// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/// | time_low |
/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/// | time_mid | time_hi_and_version |
/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/// |clk_seq_hi_res | clk_seq_low | node (0-1) |
/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/// | node (2-5) |
/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/// ```
array_type bytes_;
};
/// @relates uuid
inline bool operator==(const uuid& x, const uuid& y) noexcept {
return x.bytes() == y.bytes();
}
/// @relates uuid
inline bool operator!=(const uuid& x, const uuid& y) noexcept {
return x.bytes() != y.bytes();
}
/// @relates uuid
CAF_CORE_EXPORT error parse(string_view str, uuid& dest);
/// @relates uuid
CAF_CORE_EXPORT std::string to_string(const uuid& x);
/// @relates uuid
CAF_CORE_EXPORT expected<uuid> make_uuid(string_view str);
} // namespace caf
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2020 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#include "caf/uuid.hpp"
#include <cctype>
#include <cstring>
#include <random>
#include "caf/detail/append_hex.hpp"
#include "caf/detail/network_order.hpp"
#include "caf/detail/parser/add_ascii.hpp"
#include "caf/expected.hpp"
#include "caf/make_message.hpp"
#include "caf/message.hpp"
#include "caf/parser_state.hpp"
namespace caf {
namespace {
byte nil_bytes[16];
} // namespace
uuid::uuid() noexcept {
memset(bytes_.data(), 0, bytes_.size());
}
uuid::operator bool() const noexcept {
return memcmp(bytes_.data(), nil_bytes, 16) != 0;
}
bool uuid::operator!() const noexcept {
return memcmp(bytes_.data(), nil_bytes, 16) == 0;
}
namespace {
// The following table lists the contents of the variant field, where the
// letter "x" indicates a "don't-care" value.
//
// ```
// Msb0 Msb1 Msb2 Description
//
// 0 x x Reserved, NCS backward compatibility.
//
// 1 0 x The variant specified in this document.
//
// 1 1 0 Reserved, Microsoft Corporation backward
// compatibility
//
// 1 1 1 Reserved for future definition.
// ```
uuid::variant_field variant_table[] = {
uuid::reserved, // 0 0 0
uuid::reserved, // 0 0 1
uuid::reserved, // 0 1 0
uuid::reserved, // 0 1 1
uuid::rfc4122, // 1 0 0
uuid::rfc4122, // 1 0 1
uuid::microsoft, // 1 1 0
uuid::reserved, // 1 1 1
};
} // namespace
uuid::variant_field uuid::variant() const noexcept {
return variant_table[to_integer<size_t>(bytes_[8]) >> 5];
}
uuid::version_field uuid::version() const noexcept {
return static_cast<version_field>(to_integer<int>(bytes_[6]) >> 4);
}
uint64_t uuid::timestamp() const noexcept {
// Assemble octets like this (L = low, M = mid, H = high):
// 0H HH MM MM LL LL LL LL
byte ts[8];
memcpy(ts + 4, bytes_.data() + 0, 4);
memcpy(ts + 2, bytes_.data() + 4, 2);
memcpy(ts + 0, bytes_.data() + 6, 2);
ts[0] &= byte{0x0F};
uint64_t result;
memcpy(&result, ts, 8);
// UUIDs are stored in network byte order.
return detail::from_network_order(result);
}
uint16_t uuid::clock_sequence() const noexcept {
// Read clk_seq_(hi|low) fields and strip the variant bits.
byte cs[2];
memcpy(cs, bytes_.data() + 8, 2);
cs[0] &= byte{0x3F};
uint16_t result;
memcpy(&result, cs, 2);
// UUIDs are stored in network byte order.
return detail::from_network_order(result);
}
uint64_t uuid::node() const noexcept {
byte n[8];
memset(n, 0, 2);
memcpy(n + 2, bytes_.data() + 10, 6);
uint64_t result;
memcpy(&result, n, 8);
return detail::from_network_order(result);
}
namespace {
enum parse_result {
valid_uuid,
malformed_uuid,
invalid_variant,
invalid_version,
};
parse_result parse_impl(string_parser_state& ps, uuid::array_type& x) noexcept {
// Create function object for writing into x.
auto read_byte = [pos{x.data()}](auto& ps) mutable {
uint8_t value = 0;
auto c1 = ps.current();
if (!isxdigit(c1) || !detail::parser::add_ascii<16>(value, c1))
return false;
auto c2 = ps.next();
if (!isxdigit(c2) || !detail::parser::add_ascii<16>(value, c2))
return false;
ps.next();
*pos++ = static_cast<byte>(value);
return true;
};
// Parse the formatted string.
ps.skip_whitespaces();
for (int i = 0; i < 4; ++i)
if (!read_byte(ps))
return malformed_uuid;
for (int block_size : {2, 2, 2, 6}) {
if (!ps.consume_strict('-'))
return malformed_uuid;
for (int i = 0; i < block_size; ++i)
if (!read_byte(ps))
return malformed_uuid;
}
ps.skip_whitespaces();
if (!ps.at_end())
return malformed_uuid;
// Check whether the bytes form a valid UUID.
if (memcmp(x.data(), nil_bytes, 16) == 0)
return valid_uuid;
if (auto subtype = to_integer<long>(x[6]) >> 4; subtype == 0 || subtype > 5)
return invalid_version;
return valid_uuid;
}
} // namespace
uuid uuid::random(unsigned seed) noexcept {
// Algorithm as defined in RFC4122:
// - Set the two most significant bits (bits 6 and 7) of the
// clock_seq_hi_and_reserved to zero and one, respectively.
// - Set the four most significant bits (bits 12 through 15) of the
// time_hi_and_version field to the 4-bit version number from Section 4.1.3.
// - Set all the other bits to randomly (or pseudo-randomly) chosen values.
// However, we first fill all bits with random data and then fix the variant
// and version fields. It's more straightforward that way.
std::minstd_rand engine{seed};
std::uniform_int_distribution<> rng{0, 255};
uuid result;
for (size_t index = 0; index < 16; ++index)
result.bytes_[index] = static_cast<byte>(rng(engine));
result.bytes_[6] = (result.bytes_[6] & byte{0x0F}) | byte{0x50};
result.bytes_[8] = (result.bytes_[8] & byte{0x3F}) | byte{0x80};
return result;
}
uuid uuid::random() noexcept {
std::random_device rd;
return random(rd());
}
bool uuid::can_parse(string_view str) noexcept {
array_type bytes;
string_parser_state ps{str.begin(), str.end()};
return parse_impl(ps, bytes) == valid_uuid;
}
error parse(string_view str, uuid& dest) {
string_parser_state ps{str.begin(), str.end()};
switch (parse_impl(ps, dest.bytes())) {
case valid_uuid:
return none;
default:
return make_error(ps.code);
case invalid_version:
return make_error(pec::invalid_argument,
"invalid version in variant field");
}
}
std::string to_string(const uuid& x) {
static constexpr auto lowercase = detail::hex_format::lowercase;
using detail::append_hex;
std::string result;
append_hex<lowercase>(result, x.bytes().data() + 0, 4);
result += '-';
append_hex<lowercase>(result, x.bytes().data() + 4, 2);
result += '-';
append_hex<lowercase>(result, x.bytes().data() + 6, 2);
result += '-';
append_hex<lowercase>(result, x.bytes().data() + 8, 2);
result += '-';
append_hex<lowercase>(result, x.bytes().data() + 10, 6);
return result;
}
expected<uuid> make_uuid(string_view str) {
uuid result;
if (auto err = parse(str, result))
return err;
return result;
}
} // namespace caf
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2020 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#define CAF_SUITE uuid
#include "caf/uuid.hpp"
#include "caf/test/dsl.hpp"
using namespace caf;
namespace {
uuid operator"" _uuid(const char* cstr, size_t cstr_len) {
if (cstr_len != 36)
CAF_FAIL("malformed test input");
std::string str{cstr, cstr_len};
if (str[8] != '-' || str[13] != '-' || str[18] != '-' || str[23] != '-')
CAF_FAIL("malformed test input");
str.erase(std::remove(str.begin(), str.end(), '-'), str.end());
if (str.size() != 32)
CAF_FAIL("malformed test input");
uuid result;
for (size_t index = 0; index < 16; ++index) {
auto input_index = index * 2;
char buf[] = {str[input_index], str[input_index + 1], '\0'};
result.bytes().at(index) = static_cast<byte>(std::stoi(buf, nullptr, 16));
}
return result;
}
struct fixture {
uuid nil; // 00000000-0000-0000-0000-000000000000
// A couple of UUIDs for version 1.
uuid v1[3] = {
"cbba341a-6ceb-11ea-bc55-0242ac130003"_uuid,
"cbba369a-6ceb-11ea-bc55-0242ac130003"_uuid,
"cbba38fc-6ceb-11ea-bc55-0242ac130003"_uuid,
};
// A couple of UUIDs for version 4.
uuid v4[3] = {
"2ee4ded7-69c0-4dd6-876d-02e446b21784"_uuid,
"934a33b6-7f0c-4d70-9749-5ad4292358dd"_uuid,
"bf761f7c-00f2-4161-855e-e286cfa63c11"_uuid,
};
uuid x;
};
} // namespace
CAF_TEST_FIXTURE_SCOPE(uuid_tests, fixture)
CAF_TEST(default generated UUIDs have all 128 bits set to zero) {
uuid nil;
CAF_CHECK(!nil);
auto zero = [](byte x) { return to_integer<int>(x) == 0; };
CAF_CHECK(std::all_of(nil.bytes().begin(), nil.bytes().end(), zero));
CAF_CHECK(nil == uuid::nil());
}
CAF_TEST(UUIDs print in 4 2 2 2 6 format) {
CAF_CHECK_EQUAL(to_string(nil), "00000000-0000-0000-0000-000000000000");
CAF_CHECK_EQUAL(to_string(v1[0]), "cbba341a-6ceb-11ea-bc55-0242ac130003");
CAF_CHECK_EQUAL(to_string(v1[1]), "cbba369a-6ceb-11ea-bc55-0242ac130003");
CAF_CHECK_EQUAL(to_string(v1[2]), "cbba38fc-6ceb-11ea-bc55-0242ac130003");
}
CAF_TEST(make_uuid parses strings in 4 2 2 2 6 format) {
CAF_CHECK_EQUAL(make_uuid("00000000-0000-0000-0000-000000000000"), nil);
CAF_CHECK_EQUAL(make_uuid("cbba341a-6ceb-11ea-bc55-0242ac130003"), v1[0]);
CAF_CHECK_EQUAL(make_uuid("cbba369a-6ceb-11ea-bc55-0242ac130003"), v1[1]);
CAF_CHECK_EQUAL(make_uuid("cbba38fc-6ceb-11ea-bc55-0242ac130003"), v1[2]);
}
CAF_TEST(make_uuid rejects strings with invalid variant or version values) {
CAF_CHECK(!uuid::can_parse("cbba341a-6ceb-81ea-bc55-0242ac130003"));
CAF_CHECK(!uuid::can_parse("cbba369a-6ceb-F1ea-bc55-0242ac130003"));
CAF_CHECK(!uuid::can_parse("cbba38fc-6ceb-01ea-bc55-0242ac130003"));
CAF_CHECK_EQUAL(make_uuid("cbba341a-6ceb-81ea-bc55-0242ac130003"),
pec::invalid_argument);
CAF_CHECK_EQUAL(make_uuid("cbba369a-6ceb-F1ea-bc55-0242ac130003"),
pec::invalid_argument);
CAF_CHECK_EQUAL(make_uuid("cbba38fc-6ceb-01ea-bc55-0242ac130003"),
pec::invalid_argument);
}
#define WITH(uuid_str) \
x = uuid_str##_uuid; \
for (int dummy = 0; dummy < 1; ++dummy)
CAF_TEST(version 1 defines UUIDs that are based on time) {
CAF_CHECK_EQUAL(v1[0].version(), uuid::time_based);
CAF_CHECK_EQUAL(v1[1].version(), uuid::time_based);
CAF_CHECK_EQUAL(v1[2].version(), uuid::time_based);
CAF_CHECK_NOT_EQUAL(v4[0].version(), uuid::time_based);
CAF_CHECK_NOT_EQUAL(v4[1].version(), uuid::time_based);
CAF_CHECK_NOT_EQUAL(v4[2].version(), uuid::time_based);
WITH("00000001-0000-1000-8122-334455667788") {
CAF_CHECK_EQUAL(x.variant(), uuid::rfc4122);
CAF_CHECK_EQUAL(x.version(), uuid::time_based);
CAF_CHECK_EQUAL(x.timestamp(), 0x0000000000000001ull);
CAF_CHECK_EQUAL(x.clock_sequence(), 0x0122u);
CAF_CHECK_EQUAL(x.node(), 0x334455667788ull);
}
WITH("00000001-0001-1000-8122-334455667788") {
CAF_CHECK_EQUAL(x.variant(), uuid::rfc4122);
CAF_CHECK_EQUAL(x.version(), uuid::time_based);
CAF_CHECK_EQUAL(x.timestamp(), 0x0000000100000001ull);
CAF_CHECK_EQUAL(x.clock_sequence(), 0x0122u);
CAF_CHECK_EQUAL(x.node(), 0x334455667788ull);
}
WITH("00000001-0001-1001-8122-334455667788") {
CAF_CHECK_EQUAL(x.variant(), uuid::rfc4122);
CAF_CHECK_EQUAL(x.version(), uuid::time_based);
CAF_CHECK_EQUAL(x.timestamp(), 0x0001000100000001ull);
CAF_CHECK_EQUAL(x.clock_sequence(), 0x0122u);
CAF_CHECK_EQUAL(x.node(), 0x334455667788ull);
}
WITH("ffffffff-ffff-1fff-bfff-334455667788") {
CAF_CHECK_EQUAL(x.variant(), uuid::rfc4122);
CAF_CHECK_EQUAL(x.version(), uuid::time_based);
CAF_CHECK_EQUAL(x.timestamp(), 0x0FFFFFFFFFFFFFFFull);
CAF_CHECK_EQUAL(x.clock_sequence(), 0x3FFFu);
CAF_CHECK_EQUAL(x.node(), 0x334455667788ull);
}
}
CAF_TEST_FIXTURE_SCOPE_END()
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment