From f725e63fb0718a327c48869f9c4aef3c8daa7f0e Mon Sep 17 00:00:00 2001 From: Francesco Cozzuto Date: Mon, 12 May 2025 22:23:33 +0200 Subject: [PATCH] Add engine tests --- .gitignore | 11 +- Makefile | 17 +- README.md | 2 +- tests/test.c | 77 +++++- tests/test.h | 12 + tests/test_branch_coverage_engine.c | 138 +++++++++++ ...overage.c => test_branch_coverage_parse.c} | 37 +-- tests/test_fuzz_engine.c | 230 ++++++++++++++++++ tinyhttp.c | 225 +++++++++-------- tinyhttp.h | 71 +++--- 10 files changed, 638 insertions(+), 182 deletions(-) create mode 100644 tests/test.h create mode 100644 tests/test_branch_coverage_engine.c rename tests/{test_branch_coverage.c => test_branch_coverage_parse.c} (93%) create mode 100644 tests/test_fuzz_engine.c diff --git a/.gitignore b/.gitignore index 99687df..7d649b6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,11 @@ misc -coverage_report \ No newline at end of file +coverage_report + +# Coverage stuff +*.info +*.gcda +*.gcno +*.exe + +test.out +test.exe diff --git a/Makefile b/Makefile index d93c930..f2e5642 100644 --- a/Makefile +++ b/Makefile @@ -1,14 +1,23 @@ .PHONY: all report -#LFLAGS = -lws2_32 -LFLAGS = -lssl -lcrypto -ggdb +OSTAG = LINUX +#OSTAG = WINDOWS + +EXT_WINDOWS = .exe +EXT_LINUX = .out + +LFLAGS_WINDOWS = -lws2_32 +LFLAGS_LINUX = -lssl -lcrypto -ggdb + +LFLAGS = ${LFLAGS_${OSTAG}} +EXT = ${EXT_${OSTAG}} all: - #gcc -o test tests/test.c tests/test_branch_coverage.c http.c -fprofile-arcs -ftest-coverage + gcc -o test$(EXT) tests/test.c tests/test_branch_coverage_parse.c tests/test_branch_coverage_engine.c tests/test_fuzz_engine.c tinyhttp.c -fprofile-arcs -ftest-coverage $(LFLAGS) report: lcov --capture --directory . --output-file coverage.info --rc lcov_branch_coverage=1 genhtml coverage.info --output-directory coverage_report --rc lcov_branch_coverage=1 --rc genhtml_branch_coverage=1 clean: - rm *.gcda *.gcno coverage.info test test.exe + rm *.gcda *.gcno coverage.info test$(EXT_WINDOWS) test$(EXT_LINUX) diff --git a/README.md b/README.md index 5b2a8f7..909053c 100644 --- a/README.md +++ b/README.md @@ -203,4 +203,4 @@ int main(void) return http_serve("127.0.0.1", 8080, router); } -``` \ No newline at end of file +``` diff --git a/tests/test.c b/tests/test.c index 67cf499..0737065 100644 --- a/tests/test.c +++ b/tests/test.c @@ -1,9 +1,76 @@ +#include #include -#include "../http.h" +#include "test.h" -#define TEST(X) {if (!(X)) __builtin_trap(); } +static const char *statestr(HTTP_EngineState state) +{ + switch (state) { + case HTTP_ENGINE_STATE_NONE : return "NONE"; + case HTTP_ENGINE_STATE_CLIENT_PREP_URL : return "CLIENT_PREP_URL"; + case HTTP_ENGINE_STATE_CLIENT_PREP_HEADER : return "CLIENT_PREP_HEADER"; + case HTTP_ENGINE_STATE_CLIENT_PREP_BODY_BUF: return "CLIENT_PREP_BODY_BUF"; + case HTTP_ENGINE_STATE_CLIENT_PREP_BODY_ACK: return "CLIENT_PREP_BODY_ACK"; + case HTTP_ENGINE_STATE_CLIENT_PREP_ERROR : return "CLIENT_PREP_ERROR"; + case HTTP_ENGINE_STATE_CLIENT_SEND_BUF : return "CLIENT_SEND_BUF"; + case HTTP_ENGINE_STATE_CLIENT_SEND_ACK : return "CLIENT_SEND_ACK"; + case HTTP_ENGINE_STATE_CLIENT_RECV_BUF : return "CLIENT_RECV_BUF"; + case HTTP_ENGINE_STATE_CLIENT_RECV_ACK : return "CLIENT_RECV_ACK"; + case HTTP_ENGINE_STATE_CLIENT_READY : return "CLIENT_READY"; + case HTTP_ENGINE_STATE_CLIENT_CLOSED : return "CLIENT_CLOSED"; + case HTTP_ENGINE_STATE_SERVER_RECV_BUF : return "SERVER_RECV_BUF"; + case HTTP_ENGINE_STATE_SERVER_RECV_ACK : return "SERVER_RECV_ACK"; + case HTTP_ENGINE_STATE_SERVER_PREP_STATUS : return "SERVER_PREP_STATUS"; + case HTTP_ENGINE_STATE_SERVER_PREP_HEADER : return "SERVER_PREP_HEADER"; + case HTTP_ENGINE_STATE_SERVER_PREP_BODY_BUF: return "SERVER_PREP_BODY_BUF"; + case HTTP_ENGINE_STATE_SERVER_PREP_BODY_ACK: return "SERVER_PREP_BODY_ACK"; + case HTTP_ENGINE_STATE_SERVER_PREP_ERROR : return "SERVER_PREP_ERROR"; + case HTTP_ENGINE_STATE_SERVER_SEND_BUF : return "SERVER_SEND_BUF"; + case HTTP_ENGINE_STATE_SERVER_SEND_ACK : return "SERVER_SEND_ACK"; + case HTTP_ENGINE_STATE_SERVER_CLOSED : return "SERVER_CLOSED"; + } + return "???"; +} -void test_branch_coverage(void); +void testeq_engstate(HTTP_EngineState l, HTTP_EngineState r, HTTP_String uneval_l, HTTP_String uneval_r, const char *file, int line) +{ + if (l != r) { + printf("Test failed at %s:%d\n", file, line); + printf(" TEST_EQ(%.*s, %.*s) -> TEST_EQ(%s, %s)\n", + (int) uneval_l.len, uneval_l.ptr, + (int) uneval_r.len, uneval_r.ptr, + statestr(l), statestr(r)); + abort(); + } +} + +void testeq_int(int l, int r, HTTP_String uneval_l, HTTP_String uneval_r, const char *file, int line) +{ + if (l != r) { + printf("Test failed at %s:%d\n", file, line); + printf(" TEST_EQ(%.*s, %.*s) -> TEST_EQ(%d, %d)\n", + (int) uneval_l.len, uneval_l.ptr, + (int) uneval_r.len, uneval_r.ptr, + l, r); + abort(); + } +} + +void testeq_str(HTTP_String l, HTTP_String r, HTTP_String uneval_l, HTTP_String uneval_r, const char *file, int line) +{ + if (!http_streq(l, r)) { + printf("Test failed at %s:%d\n", file, line); + printf(" TEST_EQ(\"%.*s\", \"%.*s\") -> TEST_EQ(%.*s, %.*s)\n", + (int) uneval_l.len, uneval_l.ptr, + (int) uneval_r.len, uneval_r.ptr, + (int) l.len, l.ptr, + (int) r.len, r.ptr); + abort(); + } +} + +void test_branch_coverage_parse(void); +void test_branch_coverage_engine(void); +void test_fuzz_engine(void); int main(void) { @@ -16,7 +83,9 @@ int main(void) TEST(ret == 0); } - test_branch_coverage(); + //test_branch_coverage_parse(); + test_branch_coverage_engine(); + //test_fuzz_engine(); return 0; } diff --git a/tests/test.h b/tests/test.h new file mode 100644 index 0000000..c21432a --- /dev/null +++ b/tests/test.h @@ -0,0 +1,12 @@ +#include +#include "../tinyhttp.h" + +#define S HTTP_STR + +#define COUNT(X) (int) (sizeof(X)/sizeof((X)[0])) +#define TEST(X) {if (!(X)) { fprintf(stderr, "Failed test at %s:%d\n", __FILE__, __LINE__); __builtin_trap(); }} + +void testeq_engstate(HTTP_EngineState l, HTTP_EngineState r, HTTP_String uneval_l, HTTP_String uneval_r, const char *file, int line); +void testeq_int(int l, int r, HTTP_String uneval_l, HTTP_String uneval_r, const char *file, int line); +void testeq_str(HTTP_String l, HTTP_String r, HTTP_String uneval_l, HTTP_String uneval_r, const char *file, int line); +#define TEST_EQ(X, Y) _Generic((X), HTTP_String: testeq_str, int: testeq_int, HTTP_EngineState: testeq_engstate)((X), (Y), S(#X), S(#Y), __FILE__, __LINE__) diff --git a/tests/test_branch_coverage_engine.c b/tests/test_branch_coverage_engine.c new file mode 100644 index 0000000..203ee42 --- /dev/null +++ b/tests/test_branch_coverage_engine.c @@ -0,0 +1,138 @@ +#include +#include +#include "test.h" + +static void *memfunc(HTTP_MemoryFuncTag tag, + void *ptr, int len, void *data) +{ + (void) data; + switch (tag) { + + case HTTP_MEMFUNC_MALLOC: + return malloc(len); + + case HTTP_MEMFUNC_FREE: + free(ptr); + return NULL; + } + return NULL; +} + +static void test_engine_server(void) +{ + HTTP_Engine eng; + + { + int client = 0; + memset(&eng, 0, sizeof(eng)); + http_engine_init(&eng, client, memfunc, NULL); + TEST_EQ((int) http_engine_state(&eng), HTTP_ENGINE_STATE_SERVER_RECV_BUF); + http_engine_free(&eng); + } + + { + int client = 1; + memset(&eng, 0, sizeof(eng)); + http_engine_init(&eng, client, memfunc, NULL); + TEST_EQ((int) http_engine_state(&eng), HTTP_ENGINE_STATE_CLIENT_PREP_URL); + http_engine_free(&eng); + } +} + +static int read_from_client_engine(HTTP_Engine *eng, char *dst, int cap) +{ + TEST_EQ(http_engine_state(eng), HTTP_ENGINE_STATE_CLIENT_SEND_BUF); + + int num = 0; + do { + int len; + char *src = http_engine_sendbuf(eng, &len); + TEST_EQ(http_engine_state(eng), HTTP_ENGINE_STATE_CLIENT_SEND_ACK); + + TEST(src != NULL); + TEST(cap - num >= len); + memcpy(dst + num, src, len); + + num += len; + http_engine_sendack(eng, len); + } while (http_engine_state(eng) == HTTP_ENGINE_STATEBIT_SEND_BUF); + + return num; +} + +static void send_into_client_engine(HTTP_Engine *eng, char *src, int len) +{ + int num = 0; + while (num < len) { + TEST_EQ(http_engine_state(eng), HTTP_ENGINE_STATE_CLIENT_RECV_BUF); + + int cap; + char *dst = http_engine_recvbuf(eng, &cap); + TEST_EQ(http_engine_state(eng), HTTP_ENGINE_STATE_CLIENT_RECV_ACK); + TEST(dst != NULL); + TEST(cap > 0); + + int cpy = len - num; + if (cpy > cap) cpy = cap; + + memcpy(dst, src + num, cpy); + + num += cpy; + http_engine_recvack(eng, cpy); + } +} + +static void test_engine_client_basic_flow(void) +{ + int client = 1; + HTTP_Engine eng; + http_engine_init(&eng, client, memfunc, NULL); + TEST_EQ(http_engine_state(&eng), HTTP_ENGINE_STATE_CLIENT_PREP_URL); + + http_engine_url(&eng, HTTP_METHOD_GET, "http://some.url.com/some/endpoint", 1); + TEST_EQ(http_engine_state(&eng), HTTP_ENGINE_STATE_CLIENT_PREP_HEADER); + + http_engine_header(&eng, "headerA: valueA", -1); + TEST_EQ(http_engine_state(&eng), HTTP_ENGINE_STATE_CLIENT_PREP_HEADER); + + http_engine_body(&eng, "Hello, world!", -1); + TEST_EQ(http_engine_state(&eng), HTTP_ENGINE_STATE_CLIENT_PREP_BODY_BUF); + + http_engine_done(&eng); + TEST_EQ(http_engine_state(&eng), HTTP_ENGINE_STATE_CLIENT_SEND_BUF); + + { + char sendbuf[1<<10]; + int sendnum; + sendnum = read_from_client_engine(&eng, sendbuf, (int) sizeof(sendbuf)); + + char expect[] = + "GET /some/endpoint HTTP/1.1\r\n" + "Host: some.url.com\r\n" + "headerA: valueA\r\n" + "Connection: Keep-Alive\r\n" + "Content-Length: 13 \r\n" + "\r\n" + "Hello, world!"; + + TEST_EQ(sendnum, strlen(expect)); + TEST_EQ(memcmp(expect, sendbuf, strlen(expect)), 0); + } + + { + char response[] = + "HTTP/1.1 200 OK\r\n" + "\r\n"; + send_into_client_engine(&eng, response, strlen(response)); + } + + TEST_EQ(http_engine_state(&eng), HTTP_ENGINE_STATE_CLIENT_READY); + + http_engine_free(&eng); +} + +void test_branch_coverage_engine(void) +{ + test_engine_server(); + test_engine_client_basic_flow(); +} diff --git a/tests/test_branch_coverage.c b/tests/test_branch_coverage_parse.c similarity index 93% rename from tests/test_branch_coverage.c rename to tests/test_branch_coverage_parse.c index b23d960..7017889 100644 --- a/tests/test_branch_coverage.c +++ b/tests/test_branch_coverage_parse.c @@ -1,37 +1,6 @@ -#include #include #include -#include "../http.h" - -#define COUNT(X) (int) (sizeof(X)/sizeof((X)[0])) -#define TEST(X) {if (!(X)) { fprintf(stderr, "Failed test at %s:%d\n", __FILE__, __LINE__); __builtin_trap(); }} - -#define TEST_EQ(X, Y) _Generic((X), HTTP_String: testeq_str, int: testeq_int)((X), (Y), S(#X), S(#Y), __FILE__, __LINE__) - -static void testeq_int(int l, int r, HTTP_String uneval_l, HTTP_String uneval_r, const char *file, int line) -{ - if (l != r) { - printf("Test failed at %s:%d\n", file, line); - printf(" TEST_EQ(%.*s, %.*s) -> TEST_EQ(%d, %d)\n", - (int) uneval_l.len, uneval_l.ptr, - (int) uneval_r.len, uneval_r.ptr, - l, r); - abort(); - } -} - -static void testeq_str(HTTP_String l, HTTP_String r, HTTP_String uneval_l, HTTP_String uneval_r, const char *file, int line) -{ - if (!http_streq(l, r)) { - printf("Test failed at %s:%d\n", file, line); - printf(" TEST_EQ(\"%.*s\", \"%.*s\") -> TEST_EQ(%.*s, %.*s)\n", - (int) uneval_l.len, uneval_l.ptr, - (int) uneval_r.len, uneval_r.ptr, - (int) l.len, l.ptr, - (int) r.len, r.ptr); - abort(); - } -} +#include "test.h" static void test_branch_coverage_parse_request(void) { @@ -314,8 +283,6 @@ static void test_branch_coverage_parse_response(void) #include #endif -#define S HTTP_STR - typedef struct { char *buf; int cap; @@ -510,7 +477,7 @@ static void test_branch_coverage_parse_url(void) } } -void test_branch_coverage(void) +void test_branch_coverage_parse(void) { test_branch_coverage_parse_request(); test_branch_coverage_parse_response(); diff --git a/tests/test_fuzz_engine.c b/tests/test_fuzz_engine.c new file mode 100644 index 0000000..36a2cdd --- /dev/null +++ b/tests/test_fuzz_engine.c @@ -0,0 +1,230 @@ +#include +#include +#include +#include "test.h" + +static void *memfunc(HTTP_MemoryFuncTag tag, + void *ptr, int len, void *data) +{ + (void) data; + switch (tag) { + + case HTTP_MEMFUNC_MALLOC: + { + void *ptr = NULL; + + int x = rand() & 31; + if (x == 0) + ptr = malloc(len); + return ptr; + } + + case HTTP_MEMFUNC_FREE: + free(ptr); + return NULL; + } + return NULL; +} + +const char *http_statestr(int state) +{ + switch (state) { + case HTTP_ENGINE_STATE_NONE : return "NONE"; + case HTTP_ENGINE_STATE_CLIENT_PREP_URL : return "CLIENT_PREP_URL"; + case HTTP_ENGINE_STATE_CLIENT_PREP_HEADER : return "CLIENT_PREP_HEADER"; + case HTTP_ENGINE_STATE_CLIENT_PREP_BODY_BUF: return "CLIENT_PREP_BODY_BUF"; + case HTTP_ENGINE_STATE_CLIENT_PREP_BODY_ACK: return "CLIENT_PREP_BODY_ACK"; + case HTTP_ENGINE_STATE_CLIENT_PREP_ERROR : return "CLIENT_PREP_ERROR"; + case HTTP_ENGINE_STATE_CLIENT_SEND_BUF : return "CLIENT_SEND_BUF"; + case HTTP_ENGINE_STATE_CLIENT_SEND_ACK : return "CLIENT_SEND_ACK"; + case HTTP_ENGINE_STATE_CLIENT_RECV_BUF : return "CLIENT_RECV_BUF"; + case HTTP_ENGINE_STATE_CLIENT_RECV_ACK : return "CLIENT_RECV_ACK"; + case HTTP_ENGINE_STATE_CLIENT_READY : return "CLIENT_READY"; + case HTTP_ENGINE_STATE_CLIENT_CLOSED : return "CLIENT_CLOSED"; + case HTTP_ENGINE_STATE_SERVER_RECV_BUF : return "SERVER_RECV_BUF"; + case HTTP_ENGINE_STATE_SERVER_RECV_ACK : return "SERVER_RECV_ACK"; + case HTTP_ENGINE_STATE_SERVER_PREP_STATUS : return "SERVER_PREP_STATUS"; + case HTTP_ENGINE_STATE_SERVER_PREP_HEADER : return "SERVER_PREP_HEADER"; + case HTTP_ENGINE_STATE_SERVER_PREP_BODY_BUF: return "SERVER_PREP_BODY_BUF"; + case HTTP_ENGINE_STATE_SERVER_PREP_BODY_ACK: return "SERVER_PREP_BODY_ACK"; + case HTTP_ENGINE_STATE_SERVER_PREP_ERROR : return "SERVER_PREP_ERROR"; + case HTTP_ENGINE_STATE_SERVER_SEND_BUF : return "SERVER_SEND_BUF"; + case HTTP_ENGINE_STATE_SERVER_SEND_ACK : return "SERVER_SEND_ACK"; + case HTTP_ENGINE_STATE_SERVER_CLOSED : return "SERVER_CLOSED"; + } + return "???"; +} + +static sig_atomic_t stop = 0; + +static void handle_signal(int dummy) +{ + stop = 1; +} + +void test_fuzz_engine(void) +{ + signal(SIGINT, handle_signal); + + char txt1[] = ""; + char txt2[] = "GET / HTTP/1.1\r\n\r\n"; + char txt3[] = "GET / HTTP/1.1\n"; + char txt4[] = "HTTP/1.1 200 OK\r\n"; + char txt5[] = "HTTP/1.1 200 OK\r\n\r\n"; + + int next_recvack = 0; + int next_sendack = 0; + int next_bodyack = 0; + int curr_bodycap = 0; + int client = 0; + HTTP_Engine eng; + http_engine_init(&eng, client, memfunc, NULL); + while (!stop) { + //printf("%s\n", http_statestr(http_engine_state(&eng))); + switch (rand() % 21) { + + int max; + char *buf; + + case 0: + //printf("http_engine_free/http_engine_init\n"); + client = !client; + http_engine_free(&eng); + http_engine_init(&eng, client, memfunc, NULL); + next_recvack = 0; + next_sendack = 0; + next_bodyack = 0; + curr_bodycap = 0; + break; + + case 1: + //printf("http_engine_recvbuf (1)\n"); + buf = http_engine_recvbuf(&eng, &max); + memcpy(buf, txt1, strlen(txt1)); + next_recvack = strlen(txt1); + break; + + case 2: + //printf("http_engine_recvbuf (2)\n"); + buf = http_engine_recvbuf(&eng, &max); + if (buf) { + memcpy(buf, txt2, strlen(txt2)); + next_recvack = strlen(txt2); + } + break; + + case 3: + //printf("http_engine_recvbuf (3)\n"); + buf = http_engine_recvbuf(&eng, &max); + if (buf) { + memcpy(buf, txt3, strlen(txt3)); + next_recvack = strlen(txt3); + } + break; + + case 4: + //printf("http_engine_recvbuf (4)\n"); + buf = http_engine_recvbuf(&eng, &max); + if (buf) { + memcpy(buf, txt4, strlen(txt4)); + next_recvack = strlen(txt4); + } + break; + + case 5: + //printf("http_engine_recvbuf (5)\n"); + buf = http_engine_recvbuf(&eng, &max); + if (buf) { + memcpy(buf, txt5, strlen(txt5)); + next_recvack = strlen(txt5); + } + break; + + case 6: + //printf("http_engine_recvack\n"); + http_engine_recvack(&eng, next_recvack); + next_recvack = 0; + break; + + case 7: + //printf("http_engine_sendbuf\n"); + buf = http_engine_sendbuf(&eng, &max); + if (max) + next_sendack = rand() % max; + break; + + case 8: + //printf("http_engine_sendack\n"); + http_engine_sendack(&eng, next_sendack); + next_sendack = 0; + break; + + case 9: + //printf("http_engine_getreq\n"); + http_engine_getreq(&eng); + break; + + case 10: + //printf("http_engine_getres\n"); + http_engine_getres(&eng); + break; + + case 11: + //printf("http_engine_url\n"); + http_engine_url(&eng, HTTP_METHOD_GET, "", 0); + break; + + case 12: + //printf("http_engine_url\n"); + http_engine_url(&eng, HTTP_METHOD_GET, "http://127.0.0.1/hello", 0); + break; + + case 13: + //printf("http_engine_status\n"); + http_engine_status(&eng, 200); + break; + + case 14: + //printf("http_engine_header\n"); + http_engine_header(&eng, "x:y", -1); + break; + + case 15: + //printf("http_engine_bodycap\n"); + http_engine_bodycap(&eng, rand() % 1000); + break; + + case 16: + //printf("http_engine_bodybuf\n"); + buf = http_engine_bodybuf(&eng, &max); + if (buf && max) { + max = rand() % max; + memset(buf, 0xFF, max); + next_bodyack = max; + } + break; + + case 17: + //printf("http_engine_bodyack\n"); + http_engine_bodyack(&eng, next_bodyack); + next_bodyack = 0; + break; + + case 18: + //printf("http_engine_done\n"); + http_engine_done(&eng); + break; + + case 19: + //printf("http_engine_undo\n"); + http_engine_undo(&eng); + break; + + case 20: + //printf("http_engine_close\n"); + http_engine_close(&eng); + break; + } + } + http_engine_free(&eng); +} \ No newline at end of file diff --git a/tinyhttp.c b/tinyhttp.c index 8ed6656..58855fc 100644 --- a/tinyhttp.c +++ b/tinyhttp.c @@ -1,4 +1,4 @@ -#include "http.h" +#include "tinyhttp.h" #include #include @@ -936,6 +936,14 @@ static int parse_request(Scanner *s, HTTP_Request *req) return 1; } +static int find_header(HTTP_Header *headers, int num_headers, HTTP_String name) +{ + for (int i = 0; i < num_headers; i++) + if (http_streqcase(name, headers[i].name)) + return i; + return -1; +} + static int parse_response(Scanner *s, HTTP_Response *res) { if (!contains_head(s->src + s->cur, s->len - s->cur)) @@ -992,14 +1000,16 @@ static int parse_response(Scanner *s, HTTP_Response *res) return num_headers; res->num_headers = num_headers; - unsigned long long content_length; - for (int i = 0; i < num_headers; i++) - if (http_streqcase(res->headers[i].name, HTTP_STR("Content-Length"))) { - if (parse_content_length(res->headers[i].value.ptr, res->headers[i].value.len, &content_length) < 0) - return -1; - break; - } + int content_length_index = find_header(res->headers, res->num_headers, HTTP_STR("Content-Length")); + if (content_length_index == -1) { + res->body.ptr = NULL; + res->body.len = 0; + return 1; + } + ASSERT(0); + return -1; +/* if (content_length > 1<<20) { // TODO } @@ -1010,7 +1020,10 @@ static int parse_response(Scanner *s, HTTP_Response *res) res->body.ptr = s->src + s->cur; res->body.len = content_length; + } + return 1; +*/ } int http_parse_ipv4(char *src, int len, HTTP_IPv4 *ipv4) @@ -1380,6 +1393,9 @@ static void byte_queue_patch(HTTP_ByteQueue *queue, HTTP_ByteQueueOffset off, char *src, unsigned int len) { + if (queue->flags & BYTE_QUEUE_ERROR) + return; + // Check that the offset is in range ASSERT(off >= queue->curs && off - queue->curs < queue->used); @@ -1394,6 +1410,9 @@ byte_queue_patch(HTTP_ByteQueue *queue, HTTP_ByteQueueOffset off, static void byte_queue_remove_from_offset(HTTP_ByteQueue *queue, HTTP_ByteQueueOffset offset) { + if (queue->flags & BYTE_QUEUE_ERROR) + return; + unsigned long long num = (queue->curs + queue->used) - offset; ASSERT(num <= queue->used); @@ -1514,21 +1533,16 @@ char *http_engine_recvbuf(HTTP_Engine *eng, int *cap) byte_queue_write_setmincap(&eng->input, 1<<9); if (byte_queue_error(&eng->input)) { *cap = 0; - eng->state = HTTP_ENGINE_STATE_SERVER_CLOSED; + if (eng->state & HTTP_ENGINE_STATEBIT_CLIENT) + eng->state = HTTP_ENGINE_STATE_CLIENT_CLOSED; + else + eng->state = HTTP_ENGINE_STATE_SERVER_CLOSED; return NULL; } return byte_queue_write_buf(&eng->input, cap); } -static int find_header(HTTP_Request *req, HTTP_String name) -{ - for (int i = 0; i < req->num_headers; i++) - if (http_streqcase(name, req->headers[i].name)) - return i; - return -1; -} - static int should_keep_alive(HTTP_Engine *eng) { @@ -1553,7 +1567,7 @@ should_keep_alive(HTTP_Engine *eng) // TODO: This assumes "Connection" can only hold a single token, // but this is not true. - int i = find_header(req, HTTP_STR("Connection")); + int i = find_header(req->headers, req->num_headers, HTTP_STR("Connection")); if (i >= 0 && http_streqcase(req->headers[i].value, HTTP_STR("Close"))) return 0; @@ -1564,7 +1578,7 @@ static void process_incoming_request(HTTP_Engine *eng) { ASSERT(eng->state == HTTP_ENGINE_STATE_SERVER_RECV_ACK || eng->state == HTTP_ENGINE_STATE_SERVER_SEND_ACK - || eng->state == HTTP_ENGINE_STATE_SERVER_PREP_BODY + || eng->state == HTTP_ENGINE_STATE_SERVER_PREP_BODY_BUF || eng->state == HTTP_ENGINE_STATE_SERVER_PREP_ERROR); char *src; @@ -1695,6 +1709,8 @@ void http_engine_url(HTTP_Engine *eng, HTTP_Method method, char *url, int minor) if (eng->state != HTTP_ENGINE_STATE_CLIENT_PREP_URL) return; + eng->response_offset = byte_queue_offset(&eng->output); // TODO: rename response_offset to something that makes sense for clients + int len = strlen(url); HTTP_URL parsed_url; @@ -1704,44 +1720,33 @@ void http_engine_url(HTTP_Engine *eng, HTTP_Method method, char *url, int minor) return; } - char *method_str = "???"; + HTTP_String method_and_space = HTTP_STR("???"); switch (method) { - case HTTP_METHOD_GET : method_str = "GET"; break; - case HTTP_METHOD_HEAD : method_str = "HEAD"; break; - case HTTP_METHOD_POST : method_str = "POST"; break; - case HTTP_METHOD_PUT : method_str = "PUT"; break; - case HTTP_METHOD_DELETE : method_str = "DELETE"; break; - case HTTP_METHOD_CONNECT: method_str = "CONNECT"; break; - case HTTP_METHOD_OPTIONS: method_str = "OPTIONS"; break; - case HTTP_METHOD_TRACE : method_str = "TRACE"; break; - case HTTP_METHOD_PATCH : method_str = "PATCH"; break; + case HTTP_METHOD_GET : method_and_space = HTTP_STR("GET "); break; + case HTTP_METHOD_HEAD : method_and_space = HTTP_STR("HEAD "); break; + case HTTP_METHOD_POST : method_and_space = HTTP_STR("POST "); break; + case HTTP_METHOD_PUT : method_and_space = HTTP_STR("PUT "); break; + case HTTP_METHOD_DELETE : method_and_space = HTTP_STR("DELETE "); break; + case HTTP_METHOD_CONNECT: method_and_space = HTTP_STR("CONNECT "); break; + case HTTP_METHOD_OPTIONS: method_and_space = HTTP_STR("OPTIONS "); break; + case HTTP_METHOD_TRACE : method_and_space = HTTP_STR("TRACE "); break; + case HTTP_METHOD_PATCH : method_and_space = HTTP_STR("PATCH "); break; } HTTP_String path = parsed_url.path; if (path.len == 0) path = HTTP_STR("/"); - byte_queue_write_fmt(&eng->output, - "%s %.*s%.*s HTTP/1.%d\r\n", - method_str, - path.len, - path.ptr, - parsed_url.query.len, - parsed_url.query.ptr, - minor - ); - + byte_queue_write(&eng->output, method_and_space.ptr, method_and_space.len); + byte_queue_write(&eng->output, path.ptr, path.len); + byte_queue_write(&eng->output, parsed_url.query.ptr, parsed_url.query.len); + byte_queue_write(&eng->output, minor ? " HTTP/1.1\r\nHost: " : " HTTP/1.0\r\nHost: ", -1); + byte_queue_write(&eng->output, parsed_url.authority.host.text.ptr, parsed_url.authority.host.text.len); if (parsed_url.authority.port > 0) - byte_queue_write_fmt(&eng->output, - "Host: %.*s:%d\r\n", - parsed_url.authority.host.text.len, - parsed_url.authority.host.text.ptr, - parsed_url.authority.port); - else - byte_queue_write_fmt(&eng->output, - "Host: %.*s\r\n", - parsed_url.authority.host.text.len, - parsed_url.authority.host.text.ptr); + byte_queue_write_fmt(&eng->output, "%d", parsed_url.authority.port); + byte_queue_write(&eng->output, "\r\n", 2); + + eng->keepalive = 1; // TODO eng->state = HTTP_ENGINE_STATE_CLIENT_PREP_HEADER; } @@ -1831,6 +1836,8 @@ void http_engine_header(HTTP_Engine *eng, const char *src, int len) if ((eng->state & HTTP_ENGINE_STATEBIT_PREP_HEADER) == 0) return; + if (len < 0) len = strlen(src); + // TODO: Check that the header is valid byte_queue_write(&eng->output, src, len); @@ -1857,14 +1864,10 @@ void http_engine_header_fmt(HTTP_Engine *eng, const char *fmt, ...) } static void -append_special_headers(HTTP_Engine *eng) +complete_message_head(HTTP_Engine *eng) { - ASSERT((eng->state & HTTP_ENGINE_STATEBIT_CLIENT) == 0); - - if (eng->keepalive) - byte_queue_write(&eng->output, "Connection: Keep-Alive\r\n", -1); - else - byte_queue_write(&eng->output, "Connection: Close\r\n", -1); + if (eng->keepalive) byte_queue_write(&eng->output, "Connection: Keep-Alive\r\n", -1); + else byte_queue_write(&eng->output, "Connection: Close\r\n", -1); byte_queue_write(&eng->output, "Content-Length: ", -1); eng->content_length_value_offset = byte_queue_offset(&eng->output); @@ -1874,9 +1877,37 @@ append_special_headers(HTTP_Engine *eng) eng->content_length_offset = byte_queue_offset(&eng->output); } +static void complete_message_body(HTTP_Engine *eng) +{ + unsigned int content_length = byte_queue_size_from_offset(&eng->output, eng->content_length_offset); + + if (content_length > UINT32_MAX) { + // TODO + } + + char tmp[10]; + + tmp[0] = '0' + content_length / 1000000000; content_length %= 1000000000; + tmp[1] = '0' + content_length / 100000000; content_length %= 100000000; + tmp[2] = '0' + content_length / 10000000; content_length %= 10000000; + tmp[3] = '0' + content_length / 1000000; content_length %= 1000000; + tmp[4] = '0' + content_length / 100000; content_length %= 100000; + tmp[5] = '0' + content_length / 10000; content_length %= 10000; + tmp[6] = '0' + content_length / 1000; content_length %= 1000; + tmp[7] = '0' + content_length / 100; content_length %= 100; + tmp[8] = '0' + content_length / 10; content_length %= 10; + tmp[9] = '0' + content_length; + + int i = 0; + while (i < 9 && tmp[i] == '0') + i++; + + byte_queue_patch(&eng->output, eng->content_length_value_offset, tmp + i, 10 - i); +} + void http_engine_body(HTTP_Engine *eng, void *src, int len) { - ASSERT(len >= 0); + if (len < 0) len = strlen(src); http_engine_bodycap(eng, len); int cap; @@ -1892,15 +1923,15 @@ static void ensure_body_entered(HTTP_Engine *eng) if (eng->state & HTTP_ENGINE_STATEBIT_CLIENT) { if (eng->state == HTTP_ENGINE_STATE_CLIENT_PREP_HEADER) { - byte_queue_write(&eng->output, "\r\n", 2); - eng->state = HTTP_ENGINE_STATE_CLIENT_PREP_BODY; + complete_message_head(eng); + eng->state = HTTP_ENGINE_STATE_CLIENT_PREP_BODY_BUF; } } else { if (eng->state == HTTP_ENGINE_STATE_SERVER_PREP_HEADER) { - append_special_headers(eng); - eng->state = HTTP_ENGINE_STATE_SERVER_PREP_BODY; + complete_message_head(eng); + eng->state = HTTP_ENGINE_STATE_SERVER_PREP_BODY_BUF; } } } @@ -1908,8 +1939,8 @@ static void ensure_body_entered(HTTP_Engine *eng) void http_engine_bodycap(HTTP_Engine *eng, int mincap) { ensure_body_entered(eng); - if (eng->state != HTTP_ENGINE_STATE_CLIENT_PREP_BODY && - eng->state != HTTP_ENGINE_STATE_SERVER_PREP_BODY) + if (eng->state != HTTP_ENGINE_STATE_CLIENT_PREP_BODY_BUF && + eng->state != HTTP_ENGINE_STATE_SERVER_PREP_BODY_BUF) return; byte_queue_write_setmincap(&eng->output, mincap); @@ -1918,21 +1949,32 @@ void http_engine_bodycap(HTTP_Engine *eng, int mincap) char *http_engine_bodybuf(HTTP_Engine *eng, int *cap) { ensure_body_entered(eng); - if (eng->state != HTTP_ENGINE_STATE_CLIENT_PREP_BODY && - eng->state != HTTP_ENGINE_STATE_SERVER_PREP_BODY) { + if (eng->state != HTTP_ENGINE_STATE_CLIENT_PREP_BODY_BUF && + eng->state != HTTP_ENGINE_STATE_SERVER_PREP_BODY_BUF) { *cap = 0; return NULL; } + if (eng->state & HTTP_ENGINE_STATEBIT_CLIENT) + eng->state = HTTP_ENGINE_STATE_CLIENT_PREP_BODY_ACK; + else + eng->state = HTTP_ENGINE_STATE_SERVER_PREP_BODY_ACK; + return byte_queue_write_buf(&eng->output, cap); } void http_engine_bodyack(HTTP_Engine *eng, int num) { - if (eng->state != HTTP_ENGINE_STATE_CLIENT_PREP_BODY && - eng->state != HTTP_ENGINE_STATE_SERVER_PREP_BODY) + if (eng->state != HTTP_ENGINE_STATE_CLIENT_PREP_BODY_ACK && + eng->state != HTTP_ENGINE_STATE_SERVER_PREP_BODY_ACK) return; + byte_queue_write_ack(&eng->output, num); + + if (eng->state & HTTP_ENGINE_STATEBIT_CLIENT) + eng->state = HTTP_ENGINE_STATE_CLIENT_PREP_BODY_BUF; + else + eng->state = HTTP_ENGINE_STATE_SERVER_PREP_BODY_BUF; } void http_engine_done(HTTP_Engine *eng) @@ -1948,20 +1990,21 @@ void http_engine_done(HTTP_Engine *eng) } if (eng->state == HTTP_ENGINE_STATE_CLIENT_PREP_HEADER) { - byte_queue_write(&eng->output, "\r\n", 2); - eng->state = HTTP_ENGINE_STATE_CLIENT_PREP_BODY; + complete_message_head(eng); + eng->state = HTTP_ENGINE_STATE_CLIENT_PREP_BODY_BUF; } - if (eng->state == HTTP_ENGINE_STATE_CLIENT_PREP_BODY) { - // TODO - } + if (eng->state == HTTP_ENGINE_STATE_CLIENT_PREP_BODY_BUF) + complete_message_body(eng); if (eng->state == HTTP_ENGINE_STATE_CLIENT_PREP_ERROR) { - // TODO + eng->state = HTTP_ENGINE_STATE_CLIENT_CLOSED; + return; } if (byte_queue_error(&eng->output)) { - // TODO + eng->state = HTTP_ENGINE_STATE_CLIENT_CLOSED; + return; } eng->state = HTTP_ENGINE_STATE_CLIENT_SEND_BUF; @@ -1969,37 +2012,12 @@ void http_engine_done(HTTP_Engine *eng) } else { if (eng->state == HTTP_ENGINE_STATE_SERVER_PREP_HEADER) { - append_special_headers(eng); - eng->state = HTTP_ENGINE_STATE_SERVER_PREP_BODY; + complete_message_head(eng); + eng->state = HTTP_ENGINE_STATE_SERVER_PREP_BODY_BUF; } - if (eng->state == HTTP_ENGINE_STATE_SERVER_PREP_BODY) { - - unsigned int content_length = byte_queue_size_from_offset(&eng->output, eng->content_length_offset); - - if (content_length > UINT32_MAX) { - // TODO - } - - char tmp[10]; - - tmp[0] = '0' + content_length / 1000000000; content_length %= 1000000000; - tmp[1] = '0' + content_length / 100000000; content_length %= 100000000; - tmp[2] = '0' + content_length / 10000000; content_length %= 10000000; - tmp[3] = '0' + content_length / 1000000; content_length %= 1000000; - tmp[4] = '0' + content_length / 100000; content_length %= 100000; - tmp[5] = '0' + content_length / 10000; content_length %= 10000; - tmp[6] = '0' + content_length / 1000; content_length %= 1000; - tmp[7] = '0' + content_length / 100; content_length %= 100; - tmp[8] = '0' + content_length / 10; content_length %= 10; - tmp[9] = '0' + content_length; - - int i = 0; - while (i < 9 && tmp[i] == '0') - i++; - - byte_queue_patch(&eng->output, eng->content_length_value_offset, tmp + i, 10 - i); - } + if (eng->state == HTTP_ENGINE_STATE_SERVER_PREP_BODY_BUF) + complete_message_body(eng); if (eng->state == HTTP_ENGINE_STATE_SERVER_PREP_ERROR) { byte_queue_remove_from_offset(&eng->output, eng->response_offset); @@ -2027,6 +2045,7 @@ void http_engine_undo(HTTP_Engine *eng) if ((eng->state & HTTP_ENGINE_STATEBIT_PREP) == 0) return; + byte_queue_write_ack(&eng->output, 0); byte_queue_remove_from_offset(&eng->output, eng->response_offset); if (eng->state & HTTP_ENGINE_STATEBIT_CLIENT) diff --git a/tinyhttp.h b/tinyhttp.h index a1b039e..fa1a688 100644 --- a/tinyhttp.h +++ b/tinyhttp.h @@ -169,43 +169,46 @@ typedef unsigned long long HTTP_ByteQueueOffset; ///////////////////////////////////////////////////////////////////// #if HTTP_ENGINE -#define HTTP_ENGINE_STATEBIT_CLIENT (1 << 0) -#define HTTP_ENGINE_STATEBIT_CLOSED (1 << 1) -#define HTTP_ENGINE_STATEBIT_RECV_BUF (1 << 2) -#define HTTP_ENGINE_STATEBIT_RECV_ACK (1 << 3) -#define HTTP_ENGINE_STATEBIT_SEND_BUF (1 << 4) -#define HTTP_ENGINE_STATEBIT_SEND_ACK (1 << 5) -#define HTTP_ENGINE_STATEBIT_REQUEST (1 << 6) -#define HTTP_ENGINE_STATEBIT_RESPONSE (1 << 7) -#define HTTP_ENGINE_STATEBIT_PREP (1 << 8) -#define HTTP_ENGINE_STATEBIT_PREP_HEADER (1 << 9) -#define HTTP_ENGINE_STATEBIT_PREP_BODY (1 << 10) -#define HTTP_ENGINE_STATEBIT_PREP_ERROR (1 << 11) -#define HTTP_ENGINE_STATEBIT_PREP_URL (1 << 12) -#define HTTP_ENGINE_STATEBIT_PREP_STATUS (1 << 13) -#define HTTP_ENGINE_STATEBIT_CLOSING (1 << 14) +#define HTTP_ENGINE_STATEBIT_CLIENT (1 << 0) +#define HTTP_ENGINE_STATEBIT_CLOSED (1 << 1) +#define HTTP_ENGINE_STATEBIT_RECV_BUF (1 << 2) +#define HTTP_ENGINE_STATEBIT_RECV_ACK (1 << 3) +#define HTTP_ENGINE_STATEBIT_SEND_BUF (1 << 4) +#define HTTP_ENGINE_STATEBIT_SEND_ACK (1 << 5) +#define HTTP_ENGINE_STATEBIT_REQUEST (1 << 6) +#define HTTP_ENGINE_STATEBIT_RESPONSE (1 << 7) +#define HTTP_ENGINE_STATEBIT_PREP (1 << 8) +#define HTTP_ENGINE_STATEBIT_PREP_HEADER (1 << 9) +#define HTTP_ENGINE_STATEBIT_PREP_BODY_BUF (1 << 10) +#define HTTP_ENGINE_STATEBIT_PREP_BODY_ACK (1 << 11) +#define HTTP_ENGINE_STATEBIT_PREP_ERROR (1 << 12) +#define HTTP_ENGINE_STATEBIT_PREP_URL (1 << 13) +#define HTTP_ENGINE_STATEBIT_PREP_STATUS (1 << 14) +#define HTTP_ENGINE_STATEBIT_CLOSING (1 << 15) typedef enum { HTTP_ENGINE_STATE_NONE = 0, - HTTP_ENGINE_STATE_CLIENT_PREP_URL = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_URL, - HTTP_ENGINE_STATE_CLIENT_PREP_HEADER = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_HEADER, - HTTP_ENGINE_STATE_CLIENT_PREP_BODY = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_BODY, - HTTP_ENGINE_STATE_CLIENT_PREP_ERROR = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_ERROR, - HTTP_ENGINE_STATE_CLIENT_SEND_BUF = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_SEND_BUF, - HTTP_ENGINE_STATE_CLIENT_SEND_ACK = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_SEND_ACK, - HTTP_ENGINE_STATE_CLIENT_RECV_BUF = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_RECV_BUF, - HTTP_ENGINE_STATE_CLIENT_RECV_ACK = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_RECV_ACK, - HTTP_ENGINE_STATE_CLIENT_READY = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_RESPONSE, - HTTP_ENGINE_STATE_CLIENT_CLOSED = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_CLOSED, - HTTP_ENGINE_STATE_SERVER_RECV_BUF = HTTP_ENGINE_STATEBIT_RECV_BUF, - HTTP_ENGINE_STATE_SERVER_RECV_ACK = HTTP_ENGINE_STATEBIT_RECV_ACK, - HTTP_ENGINE_STATE_SERVER_PREP_STATUS = HTTP_ENGINE_STATEBIT_REQUEST | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_STATUS, - HTTP_ENGINE_STATE_SERVER_PREP_HEADER = HTTP_ENGINE_STATEBIT_REQUEST | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_HEADER, - HTTP_ENGINE_STATE_SERVER_PREP_BODY = HTTP_ENGINE_STATEBIT_REQUEST | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_BODY, - HTTP_ENGINE_STATE_SERVER_PREP_ERROR = HTTP_ENGINE_STATEBIT_REQUEST | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_ERROR, - HTTP_ENGINE_STATE_SERVER_SEND_BUF = HTTP_ENGINE_STATEBIT_SEND_BUF, - HTTP_ENGINE_STATE_SERVER_SEND_ACK = HTTP_ENGINE_STATEBIT_SEND_ACK, - HTTP_ENGINE_STATE_SERVER_CLOSED = HTTP_ENGINE_STATEBIT_CLIENT, + HTTP_ENGINE_STATE_CLIENT_PREP_URL = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_URL, + HTTP_ENGINE_STATE_CLIENT_PREP_HEADER = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_HEADER, + HTTP_ENGINE_STATE_CLIENT_PREP_BODY_BUF = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_BODY_BUF, + HTTP_ENGINE_STATE_CLIENT_PREP_BODY_ACK = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_BODY_ACK, + HTTP_ENGINE_STATE_CLIENT_PREP_ERROR = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_ERROR, + HTTP_ENGINE_STATE_CLIENT_SEND_BUF = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_SEND_BUF, + HTTP_ENGINE_STATE_CLIENT_SEND_ACK = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_SEND_ACK, + HTTP_ENGINE_STATE_CLIENT_RECV_BUF = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_RECV_BUF, + HTTP_ENGINE_STATE_CLIENT_RECV_ACK = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_RECV_ACK, + HTTP_ENGINE_STATE_CLIENT_READY = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_RESPONSE, + HTTP_ENGINE_STATE_CLIENT_CLOSED = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_CLOSED, + HTTP_ENGINE_STATE_SERVER_RECV_BUF = HTTP_ENGINE_STATEBIT_RECV_BUF, + HTTP_ENGINE_STATE_SERVER_RECV_ACK = HTTP_ENGINE_STATEBIT_RECV_ACK, + HTTP_ENGINE_STATE_SERVER_PREP_STATUS = HTTP_ENGINE_STATEBIT_REQUEST | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_STATUS, + HTTP_ENGINE_STATE_SERVER_PREP_HEADER = HTTP_ENGINE_STATEBIT_REQUEST | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_HEADER, + HTTP_ENGINE_STATE_SERVER_PREP_BODY_BUF = HTTP_ENGINE_STATEBIT_REQUEST | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_BODY_BUF, + HTTP_ENGINE_STATE_SERVER_PREP_BODY_ACK = HTTP_ENGINE_STATEBIT_REQUEST | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_BODY_ACK, + HTTP_ENGINE_STATE_SERVER_PREP_ERROR = HTTP_ENGINE_STATEBIT_REQUEST | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_ERROR, + HTTP_ENGINE_STATE_SERVER_SEND_BUF = HTTP_ENGINE_STATEBIT_SEND_BUF, + HTTP_ENGINE_STATE_SERVER_SEND_ACK = HTTP_ENGINE_STATEBIT_SEND_ACK, + HTTP_ENGINE_STATE_SERVER_CLOSED = HTTP_ENGINE_STATEBIT_CLIENT, } HTTP_EngineState; typedef struct {