aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.kdev4/dchat.kdev44
-rw-r--r--README.md8
m---------depends/odhtdb0
-rw-r--r--include/Channel.hpp4
-rw-r--r--include/User.hpp36
-rw-r--r--project.conf2
-rw-r--r--src/Channel.cpp61
-rw-r--r--src/Command.cpp2
-rw-r--r--src/MessageBoard.cpp19
-rw-r--r--src/User.cpp35
-rw-r--r--src/main.cpp397
11 files changed, 336 insertions, 232 deletions
diff --git a/.kdev4/dchat.kdev4 b/.kdev4/dchat.kdev4
index a3fb28a..63d87da 100644
--- a/.kdev4/dchat.kdev4
+++ b/.kdev4/dchat.kdev4
@@ -13,10 +13,10 @@ Name=Clang
[CustomDefinesAndIncludes][ProjectPath0][Includes]
1=/home/dec05eba/.cache/sibs/lib/tiny-process/2.0.0
2=/home/dec05eba/git/dchat/depends/odhtdb/include
-3=/home/dec05eba/.cache/sibs/lib/sibs-serializer/0.2.0
+3=/home/dec05eba/.cache/sibs/lib/sibs-serializer/1.0.1
4=/home/dec05eba/.cache/sibs/lib/ntpclient/0.2.1/include
5=/home/dec05eba/.cache/sibs/lib/fmt/4.1.0
-6=/home/dec05eba/.cache/sibs/lib/libpreview/0.1.0/include
+6=/home/dec05eba/.cache/sibs/lib/libpreview/0.1.1/include
[Project]
VersionControlSupport=kdevgit
diff --git a/README.md b/README.md
index 67902b6..44f5995 100644
--- a/README.md
+++ b/README.md
@@ -7,6 +7,12 @@ opendht, boost, libnsgif, mpv, sfml
xsel, curl
# TODO
Use http(s) library to download content (curl?) and stream gif. If content is html, then stop downloading after we have retrieved title, header image and paragraph
-
+# Channel invite
+Inviting users to channel is done by generating an invite key which is a combination of the node hash and an encryption key (a new encryption key, not the same as the one that is used for the channel nodes themselves).
+After generating the invite key, the user that generated it will listen to requests to join the channel using InfoHash generated by the node hash and encryption key.
+The user that wants to join the channel using the invite key has to sign a message using their public key and encrypt it using the invite encryption key and send it to the same InfoHash the creator of the invite key is using.
+If the creator of the invite key can verify that the message they received was signed with the public key that exists in the message and the message can be decrypted using the invite encryption key, then they will respond with the channel node encryption key.
+The user that created the invite key will then add the user to the channel and the user that wants to join will wait until they receive 'ADD DATA' node that with the public key that they requested to be added with,
+by receiving and decrypting node data with the key the got from the creator of the invite key.
# Screenshot
![Sample screenshot](https://i.imgur.com/z0Ee0Rw.png)
diff --git a/depends/odhtdb b/depends/odhtdb
-Subproject d8e5c76c364450179f12fa985d50b7e4bfb5aa7
+Subproject 7f19b686cc7b47346ba8bab89bb34188f366db1
diff --git a/include/Channel.hpp b/include/Channel.hpp
index 93f3725..6e944c3 100644
--- a/include/Channel.hpp
+++ b/include/Channel.hpp
@@ -51,8 +51,8 @@ namespace dchat
void deleteMessage(const odhtdb::Hash &id, const odhtdb::Signature::PublicKey &requestedByUser);
void addUserLocally(User *user);
- bool addUser(const odhtdb::Signature::PublicKey &userId, const std::string &groupId);
- void replaceLocalUser(User *newLocalUser);
+ bool addUser(const odhtdb::Signature::PublicKey &userId, const odhtdb::DataView &groupId);
+ void replaceLocalUser(OnlineLocalUser *newOnlineLocalUser);
void changeNick(const std::string &newNick);
void processEvent(const sf::Event &event);
diff --git a/include/User.hpp b/include/User.hpp
index a83b9dc..85418dc 100644
--- a/include/User.hpp
+++ b/include/User.hpp
@@ -1,11 +1,7 @@
#pragma once
#include <string>
-
-namespace odhtdb
-{
- class User;
-}
+#include <odhtdb/Signature.hpp>
namespace dchat
{
@@ -14,7 +10,8 @@ namespace dchat
public:
enum class Type
{
- ONLINE,
+ ONLINE_REMOTE_USER,
+ ONLINE_LOCAL_USER,
OFFLINE,
SYSTEM
};
@@ -22,6 +19,7 @@ namespace dchat
User(Type type);
virtual ~User(){}
virtual const std::string& getName() const = 0;
+ virtual bool isOnlineUser() const { return false; }
const Type type;
std::string avatarUrl;
@@ -30,11 +28,33 @@ namespace dchat
class OnlineUser : public User
{
public:
- OnlineUser(const odhtdb::User *databaseUser);
+ OnlineUser(const std::string &name, Type type);
+ virtual ~OnlineUser(){}
+
virtual const std::string& getName() const override;
+ virtual const odhtdb::Signature::PublicKey& getPublicKey() const = 0;
+
+ bool isOnlineUser() const override { return true; }
std::string name;
- const odhtdb::User *databaseUser;
+ };
+
+ class OnlineRemoteUser : public OnlineUser
+ {
+ public:
+ OnlineRemoteUser(const std::string &name, const odhtdb::Signature::PublicKey &publicKey);
+ virtual const odhtdb::Signature::PublicKey& getPublicKey() const override;
+
+ const odhtdb::Signature::PublicKey publicKey;
+ };
+
+ class OnlineLocalUser : public OnlineUser
+ {
+ public:
+ OnlineLocalUser(const std::string &name, const odhtdb::Signature::KeyPair &keyPair);
+ virtual const odhtdb::Signature::PublicKey& getPublicKey() const override;
+
+ const odhtdb::Signature::KeyPair keyPair;
};
class OfflineUser : public User
diff --git a/project.conf b/project.conf
index f488112..116c0c1 100644
--- a/project.conf
+++ b/project.conf
@@ -14,4 +14,4 @@ mpv = "1.25.0"
gl = "17.3"
x11 = "1.6.5"
libnsgif = "0.2.0"
-libpreview = "0.1.0"
+libpreview = "0.1.1"
diff --git a/src/Channel.cpp b/src/Channel.cpp
index ed037a4..a63fcc4 100644
--- a/src/Channel.cpp
+++ b/src/Channel.cpp
@@ -1,5 +1,4 @@
#include "../include/Channel.hpp"
-#include <odhtdb/User.hpp>
#include <odhtdb/Database.hpp>
#include <odhtdb/bin2hex.hpp>
#include <cstring>
@@ -95,17 +94,15 @@ namespace dchat
void Channel::addMessage(const std::string &msg)
{
- if(database && localUser->type == User::Type::ONLINE)
+ if(database && localUser->type == User::Type::ONLINE_LOCAL_USER)
{
- auto localOnlineUser = static_cast<OnlineUser*>(localUser);
- assert(localOnlineUser->databaseUser->getType() == odhtdb::User::Type::LOCAL);
+ auto onlineLocalUser = static_cast<OnlineLocalUser*>(localUser);
sibs::SafeSerializer serializer;
serializer.add(ChannelDataType::ADD_MESSAGE);
serializer.add((const u8*)msg.data(), msg.size());
- database->addData(databaseNodeInfo, static_cast<const odhtdb::LocalUser*>(localOnlineUser->databaseUser), odhtdb::DataView(serializer.getBuffer().data(), serializer.getBuffer().size()));
- database->commit();
+ database->addData(databaseNodeInfo, onlineLocalUser->keyPair, odhtdb::DataView(serializer.getBuffer().data(), serializer.getBuffer().size()));
}
else
addLocalMessage(msg, localUser, 0, odhtdb::Hash());
@@ -118,17 +115,15 @@ namespace dchat
void Channel::deleteMessage(const odhtdb::Hash &id, const odhtdb::Signature::PublicKey &requestedByUser)
{
- if(database && localUser->type == User::Type::ONLINE)
+ if(database && localUser->type == User::Type::ONLINE_LOCAL_USER)
{
- auto localOnlineUser = static_cast<OnlineUser*>(localUser);
- assert(localOnlineUser->databaseUser->getType() == odhtdb::User::Type::LOCAL);
+ auto onlineLocalUser = static_cast<OnlineLocalUser*>(localUser);
sibs::SafeSerializer serializer;
serializer.add(ChannelDataType::DELETE_MESSAGE);
serializer.add((const u8*)id.getData(), odhtdb::HASH_BYTE_SIZE);
- database->addData(databaseNodeInfo, static_cast<const odhtdb::LocalUser*>(localOnlineUser->databaseUser), odhtdb::DataView(serializer.getBuffer().data(), serializer.getBuffer().size()));
- database->commit();
+ database->addData(databaseNodeInfo, onlineLocalUser->keyPair, odhtdb::DataView(serializer.getBuffer().data(), serializer.getBuffer().size()));
}
else
deleteLocalMessage(id, requestedByUser);
@@ -137,41 +132,39 @@ namespace dchat
void Channel::addUserLocally(User *user)
{
users.push_back(user);
- if(user->type == User::Type::ONLINE)
+ if(user->isOnlineUser())
{
auto onlineUser = static_cast<OnlineUser*>(user);
- publicKeyOnlineUsersMap[onlineUser->databaseUser->getPublicKey()] = onlineUser;
+ publicKeyOnlineUsersMap[onlineUser->getPublicKey()] = onlineUser;
}
}
- bool Channel::addUser(const odhtdb::Signature::PublicKey &userId, const string &groupId)
+ bool Channel::addUser(const odhtdb::Signature::PublicKey &userId, const odhtdb::DataView &groupId)
{
assert(database);
- if(!database || localUser->type != User::Type::ONLINE)
+ if(!database || localUser->type != User::Type::ONLINE_LOCAL_USER)
return false;
- if(groupId.size() != odhtdb::GROUP_ID_LENGTH)
+ if(groupId.size != odhtdb::GROUP_ID_LENGTH)
{
- fprintf(stderr, "Group id is wrong size. Expected to be %u bytes, was %u byte(s)\n", odhtdb::GROUP_ID_LENGTH, groupId.size());
+ fprintf(stderr, "Group id is wrong size. Expected to be %u bytes, was %u byte(s)\n", odhtdb::GROUP_ID_LENGTH, groupId.size);
return false;
}
- auto localOnlineUser = static_cast<OnlineUser*>(localUser);
- assert(localOnlineUser->databaseUser->getType() == odhtdb::User::Type::LOCAL);
-
- uint8_t groupIdRaw[odhtdb::GROUP_ID_LENGTH];
- memcpy(groupIdRaw, groupId.data(), groupId.size());
- auto groupToAddUserTo = database->getStorage().getGroupById(*databaseNodeInfo.getRequestHash(), groupIdRaw);
- if(!groupToAddUserTo)
+ auto onlineLocalUser = static_cast<OnlineLocalUser*>(localUser);
+ try
+ {
+ database->addUser(databaseNodeInfo, onlineLocalUser->keyPair, userId, groupId);
+ return true;
+ }
+ catch(std::exception &e)
{
- fprintf(stderr, "Group with id %s does not exist in channel %s\n", odhtdb::bin2hex(groupId.c_str(), groupId.size()).c_str(), databaseNodeInfo.getRequestHash()->toString().c_str());
+ fprintf(stderr, "Group with id %s does not exist in channel %s or you do not have permission to add user to that group\nError: %s\n", odhtdb::bin2hex((const char*)groupId.data, groupId.size).c_str(), databaseNodeInfo.getRequestHash()->toString().c_str(), e.what());
return false;
}
- database->addUser(databaseNodeInfo, static_cast<const odhtdb::LocalUser*>(localOnlineUser->databaseUser), "noname", userId, groupToAddUserTo);
- return true;
}
- void Channel::replaceLocalUser(User *newLocalUser)
+ void Channel::replaceLocalUser(OnlineLocalUser *newOnlineLocalUser)
{
for(vector<User*>::iterator it = users.begin(); it != users.end(); ++it)
{
@@ -183,24 +176,22 @@ namespace dchat
}
}
- localUser = newLocalUser;
- addUserLocally(newLocalUser);
+ localUser = newOnlineLocalUser;
+ addUserLocally(newOnlineLocalUser);
}
void Channel::changeNick(const std::string &newNick)
{
- if(database && localUser->type == User::Type::ONLINE)
+ if(database && localUser->type == User::Type::ONLINE_LOCAL_USER)
{
- auto localOnlineUser = static_cast<OnlineUser*>(localUser);
- assert(localOnlineUser->databaseUser->getType() == odhtdb::User::Type::LOCAL);
+ auto onlineLocalUser = static_cast<OnlineLocalUser*>(localUser);
sibs::SafeSerializer serializer;
serializer.add(ChannelDataType::NICKNAME_CHANGE);
serializer.add((u8)newNick.size());
serializer.add((const u8*)newNick.data(), newNick.size());
- database->addData(databaseNodeInfo, static_cast<const odhtdb::LocalUser*>(localOnlineUser->databaseUser), odhtdb::DataView(serializer.getBuffer().data(), serializer.getBuffer().size()));
- database->commit();
+ database->addData(databaseNodeInfo, onlineLocalUser->keyPair, odhtdb::DataView(serializer.getBuffer().data(), serializer.getBuffer().size()));
}
}
diff --git a/src/Command.cpp b/src/Command.cpp
index a074930..a28cd42 100644
--- a/src/Command.cpp
+++ b/src/Command.cpp
@@ -5,7 +5,7 @@ using namespace std;
namespace dchat
{
- unordered_map<string, CommandHandlerFunc> commandHandlerFuncs;
+ static unordered_map<string, CommandHandlerFunc> commandHandlerFuncs;
bool Command::add(const string &cmd, CommandHandlerFunc handlerFunc)
{
diff --git a/src/MessageBoard.cpp b/src/MessageBoard.cpp
index 46b9d28..b1f8b0b 100644
--- a/src/MessageBoard.cpp
+++ b/src/MessageBoard.cpp
@@ -16,7 +16,6 @@
#include <SFML/Window/Mouse.hpp>
#include <SFML/Graphics/Rect.hpp>
#include <SFML/Graphics/Text.hpp>
-#include <odhtdb/User.hpp>
#include <cmath>
using namespace std;
@@ -82,12 +81,12 @@ namespace dchat
lock_guard<mutex> lock(messageProcessMutex);
auto it = messageIdMap.find(id);
if(it == messageIdMap.end()) return;
- if(it->second->user->type == User::Type::ONLINE)
+ if(it->second->user->isOnlineUser())
{
auto onlineUser = static_cast<const OnlineUser*>(it->second->user);
- if(onlineUser->databaseUser->getPublicKey() != requestedByUser)
+ if(onlineUser->getPublicKey() != requestedByUser)
{
- fprintf(stderr, "Warning: user %s requested to delete a message owned by user %s, ignoring request\n", requestedByUser.toString().c_str(), onlineUser->databaseUser->getPublicKey().toString().c_str());
+ fprintf(stderr, "Warning: user %s requested to delete a message owned by user %s, ignoring request\n", requestedByUser.toString().c_str(), onlineUser->getPublicKey().toString().c_str());
return;
}
}
@@ -292,11 +291,11 @@ namespace dchat
message->text.processEvent(event);
}
- OnlineUser *localOnlineUser = nullptr;
- if(channel->getLocalUser()->type == User::Type::ONLINE)
- localOnlineUser = static_cast<OnlineUser*>(channel->getLocalUser());
+ OnlineLocalUser *onlineLocalUser = nullptr;
+ if(channel->getLocalUser()->type == User::Type::ONLINE_LOCAL_USER)
+ onlineLocalUser = static_cast<OnlineLocalUser*>(channel->getLocalUser());
- if(localOnlineUser && event.type == sf::Event::MouseButtonPressed && event.mouseButton.button == sf::Mouse::Button::Right)
+ if(onlineLocalUser && event.type == sf::Event::MouseButtonPressed && event.mouseButton.button == sf::Mouse::Button::Right)
{
for(auto it : messageIdMap)
{
@@ -307,9 +306,9 @@ namespace dchat
auto contextMenu = GlobalContextMenu::getEditMessageContextMenu();
contextMenu->setPosition(sf::Vector2f(event.mouseButton.x, event.mouseButton.y));
contextMenu->setVisible(true);
- GlobalContextMenu::setClickDeleteMessageCallbackFunc([this, it, localOnlineUser](ContextMenuItem *menuItem)
+ GlobalContextMenu::setClickDeleteMessageCallbackFunc([this, it, onlineLocalUser](ContextMenuItem *menuItem)
{
- channel->deleteMessage(it.first, localOnlineUser->databaseUser->getPublicKey());
+ channel->deleteMessage(it.first, onlineLocalUser->getPublicKey());
GlobalContextMenu::setClickDeleteMessageCallbackFunc(nullptr);
});
return;
diff --git a/src/User.cpp b/src/User.cpp
index 16b3648..986380c 100644
--- a/src/User.cpp
+++ b/src/User.cpp
@@ -1,6 +1,4 @@
#include "../include/User.hpp"
-#include <odhtdb/User.hpp>
-#include <cassert>
namespace dchat
{
@@ -12,18 +10,41 @@ namespace dchat
}
- OnlineUser::OnlineUser(const odhtdb::User *_databaseUser) :
- User(Type::ONLINE),
- name("randomUser69"),
- databaseUser(_databaseUser)
+ OnlineUser::OnlineUser(const std::string &_name, Type type) :
+ User(type),
+ name(_name)
{
- assert(databaseUser);
+
}
const std::string& OnlineUser::getName() const
{
return name;
}
+
+ OnlineRemoteUser::OnlineRemoteUser(const std::string &name, const odhtdb::Signature::PublicKey &_publicKey) :
+ OnlineUser(name, Type::ONLINE_REMOTE_USER),
+ publicKey(_publicKey)
+ {
+
+ }
+
+ const odhtdb::Signature::PublicKey& OnlineRemoteUser::getPublicKey() const
+ {
+ return publicKey;
+ }
+
+ OnlineLocalUser::OnlineLocalUser(const std::string &name, const odhtdb::Signature::KeyPair &_keyPair) :
+ OnlineUser(name, Type::ONLINE_LOCAL_USER),
+ keyPair(_keyPair)
+ {
+
+ }
+
+ const odhtdb::Signature::PublicKey& OnlineLocalUser::getPublicKey() const
+ {
+ return keyPair.getPublicKey();
+ }
OfflineUser::OfflineUser(const std::string &_name) :
User(Type::OFFLINE),
diff --git a/src/main.cpp b/src/main.cpp
index 18f10fe..249c5d4 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -18,6 +18,7 @@
#include <odhtdb/bin2hex.hpp>
#include <odhtdb/hex2bin.hpp>
#include <ntp/NtpClient.hpp>
+#include <sibs/SafeSerializer.hpp>
#include <X11/Xlib.h>
using namespace std;
@@ -128,19 +129,6 @@ void channelAddStoredMessage(Channel *channel, const odhtdb::Hash &requestHash,
}
}
-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)
{
/*
@@ -158,8 +146,6 @@ int main(int argc, char **argv)
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;
@@ -171,51 +157,69 @@ int main(int argc, char **argv)
vector<Channel*> channels;
- odhtdb::Signature::KeyPair *currentUserKeyPair = nullptr;
- vector<odhtdb::NodeLocalUser> localNodeUsers;
vector<odhtdb::DatabaseNode> waitingToJoinChannels;
- string currentUserName;
- string currentUserPassword;
+ odhtdb::MapHash<odhtdb::StoredNodeInfo> localNodeUsers;
+ string currentUsername;
+ string currentPassword;
recursive_mutex channelMessageMutex;
bool waitingToJoin = false;
+ bool loggedIn = false;
+
+ odhtdb::Database *database = nullptr;
+ odhtdb::DatabaseCallbackFuncs callbackFuncs;
- database.setOnCreateNodeCallback([&waitingToJoinChannels, &database, &channels, &channelMessageMutex, &waitingToJoin](const odhtdb::DatabaseCreateNodeRequest &request)
+ callbackFuncs.createNodeCallbackFunc = [&waitingToJoinChannels, &database, &channels, &channelMessageMutex, &waitingToJoin, &localNodeUsers](const odhtdb::DatabaseCreateNodeRequest &request)
{
lock_guard<recursive_mutex> lock(channelMessageMutex);
+
+ 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<odhtdb::Hash> databaseNodeHash = make_shared<odhtdb::Hash>();
+ 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);
+
+ if(localUser->type == User::Type::OFFLINE)
+ {
+ User *nodeCreatorUser = new OnlineRemoteUser("NoName", *request.creatorPublicKey);
+ channel->addUserLocally(nodeCreatorUser);
+ }
+
for(vector<odhtdb::DatabaseNode>::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)
+ callbackFuncs.addNodeCallbackFunc = [&channels, &channelMessageMutex](const odhtdb::DatabaseAddNodeRequest &request)
{
lock_guard<recursive_mutex> 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);
+ channelAddStoredMessage(channel, *request.requestHash, *request.creatorPublicKey, StringView((const char*)request.decryptedData.data, request.decryptedData.size), request.timestamp);
return;
}
}
- });
+ };
- database.setOnAddUserCallback([&currentUserKeyPair, &channels, &channelMessageMutex, &waitingToJoin, &database](const odhtdb::DatabaseAddUserRequest &request)
+ callbackFuncs.addUserCallbackFunc = [&channels, &channelMessageMutex, &waitingToJoin, &localNodeUsers](const odhtdb::DatabaseAddUserRequest &request)
{
lock_guard<recursive_mutex> lock(channelMessageMutex);
printf("Add user callback. Channel to add user to: %s\n", request.nodeHash->toString().c_str());
@@ -225,34 +229,35 @@ int main(int argc, char **argv)
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)
+ auto nodeUserData = localNodeUsers.find(*request.nodeHash);
+
+ User *userToAdd = channel->getUserByPublicKey(*request.userToAddPublicKey);
+ if(userToAdd)
{
- // 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;
+ fprintf(stderr, "User %s already exists in channel\n", request.userToAddPublicKey->toString().c_str());
return;
}
- User *userToAdd = channel->getUserByPublicKey(request.userToAdd->getPublicKey());
- if(userToAdd)
+ if(*request.userToAddPublicKey == nodeUserData->second.userKeyPair->getPublicKey())
{
- fprintf(stderr, "User %s already exists in channel\n", request.userToAdd->getPublicKey().toString().c_str());
+ 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 OnlineUser(request.userToAdd);
+ User *newRemoteUser = new OnlineRemoteUser("NoName", *request.userToAddPublicKey);
channel->addUserLocally(newRemoteUser);
return;
}
}
- });
+ };
+ database = new odhtdb::Database("bootstrap.ring.cx", 4222, Cache::getDchatDir(), callbackFuncs);
// Login to account
- Command::add("login", [&currentUserKeyPair, &currentUserName, &currentUserPassword, &localNodeUsers, &database, &channels, &channelMessageMutex](const vector<string> &args)
+ Command::add("login", [&localNodeUsers, &database, &channels, &loggedIn, &channelMessageMutex, &currentUsername, &currentPassword](const vector<string> &args)
{
+ lock_guard<recursive_mutex> lock(channelMessageMutex);
if(args.size() != 2)
{
fprintf(stderr, "Expected 2 arguments for command login (username and password), got %u argument(s)\n", args.size());
@@ -261,8 +266,7 @@ int main(int argc, char **argv)
try
{
- odhtdb::Signature::KeyPair keyPair = database.getStorage().decryptLocalEncryptedUser(args[0], args[1]);
- localNodeUsers = database.getStorage().getLocalNodeUsers(keyPair);
+ localNodeUsers = database->getStoredNodeUserInfoDecrypted(args[0], args[1]);
ChannelSidePanel::removeAllChannels();
for(Channel *channel : channels)
@@ -271,51 +275,25 @@ int main(int argc, char **argv)
}
channels.clear();
- lock_guard<recursive_mutex> lock(channelMessageMutex);
- for(auto localNodeUser : localNodeUsers)
+ printf("Loading %u channel(s) for user\n", localNodeUsers.size());
+ 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<odhtdb::Hash>(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);
+ database->loadNode(localNodeUser.first);
}
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];
+ currentUsername = args[0];
+ currentPassword = args[1];
+ loggedIn = true;
}
- catch(odhtdb::DatabaseStorageException &e)
+ catch(std::exception &e)
{
fprintf(stderr, "Failed to login, reason: %s\n", e.what());
}
});
// Register account
- Command::add("register", [&currentUserKeyPair, &currentUserName, &currentUserPassword, &localNodeUsers, &database, &channelMessageMutex](const vector<string> &args)
+ Command::add("register", [&localNodeUsers, &database, &channels, &loggedIn, &channelMessageMutex, &currentUsername, &currentPassword](const vector<string> &args)
{
lock_guard<recursive_mutex> lock(channelMessageMutex);
if(args.size() != 2)
@@ -324,33 +302,34 @@ int main(int argc, char **argv)
return;
}
- if(currentUserKeyPair)
+ try
{
- fprintf(stderr, "You can't register a new account when you are logged in, please logout first\n");
- return;
+ database->storeUserWithoutNodes(args[0], args[1]);
}
-
- odhtdb::Signature::KeyPair keyPair;
- if(!database.getStorage().storeLocalUser(args[0], keyPair, 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, 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());
+ printf("Registered user %s\n", args[0].c_str());
- if(currentUserKeyPair)
- delete currentUserKeyPair;
- currentUserKeyPair = new odhtdb::Signature::KeyPair(keyPair);
+ ChannelSidePanel::removeAllChannels();
+ for(Channel *channel : channels)
+ {
+ delete channel;
+ }
+ channels.clear();
localNodeUsers.clear();
- currentUserName = args[0];
- currentUserPassword = args[1];
+ printf("Successfully logged into user %s\n", args[0].c_str());
+ currentUsername = args[0];
+ currentPassword = args[1];
+ loggedIn = true;
});
// Create channel
- Command::add("cc", [&currentUserKeyPair, &currentUserName, &database, &channels, &channelMessageMutex](const vector<string> &args)
+ Command::add("cc", [&database, &channels, &channelMessageMutex, &loggedIn, &localNodeUsers, &currentUsername, &currentPassword](const vector<string> &args)
{
lock_guard<recursive_mutex> lock(channelMessageMutex);
if(args.size() != 1)
@@ -359,69 +338,113 @@ int main(int argc, char **argv)
return;
}
- if(!currentUserKeyPair)
+ if(!loggedIn)
{
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();
+ auto createResponse = database->create();
printf("Created database '%s', join key: '%s'\n", args[0].c_str(), createChannelJoinKey(createResponse).c_str());
- User *newLocalUser = new OnlineUser(createResponse->getNodeAdminUser());
+ User *newLocalUser = new OnlineLocalUser("NoName", *createResponse->getNodeAdminKeyPair());
odhtdb::DatabaseNode databaseNode(createResponse->getNodeEncryptionKey(), createResponse->getRequestHash());
- Channel *channel = new Channel(args[0], databaseNode, newLocalUser, &database);
+ Channel *channel = new Channel(args[0], databaseNode, newLocalUser, database);
ChannelSidePanel::addChannel(channel);
channels.push_back(channel);
Channel::setCurrent(channel);
+
+ localNodeUsers[*createResponse->getRequestHash()] = { createResponse->getNodeEncryptionKey(), createResponse->getNodeAdminKeyPair() };
+ database->storeNodeInfoForUserEncrypted(databaseNode, currentUsername, currentPassword, *createResponse->getNodeAdminKeyPair());
+ Channel::getCurrent()->addLocalMessage("Channel created and stored in database", Channel::getCurrent()->getSystemUser());
});
- // Add user
- Command::add("au", [&offlineChannel, &channelMessageMutex](const vector<string> &args)
+ // Create invite key
+ Command::add("invite", [&channelMessageMutex, &offlineChannel, &database](const vector<string> &args)
{
lock_guard<recursive_mutex> 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)
+ if(args.size() != 0)
{
- 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());
+ fprintf(stderr, "Expected 0 arguments for command invite, got %u argument(s)\n", args.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());
+ Channel::getCurrent()->addLocalMessage("You need to be in a channel to create an invite key", Channel::getCurrent()->getSystemUser());
return;
}
- auto userIdRaw = odhtdb::hex2bin(args[0].c_str(), args[0].size());
- odhtdb::Signature::PublicKey userPublicKey(userIdRaw.data(), userIdRaw.size());
+ // 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 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
+ auto channelNodeHash = currentChannel->getNodeInfo().getRequestHash();
+ auto channelEncryptionKey = currentChannel->getNodeInfo().getNodeEncryptionKey();
+ shared_ptr<odhtdb::OwnedMemory> encryptionKey = make_shared<odhtdb::OwnedMemory>(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;
+
+ Channel::getCurrent()->addLocalMessage(msg, Channel::getCurrent()->getSystemUser());
+ 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)
{
- fprintf(stderr, "Failed to add user to your channel!\n");
- }
+ // 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<OnlineLocalUser*>(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<recursive_mutex> lock(channelMessageMutex);
+ currentChannel->addUser(localUserPublicKey, 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();
+ });
});
- Command::add("jc", [&currentUserKeyPair, &database, &localNodeUsers, &channelMessageMutex, &waitingToJoin, &waitingToJoinChannels](const vector<string> &args)
+ // Join channel using invite key
+ Command::add("jc", [&loggedIn, &database, &localNodeUsers, &channelMessageMutex, &waitingToJoin, &waitingToJoinChannels, &currentUsername, &currentPassword](const vector<string> &args)
{
lock_guard<recursive_mutex> lock(channelMessageMutex);
if(args.size() != 1)
@@ -436,29 +459,90 @@ int main(int argc, char **argv)
return;
}
- if(!currentUserKeyPair)
+ if(!loggedIn)
{
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)
+ string nodeHashBinRaw = odhtdb::hex2bin(args[0].c_str(), 64);
+ shared_ptr<odhtdb::Hash> nodeHash = make_shared<odhtdb::Hash>();
+ memcpy(nodeHash->getData(), nodeHashBinRaw.data(), nodeHashBinRaw.size());
+ auto nodeUserIt = localNodeUsers.find(*nodeHash);
+ if(nodeUserIt != localNodeUsers.end())
{
- if(*databaseNode.getRequestHash() == localNodeUser.nodeHash)
- {
- fprintf(stderr, "You have already joined the channel %s\n", databaseNode.getRequestHash()->toString().c_str());
- return;
- }
+ fprintf(stderr, "You have already joined the channel %s\n", args[0].c_str());
+ return;
}
- database.seed(databaseNode);
- waitingToJoinChannels.push_back(databaseNode);
+ string encryptionKeyBinRaw = odhtdb::hex2bin(args[0].c_str() + 65, 64);
+ shared_ptr<odhtdb::OwnedMemory> encryptionKey = make_shared<odhtdb::OwnedMemory>(new u8[odhtdb::ENCRYPTION_KEY_BYTE_SIZE], odhtdb::ENCRYPTION_KEY_BYTE_SIZE);
+ memcpy(encryptionKey->data, encryptionKeyBinRaw.data(), encryptionKeyBinRaw.size());
+
+ shared_ptr<odhtdb::Signature::KeyPair> keyPair = make_shared<odhtdb::Signature::KeyPair>();
+ sibs::SafeSerializer serializer;
+ serializer.add((const u8*)keyPair->getPublicKey().getData(), keyPair->getPublicKey().getSize());
+
+ localNodeUsers[*nodeHash] = { encryptionKey, keyPair };
+
+ 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, &currentUsername, &currentPassword, &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 mean for us?\n");
+ return true;
+ }
+
+ odhtdb::DataView channelEncryptionKey(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;
+ }
+
+ odhtdb::DatabaseNode databaseNode(encryptionKey, nodeHash);
+ 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<recursive_mutex> lock(channelMessageMutex);
+ database->seed(databaseNode);
+ 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;
- // 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
@@ -556,25 +640,8 @@ int main(int argc, char **argv)
Channel::getCurrent()->addLocalMessage(msg, Channel::getCurrent()->getSystemUser());
});
- // Get username and id (public key)
- Command::add("whoami", [&currentUserKeyPair, &currentUserName](const vector<string> &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", [&currentUserKeyPair, &offlineChannel](const vector<string> &args)
+ Command::add("nick", [&loggedIn, &offlineChannel](const vector<string> &args)
{
if(args.size() != 1)
{
@@ -585,7 +652,7 @@ int main(int argc, char **argv)
return;
}
- if(!currentUserKeyPair)
+ if(!loggedIn)
{
Channel::getCurrent()->addLocalMessage("You need to be logged in to change your nickname", Channel::getCurrent()->getSystemUser());
return;
@@ -610,7 +677,7 @@ int main(int argc, char **argv)
});
// Get channel join key
- Command::add("joinkey", [&currentUserKeyPair, &currentUserName, &offlineChannel](const vector<string> &args)
+ Command::add("joinkey", [&offlineChannel](const vector<string> &args)
{
Channel *currentChannel = Channel::getCurrent();
if(!currentChannel || currentChannel == &offlineChannel)