docs: add docstrings to ~50 undocumented functions across C source files

This commit is contained in:
nicolas 2026-05-24 23:32:33 -03:00
parent 97b341fec9
commit c1c4aa4be8
9 changed files with 60 additions and 0 deletions

View File

@ -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 import asyncio
from pathlib import Path from pathlib import Path
from typing import Optional from typing import Optional

View File

@ -15,16 +15,19 @@
static order_book_t g_books[MAX_SYMBOLS]; static order_book_t g_books[MAX_SYMBOLS];
static uint32_t g_book_count = 0; static uint32_t g_book_count = 0;
/* Zero out the global book array and reset count. */
void book_init(void) { void book_init(void) {
memset(g_books, 0, sizeof(g_books)); memset(g_books, 0, sizeof(g_books));
g_book_count = 0; 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) { order_book_t *book_get(uint16_t symbol_idx) {
if (symbol_idx >= g_book_count) return NULL; if (symbol_idx >= g_book_count) return NULL;
return &g_books[symbol_idx]; return &g_books[symbol_idx];
} }
/* Return the number of books currently stored. */
uint32_t book_count(void) { uint32_t book_count(void) {
return g_book_count; return g_book_count;
} }

View File

@ -26,12 +26,15 @@
#include <sys/socket.h> #include <sys/socket.h>
#include <arpa/inet.h> #include <arpa/inet.h>
/* Set O_NONBLOCK on an fd. Returns 0 on success, -1 on error. */
static int set_nonblocking(int fd) { static int set_nonblocking(int fd) {
int flags = fcntl(fd, F_GETFL, 0); int flags = fcntl(fd, F_GETFL, 0);
if (flags < 0) return -1; if (flags < 0) return -1;
return fcntl(fd, F_SETFL, flags | O_NONBLOCK); 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, 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) { uint32_t ws_idx, void *user_data, uint32_t events) {
if (set->fd_count >= MAX_EPOLL_FDS) { 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); 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) { void event_loops_remove_fd(epoll_set_t *set, int fd) {
epoll_ctl(set->epoll_fd, EPOLL_CTL_DEL, fd, NULL); epoll_ctl(set->epoll_fd, EPOLL_CTL_DEL, fd, NULL);
for (uint32_t i = 0; i < set->fd_count; i++) { 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) { static void epoll_set_init(epoll_set_t *set) {
memset(set, 0, sizeof(*set)); memset(set, 0, sizeof(*set));
set->epoll_fd = epoll_create1(EPOLL_CLOEXEC); 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, 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) { spsc_queue_t *signal_queue, const config_t *cfg, int wakeup_fd) {
memset(loops, 0, sizeof(*loops)); memset(loops, 0, sizeof(*loops));
@ -106,6 +113,7 @@ int event_loops_init(event_loops_t *loops, ws_client_t *ws_client,
return 0; return 0;
} }
/* Stop the event loops and close all fds (timer, eventfd, sockets, epolls). */
void event_loops_destroy(event_loops_t *loops) { void event_loops_destroy(event_loops_t *loops) {
loops->running = false; loops->running = false;
if (loops->timer_fd >= 0) close(loops->timer_fd); 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); 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 unix_client_connect(const char *socket_path) {
int fd = socket(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0); int fd = socket(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0);
if (fd < 0) return -1; if (fd < 0) return -1;
@ -139,6 +150,8 @@ int unix_client_connect(const char *socket_path) {
return fd; 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 unix_server_create(const char *socket_path) {
int fd = socket(AF_UNIX, SOCK_STREAM, 0); int fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (fd < 0) return -1; if (fd < 0) return -1;

View File

@ -15,6 +15,7 @@
static const uint32_t FNV_OFFSET = 2166136261u; static const uint32_t FNV_OFFSET = 2166136261u;
static const uint32_t FNV_PRIME = 16777619u; 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 fnv1a_hash(const char *str, uint32_t len) {
uint32_t hash = FNV_OFFSET; uint32_t hash = FNV_OFFSET;
for (uint32_t i = 0; i < len; i++) { for (uint32_t i = 0; i < len; i++) {
@ -24,22 +25,27 @@ uint32_t fnv1a_hash(const char *str, uint32_t len) {
return hash; return hash;
} }
/* Initialise an empty symbol table with initial capacity. */
void symbol_table_init(symbol_table_t *table) { void symbol_table_init(symbol_table_t *table) {
table->capacity = SYMBOL_TABLE_INITIAL; table->capacity = SYMBOL_TABLE_INITIAL;
table->count = 0; table->count = 0;
table->entries = calloc(table->capacity, sizeof(symbol_entry_t)); 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) { static int entry_cmp(const void *a, const void *b) {
const symbol_entry_t *ea = (const symbol_entry_t *)a; const symbol_entry_t *ea = (const symbol_entry_t *)a;
const symbol_entry_t *eb = (const symbol_entry_t *)b; const symbol_entry_t *eb = (const symbol_entry_t *)b;
return strcmp(ea->name, eb->name); 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) { static int entry_cmp_qsort(const void *a, const void *b) {
return entry_cmp(a, 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) { void symbol_table_sort(symbol_table_t *table) {
qsort(table->entries, table->count, sizeof(symbol_entry_t), entry_cmp_qsort); qsort(table->entries, table->count, sizeof(symbol_entry_t), entry_cmp_qsort);
for (uint32_t i = 0; i < table->count; i++) { 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) { int symbol_table_add(symbol_table_t *table, const char *name) {
if (table->count >= table->capacity) { if (table->count >= table->capacity) {
uint32_t new_cap = table->capacity * 2; 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); 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) { int16_t symbol_table_lookup(const symbol_table_t *table, const char *name) {
symbol_entry_t key; symbol_entry_t key;
strncpy(key.name, name, SYMBOL_NAME_LEN - 1); strncpy(key.name, name, SYMBOL_NAME_LEN - 1);

View File

@ -102,6 +102,7 @@ int http_server_accept(http_server_t *srv) {
return srv->client_fd; 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, static void http_send(http_server_t *srv, const char *status,
const char *content_type, const char *body) { const char *content_type, const char *body) {
char header[512]; 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) { static void http_send_json(http_server_t *srv, const char *body) {
http_send(srv, "200 OK", "application/json", 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) { static void http_send_error(http_server_t *srv, const char *status, const char *msg) {
http_send(srv, status, "text/plain", 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) { static void handle_health(http_server_t *srv) {
char body[256]; char body[256];
int connected = 0; int connected = 0;
@ -141,6 +145,7 @@ static void handle_health(http_server_t *srv) {
http_send_json(srv, body); 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) { static void handle_book(http_server_t *srv, const char *symbol) {
if (!srv->symbols || !srv->books) { if (!srv->symbols || !srv->books) {
http_send_error(srv, "500 Internal Server Error", "not initialized\n"); 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); http_send_json(srv, body);
} }
/* GET /books — return all order books as a JSON array. */
static void handle_books(http_server_t *srv) { static void handle_books(http_server_t *srv) {
if (!srv->symbols || !srv->books) { if (!srv->symbols || !srv->books) {
http_send_error(srv, "500 Internal Server Error", "not initialized\n"); 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); 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) { static void handle_symbols_list(http_server_t *srv) {
if (!srv->symbols) { if (!srv->symbols) {
http_send_error(srv, "500 Internal Server Error", "not initialized\n"); 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); 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) { static void handle_symbols_add(http_server_t *srv, const char *body) {
if (!srv->symbols || !srv->ws_client) { if (!srv->symbols || !srv->ws_client) {
http_send_error(srv, "500 Internal Server Error", "not initialized\n"); 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); 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) { static void handle_symbols_remove(http_server_t *srv, const char *symbol) {
if (!srv->symbols || !srv->ws_client) { if (!srv->symbols || !srv->ws_client) {
http_send_error(srv, "500 Internal Server Error", "not initialized\n"); http_send_error(srv, "500 Internal Server Error", "not initialized\n");

View File

@ -23,6 +23,8 @@ static int log_pipe[2] = {-1, -1};
static pthread_t log_thread; static pthread_t log_thread;
static atomic_bool log_running = false; 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) { static void *log_worker(void *arg) {
(void)arg; (void)arg;
char buf[4096]; char buf[4096];
@ -43,6 +45,7 @@ static void *log_worker(void *arg) {
return NULL; return NULL;
} }
/* Create a non-blocking pipe and start the background drain thread. */
void log_init(void) { void log_init(void) {
if (pipe2(log_pipe, O_NONBLOCK) != 0) { if (pipe2(log_pipe, O_NONBLOCK) != 0) {
log_pipe[0] = log_pipe[1] = -1; log_pipe[0] = log_pipe[1] = -1;
@ -52,6 +55,7 @@ void log_init(void) {
pthread_create(&log_thread, NULL, log_worker, NULL); pthread_create(&log_thread, NULL, log_worker, NULL);
} }
/* Stop the worker thread and close both pipe ends. */
void log_shutdown(void) { void log_shutdown(void) {
atomic_store(&log_running, false); atomic_store(&log_running, false);
if (log_thread) { if (log_thread) {
@ -61,6 +65,8 @@ void log_shutdown(void) {
if (log_pipe[1] >= 0) { close(log_pipe[1]); log_pipe[1] = -1; } 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, ...) { void log_write(const char *fmt, ...) {
if (log_pipe[1] < 0) { if (log_pipe[1] < 0) {
/* fallback: sync write to stderr */ /* fallback: sync write to stderr */

View File

@ -30,6 +30,8 @@
static volatile sig_atomic_t g_running = 1; 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) { static void signal_handler(int sig) {
(void)sig; (void)sig;
g_running = 0; g_running = 0;

View File

@ -38,6 +38,7 @@ typedef struct {
ph_entry_t entries[PAIR_HASH_SIZE]; ph_entry_t entries[PAIR_HASH_SIZE];
} pair_hash_t; } 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) { static uint32_t ph_hash(const char *a, const char *b) {
uint32_t h = 5381; uint32_t h = 5381;
const uint8_t *s = (const uint8_t *)a; 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; return h % PAIR_HASH_SIZE;
} }
/* Zero-initialise the pair hash table. */
static void ph_init(pair_hash_t *ph) { static void ph_init(pair_hash_t *ph) {
memset(ph, 0, sizeof(*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) { 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 // Normalize: store lexicographically smaller first for unordered lookup
const char *a = c1, *b = c2; 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; 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) { static bool ph_find(const pair_hash_t *ph, const char *c1, const char *c2, uint32_t *idx) {
const char *a = c1, *b = c2; const char *a = c1, *b = c2;
if (strcmp(a, b) > 0) { a = c2; b = c1; } if (strcmp(a, b) > 0) { a = c2; b = c1; }

View File

@ -45,6 +45,8 @@ static uint64_t now_realtime_ms(void) {
return (uint64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000; 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) { static void ws_connection_reset(ws_connection_t *conn) {
conn->state = WS_STATE_DISCONNECTED; conn->state = WS_STATE_DISCONNECTED;
conn->read_pos = 0; 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) { static SSL_CTX *create_ssl_ctx(void) {
SSL_CTX *ctx = SSL_CTX_new(TLS_client_method()); SSL_CTX *ctx = SSL_CTX_new(TLS_client_method());
if (!ctx) { if (!ctx) {
@ -85,6 +88,8 @@ static SSL_CTX *create_ssl_ctx(void) {
return ctx; 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) { static int resolve_and_connect(const char *host, int port) {
struct addrinfo hints = {0}, *res = NULL; struct addrinfo hints = {0}, *res = NULL;
hints.ai_family = AF_UNSPEC; hints.ai_family = AF_UNSPEC;
@ -112,6 +117,8 @@ static int resolve_and_connect(const char *host, int port) {
return fd; 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) { static int setup_tls(ws_connection_t *conn) {
conn->ssl = SSL_new(conn->ctx); conn->ssl = SSL_new(conn->ctx);
if (!conn->ssl) { 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). * Supports payload lengths < 126 (inline), < 65536 (16-bit ext), and >= 65536 (64-bit ext).
* Masking key derived from monotonic clock to avoid predictable patterns. * 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, static int ws_send_frame(ws_connection_t *conn, uint8_t opcode,
const uint8_t *payload, size_t payload_len) { const uint8_t *payload, size_t payload_len) {
uint8_t header[14]; uint8_t header[14];