Skip to content

Commit 9ef6248

Browse files
authored
Add general UUID / GUID support (#2552)
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
1 parent 5227df3 commit 9ef6248

File tree

7 files changed

+378
-3
lines changed

7 files changed

+378
-3
lines changed

Sming/Core/Data/Uuid.cpp

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
/****
2+
* Sming Framework Project - Open Source framework for high efficiency native ESP8266 development.
3+
* Created 2015 by Skurydin Alexey
4+
* http://github.com/SmingHub/Sming
5+
* All files of the Sming Core are provided under the LGPL v3 license.
6+
*
7+
* Uuid.cpp - Universal Unique Identifier
8+
*
9+
* See https://pubs.opengroup.org/onlinepubs/9629399/apdxa.htm.
10+
*
11+
* @author mikee47 <[email protected]>
12+
*
13+
****/
14+
15+
#include "Uuid.h"
16+
#include <SystemClock.h>
17+
#include <stringconversion.h>
18+
19+
extern "C" {
20+
uint32_t os_random();
21+
void os_get_random(void* buf, size_t n);
22+
}
23+
24+
bool Uuid::generate(MacAddress mac)
25+
{
26+
uint8_t version = 1; // DCE version
27+
uint8_t variant = 2; // DCE variant
28+
uint16_t clock_seq = os_random();
29+
uint32_t time;
30+
if(SystemClock.isSet()) {
31+
time = SystemClock.now(eTZ_UTC);
32+
} else {
33+
time = os_random();
34+
}
35+
// Time only provides 32 bits, we need 60
36+
time_low = (os_random() & 0xFFFFFFFC) | (time & 0x00000003);
37+
time_mid = (time >> 2) & 0xFFFF;
38+
time_hi_and_version = (version << 12) | ((time >> 18) << 2);
39+
clock_seq_hi_and_reserved = (variant << 6) | ((clock_seq >> 8) & 0x3F);
40+
clock_seq_low = clock_seq & 0xFF;
41+
mac.getOctets(node);
42+
43+
return SystemClock.isSet();
44+
}
45+
46+
bool Uuid::generate()
47+
{
48+
MacAddress::Octets mac;
49+
os_get_random(mac, sizeof(mac));
50+
// RFC4122 requires LSB of first octet to be 1
51+
mac[0] |= 0x01;
52+
return generate(mac);
53+
}
54+
55+
bool Uuid::decompose(const char* s, size_t len)
56+
{
57+
if(len != stringSize) {
58+
return false;
59+
}
60+
61+
char* p;
62+
time_low = strtoul(s, &p, 16);
63+
if(*p != '-' || p - s != 8) {
64+
return false;
65+
}
66+
s = ++p;
67+
68+
time_mid = strtoul(s, &p, 16);
69+
if(*p != '-' || p - s != 4) {
70+
return false;
71+
}
72+
s = ++p;
73+
74+
time_hi_and_version = strtoul(s, &p, 16);
75+
if(*p != '-' || p - s != 4) {
76+
return false;
77+
}
78+
s = ++p;
79+
80+
uint16_t x = strtoul(s, &p, 16);
81+
if(*p != '-' || p - s != 4) {
82+
return false;
83+
}
84+
clock_seq_hi_and_reserved = x >> 8;
85+
clock_seq_low = x & 0xff;
86+
s = ++p;
87+
88+
for(unsigned i = 0; i < sizeof(node); ++i) {
89+
uint8_t c = unhex(*s++) << 4;
90+
c |= unhex(*s++);
91+
node[i] = c;
92+
}
93+
94+
return true;
95+
}
96+
97+
size_t Uuid::toString(char* buffer, size_t bufSize) const
98+
{
99+
if(isFlashPtr(this)) {
100+
return Uuid(*this).toString(buffer, bufSize);
101+
}
102+
103+
if(buffer == nullptr || bufSize < stringSize) {
104+
return 0;
105+
}
106+
107+
auto set = [&](unsigned offset, uint32_t value, unsigned digits) {
108+
ultoa_wp(value, &buffer[offset], 16, digits, '0');
109+
};
110+
111+
// 2fac1234-31f8-11b4-a222-08002b34c003
112+
// 0 9 14 19 24 36
113+
114+
set(0, time_low, 8);
115+
buffer[8] = '-';
116+
set(9, time_mid, 4);
117+
buffer[13] = '-';
118+
set(14, time_hi_and_version, 4);
119+
buffer[18] = '-';
120+
set(19, clock_seq_hi_and_reserved, 2);
121+
set(21, clock_seq_low, 2);
122+
buffer[23] = '-';
123+
124+
unsigned pos = 24;
125+
for(unsigned i = 0; i < 6; ++i) {
126+
buffer[pos++] = hexchar(node[i] >> 4);
127+
buffer[pos++] = hexchar(node[i] & 0x0f);
128+
}
129+
130+
return stringSize;
131+
}
132+
133+
String Uuid::toString() const
134+
{
135+
String s;
136+
if(s.setLength(stringSize)) {
137+
toString(s.begin(), stringSize);
138+
}
139+
return s;
140+
}

Sming/Core/Data/Uuid.h

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
/****
2+
* Sming Framework Project - Open Source framework for high efficiency native ESP8266 development.
3+
* Created 2015 by Skurydin Alexey
4+
* http://github.com/SmingHub/Sming
5+
* All files of the Sming Core are provided under the LGPL v3 license.
6+
*
7+
* Uuid.h - Universal Unique Identifier
8+
*
9+
* @author mikee47 <[email protected]>
10+
*
11+
****/
12+
13+
#pragma once
14+
15+
#include <WString.h>
16+
#include <MacAddress.h>
17+
18+
/**
19+
* @brief Class for manipulating UUID (aka GUID) entities
20+
*
21+
* UUID: Universally Unique IDentifier
22+
* GUID: Globally Unique IDentifier
23+
*
24+
* See https://pubs.opengroup.org/onlinepubs/9629399/apdxa.htm.
25+
*/
26+
struct Uuid {
27+
uint32_t time_low{0}; // 0-3
28+
uint16_t time_mid{0}; // 4-5
29+
uint16_t time_hi_and_version{0}; // 6-7, version = top 4 bits
30+
uint8_t clock_seq_hi_and_reserved{0}; // 8, variant = top 2 bits
31+
uint8_t clock_seq_low{0}; // 9
32+
uint8_t node[6]{}; // 10-15
33+
34+
/**
35+
* @brief Number of characters in a UUID string (excluding NUL terminator)
36+
*/
37+
static constexpr size_t stringSize = 36;
38+
39+
Uuid()
40+
{
41+
}
42+
43+
explicit Uuid(const char* s)
44+
{
45+
decompose(s);
46+
}
47+
48+
explicit Uuid(const char* s, size_t len)
49+
{
50+
decompose(s, len);
51+
}
52+
53+
explicit Uuid(const String& s) : Uuid(s.c_str(), s.length())
54+
{
55+
}
56+
57+
explicit Uuid(const FlashString& s) : Uuid(String(s))
58+
{
59+
}
60+
61+
explicit constexpr Uuid(uint32_t time_low, uint16_t time_mid, uint16_t time_hi_and_version,
62+
uint8_t clock_seq_hi_and_reserved, uint8_t clock_seq_low, uint8_t n1, uint8_t n2,
63+
uint8_t n3, uint8_t n4, uint8_t n5, uint8_t n6)
64+
: time_low(time_low), time_mid(time_mid), time_hi_and_version(time_hi_and_version),
65+
clock_seq_hi_and_reserved(clock_seq_hi_and_reserved),
66+
clock_seq_low(clock_seq_low), node{n1, n2, n3, n4, n5, n6}
67+
{
68+
}
69+
70+
explicit operator bool() const
71+
{
72+
Uuid Null{};
73+
return memcmp(this, &Null, sizeof(Null)) != 0;
74+
}
75+
76+
bool operator==(const Uuid& other) const
77+
{
78+
return memcmp(this, &other, sizeof(Uuid)) == 0;
79+
}
80+
81+
bool operator!=(const Uuid& other) const
82+
{
83+
return !operator==(other);
84+
}
85+
86+
/**
87+
* @brief Generate a UUID using a MAC node address
88+
* @param mac Node address to use in generating the UUID, typically from WifiStation
89+
* @retval bool true if system clock time was used, false if substituted with random number
90+
*/
91+
bool generate(MacAddress mac);
92+
93+
/**
94+
* @brief Generate UUID using random number instead of MAC
95+
* @retval bool true if system clock time was used, false if substituted with random number
96+
*
97+
* Used where MAC address is not available or it is not desirable to expose it.
98+
*/
99+
bool generate();
100+
101+
/**
102+
* @name Decompse string into UUID
103+
* @{
104+
*/
105+
bool decompose(const char* s, size_t len);
106+
107+
bool decompose(const char* s)
108+
{
109+
return s ? decompose(s, strlen(s)) : false;
110+
}
111+
112+
bool decompose(const String& s)
113+
{
114+
return decompose(s.c_str(), s.length());
115+
}
116+
/** @} */
117+
118+
/**
119+
* @name Get string representation of UUID
120+
* @{
121+
*/
122+
123+
/**
124+
* @param uuid
125+
* @param buffer
126+
* @param bufSize Must be at least UUID_STRING_SIZE
127+
* @retval size_t number of characters written (either 0 or UUID_STRING_SIZE)
128+
* @note Converts UUID into a string of the form
129+
*
130+
* <time_low>-<time_mid>-<time_high_and_version>-<clock_seq_and_reserved><clock_seq_low>-<node>
131+
*
132+
* e.g. 2fac1234-31f8-11b4-a222-08002b34c003
133+
*/
134+
size_t toString(char* buffer, size_t bufSize) const;
135+
136+
String toString() const;
137+
138+
operator String() const
139+
{
140+
return toString();
141+
}
142+
143+
/** @} */
144+
};
145+
146+
static_assert(sizeof(Uuid) == 16, "Bad Uuid");
147+
148+
inline String toString(const Uuid& uuid)
149+
{
150+
return uuid.toString();
151+
}
152+
153+
inline bool fromString(const char* s, Uuid& uuid)
154+
{
155+
return uuid.decompose(s);
156+
}
157+
158+
inline bool fromString(const String& s, Uuid& uuid)
159+
{
160+
return uuid.decompose(s);
161+
}
162+
163+
/**
164+
* @deprecated Use `Uuid` instead.
165+
*/
166+
typedef Uuid UUID SMING_DEPRECATED;

tests/HostTests/include/modules.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
XX(ObjectMap) \
3434
XX_NET(Base64) \
3535
XX(DateTime) \
36+
XX(Uuid) \
3637
XX_NET(Http) \
3738
XX_NET(Url) \
3839
XX(ArduinoJson5) \

tests/HostTests/modules/Uuid.cpp

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
#include <HostTests.h>
2+
3+
#include <Data/Uuid.h>
4+
#include <Data/Stream/MemoryDataStream.h>
5+
6+
namespace
7+
{
8+
using guid_t = Uuid;
9+
10+
#define DEFINE_GUID(name, a, b, c, d...) static constexpr guid_t name PROGMEM{a, b, c, d};
11+
12+
DEFINE_GUID(PARTITION_SYSTEM_GUID, 0xc12a7328, 0xf81f, 0x11d2, 0xba, 0x4b, 0x00, 0xa0, 0xc9, 0x3e, 0xc9, 0x3b)
13+
#define PARTITION_SYSTEM_GUID_PSTR "c12a7328-f81f-11d2-ba4b-00a0c93ec93b"
14+
DEFINE_FSTR_LOCAL(PARTITION_SYSTEM_GUID_FSTR, PARTITION_SYSTEM_GUID_PSTR)
15+
16+
} // namespace
17+
18+
class UuidTest : public TestGroup
19+
{
20+
public:
21+
UuidTest() : TestGroup(_F("UUID"))
22+
{
23+
}
24+
25+
void execute() override
26+
{
27+
TEST_CASE("NULL GUID")
28+
{
29+
Uuid uuid;
30+
uint8_t empty[16]{};
31+
REQUIRE(memcmp(&uuid, empty, 16) == 0);
32+
}
33+
34+
TEST_CASE("Struct")
35+
{
36+
REQUIRE_EQ(String(PARTITION_SYSTEM_GUID_FSTR), Uuid(PARTITION_SYSTEM_GUID));
37+
}
38+
39+
TEST_CASE("Decomposition")
40+
{
41+
REQUIRE_EQ(String(PARTITION_SYSTEM_GUID_FSTR), Uuid(PARTITION_SYSTEM_GUID_PSTR));
42+
REQUIRE_EQ(String(PARTITION_SYSTEM_GUID_FSTR), Uuid(PARTITION_SYSTEM_GUID_FSTR));
43+
}
44+
45+
TEST_CASE("Copy")
46+
{
47+
Uuid u1;
48+
Uuid u2(PARTITION_SYSTEM_GUID);
49+
u1 = u2;
50+
REQUIRE_EQ(u1, u2);
51+
}
52+
53+
TEST_CASE("Printing")
54+
{
55+
MemoryDataStream str;
56+
const Uuid& u1(PARTITION_SYSTEM_GUID);
57+
str << u1;
58+
String s = str.readString(Uuid::stringSize);
59+
REQUIRE_EQ(str.available(), 0);
60+
REQUIRE_EQ(s, u1);
61+
}
62+
}
63+
};
64+
65+
void REGISTER_TEST(Uuid)
66+
{
67+
registerGroup<UuidTest>();
68+
}

0 commit comments

Comments
 (0)