new I/O multiplexer api; http echo server example draft to test the mux api; the library is now released as an amalgamation; recv/send now return the error MICROTCP_ERRCODE_WOULDBLOCK in a similar way to the BSD socket api; moved tests/loop2.c to examples/echo_tcp.c

This commit is contained in:
cozis
2023-05-19 20:24:13 +02:00
parent 2a8741afe4
commit 464a1ee724
27 changed files with 3370 additions and 690 deletions
+64
View File
@@ -0,0 +1,64 @@
#include <stdio.h>
#include <microtcp.h>
int main(void)
{
microtcp_errcode_t errcode;
microtcp_t *mtcp = microtcp_create("10.0.0.5", "10.0.0.4", NULL, NULL);
if (mtcp == NULL) {
fprintf(stderr, "Error: Failed to instanciate microtcp stack\n");
return -1;
}
uint16_t port = 80;
microtcp_socket_t *server = microtcp_open(mtcp, port, &errcode);
if (errcode) {
fprintf(stderr, "Error: %s\n", microtcp_strerror(errcode));
microtcp_destroy(mtcp);
return -1;
}
assert(server);
fprintf(stderr, "Listening on port %d\n", port);
while (1) {
fprintf(stderr, "About to accept\n");
microtcp_socket_t *client = microtcp_accept(server, false, &errcode);
if (errcode) {
fprintf(stderr, "Error: %s\n", microtcp_strerror(errcode));
break;
}
fprintf(stderr, "Accepted a connection\n");
char buffer[1024];
size_t num = microtcp_recv(client, buffer, sizeof(buffer), false, &errcode);
if (errcode) {
fprintf(stderr, "Error: %s\n", microtcp_strerror(errcode));
goto handled;
}
fprintf(stderr, "(%d bytes received)\n", (int) num);
size_t sent1 = microtcp_send(client, "echo: ", 6, false, &errcode);
if (errcode) {
fprintf(stderr, "Error: %s\n", microtcp_strerror(errcode));
goto handled;
}
fprintf(stderr, "(%d bytes sent 1)\n", (int) sent1);
size_t sent2 = microtcp_send(client, buffer, num, false, &errcode);
if (errcode) {
fprintf(stderr, "Error: %s\n", microtcp_strerror(errcode));
goto handled;
}
fprintf(stderr, "(%d bytes sent 2)\n", (int) sent2);
handled:
microtcp_close(client);
}
microtcp_close(server);
microtcp_destroy(mtcp);
return 0;
}
-59
View File
@@ -1,59 +0,0 @@
#include <microtcp.h>
typedef struct microhttp_server_t http_server_t;
http_server_t *microhttp_server_create(microtcp_t *mtcp, uint16_t port);
void microhttp_server_destroy(http_server_t *server);
void microhttp_server_serve(microhttp_server_t *server, void *data, void (*callback)(void*, microhttp_request_t*));
struct microhttp_server_t {
microtcp_t *tcp;
microtcp_listener_t *listener;
};
microhttp_server_t *microhttp_server_create(microtcp_t *mtcp, uint16_t port)
{
microhttp_server_t *server = malloc(sizeof(microhttp_server_t));
if (!server)
return NULL;
microtcp_listener_t *listener = microtcp_listener_create(mtcp, port);
if (!listener) {
free(server);
return NULL;
}
server->mtcp = mtcp;
server->listener = listener;
return server;
}
void microhttp_server_serve(microhttp_server_t *server, void *data, void (*callback)(void*, microhttp_request_t*))
{
char buffer[65536];
while (1) {
microtcp_socket_t *socket = microtcp_listener_accept(server->listener);
if (!socket)
continue;
int num = microtcp_socket_recv(socket, buffer, sizeof(buffer));
if (num >= 0) {
hp_error_t error;
hp_request_t request;
if (!hp_parse(buffer, num, &request, &error)) {
..
} else {
..
}
}
microtcp_socket_destroy(socket);
}
}
void microhttp_server_destroy(microhttp_server_t *server)
{
microtcp_listener_destroy(server->listener);
free(server);
}
-49
View File
@@ -1,49 +0,0 @@
#include "net.h"
static size_t send_callback()
{
}
int tun_fd;
static size_t recv_callback(void *context, void *dst, size_t len)
{
return read(tun_fd, dst, len);
}
int main(void)
{
net_t net;
net_init(&net, ip, mac, NULL, send_callback, recv_callback);
net_spawn_thread(&net);
uint16_t port = 8080;
net_listener_t *listener = net_listener_create(&net, port);
if (listener == NULL) {
fprintf(stderr, "Failed to start listening\n");
net_free(&net);
return -1;
}
while (1) {
net_socket_t *client = net_listener_accept(listener);
if (client == NULL)
continue;
char message[1024];
size_t messlen;
messlen = net_socket_recv(client, message, sizeof(message));
net_socket_send(client, "echo: ", sizeof("echo: "));
net_socket_send(client, message, messlen);
net_socket_destroy(client);
}
net_listener_destroy(listener);
net_free(&net);
return 0;
}
+883
View File
@@ -0,0 +1,883 @@
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <assert.h>
#include "http_parser.h"
typedef struct {
size_t offset;
size_t length;
} slice_t;
typedef struct {
const unsigned char *src;
size_t len, cur;
} scanner_t;
#define EMPTY_SLICE ((slice_t) {0, 0})
#define EMPTY_STRING ((hp_string_t) {NULL, 0})
static bool is_lower_alpha(unsigned char c)
{
return c >= 'a' && c <= 'z';
}
static bool is_upper_alpha(unsigned char c)
{
return c >= 'A' && c <= 'Z';
}
static bool is_alpha(unsigned char c)
{
return is_upper_alpha(c)
|| is_lower_alpha(c);
}
static bool is_digit(unsigned char c)
{
return c >= '0' && c <= '9';
}
static bool is_hex_digit(unsigned char c)
{
return is_digit(c)
|| (c >= 'a' && c <= 'f')
|| (c >= 'A' && c <= 'F');
}
static bool is_unreserved(unsigned char c)
{
return is_alpha(c) || is_digit(c)
|| c == '-' || c == '.'
|| c == '_' || c == '~';
}
static bool is_subdelim(unsigned char c)
{
return c == '!' || c == '$'
|| c == '&' || c == '\''
|| c == '(' || c == ')'
|| c == '*' || c == '+'
|| c == ',' || c == ';'
|| c == '=';
}
static bool is_pchar(unsigned char c)
{
return is_unreserved(c)
|| is_subdelim(c)
|| c == ':' || c == '@';
}
static void report(hp_error_t *err, const char *fmt, ...)
{
if (err != NULL && !err->occurred) {
va_list args;
va_start(args, fmt);
vsnprintf(err->msg, sizeof(err->msg), fmt, args);
va_end(args);
err->occurred = true;
}
}
static slice_t slice_up(scanner_t *scanner,
bool (*is_head)(unsigned char),
bool (*is_body)(unsigned char))
{
size_t offset = scanner->cur;
if (scanner->cur < scanner->len && is_head(scanner->src[scanner->cur]))
do
scanner->cur++;
while (scanner->cur < scanner->len && is_body(scanner->src[scanner->cur]));
return (slice_t) {offset, scanner->cur-offset};
}
static bool follows_char(scanner_t scanner, unsigned char c)
{
return scanner.cur < scanner.len && scanner.src[scanner.cur] == c;
}
static bool follows_pchar(scanner_t scanner)
{
return scanner.cur < scanner.len && is_pchar(scanner.src[scanner.cur]);
}
static bool follows_pair(scanner_t scanner, char pair[2])
{
return scanner.cur+1 < scanner.len
&& scanner.src[scanner.cur+0] == (unsigned char) pair[0]
&& scanner.src[scanner.cur+1] == (unsigned char) pair[1];
}
static bool follows_digit(scanner_t scanner)
{
return scanner.cur < scanner.len
&& is_digit(scanner.src[scanner.cur]);
}
static bool follows_hex_digit(scanner_t scanner)
{
return scanner.cur < scanner.len
&& is_hex_digit(scanner.src[scanner.cur]);
}
static bool consume_char(scanner_t *scanner, unsigned char c)
{
if (follows_char(*scanner, c)) {
scanner->cur++;
return true;
} else
return false;
}
static void unconsume_char(scanner_t *scanner)
{
assert(scanner->cur > 0);
scanner->cur--;
}
static bool consume_pchar(scanner_t *scanner)
{
if (follows_pchar(*scanner)) {
scanner->cur++;
return true;
} else
return false;
}
static bool consume_pair(scanner_t *scanner, char pair[static 2])
{
if (follows_pair(*scanner, pair)) {
scanner->cur += 2;
return true;
} else
return false;
}
static bool consume_digit(scanner_t *scanner, unsigned char *digit)
{
if (follows_digit(*scanner)) {
*digit = scanner->src[scanner->cur];
scanner->cur++;
return true;
} else
return false;
}
static bool consume_hex_digit(scanner_t *scanner, unsigned char *digit)
{
if (follows_hex_digit(*scanner)) {
*digit = scanner->src[scanner->cur];
scanner->cur++;
return true;
} else
return false;
}
static void consume_spaces(scanner_t *scanner)
{
while (consume_char(scanner, ' '));
}
static bool consume_u64_base_10(scanner_t *scanner, uint64_t max, uint64_t *out)
{
if (!follows_digit(*scanner))
return false;
uint64_t num = 0;
unsigned char digit;
while (consume_digit(scanner, &digit)) {
int u = digit - '0';
if (num > (max - u) / 10) {
unconsume_char(scanner);
break;
}
num = num * 10 + u;
}
*out = num;
return true;
}
static int hex_digit_to_int(char c)
{
assert(is_hex_digit(c));
if (is_lower_alpha(c))
return c - 'a' + 10;
if (is_upper_alpha(c))
return c - 'A' + 10;
assert(is_digit(c));
return c - '0';
}
static bool consume_u64_base_16(scanner_t *scanner, uint64_t max, uint64_t *out)
{
if (!follows_hex_digit(*scanner))
return false;
uint64_t num = 0;
unsigned char digit;
while (consume_hex_digit(scanner, &digit)) {
int u = hex_digit_to_int(digit);
if (num > (max - u) / 16) {
unconsume_char(scanner);
break;
}
num = num * 16 + u;
}
*out = num;
return true;
}
static bool consume_u8_base_10(scanner_t *scanner, uint8_t *out)
{
uint64_t buffer;
bool ok = consume_u64_base_10(scanner, UINT8_MAX, &buffer);
assert(!ok || buffer <= UINT8_MAX);
*out = buffer;
return ok;
}
static bool consume_u16_base_16(scanner_t *scanner, uint16_t *out)
{
uint64_t buffer;
bool ok = consume_u64_base_16(scanner, UINT16_MAX, &buffer);
assert(!ok || buffer <= UINT16_MAX);
*out = buffer;
return ok;
}
static bool consume_u16_base_10(scanner_t *scanner, uint16_t *out)
{
uint64_t buffer;
bool ok = consume_u64_base_10(scanner, UINT16_MAX, &buffer);
assert(!ok || buffer <= UINT16_MAX);
*out = buffer;
return ok;
}
// [<schema> : ] // [ <username> [ : <password> ] @ ] { <name> | <IPv4> | "[" <IPv5> "]" } [ : <port> ] [ </path> ] [ ? <query> ] [ # <fragment> ]
static bool is_schema_first(unsigned char c)
{
return is_alpha(c);
}
static bool is_schema(unsigned char c)
{
return is_alpha(c)
|| is_digit(c)
|| c == '+'
|| c == '-'
|| c == '.';
}
static slice_t parse_schema(scanner_t *scanner)
{
size_t start = scanner->cur;
slice_t schema = slice_up(scanner, is_schema_first, is_schema);
if (schema.length > 0)
if (!consume_char(scanner, ':')) {
scanner->cur = start;
return EMPTY_SLICE;
}
return schema;
}
static bool is_username(unsigned char c)
{
return is_unreserved(c) || is_subdelim(c);
}
static bool is_username_first(unsigned char c)
{
return is_username(c);
}
static bool is_password(unsigned char c)
{
return is_username(c);
}
static bool is_password_first(unsigned char c)
{
return is_password(c);
}
static hp_string_t string_from_slice(const unsigned char *src, slice_t slice)
{
assert(src != NULL);
if (slice.length == 0)
return EMPTY_STRING;
else
return (hp_string_t) {(char*) src + slice.offset, slice.length};
}
static void parse_userinfo(scanner_t *scanner, slice_t *username, slice_t *password)
{
size_t start = scanner->cur;
*password = EMPTY_SLICE;
*username = slice_up(scanner, is_username_first, is_username);
if (username->length > 0) {
if (consume_char(scanner, ':'))
*password = slice_up(scanner, is_password_first, is_password);
if (!consume_char(scanner, '@')) {
*username = EMPTY_SLICE;
*password = EMPTY_SLICE;
scanner->cur = start; // Rollback changes
}
}
}
static bool parse_ipv4(scanner_t *scanner, uint32_t *out, hp_error_t *err)
{
uint8_t byte;
uint32_t ipv4 = 0;
for (int u = 0; u < 3; u++) {
if (!consume_u8_base_10(scanner, &byte)) {
if (u == 0)
report(err, "Missing IPv4");
else
report(err, "Missing IPv4 byte");
return false;
}
ipv4 = (ipv4 << 8) + byte;
if (!consume_char(scanner, '.'))
return false;
}
if (!consume_u8_base_10(scanner, &byte)) {
report(err, "Missing IPv4 byte");
return false;
}
ipv4 = (ipv4 << 8) + byte;
*out = ipv4;
return true;
}
static bool parse_ipv6(scanner_t *scanner, uint16_t ipv6[static 8], hp_error_t *err)
{
uint16_t tail[8];
size_t head_count = 0;
size_t tail_count = 0;
if (!consume_pair(scanner, "::")) {
do {
uint16_t word;
if (!consume_u16_base_16(scanner, &word)) {
if (scanner->cur == scanner->len) {
if (head_count == 0)
report(err, "Missing IPv6");
else
report(err, "Missing IPv6 hex value");
} else
report(err, "Invalid IPv6");
return false;
}
ipv6[head_count++] = word;
if (head_count == 8)
break;
if (!consume_char(scanner, ':')) {
report(err, "Missing ':' after IPv6 hex value");
return false;
}
} while (!consume_char(scanner, ':'));
}
if (head_count + tail_count < 8) {
while (follows_hex_digit(*scanner)) {
// We know the current character is a
// hex digit, therefore [parse_ipv6_word]
// won't fail.
uint16_t word;
(void) consume_u16_base_16(scanner, &word);
tail[tail_count++] = word;
if (head_count + tail_count == 8)
break;
if (!consume_char(scanner, ':'))
break;
}
}
assert(head_count + tail_count <= 8);
for (size_t p = 0; p < 8 - head_count - tail_count; p++)
ipv6[head_count + p] = 0;
for (size_t p = 0; p < tail_count; p++)
ipv6[8 - tail_count + p] = tail[p];
return true;
}
static bool is_hostname(unsigned char c)
{
return is_unreserved(c) || is_subdelim(c);
}
static bool is_hostname_first(unsigned char c)
{
return is_hostname(c);
}
static bool parse_host(scanner_t *scanner, hp_host_t *host, hp_error_t *err)
{
if (consume_char(scanner, '[')) {
if (!parse_ipv6(scanner, host->ipv6, err))
return false;
if (!consume_char(scanner, ']')) {
report(err, "Missing ']' after IPv6");
return false;
}
host->mode = HP_HOSTMODE_IPV6;
} else {
uint32_t ipv4;
bool is_ipv4;
if (follows_digit(*scanner)) {
size_t start = scanner->cur;
is_ipv4 = parse_ipv4(scanner, &ipv4, NULL);
if (!is_ipv4)
scanner->cur = start;
} else
is_ipv4 = false;
if (is_ipv4) {
host->ipv4 = ipv4;
host->mode = HP_HOSTMODE_IPV4;
} else {
slice_t hostname = slice_up(scanner, is_hostname_first, is_hostname);
if (hostname.length == 0) {
report(err, "Missing host");
return false;
}
host->mode = HP_HOSTMODE_NAME;
host->name = string_from_slice(scanner->src, hostname);
}
}
host->no_port = !consume_u16_base_10(scanner, &host->port);
return true;
}
static bool parse_path(scanner_t *scanner, slice_t *out, hp_error_t *err)
{
out->offset = scanner->cur;
if (!consume_char(scanner, '/'))
if (!follows_pchar(*scanner)) {
report(err, "Missing path");
return false;
}
while (consume_pchar(scanner)) {
while (consume_pchar(scanner));
if (!consume_char(scanner, '/'))
break;
}
out->length = scanner->cur - out->offset;
return true;
}
static bool is_query(unsigned char c)
{
return is_pchar(c) || c == '/' || c == '?';
}
static bool is_fragment(unsigned char c)
{
return is_pchar(c) || c == '/';
}
static bool parse_url(scanner_t *scanner, hp_url_t *url, hp_error_t *err)
{
url->schema = string_from_slice(scanner->src, parse_schema(scanner));
if (consume_pair(scanner, "//")) {
slice_t username, password;
parse_userinfo(scanner, &username, &password);
url->username = string_from_slice(scanner->src, username);
url->password = string_from_slice(scanner->src, password);
if (!parse_host(scanner, &url->host, err))
return false;
if (follows_char(*scanner, '/')) {
/* absolute path */
// The parsing of the path can't fail
// because we already know there's at
// leat a '/' for it.
slice_t path;
(void) parse_path(scanner, &path, err);
url->path = string_from_slice(scanner->src, path);
} else
url->path = EMPTY_STRING;
} else {
url->host.mode = HP_HOSTMODE_NAME;
url->host.name = EMPTY_STRING;
url->host.no_port = true;
url->host.port = 0;
url->username = EMPTY_STRING;
url->password = EMPTY_STRING;
// TODO: Since there was no authority,
// the path is non optional.
if (follows_char(*scanner, '?')) {
report(err, "Missing path before query");
return false;
}
if (follows_char(*scanner, '#')) {
report(err, "Missing path before fragment");
return false;
}
slice_t path;
if (!parse_path(scanner, &path, err))
return false;
url->path = string_from_slice(scanner->src, path);
}
url->query = consume_char(scanner, '?')
? string_from_slice(scanner->src, slice_up(scanner, is_query, is_query))
: EMPTY_STRING;
url->fragment = consume_char(scanner, '#')
? string_from_slice(scanner->src, slice_up(scanner, is_fragment, is_fragment))
: EMPTY_STRING;
return true;
}
static bool is_header_name_body(unsigned char c)
{
return is_alpha(c) || is_digit(c) || c == '-';
}
static bool is_header_name_head(unsigned char c)
{
return is_header_name_body(c);
}
static bool is_header_body_body(unsigned char c)
{
return c != '\r';
}
static bool is_header_body_head(unsigned char c)
{
return is_header_body_body(c);
}
static bool parse_header(scanner_t *scanner, hp_header_t *header, hp_error_t *err)
{
slice_t name, body;
name = slice_up(scanner, is_header_name_head, is_header_name_body);
if (name.length == 0) {
report(err, "Missing header name");
return false;
}
if (!consume_char(scanner, ':')) {
report(err, "Missing ':' after header name");
return false;
}
body = slice_up(scanner, is_header_body_head, is_header_body_body);
if (!consume_pair(scanner, "\r\n")) {
report(err, "Missing CRLF after header");
return false;
}
header->name = string_from_slice(scanner->src, name);
header->body = string_from_slice(scanner->src, body);
return true;
}
static bool parse_version(scanner_t *scanner, int *major, int *minor, hp_error_t *err)
{
unsigned char char_major = '0';
unsigned char char_minor = '0';
if (!consume_char(scanner, 'H') ||
!consume_char(scanner, 'T') ||
!consume_char(scanner, 'T') ||
!consume_char(scanner, 'P') ||
!consume_char(scanner, '/') ||
!consume_digit(scanner, &char_major)) {
report(err, "Invalid version token");
return false;
}
if (consume_char(scanner, '.'))
if (!consume_digit(scanner, &char_minor)) {
report(err, "Invalid version token");
return false;
}
*major = char_major - '0';
*minor = char_minor - '0';
return true;
}
static bool get_method_id(hp_string_t str, hp_method_t *method)
{
// CONNECT OPTIONS TRACE PATCH
switch (str.len) {
case 3:
if (str.str[0] == 'G' &&
str.str[1] == 'E' &&
str.str[2] == 'T') {
*method = HP_METHOD_GET;
return true;
}
if (str.str[0] == 'P' &&
str.str[1] == 'U' &&
str.str[2] == 'T') {
*method = HP_METHOD_PUT;
return true;
}
break;
case 4:
if (str.str[0] == 'P' &&
str.str[1] == 'O' &&
str.str[2] == 'S' &&
str.str[3] == 'T') {
*method = HP_METHOD_POST;
return true;
}
if (str.str[0] == 'H' &&
str.str[1] == 'E' &&
str.str[2] == 'A' &&
str.str[3] == 'D') {
*method = HP_METHOD_HEAD;
return true;
}
break;
case 5:
if (str.str[0] == 'T' &&
str.str[1] == 'R' &&
str.str[2] == 'A' &&
str.str[3] == 'C' &&
str.str[4] == 'E') {
*method = HP_METHOD_TRACE;
return true;
}
if (str.str[0] == 'P' &&
str.str[1] == 'A' &&
str.str[2] == 'T' &&
str.str[3] == 'C' &&
str.str[4] == 'H') {
*method = HP_METHOD_PATCH;
return true;
}
break;
case 6:
if (str.str[0] == 'D' &&
str.str[1] == 'E' &&
str.str[2] == 'L' &&
str.str[3] == 'E' &&
str.str[4] == 'T' &&
str.str[5] == 'E') {
*method = HP_METHOD_DELETE;
return true;
}
break;
}
return false;
}
static bool is_method_body(unsigned char c)
{
return is_upper_alpha(c);
}
static bool is_method_head(unsigned char c)
{
return is_method_body(c);
}
static bool parse_method(scanner_t *scanner, hp_method_t *method, hp_error_t *err)
{
slice_t method_slice = slice_up(scanner, is_method_head, is_method_body);
if (method_slice.length == 0) {
report(err, "Missing method");
return false;
}
hp_string_t method_string = string_from_slice(scanner->src, method_slice);
if (!get_method_id(method_string, method)) {
report(err, "Invalid method %.*s", (int) method_string.len, method_string.str);
return false;
}
return true;
}
static bool parse_status_line(scanner_t *scanner,
hp_method_t *method,
hp_url_t *url,
int *major, int *minor,
hp_error_t *err)
{
if (!parse_method(scanner, method, err))
return false;
if (!consume_char(scanner, ' ')) {
report(err, "Missing space after method");
return false;
}
if (!parse_url(scanner, url, err))
return false;
if (!consume_char(scanner, ' ')) {
report(err, "Missing space after URL");
return false;
}
if (!parse_version(scanner, major, minor, err))
return false;
if (!consume_pair(scanner, "\r\n")) {
report(err, "Missing CRLF after version token");
return false;
}
return true;
}
static void append_header(hp_request_t *req,
hp_header_t header)
{
if (req->num_headers < HP_MAX_HEADERS)
req->headers[req->num_headers++] = header;
}
bool hp_parse(const char *src, size_t len,
hp_request_t *out, hp_error_t *err)
{
scanner_t scanner = {(unsigned char*) src, len, 0};
if (!parse_status_line(&scanner, &out->method,
&out->url, &out->major,
&out->minor, err))
return false;
out->num_headers = 0;
while (!consume_pair(&scanner, "\r\n")) {
hp_header_t header;
if (!parse_header(&scanner, &header, err))
return false;
append_header(out, header);
}
return true;
}
bool hp_parse_url(const char *src, size_t len,
hp_url_t *url, hp_error_t *err)
{
scanner_t scanner = {(unsigned char*) src, len, 0};
return parse_url(&scanner, url, err);
}
static char to_lower(char c)
{
if (is_upper_alpha(c))
return c - 'A' + 'a';
else
return c;
}
static bool case_insensitive_string_compare(hp_string_t s1, hp_string_t s2)
{
if (s1.len != s2.len)
return false;
for (size_t i = 0; i < s1.len; i++)
if (to_lower(s1.str[i]) != to_lower(s2.str[i]))
return false;
return true;
}
hp_header_t *hp_get_header(hp_request_t req, const char *name)
{
hp_string_t name2 = {name, strlen(name)};
for (size_t i = 0; i < req.num_headers; i++) {
hp_header_t *header = req.headers + i;
if (case_insensitive_string_compare(name2, header->name))
return header;
}
return NULL;
}
static bool parse_content_length(const char *src, size_t len, size_t *out, hp_error_t *error)
{
scanner_t scanner = {(unsigned char*) src, len, 0};
consume_spaces(&scanner);
if (!follows_digit(scanner)) {
report(error, "Non-digit character in Content-Length header");
return false;
}
*out = 0;
unsigned char digit;
while (consume_digit(&scanner, &digit)) {
int k = digit - '0';
if (*out > (SIZE_MAX - k) / 10) {
report(error, "Unsigned integer is too big");
return false;
}
*out = *out * 10 + k;
}
if (scanner.cur < scanner.len) {
report(error, "Invalid character '%c'", scanner.src[scanner.cur]);
return false;
}
return true;
}
bool hp_get_content_length(hp_request_t req, size_t *out, hp_error_t *error)
{
hp_header_t *header = hp_get_header(req, "Content-Length");
if (header == NULL)
return 0;
return parse_content_length(header->body.str, header->body.len, out, error);
}
+70
View File
@@ -0,0 +1,70 @@
#include <stddef.h>
#include <stdint.h>
#include <stdbool.h>
#define HP_MAX_HEADERS 8
typedef struct {
const char *str;
size_t len;
} hp_string_t;
typedef struct {
bool occurred;
char msg[256];
} hp_error_t;
typedef enum {
HP_METHOD_GET,
HP_METHOD_PUT,
HP_METHOD_POST,
HP_METHOD_HEAD,
HP_METHOD_PATCH,
HP_METHOD_TRACE,
HP_METHOD_DELETE,
} hp_method_t;
typedef enum {
HP_HOSTMODE_NAME,
HP_HOSTMODE_IPV4,
HP_HOSTMODE_IPV6,
} hp_hostmode_t;
typedef struct {
hp_hostmode_t mode;
union {
uint32_t ipv4;
uint16_t ipv6[8];
hp_string_t name;
};
bool no_port;
uint16_t port;
} hp_host_t;
typedef struct {
hp_host_t host;
hp_string_t path;
hp_string_t query;
hp_string_t schema;
hp_string_t fragment;
hp_string_t username;
hp_string_t password;
} hp_url_t;
typedef struct {
hp_string_t name;
hp_string_t body;
} hp_header_t;
typedef struct {
int major, minor;
hp_url_t url;
hp_method_t method;
hp_header_t headers[HP_MAX_HEADERS];
size_t num_headers;
} hp_request_t;
bool hp_parse(const char *src, size_t len, hp_request_t *out, hp_error_t *err);
bool hp_parse_url(const char *src, size_t len, hp_url_t *url, hp_error_t *err);
hp_header_t *hp_get_header(hp_request_t req, const char *name);
bool hp_get_content_length(hp_request_t req, size_t *out, hp_error_t *error);
+81
View File
@@ -0,0 +1,81 @@
#include <string.h>
#include <signal.h>
#include <stdlib.h>
#include <stdio.h>
#include <tuntap.h>
#include "xhttp.h"
static xh_handle handle;
static void callback(xh_request *req, xh_response *res, void *userp)
{
(void) req;
(void) userp;
res->status = 200;
res->body.str = "Hello, world!";
xh_header_add(res, "Content-Type", "text/plain");
}
/*
static void handle_sigterm(int signum)
{
(void) signum;
xh_quit(handle);
}
*/
int main(void)
{
/*
signal(SIGTERM, handle_sigterm);
signal(SIGINT, handle_sigterm);
#ifndef _WIN32
signal(SIGQUIT, handle_sigterm);
#endif
*/
char ip[] = "10.0.0.4";
char mac[] = "00:01:00:01:00:00";
struct device *dev = tuntap_init();
if (!dev) {
fprintf(stderr, "Error: Couldn't initialize the TAP library\n");
return -1;
}
// This must be set AFTER tuntap_init because
// it sets the callback function to the default
// callback which writes to stderr.
//tuntap_log_set_cb(NULL);
int netmask = 24; // TODO: Make this configurable
if (tuntap_start(dev, TUNTAP_MODE_ETHERNET, TUNTAP_ID_ANY)) {
fprintf(stderr, "Error: Couldn't set up the TAP device\n");
tuntap_release(dev);
return -1;
}
tuntap_set_ip(dev, ip, netmask);
tuntap_set_hwaddr(dev, mac);
if (tuntap_up(dev)) {
fprintf(stderr, "Error: Couldn't activate the TAP device\n");
tuntap_release(dev);
}
microhttp_config_t config = {
.userp = dev,
.ip = ip,
.mac = mac,
.recv_frame = (int(*)(void*, void*, size_t)) tuntap_read,
.send_frame = (int(*)(void*, const void*, size_t)) tuntap_write,
};
const char *error = xhttp(80, callback, NULL, &handle, config);
tuntap_release(dev);
if(error != NULL) {
fprintf(stderr, "Error: %s\n", error);
return 1;
}
return 0;
}
File diff suppressed because it is too large Load Diff
+81
View File
@@ -0,0 +1,81 @@
#ifndef XHTTP_H
#define XHTTP_H
#define MICROHTTP_MAX_CLIENTS 64
typedef struct {
void *userp;
const char *ip;
const char *mac;
int (*send_frame)(void *userp, const void *src, size_t len);
int (*recv_frame)(void *userp, void *dst, size_t len);
} microhttp_config_t;
typedef void *xh_handle;
typedef struct {
char *str; int len;
} xh_string;
typedef struct {
xh_string key, val;
} xh_pair;
typedef struct {
xh_pair *list;
int count;
} xh_table;
typedef enum {
XH_GET = 1,
XH_HEAD = 2,
XH_POST = 4,
XH_PUT = 8,
XH_DELETE = 16,
XH_CONNECT = 32,
XH_OPTIONS = 64,
XH_TRACE = 128,
XH_PATCH = 256,
} xh_method;
typedef struct {
xh_method method_id;
xh_string method;
xh_string params;
xh_string URL;
unsigned int version_minor;
unsigned int version_major;
xh_table headers;
xh_string body;
} xh_request;
typedef struct {
int status;
xh_table headers;
xh_string body;
_Bool close;
} xh_response;
typedef void (*xh_callback)(xh_request*, xh_response*, void*);
const char *xhttp(unsigned short port, xh_callback callback,
void *userp, xh_handle *handle,
microhttp_config_t config);
void xh_quit(xh_handle handle);
void xh_header_add(xh_response *res, const char *name, const char *valfmt, ...);
void xh_header_rem(xh_response *res, const char *name);
const char *xh_header_get(void *req_or_res, const char *name);
_Bool xh_header_cmp(const char *a, const char *b);
int xh_urlcmp(const char *URL, const char *fmt, ...);
int xh_vurlcmp(const char *URL, const char *fmt, va_list va);
#define xh_string_new(s, l) \
((xh_string) { (s), ((int) (l)) < 0 ? (int) strlen(s) : (int) (l) })
#define xh_string_from_literal(s) \
((xh_string) { (s), sizeof(s)-1 })
#endif // #ifndef XHTTP_H
-302
View File
@@ -1,302 +0,0 @@
#include "queue.h"
typedef struct queue_entry_t queue_entry_t;
typedef struct queue_event_t queue_event_t;
struct queue_entry_t {
queue_entry_t *prev;
queue_entry_t *next;
void *data;
int events;
microtcp_queue_t *queue;
microtcp_socket_t *socket;
queue_event_t *event_entry;
};
struct queue_event_t {
queue_event_t *prev;
queue_event_t *next;
int events;
queue_entry_t *socket_entry;
};
struct microtcp_queue_t {
microtcp_t *mtcp;
pthread_cond_t something_happened;
queue_event_t *queue_head;
queue_event_t *queue_tail;
queue_entry_t *entry_free_list;
queue_entry_t entry_pool[MICROTCP_QUEUE_ENTRIES_MAX];
queue_event_t *event_free_list;
queue_event_t event_pool[MICROTCP_QUEUE_ENTRIES_MAX];
};
static event_entry_t*
pop_event_entry_from_queue(microtcp_queue_t *queue)
{
event_entry_t *event;
if (queue->queue_tail) {
// An event is present in the queue. Pop it.
event = queue->queue_tail;
if (event->prev)
event->prev->next = NULL;
else
queue->queue_head = NULL;
queue->queue_tail = event->prev;
} else
event = NULL;
return event;
}
microtcp_event_t microtcp_queue_next(microtcp_queue_t *queue, bool no_block)
{
microtcp_t *mtcp;
event_entry_t *event = pop_event_entry_from_queue(queue);
while (!event && !no_block) {
pthread_cond_wait(&queue->something_happened, &mtcp->lock);
event = pop_event_entry_from_queue(queue);
}
microtcp_event_t result;
if (event) {
queue_entry_t *socket_entry = event->socket_entry;
result.type = event->events;
result.data = socket_entry->data;
result.socket = socket_entry->socket;
} else {
result.type = 0;
result.data = NULL;
result.socket = NULL;
}
return result;
}
static void signal_socket_events_to_queues(microtcp_socket_t *socket, int events)
{
queue_entry_t *entry = socket->queue_entry;
while (entry) {
int interesting_events = events & entry->events;
if (interesting_events) {
// Some events that are of interest to
// this queue are being signaled.
// Push them onto the queue's event queue
microtcp_queue_t *queue = entry->queue;
if (entry->event_entry) {
// At least one event was already signaled, so
// an event structure was already created.
// We just need to add to it the newly signaled
// events.
event_entry_t *event = entry->event_entry;
event->events |= interesting_events;
} else {
// No events related to this socket are in the
// queue, so we need to create a new event structure.
//
// Unused event structure are organized in a free
// list, so we just need to pop one from there.
//
// We know for sure that a free event structure is
// available at this point because there is one for
// each socket registered into the queue.
assert(queue->event_free_list);
event_entry_t *event = queue->event_free_list;
queue->event_free_list = event->next;
// Set-up the event structure
event->prev = NULL;
event->next = NULL;
event->events = interesting_events;
event->socket_entry = entry;
// Now push the event structure into the queue's queue
event->next = queue->queue_head;
if (queue->queue_head)
queue->queue_head->prev = event;
else
queue->queue_tail = event;
queue->queue_head = event;
}
pthread_cond_signal(&queue->something_happened);
}
entry = entry->next;
}
}
microtcp_queue_t *microtcp_queue_create(microtcp_t *mtcp)
{
microtcp_queue_t *queue = malloc(sizeof(microtcp_queue_t));
if (!queue)
return NULL;
queue->mtcp = mtcp;
queue->queue_head = NULL;
queue->queue_tail = NULL;
queue->entry_free_list = queue->entry_pool;
queue->event_free_list = queue->event_pool;
for (size_t i = 0; i < MICROTCP_QUEUE_ENTRIES_MAX-1; i++) {
mtcp->entry_pool[i].mtcp = NULL;
mtcp->entry_pool[i].prev = NULL;
mtcp->entry_pool[i].next = mtcp->entry_pool + i+1;
mtcp->event_pool[i].mtcp = NULL;
mtcp->event_pool[i].prev = NULL;
mtcp->event_pool[i].next = mtcp->event_pool + i+1;
}
mtcp->entry_pool[MICROTCP_QUEUE_ENTRIES_MAX-1].mtcp = NULL;
mtcp->entry_pool[MICROTCP_QUEUE_ENTRIES_MAX-1].prev = NULL;
mtcp->entry_pool[MICROTCP_QUEUE_ENTRIES_MAX-1].next = NULL;
mtcp->event_pool[MICROTCP_QUEUE_ENTRIES_MAX-1].prev = NULL;
mtcp->event_pool[MICROTCP_QUEUE_ENTRIES_MAX-1].next = NULL;
if (pthread_cond_init(&queue->something_happened, NULL)) {
free(queue);
return NULL;
}
return queue;
}
void microtcp_queue_destroy(microtcp_queue_t *queue)
{
for (size_t i = 0; i < MICROTCP_QUEUE_ENTRIES_MAX; i++) {
if (queue->entries[i].mtcp == NULL)
continue;
microtcp_queue_unregister(queue, queue->entries[i].socket, MICROTCP_EVENT_ALL);
}
free(queue);
}
bool microtcp_queue_register(microtcp_queue_t *queue, microtcp_socket_t *socket, void *data, int events)
{
if (!events)
return true; // No events registered
if (!queue->free_list)
return false; // Registration limit reached
queue_entry_t *entry = queue->free_list;
queue->free_list = entry->next;
entry->prev = NULL;
entry->next = NULL;
entry->data = data;
entry->queue = queue;
entry->events = events;
entry->socket = socket;
entry->next = socket->queue_entry;
if (socket->queue_entry)
socket->queue_entry->prev = entry;
socket->queue_entry = entry;
return true;
}
static queue_entry_t *find_entry(microtcp_queue_t *queue, microtcp_socket_t *socket)
{
queue_entry_t *entry = socket->queue_entry;
while (entry) {
if (entry->queue == queue)
return entry;
entry = entry->next;
}
return NULL;
}
static void
unregister_socket_from_all_queues(microtcp_socket_t *socket)
{
while (socket->queue_entry)
microtcp_queue_unregister(socket->queue_entry->queue, socket, MICROTCP_EVENT_ALL);
}
bool microtcp_queue_unregister(microtcp_queue_t *queue, microtcp_socket_t *socket, int unregister_events)
{
if (!queue || !socket || queue->mtcp != socket->mtcp)
return false;
queue_entry_t *entry = find_entry(queue, socket);
if (!entry)
return false; // Socket wasn't registered in this queue
int remaining_events = entry->events & ~unregister_events;
if (!remaining_events) {
// All events were unregistered, so the
// socket must be removed from the queue.
// Unlink any events related to this socket
// from the queue
queue_event_t *event = entry->event_entry;
if (event) {
// Remove the event structure from the queue
if (event->prev)
event->prev->next = event->next;
else
queue->queue_head = event->next;
if (event->next)
event->next->prev = event->prev;
else
queue->queue_tail = event->prev;
// Push the now unlinked event structure onto
// the free list
event->prev = NULL;
event->next = queue->event_free_list;
queue->event_free_list = event;
}
// Unlink the entry from the socket's list
if (entry->prev)
entry->prev->next = entry->next;
else
socket->queue_entry = entry->next;
if (entry->next)
entry->next->prev = entry->prev;
// Put the entry structure back into the
// free list of the queue
entry->mtcp = NULL; // It's important to set this to NULL because
// this way the queue knows it's not used anymore.
entry->prev = NULL;
entry->next = queue->free_list;
queue->free_list = entry;
} else
entry->events = remaining_events;
return true;
}
-25
View File
@@ -1,25 +0,0 @@
enum {
MICROTCP_EVENT_NONE = 0,
MICROTCP_EVENT_SEND = 1,
MICROTCP_EVENT_RECV = 2,
MICROTCP_EVENT_ACCEPT = 4,
MICROTCP_EVENT_ALL = MICROTCP_EVENT_RECV
| MICROTCP_EVENT_SEND
| MICROTCP_EVENT_ACCEPT,
};
typedef struct microtcp_queue_t microtcp_queue_t;
typedef struct {
int type;
void *data;
microtcp_socket_t *socket;
} microtcp_event_t;
microtcp_queue_t *microtcp_queue_create(microtcp_t *mtcp);
void microtcp_queue_destroy(microtcp_queue_t *queue);
microtcp_event_t microtcp_queue_next(microtcp_queue_t *queue, bool no_block);
bool microtcp_queue_register(microtcp_queue_t *queue, microtcp_socket_t *socket, void *data, int events);
bool microtcp_queue_unregister(microtcp_queue_t *queue, microtcp_socket_t *socket, int unregister_events);
-54
View File
@@ -1,54 +0,0 @@
typedef struct {
size_t size;
size_t used;
uint8_t *data;
} buffer_t;
typedef struct client_t client_t;
struct client_t {
client_t *prev;
client_t *next;
microtcp_socket_t *socket;
buffer_t input;
buffer_t output;
};
int main(void)
{
microtcp_t *mtcp = microtcp_create();
microtcp_socket_t *server = microtcp_open(mtcp, 80);
microtcp_queue_t *queue = microtcp_queue_create(mtcp);
microtcp_queue_register(queue, server, NULL, MICROTCP_EVENT_ACCEPT);
while (1) {
microtcp_event_t event = microtcp_queue_next(queue, no_block);
switch (event.type) {
case MICROTCP_EVENT_NONE:
break;
case MICROTCP_EVENT_READ:
break;
case MICROTCP_EVENT_RECV:
{
microtcp_socket_t *client_socket = event.socket;
microtcp_recv(client_socket, );
}
break;
case MICROTCP_EVENT_ACCEPT:
{
microtcp_socket_t *client_socket = microtcp_accept(event.socket, true);
microtcp_queue_register(queue, client_socket, NULL, MICROTCP_EVENT_SEND | MICROTCP_EVENT_RECV);
}
break;
}
}
microtcp_queue_destroy(queue);
microtcp_destroy(mtcp);
return 0;
}
-26
View File
@@ -1,26 +0,0 @@
#include "mutcp.h"
int main(void)
{
mutcp_t state;
mutcp_create(&state);
mutcp_listener_socket_t *listener =
mutcp_listener_socket_create(&state, 8080);
while (1) {
mutcp_socket_t *socket =
mutcp_listener_socket_accept(listener);
char buffer[1024];
size_t num = mutcp_socket_read(socket, buffer, sizeof(buffer));
mutcp_socket_write(socket, "echo: ", 6);
mutcp_socket_write(socket, buffer, num);
mutcp_socket_destroy(socket);
}
mutcp_listener_socket_destroy(listener);
mutcp_destroy(&state);
return 0;
}