From f5aaf1b1cc94e28d4fa423a3d0b8ca286cf7f87d Mon Sep 17 00:00:00 2001 From: dec05eba Date: Mon, 21 May 2018 08:27:47 +0200 Subject: Implement online/offline users (pinging) --- depends/odhtdb | 2 +- include/Channel.hpp | 12 +++++ include/User.hpp | 3 ++ src/Channel.cpp | 82 +++++++++++++++++++++++++++++ src/User.cpp | 4 +- src/UsersSidePanel.cpp | 140 ++++++++++++++++++++++++++++++++++++------------- src/main.cpp | 14 +++++ 7 files changed, 218 insertions(+), 39 deletions(-) diff --git a/depends/odhtdb b/depends/odhtdb index c9cd775..f2cf653 160000 --- a/depends/odhtdb +++ b/depends/odhtdb @@ -1 +1 @@ -Subproject commit c9cd77556707c9c0d866d206395dd0312deead98 +Subproject commit f2cf653eb9967dc9ee26cbe7a43d1815bd526563 diff --git a/include/Channel.hpp b/include/Channel.hpp index 16c2fb3..28d174b 100644 --- a/include/Channel.hpp +++ b/include/Channel.hpp @@ -6,10 +6,13 @@ #include "Channel.hpp" #include "types.hpp" #include +#include #include #include #include #include +#include +#include namespace odhtdb { @@ -58,8 +61,14 @@ namespace dchat void processEvent(const sf::Event &event, Cache &cache); void draw(sf::RenderWindow &window, Cache &cache); + void update(); + // Returns 0 if we are offline + u64 getSyncedTimestampUtcCombined(); + static void setCurrent(Channel *channel); static Channel* getCurrent(); + private: + void sendPing(u32 pingCounter, u64 pingTimestamp); protected: odhtdb::Database *database; odhtdb::DatabaseNode databaseNodeInfo; @@ -70,5 +79,8 @@ namespace dchat SystemUser systemUser; std::vector users; odhtdb::Signature::MapPublicKey publicKeyOnlineUsersMap; + dht::InfoHash pingKey; + std::future pingListener; + sf::Clock pingTimer; }; } diff --git a/include/User.hpp b/include/User.hpp index 85418dc..ac08e36 100644 --- a/include/User.hpp +++ b/include/User.hpp @@ -1,5 +1,6 @@ #pragma once +#include "types.hpp" #include #include @@ -37,6 +38,8 @@ namespace dchat bool isOnlineUser() const override { return true; } std::string name; + u32 pingCounter; + u64 pingTimestamp; }; class OnlineRemoteUser : public OnlineUser diff --git a/src/Channel.cpp b/src/Channel.cpp index 0bacbc3..41233ad 100644 --- a/src/Channel.cpp +++ b/src/Channel.cpp @@ -25,13 +25,60 @@ namespace dchat // addLocalMessage(u8"ht clic", &systemUser, 0, odhtdb::Hash()); if(database) + { database->seed(databaseNodeInfo); + + pingKey = odhtdb::DhtKey(*databaseNodeInfo.getRequestHash()).getPingKey(); + // TODO: Ban peers that spam this key (take in account that creator of packets can be forged) + pingListener = database->receiveCustomMessage(pingKey, [this](const void *data, usize size) + { + sibs::SafeSerializer result; + try + { + sibs::SafeDeserializer deserializer((const u8*)data, size); + u8 userPublicKeyRaw[odhtdb::PUBLIC_KEY_NUM_BYTES]; + deserializer.extract(userPublicKeyRaw, odhtdb::PUBLIC_KEY_NUM_BYTES); + odhtdb::Signature::PublicKey userPublicKey((const char*)userPublicKeyRaw, odhtdb::PUBLIC_KEY_NUM_BYTES); + auto user = getUserByPublicKey(userPublicKey); + if(!user) + { + // TODO: Ban peer if this happens too often + return result; + } + + string unsignedData = userPublicKey.unsign(odhtdb::DataView((void*)deserializer.getBuffer(), deserializer.getSize())); + sibs::SafeDeserializer unsignedDeserializer((const u8*)unsignedData.data(), unsignedData.size()); + u32 pingCounter = unsignedDeserializer.extract(); + u64 pingTimestamp = unsignedDeserializer.extract(); + // TODO: A malicious peer can capture the packets and reply them after the user has reconnect and counter has reset, need to fix this somehow. + // One solution is for the user to store the counter locally in file and continue using it when reconnecting + if(pingCounter > user->pingCounter) + { + user->pingCounter = pingCounter; + user->pingTimestamp = pingTimestamp; + } + } + catch(std::exception &e) + { + fprintf(stderr, "Failed while deseralizing ping\n"); + } + return result; + }); + } } Channel::~Channel() { if(database) + { + database->cancelNodeListener(pingKey, pingListener); database->stopSeeding(*databaseNodeInfo.getRequestHash()); + if(database && localUser->type == User::Type::ONLINE_LOCAL_USER) + { + auto onlineLocalUser = static_cast(localUser); + sendPing(onlineLocalUser->pingCounter + 1, 0); + } + } for(User *user : users) { @@ -208,6 +255,41 @@ namespace dchat chatbar.draw(window, cache); } + void Channel::update() + { + if(database && localUser->type == User::Type::ONLINE_LOCAL_USER && pingTimer.getElapsedTime().asMilliseconds() > 5000) + { + pingTimer.restart(); + auto onlineLocalUser = static_cast(localUser); + sendPing(onlineLocalUser->pingCounter + 1, database->getSyncedTimestampUtc().getCombined()); + } + } + + void Channel::sendPing(u32 pingCounter, u64 pingTimestamp) + { + if(database && localUser->type == User::Type::ONLINE_LOCAL_USER) + { + //printf("Sending ping, counter: %u\n", pingCounter); + auto onlineLocalUser = static_cast(localUser); + sibs::SafeSerializer serializer; + serializer.add((const u8*)onlineLocalUser->getPublicKey().getData(), onlineLocalUser->getPublicKey().getSize()); + + sibs::SafeSerializer signedSerializer; + signedSerializer.add(pingCounter); + signedSerializer.add(pingTimestamp); + string signedData = onlineLocalUser->keyPair.getPrivateKey().sign(odhtdb::DataView(signedSerializer.getBuffer().data(), signedSerializer.getBuffer().size())); + serializer.add((const u8*)signedData.data(), signedData.size()); + database->sendCustomMessage(pingKey, move(serializer.getBuffer())); + } + } + + u64 Channel::getSyncedTimestampUtcCombined() + { + if(!database) + return 0; + return database->getSyncedTimestampUtc().getCombined(); + } + void Channel::setCurrent(Channel *channel) { currentChannel = channel; diff --git a/src/User.cpp b/src/User.cpp index 986380c..0cfbb4e 100644 --- a/src/User.cpp +++ b/src/User.cpp @@ -12,7 +12,9 @@ namespace dchat OnlineUser::OnlineUser(const std::string &_name, Type type) : User(type), - name(_name) + name(_name), + pingCounter(0), + pingTimestamp(0) { } diff --git a/src/UsersSidePanel.cpp b/src/UsersSidePanel.cpp index c3f2a78..035826f 100644 --- a/src/UsersSidePanel.cpp +++ b/src/UsersSidePanel.cpp @@ -22,6 +22,49 @@ namespace dchat const float AVATAR_DIAMETER = 40.0f; const float AVATAR_PADDING_SIDE = 10.0f; const float PADDING_BOTTOM = 20.0f; + const i64 USER_TIMEOUT_SEC = 10; + + static void renderUser(Cache &cache, User *user, sf::Shader *circleShader, sf::RenderWindow &window, sf::Vector2f &position, const sf::Font *font, const float textHeight) + { + // Max avatar size = 1mb + const ContentByUrlResult avatarResult = cache.getContentByUrl(user->avatarUrl, 1024 * 1024); + if(avatarResult.type == ContentByUrlResult::Type::CACHED) + { + circleShader->setUniform("texture", sf::Shader::CurrentTexture); + + if(avatarResult.cachedType == ContentByUrlResult::CachedType::TEXTURE) + { + // TODO: Store this sprite somewhere, might not be efficient to create a new sprite object every frame + sf::Sprite sprite(*avatarResult.texture); + auto textureSize = avatarResult.texture->getSize(); + sprite.setPosition(sf::Vector2f(floor(position.x), floor(position.y))); + sprite.setScale(sf::Vector2f(AVATAR_DIAMETER * Settings::getScaling() / (float)textureSize.x, AVATAR_DIAMETER * Settings::getScaling() / (float)textureSize.y)); + window.draw(sprite, circleShader); + } + else if(avatarResult.cachedType == ContentByUrlResult::CachedType::GIF) + { + auto gifSize = avatarResult.gif->getSize(); + avatarResult.gif->setPosition(sf::Vector2f(floor(position.x), floor(position.y))); + avatarResult.gif->setScale(sf::Vector2f(AVATAR_DIAMETER * Settings::getScaling() / (float)gifSize.x, AVATAR_DIAMETER * Settings::getScaling() / (float)gifSize.y)); + avatarResult.gif->draw(window, circleShader); + } + } + else + { + sf::CircleShape avatarCircle(AVATAR_DIAMETER * 0.5f * Settings::getScaling(), 60 * Settings::getScaling()); + avatarCircle.setPosition(sf::Vector2f(floor(position.x), floor(position.y))); + avatarCircle.setFillColor(ColorScheme::getBackgroundColor() + sf::Color(30, 30, 30)); + window.draw(avatarCircle); + } + + // TODO: Remove this shit + sf::String str = sf::String::fromUtf8(user->getName().begin(), user->getName().end()); + sf::Text text(str, *font, FONT_SIZE * Settings::getScaling()); + text.setPosition(floor(position.x + (AVATAR_DIAMETER + AVATAR_PADDING_SIDE) * Settings::getScaling()), floor(position.y + AVATAR_DIAMETER * 0.5f - textHeight * 0.5f)); + text.setFillColor(sf::Color(15, 192, 252)); + window.draw(text); + position.y += ((AVATAR_DIAMETER + PADDING_BOTTOM) * Settings::getScaling()); + } void UsersSidePanel::draw(sf::RenderWindow &window, Cache &cache) { @@ -38,59 +81,82 @@ namespace dchat const sf::Font *font = ResourceCache::getFont("fonts/Roboto-Regular.ttf"); sf::Vector2f position(rect.getPosition().x + 10.0f, posY); + const float textHeight = font->getLineSpacing(FONT_SIZE * Settings::getScaling()); + + i64 timestamp = currentChannel->getSyncedTimestampUtcCombined(); + + u32 numOnlineUsers = 0; + u32 numOfflineUsers = 0; + for(User *user : currentChannel->getUsers()) + { + bool hasUserTimedOut = false; + if(user->isOnlineUser()) + { + auto onlineUser = static_cast(user); + i64 pingTimeDiffSec = (timestamp - (i64)onlineUser->pingTimestamp) >> 32LL; + if(pingTimeDiffSec > USER_TIMEOUT_SEC) + hasUserTimedOut = true; + } + + if(hasUserTimedOut) + ++numOfflineUsers; + else + ++numOnlineUsers; + } + // TODO: Remove this shit sf::String str = "Online - "; - str += to_string(currentChannel->getUsers().size()); - sf::Text text(str, *font, FONT_SIZE * Settings::getScaling() * 1.0f); + str += to_string(numOnlineUsers); + sf::Text text(str, *font, textHeight * 1.0f); text.setPosition(position); text.setFillColor(ColorScheme::getTextRegularColor() * sf::Color(255, 255, 255, 100)); window.draw(text); position.y += floor(font->getLineSpacing(text.getCharacterSize())); - position.y += (PADDING_BOTTOM * Settings::getScaling()); + position.y += PADDING_BOTTOM * Settings::getScaling() * 0.5f; sf::Shader *circleShader = ResourceCache::getShader("shaders/circleMask.glsl", sf::Shader::Fragment); - const float textHeight = font->getLineSpacing(FONT_SIZE * Settings::getScaling()); for(User *user : currentChannel->getUsers()) { - // Max avatar size = 1mb - const ContentByUrlResult avatarResult = cache.getContentByUrl(user->avatarUrl, 1024 * 1024); - if(avatarResult.type == ContentByUrlResult::Type::CACHED) + bool isUserOnline = true; + if(user->isOnlineUser()) { - circleShader->setUniform("texture", sf::Shader::CurrentTexture); - - if(avatarResult.cachedType == ContentByUrlResult::CachedType::TEXTURE) - { - // TODO: Store this sprite somewhere, might not be efficient to create a new sprite object every frame - sf::Sprite sprite(*avatarResult.texture); - auto textureSize = avatarResult.texture->getSize(); - sprite.setPosition(sf::Vector2f(floor(position.x), floor(position.y))); - sprite.setScale(sf::Vector2f(AVATAR_DIAMETER * Settings::getScaling() / (float)textureSize.x, AVATAR_DIAMETER * Settings::getScaling() / (float)textureSize.y)); - window.draw(sprite, circleShader); - } - else if(avatarResult.cachedType == ContentByUrlResult::CachedType::GIF) - { - auto gifSize = avatarResult.gif->getSize(); - avatarResult.gif->setPosition(sf::Vector2f(floor(position.x), floor(position.y))); - avatarResult.gif->setScale(sf::Vector2f(AVATAR_DIAMETER * Settings::getScaling() / (float)gifSize.x, AVATAR_DIAMETER * Settings::getScaling() / (float)gifSize.y)); - avatarResult.gif->draw(window, circleShader); - } + auto onlineUser = static_cast(user); + i64 pingTimeDiffSec = (timestamp - (i64)onlineUser->pingTimestamp) >> 32LL; + if(pingTimeDiffSec > USER_TIMEOUT_SEC) + isUserOnline = false; } - else + + if(isUserOnline) + renderUser(cache, user, circleShader, window, position, font, textHeight); + } + + if(numOfflineUsers == 0) return; + position.y += PADDING_BOTTOM * Settings::getScaling(); + + // TODO: Remove this shit + str = "Offline - "; + str += to_string(numOfflineUsers); + text.setString(str); + text.setPosition(position); + text.setFillColor(ColorScheme::getTextRegularColor() * sf::Color(255, 255, 255, 100)); + window.draw(text); + position.y += floor(font->getLineSpacing(text.getCharacterSize())); + position.y += PADDING_BOTTOM * Settings::getScaling() * 0.5f; + + for(User *user : currentChannel->getUsers()) + { + bool isUserOnline = true; + if(user->isOnlineUser()) { - sf::CircleShape avatarCircle(AVATAR_DIAMETER * 0.5f * Settings::getScaling(), 60 * Settings::getScaling()); - avatarCircle.setPosition(sf::Vector2f(floor(position.x), floor(position.y))); - avatarCircle.setFillColor(ColorScheme::getBackgroundColor() + sf::Color(30, 30, 30)); - window.draw(avatarCircle); + auto onlineUser = static_cast(user); + i64 pingTimeDiffSec = (timestamp - (i64)onlineUser->pingTimestamp) >> 32LL; + if(pingTimeDiffSec > USER_TIMEOUT_SEC) + isUserOnline = false; } - // TODO: Remove this shit - sf::String str = sf::String::fromUtf8(user->getName().begin(), user->getName().end()); - sf::Text text(str, *font, FONT_SIZE * Settings::getScaling()); - text.setPosition(floor(position.x + (AVATAR_DIAMETER + AVATAR_PADDING_SIDE) * Settings::getScaling()), floor(position.y + AVATAR_DIAMETER * 0.5f - textHeight * 0.5f)); - text.setFillColor(sf::Color(15, 192, 252)); - window.draw(text); - position.y += ((AVATAR_DIAMETER + PADDING_BOTTOM) * Settings::getScaling()); + if(!isUserOnline) + renderUser(cache, user, circleShader, window, position, font, textHeight); } } diff --git a/src/main.cpp b/src/main.cpp index a6dfb5f..5317907 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -724,6 +724,11 @@ int main(int argc, char **argv) lastFocusedTimer.restart(); } + + for(Channel *channel : channels) + { + channel->update(); + } if(lastFocusedTimer.getElapsedTime().asMilliseconds() > 3000) { @@ -757,6 +762,15 @@ int main(int argc, char **argv) //video.draw(window); window.display(); } + + for(Channel *channel : channels) + { + channel->update(); + } + + // We need to wait until our `ping` packet for disconnecting is sent to all channels + printf("Shutting down..."); + this_thread::sleep_for(chrono::seconds(3)); return 0; } -- cgit v1.2.3