Unverified Commit 5310fbaf authored by Dominik Charousset's avatar Dominik Charousset Committed by GitHub

Merge pull request #1074

Add new UUID class
parents f72da4d9 f0469b05
...@@ -40,6 +40,8 @@ is based on [Keep a Changelog](https://keepachangelog.com). ...@@ -40,6 +40,8 @@ is based on [Keep a Changelog](https://keepachangelog.com).
- Actors can now `monitor` and `demonitor` CAF nodes (#1042). Monitoring a CAF - Actors can now `monitor` and `demonitor` CAF nodes (#1042). Monitoring a CAF
node causes the actor system to send a `node_down_msg` to the observer when node causes the actor system to send a `node_down_msg` to the observer when
losing connection to the monitored node. losing connection to the monitored node.
- In preparation of potential future API additions/changes, CAF now includes an
RFC4122-compliant `uuid` class.
### Changed ### Changed
...@@ -77,6 +79,11 @@ is based on [Keep a Changelog](https://keepachangelog.com). ...@@ -77,6 +79,11 @@ is based on [Keep a Changelog](https://keepachangelog.com).
argument). Furthermore, the state class can now provide a `make_behavior` argument). Furthermore, the state class can now provide a `make_behavior`
member function to initialize the actor (this has no effect for function-based member function to initialize the actor (this has no effect for function-based
actors). actors).
- In order to stay more consistent with naming conventions of the standard
library, we have renamed some values of the `pec` enumeration:
+ `illegal_escape_sequence` => `invalid_escape_sequence`
+ `illegal_argument` => `invalid_argument`
+ `illegal_category` => `invalid_category`
### Removed ### Removed
......
...@@ -56,7 +56,6 @@ set(CAF_CORE_SOURCES ...@@ -56,7 +56,6 @@ set(CAF_CORE_SOURCES
src/deserializer.cpp src/deserializer.cpp
src/detail/abstract_worker.cpp src/detail/abstract_worker.cpp
src/detail/abstract_worker_hub.cpp src/detail/abstract_worker_hub.cpp
src/detail/append_hex.cpp
src/detail/append_percent_encoded.cpp src/detail/append_percent_encoded.cpp
src/detail/behavior_impl.cpp src/detail/behavior_impl.cpp
src/detail/behavior_stack.cpp src/detail/behavior_stack.cpp
...@@ -159,6 +158,7 @@ set(CAF_CORE_SOURCES ...@@ -159,6 +158,7 @@ set(CAF_CORE_SOURCES
src/uniform_type_info_map.cpp src/uniform_type_info_map.cpp
src/uri.cpp src/uri.cpp
src/uri_builder.cpp src/uri_builder.cpp
src/uuid.cpp
) )
# -- list cpp files for caf-core-test ------------------------------------------ # -- list cpp files for caf-core-test ------------------------------------------
...@@ -280,6 +280,7 @@ set(CAF_CORE_TEST_SOURCES ...@@ -280,6 +280,7 @@ set(CAF_CORE_TEST_SOURCES
test/typed_spawn.cpp test/typed_spawn.cpp
test/unit.cpp test/unit.cpp
test/uri.cpp test/uri.cpp
test/uuid.cpp
test/variant.cpp test/variant.cpp
) )
......
...@@ -118,6 +118,7 @@ ...@@ -118,6 +118,7 @@
#include "caf/typed_response_promise.hpp" #include "caf/typed_response_promise.hpp"
#include "caf/unit.hpp" #include "caf/unit.hpp"
#include "caf/upstream_msg.hpp" #include "caf/upstream_msg.hpp"
#include "caf/uuid.hpp"
#include "caf/decorator/sequencer.hpp" #include "caf/decorator/sequencer.hpp"
......
...@@ -21,25 +21,41 @@ ...@@ -21,25 +21,41 @@
#include <string> #include <string>
#include <type_traits> #include <type_traits>
#include "caf/byte.hpp"
#include "caf/detail/core_export.hpp" #include "caf/detail/core_export.hpp"
#include "caf/detail/type_traits.hpp" #include "caf/detail/type_traits.hpp"
namespace caf::detail { namespace caf::detail {
CAF_CORE_EXPORT void enum class hex_format {
append_hex(std::string& result, const uint8_t* xs, size_t n); uppercase,
lowercase,
template <class T> };
enable_if_t<has_data_member<T>::value>
append_hex(std::string& result, const T& x) { template <hex_format format = hex_format::uppercase>
return append_hex(result, reinterpret_cast<const uint8_t*>(x.data()), void append_hex(std::string& result, const void* vptr, size_t n) {
x.size()); if (n == 0) {
result += "00";
return;
}
auto xs = reinterpret_cast<const uint8_t*>(vptr);
const char* tbl;
if constexpr (format == hex_format::uppercase)
tbl = "0123456789ABCDEF";
else
tbl = "0123456789abcdef";
char buf[3] = {0, 0, 0};
for (size_t i = 0; i < n; ++i) {
auto c = xs[i];
buf[0] = tbl[c >> 4];
buf[1] = tbl[c & 0x0F];
result += buf;
}
} }
template <class T> template <hex_format format = hex_format::uppercase, class T = int>
enable_if_t<std::is_integral<T>::value> void append_hex(std::string& result, const T& x) {
append_hex(std::string& result, const T& x) { append_hex<format>(result, &x, sizeof(T));
return append_hex(result, reinterpret_cast<const uint8_t*>(&x), sizeof(T));
} }
} // namespace caf::detail } // namespace caf::detail
...@@ -18,7 +18,8 @@ ...@@ -18,7 +18,8 @@
#pragma once #pragma once
#include "caf/detail/append_hex.hpp" #include <string>
#include "caf/detail/core_export.hpp" #include "caf/detail/core_export.hpp"
#include "caf/fwd.hpp" #include "caf/fwd.hpp"
...@@ -26,7 +27,7 @@ namespace caf::detail { ...@@ -26,7 +27,7 @@ namespace caf::detail {
// Escapes all reserved characters according to RFC 3986 in `x` and // Escapes all reserved characters according to RFC 3986 in `x` and
// adds the encoded string to `str`. // adds the encoded string to `str`.
CAF_CORE_EXPORT void CAF_CORE_EXPORT void append_percent_encoded(std::string& str, string_view x,
append_percent_encoded(std::string& str, string_view x, bool is_path = false); bool is_path = false);
} // namespace caf::detail } // namespace caf::detail
...@@ -87,7 +87,7 @@ public: ...@@ -87,7 +87,7 @@ public:
dispatch_parse_cli(ps, tmp, char_blacklist); dispatch_parse_cli(ps, tmp, char_blacklist);
if (ps.code <= pec::trailing_character) { if (ps.code <= pec::trailing_character) {
if (predicate_ && !predicate_(tmp)) if (predicate_ && !predicate_(tmp))
ps.code = pec::illegal_argument; ps.code = pec::invalid_argument;
else else
set_value(x, std::move(tmp)); set_value(x, std::move(tmp));
} }
......
...@@ -21,6 +21,7 @@ ...@@ -21,6 +21,7 @@
#include <limits> #include <limits>
#include "caf/config.hpp"
#include "caf/detail/parser/ascii_to_int.hpp" #include "caf/detail/parser/ascii_to_int.hpp"
#include "caf/detail/type_traits.hpp" #include "caf/detail/type_traits.hpp"
......
...@@ -60,7 +60,7 @@ void read_string(State& ps, Consumer&& consumer) { ...@@ -60,7 +60,7 @@ void read_string(State& ps, Consumer&& consumer) {
transition(read_chars, 't', res += '\t') transition(read_chars, 't', res += '\t')
transition(read_chars, '\\', res += '\\') transition(read_chars, '\\', res += '\\')
transition(read_chars, '"', res += '"') transition(read_chars, '"', res += '"')
error_transition(pec::illegal_escape_sequence) error_transition(pec::invalid_escape_sequence)
} }
term_state(read_unquoted_chars) { term_state(read_unquoted_chars) {
transition(read_unquoted_chars, alphanumeric_chars, res += ch) transition(read_unquoted_chars, alphanumeric_chars, res += ch)
......
...@@ -175,7 +175,12 @@ public: ...@@ -175,7 +175,12 @@ public:
template <class T, class... Ts> template <class T, class... Ts>
void traverse(const meta::hex_formatted_t&, const T& x, const Ts&... xs) { void traverse(const meta::hex_formatted_t&, const T& x, const Ts&... xs) {
sep(); sep();
if constexpr (std::is_integral<T>::value) {
append_hex(result_, x); append_hex(result_, x);
} else {
static_assert(sizeof(typename T::value_type) == 1);
append_hex(result_, x.data(), x.size());
}
traverse(xs...); traverse(xs...);
} }
......
...@@ -143,6 +143,7 @@ class type_erased_value; ...@@ -143,6 +143,7 @@ class type_erased_value;
class uniform_type_info_map; class uniform_type_info_map;
class uri; class uri;
class uri_builder; class uri_builder;
class uuid;
// -- templates with default parameters ---------------------------------------- // -- templates with default parameters ----------------------------------------
......
...@@ -267,8 +267,7 @@ bool operator<(const T* x, const intrusive_ptr<T>& y) { ...@@ -267,8 +267,7 @@ bool operator<(const T* x, const intrusive_ptr<T>& y) {
template <class T> template <class T>
std::string to_string(const intrusive_ptr<T>& x) { std::string to_string(const intrusive_ptr<T>& x) {
std::string result; std::string result;
auto v = reinterpret_cast<uintptr_t>(x.get()); detail::append_hex(result, reinterpret_cast<uintptr_t>(x.get()));
detail::append_hex(result, reinterpret_cast<uint8_t*>(&v), sizeof(v));
return result; return result;
} }
......
...@@ -45,7 +45,7 @@ enum class pec : uint8_t { ...@@ -45,7 +45,7 @@ enum class pec : uint8_t {
/// Too many characters for an atom. /// Too many characters for an atom.
too_many_characters, too_many_characters,
/// Unrecognized character after escaping `\`. /// Unrecognized character after escaping `\`.
illegal_escape_sequence, invalid_escape_sequence,
/// Misplaced newline, e.g., inside a string. /// Misplaced newline, e.g., inside a string.
unexpected_newline, unexpected_newline,
/// Parsed positive integer exceeds the number of available bits. /// Parsed positive integer exceeds the number of available bits.
...@@ -61,11 +61,11 @@ enum class pec : uint8_t { ...@@ -61,11 +61,11 @@ enum class pec : uint8_t {
/// Stopped at an unrecognized option name. /// Stopped at an unrecognized option name.
not_an_option, not_an_option,
/// Stopped at an unparsable argument. /// Stopped at an unparsable argument.
illegal_argument = 15, invalid_argument = 15,
/// Stopped because an argument was omitted. /// Stopped because an argument was omitted.
missing_argument, missing_argument,
/// Stopped because the key of a category was taken. /// Stopped because the key of a category was taken.
illegal_category, invalid_category,
/// Stopped at an unexpected field name while reading a user-defined type. /// Stopped at an unexpected field name while reading a user-defined type.
invalid_field_name, invalid_field_name,
/// Stopped at a repeated field name while reading a user-defined type. /// Stopped at a repeated field name while reading a user-defined type.
......
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| 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
...@@ -145,7 +145,7 @@ auto config_option_set::parse(settings& config, argument_iterator first, ...@@ -145,7 +145,7 @@ auto config_option_set::parse(settings& config, argument_iterator first,
// Flags only consume the current element. // Flags only consume the current element.
if (opt.is_flag()) { if (opt.is_flag()) {
if (arg_begin != arg_end) if (arg_begin != arg_end)
return pec::illegal_argument; return pec::invalid_argument;
config_value cfg_true{true}; config_value cfg_true{true};
opt.store(cfg_true); opt.store(cfg_true);
entry[opt_name] = cfg_true; entry[opt_name] = cfg_true;
...@@ -159,7 +159,7 @@ auto config_option_set::parse(settings& config, argument_iterator first, ...@@ -159,7 +159,7 @@ auto config_option_set::parse(settings& config, argument_iterator first,
auto& err = val.error(); auto& err = val.error();
if (err.category() == error_category<pec>::value) if (err.category() == error_category<pec>::value)
return static_cast<pec>(err.code()); return static_cast<pec>(err.code());
return pec::illegal_argument; return pec::invalid_argument;
} }
entry[opt_name] = std::move(*val); entry[opt_name] = std::move(*val);
} }
......
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2018 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/detail/append_hex.hpp"
namespace caf::detail {
void append_hex(std::string& result, const uint8_t* xs, size_t n) {
if (n == 0) {
result += "00";
return;
}
auto tbl = "0123456789ABCDEF";
char buf[3] = {0, 0, 0};
for (size_t i = 0; i < n; ++i) {
auto c = xs[i];
buf[0] = tbl[c >> 4];
buf[1] = tbl[c & 0x0F];
result += buf;
}
}
} // namespace caf::detail
...@@ -53,7 +53,7 @@ void append_percent_encoded(std::string& str, string_view x, bool is_path) { ...@@ -53,7 +53,7 @@ void append_percent_encoded(std::string& str, string_view x, bool is_path) {
case ';': case ';':
case '=': case '=':
str += '%'; str += '%';
append_hex(str, reinterpret_cast<uint8_t*>(&ch), 1); append_hex(str, ch);
break; break;
default: default:
str += ch; str += ch;
......
...@@ -20,7 +20,6 @@ ...@@ -20,7 +20,6 @@
#include <cstring> #include <cstring>
#include "caf/detail/append_hex.hpp"
#include "caf/detail/network_order.hpp" #include "caf/detail/network_order.hpp"
#include "caf/detail/parser/read_ipv6_address.hpp" #include "caf/detail/parser/read_ipv6_address.hpp"
#include "caf/error.hpp" #include "caf/error.hpp"
......
...@@ -29,6 +29,7 @@ ...@@ -29,6 +29,7 @@
#include "caf/binary_serializer.hpp" #include "caf/binary_serializer.hpp"
#include "caf/config.hpp" #include "caf/config.hpp"
#include "caf/deserializer.hpp" #include "caf/deserializer.hpp"
#include "caf/detail/append_hex.hpp"
#include "caf/detail/get_mac_addresses.hpp" #include "caf/detail/get_mac_addresses.hpp"
#include "caf/detail/get_process_id.hpp" #include "caf/detail/get_process_id.hpp"
#include "caf/detail/get_root_uuid.hpp" #include "caf/detail/get_root_uuid.hpp"
......
...@@ -25,8 +25,8 @@ std::string to_string(pec x) { ...@@ -25,8 +25,8 @@ std::string to_string(pec x) {
return "fractional_timespan"; return "fractional_timespan";
case pec::too_many_characters: case pec::too_many_characters:
return "too_many_characters"; return "too_many_characters";
case pec::illegal_escape_sequence: case pec::invalid_escape_sequence:
return "illegal_escape_sequence"; return "invalid_escape_sequence";
case pec::unexpected_newline: case pec::unexpected_newline:
return "unexpected_newline"; return "unexpected_newline";
case pec::integer_overflow: case pec::integer_overflow:
...@@ -41,12 +41,12 @@ std::string to_string(pec x) { ...@@ -41,12 +41,12 @@ std::string to_string(pec x) {
return "type_mismatch"; return "type_mismatch";
case pec::not_an_option: case pec::not_an_option:
return "not_an_option"; return "not_an_option";
case pec::illegal_argument: case pec::invalid_argument:
return "illegal_argument"; return "invalid_argument";
case pec::missing_argument: case pec::missing_argument:
return "missing_argument"; return "missing_argument";
case pec::illegal_category: case pec::invalid_category:
return "illegal_category"; return "invalid_category";
case pec::invalid_field_name: case pec::invalid_field_name:
return "invalid_field_name"; return "invalid_field_name";
case pec::repeated_field_name: case pec::repeated_field_name:
......
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| 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,
};
};
} // 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) if (auto x = uuid_str##_uuid; true)
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