first commit

This commit is contained in:
2024-03-25 21:05:36 +01:00
commit 1014d2526c
22 changed files with 2998 additions and 0 deletions
+26
View File
@@ -0,0 +1,26 @@
ifeq ($(OS),Windows_NT)
# Windows
EXT = .exe
LFLAGS = -lws2_32
else
# Linux
EXT =
LFLAGS =
endif
all: http$(EXT) test_queue$(EXT) test_parse_ipv4$(EXT) # fuzz_parse_ipv4$(EXT) fuzz_parse_ipv6$(EXT)
http$(EXT): src/main.cpp src/parse.cpp src/socket.cpp
g++ $^ -o $@ -Wall -Wextra -ggdb $(LFLAGS)
test_queue$(EXT):
g++ test/test_queue.cpp test/test_utils.cpp -o $@ -Wall -Wextra -ggdb
test_parse_ipv4$(EXT):
g++ test/test_parse_ipv4.cpp test/test_utils.cpp src/parse.cpp -o $@ -Wall -Wextra -ggdb
fuzz_parse_ipv4$(EXT):
clang++ test/fuzz_parse_ipv4.cpp -o $@ -fsanitize=fuzzer
fuzz_parse_ipv6$(EXT):
clang++ test/fuzz_parse_ipv6.cpp -o $@ -fsanitize=fuzzer
+24
View File
@@ -0,0 +1,24 @@
import matplotlib.pyplot as plt
import numpy as np
import scipy.stats as stats
import math
http_mu = 4.2
http_std = 28.99
nginx_mu = 160.88
nginx_std = 234.39
def draw_normal(mu, std):
mu = 0
sigma = std
x = np.linspace(mu - 3*sigma, mu + 3*sigma, 100)
plt.plot(x, stats.norm.pdf(x, mu, sigma))
draw_normal(http_mu, http_std)
draw_normal(nginx_mu, nginx_std)
plt.savefig('foo.png')
#plt.show()
+11
View File
@@ -0,0 +1,11 @@
worker_processes 1;
http {
server {
listen 8000;
location / {
add_header Content-Type text/plain;
return 200 'Hello, World!';
}
}
}
+286
View File
@@ -0,0 +1,286 @@
#include <cassert>
#include <utility>
#include <new>
#include <limits>
#include <algorithm>
#include "slice.hpp"
#include "socket.hpp"
#define MAX_VALUE(X) std::numeric_limits<decltype(X)>::max()
struct Buffer {
char *data; // Buffer content (or NULL when size=0)
int size; // Number of bytes stored in the buffer
int used; // Allocated bytes
bool fail; // True if at least one read or write operation failed at one point. No read or write operations can be performed after this is set.
int crlfcrlf; // Cache for the result of seek(\r\n\r\n)
bool ensure_unused_space(int min)
{
assert(!fail);
if (used + min > size) {
// Allocation of a new buffer is necessary
int new_size = size > 0 ? 2 * size : 256;
char *new_data = new (std::nothrow) char[new_size];
if (new_data == nullptr) {
fail = true;
return false;
}
memcpy(new_data, data, used);
delete[] data;
data = new_data;
size = new_size;
}
return true;
}
public:
Buffer()
{
data = nullptr;
size = 0;
used = 0;
fail = false;
crlfcrlf = -1;
}
Buffer(Buffer&) = delete;
Buffer& operator=(Buffer& other) = delete;
Buffer(Buffer&& other)
{
data = other.data;
size = other.size;
used = other.used;
fail = other.fail;
other.data = nullptr;
other.size = 0;
other.used = 0;
other.fail = false;
}
Buffer& operator=(Buffer&& other)
{
if (this != &other) {
delete[] data;
data = other.data;
size = other.size;
used = other.used;
fail = other.fail;
other.data = nullptr;
other.size = 0;
other.used = 0;
other.fail = false;
}
return *this;
}
~Buffer()
{
delete[] data;
}
int length() const
{
return used;
}
bool failed() const
{
return fail;
}
void overwrite(int off, const char *src, int len)
{
if (fail) return;
if (off < 0 || off + len > used) {
// Slice (off, len) isn't fully contained by
// the buffer.
fail = true;
return;
}
memmove(data + off, src, len);
}
void write(const char *src, int len=-1)
{
// Only perform the write if no allocations failed previously
if (fail) return;
if (len < 0) len = strlen(src);
// Check for overflows
if (used > MAX_VALUE(used) - len) {
fail = true;
return;
}
if (!ensure_unused_space(len))
return;
memcpy(data + used, src, len);
used += len;
}
int read(char *dst, int max)
{
if (fail) return 0;
crlfcrlf = -1; // Invalidate cache
int copy = std::min(used, max);
memcpy(dst, data, copy);
memmove(data, data + copy, used - copy);
used -= copy;
return copy;
}
// Moves byte from the socket to the buffer and
// returns true iff the peer closed the connection
bool write(Socket& sock)
{
if (fail) return false;
bool closed = false;
while (1) {
// Make sure the buffer has at least a certain amount of free memory
// to avoid small copies
constexpr int min_read = 256;
if (!ensure_unused_space(min_read))
break;
int res = sock.read(data + used, size - used);
if (res == Socket::WOULD_BLOCK)
break;
if (res == Socket::OTHER_ERROR) {
fail = true;
return false;
}
if (res == 0) {
closed = true;
break;
}
assert(res > 0);
// Check for overflow
if (used > MAX_VALUE(used) - res) {
fail = true;
return false;
}
used += res;
}
return closed;
}
// Moves byte from the buffer to the socket
int read(Socket& sock)
{
if (fail) return 0;
int copied = 0;
while (copied < used) {
// Make sure the buffer has at least a certain amount
// of free memory to avoid small copies
constexpr int min_read = 256;
if (!ensure_unused_space(min_read))
break;
int res = sock.write(data + copied, used - copied);
if (res == Socket::WOULD_BLOCK)
break;
if (res == Socket::OTHER_ERROR || res == 0) {
fail = true;
return 0;
}
assert(res > 0);
copied += res;
}
crlfcrlf = -1; // Invalidate cache
memmove(data + copied, data, used - copied);
used -= copied;
return copied;
}
// Find the index of the first occurrence of "needle"
// in the buffer's contents. Return -1 if it wasn't
// found.
int seek(const char *needle)
{
int len = strlen(needle);
bool is_crlfcrlf = false;
if (len == 4 && !strcmp(needle, "\r\n\r\n")) {
if (crlfcrlf > -1) return crlfcrlf;
is_crlfcrlf = true;
}
// If the token is contained by the buffer, its index
// must be lower than:
int lim = used - len + 1;
// If the needle is larger than the buffer, then the
// limit is negative
int i = 0;
while (i < lim && memcmp(data+i, needle, len))
i++;
if (i > lim)
return -1;
else {
if (is_crlfcrlf) crlfcrlf = i;
return i;
}
}
// Removed "num" bytes from the head of the buffer
void consume(int num)
{
assert(num <= used);
memmove(data, data + num, used - num);
used -= num;
crlfcrlf = -1; // Invalidate cache
}
bool contains(const char *needle)
{
return seek(needle) >= 0;
}
// Make a slice of the buffer's contents up to
// a given token. If include_token is true, then
// the slice will include the token at the end.
Slice slice_until(const char *token, bool include_token=false)
{
int end = seek(token);
if (end < 0)
return Slice("", 0);
else {
if (include_token) end += strlen(token);
return Slice(data, end);
}
}
Slice slice(int off, int end)
{
if (end < off || off < 0 || end >= used)
return Slice("", 0);
return Slice(data + off, end - off);
}
};
+25
View File
@@ -0,0 +1,25 @@
#include "server.hpp"
int main()
{
SocketSubsystem ss;
Server<16384> server;
int port = 8080;
if (!server.listen(port)) {
std::clog << "Couldn't start tcp server\n";
return -1;
}
for (;;) {
Request req;
server.wait(req);
server.status(200);
server.header("Content-Type", "text/plain");
server.write("Hello, world!");
server.send();
}
return 0;
}
+40
View File
@@ -0,0 +1,40 @@
#include <cstdint>
struct IPv4 {
uint32_t data;
IPv4(uint32_t d=0)
{
data = d;
}
IPv4(const char *str)
{
if (!parse(str))
data = 0;
}
bool parse(const char *str, int len=-1);
};
struct IPv6 {
uint16_t data[8];
IPv6()
{
for (int i = 0; i < 8; i++)
data[i] = 0;
}
IPv6(const char *str)
{
if (!parse(str)) {
for (int i = 0; i < 8; i++)
data[i] = 0;
}
}
bool parse(const char *str, int len=-1);
};
+713
View File
@@ -0,0 +1,713 @@
#include <cassert>
#include <limits>
#include <type_traits>
#include "parse.hpp"
#define MAX_VALUE(X) std::numeric_limits<decltype(X)>::max()
struct Scanner {
const char *str;
intptr_t len;
intptr_t off;
Scanner(const char *s, int l)
{
str = s;
len = l;
off = 0;
}
bool end() const
{
return off == len;
}
char curr() const
{
assert(off < len);
return str[off];
}
void back()
{
assert(off > 0);
off--;
}
char peek()
{
assert(off+1 < len);
return str[off+1];
}
void consume()
{
assert(off < len);
off++;
}
bool consume(char c)
{
if (end() || curr() != c)
return false;
consume();
return true;
}
bool consume(const char *s)
{
intptr_t l = strlen(s);
if (off + l > len)
return false;
if (strncmp(str + off, s, l))
return false;
off += l;
return true;
}
typedef bool (*CharTestFunc)(char c);
/*
* Consumes a substring that starts with a character
* c such that testfn_head(c) is true and following
* chars are such that testfn_body(c) is true.
*
* Returns true iff at least a character was consumed.
*/
bool consume(CharTestFunc testfn_head, CharTestFunc testfn_body)
{
if (end() || !testfn_head(curr()))
return false;
do
consume();
while (!end() && testfn_body(curr()));
return true;
}
/*
* It's like the previous function but uses the same
* function for head of the substring and body.
*/
bool consume(CharTestFunc testfn)
{
return consume(testfn, testfn);
}
};
bool is_upper_alpha(char c)
{
return c >= 'A' && c <= 'Z';
}
bool is_alpha(char c)
{
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
}
bool is_digit(char c)
{
return (c >= '0' && c <= '9');
}
/*
* Parse the scheme token of the following URL in "src" if present
*/
void parse_scheme(Scanner &src, Slice &dst)
{
intptr_t start = src.off;
auto schema_testfn_head = [](auto c) { return is_upper_alpha(c); };
auto schema_testfn_body = [](auto c) { return is_upper_alpha(c) || is_digit(c) || c == '+' || c == '-' || c == '.'; };
if (!src.consume(schema_testfn_head, schema_testfn_body))
dst.whipe();
else {
// May have a schema if ':' follows
if (!src.consume(':')) {
// Not a schema. Empty schema substring and
// restore the scanner cursor to the start
dst.whipe();
src.off = start;
} else {
dst.str = src.str + src.off;
dst.off = start;
dst.len = src.off - start;
}
}
}
/*
* From RFC 3986, Appendix A:
*
* sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
* / "*" / "+" / "," / ";" / "="
*/
bool is_subdelim(char c)
{
return c == '!' || c == '$' || c == '&' || c == '\'' || c == '('
|| c == ')' || c == '*' || c == '+' || c == ',' || c == ';'
|| c == '=';
}
/*
* From RFC 3986, Section 2.3:
* Characters that are allowed in a URI but do not have a reserved
* purpose are called unreserved. These include uppercase and lowercase
* letters, decimal digits, hyphen, period, underscore, and tilde.
*
* unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
*/
bool is_unreserved(char c)
{
return is_alpha(c) || is_digit(c) || c == '-' || c == '.' || c == '_' || c == '~';
}
/*
* From RFC 3986, Appendix A:
* pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
*/
bool is_pchar(char c)
{
return is_unreserved(c) || is_subdelim(c) || c == ':' || c == '@';
}
void parse_user_info(Scanner &src, Slice &dst)
{
intptr_t start = src.off;
if (src.consume([](char c) { return is_unreserved(c) || is_subdelim(c) || c == ':'; })) {
dst.str = src.str;
dst.off = start;
dst.len = src.len - start;
if (!src.consume("@")) {
// The scanned string wasn't an userinfo string
// after all. Clear the substring and put the
// scanner's cursor back to the start.
dst.whipe();
src.off = start;
}
} else
dst.whipe();
}
bool is_hex(char c)
{
return (c >= '0' && c <= '9')
|| (c >= 'a' && c <= 'f')
|| (c >= 'A' && c <= 'F');
}
bool parse_u16_base16(Scanner &src, uint16_t &dst)
{
if (src.end() || !is_hex(src.curr()))
return false; // Missing hex number
dst = 0;
do {
// Hex digit to int
int d = src.curr() - '0';
assert(d >= 0 && d < 16);
// Only consume hex digits up to 2^16-1
if (dst > (UINT16_MAX - d) / 16) {
src.back();
break;
}
dst = dst * 16 + d;
src.consume();
} while (!src.end() && is_hex(src.curr()));
return true;
}
bool parse_ipv6(Scanner &src, IPv6 &dst)
{
/*
* An IPv6 address is a sequence of 8 byte pairs expressed in
* hex and separated by ':' tokens.
*
* Each hex number represents a number from 0 to 2^16-1 which
* means it has a maximum of 4 hex digits.
*
* At any point between two hex numbers the "::" token may be
* used in place of ":". If that's the case the IPv6 will have
* less than 8 groups. The remaining hex numbers are assumed to
* be 0 and inserted where "::" is. It's also possible to have
* "::" at the beginning or end of the address.
*/
// Current number of numbers appended to
// the IPv6 data. By the end this must be 8.
int count = 0;
while (count < 8 && !src.consume("::")) {
// If this isn't the first number, consume the preceding ':'.
if (count > 0 && !src.consume(":"))
return false;
if (!parse_u16_base16(src, dst.data[count]))
return false;
count++;
}
if (count < 8) {
// The "::" was used.
// Parse the remaining ones in a buffer, then
// calculate the implicit zero numbers necessary
// to get to 8. Then, append the zeros and tail
// to the final array.
uint16_t tail[8];
int tail_count = 0;
while (count + tail_count < 7) {
// If this isn't the first number, consume the preceding ':'.
if (tail_count > 0 && !src.consume(":"))
return false;
if (!parse_u16_base16(src, tail[tail_count]))
return false;
tail_count++;
}
assert(count + tail_count < 8);
int implicit_pairs = 8 - (count + tail_count);
for (int i = 0; i < implicit_pairs; i++)
dst.data[count++] = 0;
// Now append the tail
for (int i = 0; i < tail_count; i++)
dst.data[count++] = tail[i];
}
assert(count == 8);
return true;
}
bool parse_ipv6(Scanner &src, Host &dst)
{
intptr_t start = src.off;
if (parse_ipv6(src, dst.ipv6)) {
dst.type = Host::IPV6;
dst.text.str = src.str;
dst.text.off = start;
dst.text.len = src.off - start;
return true;
}
return false;
}
bool parse_u8_base10(Scanner &src, uint8_t &dst)
{
if (src.end() || !is_digit(src.curr()))
return false;
dst = 0;
do {
int d = src.curr() - '0';
assert(d >= 0 && d < 10);
if (dst > (UINT8_MAX - d) / 10) {
src.back();
break;
}
dst = dst * 10 + d;
src.consume();
} while (!src.end() && is_digit(src.curr()));
return true;
}
bool parse_u16_base10(Scanner &src, uint16_t &dst)
{
if (src.end() || !is_digit(src.curr()))
return false;
dst = 0;
do {
int d = src.curr() - '0';
assert(d >= 0 && d < 10);
if (dst > (UINT16_MAX - d) / 10) {
src.back();
break;
}
dst = dst * 10 + d;
src.consume();
} while (!src.end() && is_digit(src.curr()));
return true;
}
bool parse_ipv4(Scanner &src, IPv4 &dst)
{
uint32_t word = 0;
for (int i = 0; i < 4; i++) {
if (i > 0 && !src.consume('.'))
return false;
uint8_t byte;
if (!parse_u8_base10(src, byte))
return false;
word = (word << 8) | byte;
}
dst = word;
return true;
}
bool parse_ipv4(Scanner &src, Host &dst)
{
intptr_t start = src.off;
if (parse_ipv4(src, dst.ipv4)) {
dst.type = Host::IPV4;
dst.text.str = src.str;
dst.text.off = start;
dst.text.len = src.off - start;
return true;
}
return false;
}
bool parse_host(Scanner &src, Host &dst)
{
if (src.end())
return false;
if (src.curr() == '[') {
// IPv6 or IPvFuture
if (!parse_ipv6(src, dst))
return false;
if (!src.consume(']'))
return false;
} else {
bool ipv4;
if (is_digit(src.curr()))
// May be a registered name or IPv4
ipv4 = parse_ipv4(src, dst);
else
ipv4 = false;
if (!ipv4) {
// It's a registered name.
//
// From RFC 3986, Appendix A:
//
// reg-name = *( unreserved / pct-encoded / sub-delims )
//
// It's worth noting that the registered name may be empty.
intptr_t start = src.off;
src.consume([](char c) { return is_unreserved(c) || is_subdelim(c); });
dst.type = Host::NAME;
dst.text.str = src.str;
dst.text.off = start;
dst.text.len = src.off - start;
}
}
return true;
}
bool parse_authority(Scanner &src, Authority &dst)
{
parse_user_info(src, dst.userinfo);
if (!parse_host(src, dst.host))
return false;
if (src.consume(':')) {
// There may be a port
if (src.end() || !is_digit(src.curr()))
dst.port = -1; // No port
else {
uint16_t buffer;
if (!parse_u16_base10(src, buffer))
return false;
dst.port = buffer;
}
} else
dst.port = -1; // No port
return true;
}
/*
* From RFC 3986, Section 3.4:
*
* query = *( pchar / "/" / "?" )
*
* and Section 3.5:
*
* fragment = *( pchar / "/" / "?" )
*/
Slice parse_query_or_fragment(Scanner& src)
{
Slice res;
res.str = src.str;
res.off = src.off;
src.consume([](char c) { return is_pchar(c) || c == '/' || c == '?'; });
res.len = src.off - res.off;
return res;
}
Slice parse_path_abempty(Scanner& src)
{
Slice path;
path.str = src.str;
path.off = src.off;
while (src.consume('/'))
src.consume(is_pchar);
path.len = src.off - path.off;
return path;
}
Slice parse_path(Scanner& src)
{
Slice path;
path.str = src.str;
path.off = src.off;
src.consume([](char c) { return is_pchar(c) || c == '/'; });
path.len = src.off - path.off;
return path;
}
/*
* See RFC 3986
*/
bool parse_url(Scanner &src, URL &dst)
{
Slice full;
full.str = src.str;
full.off = src.off;
parse_scheme(src, dst.scheme);
/*
* From RFC 3986, Section 3.2:
* The authority component is preceded by a double slash ("//") and is
* terminated by the next slash ("/"), question mark ("?"), or number
* sign ("#") character, or by the end of the URI.
*/
if (src.consume("//")) {
if (!parse_authority(src, dst.authority))
return false;
if (src.peek() == '/')
dst.path = parse_path_abempty(src);
} else
dst.path = parse_path(src);
if (src.consume('?')) dst.query = parse_query_or_fragment(src);
if (src.consume('#')) dst.fragment = parse_query_or_fragment(src);
full.len = src.off - full.off;
dst.full = full;
return true;
}
bool parse_method(Scanner& src, Method& dst, ParseError& error)
{
intptr_t start = src.off;
if (!src.consume(is_upper_alpha)) {
error.write("Missing method");
return false;
}
Slice text;
text.str = src.str;
text.off = start;
text.len = src.off - start;
if (text == "GET")
dst = GET;
else if (text == "POST")
dst = POST;
else {
error.write("Method not supported");
return false;
}
return true;
}
bool parse_request(Scanner &src, Request &dst, ParseError& error)
{
if (!parse_method(src, dst.method, error))
return false;
// Skip one space
if (!src.consume(' ')) {
error.write("Missing space after method");
return false;
}
URL url;
if (!parse_url(src, url)) {
error.write("Invalid URL");
return false;
}
dst.url = url;
if (!src.consume(" HTTP/1\r\n") &&
!src.consume(" HTTP/1.0\r\n") &&
!src.consume(" HTTP/1.1\r\n")) {
error.write("Invalid HTTP version token\n");
return false;
}
// Parse headers
if (!src.consume("\r\n")) {
do {
Slice name;
Slice value;
name.str = src.str;
name.off = src.off;
src.consume([](char c) { return c != ':'; });
name.len = src.off - name.off;
if (!src.consume(':')) {
error.write("Missing ':' after header name");
return false;
}
value.str = src.str;
value.off = src.off;
src.consume([](char c) { return c != '\r'; });
value.len = src.off - value.off;
// Add the parsed header to the request structure.
// If the header limit was reached, report that
// the header was dropped.
if (dst.count < MAX_REQUEST_HEADERS)
dst.headers[dst.count++] = (Header) {name, value};
else
dst.ignored_count++;
if (!src.consume("\r\n")) {
error.write("Missing CRLF after header body");
return false;
}
} while (!src.consume("\r\n")); // Parse headers until you find and empty line.
}
// Now the cursor points to after the final CRLF. Lets make sure
// that nothing comes after that.
if (src.off != src.len) {
error.write("Bad characters after empty line");
return false;
}
// All done.
return true;
}
bool Request::parse(const char *str, int len)
{
ParseError error;
Scanner scanner(str, len);
valid = parse_request(scanner, *this, error);
return valid;
}
bool Request::parse(const char *str, int len, ParseError& error)
{
Scanner scanner(str, len);
valid = parse_request(scanner, *this, error);
return valid;
}
bool Request::parse(Slice src)
{
return parse(src.str, src.len);
}
bool Request::parse(Slice src, ParseError& error)
{
return parse(src.str, src.len, error);
}
bool IPv4::parse(const char *str, int len)
{
if (len < 0) len = strlen(str);
Scanner scanner(str, len);
return parse_ipv4(scanner, *this);
}
bool IPv6::parse(const char *str, int len)
{
if (len < 0) len = strlen(str);
Scanner scanner(str, len);
return parse_ipv6(scanner, *this);
}
bool is_space(char c)
{
return c == ' ' || c == '\t' || c == '\r' || c == '\n';
}
int Request::content_length() const
{
if (!valid)
return 0;
int i; // Header index
for (i = 0; i < count; i++)
if (headers[i].name == "Content-Length")
break;
if (i == count)
return 0; // Content-Length not found, assume 0
Slice value = headers[i].value;
int j = 0; // Header value cursor
// Consume optional spaces
while (j < value.len && is_space(value[j]))
j++;
// After the spaces is expected a number
if (j == value.len || !is_digit(value[j]))
return 0; // No number, assume 0 length
// Found a digit. Parse the entire number until
// no more digits are found
int length = 0;
do {
char c = value[j];
assert(is_digit(c));
int digit = c - '0';
if (length > (MAX_VALUE(length) - digit) / 10) {
// Overflow. The reported length is too big.
return -1;
}
length = length * 10 + digit;
j++;
} while (j < value.len && is_digit(value[j]));
return length;
}
+89
View File
@@ -0,0 +1,89 @@
#include "slice.hpp"
#include "netutils.hpp"
struct Host {
enum Type { NAME, IPV4, IPV6 };
Type type;
Slice text; // any type
IPv4 ipv4; // when type=IPV4
IPv6 ipv6; // when type=IPV6;
Host()
{
type = IPV4;
}
};
struct Authority {
Slice userinfo;
Host host;
int port; // -1 means no port
Authority()
{
port = -1;
}
};
struct URL {
Slice full;
Slice scheme;
Authority authority;
Slice path;
Slice query;
Slice fragment;
};
struct Header {
Slice name;
Slice value;
};
enum Method {
GET, POST,
};
#include <cstdio>
#include <cstdarg>
struct ParseError {
char text[256];
void write(const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
vsnprintf(text, sizeof(text), fmt, args);
va_end(args);
}
};
constexpr int MAX_REQUEST_HEADERS = 32;
struct Request {
bool valid;
Method method;
URL url;
Header headers[MAX_REQUEST_HEADERS];
int count, ignored_count;
Slice body;
Request()
{
valid = false;
method = GET;
count = 0;
ignored_count = 0;
}
bool parse(const char *str, int len);
bool parse(const char *str, int len, ParseError& error);
bool parse(Slice slice);
bool parse(Slice slice, ParseError& error);
int content_length() const;
};
+206
View File
@@ -0,0 +1,206 @@
#include <cstdint>
template <typename T, int N>
class Pool {
// Structure representing the bytes that can
// hold a "T". This lets us speak about T's
// memory without calling the constructor.
struct Slot {
alignas(T) char pad[sizeof(T)];
};
static constexpr int ceil(int U, int V)
{
return (U + V - 1) / V;
}
static const int NUM_BITSETS = ceil(N, 64);
int num_allocated;
// Memory available for allocation
Slot slots[N];
// Packed array of booleans. Each bit describes
// if its associated slot was allocated or not
uint64_t used[NUM_BITSETS];
// Returns the index from the right of the
// first set bit or -1 otherwise.
static int find_first_set_bit(uint64_t bits)
{
// First check that at least one bit is set
if (bits == 0) return -1;
uint64_t bits_no_rightmost = bits & (bits - 1);
uint64_t bits_only_rightmost = bits - bits_no_rightmost;
int index = 0;
uint64_t temp;
// The index of the rightmost bit is the log2
temp = bits_only_rightmost >> 32;
if (temp) {
// Bit is in the upper 32 bits
index += 32;
bits_only_rightmost = temp;
}
temp = bits_only_rightmost >> 16;
if (temp) {
index += 16;
bits_only_rightmost = temp;
}
temp = bits_only_rightmost >> 8;
if (temp) {
index += 8;
bits_only_rightmost = temp;
}
static const uint8_t table[] = {
0, 0, 1, 0, 2, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0,
4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
};
index += table[bits_only_rightmost];
return index;
}
void set_bit(int index, bool val)
{
assert(index >= 0 && index < N);
uint64_t mask = 1ULL << (index & 63);
if (val)
used[index >> 6] |= mask;
else
used[index >> 6] &= ~mask;
}
bool get_bit(int index) const
{
uint64_t set = used[index >> 6];
uint64_t mask = 1ULL << (index & 63);
return (set & mask) == mask;
}
int get_index(T* addr) const
{
return (Slot*) addr - slots;
}
T* find_not_allocated()
{
if (num_allocated == N)
return nullptr;
// Find the index of a bitset with a zero bit
int i = 0;
while (used[i] == ~0ULL)
i++;
// Now find the zero bit
int j = find_first_set_bit(~used[i]);
assert(j > -1);
T* addr = (T*) &slots[i * 64 + j];
assert(!allocated(addr));
return addr;
}
void mark_allocated(T* addr)
{
num_allocated++;
set_bit(get_index(addr), 1);
}
void mark_not_allocated(T* addr)
{
num_allocated--;
set_bit(get_index(addr), 0);
}
public:
Pool()
{
num_allocated = 0;
for (int i = 0; i < NUM_BITSETS; i++)
used[i] = 0;
}
~Pool()
{
// Free allocated objects
for (int i = 0; i < N; i++) {
T* addr = (T*) &slots[i];
if (allocated(addr))
deallocate(addr);
}
}
Pool(Pool& other) = delete;
Pool(Pool&& other) = delete;
Pool& operator=(Pool& other) = delete;
Pool& operator=(Pool&& other) = delete;
T* allocate()
{
T* addr = find_not_allocated();
if (addr == nullptr) return nullptr;
new (addr) T();
mark_allocated(addr);
return addr;
}
bool owned(T* addr) const
{
return (intptr_t) addr >= (intptr_t) slots
&& (intptr_t) addr < (intptr_t) (slots + N);
}
bool allocated(T* addr)
{
return owned(addr) && get_bit(get_index(addr));
}
int currently_allocated_count() const
{
return num_allocated;
}
bool have_free_space() const
{
return num_allocated < N;
}
void deallocate(T* addr)
{
if (!owned(addr) || !allocated(addr))
return;
// Destroy the object
addr->~T();
mark_not_allocated(addr);
}
};
+39
View File
@@ -0,0 +1,39 @@
#include "print.hpp"
void vprint(std::ostream& dst, const char* fmt, PrintArg* pargs, int num_args)
{
char sep = '@';
int curr_arg = 0;
int len = strlen(fmt);
int cur = 0;
while (cur < len) {
// Move the cursor until the next separator
// or end of format string
int plain_text_off = cur;
while (cur < len && fmt[cur] != sep)
cur++;
int plain_text_len = cur - plain_text_off;
// Write the plain text string
dst.write(fmt + plain_text_off, plain_text_len);
// If the plain text string ended with a
// separator, print an argument
if (cur < len) {
assert(fmt[cur] == sep);
if (curr_arg == num_args) {
// The '%' doesn't refer to any arguments
dst << sep;
} else {
dst << pargs[curr_arg++];
}
// Consume the '%'
cur++;
}
}
}
+74
View File
@@ -0,0 +1,74 @@
#include <cstring>
#include <cstdint>
#include <cassert>
#include <ostream>
struct PrintArg {
enum Type {
INT,
FLOAT,
STRING,
OTHER,
};
Type type;
union {
int64_t as_int;
double as_float;
const char* as_string;
void* other;
} data;
#define IS_INT(T) typename std::enable_if< std::is_integral<T>::value>::type
#define ISNT_INT(T) typename std::enable_if<!std::is_integral<T>::value>::type
// Only used when type=OTHER
void (*print_ptr)(void* data, std::ostream& dst);
template <typename T>
PrintArg(IS_INT(T) value)
{
type = INT;
data.as_int = value;
}
PrintArg(double value)
{
type = FLOAT;
data.as_float = value;
}
PrintArg(const char* value)
{
type = STRING;
data.as_string = value;
}
template <typename T>
PrintArg(ISNT_INT(T)&& value)
{
data.other = &value;
print_ptr = [](void* data, std::ostream& dst) { dst << (*(T*) data); };
}
friend std::ostream& operator<<(std::ostream& dst, PrintArg& arg)
{
switch (arg.type) {
case INT : dst << arg.data.as_int; break;
case FLOAT : dst << arg.data.as_float; break;
case STRING: dst << arg.data.as_string; break;
case OTHER : arg.print_ptr(arg.data.other, dst); break;
}
return dst;
}
};
void vprint(std::ostream& dst, const char* fmt, PrintArg* pargs, int num_args);
template <typename ...Ts>
void print(std::ostream& dst, const char *fmt, Ts&& ...args)
{
PrintArg pargs[] = {args...};
vprint(dst, fmt, pargs, sizeof...(args));
}
+82
View File
@@ -0,0 +1,82 @@
#include <utility>
template <typename T, int N>
class Queue {
int head;
int used;
T items[N];
public:
Queue()
{
head = 0;
used = 0;
}
Queue(Queue& other) = delete;
Queue(Queue&& other) = delete;
Queue& operator=(Queue& other) = delete;
Queue& operator=(Queue&& other) = delete;
bool empty() const
{
return used == 0;
}
int size() const
{
return used;
}
template <typename U>
bool push(U&& item)
{
if (used == N)
return false;
int tail = (head + used) % N;
items[tail] = std::forward<U>(item);
used++;
return true;
}
bool pop()
{
if (used == 0)
return false;
items[head] = T();
head = (head + 1) % N;
used--;
return true;
}
bool pop(T& item)
{
if (used == 0)
return false;
item = std::move(items[head]);
return pop();
}
// Removes the first occurrence of item from the queue. Returns true if an
// item was found or false if it wasn't.
bool remove(const T& item)
{
int i;
for (i = 0; i < used; i++) {
int j = (head + i) % N;
if (items[j] == item)
break;
}
if (i == used)
return false;
for (; i < used-1; i++) {
int j = (head + i) % N;
items[j] = std::move(items[(j+1) % N]);
}
items[(head + used - 1) % N] = T();
return true;
}
};
+704
View File
@@ -0,0 +1,704 @@
#include <utility>
#include <cassert>
#include <iostream>
#include "pool.hpp"
#include "queue.hpp"
#include "parse.hpp"
#include "socket.hpp"
#include "buffer.hpp"
/*
* Structure that represents the connection with a client
*/
struct Client {
Socket sock;
Buffer in;
Buffer out;
// Number of requests from this client that were
// handled.
int num_served;
// True iff the client's reference is in the
// server's candidate queue
bool queued;
// Tells the server that the connection with this
// client should be terminated when the output
// buffer is fully flushed.
bool close_when_flushed;
Client()
{
queued = false;
num_served = 0;
close_when_flushed = false;
}
Client(Client&) = delete;
Client(Client&&) = delete;
Client& operator=(Client&) = delete;
Client& operator=(Client&&) = delete;
};
template <int MAX_CLIENTS>
class Server {
public:
Server()
{
state = NOTARGET;
}
Server(Server& other) = delete;
Server(Server&& other) = delete;
Server& operator=(Server& other) = delete;
Server& operator=(Server&& other) = delete;
/*
* Start listening for incoming connections on
* the specified port and on the "addr" interface.
*
* The "addr" argument must be an ipv4 address in
* dotted decimal notation. If it's NULL, the server
* will listen on all available interfaces.
*/
bool listen(int port=8080, const char *addr=nullptr);
/*
* Get an HTTP request to handle. If a request was
* already queued this call won't block, else it
* will.
*/
void wait(Request& req);
/*
* This function can only be called between two
* "wait" calls and it sets the HTTP status code
* of the reply to the last request returned by
* wait.
*
* You can't call this function more than once
* per request and it must be done before calling
* "status", "header", "write" or "send".
*/
void status(int code);
/*
* Similarly to "status", this function sets a
* response header for the last request returned
* by "wait".
*
* It may be called more than once. But before
* "write" and "send".
*/
void header(const char *name, const char *value);
/*
* Similar to "header" but appends bytes to the
* response's body. It must be called after "send".
*/
void write(const char *str, int len=-1);
/*
* Mark a request as handled. You can no longer
* modify the response after you call this function.
*/
void send();
private:
// Since responses are built using a kind of
// immediate-mode API ("status", "header", "write"
// and "send"), the server needs to hold a state
// to discriminate between valid and invalid calls
// for the response creation.
enum State {
NOTARGET, // No request is being handled. This is both the
// starting value and that set by "send".
STATUS, // A request was returned though "wait" but no
// "status" call was done
HEADERS, // "status" has been called. Now either "header"
// or "write" are allowed.
CONTENT, // A call to "write" has been done, so only other
// calls to it are allowed.
// After any of these states you can "send" and
// go to the "NOTARGET" state.
};
State state;
// Listening socket.
Socket socket_;
// Pool of client structures
Pool<Client, MAX_CLIENTS> pool;
// The eventloop must be able to hold one entry per
// client and one more for the listening socket.
//
// It may be necessary to hold some timers for each
// client.
EventLoop<MAX_CLIENTS+1> evloop;
// This queue holds references to clients that are
// "response candidates". A candidate is a client
// for which a request head was received, but the
// body may or may not.
//
// When a client is sending to the server a request,
// the moment the server receives the "\r\n\r\n" token
// it considers the client a "candidate" for being
// responded to.
//
// An HTTP request has this general structure:
//
// GET /home HTTP/1.1 \r\n
// header1: value1 \r\n
// header2: value2 \r\n
// Content-Length: XXX \r\n
// \r\n
// ... Content ...
//
// So the \r\n\r\n determines the end of the request's
// head and start of the body. There is no way of knowing
// if the request body was also received without parsing
// the entire request and getting the value of the
// "Content-Length" header. To avoid parsing the request
// twice or having to cache the result, we just mark the
// client as "candidate" and push it to this queue. The
// "wait" function will pop elements of this queue looking
// for one that's actually ready and return that to the
// user.
Queue<Client*, MAX_CLIENTS> queue;
// The following fields are state necessary when responding
// to a request. They only hold meaning when the state isn't
// NOTARGET.
Client* target; // Current client that's being responded to
int offset_content_length; // Offset (in bytes) of the "Content-Length" header's value
// in the output buffer of the target client. This is set
// during the first "write" call after a "wait".
int offset_content; // Offset (in bytes) of the response body in the output buffer
// of the target client. It's set at the first "write" call after
// "wait".
int keep_alive; // This is 1 if the user set the "Connection: Keep-Alive" header or
// 0 if it set "Connection: Close". Its initial value is -1, so reading
// -1 means the user didn't specify anything yet.
int req_bytes; // Size (in bytes) of the request that's being served. This is necessary
// when the response is completed and the request bytes can be dropped.
static const char* status_text(int code);
// Choose if a given connection can be kept alive.
// This is a function of:
// 1. num_clients: The number of curretly connected clients
// 2. max_clients: The client limit
// 3. How many responses were previously served to this client
static bool should_keep_alive(int num_clients, int max_clients, int num_served);
void remove_client(Client* client);
void accept_incoming_connections();
void handle_single_event(Event event);
void handle_client_data_and_queue_if_candidate(Client* client);
void flush_buffered_bytes_to_client_and_close_if_done(Client* client);
};
template <int N>
bool Server<N>::listen(int port, const char *addr)
{
if (socket_.active())
return false; // Already listening
Socket socket;
if (!socket.start_server(port, addr))
return false;
// We want to know when calling "accept" on the socket
// will not block. From the point of view of "poll"
// (the underlying syscall of the event loop) an ACCEPT
// operation is a read operation.
if (!evloop.add(socket, Event::RECV, this)) {
std::clog << "Couldn't add socket to event loop object\n";
return false;
}
std::clog << "Listening on " << (addr ? addr : "0.0.0.0") << ":" << port << "\n";
// Commit the socket structure
socket_ = std::move(socket);
return true;
}
/*
* See the forward declaration.
*/
template <int N>
void Server<N>::wait(Request& req)
{
// Make sure any pending response is sent and
// the state is NOTARGET.
send();
assert(state == NOTARGET);
/*
* Basically what this loop is doing is handling
* TCP level I/O until one or more clients become
* response candidates. When that's true it pops
* a client, parses the request into the user-provided
* structure and checks that the request body was
* fully received. If it wasn't it drops the client
* and gets or waits for a new candidate. If the body
* was received, it returns to the user.
*
* Clients that were considered candidates but
* couldn't be served yet may receive more bytes
* in the future. Whenever they receive bytes they
* will be considered candidates again until they're
* served.
*/
do {
while (queue.empty()) {
Event event = evloop.wait();
handle_single_event(event);
}
Client* candidate;
queue.pop(candidate);
candidate->queued = false;
assert(pool.allocated(candidate));
// It's known that the input buffer contains
// a \r\n\r\n or the client wouldn't have been
// inserted in the queue.
Slice head = candidate->in.slice_until("\r\n\r\n", true);
ParseError error;
if (!req.parse(head, error)) {
// Invalid request
// TODO: Send a message to the client before removing it
std::clog << "Parsing Error: " << error.text << "\n";
remove_client(candidate);
continue;
}
int head_len = head.len;
int body_len = req.content_length();
if (body_len < 0) {
// Malformed Content-Length header
std::clog << "Malformed Content-Length header\n";
remove_client(candidate);
continue;
}
int total_len = head_len + body_len;
// We know the head of the request was received, but
// if the body wasn't we can't respond yet.
if (candidate->in.length() >= total_len) {
// Request was fully received
req.body = candidate->in.slice(head_len, total_len);
target = candidate;
state = STATUS;
req_bytes = total_len;
keep_alive = -1;
break;
}
// Still waiting for the request's body.
// Go back to waiting for a candidate.
} while (1);
}
template <int N>
bool Server<N>::should_keep_alive(int num_clients, int max_clients, int num_served)
{
// If the server is about 70% full, don't keep connections alive
if (10 * num_clients > 7 * max_clients)
return false;
// Only keep alive if less than 5 responses were served
if (num_served >= 5)
return false;
return true;
}
template <int N>
void Server<N>::remove_client(Client* client)
{
assert(pool.allocated(client));
evloop.remove(client->sock);
if (client->queued)
queue.remove(client);
pool.deallocate(client);
assert(!pool.allocated(client));
}
template <int N>
void Server<N>::accept_incoming_connections()
{
// TODO: Since we're leaving some connections in the queue
// when the client limit is reached, we need to make
// sure to serve them when some client structs are freed.
// Accept all incoming connections until the client pool is full
for (Socket sock; pool.have_free_space() && socket_.accept(sock); ) {
Client* client = pool.allocate();
assert(client);
// At first only register for receive events since
// there's nothing to be sent.
if (!evloop.add(sock, Event::RECV, client)) {
pool.deallocate(client);
continue;
}
// Commit socket
client->sock = std::move(sock);
// The newly accepted client may already have some
// data to be read. Generate a RECV event manually.
handle_single_event(Event(Event::RECV, client));
}
}
template <int N>
void Server<N>::handle_client_data_and_queue_if_candidate(Client* client)
{
// Client sent data. Copy it into the buffer
bool closed = client->in.write(client->sock);
if (closed || client->in.failed()) {
remove_client(client);
return;
}
// If the client isn't already ready to be served,
// it may be now. Check wether the head of the request
// was fully received.
// The head of a request is terminated by a CRLF CRLF
// token.
if (client->in.contains("\r\n\r\n")) {
// Head was received! Push the client into the queue if
// it wasn't already.
if (!client->queued) {
queue.push(client);
client->queued = true;
}
// It's possible to avoid to check wether the client is
// contained in the queue by caching the information.
// This would speed up the process but also add redundancy
// to the class and possible bugs.
}
}
template <int N>
void Server<N>::flush_buffered_bytes_to_client_and_close_if_done(Client* client)
{
// Client is ready to receive data
client->out.read(client->sock);
if (client->out.failed()) {
remove_client(client);
return;
}
if (client->out.length() == 0) {
// Nothing more to send.
if (client->close_when_flushed) {
remove_client(client);
return;
}
// Tell the eventloop we're not interested
// in output events for this client
evloop.remove_events(client->sock, Event::SEND);
}
}
template <int N>
void Server<N>::handle_single_event(Event event)
{
if (event.data == nullptr)
return; // Event isn't relative to a socket
if (event.data == this)
accept_incoming_connections();
else {
Client* client = (Client*) event.data;
assert(pool.allocated(client));
switch (event.type) {
case Event::FAILURE: remove_client(client); break;
case Event::RECV: handle_client_data_and_queue_if_candidate(client); break;
case Event::SEND: flush_buffered_bytes_to_client_and_close_if_done(client); break;
}
}
}
template <int N>
void Server<N>::status(int code)
{
if (state == NOTARGET)
return;
assert(target);
if (state != STATUS)
return; // "status" called twice
char buf[256];
int len = snprintf(buf, sizeof(buf), "HTTP/1.1 %d %s\r\n", code, status_text(code));
assert(len > 0);
// No need to check for errors. We'll do it
// when "reply" is called.
target->out.write(buf, len);
state = HEADERS;
}
template <int N>
void Server<N>::header(const char *name, const char *value)
{
if (state == NOTARGET)
return;
if (state == STATUS)
// Header added before a status, so first
// add a 200 for correctness sake
status(200);
if (state == CONTENT)
// Can't add a header after the start of
// the response's body.
return;
assert(state == HEADERS);
// Make sure that the caller isn't writing
// a header that must be added automatically
// by this class.
// TODO: Make the check case-insensitive.
if (!strcmp(name, "Content-Length"))
return;
if (!strcmp(name, "Connection")) {
if (!strcmp(value, "Close"))
keep_alive = 0;
else
keep_alive = 1;
return;
}
target->out.write(name);
target->out.write(": ");
target->out.write(value);
target->out.write("\r\n");
}
template <int N>
void Server<N>::write(const char *str, int len)
{
if (len < 0) len = strlen(str);
if (state == NOTARGET)
return;
assert(target);
if (state == STATUS)
status(200);
// If this is the first time we append to the
// body of the response, append special headers
if (state == HEADERS) {
// This is the start of the response body, so
// add any special header and the empty line
// separator.
if (keep_alive == -1) keep_alive = 1;
// If the user wants to keep the connection alive
// (or didn't specify it) then check first if it's
// reasonable given the server's state.
int num_clients = pool.currently_allocated_count();
int max_clients = N;
int num_served = target->num_served;
if (!should_keep_alive(num_clients, max_clients, num_served))
keep_alive = false;
switch (keep_alive) {
case 0: target->out.write("Connection: Close\r\n"); break;
case 1: target->out.write("Connection: Keep-Alive\r\n"); break;
}
// Append the Content-Length header with an empty value.
// When the response content is known we'll fill the value in.
target->out.write("Content-Length: ");
offset_content_length = target->out.length();
target->out.write(" \r\n"); // This is exactly 9 spaces before the \r\n
// Write an empty line
target->out.write("\r\n");
offset_content = target->out.length();
state = CONTENT;
}
target->out.write(str, len);
}
template <int N>
void Server<N>::send()
{
if (state == NOTARGET)
return;
assert(target);
// Make sure the previous response parts are written
write("");
if (target->out.failed()) {
// Actually the response construction failed, so drop the client.
remove_client(target);
} else {
// Update the Content-Length header's vale now
// that we know the content's length.
int content_length = target->out.length() - offset_content;
char buf[32];
int len = snprintf(buf, sizeof(buf), "%d", content_length);
assert(len < 10);
target->out.overwrite(offset_content_length, buf, len);
// Tell the eventloop that we're interested in output
// events for this client.
evloop.add_events(target->sock, Event::SEND);
// If the connection isn't marked as reusable, mark it
// to be closed when the output buffer is flushed and
// stop listening for input data.
// NOTE: "keep_alive" can't be -1 at this point because
// it was set to either 0 or 1 when writing the
// "Connection" header.
if (keep_alive == 0) {
target->close_when_flushed = true;
evloop.remove_events(target->sock, Event::RECV);
}
// Now that the request was served, we can remove it
// from the input buffer.
target->in.consume(req_bytes);
// If the connection is keep-alive, pipelining is allowed
// so check if an other request is pending and if it is,
// put the client back into the queue.
if (keep_alive && target->in.contains("\r\n\r\n")) {
// We know that the client isn't already in the queue
// because we just popped and served it.
queue.push(target);
target->queued = true;
}
target->num_served++;
}
state = NOTARGET;
target = nullptr;
keep_alive = -1;
req_bytes = -1;
}
template <int N>
const char* Server<N>::status_text(int code)
{
switch(code) {
case 100: return "Continue";
case 101: return "Switching Protocols";
case 102: return "Processing";
case 200: return "OK";
case 201: return "Created";
case 202: return "Accepted";
case 203: return "Non-Authoritative Information";
case 204: return "No Content";
case 205: return "Reset Content";
case 206: return "Partial Content";
case 207: return "Multi-Status";
case 208: return "Already Reported";
case 300: return "Multiple Choices";
case 301: return "Moved Permanently";
case 302: return "Found";
case 303: return "See Other";
case 304: return "Not Modified";
case 305: return "Use Proxy";
case 306: return "Switch Proxy";
case 307: return "Temporary Redirect";
case 308: return "Permanent Redirect";
case 400: return "Bad Request";
case 401: return "Unauthorized";
case 402: return "Payment Required";
case 403: return "Forbidden";
case 404: return "Not Found";
case 405: return "Method Not Allowed";
case 406: return "Not Acceptable";
case 407: return "Proxy Authentication Required";
case 408: return "Request Timeout";
case 409: return "Conflict";
case 410: return "Gone";
case 411: return "Length Required";
case 412: return "Precondition Failed";
case 413: return "Request Entity Too Large";
case 414: return "Request-URI Too Long";
case 415: return "Unsupported Media Type";
case 416: return "Requested Range Not Satisfiable";
case 417: return "Expectation Failed";
case 418: return "I'm a teapot";
case 420: return "Enhance your calm";
case 422: return "Unprocessable Entity";
case 426: return "Upgrade Required";
case 429: return "Too many requests";
case 431: return "Request Header Fields Too Large";
case 449: return "Retry With";
case 451: return "Unavailable For Legal Reasons";
case 500: return "Internal Server Error";
case 501: return "Not Implemented";
case 502: return "Bad Gateway";
case 503: return "Service Unavailable";
case 504: return "Gateway Timeout";
case 505: return "HTTP Version Not Supported";
case 509: return "Bandwidth Limit Exceeded";
}
return "???";
}
+54
View File
@@ -0,0 +1,54 @@
#ifndef SLICE_HPP
#define SLICE_HPP
#include <cstdint>
#include <cstring>
#include <ostream>
struct Slice {
const char *str;
intptr_t off;
intptr_t len;
Slice()
{
whipe();
}
Slice(const char *str2, int len2)
{
str = str2;
len = len2;
off = 0;
}
void whipe()
{
str = nullptr;
off = 0;
len = 0;
}
char operator[](int index) const
{
assert(index >= 0 && index < len);
return str[off + index];
}
bool operator==(const char *s) const
{
intptr_t l = strlen(s);
if (l != len)
return false;
return !strncmp(str+off, s, len);
}
friend std::ostream& operator<<(std::ostream& os, Slice& sl)
{
os.write(sl.str + sl.off, sl.len);
return os;
}
};
#endif
+19
View File
@@ -0,0 +1,19 @@
#include <cassert>
#include <iostream>
#include "socket.hpp"
std::ostream& operator<<(std::ostream& os, Event::Type const& type)
{
switch (type) {
case Event::FAILURE: os << "FAILURE"; break;
case Event::RECV: os << "RECV"; break;
case Event::SEND: os << "SEND"; break;
}
os << " (" << (int) type << ")";
return os;
}
std::ostream& operator<<(std::ostream& os, Event const& event)
{
return os << "Event { type=" << event.type << ", data=" << event.data << " }";
}
+401
View File
@@ -0,0 +1,401 @@
#ifndef SOCKET_HPP
#define SOCKET_HPP
#include <ostream>
#ifdef _WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#define POLL WSAPoll
#define EWOULDBLOCK_2 WSAEWOULDBLOCK
#define EAGAIN_2 WSAEWOULDBLOCK
#define EINVAL_2 WSAEINVAL
#define CLOSESOCKET closesocket
#else
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/poll.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#define SOCKET int
#define INVALID_SOCKET -1
#define POLL poll
#define CLOSESOCKET close
#define EWOULDBLOCK_2 EWOULDBLOCK
#define EAGAIN_2 EAGAIN
#define EINVAL_2 EINVAL
#endif
struct SocketSubsystem {
SocketSubsystem()
{
#ifdef _WIN32
WSADATA data;
int res = WSAStartup(MAKEWORD(2,2), &data);
if (res)
std::cout << "WSAStartup failed\n";
#endif
}
~SocketSubsystem()
{
#ifdef _WIN32
WSACleanup();
#endif
}
};
struct Socket {
private:
static int get_last_error()
{
#ifdef _WIN32
return WSAGetLastError();
#else
return errno;
#endif
}
static bool set_blocking(SOCKET fd, bool value)
{
#ifdef _WIN32
unsigned long not_value = !value;
return ioctlsocket(fd, FIONBIO, &not_value) != SOCKET_ERROR;
#else
int flags = fcntl(fd, F_GETFL);
if (flags == -1)
return false;
if (value)
flags &= ~O_NONBLOCK;
else
flags |= O_NONBLOCK;
return fcntl(fd, F_SETFL, flags) != -1;
#endif
}
public:
SOCKET fd_;
enum {
WOULD_BLOCK = -1,
OTHER_ERROR = -2,
};
Socket(SOCKET fd=INVALID_SOCKET)
{
fd_ = fd;
}
Socket(Socket&) = delete;
Socket& operator=(Socket&) = delete;
Socket(Socket&& other)
{
if (this != &other) {
fd_ = other.fd_;
other.fd_ = INVALID_SOCKET;
}
}
Socket& operator=(Socket&& other)
{
if (this != &other) {
if (fd_ != INVALID_SOCKET) CLOSESOCKET(fd_);
fd_ = other.fd_;
other.fd_ = INVALID_SOCKET;
}
return *this;
}
~Socket()
{
if (fd_ != INVALID_SOCKET)
CLOSESOCKET(fd_);
}
bool active() const
{
return fd_ != INVALID_SOCKET;
}
bool accept(Socket& dst)
{
if (!active()) return false;
int accepted = ::accept(fd_, nullptr, nullptr);
if (accepted < 0)
return false;
if (!set_blocking(accepted, false)) {
CLOSESOCKET(accepted);
return false;
}
dst = accepted;
return true;
}
int read(char *dst, int max)
{
if (!active()) return -1;
int res = recv(fd_, dst, max, 0);
if (res < 0) {
int code = get_last_error();
if (code == EWOULDBLOCK_2 || code == EAGAIN_2)
return WOULD_BLOCK;
else
return OTHER_ERROR;
}
assert(res >= 0);
return res;
}
int write(char *src, int num)
{
if (!active()) return -1;
int res = send(fd_, src, num, 0);
if (res < 0) {
int code = get_last_error();
if (code == EWOULDBLOCK_2 || code == EAGAIN_2)
return WOULD_BLOCK;
else
return OTHER_ERROR;
}
assert(res >= 0);
return res;
}
bool start_server(int port, const char *addr)
{
if (active()) return false;
SOCKET fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd == INVALID_SOCKET) {
std::clog << "Couldn't create socket (did you initialize the socket system?)\n";
return false;
}
if (!set_blocking(fd, false)) {
CLOSESOCKET(fd);
std::clog << "Couldn't set socket as non-blocking\n";
return false;
}
struct in_addr addr_buf;
if (addr == nullptr)
addr_buf.s_addr = INADDR_ANY;
else {
int res = inet_pton(AF_INET, addr, &addr_buf);
if (res == 0 || res == -1) {
if (res == 0) {
// Invalid address string
std::cout << "Invalid address string\n";
} else {
// Unknown error
std::cout << "Unknown error\n";
}
CLOSESOCKET(fd);
return false;
}
assert(res == 1);
}
// Probably should only use this in debug
int v = 1;
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char*) &v, sizeof(int));
struct sockaddr_in full_addr_buf;
full_addr_buf.sin_family = AF_INET;
full_addr_buf.sin_port = htons(port);
full_addr_buf.sin_addr = addr_buf;
if (bind(fd, (struct sockaddr*) &full_addr_buf, sizeof(full_addr_buf))) {
int code = get_last_error();
std::cout << "Couldn't bind to the specified address (code " << code << ")\n";
CLOSESOCKET(fd);
return false;
}
int backlog = 32;
if (listen(fd, backlog)) {
std::cout << "Couldn't start listening on " << addr << ":" << port << "\n";
CLOSESOCKET(fd);
return false;
}
fd_ = fd;
return true;
}
};
struct Event {
enum Type {
FAILURE = 0,
RECV = 1 << 0,
SEND = 1 << 1,
};
Type type;
void *data;
Event()
{
type = FAILURE;
data = nullptr;
}
Event(Type t, void* p=nullptr)
{
type = t;
data = p;
}
friend std::ostream& operator<<(std::ostream& os, Event::Type const& type);
friend std::ostream& operator<<(std::ostream& os, Event const& event);
};
template <int N>
class EventLoop {
void *ptrs[N];
struct pollfd bufs[N];
int count;
int cursor;
int find_socket_index(const Socket& sock)
{
for (int i = 0; i < count; i++)
if (bufs[i].fd == sock.fd_)
return i;
return -1;
}
static int convert_event_flags(int in)
{
int out = 0;
if (in & Event::RECV) out |= POLLIN; // Could OR POLLPRI but it's not supported by windows
if (in & Event::SEND) out |= POLLOUT;
return out;
}
public:
EventLoop()
{
count = 0;
cursor = 0;
}
~EventLoop()
{
}
bool add(const Socket& sock, int events, void *ptr=nullptr)
{
if (count == N)
return false;
bufs[count].fd = sock.fd_;
bufs[count].events = convert_event_flags(events);
bufs[count].revents = 0;
ptrs[count] = ptr;
count++;
return true;
}
void add_events(const Socket& sock, int events)
{
int i = find_socket_index(sock);
if (i < 0) return; // Not found
bufs[i].events |= convert_event_flags(events);
}
void remove_events(const Socket& sock, int events)
{
int i = find_socket_index(sock);
if (i < 0) return; // Not found
bufs[i].events &= ~convert_event_flags(events);
}
bool remove(const Socket& sock)
{
int i = find_socket_index(sock);
if (i < 0) return false; // Not found
bufs[i] = bufs[count-1];
ptrs[i] = ptrs[count-1];
count--;
if (cursor > i) cursor--;
// TODO: Remove all buffered events that refer
// to this socket.
return true;
}
// Move the cursor forward until a struct
// with some reported events is found. If
// no such structs exists, then "cursor"
// reaches "count".
void skip()
{
while (cursor < count && bufs[cursor].revents == 0)
cursor++;
}
Event wait()
{
skip();
// If no more buffers have events, poll for more events
while (cursor == count) {
int n = POLL(bufs, count, -1);
if (n < 0)
return Event(Event::FAILURE);
cursor = 0;
skip();
}
assert(cursor < count);
// At this point we know the cursor refers a struct
// with at least one reported event.
void* ptr = ptrs[cursor];
auto& revents = bufs[cursor].revents;
assert(revents != 0);
// Report to the caller only one of those events
// at the time. If report RECV events. Once those
// are reported, at the next iteration SEND events
// will be reported.
// We assume POLLPRI isn't reported (Windows doesn't
// support it).
assert((revents & POLLPRI) == 0);
if (revents & POLLIN) {
revents &= ~POLLIN;
return Event(Event::RECV, ptr);
}
if (revents & POLLOUT) {
revents &= ~POLLOUT;
return Event(Event::SEND, ptr);
}
// Report other events as errors
revents = 0;
return Event(Event::FAILURE, ptr);
}
};
#endif
+33
View File
@@ -0,0 +1,33 @@
#include <iostream>
#include "test_utils.hpp"
#include "../src/netutils.hpp"
#include <winsock2.h>
#include <ws2tcpip.h>
extern "C"
int LLVMFuzzerTestOneInput(const char *data,
size_t size)
{
IPv4 ip;
bool ok = ip.parse(data, size);
char buf[256];
if (size < sizeof(buf)) {
memcpy(buf, data, size);
buf[size] = '\0';
struct in_addr buf2;
switch (inet_pton(AF_INET, buf, &buf2)) {
case 1:
test(ok);
test(ip.data == buf2.s_addr);
break;
case 0:
case -1:
test(!ok);
break;
}
}
return 0; // Values other than 0 and -1 are reserved for future use.
}
+34
View File
@@ -0,0 +1,34 @@
#include <iostream>
#include "test_utils.hpp"
#include "../src/netutils.hpp"
#include <winsock2.h>
#include <ws2tcpip.h>
extern "C"
int LLVMFuzzerTestOneInput(const char *data,
size_t size)
{
IPv6 ip;
bool ok = ip.parse(data, size);
char buf[512];
if (size < sizeof(buf)) {
memcpy(buf, data, size);
buf[size] = '\0';
struct in6_addr buf2;
switch (inet_pton(AF_INET6, buf, &buf2)) {
case 1:
test(ok);
test(!memcmp(&ip.data, &buf2, 16));
break;
case 0:
case -1:
test(!ok);
break;
}
}
return 0; // Values other than 0 and -1 are reserved for future use.
}
+16
View File
@@ -0,0 +1,16 @@
#include <iostream>
#include "test_utils.hpp"
#include "../src/netutils.hpp"
int main()
{
IPv4 ip;
test(ip.parse("") == false);
test(ip.parse("@") == false);
test(ip.parse("1") == false);
test(ip.parse("500") == false);
test(ip.parse("45.") == false);
test(ip.parse("45.54.56.98") == true);
std::cout << "Passed\n";
return 0;
}
+109
View File
@@ -0,0 +1,109 @@
#include <cstdlib>
#include <iostream>
#include "test_utils.hpp"
#include "../src/queue.hpp"
int main()
{
{
Queue<int, 0> q;
test(q.push(10) == false);
test(q.size() == 0);
test(q.empty() == true);
test(q.pop() == false);
}
{
Queue<int, 1> q;
test(q.size() == 0);
test(q.empty() == true);
test(q.push(10) == true);
test(q.size() == 1);
test(q.empty() == false);
test(q.push(4) == false);
test(q.size() == 1);
test(q.empty() == false);
test(q.pop() == true);
test(q.pop() == false);
}
{
Queue<int, 4> q;
test(q.push(1) == true);
test(q.size() == 1);
test(q.push(2) == true);
test(q.size() == 2);
test(q.push(3) == true);
test(q.size() == 3);
test(q.push(4) == true);
test(q.size() == 4);
int x;
test(q.pop(x) == true);
test(x == 1);
test(q.size() == 3);
test(q.push(5) == true);
test(q.size() == 4);
test(q.pop(x) == true);
test(x == 2);
test(q.size() == 3);
test(q.push(6) == true);
test(q.size() == 4);
test(q.pop(x) == true);
test(x == 3);
test(q.size() == 3);
test(q.push(7) == true);
test(q.size() == 4);
test(q.pop(x) == true);
test(x == 4);
test(q.size() == 3);
test(q.push(8) == true);
test(q.size() == 4);
test(q.pop(x) == true);
test(x == 5);
test(q.size() == 3);
test(q.push(9) == true);
test(q.size() == 4);
test(q.pop(x) == true);
test(x == 6);
test(q.size() == 3);
test(q.push(10) == true);
test(q.size() == 4);
test(q.pop(x) == true);
test(x == 7);
test(q.size() == 3);
test(q.pop(x) == true);
test(x == 8);
test(q.size() == 2);
test(q.pop(x) == true);
test(x == 9);
test(q.size() == 1);
test(q.pop(x) == true);
test(x == 10);
test(q.size() == 0);
test(q.pop(x) == false);
}
std::cout << "Passed\n";
}
+10
View File
@@ -0,0 +1,10 @@
#include <cstdlib>
#include <iostream>
void test_(bool expr, const char *text, const char *file, int line)
{
if (!expr) {
std::cout << "Failure in " << file << ":" << line << " [" << text << "]\n";
abort();
}
}
+3
View File
@@ -0,0 +1,3 @@
void test_(bool expr, const char *text, const char *file, int line);
#define test(expr) test_(expr, #expr, __FILE__, __LINE__)