diff options
| author | Anders Betts <anders.betts@gmail.com> | 2026-09-17 19:55:36 +0200 |
|---|---|---|
| committer | Anders Betts <anders.betts@gmail.com> | 2026-09-17 19:55:36 +0200 |
| commit | 380195f7cd5e57acf2c1cf2bc41069e6b0b979ed (patch) | |
| tree | 32a88fb22a7fbe8f1fd5c105156d1f928c93950d /src/util.c | |
| download | bokf-a154c5d9585d54ebdaae7234e65bbf120121698d.tar.gz bokf-a154c5d9585d54ebdaae7234e65bbf120121698d.zip | |
Initial commit: daemon, clients, docs, Docker deploy pipelinev0.1.0
Diffstat (limited to 'src/util.c')
| -rw-r--r-- | src/util.c | 564 |
1 files changed, 564 insertions, 0 deletions
diff --git a/src/util.c b/src/util.c new file mode 100644 index 0000000..9543875 --- /dev/null +++ b/src/util.c @@ -0,0 +1,564 @@ +#include "util.h" + +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/random.h> +#include <time.h> +#include <unistd.h> + +#include "sha256.h" + +void *xmalloc(size_t n) +{ + void *p = malloc(n ? n : 1); + if (!p) { + perror("malloc"); + exit(1); + } + return p; +} + +void *xcalloc(size_t n, size_t sz) +{ + void *p = calloc(n ? n : 1, sz ? sz : 1); + if (!p) { + perror("calloc"); + exit(1); + } + return p; +} + +void *xrealloc(void *p, size_t n) +{ + void *q = realloc(p, n ? n : 1); + if (!q) { + perror("realloc"); + exit(1); + } + return q; +} + +char *xstrdup(const char *s) +{ + char *p = strdup(s); + if (!p) { + perror("strdup"); + exit(1); + } + return p; +} + +void buf_init(struct buf *b) +{ + b->p = NULL; + b->len = 0; + b->cap = 0; +} + +void buf_free(struct buf *b) +{ + free(b->p); + b->p = NULL; + b->len = b->cap = 0; +} + +static void buf_reserve(struct buf *b, size_t extra) +{ + if (b->len + extra <= b->cap) + return; + size_t nc = b->cap ? b->cap : 64; + while (nc < b->len + extra) + nc *= 2; + b->p = xrealloc(b->p, nc); + b->cap = nc; +} + +void buf_append(struct buf *b, const void *data, size_t n) +{ + if (!n) + return; + buf_reserve(b, n); + memcpy(b->p + b->len, data, n); + b->len += n; +} + +void buf_append_u16be(struct buf *b, uint16_t v) +{ + unsigned char t[2] = { (unsigned char)(v >> 8), (unsigned char)v }; + buf_append(b, t, sizeof t); +} + +void buf_append_u32be(struct buf *b, uint32_t v) +{ + unsigned char t[4] = { (unsigned char)(v >> 24), (unsigned char)(v >> 16), + (unsigned char)(v >> 8), (unsigned char)v }; + buf_append(b, t, sizeof t); +} + +void buf_append_u64be(struct buf *b, uint64_t v) +{ + unsigned char t[8]; + for (int i = 0; i < 8; i++) + t[i] = (unsigned char)(v >> (56 - 8 * i)); + buf_append(b, t, sizeof t); +} + +void util_sha256(const void *data, size_t n, unsigned char out[32]) +{ + SHA256_CTX ctx; + sha256_init(&ctx); + sha256_update(&ctx, (const BYTE *)data, n); + sha256_final(&ctx, out); +} + +void util_hex(const unsigned char *in, size_t n, char *out) +{ + static const char hex[] = "0123456789abcdef"; + for (size_t i = 0; i < n; i++) { + out[2 * i] = hex[in[i] >> 4]; + out[2 * i + 1] = hex[in[i] & 15]; + } + out[2 * n] = '\0'; +} + +static const char B64URL[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; + +char *util_b64url(const unsigned char *in, size_t n) +{ + size_t olen = ((n + 2) / 3) * 4 + 1; + char *out = xmalloc(olen); + size_t i = 0, o = 0; + while (i + 3 <= n) { + uint32_t v = ((uint32_t)in[i] << 16) | ((uint32_t)in[i + 1] << 8) | + (uint32_t)in[i + 2]; + out[o++] = B64URL[(v >> 18) & 63]; + out[o++] = B64URL[(v >> 12) & 63]; + out[o++] = B64URL[(v >> 6) & 63]; + out[o++] = B64URL[v & 63]; + i += 3; + } + if (i + 1 == n) { + uint32_t v = (uint32_t)in[i] << 16; + out[o++] = B64URL[(v >> 18) & 63]; + out[o++] = B64URL[(v >> 12) & 63]; + } else if (i + 2 == n) { + uint32_t v = ((uint32_t)in[i] << 16) | ((uint32_t)in[i + 1] << 8); + out[o++] = B64URL[(v >> 18) & 63]; + out[o++] = B64URL[(v >> 12) & 63]; + out[o++] = B64URL[(v >> 6) & 63]; + } + out[o] = '\0'; + return out; +} + +char *util_b64(const unsigned char *in, size_t n) +{ + static const char B64[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + size_t olen = ((n + 2) / 3) * 4 + 1; + char *out = xmalloc(olen); + size_t i = 0, o = 0; + while (i + 3 <= n) { + uint32_t v = ((uint32_t)in[i] << 16) | ((uint32_t)in[i + 1] << 8) | + (uint32_t)in[i + 2]; + out[o++] = B64[(v >> 18) & 63]; + out[o++] = B64[(v >> 12) & 63]; + out[o++] = B64[(v >> 6) & 63]; + out[o++] = B64[v & 63]; + i += 3; + } + if (i + 1 == n) { + uint32_t v = (uint32_t)in[i] << 16; + out[o++] = B64[(v >> 18) & 63]; + out[o++] = B64[(v >> 12) & 63]; + out[o++] = '='; + out[o++] = '='; + } else if (i + 2 == n) { + uint32_t v = ((uint32_t)in[i] << 16) | ((uint32_t)in[i + 1] << 8); + out[o++] = B64[(v >> 18) & 63]; + out[o++] = B64[(v >> 12) & 63]; + out[o++] = B64[(v >> 6) & 63]; + out[o++] = '='; + } + out[o] = '\0'; + return out; +} + +int util_random(void *out, size_t n) +{ + unsigned char *p = out; + size_t got = 0; + while (got < n) { + ssize_t r = getrandom(p + got, n - got, 0); + if (r < 0) { + if (errno == EINTR) + continue; + break; + } + got += (size_t)r; + } + if (got == n) + return 0; + int fd = open("/dev/urandom", O_RDONLY); + if (fd < 0) + return -1; + got = 0; + while (got < n) { + ssize_t r = read(fd, p + got, n - got); + if (r <= 0) { + if (r < 0 && errno == EINTR) + continue; + close(fd); + return -1; + } + got += (size_t)r; + } + close(fd); + return 0; +} + +char *util_random_id(const char *prefix, size_t nbytes) +{ + unsigned char raw[64]; + if (nbytes > sizeof raw) + nbytes = sizeof raw; + if (util_random(raw, nbytes) != 0) { + fprintf(stderr, "fatal: no entropy source\n"); + exit(1); + } + char *b = util_b64url(raw, nbytes); + size_t plen = prefix ? strlen(prefix) : 0; + char *out = xmalloc(plen + strlen(b) + 1); + memcpy(out, prefix ? prefix : "", plen); + strcpy(out + plen, b); + free(b); + return out; +} + +int util_const_eq(const void *a, const void *b, size_t n) +{ + const volatile unsigned char *x = a; + const volatile unsigned char *y = b; + unsigned char d = 0; + for (size_t i = 0; i < n; i++) + d |= (unsigned char)(x[i] ^ y[i]); + return d == 0; +} + +int64_t util_now(void) +{ + return (int64_t)time(NULL); +} + +void util_iso8601(int64_t t, char *buf, size_t n) +{ + time_t tt = (time_t)t; + struct tm tm; + gmtime_r(&tt, &tm); + strftime(buf, n, "%Y-%m-%dT%H:%M:%SZ", &tm); +} + +char *util_str_trim(char *s) +{ + while (*s == ' ' || *s == '\t' || *s == '\r' || *s == '\n') + s++; + size_t len = strlen(s); + while (len > 0) { + char c = s[len - 1]; + if (c != ' ' && c != '\t' && c != '\r' && c != '\n') + break; + s[--len] = '\0'; + } + return s; +} + +static int is_leap(int y) +{ + return (y % 4 == 0 && y % 100 != 0) || y % 400 == 0; +} + +int util_parse_iso_date(const char *s) +{ + if (!s || strlen(s) != 10) + return 0; + if (s[4] != '-' || s[7] != '-') + return 0; + static const int mdays[] = { 31, 28, 31, 30, 31, 30, + 31, 31, 30, 31, 30, 31 }; + int y = 0, m = 0, d = 0; + for (int i = 0; i < 10; i++) { + if (i == 4 || i == 7) + continue; + if (s[i] < '0' || s[i] > '9') + return 0; + } + y = (s[0] - '0') * 1000 + (s[1] - '0') * 100 + (s[2] - '0') * 10 + + (s[3] - '0'); + m = (s[5] - '0') * 10 + (s[6] - '0'); + d = (s[8] - '0') * 10 + (s[9] - '0'); + if (y < 1900 || y > 2200 || m < 1 || m > 12 || d < 1) + return 0; + int max = mdays[m - 1] + (m == 2 && is_leap(y) ? 1 : 0); + return d <= max; +} + +/* ------------------------------------------------------------------ */ +/* base64 */ +/* ------------------------------------------------------------------ */ + +static int b64_val(char c) +{ + if (c >= 'A' && c <= 'Z') + return c - 'A'; + if (c >= 'a' && c <= 'z') + return c - 'a' + 26; + if (c >= '0' && c <= '9') + return c - '0' + 52; + if (c == '+') + return 62; + if (c == '/') + return 63; + return -1; +} + +int util_b64_decode(const char *in, size_t in_len, unsigned char **out, + size_t *out_len) +{ + while (in_len > 0 && (in[in_len - 1] == '\n' || in[in_len - 1] == '\r' || + in[in_len - 1] == ' ' || in[in_len - 1] == '\t')) + in_len--; + size_t i = 0; + while (i < in_len && (in[i] == '\n' || in[i] == '\r' || in[i] == ' ' || + in[i] == '\t')) + i++; + if (i >= in_len) + return -1; + size_t cap = (in_len - i) / 4 * 3 + 3; + unsigned char *buf = xmalloc(cap); + size_t o = 0; + int pad = 0; + for (; i < in_len; i += 4) { + if (i + 1 > in_len - 1) + break; + int v[4]; + for (int k = 0; k < 4 && i + (size_t)k < in_len; k++) { + char c = in[i + (size_t)k]; + if (c == '=') { + pad++; + v[k] = 0; + } else { + v[k] = b64_val(c); + if (v[k] < 0) { + free(buf); + return -1; + } + if (pad) { + free(buf); + return -1; + } + } + } + if (pad > 2) { + free(buf); + return -1; + } + buf[o++] = (unsigned char)((v[0] << 2) | (v[1] >> 4)); + if (pad < 2) + buf[o++] = (unsigned char)(((v[1] & 15) << 4) | (v[2] >> 2)); + if (pad < 1) + buf[o++] = (unsigned char)(((v[2] & 3) << 6) | v[3]); + } + *out = buf; + *out_len = o; + return 0; +} + +/* ------------------------------------------------------------------ */ +/* dates */ +/* ------------------------------------------------------------------ */ + +int util_date_parse(const char *s, int *y, int *m, int *d) +{ + if (!util_parse_iso_date(s)) + return -1; + *y = (s[0] - '0') * 1000 + (s[1] - '0') * 100 + (s[2] - '0') * 10 + + (s[3] - '0'); + *m = (s[5] - '0') * 10 + (s[6] - '0'); + *d = (s[8] - '0') * 10 + (s[9] - '0'); + return 0; +} + +void util_date_fmt(int y, int m, int d, char *buf, size_t n) +{ + snprintf(buf, n, "%04d-%02d-%02d", y, m, d); +} + +int util_days_in_month(int y, int m) +{ + static const int mdays[] = { 31, 28, 31, 30, 31, 30, + 31, 31, 30, 31, 30, 31 }; + if (m < 1 || m > 12) + return 0; + return mdays[m - 1] + (m == 2 && is_leap(y) ? 1 : 0); +} + +void util_date_add_months(const char *date, int months, char *out, size_t n) +{ + int y, m, d; + if (util_date_parse(date, &y, &m, &d) != 0) { + snprintf(out, n, "%s", date); + return; + } + int total = y * 12 + (m - 1) + months; + int ny = total / 12; + int nm = total % 12 + 1; + int max = util_days_in_month(ny, nm); + if (d > max) + d = max; + util_date_fmt(ny, nm, d, out, n); +} + +static int64_t days_from_civil(int y, int m, int d) +{ + y -= m <= 2; + int64_t era = (y >= 0 ? y : y - 399) / 400; + unsigned yoe = (unsigned)(y - era * 400); + unsigned doy = (unsigned)((153 * (m + (m > 2 ? -3 : 9)) + 2) / 5 + d - 1); + unsigned doe = yoe * 365 + yoe / 4 - yoe / 100 + doy; + return era * 146097 + (int64_t)doe - 719468; +} + +static void civil_from_days(int64_t z, int *y, int *m, int *d) +{ + z += 719468; + int64_t era = (z >= 0 ? z : z - 146096) / 146097; + unsigned doe = (unsigned)(z - era * 146097); + unsigned yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365; + int yy = (int)(yoe + era * 400); + unsigned doy = doe - (365 * yoe + yoe / 4 - yoe / 100); + unsigned mp = (5 * doy + 2) / 153; + *d = (int)(doy - (153 * mp + 2) / 5 + 1); + *m = (int)(mp + (mp < 10 ? 3 : -9)); + *y = yy + (*m <= 2); +} + +void util_date_add_days(const char *date, int days, char *out, size_t n) +{ + int y, m, d; + if (util_date_parse(date, &y, &m, &d) != 0) { + snprintf(out, n, "%s", date); + return; + } + int64_t z = days_from_civil(y, m, d) + days; + civil_from_days(z, &y, &m, &d); + util_date_fmt(y, m, d, out, n); +} + +int util_date_valid(const char *s) +{ + return util_parse_iso_date(s); +} + +/* ------------------------------------------------------------------ */ +/* CP437 conversion (subset: Latin-1 letters used in SIE files) */ +/* ------------------------------------------------------------------ */ + +/* Unicode codepoints for CP437 bytes 0x80..0xDF. Bytes 0xE0..0xFF are + Greek/math symbols that do not occur in Swedish bookkeeping text and + convert to '?'. */ +static const uint16_t CP437_HIGH[0x60] = { + 0x00C7, 0x00FC, 0x00E9, 0x00E2, 0x00E4, 0x00E0, 0x00E5, 0x00E7, + 0x00EA, 0x00EB, 0x00E8, 0x00EF, 0x00EE, 0x00EC, 0x00C4, 0x00C5, + 0x00C9, 0x00E6, 0x00C6, 0x00F4, 0x00F6, 0x00F2, 0x00FB, 0x00F9, + 0x00FF, 0x00D6, 0x00DC, 0x00A2, 0x00A3, 0x00A5, 0x20A7, 0x0192, + 0x00E1, 0x00ED, 0x00F3, 0x00FA, 0x00F1, 0x00D1, 0x00AA, 0x00BA, + 0x00BF, 0x2310, 0x00AC, 0x00BD, 0x00BC, 0x00A1, 0x00AB, 0x00BB, + 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556, + 0x2555, 0x2563, 0x2551, 0x2557, 0x255D, 0x255C, 0x255B, 0x2510, + 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x255E, 0x255F, + 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x2567, + 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256B, + 0x256A, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580, +}; + +static size_t cp437_put_utf8(unsigned char *out, size_t o, uint16_t cp) +{ + if (cp < 0x80) { + out[o++] = (unsigned char)cp; + } else if (cp < 0x800) { + out[o++] = (unsigned char)(0xC0 | (cp >> 6)); + out[o++] = (unsigned char)(0x80 | (cp & 0x3F)); + } else { + out[o++] = (unsigned char)(0xE0 | (cp >> 12)); + out[o++] = (unsigned char)(0x80 | ((cp >> 6) & 0x3F)); + out[o++] = (unsigned char)(0x80 | (cp & 0x3F)); + } + return o; +} + +char *util_cp437_to_utf8(const unsigned char *in, size_t n) +{ + char *out = xmalloc(n * 2 + 1); + size_t o = 0; + for (size_t i = 0; i < n; i++) { + unsigned char b = in[i]; + if (b < 0x80) { + out[o++] = (char)b; + } else if (b <= 0xDF) { + o = cp437_put_utf8((unsigned char *)out, o, CP437_HIGH[b - 0x80]); + } else { + out[o++] = '?'; + } + } + out[o] = '\0'; + return out; +} + +size_t util_utf8_to_cp437(const char *in, unsigned char *out, size_t out_sz) +{ + size_t o = 0; + const unsigned char *p = (const unsigned char *)in; + while (*p && o < out_sz) { + uint32_t cp; + size_t len; + if (*p < 0x80) { + cp = *p; + len = 1; + } else if ((*p & 0xE0) == 0xC0) { + cp = (uint32_t)(*p & 0x1F) << 6; + if ((p[1] & 0xC0) != 0x80) + break; + cp |= p[1] & 0x3F; + len = 2; + } else if ((*p & 0xF0) == 0xE0) { + cp = (uint32_t)(*p & 0x0F) << 12; + if ((p[1] & 0xC0) != 0x80 || (p[2] & 0xC0) != 0x80) + break; + cp |= (uint32_t)(p[1] & 0x3F) << 6; + cp |= p[2] & 0x3F; + len = 3; + } else { + p++; + out[o++] = '?'; + continue; + } + if (cp < 0x80) { + out[o++] = (unsigned char)cp; + } else { + unsigned char best = '?'; + for (unsigned b = 0x80; b <= 0xDF; b++) { + if (CP437_HIGH[b - 0x80] == cp) { + best = (unsigned char)b; + break; + } + } + out[o++] = best; + } + p += len; + } + return o; +} |
