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