#ifndef PK_UUID_H #define PK_UUID_H #include "stddef.h" #include struct pk_uuid { alignas(max_align_t) unsigned char uuid[16]; }; const struct pk_uuid pk_uuid_zed = { .uuid = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }; const struct pk_uuid pk_uuid_max = { .uuid = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF } }; #define pk_uuid_printf_format PK_Q(%.2x%.2x%.2x%.2x-%.2x%.2x-%.2x%.2x-%.2x%.2x-%.2x%.2x%.2x%.2x%.2x%.2x) #define pk_uuid_printf_var(id) id.uuid[0], id.uuid[1], id.uuid[2], id.uuid[3], id.uuid[4], id.uuid[5], id.uuid[6], id.uuid[7], id.uuid[8], id.uuid[9], id.uuid[10], id.uuid[11], id.uuid[12], id.uuid[13], id.uuid[14], id.uuid[15] void pk_uuid_init(time_t srand_seed); void pk_uuid_teardown(); struct pk_uuid pk_uuid_new_v7(); bool pk_uuid_equals(struct pk_uuid lhs, struct pk_uuid rhs); bool pk_uuid_parse(const char *s, struct pk_uuid *uuid); #if defined(__cplusplus) #include #include std::ostream& operator<<(std::ostream &o, const struct pk_uuid& uuid); std::istream& operator>>(std::istream &i, struct pk_uuid& uuid); const char* operator>>(const char *s, struct pk_uuid& uuid); struct pk_uuid& operator<<(struct pk_uuid& uuid, const char *s); bool operator==(const pk_uuid &lhs, const pk_uuid &rhs); bool operator!=(const pk_uuid &lhs, const pk_uuid &rhs); #endif #endif /* PK_UUID_H */ #ifdef PK_IMPL_UUID #include "./pkstn.h" /* deleteme */ #include #include // TODO JCB - 2025-03-19 // This should have platform-specific defines #ifndef PK_UUID_CLOCK #ifdef CLOCK_TAI #define PK_UUID_CLOCK CLOCK_TAI #else #define PK_UUID_CLOCK CLOCK_REALTIME #endif #endif void pk_uuid_init(time_t srand_seed) { // TODO 2025-03-19 - JCB // pk.h should NOT be setting srand. // Replace dependency on rand/srand with a sufficient rand() implementation. // I would prefer if generating a UUID did not advance a global random. // Consider creating a pkrand.h to resolve this. srand(srand_seed); } void pk_uuid_teardown() { } struct pk_uuid pk_uuid_new_v7() { const int n = 1; uint32_t r; // https://www.rfc-editor.org/rfc/rfc9562.html#name-uuid-version-7 struct pk_uuid ret; struct timespec t; clock_gettime(PK_UUID_CLOCK, &t); uint32_t sec = (uint32_t)t.tv_sec; uint32_t nsec = (uint32_t)t.tv_nsec; // [000-047] (6 bytes) big-endian unix epoch // TODO test this on a big-endian machine, I don't think this is correct. // This `if` determines if we are big or little endian. // A return value of 1 says we are little endian, so swap the bits. if (*(char *)&n == 1) { ret.uuid[0] = (uint8_t)((sec & 0xFF000000) >> 24); ret.uuid[1] = (uint8_t)((sec & 0x00FF0000) >> 16); ret.uuid[2] = (uint8_t)((sec & 0x0000FF00) >> 8); ret.uuid[3] = (uint8_t)((sec & 0x000000FF) >> 0); ret.uuid[4] = (uint8_t)((nsec & 0x0000FF00) >> 8); ret.uuid[5] = (uint8_t)((nsec & 0x000000FF) >> 0); } else { ret.uuid[0] = (uint8_t)((sec & 0xFF000000) >> 0); ret.uuid[1] = (uint8_t)((sec & 0x00FF0000) >> 8); ret.uuid[2] = (uint8_t)((sec & 0x0000FF00) >> 16); ret.uuid[3] = (uint8_t)((sec & 0x000000FF) >> 24); ret.uuid[4] = (uint8_t)((nsec & 0xFF000000) >> 0); ret.uuid[5] = (uint8_t)((nsec & 0x00FF0000) >> 8); } // [052-127] random r = (uint32_t)rand(); if (*(char *)&n == 1) { ret.uuid[8] = (uint8_t)((r & 0xFF000000) >> 24); ret.uuid[9] = (uint8_t)((r & 0x00FF0000) >> 16); ret.uuid[10] = (uint8_t)((r & 0x0000FF00) >> 8); ret.uuid[11] = (uint8_t)((r & 0x000000FF) >> 0); } else { ret.uuid[8] = (uint8_t)((r & 0xFF000000) >> 0); ret.uuid[9] = (uint8_t)((r & 0x00FF0000) >> 8); ret.uuid[10] = (uint8_t)((r & 0x0000FF00) >> 16); ret.uuid[11] = (uint8_t)((r & 0x000000FF) >> 24); } r = rand(); if (*(char *)&n == 1) { ret.uuid[12] = (uint8_t)((r & 0xFF000000) >> 24); ret.uuid[13] = (uint8_t)((r & 0x00FF0000) >> 16); ret.uuid[14] = (uint8_t)((r & 0x0000FF00) >> 8); ret.uuid[15] = (uint8_t)((r & 0x000000FF) >> 0); } else { ret.uuid[12] = (uint8_t)((r & 0xFF000000) >> 0); ret.uuid[13] = (uint8_t)((r & 0x00FF0000) >> 8); ret.uuid[14] = (uint8_t)((r & 0x0000FF00) >> 16); ret.uuid[15] = (uint8_t)((r & 0x000000FF) >> 24); } ret.uuid[6] = ret.uuid[9] ^ ret.uuid[12]; ret.uuid[7] = ret.uuid[10] ^ ret.uuid[15]; // [048-051] v7 nibble // version must be 0x7_ // 0x70 is 0b01110000 // 0x7F is 0b01111111 ret.uuid[6] |= 0x70; ret.uuid[6] &= 0x7F; // [064-065] 2-bit variant field // variant must be 0b10 // 0x80 is 0b10000000 // 0xBF is 0b10111111 ret.uuid[8] |= 0x80; ret.uuid[8] &= 0xBF; return ret; } bool pk_uuid_equals(struct pk_uuid lhs, struct pk_uuid rhs) { int i; for (i = 0; i < 16; ++i) { if (lhs.uuid[i] != rhs.uuid[i]) return false; } return true; } bool pk_uuid_parse(const char *s, struct pk_uuid *uuid) { // ffffffff-ffff-ffff-ffff-ffffffffffff // 0 8 13 18 23 35 char c[3] = {'\0','\0','\0'}; unsigned char k, kk; if (s == nullptr) goto err_out; for (k = 0, kk = 0; k < 36; k+=2, ++kk) { if (s[k] == '\0' || s[k+1] == '\0') goto err_out; if (k == 8 || k == 13 || k == 18 || k == 23) { if (s[k] != '-') goto err_out; k -= 1; kk -= 1; continue; } c[0] = s[k]; c[1] = s[k+1]; if (pk_stn_uint8_t(&uuid->uuid[kk], c, nullptr, 16) != PK_STN_RES_SUCCESS) { goto err_out; } } return true; err_out: *uuid = pk_uuid_zed; return false; } #if defined(__cplusplus) std::ostream& operator<<(std::ostream &o, const struct pk_uuid& uuid) { int i; std::ios_base::fmtflags orig_flags = o.flags(); auto fill = o.fill(); o << std::hex; for (i = 0; i < 4; ++i) { o << std::setw(2) << std::setfill('0'); o << (uint16_t)uuid.uuid[i]; } o << "-"; for (i = 4; i < 6; ++i) { o << std::setw(2) << std::setfill('0'); o << (uint16_t)uuid.uuid[i]; } o << "-"; for (i = 6; i < 8; ++i) { o << std::setw(2) << std::setfill('0'); o << (uint16_t)uuid.uuid[i]; } o << "-"; for (i = 8; i < 10; ++i) { o << std::setw(2) << std::setfill('0'); o << (uint16_t)uuid.uuid[i]; } o << "-"; for (i = 10; i < 16; ++i) { o << std::setw(2) << std::setfill('0'); o << (uint16_t)uuid.uuid[i]; } o.fill(fill); o.flags(orig_flags); return o; } std::istream& operator>>(std::istream &i, struct pk_uuid& uuid) { char u[36]; i.read(u, 36); if (i.rdstate() & std::ios::failbit) { goto err_out; } else if (pk_uuid_parse(u, &uuid) == false) { goto err_out; } return i; err_out: uuid = pk_uuid_zed; i.seekg(-36, std::ios_base::cur); i.setstate(std::ios::failbit); return i; } const char * operator>>(const char *s, struct pk_uuid& uuid) { if (pk_uuid_parse(s, &uuid)) { return s+36; } return s; } struct pk_uuid& operator<<(struct pk_uuid& uuid, const char *s) { pk_uuid_parse(s, &uuid); return uuid; } bool operator==(const pk_uuid &lhs, const pk_uuid &rhs) { return pk_uuid_equals(lhs, rhs); } bool operator!=(const pk_uuid &lhs, const pk_uuid &rhs) { return !pk_uuid_equals(lhs, rhs); } #endif #endif