#include "../include/Channel.hpp" #include "../include/ChannelSidePanel.hpp" #include "../include/UsersSidePanel.hpp" #include "../include/ChannelTopPanel.hpp" #include "../include/Cache.hpp" #include "../include/ResourceCache.hpp" #include "../include/Video.hpp" #include "../include/Command.hpp" #include "../include/Settings.hpp" #include "../include/ColorScheme.hpp" #include "../include/GlobalContextMenu.hpp" #include "../include/StringUtils.hpp" #include "../include/ImagePreview.hpp" #include "../include/Rpc.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace std; using namespace dchat; static bool focused = true; static bool windowFocused = true; static void channelChangeUserNickname(Channel *channel, const StringView data, const odhtdb::Signature::PublicKey &userPublicKey) { auto user = channel->getUserByPublicKey(userPublicKey); if(!user) { fprintf(stderr, "Nickname change: user with public key %s not found in channel %s\n", userPublicKey.toString().c_str(), channel->getName().c_str()); return; } sibs::SafeDeserializer deserializer((const u8*)data.data, data.size); u8 nameLength = deserializer.extract(); if(nameLength > 0) { user->name.resize(nameLength); deserializer.extract((u8*)&user->name[0], nameLength); } // We dont care if there is more data to read (malicious packet), we already got all the data we need } static bool looksLikeUrl(const string &str) { if(str.find('.') == string::npos) return false; if(str.size() >= 7 && strncmp(str.c_str(), "http://", 7) == 0) return true; if(str.size() >= 8 && strncmp(str.c_str(), "https://", 8) == 0) return true; return false; } static void channelChangeUserAvatar(Channel *channel, const StringView data, const odhtdb::Signature::PublicKey &userPublicKey) { auto user = channel->getUserByPublicKey(userPublicKey); if(!user) { fprintf(stderr, "Avatar change: user with public key %s not found in channel %s\n", userPublicKey.toString().c_str(), channel->getName().c_str()); return; } sibs::SafeDeserializer deserializer((const u8*)data.data, data.size); u16 avatarLength = deserializer.extract(); if(avatarLength >= 10 && avatarLength <= 512) { string avatarUrl; avatarUrl.resize(avatarLength); deserializer.extract((u8*)&avatarUrl[0], avatarLength); if(looksLikeUrl(avatarUrl)) { user->avatarUrl = move(avatarUrl); } } // We dont care if there is more data to read (malicious packet), we already got all the data we need } static void channelChangeChannelName(Channel *channel, const StringView data, const odhtdb::Signature::PublicKey &userPublicKey) { auto user = channel->getUserByPublicKey(userPublicKey); if(!user) { fprintf(stderr, "Channel change name: user with public key %s not found in channel %s\n", userPublicKey.toString().c_str(), channel->getName().c_str()); return; } int userPermissionLevel = channel->getUserLowestPermissionLevel(user); if(userPermissionLevel != odhtdb::PERMISSION_LEVEL_ADMIN) { fprintf(stderr, "Channel change name: attempted by user %s who is not an admin\n", user->getName().c_str()); return; } sibs::SafeDeserializer deserializer((const u8*)data.data, data.size); u16 channelNameLength = deserializer.extract(); if(channelNameLength > 0 && channelNameLength <= 32) { string channelName; channelName.resize(channelNameLength); deserializer.extract((u8*)&channelName[0], channelNameLength); channel->setNameLocally(channelName); } // We dont care if there is more data to read (malicious packet), we already got all the data we need } static void channelAddDiscordMessage(Channel *channel, const StringView data, const odhtdb::Signature::PublicKey &userPublicKey, u64 timestamp, const odhtdb::Hash &requestHash) { auto bridgeOwner = channel->getUserByPublicKey(userPublicKey); if(!bridgeOwner) { fprintf(stderr, "Channel add discord message: user with public key %s not found in channel %s\n", userPublicKey.toString().c_str(), channel->getName().c_str()); return; } int userPermissionLevel = channel->getUserLowestPermissionLevel(bridgeOwner); if(userPermissionLevel != odhtdb::PERMISSION_LEVEL_ADMIN) { fprintf(stderr, "Channel add discord message: attempted by user %s who is not an admin\n", bridgeOwner->getName().c_str()); return; } sibs::SafeDeserializer deserializer((const u8*)data.data, data.size); u64 discordUserId = deserializer.extract(); u8 discordNameLength = deserializer.extract(); if(discordNameLength == 0) return; string discordUserName; discordUserName.resize(discordNameLength); deserializer.extract((u8*)&discordUserName[0], discordNameLength); usize msgSize = deserializer.getSize(); if(msgSize == 0) return; string msg(deserializer.getBuffer(), deserializer.getBuffer() + deserializer.getSize()); auto timestampSeconds = ntp::NtpTimestamp::fromCombined(timestamp).seconds; channel->addLocalDiscordMessage(discordUserName, discordUserId, msg, bridgeOwner, timestampSeconds, requestHash); // We dont care if there is more data to read (malicious packet), we already got all the data we need } static void channelAddStoredMessage(Channel *channel, const odhtdb::Hash &requestHash, const odhtdb::Signature::PublicKey &creatorPublicKey, const StringView decryptedObject, u64 timestamp, bool loadedFromCache) { User *user = channel->getUserByPublicKey(creatorPublicKey); if(!user) { fprintf(stderr, "Missing user? %s\n", creatorPublicKey.toString().c_str()); return; } if(decryptedObject.size > 1) { auto channelDataType = (ChannelDataType)static_cast(decryptedObject.data)[0]; StringView decryptedData((const char*)decryptedObject.data + 1, decryptedObject.size - 1); switch(channelDataType) { case ChannelDataType::ADD_MESSAGE: { string msg(decryptedData.data, decryptedData.size); Message *latestMessage = channel->getLatestMessage(); auto timestampSeconds = ntp::NtpTimestamp::fromCombined(timestamp).seconds; channel->addLocalMessage(msg, user, timestampSeconds, requestHash); if(!loadedFromCache && !windowFocused && channel->getLocalUser()->isOnlineUser() && (!latestMessage || (latestMessage && timestampSeconds >= latestMessage->timestampSeconds))) { auto onlineLocalUser = static_cast(channel->getLocalUser()); if(creatorPublicKey != onlineLocalUser->getPublicKey()) { stringReplaceChar(msg, "'", ""); stringReplaceChar(msg, "\\", ""); string cmd = "notify-send dchat '"; cmd += msg; cmd += "'"; system(cmd.c_str()); } } break; } case ChannelDataType::DELETE_MESSAGE: { sibs::SafeDeserializer deserializer((const u8*)decryptedData.data, decryptedData.size); odhtdb::Hash messageId; deserializer.extract((u8*)messageId.getData(), odhtdb::HASH_BYTE_SIZE); channel->deleteLocalMessage(messageId, creatorPublicKey); break; } case ChannelDataType::NICKNAME_CHANGE: { try { channelChangeUserNickname(channel, decryptedData, creatorPublicKey); } catch(sibs::DeserializeException &e) { fprintf(stderr, "Failed to deserialize nick change\n"); } break; } case ChannelDataType::CHANGE_AVATAR: { try { channelChangeUserAvatar(channel, decryptedData, creatorPublicKey); } catch(sibs::DeserializeException &e) { fprintf(stderr, "Failed to deserialize avatar change\n"); } break; } case ChannelDataType::CHANGE_CHANNEL_NAME: { try { channelChangeChannelName(channel, decryptedData, creatorPublicKey); } catch(sibs::DeserializeException &e) { fprintf(stderr, "Failed to deserialize channel name change\n"); } break; } case ChannelDataType::ADD_DISCORD_MESSAGE: { try { channelAddDiscordMessage(channel, decryptedData, creatorPublicKey, timestamp, requestHash); } catch(sibs::DeserializeException &e) { fprintf(stderr, "Failed to deserialize channel add discord message\n"); } break; } default: fprintf(stderr, "Got unexpected channel data type: %u\n", channelDataType); break; } } else { fprintf(stderr, "ADD_DATA packet too small, ignoring...\n"); } } int main(int argc, char **argv) { if(argc > 1) { boost::filesystem::path resourcesPath(argv[1]); boost::filesystem::current_path(resourcesPath); printf("Resource path set to: %s\n", resourcesPath.string().c_str()); } else printf("Resource directory not defined, using currently directory\n"); const sf::Int64 FRAMERATE_FOCUSED = 144; const sf::Int64 FRAMERATE_NOT_FOCUSED = 10; XInitThreads(); sf::RenderWindow window(sf::VideoMode(1280, 768), "dchat"); window.setVerticalSyncEnabled(false); window.setFramerateLimit(FRAMERATE_FOCUSED); //Video video(500, 500, "https://www.youtube.com/watch?v=bs0-EX9mJmg"); Cache cache; Cache::loadBindsFromFile(); Channel offlineChannel("Offline"); ChannelSidePanel::addChannel(&offlineChannel); Channel::setCurrent(&offlineChannel); vector channels; vector waitingToJoinChannels; odhtdb::MapHash localNodeUsers; string currentUsername; string currentPassword; recursive_mutex channelMessageMutex; bool waitingToJoin = false; bool loggedIn = false; sf::Clock lastFocusedTimer; odhtdb::Database *database = nullptr; odhtdb::DatabaseCallbackFuncs callbackFuncs; using LocalUserMessageCallback = function; LocalUserMessageCallback onMessageByLocalUser = nullptr; callbackFuncs.createNodeCallbackFunc = [&waitingToJoinChannels, &database, &channels, &channelMessageMutex, &waitingToJoin, &localNodeUsers, &lastFocusedTimer](const odhtdb::DatabaseCreateNodeRequest &request) { lock_guard lock(channelMessageMutex); //printf("Create node callback func %s\n", request.nodeHash->toString().c_str()); auto nodeUserData = localNodeUsers.find(*request.nodeHash); if(nodeUserData == localNodeUsers.end()) return; User *localUser; if(nodeUserData->second.userKeyPair->getPublicKey() == *request.creatorPublicKey) localUser = new OnlineLocalUser("NoName", *nodeUserData->second.userKeyPair); else localUser = new OfflineUser("You"); shared_ptr databaseNodeHash = make_shared(); memcpy(databaseNodeHash->getData(), request.nodeHash->getData(), odhtdb::HASH_BYTE_SIZE); odhtdb::DatabaseNode databaseNode(nodeUserData->second.nodeEncryptionKey, databaseNodeHash); Channel *channel = new Channel("NoChannelName", databaseNode, localUser, database); ChannelSidePanel::addChannel(channel); channels.push_back(channel); Channel::setCurrent(channel); lastFocusedTimer.restart(); if(localUser->type == User::Type::OFFLINE) { User *nodeCreatorUser = new OnlineRemoteUser("NoName", *request.creatorPublicKey); channel->addUserLocally(nodeCreatorUser); } for(vector::iterator it = waitingToJoinChannels.begin(); it != waitingToJoinChannels.end(); ++it) { if(*request.nodeHash == *it->getRequestHash()) { waitingToJoin = true; waitingToJoinChannels.erase(it); return; } } }; callbackFuncs.addNodeCallbackFunc = [&channels, &channelMessageMutex, &lastFocusedTimer, &onMessageByLocalUser](const odhtdb::DatabaseAddNodeRequest &request) { lock_guard lock(channelMessageMutex); //printf("Add node callback func %s\n", request.requestHash->toString().c_str()); for(Channel *channel : channels) { if(*request.nodeHash == *channel->getNodeInfo().getRequestHash()) { channelAddStoredMessage(channel, *request.requestHash, *request.creatorPublicKey, StringView((const char*)request.decryptedData.data, request.decryptedData.size), request.timestamp, request.loadedFromCache); if(channel == Channel::getCurrent()) lastFocusedTimer.restart(); if(!request.loadedFromCache && *request.creatorPublicKey == static_cast(channel->getLocalUser())->getPublicKey()) { if(onMessageByLocalUser) onMessageByLocalUser(request); } return; } } }; callbackFuncs.addUserCallbackFunc = [&channels, &channelMessageMutex, &waitingToJoin, &localNodeUsers, &lastFocusedTimer](const odhtdb::DatabaseAddUserRequest &request) { lock_guard lock(channelMessageMutex); printf("Add user callback. Channel to add user to: %s\n", request.nodeHash->toString().c_str()); for(Channel *channel : channels) { printf("My channel: %s (%s)\n", channel->getNodeInfo().getRequestHash()->toString().c_str(), channel->getName().c_str()); if(*request.nodeHash == *channel->getNodeInfo().getRequestHash()) { printf("Add user to one of my channels\n"); auto nodeUserData = localNodeUsers.find(*request.nodeHash); User *userToAdd = channel->getUserByPublicKey(*request.userToAddPublicKey); if(userToAdd) { fprintf(stderr, "User %s already exists in channel\n", request.userToAddPublicKey->toString().c_str()); return; } if(channel == Channel::getCurrent()) lastFocusedTimer.restart(); if(*request.userToAddPublicKey == nodeUserData->second.userKeyPair->getPublicKey()) { printf("You were added to channel %s by %s\n", request.nodeHash->toString().c_str(), request.creatorPublicKey->toString().c_str()); channel->replaceLocalUser(new OnlineLocalUser("NoName", *nodeUserData->second.userKeyPair)); waitingToJoin = false; return; } User *newRemoteUser = new OnlineRemoteUser("NoName", *request.userToAddPublicKey); channel->addUserLocally(newRemoteUser); return; } } }; database = new odhtdb::Database("bootstrap.ring.cx", 4222, Cache::getDchatDir(), callbackFuncs); auto addSystemMessage = [&lastFocusedTimer](const std::string &msg, bool plainText = true) { Channel::getCurrent()->addSystemMessage(msg, plainText); lastFocusedTimer.restart(); }; // Login to account Command::add("login", [&localNodeUsers, &database, &channels, &loggedIn, &channelMessageMutex, ¤tUsername, ¤tPassword](const vector &args) { lock_guard lock(channelMessageMutex); if(args.size() != 2) { fprintf(stderr, "Expected 2 arguments for command login (username and password), got %u argument(s)\n", args.size()); return; } try { localNodeUsers = database->getStoredNodeUserInfoDecrypted(args[0], args[1]); ChannelSidePanel::removeAllChannels(); for(Channel *channel : channels) { delete channel; } channels.clear(); printf("Loading %u channel(s) for user\n", localNodeUsers.size()); for(auto &localNodeUser : localNodeUsers) { database->loadNode(localNodeUser.first); } printf("Successfully logged into user %s\n", args[0].c_str()); currentUsername = args[0]; currentPassword = args[1]; loggedIn = true; } catch(std::exception &e) { fprintf(stderr, "Failed to login, reason: %s\n", e.what()); } }); // Register account Command::add("register", [&localNodeUsers, &database, &channels, &loggedIn, &channelMessageMutex, ¤tUsername, ¤tPassword](const vector &args) { lock_guard lock(channelMessageMutex); if(args.size() != 2) { fprintf(stderr, "Expected 2 arguments for command register (username and password), got %u argument(s)\n", args.size()); return; } try { database->storeUserWithoutNodes(args[0], args[1]); } catch(odhtdb::SqlExecException &e) { fprintf(stderr, "User with name %s already exists in storage\n", args[0].c_str()); return; } printf("Registered user %s\n", args[0].c_str()); ChannelSidePanel::removeAllChannels(); for(Channel *channel : channels) { delete channel; } channels.clear(); localNodeUsers.clear(); printf("Successfully logged into user %s\n", args[0].c_str()); currentUsername = args[0]; currentPassword = args[1]; loggedIn = true; }); // TODO: Use database->addData to change channel name // Create channel Command::add("cc", [&database, &channels, &channelMessageMutex, &loggedIn, &localNodeUsers, ¤tUsername, ¤tPassword, &lastFocusedTimer, addSystemMessage](const vector &args) { lock_guard lock(channelMessageMutex); if(args.size() != 1) { fprintf(stderr, "Expected 1 argument for command cc (channel name), got %u argument(s)\n", args.size()); return; } if(!loggedIn) { fprintf(stderr, "You are not logged in. Please login before creating a channel\n"); return; } auto createResponse = database->create(); printf("Created channel %s\n", createResponse->getRequestHash()->toString().c_str()); User *newLocalUser = new OnlineLocalUser("NoName", *createResponse->getNodeAdminKeyPair()); odhtdb::DatabaseNode databaseNode(createResponse->getNodeEncryptionKey(), createResponse->getRequestHash()); Channel *channel = new Channel(args[0], databaseNode, newLocalUser, database); ChannelSidePanel::addChannel(channel); channels.push_back(channel); Channel::setCurrent(channel); channel->setName(args[0]); lastFocusedTimer.restart(); localNodeUsers[*createResponse->getRequestHash()] = { createResponse->getNodeEncryptionKey(), createResponse->getNodeAdminKeyPair() }; database->storeNodeInfoForUserEncrypted(databaseNode, currentUsername, currentPassword, *createResponse->getNodeAdminKeyPair()); addSystemMessage("Channel created and stored in database"); }); // Create invite key Command::add("invite", [&channelMessageMutex, &offlineChannel, &database, addSystemMessage](const vector &args) { lock_guard lock(channelMessageMutex); if(args.size() != 0) { fprintf(stderr, "Expected 0 arguments for command invite, got %u argument(s)\n", args.size()); return; } Channel *currentChannel = Channel::getCurrent(); if(currentChannel == &offlineChannel) { addSystemMessage("You need to be in a channel to create an invite key"); return; } // TODO: Verify user has permission to add users before generating invite key, otherwise invite will fail and the users attempting to join will wait in futile auto channelNodeHash = currentChannel->getNodeInfo().getRequestHash(); auto channelEncryptionKey = currentChannel->getNodeInfo().getNodeEncryptionKey(); shared_ptr encryptionKey = make_shared(new u8[odhtdb::ENCRYPTION_KEY_BYTE_SIZE], odhtdb::ENCRYPTION_KEY_BYTE_SIZE); odhtdb::Encryption::generateKey((unsigned char*)encryptionKey->data); string inviteKey = odhtdb::bin2hex((const char*)channelNodeHash->getData(), channelNodeHash->getSize()); inviteKey += '&'; inviteKey += odhtdb::bin2hex((const char*)encryptionKey->data, odhtdb::ENCRYPTION_KEY_BYTE_SIZE); string msg = "You are now listening for users to join the channel using the key: "; msg += inviteKey; addSystemMessage(msg); printf("%s\n", msg.c_str()); sibs::SafeSerializer keySerializer; keySerializer.add((const u8*)channelNodeHash->getData(), channelNodeHash->getSize()); keySerializer.add((const u8*)encryptionKey->data, odhtdb::ENCRYPTION_KEY_BYTE_SIZE); dht::InfoHash key = odhtdb::Database::getInfoHash(keySerializer.getBuffer().data(), keySerializer.getBuffer().size()); database->receiveCustomMessage(key, [&channelMessageMutex, encryptionKey, channelEncryptionKey, currentChannel, &database](const void *data, usize size) { // TODO: User can remove channel @currentChannel before we get here, meaning @currentChannel is deleted and would be invalid; causing the program to crash try { sibs::SafeDeserializer deserializer((const u8*)data, size); u8 userToAddPublicKeyRaw[odhtdb::PUBLIC_KEY_NUM_BYTES]; deserializer.extract(userToAddPublicKeyRaw, odhtdb::PUBLIC_KEY_NUM_BYTES); odhtdb::Signature::PublicKey userToAddPublicKey((const char*)userToAddPublicKeyRaw, odhtdb::PUBLIC_KEY_NUM_BYTES); string unsignedEncryptedMsg = userToAddPublicKey.unsign(odhtdb::DataView((void*)deserializer.getBuffer(), deserializer.getSize())); sibs::SafeDeserializer encryptedDataDeserializer((const u8*)unsignedEncryptedMsg.data(), unsignedEncryptedMsg.size()); u8 nonce[odhtdb::ENCRYPTION_NONCE_BYTE_SIZE]; encryptedDataDeserializer.extract(nonce, odhtdb::ENCRYPTION_NONCE_BYTE_SIZE); odhtdb::Decryption decryptedMsg(odhtdb::DataView((void*)encryptedDataDeserializer.getBuffer(), encryptedDataDeserializer.getSize()), odhtdb::DataView(nonce, odhtdb::ENCRYPTION_NONCE_BYTE_SIZE), encryptionKey->getView()); // TODO: Create GUI for accepting users into channel instead of accepting ALL users sibs::SafeSerializer encryptedDataSerializer; encryptedDataSerializer.add((const u8*)channelEncryptionKey->data, channelEncryptionKey->size); encryptedDataSerializer.add((const u8*)userToAddPublicKey.getData(), userToAddPublicKey.getSize()); odhtdb::Encryption encryptedChannelKey(odhtdb::DataView((void*)encryptedDataSerializer.getBuffer().data(), encryptedDataSerializer.getBuffer().size()), encryptionKey->getView()); sibs::SafeSerializer serializer; serializer.add((const u8*)encryptedChannelKey.getNonce().data, encryptedChannelKey.getNonce().size); serializer.add((const u8*)encryptedChannelKey.getCipherText().data, encryptedChannelKey.getCipherText().size); const auto &localUserPublicKey = static_cast(currentChannel->getLocalUser())->getPublicKey(); auto localUserGroups = database->getUserGroups(*currentChannel->getNodeInfo().getRequestHash(), localUserPublicKey); if(localUserGroups.empty()) { fprintf(stderr, "No group to add user to...\n"); return sibs::SafeSerializer(); } lock_guard lock(channelMessageMutex); currentChannel->addUser(userToAddPublicKey, localUserGroups[0].getView()); return serializer; } catch(std::exception &e) { fprintf(stderr, "Failed while parsing user data to add to channel, reason: %s\n", e.what()); } return sibs::SafeSerializer(); }); }); // Join channel using invite key Command::add("jc", [&loggedIn, &database, &localNodeUsers, &channelMessageMutex, &waitingToJoin, &waitingToJoinChannels, ¤tUsername, ¤tPassword](const vector &args) { lock_guard lock(channelMessageMutex); if(args.size() != 1) { fprintf(stderr, "Expected 1 argument for command jc (channel join key), got %u argument(s)\n", args.size()); return; } if(args[0].size() != 129) { fprintf(stderr, "Expected join key to be 129 characters, was %u character(s)\n", args[0].size()); return; } if(!loggedIn) { fprintf(stderr, "You are not logged in. Please login before joining a channel\n"); return; } string nodeHashBinRaw = odhtdb::hex2bin(args[0].c_str(), 64); shared_ptr nodeHash = make_shared(); memcpy(nodeHash->getData(), nodeHashBinRaw.data(), nodeHashBinRaw.size()); auto nodeUserIt = localNodeUsers.find(*nodeHash); if(nodeUserIt != localNodeUsers.end()) { fprintf(stderr, "You have already joined the channel %s\n", args[0].c_str()); return; } string encryptionKeyBinRaw = odhtdb::hex2bin(args[0].c_str() + 65, 64); shared_ptr encryptionKey = make_shared(new u8[odhtdb::ENCRYPTION_KEY_BYTE_SIZE], odhtdb::ENCRYPTION_KEY_BYTE_SIZE); memcpy(encryptionKey->data, encryptionKeyBinRaw.data(), encryptionKeyBinRaw.size()); shared_ptr keyPair = make_shared(); sibs::SafeSerializer serializer; serializer.add((const u8*)keyPair->getPublicKey().getData(), keyPair->getPublicKey().getSize()); const char *msg = "please let me join"; odhtdb::Encryption encryptedJoinMsg(odhtdb::DataView((void*)msg, strlen(msg)), encryptionKey->getView()); sibs::SafeSerializer encryptedDataSerializer; encryptedDataSerializer.add((const u8*)encryptedJoinMsg.getNonce().data, encryptedJoinMsg.getNonce().size); encryptedDataSerializer.add((const u8*)encryptedJoinMsg.getCipherText().data, encryptedJoinMsg.getCipherText().size); string signedEncryptedMsg = keyPair->getPrivateKey().sign(odhtdb::DataView(encryptedDataSerializer.getBuffer().data(), encryptedDataSerializer.getBuffer().size())); serializer.add((const u8*)signedEncryptedMsg.data(), signedEncryptedMsg.size()); sibs::SafeSerializer keySerializer; keySerializer.add((const u8*)nodeHash->getData(), nodeHash->getSize()); keySerializer.add((const u8*)encryptionKeyBinRaw.data(), odhtdb::ENCRYPTION_KEY_BYTE_SIZE); dht::InfoHash key = odhtdb::Database::getInfoHash(keySerializer.getBuffer().data(), keySerializer.getBuffer().size()); database->sendCustomMessage(key, move(serializer.getBuffer()), [&database, nodeHash, encryptionKey, &waitingToJoinChannels, &channelMessageMutex, keyPair, ¤tUsername, ¤tPassword, &localNodeUsers](bool gotResponse, const void *data, usize size) { if(!gotResponse) { printf("We didn't get a response from anybody in the channel. Is there nobody that can add us (nobody with the permission required to do so or no online users) or is the node hash invalid?\n"); return false; } try { sibs::SafeDeserializer deserializer((const u8*)data, size); u8 nonce[odhtdb::ENCRYPTION_NONCE_BYTE_SIZE]; deserializer.extract(nonce, odhtdb::ENCRYPTION_NONCE_BYTE_SIZE); odhtdb::Decryption decryptedMsg(odhtdb::DataView((void*)deserializer.getBuffer(), deserializer.getSize()), odhtdb::DataView(nonce, odhtdb::ENCRYPTION_NONCE_BYTE_SIZE), encryptionKey->getView()); if(decryptedMsg.getDecryptedText().size != odhtdb::ENCRYPTION_KEY_BYTE_SIZE + odhtdb::PUBLIC_KEY_NUM_BYTES) { fprintf(stderr, "Invite response was of unexpected size, maybe it wasn't meant for us?\n"); return true; } odhtdb::DataView channelEncryptionKeyRaw(decryptedMsg.getDecryptedText().data, odhtdb::ENCRYPTION_KEY_BYTE_SIZE); odhtdb::DataView invitedUserPublicKey((u8*)decryptedMsg.getDecryptedText().data + odhtdb::ENCRYPTION_KEY_BYTE_SIZE, odhtdb::PUBLIC_KEY_NUM_BYTES); if(memcmp(keyPair->getPublicKey().getData(), invitedUserPublicKey.data, odhtdb::PUBLIC_KEY_NUM_BYTES) != 0) { fprintf(stderr, "Invite response was not meant for us\n"); return true; } shared_ptr channelEncryptionKey = make_shared(new u8[channelEncryptionKeyRaw.size], channelEncryptionKeyRaw.size); memcpy(channelEncryptionKey->data, channelEncryptionKeyRaw.data, channelEncryptionKeyRaw.size); odhtdb::DatabaseNode databaseNode(channelEncryptionKey, nodeHash); localNodeUsers[*nodeHash] = { channelEncryptionKey, keyPair }; database->storeNodeInfoForUserEncrypted(databaseNode, currentUsername, currentPassword, *keyPair); printf("Got a response from a person in the channel, we might get added...\n"); waitingToJoinChannels.push_back(databaseNode); lock_guard lock(channelMessageMutex); database->seed(databaseNode, odhtdb::DatabaseFetchOrder::NEWEST_FIRST); return false; } catch(std::exception &e) { fprintf(stderr, "Failed while parsing join response for invite link, reason: %s\n", e.what()); } return true; }); waitingToJoin = true; }); // Scale UI Command::add("scale", [](const vector &args) { if(args.size() != 1) { fprintf(stderr, "Expected 1 argument for command scale, got %u argument(s)\n", args.size()); return; } float scaling = stof(args[0]); Settings::setScaling(scaling); printf("UI scaling set to %f\n", scaling); }); Command::add("addbind", [addSystemMessage](const vector &args) { if(args.size() != 2) { string errMsg = "Expected 2 arguments for command addbind, got "; errMsg += to_string(args.size()); errMsg += " argument(s)"; addSystemMessage(errMsg); return; } string key = ":"; key += args[0]; key += ":"; if(key.size() > 255) { // 253 = bind + two colons addSystemMessage("Bind is too long. Max size is 253 bytes"); return; } bool bindAdded = Chatbar::addBind(key, args[1]); if(bindAdded) addSystemMessage("Bind added"); else addSystemMessage("Bind already exists. Remove it first if you want to replace it"); }); Command::add("removebind", [addSystemMessage](const vector &args) { if(args.size() != 1) { string errMsg = "Expected 1 argument for command removebind, got "; errMsg += to_string(args.size()); errMsg += " argument(s)"; addSystemMessage(errMsg); return; } string key = ":"; key += args[0]; key += ":"; if(key.size() > 255) { // 253 = bind + two colons addSystemMessage("Bind is too long. Max size is 253 bytes"); return; } bool bindRemoved = Chatbar::removeBind(key); if(bindRemoved) addSystemMessage("Bind removed"); else addSystemMessage("Bind doesn't exist, nothing was removed"); }); Command::add("binds", [addSystemMessage](const vector &args) { if(args.size() != 0) { string errMsg = "Expected 0 arguments for command removebind, got "; errMsg += to_string(args.size()); errMsg += " argument(s)"; addSystemMessage(errMsg); return; } string msg = "Binds:"; auto binds = Chatbar::getBinds(); for(auto &bind : binds) { msg += "\n"; msg += bind.first; msg += " "; msg += bind.second; } addSystemMessage(msg, false); }); // Change nick of current user in current channel Command::add("nick", [&loggedIn, &offlineChannel, addSystemMessage](const vector &args) { if(args.size() != 1) { string errMsg = "Expected 1 argument for command nick, got "; errMsg += to_string(args.size()); errMsg += " argument(s)"; addSystemMessage(errMsg); return; } if(!loggedIn) { addSystemMessage("You need to be logged in to change your nickname"); return; } if(Channel::getCurrent() == &offlineChannel) { addSystemMessage("You need to be in a channel to change your nickname"); return; } if(args[0].size() == 0 || args[0].size() > 255) { addSystemMessage("Invalid nickname. Nickname has to be between 1 and 255 characters"); return; } Channel::getCurrent()->changeNick(args[0]); string msg = "Your nickname was changed to "; msg += args[0]; addSystemMessage(msg); }); // Change avatar of current user in current channel Command::add("avatar", [&loggedIn, &offlineChannel, addSystemMessage](const vector &args) { if(args.size() != 1) { string errMsg = "Expected 1 argument for command avatar, got "; errMsg += to_string(args.size()); errMsg += " argument(s)"; addSystemMessage(errMsg); return; } if(!loggedIn) { addSystemMessage("You need to be logged in to change your avatar"); return; } if(Channel::getCurrent() == &offlineChannel) { addSystemMessage("You need to be in a channel to change your avatar"); return; } if(args[0].size() < 10 || args[0].size() > 512) { addSystemMessage("Invalid avatar url size, expected to be between 10 and 512 bytes"); return; } if(looksLikeUrl(args[0])) { Channel::getCurrent()->setAvatar(args[0]); addSystemMessage("Your avatar has been changed (Note: max avatar size is 1 Mb, if your avatar is larger then it will not be visible)"); } else { addSystemMessage("Avatar url needs to start with either http:// or https:// and include a dot"); } }); // Change name of the current channel Command::add("channelname", [&offlineChannel, addSystemMessage](const vector &args) { if(args.size() != 1) { string errMsg = "Expected 1 argument for command channelname, got "; errMsg += to_string(args.size()); errMsg += " argument(s)"; addSystemMessage(errMsg); return; } Channel *currentChannel = Channel::getCurrent(); if(currentChannel == &offlineChannel) { addSystemMessage("You need to be in a channel to change channel name"); return; } if(!currentChannel->getLocalUser()->isOnlineUser()) { addSystemMessage("You need to be logged in to change channel name"); return; } if(args[0].size() == 0 || args[0].size() > 32) { addSystemMessage("Channel name has to be between 1 and 32 bytes long"); return; } int localUserPermissionLevel = currentChannel->getUserLowestPermissionLevel(static_cast(currentChannel->getLocalUser())); if(localUserPermissionLevel != odhtdb::PERMISSION_LEVEL_ADMIN) { addSystemMessage("You need to be admin to change channel name"); return; } currentChannel->setName(args[0]); string msg = "Channel name has been changed to "; msg += args[0]; addSystemMessage(msg); }); Command::add("clearcache", [&database](const vector &args) { printf("Cleared cache (%d bytes)\n", database->clearCache()); }); string commandsMsg = "Available commands: "; for(const auto &commandIt : Command::getCommands()) { commandsMsg += "\n/"; commandsMsg += commandIt.first; } addSystemMessage(commandsMsg); odhtdb::MapHash myDiscordIdsByChannel; bool running = true; thread rpcThread([&running, &onMessageByLocalUser, &channels, &lastFocusedTimer, &myDiscordIdsByChannel]() { Rpc rpc(5555); mutex messageMutex; vector messagesToSend; onMessageByLocalUser = [&messagesToSend, &messageMutex](const odhtdb::DatabaseAddNodeRequest &request) { lock_guard lock(messageMutex); auto channelDataType = (ChannelDataType)static_cast(request.decryptedData.data)[0]; usize size = request.decryptedData.size - 1; const char *data = (const char*)request.decryptedData.data + 1; const char *action = nullptr; if(channelDataType == ChannelDataType::ADD_MESSAGE) action = "addMessage"; if(!action) return; vector msg = { action, string(data, data + size), request.nodeHash->toString() }; msgpack::sbuffer buffer; msgpack::pack(buffer, msg); messagesToSend.emplace_back(move(buffer)); }; while(running) { rpc.recv([&channels, &lastFocusedTimer, &myDiscordIdsByChannel](zmq::message_t *message) { try { msgpack::object_handle oh = msgpack::unpack((const char*)message->data(), message->size()); auto deserialized = oh.get(); vector msg; deserialized.convert(msg); if(msg.size() < 2) { fprintf(stderr, "Rpc receive, data length expected to be at least 2, was %u\n", msg.size()); return; } auto &action = msg[0]; string dchatChannelIdRaw = odhtdb::hex2bin(msg[1].c_str(), msg[1].size()); odhtdb::Hash dchatChannelId; memcpy(dchatChannelId.getData(), dchatChannelIdRaw.data(), dchatChannelIdRaw.size()); Channel *bridgedChannel = nullptr; for(Channel *channel : channels) { if(*channel->getNodeInfo().getRequestHash() == dchatChannelId) { bridgedChannel = channel; break; } } if(!bridgedChannel) { fprintf(stderr, "Rcp addMessage, invalid dchat channel %s\n", msg[1].c_str()); return; } if(bridgedChannel == Channel::getCurrent()) lastFocusedTimer.restart(); fprintf(stderr, "Received rpc, action: %s\n", action.c_str()); if(action == "addMessage") { if((msg.size() - 2) % 4 != 0) { fprintf(stderr, "Rpc addMessage, request was malformed\n"); return; } for(size_t i = 2; i < msg.size(); i += 4) { auto &content = msg[i]; auto &discordUserId = msg[i + 1]; u64 discordUserIdNumber = 0; auto &discordUserName = msg[i + 2]; auto &messageTimestampMillisec = msg[i + 3]; u64 messageTimestampSecondsNumber = 0; try { discordUserIdNumber = stoull(discordUserId); } catch(...) { fprintf(stderr, "Rpc receive, failed to convert discord id to uint64_t: %s\n", discordUserId.c_str()); return; } try { messageTimestampSecondsNumber = stoull(messageTimestampMillisec) / 1000; } catch(...) { fprintf(stderr, "Rpc receive, failed to convert discord message timestamp to uint64_t: %s\n", messageTimestampMillisec.c_str()); return; } auto myDiscordIdIt = myDiscordIdsByChannel.find(dchatChannelId); if(myDiscordIdIt != myDiscordIdsByChannel.end() && myDiscordIdIt->second == discordUserIdNumber) { auto &channelMessages = bridgedChannel->getMessageBoard().getMessages(); Message *myLatestMessage = nullptr; for(auto it = channelMessages.rbegin(), end = channelMessages.rend(); it != end; ++it) { if((*it)->user == bridgedChannel->getLocalUser()) { myLatestMessage = *it; break; } } if(myLatestMessage && (i64)messageTimestampSecondsNumber - (i64)myLatestMessage->timestampSeconds <= 3) { return; /* auto myMessageUtf8 = myLatestMessage->text.getString().toUtf8(); odhtdb::Hash myMessageHash(myMessageUtf8.data(), myMessageUtf8.size()); odhtdb::Hash myDiscordMessageHash(content.data(), content.size()); if(myMessageHash == myDiscordMessageHash) return; */ } } bridgedChannel->addLocalDiscordMessage(discordUserName, discordUserIdNumber, content, bridgedChannel->getLocalUser(), messageTimestampSecondsNumber, odhtdb::Hash(messageTimestampMillisec.c_str(), messageTimestampMillisec.size())); } } else if(action == "addUser") { if((msg.size() - 2) % 4 != 0) { fprintf(stderr, "Rpc addUser, request was malformed\n"); return; } for(size_t i = 2; i < msg.size(); i += 4) { auto &discordUsername = msg[i]; auto &discordUserId = msg[i + 1]; auto &userStatus = msg[i + 2]; auto &avatarURL = msg[i + 3]; try { u64 discordUserIdNumber = stoull(discordUserId); bool online = (userStatus != "offline"); printf("Rpc, adding user %s with status %s\n", discordUsername.c_str(), userStatus.c_str()); DiscordServiceUser *discordUser = bridgedChannel->getDiscordService()->getUserById(discordUserIdNumber); if(discordUser) { discordUser->connected = online; } else { discordUser = new DiscordServiceUser(discordUsername, discordUserIdNumber, online); bridgedChannel->getDiscordService()->addUser(discordUser); } discordUser->avatarUrl = avatarURL; } catch(...) { fprintf(stderr, "Warning: Rpc receive, failed to convert discord id to uint64_t: %s\n", discordUserId.c_str()); // Ignore for now.. should we really handle this error other than showing warning? } } } else if(action == "removeUser") { for(size_t i = 2; i < msg.size(); ++i) { auto &discordUserId = msg[i]; try { u64 discordUserIdNumber = stoull(discordUserId); bridgedChannel->getDiscordService()->removeUser(discordUserIdNumber); } catch(...) { fprintf(stderr, "Warning: Rpc receive, failed to convert discord id to uint64_t: %s\n", discordUserId.c_str()); // Ignore for now.. should we really handle this error other than showing warning? } } } else if(action == "statusChange") { if((msg.size() - 2) != 2) { fprintf(stderr, "Rpc statusChange, request was malformed\n"); return; } auto &discordUserId = msg[2]; auto &userStatus = msg[3]; try { u64 discordUserIdNumber = stoull(discordUserId); DiscordServiceUser *discordUser = bridgedChannel->getDiscordService()->getUserById(discordUserIdNumber); if(discordUser) { discordUser->connected = (userStatus != "offline"); printf("Rcp statusChange, changed user %s (%s) status to %s\n", discordUserId.c_str(), discordUser->getName().c_str(), userStatus.c_str()); } } catch(...) { fprintf(stderr, "Warning: Rpc receive, failed to convert discord id to uint64_t: %s\n", discordUserId.c_str()); // Ignore for now.. should we really handle this error other than showing warning? } } else if(action == "addMe") { if((msg.size() - 2) != 1) { fprintf(stderr, "Rpc addMe, request was malformed\n"); return; } auto &myDiscordId = msg[2]; try { u64 myDiscordIdNumber = stoull(myDiscordId); myDiscordIdsByChannel[dchatChannelId] = myDiscordIdNumber; } catch(...) { fprintf(stderr, "Warning: Rpc receive, failed to convert discord id to uint64_t: %s\n", myDiscordId.c_str()); // Ignore for now.. should we really handle this error other than showing warning? } } else { fprintf(stderr, "Rcp received unknown action %s\n", action.c_str()); } } catch(msgpack::type_error &e) { fprintf(stderr, "Failed to deserialize received rpc, error: %s\n", e.what()); } }); { lock_guard lock(messageMutex); for(auto &messageToSend : messagesToSend) { fprintf(stderr, "Rpc, sending message\n"); rpc.send(messageToSend.data(), messageToSend.size()); } messagesToSend.clear(); } this_thread::sleep_for(chrono::milliseconds(50)); } }); sf::Clock frameTimer; while (running) { frameTimer.restart(); Channel *currentChannel = Channel::getCurrent(); sf::Event event; while (window.pollEvent(event)) { if (event.type == sf::Event::Closed) { window.close(); running = false; } else if(event.type == sf::Event::Resized) { sf::FloatRect viewRect(0.0f, 0.0f, event.size.width, event.size.height); /* // TODO: Use xlib/xcb to set window minimum size instead const int minWidth = 800; if(event.size.width < minWidth) { viewRect.width = minWidth; window.setSize(sf::Vector2u(minWidth, event.size.height)); } */ sf::View view(viewRect); window.setView(view); } else if(event.type == sf::Event::MouseEntered) window.setFramerateLimit(FRAMERATE_FOCUSED); //else if(event.type == sf::Event::MouseLeft) // window.setFramerateLimit(FRAMERATE_NOT_FOCUSED); if(event.type == sf::Event::GainedFocus) windowFocused = true; else if(event.type == sf::Event::LostFocus) windowFocused = false; if(event.type == sf::Event::MouseEntered) focused = true; else if(event.type == sf::Event::MouseLeft) focused = false; if(focused) { ImagePreview::processEvent(event); } if(!ImagePreview::getPreviewContentPtr() && ImagePreview::getTimeSinceLastSeenMs() > 250) { GlobalContextMenu::processEvent(event); currentChannel->processEvent(event, cache); } lastFocusedTimer.restart(); } for(Channel *channel : channels) { channel->update(); } if((!windowFocused || !focused) && lastFocusedTimer.getElapsedTime().asMilliseconds() > 5000) { this_thread::sleep_for(chrono::milliseconds(250)); continue; } window.clear(ColorScheme::getBackgroundColor()); ChannelSidePanel::draw(window); currentChannel->draw(window, cache); UsersSidePanel::draw(window, cache); ChannelTopPanel::draw(window); GlobalContextMenu::draw(window); ImagePreview::draw(window); if(waitingToJoin) { auto windowSize = window.getSize(); sf::RectangleShape shadeRect(sf::Vector2f(windowSize.x, windowSize.y)); shadeRect.setFillColor(sf::Color(0, 0, 0, 200)); window.draw(shadeRect); const sf::Font *FONT = ResourceCache::getFont("fonts/Nunito-Regular.ttf"); const float FONT_SIZE = (double)windowSize.x / 40.0; sf::Text text("Wait until you are added to the channel", *FONT, FONT_SIZE); text.setPosition(floor((float)windowSize.x * 0.5f - text.getLocalBounds().width * 0.5f), floor((float)windowSize.y * 0.5f - FONT->getLineSpacing(FONT_SIZE) * 0.5f)); window.draw(text); } //video.draw(window); window.display(); } onMessageByLocalUser = nullptr; rpcThread.join(); for(Channel *channel : channels) { delete channel; } // We need to wait until our `ping` packet for disconnecting is sent to all channels printf("Shutting down...\n"); this_thread::sleep_for(chrono::seconds(3)); delete database; return 0; }