/*
**  OSSP uuid - Universally Unique Identifier
**  Copyright (c) 2004-2008 Ralf S. Engelschall <rse@engelschall.com>
**  Copyright (c) 2004-2008 The OSSP Project <http://www.ossp.org/>
**
**  This file is part of OSSP uuid, a library for the generation
**  of UUIDs which can found at http://www.ossp.org/pkg/lib/uuid/
**
**  Permission to use, copy, modify, and distribute this software for
**  any purpose with or without fee is hereby granted, provided that
**  the above copyright notice and this permission notice appear in all
**  copies.
**
**  THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
**  WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
**  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
**  IN NO EVENT SHALL THE AUTHORS AND COPYRIGHT HOLDERS AND THEIR
**  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
**  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
**  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
**  USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
**  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
**  OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
**  OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
**  SUCH DAMAGE.
**
**  uuid.c: library API implementation
*/

/* own headers (part 1/2) */
#include "uuid.h"
#include "config.h"

/* system headers */
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <unistd.h>
#include <ctype.h>
#include <fcntl.h>
#include <time.h>
#include <inttypes.h>
#include <sys/time.h>
#include <sys/types.h>
#include <md5.h>
#if __has_include(<sha1.h>)
#include <sha1.h>
#else
#include <sha.h>  // FreeBSD
#define SHA1Init SHA1_Init
#define SHA1Update SHA1_Update
#define SHA1Final SHA1_Final
#define SHA1_DIGEST_LENGTH 20
#endif

/* own headers (part 2/2) */
#include "uuid_random.h"
#include "uuid_mac.h"
#include "uuid_time.h"
#include "uuid_ui128.h"
#include "config.h"

/* time offset between UUID and Unix Epoch time in seconds.
   (UUID UTC base time is October 15, 1582
    Unix UTC base time is January  1, 1970) */
static const uint64_t UUID_TIMEOFFSET = 12219292800ULL;

/* IEEE 802 MAC address encoding/decoding bit fields */
#define IEEE_MAC_MCBIT 0b00000001
#define IEEE_MAC_LOBIT 0b00000010


/* UUID v1 representation */
typedef struct {
    uint32_t  time_low;                  /* bits  0-31 of time field */
    uint16_t  time_mid;                  /* bits 32-47 of time field */
    uint16_t  time_hi_and_version;       /* bits 48-59 of time field plus 4 bit version */
    uint8_t   clock_seq_hi_and_reserved; /* bits  8-13 of clock sequence field plus 2 bit variant */
    uint8_t   clock_seq_low;             /* bits  0-7  of clock sequence field */
    uint8_t   node[MAC_LEN];             /* bits  0-47 of node MAC address */
} uuid_obj_t;

/* abstract data type (ADT) of API */
struct uuid_st {
    uuid_obj_t      obj;                    /* inlined UUID object */
    struct timespec time_last;              /* last retrieved timestamp */
    random_t        random;                 /* random sub-object */
};

static uint8_t selected_mac[MAC_LEN];       /* resolve MAC address for insertion into node field of UUIDs */
static  __attribute__((constructor)) void loadmac() {
    if (!mac_address(selected_mac))
        selected_mac[0] = IEEE_MAC_MCBIT;
}

/* create UUID object */
uuid_rc_t uuid_create(uuid_t **uuid)
{
    uuid_t *obj;

    /* argument sanity check */
    if (uuid == NULL)
        return UUID_RC_ARG;

    /* allocate UUID object */
    if ((obj = (uuid_t *)malloc(sizeof(uuid_t))) == NULL)
        return UUID_RC_MEM;

    /* create random sub-object */
    obj->random = (random_t){.fd = -1};

    /* set UUID object initially to "Nil UUID" */
    obj->obj = (uuid_obj_t){};

    /* initialize time attributes */
    obj->time_last.tv_sec  = 0;
    obj->time_last.tv_nsec = 0;

    /* store result object */
    *uuid = obj;

    return UUID_RC_OK;
}

/* destroy UUID object */
uuid_rc_t uuid_destroy(uuid_t *uuid)
{
    /* argument sanity check */
    if (uuid == NULL)
        return UUID_RC_ARG;

    random_destroy(&uuid->random);

    /* free UUID object */
    free(uuid);

    return UUID_RC_OK;
}

/* clone UUID object */
uuid_rc_t uuid_clone(const uuid_t *uuid, uuid_t **clone)
{
    uuid_t *obj;

    /* argument sanity check */
    if (uuid == NULL || clone == NULL)
        return UUID_RC_ARG;

    /* allocate UUID object */
    if ((obj = (uuid_t *)malloc(sizeof(uuid_t))) == NULL)
        return UUID_RC_MEM;

    /* clone entire internal state */
    memcpy(obj, uuid, sizeof(uuid_t));

    /* re-initialize with new random sub-object */
    obj->random = (random_t){.fd = -1};

    /* store result object */
    *clone = obj;

    return UUID_RC_OK;
}

/* check whether UUID object represents "Nil UUID" */
uuid_rc_t uuid_isnil(const uuid_t *uuid, int *result)
{
    /* sanity check argument(s) */
    if (uuid == NULL || result == NULL)
        return UUID_RC_ARG;

    /* a "Nil UUID" is defined as all octets zero, so check for this case */
    uuid_obj_t nil = {};
    *result = !memcmp(&uuid->obj, &nil, sizeof(nil));
    return UUID_RC_OK;
}

/* check whether UUID object represents "Max UUID" */
uuid_rc_t uuid_ismax(const uuid_t *uuid, int *result)
{
    /* sanity check argument(s) */
    if (uuid == NULL || result == NULL)
        return UUID_RC_ARG;

    /* a "Max UUID" is defined as all octets 0xFF, so check for this case */
    uuid_obj_t max;
    memset(&max, 0xFF, sizeof(uuid->obj));;
    *result = !memcmp(&uuid->obj, &max, sizeof(max));
    return UUID_RC_OK;
}

/* compare UUID objects */
uuid_rc_t uuid_compare(const uuid_t *uuid1, const uuid_t *uuid2, int *result)
{
    int r;

    /* argument sanity check */
    if (result == NULL)
        return UUID_RC_ARG;

    /* convenience macro for setting result */
#define RESULT(r) \
    do { \
        *result = (r); \
        return UUID_RC_OK; \
    } while (0) \

    /* special cases: NULL or equal UUIDs */
    if (uuid1 == uuid2)
        RESULT(0);
    if (uuid1 == NULL && uuid2 == NULL)
        RESULT(0);
    if (uuid1 == NULL)
        RESULT((uuid_isnil(uuid2, &r) == UUID_RC_OK ? r : 0) ? 0 : -1);
    if (uuid2 == NULL)
        RESULT((uuid_isnil(uuid1, &r) == UUID_RC_OK ? r : 0) ? 0 : 1);

    /* standard cases: regular different UUIDs */
    if (uuid1->obj.time_low != uuid2->obj.time_low)
        RESULT((uuid1->obj.time_low < uuid2->obj.time_low) ? -1 : 1);
    if ((r = (int)uuid1->obj.time_mid
           - (int)uuid2->obj.time_mid) != 0)
        RESULT((r < 0) ? -1 : 1);
    if ((r = (int)uuid1->obj.time_hi_and_version
           - (int)uuid2->obj.time_hi_and_version) != 0)
        RESULT((r < 0) ? -1 : 1);
    if ((r = (int)uuid1->obj.clock_seq_hi_and_reserved
           - (int)uuid2->obj.clock_seq_hi_and_reserved) != 0)
        RESULT((r < 0) ? -1 : 1);
    if ((r = (int)uuid1->obj.clock_seq_low
           - (int)uuid2->obj.clock_seq_low) != 0)
        RESULT((r < 0) ? -1 : 1);
    if ((r = memcmp(uuid1->obj.node, uuid2->obj.node, sizeof(uuid1->obj.node))) != 0)
        RESULT((r < 0) ? -1 : 1);

    /* default case: the keys are equal */
    *result = 0;
    return UUID_RC_OK;
}

/* INTERNAL: unpack UUID binary presentation into UUID object
   (allows in-place operation for internal efficiency!) */
static uuid_rc_t uuid_import_bin(uuid_t *uuid, const void *data_ptr, size_t data_len)
{
    const uint8_t *in;
    uint32_t tmp32;
    uint16_t tmp16;
    unsigned int i;

    /* sanity check argument(s) */
    if (uuid == NULL || data_ptr == NULL || data_len < UUID_LEN_BIN)
        return UUID_RC_ARG;

    /* treat input data buffer as octet stream */
    in = (const uint8_t *)data_ptr;

    /* unpack "time_low" field */
    tmp32 = (uint32_t)(*in++);
    tmp32 = (tmp32 << 8) | (uint32_t)(*in++);
    tmp32 = (tmp32 << 8) | (uint32_t)(*in++);
    tmp32 = (tmp32 << 8) | (uint32_t)(*in++);
    uuid->obj.time_low = tmp32;

    /* unpack "time_mid" field */
    tmp16 = (uint16_t)(*in++);
    tmp16 = (uint16_t)(tmp16 << 8) | (uint16_t)(*in++);
    uuid->obj.time_mid = tmp16;

    /* unpack "time_hi_and_version" field */
    tmp16 = (uint16_t)*in++;
    tmp16 = (uint16_t)(tmp16 << 8) | (uint16_t)(*in++);
    uuid->obj.time_hi_and_version = tmp16;

    /* unpack "clock_seq_hi_and_reserved" field */
    uuid->obj.clock_seq_hi_and_reserved = *in++;

    /* unpack "clock_seq_low" field */
    uuid->obj.clock_seq_low = *in++;

    /* unpack "node" field */
    for (i = 0; i < (unsigned int)sizeof(uuid->obj.node); i++)
        uuid->obj.node[i] = *in++;

    return UUID_RC_OK;
}

/* INTERNAL: pack UUID object into binary representation
   (allows in-place operation for internal efficiency!) */
static uuid_rc_t uuid_export_bin(const uuid_t *uuid, void *_data_ptr, size_t *data_len)
{
    uint8_t **data_ptr;
    uint8_t *out;
    uint32_t tmp32;
    uint16_t tmp16;
    unsigned int i;

    /* cast generic data pointer to particular pointer to pointer type */
    data_ptr = (uint8_t **)_data_ptr;

    /* sanity check argument(s) */
    if (uuid == NULL || data_ptr == NULL)
        return UUID_RC_ARG;

    /* optionally allocate octet data buffer */
    if (*data_ptr == NULL) {
        if ((*data_ptr = (uint8_t *)malloc(sizeof(uuid_t))) == NULL)
            return UUID_RC_MEM;
        if (data_len != NULL)
            *data_len = UUID_LEN_BIN;
    }
    else {
        if (data_len == NULL)
            return UUID_RC_ARG;
        if (*data_len < UUID_LEN_BIN)
            return UUID_RC_MEM;
        *data_len = UUID_LEN_BIN;
    }

    /* treat output data buffer as octet stream */
    out = *data_ptr;

    /* pack "time_low" field */
    tmp32 = uuid->obj.time_low;
    out[3] = (uint8_t)(tmp32 & 0xff); tmp32 >>= 8;
    out[2] = (uint8_t)(tmp32 & 0xff); tmp32 >>= 8;
    out[1] = (uint8_t)(tmp32 & 0xff); tmp32 >>= 8;
    out[0] = (uint8_t)(tmp32 & 0xff);

    /* pack "time_mid" field */
    tmp16 = uuid->obj.time_mid;
    out[5] = (uint8_t)(tmp16 & 0xff); tmp16 >>= 8;
    out[4] = (uint8_t)(tmp16 & 0xff);

    /* pack "time_hi_and_version" field */
    tmp16 = uuid->obj.time_hi_and_version;
    out[7] = (uint8_t)(tmp16 & 0xff); tmp16 >>= 8;
    out[6] = (uint8_t)(tmp16 & 0xff);

    /* pack "clock_seq_hi_and_reserved" field */
    out[8] = uuid->obj.clock_seq_hi_and_reserved;

    /* pack "clock_seq_low" field */
    out[9] = uuid->obj.clock_seq_low;

    /* pack "node" field */
    for (i = 0; i < (unsigned int)sizeof(uuid->obj.node); i++)
        out[10+i] = uuid->obj.node[i];

    return UUID_RC_OK;
}

/* INTERNAL: check for valid UUID string representation syntax like "f81d4fae-7dec-11d0-a765-00a0c91e6bf6" */
static bool uuid_isstr(const char *str, size_t str_len)
{
    if (strnlen(str, str_len) != UUID_LEN_STR)
        return false;
    for (size_t i = 0; i < UUID_LEN_STR; i++, str++) {
        if ((i == 8) || (i == 13) || (i == 18) || (i == 23)) {
            if (*str == '-')
                continue;
            else
                return false;
        }
        if (!isxdigit((unsigned char)*str))
            return false;
    }
    return true;
}

/* INTERNAL: import UUID object from string representation */
static uuid_rc_t uuid_import_str(uuid_t *uuid, const void *data_ptr, size_t data_len)
{
    uint16_t tmp16;
    const char *cp;
    char hexbuf[3];
    const char *str = data_ptr;
    unsigned int i;

    /* sanity check argument(s) */
    if (uuid == NULL || data_ptr == NULL)
        return UUID_RC_ARG;

    /* check for correct UUID string representation syntax */
    if (!uuid_isstr(str, data_len))
        return UUID_RC_ARG;

    /* parse hex values of "time" parts */
    uuid->obj.time_low            = (uint32_t)strtoul(str,    NULL, 16);
    uuid->obj.time_mid            = (uint16_t)strtoul(str+9,  NULL, 16);
    uuid->obj.time_hi_and_version = (uint16_t)strtoul(str+14, NULL, 16);

    /* parse hex values of "clock" parts */
    tmp16 = (uint16_t)strtoul(str+19, NULL, 16);
    uuid->obj.clock_seq_low             = (uint8_t)(tmp16 & 0xff); tmp16 >>= 8;
    uuid->obj.clock_seq_hi_and_reserved = (uint8_t)(tmp16 & 0xff);

    /* parse hex values of "node" part */
    cp = str+24;
    hexbuf[2] = '\0';
    for (i = 0; i < (unsigned int)sizeof(uuid->obj.node); i++) {
        hexbuf[0] = *cp++;
        hexbuf[1] = *cp++;
        uuid->obj.node[i] = (uint8_t)strtoul(hexbuf, NULL, 16);
    }

    return UUID_RC_OK;
}

/* INTERNAL: import UUID object from single integer value representation */
static uuid_rc_t uuid_import_siv(uuid_t *uuid, const void *data_ptr, size_t data_len)
{
    /* sanity check argument(s) */
    if (uuid == NULL || data_ptr == NULL || data_len < 1)
        return UUID_RC_ARG;

    const char *str = data_ptr, *end;
    ui128_t parsed = strtou128(str, data_len, &end);
    if (end != str+data_len && *end != '\0')
        return UUID_RC_ARG;

    _Static_assert(UUID_LEN_BIN == sizeof(parsed.x), "16 != 16");
    uint8_t tmp[UUID_LEN_BIN];
    for(size_t i = 0; i < sizeof(tmp); ++i) // UUIDs are reverse endianness vs our 128-bit ints
        tmp[i] = parsed.x[(sizeof(parsed.x)-1) - i];

    /* import into internal UUID representation */
    return uuid_import(uuid, UUID_FMT_BIN, tmp, sizeof(tmp));
}

/* INTERNAL: export UUID object to string representation */
static uuid_rc_t uuid_export_str(const uuid_t *uuid, void *_data_ptr, size_t *data_len)
{
    char **data_ptr;
    char *data_buf;

    /* cast generic data pointer to particular pointer to pointer type */
    data_ptr = (char **)_data_ptr;

    /* sanity check argument(s) */
    if (uuid == NULL || data_ptr == NULL)
        return UUID_RC_ARG;

    /* determine output buffer */
    if (*data_ptr == NULL) {
        if ((data_buf = (char *)malloc(UUID_LEN_STR+1)) == NULL)
            return UUID_RC_MEM;
        if (data_len != NULL)
            *data_len = UUID_LEN_STR+1;
    }
    else {
        data_buf = (char *)(*data_ptr);
        if (data_len == NULL)
            return UUID_RC_ARG;
        if (*data_len < UUID_LEN_STR+1)
            return UUID_RC_MEM;
        *data_len = UUID_LEN_STR+1;
    }

    /* format UUID into string representation */
    if (snprintf(data_buf, UUID_LEN_STR+1,
        "%08lx-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x",
        (unsigned long)uuid->obj.time_low,
        (unsigned int)uuid->obj.time_mid,
        (unsigned int)uuid->obj.time_hi_and_version,
        (unsigned int)uuid->obj.clock_seq_hi_and_reserved,
        (unsigned int)uuid->obj.clock_seq_low,
        (unsigned int)uuid->obj.node[0],
        (unsigned int)uuid->obj.node[1],
        (unsigned int)uuid->obj.node[2],
        (unsigned int)uuid->obj.node[3],
        (unsigned int)uuid->obj.node[4],
        (unsigned int)uuid->obj.node[5]) != UUID_LEN_STR) {
        if (*data_ptr == NULL)
            free(data_buf);
        return UUID_RC_INT;
    }

    /* pass back new buffer if locally allocated */
    if (*data_ptr == NULL)
        *data_ptr = data_buf;

    return UUID_RC_OK;
}

/* INTERNAL: export UUID object to single integer value representation */
static uuid_rc_t uuid_export_siv(const uuid_t *uuid, void *_data_ptr, size_t *data_len)
{
    char **data_ptr;
    char *data_buf;
    void *tmp_ptr;
    size_t tmp_len;
    uint8_t tmp_bin[UUID_LEN_BIN];
    uuid_rc_t rc;

    /* cast generic data pointer to particular pointer to pointer type */
    data_ptr = (char **)_data_ptr;

    /* sanity check argument(s) */
    if (uuid == NULL || data_ptr == NULL)
        return UUID_RC_ARG;

    /* determine output buffer */
    if (*data_ptr == NULL) {
        if ((data_buf = (char *)malloc(UUID_LEN_SIV+1)) == NULL)
            return UUID_RC_MEM;
        if (data_len != NULL)
            *data_len = UUID_LEN_SIV+1;
    }
    else {
        data_buf = (char *)(*data_ptr);
        if (data_len == NULL)
            return UUID_RC_ARG;
        if (*data_len < UUID_LEN_SIV+1)
            return UUID_RC_MEM;
        *data_len = UUID_LEN_SIV+1;
    }

    /* export into UUID binary representation */
    tmp_ptr = (void *)&tmp_bin;
    tmp_len = sizeof(tmp_bin);
    if ((rc = uuid_export(uuid, UUID_FMT_BIN, &tmp_ptr, &tmp_len)) != UUID_RC_OK) {
        if (*data_ptr == NULL)
            free(data_buf);
        return rc;
    }

    /* import from UUID binary representation */
    ui128_t ret = {};
    _Static_assert(sizeof(tmp_bin) == sizeof(ret.x), "16 != 16");
    for(size_t i = 0; i < sizeof(tmp_bin); ++i) // UUIDs are reverse endianness vs our 128-bit ints
        ret.x[(sizeof(ret.x)-1) - i] = tmp_bin[i];
    u128tostr(ret, data_buf, UUID_LEN_SIV+1);

    /* pass back new buffer if locally allocated */
    if (*data_ptr == NULL)
        *data_ptr = data_buf;

    return UUID_RC_OK;
}

/* INTERNAL: dump UUID object as descriptive text */
static uuid_rc_t uuid_export_txt(const uuid_t *uuid, void *_data_ptr, size_t *data_len)
{
    char **data_ptr;
    uuid_rc_t rc;
    const char *version;
    const char *variant;
    char *content;
    int isnil, ismax;
    uint32_t tmp32;
    uint8_t tmp_bin[UUID_LEN_BIN];
    char tmp_str[UUID_LEN_STR+1];
    char tmp_siv[UUID_LEN_SIV+1];
    void *tmp_ptr;
    size_t tmp_len;
    char t_buf[sizeof("10889-08-02 07:31:50")]; // The maximal representable UUID time is 0x0FFFFFFFFFFFFFFF (5236-03-31 21:21:00);
                                                // the maximal UUIDv7 UNIX time is 0xFFFFFFFFFFFF, q.v.
    struct tm *tm;
    char out_buf[512], *cur = out_buf;

    /* cast generic data pointer to particular pointer to pointer type */
    data_ptr = (char **)_data_ptr;

    /* sanity check argument(s) */
    if (uuid == NULL || data_ptr == NULL)
        return UUID_RC_ARG;

    /* check for special case of "Nil" and "Max"  UUID */
    if ((rc = uuid_isnil(uuid, &isnil)) != UUID_RC_OK)
        return rc;
    if ((rc = uuid_ismax(uuid, &ismax)) != UUID_RC_OK)
        return rc;

    /* decode into various representations */
    tmp_ptr = (void *)&tmp_str;
    tmp_len = sizeof(tmp_str);
    if ((rc = uuid_export(uuid, UUID_FMT_STR, &tmp_ptr, &tmp_len)) != UUID_RC_OK)
        return rc;
    tmp_ptr = (void *)&tmp_siv;
    tmp_len = sizeof(tmp_siv);
    if ((rc = uuid_export(uuid, UUID_FMT_SIV, &tmp_ptr, &tmp_len)) != UUID_RC_OK)
        return rc;
    cur += sprintf(cur, "encode: STR:     %s\n", tmp_str);
    cur += sprintf(cur, "        SIV:     %s\n", tmp_siv);

    /* decode UUID variant */
    uint8_t cshar = uuid->obj.clock_seq_hi_and_reserved;
    unsigned variant_n = (cshar == 0xFF) ? 8 : __builtin_clz(~(unsigned)cshar << ((sizeof(unsigned) - 1) * 8));  // variant = count leading 1s
                                                                                                                 // TODO: replace with stdc_leading_ones
    if(isnil || ismax) variant = "n.a."; else
    switch(variant_n) {
        case 0:  variant = "reserved (NCS backward compatible)"; break;
        case 1:  variant = "DCE 1.1, ISO/IEC 11578:1996"; break;
        case 2:  variant = "reserved (Microsoft GUID)"; break;
        case 3:  variant = "reserved (future use)"; break;
        default: variant = "unknown"; break;
    }
    cur += sprintf(cur, "decode: variant: %s\n", variant);

    /* decode UUID version */
    unsigned version_n = (uuid->obj.time_hi_and_version >> 12) & 0b1111;
    if (isnil || ismax) version = "n.a."; else
    switch(version_n) {
        case 1: version = "time and node based"; break;
        case 6: version = "time and node based (time in reverse order)"; break;
        case 7: version = "UNIX time + random data"; break;
        case 3: version = "name based, MD5"; break;
        case 4: version = "random data based"; break;
        case 5: version = "name based, SHA-1"; break;
        default: version = "unknown"; break;
    }
    cur += sprintf(cur, "        version: %u (%s)\n", version_n, version);

    /*
     * decode UUID content
     */

    if (variant_n == 1 && (version_n == 1 || version_n == 6)) {
        /* decode RFC 9562 version 1 or 6 UUID */

        /* decode system time */
        uint64_t t = 0;
        if(version_n == 1) {
            t |= (uint64_t)(uuid->obj.time_low & 0xFFFFFFFF) << (64 - 64);
            t |= (uint64_t)(uuid->obj.time_mid & 0xFFFF) << (64 - 32);
            t |= (uint64_t)(uuid->obj.time_hi_and_version & 0x0FFF) << (64 - 16);
        } else {
            t |= (uint64_t)(uuid->obj.time_low & 0xFFFFFFFF) << 28;
            t |= (uint64_t)(uuid->obj.time_mid & 0xFFFF) << 12;
            t |= (uint64_t)(uuid->obj.time_hi_and_version & 0x0FFF) << 0;
        }

        uint64_t t_nsec = t % 10;
        t /= 10;
        uint64_t t_usec = t % 1000000;
        t /= 1000000;

        tm = gmtime(&(time_t){t - UUID_TIMEOFFSET});
        (void)strftime(t_buf, sizeof(t_buf), "%Y-%m-%d %H:%M:%S", tm);
        cur += sprintf(cur, "        content: time:  %s.%06" PRIu64 ".%" PRIu64 " UTC\n", t_buf, t_usec, t_nsec);

        /* decode clock sequence */
        tmp32 = ((uuid->obj.clock_seq_hi_and_reserved & 0b111111) << 8)
                + uuid->obj.clock_seq_low;
        cur += sprintf(cur, "                 clock: %ld (usually random)\n", (long)tmp32);

        /* decode node MAC address */
        cur += sprintf(cur, "                 node:  %02x:%02x:%02x:%02x:%02x:%02x (%s %s)\n",
            (unsigned int)uuid->obj.node[0],
            (unsigned int)uuid->obj.node[1],
            (unsigned int)uuid->obj.node[2],
            (unsigned int)uuid->obj.node[3],
            (unsigned int)uuid->obj.node[4],
            (unsigned int)uuid->obj.node[5],
            (uuid->obj.node[0] & IEEE_MAC_LOBIT ? "local" : "global"),
            (uuid->obj.node[0] & IEEE_MAC_MCBIT ? "multicast" : "unicast"));
    }
    else if (variant_n == 1 && version_n == 7) {
        /* decode RFC 9562 version 7 UUID */

        /* pack UUID into binary representation */
        tmp_ptr = (void *)&tmp_bin;
        tmp_len = sizeof(tmp_bin);
        if ((rc = uuid_export(uuid, UUID_FMT_BIN, &tmp_ptr, &tmp_len)) != UUID_RC_OK)
            return rc;

        /* decode system time */
        uint64_t t = ((uint64_t)tmp_bin[0] << 40) |
                     ((uint64_t)tmp_bin[1] << 32) |
                     ((uint64_t)tmp_bin[2] << 24) |
                     ((uint64_t)tmp_bin[3] << 16) |
                     ((uint64_t)tmp_bin[4] << 8) |
                     ((uint64_t)tmp_bin[5] << 0);
        uint64_t millis = t % 1000;
        t /= 1000;

        tm = gmtime(&(time_t){t});
        (void)strftime(t_buf, sizeof(t_buf), "%Y-%m-%d %H:%M:%S", tm);
        cur += sprintf(cur, "        content: time:   %s.%03" PRIu64 " UTC\n", t_buf, millis);

        tmp_bin[6] &= ~0b11110000; // clear version
        tmp_bin[8] &= ~0b11000000; // clear variant

        cur += sprintf(cur, "                 random: %02hhX:%02hhX:%02hhX:%02hhX:%02hhX:%02hhX:%02hhX:%02hhX:%02hhX:%02hhX\n",
                       tmp_bin[6], tmp_bin[7], tmp_bin[8], tmp_bin[9], tmp_bin[10], tmp_bin[11], tmp_bin[12], tmp_bin[13], tmp_bin[14], tmp_bin[15]);
    }
    else {
        /* decode anything else as hexadecimal byte-string only */
        if(isnil) content = "special case: DCE 1.1 Nil UUID"; else
        if(ismax) content = "special case: RFC 9562 Max UUID"; else
        switch(version_n) {
            case 3: content = "not decipherable: MD5 message digest only"; break;
            case 4: content = "no semantics: random data only"; break;
            case 5: content = "not decipherable: truncated SHA-1 message digest only"; break;
            default: content = "not decipherable: unknown UUID version"; break;
        }

        /* pack UUID into binary representation */
        tmp_ptr = (void *)&tmp_bin;
        tmp_len = sizeof(tmp_bin);
        if ((rc = uuid_export(uuid, UUID_FMT_BIN, &tmp_ptr, &tmp_len)) != UUID_RC_OK)
            return rc;

        /* mask out version and variant parts */
        tmp_bin[6] &= 0b1111;
        tmp_bin[8] &= 0b111111;

        /* dump as colon-seperated hexadecimal byte-string */
        cur += sprintf(cur,
            "        content: %02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X\n"
            "                 (%s)\n",
            (unsigned int)tmp_bin[0],  (unsigned int)tmp_bin[1],  (unsigned int)tmp_bin[2],
            (unsigned int)tmp_bin[3],  (unsigned int)tmp_bin[4],  (unsigned int)tmp_bin[5],
            (unsigned int)tmp_bin[6],  (unsigned int)tmp_bin[7],  (unsigned int)tmp_bin[8],
            (unsigned int)tmp_bin[9],  (unsigned int)tmp_bin[10], (unsigned int)tmp_bin[11],
            (unsigned int)tmp_bin[12], (unsigned int)tmp_bin[13], (unsigned int)tmp_bin[14],
            (unsigned int)tmp_bin[15], content);
    }

    /* provide result */
    if (*data_ptr == NULL) {
        *data_ptr = strdup(out_buf);
        if (data_len != NULL)
            *data_len = cur - out_buf + 1;
    }
    else {
        if (data_len == NULL)
            return UUID_RC_ARG;
        if (*data_len < (size_t)(cur - out_buf + 1))
            return UUID_RC_MEM;
        memcpy(*data_ptr, out_buf, cur - out_buf + 1);
    }

    return UUID_RC_OK;
}

/* UUID importing */
uuid_rc_t uuid_import(uuid_t *uuid, uuid_fmt_t fmt, const void *data_ptr, size_t data_len)
{
    uuid_rc_t rc;

    /* sanity check argument(s) */
    if (uuid == NULL || data_ptr == NULL)
        return UUID_RC_ARG;

    /* dispatch into format-specific functions */
    switch (fmt) {
        case UUID_FMT_BIN: rc = uuid_import_bin(uuid, data_ptr, data_len); break;
        case UUID_FMT_STR: rc = uuid_import_str(uuid, data_ptr, data_len); break;
        case UUID_FMT_SIV: rc = uuid_import_siv(uuid, data_ptr, data_len); break;
        case UUID_FMT_TXT: rc = UUID_RC_IMP; /* not implemented */ break;
        default:           rc = UUID_RC_ARG;
    }

    return rc;
}

/* UUID exporting */
uuid_rc_t uuid_export(const uuid_t *uuid, uuid_fmt_t fmt, void *data_ptr, size_t *data_len)
{
    uuid_rc_t rc;

    /* sanity check argument(s) */
    if (uuid == NULL || data_ptr == NULL)
        return UUID_RC_ARG;

    /* dispatch into format-specific functions */
    switch (fmt) {
        case UUID_FMT_BIN: rc = uuid_export_bin(uuid, data_ptr, data_len); break;
        case UUID_FMT_STR: rc = uuid_export_str(uuid, data_ptr, data_len); break;
        case UUID_FMT_SIV: rc = uuid_export_siv(uuid, data_ptr, data_len); break;
        case UUID_FMT_TXT: rc = uuid_export_txt(uuid, data_ptr, data_len); break;
        default:           rc = UUID_RC_ARG;
    }

    return rc;
}

/* INTERNAL: brand UUID with version and variant */
static void uuid_brand(uuid_t *uuid, unsigned int version)
{
    /* set version (as given) */
    uuid->obj.time_hi_and_version &= 0b111111111111;
    uuid->obj.time_hi_and_version |= (uint16_t)(version << 12);

    /* set variant (always DCE 1.1 only) */
    uuid->obj.clock_seq_hi_and_reserved &= 0b111111;
    uuid->obj.clock_seq_hi_and_reserved |= (0x02 << 6);
    return;
}

/* INTERNAL: generate UUID version 1 or 6: time, clock and node based */
static uuid_rc_t uuid_make_v1_v6(uuid_t *uuid, unsigned int mode, unsigned version)
{
    struct timespec time_now;
    uint16_t clck;

    /*
     *  GENERATE TIME
     */

    /* determine current system time and sequence counter */
    for (;;) {
        /* determine current system time */
        timespec_get(&time_now, TIME_UTC);

        /* check whether system time changed since last retrieve */
        if (!(    time_now.tv_sec         == uuid->time_last.tv_sec
              && (time_now.tv_nsec / 100) == (uuid->time_last.tv_nsec / 100))) {
            /* reset time sequence counter and continue */
            break;
        }
        /* stall the UUID generation until the system clock
           (rounded up to our 100ns resolution) catches up */
        sched_yield();
    }

    /* convert from timeval (sec,usec,UNIX) to UUID (nsec/100,UUID) format */
    uint64_t t = time_now.tv_sec + UUID_TIMEOFFSET;
    t *= 10000000;
    t += time_now.tv_nsec / 100;

    /* store the 60 LSB of the time in the UUID */
    if (version == 1) {
        uuid->obj.time_hi_and_version = (uint16_t)((t & 0x0FFF000000000000ull) >> (64 - 16)); /* 12 of 16 bits only! */
        uuid->obj.time_mid            = (uint16_t)((t & 0x0000FFFF00000000ull) >> (64 - 32)); /* all 16 bits */
        uuid->obj.time_low            = (uint32_t)((t & 0x00000000FFFFFFFFull) >> (64 - 64)); /* all 32 bits */
    } else { // version == 6
        uuid->obj.time_low            = (uint32_t)((t & 0x0FFFFFFFF0000000ull) >> 28);
        uuid->obj.time_mid            = (uint16_t)((t & 0x000000000FFFF000ull) >> 12);
        uuid->obj.time_hi_and_version = (uint16_t)((t & 0x0000000000000FFFull) >> 0);
    }

    /*
     *  GENERATE CLOCK
     */

    /* retrieve current clock sequence */
    clck = ((uuid->obj.clock_seq_hi_and_reserved & 0b111111) << 8)
           + uuid->obj.clock_seq_low;

    /* generate new random clock sequence (initially or if the
       time has stepped backwards or if generating v6)
       or else just increase it */
    if (   version != 1
        || clck == 0
        || (   time_now.tv_sec < uuid->time_last.tv_sec
            || (   time_now.tv_sec == uuid->time_last.tv_sec
                && time_now.tv_nsec < uuid->time_last.tv_nsec))) {
        if (!random_data(&uuid->random, (void *)&clck, sizeof(clck)))
            return UUID_RC_INT;
    }
    else
        clck++;
    clck %= (1 << 14);  // 2^14

    /* store back new clock sequence */
    uuid->obj.clock_seq_hi_and_reserved =
        (uuid->obj.clock_seq_hi_and_reserved & 0b11000000)
        | (uint8_t)((clck >> 8) & 0xff);
    uuid->obj.clock_seq_low =
        (uint8_t)(clck & 0xff);

    /*
     *  GENERATE NODE
     */

    if ((mode & UUID_MAKE_MC) || (selected_mac[0] & IEEE_MAC_MCBIT)) {
        /* generate random IEEE 802 local multicast MAC address */
        if (!random_data(&uuid->random, uuid->obj.node, sizeof(uuid->obj.node)))
            return UUID_RC_INT;
        uuid->obj.node[0] |= IEEE_MAC_MCBIT;
        uuid->obj.node[0] |= IEEE_MAC_LOBIT;
    }
    else {
        /* use real regular MAC address */
        memcpy(uuid->obj.node, selected_mac, sizeof(selected_mac));
    }

    /*
     *  FINISH
     */

    /* remember current system time for next iteration */
    uuid->time_last = time_now;

    /* brand with version and variant */
    uuid_brand(uuid, version);

    return UUID_RC_OK;
}

/* INTERNAL: pre-defined UUID values.
   (defined as network byte ordered octet stream) */
#define UUID_MAKE(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16) \
    { (uint8_t)(a1),  (uint8_t)(a2),  (uint8_t)(a3),  (uint8_t)(a4),  \
      (uint8_t)(a5),  (uint8_t)(a6),  (uint8_t)(a7),  (uint8_t)(a8),  \
      (uint8_t)(a9),  (uint8_t)(a10), (uint8_t)(a11), (uint8_t)(a12), \
      (uint8_t)(a13), (uint8_t)(a14), (uint8_t)(a15), (uint8_t)(a16) }
static const struct {
    char *name;
    uint8_t uuid[UUID_LEN_BIN];
} uuid_value_table[] = {
    { "nil",     /* 00000000-0000-0000-0000-000000000000 ("Nil UUID") */
      UUID_MAKE(0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00) },
    { "max",     /* ffffffff-ffff-ffff-ffff-ffffffffffff ("Max UUID") */
      UUID_MAKE(0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF) },
    { "ns:DNS",  /* 6ba7b810-9dad-11d1-80b4-00c04fd430c8 (see RFC 4122) */
      UUID_MAKE(0x6b,0xa7,0xb8,0x10,0x9d,0xad,0x11,0xd1,0x80,0xb4,0x00,0xc0,0x4f,0xd4,0x30,0xc8) },
    { "ns:URL",  /* 6ba7b811-9dad-11d1-80b4-00c04fd430c8 (see RFC 4122) */
      UUID_MAKE(0x6b,0xa7,0xb8,0x11,0x9d,0xad,0x11,0xd1,0x80,0xb4,0x00,0xc0,0x4f,0xd4,0x30,0xc8) },
    { "ns:OID",  /* 6ba7b812-9dad-11d1-80b4-00c04fd430c8 (see RFC 4122) */
      UUID_MAKE(0x6b,0xa7,0xb8,0x12,0x9d,0xad,0x11,0xd1,0x80,0xb4,0x00,0xc0,0x4f,0xd4,0x30,0xc8) },
    { "ns:X500", /* 6ba7b814-9dad-11d1-80b4-00c04fd430c8 (see RFC 4122) */
      UUID_MAKE(0x6b,0xa7,0xb8,0x14,0x9d,0xad,0x11,0xd1,0x80,0xb4,0x00,0xc0,0x4f,0xd4,0x30,0xc8) }
};

/* load UUID object with pre-defined value */
uuid_rc_t uuid_load(uuid_t *uuid, const char *name)
{
    const uint8_t *uuid_octets;
    uuid_rc_t rc;
    unsigned int i;

    /* sanity check argument(s) */
    if (uuid == NULL || name == NULL)
        return UUID_RC_ARG;

    /* search for UUID in table */
    uuid_octets = NULL;
    for (i = 0; i < (unsigned int)sizeof(uuid_value_table)/sizeof(uuid_value_table[0]); i++) {
         if (strcmp(uuid_value_table[i].name, name) == 0) {
             uuid_octets = uuid_value_table[i].uuid;
             break;
         }
    }
    if (uuid_octets == NULL)
        return UUID_RC_ARG;

    /* import value into UUID object */
    if ((rc = uuid_import(uuid, UUID_FMT_BIN, uuid_octets, UUID_LEN_BIN)) != UUID_RC_OK)
        return rc;

    return UUID_RC_OK;
}

/* INTERNAL: generate UUID version 3: name based with MD5 */
static uuid_rc_t uuid_make_v3(uuid_t *uuid, unsigned int mode, va_list ap)
{
    (void)mode;
    char *str;
    uuid_t *uuid_ns;
    uint8_t uuid_buf[UUID_LEN_BIN];
    void *uuid_ptr;
    size_t uuid_len;
    uuid_rc_t rc;

    /* determine namespace UUID and name string arguments */
    if ((uuid_ns = va_arg(ap, uuid_t *)) == NULL)
        return UUID_RC_ARG;
    if ((str = va_arg(ap, char *)) == NULL)
        return UUID_RC_ARG;

    /* initialize MD5 context */
    MD5_CTX ctx;
    MD5Init(&ctx);

    /* load the namespace UUID into MD5 context */
    uuid_ptr = (void *)&uuid_buf;
    uuid_len = sizeof(uuid_buf);
    if ((rc = uuid_export(uuid_ns, UUID_FMT_BIN, &uuid_ptr, &uuid_len)) != UUID_RC_OK)
        return rc;
    MD5Update(&ctx, uuid_buf, uuid_len);

    /* load the argument name string into MD5 context */
    MD5Update(&ctx, (void *)str, strlen(str));

    /* store MD5 result into UUID
       (requires MD5_LEN_BIN space, UUID_LEN_BIN space is available,
       and both are equal in size, so we are safe!) */
    uuid_ptr = (void *)&(uuid->obj);
    MD5Final(uuid_ptr, &ctx);

    /* fulfill requirement of standard and convert UUID data into
       local/host byte order (this uses fact that uuid_import_bin() is
       able to operate in-place!) */
    if ((rc = uuid_import(uuid, UUID_FMT_BIN, (void *)&(uuid->obj), UUID_LEN_BIN)) != UUID_RC_OK)
        return rc;

    /* brand UUID with version and variant */
    uuid_brand(uuid, 3);

    return UUID_RC_OK;
}

/* INTERNAL: generate UUID version 4: random number based */
static uuid_rc_t uuid_make_v4(uuid_t *uuid, unsigned int mode, va_list ap)
{
    (void)mode, (void)ap;
    /* fill UUID with random data */
    if (!random_data(&uuid->random, (void *)&(uuid->obj), sizeof(uuid->obj)))
        return UUID_RC_INT;

    /* brand UUID with version and variant */
    uuid_brand(uuid, 4);

    return UUID_RC_OK;
}

/* INTERNAL: generate UUID version 5: name based with SHA-1 */
static uuid_rc_t uuid_make_v5(uuid_t *uuid, unsigned int mode, va_list ap)
{
    (void)mode;
    char *str;
    uuid_t *uuid_ns;
    uint8_t uuid_buf[UUID_LEN_BIN];
    void *uuid_ptr;
    size_t uuid_len;
    uint8_t sha1_buf[SHA1_DIGEST_LENGTH];
    uuid_rc_t rc;

    /* determine namespace UUID and name string arguments */
    if ((uuid_ns = (uuid_t *)va_arg(ap, void *)) == NULL)
        return UUID_RC_ARG;
    if ((str = (char *)va_arg(ap, char *)) == NULL)
        return UUID_RC_ARG;

    /* initialize SHA-1 context */
    SHA1_CTX ctx;
    SHA1Init(&ctx);

    /* load the namespace UUID into SHA-1 context */
    uuid_ptr = (void *)&uuid_buf;
    uuid_len = sizeof(uuid_buf);
    if ((rc = uuid_export(uuid_ns, UUID_FMT_BIN, &uuid_ptr, &uuid_len)) != UUID_RC_OK)
        return rc;
    SHA1Update(&ctx, uuid_buf, uuid_len);

    /* load the argument name string into SHA-1 context */
    SHA1Update(&ctx, (void *)str, strlen(str));

    /* store SHA-1 result into UUID
       (requires SHA1_DIGEST_LENGTH space, but UUID_LEN_BIN space is available
       only, so use a temporary buffer to store SHA-1 results and then
       use lower part only according to standard */
    SHA1Final(sha1_buf, &ctx);
    memcpy(&(uuid->obj), sha1_buf, UUID_LEN_BIN);

    /* fulfill requirement of standard and convert UUID data into
       local/host byte order (this uses fact that uuid_import_bin() is
       able to operate in-place!) */
    if ((rc = uuid_import(uuid, UUID_FMT_BIN, (void *)&(uuid->obj), UUID_LEN_BIN)) != UUID_RC_OK) // TODO: strict aliasing? packing? what the fuck?
        return rc;

    /* brand UUID with version and variant */
    uuid_brand(uuid, 5);

    return UUID_RC_OK;
}

/* INTERNAL: generate UUID version 7: UNIX timestamp + random data */
static uuid_rc_t uuid_make_v7(uuid_t *uuid, unsigned int mode, va_list ap)
{
    (void)mode, (void)ap;

    struct timespec time_now;
    timespec_get(&time_now, TIME_UTC);
    uint64_t millis = time_now.tv_sec * 1000 + time_now.tv_nsec / 1000000;

    uint8_t uuid_buf[UUID_LEN_BIN] = {};
    uuid_buf[0] = (millis & 0xFF0000000000) >> 40;
    uuid_buf[1] = (millis & 0x00FF00000000) >> 32;
    uuid_buf[2] = (millis & 0x0000FF000000) >> 24;
    uuid_buf[3] = (millis & 0x000000FF0000) >> 16;
    uuid_buf[4] = (millis & 0x00000000FF00) >> 8;
    uuid_buf[5] = (millis & 0x0000000000FF) >> 0;

    if (!random_data(&uuid->random, &uuid_buf[6], sizeof(uuid_buf)-6))
        return UUID_RC_INT;

    uuid_rc_t rc;
    if ((rc = uuid_import(uuid, UUID_FMT_BIN, uuid_buf, sizeof(uuid_buf))) != UUID_RC_OK)
        return rc;

    uuid_brand(uuid, 7);
    return UUID_RC_OK;
}

/* generate UUID */
uuid_rc_t uuid_make(uuid_t *uuid, unsigned int mode, ...)
{
    va_list ap;
    uuid_rc_t rc;

    /* sanity check argument(s) */
    if (uuid == NULL)
        return UUID_RC_ARG;

    /* dispatch into version dependent generation functions */
    va_start(ap, mode);
    if (mode & UUID_MAKE_V1)
        rc = uuid_make_v1_v6(uuid, mode, 1);
    else if (mode & UUID_MAKE_V3)
        rc = uuid_make_v3(uuid, mode, ap);
    else if (mode & UUID_MAKE_V4)
        rc = uuid_make_v4(uuid, mode, ap);
    else if (mode & UUID_MAKE_V5)
        rc = uuid_make_v5(uuid, mode, ap);
    else if (mode & UUID_MAKE_V6)
        rc = uuid_make_v1_v6(uuid, mode, 6);
    else if (mode & UUID_MAKE_V7)
        rc = uuid_make_v7(uuid, mode, ap);
    else
        rc = UUID_RC_ARG;
    va_end(ap);

    return rc;
}

/* translate UUID API error code into corresponding error string */
char *uuid_error(uuid_rc_t rc)
{
    switch (rc) {
        case UUID_RC_OK:  return "everything ok";
        case UUID_RC_ARG: return "invalid argument";
        case UUID_RC_MEM: return "out of memory";
        case UUID_RC_SYS: return "system error";
        case UUID_RC_INT: return "internal error";
        case UUID_RC_IMP: return "not implemented";
        default:          return NULL;
    }
}

/* OSSP uuid version (link-time information) */
unsigned long uuid_version(void)
{
    return (unsigned long)UUID_VERSION;
}
