Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add general UUID / GUID support #2552

Merged
merged 10 commits into from Sep 15, 2022
140 changes: 140 additions & 0 deletions Sming/Core/Data/Uuid.cpp
@@ -0,0 +1,140 @@
/****
* Sming Framework Project - Open Source framework for high efficiency native ESP8266 development.
* Created 2015 by Skurydin Alexey
* http://github.com/SmingHub/Sming
* All files of the Sming Core are provided under the LGPL v3 license.
*
* Uuid.cpp - Universal Unique Identifier
*
* See https://pubs.opengroup.org/onlinepubs/9629399/apdxa.htm.
*
* @author mikee47 <mike@sillyhouse.net>
*
****/

#include "Uuid.h"
#include <SystemClock.h>
#include <stringconversion.h>

extern "C" {
uint32_t os_random();
void os_get_random(void* buf, size_t n);
}

bool Uuid::generate(MacAddress mac)
{
uint8_t version = 1; // DCE version
uint8_t variant = 2; // DCE variant
uint16_t clock_seq = os_random();
uint32_t time;
if(SystemClock.isSet()) {
time = SystemClock.now(eTZ_UTC);
} else {
time = os_random();
}
// Time only provides 32 bits, we need 60
time_low = (os_random() & 0xFFFFFFFC) | (time & 0x00000003);
time_mid = (time >> 2) & 0xFFFF;
time_hi_and_version = (version << 12) | ((time >> 18) << 2);
clock_seq_hi_and_reserved = (variant << 6) | ((clock_seq >> 8) & 0x3F);
clock_seq_low = clock_seq & 0xFF;
mac.getOctets(node);

return SystemClock.isSet();
}

bool Uuid::generate()
{
MacAddress::Octets mac;
os_get_random(mac, sizeof(mac));
// RFC4122 requires LSB of first octet to be 1
mac[0] |= 0x01;
return generate(mac);
}

bool Uuid::decompose(const char* s, size_t len)
{
if(len != stringSize) {
return false;
}

char* p;
time_low = strtoul(s, &p, 16);
if(*p != '-' || p - s != 8) {
return false;
}
s = ++p;

time_mid = strtoul(s, &p, 16);
if(*p != '-' || p - s != 4) {
return false;
}
s = ++p;

time_hi_and_version = strtoul(s, &p, 16);
if(*p != '-' || p - s != 4) {
return false;
}
s = ++p;

uint16_t x = strtoul(s, &p, 16);
if(*p != '-' || p - s != 4) {
return false;
}
clock_seq_hi_and_reserved = x >> 8;
clock_seq_low = x & 0xff;
s = ++p;

for(unsigned i = 0; i < sizeof(node); ++i) {
uint8_t c = unhex(*s++) << 4;
c |= unhex(*s++);
node[i] = c;
}

return true;
}

size_t Uuid::toString(char* buffer, size_t bufSize) const
{
if(isFlashPtr(this)) {
return Uuid(*this).toString(buffer, bufSize);
}

if(buffer == nullptr || bufSize < stringSize) {
return 0;
}

auto set = [&](unsigned offset, uint32_t value, unsigned digits) {
ultoa_wp(value, &buffer[offset], 16, digits, '0');
};

// 2fac1234-31f8-11b4-a222-08002b34c003
// 0 9 14 19 24 36

set(0, time_low, 8);
buffer[8] = '-';
set(9, time_mid, 4);
buffer[13] = '-';
set(14, time_hi_and_version, 4);
buffer[18] = '-';
set(19, clock_seq_hi_and_reserved, 2);
set(21, clock_seq_low, 2);
buffer[23] = '-';

unsigned pos = 24;
for(unsigned i = 0; i < 6; ++i) {
buffer[pos++] = hexchar(node[i] >> 4);
buffer[pos++] = hexchar(node[i] & 0x0f);
}

return stringSize;
}

String Uuid::toString() const
{
String s;
if(s.setLength(stringSize)) {
toString(s.begin(), stringSize);
}
return s;
}
166 changes: 166 additions & 0 deletions Sming/Core/Data/Uuid.h
@@ -0,0 +1,166 @@
/****
* Sming Framework Project - Open Source framework for high efficiency native ESP8266 development.
* Created 2015 by Skurydin Alexey
* http://github.com/SmingHub/Sming
* All files of the Sming Core are provided under the LGPL v3 license.
*
* Uuid.h - Universal Unique Identifier
*
* @author mikee47 <mike@sillyhouse.net>
*
****/

#pragma once

#include <WString.h>
#include <MacAddress.h>

/**
* @brief Class for manipulating UUID (aka GUID) entities
*
* UUID: Universally Unique IDentifier
* GUID: Globally Unique IDentifier
*
* See https://pubs.opengroup.org/onlinepubs/9629399/apdxa.htm.
*/
struct Uuid {
uint32_t time_low{0}; // 0-3
uint16_t time_mid{0}; // 4-5
uint16_t time_hi_and_version{0}; // 6-7, version = top 4 bits
uint8_t clock_seq_hi_and_reserved{0}; // 8, variant = top 2 bits
uint8_t clock_seq_low{0}; // 9
uint8_t node[6]{}; // 10-15

/**
* @brief Number of characters in a UUID string (excluding NUL terminator)
*/
static constexpr size_t stringSize = 36;

Uuid()
{
}

explicit Uuid(const char* s)
{
decompose(s);
}

explicit Uuid(const char* s, size_t len)
{
decompose(s, len);
}

explicit Uuid(const String& s) : Uuid(s.c_str(), s.length())
{
}

explicit Uuid(const FlashString& s) : Uuid(String(s))
{
}

explicit constexpr Uuid(uint32_t time_low, uint16_t time_mid, uint16_t time_hi_and_version,
uint8_t clock_seq_hi_and_reserved, uint8_t clock_seq_low, uint8_t n1, uint8_t n2,
uint8_t n3, uint8_t n4, uint8_t n5, uint8_t n6)
: time_low(time_low), time_mid(time_mid), time_hi_and_version(time_hi_and_version),
clock_seq_hi_and_reserved(clock_seq_hi_and_reserved),
clock_seq_low(clock_seq_low), node{n1, n2, n3, n4, n5, n6}
{
}

explicit operator bool() const
{
Uuid Null{};
return memcmp(this, &Null, sizeof(Null)) != 0;
}

bool operator==(const Uuid& other) const
{
return memcmp(this, &other, sizeof(Uuid)) == 0;
}

bool operator!=(const Uuid& other) const
{
return !operator==(other);
}

/**
* @brief Generate a UUID using a MAC node address
* @param mac Node address to use in generating the UUID, typically from WifiStation
* @retval bool true if system clock time was used, false if substituted with random number
*/
bool generate(MacAddress mac);

/**
* @brief Generate UUID using random number instead of MAC
* @retval bool true if system clock time was used, false if substituted with random number
*
* Used where MAC address is not available or it is not desirable to expose it.
*/
bool generate();

/**
* @name Decompse string into UUID
* @{
*/
bool decompose(const char* s, size_t len);

bool decompose(const char* s)
{
return s ? decompose(s, strlen(s)) : false;
}

bool decompose(const String& s)
{
return decompose(s.c_str(), s.length());
}
/** @} */

/**
* @name Get string representation of UUID
* @{
*/

/**
* @param uuid
* @param buffer
* @param bufSize Must be at least UUID_STRING_SIZE
* @retval size_t number of characters written (either 0 or UUID_STRING_SIZE)
* @note Converts UUID into a string of the form
*
* <time_low>-<time_mid>-<time_high_and_version>-<clock_seq_and_reserved><clock_seq_low>-<node>
*
* e.g. 2fac1234-31f8-11b4-a222-08002b34c003
*/
size_t toString(char* buffer, size_t bufSize) const;

String toString() const;

operator String() const
{
return toString();
}

/** @} */
};

static_assert(sizeof(Uuid) == 16, "Bad Uuid");

inline String toString(const Uuid& uuid)
{
return uuid.toString();
}

inline bool fromString(const char* s, Uuid& uuid)
{
return uuid.decompose(s);
}

inline bool fromString(const String& s, Uuid& uuid)
{
return uuid.decompose(s);
}

/**
* @deprecated Use `Uuid` instead.
*/
typedef Uuid UUID SMING_DEPRECATED;
2 changes: 1 addition & 1 deletion Sming/Libraries/SSDP
2 changes: 1 addition & 1 deletion Sming/Libraries/SmingTest
2 changes: 1 addition & 1 deletion Sming/Libraries/UPnP
1 change: 1 addition & 0 deletions tests/HostTests/include/modules.h
Expand Up @@ -33,6 +33,7 @@
XX(ObjectMap) \
XX_NET(Base64) \
XX(DateTime) \
XX(Uuid) \
XX_NET(Http) \
XX_NET(Url) \
XX(ArduinoJson5) \
Expand Down
68 changes: 68 additions & 0 deletions tests/HostTests/modules/Uuid.cpp
@@ -0,0 +1,68 @@
#include <HostTests.h>

#include <Data/Uuid.h>
#include <Data/Stream/MemoryDataStream.h>

namespace
{
using guid_t = Uuid;

#define DEFINE_GUID(name, a, b, c, d...) static constexpr guid_t name PROGMEM{a, b, c, d};

DEFINE_GUID(PARTITION_SYSTEM_GUID, 0xc12a7328, 0xf81f, 0x11d2, 0xba, 0x4b, 0x00, 0xa0, 0xc9, 0x3e, 0xc9, 0x3b)
#define PARTITION_SYSTEM_GUID_PSTR "c12a7328-f81f-11d2-ba4b-00a0c93ec93b"
DEFINE_FSTR_LOCAL(PARTITION_SYSTEM_GUID_FSTR, PARTITION_SYSTEM_GUID_PSTR)

} // namespace

class UuidTest : public TestGroup
{
public:
UuidTest() : TestGroup(_F("UUID"))
{
}

void execute() override
{
TEST_CASE("NULL GUID")
{
Uuid uuid;
uint8_t empty[16]{};
REQUIRE(memcmp(&uuid, empty, 16) == 0);
}

TEST_CASE("Struct")
{
REQUIRE_EQ(String(PARTITION_SYSTEM_GUID_FSTR), Uuid(PARTITION_SYSTEM_GUID));
}

TEST_CASE("Decomposition")
{
REQUIRE_EQ(String(PARTITION_SYSTEM_GUID_FSTR), Uuid(PARTITION_SYSTEM_GUID_PSTR));
REQUIRE_EQ(String(PARTITION_SYSTEM_GUID_FSTR), Uuid(PARTITION_SYSTEM_GUID_FSTR));
}

TEST_CASE("Copy")
{
Uuid u1;
Uuid u2(PARTITION_SYSTEM_GUID);
u1 = u2;
REQUIRE_EQ(u1, u2);
}

TEST_CASE("Printing")
{
MemoryDataStream str;
const Uuid& u1(PARTITION_SYSTEM_GUID);
str << u1;
String s = str.readString(Uuid::stringSize);
REQUIRE_EQ(str.available(), 0);
REQUIRE_EQ(s, u1);
}
}
};

void REGISTER_TEST(Uuid)
{
registerGroup<UuidTest>();
}