#include "../include/dchat/Room.hpp" #include "../include/dchat/Cache.hpp" #include "../include/dchat/RoomDataType.hpp" #include #include #include #include // TODO: Remove error checks when odhtdb has been improved to take care of such errors namespace dchat { Room::Room(Rooms *_rooms, std::shared_ptr _id) : rooms(_rooms), id(_id), userdata(nullptr) { } std::shared_ptr 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(userPublicKey); userByPublicKey[userPublicKey] = user; return user; } std::shared_ptr 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) { assert(localUser); assert(publicKeyToKeyPairMap.find(localUser->publicKey) != publicKeyToKeyPairMap.end()); assert(encryptionKey); 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); rooms->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) { std::lock_guard lock(roomModifyMutex); 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 roomId = std::make_shared(*request.nodeHash); auto room = std::make_shared(this, roomId); auto encryptionKeyIt = roomEncryptionKey.find(*request.nodeHash); if(encryptionKeyIt != roomEncryptionKey.end()) room->encryptionKey = encryptionKeyIt->second; roomById[*request.nodeHash] = room; if(callbackFuncs.createRoomCallbackFunc) callbackFuncs.createRoomCallbackFunc(room); auto user = room->addUser(*request.creatorPublicKey, request.groupId); auto localUserIt = roomLocalUser.find(*request.nodeHash); if(localUserIt != roomLocalUser.end()) { room->localUser = user; room->publicKeyToKeyPairMap[user->publicKey] = localUserIt->second; } if(callbackFuncs.addUserCallbackFunc) callbackFuncs.addUserCallbackFunc(room, user); } void Rooms::addNodeCallbackFunc(const odhtdb::DatabaseAddNodeRequest &request) { std::lock_guard lock(roomModifyMutex); 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(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(); 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; } case RoomDataType::CHANGE_ROOM_NAME: { int userPermissionLevel = database->getUserLowestPermissionLevel(*room->id, user->publicKey); if(userPermissionLevel != odhtdb::PERMISSION_LEVEL_ADMIN) { odhtdb::Log::debug("Room change name: attempted by user %s who is not an admin (permission level: %d)\n", user->publicKey.toString().c_str(), userPermissionLevel); return; } sibs::SafeDeserializer deserializer((const u8*)request.decryptedData.data + 1, request.decryptedData.size - 1); u16 channelNameLength = deserializer.extract(); if(channelNameLength > 0 && channelNameLength <= 32) { RoomChangeNameRequest roomRequest; roomRequest.room = room; roomRequest.user = user; roomRequest.timestampSeconds = timestampSeconds; roomRequest.loadedFromCache = request.loadedFromCache; roomRequest.newName.resize(channelNameLength); deserializer.extract((u8*)&roomRequest.newName[0], channelNameLength); if(callbackFuncs.changeRoomNameCallbackFunc) callbackFuncs.changeRoomNameCallbackFunc(roomRequest); room->name = std::move(roomRequest.newName); } 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) { std::lock_guard lock(roomModifyMutex); 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(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) { std::lock_guard lock(roomModifyMutex); if(loggedIn) throw std::runtime_error(std::string("You are already logged in as ") + username); auto storedNodes = database->getStoredNodeUserInfoDecrypted(username, password); loggedIn = true; currentUsername = username; currentUserPassword = password; for(auto &nodeInfo : storedNodes) { roomLocalUser[nodeInfo.first] = nodeInfo.second.userKeyPair; roomEncryptionKey[nodeInfo.first] = nodeInfo.second.nodeEncryptionKey; auto roomId = std::make_shared(nodeInfo.first); odhtdb::DatabaseNode roomNode(nodeInfo.second.nodeEncryptionKey, roomId); database->seed(roomNode); database->loadNode(nodeInfo.first); } } void Rooms::registerUser(const std::string &username, const std::string &password) { std::lock_guard lock(roomModifyMutex); if(loggedIn) throw std::runtime_error(std::string("You are already logged in as ") + username); database->storeUserWithoutNodes(username, password); loggedIn = true; currentUsername = username; currentUserPassword = password; } void Rooms::createRoom(const std::string &name) { std::lock_guard lock(roomModifyMutex); if(!loggedIn) throw std::runtime_error("You need to be logged in to create a room "); auto newNode = database->create(); roomLocalUser[*newNode->getRequestHash()] = newNode->getNodeAdminKeyPair(); roomEncryptionKey[*newNode->getRequestHash()] = newNode->getNodeEncryptionKey(); auto roomIt = roomById.find(*newNode->getRequestHash()); if(roomIt != roomById.end()) { roomIt->second->encryptionKey = newNode->getNodeEncryptionKey(); auto user = roomIt->second->getUserByPublicKey(newNode->getNodeAdminKeyPair()->getPublicKey()); if(user) { roomIt->second->localUser = user; roomIt->second->publicKeyToKeyPairMap[user->publicKey] = newNode->getNodeAdminKeyPair(); } } odhtdb::DatabaseNode nodeInfo(newNode->getNodeEncryptionKey(), newNode->getRequestHash()); database->storeNodeInfoForUserEncrypted(nodeInfo, currentUsername, currentUserPassword, *newNode->getNodeAdminKeyPair()); sibs::SafeSerializer serializer; serializer.add(RoomDataType::CHANGE_ROOM_NAME); serializer.add((u16)name.size()); serializer.add(name.data(), name.size()); database->addData(nodeInfo, *newNode->getNodeAdminKeyPair(), { serializer.getBuffer().data(), serializer.getSize() }); } }