Skip to content

Commit

Permalink
Add general UUID / GUID support (#2552)
Browse files Browse the repository at this point in the history
This PR moves the `Uuid` object out of the `SSDP` library and makes some improvements.

UUIDs/GUIDs are not exclusively network entities. They're required for filesystems, for example.

- Add template constructor for arbitrary 16-byte entities
- Add comparison operators
- Add Uuid test module
- Fix test message truncation by updating SmingTest to write messages directly to Serial
  • Loading branch information
mikee47 committed Sep 15, 2022
1 parent 5227df3 commit 9ef6248
Show file tree
Hide file tree
Showing 7 changed files with 378 additions and 3 deletions.
140 changes: 140 additions & 0 deletions Sming/Core/Data/Uuid.cpp
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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>();
}

0 comments on commit 9ef6248

Please sign in to comment.