232 lines
5.2 KiB
C++
232 lines
5.2 KiB
C++
/*
|
|
* Copyright 2015 - 2025 gary@drinkingtea.net
|
|
*
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
*/
|
|
|
|
#pragma once
|
|
|
|
#include "bit.hpp"
|
|
#include "def.hpp"
|
|
#include "ignore.hpp"
|
|
#include "istring.hpp"
|
|
#include "buffer.hpp"
|
|
#include "hash.hpp"
|
|
#include "random.hpp"
|
|
#include "ranges.hpp"
|
|
#include "stringview.hpp"
|
|
#include "strops.hpp"
|
|
|
|
OX_CLANG_NOWARN_BEGIN(-Wunsafe-buffer-usage)
|
|
|
|
namespace ox {
|
|
|
|
using UUIDStr = ox::IString<36>;
|
|
|
|
namespace detail {
|
|
|
|
[[nodiscard]]
|
|
constexpr auto isHexChar(char c) noexcept {
|
|
return (c >= '0' && c <= '9')
|
|
|| (c >= 'a' && c <= 'f')
|
|
|| (c >= 'A' && c <= 'F');
|
|
}
|
|
|
|
constexpr ox::Result<uint8_t> fromHex(ox::StringViewCR v) noexcept {
|
|
constexpr auto valMap = [] {
|
|
ox::Array<uint8_t, 128> out;
|
|
out['A'] = out['a'] = 10;
|
|
out['B'] = out['b'] = 11;
|
|
out['C'] = out['c'] = 12;
|
|
out['D'] = out['d'] = 13;
|
|
out['E'] = out['e'] = 14;
|
|
out['F'] = out['f'] = 15;
|
|
out['0'] = 0;
|
|
out['1'] = 1;
|
|
out['2'] = 2;
|
|
out['3'] = 3;
|
|
out['4'] = 4;
|
|
out['5'] = 5;
|
|
out['6'] = 6;
|
|
out['7'] = 7;
|
|
out['8'] = 8;
|
|
out['9'] = 9;
|
|
return out;
|
|
}();
|
|
if (!detail::isHexChar(v[0]) || !detail::isHexChar(v[1])) {
|
|
return ox::Error(1, "Invalid UUID");
|
|
}
|
|
if (v.len() != 2) {
|
|
return ox::Error(2);
|
|
}
|
|
uint8_t out = 0;
|
|
out += static_cast<uint8_t>(valMap[static_cast<unsigned char>(v[0])] * 16);
|
|
out += valMap[static_cast<unsigned char>(v[1])];
|
|
return out;
|
|
}
|
|
|
|
constexpr ox::IString<2> toHex(uint8_t v) noexcept {
|
|
constexpr ox::Array<char, 16> valMap {
|
|
'0',
|
|
'1',
|
|
'2',
|
|
'3',
|
|
'4',
|
|
'5',
|
|
'6',
|
|
'7',
|
|
'8',
|
|
'9',
|
|
'a',
|
|
'b',
|
|
'c',
|
|
'd',
|
|
'e',
|
|
'f',
|
|
};
|
|
ox::IString<2> out;
|
|
std::ignore = out.resize(2);
|
|
out[0] = valMap[static_cast<unsigned>((v & 0xf0) / 16)];
|
|
out[1] = valMap[static_cast<unsigned>(v & 0x0f)];
|
|
out[2] = 0;
|
|
return out.data();
|
|
}
|
|
|
|
}
|
|
|
|
class UUID {
|
|
template<typename T>
|
|
friend constexpr Error model(T *io, ox::CommonPtrWith<UUID> auto *obj) noexcept;
|
|
protected:
|
|
static bool s_seeded;
|
|
static Random s_rand;
|
|
ox::Array<uint8_t, 16> m_value{};
|
|
|
|
public:
|
|
|
|
static constexpr auto TypeName = "net.drinkingtea.ox.UUID";
|
|
static constexpr auto TypeVersion = 1;
|
|
|
|
static void seedGenerator(const RandomSeed &seed) noexcept;
|
|
|
|
static ox::Result<UUID> generate() noexcept;
|
|
|
|
[[nodiscard]]
|
|
constexpr ox::Array<uint8_t, 16> const&value() const noexcept {
|
|
return m_value;
|
|
}
|
|
|
|
[[nodiscard]]
|
|
constexpr bool isNull() const noexcept {
|
|
if (std::is_constant_evaluated()) {
|
|
if (ox::all_of(m_value.begin(), m_value.end(), [](auto v) { return v == 0; })) {
|
|
return true;
|
|
}
|
|
return false;
|
|
} else {
|
|
constexpr uint64_t zero = 0;
|
|
return memcmp(&zero, m_value.data() + 0, 8) == 0
|
|
&& memcmp(&zero, m_value.data() + 8, 8) == 0;
|
|
}
|
|
}
|
|
|
|
static constexpr ox::Result<ox::UUID> fromString(ox::StringViewCR s) noexcept {
|
|
if (s.len() < 36) {
|
|
return ox::Error(1, "Insufficient data to contain a complete UUID");
|
|
}
|
|
UUID out;
|
|
auto valueI = 0u;
|
|
for (size_t i = 0; i < s.len();) {
|
|
if (s[i] == '-') {
|
|
++i;
|
|
continue;
|
|
}
|
|
const auto seg = substr(s, i, i + 2);
|
|
if (seg.len() != 2) {
|
|
return ox::Error(1, "Invalid UUID");
|
|
}
|
|
OX_REQUIRE(val, detail::fromHex(seg));
|
|
out.m_value[valueI] = val;
|
|
i += 2;
|
|
++valueI;
|
|
}
|
|
return out;
|
|
}
|
|
|
|
constexpr bool operator==(UUID const&o) const noexcept {
|
|
return m_value == o.m_value;
|
|
}
|
|
|
|
constexpr bool operator!=(UUID const&o) const noexcept {
|
|
return !operator==(o);
|
|
}
|
|
|
|
[[nodiscard]]
|
|
constexpr ox::Error toString(Writer_c auto &writer) const noexcept {
|
|
auto valueI = 0u;
|
|
constexpr auto printChars = [](
|
|
Writer_c auto &writer,
|
|
const Array<uint8_t, 16> &value,
|
|
std::size_t cnt,
|
|
unsigned &valueI) {
|
|
for (auto i = 0u; i < cnt; ++i) {
|
|
const auto v = value[valueI];
|
|
const auto h = detail::toHex(v);
|
|
std::ignore = writer.write(h.c_str(), h.len());
|
|
++valueI;
|
|
}
|
|
};
|
|
printChars(writer, m_value, 4, valueI);
|
|
OX_RETURN_ERROR(writer.put('-'));
|
|
printChars(writer, m_value, 2, valueI);
|
|
OX_RETURN_ERROR(writer.put('-'));
|
|
printChars(writer, m_value, 2, valueI);
|
|
OX_RETURN_ERROR(writer.put('-'));
|
|
printChars(writer, m_value, 2, valueI);
|
|
OX_RETURN_ERROR(writer.put('-'));
|
|
printChars(writer, m_value, 6, valueI);
|
|
return {};
|
|
}
|
|
|
|
[[nodiscard]]
|
|
constexpr UUIDStr toString() const noexcept {
|
|
UUIDStr out;
|
|
std::ignore = out.resize(UUIDStr::cap());
|
|
ox::CharBuffWriter bw{{out.data(), UUIDStr::cap()}};
|
|
std::ignore = toString(bw);
|
|
out[UUIDStr::cap()] = 0;
|
|
return out;
|
|
}
|
|
};
|
|
|
|
|
|
template<>
|
|
struct hash<ox::UUID> {
|
|
[[nodiscard]]
|
|
constexpr size_t operator()(ox::UUID v) const noexcept {
|
|
size_t out{};
|
|
if (std::is_constant_evaluated()) {
|
|
for (auto i = 0u; i < sizeof(out); ++i) {
|
|
out |= static_cast<size_t>(v.value()[i]) << (i * 8);
|
|
}
|
|
} else {
|
|
memcpy(&out, &v, sizeof(out));
|
|
}
|
|
return out;
|
|
}
|
|
};
|
|
|
|
|
|
template<typename T>
|
|
constexpr Error model(T *io, ox::CommonPtrWith<UUID> auto *obj) noexcept {
|
|
OX_RETURN_ERROR(io->template setTypeInfo<UUID>());
|
|
OX_RETURN_ERROR(io->field("value", &obj->m_value));
|
|
return {};
|
|
}
|
|
|
|
}
|
|
|
|
OX_CLANG_NOWARN_END
|