first commit
This commit is contained in:
@@ -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
|
||||||
@@ -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()
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
worker_processes 1;
|
||||||
|
|
||||||
|
http {
|
||||||
|
server {
|
||||||
|
listen 8000;
|
||||||
|
location / {
|
||||||
|
add_header Content-Type text/plain;
|
||||||
|
return 200 'Hello, World!';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+286
@@ -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);
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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
@@ -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;
|
||||||
|
}
|
||||||
@@ -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
@@ -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);
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -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++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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));
|
||||||
|
}
|
||||||
@@ -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
@@ -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 "???";
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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
@@ -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, ¬_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
|
||||||
@@ -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.
|
||||||
|
}
|
||||||
@@ -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.
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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";
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
|
||||||
|
void test_(bool expr, const char *text, const char *file, int line);
|
||||||
|
#define test(expr) test_(expr, #expr, __FILE__, __LINE__)
|
||||||
Reference in New Issue
Block a user