Commit Diff


commit - 1a06caead69676af14aec4f145173cd819caf17d
commit + ec1b43354cef997f168f889d6830105e4ffcc292
blob - 82a7d72d0085fded034b6622d51eb0a1b5456546
blob + 5929800cc2f0c3b446496b3b877d8905ea0c6d2b
--- .gitignore
+++ .gitignore
@@ -10,3 +10,4 @@ testrunner
 test_*
 .idea
 netmanual
+CLAUDE.md
blob - c26da16ce4292aed9068c75fc09281101633362a
blob + 1c5b6b7488489a9b65510916e0c9bed3cf3a231b
--- CMakeLists.txt
+++ CMakeLists.txt
@@ -57,6 +57,8 @@ if(${CMAKE_PROJECT_NAME} STREQUAL flint)
         crypto
         parsing
         memory
+        utility
+        input
     )
 
     foreach(t ${TESTS})
blob - 5141822302a27a762d2adb8e63070a4c8f59207a
blob + 990a6b4b4f40c572c5304d2b7b4bf11c32065a5e
--- include/lfbinarytree.h
+++ include/lfbinarytree.h
@@ -1,6 +1,8 @@
 #ifndef LIBFLINT_BINARY_TREE_H
 #define LIBFLINT_BINARY_TREE_H
 
+#include <stddef.h>
+
 typedef struct BinTreeNode {
     void *data;
     struct BinTreeNode *left;
@@ -10,13 +12,17 @@ typedef struct BinTreeNode {
 typedef struct {
     int size;
 
-    int (*compare)(const void *a, const void *b);
-
     void (*destroy)(void *data);
 
     struct BinTreeNode *root;
 } BinTree;
 
+enum {
+    BINTREE_PREORDER,
+    BINTREE_INORDER,
+    BINTREE_POSTORDER
+};
+
 void bintree_init(BinTree *tree, void (*destroy)(void *data));
 
 void bintree_destroy(BinTree *tree);
@@ -29,10 +35,14 @@ void bintree_rem_left(BinTree *tree, BinTreeNode *node
 
 void bintree_rem_right(BinTree *tree, BinTreeNode *node);
 
-//int bintree_merge(BinTree *merge, BinTree *left, BinTree *right, void *data);
+int bintree_merge(BinTree *merge, BinTree *left, BinTree *right, void *data);
 
-void bintree_debug_print(BinTree *tree);
+void bintree_traverse(BinTree *tree, BinTreeNode *node, int order,
+                      void (*visitor)(void *data));
 
+void bintree_debug_print(BinTree *tree,
+                         void (*print_fn)(void *data, char *buf, size_t buf_sz));
+
 #define bintree_is_eob(node) ((node) == NULL)
 #define bintree_is_leaf(node) ((node)->left == NULL && (node)->right == NULL)
 
blob - 116107cf7edb41ba8c57145587e606f5ac7cd369
blob + 95b459a7133a40b484189cf4ccb3af097805d71c
--- include/lfcrypto.h
+++ include/lfcrypto.h
@@ -13,7 +13,7 @@ char *hex_to_str(const unsigned char *hex, size_t sz);
 unsigned char* repeating_key_xor(const unsigned char* s, size_t s_sz, const unsigned char* key, size_t k_sz);
 unsigned char *repeating_key_xor_s(const char* s, const char* key);
 
-unsigned int hamming_distance_s(const char *a, const char *b);
-unsigned int hamming_distance(unsigned char *a, unsigned char *b, size_t sz);
+int hamming_distance_s(const char *a, const char *b);
+int hamming_distance(const unsigned char *a, const unsigned char *b, size_t sz);
 
 #endif // LIBFLINT_CRYPTO_H
blob - 3e5bc239140cb52650fb5c582e14275216e684a3
blob + 3fd88e9c777b1603f9e8a9b252d2662d1b3c38c7
--- include/lfinput.h
+++ include/lfinput.h
@@ -3,22 +3,22 @@
 
 #include <stdlib.h>
 
-unsigned char *get_binary(const char *, size_t *fsz);
+unsigned char *inp_get_binary(const char *, size_t *fsz);
 
-char *get_input(const char *);
+char *inp_get_input(const char *);
 
-char **split(char *, size_t *, const char *);
+char **inp_split(char *, size_t *, const char *);
 
-char **get_lines(const char *, size_t *);
+char **inp_get_lines(const char *, size_t *);
 
-int *get_ints(const char *, size_t *);
+int *inp_get_ints(const char *, size_t *);
 
-void del_split(char **);
+void inp_del_split(char **);
 
-void del_lines(char **);
+void inp_del_lines(char **);
 
 #define DEFAULT_CAPTURE_SYSTEM_BUFSIZE 1024
 
-const char *capture_system(const char *cmd, int buf_sz);
+char *inp_capture_system(const char *cmd, size_t buf_sz);
 
 #endif // LIBFLINT_INPUT_H
blob - 6c63e57c408b7f62baa2bed580e9fa6b81c19e00
blob + a7e55bd3ed794ace7ba3394309b78fc3c49e9f93
--- include/lflinkedlist.h
+++ include/lflinkedlist.h
@@ -20,7 +20,8 @@ typedef struct {
     struct ListNode *tail;
 } List;
 
-void ll_init(List *list, void (*destroy)(void *data));
+void ll_init(List *list, void (*destroy)(void *data),
+             int (*match)(const void *a, const void *b));
 
 void ll_destroy(List *list);
 
@@ -34,13 +35,13 @@ int ll_remove_next(List *list, ListNode *node, void **
 
 int ll_remove_prev(List *list, ListNode *node, void **data);
 
-ListNode *ll_find(List *list, const void *data);
+ListNode *ll_find(const List *list, const void *data);
 
 void ll_reverse(List *list);
 
 void ll_sort(List *list, int (*cmp)(const void *a, const void *b));
 
-void **ll_to_array(List *list);
+void **ll_to_array(const List *list);
 
 /* Provides ListNode *node for the iteration loop */
 #define LL_ITER(list) for(ListNode *node = (list)->head; node != NULL; node = node->next)
blob - 4dde872bd863159c446a310946045764595ac6c6
blob + 77e756ed98a238fd981a346aae8fcb8baa780a4a
--- include/lfmacos.h
+++ include/lfmacos.h
@@ -3,6 +3,7 @@
 
 #if defined(__APPLE__) || defined(__MACH__)
 
+#include <stdint.h>
 #include <time.h>
 
 typedef struct {
@@ -19,6 +20,7 @@ typedef struct {
 } ProcessData;
 
 ProcessData *new_ProcessData();
+void destroy_ProcessData(ProcessData *pd);
 int update_process(pid_t pid, ProcessData *proc);
 void *reallocarray(void *optr, size_t nmemb, size_t size);
 
blob - 5267d993e8cabdd120bb33ff40697062085caf05
blob + 498c4fb3364edcf2c35928df63fa8685c10d13a0
--- include/lfmath.h
+++ include/lfmath.h
@@ -5,6 +5,8 @@
 
 #include "lfutility.h"
 
+int abs_int(int a);
+
 int max_int(int a, int b);
 
 int min_int(int a, int b);
@@ -13,8 +15,6 @@ int clamp_int(int i, int low, int high);
 
 int binstr_to_int(const char *s);
 
-int abs_int(int i);
-
 int is_power_of_two(int i);
 
 int gcd(int a, int b);
blob - a1a15aebc088f0eccd463e74c523616e8a1a64d6
blob + 574d29a129a4809bbdc23f1b0d4adaf645caae65
--- include/lfmemory.h
+++ include/lfmemory.h
@@ -23,24 +23,25 @@ typedef struct {
     size_t save_count;
 } ArenaAllocator;
 
-void arena_init(ArenaAllocator *allocator, size_t buf_sz);
+int arena_init(ArenaAllocator *allocator, size_t buf_sz);
 void arena_free(ArenaAllocator *allocator);
 void *arena_malloc(ArenaAllocator* allocator, size_t size);
-void arena_resize_buf(ArenaAllocator *allocator, size_t new_sz);
+int arena_resize_buf(ArenaAllocator *allocator, const size_t new_sz);
 void *arena_resize(ArenaAllocator *allocator, void *mem, size_t old_sz, size_t new_sz);
 void arena_clear(ArenaAllocator *allocator);
 int arena_save(ArenaAllocator *allocator);
 void arena_restore(ArenaAllocator *allocator);
 
 typedef struct {
-	unsigned char *buf;
+    unsigned char *buf;
     size_t buf_sz;
     size_t chunk_size;
+    size_t aligned_start;
 
     List *free_list;
 } PoolAllocator;
 
-void pool_init(PoolAllocator *allocator, size_t buf_sz, size_t chunk_sz, size_t chunk_align);
+int pool_init(PoolAllocator *allocator, size_t buf_sz, size_t chunk_sz, size_t chunk_align);
 void pool_free(PoolAllocator *allocator, void *ptr);
 void pool_free_all(PoolAllocator *allocator);
 void *pool_alloc(PoolAllocator *allocator);
blob - 2cda586c07b5cccd7e27814fc6002bef118f21f3
blob + 328826df46992f5535d9323a8f63e1cec6afe61d
--- include/lfqueue.h
+++ include/lfqueue.h
@@ -4,12 +4,14 @@
 #include "lflinkedlist.h"
 
 #define Queue List
+#define queue_size(q) ((q)->size)
+#define queue_is_empty(q) ((q)->size == 0)
 
 void queue_init(Queue *queue, void (*destroy)(void *data));
 
 void queue_destroy(Queue *queue);
 
-int queue_enqueue(Queue *queue, void *data);
+int queue_enqueue(Queue *queue, const void *data);
 
 int queue_dequeue(Queue *queue, void **data);
 
blob - b2c240f8a7546642af828c74086afbb91d4fa8f9
blob + 5ac9dc2b321fb8c32912d5d29a505e0c842e5349
--- include/lfset.h
+++ include/lfset.h
@@ -4,12 +4,14 @@
 #include "lflinkedlist.h"
 
 #define Set List
+#define set_size(s) ((s)->size)
 
 void set_init(Set *set, int (*match)(const void *a, const void *b),
               void (*destroy)(void *data));
 
 void set_destroy(Set *set);
 
+/* Returns 0 on success, 1 if data is a duplicate, -1 on error */
 int set_insert(Set *set, const void *data);
 
 int set_remove(Set *set, void **data);
blob - 6f0739d385fe5b777b1296e05674f7d38beba69a
blob + 529493a95ebb78a3ef4c9a172475dabd2b08fb54
--- include/lfstack.h
+++ include/lfstack.h
@@ -4,6 +4,8 @@
 #include "lflinkedlist.h"
 
 #define Stack List
+#define stack_size(s) ((s)->size)
+#define stack_is_empty(s) ((s)->size == 0)
 
 void stack_init(Stack *stack, void (*destroy)(void *data));
 
blob - 1e49819533239f64b0c2f0d005cdc4802c9cfd2d
blob + 7c37ca946b57b7d24bdb27a1948635e6866e069d
--- include/lfstring.h
+++ include/lfstring.h
@@ -3,9 +3,9 @@
 
 #include <stddef.h>
 
-int find_substrings(const char* haystack, const char* needle, size_t *num_substrings, size_t **substrings);
+int str_find(const char* haystack, const char* needle, size_t *num_substrings, size_t **substrings);
 
-char* substr(const char* str, size_t idx, size_t len);
+char* str_substr(const char* str, size_t idx, size_t len);
 
 char *str_trim(const char *str);
 
@@ -21,4 +21,8 @@ char *str_to_upper(const char *str);
 
 char *str_to_lower(const char *str);
 
+char **str_split(const char *str, const char *delim, int *count);
+
+int str_contains(const char *str, const char *substr);
+
 #endif // LIBFLINT_H_STRING
blob - 9cf55b9abff79b53ae28c4118350e650b983fbd0
blob + 6278e8bb576be1f170d997739bf1e2d68ce76100
--- include/lfutility.h
+++ include/lfutility.h
@@ -8,7 +8,8 @@ typedef struct Point {
 
 Point Point_new(int x, int y);
 Point *Point_new_p(int x, int y);
-int Point_cmp(Point a, Point b);
+void Point_destroy(Point *p);
+int Point_eq(const Point a, const Point b);
 int Point_cmp_p(const Point *a, const Point *b);
 int Point_cmp_v(const void *a, const void *b);
 
blob - ba38adb6678861381669e0b59ea2d874f47eb8ad
blob + 5a9eb12170d7652d176781cd41685ffa0547a466
--- include/lfvector.h
+++ include/lfvector.h
@@ -1,7 +1,7 @@
 #ifndef LIBFLINT_H_VECTOR
 #define LIBFLINT_H_VECTOR
 
-#include <memory.h>
+#include <string.h>
 
 typedef struct Vector {
     size_t capacity;
@@ -14,7 +14,7 @@ int vec_init(Vector *vec, void (*destroy)(void *data))
 
 int vec_init_with_capacity(Vector *vec, void (*destroy)(void *data), size_t cap);
 
-void vec_clear(Vector *vec);
+int vec_clear(Vector *vec);
 
 void vec_destroy(Vector *vec);
 
blob - 037cd2638c2f251bc8df208e644d4732c6b0fe71
blob + c1e9ea6c0a08802cf5cb014032f12ccd1906899a
--- src/binarytree.c
+++ src/binarytree.c
@@ -7,7 +7,6 @@
 void bintree_init(BinTree *tree, void (*destroy)(void *data)) {
     tree->size = 0;
     tree->destroy = destroy;
-    tree->compare = NULL;
     tree->root = NULL;
 }
 
@@ -22,7 +21,7 @@ int bintree_ins_left(BinTree *tree, BinTreeNode *node,
 
     if (node == NULL) {
         if (tree->size > 0) {
-            return 1;
+            return -1;
         }
         pos = &tree->root;
     } else {
@@ -33,7 +32,7 @@ int bintree_ins_left(BinTree *tree, BinTreeNode *node,
     }
 
     if ((new_node = malloc(sizeof(BinTreeNode))) == NULL) {
-        return 2;
+        return -1;
     }
 
     new_node->data = data;
@@ -51,7 +50,7 @@ int bintree_ins_right(BinTree *tree, BinTreeNode *node
 
     if (node == NULL) {
         if (tree->size > 0) {
-            return 1;
+            return -1;
         }
         pos = &tree->root;
     } else {
@@ -62,7 +61,7 @@ int bintree_ins_right(BinTree *tree, BinTreeNode *node
     }
 
     if ((new_node = malloc(sizeof(BinTreeNode))) == NULL) {
-        return 2;
+        return -1;
     }
 
     new_node->data = data;
@@ -123,6 +122,10 @@ void bintree_rem_right(BinTree *tree, BinTreeNode *nod
 }
 
 int bintree_merge(BinTree *merge, BinTree *left, BinTree *right, void *data) {
+    if (left == right) {
+        return -1;
+    }
+
     bintree_init(merge, left->destroy);
     if (bintree_ins_left(merge, NULL, data) != 0) {
         bintree_destroy(merge);
@@ -135,30 +138,63 @@ int bintree_merge(BinTree *merge, BinTree *left, BinTr
 
     left->root = NULL;
     left->size = 0;
+
+    /* Preserve right's destroy callback before clearing */
     right->root = NULL;
     right->size = 0;
 
     return 0;
 }
 
-void print_node(char *prefix, BinTreeNode *node, int is_left, void (*pfunc)(void *data)) {
+void bintree_traverse(BinTree *tree, BinTreeNode *node, int order,
+                      void (*visitor)(void *data)) {
+    (void)tree;
+    if (node == NULL) {
+        return;
+    }
+
+    switch (order) {
+    case BINTREE_PREORDER:
+        visitor(node->data);
+        bintree_traverse(tree, node->left, order, visitor);
+        bintree_traverse(tree, node->right, order, visitor);
+        break;
+    case BINTREE_INORDER:
+        bintree_traverse(tree, node->left, order, visitor);
+        visitor(node->data);
+        bintree_traverse(tree, node->right, order, visitor);
+        break;
+    case BINTREE_POSTORDER:
+        bintree_traverse(tree, node->left, order, visitor);
+        bintree_traverse(tree, node->right, order, visitor);
+        visitor(node->data);
+        break;
+    }
+}
+
+static void print_node(char *prefix, BinTreeNode *node, int is_left,
+                        void (*print_fn)(void *data, char *buf, size_t buf_sz)) {
     if (node != NULL) {
-        printf("%s%s", prefix, (is_left ? "├──" : "└──"));
-        pfunc(node->data);
-        char new_prefix[64];
-        memset(new_prefix, 0, 64);
-        strlcat(new_prefix, prefix, 64);
-        strlcat(new_prefix, (is_left == 1 ? "│   " : "    "), 64 - strlen(prefix));
-        print_node(new_prefix, node->left, 1, pfunc);
-        print_node(new_prefix, node->right, 0, pfunc);
+        char label[64];
+        print_fn(node->data, label, sizeof(label));
+        printf("%s%s%s\n", prefix, (is_left ? "├──" : "└──"), label);
+
+        char new_prefix[256];
+        snprintf(new_prefix, sizeof(new_prefix), "%s%s",
+                 prefix, (is_left ? "│   " : "    "));
+        print_node(new_prefix, node->left, 1, print_fn);
+        print_node(new_prefix, node->right, 0, print_fn);
     }
 }
 
-void bintree_debug_pfunc_int(void *data) {
-    int i = *((int *) data);
-    printf("%d\n", i);
+static void default_print_int(void *data, char *buf, size_t buf_sz) {
+    snprintf(buf, buf_sz, "%d", *(int *)data);
 }
 
-void bintree_debug_print(BinTree *tree) {
-    print_node("", tree->root, 0, bintree_debug_pfunc_int);
+void bintree_debug_print(BinTree *tree,
+                         void (*print_fn)(void *data, char *buf, size_t buf_sz)) {
+    if (print_fn == NULL) {
+        print_fn = default_print_int;
+    }
+    print_node("", tree->root, 0, print_fn);
 }
blob - 7dc6dc22e56c5d9d88b4e00d81e93377619c0ffc
blob + 8139381294e40e86fb576e7cea57329dd738e9eb
--- src/crypto.c
+++ src/crypto.c
@@ -203,6 +203,10 @@ unsigned char *b64_decode(const char *s, size_t sz, si
 }
 
 unsigned char *hex_decode(const char *orig, size_t *sz) {
+    if (sz == NULL) {
+        return NULL;
+    }
+
     size_t buf_sz = strlen(orig) + 1;
     const char *sptr = orig;
     if (strncmp(orig, "0x", 2) == 0) {
@@ -215,16 +219,25 @@ unsigned char *hex_decode(const char *orig, size_t *sz
     }
 
     char *buf = malloc(sizeof(char) * buf_sz);
+    if (buf == NULL) {
+        return NULL;
+    }
+
     if (strlen(sptr) % 2 != 0) {
-        strlcpy(buf + 1, sptr, buf_sz - 1);
+        memcpy(buf + 1, sptr, strlen(sptr));
         buf[0] = '0';
     } else {
-        strlcpy(buf, sptr, buf_sz);
+        memcpy(buf, sptr, strlen(sptr));
     }
     buf[buf_sz - 1] = '\0';
 
     *sz = buf_sz / 2;
     unsigned char *hex = malloc(sizeof(unsigned char) * *sz);
+    if (hex == NULL) {
+        free(buf);
+        return NULL;
+    }
+
     const char *pos = buf;
 
     for (size_t i = 0; i < *sz; ++i) {
@@ -239,6 +252,10 @@ unsigned char *hex_decode(const char *orig, size_t *sz
 char *hex_encode(const unsigned char *hex, size_t sz) {
     size_t ssz = sz * 2 + 1;
     char *s = malloc(sizeof(char) * ssz);
+    if (s == NULL) {
+        return NULL;
+    }
+
     char *pos = s;
 
     for (size_t i = 0; i < sz; ++i) {
@@ -252,6 +269,10 @@ char *hex_encode(const unsigned char *hex, size_t sz) 
 
 char *hex_to_str(const unsigned char *hex, size_t sz) {
     char *s = malloc(sizeof(char) * (sz + 1));
+    if (s == NULL) {
+        return NULL;
+    }
+
     for (size_t i = 0; i < sz; ++i) {
         s[i] = (char)hex[i];
     }
@@ -260,7 +281,15 @@ char *hex_to_str(const unsigned char *hex, size_t sz) 
 }
 
 unsigned char* repeating_key_xor(const unsigned char* s, size_t s_sz, const unsigned char* key, size_t k_sz) {
+    if (k_sz == 0) {
+        return NULL;
+    }
+
     unsigned char* r = malloc(sizeof(unsigned char) * s_sz);
+    if (r == NULL) {
+        return NULL;
+    }
+
     for (size_t i = 0, j = 0; i < s_sz; ++i) {
         r[i] = s[i] ^ key[j];
         j = (j + 1) % k_sz;
@@ -272,23 +301,23 @@ unsigned char *repeating_key_xor_s(const char* s, cons
     return repeating_key_xor((unsigned char*)s, strlen(s), (unsigned char*)key, strlen(key));
 }
 
-unsigned int hamming_distance_s(const char *a, const char *b) {
+int hamming_distance_s(const char *a, const char *b) {
     size_t sz = strlen(a);
     if (sz != strlen(b)) {
         return -1;
     }
 
-    return hamming_distance((unsigned char *)a, (unsigned char *)b, sz);
+    return hamming_distance((const unsigned char *)a, (const unsigned char *)b, sz);
 }
 
-unsigned int hamming_distance(unsigned char *a, unsigned char *b, size_t sz) {
-    unsigned int hamming = 0;
+int hamming_distance(const unsigned char *a, const unsigned char *b, size_t sz) {
+    int hamming = 0;
     for (size_t i = 0; i < sz; ++i) {
         if (a[i] == b[i]) {
             continue;
         }
         unsigned char c = a[i] ^ b[i];
-        unsigned int count = 0;
+        int count = 0;
         for (; c; count++) {
             c &= c - 1;
         }
blob - dfb3e5a33f8c3184643b3c716537b25f2949adcc
blob + c90bf7dfe7be8484d7200668e9a862d2f7357ffd
--- src/input.c
+++ src/input.c
@@ -12,23 +12,29 @@
 
 #include "lfinput.h"
 
-static FILE* open_file(const char *path, size_t *fsz) {
+static FILE* inp_open_file(const char *path, size_t *fsz, const char *mode) {
     FILE *fp = NULL;
-    fp = fopen(path, "r");
+    fp = fopen(path, mode);
     if (fp == NULL) {
         fprintf(stderr, "Failed to open %s. Returning NULL\n", path);
         return NULL;
     }
 
     fseek(fp, 0, SEEK_END);
-    *fsz = ftell(fp);
+    long pos = ftell(fp);
+    if (pos < 0) {
+        fprintf(stderr, "ftell failed for %s. Returning NULL\n", path);
+        fclose(fp);
+        return NULL;
+    }
+    *fsz = (size_t)pos;
     rewind(fp);
 
     return fp;
 }
 
-unsigned char *get_binary(const char *path, size_t *fsz) {
-    FILE *fp = open_file(path, fsz);
+unsigned char *inp_get_binary(const char *path, size_t *fsz) {
+    FILE *fp = inp_open_file(path, fsz, "rb");
     if (fp == NULL) {
         return NULL;
     }
@@ -47,9 +53,9 @@ unsigned char *get_binary(const char *path, size_t *fs
     return buf;
 }
 
-char *get_input(const char *path) {
+char *inp_get_input(const char *path) {
     size_t fsz = 0;
-    FILE *fp = open_file(path, &fsz);
+    FILE *fp = inp_open_file(path, &fsz, "r");
     if (fp == NULL) {
         return NULL;
     }
@@ -69,18 +75,24 @@ char *get_input(const char *path) {
     return buf;
 }
 
-char **split(char *s, size_t *lsz, const char *delim) {
+char **inp_split(char *s, size_t *lsz, const char *delim) {
+    if (s == NULL || lsz == NULL || delim == NULL) {
+        return NULL;
+    }
+
     char **lines = NULL;
     char *t = strtok(s, delim);
     size_t n = 0;
 
     while (t != NULL) {
-        lines = realloc(lines, sizeof(char *) * ++n);
-        if (lines == NULL) {
+        char **tmp = realloc(lines, sizeof(char *) * (n + 1));
+        if (tmp == NULL) {
             fprintf(stderr, "Failed to realloc lines buffer. Returning NULL\n");
-            free(s);
+            free(lines);
             return NULL;
         }
+        lines = tmp;
+        n++;
         lines[n - 1] = t;
         t = strtok(NULL, delim);
     }
@@ -89,13 +101,75 @@ char **split(char *s, size_t *lsz, const char *delim) 
     return lines;
 }
 
-char **get_lines(const char *path, size_t *lsz) {
-    return split(get_input(path), lsz, "\n");
+/*
+ * Internal split used by inp_get_lines. Stores the original string pointer
+ * at index [-1] (one slot before the returned array) so inp_del_lines can
+ * free the correct base even when strtok skips leading delimiters.
+ */
+static char **split_lines(char *s, size_t *lsz, const char *delim) {
+    if (s == NULL || lsz == NULL || delim == NULL) {
+        return NULL;
+    }
+
+    char *base = s;
+    /* Start with space for the hidden base slot + 1 token */
+    size_t n = 0;
+    size_t cap = 2;
+    char **raw = malloc(sizeof(char *) * cap);
+    if (raw == NULL) {
+        free(base);
+        return NULL;
+    }
+    raw[0] = base;  /* hidden slot stores original allocation */
+
+    char *t = strtok(s, delim);
+    while (t != NULL) {
+        n++;
+        if (n + 1 >= cap) {
+            cap *= 2;
+            char **tmp = realloc(raw, sizeof(char *) * cap);
+            if (tmp == NULL) {
+                free(raw);
+                free(base);
+                *lsz = 0;
+                return NULL;
+            }
+            raw = tmp;
+        }
+        raw[n] = t;
+        t = strtok(NULL, delim);
+    }
+
+    if (n == 0) {
+        free(base);
+        free(raw);
+        *lsz = 0;
+        return NULL;
+    }
+
+    *lsz = n;
+    return raw + 1;  /* return pointer past the hidden base slot */
 }
 
-int *get_ints(const char *path, size_t *sz) {
-    char **lines = get_lines(path, sz);
+char **inp_get_lines(const char *path, size_t *lsz) {
+    return split_lines(inp_get_input(path), lsz, "\n");
+}
+
+int *inp_get_ints(const char *path, size_t *sz) {
+    if (path == NULL || sz == NULL) {
+        return NULL;
+    }
+
+    char **lines = inp_get_lines(path, sz);
+    if (lines == NULL) {
+        return NULL;
+    }
+
     int *i = malloc(sizeof(int) * *sz);
+    if (i == NULL) {
+        inp_del_lines(lines);
+        return NULL;
+    }
 
     for (size_t idx = 0; idx < *sz; idx++) {
         int n;
@@ -104,37 +178,71 @@ int *get_ints(const char *path, size_t *sz) {
         if (errstr) {
             fprintf(stderr, "Failed to convert %s to int. Returning NULL\n", lines[idx]);
             free(i);
-            del_lines(lines);
+            inp_del_lines(lines);
             return NULL;
         }
         i[idx] = n;
     }
 
-    del_lines(lines);
+    inp_del_lines(lines);
     return i;
 }
 
-void del_split(char **sp) {
-    free(sp[0]);
+void inp_del_split(char **sp) {
+    if (sp == NULL) {
+        return;
+    }
     free(sp);
 }
 
-void del_lines(char **lines) {
-    del_split(lines);
+void inp_del_lines(char **lines) {
+    if (lines == NULL) {
+        return;
+    }
+    /* lines[-1] holds the original string base pointer (set by split_lines) */
+    free(lines[-1]);
+    free(lines - 1);
 }
 
-const char *capture_system(const char *cmd, int buf_sz) {
+char *inp_capture_system(const char *cmd, size_t buf_sz) {
+    if (cmd == NULL) {
+        return NULL;
+    }
+
     if (buf_sz == 0) {
         buf_sz = DEFAULT_CAPTURE_SYSTEM_BUFSIZE;
     }
-    char *buf = malloc(buf_sz);
+
     FILE *tmp = popen(cmd, "r");
     if (tmp == NULL) {
         fprintf(stderr, "libflint: failed to open FILE *tmp in capture_system. Errno: %d\n", errno);
-        free(buf);
         return NULL;
     }
-    fgets(buf, buf_sz, tmp);
+
+    size_t total = 0;
+    size_t cap = buf_sz;
+    char *buf = malloc(cap);
+    if (buf == NULL) {
+        pclose(tmp);
+        return NULL;
+    }
+
+    size_t nread;
+    while ((nread = fread(buf + total, 1, cap - total - 1, tmp)) > 0) {
+        total += nread;
+        if (total + 1 >= cap) {
+            cap *= 2;
+            char *tmp2 = realloc(buf, cap);
+            if (tmp2 == NULL) {
+                free(buf);
+                pclose(tmp);
+                return NULL;
+            }
+            buf = tmp2;
+        }
+    }
+
+    buf[total] = '\0';
     pclose(tmp);
     return buf;
-}
\ No newline at end of file
+}
blob - 3efce6794774833d7c2752b2fe570b9e354da7dd
blob + 5caa87bc58c50c7faf32f28982bb3a2ca98399b9
--- src/linkedlist.c
+++ src/linkedlist.c
@@ -3,9 +3,11 @@
 
 #include "lflinkedlist.h"
 
-void ll_init(List *list, void (*destroy)(void *data)) {
+void ll_init(List *list, void (*destroy)(void *data),
+             int (*match)(const void *a, const void *b)) {
     list->size = 0;
     list->destroy = destroy;
+    list->match = match;
     list->head = NULL;
     list->tail = NULL;
 }
@@ -81,7 +83,7 @@ int ll_ins_prev(List *list, ListNode *node, const void
 }
 
 int ll_remove(List *list, ListNode *node, void **data) {
-    if (node == NULL || list->size == 0) {
+    if (node == NULL || data == NULL || list->size == 0) {
         return -1;
     }
 
@@ -120,14 +122,22 @@ int ll_remove_prev(List *list, ListNode *node, void **
     return ll_remove(list, node->prev, data);
 }
 
-ListNode *ll_find(List *list, const void *data) {
-    if (list->match == NULL) {
+ListNode *ll_find(const List *list, const void *data) {
+    if (list == NULL) {
         return NULL;
     }
-    for (ListNode *node = list->head; node != NULL; node = node->next) {
-        if (list->match(data, node->data)) {
-            return node;
+    ListNode *node = list->head;
+    while (node != NULL) {
+        if (list->match != NULL) {
+            if (list->match(node->data, data) == 0) {
+                return node;
+            }
+        } else {
+            if (node->data == data) {
+                return node;
+            }
         }
+        node = node->next;
     }
     return NULL;
 }
@@ -212,8 +222,8 @@ void ll_sort(List *list, int (*cmp)(const void *a, con
     list->tail = node;
 }
 
-void **ll_to_array(List *list) {
-    if (list->size == 0) {
+void **ll_to_array(const List *list) {
+    if (list == NULL || list->size == 0) {
         return NULL;
     }
     void **arr = malloc(list->size * sizeof(void *));
blob - 3bbcf1c246a5b5475ef1fb51e62831f676c6dcf6
blob + 3409cf0851cd610e64365c0e575fc0044547bf0f
--- src/macos.c
+++ src/macos.c
@@ -1,3 +1,5 @@
+#if defined(__APPLE__) || defined(__MACH__)
+
 #include <libproc.h>
 #include <time.h>
 #include <mach/mach_time.h>
@@ -10,20 +12,29 @@
 #define NEW_PROCESS_SENTINEL (-1.0)
 
 ProcessData *new_ProcessData() {
-    ProcessData *pd = malloc(sizeof(ProcessData));
+    ProcessData *pd = calloc(1, sizeof(ProcessData));
+    if (!pd) return NULL;
     pd->last_total_consumed = NEW_PROCESS_SENTINEL;
     return pd;
 }
 
+void destroy_ProcessData(ProcessData *pd) {
+    free(pd);
+}
+
 int update_process(pid_t pid, ProcessData *proc) {
     struct proc_taskinfo taskinfo;
     const int r = proc_pidinfo(pid, PROC_PIDTASKINFO, 0, &taskinfo, PROC_PIDTASKINFO_SIZE);
     if (r != PROC_PIDTASKINFO_SIZE) {
-        return 1;
+        return -1;
     }
 
-    mach_timebase_info_data_t info;
-    mach_timebase_info(&info);
+    static mach_timebase_info_data_t info;
+    static int info_init = 0;
+    if (!info_init) {
+        mach_timebase_info(&info);
+        info_init = 1;
+    }
     const double ns_per_tick = (double)info.numer / (double)info.denom;
 
     time(&(proc->timestamp));
@@ -34,7 +45,11 @@ int update_process(pid_t pid, ProcessData *proc) {
 
     if (proc->last_total_consumed != NEW_PROCESS_SENTINEL) {
         time_t t = proc->timestamp - proc->last_timestamp;
-        proc->percent_cpu = 100.0 * (proc->total_user_time + proc->total_kernel_time - proc->last_total_consumed) / t;
+        if (t > 0) {
+            proc->percent_cpu = 100.0 * (proc->total_user_time + proc->total_kernel_time - proc->last_total_consumed) / t;
+        } else {
+            proc->percent_cpu = 0.0;
+        }
     } else {
         proc->percent_cpu = 0.0;
     }
@@ -80,3 +95,5 @@ void *reallocarray(void *optr, size_t nmemb, size_t si
     }
     return realloc(optr, size * nmemb);
 }
+
+#endif /* defined(__APPLE__) || defined(__MACH__) */
blob - 08fed2d2db0c250cc94df7548df5cff989f536f2
blob + efcb9566eda70adaf457b15114aeb99bb658a2c6
--- src/math.c
+++ src/math.c
@@ -1,8 +1,16 @@
+#include <limits.h>
 #include <string.h>
 #include <stdlib.h>
 
 #include "lfmath.h"
 
+int abs_int(int a) {
+    if (a == INT_MIN) {
+        return INT_MAX;
+    }
+    return a < 0 ? -a : a;
+}
+
 int max_int(int a, int b) {
     if (a > b) {
         return a;
@@ -18,6 +26,11 @@ int min_int(int a, int b) {
 }
 
 int clamp_int(int i, int low, int high) {
+    if (low > high) {
+        int tmp = low;
+        low = high;
+        high = tmp;
+    }
     if (i > high) {
         return high;
     } else if (i < low) {
@@ -27,6 +40,9 @@ int clamp_int(int i, int low, int high) {
 }
 
 int binstr_to_int(const char *s) {
+    if (!s) {
+        return -1;
+    }
     int n = 0, m = 1;
     for (int i = (int) strlen(s) - 1; i >= 0; --i) {
         if (s[i] == '_') {
@@ -41,19 +57,26 @@ int binstr_to_int(const char *s) {
 }
 
 Point *bresenham(int x0, int y0, int x1, int y1, size_t *sz) {
-    Point *line = NULL;
-    size_t n = 0;
     *sz = 0;
 
-    int dx = abs(x1 - x0), sx = x0 < x1 ? 1 : -1;
-    int dy = abs(y1 - y0), sy = y0 < y1 ? 1 : -1;
+    int dx = abs(x1 - x0);
+    int dy = abs(y1 - y0);
+    size_t npts = (size_t)(dx > dy ? dx : dy) + 1;
+
+    Point *line = malloc(sizeof(Point) * npts);
+    if (!line) {
+        return NULL;
+    }
+
+    int sx = x0 < x1 ? 1 : -1;
+    int sy = y0 < y1 ? 1 : -1;
     int err = (dx > dy ? dx : -dy) / 2, e2;
+    size_t n = 0;
 
     for (;;) {
-        line = realloc(line, sizeof(Point) * ++n);
-        ++*sz;
-        line[n - 1].x = x0;
-        line[n - 1].y = y0;
+        line[n].x = x0;
+        line[n].y = y0;
+        n++;
 
         if (x0 == x1 && y0 == y1) {
             break;
@@ -70,6 +93,7 @@ Point *bresenham(int x0, int y0, int x1, int y1, size_
         }
     }
 
+    *sz = n;
     return line;
 }
 
@@ -77,10 +101,6 @@ Point *bresenham_p(Point p1, Point p2, size_t *sz) {
     return bresenham(p1.x, p1.y, p2.x, p2.y, sz);
 }
 
-int abs_int(int i) {
-    return i < 0 ? -i : i;
-}
-
 int is_power_of_two(int i) {
     return i > 0 && (i & (i - 1)) == 0;
 }
blob - 627dbf52c50beebcc40de79d911dd68f39120235
blob + f3385917f6f35f40d939609e8e961ca55cdbed16
--- src/memory.c
+++ src/memory.c
@@ -5,18 +5,20 @@
 
 #include "lfmemory.h"
 
-#define arena_sz(a) (a)->buf_sz
-
-void arena_init(ArenaAllocator *allocator, size_t buf_sz) {
+int arena_init(ArenaAllocator *allocator, size_t buf_sz) {
     if (allocator == NULL) {
-        return;
+        return -1;
     }
 
     allocator->buf = malloc(sizeof(unsigned char) * buf_sz);
+    if (allocator->buf == NULL) {
+        return -1;
+    }
     allocator->buf_sz = buf_sz;
     allocator->offset_cur = 0;
     allocator->offset_prev = 0;
     allocator->save_count = 0;
+    return 0;
 }
 
 void arena_free(ArenaAllocator *allocator) {
@@ -119,13 +121,17 @@ static void *arena_resize_align(ArenaAllocator *alloca
     return NULL;
 }
 
-void arena_resize_buf(ArenaAllocator *allocator, const size_t new_sz) {
+int arena_resize_buf(ArenaAllocator *allocator, const size_t new_sz) {
+    if (new_sz < allocator->offset_cur) {
+        return -1;
+    }
     unsigned char *new_buf = realloc(allocator->buf, sizeof(unsigned char) * new_sz);
     if (new_buf == NULL) {
-        return;
+        return -1;
     }
     allocator->buf = new_buf;
     allocator->buf_sz = new_sz;
+    return 0;
 }
 
 void *arena_malloc(ArenaAllocator *allocator, const size_t size) {
@@ -136,33 +142,44 @@ void *arena_resize(ArenaAllocator *allocator, void *me
     return arena_resize_align(allocator, mem, old_sz, new_sz, LF_DEFAULT_ALIGNMENT);
 }
 
-void pool_init(PoolAllocator *allocator, size_t buf_sz, size_t chunk_sz, size_t chunk_align) {
+int pool_init(PoolAllocator *allocator, size_t buf_sz, size_t chunk_sz, size_t chunk_align) {
     if (allocator == NULL) {
-        return;
+        return -1;
     }
 
     allocator->buf = malloc(sizeof(unsigned char) * buf_sz);
+    if (allocator->buf == NULL) {
+        return -1;
+    }
     uintptr_t istart = (uintptr_t)allocator->buf;
     uintptr_t start = align_forward(istart, chunk_align);
-    allocator->buf_sz = buf_sz - (start - istart);
+    allocator->aligned_start = (size_t)(start - istart);
+    allocator->buf_sz = buf_sz - allocator->aligned_start;
 
     allocator->chunk_size = align_forward(chunk_sz, chunk_align);
     if (allocator->chunk_size < sizeof(void *) || allocator->buf_sz < allocator->chunk_size) {
         free(allocator->buf);
         allocator->buf = NULL;
         allocator->buf_sz = 0;
-        return;
+        return -1;
     }
 
     allocator->free_list = malloc(sizeof(List));
-    ll_init(allocator->free_list, NULL);
+    if (allocator->free_list == NULL) {
+        free(allocator->buf);
+        allocator->buf = NULL;
+        allocator->buf_sz = 0;
+        return -1;
+    }
+    ll_init(allocator->free_list, NULL, NULL);
 
     pool_free_all(allocator);
+    return 0;
 }
 
 void pool_free(PoolAllocator *allocator, void *ptr) {
-    const void *start = allocator->buf;
-    const void *end = &allocator->buf[allocator->buf_sz];
+    const void *start = allocator->buf + allocator->aligned_start;
+    const void *end = allocator->buf + allocator->aligned_start + allocator->buf_sz;
 
     if (ptr == NULL) {
         return;
@@ -177,11 +194,12 @@ void pool_free(PoolAllocator *allocator, void *ptr) {
 
 void pool_free_all(PoolAllocator *allocator) {
     ll_destroy(allocator->free_list);
-    ll_init(allocator->free_list, NULL);
+    ll_init(allocator->free_list, NULL, NULL);
 
     size_t chunk_count = allocator->buf_sz / allocator->chunk_size;
     for (size_t i = 0; i < chunk_count; ++i) {
-        ll_ins_next(allocator->free_list, allocator->free_list->head, &allocator->buf[i * allocator->chunk_size]);
+        ll_ins_next(allocator->free_list, allocator->free_list->head,
+                    &allocator->buf[allocator->aligned_start + i * allocator->chunk_size]);
     }
 }
 
@@ -203,5 +221,7 @@ void pool_destroy(PoolAllocator *allocator) {
     ll_destroy(allocator->free_list);
     free(allocator->free_list);
     free(allocator->buf);
-    free(allocator);
+    allocator->buf = NULL;
+    allocator->free_list = NULL;
+    allocator->buf_sz = 0;
 }
blob - b4f2ebfa3ef9b4d8dd0f24d89f8d435b77ab21c0
blob + fb880b8851bdf1114fda35c721753926cf02e1ad
--- src/network.c
+++ src/network.c
@@ -1,6 +1,7 @@
 #include <errno.h>
 #include <string.h>
 #include <stdlib.h>
+#include <stdint.h>
 #include <sys/socket.h>
 #include <sys/types.h>
 #include <arpa/inet.h>
@@ -60,6 +61,9 @@ Server *new_server(ServerType type, const char *port, 
             continue;
         }
 
+        int yes = 1;
+        setsockopt(s->fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes));
+
         if (bind(s->fd, p->ai_addr, p->ai_addrlen) != 0) {
             close(s->fd);
             continue;
@@ -70,6 +74,7 @@ Server *new_server(ServerType type, const char *port, 
 
     if (p == NULL) {
         fprintf(stderr, "Failed to bind\n");
+        freeaddrinfo(addr);
         free(s);
         return NULL;
     }
@@ -79,8 +84,9 @@ Server *new_server(ServerType type, const char *port, 
 }
 
 void delete_server(Server *s) {
+    if (s == NULL) return;
+    close(s->fd);
     free(s);
-    s = NULL;
 }
 
 int serve(Server *s, int backlog_size) {
@@ -88,8 +94,10 @@ int serve(Server *s, int backlog_size) {
         backlog_size = DEFAULT_BACKLOG;
     }
 
-    if (listen(s->fd, backlog_size) != 0) {
-        return 1;
+    if (s->server_type != SERVERTYPE_UDP) {
+        if (listen(s->fd, backlog_size) != 0) {
+            return -1;
+        }
     }
 
     // Linux doesn't handle SA_RESTART properly, and I don't know about Windows (nor do I care)
@@ -101,7 +109,7 @@ int serve(Server *s, int backlog_size) {
     sa.sa_flags = SA_RESTART;
     if (sigaction(SIGCHLD, &sa, NULL) == -1) {
         fprintf(stderr, "Failed to set sigaction\n");
-        return 1;
+        return -1;
     }
     #endif
 
@@ -111,9 +119,9 @@ int serve(Server *s, int backlog_size) {
 }
 
 static void *tcp_echo_thread(void *vargp) {
+    int fd = (int)(intptr_t)vargp;
     while (1) {
         char recv_buf[256];
-        int fd = *(int *) vargp;
 
         int r = (int)recv(fd, recv_buf, 256, 0);
         if (r < 1) {
@@ -147,6 +155,7 @@ void handler_tcp_echo(Server *s) {
         pthread_attr_t attr;
         pthread_attr_init(&attr);
         pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
-        pthread_create(&srv_tid, &attr, tcp_echo_thread, &new_fd);
+        pthread_create(&srv_tid, &attr, tcp_echo_thread, (void *)(intptr_t)new_fd);
+        pthread_attr_destroy(&attr);
     }
 }
blob - 5fa7f48c7a3818d6b9211a70803e00e0572e2362
blob + 18a81d0e82958063db5ea097fa554dbb7df9ea02
--- src/parsing.c
+++ src/parsing.c
@@ -1,3 +1,4 @@
+#include <stddef.h>
 #include "lfparsing.h"
 
 static int ses_score_sw(char c) {
@@ -20,6 +21,9 @@ static int ses_score_sw(char c) {
 }
 
 int simple_english_scoring(const char *s) {
+    if (s == NULL) {
+        return -1;
+    }
     int score = 0;
     for (const char *c = s; *c != '\0'; ++c) {
         score += ses_score_sw(*c);
blob - c591d62d376d9c307faf43a8ab13cdf29fe75098
blob + 264690c25a6336bdd59acf253c19fff5db139a6a
--- src/queue.c
+++ src/queue.c
@@ -1,17 +1,14 @@
 #include "lfqueue.h"
 
 void queue_init(Queue *queue, void (*destroy)(void *data)) {
-    ll_init(queue, destroy);
+    ll_init(queue, destroy, NULL);
 }
 
 void queue_destroy(Queue *queue) {
     ll_destroy(queue);
 }
 
-int queue_enqueue(Queue *queue, void *data) {
-    if (queue->size == 0) {
-        return ll_ins_next(queue, NULL, data);
-    }
+int queue_enqueue(Queue *queue, const void *data) {
     return ll_ins_next(queue, queue->tail, data);
 }
 
blob - e9ea33f070449ed8609fba04178972b065dba6a4
blob + 9370b765ba442a103da7c95ae4cafb9809a705e6
--- src/set.c
+++ src/set.c
@@ -2,8 +2,7 @@
 
 void set_init(Set *set, int (*match)(const void *a, const void *b),
               void (*destroy)(void *data)) {
-    ll_init(set, destroy);
-    set->match = match;
+    ll_init(set, destroy, match);
 }
 
 void set_destroy(Set *set) {
@@ -18,6 +17,9 @@ int set_insert(Set *set, const void *data) {
 }
 
 int set_remove(Set *set, void **data) {
+    if (data == NULL) {
+        return -1;
+    }
     ListNode *node = ll_find(set, *data);
     if (node == NULL) {
         return -1;
@@ -89,7 +91,7 @@ int set_difference(Set *setd, const Set *a, const Set 
 }
 
 int set_is_member(const Set *set, const void *data) {
-    return ll_find((List *)set, data) != NULL;
+    return ll_find(set, data) != NULL;
 }
 
 int set_is_subset(const Set *a, const Set *b) {
blob - f8d1c4953df8c14efac4525ba9cf646abf697df6
blob + 7e0123a98021a825274196aed34c7e5aecb5480d
--- src/stack.c
+++ src/stack.c
@@ -1,7 +1,7 @@
 #include "lfstack.h"
 
 void stack_init(Stack *stack, void (*destroy)(void *data)) {
-    ll_init(stack, destroy);
+    ll_init(stack, destroy, NULL);
 }
 
 void stack_destroy(Stack *stack) {
blob - a39bd8b1795b4f994bca76be4054f8820f1fa1c0
blob + 77e8f45842e4a65ef489d3d6940f5a23bbfffcc1
--- src/string.c
+++ src/string.c
@@ -4,18 +4,19 @@
 
 #include "lfstring.h"
 
-int find_substrings(const char* haystack, const char* needle, size_t *num_substrings, size_t **substrings) {
+int str_find(const char* haystack, const char* needle, size_t *num_substrings, size_t **substrings) {
     if (*substrings != NULL) {
         return 1;
     }
 
+    *num_substrings = 0;
+
     size_t sz_h = strlen(haystack);
     size_t sz_n = strlen(needle);
-    if ((int)sz_h - (int)sz_n < 0) {
+    if (sz_n > sz_h) {
         return 0;
     }
 
-    *num_substrings = 0;
     for (size_t i = 0; i <= sz_h - sz_n; ++i) {
         if (strncmp(haystack + i, needle, sz_n) == 0) {
             ++(*num_substrings);
@@ -41,21 +42,21 @@ int find_substrings(const char* haystack, const char* 
     return 0;
 }
 
-char* substr(const char* str, size_t idx, size_t len) {
+char* str_substr(const char* str, size_t idx, size_t len) {
    size_t sz_str = strlen(str);
    if (sz_str < len || idx + len > sz_str) {
        return NULL;
    }
 
-   char *substr = malloc(sizeof(char) * (len + 1));
-   if (substr == NULL) {
+   char *sub = malloc(sizeof(char) * (len + 1));
+   if (sub == NULL) {
        return NULL;
    }
 
-   memcpy(substr, str + idx, len);
-   substr[len] = '\0';
+   memcpy(sub, str + idx, len);
+   sub[len] = '\0';
 
-   return substr;
+   return sub;
 }
 
 char *str_trim(const char *str) {
@@ -184,3 +185,70 @@ char *str_to_lower(const char *str) {
     out[len] = '\0';
     return out;
 }
+
+char **str_split(const char *str, const char *delim, int *count) {
+    *count = 0;
+    size_t delim_len = strlen(delim);
+
+    if (delim_len == 0) {
+        char **result = malloc(sizeof(char *));
+        if (result == NULL) {
+            return NULL;
+        }
+        result[0] = strdup(str);
+        if (result[0] == NULL) {
+            free(result);
+            return NULL;
+        }
+        *count = 1;
+        return result;
+    }
+
+    /* count segments */
+    int n = 1;
+    const char *p = str;
+    while ((p = strstr(p, delim)) != NULL) {
+        n++;
+        p += delim_len;
+    }
+
+    char **result = malloc(sizeof(char *) * n);
+    if (result == NULL) {
+        return NULL;
+    }
+
+    int i = 0;
+    p = str;
+    const char *next;
+    while ((next = strstr(p, delim)) != NULL) {
+        size_t seg_len = next - p;
+        result[i] = malloc(seg_len + 1);
+        if (result[i] == NULL) {
+            for (int j = 0; j < i; j++) {
+                free(result[j]);
+            }
+            free(result);
+            return NULL;
+        }
+        memcpy(result[i], p, seg_len);
+        result[i][seg_len] = '\0';
+        i++;
+        p = next + delim_len;
+    }
+
+    /* last segment */
+    result[i] = strdup(p);
+    if (result[i] == NULL) {
+        for (int j = 0; j < i; j++) {
+            free(result[j]);
+        }
+        free(result);
+        return NULL;
+    }
+    *count = n;
+    return result;
+}
+
+int str_contains(const char *str, const char *substr) {
+    return strstr(str, substr) != NULL;
+}
blob - 9330ab238d5ddcad1e3170a2bd13ee8171d2d716
blob + 5e347290ae4488a0d8be1d5f61fa2c54ea42eb3f
--- src/utility.c
+++ src/utility.c
@@ -11,11 +11,19 @@ Point Point_new(int x, int y) {
 
 Point *Point_new_p(int x, int y) {
     Point *p = malloc(sizeof(struct Point));
+    if (!p) {
+        return NULL;
+    }
     p->x = x;
     p->y = y;
     return p;
 }
-int Point_cmp(const Point a, const Point b) {
+
+void Point_destroy(Point *p) {
+    free(p);
+}
+
+int Point_eq(const Point a, const Point b) {
     if (a.x == b.x && a.y == b.y) {
         return 1;
     }
@@ -32,4 +40,3 @@ int Point_cmp_p(const Point *a, const Point *b) {
 int Point_cmp_v(const void *a, const void *b) {
     return Point_cmp_p(a, b);
 }
-
blob - 4fcf4f00c195c3b8f19fab2c186ffad1ee7d1aa9
blob + 42f1e34652af4031642d8236205413ea425b8c1c
--- src/vector.c
+++ src/vector.c
@@ -13,8 +13,6 @@
 
 #define VEC_INIT_CAP 2
 
-static int (*vec_sort_cmp_fn)(const void *, const void *);
-
 int vec_init(Vector *vec, void (*destroy)(void *data)) {
     return vec_init_with_capacity(vec, destroy, VEC_INIT_CAP);
 }
@@ -36,19 +34,19 @@ int vec_init_with_capacity(Vector *vec, void (*destroy
 }
 
 static int vec_expand(Vector *vec, size_t new_cap) {
-    vec->capacity = new_cap;
-    vec->elements = reallocarray(vec->elements, new_cap, sizeof(void *));
-
-    if (vec->elements == NULL) {
+    void *tmp = reallocarray(vec->elements, new_cap, sizeof(void *));
+    if (tmp == NULL) {
         return -1;
     }
+    vec->elements = tmp;
+    vec->capacity = new_cap;
     return 0;
 }
 
 static int vec_grow(Vector *const vec) {
     size_t new_cap;
     if (vec->capacity == 0) {
-        new_cap = VEC_INIT_CAP; ;
+        new_cap = VEC_INIT_CAP;
     } else {
         new_cap = vec->capacity * 2;
     }
@@ -62,9 +60,13 @@ int vec_grow_to(Vector *vec, const size_t new_cap) {
     return vec_expand(vec, new_cap);
 }
 
-void vec_clear(Vector *vec) {
+int vec_clear(Vector *vec) {
+    void (*destroy)(void *data) = vec->destroy;
     vec_destroy(vec);
-    vec_init(vec, vec->destroy);
+    if (vec_init(vec, destroy) != 0) {
+        return -1;
+    }
+    return 0;
 }
 
 void vec_destroy(Vector *vec) {
@@ -81,16 +83,12 @@ int vec_insert(Vector *vec, void *data, size_t index) 
         return -1;
     }
 
-    if (vec_len(vec) + 1 >= vec->capacity) {
+    if (vec_len(vec) + 1 > vec->capacity) {
         if (vec_grow(vec) != 0) {
             return -1;
         }
     }
 
-    if (index > vec_len(vec)) {
-        return -1;
-    }
-
     if (index < vec_len(vec)) {
         void *a = vec->elements[index];
         vec->elements[index] = data;
@@ -139,18 +137,28 @@ int vec_shrink(Vector *vec) {
         return 0;
     }
 
-    vec->capacity = vec_len(vec);
+    if (vec_len(vec) == 0) {
+        free(vec->elements);
+        vec->elements = NULL;
+        vec->capacity = 0;
+        return 0;
+    }
 
+    size_t new_cap = vec_len(vec);
+
 #if !defined(__OpenBSD__)
-    vec->elements = reallocf(vec->elements, sizeof(void *) * vec->capacity);
+    void *tmp = reallocf(vec->elements, sizeof(void *) * new_cap);
 #else
-    vec->elements = reallocarray(vec->elements, vec->capacity, sizeof(void *));
+    void *tmp = reallocarray(vec->elements, new_cap, sizeof(void *));
 #endif
 
-    if (vec->elements == NULL) {
+    if (tmp == NULL) {
         return -1;
     }
 
+    vec->elements = tmp;
+    vec->capacity = new_cap;
+
     return 0;
 }
 
@@ -199,19 +207,31 @@ void vec_reverse(Vector *vec) {
     }
 }
 
-static int vec_sort_cmp_wrapper(const void *a, const void *b) {
-    /* qsort passes pointers to elements; our elements are void*, so a is void** */
+#if defined(__APPLE__)
+static int vec_sort_cmp_wrapper(void *thunk, const void *a, const void *b) {
+    int (*cmp)(const void *, const void *) = thunk;
     const void *ea = *(const void **)a;
     const void *eb = *(const void **)b;
-    return vec_sort_cmp_fn(ea, eb);
+    return cmp(ea, eb);
 }
+#else
+static int vec_sort_cmp_wrapper(const void *a, const void *b, void *thunk) {
+    int (*cmp)(const void *, const void *) = thunk;
+    const void *ea = *(const void **)a;
+    const void *eb = *(const void **)b;
+    return cmp(ea, eb);
+}
+#endif
 
 void vec_sort(Vector *vec, int (*cmp)(const void *a, const void *b)) {
     if (vec_len(vec) < 2) {
         return;
     }
-    vec_sort_cmp_fn = cmp;
-    qsort(vec->elements, vec_len(vec), sizeof(void *), vec_sort_cmp_wrapper);
+#if defined(__APPLE__)
+    qsort_r(vec->elements, vec_len(vec), sizeof(void *), (void *)cmp, vec_sort_cmp_wrapper);
+#else
+    qsort_r(vec->elements, vec_len(vec), sizeof(void *), vec_sort_cmp_wrapper, (void *)cmp);
+#endif
 }
 
 void *vec_bsearch(Vector *vec, const void *key, int (*cmp)(const void *a, const void *b)) {
blob - fe79293ff6446784eaeff8ae652c0ab90891af70
blob + 3346d0cbbfc1802e839c173c5fe00ab6e0176fec
--- tests/test_binarytree.c
+++ tests/test_binarytree.c
@@ -1,9 +1,26 @@
 #include <stdio.h>
 #include <stdlib.h>
+#include <string.h>
 #include "lftest.h"
 #include "lfbinarytree.h"
 
+static int destroy_count;
+
+static void counting_destroy(void *data) {
+    (void)data;
+    destroy_count++;
+}
+
+/* Traversal helper: append int values to a static buffer */
+static int traversal_buf[32];
+static int traversal_idx;
+
+static void collect_int(void *data) {
+    traversal_buf[traversal_idx++] = *(int *)data;
+}
+
 int main() {
+    /* Basic insertion and data access */
     BinTree *tree = malloc(sizeof(BinTree));
     bintree_init(tree, NULL);
 
@@ -13,22 +30,162 @@ int main() {
     int r1 = 12;
     int r2 = 200;
 
-    bintree_ins_left(tree, NULL, &root);
+    ASSERT_EQ(bintree_ins_left(tree, NULL, &root), 0);
     ASSERT_NOT_NULL(tree->root);
+    ASSERT_EQ(tree->size, 1);
 
-    bintree_ins_left(tree, tree->root, &l1);
-    bintree_ins_left(tree, tree->root->left, &l2);
-    bintree_ins_right(tree, tree->root->left, &r2);
-    bintree_ins_right(tree, tree->root, &r1);
-    bintree_ins_right(tree, tree->root->right, &r2);
-    bintree_ins_left(tree, tree->root->right, &l1);
+    ASSERT_EQ(bintree_ins_left(tree, tree->root, &l1), 0);
+    ASSERT_EQ(bintree_ins_left(tree, tree->root->left, &l2), 0);
+    ASSERT_EQ(bintree_ins_right(tree, tree->root->left, &r2), 0);
+    ASSERT_EQ(bintree_ins_right(tree, tree->root, &r1), 0);
+    ASSERT_EQ(bintree_ins_right(tree, tree->root->right, &r2), 0);
+    ASSERT_EQ(bintree_ins_left(tree, tree->root->right, &l1), 0);
+    ASSERT_EQ(tree->size, 7);
 
     ASSERT_EQ(*(int *)tree->root->data, 0);
     ASSERT_EQ(*(int *)tree->root->left->data, 1);
     ASSERT_EQ(*(int *)tree->root->right->data, 12);
 
+    /* Error: insert root when tree already has one */
+    ASSERT_EQ(bintree_ins_left(tree, NULL, &root), -1);
+    ASSERT_EQ(bintree_ins_right(tree, NULL, &root), -1);
+
+    /* Error: insert left/right where child already exists */
+    ASSERT_EQ(bintree_ins_left(tree, tree->root, &l1), -1);
+    ASSERT_EQ(bintree_ins_right(tree, tree->root, &r1), -1);
+
+    /* Macros */
+    ASSERT_FALSE(bintree_is_eob(tree->root));
+    ASSERT_TRUE(bintree_is_eob(NULL));
+    ASSERT_FALSE(bintree_is_leaf(tree->root));
+    ASSERT_TRUE(bintree_is_leaf(tree->root->left->left));
+
     bintree_destroy(tree);
+    ASSERT_EQ(tree->size, 0);
+    ASSERT_NULL(tree->root);
     free(tree);
 
+    /* Destroy callback */
+    destroy_count = 0;
+    BinTree dt;
+    bintree_init(&dt, counting_destroy);
+
+    int a = 10, b = 20, c = 30;
+    bintree_ins_left(&dt, NULL, &a);
+    bintree_ins_left(&dt, dt.root, &b);
+    bintree_ins_right(&dt, dt.root, &c);
+    ASSERT_EQ(dt.size, 3);
+
+    bintree_destroy(&dt);
+    ASSERT_EQ(destroy_count, 3);
+
+    /* Partial removal with destroy callback */
+    destroy_count = 0;
+    BinTree dt2;
+    bintree_init(&dt2, counting_destroy);
+    bintree_ins_left(&dt2, NULL, &a);
+    bintree_ins_left(&dt2, dt2.root, &b);
+    bintree_ins_right(&dt2, dt2.root, &c);
+    bintree_rem_left(&dt2, dt2.root);
+    ASSERT_EQ(destroy_count, 1);
+    ASSERT_EQ(dt2.size, 2);
+    ASSERT_NULL(dt2.root->left);
+    bintree_destroy(&dt2);
+    ASSERT_EQ(destroy_count, 3);
+
+    /* Traversal: build tree
+     *       1
+     *      / \
+     *     2   3
+     *    / \
+     *   4   5
+     */
+    BinTree tt;
+    bintree_init(&tt, NULL);
+    int v1 = 1, v2 = 2, v3 = 3, v4 = 4, v5 = 5;
+    bintree_ins_left(&tt, NULL, &v1);
+    bintree_ins_left(&tt, tt.root, &v2);
+    bintree_ins_right(&tt, tt.root, &v3);
+    bintree_ins_left(&tt, tt.root->left, &v4);
+    bintree_ins_right(&tt, tt.root->left, &v5);
+
+    /* Preorder: 1 2 4 5 3 */
+    traversal_idx = 0;
+    bintree_traverse(&tt, tt.root, BINTREE_PREORDER, collect_int);
+    ASSERT_EQ(traversal_idx, 5);
+    ASSERT_EQ(traversal_buf[0], 1);
+    ASSERT_EQ(traversal_buf[1], 2);
+    ASSERT_EQ(traversal_buf[2], 4);
+    ASSERT_EQ(traversal_buf[3], 5);
+    ASSERT_EQ(traversal_buf[4], 3);
+
+    /* Inorder: 4 2 5 1 3 */
+    traversal_idx = 0;
+    bintree_traverse(&tt, tt.root, BINTREE_INORDER, collect_int);
+    ASSERT_EQ(traversal_idx, 5);
+    ASSERT_EQ(traversal_buf[0], 4);
+    ASSERT_EQ(traversal_buf[1], 2);
+    ASSERT_EQ(traversal_buf[2], 5);
+    ASSERT_EQ(traversal_buf[3], 1);
+    ASSERT_EQ(traversal_buf[4], 3);
+
+    /* Postorder: 4 5 2 3 1 */
+    traversal_idx = 0;
+    bintree_traverse(&tt, tt.root, BINTREE_POSTORDER, collect_int);
+    ASSERT_EQ(traversal_idx, 5);
+    ASSERT_EQ(traversal_buf[0], 4);
+    ASSERT_EQ(traversal_buf[1], 5);
+    ASSERT_EQ(traversal_buf[2], 2);
+    ASSERT_EQ(traversal_buf[3], 3);
+    ASSERT_EQ(traversal_buf[4], 1);
+
+    /* Traversal on NULL node is a no-op */
+    traversal_idx = 0;
+    bintree_traverse(&tt, NULL, BINTREE_INORDER, collect_int);
+    ASSERT_EQ(traversal_idx, 0);
+
+    bintree_destroy(&tt);
+
+    /* Merge */
+    BinTree ml, mr, mm;
+    int mroot = 100, mlv = 10, mrv = 20;
+    bintree_init(&ml, NULL);
+    bintree_init(&mr, NULL);
+    bintree_ins_left(&ml, NULL, &mlv);
+    bintree_ins_left(&mr, NULL, &mrv);
+
+    ASSERT_EQ(bintree_merge(&mm, &ml, &mr, &mroot), 0);
+    ASSERT_EQ(mm.size, 3);
+    ASSERT_EQ(*(int *)mm.root->data, 100);
+    ASSERT_EQ(*(int *)mm.root->left->data, 10);
+    ASSERT_EQ(*(int *)mm.root->right->data, 20);
+    ASSERT_EQ(ml.size, 0);
+    ASSERT_NULL(ml.root);
+    ASSERT_EQ(mr.size, 0);
+    ASSERT_NULL(mr.root);
+    bintree_destroy(&mm);
+
+    /* Merge: left == right should fail */
+    BinTree self;
+    bintree_init(&self, NULL);
+    bintree_ins_left(&self, NULL, &a);
+    BinTree merged;
+    ASSERT_EQ(bintree_merge(&merged, &self, &self, &b), -1);
+    bintree_destroy(&self);
+
+    /* Size tracking through insertions and removals */
+    BinTree st;
+    bintree_init(&st, NULL);
+    ASSERT_EQ(st.size, 0);
+    bintree_ins_left(&st, NULL, &a);
+    ASSERT_EQ(st.size, 1);
+    bintree_ins_left(&st, st.root, &b);
+    ASSERT_EQ(st.size, 2);
+    bintree_ins_right(&st, st.root, &c);
+    ASSERT_EQ(st.size, 3);
+    bintree_rem_right(&st, st.root);
+    ASSERT_EQ(st.size, 2);
+    bintree_destroy(&st);
+
     TEST_REPORT();
 }
blob - 42b3af2038c8a4078c1f6260551b08f1dd30c637
blob + a7a6a4e8933defb45ed9cdec55444cd752cfc482
--- tests/test_crypto.c
+++ tests/test_crypto.c
@@ -5,42 +5,42 @@
 #include "lfcrypto.h"
 
 int main() {
-    char *in = "BUTT";
-    unsigned char *s = b64_encode(in, strlen(in));
-    ASSERT_STR_EQ((char *)s, "QlVUVA==");
-    free(s);
+    const unsigned char *in = (const unsigned char *)"BUTT";
+    char *cs = b64_encode(in, strlen((const char *)in));
+    ASSERT_STR_EQ(cs, "QlVUVA==");
+    free(cs);
 
-    char *in2 = "a longer base64 test, apparently";
-    s = b64_encode(in2, strlen(in2));
-    ASSERT_STR_EQ((char *)s, "YSBsb25nZXIgYmFzZTY0IHRlc3QsIGFwcGFyZW50bHk=");
-    free(s);
+    const unsigned char *in2 = (const unsigned char *)"a longer base64 test, apparently";
+    cs = b64_encode(in2, strlen((const char *)in2));
+    ASSERT_STR_EQ(cs, "YSBsb25nZXIgYmFzZTY0IHRlc3QsIGFwcGFyZW50bHk=");
+    free(cs);
 
     char *out2 = "YSBsb25nZXIgYmFzZTY0IHRlc3QsIGFwcGFyZW50bHk=";
     size_t s_sz = 0;
-    s = (unsigned char *) b64_decode(out2, strlen(out2), &s_sz);
-    ASSERT_STR_EQ((char *)s, "a longer base64 test, apparently");
-    ASSERT_EQ(strlen((char *)s), s_sz);
-    free(s);
+    unsigned char *us = b64_decode(out2, strlen(out2), &s_sz);
+    ASSERT_STR_EQ((char *)us, "a longer base64 test, apparently");
+    ASSERT_EQ(strlen((char *)us), s_sz);
+    free(us);
 
-    s = hex_decode("DEADBEEF", &s_sz);
+    us = hex_decode("DEADBEEF", &s_sz);
     unsigned char h[4] = { 0xDE, 0xAD, 0xBE, 0xEF };
     for (size_t i = 0; i < 4; ++i) {
-        ASSERT_EQ(s[i], h[i]);
+        ASSERT_EQ(us[i], h[i]);
     }
-    free(s);
+    free(us);
 
-    s = hex_decode("f00f5", &s_sz);
+    us = hex_decode("f00f5", &s_sz);
     unsigned char h2[3] = { 0x0F, 0x00, 0xF5 };
     for (size_t i = 0; i < 3; ++i) {
-        ASSERT_EQ(s[i], h2[i]);
+        ASSERT_EQ(us[i], h2[i]);
     }
-    free(s);
+    free(us);
 
-    s = hex_decode("0xf00f5", &s_sz);
+    us = hex_decode("0xf00f5", &s_sz);
     for (size_t i = 0; i < 3; ++i) {
-        ASSERT_EQ(s[i], h2[i]);
+        ASSERT_EQ(us[i], h2[i]);
     }
-    free(s);
+    free(us);
 
     char *enc_s = hex_encode(h, 4);
     ASSERT_STR_EQ(enc_s, "deadbeef");
@@ -59,11 +59,73 @@ int main() {
 
     unsigned char ua[2] = {0x2, 0xF};
     unsigned char ub[2] = {0x4, 0xE};
-    unsigned int hamming = hamming_distance(ua, ub, 2);
+    int hamming = hamming_distance(ua, ub, 2);
     ASSERT_EQ(hamming, 3);
 
     hamming = hamming_distance_s("this is a test", "wokka wokka!!!");
     ASSERT_EQ(hamming, 37);
 
+    /* -- expanded tests -- */
+
+    /* empty b64 encode/decode */
+    cs = b64_encode((const unsigned char *)"", 0);
+    ASSERT_STR_EQ(cs, "");
+    free(cs);
+
+    us = b64_decode("", 0, &s_sz);
+    ASSERT_EQ(s_sz, (size_t)0);
+    free(us);
+
+    /* empty hex encode */
+    enc_s = hex_encode((const unsigned char *)"", 0);
+    ASSERT_STR_EQ(enc_s, "");
+    free(enc_s);
+
+    /* hex_to_str empty */
+    enc_s = hex_to_str((const unsigned char *)"", 0);
+    ASSERT_STR_EQ(enc_s, "");
+    free(enc_s);
+
+    /* binary roundtrip through hex encode/decode */
+    unsigned char bin[5] = { 0x00, 0xFF, 0x7F, 0x80, 0x01 };
+    enc_s = hex_encode(bin, 5);
+    us = hex_decode(enc_s, &s_sz);
+    ASSERT_EQ(s_sz, (size_t)5);
+    for (size_t i = 0; i < 5; ++i) {
+        ASSERT_EQ(us[i], bin[i]);
+    }
+    free(enc_s);
+    free(us);
+
+    /* hex_decode with NULL sz returns NULL */
+    us = hex_decode("DEADBEEF", NULL);
+    ASSERT_NULL(us);
+
+    /* hex_decode size validation */
+    us = hex_decode("AB", &s_sz);
+    ASSERT_EQ(s_sz, (size_t)1);
+    ASSERT_EQ(us[0], (unsigned char)0xAB);
+    free(us);
+
+    us = hex_decode("ABCD", &s_sz);
+    ASSERT_EQ(s_sz, (size_t)2);
+    free(us);
+
+    /* hamming_distance_s mismatched lengths returns -1 */
+    hamming = hamming_distance_s("short", "longer string");
+    ASSERT_EQ(hamming, -1);
+
+    /* hamming_distance empty strings */
+    hamming = hamming_distance_s("", "");
+    ASSERT_EQ(hamming, 0);
+
+    /* hamming_distance identical strings */
+    hamming = hamming_distance_s("same", "same");
+    ASSERT_EQ(hamming, 0);
+
+    /* repeating_key_xor with zero-length key returns NULL */
+    xor_s = repeating_key_xor((const unsigned char *)"test", 4, (const unsigned char *)"", 0);
+    ASSERT_NULL(xor_s);
+
     TEST_REPORT();
 }
blob - ff52d5816b27025a8199993784ea05baa32931ad
blob + c4ac31580e34498b9875dd6a0bc03e7a2ec977a5
--- tests/test_linkedlist.c
+++ tests/test_linkedlist.c
@@ -1,10 +1,11 @@
 #include <stdio.h>
 #include <stdlib.h>
+#include <string.h>
 #include "lftest.h"
 #include "lflinkedlist.h"
 
 static int match_int(const void *a, const void *b) {
-    return *(const int *)a == *(const int *)b;
+    return *(const int *)a - *(const int *)b;
 }
 
 static int cmp_int(const void *a, const void *b) {
@@ -14,9 +15,9 @@ static int cmp_int(const void *a, const void *b) {
 }
 
 int main() {
-    /* basic insert/remove */
+    /* Basic insertion and size */
     List *list = malloc(sizeof(List));
-    ll_init(list, NULL);
+    ll_init(list, NULL, NULL);
 
     int i = 1;
     int j = 2;
@@ -36,31 +37,165 @@ int main() {
     ll_destroy(list);
     free(list);
 
-    /* ll_find */
+    /* ll_ins_prev */
     list = malloc(sizeof(List));
-    ll_init(list, NULL);
-    list->match = match_int;
+    ll_init(list, NULL, NULL);
 
     int a = 10, b = 20, c = 30;
-    ll_ins_next(list, list->head, &a);
-    ll_ins_next(list, list->tail, &b);
-    ll_ins_next(list, list->tail, &c);
+    ll_ins_next(list, list->head, &b);      /* [20] */
+    ll_ins_prev(list, list->head, &a);      /* [10, 20] */
+    ll_ins_next(list, list->tail, &c);      /* [10, 20, 30] */
 
-    ListNode *found = ll_find(list, &b);
+    ASSERT_EQ(list->size, 3);
+    ASSERT_EQ(*(int *)list->head->data, 10);
+    ASSERT_EQ(*(int *)list->head->next->data, 20);
+    ASSERT_EQ(*(int *)list->tail->data, 30);
+
+    /* ll_ins_prev into middle */
+    int d = 15;
+    ll_ins_prev(list, list->head->next, &d); /* [10, 15, 20, 30] */
+    ASSERT_EQ(list->size, 4);
+    ASSERT_EQ(*(int *)list->head->next->data, 15);
+
+    ll_destroy(list);
+    free(list);
+
+    /* ll_remove direct */
+    list = malloc(sizeof(List));
+    ll_init(list, NULL, NULL);
+
+    int x = 100, y = 200, z = 300;
+    ll_ins_next(list, list->head, &x);
+    ll_ins_next(list, list->tail, &y);
+    ll_ins_next(list, list->tail, &z);
+
+    /* Remove middle node */
+    ll_remove(list, list->head->next, &data);
+    ASSERT_EQ(*(int *)data, 200);
+    ASSERT_EQ(list->size, 2);
+    ASSERT_EQ(*(int *)list->head->data, 100);
+    ASSERT_EQ(*(int *)list->tail->data, 300);
+
+    /* Remove head */
+    ll_remove(list, list->head, &data);
+    ASSERT_EQ(*(int *)data, 100);
+    ASSERT_EQ(list->size, 1);
+    ASSERT_EQ(*(int *)list->head->data, 300);
+
+    /* Remove tail (last element) */
+    ll_remove(list, list->tail, &data);
+    ASSERT_EQ(*(int *)data, 300);
+    ASSERT_EQ(list->size, 0);
+    ASSERT_NULL(list->head);
+    ASSERT_NULL(list->tail);
+
+    ll_destroy(list);
+    free(list);
+
+    /* ll_remove_prev */
+    list = malloc(sizeof(List));
+    ll_init(list, NULL, NULL);
+
+    int p = 1, q = 2, r = 3;
+    ll_ins_next(list, list->head, &p);
+    ll_ins_next(list, list->tail, &q);
+    ll_ins_next(list, list->tail, &r);
+
+    /* Remove prev of tail (should remove middle) */
+    ll_remove_prev(list, list->tail, &data);
+    ASSERT_EQ(*(int *)data, 2);
+    ASSERT_EQ(list->size, 2);
+
+    /* Remove prev of head should fail */
+    ASSERT_EQ(ll_remove_prev(list, list->head, &data), -1);
+
+    ll_destroy(list);
+    free(list);
+
+    /* ll_remove NULL data pointer */
+    list = malloc(sizeof(List));
+    ll_init(list, NULL, NULL);
+
+    int val = 42;
+    ll_ins_next(list, list->head, &val);
+    ASSERT_EQ(ll_remove(list, list->head, NULL), -1);
+    ASSERT_EQ(list->size, 1);
+
+    ll_destroy(list);
+    free(list);
+
+    /* Destroy callback with free (heap-allocated data) */
+    list = malloc(sizeof(List));
+    ll_init(list, free, NULL);
+
+    for (int n = 0; n < 5; n++) {
+        int *heap_val = malloc(sizeof(int));
+        *heap_val = n * 10;
+        ll_ins_next(list, list->tail, heap_val);
+    }
+    ASSERT_EQ(list->size, 5);
+    ll_destroy(list);
+    ASSERT_EQ(list->size, 0);
+    free(list);
+
+    /* Error paths: NULL node for insert when list non-empty */
+    list = malloc(sizeof(List));
+    ll_init(list, NULL, NULL);
+
+    int e = 1;
+    ll_ins_next(list, list->head, &e);
+    ASSERT_EQ(ll_ins_next(list, NULL, &e), -1);
+    ASSERT_EQ(ll_ins_prev(list, NULL, &e), -1);
+
+    /* Error paths: remove from empty list */
+    List *empty = malloc(sizeof(List));
+    ll_init(empty, NULL, NULL);
+    ASSERT_EQ(ll_remove(empty, NULL, &data), -1);
+    ASSERT_EQ(ll_remove_next(empty, NULL, &data), -1);
+    ASSERT_EQ(ll_remove_prev(empty, NULL, &data), -1);
+    free(empty);
+
+    ll_destroy(list);
+    free(list);
+
+    /* ll_find with match callback */
+    list = malloc(sizeof(List));
+    ll_init(list, NULL, match_int);
+
+    int f1 = 10, f2 = 20, f3 = 30;
+    ll_ins_next(list, list->head, &f1);
+    ll_ins_next(list, list->tail, &f2);
+    ll_ins_next(list, list->tail, &f3);
+
+    int search = 20;
+    ListNode *found = ll_find(list, &search);
     ASSERT_NOT_NULL(found);
     ASSERT_EQ(*(int *)found->data, 20);
 
     int missing = 99;
-    found = ll_find(list, &missing);
-    ASSERT_NULL(found);
+    ASSERT_NULL(ll_find(list, &missing));
 
-    /* ll_find with no match callback */
+    /* ll_find with NULL match (pointer comparison) */
     list->match = NULL;
-    found = ll_find(list, &a);
-    ASSERT_NULL(found);
-    list->match = match_int;
+    found = ll_find(list, &f2);
+    ASSERT_NOT_NULL(found);
+    ASSERT_EQ(*(int *)found->data, 20);
 
+    ASSERT_NULL(ll_find(list, &missing));
+    ASSERT_NULL(ll_find(NULL, &search));
+
+    ll_destroy(list);
+    free(list);
+
     /* ll_reverse */
+    list = malloc(sizeof(List));
+    ll_init(list, NULL, NULL);
+
+    int ra = 10, rb = 20, rc = 30;
+    ll_ins_next(list, list->head, &ra);
+    ll_ins_next(list, list->tail, &rb);
+    ll_ins_next(list, list->tail, &rc);
+
     ll_reverse(list);
     ASSERT_EQ(*(int *)list->head->data, 30);
     ASSERT_EQ(*(int *)list->tail->data, 10);
@@ -78,20 +213,12 @@ int main() {
         ASSERT_EQ(*(int *)node->data, expected_fwd[idx++]);
     }
 
-    /* ll_to_array */
-    void **arr = ll_to_array(list);
-    ASSERT_NOT_NULL(arr);
-    ASSERT_EQ(*(int *)arr[0], 30);
-    ASSERT_EQ(*(int *)arr[1], 20);
-    ASSERT_EQ(*(int *)arr[2], 10);
-    free(arr);
-
     ll_destroy(list);
     free(list);
 
     /* ll_sort */
     list = malloc(sizeof(List));
-    ll_init(list, NULL);
+    ll_init(list, NULL, NULL);
 
     int s1 = 3, s2 = 1, s3 = 5, s4 = 2, s5 = 4;
     ll_ins_next(list, list->head, &s1);
@@ -119,7 +246,7 @@ int main() {
 
     /* sort empty and single-element lists */
     ll_destroy(list);
-    ll_init(list, NULL);
+    ll_init(list, NULL, NULL);
     ll_sort(list, cmp_int);
     ASSERT_EQ(list->size, 0);
 
@@ -129,11 +256,47 @@ int main() {
     ASSERT_EQ(list->size, 1);
     ASSERT_EQ(*(int *)list->head->data, 42);
 
-    /* ll_to_array on empty list */
     ll_destroy(list);
-    ll_init(list, NULL);
+    free(list);
+
+    /* ll_to_array: single element */
+    list = malloc(sizeof(List));
+    ll_init(list, NULL, NULL);
+
+    int single = 42;
+    ll_ins_next(list, list->head, &single);
+
+    void **arr = ll_to_array(list);
+    ASSERT_NOT_NULL(arr);
+    ASSERT_EQ(*(int *)arr[0], 42);
+    free(arr);
+
+    ll_destroy(list);
+    free(list);
+
+    /* ll_to_array: empty list */
+    list = malloc(sizeof(List));
+    ll_init(list, NULL, NULL);
+    ASSERT_NULL(ll_to_array(list));
+    ASSERT_NULL(ll_to_array(NULL));
+    ll_destroy(list);
+    free(list);
+
+    /* ll_to_array: multiple elements */
+    list = malloc(sizeof(List));
+    ll_init(list, NULL, NULL);
+
+    int m1 = 1, m2 = 2, m3 = 3;
+    ll_ins_next(list, list->head, &m1);
+    ll_ins_next(list, list->tail, &m2);
+    ll_ins_next(list, list->tail, &m3);
+
     arr = ll_to_array(list);
-    ASSERT_NULL(arr);
+    ASSERT_NOT_NULL(arr);
+    ASSERT_EQ(*(int *)arr[0], 1);
+    ASSERT_EQ(*(int *)arr[1], 2);
+    ASSERT_EQ(*(int *)arr[2], 3);
+    free(arr);
 
     ll_destroy(list);
     free(list);
blob - /dev/null
blob + ba4d898681161690dee7dbb49800e4f0771585a7 (mode 644)
--- /dev/null
+++ tests/test_input.c
@@ -0,0 +1,154 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "lftest.h"
+#include "lfinput.h"
+
+static void test_split_basic(void) {
+    char *s = strdup("one,two,three");
+    size_t n = 0;
+    char **parts = inp_split(s, &n, ",");
+    ASSERT_NOT_NULL(parts);
+    ASSERT_EQ(n, 3);
+    ASSERT_STR_EQ(parts[0], "one");
+    ASSERT_STR_EQ(parts[1], "two");
+    ASSERT_STR_EQ(parts[2], "three");
+    inp_del_split(parts);
+    free(s);
+}
+
+static void test_split_single(void) {
+    char *s = strdup("hello");
+    size_t n = 0;
+    char **parts = inp_split(s, &n, ",");
+    ASSERT_NOT_NULL(parts);
+    ASSERT_EQ(n, 1);
+    ASSERT_STR_EQ(parts[0], "hello");
+    inp_del_split(parts);
+    free(s);
+}
+
+static void test_split_null(void) {
+    size_t n = 0;
+    ASSERT_NULL(inp_split(NULL, &n, ","));
+    char *s = strdup("a,b");
+    ASSERT_NULL(inp_split(s, NULL, ","));
+    free(s);
+    s = strdup("a,b");
+    ASSERT_NULL(inp_split(s, &n, NULL));
+    free(s);
+}
+
+static void test_del_split_null(void) {
+    inp_del_split(NULL);  /* should not crash */
+}
+
+static void test_del_lines_null(void) {
+    inp_del_lines(NULL);  /* should not crash */
+}
+
+static void test_capture_system(void) {
+    char *out = inp_capture_system("echo hello", 0);
+    ASSERT_NOT_NULL(out);
+    ASSERT_STR_EQ(out, "hello\n");
+    free(out);
+}
+
+static void test_capture_system_null(void) {
+    ASSERT_NULL(inp_capture_system(NULL, 0));
+}
+
+static void test_capture_system_large(void) {
+    /* Generate output larger than the default buffer size */
+    char *out = inp_capture_system("seq 1 1000", 32);
+    ASSERT_NOT_NULL(out);
+    /* Check it contains the last line */
+    ASSERT_TRUE(strstr(out, "1000") != NULL);
+    free(out);
+}
+
+static void test_file_roundtrip(void) {
+    /* Write a temp file, read it back */
+    const char *tmppath = "/tmp/libflint_test_input.txt";
+    FILE *f = fopen(tmppath, "w");
+    ASSERT_NOT_NULL(f);
+    fprintf(f, "line1\nline2\nline3\n");
+    fclose(f);
+
+    char *contents = inp_get_input(tmppath);
+    ASSERT_NOT_NULL(contents);
+    ASSERT_STR_EQ(contents, "line1\nline2\nline3\n");
+    free(contents);
+
+    size_t n = 0;
+    char **lines = inp_get_lines(tmppath, &n);
+    ASSERT_NOT_NULL(lines);
+    ASSERT_EQ(n, 3);
+    ASSERT_STR_EQ(lines[0], "line1");
+    ASSERT_STR_EQ(lines[1], "line2");
+    ASSERT_STR_EQ(lines[2], "line3");
+    inp_del_lines(lines);
+
+    remove(tmppath);
+}
+
+static void test_get_binary(void) {
+    const char *tmppath = "/tmp/libflint_test_input.bin";
+    FILE *f = fopen(tmppath, "wb");
+    ASSERT_NOT_NULL(f);
+    unsigned char data[] = {0x00, 0x01, 0x02, 0xFF};
+    fwrite(data, 1, sizeof(data), f);
+    fclose(f);
+
+    size_t fsz = 0;
+    unsigned char *buf = inp_get_binary(tmppath, &fsz);
+    ASSERT_NOT_NULL(buf);
+    ASSERT_EQ(fsz, 4);
+    ASSERT_EQ(buf[0], 0x00);
+    ASSERT_EQ(buf[3], 0xFF);
+    free(buf);
+
+    remove(tmppath);
+}
+
+static void test_get_ints(void) {
+    const char *tmppath = "/tmp/libflint_test_ints.txt";
+    FILE *f = fopen(tmppath, "w");
+    ASSERT_NOT_NULL(f);
+    fprintf(f, "10\n20\n30\n");
+    fclose(f);
+
+    size_t n = 0;
+    int *ints = inp_get_ints(tmppath, &n);
+    ASSERT_NOT_NULL(ints);
+    ASSERT_EQ(n, 3);
+    ASSERT_EQ(ints[0], 10);
+    ASSERT_EQ(ints[1], 20);
+    ASSERT_EQ(ints[2], 30);
+    free(ints);
+
+    remove(tmppath);
+}
+
+static void test_get_ints_null(void) {
+    size_t n = 0;
+    ASSERT_NULL(inp_get_ints(NULL, &n));
+    ASSERT_NULL(inp_get_ints("/tmp/nonexistent_libflint", NULL));
+}
+
+int main(void) {
+    test_split_basic();
+    test_split_single();
+    test_split_null();
+    test_del_split_null();
+    test_del_lines_null();
+    test_capture_system();
+    test_capture_system_null();
+    test_capture_system_large();
+    test_file_roundtrip();
+    test_get_binary();
+    test_get_ints();
+    test_get_ints_null();
+
+    TEST_REPORT();
+}
blob - 48c994ca9950ca8f7a752bc05da4cd93b1d76514
blob + 262b329f842039d67b578681399b563fef392c93
--- tests/test_macos.c
+++ tests/test_macos.c
@@ -8,16 +8,39 @@
 
 int main() {
     pid_t pid = getpid();
+
+    /* new_ProcessData returns zeroed memory with sentinel */
     ProcessData *pd = new_ProcessData();
     ASSERT_NOT_NULL(pd);
+    ASSERT_EQ(pd->percent_cpu, 0.0);
+    ASSERT_EQ(pd->virtual_memory, 0);
+    ASSERT_EQ(pd->resident_memory, 0);
+    ASSERT_EQ(pd->total_user_time, 0.0);
+    ASSERT_EQ(pd->total_kernel_time, 0.0);
 
-    for (int i = 0; i < 2; i++) {
-        update_process(pid, pd);
-        sleep(1);
-    }
+    /* first update succeeds and populates fields */
+    int rc = update_process(pid, pd);
+    ASSERT_EQ(rc, 0);
+    ASSERT_TRUE(pd->resident_memory > 0);
+    ASSERT_TRUE(pd->virtual_memory > 0);
+    ASSERT_TRUE(pd->percent_cpu == 0.0);
+
+    sleep(1);
+
+    /* second update succeeds */
+    rc = update_process(pid, pd);
+    ASSERT_EQ(rc, 0);
     ASSERT_TRUE(pd->percent_cpu >= 0.0);
 
-    free(pd);
+    /* error path: invalid PID returns -1 */
+    rc = update_process(-1, pd);
+    ASSERT_EQ(rc, -1);
+
+    destroy_ProcessData(pd);
+
+    /* destroy_ProcessData handles NULL safely */
+    destroy_ProcessData(NULL);
+
     TEST_REPORT();
 }
 #else
blob - 50629b4a980d0cab64d560728ae38875b324dfb6
blob + b55e738cf14edadb083270493ac21260e5cddfd6
--- tests/test_math.c
+++ tests/test_math.c
@@ -1,28 +1,41 @@
+#include <limits.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include "lftest.h"
 #include "lfmath.h"
 
 int main() {
-    int i = 1, j = 2;
-    ASSERT_EQ(max_int(i, j), j);
-    ASSERT_EQ(min_int(i, j), i);
-
-    char *s = "10101101";
-    ASSERT_EQ(binstr_to_int(s), 173);
-
-    char *s2 = "1010_1101";
-    ASSERT_EQ(binstr_to_int(s2), 173);
-
-    i = 10;
-    i = clamp_int(i, 2, 5);
-    ASSERT_EQ(i, 5);
-
     /* abs_int */
     ASSERT_EQ(abs_int(5), 5);
     ASSERT_EQ(abs_int(-5), 5);
     ASSERT_EQ(abs_int(0), 0);
+    ASSERT_EQ(abs_int(INT_MIN), INT_MAX);
 
+    /* max/min */
+    ASSERT_EQ(max_int(1, 2), 2);
+    ASSERT_EQ(min_int(1, 2), 1);
+
+    /* binstr_to_int */
+    ASSERT_EQ(binstr_to_int("10101101"), 173);
+    ASSERT_EQ(binstr_to_int("1010_1101"), 173);
+    ASSERT_EQ(binstr_to_int(NULL), -1);
+
+    /* clamp_int - all branches */
+    ASSERT_EQ(clamp_int(10, 2, 5), 5);   /* above high */
+    ASSERT_EQ(clamp_int(1, 2, 5), 2);    /* below low */
+    ASSERT_EQ(clamp_int(3, 2, 5), 3);    /* in range */
+    ASSERT_EQ(clamp_int(3, 5, 2), 3);    /* low > high, swap */
+    ASSERT_EQ(clamp_int(10, 5, 2), 5);   /* low > high, above */
+
+    /* is_power_of_two */
+    ASSERT_TRUE(is_power_of_two(1));
+    ASSERT_TRUE(is_power_of_two(2));
+    ASSERT_TRUE(is_power_of_two(4));
+    ASSERT_TRUE(is_power_of_two(1024));
+    ASSERT_FALSE(is_power_of_two(0));
+    ASSERT_FALSE(is_power_of_two(3));
+    ASSERT_FALSE(is_power_of_two(-1));
+
     /* gcd */
     ASSERT_EQ(gcd(12, 8), 4);
     ASSERT_EQ(gcd(7, 13), 1);
@@ -51,11 +64,36 @@ int main() {
     ASSERT_EQ(isqrt(100), 10);
     ASSERT_EQ(isqrt(-1), -1);
 
+    /* bresenham coordinate verification */
     size_t sz = 0;
     Point *line = bresenham(0, 0, 2, 5, &sz);
+    ASSERT_NOT_NULL(line);
     ASSERT_TRUE(sz > 0);
+    /* first point */
+    ASSERT_EQ(line[0].x, 0);
+    ASSERT_EQ(line[0].y, 0);
+    /* last point */
+    ASSERT_EQ(line[sz - 1].x, 2);
+    ASSERT_EQ(line[sz - 1].y, 5);
+    free(line);
+
+    /* bresenham single point */
+    line = bresenham(3, 3, 3, 3, &sz);
     ASSERT_NOT_NULL(line);
+    ASSERT_EQ(sz, 1);
+    ASSERT_EQ(line[0].x, 3);
+    ASSERT_EQ(line[0].y, 3);
     free(line);
 
+    /* bresenham_p */
+    Point p1 = Point_new(0, 0);
+    Point p2 = Point_new(4, 0);
+    line = bresenham_p(p1, p2, &sz);
+    ASSERT_NOT_NULL(line);
+    ASSERT_EQ(sz, 5);
+    ASSERT_EQ(line[0].x, 0);
+    ASSERT_EQ(line[4].x, 4);
+    free(line);
+
     TEST_REPORT();
 }
blob - 6fc0580bc98896e1b56234a8c7a163f12e7c9289
blob + 43153093ad2f9e586c56f012db8fb3943526d80a
--- tests/test_memory.c
+++ tests/test_memory.c
@@ -5,7 +5,7 @@
 
 int main() {
     ArenaAllocator *arena = malloc(sizeof(ArenaAllocator));
-    arena_init(arena, 1024);
+    ASSERT_EQ(arena_init(arena, 1024), 0);
 
     int *i1 = arena_malloc(arena, sizeof(int));
     int *i2 = arena_malloc(arena, sizeof(int));
@@ -43,24 +43,31 @@ int main() {
     ASSERT_NOT_NULL(after_clear);
 
     /* Test arena_resize_buf */
-    arena_resize_buf(arena, 2048);
+    ASSERT_EQ(arena_resize_buf(arena, 2048), 0);
     void *big_alloc = arena_malloc(arena, 1500);
     ASSERT_NOT_NULL(big_alloc);
 
+    /* Test arena_resize_buf rejects shrink below offset_cur */
+    ASSERT_EQ(arena_resize_buf(arena, 1), -1);
+
+    /* Test arena_init returns -1 for NULL */
+    ASSERT_EQ(arena_init(NULL, 64), -1);
+
     arena_free(arena);
     free(arena);
 
     /* Test stack-allocated arena */
     ArenaAllocator stack_arena;
-    arena_init(&stack_arena, 256);
+    ASSERT_EQ(arena_init(&stack_arena, 256), 0);
     int *si = arena_malloc(&stack_arena, sizeof(int));
     ASSERT_NOT_NULL(si);
     *si = 99;
     ASSERT_EQ(*si, 99);
     arena_free(&stack_arena);
 
+    /* Pool tests */
     PoolAllocator *pool = malloc(sizeof(PoolAllocator));
-    pool_init(pool, 64, 16, LF_DEFAULT_ALIGNMENT);
+    ASSERT_EQ(pool_init(pool, 64, 16, LF_DEFAULT_ALIGNMENT), 0);
     void *a = pool_alloc(pool);
     void *b = pool_alloc(pool);
     void *c = pool_alloc(pool);
@@ -77,7 +84,7 @@ int main() {
 
     /* Test pool_free_all resets properly */
     pool_free_all(pool);
-    size_t chunk_count = 64 / 16;
+    size_t chunk_count = pool->buf_sz / pool->chunk_size;
     ASSERT_EQ(pool_count_available(pool), chunk_count);
 
     /* Allocate again after free_all to verify no corruption */
@@ -87,7 +94,50 @@ int main() {
     ASSERT_NOT_NULL(b);
 
     pool_destroy(pool);
+    free(pool);
 
+    /* Pool exhaustion: allocating more chunks than capacity returns NULL */
+    PoolAllocator pool_ex;
+    ASSERT_EQ(pool_init(&pool_ex, 64, 16, LF_DEFAULT_ALIGNMENT), 0);
+    size_t ex_count = pool_ex.buf_sz / pool_ex.chunk_size;
+    for (size_t i = 0; i < ex_count; ++i) {
+        ASSERT_NOT_NULL(pool_alloc(&pool_ex));
+    }
+    ASSERT_NULL(pool_alloc(&pool_ex));
+    pool_destroy(&pool_ex);
+
+    /* Pool free with bad pointers (outside buffer range) */
+    PoolAllocator pool_bad;
+    ASSERT_EQ(pool_init(&pool_bad, 64, 16, LF_DEFAULT_ALIGNMENT), 0);
+    size_t before = pool_count_available(&pool_bad);
+    int dummy = 0;
+    pool_free(&pool_bad, &dummy);           /* pointer outside pool */
+    pool_free(&pool_bad, NULL);             /* NULL pointer */
+    ASSERT_EQ(pool_count_available(&pool_bad), before); /* count unchanged */
+    pool_destroy(&pool_bad);
+
+    /* Pool free + realloc cycle */
+    PoolAllocator pool_cycle;
+    ASSERT_EQ(pool_init(&pool_cycle, 64, 16, LF_DEFAULT_ALIGNMENT), 0);
+    /* drain all chunks first */
+    size_t cycle_count = pool_cycle.buf_sz / pool_cycle.chunk_size;
+    for (size_t i = 0; i < cycle_count; ++i) {
+        pool_alloc(&pool_cycle);
+    }
+    ASSERT_EQ(pool_count_available(&pool_cycle), 0);
+    /* free one, then alloc -- must get it back */
+    void *p1 = &pool_cycle.buf[pool_cycle.aligned_start]; /* first chunk addr */
+    pool_free(&pool_cycle, p1);
+    void *p2 = pool_alloc(&pool_cycle);
+    ASSERT_NOT_NULL(p2);
+    ASSERT_EQ(p1, p2); /* should reuse the freed chunk */
+    pool_destroy(&pool_cycle);
+
+    /* Pool init with bad params */
+    PoolAllocator pool_bp;
+    ASSERT_EQ(pool_init(&pool_bp, 4, 16, LF_DEFAULT_ALIGNMENT), -1);  /* buf too small */
+    ASSERT_EQ(pool_init(NULL, 64, 16, LF_DEFAULT_ALIGNMENT), -1);     /* NULL allocator */
+
     /* arena save/restore */
     ArenaAllocator save_arena;
     arena_init(&save_arena, 512);
blob - 9fa89c42d0b00b733699a8d0ad057431232d3eda
blob + b1c95cf8ed22b0e2919ac7558c9b4e65077c41cf
--- tests/test_network.c
+++ tests/test_network.c
@@ -46,17 +46,46 @@ static void *udp_server_thread(void *vargp) {
     return NULL;
 }
 
+static void dummy_handler(Server *s) {
+    (void)s;
+}
+
 int main() {
+    /* Test new_server with bad port returns NULL */
+    Server *bad = new_server(SERVERTYPE_TCP, "notaport", dummy_handler);
+    ASSERT_NULL(bad);
+
+    /* Test new_server creates valid TCP server and delete_server cleans up */
+    Server *tcp = new_server(SERVERTYPE_TCP, "18640", dummy_handler);
+    ASSERT_NOT_NULL(tcp);
+    ASSERT_EQ(tcp->server_type, SERVERTYPE_TCP);
+    ASSERT_EQ(tcp->port, 18640);
+    ASSERT_TRUE(tcp->fd >= 0);
+    delete_server(tcp);
+
+    /* Test new_server creates valid UDP server and delete_server cleans up */
+    Server *udp = new_server(SERVERTYPE_UDP, "18641", dummy_handler);
+    ASSERT_NOT_NULL(udp);
+    ASSERT_EQ(udp->server_type, SERVERTYPE_UDP);
+    ASSERT_EQ(udp->port, 18641);
+    ASSERT_TRUE(udp->fd >= 0);
+    delete_server(udp);
+
+    /* Test delete_server with NULL doesn't crash */
+    delete_server(NULL);
+
+    /* TCP echo test */
     pthread_t srv_tid;
     pthread_create(&srv_tid, NULL, tcp_server_thread, NULL);
 
     sleep(1);
-    const char *s = capture_system("echo hello | nc localhost 18632", 0);
+    char *s = inp_capture_system("echo hello | nc localhost 18632", 0);
     ASSERT_STR_EQ(s, NET_MSG);
-    free((char *) s);
+    free(s);
 
     pthread_join(srv_tid, NULL);
 
+    /* UDP test */
     pthread_create(&srv_tid, NULL, udp_server_thread, NULL);
     sleep(1);
     system("echo hello | nc localhost 18633");
blob - 57abe442572fbbbc6526d2271a7729463d8b5d61
blob + 6247350fe666d76a5521f0f7b9a2cbae9382810c
--- tests/test_parsing.c
+++ tests/test_parsing.c
@@ -7,5 +7,20 @@ int main() {
     char *english = "This is an English sentence!";
     ASSERT_TRUE(simple_english_scoring(english) > simple_english_scoring(nonsense));
 
+    /* NULL input */
+    ASSERT_EQ(simple_english_scoring(NULL), -1);
+
+    /* empty string */
+    ASSERT_EQ(simple_english_scoring(""), 0);
+
+    /* known absolute score: "the" = t(12) + h(5) + e(13) = 30 */
+    ASSERT_EQ(simple_english_scoring("the"), 30);
+
+    /* single space scores 7 */
+    ASSERT_EQ(simple_english_scoring(" "), 7);
+
+    /* unknown chars score 0 */
+    ASSERT_EQ(simple_english_scoring("!!!"), 0);
+
     TEST_REPORT();
 }
blob - 89facce7f60029c4e811cf64a51698d0f9ac07fc
blob + 9006ae42082a6ff913cb0eee7cc7faaa21748fe8
--- tests/test_queue.c
+++ tests/test_queue.c
@@ -3,7 +3,15 @@
 #include "lftest.h"
 #include "lfqueue.h"
 
+static int destroyed_count;
+
+static void counting_destroy(void *data) {
+    (void)data;
+    destroyed_count++;
+}
+
 int main() {
+    /* basic enqueue/dequeue FIFO */
     Queue *q = malloc(sizeof(Queue));
     queue_init(q, NULL);
 
@@ -11,32 +19,100 @@ int main() {
     queue_enqueue(q, &a);
     queue_enqueue(q, &b);
     queue_enqueue(q, &c);
-    ASSERT_EQ(q->size, 3);
+    ASSERT_EQ(queue_size(q), 3);
+    ASSERT_FALSE(queue_is_empty(q));
 
-    /* peek returns front without removing */
     int *p = queue_peek(q);
     ASSERT_EQ(*p, 1);
-    ASSERT_EQ(q->size, 3);
+    ASSERT_EQ(queue_size(q), 3);
 
-    /* FIFO order */
     queue_dequeue(q, (void **)&p);
     ASSERT_EQ(*p, 1);
     queue_dequeue(q, (void **)&p);
     ASSERT_EQ(*p, 2);
     queue_dequeue(q, (void **)&p);
     ASSERT_EQ(*p, 3);
-    ASSERT_EQ(q->size, 0);
+    ASSERT_EQ(queue_size(q), 0);
+    ASSERT_TRUE(queue_is_empty(q));
 
     /* peek on empty queue */
     p = queue_peek(q);
     ASSERT_NULL(p);
 
-    /* dequeue on empty queue */
+    /* dequeue on empty queue returns -1 */
     int ret = queue_dequeue(q, (void **)&p);
     ASSERT_EQ(ret, -1);
 
     queue_destroy(q);
     free(q);
 
+    /* return values from enqueue/dequeue */
+    q = malloc(sizeof(Queue));
+    queue_init(q, NULL);
+
+    ret = queue_enqueue(q, &a);
+    ASSERT_EQ(ret, 0);
+    ret = queue_dequeue(q, (void **)&p);
+    ASSERT_EQ(ret, 0);
+    ASSERT_EQ(*p, 1);
+
+    queue_destroy(q);
+    free(q);
+
+    /* destroy callback */
+    q = malloc(sizeof(Queue));
+    destroyed_count = 0;
+    queue_init(q, counting_destroy);
+
+    int *d1 = malloc(sizeof(int));
+    int *d2 = malloc(sizeof(int));
+    *d1 = 10; *d2 = 20;
+    queue_enqueue(q, d1);
+    queue_enqueue(q, d2);
+
+    queue_destroy(q);
+    ASSERT_EQ(destroyed_count, 2);
+    free(d1); free(d2);
+    free(q);
+
+    /* enqueue after drain */
+    q = malloc(sizeof(Queue));
+    queue_init(q, NULL);
+
+    queue_enqueue(q, &a);
+    queue_dequeue(q, (void **)&p);
+    ASSERT_TRUE(queue_is_empty(q));
+
+    queue_enqueue(q, &b);
+    ASSERT_EQ(queue_size(q), 1);
+    p = queue_peek(q);
+    ASSERT_EQ(*p, 2);
+
+    queue_dequeue(q, (void **)&p);
+    ASSERT_EQ(*p, 2);
+    ASSERT_TRUE(queue_is_empty(q));
+
+    queue_destroy(q);
+    free(q);
+
+    /* single-element operations */
+    q = malloc(sizeof(Queue));
+    queue_init(q, NULL);
+
+    int v = 77;
+    queue_enqueue(q, &v);
+    ASSERT_EQ(queue_size(q), 1);
+
+    p = queue_peek(q);
+    ASSERT_EQ(*p, 77);
+
+    ret = queue_dequeue(q, (void **)&p);
+    ASSERT_EQ(ret, 0);
+    ASSERT_EQ(*p, 77);
+    ASSERT_TRUE(queue_is_empty(q));
+
+    queue_destroy(q);
+    free(q);
+
     TEST_REPORT();
 }
blob - 278cc30436e944c2424022d7e15b7ed2b6d3e657
blob + 894da44be9bdb69a88e1342314d9b31fa360dc48
--- tests/test_set.c
+++ tests/test_set.c
@@ -1,13 +1,19 @@
 #include <stdio.h>
 #include <stdlib.h>
+#include <string.h>
 #include "lftest.h"
 #include "lfset.h"
 
 static int int_match(const void *a, const void *b) {
-    return *((int *) a) == *((int *) b);
+    return *(const int *)a - *(const int *)b;
 }
 
+static void free_data(void *data) {
+    free(data);
+}
+
 int main() {
+    /* Basic insert and duplicate rejection */
     Set *set = malloc(sizeof(Set));
     set_init(set, int_match, NULL);
 
@@ -15,12 +21,34 @@ int main() {
     int j = 2;
     int k = 2;
 
-    set_insert(set, &i);
+    ASSERT_EQ(set_insert(set, &i), 0);
+    ASSERT_EQ(set_insert(set, &j), 0);
+    ASSERT_EQ(set_insert(set, &k), 1);  /* duplicate returns 1 */
+
+    ASSERT_EQ(set_size(set), 2);
+
+    /* set_is_member */
+    ASSERT_TRUE(set_is_member(set, &i));
+    ASSERT_TRUE(set_is_member(set, &j));
+    int missing = 99;
+    ASSERT_FALSE(set_is_member(set, &missing));
+
+    /* set_remove */
+    void *removed = &j;
+    ASSERT_EQ(set_remove(set, &removed), 0);
+    ASSERT_EQ(set_size(set), 1);
+    ASSERT_FALSE(set_is_member(set, &j));
+
+    /* set_remove: not found */
+    removed = &missing;
+    ASSERT_EQ(set_remove(set, &removed), -1);
+
+    /* set_remove: NULL data pointer */
+    ASSERT_EQ(set_remove(set, NULL), -1);
+
+    /* Re-insert removed element for set operations */
     set_insert(set, &j);
-    set_insert(set, &k);
 
-    ASSERT_EQ(set->size, 2);  /* duplicate should not be inserted */
-
     int i2 = 1;
     int j2 = 4;
 
@@ -37,20 +65,60 @@ int main() {
     set_difference(set_d, set, set2);
     set_intersection(set_i, set, set2);
 
-    ASSERT_EQ(set_u->size, 3);  /* {1, 2, 4} */
-    ASSERT_EQ(set_d->size, 1);  /* {2} */
-    ASSERT_EQ(set_i->size, 1);  /* {1} */
+    ASSERT_EQ(set_size(set_u), 3);  /* {1, 2, 4} */
+    ASSERT_EQ(set_size(set_d), 1);  /* {2} */
+    ASSERT_EQ(set_size(set_i), 1);  /* {1} */
 
+    /* set_is_subset */
+    Set *sub = malloc(sizeof(Set));
+    set_init(sub, int_match, NULL);
+    set_insert(sub, &i);  /* {1} is subset of {1, 2} */
+    ASSERT_TRUE(set_is_subset(sub, set));
+    ASSERT_FALSE(set_is_subset(set, sub));
+
+    /* set_is_equal */
+    Set *eq = malloc(sizeof(Set));
+    set_init(eq, int_match, NULL);
+    set_insert(eq, &i);
+    set_insert(eq, &j);
+    ASSERT_TRUE(set_is_equal(set, eq));
+    ASSERT_FALSE(set_is_equal(set, sub));
+
     set_destroy(set);
     set_destroy(set2);
     set_destroy(set_u);
     set_destroy(set_i);
     set_destroy(set_d);
+    set_destroy(sub);
+    set_destroy(eq);
     free(set);
     free(set2);
     free(set_u);
     free(set_i);
     free(set_d);
+    free(sub);
+    free(eq);
 
+    /* Heap-allocated data with destroy callback */
+    Set *hset = malloc(sizeof(Set));
+    set_init(hset, int_match, free_data);
+
+    int *a = malloc(sizeof(int)); *a = 10;
+    int *b = malloc(sizeof(int)); *b = 20;
+    int *c = malloc(sizeof(int)); *c = 10;  /* duplicate value */
+
+    ASSERT_EQ(set_insert(hset, a), 0);
+    ASSERT_EQ(set_insert(hset, b), 0);
+    ASSERT_EQ(set_insert(hset, c), 1);  /* duplicate */
+    free(c);  /* caller responsible for rejected data */
+
+    ASSERT_EQ(set_size(hset), 2);
+    ASSERT_TRUE(set_is_member(hset, a));
+    ASSERT_TRUE(set_is_member(hset, b));
+
+    /* destroy frees heap data */
+    set_destroy(hset);
+    free(hset);
+
     TEST_REPORT();
 }
blob - ae60a2174307462510938d3759e5dd662d312bdf
blob + 0ef2e1eef7f25d0c07f3de0074bcfb23e14e4da0
--- tests/test_stack.c
+++ tests/test_stack.c
@@ -3,25 +3,110 @@
 #include "lftest.h"
 #include "lfstack.h"
 
+static int destroyed_count;
+
+static void counting_destroy(void *data) {
+    (void)data;
+    destroyed_count++;
+}
+
 int main() {
+    /* basic push/pop */
     Stack *stack = malloc(sizeof(Stack));
     stack_init(stack, NULL);
 
     int a = 1, b = 2;
     stack_push(stack, &a);
     stack_push(stack, &b);
-    ASSERT_EQ(stack->size, 2);
+    ASSERT_EQ(stack_size(stack), 2);
+    ASSERT_FALSE(stack_is_empty(stack));
 
     int *p = NULL;
-    stack_pop(stack, (void **) &p);
+    stack_pop(stack, (void **)&p);
     ASSERT_EQ(*p, 2);
 
-    stack_pop(stack, (void **) &p);
+    stack_pop(stack, (void **)&p);
     ASSERT_EQ(*p, 1);
-    ASSERT_EQ(stack->size, 0);
+    ASSERT_EQ(stack_size(stack), 0);
+    ASSERT_TRUE(stack_is_empty(stack));
 
     stack_destroy(stack);
     free(stack);
 
+    /* peek */
+    stack = malloc(sizeof(Stack));
+    stack_init(stack, NULL);
+
+    ASSERT_NULL(stack_peek(stack));
+
+    int x = 42;
+    stack_push(stack, &x);
+    p = stack_peek(stack);
+    ASSERT_NOT_NULL(p);
+    ASSERT_EQ(*p, 42);
+    ASSERT_EQ(stack_size(stack), 1);
+
+    stack_destroy(stack);
+    free(stack);
+
+    /* empty pop returns -1 */
+    stack = malloc(sizeof(Stack));
+    stack_init(stack, NULL);
+
+    int ret = stack_pop(stack, (void **)&p);
+    ASSERT_EQ(ret, -1);
+
+    stack_destroy(stack);
+    free(stack);
+
+    /* return values from push/pop */
+    stack = malloc(sizeof(Stack));
+    stack_init(stack, NULL);
+
+    ret = stack_push(stack, &a);
+    ASSERT_EQ(ret, 0);
+    ret = stack_pop(stack, (void **)&p);
+    ASSERT_EQ(ret, 0);
+
+    stack_destroy(stack);
+    free(stack);
+
+    /* destroy callback */
+    stack = malloc(sizeof(Stack));
+    destroyed_count = 0;
+    stack_init(stack, counting_destroy);
+
+    int *d1 = malloc(sizeof(int));
+    int *d2 = malloc(sizeof(int));
+    int *d3 = malloc(sizeof(int));
+    *d1 = 10; *d2 = 20; *d3 = 30;
+    stack_push(stack, d1);
+    stack_push(stack, d2);
+    stack_push(stack, d3);
+
+    stack_destroy(stack);
+    ASSERT_EQ(destroyed_count, 3);
+    free(d1); free(d2); free(d3);
+    free(stack);
+
+    /* single-element push and pop */
+    stack = malloc(sizeof(Stack));
+    stack_init(stack, NULL);
+
+    int v = 99;
+    stack_push(stack, &v);
+    ASSERT_EQ(stack_size(stack), 1);
+
+    p = stack_peek(stack);
+    ASSERT_EQ(*p, 99);
+
+    ret = stack_pop(stack, (void **)&p);
+    ASSERT_EQ(ret, 0);
+    ASSERT_EQ(*p, 99);
+    ASSERT_TRUE(stack_is_empty(stack));
+
+    stack_destroy(stack);
+    free(stack);
+
     TEST_REPORT();
 }
blob - 8e29b506d9e134285c55909bb2bf72a0651d9280
blob + 3f0b829a6caea9cf9d0e8fbca2ddd54a7a8e8740
--- tests/test_string.c
+++ tests/test_string.c
@@ -5,7 +5,7 @@
 #include "lfstring.h"
 
 int main() {
-    /* existing find_substrings tests */
+    /* str_find tests */
     const char *haystack =
             "Test one two one and also maybe two but not Gabe's least favorite number, which is not one.";
     const char *needles[] = {
@@ -16,39 +16,81 @@ int main() {
 
     size_t sub_sz = 0;
     size_t *subs = NULL;
-    find_substrings(haystack, needles[0], &sub_sz, &subs);
+    str_find(haystack, needles[0], &sub_sz, &subs);
 
     ASSERT_EQ(sub_sz, 3);
     ASSERT_EQ(subs[0], 5);
     ASSERT_EQ(subs[1], 13);
     ASSERT_EQ(subs[2], 87);
 
-    char *s = substr(haystack, subs[0], strlen(needles[0]));
+    char *s = str_substr(haystack, subs[0], strlen(needles[0]));
     ASSERT_STR_EQ(s, needles[0]);
 
     free(s);
     free(subs);
     subs = NULL;
 
-    find_substrings(haystack, needles[1], &sub_sz, &subs);
+    str_find(haystack, needles[1], &sub_sz, &subs);
     ASSERT_EQ(sub_sz, 2);
     ASSERT_EQ(subs[0], 9);
 
     free(subs);
     subs = NULL;
 
-    find_substrings("test one two", "nope", &sub_sz, &subs);
+    str_find("test one two", "nope", &sub_sz, &subs);
     ASSERT_EQ(sub_sz, 0);
     ASSERT_NULL(subs);
     free(subs);
     subs = NULL;
 
-    find_substrings("123", "nopes", &sub_sz, &subs);
+    str_find("123", "nopes", &sub_sz, &subs);
     ASSERT_EQ(sub_sz, 0);
     ASSERT_NULL(subs);
     free(subs);
     subs = NULL;
 
+    /* str_find: empty needle matches every position */
+    str_find("abc", "", &sub_sz, &subs);
+    ASSERT_EQ(sub_sz, 4);
+    free(subs);
+    subs = NULL;
+
+    /* str_find: empty haystack with empty needle */
+    str_find("", "", &sub_sz, &subs);
+    ASSERT_EQ(sub_sz, 1);
+    free(subs);
+    subs = NULL;
+
+    /* str_find: empty haystack with non-empty needle */
+    str_find("", "abc", &sub_sz, &subs);
+    ASSERT_EQ(sub_sz, 0);
+    ASSERT_NULL(subs);
+    subs = NULL;
+
+    /* str_substr edge cases */
+    s = str_substr("hello", 0, 5);
+    ASSERT_STR_EQ(s, "hello");
+    free(s);
+
+    s = str_substr("hello", 0, 0);
+    ASSERT_STR_EQ(s, "");
+    free(s);
+
+    s = str_substr("hello", 5, 0);
+    ASSERT_STR_EQ(s, "");
+    free(s);
+
+    s = str_substr("hello", 4, 1);
+    ASSERT_STR_EQ(s, "o");
+    free(s);
+
+    /* substr beyond bounds */
+    s = str_substr("hello", 3, 5);
+    ASSERT_NULL(s);
+
+    s = str_substr("hello", 0, 6);
+    ASSERT_NULL(s);
+
     /* str_trim */
     s = str_trim("  hello world  ");
     ASSERT_STR_EQ(s, "hello world");
@@ -66,6 +108,10 @@ int main() {
     ASSERT_STR_EQ(s, "hi");
     free(s);
 
+    s = str_trim("");
+    ASSERT_STR_EQ(s, "");
+    free(s);
+
     /* str_join */
     const char *parts[] = {"foo", "bar", "baz"};
     s = str_join(parts, 3, ", ");
@@ -89,6 +135,8 @@ int main() {
     ASSERT_TRUE(str_starts_with("hello", "hello"));
     ASSERT_FALSE(str_starts_with("hello", "world"));
     ASSERT_TRUE(str_starts_with("hello", ""));
+    ASSERT_TRUE(str_starts_with("", ""));
+    ASSERT_FALSE(str_starts_with("", "a"));
 
     /* str_ends_with */
     ASSERT_TRUE(str_ends_with("hello world", "world"));
@@ -96,6 +144,8 @@ int main() {
     ASSERT_FALSE(str_ends_with("hello", "world"));
     ASSERT_TRUE(str_ends_with("hello", ""));
     ASSERT_FALSE(str_ends_with("hi", "longer"));
+    ASSERT_TRUE(str_ends_with("", ""));
+    ASSERT_FALSE(str_ends_with("", "a"));
 
     /* str_replace */
     s = str_replace("foo bar foo baz foo", "foo", "qux");
@@ -114,6 +164,16 @@ int main() {
     ASSERT_STR_EQ(s, "");
     free(s);
 
+    /* str_replace with empty old string returns copy */
+    s = str_replace("hello", "", "X");
+    ASSERT_STR_EQ(s, "hello");
+    free(s);
+
+    /* str_replace on empty input */
+    s = str_replace("", "a", "b");
+    ASSERT_STR_EQ(s, "");
+    free(s);
+
     /* str_to_upper / str_to_lower */
     s = str_to_upper("hello World 123");
     ASSERT_STR_EQ(s, "HELLO WORLD 123");
@@ -123,5 +183,71 @@ int main() {
     ASSERT_STR_EQ(s, "hello world 123");
     free(s);
 
+    s = str_to_upper("");
+    ASSERT_STR_EQ(s, "");
+    free(s);
+
+    s = str_to_lower("");
+    ASSERT_STR_EQ(s, "");
+    free(s);
+
+    /* str_split */
+    int count = 0;
+    char **split = str_split("foo,bar,baz", ",", &count);
+    ASSERT_EQ(count, 3);
+    ASSERT_STR_EQ(split[0], "foo");
+    ASSERT_STR_EQ(split[1], "bar");
+    ASSERT_STR_EQ(split[2], "baz");
+    for (int i = 0; i < count; i++) free(split[i]);
+    free(split);
+
+    split = str_split("hello", ",", &count);
+    ASSERT_EQ(count, 1);
+    ASSERT_STR_EQ(split[0], "hello");
+    free(split[0]);
+    free(split);
+
+    split = str_split(",a,,b,", ",", &count);
+    ASSERT_EQ(count, 5);
+    ASSERT_STR_EQ(split[0], "");
+    ASSERT_STR_EQ(split[1], "a");
+    ASSERT_STR_EQ(split[2], "");
+    ASSERT_STR_EQ(split[3], "b");
+    ASSERT_STR_EQ(split[4], "");
+    for (int i = 0; i < count; i++) free(split[i]);
+    free(split);
+
+    /* str_split with multi-char delimiter */
+    split = str_split("a::b::c", "::", &count);
+    ASSERT_EQ(count, 3);
+    ASSERT_STR_EQ(split[0], "a");
+    ASSERT_STR_EQ(split[1], "b");
+    ASSERT_STR_EQ(split[2], "c");
+    for (int i = 0; i < count; i++) free(split[i]);
+    free(split);
+
+    /* str_split with empty delimiter returns whole string */
+    split = str_split("hello", "", &count);
+    ASSERT_EQ(count, 1);
+    ASSERT_STR_EQ(split[0], "hello");
+    free(split[0]);
+    free(split);
+
+    /* str_split empty string */
+    split = str_split("", ",", &count);
+    ASSERT_EQ(count, 1);
+    ASSERT_STR_EQ(split[0], "");
+    free(split[0]);
+    free(split);
+
+    /* str_contains */
+    ASSERT_TRUE(str_contains("hello world", "world"));
+    ASSERT_TRUE(str_contains("hello world", "hello"));
+    ASSERT_TRUE(str_contains("hello", "hello"));
+    ASSERT_FALSE(str_contains("hello", "xyz"));
+    ASSERT_TRUE(str_contains("hello", ""));
+    ASSERT_TRUE(str_contains("", ""));
+    ASSERT_FALSE(str_contains("", "a"));
+
     TEST_REPORT();
 }
blob - 104b6f1acfc1c7a060396ba8ebc70626d3995941
blob + fa31ddfd0081614dd96eb80b4bc1b218634abf4c
--- tests/test_vector.c
+++ tests/test_vector.c
@@ -3,7 +3,15 @@
 #include "lftest.h"
 #include "lfvector.h"
 
+static int destroy_count;
+
+static void counting_destroy(void *data) {
+    (void)data;
+    destroy_count++;
+}
+
 int main() {
+    /* --- existing tests --- */
     Vector *v = malloc(sizeof(Vector));
     vec_init(v, NULL);
 
@@ -132,5 +140,98 @@ int main() {
     vec_destroy(v);
     free(v);
 
+    /* --- destroy callback --- */
+    destroy_count = 0;
+    Vector dv;
+    vec_init(&dv, counting_destroy);
+    int a = 1, b = 2, c = 3;
+    vec_push(&dv, &a);
+    vec_push(&dv, &b);
+    vec_push(&dv, &c);
+    vec_destroy(&dv);
+    ASSERT_EQ(destroy_count, 3);
+
+    /* --- vec_init_with_capacity --- */
+    Vector cv;
+    ASSERT_EQ(vec_init_with_capacity(&cv, NULL, 16), 0);
+    ASSERT_EQ(vec_cap(&cv), 16);
+    ASSERT_EQ(vec_len(&cv), 0);
+    vec_destroy(&cv);
+
+    /* --- vec_init NULL check --- */
+    ASSERT_EQ(vec_init(NULL, NULL), -1);
+    ASSERT_EQ(vec_init_with_capacity(NULL, NULL, 4), -1);
+
+    /* --- insert at index 0 --- */
+    Vector iv;
+    vec_init(&iv, NULL);
+    int x1 = 10, x2 = 20, x3 = 30;
+    vec_push(&iv, &x1);
+    vec_push(&iv, &x2);
+    vec_insert(&iv, &x3, 0);
+    ASSERT_EQ(*(int *)vec_at(&iv, 0), 30);
+    ASSERT_EQ(*(int *)vec_at(&iv, 1), 10);
+    ASSERT_EQ(*(int *)vec_at(&iv, 2), 20);
+    ASSERT_EQ(vec_len(&iv), 3);
+
+    /* --- insert out of bounds --- */
+    ASSERT_EQ(vec_insert(&iv, &x1, 100), -1);
+
+    vec_destroy(&iv);
+
+    /* --- vec_sort empty --- */
+    Vector sv;
+    vec_init(&sv, NULL);
+    vec_sort(&sv, vec_cmp_int);
+    ASSERT_EQ(vec_len(&sv), 0);
+
+    /* --- vec_sort single element --- */
+    int ss1 = 42;
+    vec_push(&sv, &ss1);
+    vec_sort(&sv, vec_cmp_int);
+    ASSERT_EQ(vec_len(&sv), 1);
+    ASSERT_EQ(*(int *)vec_at(&sv, 0), 42);
+
+    /* --- vec_sort multiple elements --- */
+    int ss2 = 5, ss3 = 99, ss4 = 1;
+    vec_push(&sv, &ss2);
+    vec_push(&sv, &ss3);
+    vec_push(&sv, &ss4);
+    vec_sort(&sv, vec_cmp_int);
+    ASSERT_EQ(*(int *)vec_at(&sv, 0), 1);
+    ASSERT_EQ(*(int *)vec_at(&sv, 1), 5);
+    ASSERT_EQ(*(int *)vec_at(&sv, 2), 42);
+    ASSERT_EQ(*(int *)vec_at(&sv, 3), 99);
+    vec_destroy(&sv);
+
+    /* --- vec_shrink on empty vector --- */
+    Vector ev;
+    vec_init(&ev, NULL);
+    ASSERT_EQ(vec_shrink(&ev), 0);
+    ASSERT_EQ(vec_cap(&ev), 0);
+    ASSERT_NULL(ev.elements);
+
+    /* --- vec_clear return check --- */
+    Vector clv;
+    vec_init(&clv, NULL);
+    int cl1 = 1;
+    vec_push(&clv, &cl1);
+    ASSERT_EQ(vec_clear(&clv), 0);
+    ASSERT_EQ(vec_len(&clv), 0);
+    vec_destroy(&clv);
+
+    /* --- vec_remove out of bounds --- */
+    Vector rv;
+    vec_init(&rv, NULL);
+    ASSERT_NULL(vec_remove(&rv, 0));
+    ASSERT_NULL(vec_safe_at(&rv, 0));
+    vec_destroy(&rv);
+
+    /* --- vec_grow_to smaller fails --- */
+    Vector gv;
+    vec_init(&gv, NULL);
+    ASSERT_EQ(vec_grow_to(&gv, 1), -1);
+    vec_destroy(&gv);
+
     TEST_REPORT();
 }
blob - /dev/null
blob + 063e3213f5fbb2126021a6460d57417a91325581 (mode 644)
--- /dev/null
+++ tests/test_utility.c
@@ -0,0 +1,47 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include "lftest.h"
+#include "lfutility.h"
+
+int main() {
+    /* Point_new */
+    Point p = Point_new(3, 7);
+    ASSERT_EQ(p.x, 3);
+    ASSERT_EQ(p.y, 7);
+
+    /* Point_new_p */
+    Point *pp = Point_new_p(5, 11);
+    ASSERT_NOT_NULL(pp);
+    ASSERT_EQ(pp->x, 5);
+    ASSERT_EQ(pp->y, 11);
+
+    /* Point_eq */
+    Point a = Point_new(1, 2);
+    Point b = Point_new(1, 2);
+    Point c = Point_new(3, 4);
+    ASSERT_TRUE(Point_eq(a, b));
+    ASSERT_FALSE(Point_eq(a, c));
+
+    /* Point_cmp_p */
+    Point *pa = Point_new_p(1, 2);
+    Point *pb = Point_new_p(1, 2);
+    Point *pc = Point_new_p(9, 9);
+    ASSERT_NOT_NULL(pa);
+    ASSERT_NOT_NULL(pb);
+    ASSERT_NOT_NULL(pc);
+    ASSERT_TRUE(Point_cmp_p(pa, pb));
+    ASSERT_FALSE(Point_cmp_p(pa, pc));
+
+    /* Point_cmp_v */
+    ASSERT_TRUE(Point_cmp_v(pa, pb));
+    ASSERT_FALSE(Point_cmp_v(pa, pc));
+
+    /* Point_destroy */
+    Point_destroy(pp);
+    Point_destroy(pa);
+    Point_destroy(pb);
+    Point_destroy(pc);
+    Point_destroy(NULL); /* should not crash */
+
+    TEST_REPORT();
+}