aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordec05eba <dec05eba@protonmail.com>2018-05-21 08:27:47 +0200
committerdec05eba <dec05eba@protonmail.com>2018-05-21 08:27:50 +0200
commitf5aaf1b1cc94e28d4fa423a3d0b8ca286cf7f87d (patch)
tree839418cbb8a8f5ee4bbabc79c3ca915babab8302
parente1322b06148b633df365d8916404a2748945de65 (diff)
Implement online/offline users (pinging)
m---------depends/odhtdb0
-rw-r--r--include/Channel.hpp12
-rw-r--r--include/User.hpp3
-rw-r--r--src/Channel.cpp82
-rw-r--r--src/User.cpp4
-rw-r--r--src/UsersSidePanel.cpp140
-rw-r--r--src/main.cpp14
7 files changed, 217 insertions, 38 deletions
diff --git a/depends/odhtdb b/depends/odhtdb
-Subproject c9cd77556707c9c0d866d206395dd0312deead9
+Subproject f2cf653eb9967dc9ee26cbe7a43d1815bd52656
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 <vector>
+#include <SFML/System/Clock.hpp>
#include <odhtdb/DatabaseNode.hpp>
#include <odhtdb/Signature.hpp>
#include <odhtdb/Group.hpp>
#include <odhtdb/Hash.hpp>
+#include <odhtdb/DhtKey.hpp>
+#include <future>
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<User*> users;
odhtdb::Signature::MapPublicKey<OnlineUser*> publicKeyOnlineUsersMap;
+ dht::InfoHash pingKey;
+ std::future<size_t> 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 <string>
#include <odhtdb/Signature.hpp>
@@ -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<u32>();
+ u64 pingTimestamp = unsignedDeserializer.extract<u64>();
+ // 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<OnlineLocalUser*>(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<OnlineLocalUser*>(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<OnlineLocalUser*>(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<OnlineUser*>(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<OnlineUser*>(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<OnlineUser*>(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;
}