Compare commits

...

5 Commits

Author SHA1 Message Date
248c5647e2 test: add comprehensive tests for auth, buffer, and connection modules
Some checks failed
Build and Test / build (push) Failing after 31s
Build and Test / coverage (push) Failing after 38s
build: add logging test support and increase minimum coverage threshold
2025-12-15 02:36:54 +01:00
c5af589ceb feat: add socket optimization functions for TCP_NODELAY and upstream handling
test: add tests for connection optimizations, caching, and cleanup
2025-12-15 01:31:27 +01:00
87e165dc52 perf: optimize socket settings and cache time for better performance
feat: add host header validation in client requests
fix: improve dns resolution error handling
refactor: cache epoll events in connection struct
2025-12-15 01:28:34 +01:00
a5353c4db8 perf: implement zero-copy data forwarding using linux splice syscall
refactor: cache patch buffer in connection struct to reduce allocations
perf: optimize buffer compaction in connection read handling
2025-12-15 01:12:09 +01:00
b356a13b57 Updated build steps. 2025-12-13 00:21:55 +01:00
14 changed files with 2450 additions and 24 deletions

View File

@ -1,3 +1,4 @@
# retoor <retoor@molodetz.nl>
name: Build and Test
on:
@ -20,7 +21,7 @@ jobs:
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y gcc make libssl-dev libsqlite3-dev
sudo apt-get install -y gcc make libssl-dev libsqlite3-dev bc
- name: Build
run: make all
@ -28,5 +29,22 @@ jobs:
- name: Run tests
run: make test
- name: Build legacy
run: make legacy
- name: Clean
run: make clean
coverage:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y gcc make libssl-dev libsqlite3-dev bc gcovr lcov
- name: Run coverage
run: make coverage

34
CHANGELOG.md Normal file
View File

@ -0,0 +1,34 @@
# Changelog
## Version 0.4.0 - 2025-12-15
Add comprehensive tests for the auth, buffer, and connection modules. Enhance the build process with logging test support and an increased minimum coverage threshold.
**Changes:** 8 files, 2046 lines
**Languages:** C (2036 lines), Other (10 lines)
## Version 0.3.0 - 2025-12-15
Adds socket optimization functions that enable TCP_NODELAY for reduced connection latency and enhance upstream handling. Includes tests for connection optimizations, caching, and cleanup to verify functionality.
**Changes:** 2 files, 146 lines
**Languages:** C (146 lines)
## Version 0.2.0 - 2025-12-15
Optimizes performance through enhanced socket settings and caching, reducing latency in connections. Adds host header validation to client requests and improves DNS resolution error handling for more reliable network operations.
**Changes:** 4 files, 79 lines
**Languages:** C (79 lines)
## Version 0.1.0 - 2025-12-15
Implements zero-copy data forwarding to enhance connection performance. Caches patch buffers in connection structures and optimizes buffer compaction during read handling to reduce memory allocations.
**Changes:** 2 files, 153 lines
**Languages:** C (153 lines)

View File

@ -42,7 +42,8 @@ TEST_SOURCES = $(TESTS_DIR)/test_main.c \
$(TESTS_DIR)/test_dashboard.c \
$(TESTS_DIR)/test_health_check.c \
$(TESTS_DIR)/test_ssl_handler.c \
$(TESTS_DIR)/test_connection.c
$(TESTS_DIR)/test_connection.c \
$(TESTS_DIR)/test_logging.c
TEST_OBJECTS = $(patsubst %.c,$(BUILD_DIR)/%.o,$(notdir $(TEST_SOURCES)))
@ -64,7 +65,7 @@ TEST_LIB_OBJECTS = $(patsubst %.c,$(BUILD_DIR)/%.o,$(notdir $(TEST_LIB_SOURCES))
TEST_TARGET = rproxy_test
MIN_COVERAGE = 60
MIN_COVERAGE = 69
COVERAGE_MODULES = auth.c buffer.c config.c http.c logging.c patch.c rate_limit.c monitor.c dashboard.c health_check.c ssl_handler.c connection.c
.PHONY: all clean test legacy run coverage coverage-html valgrind
@ -164,6 +165,9 @@ $(BUILD_DIR)/test_ssl_handler.o: $(TESTS_DIR)/test_ssl_handler.c
$(BUILD_DIR)/test_connection.o: $(TESTS_DIR)/test_connection.c
$(CC) $(CFLAGS) -I$(SRC_DIR) -c $< -o $@
$(BUILD_DIR)/test_logging.o: $(TESTS_DIR)/test_logging.c
$(CC) $(CFLAGS) -I$(SRC_DIR) -c $< -o $@
$(TEST_TARGET): $(BUILD_DIR) $(TEST_OBJECTS) $(TEST_LIB_OBJECTS)
$(CC) $(TEST_OBJECTS) $(TEST_LIB_OBJECTS) -o $@ $(LDFLAGS)
@ -193,6 +197,7 @@ coverage: clean
$(CC) $(CFLAGS_COV) -Isrc -c tests/test_health_check.c -o build/test_health_check.o
$(CC) $(CFLAGS_COV) -Isrc -c tests/test_ssl_handler.c -o build/test_ssl_handler.o
$(CC) $(CFLAGS_COV) -Isrc -c tests/test_connection.c -o build/test_connection.o
$(CC) $(CFLAGS_COV) -Isrc -c tests/test_logging.c -o build/test_logging.o
$(CC) $(CFLAGS_COV) -c src/buffer.c -o build/buffer.o
$(CC) $(CFLAGS_COV) -c src/logging.c -o build/logging.o
$(CC) $(CFLAGS_COV) -c src/config.c -o build/config.o
@ -276,6 +281,7 @@ valgrind: clean
$(CC) $(CFLAGS_DEBUG) -Isrc -c tests/test_health_check.c -o build/test_health_check.o
$(CC) $(CFLAGS_DEBUG) -Isrc -c tests/test_ssl_handler.c -o build/test_ssl_handler.o
$(CC) $(CFLAGS_DEBUG) -Isrc -c tests/test_connection.c -o build/test_connection.o
$(CC) $(CFLAGS_DEBUG) -Isrc -c tests/test_logging.c -o build/test_logging.o
$(CC) $(CFLAGS_DEBUG) -c src/buffer.c -o build/buffer.o
$(CC) $(CFLAGS_DEBUG) -c src/logging.c -o build/logging.o
$(CC) $(CFLAGS_DEBUG) -c src/config.c -o build/config.o

View File

@ -22,13 +22,29 @@
#include <arpa/inet.h>
#include <netdb.h>
#ifdef __linux__
#ifndef SPLICE_F_MOVE
#define SPLICE_F_MOVE 1
#endif
#ifndef SPLICE_F_NONBLOCK
#define SPLICE_F_NONBLOCK 2
#endif
#endif
connection_t connections[MAX_FDS];
int epoll_fd = -1;
time_t cached_time = 0;
void connection_update_cached_time(void) {
cached_time = time(NULL);
}
void connection_init_all(void) {
for (int i = 0; i < MAX_FDS; i++) {
connections[i].type = CONN_TYPE_UNUSED;
connections[i].fd = -1;
connections[i].splice_pipe[0] = -1;
connections[i].splice_pipe[1] = -1;
}
}
@ -64,20 +80,57 @@ void connection_set_tcp_keepalive(int fd) {
}
}
void connection_set_tcp_nodelay(int fd) {
int yes = 1;
if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &yes, sizeof(yes)) < 0) {
log_debug("setsockopt TCP_NODELAY failed for fd %d: %s", fd, strerror(errno));
}
}
#ifdef TCP_QUICKACK
void connection_set_tcp_quickack(int fd) {
int yes = 1;
if (setsockopt(fd, IPPROTO_TCP, TCP_QUICKACK, &yes, sizeof(yes)) < 0) {
log_debug("setsockopt TCP_QUICKACK failed for fd %d: %s", fd, strerror(errno));
}
}
#endif
void connection_optimize_socket(int fd, int is_upstream) {
connection_set_tcp_nodelay(fd);
connection_set_tcp_keepalive(fd);
#ifdef TCP_QUICKACK
if (!is_upstream) {
connection_set_tcp_quickack(fd);
}
#endif
int sndbuf = is_upstream ? 262144 : 131072;
int rcvbuf = is_upstream ? 524288 : 131072;
setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &sndbuf, sizeof(sndbuf));
setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof(rcvbuf));
}
void connection_add_to_epoll(int fd, uint32_t events) {
struct epoll_event event = { .data.fd = fd, .events = events };
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &event) == -1) {
log_error("epoll_ctl_add failed");
close(fd);
} else if (fd >= 0 && fd < MAX_FDS) {
connections[fd].epoll_events = events;
}
}
void connection_modify_epoll(int fd, uint32_t events) {
if (fd >= 0 && fd < MAX_FDS && connections[fd].epoll_events == events) {
return;
}
struct epoll_event event = { .data.fd = fd, .events = events };
if (epoll_ctl(epoll_fd, EPOLL_CTL_MOD, fd, &event) == -1) {
if(errno != EBADF && errno != ENOENT) {
log_debug("epoll_ctl_mod failed for fd %d: %s", fd, strerror(errno));
}
} else if (fd >= 0 && fd < MAX_FDS) {
connections[fd].epoll_events = events;
}
}
@ -142,7 +195,7 @@ void connection_accept(int listener_fd) {
}
connection_set_non_blocking(client_fd);
connection_set_tcp_keepalive(client_fd);
connection_optimize_socket(client_fd, 0);
connection_add_to_epoll(client_fd, EPOLLIN);
connection_t *conn = &connections[client_fd];
@ -150,7 +203,7 @@ void connection_accept(int listener_fd) {
conn->type = CONN_TYPE_CLIENT;
conn->state = CLIENT_STATE_READING_HEADERS;
conn->fd = client_fd;
conn->last_activity = time(NULL);
conn->last_activity = cached_time;
if (buffer_init(&conn->read_buf, CHUNK_SIZE) < 0 ||
buffer_init(&conn->write_buf, CHUNK_SIZE) < 0) {
@ -186,6 +239,9 @@ void connection_close(int fd) {
pair->patch_blocked = 0;
pair->half_closed = 0;
pair->write_shutdown = 0;
pair->splice_pipe[0] = -1;
pair->splice_pipe[1] = -1;
pair->can_splice = 0;
} else if (conn->type == CONN_TYPE_CLIENT && pair->type == CONN_TYPE_UPSTREAM) {
log_debug("Client fd %d is closing. Closing orphaned upstream pair fd %d.", fd, pair->fd);
pair->pair = NULL;
@ -217,6 +273,20 @@ void connection_close(int fd) {
buffer_free(&conn->read_buf);
buffer_free(&conn->write_buf);
if (conn->patch_buf) {
free(conn->patch_buf);
conn->patch_buf = NULL;
conn->patch_buf_capacity = 0;
}
if (conn->type == CONN_TYPE_UPSTREAM && conn->splice_pipe[0] >= 0) {
close(conn->splice_pipe[0]);
close(conn->splice_pipe[1]);
}
conn->splice_pipe[0] = -1;
conn->splice_pipe[1] = -1;
conn->can_splice = 0;
if (conn->config) {
config_ref_dec(conn->config);
}
@ -224,13 +294,18 @@ void connection_close(int fd) {
memset(conn, 0, sizeof(connection_t));
conn->type = CONN_TYPE_UNUSED;
conn->fd = -1;
conn->splice_pipe[0] = -1;
conn->splice_pipe[1] = -1;
}
int connection_do_read(connection_t *conn) {
if (!conn) return -1;
buffer_t *buf = &conn->read_buf;
buffer_compact(buf);
if (buf->head > buf->capacity / 4) {
buffer_compact(buf);
}
size_t available = buffer_available_write(buf);
if (available == 0) {
@ -260,7 +335,7 @@ int connection_do_read(connection_t *conn) {
if (bytes_read > 0) {
buf->tail += bytes_read;
conn->last_activity = time(NULL);
conn->last_activity = cached_time;
if (conn->vhost_stats) {
monitor_record_bytes(conn->vhost_stats, 0, bytes_read);
}
@ -292,7 +367,7 @@ int connection_do_write(connection_t *conn) {
if (written > 0) {
buffer_consume(buf, written);
conn->last_activity = time(NULL);
conn->last_activity = cached_time;
if (conn->vhost_stats) {
monitor_record_bytes(conn->vhost_stats, written, 0);
}
@ -392,7 +467,7 @@ static int try_upstream_connect(struct sockaddr_in *addr, int *out_fd) {
}
connection_set_non_blocking(up_fd);
connection_set_tcp_keepalive(up_fd);
connection_optimize_socket(up_fd, 1);
int connect_result = connect(up_fd, (struct sockaddr*)addr, sizeof(*addr));
if (connect_result < 0 && errno != EINPROGRESS) {
@ -445,8 +520,10 @@ void connection_connect_to_upstream(connection_t *client, const char *data, size
hints.ai_socktype = SOCK_STREAM;
int gai_err = getaddrinfo(route->upstream_host, NULL, &hints, &result);
if (gai_err != 0) {
log_debug("DNS resolution failed for %s: %s", route->upstream_host, gai_strerror(gai_err));
if (gai_err != 0 || !result || !result->ai_addr) {
if (gai_err == 0 && result) freeaddrinfo(result);
log_debug("DNS resolution failed for %s: %s", route->upstream_host,
gai_err ? gai_strerror(gai_err) : "no address returned");
connection_send_error_response(client, 502, "Bad Gateway", "Cannot resolve upstream hostname");
return;
}
@ -485,7 +562,9 @@ void connection_connect_to_upstream(connection_t *client, const char *data, size
memset(up, 0, sizeof(connection_t));
up->type = CONN_TYPE_UPSTREAM;
up->fd = up_fd;
up->last_activity = time(NULL);
up->last_activity = cached_time;
up->splice_pipe[0] = -1;
up->splice_pipe[1] = -1;
client->pair = up;
up->pair = client;
@ -495,6 +574,19 @@ void connection_connect_to_upstream(connection_t *client, const char *data, size
up->config = client->config;
config_ref_inc(up->config);
int use_splice = !patch_has_rules(&route->patches) && !route->use_ssl;
if (use_splice) {
if (pipe(up->splice_pipe) == 0) {
up->can_splice = 1;
client->can_splice = 1;
client->splice_pipe[0] = up->splice_pipe[0];
client->splice_pipe[1] = up->splice_pipe[1];
} else {
up->splice_pipe[0] = -1;
up->splice_pipe[1] = -1;
}
}
if (buffer_init(&up->read_buf, CHUNK_SIZE) < 0) {
cleanup_upstream_partial(up, up_fd, client, 0, 0);
connection_send_error_response(client, 502, "Bad Gateway", "Memory allocation failed");
@ -642,6 +734,11 @@ static void handle_client_read(connection_t *conn) {
break;
}
if (conn->request.host[0] == '\0') {
connection_send_error_response(conn, 400, "Bad Request", "Missing Host header.");
return;
}
long long body_len = (conn->request.content_length > 0) ? conn->request.content_length : 0;
size_t total_request_len = headers_len + body_len;
@ -724,6 +821,49 @@ static void handle_client_read(connection_t *conn) {
}
}
#ifdef __linux__
static ssize_t splice_forward(connection_t *conn, connection_t *pair) {
if (conn->splice_pipe[0] < 0 || conn->splice_pipe[1] < 0) {
return -1;
}
ssize_t bytes_to_pipe = splice(conn->fd, NULL, conn->splice_pipe[1], NULL,
CHUNK_SIZE, SPLICE_F_NONBLOCK | SPLICE_F_MOVE);
if (bytes_to_pipe <= 0) {
if (bytes_to_pipe == 0) {
return 0;
}
if (errno == EAGAIN || errno == EWOULDBLOCK) {
return -1;
}
return -2;
}
ssize_t bytes_from_pipe = splice(conn->splice_pipe[0], NULL, pair->fd, NULL,
bytes_to_pipe, SPLICE_F_NONBLOCK | SPLICE_F_MOVE);
if (bytes_from_pipe < 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
char discard[CHUNK_SIZE];
ssize_t discarded = read(conn->splice_pipe[0], discard, bytes_to_pipe);
(void)discarded;
return -1;
}
return -2;
}
conn->last_activity = cached_time;
pair->last_activity = cached_time;
if (conn->vhost_stats) {
monitor_record_bytes(conn->vhost_stats, bytes_from_pipe, bytes_to_pipe);
}
return bytes_from_pipe;
}
#endif
static void handle_forwarding(connection_t *conn) {
connection_t *pair = conn->pair;
@ -741,6 +881,40 @@ static void handle_forwarding(connection_t *conn) {
return;
}
#ifdef __linux__
int is_response = (conn->type == CONN_TYPE_UPSTREAM);
int use_splice = conn->can_splice &&
buffer_available_read(&conn->read_buf) == 0 &&
buffer_available_read(&pair->write_buf) == 0 &&
(!is_response || conn->response_headers_parsed);
if (use_splice) {
ssize_t splice_result = splice_forward(conn, pair);
if (splice_result == 0) {
log_debug("EOF via splice on fd %d", conn->fd);
conn->half_closed = 1;
if (pair->fd != -1 && !pair->write_shutdown) {
shutdown(pair->fd, SHUT_WR);
pair->write_shutdown = 1;
}
if (pair->half_closed) {
connection_close(conn->fd);
}
return;
}
if (splice_result > 0) {
return;
}
if (splice_result == -2) {
connection_close(conn->fd);
return;
}
}
#endif
int bytes_read = connection_do_read(conn);
if (conn->type == CONN_TYPE_CLIENT && bytes_read > 0) {
@ -845,18 +1019,23 @@ static void handle_forwarding(connection_t *conn) {
char *output_data = src_data;
size_t output_len = src_len;
char *patched_buf = NULL;
if (should_patch) {
size_t max_output = src_len * 4;
if (max_output < CHUNK_SIZE) max_output = CHUNK_SIZE;
patched_buf = malloc(max_output);
if (patched_buf) {
patch_result_t result = patch_apply(&route->patches, src_data, src_len, patched_buf, max_output);
if (conn->patch_buf_capacity < max_output) {
char *new_buf = realloc(conn->patch_buf, max_output);
if (new_buf) {
conn->patch_buf = new_buf;
conn->patch_buf_capacity = max_output;
}
}
if (conn->patch_buf) {
patch_result_t result = patch_apply(&route->patches, src_data, src_len, conn->patch_buf, conn->patch_buf_capacity);
if (result.should_block) {
free(patched_buf);
log_info("Blocked content during patching on fd %d", conn->fd);
conn->patch_blocked = 1;
if (is_response) {
@ -869,7 +1048,7 @@ static void handle_forwarding(connection_t *conn) {
}
if (result.output_len > 0 && result.size_delta != 0) {
output_data = patched_buf;
output_data = conn->patch_buf;
output_len = result.output_len;
if ((result.size_delta > 0 && conn->content_length_delta > LONG_MAX - result.size_delta) ||
(result.size_delta < 0 && conn->content_length_delta < LONG_MIN - result.size_delta)) {
@ -879,7 +1058,7 @@ static void handle_forwarding(connection_t *conn) {
}
log_debug("Patched data: %zu -> %zu bytes (delta: %ld)", src_len, output_len, result.size_delta);
} else if (result.output_len > 0) {
output_data = patched_buf;
output_data = conn->patch_buf;
output_len = result.output_len;
}
}
@ -887,7 +1066,6 @@ static void handle_forwarding(connection_t *conn) {
size_t space_needed = pair->write_buf.tail + output_len;
if (buffer_ensure_capacity(&pair->write_buf, space_needed) < 0) {
if (patched_buf) free(patched_buf);
log_debug("Failed to buffer data for fd %d, closing connection", conn->fd);
connection_close(conn->fd);
return;
@ -897,8 +1075,6 @@ static void handle_forwarding(connection_t *conn) {
pair->write_buf.tail += output_len;
buffer_consume(&conn->read_buf, data_to_forward);
if (patched_buf) free(patched_buf);
connection_do_write(pair);
connection_modify_epoll(pair->fd, EPOLLIN | EPOLLOUT);
}
@ -969,7 +1145,7 @@ static void handle_ssl_handshake(connection_t *conn) {
}
static void handle_write_event(connection_t *conn) {
conn->last_activity = time(NULL);
conn->last_activity = cached_time;
if (conn->type == CONN_TYPE_UPSTREAM && conn->ssl && !conn->ssl_handshake_done) {
handle_ssl_handshake(conn);

View File

@ -6,7 +6,9 @@
extern connection_t connections[MAX_FDS];
extern int epoll_fd;
extern time_t cached_time;
void connection_update_cached_time(void);
void connection_init_all(void);
void connection_setup_listener(int port);
void connection_accept(int listener_fd);
@ -16,6 +18,8 @@ void connection_cleanup_idle(void);
int connection_set_non_blocking(int fd);
void connection_set_tcp_keepalive(int fd);
void connection_set_tcp_nodelay(int fd);
void connection_optimize_socket(int fd, int is_upstream);
void connection_add_to_epoll(int fd, uint32_t events);
void connection_modify_epoll(int fd, uint32_t events);

View File

@ -196,11 +196,13 @@ int main(int argc, char *argv[]) {
break;
}
connection_update_cached_time();
for (int i = 0; i < n; i++) {
connection_handle_event(&events[i]);
}
time_t current_time = time(NULL);
time_t current_time = cached_time;
if (current_time > last_monitor_update) {
monitor_update();

View File

@ -6,6 +6,7 @@
#endif
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <time.h>
#include <openssl/ssl.h>
#include <sqlite3.h>
@ -99,6 +100,11 @@ typedef struct connection_s {
int response_headers_parsed;
long original_content_length;
long content_length_delta;
char *patch_buf;
size_t patch_buf_capacity;
int splice_pipe[2];
int can_splice;
uint32_t epoll_events;
} connection_t;
typedef struct {

View File

@ -85,6 +85,78 @@ void test_auth_route_basic_auth(void) {
TEST_SUITE_END();
}
void test_auth_route_full_flow(void) {
TEST_SUITE_BEGIN("Auth Route Full Flow");
route_config_t route;
memset(&route, 0, sizeof(route));
route.use_auth = 1;
strcpy(route.username, "routeuser");
strcpy(route.password_hash, "5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8");
char error[256];
int result = auth_check_route_basic_auth(&route, "Basic cm91dGV1c2VyOnBhc3N3b3Jk", error, sizeof(error));
TEST_ASSERT_EQ(1, result, "Valid route credentials accepted");
result = auth_check_route_basic_auth(&route, "Basic d3Jvbmd1c2VyOnBhc3N3b3Jk", error, sizeof(error));
TEST_ASSERT_EQ(0, result, "Wrong username rejected");
result = auth_check_route_basic_auth(&route, "Basic cm91dGV1c2VyOndyb25ncGFzcw==", error, sizeof(error));
TEST_ASSERT_EQ(0, result, "Wrong password rejected");
result = auth_check_route_basic_auth(&route, "Bearer token123", error, sizeof(error));
TEST_ASSERT_EQ(0, result, "Bearer auth rejected for route");
result = auth_check_route_basic_auth(&route, "Basic !!invalid!!", error, sizeof(error));
TEST_ASSERT_EQ(0, result, "Invalid base64 rejected for route");
result = auth_check_route_basic_auth(&route, "Basic bm9jb2xvbg==", error, sizeof(error));
TEST_ASSERT_EQ(0, result, "Base64 without colon rejected for route");
TEST_SUITE_END();
}
void test_auth_base64_edge_cases(void) {
TEST_SUITE_BEGIN("Auth Base64 Edge Cases");
auth_init("admin", "pass");
char error[256];
int result = auth_check_basic_auth("Basic YWRtaW46cGFzcw==", error, sizeof(error));
TEST_ASSERT_EQ(1, result, "Standard base64 works");
result = auth_check_basic_auth("Basic YTo=", error, sizeof(error));
TEST_ASSERT_EQ(0, result, "Short credentials rejected");
result = auth_check_basic_auth("Basic YTpi", error, sizeof(error));
TEST_ASSERT_EQ(0, result, "Minimal base64 without padding");
TEST_SUITE_END();
}
void test_auth_error_messages(void) {
TEST_SUITE_BEGIN("Auth Error Messages");
auth_init("admin", "secret");
char error[256];
memset(error, 0, sizeof(error));
auth_check_basic_auth(NULL, error, sizeof(error));
TEST_ASSERT(strlen(error) > 0, "Error message set for NULL header");
memset(error, 0, sizeof(error));
auth_check_basic_auth("Invalid", error, sizeof(error));
TEST_ASSERT(strlen(error) > 0, "Error message set for invalid method");
auth_check_basic_auth(NULL, NULL, 0);
TEST_ASSERT(1, "NULL error buffer handled");
TEST_SUITE_END();
}
void test_auth_disabled_passthrough(void) {
TEST_SUITE_BEGIN("Auth Disabled Passthrough");
@ -103,5 +175,8 @@ void run_auth_tests(void) {
test_auth_check_credentials();
test_auth_check_basic_auth();
test_auth_route_basic_auth();
test_auth_route_full_flow();
test_auth_base64_edge_cases();
test_auth_error_messages();
test_auth_disabled_passthrough();
}

View File

@ -149,6 +149,123 @@ void test_buffer_multiple_operations(void) {
TEST_SUITE_END();
}
void test_buffer_null_safety(void) {
TEST_SUITE_BEGIN("Buffer NULL Safety");
TEST_ASSERT_EQ(-1, buffer_init(NULL, 1024), "NULL buffer init returns -1");
buffer_free(NULL);
TEST_ASSERT(1, "NULL buffer free doesn't crash");
TEST_ASSERT_EQ(0, buffer_available_read(NULL), "NULL buffer read returns 0");
TEST_ASSERT_EQ(0, buffer_available_write(NULL), "NULL buffer write returns 0");
TEST_ASSERT_EQ(-1, buffer_ensure_capacity(NULL, 1024), "NULL buffer ensure capacity returns -1");
buffer_compact(NULL);
TEST_ASSERT(1, "NULL buffer compact doesn't crash");
buffer_consume(NULL, 100);
TEST_ASSERT(1, "NULL buffer consume doesn't crash");
TEST_SUITE_END();
}
void test_buffer_consume_overflow(void) {
TEST_SUITE_BEGIN("Buffer Consume Overflow");
buffer_t buf;
buffer_init(&buf, 64);
memcpy(buf.data, "TEST", 4);
buf.tail = 4;
buffer_consume(&buf, 100);
TEST_ASSERT_EQ(0, buf.head, "Head reset after over-consume");
TEST_ASSERT_EQ(0, buf.tail, "Tail reset after over-consume");
TEST_ASSERT_EQ(0, buffer_available_read(&buf), "No data after over-consume");
buffer_free(&buf);
TEST_SUITE_END();
}
void test_buffer_compact_edge_cases(void) {
TEST_SUITE_BEGIN("Buffer Compact Edge Cases");
buffer_t buf;
buffer_init(&buf, 64);
buffer_compact(&buf);
TEST_ASSERT_EQ(0, buf.head, "Compact empty buffer - head is 0");
TEST_ASSERT_EQ(0, buf.tail, "Compact empty buffer - tail is 0");
memcpy(buf.data, "TEST", 4);
buf.tail = 4;
buf.head = 0;
buffer_compact(&buf);
TEST_ASSERT_EQ(0, buf.head, "Compact with head=0 unchanged");
buffer_free(&buf);
TEST_SUITE_END();
}
void test_buffer_capacity_limits(void) {
TEST_SUITE_BEGIN("Buffer Capacity Limits");
buffer_t buf;
buffer_init(&buf, 64);
int result = buffer_ensure_capacity(&buf, MAX_BUFFER_SIZE + 1);
TEST_ASSERT_EQ(-1, result, "Exceeding MAX_BUFFER_SIZE returns -1");
buffer_free(&buf);
TEST_SUITE_END();
}
void test_buffer_large_growth(void) {
TEST_SUITE_BEGIN("Buffer Large Growth");
buffer_t buf;
buffer_init(&buf, 64);
int result = buffer_ensure_capacity(&buf, 1024);
TEST_ASSERT_EQ(0, result, "Grow to 1024 succeeds");
TEST_ASSERT(buf.capacity >= 1024, "Capacity at least 1024");
result = buffer_ensure_capacity(&buf, 4096);
TEST_ASSERT_EQ(0, result, "Grow to 4096 succeeds");
TEST_ASSERT(buf.capacity >= 4096, "Capacity at least 4096");
result = buffer_ensure_capacity(&buf, 65536);
TEST_ASSERT_EQ(0, result, "Grow to 65536 succeeds");
TEST_ASSERT(buf.capacity >= 65536, "Capacity at least 65536");
buffer_free(&buf);
TEST_SUITE_END();
}
void test_buffer_near_max_capacity(void) {
TEST_SUITE_BEGIN("Buffer Near Max Capacity");
buffer_t buf;
buffer_init(&buf, 1024);
int result = buffer_ensure_capacity(&buf, MAX_BUFFER_SIZE - 1024);
TEST_ASSERT_EQ(0, result, "Grow to near max succeeds");
result = buffer_ensure_capacity(&buf, MAX_BUFFER_SIZE);
TEST_ASSERT_EQ(0, result, "Grow to exactly max succeeds");
TEST_ASSERT(buf.capacity <= MAX_BUFFER_SIZE, "Capacity at max");
buffer_free(&buf);
TEST_SUITE_END();
}
void run_buffer_tests(void) {
test_buffer_init();
test_buffer_read_write();
@ -156,4 +273,10 @@ void run_buffer_tests(void) {
test_buffer_compact();
test_buffer_ensure_capacity();
test_buffer_multiple_operations();
test_buffer_null_safety();
test_buffer_consume_overflow();
test_buffer_compact_edge_cases();
test_buffer_capacity_limits();
test_buffer_large_growth();
test_buffer_near_max_capacity();
}

File diff suppressed because it is too large Load Diff

View File

@ -30,6 +30,7 @@ extern void run_dashboard_tests(void);
extern void run_health_check_tests(void);
extern void run_ssl_handler_tests(void);
extern void run_connection_tests(void);
extern void run_logging_tests(void);
int main(int argc, char *argv[]) {
(void)argc;
@ -54,6 +55,7 @@ int main(int argc, char *argv[]) {
run_health_check_tests();
run_ssl_handler_tests();
run_connection_tests();
run_logging_tests();
test_summary();

View File

@ -230,6 +230,91 @@ void test_monitor_update(void) {
TEST_SUITE_END();
}
void test_monitor_record_request_end(void) {
TEST_SUITE_BEGIN("Monitor Record Request End");
monitor_init(NULL);
vhost_stats_t *stats = monitor_get_or_create_vhost_stats("timing.test.com");
TEST_ASSERT(stats != NULL, "Stats created");
double start_time = 1000.0;
monitor_record_request_end(stats, start_time);
TEST_ASSERT(stats->avg_request_time_ms >= 0, "Avg request time recorded");
monitor_record_request_end(stats, start_time);
monitor_record_request_end(stats, start_time);
TEST_ASSERT(1, "Multiple request end calls work");
monitor_record_request_end(NULL, 0);
TEST_ASSERT(1, "NULL stats doesn't crash");
monitor_cleanup();
TEST_SUITE_END();
}
void test_monitor_deque_overflow(void) {
TEST_SUITE_BEGIN("Monitor Deque Overflow Behavior");
history_deque_t dq;
history_deque_init(&dq, 3);
history_deque_push(&dq, 1.0, 10.0);
history_deque_push(&dq, 2.0, 20.0);
history_deque_push(&dq, 3.0, 30.0);
TEST_ASSERT_EQ(3, dq.count, "Count is at capacity");
history_deque_push(&dq, 4.0, 40.0);
TEST_ASSERT_EQ(3, dq.count, "Count stays at capacity");
history_deque_push(&dq, 5.0, 50.0);
history_deque_push(&dq, 6.0, 60.0);
TEST_ASSERT_EQ(3, dq.count, "Count still at capacity after multiple overflows");
free(dq.points);
TEST_SUITE_END();
}
void test_monitor_network_deque_overflow(void) {
TEST_SUITE_BEGIN("Monitor Network Deque Overflow");
network_history_deque_t dq;
network_history_deque_init(&dq, 3);
network_history_deque_push(&dq, 1.0, 100.0, 50.0);
network_history_deque_push(&dq, 2.0, 200.0, 100.0);
network_history_deque_push(&dq, 3.0, 300.0, 150.0);
TEST_ASSERT_EQ(3, dq.count, "Count is at capacity");
network_history_deque_push(&dq, 4.0, 400.0, 200.0);
TEST_ASSERT_EQ(3, dq.count, "Count stays at capacity");
free(dq.points);
TEST_SUITE_END();
}
void test_monitor_disk_deque_overflow(void) {
TEST_SUITE_BEGIN("Monitor Disk Deque Overflow");
disk_history_deque_t dq;
disk_history_deque_init(&dq, 3);
disk_history_deque_push(&dq, 1.0, 10.0, 5.0);
disk_history_deque_push(&dq, 2.0, 20.0, 10.0);
disk_history_deque_push(&dq, 3.0, 30.0, 15.0);
TEST_ASSERT_EQ(3, dq.count, "Count is at capacity");
disk_history_deque_push(&dq, 4.0, 40.0, 20.0);
TEST_ASSERT_EQ(3, dq.count, "Count stays at capacity");
free(dq.points);
TEST_SUITE_END();
}
void run_monitor_tests(void) {
test_history_deque_init();
test_history_deque_push();
@ -241,4 +326,8 @@ void run_monitor_tests(void) {
test_monitor_record_request();
test_monitor_record_bytes();
test_monitor_update();
test_monitor_record_request_end();
test_monitor_deque_overflow();
test_monitor_network_deque_overflow();
test_monitor_disk_deque_overflow();
}

View File

@ -224,6 +224,93 @@ void test_patch_apply_block_rule(void) {
TEST_SUITE_END();
}
void test_patch_apply_small_output_buffer(void) {
TEST_SUITE_BEGIN("Patch Apply Small Output Buffer");
patch_config_t config;
memset(&config, 0, sizeof(config));
config.rule_count = 1;
strcpy(config.rules[0].key, "x");
config.rules[0].key_len = 1;
strcpy(config.rules[0].value, "longer");
config.rules[0].value_len = 6;
config.rules[0].is_null = 0;
const char *input = "x x x x x x x x x x";
char output[10];
patch_result_t result = patch_apply(&config, input, strlen(input), output, sizeof(output));
TEST_ASSERT_EQ(0, result.should_block, "Small buffer does not block");
TEST_ASSERT(result.output_len <= sizeof(output), "Output truncated to buffer size");
TEST_SUITE_END();
}
void test_patch_apply_null_input(void) {
TEST_SUITE_BEGIN("Patch Apply NULL Input");
patch_config_t config;
memset(&config, 0, sizeof(config));
config.rule_count = 1;
strcpy(config.rules[0].key, "test");
config.rules[0].key_len = 4;
strcpy(config.rules[0].value, "demo");
config.rules[0].value_len = 4;
char output[256];
patch_result_t result = patch_apply(&config, NULL, 0, output, sizeof(output));
TEST_ASSERT_EQ(0, result.should_block, "NULL input does not block");
result = patch_apply(NULL, "test", 4, output, sizeof(output));
TEST_ASSERT_EQ(0, result.should_block, "NULL config does not block");
TEST_SUITE_END();
}
void test_patch_check_block_edge_cases(void) {
TEST_SUITE_BEGIN("Patch Check Block Edge Cases");
patch_config_t config;
memset(&config, 0, sizeof(config));
config.rule_count = 1;
strcpy(config.rules[0].key, "verylongkeythatwontmatch");
config.rules[0].key_len = strlen("verylongkeythatwontmatch");
config.rules[0].is_null = 1;
const char *short_data = "hi";
TEST_ASSERT_EQ(0, patch_check_for_block(&config, short_data, strlen(short_data)),
"Key longer than data doesn't match");
config.rules[0].key_len = 0;
TEST_ASSERT_EQ(0, patch_check_for_block(&config, "any data", 8),
"Zero length key doesn't match");
TEST_SUITE_END();
}
void test_patch_apply_only_block_rules(void) {
TEST_SUITE_BEGIN("Patch Apply Only Block Rules");
patch_config_t config;
memset(&config, 0, sizeof(config));
config.rule_count = 2;
strcpy(config.rules[0].key, "bad1");
config.rules[0].key_len = 4;
config.rules[0].is_null = 1;
strcpy(config.rules[1].key, "bad2");
config.rules[1].key_len = 4;
config.rules[1].is_null = 1;
const char *input = "good content here";
char output[256];
patch_result_t result = patch_apply(&config, input, strlen(input), output, sizeof(output));
TEST_ASSERT_EQ(0, result.should_block, "No block when content is clean");
TEST_ASSERT_EQ(strlen(input), result.output_len, "Output unchanged with only block rules");
TEST_SUITE_END();
}
void run_patch_tests(void) {
test_patch_has_rules();
test_patch_check_for_block();
@ -234,4 +321,8 @@ void run_patch_tests(void) {
test_patch_apply_no_match();
test_patch_apply_empty_config();
test_patch_apply_block_rule();
test_patch_apply_small_output_buffer();
test_patch_apply_null_input();
test_patch_check_block_edge_cases();
test_patch_apply_only_block_rules();
}

View File

@ -191,6 +191,85 @@ void test_ssl_reinit(void) {
TEST_SUITE_END();
}
void test_ssl_handshake_already_done(void) {
TEST_SUITE_BEGIN("SSL Handshake Already Done");
connection_t conn;
memset(&conn, 0, sizeof(conn));
conn.ssl_handshake_done = 1;
ssl_set_verify(0);
ssl_init();
conn.ssl = SSL_new(ssl_ctx);
TEST_ASSERT(conn.ssl != NULL, "SSL object created");
int result = ssl_do_handshake(&conn);
TEST_ASSERT_EQ(1, result, "Already done returns 1");
SSL_free(conn.ssl);
ssl_cleanup();
TEST_SUITE_END();
}
void test_ssl_read_no_handshake(void) {
TEST_SUITE_BEGIN("SSL Read Without Handshake");
connection_t conn;
memset(&conn, 0, sizeof(conn));
conn.ssl_handshake_done = 0;
ssl_set_verify(0);
ssl_init();
conn.ssl = SSL_new(ssl_ctx);
TEST_ASSERT(conn.ssl != NULL, "SSL object created");
char buf[100];
int result = ssl_read(&conn, buf, sizeof(buf));
TEST_ASSERT_EQ(-1, result, "Read without handshake returns -1");
SSL_free(conn.ssl);
ssl_cleanup();
TEST_SUITE_END();
}
void test_ssl_write_no_handshake(void) {
TEST_SUITE_BEGIN("SSL Write Without Handshake");
connection_t conn;
memset(&conn, 0, sizeof(conn));
conn.ssl_handshake_done = 0;
ssl_set_verify(0);
ssl_init();
conn.ssl = SSL_new(ssl_ctx);
TEST_ASSERT(conn.ssl != NULL, "SSL object created");
const char *buf = "test";
int result = ssl_write(&conn, buf, strlen(buf));
TEST_ASSERT_EQ(-1, result, "Write without handshake returns -1");
SSL_free(conn.ssl);
ssl_cleanup();
TEST_SUITE_END();
}
void test_ssl_verify_with_ca(void) {
TEST_SUITE_BEGIN("SSL Verify With CA Settings");
ssl_set_verify(1);
ssl_set_ca_file("/etc/ssl/certs/ca-certificates.crt");
ssl_set_ca_path("/etc/ssl/certs");
ssl_init();
TEST_ASSERT(ssl_ctx != NULL, "Context created with CA settings");
ssl_cleanup();
TEST_SUITE_END();
}
void run_ssl_handler_tests(void) {
test_ssl_init_cleanup();
test_ssl_multiple_init();
@ -202,4 +281,8 @@ void run_ssl_handler_tests(void) {
test_ssl_handshake_null();
test_ssl_read_write_null();
test_ssl_reinit();
test_ssl_handshake_already_done();
test_ssl_read_no_handshake();
test_ssl_write_no_handshake();
test_ssl_verify_with_ca();
}