Uuid.h#

Fully qualified name: carb/extras/Uuid.h

File members: carb/extras/Uuid.h

// SPDX-FileCopyrightText: Copyright (c) 2021-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: LicenseRef-NvidiaProprietary
//
// NVIDIA CORPORATION, its affiliates and licensors retain all intellectual
// property and proprietary rights in and to this material, related
// documentation and any modifications thereto. Any use, reproduction,
// disclosure or distribution of this material and related documentation
// without an express license agreement from NVIDIA CORPORATION or
// its affiliates is strictly prohibited.

#pragma once

#include "../Defines.h"
#include "../cpp/StringView.h"
#include "../cpp/UnboundedString.h"
#include "../cpp/Bit.h"
#include "StringSafe.h"
#include "StringUtils.h"

#include <array>
#include <cctype>
#include <cinttypes>
#include <cstring>
#include <ostream>
#include <random>
#include <string> // for std::string and std::hash
#include <type_traits>

namespace carb
{
namespace extras
{

class Uuid final
{
public:
    using value_type = std::array<uint8_t, 16>;

    Uuid() noexcept : m_data{ 0U }
    {
    }

    Uuid(carb::cpp::string_view uuidStr) noexcept : m_data{ 0U }
    {
        carb::cpp::string_view uuidView;

        if (uuidStr.size() == 38 && uuidStr[0] == '{')
        {
            uuidView = uuidStr.substr(1, uuidStr.size() - 2);
        }
        else if (uuidStr.starts_with("urn:uuid:") && uuidStr.size() == 45) // RFC 4122
        {
            uuidView = uuidStr.substr(9, uuidStr.size());
        }
        else if (uuidStr.size() == 36)
        {
            uuidView = uuidStr;
        }
        else
        {
            return;
        }

        CARB_ASSERT(uuidView.size() == 36);

        std::array<uint8_t, 16> data{ 0U };

        for (size_t i = 0, j = 0; i < uuidView.size() && j < data.size();)
        {
            if ((i == 8) || (i == 13) || (i == 18) || (i == 23))
            {
                if (uuidView[i] != '-')
                {
                    return;
                }
                ++i;
                continue;
            }

            if (i + 1 >= uuidView.size())
            {
                return;
            }

            if (std::isxdigit(uuidView[i]) && std::isxdigit(uuidView[i + 1]))
            {
                auto hexToNibble = [](char c) -> uint8_t {
                    if (c >= '0' && c <= '9')
                        return static_cast<uint8_t>(c - '0');
                    if (c >= 'a' && c <= 'f')
                        return static_cast<uint8_t>(c - 'a' + 10);
                    if (c >= 'A' && c <= 'F')
                        return static_cast<uint8_t>(c - 'A' + 10);
                    return 0; // Won't happen since we already checked isxdigit
                };

                data[j++] = static_cast<uint8_t>((hexToNibble(uuidView[i]) << 4) | hexToNibble(uuidView[i + 1]));
                i += 2;
            }
            else
            {
                // error parsing, unknown character
                return;
            }
        }
        // if we parsed the entire view and filled the entire array, copy it
        m_data = data;
    }

    Uuid(carb::cpp::unbounded_string uuidStr) noexcept
        : Uuid(carb::cpp::string_view(carb::cpp::unsafe_length, uuidStr.data()))
    {
    }

    Uuid& operator=(carb::cpp::string_view uuidStr) noexcept
    {
        *this = Uuid(uuidStr);
        return *this;
    }

    static Uuid createV4() noexcept
    {
        Uuid uuidv4;
        std::random_device rd;

        for (size_t i = 0; i < uuidv4.m_data.size(); i += 4)
        {
            // use the entire 32-bits returned by random device
            uint32_t rdata = rd();
            uuidv4.m_data[i + 0] = uint8_t(rdata >> 0) & 0xff;
            uuidv4.m_data[i + 1] = uint8_t(rdata >> 8) & 0xff;
            uuidv4.m_data[i + 2] = uint8_t(rdata >> 16) & 0xff;
            uuidv4.m_data[i + 3] = uint8_t(rdata >> 24) & 0xff;
        }

        uuidv4.m_data[6] = (uuidv4.m_data[6] & 0x0f) | 0x40; // RFC 4122 for UUIDv4
        uuidv4.m_data[8] = (uuidv4.m_data[8] & 0x3f) | 0x80; // RFC 4122 for UUIDv4

        return uuidv4;
    }

    bool isEmpty() const noexcept
    {
        auto data = m_data.data();
        return *data == 0 && memcmp(data, data + 1, m_data.size() - 1) == 0;
    }

    const value_type& data() const noexcept
    {
        return m_data;
    }

    bool operator==(const Uuid& rhs) const noexcept
    {
        return !memcmp(m_data.data(), rhs.m_data.data(), m_data.size());
    }

    bool operator!=(const Uuid& rhs) const noexcept
    {
        return !(*this == rhs);
    }

#ifndef DOXYGEN_SHOULD_SKIP_THIS
    friend std::string to_string(const Uuid& uuid) noexcept
    {
        // UUID format 00000000-0000-0000-0000-000000000000
        static constexpr char kFmtString[] =
            "%02" SCNx8 "%02" SCNx8 "%02" SCNx8 "%02" SCNx8 "-%02" SCNx8 "%02" SCNx8 "-%02" SCNx8 "%02" SCNx8
            "-%02" SCNx8 "%02" SCNx8 "-%02" SCNx8 "%02" SCNx8 "%02" SCNx8 "%02" SCNx8 "%02" SCNx8 "%02" SCNx8;

        // 32 chars + 4 dashes + 1 null termination
        char strBuffer[37];

        formatString(strBuffer, CARB_COUNTOF(strBuffer), kFmtString, uuid.m_data[0], uuid.m_data[1], uuid.m_data[2],
                     uuid.m_data[3], uuid.m_data[4], uuid.m_data[5], uuid.m_data[6], uuid.m_data[7], uuid.m_data[8],
                     uuid.m_data[9], uuid.m_data[10], uuid.m_data[11], uuid.m_data[12], uuid.m_data[13],
                     uuid.m_data[14], uuid.m_data[15]);

        return std::string(strBuffer);
    }

    friend std::ostream& operator<<(std::ostream& os, const Uuid& uuid)
    {
        return os << to_string(uuid);
    }
#endif // DOXYGEN_SHOULD_SKIP_THIS

private:
    value_type m_data;
};

CARB_ASSERT_INTEROP_SAFE(Uuid);
static_assert(std::is_trivially_move_assignable<Uuid>::value, "Uuid must be move assignable");
static_assert(sizeof(Uuid) == 16, "Uuid must be exactly 16 bytes");

} // namespace extras
} // namespace carb

#ifndef DOXYGEN_SHOULD_SKIP_THIS

namespace std
{

template <>
struct hash<carb::extras::Uuid>
{
    using argument_type = carb::extras::Uuid;
    using result_type = std::size_t;

    result_type operator()(argument_type const& uuid) const noexcept
    {
        // uuid is random bytes, so just XOR
        // bit_cast() won't compile unless sizes match
        auto parts = carb::cpp::bit_cast<std::array<result_type, 2>>(uuid.data());
        return parts[0] ^ parts[1];
    }
};

} // namespace std

#endif // DOXYGEN_SHOULD_SKIP_THIS