From c1c4aa4be80351ea1a0fad902a505b32bb42fde1 Mon Sep 17 00:00:00 2001 From: nicolas Date: Sun, 24 May 2026 23:32:33 -0300 Subject: [PATCH] docs: add docstrings to ~50 undocumented functions across C source files --- common/config.py | 2 ++ src/book.c | 3 +++ src/events.c | 13 +++++++++++++ src/hash.c | 9 +++++++++ src/http_server.c | 9 +++++++++ src/log.c | 6 ++++++ src/main.c | 2 ++ src/symbols_api.c | 6 ++++++ src/ws_client.c | 10 ++++++++++ 9 files changed, 60 insertions(+) diff --git a/common/config.py b/common/config.py index 7b36eb1..5f9e7dc 100644 --- a/common/config.py +++ b/common/config.py @@ -1,3 +1,5 @@ +"""Deprecated config schema for the old fh_ob Python process. Kept so that +common/log.py can be imported without errors. Not used at runtime.""" import asyncio from pathlib import Path from typing import Optional diff --git a/src/book.c b/src/book.c index be613e1..4317587 100644 --- a/src/book.c +++ b/src/book.c @@ -15,16 +15,19 @@ static order_book_t g_books[MAX_SYMBOLS]; static uint32_t g_book_count = 0; +/* Zero out the global book array and reset count. */ void book_init(void) { memset(g_books, 0, sizeof(g_books)); g_book_count = 0; } +/* Return book for a symbol index, or NULL if out of range. */ order_book_t *book_get(uint16_t symbol_idx) { if (symbol_idx >= g_book_count) return NULL; return &g_books[symbol_idx]; } +/* Return the number of books currently stored. */ uint32_t book_count(void) { return g_book_count; } diff --git a/src/events.c b/src/events.c index 22dccfe..f249809 100644 --- a/src/events.c +++ b/src/events.c @@ -26,12 +26,15 @@ #include #include +/* Set O_NONBLOCK on an fd. Returns 0 on success, -1 on error. */ static int set_nonblocking(int fd) { int flags = fcntl(fd, F_GETFL, 0); if (flags < 0) return -1; return fcntl(fd, F_SETFL, flags | O_NONBLOCK); } +/* Register an fd with the epoll set. If the fd is already tracked, modify + its events instead of re-adding. Returns 0 on success, -1 on error. */ int event_loops_add_fd(epoll_set_t *set, int fd, fd_type_t type, uint32_t ws_idx, void *user_data, uint32_t events) { if (set->fd_count >= MAX_EPOLL_FDS) { @@ -63,6 +66,7 @@ int event_loops_add_fd(epoll_set_t *set, int fd, fd_type_t type, return epoll_ctl(set->epoll_fd, EPOLL_CTL_ADD, fd, &ev); } +/* Remove an fd from the epoll set and mark its tracked entry as unused. */ void event_loops_remove_fd(epoll_set_t *set, int fd) { epoll_ctl(set->epoll_fd, EPOLL_CTL_DEL, fd, NULL); for (uint32_t i = 0; i < set->fd_count; i++) { @@ -73,6 +77,7 @@ void event_loops_remove_fd(epoll_set_t *set, int fd) { } } +/* Initialise an epoll set: create epoll fd, zero the tracked fd array. */ static void epoll_set_init(epoll_set_t *set) { memset(set, 0, sizeof(*set)); set->epoll_fd = epoll_create1(EPOLL_CLOEXEC); @@ -82,6 +87,8 @@ static void epoll_set_init(epoll_set_t *set) { } } +/* Initialise both epoll sets (hot + cold), timer fd, and wakeup fd. + The cold epoll set monitors the wakeup eventfd for SPSC drain signals. */ int event_loops_init(event_loops_t *loops, ws_client_t *ws_client, spsc_queue_t *signal_queue, const config_t *cfg, int wakeup_fd) { memset(loops, 0, sizeof(*loops)); @@ -106,6 +113,7 @@ int event_loops_init(event_loops_t *loops, ws_client_t *ws_client, return 0; } +/* Stop the event loops and close all fds (timer, eventfd, sockets, epolls). */ void event_loops_destroy(event_loops_t *loops) { loops->running = false; if (loops->timer_fd >= 0) close(loops->timer_fd); @@ -116,6 +124,9 @@ void event_loops_destroy(event_loops_t *loops) { if (loops->cold_epoll.epoll_fd >= 0) close(loops->cold_epoll.epoll_fd); } +/* Connect to a Unix domain socket at the given path. Uses SOCK_NONBLOCK + with a poll-based 100ms timeout for the connection to complete. + Returns connected fd on success, -1 on failure. */ int unix_client_connect(const char *socket_path) { int fd = socket(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0); if (fd < 0) return -1; @@ -139,6 +150,8 @@ int unix_client_connect(const char *socket_path) { return fd; } +/* Create and bind a Unix domain stream socket server, remove stale socket + file first. Sets O_NONBLOCK on the listening fd. Returns fd, or -1. */ int unix_server_create(const char *socket_path) { int fd = socket(AF_UNIX, SOCK_STREAM, 0); if (fd < 0) return -1; diff --git a/src/hash.c b/src/hash.c index 1f25dc2..123e037 100644 --- a/src/hash.c +++ b/src/hash.c @@ -15,6 +15,7 @@ static const uint32_t FNV_OFFSET = 2166136261u; static const uint32_t FNV_PRIME = 16777619u; +/* FNV-1a non-cryptographic hash for arbitrary-length strings. */ uint32_t fnv1a_hash(const char *str, uint32_t len) { uint32_t hash = FNV_OFFSET; for (uint32_t i = 0; i < len; i++) { @@ -24,22 +25,27 @@ uint32_t fnv1a_hash(const char *str, uint32_t len) { return hash; } +/* Initialise an empty symbol table with initial capacity. */ void symbol_table_init(symbol_table_t *table) { table->capacity = SYMBOL_TABLE_INITIAL; table->count = 0; table->entries = calloc(table->capacity, sizeof(symbol_entry_t)); } +/* Compare two symbol entries by name (for bsearch). */ static int entry_cmp(const void *a, const void *b) { const symbol_entry_t *ea = (const symbol_entry_t *)a; const symbol_entry_t *eb = (const symbol_entry_t *)b; return strcmp(ea->name, eb->name); } +/* Wrapper matching qsort's comparison signature (same as entry_cmp). */ static int entry_cmp_qsort(const void *a, const void *b) { return entry_cmp(a, b); } +/* Sort entries by name and re-assign dense indices. Must be called after + all additions are complete, before any lookups via bsearch. */ void symbol_table_sort(symbol_table_t *table) { qsort(table->entries, table->count, sizeof(symbol_entry_t), entry_cmp_qsort); for (uint32_t i = 0; i < table->count; i++) { @@ -47,6 +53,8 @@ void symbol_table_sort(symbol_table_t *table) { } } +/* Append a new symbol entry, doubling capacity if full. Returns the + assigned index, or -1 on allocation failure. */ int symbol_table_add(symbol_table_t *table, const char *name) { if (table->count >= table->capacity) { uint32_t new_cap = table->capacity * 2; @@ -66,6 +74,7 @@ int symbol_table_add(symbol_table_t *table, const char *name) { return (int)(table->count - 1); } +/* Binary-search for a symbol by name. Returns its 16-bit index or -1. */ int16_t symbol_table_lookup(const symbol_table_t *table, const char *name) { symbol_entry_t key; strncpy(key.name, name, SYMBOL_NAME_LEN - 1); diff --git a/src/http_server.c b/src/http_server.c index 35487d0..1c282c1 100644 --- a/src/http_server.c +++ b/src/http_server.c @@ -102,6 +102,7 @@ int http_server_accept(http_server_t *srv) { return srv->client_fd; } +/* Write an HTTP response with status line, headers, and body to the client. */ static void http_send(http_server_t *srv, const char *status, const char *content_type, const char *body) { char header[512]; @@ -119,14 +120,17 @@ static void http_send(http_server_t *srv, const char *status, } } +/* Send a 200 OK JSON response. */ static void http_send_json(http_server_t *srv, const char *body) { http_send(srv, "200 OK", "application/json", body); } +/* Send an error response with the given status code and plain text body. */ static void http_send_error(http_server_t *srv, const char *status, const char *msg) { http_send(srv, status, "text/plain", msg); } +/* GET /health — return WS connection count and symbol count as JSON. */ static void handle_health(http_server_t *srv) { char body[256]; int connected = 0; @@ -141,6 +145,7 @@ static void handle_health(http_server_t *srv) { http_send_json(srv, body); } +/* GET /book/{symbol} — return a single order book as JSON, or 404. */ static void handle_book(http_server_t *srv, const char *symbol) { if (!srv->symbols || !srv->books) { http_send_error(srv, "500 Internal Server Error", "not initialized\n"); @@ -174,6 +179,7 @@ static void handle_book(http_server_t *srv, const char *symbol) { http_send_json(srv, body); } +/* GET /books — return all order books as a JSON array. */ static void handle_books(http_server_t *srv) { if (!srv->symbols || !srv->books) { http_send_error(srv, "500 Internal Server Error", "not initialized\n"); @@ -204,6 +210,7 @@ static void handle_books(http_server_t *srv) { http_send_json(srv, body); } +/* GET /symbols — list all tracked symbol names as a JSON array. */ static void handle_symbols_list(http_server_t *srv) { if (!srv->symbols) { http_send_error(srv, "500 Internal Server Error", "not initialized\n"); @@ -220,6 +227,7 @@ static void handle_symbols_list(http_server_t *srv) { http_send_json(srv, body); } +/* POST /symbols — subscribe to new symbols (add to symbol table + WS subscribe). */ static void handle_symbols_add(http_server_t *srv, const char *body) { if (!srv->symbols || !srv->ws_client) { http_send_error(srv, "500 Internal Server Error", "not initialized\n"); @@ -265,6 +273,7 @@ static void handle_symbols_add(http_server_t *srv, const char *body) { http_send_json(srv, resp); } +/* DELETE /symbols/{name} — unsubscribe and remove from symbol table. */ static void handle_symbols_remove(http_server_t *srv, const char *symbol) { if (!srv->symbols || !srv->ws_client) { http_send_error(srv, "500 Internal Server Error", "not initialized\n"); diff --git a/src/log.c b/src/log.c index 60ca794..5bdc9ad 100644 --- a/src/log.c +++ b/src/log.c @@ -23,6 +23,8 @@ static int log_pipe[2] = {-1, -1}; static pthread_t log_thread; static atomic_bool log_running = false; +/* Background thread: drains the pipe and writes each chunk to stderr. + Spins on EAGAIN with 100us sleep when the pipe is empty. */ static void *log_worker(void *arg) { (void)arg; char buf[4096]; @@ -43,6 +45,7 @@ static void *log_worker(void *arg) { return NULL; } +/* Create a non-blocking pipe and start the background drain thread. */ void log_init(void) { if (pipe2(log_pipe, O_NONBLOCK) != 0) { log_pipe[0] = log_pipe[1] = -1; @@ -52,6 +55,7 @@ void log_init(void) { pthread_create(&log_thread, NULL, log_worker, NULL); } +/* Stop the worker thread and close both pipe ends. */ void log_shutdown(void) { atomic_store(&log_running, false); if (log_thread) { @@ -61,6 +65,8 @@ void log_shutdown(void) { if (log_pipe[1] >= 0) { close(log_pipe[1]); log_pipe[1] = -1; } } +/* Non-blocking log write. Formats a timestamped message into the pipe. + Falls back to sync stderr write if the pipe has not been initialised. */ void log_write(const char *fmt, ...) { if (log_pipe[1] < 0) { /* fallback: sync write to stderr */ diff --git a/src/main.c b/src/main.c index ccaaf19..c8170ef 100644 --- a/src/main.c +++ b/src/main.c @@ -30,6 +30,8 @@ static volatile sig_atomic_t g_running = 1; +/* Set the global running flag to zero on SIGINT/SIGTERM. Thread-safe + via sig_atomic_t. The main loop checks g_running to exit cleanly. */ static void signal_handler(int sig) { (void)sig; g_running = 0; diff --git a/src/symbols_api.c b/src/symbols_api.c index d3cf675..695daac 100644 --- a/src/symbols_api.c +++ b/src/symbols_api.c @@ -38,6 +38,7 @@ typedef struct { ph_entry_t entries[PAIR_HASH_SIZE]; } pair_hash_t; +/* Hash an unordered currency pair (a, b) to a bucket index (djb2 variant). */ static uint32_t ph_hash(const char *a, const char *b) { uint32_t h = 5381; const uint8_t *s = (const uint8_t *)a; @@ -47,10 +48,13 @@ static uint32_t ph_hash(const char *a, const char *b) { return h % PAIR_HASH_SIZE; } +/* Zero-initialise the pair hash table. */ static void ph_init(pair_hash_t *ph) { memset(ph, 0, sizeof(*ph)); } +/* Insert an unordered currency pair -> pair_index mapping with open + addressing linear probing. Silently ignores duplicates (keeps first). */ static void ph_insert(pair_hash_t *ph, const char *c1, const char *c2, uint32_t idx) { // Normalize: store lexicographically smaller first for unordered lookup const char *a = c1, *b = c2; @@ -67,6 +71,8 @@ static void ph_insert(pair_hash_t *ph, const char *c1, const char *c2, uint32_t ph->entries[h].used = true; } +/* Look up an unordered currency pair, writing its index to *idx if found. + Returns true on hit, false on miss. */ static bool ph_find(const pair_hash_t *ph, const char *c1, const char *c2, uint32_t *idx) { const char *a = c1, *b = c2; if (strcmp(a, b) > 0) { a = c2; b = c1; } diff --git a/src/ws_client.c b/src/ws_client.c index 0dbeed2..bfc4c91 100644 --- a/src/ws_client.c +++ b/src/ws_client.c @@ -45,6 +45,8 @@ static uint64_t now_realtime_ms(void) { return (uint64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000; } +/* Reset a WebSocket connection to its initial disconnected state, freeing + any SSL objects, BIOs, and the socket fd. Safe to call multiple times. */ static void ws_connection_reset(ws_connection_t *conn) { conn->state = WS_STATE_DISCONNECTED; conn->read_pos = 0; @@ -74,6 +76,7 @@ static void ws_connection_reset(ws_connection_t *conn) { } } +/* Create a shared SSL context for all WS connections (TLS client, no peer verification). */ static SSL_CTX *create_ssl_ctx(void) { SSL_CTX *ctx = SSL_CTX_new(TLS_client_method()); if (!ctx) { @@ -85,6 +88,8 @@ static SSL_CTX *create_ssl_ctx(void) { return ctx; } +/* Resolve hostname via getaddrinfo and try each address until one connects. + Returns a connected socket fd, or -1 on failure. */ static int resolve_and_connect(const char *host, int port) { struct addrinfo hints = {0}, *res = NULL; hints.ai_family = AF_UNSPEC; @@ -112,6 +117,8 @@ static int resolve_and_connect(const char *host, int port) { return fd; } +/* Create SSL object, set SNI hostname, attach socket fd, and perform TLS handshake. + Returns 0 on success, -1 on failure. */ static int setup_tls(ws_connection_t *conn) { conn->ssl = SSL_new(conn->ctx); if (!conn->ssl) { @@ -371,6 +378,9 @@ int ws_client_write(ws_connection_t *conn, const void *data, size_t len) { * Supports payload lengths < 126 (inline), < 65536 (16-bit ext), and >= 65536 (64-bit ext). * Masking key derived from monotonic clock to avoid predictable patterns. */ +/* Build and send an RFC 6455 WebSocket frame with masking. + Supports payload lengths < 126 (inline), < 65536 (16-bit ext), >= 65536 (64-bit ext). + Masking key derived from monotonic clock. Returns bytes sent on success, -1 on error. */ static int ws_send_frame(ws_connection_t *conn, uint8_t opcode, const uint8_t *payload, size_t payload_len) { uint8_t header[14];