diff options
m--------- | depends/odhtdb | 0 | ||||
-rw-r--r-- | include/dchat/Room.hpp | 102 | ||||
-rw-r--r-- | include/dchat/RoomDataType.hpp | 16 | ||||
-rw-r--r-- | include/dchat/User.hpp | 16 | ||||
-rw-r--r-- | src/Room.cpp | 226 |
5 files changed, 360 insertions, 0 deletions
diff --git a/depends/odhtdb b/depends/odhtdb -Subproject e2eb4f72050a297668850deed91cc88860b6ad4 +Subproject 88149764207c7719b6a979c5eb4dea3269bdd3e diff --git a/include/dchat/Room.hpp b/include/dchat/Room.hpp new file mode 100644 index 0000000..4422a6f --- /dev/null +++ b/include/dchat/Room.hpp @@ -0,0 +1,102 @@ +#pragma once + +#include "utils.hpp" +#include "types.hpp" +#include "StringView.hpp" +#include "User.hpp" +#include <string> +#include <memory> +#include <vector> +#include <functional> +#include <odhtdb/Database.hpp> + +namespace dchat +{ + struct RoomMessage + { + odhtdb::Hash id; + std::shared_ptr<User> creator; + uint32_t timestampSeconds; + std::string text; + }; + + class Room + { + DISABLE_COPY(Room) + public: + Room(std::shared_ptr<odhtdb::Database> database, std::shared_ptr<odhtdb::Hash> id, std::shared_ptr<odhtdb::OwnedByteArray> encryptionKey); + std::shared_ptr<User> addUser(const odhtdb::Signature::PublicKey &userPublicKey, const odhtdb::DataView groupId); + // Returns null if user doesn't exist in room + std::shared_ptr<User> getUserByPublicKey(const odhtdb::Signature::PublicKey &userPublicKey); + void publishMessage(const std::string &msg); + + std::shared_ptr<odhtdb::Database> database; + std::shared_ptr<odhtdb::Hash> id; + std::shared_ptr<odhtdb::OwnedByteArray> encryptionKey; + std::string name; + odhtdb::Signature::MapPublicKey<std::shared_ptr<User>> userByPublicKey; + std::vector<RoomMessage> messages; + std::shared_ptr<User> localUser; + // Used for local users + odhtdb::Signature::MapPublicKey<std::shared_ptr<odhtdb::Signature::KeyPair>> publicKeyToKeyPairMap; + void *userdata; + }; + + class Rooms; + + struct RoomAddMessageRequest + { + std::shared_ptr<Room> room; + bool loadedFromCache; + RoomMessage message; + }; + + struct UserChangeNicknameRequest + { + std::shared_ptr<Room> room; + std::shared_ptr<User> user; + uint32_t timestampSeconds; + bool loadedFromCache; + std::string newNickname; + }; + + // if connection failed then @rooms is null and errMsg contains the error + using ConnectBoostrapNodeCallbackFunc = std::function<void(std::shared_ptr<Rooms> rooms, const char *errMsg)>; + using CreateRoomCallbackFunc = std::function<void(std::shared_ptr<Room> room)>; + using RoomAddUserCallbackFunc = std::function<void(std::shared_ptr<Room> room, std::shared_ptr<User> user)>; + using RoomAddMessageCallbackFunc = std::function<void(const RoomAddMessageRequest &request)>; + using UserChangeNicknameCallbackFunc = std::function<void(const UserChangeNicknameRequest &request)>; + struct RoomCallbackFuncs + { + ConnectBoostrapNodeCallbackFunc connectCallbackFunc; + CreateRoomCallbackFunc createRoomCallbackFunc; + RoomAddUserCallbackFunc addUserCallbackFunc; + RoomAddMessageCallbackFunc addMessageCallbackFunc; + UserChangeNicknameCallbackFunc userChangeNicknameCallbackFunc; + }; + + class Rooms + { + DISABLE_COPY(Rooms) + public: + // @callbackFuncs.connectCallbackFunc can't be null + static void connect(const char *address, u16 port, RoomCallbackFuncs callbackFuncs); + // Throws on failure + void loginUser(const std::string &username, const std::string &password); + // Throws on failure + void registerUser(const std::string &username, const std::string &password); + + std::shared_ptr<odhtdb::Database> database; + private: + Rooms(const char *address, u16 port, RoomCallbackFuncs callbackFuncs); + void createNodeCallbackFunc(const odhtdb::DatabaseCreateNodeRequest &request); + void addNodeCallbackFunc(const odhtdb::DatabaseAddNodeRequest &request); + void addUserCallbackFunc(const odhtdb::DatabaseAddUserRequest &request); + private: + odhtdb::MapHash<std::shared_ptr<Room>> roomById; + RoomCallbackFuncs callbackFuncs; + bool loggedIn; + odhtdb::MapHash<std::shared_ptr<odhtdb::Signature::KeyPair>> roomLocalUser; + odhtdb::MapHash<std::shared_ptr<odhtdb::OwnedByteArray>> roomEncryptionKey; + }; +}
\ No newline at end of file diff --git a/include/dchat/RoomDataType.hpp b/include/dchat/RoomDataType.hpp new file mode 100644 index 0000000..cef2a8a --- /dev/null +++ b/include/dchat/RoomDataType.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include "types.hpp" + +namespace dchat +{ + enum class RoomDataType : u8 + { + ADD_MESSAGE, + EDIT_MESSAGE, + DELETE_MESSAGE, + NICKNAME_CHANGE, + CHANGE_AVATAR, + CHANGE_ROOM_NAME, + }; +}
\ No newline at end of file diff --git a/include/dchat/User.hpp b/include/dchat/User.hpp new file mode 100644 index 0000000..4021c78 --- /dev/null +++ b/include/dchat/User.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include <string> +#include <odhtdb/Signature.hpp> + +namespace dchat +{ + class User + { + public: + User(const odhtdb::Signature::PublicKey &_publicKey) : publicKey(_publicKey), userdata(nullptr) {} + const odhtdb::Signature::PublicKey publicKey; + std::string nickname; + void *userdata; + }; +}
\ No newline at end of file diff --git a/src/Room.cpp b/src/Room.cpp new file mode 100644 index 0000000..cd6476d --- /dev/null +++ b/src/Room.cpp @@ -0,0 +1,226 @@ +#include "../include/dchat/Room.hpp" +#include "../include/dchat/Cache.hpp" +#include "../include/dchat/RoomDataType.hpp" +#include <odhtdb/Database.hpp> +#include <odhtdb/Log.hpp> +#include <sibs/SafeSerializer.hpp> +#include <thread> + +// TODO: Remove error checks when odhtdb has been improved to take care of such errors +namespace dchat +{ + Room::Room(std::shared_ptr<odhtdb::Database> _database, std::shared_ptr<odhtdb::Hash> _id, std::shared_ptr<odhtdb::OwnedByteArray> _encryptionKey) : + database(_database), + id(_id), + encryptionKey(_encryptionKey), + userdata(nullptr) + { + + } + + std::shared_ptr<User> Room::addUser(const odhtdb::Signature::PublicKey &userPublicKey, const odhtdb::DataView groupId) + { + auto it = userByPublicKey.find(userPublicKey); + if(it != userByPublicKey.end()) + { + odhtdb::Log::error("The user %s already exists in the room %s", userPublicKey.toString().c_str(), id->toString().c_str()); + assert(false); + return nullptr; + } + + auto user = std::make_shared<User>(userPublicKey); + userByPublicKey[userPublicKey] = user; + return user; + } + + std::shared_ptr<User> Room::getUserByPublicKey(const odhtdb::Signature::PublicKey &userPublicKey) + { + auto userIt = userByPublicKey.find(userPublicKey); + if(userIt != userByPublicKey.end()) + return userIt->second; + return nullptr; + } + + void Room::publishMessage(const std::string &msg) + { + sibs::SafeSerializer serializer; + serializer.add(RoomDataType::ADD_MESSAGE); + serializer.add((const u8*)msg.data(), msg.size()); + auto &keyPair = publicKeyToKeyPairMap[localUser->publicKey]; + odhtdb::DatabaseNode roomNode(encryptionKey, id); + database->addData(roomNode, *keyPair, { serializer.getBuffer().data(), serializer.getSize() }); + } + + Rooms::Rooms(const char *address, u16 port, RoomCallbackFuncs _callbackFuncs) : + callbackFuncs(_callbackFuncs), + loggedIn(false) + { + odhtdb::DatabaseCallbackFuncs databaseCallbackFuncs; + databaseCallbackFuncs.createNodeCallbackFunc = std::bind(&Rooms::createNodeCallbackFunc, this, std::placeholders::_1); + databaseCallbackFuncs.addNodeCallbackFunc = std::bind(&Rooms::addNodeCallbackFunc, this, std::placeholders::_1); + databaseCallbackFuncs.addUserCallbackFunc = std::bind(&Rooms::addUserCallbackFunc, this, std::placeholders::_1); + database = odhtdb::Database::connect(address, port, Cache::getDchatDir(), databaseCallbackFuncs).get(); + } + + void Rooms::createNodeCallbackFunc(const odhtdb::DatabaseCreateNodeRequest &request) + { + auto roomIt = roomById.find(*request.nodeHash); + if(roomIt != roomById.end()) + { + odhtdb::Log::error("Room %s has already been created once", request.nodeHash->toString().c_str()); + assert(false); + return; + } + + auto encryptionKey = roomEncryptionKey[*request.nodeHash]; + auto roomId = std::make_shared<odhtdb::Hash>(*request.nodeHash); + auto room = std::make_shared<Room>(database, roomId, encryptionKey); + roomById[*request.nodeHash] = room; + if(callbackFuncs.createRoomCallbackFunc) + callbackFuncs.createRoomCallbackFunc(room); + + auto user = room->addUser(*request.creatorPublicKey, request.groupId); + if(callbackFuncs.addUserCallbackFunc) + callbackFuncs.addUserCallbackFunc(room, user); + } + + void Rooms::addNodeCallbackFunc(const odhtdb::DatabaseAddNodeRequest &request) + { + auto roomIt = roomById.find(*request.nodeHash); + if(roomIt == roomById.end()) + { + odhtdb::Log::error("Attempting to add data to node %s but the node has not been created", request.nodeHash->toString().c_str()); + assert(false); + return; + } + auto room = roomIt->second; + + auto user = room->getUserByPublicKey(*request.creatorPublicKey); + if(!user) + { + odhtdb::Log::error("Attempting to add data to node %s but the the user %s doesn't exist in the node %s", request.creatorPublicKey->toString().c_str(), request.nodeHash->toString().c_str()); + assert(false); + return; + } + + if(request.decryptedData.size == 0) + return; + + uint32_t timestampSeconds = ntp::NtpTimestamp::fromCombined(request.timestamp).seconds; + + RoomDataType roomDataType = (RoomDataType)static_cast<const char*>(request.decryptedData.data)[0]; + try + { + switch(roomDataType) + { + case RoomDataType::ADD_MESSAGE: + { + RoomMessage message; + message.id = *request.requestHash; + message.creator = user; + message.timestampSeconds = timestampSeconds; + message.text = std::string((const char*)request.decryptedData.data + 1, request.decryptedData.size - 1); + + RoomAddMessageRequest roomRequest; + roomRequest.room = room; + roomRequest.loadedFromCache = request.loadedFromCache; + roomRequest.message = std::move(message); + if(callbackFuncs.addMessageCallbackFunc) + callbackFuncs.addMessageCallbackFunc(roomRequest); + + room->messages.push_back(std::move(roomRequest.message)); + break; + } + case RoomDataType::NICKNAME_CHANGE: + { + sibs::SafeDeserializer deserializer((const u8*)request.decryptedData.data + 1, request.decryptedData.size - 1); + u8 nameLength = deserializer.extract<u8>(); + if(nameLength > 0) + { + std::string nickname; + nickname.resize(nameLength); + deserializer.extract((u8*)&nickname[0], nameLength); + + UserChangeNicknameRequest roomRequest; + roomRequest.room = room; + roomRequest.user = user; + roomRequest.timestampSeconds = timestampSeconds; + roomRequest.loadedFromCache = request.loadedFromCache; + roomRequest.newNickname = nickname; + if(callbackFuncs.userChangeNicknameCallbackFunc) + callbackFuncs.userChangeNicknameCallbackFunc(roomRequest); + user->nickname = std::move(nickname); + } + break; + } + default: + break; + } + } + catch(std::exception &e) + { + odhtdb::Log::warn("Failed to process add node request, reason: %s\n", e.what()); + } + } + + void Rooms::addUserCallbackFunc(const odhtdb::DatabaseAddUserRequest &request) + { + auto roomIt = roomById.find(*request.nodeHash); + if(roomIt == roomById.end()) + { + odhtdb::Log::error("User %s was added to node %s but the node has not been created", request.userToAddPublicKey->toString().c_str(), request.nodeHash->toString().c_str()); + assert(false); + return; + } + + auto user = roomIt->second->addUser(*request.userToAddPublicKey, request.groupToAddUserTo); + auto localUserIt = roomLocalUser.find(*request.nodeHash); + if(localUserIt != roomLocalUser.end()) + { + roomIt->second->localUser = user; + roomIt->second->publicKeyToKeyPairMap[user->publicKey] = localUserIt->second; + } + if(callbackFuncs.addUserCallbackFunc) + callbackFuncs.addUserCallbackFunc(roomIt->second, user); + } + + void Rooms::connect(const char *address, u16 port, RoomCallbackFuncs callbackFuncs) + { + assert(callbackFuncs.connectCallbackFunc); + std::thread([](const char *address, u16 port, RoomCallbackFuncs callbackFuncs) + { + try + { + callbackFuncs.connectCallbackFunc(std::shared_ptr<Rooms>(new Rooms(address, port, callbackFuncs)), nullptr); + } + catch(std::exception &e) + { + callbackFuncs.connectCallbackFunc(nullptr, e.what()); + } + }, address, port, callbackFuncs).detach(); + } + + void Rooms::loginUser(const std::string &username, const std::string &password) + { + if(loggedIn) + throw std::runtime_error(std::string("You are already logged in as ") + username); + + auto storedNodes = database->getStoredNodeUserInfoDecrypted(username, password); + loggedIn = true; + for(auto &nodeInfo : storedNodes) + { + roomLocalUser[nodeInfo.first] = nodeInfo.second.userKeyPair; + roomEncryptionKey[nodeInfo.first] = nodeInfo.second.nodeEncryptionKey; + database->loadNode(nodeInfo.first); + } + } + + void Rooms::registerUser(const std::string &username, const std::string &password) + { + if(loggedIn) + throw std::runtime_error(std::string("You are already logged in as ") + username); + + database->storeUserWithoutNodes(username, password); + loggedIn = true; + } +}
\ No newline at end of file |