diff --git a/Sming/Core/Data/Uuid.cpp b/Sming/Core/Data/Uuid.cpp new file mode 100644 index 0000000000..fdb8ddc71c --- /dev/null +++ b/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 + * + ****/ + +#include "Uuid.h" +#include +#include + +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; +} diff --git a/Sming/Core/Data/Uuid.h b/Sming/Core/Data/Uuid.h new file mode 100644 index 0000000000..75a351f721 --- /dev/null +++ b/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 + * + ****/ + +#pragma once + +#include +#include + +/** + * @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 + * + * ---- + * + * 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; diff --git a/Sming/Libraries/SSDP b/Sming/Libraries/SSDP index a864d47dc7..f2fe3f8b19 160000 --- a/Sming/Libraries/SSDP +++ b/Sming/Libraries/SSDP @@ -1 +1 @@ -Subproject commit a864d47dc7a554e451ce3abe458285ec64a68ecf +Subproject commit f2fe3f8b19473ee28bcaa3fb053607b76683960c diff --git a/Sming/Libraries/SmingTest b/Sming/Libraries/SmingTest index 1a25cc1ea7..a6c7f94341 160000 --- a/Sming/Libraries/SmingTest +++ b/Sming/Libraries/SmingTest @@ -1 +1 @@ -Subproject commit 1a25cc1ea71b68139fbccbd4e66afc5db8b89846 +Subproject commit a6c7f9434135fc6350da72b7e226a825f0092356 diff --git a/Sming/Libraries/UPnP b/Sming/Libraries/UPnP index 983ecdfd2b..28348c3d3e 160000 --- a/Sming/Libraries/UPnP +++ b/Sming/Libraries/UPnP @@ -1 +1 @@ -Subproject commit 983ecdfd2b814e744fe2231867d45e5d50edd312 +Subproject commit 28348c3d3e60d7e3b38e53ba853493f0549b894c diff --git a/tests/HostTests/include/modules.h b/tests/HostTests/include/modules.h index 9a87341b3f..dd302fb86b 100644 --- a/tests/HostTests/include/modules.h +++ b/tests/HostTests/include/modules.h @@ -33,6 +33,7 @@ XX(ObjectMap) \ XX_NET(Base64) \ XX(DateTime) \ + XX(Uuid) \ XX_NET(Http) \ XX_NET(Url) \ XX(ArduinoJson5) \ diff --git a/tests/HostTests/modules/Uuid.cpp b/tests/HostTests/modules/Uuid.cpp new file mode 100644 index 0000000000..1707fa5979 --- /dev/null +++ b/tests/HostTests/modules/Uuid.cpp @@ -0,0 +1,68 @@ +#include + +#include +#include + +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(); +}