#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 #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; } odhtdb::DatabaseNode createDatabaseNodeFromJoinKey(const string &joinKey) { odhtdb::DatabaseNode result; string nodeHashStr = odhtdb::hex2bin(joinKey.c_str(), 64); memcpy(result.getRequestHash()->getData(), nodeHashStr.data(), nodeHashStr.size()); string nodeEncryptionKeyStr = odhtdb::hex2bin(joinKey.c_str() + 65, 64); char *nodeEncryptionKeyRaw = new char[nodeEncryptionKeyStr.size()]; result.getNodeEncryptionKey()->data = nodeEncryptionKeyRaw; result.getNodeEncryptionKey()->size = nodeEncryptionKeyStr.size(); return result; } void channelAddStoredMessage(Channel *channel, odhtdb::DatabaseStorageObject *nodeStorageObject) { User *user = channel->getUserByPublicKey(nodeStorageObject->creatorPublicKey); if(!user) { fprintf(stderr, "Missing user? %s\n", nodeStorageObject->creatorPublicKey.toString().c_str()); return; } string msg((const char*)nodeStorageObject->decryptedObject.data.data, nodeStorageObject->decryptedObject.data.size); channel->addLocalMessage(msg, user, ntp::NtpTimestamp::fromCombined(nodeStorageObject->createdTimestamp).seconds); } void channelAddStoredMessages(Channel *channel, const odhtdb::DatabaseStorageObjectList *nodeStorage) { printf("Load %u messages in channel %s\n", nodeStorage->objects.size(), channel->getName().c_str()); for(auto nodeStorageAddedObject : nodeStorage->objects) { if(nodeStorageAddedObject->decryptedObject.operation == odhtdb::DatabaseOperation::ADD_DATA) { channelAddStoredMessage(channel, nodeStorageAddedObject); } } } 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; 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; database.setOnCreateNodeCallback([&waitingToJoinChannels, &database, &channels, &channelMessageMutex](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); waitingToJoinChannels.erase(it); return; } } }); database.setOnAddNodeCallback([&channels, &channelMessageMutex](const odhtdb::DatabaseAddNodeRequest &request) { lock_guard lock(channelMessageMutex); for(Channel *channel : channels) { if(*request.nodeHash == *channel->getId()) { User *user = channel->getUserByPublicKey(request.creatorUser->getPublicKey()); if(!user) { fprintf(stderr, "Missing user? %s\n", request.creatorUser->getPublicKey().toString().c_str()); return; } string msg((const char*)request.decryptedData.data, request.decryptedData.size); channel->addLocalMessage(msg, user, ntp::NtpTimestamp::fromCombined(request.timestamp).seconds); return; } } }); database.setOnAddUserCallback([¤tUserKeyPair, &channels, &channelMessageMutex](const odhtdb::DatabaseAddUserRequest &request) { lock_guard lock(channelMessageMutex); if(currentUserKeyPair && request.userToAdd->getPublicKey() == currentUserKeyPair->getPublicKey()) { printf("You were added to channel %s by %s\n", request.nodeHash->toString().c_str(), request.creatorUser->getName().c_str()); return; } for(Channel *channel : channels) { if(*request.nodeHash == *channel->getId()) { User *userToAdd = channel->getUserByPublicKey(request.userToAdd->getPublicKey()); if(userToAdd && currentUserKeyPair && request.userToAdd->getPublicKey() == currentUserKeyPair->getPublicKey() && channel->getLocalUser()->type != User::Type::ONLINE) { channel->replaceLocalUser(new OnlineUser(request.userToAdd)); return; } 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](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); // 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); }); // 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()); }); sf::Event event; while (window.isOpen()) { channelMessageMutex.lock(); Channel *currentChannel = Channel::getCurrent(); bool waitingToJoin = currentChannel != &offlineChannel && currentChannel->getLocalUser()->type != User::Type::ONLINE; channelMessageMutex.unlock(); 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); currentChannel->processEvent(event); } window.clear(ColorScheme::getBackgroundColor()); ChannelSidePanel::draw(window); currentChannel->draw(window, cache); UsersSidePanel::draw(window); ChannelTopPanel::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 = 30 * Settings::getScaling(); 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; }