OpenLCP/uuid/uuid.cpp

411 lines
7.8 KiB
C++

/*
uuid.h
Copyright (c) 2021 Kevin Matz (kevin.matz@gmail.com)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include "uuid.h"
#include <algorithm>
#include <chrono>
#include <cstdlib>
#include <sstream>
#include <iomanip>
namespace UUID {
/**
* @brief uuid::uuid
*/
uuid::uuid()
: raw_(new uint8_t[UUID_LENGTH])
{
type_ = NIL;
version_ = VOID;
timestamp_ = 0;
clock_seq_ = 0;
node_ = 0;
setBytes_();
}
/**
* @brief uuid::uuid
* @param other
*/
uuid::uuid(const uuid& other)
: raw_(new uint8_t[UUID_LENGTH])
{
type_ = other.type_;
version_ = other.version_;
timestamp_ = other.timestamp_;
clock_seq_ = other.clock_seq_;
node_ = other.node_;
setBytes_();
}
/**
* @brief uuid::~uuid
*/
uuid::~uuid()
{
delete[] raw_;
}
/**
* @brief uuid::fromArray
* @param bytes
*
* create this UUID from a byte array
*/
void uuid::fromArray_(const uint8_t * bytes)
{
std::memcpy(raw_, bytes, UUID_LENGTH);
setFields_();
}
/**
* @brief uuid::fromCstring
* @param cstr
*
* create this UUID from a C style char array
*/
void uuid::fromCstring_(const char * cstr)
{
fromString_(std::string(cstr));
}
/**
* @brief uuid::fromString
* @param str
*
* create this UUID from a string representation
*/
void uuid::fromString_(std::string str)
{
// remove formating
if (str.front() == '{' && str.back() == '}')
str = str.substr(1, str.size() - 2);
str.erase(std::remove(str.begin(), str.end(), '-'), str.end());
if (str.length() != 32) return;
// fill buffer
uint8_t buf[UUID_LENGTH];
for (unsigned int i = 0; i < sizeof(buf); i++)
buf[i] = std::strtoul(str.substr((i*2), 2).c_str(), NULL, UUID_LENGTH);
// process buffer
fromArray_(buf);
};
/**
* @brief uuid::hex
* @return
*
* output this UUID as a hex string
*/
std::string uuid::hex() const
{
std::ostringstream oss;
oss << std::hex;
for (int i = 0; i < UUID_LENGTH; i++)
oss << std::setw(2) << std::setfill('0') << (int)raw_[i];
return oss.str();
}
/**
* @brief uuid::string
* @return
*
* output this UUID as formated hex string
*/
std::string uuid::string() const
{
std::string str = hex();
str.reserve(str.length() + 6);
for (int idx : {8, 13, 18, 23})
str.insert(idx, "-");
str.insert(0, "{");
str.insert(str.length(), "}");
return str;
};
/**
* @brief uuid::urn
* @return
*
* output this UUID as a urn specified in RFC 4122
*/
std::string uuid::urn() const
{
return "urn:uuid:" + string();
}
/**
* @brief uuid::setBytes
* make the raw bytes match the field contents
*/
void uuid::setBytes_()
{
switch (type_) {
case NIL:
std::memset(raw_, 0, UUID_LENGTH);
break;
case NCS:
setNCSBytes_();
break;
case RFC4122:
setRFC4122Bytes_();
break;
case MS:
break;
case RESVERED:
break;
}
}
/**
* @brief uuid::setNCSBytes
*/
void uuid::setNCSBytes_()
{
NCSFields fields;
fields.time = timestamp_;
fields.res = 0;
fields.family = version_;
fields.node = node_;
std::memcpy(raw_, &fields, UUID_LENGTH);
}
/**
* @brief uuid::setRFC4122Bytes
* TODO: Check endianness
*/
void uuid::setRFC4122Bytes_()
{
RFC4122Fields fields;
fields.time_low = timestamp_;
fields.time_mid = timestamp_ >> 32;
fields.time_hi_version = timestamp_ >> 48;
fields.clock_seq_low = clock_seq_;
fields.clock_seq_hi_variant = clock_seq_ >> 8;
fields.node_low = node_;
fields.node_high = node_ >> 16;
std::memcpy(raw_, &fields, UUID_LENGTH);
// version
raw_[6] = (raw_[6] & 0x0f) | (version_ << 4);
// type
raw_[8] = (raw_[8] & 0b00111111) | (0b10 << 6);
}
/**
* @brief uuid::setFields
*/
void uuid::setFields_()
{
setType_();
switch (type_) {
case NCS:
setNCSFields_();
break;
case RFC4122:
setRFC4122Fields_();
break;
default:
break;
}
}
/**
* @brief uuid::setNCSFields
*/
void uuid::setNCSFields_()
{
NCSFields fields;
std::memcpy(&fields, raw_, UUID_LENGTH);
timestamp_ = fields.time;
version_ = fields.family;
node_ = fields.node;
}
/**
* @brief uuid::setRFC4122Fields
*/
void uuid::setRFC4122Fields_()
{
RFC4122Fields fields;
std::memcpy(&fields, raw_, UUID_LENGTH);
timestamp_ = fields.time_low;
timestamp_ |= (uint64_t)fields.time_mid << 32;
timestamp_ |= (uint64_t)(fields.time_hi_version & 0x0fff) << 48;
switch (raw_[6] >> 4) {
case TIME:
version_ = TIME;
break;
case DCE:
version_ = DCE;
break;
case MD5:
version_ = MD5;
break;
case RAND:
version_ = RAND;
break;
case SHA1:
version_ = SHA1;
break;
default:
version_ = VOID;
}
clock_seq_ = fields.clock_seq_low;
clock_seq_ |= (fields.clock_seq_hi_variant & 0b00111111) << 8;
node_ = fields.node_low;
node_ |= (fields.node_high << 16);
}
/**
* @brief uuid::setType
*
* exctract the type from the raw bytes
*/
void uuid::setType_()
{
if ((raw_[8] >> 7) == 0b0)
type_ = NCS;
else if ((raw_[8] >> 6) == 0b10)
type_ = RFC4122;
else if ((raw_[8] >> 5) == 0b110)
type_ = MS;
else
type_ = RESVERED;
}
/**
* @brief uuid::operator =
* @param other
* @return
*/
uuid& uuid::operator=(const uuid& other)
{
if (this != &other) // protect against invalid self-assignment
{
std::memcpy(raw_, other.raw_, UUID_LENGTH);
setFields_();
}
return *this;
}
/**
* @brief operator ==
* @param a
* @param b
* @return
*/
bool operator== (const uuid& a, const uuid& b)
{
if (std::memcmp(a.raw_, b.raw_, UUID_LENGTH) == 0)
return true;
return false;
}
/**
* @brief uuid::uuid1
* @param node
* @param clock_seq
* @return incrimented clock_sequence
*
* node ID and clock sequence tracking are the responsibility of the caller
*/
uint16_t uuid::uuid1(uint64_t node, uint16_t clock_seq)
{
type_ = RFC4122;
version_ = TIME;
timestamp_ = now_();
clock_seq_ = clock_seq + 1;
node_ = node;
setBytes_();
return clock_seq_;
}
/**
* @brief uuid::uuid4
*
* create this UUID as a Version 4 (RANDOM) UUID
*/
void uuid::uuid4() {
type_ = RFC4122;
version_ = RAND;
std::srand(now_());
timestamp_ = std::rand();
timestamp_ |= (uint64_t)std::rand() << 32;
clock_seq_ = std::rand();
node_ = std::rand();
node_ |= (uint64_t)std::rand() << 32;
setBytes_();
}
/**
* @brief uuid::time_ 4.1.4. Timestamp
* @return
*
* The timestamp is a 60-bit value. For UUID version 1, this is represented
* by Coordinated Universal Time (UTC) as a count of 100-nanosecond intervals
* since 00:00:00.00, 15 October 1582 (the date of Gregorian reform to the
* Christian calendar).
*/
uint64_t uuid::now_()
{
auto now = std::chrono::high_resolution_clock::now();
std::chrono::duration<uint64_t, std::nano> since = now.time_since_epoch();
uint64_t periods = since.count() / 100;
periods += UUID_TICKS_BETWEEK_EPOCH;
return periods;
}
}; // UUID