diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..322bbf9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*.o +*.a +example_simple +connection_afl diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..75630ec --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "firebuf"] + path = firebuf + url = git@github.com:firestuff/firebuf.git diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..9727f30 --- /dev/null +++ b/Makefile @@ -0,0 +1,37 @@ +FIRE_CXX ?= clang++ +FIRE_CXXFLAGS ?= -O3 -std=gnu++2a -Wall -Werror +FIRE_LDLIBS ?= -lgflags -lglog -lpthread + +all: firecgi.a example_simple + +objects = firecgi.o connection.o request.o parse.o + +firecgi.a: $(objects) + $(MAKE) --directory=firebuf + ar rcs $@ $^ + +example_simple: example_simple.o $(objects) + $(FIRE_CXX) $(FIRE_CXXFLAGS) -o $@ $+ firebuf/firebuf.a $(FIRE_LDLIBS) + +%.o: %.cc *.h Makefile + $(FIRE_CXX) $(FIRE_CXXFLAGS) -c -o $@ $< + +clean: + $(MAKE) --directory=firebuf clean + rm --force example_simple connection_afl *.o + +afl: + $(MAKE) clean + FIRE_CXX=afl-g++ $(MAKE) afl_int + +afl_int: connection_afl + +connection_afl: connection_afl.o $(objects) + $(FIRE_CXX) $(FIRE_CXXFLAGS) -o $@ $+ $(FIRE_LDLIBS) + +test: test_connection + +test_connection: connection_afl_afl + @echo "Running $$(ls afl_state/testcases | wc -l) tests" + for FILE in afl_state/testcases/*; do ./connection_afl < $$FILE; done + @printf '\033[0;32mALL TESTS PASSED\033[0m\n' diff --git a/connection.cc b/connection.cc index d77418b..034ee03 100644 --- a/connection.cc +++ b/connection.cc @@ -13,7 +13,7 @@ Connection::Connection(int sock, const sockaddr_in6& client_addr, const std::fun : sock_(sock), callback_(callback), headers_(headers), - buf_(sock, fastcgi_max_record_len) { + buf_(sock, max_record_len) { char client_addr_str[INET6_ADDRSTRLEN]; PCHECK(inet_ntop(AF_INET6, &client_addr.sin6_addr, client_addr_str, sizeof(client_addr_str))); @@ -81,7 +81,7 @@ int Connection::Read() { return sock_; } - ConstBuffer param_buf(buf_.Read(header->ContentLength()), header->ContentLength()); + firebuf::ConstBuffer param_buf(buf_.Read(header->ContentLength()), header->ContentLength()); while (param_buf.ReadMaxLen() > 0) { const auto *param_header = param_buf.ReadObj(); if (!param_header) { diff --git a/connection.h b/connection.h index 22ac583..f9cc8df 100644 --- a/connection.h +++ b/connection.h @@ -5,8 +5,9 @@ #include #include +#include "firebuf/stream_buffer.h" + #include "request.h" -#include "stream_buffer.h" namespace firecgi { @@ -25,7 +26,7 @@ class Connection { uint64_t requests_ = 0; - StreamBuffer buf_; + firebuf::StreamBuffer buf_; std::unique_ptr request_; }; diff --git a/example_simple.cc b/example_simple.cc new file mode 100644 index 0000000..b008397 --- /dev/null +++ b/example_simple.cc @@ -0,0 +1,19 @@ +#include +#include + +#include "firecgi.h" + +DEFINE_int32(port, 9000, "TCP port to bind"); +DEFINE_int32(threads, 1, "Number of server threads"); + +int main(int argc, char *argv[]) { + google::InitGoogleLogging(argv[0]); + gflags::ParseCommandLineFlags(&argc, &argv, true); + + firecgi::Server server(FLAGS_port, [](std::unique_ptr request) { + request->WriteHeader("Content-Type", "text/plain"); + request->WriteBody("Hello world"); + request->End(); + }, FLAGS_threads); + server.Serve(); +} diff --git a/firebuf b/firebuf new file mode 160000 index 0000000..dd9a0a9 --- /dev/null +++ b/firebuf @@ -0,0 +1 @@ +Subproject commit dd9a0a974fdbff71185094ef93890dde5427adcc diff --git a/request.cc b/request.cc new file mode 100644 index 0000000..b196664 --- /dev/null +++ b/request.cc @@ -0,0 +1,110 @@ +#include + +#include "request.h" + +#include "connection.h" + +namespace firecgi { +namespace { + +template void AppendVec(const T& obj, std::vector* vec) { + vec->push_back(iovec{ + .iov_base = (void*)(&obj), + .iov_len = sizeof(obj), + }); +} + +} // namespace + +Request::Request(uint16_t request_id, Connection* conn) + : request_id_(request_id), + conn_(conn), + out_buf_(max_record_len) {} + +uint16_t Request::RequestId() { + return request_id_; +} + +void Request::AddParam(const std::string_view& key, const std::string_view& value) { + params_.try_emplace(std::string(key), std::string(value)); +} + +void Request::AddIn(const std::string_view& in) { + in_.append(in); +} + +const std::string& Request::GetParam(const std::string& key) { + auto iter = params_.find(key); + if (iter == params_.end()) { + static const std::string none; + return none; + } + return iter->second; +} + +void Request::WriteHeader(const std::string_view& name, const std::string_view& value) { + CHECK(!body_written_); + CHECK(out_buf_.Write(name)); + CHECK(out_buf_.Write(": ")); + CHECK(out_buf_.Write(value)); + CHECK(out_buf_.Write("\n")); +} + +void Request::WriteBody(const std::string_view& body) { + if (!body_written_) { + CHECK(out_buf_.Write("\n")); + body_written_ = true; + } + // TODO: make this able to span multiple packets + CHECK(out_buf_.Write(body)); +} + +bool Request::Flush() { + std::vector vecs; + + auto header = OutputHeader(); + AppendVec(header, &vecs); + + vecs.push_back(OutputVec()); + + if (!conn_->Write(vecs)) { + return false; + } + out_buf_.Commit(); + return true; +} + +bool Request::End() { + // Fully empty response not allowed + WriteBody(""); + + std::vector vecs; + + // Must be outside if block, so it lives through Write() below + auto output_header = OutputHeader(); + if (output_header.ContentLength()) { + AppendVec(output_header, &vecs); + vecs.push_back(OutputVec()); + } + + EndRequest end; + Header end_header(3, request_id_, sizeof(end)); + AppendVec(end_header, &vecs); + AppendVec(end, &vecs); + + return conn_->Write(vecs); +} + +iovec Request::OutputVec() { + const auto output_len = out_buf_.ReadMaxLen(); + return iovec{ + .iov_base = (void *)(CHECK_NOTNULL(out_buf_.Read(output_len))), + .iov_len = output_len, + }; +} + +Header Request::OutputHeader() { + return Header(6, request_id_, out_buf_.ReadMaxLen()); +} + +} // namespace firecgi diff --git a/request.h b/request.h new file mode 100644 index 0000000..45ee8ed --- /dev/null +++ b/request.h @@ -0,0 +1,43 @@ +#pragma once + +#include + +#include "firebuf/buffer.h" + +#include "parse.h" + +namespace firecgi { + +class Connection; + +class Request { + public: + Request(uint16_t request_id, Connection *conn); + + uint16_t RequestId(); + + void AddParam(const std::string_view& key, const std::string_view& value); + void AddIn(const std::string_view& in); + + const std::string& GetParam(const std::string& key); + + void WriteHeader(const std::string_view& name, const std::string_view& value); + void WriteBody(const std::string_view& body); + [[nodiscard]] bool Flush(); + bool End(); + + private: + Header OutputHeader(); + iovec OutputVec(); + + const uint16_t request_id_; + Connection *conn_; + + std::unordered_map params_; + std::string in_; + + firebuf::Buffer out_buf_; + bool body_written_ = false; +}; + +} // namespace firecgi