diff --git a/Makefile.am b/Makefile.am index a61df05..bf0ad8f 100644 --- a/Makefile.am +++ b/Makefile.am @@ -37,31 +37,11 @@ bin_PROGRAMS = paxos CONFIG = \ src/config/config.hh \ src/config/config.cc \ - src/config/proxy.hh \ - src/config/proxy.cc \ src/config/parse.hh \ src/config/parse.hxx \ - src/config/parse.cc \ - src/config/timeout.hh \ - src/config/timeout.cc \ - src/config/upstream.hh \ - src/config/upstream.cc - -VHOST = \ - src/vhost/vhost-factory.cc \ - src/vhost/vhost-static-file.cc \ - src/vhost/vhost-reverse-proxy.cc \ - src/vhost/connection.hh \ - src/vhost/connection.cc \ - src/vhost/apm.hh \ - src/vhost/apm.cc \ - src/vhost/vhost.cc + src/config/parse.cc MISC = \ - src/misc/buffer.hh \ - src/misc/buffer.cc \ - src/misc/option.hh \ - src/misc/option.cc \ src/misc/addrinfo/addrinfo-error.cc \ src/misc/addrinfo/addrinfo-error.hh \ src/misc/addrinfo/addrinfo-iterator.hh \ @@ -70,17 +50,9 @@ MISC = \ src/misc/fd.cc \ src/misc/fd.hh \ src/misc/json.hh \ - src/misc/openssl/base64.cc \ - src/misc/openssl/base64.hh \ - src/misc/openssl/ssl-error.cc \ - src/misc/openssl/ssl-error.hh \ - src/misc/openssl/ssl-wrapper.hh \ - src/misc/openssl/ssl.hh \ src/misc/socket.hh \ src/misc/sys-wrapper.hh \ src/misc/unistd.hh \ - src/misc/readiness/readiness.cc \ - src/misc/readiness/readiness.hh \ src/misc/logger.cc \ src/misc/logger.hh @@ -109,76 +81,22 @@ EVENTS = \ src/events/reverse-proxy.cc \ src/events/reverse-proxy.hh -ERROR = \ - src/error/init-error.hh \ - src/error/not-implemented.hh \ - src/error/parsing-error.hh \ - src/error/connection-closed.hh \ - src/error/connection-failed.hh \ - src/error/backend-unavailable.hh - SOCKET = \ src/socket/default-socket.cc \ src/socket/default-socket.hh \ - src/socket/socket.hh \ - src/socket/ssl-socket.cc \ - src/socket/ssl-socket.hh - -TIMER = \ - src/timer/timer.cc \ - src/timer/timer.hh \ - src/timer/queue.cc \ - src/timer/queue.hh \ - src/timer/throughput-queue.cc \ - src/timer/throughput-queue.hh \ - src/timer/proxy-queue.cc \ - src/timer/procy-queue.hh \ - src/timer/queue-manager.cc \ - src/timer/queue-manager.hh - -SCHEDULER = \ - src/scheduler/scheduler.cc \ - src/scheduler/scheduler.hh \ - src/scheduler/round-robin.cc \ - src/scheduler/round-robin.hh \ - src/scheduler/failover.cc \ - src/scheduler/failover.hh \ - src/scheduler/fail-robin.cc \ - src/scheduler/fail-robin.hh \ - src/scheduler/send-health.cc \ - src/scheduler/send-health.hh \ - src/scheduler/recv-health.cc \ - src/scheduler/recv-health.hh + src/socket/socket.hh SOURCES = \ - src/request/error.cc \ - src/request/error.hh \ - src/request/request.cc \ - src/request/request.hh \ - src/request/message.hh \ - src/request/message.cc \ - src/request/response.hh \ - src/request/response.cc \ - src/request/types.hh \ - src/vhost/dispatcher.cc \ - src/vhost/dispatcher.hh \ - src/vhost/vhost-factory.hh \ - src/vhost/vhost-reverse-proxy.hh \ - src/vhost/vhost-static-file.hh \ - src/vhost/vhost.hh \ $(CONFIG) \ - $(VHOST) \ $(MISC) \ - $(EVENTS) \ - $(ERROR) \ $(SOCKET) \ - $(TIMER) \ $(SCHEDULER) \ $(NULL) # Compile source files again to avoid silenting undefined references. paxos_SOURCES = \ + $(SOURCES) \ src/main.cc \ $(NULL) diff --git a/config-files/one-legislator.json b/config-files/one-legislator.json new file mode 100644 index 0000000..85ac252 --- /dev/null +++ b/config-files/one-legislator.json @@ -0,0 +1,10 @@ +{ + "legislators" : [ + { + "ip" : "127.0.0.1", + "port" : 8000, + "name" : "test", + "self" : true + } + ] +} diff --git a/src/config/config.cc b/src/config/config.cc new file mode 100644 index 0000000..7ca7cb4 --- /dev/null +++ b/src/config/config.cc @@ -0,0 +1,112 @@ +#include +#include +#include + +#include "config.hh" +#include "parse.hh" + +namespace paxos +{ + + ServerConfig::ServerConfig(const std::vector& legislators) + : legislators_(legislators) + { + } + + static std::string format_ip(std::string ip) + { + int family = 0; + + /* Check if it is an IPv4 or Ipv6 address */ + if (ip.find(":") != std::string::npos) + family = AF_INET6; + else + family = AF_INET; + + /* Allocated static buffer to make conversion */ + unsigned char buf[sizeof(struct in6_addr)]; + char formatted_ip[INET6_ADDRSTRLEN]; + + /* Convert ip string to network address structure */ + int success = inet_pton(family, ip.c_str(), buf); + + if (success <= 0) + error_and_exit(1, "parse_configuration: " + ip + " is an invalid ip"); + + /* Convert back the network address to an string */ + inet_ntop(family, buf, formatted_ip, INET6_ADDRSTRLEN); + return formatted_ip; + } + + + static void parse_mandatory_keys(const json& j, LegislatorConfig& config) + { + int port_int; + + safe_get_key(j, "ip", config.ip, true); + config.ip = format_ip(config.ip); + + safe_get_key(j, "port", port_int, true); + config.port = std::to_string(port_int); + + safe_get_key(j, "name", config.name, true); + + safe_get_key(j, "self", config.is_self, true); + } + + + static void from_json(const json& j, LegislatorConfig& config) + { + parse_mandatory_keys(j, config); + } + + + static std::vector parse_legislators(const json& j) + { + /* Get 'vhosts' value */ + std::vector legislators; + safe_get_key(j, "legislators", legislators, true); + + /* Create VHostConfig vector and fill with VHostConfig */ + std::vector legislator_configs; + + for (auto it : legislators) + { + /* Differenciable vhost checking */ + auto legislator = it.get(); + + legislator_configs.push_back(legislator); + } + + return legislator_configs; + } + + + ServerConfig ServerConfig::parse(const std::string& path) + { + json json_dict; + std::ifstream json_file(path); + + /* Check if file is corretly opened */ + if (!json_file.is_open()) + { + error_and_exit(1, "parse_configuration: file " + path + + " doesn't exist."); + } + + + /* Check if file respect the json syntax */ + try + { + json_file >> json_dict; + } + catch (const nlohmann::detail::parse_error& e) + { + error_and_exit(1, e.what()); + } + + std::vector legislators = parse_legislators(json_dict); + + return ServerConfig(legislators); + } +} diff --git a/src/config/config.hh b/src/config/config.hh new file mode 100644 index 0000000..a630744 --- /dev/null +++ b/src/config/config.hh @@ -0,0 +1,25 @@ +#pragma once + +#include +#include +#include "misc/json.hh" + +namespace paxos +{ + struct LegislatorConfig + { + std::string ip; + std::string port; + std::string name; + bool is_self; + }; + + struct ServerConfig + { + ServerConfig(const std::vector& legislators); + + std::vector legislators_; + + static ServerConfig parse(const std::string& path); + }; +} diff --git a/src/config/parse.cc b/src/config/parse.cc new file mode 100644 index 0000000..f175dff --- /dev/null +++ b/src/config/parse.cc @@ -0,0 +1,12 @@ +#include "parse.hh" + +namespace paxos +{ + void error_and_exit(int code, const std::string& error) + { + /* Display 'error' on stderr and exit with 'code' */ + std::cerr << error << "\n"; + exit(code); + } + +} // namespace http diff --git a/src/config/parse.hh b/src/config/parse.hh new file mode 100644 index 0000000..7a99dc7 --- /dev/null +++ b/src/config/parse.hh @@ -0,0 +1,12 @@ +#pragma once + +#include +#include "misc/json.hh" + +namespace paxos +{ + [[noreturn]] void error_and_exit(int code, const std::string& error); + +} // namespace http + +#include "parse.hxx" diff --git a/src/config/parse.hxx b/src/config/parse.hxx new file mode 100644 index 0000000..c41117c --- /dev/null +++ b/src/config/parse.hxx @@ -0,0 +1,32 @@ +#pragma once + +#include +#include "parse.hh" + +namespace paxos +{ + template + bool safe_get_key(const json& json_obj, const std::string& key, + ValueType& value, bool mandatory = false) + { + try + { + json_obj.at(key).get_to(value); + } + catch (json::out_of_range& e) + { + if (mandatory) + error_and_exit(1, "parse_configuration: mandatory key '" + + key + "' not found."); + return 0; + } + catch (const std::exception& e) + { + std::cout << e.what() << " in " << key << "\n"; + error_and_exit(1, "parse_configuration: key '" + key + + "' is associated with a value of bad type"); + } + return 1; + } + +} // namespace http diff --git a/src/error/connection-closed.hh b/src/error/connection-closed.hh new file mode 100644 index 0000000..a8e7759 --- /dev/null +++ b/src/error/connection-closed.hh @@ -0,0 +1,15 @@ +#pragma once + +#include +#include +#include + +namespace http +{ + struct ConnectionRecvClosedError: public std::system_error + { + explicit ConnectionRecvClosedError() + : std::system_error{errno, std::system_category()} + {} + }; +} //namespace http diff --git a/src/error/connection-failed.hh b/src/error/connection-failed.hh new file mode 100644 index 0000000..fb96959 --- /dev/null +++ b/src/error/connection-failed.hh @@ -0,0 +1,15 @@ +#pragma once + +#include +#include +#include + +namespace http +{ + struct ConnectionFailed : public std::system_error + { + explicit ConnectionFailed() + : std::system_error{errno, std::system_category()} + {} + }; +} //namespace http diff --git a/src/error/init-error.hh b/src/error/init-error.hh new file mode 100644 index 0000000..d2f66a3 --- /dev/null +++ b/src/error/init-error.hh @@ -0,0 +1,15 @@ +#pragma once + +#include +#include + +namespace http +{ + struct InitializationError : public std::logic_error + { + explicit InitializationError(const std::string& what_arg) + : std::logic_error{"Initialization error: " + what_arg} + {} + virtual ~InitializationError() = default; + }; +} // namespace http diff --git a/src/error/not-implemented.hh b/src/error/not-implemented.hh new file mode 100644 index 0000000..a985893 --- /dev/null +++ b/src/error/not-implemented.hh @@ -0,0 +1,15 @@ +#pragma once + +#include + +namespace http +{ + class NotImplemented : public std::logic_error + { + public: + NotImplemented() + : std::logic_error{"not implemented exception"} + {} + virtual ~NotImplemented() = default; + }; +} // namespace http diff --git a/src/error/parsing-error.hh b/src/error/parsing-error.hh new file mode 100644 index 0000000..6dc4e75 --- /dev/null +++ b/src/error/parsing-error.hh @@ -0,0 +1,15 @@ +#pragma once + +#include +#include + +namespace http +{ + struct ParsingError : public std::logic_error + { + explicit ParsingError(const std::string& what_arg) + : std::logic_error{"Parsing error: " + what_arg} + {} + virtual ~ParsingError() = default; + }; +} // namespace http diff --git a/src/legislator/legislator.cc b/src/legislator/legislator.cc new file mode 100644 index 0000000..e69de29 diff --git a/src/legislator/legislator.hh b/src/legislator/legislator.hh new file mode 100644 index 0000000..2b29dfd --- /dev/null +++ b/src/legislator/legislator.hh @@ -0,0 +1,12 @@ +#pragma once +#include "legislator-config.hh" + +namespace paxos +{ + class Legislator + { + public: + LegislatorConfig config; + + } +} diff --git a/src/main.cc b/src/main.cc index 3693409..fbeaeab 100644 --- a/src/main.cc +++ b/src/main.cc @@ -1,5 +1,10 @@ -int main(int argc, char **argv) +#include + +#include "config/config.hh" + + +int main(int, char **argv) { - argv = argv; - return argc; + paxos::ServerConfig config = paxos::ServerConfig::parse(argv[1]); + std::cout << config.legislators_[0].name << "\n"; } diff --git a/src/misc/addrinfo/addrinfo-error.cc b/src/misc/addrinfo/addrinfo-error.cc new file mode 100644 index 0000000..3d34aa6 --- /dev/null +++ b/src/misc/addrinfo/addrinfo-error.cc @@ -0,0 +1,15 @@ +#include "addrinfo-error.hh" + +namespace misc +{ + const std::error_category& addrinfo_category() + { + static addrinfo_error_category instance; + return instance; + } +} // namespace misc + +inline std::error_code std::make_error_code(misc::addrinfo_error e) +{ + return std::error_code(static_cast(e), misc::addrinfo_category()); +} diff --git a/src/misc/addrinfo/addrinfo-error.hh b/src/misc/addrinfo/addrinfo-error.hh new file mode 100644 index 0000000..0377f97 --- /dev/null +++ b/src/misc/addrinfo/addrinfo-error.hh @@ -0,0 +1,82 @@ +/** + * \file misc/addrinfo/addrinfo-error.hh + * \brief Implementation of an extention to system_errors for addrinfo. + */ +#pragma once + +#include +#include +#include + +namespace misc +{ + /** + * \enum addrinfo_error + * \brief Possible errors returned by getaddrinfo. + */ + enum addrinfo_error + { + BADFLAGS = EAI_BADFLAGS, + NONAME = EAI_NONAME, + AGAIN = EAI_AGAIN, + FAIL = EAI_FAIL, + FAMILY = EAI_FAMILY, + SOCKTYPE = EAI_SOCKTYPE, + SERVICE = EAI_SERVICE, + MEMORY = EAI_MEMORY, + SYSTEM = EAI_SYSTEM, + OVERFLOW = EAI_OVERFLOW, + NODATA = EAI_NODATA, + ADDRFAMILY = EAI_ADDRFAMILY, + INPROGRESS = EAI_INPROGRESS, + CANCELED = EAI_CANCELED, + NOTCANCELED = EAI_NOTCANCELED, + ALLDONE = EAI_ALLDONE, + INTR = EAI_INTR, + IDN_ENCODE = EAI_IDN_ENCODE, + }; + + /** + * \class addrinfo_error_category + * \brief Error category signifying the system error coming from + * getaddrinfo. + */ + class addrinfo_error_category : public std::error_category + { + public: + /** + * \brief Name of the category. + */ + virtual const char* name() const noexcept override + { + return "addrinfo_error_category"; + } + /** + * \brief Error message. + */ + virtual std::string message(int value) const override + { + return gai_strerror(value); + } + }; + + /** + * \brief Singleton instance of the new error_category. + */ + const std::error_category& addrinfo_category(); +} // namespace misc + +namespace std +{ + /** + * \brief Setup addrinfo_error to be used as error_codes. + */ + template <> + struct is_error_code_enum : public std::true_type + {}; + + /** + * \brief Translation between addrinfo_errors and error_codes. + */ + inline std::error_code make_error_code(misc::addrinfo_error e); +} // namespace std diff --git a/src/misc/addrinfo/addrinfo-iterator.hh b/src/misc/addrinfo/addrinfo-iterator.hh new file mode 100644 index 0000000..3564028 --- /dev/null +++ b/src/misc/addrinfo/addrinfo-iterator.hh @@ -0,0 +1,102 @@ +/** + * /file misc/addrinfo/addrinfo-iterator.hh + * /brief Iterator over AddrInfo declaration. + */ +#pragma once + +#include +#include + +namespace misc +{ + /** + * \class addrinfo_iterator + * \brief Iterator over AddrInfo objects. + */ + template + class addrinfo_iter + : public boost::iterator_facade, Value, + boost::forward_traversal_tag> + { + public: + /** + * \brief Default constructor (end iterator). + */ + addrinfo_iter() + : m_node(nullptr) + {} + + /** + * \brief Constructor from a given AddrInfo. + * \param p Value* the AddrInfo + */ + explicit addrinfo_iter(Value* p) + : m_node(p) + {} + + /** + * \brief Begin of the iterator range. + */ + addrinfo_iter begin() + { + return *this; + } + + /** + * \brief End of the iterator range. + */ + addrinfo_iter end() + { + return addrinfo_iter{}; + } + + private: + friend class boost::iterator_core_access; + + /** + * \brief Advance the iterator by one step. + */ + void increment() + { + m_node = m_node->ai_next; + } + + /** + * \brief Check equality between iterators. + */ + bool equal(const addrinfo_iter& other) const + { + return this->m_node == other.m_node; + } + + /** + * \brief Access to the value through dereferenciation + */ + Value& dereference() const + { + return *m_node; + } + + /** + * \brief Begin of a given iterator. + */ + Value& begin(const addrinfo_iter& n) + { + return n->m_node; + } + + /** + * \brief Current value of the iterator. + */ + Value* m_node; + }; + + /** + * \brief Alias to AddrInfo iterators type. + */ + using addrinfo_iterator = addrinfo_iter; + /** + * \brief Alias to const AddrInfo iterators type. + */ + using addrinfo_const_iterator = addrinfo_iter; +} // namespace misc diff --git a/src/misc/addrinfo/addrinfo.cc b/src/misc/addrinfo/addrinfo.cc new file mode 100644 index 0000000..59e4745 --- /dev/null +++ b/src/misc/addrinfo/addrinfo.cc @@ -0,0 +1,24 @@ +#include "addrinfo.hh" + +namespace misc +{ + AddrInfo getaddrinfo(const char* node, const char* service, + const AddrInfoHint& hints) + { + struct addrinfo* res; + + auto rc = ::getaddrinfo(node, service, &hints, &res); + if (rc) + throw std::system_error(rc, addrinfo_error_category(), + "getaddrinfo"); + + return AddrInfo(res); + } +} // namespace misc + +std::ostream& operator<<(std::ostream& os, const addrinfo&) +{ + os << "addrinfo{" + << "}"; + return os; +} diff --git a/src/misc/addrinfo/addrinfo.hh b/src/misc/addrinfo/addrinfo.hh new file mode 100644 index 0000000..56df1ce --- /dev/null +++ b/src/misc/addrinfo/addrinfo.hh @@ -0,0 +1,185 @@ +/** + * \file misc/addrinfo/addrinfo.hh + * \brief Structures used to wrap getaddrinfo(3). + */ +#pragma once + +#include +#include +#include +#include +#include + +#include "addrinfo-error.hh" +#include "addrinfo-iterator.hh" + +namespace misc +{ + /** + * \class AddrInfo + * \brief Object representing list of network addresses (IP, Port, Host). + */ + class AddrInfo + { + public: + /** + * \brief Constructor from an addrinfo struct. + */ + AddrInfo(::addrinfo* info) + : info_{info} + {} + + ~AddrInfo() + { + freeaddrinfo(info_); + } + + AddrInfo(const AddrInfo&) = delete; + AddrInfo& operator=(const AddrInfo&) = delete; + AddrInfo(AddrInfo&& addr) + : info_{std::exchange(addr.info_, nullptr)} + {} + AddrInfo& operator=(AddrInfo&& addr) + { + freeaddrinfo(info_); + info_ = std::exchange(addr.info_, nullptr); + return *this; + } + + /** + * \brief Begin of the list of addresses. + */ + addrinfo_iterator begin() + { + return addrinfo_iterator(info_); + } + + /** + * \brief Begin of the const list of addresses. + */ + addrinfo_const_iterator begin() const + { + return addrinfo_const_iterator(info_); + } + + /** + * \brief End of the list of addresses. + */ + addrinfo_iterator end() + { + return addrinfo_iterator(); + } + + /** + * \brief End of the const list of addresses. + */ + addrinfo_const_iterator end() const + { + return addrinfo_const_iterator(); + } + + private: + /** + * \brief Addrinfo structure held by the object. + */ + ::addrinfo* info_; + }; + + /** + * \class AddrInfoHint + * \brief Builder class used to specify requirements about addresses + * returned by getaddrinfo(3). + */ + struct AddrInfoHint : public ::addrinfo + { + AddrInfoHint() + { + memset(this, 0, sizeof(::addrinfo)); + } + + /** + * \brief Specify the flag member of the structure. + */ + AddrInfoHint& flags(int val) + { + this->ai_flags = val; + return *this; + } + + /** + * \brief Specify the family member of the structure. + */ + AddrInfoHint& family(int val) + { + this->ai_family = val; + return *this; + } + + /** + * \brief Specify the socktype member of the structure. + */ + AddrInfoHint& socktype(int val) + { + this->ai_socktype = val; + return *this; + } + + /** + * \brief Specify the protocol member of the structure. + */ + AddrInfoHint& protocol(int val) + { + this->ai_protocol = val; + return *this; + } + + /** + * \brief Specify the member of the structure. + */ + AddrInfoHint& addrlen(socklen_t val) + { + this->ai_addrlen = val; + return *this; + } + + /** + * \brief Specify the addr member of the structure. + */ + AddrInfoHint& addr(::sockaddr* val) + { + this->ai_addr = val; + return *this; + } + + /** + * \brief Specify the canonname member of the structure. + */ + AddrInfoHint& canonname(char* val) + { + this->ai_canonname = val; + return *this; + } + + /** + * \brief Specify the member next of the structure. + */ + AddrInfoHint& next(::addrinfo* val) + { + this->ai_next = val; + return *this; + } + }; + + /** + * \brief Wrapper around getaddrinfo(3). + */ + AddrInfo getaddrinfo(const char* node, const char* service, + const AddrInfoHint& hints); +} // namespace misc + +/** + * \brief Outputs the addrinfo to a stream. + * + * Only used for debug purposes. + */ +std::ostream& operator<<(std::ostream&, const ::addrinfo&); diff --git a/src/misc/buffer.cc b/src/misc/buffer.cc new file mode 100644 index 0000000..7fc86f7 --- /dev/null +++ b/src/misc/buffer.cc @@ -0,0 +1,113 @@ +#include +#include + +#include "error/connection-closed.hh" +#include "misc/buffer.hh" + +namespace misc +{ + constexpr size_t CRLF_LENGTH = 2; + + constexpr size_t BUFF_SIZE = 8192; + + Buffer::Buffer() + { + data_ = std::deque(1); + } + + void Buffer::append(const std::string& tab) + { + size_t crlf_pos = tab.find("\r\n"); + data_.back() += tab.substr(0, crlf_pos); + size_t line_begin = crlf_pos + CRLF_LENGTH; + + while (crlf_pos != tab.npos && line_begin < tab.size()) + { + data_.back() += "\r\n"; + + crlf_pos = tab.find("\r\n", line_begin); + + data_.emplace_back(tab, line_begin, crlf_pos - line_begin); + + line_begin = crlf_pos + CRLF_LENGTH; + } + + if (crlf_pos != tab.npos && line_begin == tab.size()) + { + data_.back() += "\r\n"; + data_.emplace_back(); + } + } + + size_t Buffer::receive(http::Socket& socket) + { + char buf[BUFF_SIZE]; + ssize_t n = socket.recv(buf, BUFF_SIZE); + if (n == 0) + throw http::ConnectionRecvClosedError(); + if (n == -1) + return 0; + + auto str = std::string(buf, n); + + this->append(str); + return n; + } + + std::string Buffer::popline() + { + if (this->empty()) + throw std::logic_error("Buffer::popline : pop in a empty Buffer"); + + std::string toReturn = data_.front(); + data_.pop_front(); + if (data_.size() == 0) + data_.emplace_back(); + + return toReturn; + } + + void Buffer::pop_front(size_t content_length) + { + while (data_.front().size() < content_length) + { + content_length -= data_.front().size(); + data_.pop_front(); + } + data_.front() = data_.front().substr(content_length); + if (data_.front().empty() && data_.size() > 1) + data_.pop_front(); + } + + bool Buffer::empty() const + { + return this->get_nb_lines() == 0 && data_.back().empty(); + } + + const std::string& Buffer::getline(size_t line) const + { + try + { + return data_[line]; + } + catch (const std::out_of_range& e) + { + std::cerr << "[Buffer::getline(size_t line)] " + << line << "out of " << data_.size(); + exit(1); + } + } + + size_t Buffer::get_nb_lines() const + { + return data_.size() - 1; + } + + std::string Buffer::to_string() const + { + std::string str; + for (auto line : data_) + str += line; + return str; + } +} diff --git a/src/misc/buffer.hh b/src/misc/buffer.hh new file mode 100644 index 0000000..fe5883c --- /dev/null +++ b/src/misc/buffer.hh @@ -0,0 +1,49 @@ +#pragma once + +#include +#include + +#include "socket/default-socket.hh" + +namespace misc +{ + class Buffer + { + public: + Buffer(); + + ~Buffer() = default; + Buffer(const Buffer&) = default; + Buffer& operator=(const Buffer&) = default; + + /** + ** \brief Append parsed tab on CRLF characters in data_. + ** \param tab The buffer received from Socket::recv() to parse. + */ + void append(const std::string& tab); + + /** + ** \brief Call the socket recv method and append to the buffer + ** \param socket The default socket + */ + size_t receive(http::Socket& socket); + + /** + ** \brief Pop the string in front of data_ and returns it. + */ + std::string popline(); + + void pop_front(size_t content_length); + + bool empty() const; + + const std::string& getline(size_t line) const; + + size_t get_nb_lines() const; + + std::string to_string() const; + + private: + std::deque data_; + }; +} diff --git a/src/misc/fd.cc b/src/misc/fd.cc new file mode 100644 index 0000000..f2174f8 --- /dev/null +++ b/src/misc/fd.cc @@ -0,0 +1,42 @@ +#include "misc/fd.hh" + +#include "misc/socket.hh" +#include "misc/unistd.hh" + +#include + +namespace misc +{ + FileDescriptor& FileDescriptor::operator=(FileDescriptor&& fileDescriptor) + { + if (fd_ != -1) + sys::close(fd_); + fd_ = std::exchange(fileDescriptor.fd_, -1); + return *this; + } + + FileDescriptor::~FileDescriptor() + { + if (*this) + { + sys::close(fd_); + std::cerr << "Closed " << fd_ << "\n"; + } + } + + FileDescriptor::operator int() const& + { + return fd_; + } + + FileDescriptor::operator bool() const& + { + return fd_ >= 0; + } + + void FileDescriptor::fcntl_set(int flags) + { + sys::fcntl_set(fd_, flags); + } + +} // namespace misc diff --git a/src/misc/fd.hh b/src/misc/fd.hh new file mode 100644 index 0000000..e80c3cb --- /dev/null +++ b/src/misc/fd.hh @@ -0,0 +1,70 @@ +/** + * \file misc/fd.hh + * \brief FileDescriptor declaration. + */ +#pragma once + +#include +#include +#include +#include + +#include "misc/sys-wrapper.hh" + +namespace misc +{ + /** + * \class FileDescriptor + * \brief A resource object representing a fd. + */ + struct FileDescriptor + { + /** + * \brief Construct a FileDescriptor from a given fd. + * \param fd int + */ + + explicit FileDescriptor(int fd) + : fd_{fd} + {} + + FileDescriptor() = default; + FileDescriptor(const FileDescriptor&) = delete; + FileDescriptor& operator=(const FileDescriptor&) = delete; + + FileDescriptor(FileDescriptor&& fileDescriptor) + : fd_{std::exchange(fileDescriptor.fd_, -1)} + {} + FileDescriptor& operator=(FileDescriptor&& fileDescriptor); + + ~FileDescriptor(); + + /** + * \brief Implicit conversion to int. + */ + operator int() const&; + + /** + * \brief Implicit conversion to bool. + * + * \return Whether or not the FileDescriptor holds a valid fd. + */ + operator bool() const&; + + /** + * \brief man 2 fcntl + */ + void fcntl_set(int flags); + + /** + * \brief The fd held by the FileDescriptor object. + */ + int fd_ = -1; + }; + + /** + * \brief Alias to the smart pointer used to manage FileDescriptors + * memory allocation. + */ + using shared_fd = std::shared_ptr; +} // namespace misc diff --git a/src/misc/json.hh b/src/misc/json.hh new file mode 100644 index 0000000..84b6d8b --- /dev/null +++ b/src/misc/json.hh @@ -0,0 +1,13 @@ +#pragma once + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wswitch-enum" +#pragma GCC diagnostic ignored "-Wswitch-default" +#pragma GCC diagnostic ignored "-Winline" +#ifdef __clang__ +#pragma GCC diagnostic ignored "-Wlogical-op-parentheses" +#endif +#include +#pragma GCC diagnostic pop + +using json = nlohmann::json; diff --git a/src/misc/logger.cc b/src/misc/logger.cc new file mode 100644 index 0000000..b2b0f77 --- /dev/null +++ b/src/misc/logger.cc @@ -0,0 +1,11 @@ +#include "logger.hh" + +#include + +namespace http +{ + void log(std::string str, Color color) + { + std::cout << "\033[1;" + std::to_string(color) + "m" + str + "\033[0m\n"; + } +} // namespace http diff --git a/src/misc/logger.hh b/src/misc/logger.hh new file mode 100644 index 0000000..f9f2b04 --- /dev/null +++ b/src/misc/logger.hh @@ -0,0 +1,20 @@ +#pragma once + +#include + +namespace http +{ + enum Color + { + black = 30, + red = 31, + green = 32, + yellow = 33, + blue = 34, + magenta = 35, + cyan = 36, + white = 37 + }; + + void log(std::string str, Color color); +} // namespace http diff --git a/src/misc/socket.hh b/src/misc/socket.hh new file mode 100644 index 0000000..61926bb --- /dev/null +++ b/src/misc/socket.hh @@ -0,0 +1,100 @@ +/** + * \file misc/socket.hh + * \brief Socket related syscalls. + */ + +#pragma once + +#include +#include +#include +#include + +#include "misc/fd.hh" +#include "misc/sys-wrapper.hh" + +namespace sys +{ + /** + * \brief accept(2). + */ + inline auto accept = make_wrapper(::accept); + + /** + * \brief bind(2). + */ + inline auto bind = make_wrapper(::bind); + + /** + * \brief connect(2). + */ + inline auto connect = make_wrapper(::connect); + + static int fcntl_wrapper(int fildes, int opts) + { + auto flags = ::fcntl(fildes, F_GETFL); + return ::fcntl(fildes, F_SETFL, flags | opts); + } + /** + * \brief fcntl(2). + * + * Since fcntl is a variadic syscall we need to call a helper function + * with a fixed number of parameter to create a wrapper out of it. + */ + inline auto fcntl_set = make_wrapper(fcntl_wrapper); + + /** + * \brief getsockname(2). + */ + inline auto getsockname = make_wrapper(::getsockname); + + /** + * \brief listen(2). + */ + inline auto listen = make_wrapper(::listen); + + /** + * \brief lseek(2). + */ + inline auto lseek = make_wrapper(::lseek); + + /** + * \brief read(2). + */ + inline auto read = make_wrapper(::read); + + /** + * \brief recv(2). + */ + inline auto recv = make_wrapper(::recv); + + /** + * \brief send(2). + */ + inline auto send = make_wrapper(::send); + + /** + * \brief sendfile(2). + */ + inline auto sendfile = make_wrapper(::sendfile); + + /** + * \brief setsockopt(2). + */ + inline auto setsockopt = make_wrapper(::setsockopt); + + /** + * \brief getsockopt(2). + */ + inline auto getsockopt = make_wrapper(::getsockopt); + + /** + * \brief socket(2). + */ + inline auto socket = make_wrapper(::socket); + + /** + * \brief getpeername(2). + */ + inline auto getpeername = make_wrapper(::getpeername); +} // namespace sys diff --git a/src/misc/sys-wrapper.hh b/src/misc/sys-wrapper.hh new file mode 100644 index 0000000..4d853d2 --- /dev/null +++ b/src/misc/sys-wrapper.hh @@ -0,0 +1,63 @@ +/** + * \file misc/sys-wrapper.hh + * \brief SysWrapper declaration. + */ + +#pragma once + +#include +#include +#include + +namespace sys +{ + /** + * \class SysWrapper + * \brief Wrapping around syscalls to convert failure into exceptions. + * + * Wrapping syscalls this way helps to insure the RAII idiom. + */ + template + class SysWrapper + { + public: + SysWrapper(SysRetType syscall(Args...)) + : syscall_{syscall} + {} + + /* Needs template to enable universal referencing. + Template deduction should be done automatically. */ + template + RetType operator()(UniversalArgs&&... args) + { + SysRetType ret; + + do + { + errno = 0; + ret = syscall_(std::forward(args)...); + } while (errno == EINTR); + + if (ret == SysRetType(-1) && errno != EWOULDBLOCK + && errno != EAGAIN) + { + throw std::system_error(errno, std::system_category()); + } + + return RetType(ret); + } + + private: + std::function syscall_; + }; + + /** + * \brief Helper function to build SysWrapper. + */ + template + SysWrapper + make_wrapper(SysRetType syscall(Args...)) + { + return SysWrapper(syscall); + } +} // namespace sys diff --git a/src/misc/unistd.hh b/src/misc/unistd.hh new file mode 100644 index 0000000..486746b --- /dev/null +++ b/src/misc/unistd.hh @@ -0,0 +1,55 @@ +/** + * \file misc/unistd.hh + * \brief unistd.h syscall wrappers. + */ + +#pragma once + +#include +#include +#include +#include +#include + +#include "misc/fd.hh" +#include "misc/sys-wrapper.hh" + +namespace sys +{ + /** + * \brief close(2). + */ + inline auto close = make_wrapper(::close); + + /** + * \brief fork(2). + */ + inline auto fork = make_wrapper(::fork); + + /** + * \brief fstat(2). + */ + inline auto fstat = make_wrapper(::fstat); + + /** + * \brief kill(2). + */ + inline auto kill = make_wrapper(::kill); + + static int open_wrapper(const char* path, int flags) + { + return ::open(path, flags); + } + /** + * \brief open(2). + * + * Since open is a variadic syscall we need to call a helper with a + * fixed number of parameters to create a wrapper out of it. + */ + inline auto open = make_wrapper(open_wrapper); + + /** + * \brief wait(2). + */ + inline auto waitpid = make_wrapper(::waitpid); +} // namespace sys diff --git a/src/socket/default-socket.cc b/src/socket/default-socket.cc new file mode 100644 index 0000000..45aea54 --- /dev/null +++ b/src/socket/default-socket.cc @@ -0,0 +1,81 @@ +#include + +#include "socket/default-socket.hh" +#include "misc/socket.hh" +#include "misc/unistd.hh" +#include "error/connection-closed.hh" + +#include + +namespace paxos +{ + DefaultSocket::DefaultSocket(const misc::shared_fd& fd) + : Socket(fd) + {} + + DefaultSocket::DefaultSocket(int domain, int type, int protocol) + : Socket{std::make_shared( + sys::socket(domain, type, protocol))} + { + } + + void DefaultSocket::listen(int backlog) + { + sys::listen(*fd_, backlog); + } + + ssize_t DefaultSocket::recv(void* dst, size_t len) + { + return sys::recv(*fd_, dst, len, MSG_NOSIGNAL); + } + + ssize_t DefaultSocket::send(const void* src, size_t len) + { + return sys::send(*fd_, src, len, MSG_NOSIGNAL); + } + + ssize_t DefaultSocket::sendfile(misc::shared_fd& fd, off_t& offset, size_t len) + { + return sys::sendfile(*fd_, *fd, &offset, len); + } + + void DefaultSocket::bind(const sockaddr* addr, socklen_t addrlen) + { + sys::bind(*fd_, addr, addrlen); + } + + void DefaultSocket::fcntl_set_O_NONBLOCK() + { + fd_->fcntl_set(O_NONBLOCK); + } + + void DefaultSocket::setsockopt(int level, int optname, int optval) + { + sys::setsockopt(*fd_, level, optname, &optval, sizeof(int)); + } + + void DefaultSocket::getsockopt(int level, int optname, int& optval) + { + unsigned int optlen; + sys::getsockopt(*fd_, level, optname, &optval, &optlen); + } + + shared_socket DefaultSocket::accept(sockaddr* addr, socklen_t* addrlen) + { + misc::shared_fd client_fd = std::make_shared(sys::accept(*fd_, addr, addrlen)); + + auto ret = std::make_shared(client_fd); + + //Set the ip and listener_port of the accepted socket + ret->set_listener_port(port_); + ret->set_ip(ip_); + return ret; + } + + void DefaultSocket::connect(const sockaddr* addr, socklen_t len) + { + sys::connect(*fd_, addr, len); + } + + // Implement all other methods (see header). +} // namespace http diff --git a/src/socket/default-socket.hh b/src/socket/default-socket.hh new file mode 100644 index 0000000..46e0f07 --- /dev/null +++ b/src/socket/default-socket.hh @@ -0,0 +1,49 @@ +/** + * \file socket/default_socket.hh + * \brief DefaultSocket declaration. + */ + +#pragma once + +#include "misc/socket.hh" +#include "socket/socket.hh" + +namespace paxos +{ + /** + * \struct DefaultSocket + * \brief Implementation of the Socket interface. + */ + struct DefaultSocket : public Socket + { + explicit DefaultSocket(const misc::shared_fd&); + DefaultSocket(int domain, int type, int protocol); + + DefaultSocket() = default; + DefaultSocket(const DefaultSocket&) = delete; + DefaultSocket& operator=(const DefaultSocket&) = delete; + DefaultSocket(DefaultSocket&&) = default; + DefaultSocket& operator=(DefaultSocket&&) = default; + ~DefaultSocket() = default; + + ssize_t recv(void* dst, size_t len) final; + + ssize_t send(const void* src, size_t len) final; + + ssize_t sendfile(misc::shared_fd& fd, off_t& offset, size_t len) final; + + void bind(const sockaddr* addr, socklen_t addrlen) final; + + void listen(int backlog) final; + + void fcntl_set_O_NONBLOCK() final; + + void setsockopt(int level, int optname, int optval) final; + + void getsockopt(int level, int optname, int& optval) final; + + shared_socket accept(sockaddr* addr, socklen_t* addrlen) final; + + void connect(const sockaddr*, socklen_t) final; + }; +} // namespace http diff --git a/src/socket/socket.hh b/src/socket/socket.hh new file mode 100644 index 0000000..7aac9cf --- /dev/null +++ b/src/socket/socket.hh @@ -0,0 +1,189 @@ +/** + * \file socket/socket.hh + * \brief Socket declaration. + */ + +#pragma once +#include + +#include +#include +#include +#include +#include + +#include "misc/fd.hh" +#include "misc/socket.hh" + +namespace paxos +{ + /** + * \struct Socket + * \brief Value object representing a socket. + * + * socket(7) + */ + struct Socket + { + /** + * \brief Create a Socket from a fd. + */ + explicit Socket(const misc::shared_fd& fd) + : fd_{fd} + {} + + Socket() = default; + Socket(const Socket&) = delete; + Socket& operator=(const Socket&) = delete; + Socket(Socket&&) = default; + Socket& operator=(Socket&&) = default; + virtual ~Socket() = default; + + /** + * \brief recv(2). + */ + virtual ssize_t recv(void* dst, size_t len) = 0; + + /** + * \brief send(2). + */ + virtual ssize_t send(const void* src, size_t len) = 0; + + /** + * \brief sendfile(2). + */ + virtual ssize_t sendfile(misc::shared_fd&, off_t&, size_t) = 0; + + /** + * \brief bind(2). + */ + virtual void bind(const sockaddr* addr, socklen_t addrlen) = 0; + + /** + * \brief listen(2). + */ + virtual void listen(int backlog) = 0; + + /** + * \brief fcntl(2). Demander aux yaka + */ + virtual void fcntl_set_O_NONBLOCK() = 0; + + /** + * \brief setsockopt(2). + */ + virtual void setsockopt(int level, int optname, int optval) = 0; + + /** + * \brief getsockopt(2). + */ + virtual void getsockopt(int level, int optname, int& optval) = 0; + + /** + * \brief accept(2). + */ + virtual std::shared_ptr accept(sockaddr* addr, + socklen_t* addrlen) = 0; + + + std::string getsockname() const + { + char ip[INET6_ADDRSTRLEN]; + struct sockaddr_in6 addr; + bzero(&addr, sizeof(struct sockaddr_in6)); + unsigned int len = sizeof(struct sockaddr_in6); + sys::getsockname(fd_->fd_, (struct sockaddr *) &addr, &len); + if (addr.sin6_family == AF_INET6) + inet_ntop(addr.sin6_family, &addr.sin6_addr, ip, + INET6_ADDRSTRLEN); + else + inet_ntop(addr.sin6_family, + &((struct sockaddr_in *)&addr)->sin_addr, ip, + INET6_ADDRSTRLEN); + return ip; + } + + std::string getpeername() + { + char ip[INET6_ADDRSTRLEN]; + struct sockaddr_in6 addr; + bzero(&addr, sizeof(struct sockaddr_in6)); + unsigned int len = sizeof(struct sockaddr_in6); + sys::getpeername(fd_->fd_, (struct sockaddr *) &addr, &len); + if (addr.sin6_family == AF_INET6) + inet_ntop(addr.sin6_family, &addr.sin6_addr, ip, + INET6_ADDRSTRLEN); + else + inet_ntop(addr.sin6_family, + &((struct sockaddr_in *)&addr)->sin_addr, ip, + INET6_ADDRSTRLEN); + return ip; + } + + + + /** + * \brief connect(2). + */ + virtual void connect(const sockaddr*, socklen_t) = 0; + + const misc::shared_fd fd_get() const noexcept + { + return fd_; + } + + + const std::string& get_port() const + { + return port_; + } + + void set_port(const std::string& port) + { + port_ = port; + } + + const std::string& get_listener_port() const + { + return listener_port_; + } + + void set_listener_port(const std::string& listener_port) + { + listener_port_ = listener_port; + } + + const std::string& get_ip() const + { + return ip_; + } + + void set_ip(const std::string& ip) + { + ip_ = ip; + } + + protected: + /** + * \brief File descriptor of the socket. + */ + misc::shared_fd fd_; + + /** + * \brief The port of the socket + */ + std::string port_; + + /** + * \brief The port of the listening socket which accepted this socket + */ + std::string listener_port_; + + /** + * \brief The ip of the socket + */ + std::string ip_; + }; + + using shared_socket = std::shared_ptr; +} // namespace http