diff --git a/Readme.md b/Readme.md index 87f0838..76c1e46 100644 --- a/Readme.md +++ b/Readme.md @@ -22,7 +22,7 @@ The result will include: ```bash # Build only the library (plugin) -nix build '.#logos-chat-module-lib' +nix build '.#lib' # Build only the generated headers nix build '.#logos-chat-module-include' diff --git a/chat_interface.h b/chat_interface.h index b165742..b504354 100644 --- a/chat_interface.h +++ b/chat_interface.h @@ -1,26 +1,43 @@ #pragma once #include +#include #include "interface.h" #include // Define a callback type for message handling using MessageCallback = std::function; +// Discovery mode enum +enum class DiscoveryMode { + ExtKadOnly = 0, // Extended Kademlia only + StdDiscovery = 1, // Rendezvous + Peer Exchange (no discv5) + All = 2 // Kad + Rendezvous + Peer Exchange +}; + class ChatInterface : public PluginInterface { public: virtual ~ChatInterface() {} - // Core chat functionality - Q_INVOKABLE virtual bool initialize() = 0; + // Initialize with JSON configuration + // configJson: JSON object with fields: + // "mode": int (0=ExtKadOnly, 1=StdDiscovery, 2=All) + // "bootstrapNodes": comma-separated multiaddr strings + // "mixnodes": comma-separated "multiaddr:mixPubKey" strings + // "storeNode": multiaddr of the store node for history retrieval + Q_INVOKABLE virtual bool initialize(const QString& configJson) = 0; Q_INVOKABLE virtual bool joinChannel(const QString& channelName) = 0; Q_INVOKABLE virtual void sendMessage(const QString& channelName, const QString& username, const QString& message) = 0; Q_INVOKABLE virtual bool retrieveHistory(const std::string& channelName) = 0; + // Network metrics + Q_INVOKABLE virtual bool getMixnodePoolSize() = 0; + Q_INVOKABLE virtual bool getLightpushPeersCount() = 0; + signals: // for now this is required for events, later it might not be necessary if using a proxy void eventResponse(const QString& eventName, const QVariantList& data); }; #define ChatInterface_iid "org.logos.ChatInterface" -Q_DECLARE_INTERFACE(ChatInterface, ChatInterface_iid) \ No newline at end of file +Q_DECLARE_INTERFACE(ChatInterface, ChatInterface_iid) diff --git a/chat_plugin.cpp b/chat_plugin.cpp index 50cd4fd..e6f5502 100644 --- a/chat_plugin.cpp +++ b/chat_plugin.cpp @@ -3,31 +3,41 @@ #include #include #include +#include +#include +#include #include "logos_api_client.h" -ChatPlugin::ChatPlugin() : currentRelayTopic("/waku/2/rs/16/32") { +ChatPlugin::ChatPlugin() : currentRelayTopic("") +{ } -ChatPlugin::~ChatPlugin() { - if (logos) { +ChatPlugin::~ChatPlugin() +{ + if (logos) + { delete logos; logos = nullptr; } - if (logosAPI) { + if (logosAPI) + { delete logosAPI; logosAPI = nullptr; } } -bool ChatPlugin::ensureLogosContext(const char* caller) const { +bool ChatPlugin::ensureLogosContext(const char *caller) const +{ const QString context = QString::fromLatin1(caller ? caller : "unknown"); - if (!logosAPI) { + if (!logosAPI) + { qWarning() << "ChatPlugin:" << context << "- LogosAPI not initialized"; return false; } - if (!logos) { + if (!logos) + { qWarning() << "ChatPlugin:" << context << "- LogosModules not initialized"; return false; } @@ -35,32 +45,85 @@ bool ChatPlugin::ensureLogosContext(const char* caller) const { return true; } -bool ChatPlugin::initialize() { - if (!ensureLogosContext("initialize")) { +bool ChatPlugin::initialize(const QString &configJson) +{ + if (!ensureLogosContext("initialize")) + { return false; } - MessageCallback actualCallback = [this](const std::string& timestamp, const std::string& nick, const std::string& message) { + // Parse JSON configuration + QJsonDocument doc = QJsonDocument::fromJson(configJson.toUtf8()); + if (!doc.isObject()) + { + qWarning() << "ChatPlugin::initialize - Invalid JSON config:" << configJson; + return false; + } + + QJsonObject config = doc.object(); + int mode = config["mode"].toInt(0); + QString bootstrapNodes = config["bootstrapNodes"].toString(); + QString mixnodes = config["mixnodes"].toString(); + QString storeNode = config["storeNode"].toString(); + QString nodeKey = config["nodeKey"].toString(); + + // Parse comma-separated strings and convert to std::vector + QStringList bootstrapNodesList = bootstrapNodes.split(",", Qt::SkipEmptyParts); + QStringList mixnodesList = mixnodes.split(",", Qt::SkipEmptyParts); + + std::vector bootstrapNodesVec; + for (const QString &node : bootstrapNodesList) + { + bootstrapNodesVec.push_back(node.trimmed().toStdString()); + } + + std::vector mixnodesVec; + for (const QString &mixnode : mixnodesList) + { + mixnodesVec.push_back(mixnode.trimmed().toStdString()); + } + + DiscoveryMode discoveryMode = static_cast(mode); + + qDebug() << "ChatPlugin::initialize - mode:" << mode + << ", bootstrap nodes:" << bootstrapNodesList.size() + << ", mixnodes:" << mixnodesList.size() + << ", storeNode:" << storeNode; + + MessageCallback actualCallback = [this](const std::string ×tamp, const std::string &nick, const std::string &message) + { QVariantList data; data << QString::fromStdString(timestamp) << QString::fromStdString(nick) << QString::fromStdString(message); emitEvent(QStringLiteral("chatMessage"), data); }; - void* result = ::initAndStart(logosAPI, logos, currentRelayTopic, actualCallback); + // Subscribe to network metrics events once during initialization + logos->waku_module.on("mixnodePoolSizeResponse", [this](const QVariantList &data) + { emitEvent(QStringLiteral("mixnodePoolSizeResponse"), data); }); + + logos->waku_module.on("lightpushPeersCountResponse", [this](const QVariantList &data) + { emitEvent(QStringLiteral("lightpushPeersCountResponse"), data); }); + + void *result = ::initAndStart(logosAPI, logos, currentRelayTopic, actualCallback, discoveryMode, bootstrapNodesVec, mixnodesVec, + storeNode.toStdString(), nodeKey.toStdString()); return (result != nullptr); } -bool ChatPlugin::joinChannel(const QString& channelName) { - if (!ensureLogosContext("joinChannel")) { +bool ChatPlugin::joinChannel(const QString &channelName) +{ + if (!ensureLogosContext("joinChannel")) + { return false; } return ::joinChannel(logosAPI, logos, channelName.toStdString(), currentRelayTopic); } -void ChatPlugin::sendMessage(const QString& channelName, const QString& username, const QString& message) { - if (!ensureLogosContext("sendMessage")) { +void ChatPlugin::sendMessage(const QString &channelName, const QString &username, const QString &message) +{ + if (!ensureLogosContext("sendMessage")) + { return; } std::cout << "ChatPlugin::sendMessage called with channelName: " << channelName.toStdString() @@ -69,12 +132,15 @@ void ChatPlugin::sendMessage(const QString& channelName, const QString& username ::sendMessage(logosAPI, logos, channelName.toStdString(), username.toStdString(), message.toStdString()); } -bool ChatPlugin::retrieveHistory(const std::string& channelName) { - if (!ensureLogosContext("retrieveHistory")) { +bool ChatPlugin::retrieveHistory(const std::string &channelName) +{ + if (!ensureLogosContext("retrieveHistory")) + { return false; } - MessageCallback actualCallback = [this](const std::string& timestamp, const std::string& nick, const std::string& message) { + MessageCallback actualCallback = [this](const std::string ×tamp, const std::string &nick, const std::string &message) + { QVariantList data; data << QString::fromStdString(timestamp) << QString::fromStdString(nick) << QString::fromStdString(message); @@ -85,36 +151,64 @@ bool ChatPlugin::retrieveHistory(const std::string& channelName) { return true; } -bool ChatPlugin::retrieveHistory(const QString& channelName) { +bool ChatPlugin::retrieveHistory(const QString &channelName) +{ return retrieveHistory(channelName.toStdString()); } -void ChatPlugin::initLogos(LogosAPI* logosAPIInstance) { - if (logos) { +void ChatPlugin::initLogos(LogosAPI *logosAPIInstance) +{ + if (logos) + { delete logos; logos = nullptr; } - if (logosAPI) { + if (logosAPI) + { delete logosAPI; logosAPI = nullptr; } logosAPI = logosAPIInstance; - if (logosAPI) { + if (logosAPI) + { logos = new LogosModules(logosAPI); } } -void ChatPlugin::emitEvent(const QString& eventName, const QVariantList& data) { - if (!logosAPI) { +void ChatPlugin::emitEvent(const QString &eventName, const QVariantList &data) +{ + if (!logosAPI) + { qWarning() << "ChatPlugin: LogosAPI not available, cannot emit" << eventName; return; } - LogosAPIClient* client = logosAPI->getClient("chat"); - if (!client) { + LogosAPIClient *client = logosAPI->getClient("chat"); + if (!client) + { qWarning() << "ChatPlugin: Failed to get chat client for event" << eventName; return; } client->onEventResponse(this, eventName, data); } + +bool ChatPlugin::getMixnodePoolSize() +{ + if (!ensureLogosContext("getMixnodePoolSize")) + { + return false; + } + + return logos->waku_module.getMixnodePoolSize(); +} + +bool ChatPlugin::getLightpushPeersCount() +{ + if (!ensureLogosContext("getLightpushPeersCount")) + { + return false; + } + + return logos->waku_module.getLightpushPeersCount(); +} diff --git a/chat_plugin.h b/chat_plugin.h index a4e35c8..0086ae0 100644 --- a/chat_plugin.h +++ b/chat_plugin.h @@ -21,12 +21,16 @@ class ChatPlugin : public QObject, public ChatInterface { QString version() const override { return "1.0.0"; } // ChatInterface implementation - Q_INVOKABLE bool initialize() override; + Q_INVOKABLE bool initialize(const QString& configJson) override; Q_INVOKABLE bool joinChannel(const QString& channelName) override; Q_INVOKABLE void sendMessage(const QString& channelName, const QString& username, const QString& message) override; Q_INVOKABLE bool retrieveHistory(const std::string& channelName) override; Q_INVOKABLE bool retrieveHistory(const QString& channelName); + // Network metrics + Q_INVOKABLE bool getMixnodePoolSize() override; + Q_INVOKABLE bool getLightpushPeersCount() override; + // LogosAPI initialization Q_INVOKABLE void initLogos(LogosAPI* logosAPIInstance); diff --git a/flake.lock b/flake.lock index e9c5025..b699148 100644 --- a/flake.lock +++ b/flake.lock @@ -1,15 +1,84 @@ { "nodes": { + "logos-capability-module": { + "inputs": { + "logos-cpp-sdk": "logos-cpp-sdk_2", + "logos-liblogos": "logos-liblogos_2", + "nixpkgs": [ + "logos-liblogos", + "logos-capability-module", + "logos-liblogos", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1767809111, + "narHash": "sha256-jehjsB+BpDJlVu3I7x+vFVOdXmy9MDmFTJtRqzFUONo=", + "owner": "logos-co", + "repo": "logos-capability-module", + "rev": "7b35383e0aa4e28a4633ed18a87efb57636939b1", + "type": "github" + }, + "original": { + "owner": "logos-co", + "repo": "logos-capability-module", + "type": "github" + } + }, + "logos-capability-module_2": { + "inputs": { + "logos-cpp-sdk": "logos-cpp-sdk_7", + "logos-liblogos": "logos-liblogos_4", + "nixpkgs": [ + "logos-waku-module", + "logos-liblogos", + "logos-capability-module", + "logos-liblogos", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1767809111, + "narHash": "sha256-jehjsB+BpDJlVu3I7x+vFVOdXmy9MDmFTJtRqzFUONo=", + "owner": "logos-co", + "repo": "logos-capability-module", + "rev": "7b35383e0aa4e28a4633ed18a87efb57636939b1", + "type": "github" + }, + "original": { + "owner": "logos-co", + "repo": "logos-capability-module", + "type": "github" + } + }, "logos-cpp-sdk": { "inputs": { "nixpkgs": "nixpkgs" }, "locked": { - "lastModified": 1761230734, - "narHash": "sha256-CMRUwXH7pJZ1OI6bd/TDDDXKqQ1tQZHQEOOwK8TgYHI=", + "lastModified": 1770132997, + "narHash": "sha256-Iv0QMXMD6kf+y2Qx37jXR7Ik6h1dqOzuxBzCdc5S6KA=", + "owner": "logos-co", + "repo": "logos-cpp-sdk", + "rev": "30ef7986f4b65b7dcf43af84bb073233b1b77821", + "type": "github" + }, + "original": { "owner": "logos-co", "repo": "logos-cpp-sdk", - "rev": "4b143922c190df00bb3835441c9f0075cb28283b", + "type": "github" + } + }, + "logos-cpp-sdk_10": { + "inputs": { + "nixpkgs": "nixpkgs_10" + }, + "locked": { + "lastModified": 1767724329, + "narHash": "sha256-UPkqxqxbKwU5Dmu00TnjiJVXUmfVylF3p1qziEuYwIE=", + "owner": "logos-co", + "repo": "logos-cpp-sdk", + "rev": "32f1d7080d784ff044d91d076ef2f0c7305d4784", "type": "github" }, "original": { @@ -58,6 +127,60 @@ "inputs": { "nixpkgs": "nixpkgs_4" }, + "locked": { + "lastModified": 1767724329, + "narHash": "sha256-UPkqxqxbKwU5Dmu00TnjiJVXUmfVylF3p1qziEuYwIE=", + "owner": "logos-co", + "repo": "logos-cpp-sdk", + "rev": "32f1d7080d784ff044d91d076ef2f0c7305d4784", + "type": "github" + }, + "original": { + "owner": "logos-co", + "repo": "logos-cpp-sdk", + "type": "github" + } + }, + "logos-cpp-sdk_5": { + "inputs": { + "nixpkgs": "nixpkgs_5" + }, + "locked": { + "lastModified": 1767724329, + "narHash": "sha256-UPkqxqxbKwU5Dmu00TnjiJVXUmfVylF3p1qziEuYwIE=", + "owner": "logos-co", + "repo": "logos-cpp-sdk", + "rev": "32f1d7080d784ff044d91d076ef2f0c7305d4784", + "type": "github" + }, + "original": { + "owner": "logos-co", + "repo": "logos-cpp-sdk", + "type": "github" + } + }, + "logos-cpp-sdk_6": { + "inputs": { + "nixpkgs": "nixpkgs_6" + }, + "locked": { + "lastModified": 1770132997, + "narHash": "sha256-Iv0QMXMD6kf+y2Qx37jXR7Ik6h1dqOzuxBzCdc5S6KA=", + "owner": "logos-co", + "repo": "logos-cpp-sdk", + "rev": "30ef7986f4b65b7dcf43af84bb073233b1b77821", + "type": "github" + }, + "original": { + "owner": "logos-co", + "repo": "logos-cpp-sdk", + "type": "github" + } + }, + "logos-cpp-sdk_7": { + "inputs": { + "nixpkgs": "nixpkgs_7" + }, "locked": { "lastModified": 1761230734, "narHash": "sha256-CMRUwXH7pJZ1OI6bd/TDDDXKqQ1tQZHQEOOwK8TgYHI=", @@ -72,9 +195,47 @@ "type": "github" } }, + "logos-cpp-sdk_8": { + "inputs": { + "nixpkgs": "nixpkgs_8" + }, + "locked": { + "lastModified": 1761230734, + "narHash": "sha256-CMRUwXH7pJZ1OI6bd/TDDDXKqQ1tQZHQEOOwK8TgYHI=", + "owner": "logos-co", + "repo": "logos-cpp-sdk", + "rev": "4b143922c190df00bb3835441c9f0075cb28283b", + "type": "github" + }, + "original": { + "owner": "logos-co", + "repo": "logos-cpp-sdk", + "type": "github" + } + }, + "logos-cpp-sdk_9": { + "inputs": { + "nixpkgs": "nixpkgs_9" + }, + "locked": { + "lastModified": 1767724329, + "narHash": "sha256-UPkqxqxbKwU5Dmu00TnjiJVXUmfVylF3p1qziEuYwIE=", + "owner": "logos-co", + "repo": "logos-cpp-sdk", + "rev": "32f1d7080d784ff044d91d076ef2f0c7305d4784", + "type": "github" + }, + "original": { + "owner": "logos-co", + "repo": "logos-cpp-sdk", + "type": "github" + } + }, "logos-liblogos": { "inputs": { - "logos-cpp-sdk": "logos-cpp-sdk_2", + "logos-capability-module": "logos-capability-module", + "logos-cpp-sdk": "logos-cpp-sdk_4", + "logos-module": "logos-module", "nixpkgs": [ "logos-liblogos", "logos-cpp-sdk", @@ -82,11 +243,11 @@ ] }, "locked": { - "lastModified": 1761240888, - "narHash": "sha256-ontCz4u3QkUBhEeNSHFSAT9tcoYCuWhu1Q3lrY2khiU=", + "lastModified": 1770837874, + "narHash": "sha256-wr75lv1q4U1FS5+l/6ypwzJFJe06l2RyUvx1npoRS88=", "owner": "logos-co", "repo": "logos-liblogos", - "rev": "d3946f80a924d8c01c802a627295bef0bf264beb", + "rev": "e3741c01fd3abf6b7bd9ff2fa8edf89c41fc0cea", "type": "github" }, "original": { @@ -97,7 +258,34 @@ }, "logos-liblogos_2": { "inputs": { - "logos-cpp-sdk": "logos-cpp-sdk_4", + "logos-cpp-sdk": "logos-cpp-sdk_3", + "nixpkgs": [ + "logos-liblogos", + "logos-capability-module", + "logos-liblogos", + "logos-cpp-sdk", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1761845775, + "narHash": "sha256-ulK8xq05ejK6qIgZ7WtWb/MJt2rk5BKfDA2z7mM3wq8=", + "owner": "logos-co", + "repo": "logos-liblogos", + "rev": "a92c2c1268bc70764c8f73c7bce07d21024f5af9", + "type": "github" + }, + "original": { + "owner": "logos-co", + "repo": "logos-liblogos", + "type": "github" + } + }, + "logos-liblogos_3": { + "inputs": { + "logos-capability-module": "logos-capability-module_2", + "logos-cpp-sdk": "logos-cpp-sdk_9", + "logos-module": "logos-module_2", "nixpkgs": [ "logos-waku-module", "logos-liblogos", @@ -106,11 +294,11 @@ ] }, "locked": { - "lastModified": 1761240888, - "narHash": "sha256-ontCz4u3QkUBhEeNSHFSAT9tcoYCuWhu1Q3lrY2khiU=", + "lastModified": 1770837874, + "narHash": "sha256-wr75lv1q4U1FS5+l/6ypwzJFJe06l2RyUvx1npoRS88=", "owner": "logos-co", "repo": "logos-liblogos", - "rev": "d3946f80a924d8c01c802a627295bef0bf264beb", + "rev": "e3741c01fd3abf6b7bd9ff2fa8edf89c41fc0cea", "type": "github" }, "original": { @@ -119,10 +307,108 @@ "type": "github" } }, + "logos-liblogos_4": { + "inputs": { + "logos-cpp-sdk": "logos-cpp-sdk_8", + "nixpkgs": [ + "logos-waku-module", + "logos-liblogos", + "logos-capability-module", + "logos-liblogos", + "logos-cpp-sdk", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1761845775, + "narHash": "sha256-ulK8xq05ejK6qIgZ7WtWb/MJt2rk5BKfDA2z7mM3wq8=", + "owner": "logos-co", + "repo": "logos-liblogos", + "rev": "a92c2c1268bc70764c8f73c7bce07d21024f5af9", + "type": "github" + }, + "original": { + "owner": "logos-co", + "repo": "logos-liblogos", + "type": "github" + } + }, + "logos-messaging-nim": { + "inputs": { + "nixpkgs": "nixpkgs_11", + "zerokit": "zerokit" + }, + "locked": { + "lastModified": 1772086348, + "narHash": "sha256-GCmgc6/9KVvJR3YyO5I5hLf/H8B2K+M41segihFfEkI=", + "ref": "poc/logos-testnet-mix", + "rev": "7ed4fed7d0f54ee4b06ca55c407edca031fb4cfc", + "revCount": 2227, + "submodules": true, + "type": "git", + "url": "https://github.com/logos-messaging/logos-delivery" + }, + "original": { + "ref": "poc/logos-testnet-mix", + "submodules": true, + "type": "git", + "url": "https://github.com/logos-messaging/logos-delivery" + } + }, + "logos-module": { + "inputs": { + "logos-cpp-sdk": "logos-cpp-sdk_5", + "nixpkgs": [ + "logos-liblogos", + "logos-module", + "logos-cpp-sdk", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1770062426, + "narHash": "sha256-zc7ZxDTlqOCYGyEHhrTA/7GS1EWh7+4amdPUKh+gGds=", + "owner": "logos-co", + "repo": "logos-module", + "rev": "f7ee69d9ad9f27c84f04f59896e9194125e951dc", + "type": "github" + }, + "original": { + "owner": "logos-co", + "repo": "logos-module", + "type": "github" + } + }, + "logos-module_2": { + "inputs": { + "logos-cpp-sdk": "logos-cpp-sdk_10", + "nixpkgs": [ + "logos-waku-module", + "logos-liblogos", + "logos-module", + "logos-cpp-sdk", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1770062426, + "narHash": "sha256-zc7ZxDTlqOCYGyEHhrTA/7GS1EWh7+4amdPUKh+gGds=", + "owner": "logos-co", + "repo": "logos-module", + "rev": "f7ee69d9ad9f27c84f04f59896e9194125e951dc", + "type": "github" + }, + "original": { + "owner": "logos-co", + "repo": "logos-module", + "type": "github" + } + }, "logos-waku-module": { "inputs": { - "logos-cpp-sdk": "logos-cpp-sdk_3", - "logos-liblogos": "logos-liblogos_2", + "logos-cpp-sdk": "logos-cpp-sdk_6", + "logos-liblogos": "logos-liblogos_3", + "logos-messaging-nim": "logos-messaging-nim", "nixpkgs": [ "logos-waku-module", "logos-liblogos", @@ -130,15 +416,16 @@ ] }, "locked": { - "lastModified": 1761248742, - "narHash": "sha256-v6f4BoDsdv+zLcXo7P5pKQBa9+yK0N8hlYgLsfZ6x3E=", + "lastModified": 1772091941, + "narHash": "sha256-NTzWNh1+j/mnzOkfV71Yv1s5OiGrzGL8ULHDbjDSAhY=", "owner": "logos-co", "repo": "logos-waku-module", - "rev": "5f2eb1eb94bdee2856f4551eeb5f50e66ecab961", + "rev": "8ea3dc6f68f6728247f63ba9f9d05acbc609c4e3", "type": "github" }, "original": { "owner": "logos-co", + "ref": "logos-testnet-demo", "repo": "logos-waku-module", "type": "github" } @@ -159,6 +446,38 @@ "type": "github" } }, + "nixpkgs_10": { + "locked": { + "lastModified": 1759036355, + "narHash": "sha256-0m27AKv6ka+q270dw48KflE0LwQYrO7Fm4/2//KCVWg=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "e9f00bd893984bc8ce46c895c3bf7cac95331127", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_11": { + "locked": { + "lastModified": 1757590060, + "narHash": "sha256-EWwwdKLMZALkgHFyKW7rmyhxECO74+N+ZO5xTDnY/5c=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "0ef228213045d2cdb5a169a95d63ded38670b293", + "type": "github" + }, + "original": { + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "0ef228213045d2cdb5a169a95d63ded38670b293", + "type": "github" + } + }, "nixpkgs_2": { "locked": { "lastModified": 1759036355, @@ -207,6 +526,86 @@ "type": "github" } }, + "nixpkgs_5": { + "locked": { + "lastModified": 1759036355, + "narHash": "sha256-0m27AKv6ka+q270dw48KflE0LwQYrO7Fm4/2//KCVWg=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "e9f00bd893984bc8ce46c895c3bf7cac95331127", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_6": { + "locked": { + "lastModified": 1759036355, + "narHash": "sha256-0m27AKv6ka+q270dw48KflE0LwQYrO7Fm4/2//KCVWg=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "e9f00bd893984bc8ce46c895c3bf7cac95331127", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_7": { + "locked": { + "lastModified": 1759036355, + "narHash": "sha256-0m27AKv6ka+q270dw48KflE0LwQYrO7Fm4/2//KCVWg=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "e9f00bd893984bc8ce46c895c3bf7cac95331127", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_8": { + "locked": { + "lastModified": 1759036355, + "narHash": "sha256-0m27AKv6ka+q270dw48KflE0LwQYrO7Fm4/2//KCVWg=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "e9f00bd893984bc8ce46c895c3bf7cac95331127", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_9": { + "locked": { + "lastModified": 1759036355, + "narHash": "sha256-0m27AKv6ka+q270dw48KflE0LwQYrO7Fm4/2//KCVWg=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "e9f00bd893984bc8ce46c895c3bf7cac95331127", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, "root": { "inputs": { "logos-cpp-sdk": "logos-cpp-sdk", @@ -217,6 +616,53 @@ "nixpkgs" ] } + }, + "rust-overlay": { + "inputs": { + "nixpkgs": [ + "logos-waku-module", + "logos-messaging-nim", + "zerokit", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1748399823, + "narHash": "sha256-kahD8D5hOXOsGbNdoLLnqCL887cjHkx98Izc37nDjlA=", + "owner": "oxalica", + "repo": "rust-overlay", + "rev": "d68a69dc71bc19beb3479800392112c2f6218159", + "type": "github" + }, + "original": { + "owner": "oxalica", + "repo": "rust-overlay", + "type": "github" + } + }, + "zerokit": { + "inputs": { + "nixpkgs": [ + "logos-waku-module", + "logos-messaging-nim", + "nixpkgs" + ], + "rust-overlay": "rust-overlay" + }, + "locked": { + "lastModified": 1762211504, + "narHash": "sha256-SbDoBElFYJ4cYebltxlO2lYnz6qOaDAVY6aNJ5bqHDE=", + "ref": "refs/heads/master", + "rev": "3160d9504d07791f2fc9b610948a6cf9a58ed488", + "revCount": 342, + "type": "git", + "url": "https://github.com/vacp2p/zerokit" + }, + "original": { + "rev": "3160d9504d07791f2fc9b610948a6cf9a58ed488", + "type": "git", + "url": "https://github.com/vacp2p/zerokit" + } } }, "root": "root", diff --git a/flake.nix b/flake.nix index 9908043..19f37ea 100644 --- a/flake.nix +++ b/flake.nix @@ -6,7 +6,8 @@ nixpkgs.follows = "logos-liblogos/nixpkgs"; logos-cpp-sdk.url = "github:logos-co/logos-cpp-sdk"; logos-liblogos.url = "github:logos-co/logos-liblogos"; - logos-waku-module.url = "github:logos-co/logos-waku-module"; + logos-waku-module.url = "github:logos-co/logos-waku-module?ref=logos-testnet-demo"; + #logos-waku-module.url = "path:/Users/prem/Code/logos-waku-module"; }; outputs = { self, nixpkgs, logos-cpp-sdk, logos-liblogos, logos-waku-module }: @@ -48,7 +49,8 @@ # Individual outputs logos-chat-module-lib = lib; logos-chat-module-include = include; - + lib = lib; + # Default package (combined) default = combined; } diff --git a/metadata.json b/metadata.json index 1bcd342..b966049 100644 --- a/metadata.json +++ b/metadata.json @@ -1,12 +1,14 @@ { - "name": "chat", - "version": "1.0.0", - "description": "A chat plugin for Logos", - "author": "Logos Core Team", + "name": "chat-mix", + "version": "1.0.1", + "description": "A chat plugin for Logos using mix and capability discovery", + "author": "Logos AnonComms Team", "type": "core", "category": "chat", "main": "chat_plugin", - "dependencies": ["waku_module"], + "dependencies": [ + "waku_module" + ], "build": { "type": "cmake", "files": [ @@ -15,4 +17,4 @@ ] }, "capabilities": [] -} \ No newline at end of file +} diff --git a/src/chat_api.cpp b/src/chat_api.cpp index 258de8c..8dc35e1 100644 --- a/src/chat_api.cpp +++ b/src/chat_api.cpp @@ -1,15 +1,15 @@ #include "chat_api.h" -#include // Add for storing message hashes +#include // Constants -const std::string TOY_CHAT_CONTENT_TOPIC = "/toy-chat/2/baixa-chiado/proto"; -const std::string DEFAULT_PUBSUB_TOPIC = "/waku/2/rs/16/32"; -const std::string STORE_NODE = "/dns4/store-01.do-ams3.status.staging.status.im/tcp/30303/p2p/16Uiu2HAm3xVDaz6SRJ6kErwC21zBJEZjavVXg7VSkoWzaV1aMA3F"; +const std::string TOY_CHAT_CONTENT_TOPIC = "/toy-chat/2/baixa-chiado-mix/proto"; +const std::string DEFAULT_PUBSUB_TOPIC = ""; +std::string currentStoreNode = ""; const std::string CONTENT_TOPIC_PREFIX = "/toy-chat/2/"; const std::string CONTENT_TOPIC_SUFFIX = "/proto"; // Global variables -void* userData = nullptr; +void *userData = nullptr; std::vector subscribedChannels; // Set to store message hashes we've already processed @@ -22,187 +22,219 @@ AppState appState; const int RET_OK = 0; // Define RET_OK since we no longer have libwaku.h // Helper function to format a channel name into a content topic -std::string formatContentTopic(const std::string& channelName) { +std::string formatContentTopic(const std::string &channelName) +{ // Return the formatted content topic return CONTENT_TOPIC_PREFIX + channelName + CONTENT_TOPIC_SUFFIX; } // Get the current UTC timestamp in seconds -uint64_t getCurrentTimestampProto() { - using namespace std::chrono; - return duration_cast(system_clock::now().time_since_epoch()).count(); +uint64_t getCurrentTimestampProto() +{ + using namespace std::chrono; + return duration_cast(system_clock::now().time_since_epoch()).count(); } // Format a timestamp to human-readable format -std::string formatTimestampProto(uint64_t timestamp) { - time_t time = static_cast(timestamp); - std::tm tm = *std::gmtime(&time); - std::stringstream ss; - ss << std::put_time(&tm, "%Y-%m-%d %H:%M:%S UTC"); - return ss.str(); +std::string formatTimestampProto(uint64_t timestamp) +{ + time_t time = static_cast(timestamp); + std::tm tm = *std::gmtime(&time); + std::stringstream ss; + ss << std::put_time(&tm, "%Y-%m-%d %H:%M:%S UTC"); + return ss.str(); } // Create a new Chat2Message -chat::Chat2Message createMessage(const std::string& nick, const std::string& message) { - chat::Chat2Message chat_msg; - chat_msg.set_timestamp(getCurrentTimestampProto()); - chat_msg.set_nick(nick); - chat_msg.set_payload(message); - return chat_msg; +chat::Chat2Message createMessage(const std::string &nick, const std::string &message) +{ + chat::Chat2Message chat_msg; + chat_msg.set_timestamp(getCurrentTimestampProto()); + chat_msg.set_nick(nick); + chat_msg.set_payload(message); + return chat_msg; } // Print a Chat2Message -void printMessage(const chat::Chat2Message& message) { - std::cout << "Timestamp: " << formatTimestampProto(message.timestamp()) << std::endl; - std::cout << "Nick: " << message.nick() << std::endl; - std::cout << "Message: " << message.payload() << std::endl; +void printMessage(const chat::Chat2Message &message) +{ + std::cout << "Timestamp: " << formatTimestampProto(message.timestamp()) << std::endl; + std::cout << "Nick: " << message.nick() << std::endl; + std::cout << "Message: " << message.payload() << std::endl; } // Create a string from a vector of bytes -std::string bytesToStringProto(const std::vector& bytes) { - std::string result; - for (size_t i = 0; i < bytes.size(); ++i) { - if (i > 0) result += ","; - result += std::to_string(bytes[i]); - } - return "[" + result + "]"; +std::string bytesToStringProto(const std::vector &bytes) +{ + std::string result; + for (size_t i = 0; i < bytes.size(); ++i) + { + if (i > 0) + result += ","; + result += std::to_string(bytes[i]); + } + return "[" + result + "]"; } // Decode a binary payload into a DecodedMessage -DecodedMessage decodeProto(const std::vector& payload) { - DecodedMessage result; - result.success = false; - - std::string binary_data(payload.begin(), payload.end()); - chat::Chat2Message message; - - if (message.ParseFromString(binary_data)) { - result.success = true; - result.timestamp = formatTimestampProto(message.timestamp()); - result.nick = message.nick(); - result.payload = message.payload(); - } - - return result; +DecodedMessage decodeProto(const std::vector &payload) +{ + DecodedMessage result; + result.success = false; + + std::string binary_data(payload.begin(), payload.end()); + chat::Chat2Message message; + + if (message.ParseFromString(binary_data)) + { + result.success = true; + result.timestamp = formatTimestampProto(message.timestamp()); + result.nick = message.nick(); + result.payload = message.payload(); + } + + return result; } // Print a decoded message -void printDecodedMessage(const DecodedMessage& message, const std::vector& originalPayload) { - if (message.success) { - std::cout << "Successfully decoded message:" << std::endl; - std::cout << "Timestamp: " << message.timestamp << std::endl; - std::cout << "Nick: " << message.nick << std::endl; - std::cout << "Message: " << message.payload << std::endl; - } else { - std::cout << "Failed to decode message from payload: " << bytesToStringProto(originalPayload) << std::endl; - } - std::cout << std::endl; +void printDecodedMessage(const DecodedMessage &message, const std::vector &originalPayload) +{ + if (message.success) + { + std::cout << "Successfully decoded message:" << std::endl; + std::cout << "Timestamp: " << message.timestamp << std::endl; + std::cout << "Nick: " << message.nick << std::endl; + std::cout << "Message: " << message.payload << std::endl; + } + else + { + std::cout << "Failed to decode message from payload: " << bytesToStringProto(originalPayload) << std::endl; + } + std::cout << std::endl; } // Decode and print a Chat2Message from a binary payload (combined operation) -void decodePayloadProto(const std::vector& payload) { - auto decodedMsg = decodeProto(payload); - printDecodedMessage(decodedMsg, payload); +void decodePayloadProto(const std::vector &payload) +{ + auto decodedMsg = decodeProto(payload); + printDecodedMessage(decodedMsg, payload); } // Function to format a timestamp to a human-readable string -std::string formatTimestamp(uint64_t timestamp) { - time_t time = static_cast(timestamp); - std::tm tm = *std::gmtime(&time); - std::stringstream ss; - ss << std::put_time(&tm, "%Y-%m-%d %H:%M:%S UTC"); - return ss.str(); +std::string formatTimestamp(uint64_t timestamp) +{ + time_t time = static_cast(timestamp); + std::tm tm = *std::gmtime(&time); + std::stringstream ss; + ss << std::put_time(&tm, "%Y-%m-%d %H:%M:%S UTC"); + return ss.str(); } // Signal handler -void signalHandler(int signal) { +void signalHandler(int signal) +{ std::cout << "Received signal " << signal << ", shutting down..." << std::endl; appState.running = false; } // Store query callback -void storeQueryCallback(int callerRet, const char* msg, size_t len, void* userData) { +void storeQueryCallback(int callerRet, const char *msg, size_t len, void *userData) +{ std::cout << "\n\n\nstoreQueryCallback called with callerRet: " << callerRet << std::endl; // Get the message callback from the context - StoreQueryContext* context = static_cast(userData); + StoreQueryContext *context = static_cast(userData); MessageCallback callback = nullptr; - if (context != nullptr) { + if (context != nullptr) + { callback = context->callback; } - if (callerRet == RET_OK && msg != nullptr && len > 0) { + if (callerRet == RET_OK && msg != nullptr && len > 0) + { std::string jsonStr(msg, len); // Find all payloads in the JSON size_t pos = 0; size_t messageCount = 0; - while ((pos = jsonStr.find("\"payload\":[", pos)) != std::string::npos) { + while ((pos = jsonStr.find("\"payload\":[", pos)) != std::string::npos) + { messageCount++; pos += 11; // Skip "payload":[ part // Find end of payload array size_t endPos = jsonStr.find("]", pos); - if (endPos != std::string::npos) { + if (endPos != std::string::npos) + { std::string payloadStr = jsonStr.substr(pos, endPos - pos); // std::cout << "Raw payload " << messageCount << ": [" << payloadStr << "]" << std::endl; // Convert payload string to vector of bytes std::vector payloadBytes; std::stringstream ss(payloadStr); std::string numberStr; - while (std::getline(ss, numberStr, ',')) { + while (std::getline(ss, numberStr, ',')) + { payloadBytes.push_back(static_cast(std::stoi(numberStr))); } // Decode the payload // std::cout << "Attempting to decode payload " << messageCount << ":" << std::endl; auto decodedMsg = decodeProto(payloadBytes); printDecodedMessage(decodedMsg, payloadBytes); - + // Call the user callback if provided and message was decoded successfully - if (callback && decodedMsg.success) { + if (callback && decodedMsg.success) + { callback(decodedMsg.timestamp, decodedMsg.nick, decodedMsg.payload); } - + // std::cout << "----------------------------------------" << std::endl; } } // std::cout << "Total messages found: " << messageCount << std::endl; } - else if (callerRet != RET_OK) { + else if (callerRet != RET_OK) + { std::cerr << "Store query error: " << callerRet; - if (msg != nullptr && len > 0) { + if (msg != nullptr && len > 0) + { std::cerr << " - " << std::string(msg, len); } std::cerr << std::endl; } // Clean up the context - if (context != nullptr) { + if (context != nullptr) + { delete context; } } // Event handler for incoming messages -void event_handler(int callerRet, const char* msg, size_t len, void* userData) { - if (msg == nullptr) { +void event_handler(int callerRet, const char *msg, size_t len, void *userData) +{ + if (msg == nullptr) + { std::cerr << "event_handler received null message" << std::endl; return; } - + std::string jsonStr(msg); - + // Check for message hash to avoid duplicates size_t hashPos = jsonStr.find("\"messageHash\":"); - if (hashPos != std::string::npos) { + if (hashPos != std::string::npos) + { size_t hashStart = jsonStr.find("\"", hashPos + 14) + 1; size_t hashEnd = jsonStr.find("\"", hashStart); - if (hashStart != std::string::npos && hashEnd != std::string::npos) { + if (hashStart != std::string::npos && hashEnd != std::string::npos) + { std::string messageHash = jsonStr.substr(hashStart, hashEnd - hashStart); - + // If we've already processed this message, skip it - if (processedMessageHashes.find(messageHash) != processedMessageHashes.end()) { + if (processedMessageHashes.find(messageHash) != processedMessageHashes.end()) + { // std::cout << "Skipping duplicate message with hash: " << messageHash << std::endl; return; } - + // Otherwise, add it to our set of processed hashes processedMessageHashes.insert(messageHash); // std::cout << "Processing new message with hash: " << messageHash << std::endl; @@ -211,41 +243,48 @@ void event_handler(int callerRet, const char* msg, size_t len, void* userData) { // Debug log the message std::cout << "event_handler called with callerRet: " << callerRet << std::endl; - - EventHandlerContext* context = static_cast(userData); + EventHandlerContext *context = static_cast(userData); MessageCallback callback = nullptr; - if (context != nullptr) { + if (context != nullptr) + { callback = context->callback; } // Check if the message contains "contentTopic" field size_t contentTopicPos = jsonStr.find("\"contentTopic\":"); - if (contentTopicPos != std::string::npos) { + if (contentTopicPos != std::string::npos) + { // Find the start and end of the content topic value size_t valueStart = jsonStr.find("\"", contentTopicPos + 14) + 1; size_t valueEnd = jsonStr.find("\"", valueStart); - if (valueStart != std::string::npos && valueEnd != std::string::npos) { + if (valueStart != std::string::npos && valueEnd != std::string::npos) + { std::string contentTopic = jsonStr.substr(valueStart, valueEnd - valueStart); // Check if the content topic is in our list of subscribed channels bool isSubscribed = false; - for (const auto& channel : subscribedChannels) { - if (contentTopic == channel) { + for (const auto &channel : subscribedChannels) + { + if (contentTopic == channel) + { isSubscribed = true; break; } } // Only process if the content topic matches one of our subscribed channels - if (isSubscribed) { + if (isSubscribed) + { // std::cout << "\nReceived message with matching content topic: " << contentTopic << std::endl; // Extract the payload size_t payloadPos = jsonStr.find("\"payload\":\""); - if (payloadPos != std::string::npos) { + if (payloadPos != std::string::npos) + { size_t payloadStart = payloadPos + 11; // Skip "payload":" size_t payloadEnd = jsonStr.find("\"", payloadStart); - if (payloadStart != std::string::npos && payloadEnd != std::string::npos) { + if (payloadStart != std::string::npos && payloadEnd != std::string::npos) + { std::string encodedPayload = jsonStr.substr(payloadStart, payloadEnd - payloadStart); // std::cout << "Encoded payload: " << encodedPayload << std::endl; // Decode the base64 payload @@ -254,9 +293,10 @@ void event_handler(int callerRet, const char* msg, size_t len, void* userData) { // std::cout << "Decoding protobuf payload:" << std::endl; auto decodedMsg = decodeProto(decodedBytes); printDecodedMessage(decodedMsg, decodedBytes); - + // Call the user callback if provided and message was decoded successfully - if (callback && decodedMsg.success) { + if (callback && decodedMsg.success) + { callback(decodedMsg.timestamp, decodedMsg.nick, decodedMsg.payload); } } @@ -267,20 +307,25 @@ void event_handler(int callerRet, const char* msg, size_t len, void* userData) { } // Base64 decoding function -std::vector base64Decode(const std::string& encoded) { +std::vector base64Decode(const std::string &encoded) +{ std::string base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz" "0123456789+/"; std::vector decoded; int val = 0, valb = -8; - for (char c : encoded) { - if (c == '=') break; + for (char c : encoded) + { + if (c == '=') + break; size_t pos = base64_chars.find(c); - if (pos == std::string::npos) continue; + if (pos == std::string::npos) + continue; val = (val << 6) + static_cast(pos); valb += 6; - if (valb >= 0) { + if (valb >= 0) + { decoded.push_back(static_cast((val >> valb) & 0xFF)); valb -= 8; } @@ -289,17 +334,20 @@ std::vector base64Decode(const std::string& encoded) { } // Base64 encoding function -std::string base64Encode(const std::vector& data) { +std::string base64Encode(const std::vector &data) +{ std::string base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz" "0123456789+/"; std::string encoded; int val = 0, valb = -6; - for (uint8_t c : data) { + for (uint8_t c : data) + { val = (val << 8) + c; valb += 8; - while (valb >= 0) { + while (valb >= 0) + { encoded.push_back(base64_chars[(val >> valb) & 0x3F]); valb -= 6; } @@ -313,38 +361,44 @@ std::string base64Encode(const std::vector& data) { } // Function to create a chat message -ChatMessage createChatMessage(const std::string& username, const std::string& message) { +ChatMessage createChatMessage(const std::string &username, const std::string &message) +{ // Use ChatMessage constructor from protocol.h return ChatMessage(username, message); } // Function to encode a chat message using protobuf -bool encodeProto(const ChatMessage& msg, std::vector& output) { +bool encodeProto(const ChatMessage &msg, std::vector &output) +{ // Use the ChatMessage's serialize method output = msg.serialize(); return true; } // Function to send a message -void sendMessage(LogosAPI* logosAPI, LogosModules* logos, const std::string& channelName, const std::string& username, const std::string& message) { - if (!logosAPI) { +void sendMessage(LogosAPI *logosAPI, LogosModules *logos, const std::string &channelName, const std::string &username, const std::string &message) +{ + if (!logosAPI) + { std::cerr << "sendMessage: LogosAPI instance is null" << std::endl; return; } - if (!logos) { + if (!logos) + { std::cerr << "sendMessage: LogosModules instance is null" << std::endl; return; } - auto& wakuModule = logos->waku_module; + auto &wakuModule = logos->waku_module; std::cout << "sendMessage called with channelName: " << channelName << ", username: " << username << ", message: " << message << std::endl; std::string contentTopic = channelName; - if (channelName.find("/toy-chat/") == std::string::npos) { + if (channelName.find("/toy-chat/") == std::string::npos) + { contentTopic = formatContentTopic(channelName); } @@ -353,72 +407,184 @@ void sendMessage(LogosAPI* logosAPI, LogosModules* logos, const std::string& cha ChatMessage chatMsg = createChatMessage(username, message); std::vector encodedBytes = chatMsg.serialize(); - if (encodedBytes.empty()) { + if (encodedBytes.empty()) + { std::cerr << "Failed to encode message" << std::endl; return; } std::string base64Payload = base64Encode(encodedBytes); std::string messageJson = R"({ - "payload": ")" + base64Payload + R"(", - "contentTopic": ")" + contentTopic + R"(", + "payload": ")" + base64Payload + + R"(", + "contentTopic": ")" + contentTopic + + R"(", "version": 1, - "timestamp": )" + std::to_string(std::chrono::duration_cast( - std::chrono::system_clock::now().time_since_epoch()).count()) + R"(, + "timestamp": )" + std::to_string(std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count()) + + R"(, "ephemeral": false })"; std::cout << "Sending message as " << username << ": " << message << std::endl; std::cout << "Message JSON: " << messageJson << std::endl; - if (!wakuModule.relayPublish(QString::fromStdString(DEFAULT_PUBSUB_TOPIC), - QString::fromStdString(messageJson))) { + if (!wakuModule.lightPublish(QString::fromStdString(DEFAULT_PUBSUB_TOPIC), + QString::fromStdString(messageJson))) + { std::cerr << "Failed to publish message via WakuModule" << std::endl; } } +// Build Waku configuration based on discovery mode +// mixnodes: list of "multiaddr:mixPubKey" strings from UI configuration +std::string buildWakuConfig(DiscoveryMode discoveryMode, const std::vector &bootstrapNodes, const std::vector &mixnodes, const std::string &nodeKey) +{ + std::ostringstream config; + config << "{\n"; + config << " \"host\": \"0.0.0.0\",\n"; + config << " \"tcpPort\": 60010,\n"; + if (nodeKey.empty()) + config << " \"nodekey\": null,\n"; + else + config << " \"nodekey\": \"" << nodeKey << "\",\n"; + config << " \"clusterId\": 2,\n"; + config << " \"relay\": true,\n"; + config << " \"mix\": true,\n"; + config << " \"shards\": [0,1,2,3,4,5,6,7],\n"; + config << " \"numShardsInNetwork\": 8,\n"; + config << " \"logLevel\": \"DEBUG\",\n"; + config << " \"keepAlive\": true,\n"; + config << " \"discv5Discovery\": false,\n"; + config << " \"discv5EnrAutoUpdate\": false,\n"; + + // Configure based on discovery mode + switch (discoveryMode) + { + case DiscoveryMode::ExtKadOnly: + config << " \"enableKadDiscovery\": true,\n"; + config << " \"rendezvous\": false,\n"; + config << " \"peerExchange\": false,\n"; + config << " \"kadBootstrapNodes\": ["; + for (size_t i = 0; i < bootstrapNodes.size(); ++i) + { + config << "\"" << bootstrapNodes[i] << "\""; + if (i < bootstrapNodes.size() - 1) + config << ", "; + } + config << "],\n"; + config << " \"staticnodes\": ["; + for (size_t i = 0; i < bootstrapNodes.size() && i < 2; ++i) + { + config << "\"" << bootstrapNodes[i] << "\""; + if (i < 1 && bootstrapNodes.size() > 1) + config << ", "; + } + config << "],\n"; + // mixnodes requires ip4 multiaddresses (parseCmdArg(MixNodePubInfo) rejects dns4) + config << " \"mixnodes\": [\n"; + config << " \"/ip4/138.68.122.137/tcp/30303/p2p/16Uiu2HAmTUbnxLGT9JvV6mu9oPyDjqHK4Phs1VDJNUgESgNSkuby:c288a425a6209c74ec07e2e8b6816e9b6995d1cd59b1ab482317c3dfb3ba200f\",\n"; + config << " \"/ip4/174.138.106.244/tcp/30303/p2p/16Uiu2HAmMK7PYygBtKUQ8EHp7EfaD3bCEsJrkFooK8RQ2PVpJprH:9d92279057940efd2e5e98c8922c079c24e45c083b00360c8dc6a298b1661716\",\n"; + config << " \"/ip4/136.119.156.87/tcp/30303/p2p/16Uiu2HAm4S1JYkuzDKLKQvwgAhZKs9otxXqt8SCGtB4hoJP1S397:fe60e95c50f70db9015525064e1fff962ccc982dde480f8faae30262710ece58\",\n"; + config << " \"/ip4/34.123.201.25/tcp/30303/p2p/16Uiu2HAm8Y9kgBNtjxvCnf1X6gnZJW5EGE4UwwCL3CCm55TwqBiH:312335324231ba7963c0c7524e042d1beac2927dbf810513a7fc8d901ab4e812\",\n"; + config << " \"/ip4/47.242.130.189/tcp/30303/p2p/16Uiu2HAm8YokiNun9BkeA1ZRmhLbtNUvcwRr64F69tYj9fkGyuEP:7d683767f23f5132a79c70587fec877575460122ebd459bb29c887b7b7a32110\",\n"; + config << " \"/ip4/43.99.103.10/tcp/30303/p2p/16Uiu2HAkvwhGHKNry6LACrB8TmEFoCJKEX29XR5dDUzk3UT3UNSE:0894b2852890d244e045f2ff5875e03a6b18f233ccd2e5297f62f7546e93884d\"\n"; + config << " ]\n"; + break; + + case DiscoveryMode::StdDiscovery: + // Standard Discovery - Rendezvous + Peer Exchange, with mixnodes configured + config << " \"enableKadDiscovery\": false,\n"; + config << " \"rendezvous\": true,\n"; + config << " \"peerExchange\": true,\n"; + // Static nodes for initial connectivity + config << " \"staticnodes\": ["; + for (size_t i = 0; i < bootstrapNodes.size() && i < 2; ++i) + { + config << "\"" << bootstrapNodes[i] << "\""; + if (i < 1 && bootstrapNodes.size() > 1) + config << ", "; + } + config << "],\n"; + // Include mixnodes config from UI + config << " \"mixnodes\": ["; + for (size_t i = 0; i < mixnodes.size(); ++i) + { + config << "\"" << mixnodes[i] << "\""; + if (i < mixnodes.size() - 1) + config << ", "; + } + config << "]\n"; + break; + + case DiscoveryMode::All: + // All discovery methods - Kad + Rendezvous + Peer Exchange + config << " \"enableKadDiscovery\": true,\n"; + config << " \"rendezvous\": true,\n"; + config << " \"peerExchange\": true,\n"; + // Add kadBootstrapNodes from UI bootstrap nodes + config << " \"kadBootstrapNodes\": ["; + for (size_t i = 0; i < bootstrapNodes.size(); ++i) + { + config << "\"" << bootstrapNodes[i] << "\""; + if (i < bootstrapNodes.size() - 1) + config << ", "; + } + config << "],\n"; + // Static nodes for initial connectivity + config << " \"staticnodes\": ["; + for (size_t i = 0; i < bootstrapNodes.size() && i < 2; ++i) + { + config << "\"" << bootstrapNodes[i] << "\""; + if (i < 1 && bootstrapNodes.size() > 1) + config << ", "; + } + config << "]\n"; + break; + } + + config << "}"; + return config.str(); +} + // Function to initialize and start a Waku node -void* initAndStart(LogosAPI* logosAPI, LogosModules* logos, const std::string& relayTopic, MessageCallback messageCallback) { - if (!logosAPI) { +void *initAndStart(LogosAPI *logosAPI, LogosModules *logos, const std::string &relayTopic, MessageCallback messageCallback, + DiscoveryMode discoveryMode, const std::vector &bootstrapNodes, const std::vector &mixnodes, + const std::string &storeNode, const std::string &nodeKey) +{ + // Store the configured store node for use by retrieveHistory + currentStoreNode = storeNode; + if (!logosAPI) + { std::cerr << "initAndStart: LogosAPI instance is null" << std::endl; return nullptr; } - if (!logos) { + if (!logos) + { std::cerr << "initAndStart: LogosModules instance is null" << std::endl; return nullptr; } - auto& wakuModule = logos->waku_module; - - // Create appropriate Waku config - std::string configStr = R"({ - "host": "0.0.0.0", - "tcpPort": 60010, - "key": null, - "clusterId": 16, - "relay": true, - "relayTopics": [")" + relayTopic + R"("], - "shards": [1,32,64,128,256], - "maxMessageSize": "1024KiB", - "dnsDiscovery": true, - "dnsDiscoveryUrl": "enrtree://AMOJVZX4V6EXP7NTJPMAYJYST2QP6AJXYW76IU6VGJS7UVSNDYZG4@boot.prod.status.nodes.status.im", - "discv5Discovery": false, - "numShardsInNetwork": 257, - "discv5EnrAutoUpdate": false, - "logLevel": "INFO", - "keepAlive": true - })"; + auto &wakuModule = logos->waku_module; + + // Build Waku config based on discovery mode + std::string configStr = buildWakuConfig(discoveryMode, bootstrapNodes, mixnodes, nodeKey); + + std::cout << "Discovery mode: " << static_cast(discoveryMode) << std::endl; + std::cout << "Bootstrap nodes count: " << bootstrapNodes.size() << std::endl; + std::cout << "Mixnodes count: " << mixnodes.size() << std::endl; std::cout << "Waku node config: " << configStr << std::endl; std::cout << "Found Waku Plugin, initializing" << std::endl; - if (!wakuModule.initWaku(QString::fromStdString(configStr))) { + if (!wakuModule.initWaku(QString::fromStdString(configStr))) + { std::cerr << "Failed to initialize Waku module" << std::endl; return nullptr; } std::this_thread::sleep_for(std::chrono::seconds(3)); - if (!wakuModule.on("wakuMessage", [messageCallback](const QString&, const QVariantList& data) { + if (!wakuModule.on("wakuMessage", [messageCallback](const QString &, const QVariantList &data) + { if (!data.isEmpty()) { std::string jsonStr = data.first().toString().toStdString(); @@ -498,16 +664,18 @@ void* initAndStart(LogosAPI* logosAPI, LogosModules* logos, const std::string& r } } else { std::cout << "\n\n\n\n\n\nContent Topic: No data available" << std::endl; - } - })) { + } })) + { std::cerr << "Failed to subscribe to wakuMessage events" << std::endl; } - if (!wakuModule.setEventCallback()) { + if (!wakuModule.setEventCallback()) + { std::cerr << "Failed to register Waku event callback" << std::endl; } - if (!wakuModule.startWaku()) { + if (!wakuModule.startWaku()) + { std::cerr << "Failed to start Waku module" << std::endl; return nullptr; } @@ -517,36 +685,39 @@ void* initAndStart(LogosAPI* logosAPI, LogosModules* logos, const std::string& r // Return a non-null pointer to indicate success // We're not using this for anything meaningful anymore - return (void*)1; + return (void *)1; } // Function to join a chat channel -bool joinChannel(LogosAPI* logosAPI, LogosModules* logos, const std::string& channelName, const std::string& relayTopic) { - if (!logosAPI) { +bool joinChannel(LogosAPI *logosAPI, LogosModules *logos, const std::string &channelName, const std::string &relayTopic) +{ + if (!logosAPI) + { std::cerr << "joinChannel: LogosAPI instance is null" << std::endl; return false; } - if (!logos) { + if (!logos) + { std::cerr << "joinChannel: LogosModules instance is null" << std::endl; return false; } - auto& wakuModule = logos->waku_module; + auto &wakuModule = logos->waku_module; // Format the channel name into a content topic if not already formatted std::string contentTopic = channelName; - if (channelName.find("/toy-chat/") == std::string::npos) { + if (channelName.find("/toy-chat/") == std::string::npos) + { contentTopic = formatContentTopic(channelName); } std::cout << "Joining channel: " << channelName << std::endl; std::cout << "Subscribing to content topic: " << contentTopic << std::endl; - std::string contentTopics = "[\"" + contentTopic + "\"]"; - - if (!wakuModule.filterSubscribe(QString::fromStdString(relayTopic), - QString::fromStdString(contentTopics))) { + if (!wakuModule.relaySubscribe(QString::fromStdString(contentTopic), + QString::fromStdString(relayTopic))) + { std::cerr << "Failed to subscribe to content topic: " << contentTopic << std::endl; return false; } @@ -556,25 +727,29 @@ bool joinChannel(LogosAPI* logosAPI, LogosModules* logos, const std::string& cha } // Function to retrieve message history from store node -void retrieveHistory(LogosAPI* logosAPI, LogosModules* logos, const std::string& channelName, MessageCallback callback) { - if (!logosAPI) { +void retrieveHistory(LogosAPI *logosAPI, LogosModules *logos, const std::string &channelName, MessageCallback callback) +{ + if (!logosAPI) + { std::cerr << "retrieveHistory: LogosAPI instance is null" << std::endl; return; } - if (!logos) { + if (!logos) + { std::cerr << "retrieveHistory: LogosModules instance is null" << std::endl; return; } - auto& wakuModule = logos->waku_module; + auto &wakuModule = logos->waku_module; // Format the channel name into a content topic if not already formatted std::string contentTopic = channelName; - if (channelName.find("/toy-chat/") == std::string::npos) { + if (channelName.find("/toy-chat/") == std::string::npos) + { contentTopic = formatContentTopic(channelName); } - + std::cout << "Retrieving message history for channel: " << channelName << std::endl; std::cout << "Using content topic: " << contentTopic << std::endl; @@ -584,59 +759,55 @@ void retrieveHistory(LogosAPI* logosAPI, LogosModules* logos, const std::string& auto nowSeconds = std::chrono::duration_cast(now.time_since_epoch()).count(); uint64_t timeStart = (nowSeconds - oneDay) * 1000000000ULL; // Convert to nanoseconds - std::string queryJson = R"({ + std::string queryJson = R"({ "requestId": "15be8c48-55ce-4bf2-a34-8813d4da2dec", "includeData": true, - "contentTopics": [")" + contentTopic + R"("], - "timeStart": 1744123537000000000, + "contentTopics": [")" + + contentTopic + R"("], + "timeStart": )" + std::to_string(timeStart) + + R"(, "paginationForward": true, "paginationLimit": 100 })"; std::cout << "Query JSON: " << queryJson.c_str() << std::endl; - if (!wakuModule.on("storeQueryResponse", [channelName, callback](const QString&, const QVariantList& data) { + if (!wakuModule.on("storeQueryResponse", [channelName, callback](const QString &, const QVariantList &data) + { if (!data.isEmpty()) { std::string jsonStr = data.first().toString().toStdString(); - // parse the json and print each message decoded - // Find all payloads in the JSON + // Parse base64-encoded payloads from store query response + // Response format: "payload":"" (not byte arrays) size_t pos = 0; size_t messageCount = 0; - while ((pos = jsonStr.find("\"payload\":[", pos)) != std::string::npos) { + const std::string payloadKey = "\"payload\":\""; + while ((pos = jsonStr.find(payloadKey, pos)) != std::string::npos) { messageCount++; - pos += 11; // Skip "payload":[ part - // Find end of payload array - size_t endPos = jsonStr.find("]", pos); + pos += payloadKey.size(); // Skip past "payload":" + // Find the closing quote + size_t endPos = jsonStr.find("\"", pos); if (endPos != std::string::npos) { - std::string payloadStr = jsonStr.substr(pos, endPos - pos); - // Convert payload string to vector of bytes - std::vector payloadBytes; - std::stringstream ss(payloadStr); - std::string numberStr; - while (std::getline(ss, numberStr, ',')) { - payloadBytes.push_back(static_cast(std::stoi(numberStr))); - } - // Decode the payload - std::cout << "Attempting to decode payload " << messageCount << ":" << std::endl; - auto decodedMsg = decodeProto(payloadBytes); - // printDecodedMessage(decodedMsg, payloadBytes); - - // Call the callback if message was decoded successfully - if (callback && decodedMsg.success) { - callback(decodedMsg.timestamp, decodedMsg.nick, decodedMsg.payload); + std::string base64Payload = jsonStr.substr(pos, endPos - pos); + if (!base64Payload.empty()) { + std::vector payloadBytes = base64Decode(base64Payload); + auto decodedMsg = decodeProto(payloadBytes); + + if (callback && decodedMsg.success) { + callback(decodedMsg.timestamp, decodedMsg.nick, decodedMsg.payload); + } } - - std::cout << "----------------------------------------" << std::endl; + pos = endPos + 1; } } std::cout << "Total messages found: " << messageCount << std::endl; - } - })) { + } })) + { std::cerr << "Failed to subscribe to storeQueryResponse events" << std::endl; } - if (!wakuModule.storeQuery(QString::fromStdString(queryJson), QString::fromStdString(STORE_NODE))) { + if (!wakuModule.storeQuery(QString::fromStdString(queryJson), QString::fromStdString(currentStoreNode))) + { std::cerr << "Failed to request message history from Waku store" << std::endl; } } diff --git a/src/chat_api.h b/src/chat_api.h index d12ad63..ddeee4c 100644 --- a/src/chat_api.h +++ b/src/chat_api.h @@ -18,11 +18,12 @@ #include "protocol/protocol.h" #include "message.pb.h" #include "logos_sdk.h" +#include "chat_interface.h" // Constants extern const std::string TOY_CHAT_CONTENT_TOPIC; extern const std::string DEFAULT_PUBSUB_TOPIC; -extern const std::string STORE_NODE; +extern std::string currentStoreNode; extern const std::string CONTENT_TOPIC_PREFIX; extern const std::string CONTENT_TOPIC_SUFFIX; @@ -88,7 +89,10 @@ void storeQueryCallback(int callerRet, const char* msg, size_t len, void* userDa void nodeOperationCallback(int callerRet, const char* msg, size_t len, void* userData); void retrieveHistory(LogosAPI* logosAPI, LogosModules* logos, const std::string& channelName, MessageCallback callback = nullptr); void event_handler(int callerRet, const char* msg, size_t len, void* userData); -void* initAndStart(LogosAPI* logosAPI, LogosModules* logos, const std::string& relayTopic, MessageCallback messageCallback = nullptr); +void* initAndStart(LogosAPI* logosAPI, LogosModules* logos, const std::string& relayTopic, MessageCallback messageCallback, + DiscoveryMode discoveryMode, const std::vector& bootstrapNodes, const std::vector& mixnodes, + const std::string& storeNode, const std::string& nodeKey); bool joinChannel(LogosAPI* logosAPI, LogosModules* logos, const std::string& channelName, const std::string& relayTopic); +std::string buildWakuConfig(DiscoveryMode discoveryMode, const std::vector& bootstrapNodes, const std::vector& mixnodes, const std::string& nodeKey); #endif // CHAT_API_H