feat: harden errors, recovery, and resource usage
This commit is contained in:
+7
-2
@@ -1,7 +1,7 @@
|
||||
CC ?= cc
|
||||
CFLAGS ?= -std=c11 -Wall -Wextra -Werror -I../../include
|
||||
|
||||
all: test_command_queue test_game_domain test_bot_player test_game_lifecycle test_state_presenter test_http_api test_sync_service test_human_game_integration test_bot_game_integration
|
||||
all: test_command_queue test_game_domain test_bot_player test_game_lifecycle test_state_presenter test_http_api test_sync_service test_human_game_integration test_bot_game_integration test_robustness
|
||||
|
||||
test_command_queue: test_command_queue.c ../../src/command_queue.c
|
||||
$(CC) $(CFLAGS) $^ -o $@
|
||||
@@ -16,6 +16,7 @@ run: all
|
||||
./test_sync_service
|
||||
./test_human_game_integration
|
||||
./test_bot_game_integration
|
||||
./test_robustness
|
||||
|
||||
test_game_domain: test_game_domain.c ../../src/fleet_generator.c ../../src/game_engine.c
|
||||
$(CC) $(CFLAGS) $^ -o $@
|
||||
@@ -41,5 +42,9 @@ test_human_game_integration: test_human_game_integration.c ../../src/http_api.c
|
||||
test_bot_game_integration: test_bot_game_integration.c ../../src/session_manager.c ../../src/game_lifecycle.c ../../src/bot_player.c ../../src/fleet_generator.c ../../src/game_engine.c
|
||||
$(CC) $(CFLAGS) $^ -o $@
|
||||
|
||||
test_robustness: test_robustness.c ../../src/http_api.c ../../src/sync_service.c ../../src/application.c ../../src/command_queue.c ../../src/session_manager.c ../../src/game_lifecycle.c ../../src/bot_player.c ../../src/fleet_generator.c ../../src/game_engine.c ../../src/state_presenter.c
|
||||
|
||||
$(CC) $(CFLAGS) $^ -o $@
|
||||
|
||||
clean:
|
||||
rm -f test_command_queue test_game_domain test_bot_player test_game_lifecycle test_state_presenter test_http_api test_sync_service test_human_game_integration test_bot_game_integration
|
||||
rm -f test_command_queue test_game_domain test_bot_player test_game_lifecycle test_state_presenter test_http_api test_sync_service test_human_game_integration test_bot_game_integration test_robustness
|
||||
|
||||
@@ -18,7 +18,7 @@ static http_api_t new_api(application_t *application, test_random_t *random) {
|
||||
http_api_init(&api, application);
|
||||
http_api_set_health(&api, &(http_api_health_t){.uptime_ms = 12U, .free_heap_bytes = 200000U,
|
||||
.minimum_free_heap_bytes = 190000U, .largest_free_block_bytes = 180000U,
|
||||
.connected_clients = 2U, .rejected_input = 3U, .wifi_state = "connected"});
|
||||
.connected_clients = 2U, .rejected_input = 3U, .reset_reason = 11, .wifi_state = "connected"});
|
||||
return api;
|
||||
}
|
||||
|
||||
@@ -55,7 +55,8 @@ static void test_public_routes_and_parse_limits(void) {
|
||||
http_api_response_t response = call(&api, HTTP_API_ROUTE_INFO, HTTP_API_GET, NULL, NULL);
|
||||
assert(response.status == 200U && response.body_length <= 192U && strstr(response.body, "player1Available") != NULL);
|
||||
response = call(&api, HTTP_API_ROUTE_HEALTH, HTTP_API_GET, NULL, NULL);
|
||||
assert(response.status == 200U && response.body_length <= 320U && strstr(response.body, "connected") != NULL);
|
||||
assert(response.status == 200U && response.body_length <= 320U && strstr(response.body, "connected") != NULL &&
|
||||
strstr(response.body, "\"resetReason\":11") != NULL);
|
||||
const http_api_request_t long_target = {.method = HTTP_API_GET, .route = HTTP_API_ROUTE_INFO, .target_too_large = true};
|
||||
assert(http_api_handle(&api, &long_target, &response));
|
||||
expect_code(&response, 413U, "PAYLOAD_TOO_LARGE");
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "http_api.h"
|
||||
#include "sync_service.h"
|
||||
|
||||
typedef struct { uint32_t value; } test_random_t;
|
||||
|
||||
static uint32_t next_random(void *context) {
|
||||
test_random_t *random = context;
|
||||
random->value = random->value * 1664525U + 1013904223U;
|
||||
return random->value;
|
||||
}
|
||||
|
||||
static application_t new_application(test_random_t *random) {
|
||||
application_t application;
|
||||
application_init(&application, (random_source_t){.next_u32 = next_random, .context = random});
|
||||
return application;
|
||||
}
|
||||
|
||||
static void test_http_malformed_inputs_do_not_mutate(void) {
|
||||
test_random_t random = {.value = 7U};
|
||||
application_t application = new_application(&random);
|
||||
http_api_t api;
|
||||
http_api_init(&api, &application);
|
||||
const char *const corpus[] = {"", "{", "[]", "{\"token\":", "{\"token\":null}",
|
||||
"{\"name\":\"A\"}", "{\"token\":\"00000000000000000000000000000000\",\"gameId\":-1}",
|
||||
"{\"type\":\"shot\"}", "{\"name\":\"A\",\"requestedRole\":\"player1\",\"extra\":1}"};
|
||||
const http_api_route_t routes[] = {HTTP_API_ROUTE_JOIN, HTTP_API_ROUTE_RESUME, HTTP_API_ROUTE_CONFIG,
|
||||
HTTP_API_ROUTE_START, HTTP_API_ROUTE_SHOT, HTTP_API_ROUTE_REMATCH, HTTP_API_ROUTE_ABORT};
|
||||
for (size_t index = 0; index < 400U; ++index) {
|
||||
const char *body = corpus[index % (sizeof(corpus) / sizeof(corpus[0]))];
|
||||
const game_lifecycle_t before = application.lifecycle;
|
||||
http_api_response_t response;
|
||||
const http_api_request_t request = {.method = HTTP_API_POST, .route = routes[index % (sizeof(routes) / sizeof(routes[0]))],
|
||||
.content_type_json = true, .body = body, .body_length = strlen(body)};
|
||||
assert(http_api_handle(&api, &request, &response));
|
||||
assert(response.status >= 400U && response.status < 600U && response.body_length < sizeof(response.body));
|
||||
assert(memcmp(&before, &application.lifecycle, sizeof(before)) == 0);
|
||||
}
|
||||
char oversized[kRequestBodyCapacity + 2U];
|
||||
memset(oversized, 'x', sizeof(oversized) - 1U);
|
||||
oversized[sizeof(oversized) - 1U] = '\0';
|
||||
http_api_response_t response;
|
||||
const http_api_request_t request = {.method = HTTP_API_POST, .route = HTTP_API_ROUTE_JOIN, .content_type_json = true,
|
||||
.body = oversized, .body_length = strlen(oversized)};
|
||||
assert(http_api_handle(&api, &request, &response));
|
||||
assert(response.status == 413U);
|
||||
}
|
||||
|
||||
static void test_websocket_fuzz_and_connection_churn(void) {
|
||||
test_random_t random = {.value = 19U};
|
||||
application_t application = new_application(&random);
|
||||
sync_service_t service;
|
||||
sync_service_init(&service, &application);
|
||||
for (uint16_t attempt = 0; attempt < 600U; ++attempt) {
|
||||
char frame[kWebSocketFrameCapacity + 1U];
|
||||
const size_t length = (size_t)(next_random(&random) % (kWebSocketFrameCapacity + 1U));
|
||||
for (size_t index = 0; index < length; ++index) frame[index] = (char)(next_random(&random) & 0x7fU);
|
||||
frame[length] = '\0';
|
||||
const game_lifecycle_t before = application.lifecycle;
|
||||
char output[kStateMessageCapacity] = {0};
|
||||
size_t output_length = 0U;
|
||||
bool changed = true;
|
||||
bool close = false;
|
||||
assert(sync_service_open(&service, 1, attempt));
|
||||
assert(sync_service_receive(&service, 1, frame, length, attempt, output, &output_length, &changed, &close));
|
||||
assert(!changed && output_length < sizeof(output));
|
||||
assert(memcmp(&before, &application.lifecycle, sizeof(before)) == 0);
|
||||
sync_service_close(&service, 1);
|
||||
}
|
||||
char too_large[kWebSocketFrameCapacity + 1U] = {0};
|
||||
char output[kStateMessageCapacity] = {0};
|
||||
size_t output_length = 0U;
|
||||
bool changed = false;
|
||||
bool close = false;
|
||||
assert(sync_service_open(&service, 1, 0U));
|
||||
assert(sync_service_receive(&service, 1, too_large, sizeof(too_large), 0U, output, &output_length, &changed, &close));
|
||||
assert(!changed && strstr(output, "MALFORMED_JSON") != NULL);
|
||||
sync_service_close(&service, 1);
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
test_http_malformed_inputs_do_not_mutate();
|
||||
test_websocket_fuzz_and_connection_churn();
|
||||
puts("robustness tests passed");
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user