docs: add docstrings to ~50 undocumented functions across C source files
This commit is contained in:
parent
97b341fec9
commit
c1c4aa4be8
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
13
src/events.c
13
src/events.c
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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");
|
||||||
|
|
|
||||||
|
|
@ -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 */
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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; }
|
||||||
|
|
|
||||||
|
|
@ -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];
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue