From cdf4430a9e933277a8ca2f7c76a4050b7e8d7dd5 Mon Sep 17 00:00:00 2001 From: lda Date: Sun, 26 May 2024 22:01:06 +0200 Subject: [PATCH] [FIX] Fix issue with const strings which may be invalidated --- src/Int64.c | 399 +++++++++++++++++++++++++++++++++++++ src/Memory.c | 14 +- src/UInt64.c | 265 ++++++++++++++++++++++++ src/include/Args.h | 82 ++++++++ src/include/Array.h | 187 +++++++++++++++++ src/include/Base64.h | 99 +++++++++ src/include/Cron.h | 140 +++++++++++++ src/include/Db.h | 169 ++++++++++++++++ src/include/Graph.h | 175 ++++++++++++++++ src/include/HashMap.h | 185 +++++++++++++++++ src/include/HeaderParser.h | 125 ++++++++++++ src/include/Http.h | 211 ++++++++++++++++++++ src/include/HttpClient.h | 104 ++++++++++ src/include/HttpRouter.h | 91 +++++++++ src/include/HttpServer.h | 234 ++++++++++++++++++++++ src/include/Int.h | 122 ++++++++++++ src/include/Int64.h | 252 +++++++++++++++++++++++ src/include/Io.h | 222 +++++++++++++++++++++ src/include/Json.h | 323 ++++++++++++++++++++++++++++++ src/include/Log.h | 196 ++++++++++++++++++ src/include/Memory.h | 235 ++++++++++++++++++++++ src/include/Queue.h | 105 ++++++++++ src/include/Rand.h | 81 ++++++++ src/include/Runtime.h | 53 +++++ src/include/Sha.h | 76 +++++++ src/include/Str.h | 129 ++++++++++++ src/include/Stream.h | 223 +++++++++++++++++++++ src/include/Tls.h | 109 ++++++++++ src/include/UInt64.h | 252 +++++++++++++++++++++++ src/include/Uri.h | 69 +++++++ src/include/Util.h | 117 +++++++++++ tools/int64.c | 145 ++++++++++++++ tools/uint64.c | 119 +++++++++++ 33 files changed, 5302 insertions(+), 6 deletions(-) create mode 100644 src/Int64.c create mode 100644 src/UInt64.c create mode 100644 src/include/Args.h create mode 100644 src/include/Array.h create mode 100644 src/include/Base64.h create mode 100644 src/include/Cron.h create mode 100644 src/include/Db.h create mode 100644 src/include/Graph.h create mode 100644 src/include/HashMap.h create mode 100644 src/include/HeaderParser.h create mode 100644 src/include/Http.h create mode 100644 src/include/HttpClient.h create mode 100644 src/include/HttpRouter.h create mode 100644 src/include/HttpServer.h create mode 100644 src/include/Int.h create mode 100644 src/include/Int64.h create mode 100644 src/include/Io.h create mode 100644 src/include/Json.h create mode 100644 src/include/Log.h create mode 100644 src/include/Memory.h create mode 100644 src/include/Queue.h create mode 100644 src/include/Rand.h create mode 100644 src/include/Runtime.h create mode 100644 src/include/Sha.h create mode 100644 src/include/Str.h create mode 100644 src/include/Stream.h create mode 100644 src/include/Tls.h create mode 100644 src/include/UInt64.h create mode 100644 src/include/Uri.h create mode 100644 src/include/Util.h create mode 100644 tools/int64.c create mode 100644 tools/uint64.c diff --git a/src/Int64.c b/src/Int64.c new file mode 100644 index 0000000..21f07f5 --- /dev/null +++ b/src/Int64.c @@ -0,0 +1,399 @@ +/* + * Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net> + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include + +#include +#include + +#include + +#ifdef INT64_NATIVE +#define Int64Sign(x) ((int) (((UInt64) (x)) >> 63)) +#else +#define Int64Sign(x) ((int) ((x).i[1] >> 31)) +#endif + +size_t +Int64Str(Int64 x, int base, char *out, size_t len) +{ + static const char symbols[] = "0123456789ABCDEF"; + + size_t i = len - 1; + size_t j = 0; + + int neg = Int64Sign(x); + + Int64 base64 = Int64Create(0, base); + + /* We only have symbols up to base 16 */ + if (base < 2 || base > 16) + { + return 0; + } + + /* + * This algorithm doesn't work on INT64_MIN. + * + * But it works on all other integers in the range, so we + * just scoot the range in by one for now. It's a hack and + * I'm not a huge fan of it, but this function is mostly + * used in Json, which shouldn't have a range this large + * anyway (Json is limited to -2^53 -> 2^53-1). + * + * Proper fixes are always welcome. + */ + if (Int64Eq(x, Int64Create(0x80000000, 0x00000000))) + { + x = Int64Add(x, Int64Create(0, 1)); + } +#if 0 + else if (Int64Eq(x, Int64Create(0x7FFFFFFF, 0xFFFFFFFF))) + { + x = Int64Sub(x, Int64Create(0, 1)); + } +#endif + + if (base != 2 && base != 8 && base != 16 && neg) + { + x = Int64Neg(x); + } + + do + { + Int64 mod = Int64Rem(x, base64); + Int32 low = Int64Low(mod); + + out[i] = symbols[low]; + i--; + x = Int64Div(x, base64); + } while (Int64Gt(x, Int64Create(0, 0))); + + if (base != 2 && base != 8 && base != 16) + { + /* + * Binary, octal, and hexadecimal are known to + * be bit representations. Everything else (notably + * decimal) should include the negative sign. + */ + if (neg) + { + out[i] = '-'; + i--; + } + } + + while (++i < len) + { + out[j++] = out[i]; + } + + out[j] = '\0'; + + return j; +} + +#ifndef INT64_NATIVE + +/* No native 64-bit support, add our own */ + +Int64 +Int64Create(UInt32 high, UInt32 low) +{ + Int64 x; + + x.i[0] = low; + x.i[1] = high; + + return x; +} + +Int64 +Int64Add(Int64 x, Int64 y) +{ + Int64 z = Int64Create(0, 0); + int carry; + + z.i[0] = x.i[0] + y.i[0]; + carry = z.i[0] < x.i[0]; + z.i[1] = x.i[1] + y.i[1] + carry; + + return z; +} + +Int64 +Int64Sub(Int64 x, Int64 y) +{ + return Int64Add(x, Int64Neg(y)); +} + +Int64 +Int64Mul(Int64 x, Int64 y) +{ + Int64 z = Int64Create(0, 0); + + int xneg = Int64Sign(x); + int yneg = Int64Sign(y); + + if (xneg) + { + x = Int64Neg(x); + } + + if (yneg) + { + y = Int64Neg(y); + } + + /* while (y > 0) */ + while (Int64Gt(y, Int64Create(0, 0))) + { + /* if (y & 1 != 0) */ + if (Int64Neq(Int64And(y, Int64Create(0, 1)), Int64Create(0, 0))) + { + z = Int64Add(z, x); + } + + x = Int64Sll(x, 1); + y = Int64Sra(y, 1); + } + + if (xneg != yneg) + { + z = Int64Neg(z); + } + + return z; +} + +typedef struct +{ + Int64 q; + Int64 r; +} Int64Ldiv; + +static Int64Ldiv +Int64LongDivision(Int64 n, Int64 d) +{ + Int64Ldiv o; + + int i; + + int nneg = Int64Sign(n); + int dneg = Int64Sign(d); + + o.q = Int64Create(0, 0); + o.r = Int64Create(0, 0); + + if (Int64Eq(d, Int64Create(0, 0))) + { + raise(SIGFPE); + return o; + } + + if (nneg) + { + n = Int64Neg(n); + } + + if (dneg) + { + d = Int64Neg(d); + } + + for (i = 63; i >= 0; i--) + { + Int64 bit = Int64And(Int64Sra(n, i), Int64Create(0, 1)); + + o.r = Int64Sll(o.r, 1); + o.r = Int64Or(o.r, bit); + + if (Int64Geq(o.r, d)) + { + o.r = Int64Sub(o.r, d); + o.q = Int64Or(o.q, Int64Sll(Int64Create(0, 1), i)); + } + } + + if (nneg != dneg) + { + o.r = Int64Neg(o.r); + o.q = Int64Neg(o.q); + } + + return o; +} + +Int64 +Int64Div(Int64 x, Int64 y) +{ + return Int64LongDivision(x, y).q; +} + +Int64 +Int64Rem(Int64 x, Int64 y) +{ + return Int64LongDivision(x, y).r; +} + +Int64 +Int64Sll(Int64 x, int y) +{ + Int64 z; + + if (!y) + { + return x; + } + + z = Int64Create(0, 0); + + if (y < 32) + { + z.i[1] = (x.i[0] >> (32 - y)) | (x.i[1] << y); + z.i[0] = x.i[0] << y; + } + else + { + z.i[1] = x.i[0] << (y - 32); + } + + return z; +} + +Int64 +Int64Sra(Int64 x, int y) +{ + Int64 z; + + int neg = Int64Sign(x); + + if (!y) + { + return x; + } + + z = Int64Create(0, 0); + + if (y < 32) + { + z.i[0] = (x.i[1] << (32 - y)) | (x.i[0] >> y); + z.i[1] = x.i[1] >> y; + } + else + { + z.i[0] = x.i[1] >> (y - 32); + } + + if (neg) + { + Int64 mask = Int64Create(0xFFFFFFFF, 0xFFFFFFFF); + + z = Int64Or(Int64Sll(mask, (64 - y)), z); + } + + return z; +} + +Int64 +Int64And(Int64 x, Int64 y) +{ + return Int64Create(x.i[1] & y.i[1], x.i[0] & y.i[0]); +} + +Int64 +Int64Or(Int64 x, Int64 y) +{ + return Int64Create(x.i[1] | y.i[1], x.i[0] | y.i[0]); +} + +Int64 +Int64Xor(Int64 x, Int64 y) +{ + return Int64Create(x.i[1] ^ y.i[1], x.i[0] ^ y.i[0]); +} + +Int64 +Int64Not(Int64 x) +{ + return Int64Create(~(x.i[1]), ~(x.i[0])); +} + +int +Int64Eq(Int64 x, Int64 y) +{ + return x.i[0] == y.i[0] && x.i[1] == y.i[1]; +} + +int +Int64Lt(Int64 x, Int64 y) +{ + int xneg = Int64Sign(x); + int yneg = Int64Sign(y); + + if (xneg != yneg) + { + return xneg > yneg; + } + else + { + if (xneg) + { + /* Both negative */ + return x.i[1] > y.i[1] || (x.i[1] == y.i[1] && x.i[0] > y.i[0]); + } + else + { + /* Both positive */ + return x.i[1] < y.i[1] || (x.i[1] == y.i[1] && x.i[0] < y.i[0]); + } + } +} + +int +Int64Gt(Int64 x, Int64 y) +{ + int xneg = Int64Sign(x); + int yneg = Int64Sign(y); + + if (xneg != yneg) + { + return xneg < yneg; + } + else + { + if (xneg) + { + /* Both negative */ + return x.i[1] < y.i[1] || (x.i[1] == y.i[1] && x.i[0] < y.i[0]); + } + else + { + /* Both positive */ + return x.i[1] > y.i[1] || (x.i[1] == y.i[1] && x.i[0] > y.i[0]); + } + } + +} + +#endif diff --git a/src/Memory.c b/src/Memory.c index 4083143..01c98c5 100644 --- a/src/Memory.c +++ b/src/Memory.c @@ -40,10 +40,12 @@ #define MEMORY_HEXDUMP_WIDTH 16 #endif +#define MEMORY_FILE_SIZE 256 + struct MemoryInfo { size_t size; - const char *file; + char file[MEMORY_FILE_SIZE]; int line; void *pointer; }; @@ -227,7 +229,7 @@ MemoryAllocate(size_t size, const char *file, int line) MEM_BOUND_UPPER(p, size) = MEM_BOUND; a->size = MEM_SIZE_ACTUAL(size); - a->file = file; + strncpy(a->file, file, MEMORY_FILE_SIZE - 1); a->line = line; a->pointer = p; @@ -270,7 +272,7 @@ MemoryReallocate(void *p, size_t size, const char *file, int line) { MemoryDelete(a); a->size = MEM_SIZE_ACTUAL(size); - a->file = file; + strncpy(a->file, file, MEMORY_FILE_SIZE - 1); a->line = line; a->pointer = new; @@ -293,7 +295,7 @@ MemoryReallocate(void *p, size_t size, const char *file, int line) if (a) { a->size = 0; - a->file = file; + strncpy(a->file, file, MEMORY_FILE_SIZE - 1); a->line = line; a->pointer = p; hook(MEMORY_BAD_POINTER, a, hookArgs); @@ -322,7 +324,7 @@ MemoryFree(void *p, const char *file, int line) pthread_mutex_lock(&lock); if (hook) { - a->file = file; + strncpy(a->file, file, MEMORY_FILE_SIZE - 1); a->line = line; hook(MEMORY_FREE, a, hookArgs); } @@ -337,7 +339,7 @@ MemoryFree(void *p, const char *file, int line) a = malloc(sizeof(MemoryInfo)); if (a) { - a->file = file; + strncpy(a->file, file, MEMORY_FILE_SIZE - 1); a->line = line; a->size = 0; a->pointer = p; diff --git a/src/UInt64.c b/src/UInt64.c new file mode 100644 index 0000000..ae8eff7 --- /dev/null +++ b/src/UInt64.c @@ -0,0 +1,265 @@ +/* + * Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net> + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include + +#include +#include + +size_t +UInt64Str(UInt64 x, int base, char *out, size_t len) +{ + static const char symbols[] = "0123456789ABCDEF"; + + size_t i = len - 1; + size_t j = 0; + + UInt64 base64 = UInt64Create(0, base); + + /* We only have symbols up to base 16 */ + if (base < 2 || base > 16) + { + return 0; + } + + do + { + UInt64 mod = UInt64Rem(x, base64); + UInt32 low = UInt64Low(mod); + + out[i] = symbols[low]; + i--; + x = UInt64Div(x, base64); + } while (UInt64Gt(x, UInt64Create(0, 0))); + + while (++i < len) + { + out[j++] = out[i]; + } + + out[j] = '\0'; + + return j; +} + +#ifndef UINT64_NATIVE + +/* No native 64-bit support, add our own */ + +UInt64 +UInt64Create(UInt32 high, UInt32 low) +{ + UInt64 x; + + x.i[0] = low; + x.i[1] = high; + + return x; +} + +UInt64 +UInt64Add(UInt64 x, UInt64 y) +{ + UInt64 z = UInt64Create(0, 0); + int carry; + + z.i[0] = x.i[0] + y.i[0]; + carry = z.i[0] < x.i[0]; + z.i[1] = x.i[1] + y.i[1] + carry; + + return z; +} + +UInt64 +UInt64Sub(UInt64 x, UInt64 y) +{ + UInt64 twosCompl = UInt64Add(UInt64Not(y), UInt64Create(0, 1)); + + return UInt64Add(x, twosCompl); +} + +UInt64 +UInt64Mul(UInt64 x, UInt64 y) +{ + UInt64 z = UInt64Create(0, 0); + + /* while (y > 0) */ + while (UInt64Gt(y, UInt64Create(0, 0))) + { + /* if (y & 1 != 0) */ + if (UInt64Neq(UInt64And(y, UInt64Create(0, 1)), UInt64Create(0, 0))) + { + z = UInt64Add(z, x); + } + + x = UInt64Sll(x, 1); + y = UInt64Srl(y, 1); + } + + return z; +} + +typedef struct +{ + UInt64 q; + UInt64 r; +} UInt64Ldiv; + +static UInt64Ldiv +UInt64LongDivision(UInt64 n, UInt64 d) +{ + UInt64Ldiv o; + + int i; + + o.q = UInt64Create(0, 0); + o.r = UInt64Create(0, 0); + + if (UInt64Eq(d, UInt64Create(0, 0))) + { + raise(SIGFPE); + return o; + } + + for (i = 63; i >= 0; i--) + { + UInt64 bit = UInt64And(UInt64Srl(n, i), UInt64Create(0, 1)); + + o.r = UInt64Sll(o.r, 1); + o.r = UInt64Or(o.r, bit); + + if (UInt64Geq(o.r, d)) + { + o.r = UInt64Sub(o.r, d); + o.q = UInt64Or(o.q, UInt64Sll(UInt64Create(0, 1), i)); + } + } + + return o; +} + +UInt64 +UInt64Div(UInt64 x, UInt64 y) +{ + return UInt64LongDivision(x, y).q; +} + +UInt64 +UInt64Rem(UInt64 x, UInt64 y) +{ + return UInt64LongDivision(x, y).r; +} + +UInt64 +UInt64Sll(UInt64 x, int y) +{ + UInt64 z; + + if (!y) + { + return x; + } + + z = UInt64Create(0, 0); + + if (y < 32) + { + z.i[1] = (x.i[0] >> (32 - y)) | (x.i[1] << y); + z.i[0] = x.i[0] << y; + } + else + { + z.i[1] = x.i[0] << (y - 32); + } + + return z; +} + +UInt64 +UInt64Srl(UInt64 x, int y) +{ + UInt64 z; + + if (!y) + { + return x; + } + + z = UInt64Create(0, 0); + + if (y < 32) + { + z.i[0] = (x.i[1] << (32 - y)) | (x.i[0] >> y); + z.i[1] = x.i[1] >> y; + } + else + { + z.i[0] = x.i[1] >> (y - 32); + } + + return z; +} + +UInt64 +UInt64And(UInt64 x, UInt64 y) +{ + return UInt64Create(x.i[1] & y.i[1], x.i[0] & y.i[0]); +} + +UInt64 +UInt64Or(UInt64 x, UInt64 y) +{ + return UInt64Create(x.i[1] | y.i[1], x.i[0] | y.i[0]); +} + +UInt64 +UInt64Xor(UInt64 x, UInt64 y) +{ + return UInt64Create(x.i[1] ^ y.i[1], x.i[0] ^ y.i[0]); +} + +UInt64 +UInt64Not(UInt64 x) +{ + return UInt64Create(~(x.i[1]), ~(x.i[0])); +} + +int +UInt64Eq(UInt64 x, UInt64 y) +{ + return x.i[0] == y.i[0] && x.i[1] == y.i[1]; +} + +int +UInt64Lt(UInt64 x, UInt64 y) +{ + return x.i[1] < y.i[1] || (x.i[1] == y.i[1] && x.i[0] < y.i[0]); +} + +int +UInt64Gt(UInt64 x, UInt64 y) +{ + return x.i[1] > y.i[1] || (x.i[1] == y.i[1] && x.i[0] > y.i[0]); +} + +#endif diff --git a/src/include/Args.h b/src/include/Args.h new file mode 100644 index 0000000..3108de3 --- /dev/null +++ b/src/include/Args.h @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net> + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef CYTOPLASM_ARGS_H +#define CYTOPLASM_ARGS_H + +/*** + * @Nm Args + * @Nd Getopt-style argument parser that operates on arrays. + * @Dd May 12 2023 + * @Xr Array + * + * .Nm + * provides a simple argument parser in the style of + * .Xr getopt 3 . + * It exists because the runtime passes the program arguments as + * an Array, and it is often useful to parse the arguments to + * provide the standard command line interface. + */ + +#include + +/** + * All state is stored in this structure, instead of global + * variables. This makes + * .Nm + * thread-safe and easy to reset. + */ +typedef struct ArgParseState +{ + int optInd; + int optErr; + int optOpt; + char *optArg; + + int optPos; +} ArgParseState; + +/** + * Initialize the variables in the given parser state structure + * to their default values. This should be called before + * .Fn ArgParse + * is called with the parser state. It should also be called if + * .Fn ArgParse + * will be used again on a different array, or the same array all + * over again. + */ +extern void ArgParseStateInit(ArgParseState *); + +/** + * Parse command line arguments stored in the given array, using + * the given state and option string. This function behaves + * identically to the POSIX + * .Fn getopt + * function, and should be used in exactly the same way. Refer to + * your system's + * .Xr getopt 3 + * page for details. + */ +extern int ArgParse(ArgParseState *, Array *, const char *); + +#endif /* CYTOPLASM_ARGS_H */ diff --git a/src/include/Array.h b/src/include/Array.h new file mode 100644 index 0000000..0ba1335 --- /dev/null +++ b/src/include/Array.h @@ -0,0 +1,187 @@ +/* + * Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net> + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef CYTOPLASM_ARRAY_H +#define CYTOPLASM_ARRAY_H + +/*** + * @Nm Array + * @Nd A simple dynamic array data structure. + * @Dd November 24 2022 + * @Xr HashMap Queue + * + * These functions implement a simple array data structure that is + * automatically resized as necessary when new values are added. This + * implementation does not actually store the values of the items in it; + * it only stores pointers to the data. As such, you will still have to + * manually maintain all your data. The advantage of this is that these + * functions don't have to copy data, and thus don't care how big the + * data is. Furthermore, arbitrary data can be stored in the array. + * .Pp + * This array implementation is optimized for storage space and + * appending. Deletions are expensive in that all the items of the list + * above a deletion are moved down to fill the hole where the deletion + * occurred. Insertions are also expensive in that all the elements + * above the given index must be shifted up to make room for the new + * element. + * .Pp + * Due to these design choices, this array implementation is best + * suited to linear writing, and then linear or random reading. + */ + +#include +#include + +/** + * The functions in this API operate on an array structure which is + * opaque to the caller. + */ +typedef struct Array Array; + +/** + * Allocate a new array. This function returns a pointer that can be + * used with the other functions in this API, or NULL if there was an + * error allocating memory for the array. + */ +extern Array *ArrayCreate(void); + +/** + * Deallocate an array. Note that this function does not free any of + * the values stored in the array; it is the caller's job to manage the + * memory for each item. Typically, the caller would iterate over all + * the items in the array and free them before freeing the array. + */ +extern void ArrayFree(Array *); + +/** + * Get the size, in number of elements, of the given array. + */ +extern size_t ArraySize(Array *); + +/** + * Get the element at the specified index from the specified array. + * This function will return NULL if the array is NULL, or the index + * is out of bounds. Otherwise, it will return a pointer to a value + * put into the array using + * .Fn ArrayInsert + * or + * .Fn ArraySet . + */ +extern void *ArrayGet(Array *, size_t); + +/** + * Insert the specified element at the specified index in the specified + * array. This function will shift the element currently at that index, + * and any elements after it before inserting the given element. + * .Pp + * This function returns a boolean value indicating whether or not it + * suceeded. + */ +extern int ArrayInsert(Array *, size_t, void *); + +/** + * Set the value at the specified index in the specified array to the + * specified value. This function will return the old value at that + * index, if any. + */ +extern void *ArraySet(Array *, size_t, void *); + +/** + * Append the specified element to the end of the specified array. This + * function uses + * .Fn ArrayInsert + * under the hood to insert an element at the end. It thus has the same + * return value as + * .Fn ArrayInsert . + */ +extern int ArrayAdd(Array *, void *); + +/** + * Remove the element at the specified index from the specified array. + * This function returns the element removed, if any. + */ +extern void *ArrayDelete(Array *, size_t); + +/** + * Sort the specified array using the specified sort function. The + * sort function compares two elements. It takes two void pointers as + * parameters, and returns an integer. The return value indicates to + * the sorting algorithm how the elements relate to each other. A + * return value of 0 indicates that the elements are identical. A + * return value greater than 0 indicates that the first item is + * ``bigger'' than the second item and should thus appear after it in + * the array. A return value less than 0 indicates the opposite: the + * second element should appear after the first in the array. + */ +extern void ArraySort(Array *, int (*) (void *, void *)); + +/** + * Remove all duplicates from an array by using the given comparison + * function to sort the array, then remove matching values. This + * function returns a new array with all duplicates removed. The + * original array is unaffected. Note that arrays only store pointers + * to values, usually values on the heap. Thus, it is possible to lose + * pointers to duplicate values and have them leak. + * .P + * This is a relatively expensive operation. The array must first be + * duplicated. Then it is sorted, then it is iterated from beginning + * to end to remove duplicate entires. Note that the comparison + * function is executed on each element at least twice. + */ +extern Array *ArrayUnique(Array *, int (*) (void *, void *)); + + +/** + * Reverses the order of all elements in the array into a new array on + * the heap, to be freed using + * .Fn ArrayFree . + */ +extern Array *ArrayReverse(Array *); + +/** + * If possible, reduce the amount of memory allocated to this array + * by calling + * .Fn Realloc + * on the internal structure to perfectly fit the elements in the + * array. This function is intended to be used by functions that return + * relatively read-only arrays that will be long-lived. + */ +extern int ArrayTrim(Array *); + +/** + * Convert a variadic arguments list into an Array. In most cases, the + * Array API is much easier to work with than + * .Fn va_arg + * and friends. + */ +extern Array *ArrayFromVarArgs(size_t, va_list); + +/** + * Duplicate an existing array. Note that arrays only hold pointers to + * their data, not the data itself, so the duplicated array will point + * to the same places in memory as the original array. + */ +extern Array *ArrayDuplicate(Array *); + +#endif /* CYTOPLASM_ARRAY_H */ diff --git a/src/include/Base64.h b/src/include/Base64.h new file mode 100644 index 0000000..b36a48c --- /dev/null +++ b/src/include/Base64.h @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net> + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef CYTOPLASM_BASE64_H +#define CYTOPLASM_BASE64_H + +/*** + * @Nm Base64 + * @Nd A simple base64 encoder/decoder with unpadded base64 support. + * @Dd September 30 2022 + * @Xr Sha2 + * + * This is an efficient yet simple base64 encoding and decoding API + * that supports regular base64, as well as the Matrix specification's + * extension to base64, called ``unpadded base64.'' This API provides + * the ability to convert between the two, instead of just implementing + * unpadded base64. + */ + +#include + +/** + * This function computes the amount of bytes needed to store a message + * of the specified number of bytes as base64. + */ +extern size_t + Base64EncodedSize(size_t); + +/** + * This function computes the amount of bytes needed to store a decoded + * representation of the encoded message. It takes a pointer to the + * encoded string because it must read a few bytes off the end in order + * to accurately compute the size. + */ +extern size_t + Base64DecodedSize(const char *, size_t); + +/** + * Encode the specified number of bytes from the specified buffer as + * base64. This function returns a string on the heap that should be + * freed with + * .Fn Free , + * or NULL if a memory allocation error ocurred. + */ +extern char * + Base64Encode(const char *, size_t); + +/** + * Decode the specified number of bytes from the specified buffer of + * base64. This function returns a string on the heap that should be + * freed with + * .Fn Free , + * or NULL if a memory allocation error occured. + */ +extern char * + Base64Decode(const char *, size_t); + +/** + * Remove the padding from a specified base64 string. This function + * modifies the specified string in place. It thus has no return value + * because it cannot fail. If the passed pointer is invalid, the + * behavior is undefined. + */ +extern void + Base64Unpad(char *, size_t); + +/** + * Add padding to an unpadded base64 string. This function takes a + * pointer to a pointer because it may be necessary to grow the memory + * allocated to the string. This function returns a boolean value + * indicating whether the pad operation was successful. In practice, + * this means it will only fail if a bigger string is necessary, but it + * could not be automatically allocated on the heap. + */ +extern int + Base64Pad(char **, size_t); + +#endif /* CYTOPLASM_BASE64_H */ diff --git a/src/include/Cron.h b/src/include/Cron.h new file mode 100644 index 0000000..e78900d --- /dev/null +++ b/src/include/Cron.h @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net> + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef CYTOPLASM_CRON_H +#define CYTOPLASM_CRON_H + +/*** + * @Nm Cron + * @Nd Basic periodic job scheduler. + * @Dd December 24 2022 + * + * This is an extremely basic job scheduler. So basic, in fact, that + * it currently runs all jobs on a single thread, which means that it + * is intended for short-lived jobs. In the future, it might be + * extended to support a one-thread-per-job model, but for now, jobs + * should take into consideration the fact that they are sharing their + * thread, so they should not be long-running. + * .Pp + * .Nm + * works by ``ticking'' at an interval defined by the caller of + * .Fn CronCreate . + * At each tick, all the registered jobs are queried, and if they are + * due to run again, their function is executed. As much as possible, + * .Nm + * tries to tick at constant intervals, however it is possible that one + * or more jobs may overrun the tick duration. If this happens, + * .Nm + * ticks again immediately after all the jobs for the previous tick + * have completed. This is in an effort to compensate for lost time, + * however it is important to note that when jobs overrun the tick + * interval, the interval is pushed back by the amount that it was + * overrun. Because of this, + * .Nm + * is best suited for scheduling jobs that should happen + * ``aproximately'' every so often; it is not a real-time scheduler + * by any means. + */ + +#include + +/** + * All functions defined here operate on a structure opaque to the + * caller. + */ +typedef struct Cron Cron; + +/** + * A job function is a function that takes a void pointer and returns + * nothing. The pointer is passed when the job is scheduled, and + * is expected to remain valid until the job is no longer registered. + * The pointer is passed each time the job executes. + */ +typedef void (JobFunc) (void *); + +/** + * Create a new + * .Nm + * object that all other functions operate on. Like most of the other + * APIs in this project, it must be freed with + * .Fn CronFree + * when it is no longer needed. + * .Pp + * This function takes the tick interval in milliseconds. + */ +extern Cron * CronCreate(UInt32); + +/** + * Schedule a one-off job to be executed only at the next tick, and + * then immediately discarded. This is useful for scheduling tasks that + * only have to happen once, or very infrequently depending on + * conditions other than the current time, but don't have to happen + * immediately. The caller simply indicates that it wishes for the task + * to execute at some time in the future. How far into the future this + * practically ends up being is determined by how long it takes for + * other registered jobs to finish, and what the tick interval is. + * .Pp + * This function takes a job function and a pointer to pass to that + * function when it is executed. + */ +extern void + CronOnce(Cron *, JobFunc *, void *); + +/** + * Schedule a repetitive task to be executed at aproximately the given + * interval. As stated above, this is as fuzzy interval; depending on + * the jobs being run and the tick interval, tasks may not execute at + * exactly the scheduled time, but they will eventually execute. + * .Pp + * This function takes an interval in milliseconds, a job function, + * and a pointer to pass to that function when it is executed. + */ +extern void + CronEvery(Cron *, unsigned long, JobFunc *, void *); + +/** + * Start ticking the clock and executing registered jobs. + */ +extern void + CronStart(Cron *); + +/** + * Stop ticking the clock. Jobs that are already executing will + * continue to execute, but when they are finished, no new jobs will + * be executed until + * .Fn CronStart + * is called. + */ +extern void + CronStop(Cron *); + +/** + * Discard all job references and free all memory associated with the + * given + * .Nm Cron + * instance. + */ +extern void + CronFree(Cron *); + +#endif /* CYTOPLASM_CRON_H */ diff --git a/src/include/Db.h b/src/include/Db.h new file mode 100644 index 0000000..618a2e2 --- /dev/null +++ b/src/include/Db.h @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net> + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef CYTOPLASM_DB_H +#define CYTOPLASM_DB_H + +/*** + * @Nm Db + * @Nd A minimal flat-file database with mutex locking and cache. + * @Dd April 27 2023 + * @Xr Json + * + * Cytoplasm operates on a flat-file database instead of a + * traditional relational database. This greatly simplifies the + * persistent storage code, and creates a relatively basic API, + * described here. + */ + +#include + +#include +#include + +/** + * All functions in this API operate on a database structure that is + * opaque to the caller. + */ +typedef struct Db Db; + +/** + * When an object is locked, a reference is returned. This reference + * is owned by the current thread, and the database is inaccessible to + * other threads until all references have been returned to the + * database. + * .Pp + * This reference is opaque, but can be manipulated by the functions + * defined here. + */ +typedef struct DbRef DbRef; + +/** + * Open a data directory. This function takes a path to open, and a + * cache size in bytes. If the cache size is 0, then caching is + * disabled and objects are loaded off the disk every time they are + * locked. Otherwise, objects are stored in the cache, and they are + * evicted in a least-recently-used manner. + */ +extern Db * DbOpen(char *, size_t); + +/** + * Close the database. This function will flush anything in the cache + * to the disk, and then close the data directory. It assumes that + * all references have been unlocked. If a reference has not been + * unlocked, undefined behavior results. + */ +extern void DbClose(Db *); + +/** + * Set the maximum cache size allowed before + * .Nm + * starts evicting old objects. If this is set to 0, everything in the + * cache is immediately evicted and caching is disabled. If the + * database was opened with a cache size of 0, setting this will + * initialize the cache, and subsequent calls to + * .Fn DbLock + * will begin caching objects. + */ +extern void DbMaxCacheSet(Db *, size_t); + +/** + * Create a new object in the database with the specified name. This + * function will fail if the object already exists in the database. It + * takes a variable number of C strings, with the exact number being + * specified by the second parameter. These C strings are used to + * generate a filesystem path at which to store the object. These paths + * ensure each object is uniquely identifiable, and provides semantic + * meaning to an object. + */ +extern DbRef * DbCreate(Db *, size_t,...); + +/** + * Lock an existing object in the database. This function will fail + * if the object does not exist. It takes a variable number of C + * strings, with the exact number being specified by the second + * parameter. These C strings are used to generate the filesystem path + * at which to load the object. These paths ensure each object is + * uniquely identifiable, and provides semantic meaning to an object. + */ +extern DbRef * DbLock(Db *, size_t,...); + +/** + * Immediately and permanently remove an object from the database. + * This function assumes the object is not locked, otherwise undefined + * behavior will result. + */ +extern int DbDelete(Db *, size_t,...); + +/** + * Unlock an object and return it back to the database. This function + * immediately syncs the object to the filesystem. The cache is a + * read cache; writes are always immediate to ensure data integrity in + * the event of a system failure. + */ +extern int DbUnlock(Db *, DbRef *); + +/** + * Check the existence of the given database object in a more efficient + * manner than attempting to lock it with + * .Fn DbLock . + * This function does not lock the object, nor does it load it into + * memory if it exists. + */ +extern int DbExists(Db *, size_t,...); + +/** + * List all of the objects at a given path. Unlike the other varargs + * functions, this one does not take a path to a specific object; it + * takes a directory to be iterated, where each path part is its own + * C string. Note that the resulting list only contains the objects + * in the specified directory, it does not list any subdirectories. + * .Pp + * The array returned is an array of C strings containing the object + * name. + */ +extern Array * DbList(Db *, size_t,...); + +/** + * Free the list returned by + * .Fn DbListFree . + */ +extern void DbListFree(Array *); + +/** + * Convert a database reference into JSON that can be manipulated. + * At this time, the database actually stores objects as JSON on the + * disk, so this function just returns an internal pointer, but in the + * future it may have to be generated by decompressing a binary blob, + * or something of that nature. + */ +extern HashMap * DbJson(DbRef *); + +/** + * Free the existing JSON associated with the given reference, and + * replace it with new JSON. This is more efficient than duplicating + * a separate object into the database reference. + */ +extern int DbJsonSet(DbRef *, HashMap *); + +#endif diff --git a/src/include/Graph.h b/src/include/Graph.h new file mode 100644 index 0000000..02e4e02 --- /dev/null +++ b/src/include/Graph.h @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net> + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef CYTOPLASM_GRAPH_H +#define CYTOPLASM_GRAPH_H + +/*** + * @Nm Graph + * @Nd Extremely simple graph, implemented as an adjacency matrix. + * @Dd July 15 2023 + * + * .Nm + * is a basic graph data structure originally written for a computer + * science class on data structures and algorithms, in which it + * received full credit. This is an adaptation of the original + * implementation that follows the Cytoplasm style and uses Cytoplasm + * APIs when convenient. + * .P + * .Nm + * stores data in an adjacency matrix, which means the storage + * complexity is O(N^2), where N is the number of vertices (called + * Nodes in this implementation) in the graph. However, this makes the + * algorithms fast and efficient. + * .P + * Nodes are identified by index, so the first node is 0, the second + * is 1, and so on. This data structure does not support storing + * arbitrary data as nodes; rather, the intended use case is to add + * all your node data to an Array, thus giving each node an index, + * and then manipulating the graph with that index. This allows access + * to node data in O(1) time in call cases, and is the most memory + * efficient. + * .P + * .Nm + * can be used to store a variety of types of graphs, although it is + * primarily suited to directed and weighted graphs. + */ + +#include + +/** + * The functions provided here operate on an opaque graph structure. + * This structure really just stores a matrix in a contiguous block of + * memory, as well as the number of nodes in the graph, but the + * structure is kept opaque so that it remains internally consistent. + * It also maintains the style of the Cytoplasm library. + */ +typedef struct Graph Graph; + +/** + * An Edge is really just a weight, which is easily represented by an + * integer. However, it makes sense to alias this to Edge for clarity, + * both in the documentation and in the implementation. + */ +typedef int Edge; + +/** + * A Node is really just a row or column in the matrix, which is easily + * represented by an unsigned integer. However, it makes sense to alias + * this to Node for clarity, both in the documentation and the + * implementation. + */ +typedef size_t Node; + +/** + * Create a new graph structure with the given number of vertices. + */ +extern Graph *GraphCreate(size_t); + +/** + * Create a new graph data structure with the given number of vertices + * and the given adjacency matrix. The adjacency matrix is copied + * verbatim into the graph data structure without any validation. + */ +extern Graph *GraphCreateWithEdges(size_t, Edge *); + +/** + * Free all memory associated with the given graph. Since graphs are + * just a collection of numbers, they do not depend on each other in + * any way. + */ +extern void GraphFree(Graph *); + +/** + * Get the weight of the edge connecting the node specified first to + * the node specified second. If this is a directed graph, it does not + * necessarily follow that there is an edge from the node specified + * second to the node specified first. It also does not follow that + * such an edge, if it exists, has the same weight. + * .P + * This function will return -1 if the graph is invalid or either node + * is out of bounds. It will return 0 if there is no such edge from the + * node specified first to the node specified second. + */ +extern Edge GraphEdgeGet(Graph *, Node, Node); + +/** + * Set the weight of the edge connecting the node specified first to + * the node specified second. If this is not a directed graph, this + * function will have to be called twice, the second time reversing the + * order of the nodes. To remove the edge, specify a weight of 0. + */ +extern Edge GraphEdgeSet(Graph *, Node, Node, Edge); + +/** + * Get the number of nodes in the given graph. This operation is a + * simple memory access that happens in O(1) time. + */ +extern size_t GraphCountNodes(Graph *); + +/** + * Perform a breadth-first search on the given graph, starting at the + * specified node. This function returns a list of nodes in the order + * they were touched. The size of the list is stored in the unsigned + * integer pointed to by the last argument. + * .P + * If an error occurs, NULL will be returned. Otherwise, the returned + * pointer should be freed with the Memory API when it is no longer + * needed. + */ +extern Node * GraphBreadthFirstSearch(Graph *, Node, size_t *); + +/** + * Perform a depth-first search on the given graph, starting at the + * specified node. This function returns a list of nodes in the order + * they were touched. The size of the list is stored in the unsigned + * integer pointed to by the last argument. + * .P + * If an error occurs, NULL will be returned. Otherwise the returned + * pointer should be freed with the Memory API when it is no longer + * needed. + */ +extern Node *GraphDepthFirstSearch(Graph *, Node, size_t *); + +/** + * Perform a topological sort on the given graph. This function returns + * a list of nodes in topological ordering, though note that this is + * probably not the only topological ordering that exists for the + * graph. The size of the list is stored in the unsigned integer + * pointed to by the last argument. It should always be the number of + * nodes in the graph, but is provided for consistency and convenience. + * .P + * If an error occurs, NULL will be returned. Otherwise the returned + * pointer should be freed with the Memory API when it is no longer + * needed. + */ +extern Node *GraphTopologicalSort(Graph *, size_t *); + +/** + * Transpose the given graph, returning a brand new graph that is the + * result of the transposition. + */ +extern Graph * GraphTranspose(Graph *); + +#endif /* CYTOPLASM_GRAPH_H */ diff --git a/src/include/HashMap.h b/src/include/HashMap.h new file mode 100644 index 0000000..1359400 --- /dev/null +++ b/src/include/HashMap.h @@ -0,0 +1,185 @@ +/* + * Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net> + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef CYTOPLASM_HASHMAP_H +#define CYTOPLASM_HASHMAP_H + +/*** + * @Nm HashMap + * @Nd A simple hash map implementation. + * @Dd October 11 2022 + * @Xr Array Queue + * + * This is the public interface for Cytoplasm's hash map + * implementation. This hash map is designed to be simple, + * well-documented, and generally readable and understandable, yet also + * performant enough to be useful, because it is used extensively + * throughout the project. + * .Pp + * Fundamentally, this is an entirely generic map implementation. It + * can be used for many general purposes, but it is designed only to + * implement the features Cytoplasm needs to be functional. One + * example of a Cytoplasm-specific feature is that keys cannot be + * arbitrary data; they are NULL-terminated C strings. + */ + +#include + +#include + +/** + * These functions operate on an opaque structure, which the caller + * has no knowledge about. + */ +typedef struct HashMap HashMap; + +/** + * Create a new hash map that is ready to be used with the rest of the + * functions defined here. + */ +extern HashMap * HashMapCreate(void); + +/** + * Free the specified hash map such that it becomes invalid and any + * future use results in undefined behavior. Note that this function + * does not free the values stored in the hash map, but since it stores + * the keys internally, it will free the keys. You should use + * .Fn HashMapIterate + * to free the values stored in this map appropriately before calling + * this function. + */ +extern void HashMapFree(HashMap *); + +/** + * Control the maximum load of the hash map before it is expanded. + * When the hash map reaches the given capacity, it is grown. You + * don't want to only grow hash maps when they are full, because that + * makes them perform very poorly. The maximum load value is a + * percentage of how full the hash map is, and it should be between + * 0 and 1, where 0 means that no elements will cause the map to be + * expanded, and 1 means that the hash map must be completely full + * before it is expanded. The default maximum load on a new hash map + * is 0.75, which should be good enough for most purposes, however, + * this function exists specifically so that the maximum load can be + * fine-tuned. + */ +extern void HashMapMaxLoadSet(HashMap *, float); + +/** + * Use a custom hashing function with the given hash map. New hash + * maps have a sane hashing function that should work okay for most + * use cases, but if you have a better hashing function, it can be + * specified this way. Do not change the hash function after keys have + * been added; doing so results in undefined behavior. Only set a new + * hash function immediately after constructing a new hash map, before + * anything has been added to it. + * .Pp + * The hash function takes a pointer to a C string, and is expected + * to return a fairly unique numerical hash value which will be + * converted into an array index. + */ +extern void +HashMapFunctionSet(HashMap *, unsigned long (*) (const char *)); + +/** + * Set the given string key to the given value. Note that the key is + * copied into the hash map's own memory space, but the value is not. + * It is the caller's job to ensure that the value pointer remains + * valid for the life of the hash map, and are freed when no longer + * needed. + */ +extern void * HashMapSet(HashMap *, char *, void *); + +/** + * Retrieve the value for the given key, or return NULL if no such + * key exists in the hash map. + */ +extern void * HashMapGet(HashMap *, const char *); + +/** + * Remove a value specified by the given key from the hash map, and + * return it to the caller to deal with. This function returns NULL + * if no such key exists. + */ +extern void * HashMapDelete(HashMap *, const char *); + +/** + * Iterate over all the keys and values of a hash map. This function + * works very similarly to + * .Xr getopt 3 , + * where calls are repeatedly made in a while loop until there are no + * more items to go over. The difference is that this function does not + * rely on globals; it takes pointer pointers, and stores all + * necessary state inside the hash map itself. + * .Pp + * Note that this function is not thread-safe; two threads cannot be + * iterating over any given hash map at the same time, though they + * can each be iterating over different hash maps. + * .Pp + * This function can be tricky to use in some scenarios, as it + * continues where it left off on each call, until there are no more + * elements to go through in the hash map. If you are not iterating + * over the entire map in one go, and happen to break the loop, then + * the next time you attempt to iterate the hash map, you'll start + * somewhere in the middle, which is most likely not the intended + * behavior. Thus, it is always recommended to iterate over the entire + * hash map if you're going to use this function. + * .Pp + * Also note that the behavior of this function is undefined if + * insertions or deletions occur during the iteration. This + * functionality has not been tested, and will likely not work. + */ +extern int HashMapIterate(HashMap *, char **, void **); + +/** + * A reentrant version of + * .Fn HashMapIterate + * that allows the caller to overcome the flaws of that function by + * storing the cursor outside of the hash map structure itself. This + * allows multiple threads to iterate over the same hash map at the + * same time, and it allows the iteration to be halted midway through + * without causing any unintended side effects. + * .Pp + * The cursor should be initialized to 0 at the start of iteration. + */ +extern int +HashMapIterateReentrant(HashMap *, char **, void **, size_t *); + +/** + * Collect the string keys of a hash map and return them as an array. + * The returned array holds pointers to the strings stored in the + * hash map, so the strings should NOT be freed; it is sufficient to + * free the array itself. Likewise, once the hash map is freed, the + * array elements are invalid and the array should be freed. + */ +extern Array * HashMapKeys(HashMap *); + +/** + * Collect the values of a hash map and return them as an array. The + * returned array holds the same pointers to the values as the hash + * map. + */ +extern Array * HashMapValues(HashMap *); + +#endif /* CYTOPLASM_HASHMAP_H */ diff --git a/src/include/HeaderParser.h b/src/include/HeaderParser.h new file mode 100644 index 0000000..ffb3d0b --- /dev/null +++ b/src/include/HeaderParser.h @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net> + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef CYTOPLASM_HEADERPARSER_H +#define CYTOPLASM_HEADERPARSER_H + +/*** + * @Nm HeaderParser + * @Nd Parse simple C header files. + * @Dd April 29 2023 + * + * .Nm + * is an extremely simple parser that lacks most of the functionality + * one would expect from a C code parser. It simply maps a stream + * of tokens into a few categories, parsing major ``milestones'' in + * a header, without actually understanding any of the details. + * .Pp + * This exists because it is used to generate man pages from headers. + * See + * .Xr hdoc 1 + * for example usage of this parser. + */ + +#include +#include + +#define HEADER_EXPR_MAX 4096 + +/** + * Headers are parsed as expressions. These are the expressions that + * this parser recognizes. + */ +typedef enum HeaderExprType +{ + HP_COMMENT, + HP_PREPROCESSOR_DIRECTIVE, + HP_TYPEDEF, + HP_DECLARATION, + HP_GLOBAL, + HP_UNKNOWN, + HP_SYNTAX_ERROR, + HP_PARSE_ERROR, + HP_EOF +} HeaderExprType; + +/** + * A representation of a function declaration. + */ +typedef struct HeaderDeclaration +{ + char returnType[64]; + char name[32]; /* Enforced by ANSI C */ + Array *args; +} HeaderDeclaration; + +/** + * A global variable declaration. The type must be of the same size + * as the function declaration's return type due to the way parsing + * them is implemented. + */ +typedef struct HeaderGlobal +{ + char type[64]; + char name[HEADER_EXPR_MAX - 64]; +} HeaderGlobal; + +/** + * A representation of a single header expression. Note that that state + * structure is entirely internally managed, so it should not be + * accessed or manipulated by functions outside the functions defined + * here. + * .Pp + * The type field should be used to determine which field in the data + * union is valid. + */ +typedef struct HeaderExpr +{ + HeaderExprType type; + union + { + char text[HEADER_EXPR_MAX]; + HeaderDeclaration declaration; + HeaderGlobal global; + struct + { + int lineNo; + char *msg; + } error; + } data; + + struct + { + Stream *stream; + int lineNo; + } state; +} HeaderExpr; + +/** + * Parse the next expression into the given header expression structure. + * To parse an entire C header, this function should be called in a + * loop until the type of the expression is HP_EOF. + */ +extern void HeaderParse(Stream *, HeaderExpr *); + +#endif /* CYTOPLASM_HEADERPARSER_H */ diff --git a/src/include/Http.h b/src/include/Http.h new file mode 100644 index 0000000..d2de020 --- /dev/null +++ b/src/include/Http.h @@ -0,0 +1,211 @@ +/* + * Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net> + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef CYTOPLASM_HTTP_H +#define CYTOPLASM_HTTP_H + +/*** + * @Nm Http + * @Nd Encode and decode various parts of the HTTP protocol. + * @Dd March 12 2023 + * @Xr HttpClient HttpServer HashMap Queue Memory + * + * .Nm + * is a collection of utility functions and type definitions that are + * useful for dealing with HTTP. HTTP is not a complex protocol, but + * this API makes it a lot easier to work with. + * .Pp + * Note that this API doesn't target any particular HTTP version, but + * it is currently used with HTTP 1.0 clients and servers, and + * therefore may be lacking functionality added in later HTTP versions. + */ + +#include + +#include +#include + +#define HTTP_FLAG_NONE 0 +#define HTTP_FLAG_TLS (1 << 0) + +/** + * The request methods defined by the HTTP standard. These numeric + * constants should be preferred to strings when building HTTP APIs + * because they are more efficient. + */ +typedef enum HttpRequestMethod +{ + HTTP_METHOD_UNKNOWN, + HTTP_GET, + HTTP_HEAD, + HTTP_POST, + HTTP_PUT, + HTTP_DELETE, + HTTP_CONNECT, + HTTP_OPTIONS, + HTTP_TRACE, + HTTP_PATCH +} HttpRequestMethod; + +/** + * An enumeration that corresponds to the actual integer values of the + * valid HTTP response codes. + */ +typedef enum HttpStatus +{ + HTTP_STATUS_UNKNOWN = 0, + + /* Informational responses */ + HTTP_CONTINUE = 100, + HTTP_SWITCHING_PROTOCOLS = 101, + HTTP_EARLY_HINTS = 103, + + /* Successful responses */ + HTTP_OK = 200, + HTTP_CREATED = 201, + HTTP_ACCEPTED = 202, + HTTP_NON_AUTHORITATIVE_INFORMATION = 203, + HTTP_NO_CONTENT = 204, + HTTP_RESET_CONTENT = 205, + HTTP_PARTIAL_CONTENT = 206, + + /* Redirection messages */ + HTTP_MULTIPLE_CHOICES = 300, + HTTP_MOVED_PERMANENTLY = 301, + HTTP_FOUND = 302, + HTTP_SEE_OTHER = 303, + HTTP_NOT_MODIFIED = 304, + HTTP_TEMPORARY_REDIRECT = 307, + HTTP_PERMANENT_REDIRECT = 308, + + /* Client error messages */ + HTTP_BAD_REQUEST = 400, + HTTP_UNAUTHORIZED = 401, + HTTP_FORBIDDEN = 403, + HTTP_NOT_FOUND = 404, + HTTP_METHOD_NOT_ALLOWED = 405, + HTTP_NOT_ACCEPTABLE = 406, + HTTP_PROXY_AUTH_REQUIRED = 407, + HTTP_REQUEST_TIMEOUT = 408, + HTTP_CONFLICT = 409, + HTTP_GONE = 410, + HTTP_LENGTH_REQUIRED = 411, + HTTP_PRECONDITION_FAILED = 412, + HTTP_PAYLOAD_TOO_LARGE = 413, + HTTP_URI_TOO_LONG = 414, + HTTP_UNSUPPORTED_MEDIA_TYPE = 415, + HTTP_RANGE_NOT_SATISFIABLE = 416, + HTTP_EXPECTATION_FAILED = 417, + HTTP_TEAPOT = 418, + HTTP_UPGRADE_REQUIRED = 426, + HTTP_PRECONDITION_REQUIRED = 428, + HTTP_TOO_MANY_REQUESTS = 429, + HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE = 431, + HTTP_UNAVAILABLE_FOR_LEGAL_REASONS = 451, + + /* Server error responses */ + HTTP_INTERNAL_SERVER_ERROR = 500, + HTTP_NOT_IMPLEMENTED = 501, + HTTP_BAD_GATEWAY = 502, + HTTP_SERVICE_UNAVAILABLE = 503, + HTTP_GATEWAY_TIMEOUT = 504, + HTTP_VERSION_NOT_SUPPORTED = 505, + HTTP_VARIANT_ALSO_NEGOTIATES = 506, + HTTP_NOT_EXTENDED = 510, + HTTP_NETWORK_AUTH_REQUIRED = 511 +} HttpStatus; + +/** + * Convert an HTTP status enumeration value into a string description + * of the status, which is to be used in server response to a client, + * or a client response to a user. For example, calling + * .Fn HttpStatusToString "HTTP_GATEWAY_TIMEOUT" + * (or + * .Fn HttpStatusToString "504" ) + * produces the string "Gateway Timeout". Note that the returned + * pointers point to static space, so their manipulation is forbidden. + */ +extern const char * HttpStatusToString(const HttpStatus); + +/** + * Convert a string into a numeric code that can be used throughout + * the code of a program in an efficient manner. See the definition + * of HttpRequestMethod. This function does case-sensitive matching, + * and does not trim or otherwise process the input string. + */ +extern HttpRequestMethod HttpRequestMethodFromString(const char *); + +/** + * Convert a numeric code as defined by HttpRequestMethod into a + * string that can be sent to a server. Note that the returned pointers + * point to static space, so their manipulation is forbidden. + */ +extern const char * HttpRequestMethodToString(const HttpRequestMethod); + +/** + * Encode a C string such that it can safely appear in a URL by + * performing the necessary percent escaping. A new string on the + * heap is returned. It should be freed with + * .Fn Free , + * defined in the + * .Xr Memory 3 + * API. + */ +extern char * HttpUrlEncode(char *); + +/** + * Decode a percent-encoded string into a C string, ignoring encoded + * null characters entirely, because those would do nothing but cause + * problems. + */ +extern char * HttpUrlDecode(char *); + +/** + * Decode an encoded parameter string in the form of + * ``key=val&key2=val2'' into a hash map whose values are C strings. + * This function properly decodes keys and values using the functions + * defined above. + */ +extern HashMap * HttpParamDecode(char *); + +/** + * Encode a hash map whose values are strings as an HTTP parameter + * string suitable for GET or POST requests. + */ +extern char * HttpParamEncode(HashMap *); + +/** + * Read HTTP headers from a stream and return a hash map whose values + * are strings. All keys are lowercased to make querying them + * consistent and not dependent on the case that was read from the + * stream. This is useful for both client and server code, since the + * headers are in the same format. This function should be used after + * parsing the HTTP status line, because it does not parse that line. + * It will stop when it encounters the first blank line, which + * indicates that the body is beginning. After this function completes, + * the body may be immediately read from the stream without any + * additional processing. + */ +extern HashMap * HttpParseHeaders(Stream *); + +#endif diff --git a/src/include/HttpClient.h b/src/include/HttpClient.h new file mode 100644 index 0000000..f757041 --- /dev/null +++ b/src/include/HttpClient.h @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net> + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef CYTOPLASM_HTTPCLIENT_H +#define CYTOPLASM_HTTPCLIENT_H + +/*** + * @Nm HttpClient + * @Nd Extremely simple HTTP client. + * @Dd April 29 2023 + * @Xr Http HttpServer Tls + * + * .Nm + * HttpClient + * builds on the HTTP API to provide a simple yet functional HTTP + * client. It aims at being easy to use and minimal, yet also + * efficient. + */ + +#include + +#include +#include + +/** + * A server response is represented by a client context. It is + * opaque, so the functions defined in this API should be used to + * fetch data from and manipulate it. + */ +typedef struct HttpClientContext HttpClientContext; + +/** + * Make an HTTP request. This function takes the request method, + * any flags defined in the HTTP API, the port, hostname, and path, + * all in that order. It returns NULL if there was an error making + * the request. Otherwise it returns a client context. Note that this + * function does not actually send any data, it simply makes the + * connection. Use + * .Fn HttpRequestHeader + * to add headers to the request. Then, send headers with + * .Fn HttpRequestSendHeaders . + * Finally, the request body, if any, can be written to the output + * stream, and then the request can be fully sent using + * .Fn HttpRequestSend . + */ +extern HttpClientContext * + HttpRequest(HttpRequestMethod, int, unsigned short, char *, char *); + +/** + * Set a request header to send to the server when making the + * request. + */ +extern void HttpRequestHeader(HttpClientContext *, char *, char *); + +/** + * Send the request headers to the server. This must be called before + * the request body can be written or a response body can be read. + */ +extern void HttpRequestSendHeaders(HttpClientContext *); + +/** + * Flush the request stream to the server. This function should be + * called before the response body is read. + */ +extern HttpStatus HttpRequestSend(HttpClientContext *); + +/** + * Get the headers returned by the server. + */ +extern HashMap * HttpResponseHeaders(HttpClientContext *); + +/** + * Get the stream used to write the request body and read the + * response body. + */ +extern Stream * HttpClientStream(HttpClientContext *); + +/** + * Free all memory associated with the client context. This closes the + * connection, if it was still open. + */ +extern void HttpClientContextFree(HttpClientContext *); + +#endif /* CYTOPLASM_HTTPCLIENT_H */ diff --git a/src/include/HttpRouter.h b/src/include/HttpRouter.h new file mode 100644 index 0000000..6816178 --- /dev/null +++ b/src/include/HttpRouter.h @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net> + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef CYTOPLASM_HTTPROUTER_H +#define CYTOPLASM_HTTPROUTER_H + +/*** + * @Nm HttpRouter + * @Nd Simple HTTP request router with regular expression support. + * @Dd April 29 2023 + * @Xr HttpServer Http + * + * .Nm + * provides a simple mechanism for assigning functions to an HTTP + * request path. It is a simple tree data structure that parses the + * registered request paths and maps functions onto each part of the + * path. Then, requests can be easily routed to their appropriate + * handler functions. + */ + +#include + +/** + * The router structure is opaque and thus managed entirely by the + * functions defined in this API. + */ +typedef struct HttpRouter HttpRouter; + +/** + * A function written to handle an HTTP request takes an array + * consisting of the matched path parts in the order they appear in + * the path, and a pointer to caller-provided arguments, if any. + * It returns a pointer that the caller is assumed to know how to + * handle. + */ +typedef void *(HttpRouteFunc) (Array *, void *); + +/** + * Create a new empty routing tree. + */ +extern HttpRouter * HttpRouterCreate(void); + +/** + * Free all the memory associated with the given routing tree. + */ +extern void HttpRouterFree(HttpRouter *); + +/** + * Register the specified route function to be executed upon requests + * for the specified HTTP path. The path is parsed by splitting at + * each path separator. Each part of the path is a regular expression + * that matches the entire path part. A regular expression cannot + * match more than one path part. This allows for paths like + * .Pa /some/path/(.*)/parts + * to work as one would expect. + */ +extern int HttpRouterAdd(HttpRouter *, char *, HttpRouteFunc *); + +/** + * Route the specified request path using the specified routing + * tree. This function will parse the path and match it to the + * appropriate route handler function. The return value is a boolean + * value that indicates whether or not an appropriate route function + * was found. If an appropriate function was found, then the void + * pointer is passed to it as arguments that it is expected to know + * how to handle, and the pointer to a void pointer is where the + * route function's response will be placed. + */ +extern int HttpRouterRoute(HttpRouter *, char *, void *, void **); + +#endif /* CYTOPLASM_HTTPROUTER_H */ diff --git a/src/include/HttpServer.h b/src/include/HttpServer.h new file mode 100644 index 0000000..f8e449a --- /dev/null +++ b/src/include/HttpServer.h @@ -0,0 +1,234 @@ +/* + * Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net> + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef CYTOPLASM_HTTPSERVER_H +#define CYTOPLASM_HTTPSERVER_H + +/*** + * @Nm HttpServer + * @Nd Extremely simple HTTP server. + * @Dd December 13 2022 + * @Xr Http HttpClient + * + * .Nm + * builds on the + * .Xr Http 3 + * API, and provides a very simple, yet very functional API for + * creating an HTTP server. It aims at being easy to use and minimal, + * yet also efficient. It uses non-blocking I/O, is fully + * multi-threaded, and is very configurable. It can be set up in just + * two function calls and minimal supporting code. + * .Pp + * This API should be familar to those that have dealt with the HTTP + * server libraries of other programming languages, particularly Java. + * In fact, much of the terminology used in this API came from Java, + * and you'll notice that the way responses are sent and received very + * closely resembles Java. + */ + +#include + +#include + +#include +#include + +/** + * The functions on this API operate on an opaque structure. + */ +typedef struct HttpServer HttpServer; + +/** + * Each request receives a context structure. It is opaque, so the + * functions defined in this API should be used to fetch data from + * it. These functions allow the handler to figure out the context of + * the request, which includes the path requested, any parameters, + * and the headers and method used to make the request. The context + * also provides the means by which the handler responds to the + * request, allowing it to set the status code, headers, and body. + */ +typedef struct HttpServerContext HttpServerContext; + +/** + * The request handler function is executed when an HTTP request is + * received. It takes a request context, and a pointer as specified + * in the server configuration. + */ +typedef void (HttpHandler) (HttpServerContext *, void *); + +/** + * The number of arguments to + * .Fn HttpServerCreate + * has grown so large that arguments are now stuffed into a + * configuration structure, which is in turn passed to + * .Fn HttpServerCreate . + * This configuration is copied by value into the internal + * structures of the server. It is copied with very minimal + * validation, so ensure that all values are sensible. It may + * make sense to use + * .Fn memset + * to zero out everything in here before assigning values. + */ +typedef struct HttpServerConfig +{ + unsigned short port; + unsigned int threads; + unsigned int maxConnections; + + int flags; /* Http(3) flags */ + char *tlsCert; /* File path */ + char *tlsKey; /* File path */ + + HttpHandler *handler; + void *handlerArgs; +} HttpServerConfig; + +/** + * Create a new HTTP server using the specified configuration. + * This will set up all internal structures used by the server, + * and bind the socket and start listening for connections. However, + * it will not start accepting connections. + */ +extern HttpServer * HttpServerCreate(HttpServerConfig *); + +/** + * Retrieve the configuration that was used to instantiate the given + * server. Note that this configuration is not necessarily the exact + * one that was provided; even though its values are the same, it + * should be treated as an entirely separate configuration with no + * connection to the original. + */ +extern HttpServerConfig * HttpServerConfigGet(HttpServer *); + +/** + * Free the resources associated with the given HTTP server. Note that + * the server can only be freed after it has been stopped. Calling this + * function while the server is still running results in undefined + * behavior. + */ +extern void HttpServerFree(HttpServer *); + +/** + * Attempt to start the HTTP server, and return immediately with the + * status. This API is fully multi-threaded and asynchronous, so the + * caller can continue working while the HTTP server is running in a + * separate thread and managing a pool of threads to handle responses. + */ +extern int HttpServerStart(HttpServer *); + +/** + * Typically, at some point after calling + * .Fn HttpServerStart , + * the program will have no more work to do, so it will want to wait + * for the HTTP server to finish. This is accomplished via this + * function, which joins the HTTP worker thread to the calling thread, + * pausing the calling thread until the HTTP server has stopped. + */ +extern void HttpServerJoin(HttpServer *); + +/** + * Stop the HTTP server. Only the execution of this function will + * cause the proper shutdown of the HTTP server. If the main program + * is joined to the HTTP thread, then either another thread or a + * signal handler will have to stop the server using this function. + * The typical use case is to install a signal handler that executes + * this function on a global HTTP server. + */ +extern void HttpServerStop(HttpServer *); + +/** + * Get the request headers for the request represented by the given + * context. The data in the returned hash map should be treated as + * read only and should not be freed; it is managed entirely by the + * server. + */ +extern HashMap * HttpRequestHeaders(HttpServerContext *); + +/** + * Get the request method used to make the request represented by + * the given context. + */ +extern HttpRequestMethod HttpRequestMethodGet(HttpServerContext *); + +/** + * Get the request path for the request represented by the given + * context. The return value of this function should be treated as + * read-only, and should not be freed; it is managed entirely by the + * server. + */ +extern char * HttpRequestPath(HttpServerContext *); + +/** + * Retrieve the parsed GET parameters for the request represented by + * the given context. The returned hash map should be treated as + * read-only, and should not be freed; it is managed entirely by the + * server. + */ +extern HashMap * HttpRequestParams(HttpServerContext *); + +/** + * Set a response header to return to the client. The old value for + * the given header is returned, if any, otherwise NULL is returned. + */ +extern char * HttpResponseHeader(HttpServerContext *, char *, char *); + +/** + * Set the response status to return to the client. + */ +extern void HttpResponseStatus(HttpServerContext *, HttpStatus); + +/** + * Get the current response status that will be sent to the client + * making the request represented by the given context. + */ +extern HttpStatus HttpResponseStatusGet(HttpServerContext *); + +/** + * Send the response headers to the client that made the request + * represented by the specified context. This function must be called + * before the response body can be written, otherwise a malformed + * response will be sent. + */ +extern void HttpSendHeaders(HttpServerContext *); + +/** + * Get a stream that is both readable and writable. Reading from the + * stream reads the request body that the client sent, if there is one. + * Note that the rquest headers have already been read, so the stream + * is correctly positioned at the beginning of the body of the request. + * .Fn HttpSendHeaders + * must be called before the stream is written, otherwise a malformed + * HTTP response will be sent. An HTTP handler should properly set all + * the headers it itends to send, send those headers, and then write + * the response body to this stream. + * .Pp + * Note that the stream does not need to be closed by the HTTP + * handler; in fact doing so results in undefined behavior. The stream + * is managed entirely by the server itself, so it will close it when + * necessary. This allows the underlying protocol to differ: for + * instance, an HTTP/1.1 connection may stay for multiple requests and + * responses. + */ +extern Stream * HttpServerStream(HttpServerContext *); + +#endif /* CYTOPLASM_HTTPSERVER_H */ diff --git a/src/include/Int.h b/src/include/Int.h new file mode 100644 index 0000000..7933a69 --- /dev/null +++ b/src/include/Int.h @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net> + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef CYTOPLASM_INT_H +#define CYTOPLASM_INT_H + +/*** + * @Nm Int + * @Nd Fixed-width integer types. + * @Dd April 27 2023 + * + * This header provides cross-platform, fixed-width integer types. + * Specifically, it uses preprocessor magic to define the following + * types: + * .Bl -bullet -offset indent + * .It + * Int8 and UInt8 + * .It + * Int16 and UInt16 + * .It + * Int32 and UInt32 + * .El + * .Pp + * Note that there is no 64-bit integer type, because the ANSI C + * standard makes no guarantee that such a type will exist, even + * though it does on most platforms. + * .Pp + * The reason Cytoplasm provides its own header for this is + * because ANSI C does not define fixed-width types, and while it + * should be safe to rely on C99 fixed-width types in most cases, + * there may be cases where even that is not possible. + * + * @ignore-typedefs + */ + +#include + +#define BIT32_MAX 4294967295UL +#define BIT16_MAX 65535UL +#define BIT8_MAX 255UL + +#ifndef UCHAR_MAX +#error Size of char data type is unknown. Define UCHAR_MAX. +#endif + +#ifndef USHRT_MAX +#error Size of short data type is unknown. Define USHRT_MAX. +#endif + +#ifndef UINT_MAX +#error Size of int data type is unknown. Define UINT_MAX. +#endif + +#ifndef ULONG_MAX +#error Size of long data type is unknown. Define ULONG_MAX. +#endif + +#if UCHAR_MAX == BIT8_MAX +typedef signed char Int8; +typedef unsigned char UInt8; + +#else +#error Unable to determine suitable data type for 8-bit integers. +#endif + +#if UINT_MAX == BIT16_MAX +typedef signed int Int16; +typedef unsigned int UInt16; + +#elif USHRT_MAX == BIT16_MAX +typedef signed short Int16; +typedef unsigned short UInt16; + +#elif UCHAR_MAX == BIT16_MAX +typedef signed char Int16; +typedef unsigned char UInt16; + +#else +#error Unable to determine suitable data type for 16-bit integers. +#endif + +#if ULONG_MAX == BIT32_MAX +typedef signed long Int32; +typedef unsigned long UInt32; + +#elif UINT_MAX == BIT32_MAX +typedef signed int Int32; +typedef unsigned int UInt32; + +#elif USHRT_MAX == BIT32_MAX +typedef signed short Int32; +typedef unsigned short UInt32; + +#elif UCHAR_MAX == BIT32_MAX +typedef signed char Int32; +typedef unsigned char UInt32; + +#else +#error Unable to determine suitable data type for 32-bit integers. +#endif + +#endif /* CYTOPLASM_INT_H */ diff --git a/src/include/Int64.h b/src/include/Int64.h new file mode 100644 index 0000000..08083c1 --- /dev/null +++ b/src/include/Int64.h @@ -0,0 +1,252 @@ +/* + * Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net> + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef CYTOPLASM_INT64_H +#define CYTOPLASM_INT64_H + +/*** + * @Nm Int64 + * @Nd Fixed-width 64 bit integers. + * @Dd August 11, 2023 + * + * .Pp + * ANSI C89 (or C99 for that matter) provides no required mechanism + * for 64 bit integers. Nevertheless, many compilers provide them as + * extensions. However, since it is not a gaurantee, and to be fully + * standards-compliant and thus portable, a platform-agnostic interface + * is required. This header provides such an interface. If the platform + * has a 64 bit integer type, that is used, and native operations are + * performed by C preprocessor macro expansion. Otherwise, a + * compatibility layer is provided, which implements 64-bit + * arithmetic on an array of 2 32-bit numbers which are provided by + * .Xr Int 3 . + * .Pp + * Note that 64-bit emulation is certainly not as performant as using + * native 64-bit operations, so whenever possible, the native + * operations should be preferred. However, since C provides no required + * 64 bit integer on 32-bit and less platforms, this API can be used as + * a "good enough" fallback mechanism. + * .Pp + * Also note that this implementation, both in the native and + * non-native forms, makes some assumptions: + * .Bl -bullet -width Ds + * .It + * When a cast from a larger integer to a smaller integer is performed, + * the upper bits are truncated, not the lower bits. + * .It + * Negative numbers are represented in memory and in registers in two's + * compliment form. + * .El + * .Pp + * This API may provide unexpected output if these assumptions are + * false for a given platform. + * + * @ignore-typedefs + */ + +#include +#include + +#include + +#ifndef INT64_FORCE_EMULATED + +#define BIT64_MAX 18446744073709551615UL + +#if UINT_MAX == BIT64_MAX +typedef signed int Int64; + +#define INT64_NATIVE + +#elif ULONG_MAX == BIT64_MAX +typedef signed long Int64; + +#define INT64_NATIVE + +#endif + +#endif /* ifndef INT64_FORCE_EMULATED */ + +#ifdef INT64_NATIVE + +#define Int64Create(high, low) ((Int64) (((UInt64) (high) << 32) | (low))) +#define Int64Neg(x) (-(x)) + +#define Int64Low(a) ((UInt32) (a)) +#define Int64High(a) ((UInt32) ((a) >> 32)) + +#define Int64Add(a, b) ((a) + (b)) +#define Int64Sub(a, b) ((a) - (b)) +#define Int64Mul(a, b) ((a) * (b)) +#define Int64Div(a, b) ((a) / (b)) +#define Int64Rem(a, b) ((a) % (b)) + +#define Int64Sll(a, b) ((a) << (b)) +#define Int64Sra(a, b) ((a) >> (b)) + +#define Int64And(a, b) ((a) & (b)) +#define Int64Or(a, b) ((a) | (b)) +#define Int64Xor(a, b) ((a) ^ (b)) +#define Int64Not(a) (~(a)) + +#define Int64Eq(a, b) ((a) == (b)) +#define Int64Lt(a, b) ((a) < (b)) +#define Int64Gt(a, b) ((a) > (b)) + +#define Int64Neq(a, b) ((a) != (b)) +#define Int64Leq(a, b) ((a) <= (b)) +#define Int64Geq(a, b) ((a) >= (b)) + +#else + +#define Int64Neg(x) (Int64Add(Int64Not(x), Int64Create(0, 1))) + +/** + * The internal bit representation of a signed integer is identical + * to an unsigned integer, the difference is in the algorithms and + * the way the bits are interpreted. + */ +typedef UInt64 Int64; + +/** + * Create a new signed 64 bit integer using the given high and low + * bits. + */ +extern Int64 Int64Create(UInt32, UInt32); + +/** + * Add two signed 64 bit integers together. + */ +extern Int64 Int64Add(Int64, Int64); + +/** + * Subtract the second 64 bit integer from the first. + */ +extern Int64 Int64Sub(Int64, Int64); + +/** + * Multiply two 64 bit integers together. The non-native version of + * this function uses the Russian Peasant method of multiplication, + * which should afford more performance than a naive multiplication by + * addition, but it is still rather slow and depends on the size of + * the integers being multiplied. + */ +extern Int64 Int64Mul(Int64, Int64); + +/** + * Divide the first 64 bit integer by the second and return the + * quotient. The non-native version of this function uses naive binary + * long division, which is slow, but gauranteed to finish in constant + * time. + */ +extern Int64 Int64Div(Int64, Int64); + +/** + * Divide the first 64 bit integer by the second and return the + * remainder. The non-native version of this function uses naive binary + * long division, which is slow, but gauranteed to finish in constant + * time. + */ +extern Int64 Int64Rem(Int64, Int64); + +/** + * Perform a left logical bit shift of a 64 bit integer. The second + * parameter is how many places to shift, and is declared as a regular + * integer because anything more than 64 does not make sense. + */ +extern Int64 Int64Sll(Int64, int); + +/** + * Perform a right arithmetic bit shift of a 64 bit integer. The second + * parameter is how many places to shift, and is declared as a regular + * integer because anything more than 64 does not make sense. + * .Pp + * Note that on platforms that use the native 64-bit implementation, + * this is technically implementation-defined, and may in fact be a + * logical shift instead of an arithmetic shift. Note that typically + * this operation is not performed on signed integers. + */ +extern Int64 Int64Sra(Int64, int); + +/** + * Perform a bitwise AND (&) of the provided 64 bit integers. + */ +extern Int64 Int64And(Int64, Int64); + +/** + * Perform a bitwise OR (|) of the provided 64 bit integers. + */ +extern Int64 Int64Or(Int64, Int64); + +/** + * Perform a bitwise XOR (^) of the provided 64 bit integers. + */ +extern Int64 Int64Xor(Int64, Int64); + +/** + * Perform a bitwise NOT (~) of the provided 64 bit integer. + */ +extern Int64 Int64Not(Int64); + +/** + * Perform a comparison of the provided 64 bit integers and return a C + * boolean that is true if and only if they are equal. + */ +extern int Int64Eq(Int64, Int64); + +/** + * Perform a comparison of the provided 64 bit integers and return a C + * boolean that is true if and only if the second operand is strictly + * less than the first. + */ +extern int Int64Lt(Int64, Int64); + +/** + * Perform a comparison of the provided 64 bit integers and return a C + * boolean that is true if and only if the second operand is strictly + * greater than the first. + */ +extern int Int64Gt(Int64, Int64); + +#define Int64Low(a) ((a).i[0]) +#define Int64High(a) ((a).i[1]) + +#define Int64Neq(a, b) (!Int64Eq(a, b)) +#define Int64Leq(a, b) (Int64Eq(a, b) || Int64Lt(a, b)) +#define Int64Geq(a, b) (Int64Eq(a, b) || Int64Gt(a, b)) + +#endif + +#define INT64_STRBUF 65 /* Base 2 representation with '\0' */ + +/** + * Convert a 64 bit integer to a string in an arbitrary base + * representation specified by the second parameter, using the provided + * buffer and length specified by the third and fourth parameters. To + * guarantee that the string will fit in the buffer, allocate it of + * size INT64_STRBUF or larger. Note that a buffer size smaller than + * INT64_STRBUF will invoke undefined behavior. + */ +extern size_t Int64Str(Int64, int, char *, size_t); + +#endif /* CYTOPLASM_INT64_H */ diff --git a/src/include/Io.h b/src/include/Io.h new file mode 100644 index 0000000..06afb77 --- /dev/null +++ b/src/include/Io.h @@ -0,0 +1,222 @@ +/* + * Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net> + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef CYTOPLASM_IO_H +#define CYTOPLASM_IO_H + +/*** + * @Nm Io + * @Nd Source/sink-agnostic I/O for implementing custom streams. + * @Dd April 29 2023 + * @Xr Stream Tls + * + * Many systems provide platform-specific means of implementing custom + * streams using file pointers. However, POSIX does not define a way + * of programmatically creating custom streams. + * .Nm + * therefore fills this gap in POSIX by mimicking all of the + * functionality of these platform-specific functions, but in pure + * POSIX C. It defines a number of callback funtions to be executed + * in place of the standard POSIX I/O functions, which are used to + * implement arbitrary streams that may not be to a file or socket. + * Additionally, streams can now be pipelined; the sink of one stream + * may be the source of another lower-level stream. Additionally, all + * streams, regardless of their source or sink, share the same API, so + * streams can be handled in a much more generic manner. This allows + * the HTTP client and server libraries to seemlessly support TLS and + * plain connections without having to handle each separately. + * .Pp + * .Nm + * was heavily inspired by GNU's + * .Fn fopencookie + * and BSD's + * .Fn funopen . + * It aims to combine the best of both of these functions into a single + * API that is intuitive and easy to use. + */ + +#include +#include +#include +#include + +#ifndef IO_BUFFER +#define IO_BUFFER 4096 +#endif + +/** + * An opaque structure analogous to a POSIX file descriptor. + */ +typedef struct Io Io; + +/** + * Read input from the source of a stream. This function should + * attempt to read the specified number of bytes of data from the + * given cookie into the given buffer. It should behave identically + * to the POSIX + * .Xr read 2 + * system call, except instead of using an integer descriptor as the + * first parameter, a pointer to an implementation-defined cookie + * stores any information the function needs to read from the source. + */ +typedef ssize_t (IoReadFunc) (void *, void *, size_t); + +/** + * Write output to a sink. This function should attempt to write the + * specified number of bytes of data from the given buffer into the + * stream described by the given cookie. It should behave identically + * to the POSIX + * .Xr write 2 + * system call, except instead of using an integer descriptor as the + * first parameter, a pointer to an implementation-defined cookie + * stores any information the function needs to write to the sink. + */ +typedef ssize_t (IoWriteFunc) (void *, void *, size_t); + +/** + * Repositions the offset of the stream described by the specified + * cookie. This function should behave identically to the POSIX + * .Xr lseek 2 + * system call, except instead of using an integer descriptor as the + * first parameter, a pointer to an implementation-defined cookie + * stores any information the function needs to seek the stream. + */ +typedef off_t (IoSeekFunc) (void *, off_t, int); + +/** + * Close the given stream, making future reads or writes result in + * undefined behavior. This function should also free all memory + * associated with the cookie. It should behave identically to the + * .Xr close 2 + * system call, except instead of using an integer descriptor for the + * parameter, a pointer to an implementation-defined cookie stores any + * information the function needs to close the stream. + */ +typedef int (IoCloseFunc) (void *); + +/** + * A simple mechanism for grouping together a set of stream functions, + * to be passed to + * .Fn IoCreate . + */ +typedef struct IoFunctions +{ + IoReadFunc *read; + IoWriteFunc *write; + IoSeekFunc *seek; + IoCloseFunc *close; +} IoFunctions; + +/** + * Create a new stream using the specified cookie and the specified + * I/O functions. + */ +extern Io * IoCreate(void *, IoFunctions); + +/** + * Read the specified number of bytes from the specified stream into + * the specified buffer. This calls the stream's underlying IoReadFunc, + * which should behave identically to the POSIX + * .Xr read 2 + * system call. + */ +extern ssize_t IoRead(Io *, void *, size_t); + +/** + * Write the specified number of bytes from the specified stream into + * the specified buffer. This calls the stream's underlying + * IoWriteFunc, which should behave identically to the POSIX + * .Xr write 2 + * system call. + */ +extern ssize_t IoWrite(Io *, void *, size_t); + +/** + * Seek the specified stream using the specified offset and whence + * value. This calls the stream's underlying IoSeekFunc, which should + * behave identically to the POSIX + * .Xr lseek 2 + * system call. + */ +extern off_t IoSeek(Io *, off_t, int); + +/** + * Close the specified stream. This calls the stream's underlying + * IoCloseFunc, which should behave identically to the POSIX + * .Xr close 2 + * system call. + */ +extern int IoClose(Io *); + +/** + * Print a formatted string to the given stream. This is a + * re-implementation of the standard library function + * .Xr vfprintf 3 , + * and behaves identically. + */ +extern int IoVprintf(Io *, const char *, va_list); + +/** + * Print a formatted string to the given stream. This is a + * re-implementation of the standard library function + * .Xr fprintf 3 , + * and behaves identically. + */ +extern int IoPrintf(Io *, const char *,...); + +/** + * Read all the bytes from the first stream and write them to the + * second stream. Neither stream is closed upon the completion of this + * function. This can be used for quick and convenient buffered + * copying of data from one stream into another. + */ +extern ssize_t IoCopy(Io *, Io *); + +/** + * Wrap a POSIX file descriptor to take advantage of this API. The + * common use case for this function is when a regular file descriptor + * needs to be accessed by an application that uses this API to also + * access non-POSIX streams. + */ +extern Io * IoFd(int); + +/** + * Open or create a file for reading or writing. The specified file + * name is opened for reading or writing as specified by the given + * flags and mode. This function is a simple convenience wrapper around + * the POSIX + * .Xr open 2 + * system call that passes the opened file descriptor into + * .Fn IoFd . + */ +extern Io * IoOpen(const char *, int, mode_t); + +/** + * Wrap a standard C file pointer to take advantage of this API. The + * common use case for this function is when a regular C file pointer + * needs to be accessed by an application that uses this API to also + * access custom streams. + */ +extern Io * IoFile(FILE *); + +#endif /* CYTOPLASM_IO_H */ diff --git a/src/include/Json.h b/src/include/Json.h new file mode 100644 index 0000000..4185260 --- /dev/null +++ b/src/include/Json.h @@ -0,0 +1,323 @@ +/* + * Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net> + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef CYTOPLASM_JSON_H +#define CYTOPLASM_JSON_H + +/*** + * @Nm Json + * @Nd A fully-featured JSON API. + * @Dd March 12 2023 + * @Xr HashMap Array Stream + * + * .Nm + * is a fully-featured JSON API for C using the array and hash map + * APIs. It can parse JSON, ans serialize an in-memory structure to + * JSON. It build on the foundation of Array and HashMap because that's + * all JSON really is, just arrays and maps. + * .Nm + * also provides a structure for encapsulating an arbitrary value and + * identifying its type, making it easy for a strictly-typed language + * like C to work with loosely-typed JSON data. + * .Nm + * is very strict and tries to adhere as closely as possible to the + * proper definition of JSON. It will fail on syntax errors of any + * kind, which is fine for a Matrix homeserver that can just return + * M_BAD_JSON if anything in here fails, but this behavior may not be + * suitable for other purposes. + * .Pp + * This JSON implementation focuses primarily on serialization and + * deserialization to and from streams. It does not provide facilities + * for handling JSON strings; it only writes JSON to output streams, + * and reads them from input streams. Of course, you can use the + * POSIX + * .Xr fmemopen 3 + * and + * .Xr open_memstream 3 + * functions if you want to deal with JSON strings, but JSON is + * intended to be an exchange format. Data should be converted to JSON + * right when it is leaving the program, and converted from JSON to the + * in-memory format as soon as it is coming in. + * .Pp + * JSON objects are represented as hash maps consisting entirely of + * JsonValue structures, and arrays are represented as arrays + * consisting entirely of JsonValue structures. When generating a + * JSON object, any attempt to stuff a value into a hash map or array + * without first encoding it as a JsonValue will result in undefined + * behavior. + */ + +#include +#include +#include +#include + +#include +#include + +#define JSON_DEFAULT -1 +#define JSON_PRETTY 0 + +/** + * This opaque structure encapsulates all the possible types that can + * be stored in JSON. It is managed entirely by the functions defined + * in this API. It is important to note that strings, integers, floats, + * booleans, and the NULL value are all stored by value, but objects + * and arrays are stored by reference. That is, it doesn't store these + * itself, just pointers to them, however, the data + * .Em is + * freed when using + * .Fn JsonFree . + */ +typedef struct JsonValue JsonValue; + +/** + * These are the types that can be used to identify a JsonValue + * and act on it accordingly. + */ +typedef enum JsonType +{ + JSON_NULL, /* Maps to a C NULL */ + JSON_OBJECT, /* Maps to a HashMap of JsonValues */ + JSON_ARRAY, /* Maps to an Array of JsonValues */ + JSON_STRING, /* Maps to a null-terminated C string */ + JSON_INTEGER, /* Maps to an Int64 */ + JSON_FLOAT, /* Maps to a C double */ + JSON_BOOLEAN /* Maps to a C integer of either 0 or 1 */ +} JsonType; + +/** + * Determine the type of the specified JSON value. + */ +extern JsonType JsonValueType(JsonValue *); + +/** + * Encode a JSON object as a JSON value that can be added to another + * object, or an array. + */ +extern JsonValue * JsonValueObject(HashMap *); + +/** + * Unwrap a JSON value that represents an object. This function will + * return NULL if the value is not actually an object. + */ +extern HashMap * JsonValueAsObject(JsonValue *); + +/** + * Encode a JSON array as a JSON value that can be added to an object + * or another array. + */ +extern JsonValue * JsonValueArray(Array *); + +/** + * Unwrap a JSON value that represents an array. This function will + * return NULL if the value is not actually an array. + */ +extern Array * JsonValueAsArray(JsonValue *); + +/** + * Encode a C string as a JSON value that can be added to an object or + * an array. + */ +extern JsonValue * JsonValueString(char *); + +/** + * Unwrap a JSON value that represents a string. This function will + * return NULL if the value is not actually a string. + */ +extern char * JsonValueAsString(JsonValue *); + +/** + * Encode a number as a JSON value that can be added to an object or + * an array. + */ +extern JsonValue * JsonValueInteger(Int64); + +/** + * Unwrap a JSON value that represents a number. This function will + * return 0 if the value is not actually a number, which may be + * misleading. Check the type of the value before making assumptions + * about its value. + */ +extern Int64 JsonValueAsInteger(JsonValue *); + +/** + * Encode a floating point number as a JSON value that can be added + * to an object or an array. + */ +extern JsonValue * JsonValueFloat(double); + +/** + * Unwrap a JSON value that represents a floating point number. This + * function will return 0 if the value is not actually a floating + * point number, which may be misleading. Check the type of the value + * before making assumptions about its type. + */ +extern double JsonValueAsFloat(JsonValue *); + +/** + * Encode a C integer according to the way C treats integers in boolean + * expressions as a JSON value that can be added to an object or an + * array. + */ +extern JsonValue * JsonValueBoolean(int); + +/** + * Unwrap a JSON value that represents a boolean. This function will + * return 0 if the value is not actually a boolean, which may be + * misleading. Check the type of the value before making assumptions + * about its type. + */ +extern int JsonValueAsBoolean(JsonValue *); + +/** + * This is a special case that represents a JSON null. Because the + * Array and HashMap APIs do not accept NULL values, this function + * should be used to represent NULL in JSON. Even though a small + * amount of memory is allocated just to be a placeholder for nothing, + * this keeps the APIs clean. + */ +extern JsonValue * JsonValueNull(void); + +/** + * Free the memory being used by a JSON value. Note that this will + * recursively free all Arrays, HashMaps, and other JsonValues that are + * reachable from the given value, including any strings attached to + * this value. + */ +extern void JsonValueFree(JsonValue *); + +/** + * Recursively duplicate the given JSON value. This returns a new + * JSON value that is completely identical to the specified value, but + * in no way connected to it. + */ +extern JsonValue * JsonValueDuplicate(JsonValue *); + +/** + * Recursively duplicate the given JSON object. This returns a new + * JSON object that is completely identical to the specified object, + * but in no way connect to it. + */ +extern HashMap * JsonDuplicate(HashMap *); + +/** + * Recursively free a JSON object by iterating over all of its values + * and freeing them using + * .Fn JsonValueFree . + */ +extern void JsonFree(HashMap *); + +/** + * Encode the given string in such a way that it can be safely + * embedded in a JSON stream. This entails: + * .Bl -bullet -offset indent + * .It + * Escaping quotes, backslashes, and other special characters using + * their backslash escape. + * .It + * Encoding bytes that are not UTF-8 using escapes. + * .It + * Wrapping the entire string in double quotes. + * .El + * .Pp + * This function is only provided via the public + * .Nm + * API so that it is accessible to custom JSON encoders, such as the + * CanonicalJson encoder. This will typically be used for encoding + * object keys; to encode values, just use + * .Fn JsonEncodeValue . + * .Pp + * This function returns the number of bytes written to the stream, + * or if the stream is NULL, the number of bytes that would have + * been written. + */ +extern int JsonEncodeString(const char *, Stream *); + +/** + * Serialize a JSON value as it would appear in JSON output. This is + * a recursive function that also encodes all child values reachable + * from the given value. This function is exposed via the public + * .Nm + * API so that it is accessible to custom JSON encoders. Normal users + * that are not writing custom encoders should in most cases just use + * .Fn JsonEncode + * to encode an entire object. + * .Pp + * The third parameter is an integer that represents the indent level + * of the value to be printed, or a negative number if pretty-printing + * should be disabled and JSON should be printed as minimized as + * possible. To pretty-print a JSON object, set this to + * .Va JSON_PRETTY . + * To get minified output, set it to + * .Va JSON_DEFAULT . + * .Pp + * This function returns the number of bytes written to the stream, + * or if the stream is NULL, the number of bytes that would have + * been written. + */ +extern int JsonEncodeValue(JsonValue *, Stream *, int); + +/** + * Encode a JSON object as it would appear in JSON output, writing it + * to the given output stream. This function is recursive; it will + * serialize everything accessible from the passed object. The third + * parameter has the same behavior as described above. + * .Pp + * This function returns the number of bytes written to the stream, + * or if the stream is NULL, the number of bytes that would have + * been written. + */ +extern int JsonEncode(HashMap *, Stream *, int); + +/** + * Decode a JSON object from the given input stream and parse it into + * a hash map of JSON values. + */ +extern HashMap * JsonDecode(Stream *); + +/** + * A convenience function that allows the caller to retrieve and + * arbitrarily deep keys within a JSON object. It takes a root JSON + * object, the number of levels deep to go, and then that number of + * keys as a varargs list. All keys must have objects as values, with + * the exception of the last one, which is the value that will be + * returned. Otherwise, NULL indicates the specified path doas not + * exist. + */ +extern JsonValue * JsonGet(HashMap *, size_t,...); + +/** + * A convenience function that allows the caller to set arbitrarily + * deep keys within a JSON object. It takes a root JSON object, the + * number of levels deep to go, and then that number of keys as a + * varargs list. All keys must have object as values, with the + * exception of the last one, which is the value that will be set. + * The value currently at that key, if any, will be returned. + * This function will create any intermediate objects as necessary to + * set the proper key. + */ +extern JsonValue * JsonSet(HashMap *, JsonValue *, size_t,...); + +#endif /* CYTOPLASM_JSON_H */ diff --git a/src/include/Log.h b/src/include/Log.h new file mode 100644 index 0000000..d0300ca --- /dev/null +++ b/src/include/Log.h @@ -0,0 +1,196 @@ +/* + * Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net> + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef CYTOPLASM_LOG_H +#define CYTOPLASM_LOG_H + +/*** + * @Nm Log + * @Nd A simple logging framework for logging to multiple destinations. + * @Dd April 27 2023 + * @Xr Stream + * + * .Nm + * is a simple C logging library that allows for colorful outputs, + * timestamps, and custom log levels. It also features the ability to + * have multiple logs open at one time, although Cytoplasm primarily + * utilizes the global log. All logs are thread safe. + */ + +#include +#include +#include + +#include + +#define LOG_FLAG_COLOR (1 << 0) +#define LOG_FLAG_SYSLOG (1 << 1) + +/** + * A log is defined as a configuration that describes the properties + * of the log. This opaque structure can be manipulated by the + * functions defined in this API. + */ +typedef struct LogConfig LogConfig; + +/** + * Create a new log configuration with sane defaults that can be used + * immediately with the logging functions. + */ +extern LogConfig * LogConfigCreate(void); + +/** + * Get the global log configuration, creating a new one with + * .Fn LogConfigCreate + * if necessary. + */ +extern LogConfig * LogConfigGlobal(void); + +/** + * Free the given log configuration. Note that this does not close the + * underlying stream associated with the log, if there is one. Also + * note that to avoid memory leaks, the global log configuration must + * also be freed, but it cannot be used after it is freed. + */ +extern void LogConfigFree(LogConfig *); + +/** + * Set the current log level on the specified log configuration. + * This indicates that only messages at or above this level should be + * logged; all others are silently discarded. The passed log level + * should be one of the log levels defined by + * .Xr syslog 3 . + * Refer to that page for a complete list of acceptable log levels, + * and note that passing an invalid log level will result in undefined + * behavior. + */ +extern void LogConfigLevelSet(LogConfig *, int); + +/** + * Cause the log output to be indented two more spaces than it was + * previously. This can be helpful when generating stack traces or + * other hierarchical output. This is a simple convenience wrapper + * around + * .Fn LogConfigIndentSet . + */ +extern void LogConfigIndent(LogConfig *); + +/** + * Cause the log output to be indented two less spaces than it was + * previously. This is a simple convenience wrapper around + * .Fn LogConfigIndentSet . + */ +extern void LogConfigUnindent(LogConfig *); + +/** + * Indent future log output using the specified config by some + * arbitrary amount. + */ +extern void LogConfigIndentSet(LogConfig *, size_t); + +/** + * Set the file stream that logging output should be written to. This + * defaults to standard output, but it can be set to standard error, + * or any other arbitrary stream. Passing a NULL value for the stream + * pointer sets the log output to the standard output. Note that the + * output stream is only used if + * .Va LOG_FLAG_SYSLOG + * is not set. + */ +extern void LogConfigOutputSet(LogConfig *, Stream *); + +/** + * Set a number of boolean options on a log configuration. This + * function uses bitwise operators, so multiple options can be set with + * a single function call using bitwise OR operators. The flags are + * defined as preprocessor macros, and are as follows: + * .Bl -tag -width Ds + * .It LOG_FLAG_COLOR + * When set, enable color-coded output on TTYs. Note that colors are + * implemented as ANSI escape sequences, and are not written to file + * streams that are not actually connected to a TTY, to prevent those + * sequences from being written to a file. + * .Xr isatty 3 + * is checked before writing any terminal sequences. + * .It LOG_FLAG_SYSLOG + * When set, log output to the syslog using + * .Xr syslog 3 , + * instead of logging to the file set by + * .Fn LogConfigOutputSet . + * This flag always overrides the stream set by that function, + * regardless of when it was set, even if it was set after this flag + * was set. + * .El + */ +extern void LogConfigFlagSet(LogConfig *, int); + +/** + * Clear a boolean flag from the specified log format. See above for + * the list of flags. + */ +extern void LogConfigFlagClear(LogConfig *, int); + +/** + * Set a custom timestamp to be prepended to each message if the + * output is not going to the system log. Consult your system's + * documentation for + * .Xr strftime 3 . + * A value of NULL disables the timestamp output before messages. + */ +extern void LogConfigTimeStampFormatSet(LogConfig *, char *); + +/** + * This function does the actual logging of messages using a + * specified configuration. It takes the configuration, the log + * level, a format string, and then a list of arguments, all in that + * order. This function only logs messages if their level is above + * or equal to the currently configured log level, making it easy to + * turn some messages on or off. + * .Pp + * This function has the same usage as + * .Xr vprintf 3 . + * Consult that page for the list of format specifiers and their + * arguments. This function is typically not used directly, see the + * other log functions for the most common use cases. + */ +extern void Logv(LogConfig *, int, const char *, va_list); + +/** + * Log a message using + * .Fn Logv . + * with the specified configuration. This function has the same usage + * as + * .Xr printf 3 . + */ +extern void LogTo(LogConfig *, int, const char *, ...); + +/** + * Log a message to the global log using + * .Fn Logv . + * This function has the same usage as + * .Xr printf 3 . + */ +extern void Log(int, const char *, ...); + +#endif diff --git a/src/include/Memory.h b/src/include/Memory.h new file mode 100644 index 0000000..5d5ecec --- /dev/null +++ b/src/include/Memory.h @@ -0,0 +1,235 @@ +/* + * Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net> + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef CYTOPLASM_MEMORY_H +#define CYTOPLASM_MEMORY_H + +/*** + * @Nm Memory + * @Nd Smart memory management. + * @Dd January 9 2023 + * + * .Nm + * is an API that allows for smart memory management and profiling. It + * wraps the standard library functions + * .Xr malloc 3 , + * .Xr realloc 3 , + * and + * .Xr free 3 , + * and offers identical semantics, while providing functionality that + * the standard library doesn't have, such as getting statistics on the + * total memory allocated on the heap, and getting the size of a block + * given a pointer. Additionally, thanks to preprocessor macros, the + * exact file and line number at which an allocation, re-allocation, or + * free occured can be obtained given a pointer. Finally, all the + * blocks allocated on the heap can be iterated and evaluated, and a + * callback function can be executed every time a memory operation + * occurs. + * .Pp + * In the future, this API could include a garbage collector that + * automatically frees memory it detects as being no longer in use. + * However, this feature does not yet exist. + * .Pp + * A number of macros are available, which make the + * .Nm + * API much easier to use. They are as follows: + * .Bl -bullet -offset indent + * .It + * .Fn Malloc "x" + * .It + * .Fn Realloc "x" "y" + * .It + * .Fn Free "x" + * .El + * .Pp + * These macros expand to + * .Fn MemoryAllocate , + * .Fn MemoryReallocate , + * and + * .Fn MemoryFree + * with the second and third parameters set to __FILE__ and __LINE__. + * This allows + * .Nm + * to be used exactly how the standard library functions would be + * used. In fact, the functions to which these macros expand are not + * intended to be used directly; for the best results, use these + * macros. + */ +#include + +/** + * These values are passed into the memory hook function to indicate + * the action that just happened. + */ +typedef enum MemoryAction +{ + MEMORY_ALLOCATE, + MEMORY_REALLOCATE, + MEMORY_FREE, + MEMORY_BAD_POINTER, + MEMORY_CORRUPTED +} MemoryAction; + +#define Malloc(x) MemoryAllocate(x, __FILE__, __LINE__) +#define Realloc(x, s) MemoryReallocate(x, s, __FILE__, __LINE__) +#define Free(x) MemoryFree(x, __FILE__, __LINE__) + +/** + * The memory information is opaque, but can be accessed using the + * functions defined by this API. + */ +typedef struct MemoryInfo MemoryInfo; + +/** + * Allocate the specified number of bytes on the heap. This function + * has the same semantics as + * .Xr malloc 3 , + * except that it takes the file name and line number at which the + * allocation occurred. + */ +extern void * MemoryAllocate(size_t, const char *, int); + +/** + * Change the size of the object pointed to by the given pointer + * to the given number of bytes. This function has the same semantics + * as + * .Xr realloc 3 , + * except that it takes the file name and line number at which the + * reallocation occurred. + */ +extern void * MemoryReallocate(void *, size_t, const char *, int); + +/** + * Free the memory at the given pointer. This function has the same + * semantics as + * .Xr free 3 , + * except that it takes the file name and line number at which the + * free occurred. + */ +extern void MemoryFree(void *, const char *, int); + +/** + * Get the total number of bytes that the program has allocated on the + * heap. This operation iterates over all heap allocations made with + * .Fn MemoryAllocate + * and then returns a total count, in bytes. + */ +extern size_t MemoryAllocated(void); + +/** + * Iterate over all heap allocations made with + * .Fn MemoryAllocate + * and call + * .Fn MemoryFree + * on them. This function immediately invalidates all pointers to + * blocks on the heap, and any subsequent attempt to read or write to + * data on the heap will result in undefined behavior. This is + * typically called at the end of the program, just before exit. + */ +extern void MemoryFreeAll(void); + +/** + * Fetch information about an allocation. This function takes a raw + * pointer, and if + * . Nm + * knows about the pointer, it returns a structure that can be used + * to obtain information about the block of memory that the pointer + * points to. + */ +extern MemoryInfo * MemoryInfoGet(void *); + +/** + * Get the size in bytes of the block of memory represented by the + * specified memory info structure. + */ +extern size_t MemoryInfoGetSize(MemoryInfo *); + +/** + * Get the file name in which the block of memory represented by the + * specified memory info structure was allocated. + */ +extern const char * MemoryInfoGetFile(MemoryInfo *); + +/** + * Get the line number on which the block of memory represented by the + * specified memory info structure was allocated. + */ +extern int MemoryInfoGetLine(MemoryInfo *); + +/** + * Get a pointer to the block of memory represented by the specified + * memory info structure. + */ +extern void * MemoryInfoGetPointer(MemoryInfo *); + +/** + * This function takes a pointer to a function that takes the memory + * info structure, as well as a void pointer for caller-provided + * arguments. It iterates over all the heap memory currently allocated + * at the time of calling, executing the function on each allocation. + */ +extern void MemoryIterate(void (*) (MemoryInfo *, void *), void *); + +/** + * Specify a function to be executed whenever a memory operation + * occurs. The MemoryAction argument specifies the operation that + * occurred on the block of memory represented by the memory info + * structure. The function also takes a void pointer to caller-provided + * arguments. + */ +extern void MemoryHook(void (*) (MemoryAction, MemoryInfo *, void *), void *); + +/** + * The default memory hook, which has sane behavior and is installed + * at runtime. This function does not use any memory on the heap, + * except for the MemoryInfo passed to it, which it assumes to be + * valid. Everything else happens on the stack only, to ensure that + * the hook doesn't make any memory problems worse. + */ +extern void MemoryDefaultHook(MemoryAction, MemoryInfo *, void *); + +/** + * Read over the block of memory represented by the given memory info + * structure and generate a hexadecimal and ASCII string for each + * chunk of the block. This function takes a callback function that + * takes the following parameters in order: + * .Bl -bullet -offset indent + * .It + * The current offset from the beginning of the block of memory in + * bytes. + * .It + * A null-terminated string containing the next 16 bytes of the block + * encoded as space-separated hex values. + * .It + * A null-terminated string containing the ASCII representation of the + * same 16 bytes of memory. This ASCII representation is safe to print + * to a terminal or other text device, because non-printable characters + * are encoded as a . (period). + * .It + * Caller-passed pointer. + * .El + */ +extern void +MemoryHexDump(MemoryInfo *, void (*) (size_t, char *, char *, void *), void *); + +#endif diff --git a/src/include/Queue.h b/src/include/Queue.h new file mode 100644 index 0000000..35821d3 --- /dev/null +++ b/src/include/Queue.h @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net> + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef CYTOPLASM_QUEUE_H +#define CYTOPLASM_QUEUE_H + +/*** + * @Nm Queue + * @Nd A simple static queue data structure. + * @Dd November 25 2022 + * @Xr Array HashMap + * + * .Nm + * implements a simple queue data structure that is statically sized. + * This implementation does not actually store the values of the items + * in it; it only stores pointers to the data. As such, you will have + * to manually maintain data and make sure it remains valid as long as + * it is in the queue. The advantage of this is that + * .Nm + * doesn't have to copy data, and thus doesn't care how big the data + * is. Furthermore, any arbitrary data can be stored in the queue. + * .Pp + * This queue implementation operates on the heap. It is a circular + * queue, and it does not grow as it is used. Once the size is set, + * the queue never gets any bigger. + */ + +#include + +/** + * These functions operate on a queue structure that is opaque to the + * caller. + */ +typedef struct Queue Queue; + +/** + * Allocate a new queue that is able to store the specified number of + * items in it. + */ +extern Queue * QueueCreate(size_t); + +/** + * Free the memory associated with the specified queue structure. Note + * that this function does not free any of the values stored in the + * queue; it is the caller's job to manage memory for each item. + * Typically, the caller would dequeue all the items in the queue and + * deal with them before freeing the queue itself. + */ +extern void QueueFree(Queue *); + +/** + * Push an element into the queue. This function returns a boolean + * value indicating whether or not the push succeeded. Pushing items + * into the queue will fail if the queue is full. + */ +extern int QueuePush(Queue *, void *); + +/** + * Pop an element out of the queue. This function returns NULL if the + * queue is empty. Otherwise, it returns a pointer to the item that is + * next up in the queue. + */ +extern void * QueuePop(Queue *); + +/** + * Retrieve a pointer to the item that is next up in the queue without + * actually discarding it, such that the next call to + * .Fn QueuePeek + * or + * .Fn QueuePop + * will return the same pointer. + */ +extern void * QueuePeek(Queue *); + +/** + * Determine whether or not the queue is full. + */ +extern int QueueFull(Queue *); + +/** + * Determine whether or not the queue is empty. + */ +extern int QueueEmpty(Queue *); + +#endif diff --git a/src/include/Rand.h b/src/include/Rand.h new file mode 100644 index 0000000..a5be3ce --- /dev/null +++ b/src/include/Rand.h @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net> + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef CYTOPLASM_RAND_H +#define CYTOPLASM_RAND_H + +/*** + * @Nm Rand + * @Nd Thread-safe random numbers. + * @Dd February 16 2023 + * @Xr Util + * + * .Nm + * is used for generating random numbers in a thread-safe way. + * Currently, one generator state is shared across all threads, which + * means that only one thread can generate random numbers at a time. + * This state is protected with a mutex to guarantee this behavior. + * In the future, a seed pool may be maintained to allow multiple + * threads to generate random numbers at the same time. + * .Pp + * The generator state is seeded on the first call to a function that + * needs it. The seed is determined by the current timestamp, the ID + * of the process, and the thread ID. These should all be sufficiently + * random sources, so the seed should be secure enough. + * .Pp + * .Nm + * currently uses a simple Mersenne Twister algorithm to generate + * random numbers. This algorithm was chosen because it is extremely + * popular and widespread. While it is likely not cryptographically + * secure, and does suffer some unfortunate pitfalls, this algorithm + * has stood the test of time and is simple enough to implement, so + * it was chosen over the alternatives. + * .Pp + * .Nm + * does not use any random number generator functions from the C + * standard library, since these are often flawed. + */ + +#include + +/** + * Generate a single random integer between 0 and the passed value. + */ +extern int RandInt(unsigned int); + +/** + * Generate the number of integers specified by the second argument + * storing them into the buffer pointed to in the first argument. + * Ensure that each number is between 0 and the third argument. + * .Pp + * This function allows a caller to get multiple random numbers at once + * in a more efficient manner than repeatedly calling + * .Fn RandInt , + * since each call to these functions + * has to lock and unlock a mutex. It is therefore better to obtain + * multiple random numbers in one pass if multiple are needed. + */ +extern void RandIntN(int *, size_t, unsigned int); + +#endif /* CYTOPLASM_RAND_H */ diff --git a/src/include/Runtime.h b/src/include/Runtime.h new file mode 100644 index 0000000..6e13b6a --- /dev/null +++ b/src/include/Runtime.h @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net> + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef CYTOPLASM_RUNTIME_H +#define CYTOPLASM_RUNTIME_H + +/*** + * @Nm Runtime + * @Nd Supporting functions for the Cytoplasm runtime. + * @Dd May 23 2023 + * @Xr Memory + * + * .Nm + * provides supporting functions for the Cytoplasm runtime. These + * functions are not intended to be called directly by programs, + * but are used internally. They're exposed via a header because + * the runtime stub needs to know their definitions. + */ + +#include + +/** + * Write a memory report to a file in the current directory, using + * the provided program arguments, including the program name that + * executed. This function is to be called after all memory is + * supposed to have been freed. It iterates over all remaining + * memory and generates a text file containing all of the + * recorded information about each block, including a hex dump of + * the data stored in them. + */ +extern void GenerateMemoryReport(int argc, char **argv); + +#endif /* CYTOPLASM_RUNTIME_H */ diff --git a/src/include/Sha.h b/src/include/Sha.h new file mode 100644 index 0000000..25b6247 --- /dev/null +++ b/src/include/Sha.h @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net> + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef CYTOPLASM_SHA_H +#define CYTOPLASM_SHA_H + +/*** + * @Nm Sha + * @Nd A simple implementation of a few SHA hashing functions. + * @Dd December 19 2022 + * @Xr Memory Base64 + * + * This API defines simple functions for computing SHA hashes. + * At the moment, it only defines + * .Fn Sha256 + * and + * .Fn Sha1 , + * which compute the SHA-256 and SHA-1 hashes of the given C string, + * respectively. It is not trivial to implement SHA-512 in ANSI C + * due to the lack of a 64-bit integer type, so that hash + * function has been omitted. + */ + +/** + * This function takes a pointer to a NULL-terminated C string, and + * returns a NULL-terminated byte buffer allocated on the heap using + * the Memory API, or NULL if there was an error allocating memory. + * The returned byte buffer should be freed when it is no longer + * needed. It is important to note that the returned buffer is not + * a printable string; to get a printable string, use + * .Fn ShaToHex . + */ +extern unsigned char * Sha256(char *); + +/** + * This function takes a pointer to a NULL-terminated C string, and + * returns a NULL-terminated byte buffer allocated on the heap using + * the Memory API, or NULL if there was an error allocating memory. + * The returned byte buffer should be freed when it is no longer + * needed. It is important to note that the returned buffer is not + * a printable string; to get a printable string, use + * .Fn ShaToHex . + */ +extern unsigned char * Sha1(char *); + +/** + * Convert a SHA byte buffer into a hex string. These hex strings + * are typically what is transmitted, stored, and compared, however + * there may be times when it is necessary to work with the raw + * bytes directly, which is why the conversion to a hex string is + * a separate step. + */ +extern char * ShaToHex(unsigned char *); + +#endif /* CYTOPLASM_SHA_H */ diff --git a/src/include/Str.h b/src/include/Str.h new file mode 100644 index 0000000..714b8d8 --- /dev/null +++ b/src/include/Str.h @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net> + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef CYTOPLASM_STR_H +#define CYTOPLASM_STR_H + +/*** + * @Nm Str + * @Nd Functions for creating and manipulating strings. + * @Dd February 15 2023 + * @Xr Memory + * + * .Nm + * provides string-related functions. It is called + * .Nm , + * not String, because some platforms (Windows) do not have + * case-sensitive filesystems, which poses a problem since + * .Pa string.h + * is a standard library header. + */ + +#include + +#include + +/** + * Convert UTF-16 into a Unicode codepoint. + */ +extern UInt32 StrUtf16Decode(UInt16, UInt16); + +/** + * Take a Unicode codepoint and encode it into a string buffer containing + * between 1 and 4 bytes. The string buffer is allocated on the heap, + * so it should be freed when it is no longer needed. + */ +extern char * StrUtf8Encode(UInt32); + +/** + * Duplicate a null-terminated string, returning a new string on the + * heap. This is useful when a function takes in a string that it needs + * to store for long amounts of time, even perhaps after the original + * string is gone. + */ +extern char * StrDuplicate(const char *); + +/** + * Extract part of a null-terminated string, returning a new string on + * the heap containing only the requested subsection. Like the + * substring functions included with most programming languages, the + * starting index is inclusive, and the ending index is exclusive. + */ +extern char * StrSubstr(const char *, size_t, size_t); + +/** + * A varargs function that takes a number of null-terminated strings + * specified by the first argument, and returns a new string that + * contains their concatenation. It works similarly to + * .Xr strcat 3 , + * but it takes care of allocating memory big enough to hold all the + * strings. Any string in the list may be NULL. If a NULL pointer is + * passed, it is treated like an empty string. + */ +extern char * StrConcat(size_t,...); + +/** + * Return a boolean value indicating whether or not the null-terminated + * string consists only of blank characters, as determined by + * .Xr isblank 3 . + */ +extern int StrBlank(const char *str); + +/** + * Generate a string of the specified length, containing random + * lowercase and uppercase letters. + */ +extern char * StrRandom(size_t); + +/** + * Convert the specified integer into a string, returning the string + * on the heap, or NULL if there was a memory allocation error. The + * returned string should be freed by the caller after it is no longer + * needed. + */ +extern char * StrInt(long); + +/** + * Converts a string into a lowercase version of it using + * .Xr tolower 3 , + * returning the lowercase version on the heap, or NULL if there was + * a memory allocation error. The returned string should be freed by + * the caller after it is no longer needed. + */ +extern char * StrLower(char *); + +/** + * Compare two strings and determine whether or not they are equal. + * This is the most common use case of strcmp() in Cytoplasm, but + * strcmp() doesn't like NULL pointers, so these have to be checked + * explicitly and can cause problems if they aren't. This function, + * on the other hand, makes NULL pointers special cases. If both + * arguments are NULL, then they are considered equal. If only one + * argument is NULL, they are considered not equal. Otherwise, if + * no arguments are NULL, a regular strcmp() takes place and this + * function returns a boolean value indicating whether or not + * strcmp() returned 0. + */ +extern int StrEquals(const char *, const char *); + +#endif /* CYTOPLASM_STR_H */ diff --git a/src/include/Stream.h b/src/include/Stream.h new file mode 100644 index 0000000..3ddacc6 --- /dev/null +++ b/src/include/Stream.h @@ -0,0 +1,223 @@ +/* + * Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net> + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef CYTOPLASM_STREAM_H +#define CYTOPLASM_STREAM_H + +/*** + * @Nm Stream + * @Nd An abstraction over the Io API that implements standard C I/O. + * @Dd April 29 2023 + * @Xr Io + * + * .Nm + * implements an abstraction layer over the Io API. This layer buffers + * I/O and makes it much easier to work with, mimicking the standard + * C library and offering some more convenience features. + */ + +#include + +#include + +/** + * An opaque structure analogous to C's FILE pointers. + */ +typedef struct Stream Stream; + +/** + * Create a new stream using the specified Io for underlying I/O + * operations. + */ +extern Stream * StreamIo(Io * io); + +/** + * Create a new stream using the specified POSIX file descriptor. + * This is a convenience function for calling + * .Fn IoFd + * and then passing the result into + * .Fn StreamIo . + */ +extern Stream * StreamFd(int); + +/** + * Create a new stream using the specified C FILE pointer. This is a + * convenience function for calling + * .Fn IoFile + * and then passing the result into + * .Fn StreamIo . + */ +extern Stream * StreamFile(FILE *); + +/** + * Create a new stream using the specified path and mode. This is a + * convenience function for calling + * .Xr fopen 3 + * and then passing the result into + * .Fn StreamFile . + */ +extern Stream * StreamOpen(const char *, const char *); + +/** + * Get a stream that writes to the standard output. + */ +extern Stream * StreamStdout(void); + +/** + * Get a stream that writes to the standard error. + */ +extern Stream * StreamStderr(void); + +/** + * Get a stream that reads from the standard input. + */ +extern Stream * StreamStdin(void); + +/** + * Close the stream. This flushes the buffers and closes the underlying + * Io. It is analogous to the standard + * .Xr fclose 3 + * function. + */ +extern int StreamClose(Stream *); + +/** + * Print a formatted string. This function is analogous to the standard + * .Xr vfprintf 3 + * function. + */ +extern int StreamVprintf(Stream *, const char *, va_list); + +/** + * Print a formatted string. This function is analogous to the + * standard + * .Xr fprintf 3 + * function. + */ +extern int StreamPrintf(Stream *, const char *,...); + +/** + * Get a single character from a stream. This function is analogous to + * the standard + * .Xr fgetc 3 + * function. + */ +extern int StreamGetc(Stream *); + +/** + * Push a character back onto the input stream. This function is + * analogous to the standard + * .Xr ungetc 3 + * function. + */ +extern int StreamUngetc(Stream *, int); + +/** + * Write a single character to the stream. This function is analogous + * to the standard + * .Xr fputc 3 + * function. + */ +extern int StreamPutc(Stream *, int); + +/** + * Write a null-terminated string to the stream. This function is + * analogous to the standard + * .Xr fputs 3 + * function. + */ +extern int StreamPuts(Stream *, char *); + +/** + * Read at most the specified number of characters minus 1 from the + * specified stream and store them at the memory located at the + * specified pointer. This function is analogous to the standard + * .Xr fgets 3 + * function. + */ +extern char * StreamGets(Stream *, char *, int); + +/** + * Set the file position indicator for the specified stream. This + * function is analogous to the standard + * .Xr fseeko + * function. + */ +extern off_t StreamSeek(Stream *, off_t, int); + +/** + * Test the end-of-file indicator for the given stream, returning a + * boolean value indicating whether or not it is set. This is analogous + * to the standard + * .Xr feof 3 + * function. + */ +extern int StreamEof(Stream *); + +/** + * Test the stream for an error condition, returning a boolean value + * indicating whether or not one is present. This is analogous to the + * standard + * .Xr ferror 3 + * function. + */ +extern int StreamError(Stream *); + +/** + * Clear the error condition associated with the given stream, allowing + * future reads or writes to potentially be successful. This functio + * is analogous to the standard + * .Xr clearerr 3 + * function. + */ +extern void StreamClearError(Stream *); + +/** + * Flush all buffered data using the streams underlying write function. + * This function is analogous to the standard + * .Xr fflush 3 + * function. + */ +extern int StreamFlush(Stream *); + +/** + * Read all the bytes from the first stream and write them to the + * second stream. This is analogous to + * .Fn IoCopy , + * but it uses the internal buffers of the streams. It is probably + * less efficient than doing a + * .Fn IoCopy + * instead, but it is more convenient. + */ +extern ssize_t StreamCopy(Stream *, Stream *); + +/** + * Get the file descriptor associated with the given stream, or -1 if + * the stream is not associated with any file descriptor. This function + * is analogous to the standard + * .Xr fileno 3 + * function. + */ +extern int StreamFileno(Stream *); + +#endif /* CYTOPLASM_STREAM_H */ diff --git a/src/include/Tls.h b/src/include/Tls.h new file mode 100644 index 0000000..4407ed0 --- /dev/null +++ b/src/include/Tls.h @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net> + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef CYTOPLASM_TLS_H +#define CYTOPLASM_TLS_H + +/*** + * @Nm Tls + * @Nd Interface to platform-dependent TLS libraries. + * @Dd April 29 2023 + * @Xr Stream Io + * + * .Nm + * provides an interface to platform-dependent TLS libraries. It allows + * Cytoplasm to support any TLS library with no changes to existing + * code. Support for additional TLS libraries is added by creating a + * new compilation unit that implements all the functions here, with + * the exception of a few, which are noted. + * .Pp + * Currently, Cytoplasm has support for the following TLS libraries: + * .Bl -bullet -offset indent + * .It + * LibreSSL + * .It + * OpenSSL + * .El + */ + +#include + +#define TLS_LIBRESSL 2 +#define TLS_OPENSSL 3 + +/** + * Create a new TLS client stream using the given file descriptor and + * the given server hostname. The hostname should be used to verify + * that the server actually is who it says it is. + * .Pp + * This function does not need to be implemented by the individual + * TLS support stubs. + */ +extern Stream * TlsClientStream(int, const char *); + +/** + * Create a new TLS server stream using the given certificate and key + * file, in the format natively supported by the TLS library. + * .Pp + * This function does not need to be implemented by the individual + * TLS support stubs. + */ +extern Stream * TlsServerStream(int, const char *, const char *); + +/** + * Initialize a cookie that stores information about the given client + * connection. This cookie will be passed into the other functions + * defined by this API. + */ +extern void * TlsInitClient(int, const char *); + +/** + * Initialize a cookie that stores information about the given + * server connection. This cookie will be passed into the other + * functions defined by this API. + */ +extern void * TlsInitServer(int, const char *, const char *); + +/** + * Read from a TLS stream, decrypting it and storing the result in the + * specified buffer. This function takes the cookie, buffer, and + * number of decrypted bytes to read into it. See the documentation for + * .Fn IoRead . + */ +extern ssize_t TlsRead(void *, void *, size_t); + +/** + * Write to a TLS stream, encrypting the buffer. This function takes + * the cookie, buffer, and number of unencrypted bytes to write to + * the stream. See the documentation for + * .Fn IoWrite . + */ +extern ssize_t TlsWrite(void *, void *, size_t); + +/** + * Close the TLS stream, also freeing all memory associated with the + * cookie. + */ +extern int TlsClose(void *); + +#endif /* CYTOPLASM_TLS_H */ diff --git a/src/include/UInt64.h b/src/include/UInt64.h new file mode 100644 index 0000000..51cc30c --- /dev/null +++ b/src/include/UInt64.h @@ -0,0 +1,252 @@ +/* + * Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net> + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef CYTOPLASM_UINT64_H +#define CYTOPLASM_UINT64_H + +/*** + * @Nm UInt64 + * @Nd Fixed-width 64 bit integers. + * @Dd August 11, 2023 + * + * .Pp + * ANSI C89 (or C99 for that matter) provides no required mechanism + * for 64 bit integers. Nevertheless, many compilers provide them as + * extensions. However, since it is not a gaurantee, and to be fully + * standards-compliant and thus portable, a platform-agnostic interface + * is required. This header provides such an interface. If the platform + * has a 64 bit integer type, that is used, and native operations are + * performed by C preprocessor macro expansion. Otherwise, a + * compatibility layer is provided, which implements 64-bit + * arithmetic on an array of 2 32-bit numbers which are provided by + * .Xr Int 3 . + * .Pp + * Note that 64-bit emulation is certainly not as performant as using + * native 64-bit operations, so whenever possible, the native + * operations should be preferred. However, since C provides no required + * 64 bit integer on 32-bit and less platforms, this API can be used as + * a "good enough" fallback mechanism. + * .Pp + * Also note that this implementation, both in the native and + * non-native forms, makes some assumptions: + * .Bl -bullet -width Ds + * .It + * When a cast from a larger integer to a smaller integer is performed, + * the upper bits are truncated, not the lower bits. + * .It + * Negative numbers are represented in memory and in registers in two's + * compliment form. + * .El + * .Pp + * This API may provide unexpected output if these assumptions are + * false for a given platform. + * + * @ignore-typedefs + */ + +#include + +#include + +#ifndef INT64_FORCE_EMULATED + +#define BIT64_MAX 18446744073709551615UL + +#if UINT_MAX == BIT64_MAX +/* typedef signed int Int64; */ +typedef unsigned int UInt64; + +#define UINT64_NATIVE + +#elif ULONG_MAX == BIT64_MAX +/* typedef signed int Int64; */ +typedef unsigned long UInt64; + +#define UINT64_NATIVE + +#endif + +#endif /* ifndef INT64_FORCE_EMULATED */ + +#ifdef UINT64_NATIVE + +#define UInt64Create(high, low) (((UInt64) (high) << 32) | (low)) +#define UInt64Low(a) ((UInt32) ((a) & 0x00000000FFFFFFFF)) +#define UInt64High(a) ((UInt32) ((a) >> 32)) + +#define UInt64Add(a, b) ((a) + (b)) +#define UInt64Sub(a, b) ((a) - (b)) +#define UInt64Mul(a, b) ((a) * (b)) +#define UInt64Div(a, b) ((a) / (b)) +#define UInt64Rem(a, b) ((a) % (b)) + +#define UInt64Sll(a, b) ((a) << (b)) +#define UInt64Srl(a, b) ((a) >> (b)) + +#define UInt64And(a, b) ((a) & (b)) +#define UInt64Or(a, b) ((a) | (b)) +#define UInt64Xor(a, b) ((a) ^ (b)) +#define UInt64Not(a) (~(a)) + +#define UInt64Eq(a, b) ((a) == (b)) +#define UInt64Lt(a, b) ((a) < (b)) +#define UInt64Gt(a, b) ((a) > (b)) + +#define UInt64Neq(a, b) ((a) != (b)) +#define UInt64Leq(a, b) ((a) <= (b)) +#define UInt64Geq(a, b) ((a) >= (b)) + +#else + +/** + * For platforms that do not have a native integer large enough to + * store a 64 bit integer, this struct is used. i[0] contains the low + * bits of integer, and i[1] contains the high bits of the integer. + * .Pp + * This struct should not be accessed directly, because UInt64 may not + * actually be this struct, it might be an actual integer type. For + * maximum portability, only use the functions defined here to + * manipulate 64 bit integers. + */ +typedef struct +{ + UInt32 i[2]; +} UInt64; + +/** + * Create a new unsigned 64 bit integer using the given high and low + * bits. + */ +extern UInt64 UInt64Create(UInt32, UInt32); + +/** + * Add two unsigned 64 bit integers together. + */ +extern UInt64 UInt64Add(UInt64, UInt64); + +/** + * Subtract the second 64 bit integer from the first. + */ +extern UInt64 UInt64Sub(UInt64, UInt64); + +/** + * Multiply two 64 bit integers together. The non-native version of + * this function uses the Russian Peasant method of multiplication, + * which should afford more performance than a naive multiplication by + * addition, but it is still rather slow and depends on the size of + * the integers being multiplied. + */ +extern UInt64 UInt64Mul(UInt64, UInt64); + +/** + * Divide the first 64 bit integer by the second and return the + * quotient. The non-native version of this function uses naive binary + * long division, which is slow, but gauranteed to finish in constant + * time. + */ +extern UInt64 UInt64Div(UInt64, UInt64); + +/** + * Divide the first 64 bit integer by the second and return the + * remainder. The non-native version of this function uses naive binary + * long division, which is slow, but gauranteed to finish in constant + * time. + */ +extern UInt64 UInt64Rem(UInt64, UInt64); + +/** + * Perform a left logical bit shift of a 64 bit integer. The second + * parameter is how many places to shift, and is declared as a regular + * integer because anything more than 64 does not make sense. + */ +extern UInt64 UInt64Sll(UInt64, int); + +/** + * Perform a right logical bit shift of a 64 bit integer. The second + * parameter is how many places to shift, and is declared as a regular + * integer because anything more than 64 does not make sense. + */ +extern UInt64 UInt64Srl(UInt64, int); + +/** + * Perform a bitwise AND (&) of the provided 64 bit integers. + */ +extern UInt64 UInt64And(UInt64, UInt64); + +/** + * Perform a bitwise OR (|) of the provided 64 bit integers. + */ +extern UInt64 UInt64Or(UInt64, UInt64); + +/** + * Perform a bitwise XOR (^) of the provided 64 bit integers. + */ +extern UInt64 UInt64Xor(UInt64, UInt64); + +/** + * Perform a bitwise NOT (~) of the provided 64 bit integer. + */ +extern UInt64 UInt64Not(UInt64); + +/** + * Perform a comparison of the provided 64 bit integers and return a C + * boolean that is true if and only if they are equal. + */ +extern int UInt64Eq(UInt64, UInt64); + +/** + * Perform a comparison of the provided 64 bit integers and return a C + * boolean that is true if and only if the second operand is strictly + * less than the first. + */ +extern int UInt64Lt(UInt64, UInt64); + +/** + * Perform a comparison of the provided 64 bit integers and return a C + * boolean that is true if and only if the second operand is strictly + * greater than the first. + */ +extern int UInt64Gt(UInt64, UInt64); + +#define UInt64Low(a) ((a).i[0]) +#define UInt64High(a) ((a).i[1]) + +#define UInt64Neq(a, b) (!UInt64Eq(a, b)) +#define UInt64Leq(a, b) (UInt64Eq(a, b) || UInt64Lt(a, b)) +#define UInt64Geq(a, b) (UInt64Eq(a, b) || UInt64Gt(a, b)) + +#endif + +#define UINT64_STRBUF 65 /* Base 2 representation with '\0' */ + +/** + * Convert a 64 bit integer to a string in an arbitrary base + * representation specified by the second parameter, using the provided + * buffer and length specified by the third and fourth parameters. To + * guarantee that the string will fit in the buffer, allocate it of + * size UINT64_STRBUF or larger. Note that a buffer size smaller than + * UINT64_STRBUF will invoke undefined behavior. + */ +extern size_t UInt64Str(UInt64, int, char *, size_t); + +#endif /* CYTOPLASM_UINT64_H */ diff --git a/src/include/Uri.h b/src/include/Uri.h new file mode 100644 index 0000000..a47bf28 --- /dev/null +++ b/src/include/Uri.h @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net> + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef CYTOPLASM_URI_H +#define CYTOPLASM_URI_H + +/*** + * @Nm Uri + * @Nd Parse a URI. Typically used to parse HTTP(s) URLs. + * @Dd April 29 2023 + * @Xr Http + * + * .Nm + * provides a simple mechanism for parsing URIs. This is an extremely + * basic parser that (ab)uses + * .Xr sscanf 3 + * to parse URIs, so it may not be the most reliable, but it should + * work in most cases and on reasonable URIs that aren't too long, as + * the _MAX definitions are modest. + */ + +#define URI_PROTO_MAX 8 +#define URI_HOST_MAX 128 +#define URI_PATH_MAX 256 + +/** + * The parsed URI is stored in this structure. + */ +typedef struct Uri +{ + char proto[URI_PROTO_MAX]; + char host[URI_HOST_MAX]; + char path[URI_PATH_MAX]; + unsigned short port; +} Uri; + +/** + * Parse a URI string into the Uri structure as described above, or + * return NULL if there was a parsing error. + */ +extern Uri * UriParse(const char *); + +/** + * Free the memory associated with a Uri structure returned by + * .Fn UriParse . + */ +extern void UriFree(Uri *); + +#endif /* CYTOPLASM_URI_H */ diff --git a/src/include/Util.h b/src/include/Util.h new file mode 100644 index 0000000..d6c3830 --- /dev/null +++ b/src/include/Util.h @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net> + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef CYTOPLASM_UTIL_H +#define CYTOPLASM_UTIL_H + +/*** + * @Nm Util + * @Nd Some misc. helper functions that don't need their own headers. + * @Dd February 15 2023 + * + * This header holds a number of random functions related to strings, + * time, the filesystem, and other simple tasks that don't require a + * full separate API. For the most part, the functions here are + * entirely standalone, depending only on POSIX functions, however + * there are a few that depend explicitly on a few other APIs. Those + * are noted. + */ + +#include +#include +#include + +#include +#include + +/** + * Get the current timestamp in milliseconds since the Unix epoch. This + * uses + * .Xr gettimeofday 2 + * and time_t, and converts it to a single number, which is then + * returned to the caller. + * .Pp + * A note on the 2038 problem: as long as sizeof(time_t) >= 8, that is, + * as long as the time_t type is 64 bits or more, then everything + * should be fine. On most, if not, all, 64-bit systems, time_t is 64 + * bits. time_t is promoted to a 64-bit integer before it is converted + * to milliseconds, so there is no risk of overflue due to the + * multiplication by 1000. However, if time_t is only 32 bits, it will + * overflow before it even gets to this function, which will cause this + * function to produce unexpected results. + */ +extern UInt64 UtilServerTs(void); + +/** + * Use + * .Xr stat 2 + * to get the last modified time of the given file, or zero if there + * was an error getting the last modified time of a file. This is + * primarily useful for caching file data. + */ +extern UInt64 UtilLastModified(char *); + +/** + * This function behaves just like the system call + * .Xr mkdir 2 , + * but it creates any intermediate directories as necessary, unlike + * .Xr mkdir 2 . + */ +extern int UtilMkdir(const char *, const mode_t); + +/** + * Sleep the calling thread for the given number of milliseconds. + * POSIX does not have a very friendly way to sleep, so this wraps + * .Xr nanosleep 2 + * to make its usage much, much simpler. + */ +extern int UtilSleepMillis(UInt64); + +/** + * This function works identically to the POSIX + * .Xr getdelim 3 , + * except that it assumes pointers were allocated with the Memory API + * and it reads from a Stream instead of a file pointer. + */ +extern ssize_t UtilGetDelim(char **, size_t *, int, Stream *); + +/** + * This function is just a special case of + * .Fn UtilGetDelim + * that sets the delimiter to the newline character. + */ +extern ssize_t UtilGetLine(char **, size_t *, Stream *); + +/** + * Get a unique number associated with the current thread. + * Numbers are assigned in the order that threads call this + * function, but are guaranteed to be unique in identifying + * the thread in a more human-readable way than just casting + * the return value of + * .Fn pthread_self + * to a number. + */ +extern UInt32 UtilThreadNo(void); + +#endif /* CYTOPLASM_UTIL_H */ diff --git a/tools/int64.c b/tools/int64.c new file mode 100644 index 0000000..fb9c5cf --- /dev/null +++ b/tools/int64.c @@ -0,0 +1,145 @@ +#include + +#include + +/* AssertEquals(actual, expected) */ +int +AssertEquals(char *msg, Int64 x, Int64 y) +{ + if (!Int64Eq(x, y)) + { + Log(LOG_ERR, "%s: Expected 0x%X 0x%X, got 0x%X 0x%X", msg, + Int64High(y), Int64Low(y), + Int64High(x), Int64Low(x)); + + return 0; + } + + return 1; +} + +int +Main(void) +{ + Int64 x, y; + + Log(LOG_INFO, "sizeof(Int64) = %lu", sizeof(Int64)); + +#ifdef INT64_NATIVE + Log(LOG_INFO, "Using native 64-bit integers."); +#else + Log(LOG_INFO, "Using emulated 64-bit integers."); +#endif + + /* BSR Tests */ + + x = Int64Create(0x000000FF, 0x00000000); + + y = Int64Sra(x, 4); + AssertEquals("x >> 4", y, Int64Create(0x0000000F, 0xF0000000)); + + y = Int64Sra(x, 8); + AssertEquals("x >> 8", y, Int64Create(0x00000000, 0xFF000000)); + + y = Int64Sra(x, 36); + AssertEquals("x >> 36", y, Int64Create(0x00000000, 0x0000000F)); + + x = Int64Create(0xFF000000, 0x00000000); + + y = Int64Sra(x, 4); + AssertEquals("x >> 4", y, Int64Create(0xFFF00000, 0x00000000)); + + y = Int64Sra(x, 8); + AssertEquals("x >> 8", y, Int64Create(0xFFFF0000, 0x00000000)); + + y = Int64Sra(x, 63); + AssertEquals("x >> 63", y, Int64Create(0xFFFFFFFF, 0xFFFFFFFF)); + + /* BSL Tests */ + + x = Int64Create(0x00000000, 0xFF000000); + + y = Int64Sll(x, 4); + AssertEquals("x << 4", y, Int64Create(0x0000000F, 0xF0000000)); + + y = Int64Sll(x, 8); + AssertEquals("x << 8", y, Int64Create(0x000000FF, 0x00000000)); + + y = Int64Sll(x, 36); + AssertEquals("x << 36", y, Int64Create(0xF0000000, 0x00000000)); + + /* ADD Tests */ + + x = Int64Create(0x00000000, 0xF0000001); + y = Int64Create(0x00000000, 0x00000002); + AssertEquals("0xF0000001 + 0x00000002", Int64Add(x, y), Int64Create(0x00000000, 0xF0000003)); + + x = Int64Create(0x00000000, 0xF0000000); + y = Int64Create(0x00000000, 0x10000000); + AssertEquals("0xF0000000 + 0x10000000", Int64Add(x, y), Int64Create(0x00000001, 0x00000000)); + + x = Int64Create(0, 5); + y = Int64Neg(Int64Create(0, 10)); + AssertEquals("5 + (-10)", Int64Add(x, y), Int64Neg(Int64Create(0, 5))); + + /* SUB Tests */ + x = Int64Create(0x00000000, 0x00000005); + y = Int64Create(0x00000000, 0x00000002); + AssertEquals("0x00000005 - 0x00000002", Int64Sub(x, y), Int64Create(0x00000000, 0x00000003)); + + x = Int64Create(0x00000001, 0x00000000); + y = Int64Create(0x00000000, 0x00000001); + AssertEquals("0x00000001 0x00000000 - 0x00000001", Int64Sub(x, y), Int64Create(0x00000000, 0xFFFFFFFF)); + + x = Int64Create(0, 5); + y = Int64Create(0, 10); + AssertEquals("5 - 10", Int64Sub(x, y), Int64Neg(Int64Create(0, 5))); + + x = Int64Create(0, 5); + y = Int64Neg(Int64Create(0, 10)); + AssertEquals("5 - (-10)", Int64Sub(x, y), Int64Create(0, 15)); + + /* MUL Tests */ + x = Int64Create(0, 18); + y = Int64Create(0, 1); + AssertEquals("18 * 1", Int64Mul(x, y), Int64Create(0, 18)); + + x = Int64Create(0, 20); + y = Int64Create(0, 12); + AssertEquals("20 * 12", Int64Mul(x, y), Int64Create(0, 240)); + + x = Int64Create(0x00000000, 0x00000005); + y = Int64Create(0x00000000, 0x00000005); + AssertEquals("0x00000005 * 0x00000005", Int64Mul(x, y), Int64Create(0x00000000, 0x00000019)); + + x = Int64Create(0x00000001, 0x00000000); + y = Int64Create(0x00000000, 0x00000005); + AssertEquals("0x00000001 0x00000000 * 0x00000005", Int64Mul(x, y), Int64Create(0x00000005, 0x00000000)); + + /* DIV Tests */ + x = Int64Create(0, 12); + y = Int64Create(0, 4); + AssertEquals("12 / 4", Int64Div(x, y), Int64Create(0, 3)); + + /* MOD Tests */ + x = Int64Create(0x000000FF, 0x00000000); + y = Int64Create(0x00000000, 0x00000010); + AssertEquals("0x000000FF 0x00000000 mod 0x00000010", Int64Rem(x, y), Int64Create(0, 0)); + + x = Int64Create(0x00000000, 0xFF000000); + y = Int64Create(0x00000000, 0x00000010); + AssertEquals("0x00000000 0xFF000000 mod 0x00000010", Int64Rem(x, y), Int64Create(0, 0)); + + x = Int64Create(0xFF000000, 0x00000000); + y = Int64Create(0x00000000, 0x00000010); + AssertEquals("0xFF000000 0x00000000 mod 0x00000010", Int64Rem(x, y), Int64Create(0, 0)); + + x = Int64Create(0x00000000, 0x000000F0); + y = Int64Create(0x00000000, 0x00000010); + AssertEquals("0x00000000 0x000000F0 mod 0x00000010", Int64Rem(x, y), Int64Create(0, 0)); + + /* TODO: Add more tests for negative multiplication, division, and + * mod */ + + return 0; +} diff --git a/tools/uint64.c b/tools/uint64.c new file mode 100644 index 0000000..8e9f5f5 --- /dev/null +++ b/tools/uint64.c @@ -0,0 +1,119 @@ +#include + +#include + +/* AssertEquals(actual, expected) */ +int +AssertEquals(char *msg, UInt64 x, UInt64 y) +{ + if (!UInt64Eq(x, y)) + { + Log(LOG_ERR, "%s: Expected 0x%X 0x%X, got 0x%X 0x%X", msg, + UInt64High(y), UInt64Low(y), + UInt64High(x), UInt64Low(x)); + + return 0; + } + + return 1; +} + +int +Main(void) +{ + UInt64 x, y; + + Log(LOG_INFO, "sizeof(UInt64) = %lu", sizeof(UInt64)); + +#ifdef UINT64_NATIVE + Log(LOG_INFO, "Using native 64-bit integers."); +#else + Log(LOG_INFO, "Using emulated 64-bit integers."); +#endif + + /* BSR Tests */ + + x = UInt64Create(0x000000FF, 0x00000000); + + y = UInt64Srl(x, 4); + AssertEquals("x >> 4", y, UInt64Create(0x0000000F, 0xF0000000)); + + y = UInt64Srl(x, 8); + AssertEquals("x >> 8", y, UInt64Create(0x00000000, 0xFF000000)); + + y = UInt64Srl(x, 36); + AssertEquals("x >> 36", y, UInt64Create(0x00000000, 0x0000000F)); + + /* BSL Tests */ + + x = UInt64Create(0x00000000, 0xFF000000); + + y = UInt64Sll(x, 4); + AssertEquals("x << 4", y, UInt64Create(0x0000000F, 0xF0000000)); + + y = UInt64Sll(x, 8); + AssertEquals("x << 8", y, UInt64Create(0x000000FF, 0x00000000)); + + y = UInt64Sll(x, 36); + AssertEquals("x << 36", y, UInt64Create(0xF0000000, 0x00000000)); + + /* ADD Tests */ + + x = UInt64Create(0x00000000, 0xF0000001); + y = UInt64Create(0x00000000, 0x00000002); + AssertEquals("0xF0000001 + 0x00000002", UInt64Add(x, y), UInt64Create(0x00000000, 0xF0000003)); + + x = UInt64Create(0x00000000, 0xF0000000); + y = UInt64Create(0x00000000, 0x10000000); + AssertEquals("0xF0000000 + 0x10000000", UInt64Add(x, y), UInt64Create(0x00000001, 0x00000000)); + + /* SUB Tests */ + x = UInt64Create(0x00000000, 0x00000005); + y = UInt64Create(0x00000000, 0x00000002); + AssertEquals("0x00000005 - 0x00000002", UInt64Sub(x, y), UInt64Create(0x00000000, 0x00000003)); + + x = UInt64Create(0x00000001, 0x00000000); + y = UInt64Create(0x00000000, 0x00000001); + AssertEquals("0x00000001 0x00000000 - 0x00000001", UInt64Sub(x, y), UInt64Create(0x00000000, 0xFFFFFFFF)); + + /* MUL Tests */ + x = UInt64Create(0, 18); + y = UInt64Create(0, 1); + AssertEquals("18 * 1", UInt64Mul(x, y), UInt64Create(0, 18)); + + x = UInt64Create(0, 20); + y = UInt64Create(0, 12); + AssertEquals("20 * 12", UInt64Mul(x, y), UInt64Create(0, 240)); + + x = UInt64Create(0x00000000, 0x00000005); + y = UInt64Create(0x00000000, 0x00000005); + AssertEquals("0x00000005 * 0x00000005", UInt64Mul(x, y), UInt64Create(0x00000000, 0x00000019)); + + x = UInt64Create(0x00000001, 0x00000000); + y = UInt64Create(0x00000000, 0x00000005); + AssertEquals("0x00000001 0x00000000 * 0x00000005", UInt64Mul(x, y), UInt64Create(0x00000005, 0x00000000)); + + /* DIV Tests */ + x = UInt64Create(0, 12); + y = UInt64Create(0, 4); + AssertEquals("12 / 4", UInt64Div(x, y), UInt64Create(0, 3)); + + /* MOD Tests */ + x = UInt64Create(0x000000FF, 0x00000000); + y = UInt64Create(0x00000000, 0x00000010); + AssertEquals("0x000000FF 0x00000000 mod 0x00000010", UInt64Rem(x, y), UInt64Create(0, 0)); + + x = UInt64Create(0x00000000, 0xFF000000); + y = UInt64Create(0x00000000, 0x00000010); + AssertEquals("0x00000000 0xFF000000 mod 0x00000010", UInt64Rem(x, y), UInt64Create(0, 0)); + + x = UInt64Create(0xFF000000, 0x00000000); + y = UInt64Create(0x00000000, 0x00000010); + AssertEquals("0xFF000000 0x00000000 mod 0x00000010", UInt64Rem(x, y), UInt64Create(0, 0)); + + x = UInt64Create(0x00000000, 0x000000F0); + y = UInt64Create(0x00000000, 0x00000010); + AssertEquals("0x00000000 0x000000F0 mod 0x00000010", UInt64Rem(x, y), UInt64Create(0, 0)); + + return 0; +}