summaryrefslogtreecommitdiff
path: root/src/pk.h
diff options
context:
space:
mode:
authorJonathan Bradley <jcb@pikum.xyz>2025-03-20 11:06:50 -0400
committerJonathan Bradley <jcb@pikum.xyz>2025-03-21 11:04:43 -0400
commit9b39b4c8eab360e087423f06ecffb694a3b98b23 (patch)
treed35a32bc037bd9ed590d22022447fc11afb04e25 /src/pk.h
parent95ee6f829b9ba379e883f4295d96dad5a0c738dd (diff)
pke: update pk.h to 0.4.2
Diffstat (limited to 'src/pk.h')
-rw-r--r--src/pk.h330
1 files changed, 328 insertions, 2 deletions
diff --git a/src/pk.h b/src/pk.h
index 78b294a..30051a1 100644
--- a/src/pk.h
+++ b/src/pk.h
@@ -1,7 +1,7 @@
#ifndef PK_SINGLE_HEADER_FILE_H
#define PK_SINGLE_HEADER_FILE_H
/*******************************************************************************
-* PK Single-Header-Library V0.4.0
+* PK Single-Header-Library V0.4.2
*
* Author: Jonathan Bradley
* Copyright: © 2024-2025 Jonathan Bradley
@@ -145,6 +145,7 @@
*
* Initialize `stride`, `alignment`, and `bkt` (optional) members
* *before* calling any `pk_arr_*` methods.
+* Alternatively, if using c++, use the template ctor.
*
* Examples:
* ``` c
@@ -155,6 +156,11 @@
* pk_arr_reserve(&arr, 10); // optional
* pk_arr_append(&arr, &obj);
* ```
+* ``` c++
+* struct pk_arr<some_type> arr(bkt);
+* pk_arr_reserve(&arr, 10); // optional
+* pk_arr_append(&arr, &obj);
+* ```
* ``` c
* struct pk_arr arr = {0};
* arr.stride = sizeof(obj); // required
@@ -164,6 +170,11 @@
* obj* d = (obj*)arr->data;
* d[0] = ...;
* ```
+* ``` c++
+* struct pk_arr_t<some_type> arr();
+* pk_arr_resize(&arr, 10);
+* arr[0] = {};
+* ```
*
********************************************************************************
* pkstn.h: def PK_IMPL_STN before including pk.h to enable ad-hoc.
@@ -185,9 +196,27 @@
* `pk_tmr.e` end the end time.
* You could then call the `pk_tmr_duration...` convenience macros as needed.
*
+********************************************************************************
+* pkuuid.h: define PK_IMPL_UUID before including pk.h to enable ad-hoc.
+*
+* Provides a 16-byte unsigned char array struct for uuids.
+*
+* The following definitions (shown with defaults) can be overridden:
+* PK_UUID_CLOCK CLOCK_TAI (preferred, if available)
+* PK_UUID_CLOCK CLOCK_REALTIME (fallback)
+*
+* The `PK_UUID_CLOCK` macro has minimal built-in fallback logic.
+* The uuidv7 specification states that the timestamp portion of the uuid must be
+* a unix epoch, leap seconds EXCLUDED. Only `CLOCK_TAI` meets this requirement
+* on Linux.
+*
+* Note that this currectly calls `srand()` once at startup, and calls `rand()`
+* 2 times for each uuidv7 to fill 74 bits with random data (with an XOR for the
+* remaining 10 bits).
+*
*******************************************************************************/
-#define PK_VERSION "0.4.0"
+#define PK_VERSION "0.4.2"
#ifdef PK_IMPL_ALL
# ifndef PK_IMPL_MEM_TYPES
@@ -208,6 +237,9 @@
# ifndef PK_IMPL_STN
# define PK_IMPL_STN
# endif
+# ifndef PK_IMPL_UUID
+# define PK_IMPL_UUID
+# endif
#endif
#ifndef PK_MACROS_H
#define PK_MACROS_H
@@ -1597,8 +1629,82 @@ void pk_arr_resize(struct pk_arr *arr, uint32_t count);
void pk_arr_move_to_back(struct pk_arr *arr, uint32_t index);
void pk_arr_append(struct pk_arr *arr, void *data);
void pk_arr_remove_at(struct pk_arr *arr, uint32_t index);
+void pk_arr_clone(struct pk_arr *lhs, struct pk_arr *rhs);
+void pk_arr_swap(struct pk_arr *lhs, struct pk_arr *rhs);
uint32_t pk_arr_find_first_index(struct pk_arr *arr, void *user_data, pk_arr_item_compare *fn);
+#if defined(__cplusplus)
+template<typename T>
+struct pk_arr_t : public pk_arr {
+ pk_arr_t();
+ pk_arr_t(struct pk_membucket *bkt);
+ pk_arr_t(const pk_arr_t<T> &other);
+ pk_arr_t(pk_arr_t<T> &&other);
+ pk_arr_t &operator=(const pk_arr_t<T> &other);
+ pk_arr_t &operator=(pk_arr_t<T> &&other);
+ ~pk_arr_t();
+ T &operator[](size_t index);
+};
+template<typename T>
+pk_arr_t<T>::pk_arr_t() {
+ this->next = 0;
+ this->reserved = 0;
+ this->stride = sizeof(T);
+ this->alignment = alignof(T);
+ this->bkt = NULL;
+ this->data = NULL;
+}
+template<typename T>
+pk_arr_t<T>::pk_arr_t(struct pk_membucket *bkt) : pk_arr_t<T>() {
+ this->bkt = bkt;
+}
+template<typename T>
+pk_arr_t<T>::pk_arr_t(const pk_arr_t<T> &other) {
+ // copy ctor
+ pk_arr_clone(static_cast<struct pk_arr_t *>(&const_cast<pk_arr_t<T>&>(other)), this);
+}
+template<typename T>
+pk_arr_t<T>::pk_arr_t(pk_arr_t<T> &&other) {
+ // move ctor
+ pk_arr_swap(this, &other);
+ other.data = NULL;
+}
+template<typename T>
+pk_arr_t<T> &
+pk_arr_t<T>::operator=(const pk_arr_t<T> &other) {
+ // copy assignment
+ if (this->data != NULL) {
+ pk_arr_reset(this);
+ }
+ pk_arr_clone(static_cast<struct pk_arr_t *>(&const_cast<pk_arr_t<T>&>(other)), this);
+ return *this;
+}
+template<typename T>
+pk_arr_t<T> &
+pk_arr_t<T>::operator=(pk_arr_t<T> &&other) {
+ // move assignment
+ if (this->data != NULL) {
+ pk_arr_reset(this);
+ }
+ pk_arr_swap(this, &other);
+ other.data = NULL;
+ return *this;
+}
+template<typename T>
+pk_arr_t<T>::~pk_arr_t() {
+ if (this->data != NULL) pk_delete(this->data, this->stride * this->reserved, this->bkt);
+}
+template<typename T>
+T &pk_arr_t<T>::operator[](size_t index) {
+ if(index >= this->next) throw "pk_arr_t<T>::operator[] out of range";
+ return reinterpret_cast<T*>(this->data)[index];
+}
+template<typename T>
+void pk_arr_append_t(pk_arr_t<T> *arr, const T &item) {
+ pk_arr_append(arr, &const_cast<T&>(item));
+}
+#endif
+
#endif /* PK_PKARR_H */
#ifdef PK_IMPL_ARR
@@ -1768,6 +1874,24 @@ pk_arr_remove_at(struct pk_arr *arr, uint32_t index)
arr->next -= 1;
}
+void
+pk_arr_clone(struct pk_arr *lhs, struct pk_arr *rhs) {
+ size_t sz;
+ *rhs = *lhs;
+ if (lhs->data == NULL) return;
+ sz = lhs->stride * lhs->reserved;
+ rhs->data = pk_new(sz, lhs->alignment, lhs->bkt);
+ memcpy(rhs->data, lhs->data, sz);
+}
+
+void
+pk_arr_swap(struct pk_arr *lhs, struct pk_arr *rhs)
+{
+ struct pk_arr tmp = *lhs;
+ *lhs = *rhs;
+ *rhs = tmp;
+}
+
uint32_t
pk_arr_find_first_index(struct pk_arr *arr, void *user_data, pk_arr_item_compare *fn)
{
@@ -2083,4 +2207,206 @@ struct pk_tmr {
#define pk_tmr_duration_dbl_scnd(tmr) ((tmr.e.tv_sec + 1e-9 * tmr.e.tv_nsec) - (tmr.b.tv_sec + 1e-9 * tmr.b.tv_nsec))
#endif /* PK_PKTMR_H */
+#ifndef PK_UUID_H
+#define PK_UUID_H
+
+#include "stddef.h"
+#include <time.h>
+
+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();
+
+#if defined(__cplusplus)
+#include <ostream>
+#include <iomanip>
+std::ostream& operator<<(std::ostream &o, const struct pk_uuid& uuid);
+std::istream& operator>>(std::istream &i, struct pk_uuid& uuid);
+#endif
+
+#endif /* PK_UUID_H */
+
+#ifdef PK_IMPL_UUID
+
+#include <stdatomic.h>
+#include <stdlib.h>
+#include <stdint.h>
+
+// 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;
+}
+
+#if defined(__cplusplus)
+std::ostream&
+operator<<(std::ostream &o, const struct pk_uuid& uuid)
+{
+ int i;
+ 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];
+ }
+ return o;
+}
+
+std::istream&
+operator>>(std::istream &i, struct pk_uuid& uuid)
+{
+ char c[3];
+ char k = 0;
+ char offset = 0;
+ c[2] = '\0';
+ for (k = 0; k < 20; ++k) {
+ if (k == 4 || k == 7 || k == 10 || k == 13) {
+ offset += 1;
+ c[0] = i.peek();
+ if (c[0] != '-') {
+ goto err_out;
+ }
+ i.get(); // burn
+ continue;
+ }
+ i.get(c[0]);
+ i.get(c[1]);
+ if (pk_stn_uint8_t(&uuid.uuid[k - offset], c, 16) != PK_STN_RES_SUCCESS) {
+ goto err_out;
+ }
+ }
+ return i;
+err_out:
+ i.seekg(-(((k + 1) * 2) + offset), std::ios_base::cur);
+ uuid = pk_uuid_zed;
+ return i;
+}
+#endif
+
+#endif
#endif /* PK_SINGLE_HEADER_FILE_H */