From 1a1d3b7dc56e173d46d89fd9c05cd295bb8bcbf2 Mon Sep 17 00:00:00 2001 From: dec05eba Date: Fri, 9 Nov 2018 09:45:13 +0100 Subject: Room join/invite, fix build for mingw --- src/Room.cpp | 264 +++++++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 240 insertions(+), 24 deletions(-) (limited to 'src/Room.cpp') diff --git a/src/Room.cpp b/src/Room.cpp index 95e5818..7a51760 100644 --- a/src/Room.cpp +++ b/src/Room.cpp @@ -1,9 +1,13 @@ #include "../include/dchat/Room.hpp" #include "../include/dchat/Cache.hpp" +#include "../include/dchat/FileUtil.hpp" #include "../include/dchat/RoomDataType.hpp" #include #include +#include +#include #include +#include #include // TODO: Remove error checks when odhtdb has been improved to take care of such errors @@ -14,13 +18,23 @@ namespace dchat id(_id), userdata(nullptr) { + + } + void Room::addUser(const odhtdb::Signature::PublicKey &userPublicKey, std::shared_ptr group) + { + assert(localUser); + assert(publicKeyToKeyPairMap.find(localUser->publicKey) != publicKeyToKeyPairMap.end()); + assert(encryptionKey); + auto keyPair = publicKeyToKeyPairMap[localUser->publicKey]; + odhtdb::DatabaseNode roomNode(encryptionKey, id); + rooms->database->addUser(roomNode, *keyPair, userPublicKey, odhtdb::DataView(group->id, odhtdb::GROUP_ID_LENGTH)); } - std::shared_ptr Room::addUser(const odhtdb::Signature::PublicKey &userPublicKey, const odhtdb::DataView groupId) + std::shared_ptr Room::addUserLocally(const odhtdb::Signature::PublicKey &userPublicKey, std::shared_ptr group) { - auto it = userByPublicKey.find(userPublicKey); - if(it != userByPublicKey.end()) + auto existingUser = getUserByPublicKey(userPublicKey); + if(existingUser) { odhtdb::Log::error("The user %s already exists in the room %s", userPublicKey.toString().c_str(), id->toString().c_str()); assert(false); @@ -28,10 +42,28 @@ namespace dchat } auto user = std::make_shared(userPublicKey); + user->groups.push_back(group); userByPublicKey[userPublicKey] = user; return user; } + std::shared_ptr Room::addGroupLocally(const odhtdb::DataView groupId) + { + auto existingGroup = getGroupById(groupId); + if(existingGroup) + { + odhtdb::Log::error("The group %s already exists in the room %s", groupId.toString().c_str(), id->toString().c_str()); + assert(false); + return nullptr; + } + + auto group = std::make_shared(); + assert(groupId.size == odhtdb::GROUP_ID_LENGTH); + memcpy(group->id, groupId.data, groupId.size); + groups.push_back(group); + return group; + } + std::shared_ptr Room::getUserByPublicKey(const odhtdb::Signature::PublicKey &userPublicKey) { auto userIt = userByPublicKey.find(userPublicKey); @@ -40,6 +72,88 @@ namespace dchat return nullptr; } + std::shared_ptr Room::getGroupById(const odhtdb::DataView groupId) + { + for(std::shared_ptr &group : groups) + { + if(odhtdb::DataView(group->id, odhtdb::GROUP_ID_LENGTH) == groupId) + return group; + } + return nullptr; + } + + static std::string getInviteKeyChecksum(const std::string &inviteKey) + { + u8 checksum = 0x3d; + for(char c : inviteKey) + { + checksum ^= c; + } + return odhtdb::bin2hex((const char*)&checksum, 1); + } + + void Room::setLocalUser(std::shared_ptr user, std::shared_ptr keyPair) + { + if(localUser) return; + assert(user->publicKey == keyPair->getPublicKey()); + assert(encryptionKey); + localUser = user; + publicKeyToKeyPairMap[user->publicKey] = keyPair; + odhtdb::Log::debug("Local user set to %s for room %s", user->publicKey.toString().c_str(), id->toString().c_str()); + + // Room id + room encryption key + invite key checksum (to verify invite key is not types incorrectly) + inviteKey = id->toString() + odhtdb::bin2hex((const char*)encryptionKey->data, encryptionKey->size); + inviteKey += getInviteKeyChecksum(inviteKey); + odhtdb::InfoHash inviteInfoHash = odhtdb::InfoHash::generateHash((const u8*)inviteKey.data(), inviteKey.size()); + rooms->database->receiveCustomMessage(inviteInfoHash, [this](const void *data, usize size) + { + sibs::SafeSerializer serializer; + try + { + odhtdb::Log::debug("Received request to invite user to room"); + sibs::SafeDeserializer deserializer((const u8*)data, size); + u8 nonce[odhtdb::ENCRYPTION_NONCE_BYTE_SIZE]; + deserializer.extract(nonce, sizeof(nonce)); + odhtdb::Decryption decryptedData( + { (void*)deserializer.getBuffer(), deserializer.getSize() }, + { nonce, sizeof(nonce) }, + { (void*)encryptionKey->data, encryptionKey->size }); + + sibs::SafeDeserializer deserializerMsg((const u8*)decryptedData.getDecryptedText().data, decryptedData.getDecryptedText().size); + odhtdb::Signature::PublicKey userPublicKey; + deserializerMsg.extract((u8*)userPublicKey.getData(), userPublicKey.getSize()); + auto user = getUserByPublicKey(userPublicKey); + if(user) + { + odhtdb::Log::warn("User %s wanted us to add them to the room %s but they are already a member of it", userPublicKey.toString().c_str(), id->toString().c_str()); + // TODO: Return error message to user, so they can get a nice error message (or deny message) + return serializer; + } + + std::string userMsg; + if(!deserializerMsg.empty()) + { + userMsg.resize(deserializerMsg.getSize()); + deserializerMsg.extract((u8*)&userMsg[0], deserializerMsg.getSize()); + } + + if(rooms->callbackFuncs.receiveInviteUserCallbackFunc) + { + InviteUserRequest request; + request.room = std::shared_ptr(this); + request.userPublicKey = std::move(userPublicKey); + request.message = std::move(userMsg); + rooms->callbackFuncs.receiveInviteUserCallbackFunc(request); + } + } + catch(std::exception &e) + { + odhtdb::Log::warn("Failed to deserialize request to invite user to room"); + } + return serializer; + }); + } + void Room::publishMessage(const std::string &msg) { assert(localUser); @@ -48,7 +162,7 @@ namespace dchat sibs::SafeSerializer serializer; serializer.add(RoomDataType::ADD_MESSAGE); serializer.add((const u8*)msg.data(), msg.size()); - auto &keyPair = publicKeyToKeyPairMap[localUser->publicKey]; + auto keyPair = publicKeyToKeyPairMap[localUser->publicKey]; odhtdb::DatabaseNode roomNode(encryptionKey, id); rooms->database->addData(roomNode, *keyPair, { serializer.getBuffer().data(), serializer.getSize() }); } @@ -71,7 +185,6 @@ namespace dchat if(roomIt != roomById.end()) { odhtdb::Log::error("Room %s has already been created once", request.nodeHash->toString().c_str()); - assert(false); return; } @@ -83,16 +196,28 @@ namespace dchat roomById[*request.nodeHash] = room; if(callbackFuncs.createRoomCallbackFunc) callbackFuncs.createRoomCallbackFunc(room); - - auto user = room->addUser(*request.creatorPublicKey, request.groupId); + + auto group = room->addGroupLocally(request.groupId); + auto user = room->addUserLocally(*request.creatorPublicKey, group); + bool isLocalUser = false; auto localUserIt = roomLocalUser.find(*request.nodeHash); if(localUserIt != roomLocalUser.end()) { - room->localUser = user; - room->publicKeyToKeyPairMap[user->publicKey] = localUserIt->second; + room->setLocalUser(user, localUserIt->second); + isLocalUser = true; } + if(callbackFuncs.addUserCallbackFunc) - callbackFuncs.addUserCallbackFunc(room, user); + { + RoomAddUserRequest callbackRequest; + callbackRequest.room = room; + callbackRequest.user = user; + callbackRequest.addedByUser = user; + callbackRequest.timestampSeconds = ntp::NtpTimestamp::fromCombined(request.timestamp).seconds; + callbackRequest.loadedFromCache = request.loadedFromCache; + callbackRequest.isLocalUser = isLocalUser; + callbackFuncs.addUserCallbackFunc(callbackRequest); + } } void Rooms::addNodeCallbackFunc(const odhtdb::DatabaseAddNodeRequest &request) @@ -102,7 +227,6 @@ namespace dchat 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; @@ -111,7 +235,6 @@ namespace dchat 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; } @@ -207,20 +330,61 @@ namespace dchat 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); + 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()); return; } - auto user = roomIt->second->addUser(*request.userToAddPublicKey, request.groupToAddUserTo); - auto localUserIt = roomLocalUser.find(*request.nodeHash); - if(localUserIt != roomLocalUser.end()) + auto room = roomIt->second; + auto group = room->getGroupById(request.groupToAddUserTo); + if(!group) + { + odhtdb::Log::error("User %s was added to node %s in a non-existing group %s", + request.userToAddPublicKey->toString().c_str(), + request.nodeHash->toString().c_str(), + request.groupToAddUserTo.toString().c_str()); + return; + } + + auto addedByUser = room->getUserByPublicKey(*request.creatorPublicKey); + if(!addedByUser) { - roomIt->second->localUser = user; - roomIt->second->publicKeyToKeyPairMap[user->publicKey] = localUserIt->second; + odhtdb::Log::error("User was added to room by admin that doesn't exist in room.... (bug?)"); + return; } + + auto user = room->addUserLocally(*request.userToAddPublicKey, group); + std::lock_guard waitingToJoinRoomLock(waitingToJoinRoomMutex); + bool isLocalUser = false; + auto waitingToJoinRoomIt = waitingToJoinRoom.find(*request.nodeHash); + if(waitingToJoinRoomIt != waitingToJoinRoom.end()) + { + room->setLocalUser(user, waitingToJoinRoomIt->second); + waitingToJoinRoom.erase(waitingToJoinRoomIt); + isLocalUser = true; + } + else + { + auto localUserIt = roomLocalUser.find(*request.nodeHash); + if(localUserIt != roomLocalUser.end()) + { + room->setLocalUser(user, localUserIt->second); + isLocalUser = true; + } + } + if(callbackFuncs.addUserCallbackFunc) - callbackFuncs.addUserCallbackFunc(roomIt->second, user); + { + RoomAddUserRequest callbackRequest; + callbackRequest.room = room; + callbackRequest.user = user; + callbackRequest.addedByUser = addedByUser; + callbackRequest.timestampSeconds = ntp::NtpTimestamp::fromCombined(request.timestamp).seconds; + callbackRequest.loadedFromCache = request.loadedFromCache; + callbackRequest.isLocalUser = isLocalUser; + callbackFuncs.addUserCallbackFunc(callbackRequest); + } } void Rooms::connect(const char *address, u16 port, RoomCallbackFuncs callbackFuncs) @@ -273,24 +437,24 @@ namespace dchat currentUserPassword = password; } - void Rooms::createRoom(const std::string &name) + std::shared_ptr 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 "); + 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()); + assert(roomIt != roomById.end()); 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(); + roomIt->second->setLocalUser(user, newNode->getNodeAdminKeyPair()); } } @@ -301,5 +465,57 @@ namespace dchat serializer.add((u16)name.size()); serializer.add(name.data(), name.size()); database->addData(nodeInfo, *newNode->getNodeAdminKeyPair(), { serializer.getBuffer().data(), serializer.getSize() }); + return roomIt->second; + } + + void Rooms::requestJoinRoom(const std::string &inviteKey, const std::string &message) + { + if(!loggedIn) + throw std::runtime_error("You need to be logged in to join a room"); + + assert(inviteKey.size() == 130); + std::lock_guard waitingToJoinRoomLock(waitingToJoinRoomMutex); + std::string roomIdHex = inviteKey.substr(0, 64); + std::string roomIdBin = odhtdb::hex2bin(roomIdHex.c_str(), roomIdHex.size()); + auto roomId = std::make_shared(); + memcpy(roomId->getData(), roomIdBin.data(), roomIdBin.size()); + if(waitingToJoinRoom.find(*roomId) != waitingToJoinRoom.end()) + { + fprintf(stderr, "You have already requested to join room %s\n", roomIdHex.c_str()); + return; + } + if(roomById.find(*roomId) != roomById.end()) + { + fprintf(stderr, "You are already a member of the room %s\n", roomIdHex.c_str()); + return; + } + std::string roomEncryptionKeyHex = inviteKey.substr(64, 64); + std::string roomEncryptionKeyStr = odhtdb::hex2bin(roomEncryptionKeyHex.c_str(), roomEncryptionKeyHex.size()); + auto roomEncryptionKey = std::make_shared(new u8[odhtdb::ENCRYPTION_KEY_BYTE_SIZE], odhtdb::ENCRYPTION_KEY_BYTE_SIZE); + memcpy(roomEncryptionKey->data, roomEncryptionKeyStr.data(), roomEncryptionKeyStr.size()); + std::string inviteKeyChecksum = inviteKey.substr(128, 2); + if(inviteKeyChecksum != getInviteKeyChecksum(inviteKey.substr(0, 128))) + { + fprintf(stderr, "Invalid invite key, checksum is incorrect. Did you type the invite key incorrectly?\n"); + return; + } + + auto ourNewUser = std::make_shared(); + sibs::SafeSerializer serializer; + serializer.add(ourNewUser->getPublicKey().getData(), ourNewUser->getPublicKey().getSize()); + serializer.add(message.data(), message.size()); + odhtdb::DataView encryptionData { serializer.getBuffer().data(), serializer.getSize() }; + odhtdb::DataView encryptionKey { (void*)roomEncryptionKey->data, roomEncryptionKey->size }; + odhtdb::Encryption encryption(encryptionData, encryptionKey); + + sibs::SafeSerializer serializerFinish; + serializerFinish.add((const u8*)encryption.getNonce().data, encryption.getNonce().size); + serializerFinish.add((const u8*)encryption.getCipherText().data, encryption.getCipherText().size); + odhtdb::InfoHash inviteInfoHash = odhtdb::InfoHash::generateHash((const u8*)inviteKey.data(), inviteKey.size()); + database->sendCustomMessage(inviteInfoHash, serializerFinish.getBuffer().data(), serializerFinish.getSize()); + waitingToJoinRoom[*roomId] = ourNewUser; + odhtdb::DatabaseNode roomNode(roomEncryptionKey, roomId); + database->seed(roomNode); + database->storeNodeInfoForUserEncrypted(roomNode, currentUsername, currentUserPassword, *ourNewUser); } } -- cgit v1.2.3