File Coverage

include/sekhmet_ulid.h
Criterion Covered Total %
statement 88 90 97.7
branch 33 42 78.5
condition n/a
subroutine n/a
pod n/a
total 121 132 91.6


line stmt bran cond sub pod time code
1             #ifndef SEKHMET_ULID_H
2             #define SEKHMET_ULID_H
3              
4             /*
5             * sekhmet_ulid.h - ULID generation, encoding, decoding, conversion
6             *
7             * Binary ULID layout (16 bytes, big-endian):
8             * Bytes 0-5: 48-bit Unix epoch milliseconds
9             * Bytes 6-15: 80-bit cryptographic randomness
10             *
11             * Crockford base32 encoding (26 chars):
12             * Chars 0-9: timestamp (48 bits, first char uses only 3 bits)
13             * Chars 10-25: randomness (80 bits)
14             *
15             * Depends on horus_core.h for:
16             * horus_unix_epoch_ms(), horus_random_bytes(),
17             * horus_crockford_encode(), horus_crockford_decode()
18             */
19              
20             #include
21             #include
22              
23             /* ── Monotonic state ────────────────────────────────────────────── */
24              
25             typedef struct {
26             uint64_t last_ms;
27             unsigned char last_rand[10]; /* 80 bits of randomness */
28             } sekhmet_monotonic_state_t;
29              
30             /* ── Timestamp helpers ──────────────────────────────────────────── */
31              
32 2914           static inline void sekhmet_pack_timestamp(unsigned char *out, uint64_t ms) {
33 2914           out[0] = (unsigned char)((ms >> 40) & 0xFF);
34 2914           out[1] = (unsigned char)((ms >> 32) & 0xFF);
35 2914           out[2] = (unsigned char)((ms >> 24) & 0xFF);
36 2914           out[3] = (unsigned char)((ms >> 16) & 0xFF);
37 2914           out[4] = (unsigned char)((ms >> 8) & 0xFF);
38 2914           out[5] = (unsigned char)(ms & 0xFF);
39 2914           }
40              
41 18           static inline uint64_t sekhmet_unpack_timestamp(const unsigned char *ulid) {
42 18           return ((uint64_t)ulid[0] << 40)
43 18           | ((uint64_t)ulid[1] << 32)
44 18           | ((uint64_t)ulid[2] << 24)
45 18           | ((uint64_t)ulid[3] << 16)
46 18           | ((uint64_t)ulid[4] << 8)
47 18           | (uint64_t)ulid[5];
48             }
49              
50             /* ── Generate ULID (random mode) ───────────────────────────────── */
51              
52 1132           static inline void sekhmet_ulid_generate(unsigned char *out) {
53 1132           uint64_t ms = horus_unix_epoch_ms();
54 1132           sekhmet_pack_timestamp(out, ms);
55 1132           horus_random_bytes(out + 6, 10);
56 1132           }
57              
58             /* ── Generate ULID (monotonic mode) ────────────────────────────── */
59              
60 1782           static inline void sekhmet_ulid_monotonic(unsigned char *out,
61             sekhmet_monotonic_state_t *state) {
62 1782           uint64_t ms = horus_unix_epoch_ms();
63 1782           sekhmet_pack_timestamp(out, ms);
64              
65 1782 100         if (ms == state->last_ms) {
66             /* Same millisecond: increment 80-bit random (big-endian) */
67 1770           int i, carry = 1;
68 3548 50         for (i = 9; i >= 0 && carry; i--) {
    100          
69 1778           int val = (int)state->last_rand[i] + carry;
70 1778           state->last_rand[i] = (unsigned char)(val & 0xFF);
71 1778           carry = val >> 8;
72             }
73 1770 50         if (carry) {
74             /* Overflow of 2^80 — reseed (practically impossible) */
75 0           horus_random_bytes(state->last_rand, 10);
76             }
77 1770           memcpy(out + 6, state->last_rand, 10);
78             } else {
79             /* New millisecond: fresh random */
80 12           horus_random_bytes(out + 6, 10);
81 12           state->last_ms = ms;
82 12           memcpy(state->last_rand, out + 6, 10);
83             }
84 1782           }
85              
86             /* ── Encode binary ULID to 26-char Crockford string ────────────── */
87              
88 2858           static inline void sekhmet_ulid_encode(char *dst, const unsigned char *ulid) {
89 2858           horus_crockford_encode(dst, ulid);
90 2858           }
91              
92             /* ── Decode 26-char Crockford string to binary ULID ────────────── */
93              
94 107           static inline int sekhmet_ulid_decode(unsigned char *dst,
95             const char *src, int len) {
96 107 50         if (len != 26) return 0;
97 107           return horus_crockford_decode(dst, src, len) == 16;
98             }
99              
100             /* ── Extract timestamp as epoch seconds (double) ───────────────── */
101              
102 4           static inline double sekhmet_ulid_time(const unsigned char *ulid) {
103 4           uint64_t ms = sekhmet_unpack_timestamp(ulid);
104 4           return (double)ms / 1000.0;
105             }
106              
107             /* ── Extract timestamp as epoch milliseconds ───────────────────── */
108              
109 14           static inline uint64_t sekhmet_ulid_time_ms(const unsigned char *ulid) {
110 14           return sekhmet_unpack_timestamp(ulid);
111             }
112              
113             /* ── ULID → UUID string conversion ─────────────────────────────── *
114             *
115             * ULID: [48-bit ts][80-bit random]
116             * UUIDv7: [48-bit ts][4-bit ver=7][12-bit rand_a][2-bit var=10][62-bit rand_b]
117             *
118             * We stamp version=7 and variant=RFC4122 into the binary, then format
119             * as a standard UUID hyphenated string.
120             */
121              
122 8           static inline void sekhmet_format_uuid(char *dst, const unsigned char *uuid) {
123             static const char hex[] = "0123456789abcdef";
124 8           int i, j = 0;
125 136 100         for (i = 0; i < 16; i++) {
126 128 100         if (i == 4 || i == 6 || i == 8 || i == 10)
    100          
    100          
    100          
127 32           dst[j++] = '-';
128 128           dst[j++] = hex[(uuid[i] >> 4) & 0x0F];
129 128           dst[j++] = hex[uuid[i] & 0x0F];
130             }
131 8           }
132              
133 8           static inline void sekhmet_ulid_to_uuid_bin(unsigned char *uuid,
134             const unsigned char *ulid) {
135 8           memcpy(uuid, ulid, 16);
136             /* Stamp version 7: byte 6 high nibble = 0x7 */
137 8           uuid[6] = (uuid[6] & 0x0F) | 0x70;
138             /* Stamp variant RFC4122: byte 8 high 2 bits = 10 */
139 8           uuid[8] = (uuid[8] & 0x3F) | 0x80;
140 8           }
141              
142 8           static inline void sekhmet_ulid_to_uuid_str(char *dst,
143             const unsigned char *ulid) {
144             unsigned char uuid[16];
145 8           sekhmet_ulid_to_uuid_bin(uuid, ulid);
146 8           sekhmet_format_uuid(dst, uuid);
147 8           }
148              
149             /* ── UUID string → ULID conversion ─────────────────────────────── *
150             *
151             * Parse UUID hex string, strip version/variant bits, recover ULID binary.
152             */
153              
154 7           static inline int sekhmet_parse_uuid_hex(unsigned char *out,
155             const char *src, int len) {
156             static const unsigned char hv[256] = {
157             ['0'] = 0, ['1'] = 1, ['2'] = 2, ['3'] = 3,
158             ['4'] = 4, ['5'] = 5, ['6'] = 6, ['7'] = 7,
159             ['8'] = 8, ['9'] = 9,
160             ['a'] = 10, ['b'] = 11, ['c'] = 12, ['d'] = 13,
161             ['e'] = 14, ['f'] = 15,
162             ['A'] = 10, ['B'] = 11, ['C'] = 12, ['D'] = 13,
163             ['E'] = 14, ['F'] = 15,
164             };
165 7           int i, j = 0;
166 147 100         for (i = 0; i < len && j < 16; i++) {
    50          
167 140           char c = src[i];
168 140 100         if (c == '-') continue;
169 112 50         if (i + 1 >= len) return 0;
170             {
171 112           char c2 = src[++i];
172             /* skip hyphens in second nibble position too */
173 112 50         while (c2 == '-' && i + 1 < len) c2 = src[++i];
    0          
174 112           out[j++] = (unsigned char)((hv[(unsigned char)c] << 4)
175 112           | hv[(unsigned char)c2]);
176             }
177             }
178 7           return j == 16;
179             }
180              
181 7           static inline int sekhmet_uuid_to_ulid_bin(unsigned char *ulid,
182             const char *uuid_str, int len) {
183             unsigned char uuid[16];
184 7 50         if (!sekhmet_parse_uuid_hex(uuid, uuid_str, len))
185 0           return 0;
186              
187 7           memcpy(ulid, uuid, 16);
188             /* Restore original ULID random bits (clear version/variant stamps) */
189 7           ulid[6] = (ulid[6] & 0x0F) | (uuid[6] & 0xF0);
190 7           ulid[8] = (ulid[8] & 0x3F) | (uuid[8] & 0xC0);
191 7           return 1;
192             }
193              
194             /* ── Compare two binary ULIDs ──────────────────────────────────── */
195              
196 42           static inline int sekhmet_ulid_compare(const unsigned char *a,
197             const unsigned char *b) {
198 42           return memcmp(a, b, 16);
199             }
200              
201             /* ── Validate a ULID string ────────────────────────────────────── */
202              
203 128           static inline int sekhmet_ulid_validate(const char *str, int len) {
204             static const unsigned char valid[256] = {
205             ['0'] = 1, ['1'] = 1, ['2'] = 1, ['3'] = 1, ['4'] = 1,
206             ['5'] = 1, ['6'] = 1, ['7'] = 1, ['8'] = 1, ['9'] = 1,
207             ['A'] = 1, ['B'] = 1, ['C'] = 1, ['D'] = 1, ['E'] = 1,
208             ['F'] = 1, ['G'] = 1, ['H'] = 1, ['J'] = 1, ['K'] = 1,
209             ['M'] = 1, ['N'] = 1, ['P'] = 1, ['Q'] = 1, ['R'] = 1,
210             ['S'] = 1, ['T'] = 1, ['V'] = 1, ['W'] = 1, ['X'] = 1,
211             ['Y'] = 1, ['Z'] = 1,
212             ['a'] = 1, ['b'] = 1, ['c'] = 1, ['d'] = 1, ['e'] = 1,
213             ['f'] = 1, ['g'] = 1, ['h'] = 1, ['j'] = 1, ['k'] = 1,
214             ['m'] = 1, ['n'] = 1, ['p'] = 1, ['q'] = 1, ['r'] = 1,
215             ['s'] = 1, ['t'] = 1, ['v'] = 1, ['w'] = 1, ['x'] = 1,
216             ['y'] = 1, ['z'] = 1,
217             };
218             int i;
219 128 100         if (len != 26) return 0;
220             /* First char must be <= '7' (max timestamp 2^48-1 fits in 3 bits) */
221 125 100         if ((unsigned char)str[0] > '7') return 0;
222 3314 100         for (i = 0; i < 26; i++) {
223 3198 100         if (!valid[(unsigned char)str[i]]) return 0;
224             }
225 116           return 1;
226             }
227              
228             #endif /* SEKHMET_ULID_H */