From 8a23aa89a58d3a90d5851b449b5552e1fcdcaded Mon Sep 17 00:00:00 2001 From: rtk0c Date: Mon, 27 Jun 2022 00:13:08 +0000 Subject: (From git) Initial server setup git-svn-id: file:///home/arch/svn/epistmool/trunk@4 71f44415-077c-4ad7-a976-72ddbf76608f --- server-v1/source/EpistmoolServer/CMakeLists.txt | 10 ++ server-v1/source/EpistmoolServer/Connection.cpp | 150 ++++++++++++++++ server-v1/source/EpistmoolServer/Connection.hpp | 50 ++++++ .../source/EpistmoolServer/Protocol/CMakeLists.txt | 6 + .../source/EpistmoolServer/Protocol/Command.cpp | 200 +++++++++++++++++++++ .../source/EpistmoolServer/Protocol/Command.hpp | 124 +++++++++++++ .../source/EpistmoolServer/Protocol/Error.cpp | 1 + .../source/EpistmoolServer/Protocol/Error.hpp | 21 +++ .../source/EpistmoolServer/Protocol/Version.hpp | 9 + server-v1/source/EpistmoolServer/Protocol/fwd.hpp | 13 ++ server-v1/source/EpistmoolServer/Server.cpp | 89 +++++++++ server-v1/source/EpistmoolServer/Server.hpp | 28 +++ .../source/EpistmoolServer/ServerProperties.cpp | 1 + .../source/EpistmoolServer/ServerProperties.hpp | 9 + server-v1/source/EpistmoolServer/Session.cpp | 76 ++++++++ server-v1/source/EpistmoolServer/Session.hpp | 59 ++++++ .../EpistmoolServer/Workspace/CMakeLists.txt | 3 + server-v1/source/EpistmoolServer/Workspace/fwd.hpp | 5 + server-v1/source/EpistmoolServer/fwd.hpp | 21 +++ server-v1/source/all_fwd.hpp | 3 + server-v1/source/header_only.cpp | 1 + server-v1/source/main.cpp | 12 ++ 22 files changed, 891 insertions(+) create mode 100644 server-v1/source/EpistmoolServer/CMakeLists.txt create mode 100644 server-v1/source/EpistmoolServer/Connection.cpp create mode 100644 server-v1/source/EpistmoolServer/Connection.hpp create mode 100644 server-v1/source/EpistmoolServer/Protocol/CMakeLists.txt create mode 100644 server-v1/source/EpistmoolServer/Protocol/Command.cpp create mode 100644 server-v1/source/EpistmoolServer/Protocol/Command.hpp create mode 100644 server-v1/source/EpistmoolServer/Protocol/Error.cpp create mode 100644 server-v1/source/EpistmoolServer/Protocol/Error.hpp create mode 100644 server-v1/source/EpistmoolServer/Protocol/Version.hpp create mode 100644 server-v1/source/EpistmoolServer/Protocol/fwd.hpp create mode 100644 server-v1/source/EpistmoolServer/Server.cpp create mode 100644 server-v1/source/EpistmoolServer/Server.hpp create mode 100644 server-v1/source/EpistmoolServer/ServerProperties.cpp create mode 100644 server-v1/source/EpistmoolServer/ServerProperties.hpp create mode 100644 server-v1/source/EpistmoolServer/Session.cpp create mode 100644 server-v1/source/EpistmoolServer/Session.hpp create mode 100644 server-v1/source/EpistmoolServer/Workspace/CMakeLists.txt create mode 100644 server-v1/source/EpistmoolServer/Workspace/fwd.hpp create mode 100644 server-v1/source/EpistmoolServer/fwd.hpp create mode 100644 server-v1/source/all_fwd.hpp create mode 100644 server-v1/source/header_only.cpp create mode 100644 server-v1/source/main.cpp (limited to 'server-v1/source') diff --git a/server-v1/source/EpistmoolServer/CMakeLists.txt b/server-v1/source/EpistmoolServer/CMakeLists.txt new file mode 100644 index 0000000..c98c901 --- /dev/null +++ b/server-v1/source/EpistmoolServer/CMakeLists.txt @@ -0,0 +1,10 @@ +target_sources(EpistmoolServer PRIVATE + fwd.hpp + Connection.hpp Connection.cpp + Session.hpp Session.cpp + Server.hpp Server.cpp + ServerProperties.hpp ServerProperties.cpp +) + +add_subdirectory(Protocol) +add_subdirectory(Workspace) diff --git a/server-v1/source/EpistmoolServer/Connection.cpp b/server-v1/source/EpistmoolServer/Connection.cpp new file mode 100644 index 0000000..59dcf38 --- /dev/null +++ b/server-v1/source/EpistmoolServer/Connection.cpp @@ -0,0 +1,150 @@ +#include "Connection.hpp" + +#include +#include +#include +#include +#include +#include +#include + +using namespace Epistmool::Server; + +namespace { +constexpr int kInvalidGeneration = 0; +} // namespace + +ConnectionId ConnectionId::createInvalid() +{ + return ConnectionId{ + .index = 0, + .generation = kInvalidGeneration, + }; +} + +bool ConnectionId::isInvalid() const +{ + return generation == kInvalidGeneration; +} + +struct ConnectionManager::Connection +{ + enum Type + { + Empty, + LocalSocket, + }; + + union + { + QLocalSocket* localSocket; + }; + + Type type = Empty; + int generation = kInvalidGeneration + 1; + + void markEmpty() + { + type = Empty; + } +}; + +class ConnectionManager::Private +{ +public: + static int findFreeConnectionObj(ConnectionManager& self) + { + for (int i = 0; i < self.mConnections.size(); ++i) { + auto& conn = self.mConnections[i]; + if (conn.type == Connection::Empty) { + return i; + } + } + + self.mConnections.push_back({}); + return self.mConnections.size() - 1; + } +}; + +ConnectionManager::ConnectionManager(QObject* parent) + : QObject(parent) +{ +} + +ConnectionManager::~ConnectionManager() = default; + +bool ConnectionManager::isLocalConnectionsEnabled() const +{ + return mLocal != nullptr; +} + +void ConnectionManager::setLocalConnectionsEnabled(bool enabled) +{ + bool currentlyEnabled = isLocalConnectionsEnabled(); + if (!currentlyEnabled && enabled) { + mLocal = new QLocalServer(this); + + if (!mLocal->listen("queue")) { + qFatal("Failed to initialized event queue"); + } + + connect(mLocal, &QLocalServer::newConnection, mLocal, [&]() { + auto socket = mLocal->nextPendingConnection(); + + ConnectionId connId; + connId.index = Private::findFreeConnectionObj(*this); + auto& connObj = mConnections[connId.index]; + connId.generation = connObj.generation++; + connObj.type = Connection::LocalSocket; + connObj.localSocket = socket; + + connect(socket, &QLocalSocket::disconnected, socket, &QObject::deleteLater); + connect(socket, &QLocalSocket::disconnected, this, [this, connId]() { + mConnections[connId.index].markEmpty(); + }); + connect(socket, &QLocalSocket::readyRead, this, [this, socket, connId]() { + while (socket->canReadLine()) { + auto line = socket->readLine(); + + QJsonParseError error; + auto msg = QJsonDocument::fromJson(line, &error); + if (error.error != QJsonParseError::NoError) { + qWarning() << "Invalid message from client: " << error.errorString(); + qWarning() << "Message: " << QString(line); + } else { + qDebug() << QString(line); + } + + emit messageRecieved(msg, connId); + } + }); + + emit connectionEstablished(connId); + }); + } else if (currentlyEnabled && !enabled) { + mLocal->deleteLater(); + mLocal = nullptr; + } +} + +void ConnectionManager::replyMessage(ConnectionId id, const QJsonDocument& message) +{ + if (id.index < 0 || id.index >= mConnections.size()) { + return; + } + + auto& conn = mConnections[id.index]; + if (id.generation != conn.generation) { + return; + } + + switch (conn.type) { + case Connection::Empty: return; + + case Connection::LocalSocket: { + auto socket = conn.localSocket; + socket->write(message.toJson(QJsonDocument::Compact)); + socket->flush(); + } break; + } +} diff --git a/server-v1/source/EpistmoolServer/Connection.hpp b/server-v1/source/EpistmoolServer/Connection.hpp new file mode 100644 index 0000000..78e34db --- /dev/null +++ b/server-v1/source/EpistmoolServer/Connection.hpp @@ -0,0 +1,50 @@ +#pragma once + +#include +#include + +class QLocalServer; +class QJsonDocument; + +namespace Epistmool::Server { + +struct ConnectionId +{ + int index; + int generation; + + static ConnectionId createInvalid(); + bool isInvalid() const; + + bool operator==(const ConnectionId&) const = default; +}; + +uint qHash(const ConnectionId& id, uint seed = 0); + +class ConnectionManager : public QObject +{ + Q_OBJECT + class Private; + +private: + QLocalServer* mLocal = nullptr; + + struct Connection; + std::vector mConnections; + +public: + explicit ConnectionManager(QObject* parent = nullptr); + ~ConnectionManager(); + + bool isLocalConnectionsEnabled() const; + void setLocalConnectionsEnabled(bool enabled); + + void replyMessage(ConnectionId id, const QJsonDocument& message); + +signals: + void connectionEstablished(Epistmool::Server::ConnectionId id); + void connectionDropped(Epistmool::Server::ConnectionId id); + void messageRecieved(const QJsonDocument& message, Epistmool::Server::ConnectionId id); +}; + +} // namespace Epistmool::Server diff --git a/server-v1/source/EpistmoolServer/Protocol/CMakeLists.txt b/server-v1/source/EpistmoolServer/Protocol/CMakeLists.txt new file mode 100644 index 0000000..54f1da7 --- /dev/null +++ b/server-v1/source/EpistmoolServer/Protocol/CMakeLists.txt @@ -0,0 +1,6 @@ +target_sources(EpistmoolServer PRIVATE + fwd.hpp + Command.hpp Command.cpp + Error.hpp Error.cpp + Version.hpp +) diff --git a/server-v1/source/EpistmoolServer/Protocol/Command.cpp b/server-v1/source/EpistmoolServer/Protocol/Command.cpp new file mode 100644 index 0000000..a108e0e --- /dev/null +++ b/server-v1/source/EpistmoolServer/Protocol/Command.cpp @@ -0,0 +1,200 @@ +#include "Command.hpp" + +#include +#include +#include + +using namespace Epistmool::Server; + +namespace { +auto kKindTraits = []() { + using enum ProtocolMessage::Kind; + std::array(KindCOUNT)> array; + + array[SessionAuth] = { .isC2S = true, .hasReply = true }; + array[SessionDestroy] = { .isC2S = true, .hasReply = true }; + + array[WorkspaceCreate] = { .isC2S = true, .hasReply = true }; + array[WorkspaceOpen] = { .isC2S = true, .hasReply = true }; + array[WorkspaceFetchIndex] = { .isC2S = true, .hasReply = true }; + + array[WorkspaceFetchKnowledge] = { .isC2S = true, .hasReply = true }; + array[WorkspaceUpdateKnowledge] = { .isC2S = true, .hasReply = true }; + array[WorkspaceCreateKnowledge] = { .isC2S = true, .hasReply = true }; + array[WorkspaceDeleteKnowledge] = { .isC2S = true, .hasReply = true }; + + array[WorkspaceFetchKeyword] = { .isC2S = true, .hasReply = true }; + array[WorkspaceUpdateKeyword] = { .isC2S = true, .hasReply = true }; + array[WorkspaceCreateKeyword] = { .isC2S = true, .hasReply = true }; + array[WorkspaceDeleteKeyword] = { .isC2S = true, .hasReply = true }; + + array[NotificationWorkspaceUpdated] = { .isC2S = false, .hasReply = false }; + array[NotificationKnowledgeUpdated] = { .isC2S = false, .hasReply = false }; + + return array; +}(); +} + +std::unique_ptr ProtocolMessage::createRequest(Kind kind) +{ + switch(kind) { + case SessionAuth: return std::make_unique(); + case SessionDestroy: return nullptr; + + case WorkspaceCreate: return nullptr; + case WorkspaceOpen: return nullptr; + case WorkspaceFetchIndex: return nullptr; + + case WorkspaceFetchKnowledge: return nullptr; + case WorkspaceUpdateKnowledge: return nullptr; + case WorkspaceCreateKnowledge: return nullptr; + case WorkspaceDeleteKnowledge: return nullptr; + + case WorkspaceFetchKeyword: return nullptr; + case WorkspaceUpdateKeyword: return nullptr; + case WorkspaceCreateKeyword: return nullptr; + case WorkspaceDeleteKeyword: return nullptr; + + case NotificationWorkspaceUpdated: return nullptr; + case NotificationKnowledgeUpdated: return nullptr; + + default: return nullptr; + } +} + +std::unique_ptr ProtocolMessage::createReply(Kind kind) +{ + switch(kind) { + case SessionAuth: return std::make_unique(); + case SessionDestroy: return nullptr; + + case WorkspaceCreate: return nullptr; + case WorkspaceOpen: return nullptr; + case WorkspaceFetchIndex: return nullptr; + + case WorkspaceFetchKnowledge: return nullptr; + case WorkspaceUpdateKnowledge: return nullptr; + case WorkspaceCreateKnowledge: return nullptr; + case WorkspaceDeleteKnowledge: return nullptr; + + case WorkspaceFetchKeyword: return nullptr; + case WorkspaceUpdateKeyword: return nullptr; + case WorkspaceCreateKeyword: return nullptr; + case WorkspaceDeleteKeyword: return nullptr; + + case NotificationWorkspaceUpdated: return nullptr; + case NotificationKnowledgeUpdated: return nullptr; + + default: return nullptr; + } +} + +const ProtocolMessage::KindTrait& ProtocolMessage::getKindTrait(Kind kind) +{ + return kKindTraits[kind]; +} + +ProtocolMessage::ProtocolMessage(Kind kind, Variant variant) + : kind{ kind } + , variant{ variant } +{ +} + +ProtocolRequest::ProtocolRequest(Kind kind) + : ProtocolMessage(kind, RequestVariant) +{ +} + +QJsonObject ProtocolRequest::serialize(const ProtocolRequest& msg) +{ + QJsonObject object; + object.insert("method", QMetaEnum::fromType().valueToKey(msg.kind)); + + QJsonObject fields; + msg.serializeFields(fields); + + return object; +} + +std::unique_ptr ProtocolRequest::deserialize(const QJsonObject& object) +{ + bool ok; + auto methodName = object.value("method").toString().toStdString(); + auto method = static_cast(QMetaEnum::fromType().keysToValue(methodName.c_str(), &ok)); + if (!ok) return nullptr; + + auto msg = createRequest(method); + + auto fieldsVal = object.value("fields"); + if (!fieldsVal.isObject()) return nullptr; + msg->deserializeFields(fieldsVal.toObject()); + + return msg; +} + +ProtocolReply::ProtocolReply(Kind kind) + : ProtocolMessage(kind, ReplyVariant) +{ +} + +QJsonObject ProtocolReply::serialize(const ProtocolReply& msg) +{ + QJsonObject object; + object.insert("method", QMetaEnum::fromType().valueToKey(msg.kind)); + object.insert("sequence", msg.sequence); + + QJsonObject fields; + msg.serializeFields(fields); + + return object; +} + +std::unique_ptr ProtocolReply::deserialize(const QJsonObject& object) +{ + bool ok; + auto methodName = object.value("method").toString().toStdString(); + auto method = static_cast(QMetaEnum::fromType().keysToValue(methodName.c_str(), &ok)); + if (!ok) return nullptr; + + auto msg = createReply(method); + + msg->sequence = object.value("sequence").toInt(-1); + + auto fieldsVal = object.value("fields"); + if (!fieldsVal.isObject()) return nullptr; + msg->deserializeFields(fieldsVal.toObject()); + + return msg; +} + +ProtocolRequest_SessionAuth::ProtocolRequest_SessionAuth() + : ProtocolRequest(SessionAuth) {} + +void ProtocolRequest_SessionAuth::serializeFields(QJsonObject& object) const +{ + ProtocolRequest::serializeFields(object); + object.insert("session", theSession); + object.insert("createIfInvalid", createIfInvalid); +} + +void ProtocolRequest_SessionAuth::deserializeFields(const QJsonObject& object) +{ + ProtocolRequest::deserializeFields(object); + theSession = object.value("session").toInt(); + createIfInvalid = object.value("createIfInvalid").toBool(); +} + +ProtocolReply_SessionAuth::ProtocolReply_SessionAuth() + : ProtocolReply(SessionAuth) {} + +void ProtocolReply_SessionAuth::serializeFields(QJsonObject& object) const +{ + ProtocolReply::serializeFields(object); + object.insert("session", theSession); +} + +void ProtocolReply_SessionAuth::deserializeFields(const QJsonObject& object) +{ + ProtocolReply::deserializeFields(object); + theSession = object.value("session").toInt(); +} diff --git a/server-v1/source/EpistmoolServer/Protocol/Command.hpp b/server-v1/source/EpistmoolServer/Protocol/Command.hpp new file mode 100644 index 0000000..9ec9786 --- /dev/null +++ b/server-v1/source/EpistmoolServer/Protocol/Command.hpp @@ -0,0 +1,124 @@ +#pragma once + +#include "all_fwd.hpp" + +#include +#include +#include +#include + +class QJsonObject; + +namespace Epistmool::Server { + +// Note: not all message kinds have a corresponding reply format. In such case the Kind enum is prefixed with 'Notification'. +class ProtocolMessage +{ + Q_GADGET + +public: + struct KindTrait + { + bool isC2S; + bool hasReply; + }; + + enum Kind + { + SessionAuth, + SessionDestroy, + + WorkspaceCreate, + WorkspaceOpen, + WorkspaceFetchIndex, + + WorkspaceFetchKnowledge, + WorkspaceUpdateKnowledge, + WorkspaceCreateKnowledge, + WorkspaceDeleteKnowledge, + + WorkspaceFetchKeyword, + WorkspaceUpdateKeyword, + WorkspaceCreateKeyword, + WorkspaceDeleteKeyword, + + NotificationWorkspaceUpdated, + NotificationKnowledgeUpdated, + + KindCOUNT, + }; + Q_ENUM(Kind) + + static std::unique_ptr createRequest(Kind kind); + static std::unique_ptr createReply(Kind kind); + static const KindTrait& getKindTrait(Kind kind); + + enum Variant + { + RequestVariant, + ReplyVariant, + }; + Q_ENUM(Variant) + +public: + Kind kind; + Variant variant; + +public: + ProtocolMessage(Kind kind, Variant variant); + virtual ~ProtocolMessage() = default; + +protected: + virtual void serializeFields(QJsonObject& object) const = 0; + virtual void deserializeFields(const QJsonObject& object) = 0; +}; + +struct ProtocolRequest : public ProtocolMessage +{ + ProtocolRequest(Kind kind); + static QJsonObject serialize(const ProtocolRequest& msg); + static std::unique_ptr deserialize(const QJsonObject& object); +}; + +struct ProtocolReply : public ProtocolMessage +{ + int sequence; + + ProtocolReply(Kind kind); + static QJsonObject serialize(const ProtocolReply& msg); + static std::unique_ptr deserialize(const QJsonObject& object); +}; + +// =========================== +// Individual messages classes + +struct ProtocolRequest_SessionAuth : public ProtocolRequest +{ + int theSession; + bool createIfInvalid; + + ProtocolRequest_SessionAuth(); + +protected: + virtual void serializeFields(QJsonObject& object) const override; + virtual void deserializeFields(const QJsonObject& object) override; +}; + +struct ProtocolReply_SessionAuth : public ProtocolReply +{ + /// The same value as provided in the request message. + /// If \l ProcotolCommandSessionAuth::createIfValid is set and the given session is invalid, a new session is created and written here instead of the original value. + int theSession; + + ProtocolReply_SessionAuth(); + +protected: + virtual void serializeFields(QJsonObject& object) const override; + virtual void deserializeFields(const QJsonObject& object) override; +}; + +struct ProtocolNotification_WorkspaceUpdate : public ProtocolRequest +{ +}; + +} // namespace Epistmool::Server diff --git a/server-v1/source/EpistmoolServer/Protocol/Error.cpp b/server-v1/source/EpistmoolServer/Protocol/Error.cpp new file mode 100644 index 0000000..5bf4840 --- /dev/null +++ b/server-v1/source/EpistmoolServer/Protocol/Error.cpp @@ -0,0 +1 @@ +#include "Error.hpp" diff --git a/server-v1/source/EpistmoolServer/Protocol/Error.hpp b/server-v1/source/EpistmoolServer/Protocol/Error.hpp new file mode 100644 index 0000000..1ddedf9 --- /dev/null +++ b/server-v1/source/EpistmoolServer/Protocol/Error.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include "EpistmoolServer/Protocol/Version.hpp" + +#include +#include + +namespace Epistmool::Server { +struct ProtocolError +{ + QVersionNumber since; + QLatin1String name; +}; + +namespace ProtocolErrors { + const ProtocolError kUnsupportedVersion{ + .since = ProtocolVersions::v0_1, + .name = QLatin1String("unsupportedVersion"), + }; +} +} // namespace Epistmool::Server diff --git a/server-v1/source/EpistmoolServer/Protocol/Version.hpp b/server-v1/source/EpistmoolServer/Protocol/Version.hpp new file mode 100644 index 0000000..aae72bd --- /dev/null +++ b/server-v1/source/EpistmoolServer/Protocol/Version.hpp @@ -0,0 +1,9 @@ +#pragma once + +#include + +namespace Epistmool::Server { +namespace ProtocolVersions { + const QVersionNumber v0_1(0, 1); +} +} // namespace Epistmool::Server diff --git a/server-v1/source/EpistmoolServer/Protocol/fwd.hpp b/server-v1/source/EpistmoolServer/Protocol/fwd.hpp new file mode 100644 index 0000000..adf8138 --- /dev/null +++ b/server-v1/source/EpistmoolServer/Protocol/fwd.hpp @@ -0,0 +1,13 @@ +#pragma once + +namespace Epistmool::Server{ + +// Command.hpp +class ProtocolMessage; +struct ProtocolRequest; +struct ProtocolReply; + +// Error.hpp +struct ProtocolError; + +} // namespace Epistmool::Server diff --git a/server-v1/source/EpistmoolServer/Server.cpp b/server-v1/source/EpistmoolServer/Server.cpp new file mode 100644 index 0000000..4dc3b16 --- /dev/null +++ b/server-v1/source/EpistmoolServer/Server.cpp @@ -0,0 +1,89 @@ +#include "Server.hpp" + +#include "EpistmoolServer/Protocol/Error.hpp" +#include "EpistmoolServer/ServerProperties.hpp" + +#include +#include +#include +#include +#include + +using namespace Epistmool::Server; + +// TODO structured error handling similar to messages +class Server::Private +{ +public: + static bool messageHeader(QJsonObject& msg, SessionId sessionId, int id) + { + msg.insert("session", sessionId.index); + msg.insert("id", id); + return true; + } + + static bool messageErrInvalidVersion(QJsonObject& msg) + { + QJsonObject error; + error.insert("type", ProtocolErrors::kUnsupportedVersion.name); + error.insert("supportedVersion", ServerProperties::kVersion.toString()); + + msg.insert("error", error); + + return true; + } +}; + +Server::Server(QObject* parent) + : QObject(parent) +{ + mConnectionManager.setLocalConnectionsEnabled(true); + + connect(&mConnectionManager, &ConnectionManager::messageRecieved, this, &Server::onMessage); + connect(&mConnectionManager, &ConnectionManager::connectionDropped, &mSessionManager, &SessionManager::dropConnection); +} + +void Server::onMessage(const QJsonDocument& message, ConnectionId connId) +{ + if (!message.isObject()) return; + auto root = message.object(); + + // Extract as empty string if non-existent + auto versionString = root.value("version").toString(); + + int id = root.value("id").toInt(); + + SessionId sessionId; + if (auto index = root.value("session").toInt(-1); index == -1) { + sessionId = SessionId::makeInvalid(); + } else { + sessionId.index = index; + } + + auto version = [&]() -> QVersionNumber { + if (versionString.isEmpty()) { + return QVersionNumber(); + } else { + return QVersionNumber::fromString(versionString).normalized(); + } + }(); + if (version.isNull() || version >= ServerProperties::kVersion) { + qWarning() << "Message recieved with invalid version " << versionString; + + QJsonObject msg; + Private::messageHeader(msg, sessionId, id); + Private::messageErrInvalidVersion(msg); + mConnectionManager.replyMessage(connId, QJsonDocument(msg)); + + return; + } + + Session* session; + if (sessionId.isInvalid()) { + session = nullptr; + } else { + session = mSessionManager.findSession(sessionId); + } + + // TODO dispatch message +} diff --git a/server-v1/source/EpistmoolServer/Server.hpp b/server-v1/source/EpistmoolServer/Server.hpp new file mode 100644 index 0000000..5c2c6d2 --- /dev/null +++ b/server-v1/source/EpistmoolServer/Server.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include "EpistmoolServer/Connection.hpp" +#include "EpistmoolServer/Session.hpp" +#include "all_fwd.hpp" + +#include +#include + +namespace Epistmool::Server { + +class Server : public QObject +{ + Q_OBJECT + class Private; + +private: + ConnectionManager mConnectionManager; + SessionManager mSessionManager; + +public: + explicit Server(QObject* parent = nullptr); + +public slots: + void onMessage(const QJsonDocument& message, Epistmool::Server::ConnectionId connId); +}; + +} // namespace Epistmool::Server diff --git a/server-v1/source/EpistmoolServer/ServerProperties.cpp b/server-v1/source/EpistmoolServer/ServerProperties.cpp new file mode 100644 index 0000000..dc16b4b --- /dev/null +++ b/server-v1/source/EpistmoolServer/ServerProperties.cpp @@ -0,0 +1 @@ +#include "ServerProperties.hpp" diff --git a/server-v1/source/EpistmoolServer/ServerProperties.hpp b/server-v1/source/EpistmoolServer/ServerProperties.hpp new file mode 100644 index 0000000..5865442 --- /dev/null +++ b/server-v1/source/EpistmoolServer/ServerProperties.hpp @@ -0,0 +1,9 @@ +#pragma once + +#include "EpistmoolServer/Protocol/Version.hpp" + +namespace Epistmool::Server { +namespace ServerProperties { + const auto kVersion = ProtocolVersions::v0_1; +} +} // namespace Epistmool::Server diff --git a/server-v1/source/EpistmoolServer/Session.cpp b/server-v1/source/EpistmoolServer/Session.cpp new file mode 100644 index 0000000..6db51d8 --- /dev/null +++ b/server-v1/source/EpistmoolServer/Session.cpp @@ -0,0 +1,76 @@ +#include "Session.hpp" + +using namespace Epistmool::Server; + +namespace { +constexpr int kInvalidSessionIndex = -1; +} // namespace + +SessionId SessionId::makeInvalid() +{ + return SessionId{ + .index = kInvalidSessionIndex, + }; +} + +bool SessionId::isInvalid() const +{ + return index == kInvalidSessionIndex; +} + +SessionId Session::getSessionId() const +{ + return mSessionId; +} + +const std::vector& Session::getConnections() const +{ + return mConnections; +} + +Session::Session(SessionId id) + : mSessionId{ id } +{ +} + +// TODO delete session if isn't active for N period of time +SessionManager::SessionManager(QObject* parent) + : QObject(parent) +{ +} + +Session* SessionManager::findOrCreateSession(SessionId id) +{ + auto it = mSessions.find(id.index); + if (it != mSessions.end()) { + return &it->second; + } else { + SessionId newId{ mNextIndex++ }; + auto [it, _] = mSessions.insert({ newId.index, Session(newId) }); + return &it->second; + } +} + +Session* SessionManager::findSession(SessionId id) +{ + auto it = mSessions.find(id.index); + if (it != mSessions.end()) { + return &it->second; + } else { + return nullptr; + } +} + +void SessionManager::addConnection(SessionId id, ConnectionId connId) +{ + auto session = findSession(id); + if (!session) return; + + session->mConnections.push_back(connId); + mConnection2SessionMap.insert(connId.index, id.index); +} + +void SessionManager::dropConnection(ConnectionId connId) +{ + mConnection2SessionMap.remove(connId.index); +} diff --git a/server-v1/source/EpistmoolServer/Session.hpp b/server-v1/source/EpistmoolServer/Session.hpp new file mode 100644 index 0000000..931c428 --- /dev/null +++ b/server-v1/source/EpistmoolServer/Session.hpp @@ -0,0 +1,59 @@ +#pragma once + +#include "EpistmoolServer/Connection.hpp" + +#include +#include +#include +#include + +namespace Epistmool::Server { + +struct SessionId +{ + int index; + + static SessionId makeInvalid(); + bool isInvalid() const; +}; + +class Session +{ + friend class SessionManager; + +private: + std::vector mConnections; + SessionId mSessionId; + +public: + Session(SessionId id); + + SessionId getSessionId() const; + const std::vector& getConnections() const; +}; + +class SessionManager : public QObject +{ + Q_OBJECT + + friend class Session; + +private: + // Use this intead of QHash for 1. pointer stability 2. Session is a move-only type + std::unordered_map mSessions; + QMultiHash mConnection2SessionMap; + int mNextIndex; + +public: + explicit SessionManager(QObject* parent = nullptr); + + Session* findOrCreateSession(SessionId id); + Session* findSession(SessionId id); + + void addConnection(SessionId id, ConnectionId connId); + +public slots: + void dropConnection(Epistmool::Server::ConnectionId connId); +}; + +} // namespace Epistmool::Server diff --git a/server-v1/source/EpistmoolServer/Workspace/CMakeLists.txt b/server-v1/source/EpistmoolServer/Workspace/CMakeLists.txt new file mode 100644 index 0000000..6ef6d59 --- /dev/null +++ b/server-v1/source/EpistmoolServer/Workspace/CMakeLists.txt @@ -0,0 +1,3 @@ +target_sources(EpistmoolServer PRIVATE + fwd.hpp +) diff --git a/server-v1/source/EpistmoolServer/Workspace/fwd.hpp b/server-v1/source/EpistmoolServer/Workspace/fwd.hpp new file mode 100644 index 0000000..dd572f8 --- /dev/null +++ b/server-v1/source/EpistmoolServer/Workspace/fwd.hpp @@ -0,0 +1,5 @@ +#pragma once + +namespace Epistmool::Server +{ +} // namespace Epistmool::Server diff --git a/server-v1/source/EpistmoolServer/fwd.hpp b/server-v1/source/EpistmoolServer/fwd.hpp new file mode 100644 index 0000000..dfbcb91 --- /dev/null +++ b/server-v1/source/EpistmoolServer/fwd.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include "./Protocol/fwd.hpp" +#include "./Workspace/fwd.hpp" + +namespace Epistmool::Server +{ + +// Connection.hpp +struct ConnectionId; +class ConnectionManager; + +// Server.hpp +class Server; + +// Session.hpp +struct SessionId; +class Session; +class SessionManager; + +} // namespace Epistmool::Server diff --git a/server-v1/source/all_fwd.hpp b/server-v1/source/all_fwd.hpp new file mode 100644 index 0000000..6faf7e4 --- /dev/null +++ b/server-v1/source/all_fwd.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "./EpistmoolServer/fwd.hpp" diff --git a/server-v1/source/header_only.cpp b/server-v1/source/header_only.cpp new file mode 100644 index 0000000..83e2263 --- /dev/null +++ b/server-v1/source/header_only.cpp @@ -0,0 +1 @@ +// This file includes all header only libraries' implementation parts diff --git a/server-v1/source/main.cpp b/server-v1/source/main.cpp new file mode 100644 index 0000000..73e4006 --- /dev/null +++ b/server-v1/source/main.cpp @@ -0,0 +1,12 @@ +#include "EpistmoolServer/Server.hpp" + +#include + +using namespace Epistmool::Server; + +int main(int argc, char* argv[]) +{ + QCoreApplication app(argc, argv); + Server server; + return app.exec(); +} -- cgit v1.2.3-70-g09d2