+#include "Server.hpp"
+
+#include <cstring> // memcpy
+#include <endian.h>
+#include <sstream>
+#include <unordered_map>
+
+Server::Server(Logger& logger) : logger_(logger) {}
+
+Server::~Server() {
+ stop();
+}
+
+bool Server::init(const std::filesystem::path& tokenFilePath, const std::filesystem::path& historyFilePath) {
+ std::lock_guard lock(externalMutex_);
+
+ tokens_.clear();
+ news_.clear();
+ history_.close();
+
+ if (std::filesystem::exists(tokenFilePath)) {
+ if (std::filesystem::is_directory(tokenFilePath)) {
+ logger_.log("Server", "Token file path " + tokenFilePath.string() + " is a directory.");
+ return false;
+ }
+
+ std::ifstream tokenFile(tokenFilePath);
+
+ if (!tokenFile) {
+ logger_.log("Server", "Could not open token file " + tokenFilePath.string() + " to read.");
+ return false;
+ }
+
+ std::size_t lineN{0};
+
+ for (std::string line; std::getline(tokenFile, line);) {
+ lineN++;
+
+ std::uint64_t token{0};
+
+ try {
+ token = std::stoi(line);
+ }
+ catch (const std::exception& e) {
+ logger_.log("Server",
+ "Skipping invalid token " + line + " line " + std::to_string(lineN) + '.');
+ continue;
+ }
+
+ tokens_.insert(token);
+
+ logger_.log("Server", "Added token " + line + '.');
+ }
+ }
+
+ if (tokens_.empty()) {
+ logger_.log("Server", "No tokkens were added.");
+ return false;
+ }
+
+ if (std::filesystem::exists(historyFilePath)) {
+ if (std::filesystem::is_directory(historyFilePath)) {
+ logger_.log("Server", "History file path " + historyFilePath.string() + " is a directory.");
+ return false;
+ }
+
+ std::ifstream historyFile(historyFilePath);
+
+ if (!historyFile) {
+ logger_.log("Server", "Could not open history file " + historyFilePath.string() + " to read.");
+ return false;
+ }
+
+ std::size_t lineN{0};
+
+ for (std::string line; std::getline(historyFile, line);) {
+ lineN++;
+ std::size_t linePos = line.find('|');
+
+ if (linePos == std::string::npos or linePos == 0) {
+ logger_.log("Server", "Skipping invalid line " + std::to_string(lineN) + " in history file.");
+ continue;
+ }
+
+ std::string timestampStr = line.substr(0, linePos);
+ std::string news = line.substr(linePos + 1);
+ std::uint32_t timestamp{0};
+
+ try {
+ timestamp = std::stoi(timestampStr);
+ }
+ catch (const std::exception& e) {
+ logger_.log("Server",
+ "Skipping invalid timestamp " + timestampStr + " line " + std::to_string(lineN) + '.');
+ continue;
+ }
+
+ if (news.empty()) {
+ logger_.log("Server", "Skipping empty news line " + std::to_string(lineN) + '.');
+ }
+
+ auto const result = news_.try_emplace(timestamp, std::move(news));
+
+ if (!result.second) {
+ logger_.log("Server", "Skipping duplicated timestamp " + std::to_string(timestamp) + '.');
+ continue;
+ }
+
+ logger_.log("Server",
+ "Added timestamp " + std::to_string(result.first->first) + " news " + result.first->second);
+ }
+ }
+
+ history_.open(historyFilePath, std::ios::binary | std::ios_base::app);
+
+ if (!history_) {
+ logger_.log("Server", "Could not open history file " + historyFilePath.string() + " to write.");
+ return false;
+ }
+
+ return true;
+}
+
+bool Server::addNews(const std::uint32_t timestamp, std::string news) {
+ std::lock_guard lock(externalMutex_);
+
+ if (news.empty()) {
+ logger_.log("Server", "Could not add empty news.");
+ return false;
+ }
+
+ {
+ std::lock_guard lock(newsMutex_);
+ auto const result = news_.try_emplace(timestamp, news);
+
+ if (!result.second) {
+ logger_.log("Server", "Could not add news for existing timestamp " + std::to_string(timestamp) + '.');
+ return false;
+ }
+ }
+
+ history_ << timestamp << '|' << news << '\n';
+
+ {
+ std::lock_guard lock(queueMutex_);
+ sendQueue_.push_back({timestamp, news});
+ }
+
+ logger_.log("Server", "Added timestamp " + std::to_string(timestamp) + " news " + news);
+ return true;
+}
+
+bool Server::start(const std::uint16_t port) {
+ std::lock_guard lock(externalMutex_);
+
+ if (running_) {
+ logger_.log("Server", "Already running.");
+ return false;
+ }
+
+ running_ = true;
+ socketThread_ = std::thread([this, port](){ runThread(port); });
+
+ logger_.log("Server", "Started with port " + std::to_string(port) + '.');
+ return true;
+}
+
+void Server::stop() {
+ std::lock_guard lock(externalMutex_);
+
+ if (!running_) {
+ return;
+ }
+
+ logger_.log("Server", "Stop");
+
+ stopSignal_ = true;
+
+ if (socketThread_.joinable()) {
+ socketThread_.join();
+ }
+
+ stopSignal_ = false;
+ running_ = false;
+}
+
+bool Server::listening() const {
+ return listening_;
+}
+
+void Server::runThread(const std::uint16_t port)
+{
+ Socket serverSocket(logger_);
+
+ if (!serverSocket.init()) {
+ return;
+ }
+
+ if (!serverSocket.listen(port)) {
+ return;
+ }
+
+ SocketPoller poller(logger_);
+
+ if (!poller.init()) {
+ return;
+ }
+
+ if (!poller.add(serverSocket)) {
+ return;
+ }
+
+ listening_ = true;
+
+ std::unordered_map<std::uint32_t, Client> clients;
+
+ while (!stopSignal_) {
+ std::unordered_set<std::uint32_t> pollResult;
+
+ // Poll incoming events.
+ if (!poller.poll(pollResult)) {
+ break;
+ }
+
+ for (const auto fd : pollResult) {
+ // Event from listener socket.
+ if (fd == serverSocket.fileDescriptor()) {
+ Client newClient;
+ newClient.socket = std::make_unique<Socket>(logger_);
+
+ if (!serverSocket.accept(*newClient.socket)) {
+ continue;
+ }
+
+ if (!newClient.socket->isOpen()) {
+ continue;
+ }
+
+ if (!poller.add(*newClient.socket)) {
+ continue;
+ }
+
+ logger_.log("Server " + std::to_string(serverSocket.fileDescriptor()),
+ "Added client " + std::to_string(newClient.socket->fileDescriptor()) + '.');
+ clients.try_emplace(newClient.socket->fileDescriptor(), std::move(newClient));
+ }
+ // Event from a client socket.
+ else {
+ auto it = clients.find(fd);
+
+ if (it == clients.end()) {
+ logger_.log("Server " + std::to_string(serverSocket.fileDescriptor()),
+ "Received data from unknown source " + std::to_string(fd) + '.');
+ continue;
+ }
+
+ if (!handleMessage(it->second)) {
+ poller.remove(*it->second.socket);
+ clients.erase(it);
+ }
+ }
+ } // /for each polled event.
+
+
+ // Send news from queue.
+ decltype(sendQueue_) sendQueue;
+
+ {
+ std::lock_guard lock(queueMutex_);
+ sendQueue.swap(sendQueue_);
+ }
+
+ for (auto const& [timestamp, news] : sendQueue) {
+ message::News n;
+ n.timestamp = htobe32(timestamp);
+
+ message::Header h;
+ h.type = message::Type::News;
+ h.size = htobe16(sizeof h + sizeof n + news.size());
+
+ std::vector<std::byte> buffer;
+ buffer.reserve(sizeof h + sizeof n + news.size());
+ buffer.insert(buffer.end(),
+ reinterpret_cast<const std::byte*>(&h),
+ reinterpret_cast<const std::byte*>(&h) + sizeof h);
+ buffer.insert(buffer.end(),
+ reinterpret_cast<const std::byte*>(&n),
+ reinterpret_cast<const std::byte*>(&n) + sizeof n);
+ buffer.insert(buffer.end(),
+ reinterpret_cast<const std::byte*>(news.data()),
+ reinterpret_cast<const std::byte*>(news.data()) + news.size());
+
+ // Send to all logged in clients.
+ for (auto& [fd, client] : clients) {
+ if (client.loggedIn && !client.socket->write(buffer)) {
+ poller.remove(*client.socket);
+ clients.erase(fd);
+ continue;
+ }
+
+ logger_.log("Server " + std::to_string(serverSocket.fileDescriptor()),
+ "Sent news timestamp " + std::to_string(timestamp)
+ + " for client " + std::to_string(fd) + '.');
+ }
+ }
+ } // /while stop signal is false.
+
+ listening_ = false;
+}
+
+bool Server::handleMessage(Client& client) {
+ if (client.socket == nullptr) {
+ return false;
+ }
+
+ std::vector<std::byte> buffer;
+ buffer.resize(sizeof(message::Header));
+
+ if (!client.socket->read(buffer)) {
+ return false;
+ }
+
+ // TODO
+ if (buffer.size() < sizeof(message::Header)) {
+ logger_.log("Server",
+ "ZERO READ " + std::to_string(client.socket->fileDescriptor()));
+ return true;
+ }
+
+ message::Header h;
+ std::memcpy(&h, buffer.data(), sizeof h);
+ h.size = be16toh(h.size);
+
+ logger_.log("Server",
+ "Received message type " + std::to_string(static_cast<std::uint16_t>(h.type))
+ + " size " + std::to_string(h.size)
+ + " from client " + std::to_string(client.socket->fileDescriptor()) + '.');
+
+ switch (h.type) {
+ case message::Type::Login: {
+ buffer.resize(h.size - sizeof h);
+
+ if (!client.socket->read(buffer)) {
+ return false;
+ }
+
+ message::Login l;
+ std::memcpy(&l, buffer.data(), sizeof l);
+ l.token = be64toh(l.token);
+
+ if (!handleLogin(client, l)) {
+ return false;
+ }
+ } break;
+ case message::Type::Request: {
+ buffer.resize(h.size - sizeof h);
+
+ if (!client.socket->read(buffer)) {
+ return false;
+ }
+
+ message::Request r;
+ std::memcpy(&r, buffer.data(), sizeof r);
+ r.firstMissing = be32toh(r.firstMissing);
+ r.lastMissing = be32toh(r.lastMissing);
+
+ if (!handleRequest(client, r)) {
+ return false;
+ }
+ } break;
+ default: {
+ buffer.resize(h.size - sizeof h);
+
+ if (!client.socket->read(buffer)) {
+ return false;
+ }
+
+ if (!handleUnknownMessage(client, h)) {
+ return false;
+ }
+
+ } break;
+ } // /switch message type.
+
+ return true;
+}
+
+bool Server::handleUnknownMessage(Client& client, const message::Header& message) {
+ logger_.log("Server",
+ "Received unknown message type " + std::to_string(static_cast<std::uint16_t>(message.type))
+ + " size " + std::to_string(message.size)
+ + " from client " + std::to_string(client.socket->fileDescriptor()) + '.');
+
+ message::Reply r;
+ r.code = message::ReplyCode::UnknownType;
+
+ message::Header h;
+ h.type = message::Type::Reply;
+ h.size = htobe16(sizeof h + sizeof r);
+
+ std::vector<std::byte> buffer;
+ buffer.reserve(sizeof h + sizeof r);
+ buffer.insert(buffer.end(),
+ reinterpret_cast<const std::byte*>(&h),
+ reinterpret_cast<const std::byte*>(&h) + sizeof h);
+ buffer.insert(buffer.end(),
+ reinterpret_cast<const std::byte*>(&r),
+ reinterpret_cast<const std::byte*>(&r) + sizeof r);
+
+ if (!client.socket->write(buffer)) {
+ return false;
+ }
+
+ return true;
+}
+
+bool Server::handleLogin(Client& client, const message::Login& message) {
+ logger_.log("Server",
+ "Received login with token " + std::to_string(message.token)
+ + " from client " + std::to_string(client.socket->fileDescriptor()) + '.');
+
+ bool found = tokens_.find(message.token) != tokens_.cend();
+
+ message::Reply r;
+
+ if (!found) {
+ r.code = message::ReplyCode::WrongLogin;
+ } else {
+ r.code = message::ReplyCode::OK;
+ }
+
+ message::Header h;
+ h.type = message::Type::Reply;
+ h.size = htobe16(sizeof h + sizeof r);
+
+ std::vector<std::byte> buffer;
+ buffer.reserve(sizeof h + sizeof r);
+ buffer.insert(buffer.end(),
+ reinterpret_cast<const std::byte*>(&h),
+ reinterpret_cast<const std::byte*>(&h) + sizeof h);
+ buffer.insert(buffer.end(),
+ reinterpret_cast<const std::byte*>(&r),
+ reinterpret_cast<const std::byte*>(&r) + sizeof r);
+
+ if (!client.socket->write(buffer)) {
+ return false;
+ }
+
+ if (!found) {
+ logger_.log("Server",
+ "Rejected login with token " + std::to_string(message.token)
+ + " from client " + std::to_string(client.socket->fileDescriptor()) + '.');
+ return true;
+ }
+
+ client.loggedIn = true;
+ logger_.log("Server",
+ "Client " + std::to_string(client.socket->fileDescriptor())
+ + " logged in with token " + std::to_string(message.token) + '.');
+
+ return true;
+}
+
+bool Server::handleRequest(Client& client, const message::Request& message) {
+ logger_.log("Server",
+ "Received request " + std::to_string(static_cast<std::uint16_t>(message.firstMissing))
+ + '-' + std::to_string(static_cast<std::uint16_t>(message.lastMissing))
+ + " from client " + std::to_string(client.socket->fileDescriptor()) + '.');
+
+ message::Reply r;
+
+ if (!client.loggedIn) {
+ r.code = message::ReplyCode::NotLoggedIn;
+ } else {
+ r.code = message::ReplyCode::OK;
+ }
+
+ message::Header h;
+ h.type = message::Type::Reply;
+ h.size = htobe16(sizeof h + sizeof r);
+
+ std::vector<std::byte> buffer;
+ buffer.reserve(sizeof h + sizeof r);
+ buffer.insert(buffer.end(),
+ reinterpret_cast<const std::byte*>(&h),
+ reinterpret_cast<const std::byte*>(&h) + sizeof h);
+ buffer.insert(buffer.end(),
+ reinterpret_cast<const std::byte*>(&r),
+ reinterpret_cast<const std::byte*>(&r) + sizeof r);
+
+ if (!client.socket->write(buffer)) {
+ return false;
+ }
+
+ if (!client.loggedIn) {
+ logger_.log("Server",
+ "Rejected request " + std::to_string(static_cast<std::uint16_t>(message.firstMissing))
+ + '-' + std::to_string(static_cast<std::uint16_t>(message.lastMissing))
+ + " to unauthentified client " + std::to_string(client.socket->fileDescriptor()) + '.');
+ return true;
+ }
+
+ //TODO: SEND HISTORY.
+
+ return true;
+}