From fc03d6f694e8793a2e2d007002581bcc7fb8a5c3 Mon Sep 17 00:00:00 2001 From: Koen Zandberg Date: Sat, 28 Apr 2018 11:33:06 +0200 Subject: [PATCH 1/2] uuid: Initial import of RFC4122 UUID functions Provides functions for type 3, 4 and 5 UUID generations. UUID type 1 is timestamp based and requires an accurate time source. For this reason it is left out of this implementation. UUID type 2 is not defined in RFC 4122 and thus also not included here --- Makefile.dep | 5 ++ sys/include/uuid.h | 147 +++++++++++++++++++++++++++++++++++++++++++++ sys/uuid/Makefile | 1 + sys/uuid/uuid.c | 112 ++++++++++++++++++++++++++++++++++ 4 files changed, 265 insertions(+) create mode 100644 sys/include/uuid.h create mode 100644 sys/uuid/Makefile create mode 100644 sys/uuid/uuid.c diff --git a/Makefile.dep b/Makefile.dep index 6fbd8a1e6f..0eb348aaa4 100644 --- a/Makefile.dep +++ b/Makefile.dep @@ -728,6 +728,11 @@ ifneq (,$(filter tlsf-malloc,$(USEMODULE))) USEPKG += tlsf endif +ifneq (,$(filter uuid,$(USEMODULE))) + USEMODULE += hashes + USEMODULE += random +endif + # always select gpio (until explicit dependencies are sorted out) FEATURES_OPTIONAL += periph_gpio diff --git a/sys/include/uuid.h b/sys/include/uuid.h new file mode 100644 index 0000000000..2616ebd3b4 --- /dev/null +++ b/sys/include/uuid.h @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2018 Freie Universität Berlin + * Copyright (C) 2018 Inria + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @defgroup sys_uuid RFC 4122 compliant UUID's + * @ingroup sys + * @brief Provides RFC 4122 compliant UUID's + * + * This module provides RFC 4122 compliant UUID generation. The UUID stored in + * @ref uuid_t struct is stored in network byte order. + * + * @{ + * + * @file + * @brief [RFC 4122](https://tools.ietf.org/html/rfc4122) UUID functions + * + * @author Koen Zandberg + */ + +#ifndef UUID_H +#define UUID_H + +#include +#include +#include +#include +#include "byteorder.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define UUID_NODE_LEN (6U) /**< Size of the node identifier in bytes */ + +/** + * @name UUID version identifiers + * @{ + */ +#define UUID_V1 (0x01) /**< Type 1 UUID - timestamp based */ +#define UUID_V2 (0x02) /**< Type 2 UUID - DCE Security version */ +#define UUID_V3 (0x03) /**< Type 3 UUID - Name based with MD5 */ +#define UUID_V4 (0x04) /**< Type 4 UUID - Random generated */ +#define UUID_V5 (0x05) /**< Type 5 UUID - Name based with SHA1 */ +/** @} */ + +/** + * @name Version part of the time_hi field + */ +#define UUID_VERSION_MASK (0xF000) + +/** + * @brief UUID layout + * + * Directly from [rfc4122](https://tools.ietf.org/html/rfc4122#section-4.1.2) + */ +typedef struct __attribute__((packed)) { + network_uint32_t time_low; /**< The low field of the timestamp */ + network_uint16_t time_mid; /**< The middle field of the timestamp */ + network_uint16_t time_hi; /**< The high field of the timestamp + * multiplexed with the version number */ + uint8_t clk_seq_hi_res; /**< The high field of the clock sequence + * Multiplexed with the variant */ + uint8_t clk_seq_low; /**< The low field of the clock sequence */ + uint8_t node[UUID_NODE_LEN]; /**< The spatially unique node identifier */ +} uuid_t; + + +/** + * @name Namespace IDs from RFC4122 + * + * Copied from [rfc4122 Appendix + * C](https://tools.ietf.org/html/rfc4122#appendix-C) + * + * @{ + */ +extern const uuid_t uuid_namespace_dns; /**< DNS namespace UUID */ +extern const uuid_t uuid_namespace_url; /**< URL namespace UUID */ +extern const uuid_t uuid_namespace_iso; /**< ISO OID namespace UUID */ +extern const uuid_t uuid_namespace_x500; /**< X.500 DN namespace UUID */ +/** @} */ + +/** + * Generate a version 3(md5 based) UUID from a namespace and a byte array + * + * @param[out] uuid UUID struct to fill + * @param[in] ns Namespace UUID + * @param[in] name Ptr to byte array to use as name part + * @param[in] len Length of the byte array + */ +void uuid_v3(uuid_t *uuid, const uuid_t *ns, const uint8_t *name, size_t len); + + +/** + * Generate a version 4(Full random) UUID + * + * @param[out] uuid UUID struct to fill + */ +void uuid_v4(uuid_t *uuid); + +/** + * Generate a version 5(sha1 based) UUID from a namespace and a byte array + * + * @param[out] uuid UUID struct to fill + * @param[in] ns Namespace UUID + * @param[in] name Ptr to byte array to use as name part + * @param[in] len Length of the byte array + */ +void uuid_v5(uuid_t *uuid, const uuid_t *ns, const uint8_t *name, size_t len); + +/** + * Retrieve the type number of a UUID + * + * @param[in] uuid UUID to retrieve version number from + * + * @return Version number + */ +static inline unsigned uuid_version(uuid_t *uuid) +{ + uint16_t time_hi_vers = byteorder_ntohs(uuid->time_hi); + + return (time_hi_vers & 0xF000) >> 12; +} + +/** + * Compare two UUID's + * + * @param[in] uuid1 First uuid to compare + * @param[in] uuid2 Second uuid to compare + * + * @return True when equal + */ +static inline bool uuid_equal(uuid_t *uuid1, uuid_t *uuid2) +{ + return (memcmp(uuid1, uuid2, sizeof(uuid_t)) == 0); +} + +#ifdef __cplusplus +} +#endif +#endif /* UUID_H */ +/** @} */ diff --git a/sys/uuid/Makefile b/sys/uuid/Makefile new file mode 100644 index 0000000000..48422e909a --- /dev/null +++ b/sys/uuid/Makefile @@ -0,0 +1 @@ +include $(RIOTBASE)/Makefile.base diff --git a/sys/uuid/uuid.c b/sys/uuid/uuid.c new file mode 100644 index 0000000000..a7c9c95e40 --- /dev/null +++ b/sys/uuid/uuid.c @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2018 Freie Universität Berlin + * Copyright (C) 2018 Inria + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup sys_uuid + * @{ + * @file + * @brief Function implementations to create RFC 4122 UUID objects + * + * @author Koen Zandberg + * @} + */ + +#include +#include "byteorder.h" +#include "hashes/md5.h" +#include "hashes/sha1.h" +#include "random.h" +#include "uuid.h" + +const uuid_t uuid_namespace_dns = { /* 6ba7b810-9dad-11d1-80b4-00c04fd430c8 */ + .time_low.u8 = { 0x6b, 0xa7, 0xb8, 0x10 }, + .time_mid.u8 = { 0x9d, 0xad }, + .time_hi.u8 = { 0x11, 0xd1 }, + .clk_seq_hi_res = 0x80, + .clk_seq_low = 0xb4, + .node = { 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8 } +}; + +const uuid_t uuid_namespace_url = { /* 6ba7b811-9dad-11d1-80b4-00c04fd430c8 */ + .time_low.u8 = { 0x6b, 0xa7, 0xb8, 0x11 }, + .time_mid.u8 = { 0x9d, 0xad }, + .time_hi.u8 = { 0x11, 0xd1 }, + .clk_seq_hi_res = 0x80, + .clk_seq_low = 0xb4, + .node = { 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8 } +}; + +/* Name string is an ISO OID */ +const uuid_t uuid_namespace_iso = { /* 6ba7b812-9dad-11d1-80b4-00c04fd430c8 */ + .time_low.u8 = { 0x6b, 0xa7, 0xb8, 0x12 }, + .time_mid.u8 = { 0x9d, 0xad }, + .time_hi.u8 = { 0x11, 0xd1 }, + .clk_seq_hi_res = 0x80, + .clk_seq_low = 0xb4, + .node = { 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8 } +}; + +const uuid_t uuid_namespace_x500 = { /* 6ba7b814-9dad-11d1-80b4-00c04fd430c8 */ + .time_low.u8 = { 0x6b, 0xa7, 0xb8, 0x14 }, + .time_mid.u8 = { 0x9d, 0xad }, + .time_hi.u8 = { 0x11, 0xd1 }, + .clk_seq_hi_res = 0x80, + .clk_seq_low = 0xb4, + .node = { 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8 } +}; + +static inline void _set_version(uuid_t *uuid, unsigned version) +{ + uint16_t time_hi = byteorder_ntohs(uuid->time_hi) & 0x0fff; + + time_hi |= (version << 12); + uuid->time_hi = byteorder_htons(time_hi); +} + +static inline void _set_reserved(uuid_t *uuid) +{ + uuid->clk_seq_hi_res = (uuid->clk_seq_hi_res & 0x3f) | 0x80; +} + +void uuid_v3(uuid_t *uuid, const uuid_t *ns, const uint8_t *name, size_t len) +{ + /* Digest calculation */ + md5_ctx_t ctx; + + md5_init(&ctx); + md5_update(&ctx, ns, sizeof(uuid_t)); + md5_update(&ctx, name, len); + md5_final(&ctx, uuid); + + _set_version(uuid, UUID_V3); + _set_reserved(uuid); +} + +void uuid_v4(uuid_t *uuid) +{ + random_bytes((uint8_t *)uuid, sizeof(uuid_t)); + _set_version(uuid, UUID_V4); + _set_reserved(uuid); +} + +void uuid_v5(uuid_t *uuid, const uuid_t *ns, const uint8_t *name, size_t len) +{ + uint8_t digest[20]; + sha1_context ctx; + + sha1_init(&ctx); + sha1_update(&ctx, ns, sizeof(uuid_t)); + sha1_update(&ctx, name, len); + sha1_final(&ctx, digest); + + memcpy(uuid, digest, sizeof(uuid_t)); + + _set_version(uuid, UUID_V5); + _set_reserved(uuid); +} From b7c7d57740957f9d1183cbebb82ba12749c219bc Mon Sep 17 00:00:00 2001 From: Koen Zandberg Date: Sat, 28 Apr 2018 11:34:16 +0200 Subject: [PATCH 2/2] unittests: Add unittest for uuid module --- tests/unittests/tests-uuid/Makefile | 1 + tests/unittests/tests-uuid/Makefile.include | 1 + tests/unittests/tests-uuid/tests-uuid.c | 113 ++++++++++++++++++++ tests/unittests/tests-uuid/tests-uuid.h | 37 +++++++ 4 files changed, 152 insertions(+) create mode 100644 tests/unittests/tests-uuid/Makefile create mode 100644 tests/unittests/tests-uuid/Makefile.include create mode 100644 tests/unittests/tests-uuid/tests-uuid.c create mode 100644 tests/unittests/tests-uuid/tests-uuid.h diff --git a/tests/unittests/tests-uuid/Makefile b/tests/unittests/tests-uuid/Makefile new file mode 100644 index 0000000000..967631ff5d --- /dev/null +++ b/tests/unittests/tests-uuid/Makefile @@ -0,0 +1 @@ +include $(RIOTBASE)/Makefile.base diff --git a/tests/unittests/tests-uuid/Makefile.include b/tests/unittests/tests-uuid/Makefile.include new file mode 100644 index 0000000000..44e9a076cb --- /dev/null +++ b/tests/unittests/tests-uuid/Makefile.include @@ -0,0 +1 @@ +USEMODULE += uuid diff --git a/tests/unittests/tests-uuid/tests-uuid.c b/tests/unittests/tests-uuid/tests-uuid.c new file mode 100644 index 0000000000..bb6560ec18 --- /dev/null +++ b/tests/unittests/tests-uuid/tests-uuid.c @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2018 Freie Universität Berlin + * Copyright (C) 2018 Inria + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup tests + * @{ + * + * @file + * @brief Unit tests for uuid module + * + * @author Koen Zandberg + */ + +#include +#include "embUnit.h" +#include "uuid.h" +#include "hashes/md5.h" +#include "hashes/sha1.h" + +const char riotos_org[] = "riot-os.org"; +const char test_str[] = "unittest"; + +/* + * Generated with python: + * import uuid; + * riot = uuid.uuid3(uuid.NAMESPACE_DNS, "riot-os.org") + * print(riot.hex) + * print(uuid.uuid3(riot, "unittest").hex) + */ +const uint8_t v3_check1[] = {0x23, 0x99, 0x0b, 0xda, 0x1e, 0xe7, 0x34, 0x16, + 0x90, 0xfe, 0x69, 0x30, 0x7d, 0x90, 0x64, 0x0e}; +const uint8_t v3_check2[] = {0x20, 0xf5, 0x36, 0x91, 0x91, 0xae, 0x3c, 0xfa, + 0x99, 0xb5, 0x8e, 0xf9, 0xfa, 0xc2, 0x76, 0x55}; + +/* + * Generated with python: + * import uuid; + * riot = uuid.uuid5(uuid.NAMESPACE_DNS, "riot-os.org") + * print(riot.hex) + * print(uuid.uuid5(riot, "unittest").hex) + */ +const uint8_t v5_check1[] = {0x54, 0x7d, 0x0d, 0x74, 0x6d, 0x3a, 0x5a, 0x92, + 0x96, 0x62, 0x48, 0x81, 0xaf, 0xd9, 0x40, 0x7b}; +const uint8_t v5_check2[] = {0x7a, 0x1b, 0xf5, 0xdb, 0x5e, 0x77, 0x5e, 0x9b, + 0x80, 0x6f, 0x0f, 0x55, 0x95, 0x58, 0xc9, 0xca}; + +/* + * Length of the test strings without zero terminator. + * Python doesn't feed the zero terminator in the uuid generator, so we test + * without the zero terminator here too. + */ +#define RIOTOS_ORG_LEN (sizeof(riotos_org) -1) +#define TEST_STR_LEN (sizeof(test_str) -1) + + +void test_uuid_v3(void) +{ + uuid_t uuid, uuid_next; + uuid_v3(&uuid, &uuid_namespace_dns, + (uint8_t*)riotos_org, RIOTOS_ORG_LEN); + uuid_v3(&uuid_next, &uuid, + (uint8_t*)test_str, TEST_STR_LEN); + + TEST_ASSERT(uuid_equal(&uuid, (uuid_t*)v3_check1)); + TEST_ASSERT(uuid_equal(&uuid_next, (uuid_t*)v3_check2)); + TEST_ASSERT_EQUAL_INT(uuid_version(&uuid), UUID_V3); + TEST_ASSERT_EQUAL_INT(uuid_version(&uuid_next), UUID_V3); +} + +void test_uuid_v4(void) +{ + uuid_t uuid; + uuid_v4(&uuid); + + TEST_ASSERT_EQUAL_INT(uuid_version(&uuid), 4); +} + +void test_uuid_v5(void) +{ + uuid_t uuid, uuid_next; + uuid_v5(&uuid, &uuid_namespace_dns, + (uint8_t*)riotos_org, RIOTOS_ORG_LEN); + uuid_v5(&uuid_next, &uuid, + (uint8_t*)test_str, TEST_STR_LEN); + + TEST_ASSERT(uuid_equal(&uuid, (uuid_t*)v5_check1)); + TEST_ASSERT(uuid_equal(&uuid_next, (uuid_t*)v5_check2)); + TEST_ASSERT_EQUAL_INT(uuid_version(&uuid), UUID_V5); + TEST_ASSERT_EQUAL_INT(uuid_version(&uuid_next), UUID_V5); +} + +Test *tests_uuid_all(void) +{ + EMB_UNIT_TESTFIXTURES(fixtures) { + new_TestFixture(test_uuid_v3), + new_TestFixture(test_uuid_v4), + new_TestFixture(test_uuid_v5), + }; + + EMB_UNIT_TESTCALLER(uuid_tests, NULL, NULL, fixtures); + return (Test *)&uuid_tests; +} + +void tests_uuid(void) +{ + TESTS_RUN(tests_uuid_all()); +} diff --git a/tests/unittests/tests-uuid/tests-uuid.h b/tests/unittests/tests-uuid/tests-uuid.h new file mode 100644 index 0000000000..47bbb4ac92 --- /dev/null +++ b/tests/unittests/tests-uuid/tests-uuid.h @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2018 Freie Universität Berlin + * Copyright (C) 2018 Inria + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @addtogroup unittests + * @{ + * + * @file + * @brief Unittests for the uuid module + * + */ +#ifndef TESTS_UUID_H +#define TESTS_UUID_H + +#include "embUnit/embUnit.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief The entry point of this test suite. + */ +void tests_uuid(void); + +#ifdef __cplusplus +} +#endif + +#endif /* TESTS_UUID_H */ +/** @} */