#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 #include #include #include #include #include #include #include #include using namespace std; using namespace dchat; using namespace TinyProcessLib; string createChannelJoinKey(const unique_ptr &databaseCreateResponse) { string result; result += databaseCreateResponse->getRequestHash()->toString(); result += "&"; result += odhtdb::bin2hex((const char*)databaseCreateResponse->getNodeEncryptionKey()->data, databaseCreateResponse->getNodeEncryptionKey()->size); return result; } string createJoinKeyFromDatabaseNode(const odhtdb::DatabaseNode &databaseNode) { string result; result += databaseNode.getRequestHash()->toString(); result += "&"; result += odhtdb::bin2hex((const char*)databaseNode.getNodeEncryptionKey()->data, databaseNode.getNodeEncryptionKey()->size); return result; } odhtdb::DatabaseNode createDatabaseNodeFromJoinKey(const string &joinKey) { string nodeHashRaw = odhtdb::hex2bin(joinKey.c_str(), 64); shared_ptr nodeHash = make_shared(); memcpy(nodeHash->getData(), nodeHashRaw.data(), nodeHashRaw.size()); string nodeEncryptionKeyRaw = odhtdb::hex2bin(joinKey.c_str() + 65, 64); shared_ptr encryptionKey = make_shared(); encryptionKey->data = new char[nodeEncryptionKeyRaw.size()]; memcpy(encryptionKey->data, nodeEncryptionKeyRaw.data(), nodeEncryptionKeyRaw.size()); encryptionKey->size = nodeEncryptionKeyRaw.size(); return odhtdb::DatabaseNode(encryptionKey, nodeHash); } 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 } void channelAddStoredMessage(Channel *channel, const odhtdb::Hash &requestHash, const odhtdb::Signature::PublicKey &creatorPublicKey, const StringView decryptedObject, u64 timestamp) { 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); channel->addLocalMessage(msg, user, ntp::NtpTimestamp::fromCombined(timestamp).seconds, requestHash); 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; } default: fprintf(stderr, "Got unexpected channel data type: %u\n", channelDataType); break; } } else { fprintf(stderr, "ADD_DATA packet too small, ignoring...\n"); } } void channelAddStoredMessages(Channel *channel, const odhtdb::DatabaseStorageObjectList *nodeStorage) { printf("Load %u message(s) in channel %s\n", nodeStorage->objects.size(), channel->getName().c_str()); for(auto nodeStorageAddedObject : nodeStorage->objects) { if(nodeStorageAddedObject->decryptedObject.operation == odhtdb::DatabaseOperation::ADD_DATA) { StringView decryptedObject((const char*)nodeStorageAddedObject->decryptedObject.data.data, nodeStorageAddedObject->decryptedObject.data.size); channelAddStoredMessage(channel, nodeStorageAddedObject->requestHash, nodeStorageAddedObject->creatorPublicKey, decryptedObject, nodeStorageAddedObject->createdTimestamp); } } } int main(int argc, char **argv) { /* boost::filesystem::path programPath(argv[0]); auto parentPath = programPath.parent_path(); printf("parent path: %s\n", parentPath.string().c_str()); boost::filesystem::current_path(parentPath); // Ensures loading of resources works no matter which path we run this executable from */ const int FRAMERATE_FOCUSED = 200; const int FRAMERATE_NOT_FOCUSED = 30; XInitThreads(); sf::RenderWindow window(sf::VideoMode(1920, 1080), "dchat"); window.setVerticalSyncEnabled(false); window.setFramerateLimit(FRAMERATE_FOCUSED); odhtdb::Database database("bootstrap.ring.cx", 4222, Cache::getDchatDir()); //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; odhtdb::Signature::KeyPair *currentUserKeyPair = nullptr; vector localNodeUsers; vector waitingToJoinChannels; string currentUserName; string currentUserPassword; recursive_mutex channelMessageMutex; bool waitingToJoin = false; database.setOnCreateNodeCallback([&waitingToJoinChannels, &database, &channels, &channelMessageMutex, &waitingToJoin](const odhtdb::DatabaseCreateNodeRequest &request) { lock_guard lock(channelMessageMutex); for(vector::iterator it = waitingToJoinChannels.begin(); it != waitingToJoinChannels.end(); ++it) { if(*request.nodeHash == *it->getRequestHash()) { User *localUser = new OfflineUser("You"); Channel *channel = new Channel(request.name, *it, localUser, &database); ChannelSidePanel::addChannel(channel); channels.push_back(channel); Channel::setCurrent(channel); User *nodeCreatorUser = new OnlineUser(request.creatorUser); channel->addUserLocally(nodeCreatorUser); waitingToJoin = true; waitingToJoinChannels.erase(it); return; } } }); database.setOnAddNodeCallback([&channels, &channelMessageMutex](const odhtdb::DatabaseAddNodeRequest &request) { lock_guard lock(channelMessageMutex); for(Channel *channel : channels) { if(*request.nodeHash == *channel->getNodeInfo().getRequestHash()) { channelAddStoredMessage(channel, *request.requestHash, request.creatorUser->getPublicKey(), StringView((const char*)request.decryptedData.data, request.decryptedData.size), request.timestamp); return; } } }); database.setOnAddUserCallback([¤tUserKeyPair, &channels, &channelMessageMutex, &waitingToJoin, &database](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"); if(currentUserKeyPair && request.userToAdd->getPublicKey() == currentUserKeyPair->getPublicKey() && channel->getLocalUser()->type != User::Type::ONLINE) { // Replace remote user with local user, if we are the remote user database.getStorage().getLocalNodeUsers(*currentUserKeyPair); auto userToAdd = database.getStorage().getUserByPublicKey(*request.nodeHash, currentUserKeyPair->getPublicKey()); printf("You were added to channel %s by %s\n", request.nodeHash->toString().c_str(), request.creatorUser->getName().c_str()); channel->replaceLocalUser(new OnlineUser(userToAdd)); waitingToJoin = false; return; } User *userToAdd = channel->getUserByPublicKey(request.userToAdd->getPublicKey()); if(userToAdd) { fprintf(stderr, "User %s already exists in channel\n", request.userToAdd->getPublicKey().toString().c_str()); return; } User *newRemoteUser = new OnlineUser(request.userToAdd); channel->addUserLocally(newRemoteUser); return; } } }); // Login to account Command::add("login", [¤tUserKeyPair, ¤tUserName, ¤tUserPassword, &localNodeUsers, &database, &channels, &channelMessageMutex](const vector &args) { if(args.size() != 2) { fprintf(stderr, "Expected 2 arguments for command login (username and password), got %u argument(s)\n", args.size()); return; } try { odhtdb::Signature::KeyPair keyPair = database.getStorage().decryptLocalEncryptedUser(args[0], args[1]); localNodeUsers = database.getStorage().getLocalNodeUsers(keyPair); ChannelSidePanel::removeAllChannels(); for(Channel *channel : channels) { delete channel; } channels.clear(); lock_guard lock(channelMessageMutex); for(auto localNodeUser : localNodeUsers) { auto nodeStorage = database.getStorage().getStorage(localNodeUser.nodeHash); if(!nodeStorage) continue; auto nodeDecryptionKeyResult = database.getStorage().getNodeDecryptionKey(localNodeUser.nodeHash); if(!nodeDecryptionKeyResult.first) continue; User *newLocalUser = new OnlineUser(localNodeUser.localUser); odhtdb::DatabaseNode databaseNode(nodeDecryptionKeyResult.second, make_shared(localNodeUser.nodeHash)); Channel *channel = new Channel(nodeStorage->nodeName, databaseNode, newLocalUser, &database); auto nodeUserMapByPublicKey = database.getStorage().getNodeUsers(localNodeUser.nodeHash); for(auto nodeUserIt : *nodeUserMapByPublicKey) { if(nodeUserIt.second != localNodeUser.localUser) { User *newRemoteUser = new OnlineUser(nodeUserIt.second); channel->addUserLocally(newRemoteUser); } } ChannelSidePanel::addChannel(channel); channels.push_back(channel); Channel::setCurrent(channel); channelAddStoredMessages(channel, nodeStorage); } printf("Successfully logged into user %s\n", args[0].c_str()); if(currentUserKeyPair) delete currentUserKeyPair; currentUserKeyPair = new odhtdb::Signature::KeyPair(keyPair); currentUserName = args[0]; currentUserPassword = args[1]; } catch(odhtdb::DatabaseStorageException &e) { fprintf(stderr, "Failed to login, reason: %s\n", e.what()); } }); // Register account Command::add("register", [¤tUserKeyPair, ¤tUserName, ¤tUserPassword, &localNodeUsers, &database, &channelMessageMutex](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; } if(currentUserKeyPair) { fprintf(stderr, "You can't register a new account when you are logged in, please logout first\n"); return; } odhtdb::Signature::KeyPair keyPair; if(!database.getStorage().storeLocalUser(args[0], keyPair, args[1])) { fprintf(stderr, "User with name %s already exists in storage\n", args[0].c_str()); return; } printf("Registered user %s, public key: %s, private key: %s\n", args[0].c_str(), keyPair.getPublicKey().toString().c_str(), keyPair.getPrivateKey().toString().c_str()); printf("Successfully logged into user %s\n", args[0].c_str()); if(currentUserKeyPair) delete currentUserKeyPair; currentUserKeyPair = new odhtdb::Signature::KeyPair(keyPair); localNodeUsers.clear(); currentUserName = args[0]; currentUserPassword = args[1]; }); // Create channel Command::add("cc", [¤tUserKeyPair, ¤tUserName, &database, &channels, &channelMessageMutex](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(!currentUserKeyPair) { fprintf(stderr, "You are not logged in. Please login before creating a channel\n"); return; } auto createResponse = database.create(currentUserName, *currentUserKeyPair, args[0]); database.commit(); printf("Created database '%s', join key: '%s'\n", args[0].c_str(), createChannelJoinKey(createResponse).c_str()); User *newLocalUser = new OnlineUser(createResponse->getNodeAdminUser()); 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); }); // Add user Command::add("au", [&offlineChannel, &channelMessageMutex](const vector &args) { lock_guard lock(channelMessageMutex); if(args.size() != 2) { fprintf(stderr, "Expected 2 arguments for command au (user id, group id), got %u argument(s)\n", args.size()); return; } if(args[0].size() != odhtdb::PUBLIC_KEY_NUM_BYTES * 2) { fprintf(stderr, "User id is wrong size. Expected to be %u characters, was %u character(s)\n", odhtdb::PUBLIC_KEY_NUM_BYTES * 2, args[0].size()); return; } if(args[1].size() != odhtdb::GROUP_ID_LENGTH * 2) { fprintf(stderr, "Group id is wrong size. Expected to be %u characters, was %u character(s)\n", odhtdb::GROUP_ID_LENGTH * 2, args[1].size()); return; } Channel *currentChannel = Channel::getCurrent(); if(currentChannel == &offlineChannel) { Channel::getCurrent()->addLocalMessage("You need to be in a channel to add user to the channel", Channel::getCurrent()->getSystemUser()); return; } auto userIdRaw = odhtdb::hex2bin(args[0].c_str(), args[0].size()); odhtdb::Signature::PublicKey userPublicKey(userIdRaw.data(), userIdRaw.size()); auto groupIdRaw = odhtdb::hex2bin(args[1].c_str(), args[1].size()); bool userAddResult = currentChannel->addUser(userPublicKey, groupIdRaw); if(userAddResult) { printf("Added user to your channel!\n"); } else { fprintf(stderr, "Failed to add user to your channel!\n"); } }); Command::add("jc", [¤tUserKeyPair, &database, &localNodeUsers, &channelMessageMutex, &waitingToJoin, &waitingToJoinChannels](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(!currentUserKeyPair) { fprintf(stderr, "You are not logged in. Please login before joining a channel\n"); return; } odhtdb::DatabaseNode databaseNode = createDatabaseNodeFromJoinKey(args[0]); for(auto localNodeUser : localNodeUsers) { if(*databaseNode.getRequestHash() == localNodeUser.nodeHash) { fprintf(stderr, "You have already joined the channel %s\n", databaseNode.getRequestHash()->toString().c_str()); return; } } database.seed(databaseNode); waitingToJoinChannels.push_back(databaseNode); waitingToJoin = true; // TODO: Add the channel to join to a pending join list in a file and remove from it when we have joined the channel. // The reason for doing that is so if we crash or lose internet connection before we have got `create node` request from remote peers, // then we need to start seeding again when we login. Once we have `create node` request, then it's added to local cache and when you login, // it will be used to seed the channel. }); // 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", [](const vector &args) { if(args.size() != 2) { string errMsg = "Expected 2 arguments for command addbind, got "; errMsg += to_string(args.size()); errMsg += " argument(s)"; Channel::getCurrent()->addLocalMessage(errMsg, Channel::getCurrent()->getSystemUser()); return; } string key = ":"; key += args[0]; key += ":"; if(key.size() > 255) { // 253 = bind + two colons Channel::getCurrent()->addLocalMessage("Bind is too long. Max size is 253 bytes", Channel::getCurrent()->getSystemUser()); return; } bool bindAdded = Chatbar::addBind(key, args[1]); if(bindAdded) Channel::getCurrent()->addLocalMessage("Bind added", Channel::getCurrent()->getSystemUser()); else Channel::getCurrent()->addLocalMessage("Bind already exists. Remove it first if you want to replace it", Channel::getCurrent()->getSystemUser()); }); Command::add("removebind", [](const vector &args) { if(args.size() != 1) { string errMsg = "Expected 1 argument for command removebind, got "; errMsg += to_string(args.size()); errMsg += " argument(s)"; Channel::getCurrent()->addLocalMessage(errMsg, Channel::getCurrent()->getSystemUser()); return; } string key = ":"; key += args[0]; key += ":"; if(key.size() > 255) { // 253 = bind + two colons Channel::getCurrent()->addLocalMessage("Bind is too long. Max size is 253 bytes", Channel::getCurrent()->getSystemUser()); return; } bool bindRemoved = Chatbar::removeBind(key); if(bindRemoved) Channel::getCurrent()->addLocalMessage("Bind removed", Channel::getCurrent()->getSystemUser()); else Channel::getCurrent()->addLocalMessage("Bind doesn't exist, nothing was removed", Channel::getCurrent()->getSystemUser()); }); Command::add("binds", [](const vector &args) { if(args.size() != 0) { string errMsg = "Expected 0 arguments for command removebind, got "; errMsg += to_string(args.size()); errMsg += " argument(s)"; Channel::getCurrent()->addLocalMessage(errMsg, Channel::getCurrent()->getSystemUser()); return; } string msg = "Binds:"; auto binds = Chatbar::getBinds(); for(auto &bind : binds) { msg += "\n"; msg += bind.first; msg += " "; msg += bind.second; } Channel::getCurrent()->addLocalMessage(msg, Channel::getCurrent()->getSystemUser()); }); // Get username and id (public key) Command::add("whoami", [¤tUserKeyPair, ¤tUserName](const vector &args) { if(!currentUserKeyPair) { Channel::getCurrent()->addLocalMessage("You are not logged in", Channel::getCurrent()->getSystemUser()); return; } string response = "Username: "; response += currentUserName; response += ", id: "; response += currentUserKeyPair->getPublicKey().toString(); printf("%s\n", response.c_str()); Channel::getCurrent()->addLocalMessage(response, Channel::getCurrent()->getSystemUser()); }); // Change nick of current user in current channel Command::add("nick", [¤tUserKeyPair, &offlineChannel](const vector &args) { if(args.size() != 1) { string errMsg = "Expected 1 argument for command nick, got "; errMsg += to_string(args.size()); errMsg += " argument(s)"; Channel::getCurrent()->addLocalMessage(errMsg, Channel::getCurrent()->getSystemUser()); return; } if(!currentUserKeyPair) { Channel::getCurrent()->addLocalMessage("You need to be logged in to change your nickname", Channel::getCurrent()->getSystemUser()); return; } if(Channel::getCurrent() == &offlineChannel) { Channel::getCurrent()->addLocalMessage("You need to be in a channel to change your nickname", Channel::getCurrent()->getSystemUser()); return; } if(args[0].size() == 0 || args[0].size() > 255) { Channel::getCurrent()->addLocalMessage("Invalid nickname. Nickname has to be between 1 and 255 characters", Channel::getCurrent()->getSystemUser()); return; } Channel::getCurrent()->changeNick(args[0]); string msg = "Your nickname was changed to "; msg += args[0]; Channel::getCurrent()->addLocalMessage(msg, Channel::getCurrent()->getSystemUser()); }); // Get channel join key Command::add("joinkey", [¤tUserKeyPair, ¤tUserName, &offlineChannel](const vector &args) { Channel *currentChannel = Channel::getCurrent(); if(!currentChannel || currentChannel == &offlineChannel) { Channel::getCurrent()->addLocalMessage("You are not inside a channel", Channel::getCurrent()->getSystemUser()); return; } string response = "Join key: "; response += createJoinKeyFromDatabaseNode(currentChannel->getNodeInfo()); printf("%s\n", response.c_str()); Channel::getCurrent()->addLocalMessage(response, Channel::getCurrent()->getSystemUser()); }); sf::Event event; while (window.isOpen()) { Channel *currentChannel = Channel::getCurrent(); while (window.pollEvent(event)) { if (event.type == sf::Event::Closed) window.close(); else if(event.type == sf::Event::Resized) { sf::FloatRect viewRect(0.0f, 0.0f, event.size.width, event.size.height); 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::GainedFocus) window.setFramerateLimit(FRAMERATE_FOCUSED); //else if(event.type == sf::Event::LostFocus) // window.setFramerateLimit(FRAMERATE_NOT_FOCUSED); GlobalContextMenu::processEvent(event); currentChannel->processEvent(event); } window.clear(ColorScheme::getBackgroundColor()); ChannelSidePanel::draw(window); currentChannel->draw(window, cache); UsersSidePanel::draw(window); ChannelTopPanel::draw(window); GlobalContextMenu::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/Roboto-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(); } return 0; }