Add engine tests

This commit is contained in:
2025-05-12 22:23:33 +02:00
parent 2d4f062917
commit f725e63fb0
10 changed files with 638 additions and 182 deletions
+10 -1
View File
@@ -1,2 +1,11 @@
misc
coverage_report
coverage_report
# Coverage stuff
*.info
*.gcda
*.gcno
*.exe
test.out
test.exe
+13 -4
View File
@@ -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)
+1 -1
View File
@@ -203,4 +203,4 @@ int main(void)
return http_serve("127.0.0.1", 8080, router);
}
```
```
+73 -4
View File
@@ -1,9 +1,76 @@
#include <stdlib.h>
#include <string.h>
#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;
}
+12
View File
@@ -0,0 +1,12 @@
#include <stdio.h>
#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__)
+138
View File
@@ -0,0 +1,138 @@
#include <stdlib.h>
#include <string.h>
#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();
}
@@ -1,37 +1,6 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#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 <arpa/inet.h>
#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();
+230
View File
@@ -0,0 +1,230 @@
#include <string.h>
#include <stdlib.h>
#include <signal.h>
#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);
}
+122 -103
View File
@@ -1,4 +1,4 @@
#include "http.h"
#include "tinyhttp.h"
#include <stdio.h>
#include <stdarg.h>
@@ -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)
+37 -34
View File
@@ -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 {