diff --git a/Makefile.dep b/Makefile.dep index ea5924cfab7892ef98f0412bd6f7193d9f10b7ca..9b9dcd53abeae1c24864dcbbbd5ca46273b5310c 100644 --- a/Makefile.dep +++ b/Makefile.dep @@ -736,6 +736,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 0000000000000000000000000000000000000000..2616ebd3b4beab5f77afa2c10b61dd442a64dfd5 --- /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 <koen@bergzand.net> + */ + +#ifndef UUID_H +#define UUID_H + +#include <stdbool.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> +#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 0000000000000000000000000000000000000000..48422e909a47d7cd428d10fa73825060ccc8d8c2 --- /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 0000000000000000000000000000000000000000..a7c9c95e4073c5413c414c627d25328bc881dba3 --- /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 <koen@bergzand.net> + * @} + */ + +#include <string.h> +#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); +} diff --git a/tests/unittests/tests-uuid/Makefile b/tests/unittests/tests-uuid/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..967631ff5dcc711ba91c275e676e32075a037651 --- /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 0000000000000000000000000000000000000000..44e9a076cb7a5000ad9306bc840a82ba566b9524 --- /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 0000000000000000000000000000000000000000..bb6560ec185c5bde093885fe3ca54c4a47f8c471 --- /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 <koen@bergzand.net> + */ + +#include <stdio.h> +#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 0000000000000000000000000000000000000000..47bbb4ac92ab174a9262aabc1c25799674508f35 --- /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 */ +/** @} */