diff --git a/src/account.h b/src/account.h index 949c6dffdccb7a2740dded87edb2e1e64263c8fc..bd7a99bfc2010b3813c5aa221bbffcacaebd6966 100644 --- a/src/account.h +++ b/src/account.h @@ -112,12 +112,16 @@ public: */ virtual void loadConfig(); - const AccountConfig& config() const { - if (config_) return *config_; - else throw std::runtime_error("Account doesn't have a configuration"); + const AccountConfig& config() const + { + if (config_) + return *config_; + else + throw std::runtime_error("Account doesn't have a configuration"); } - inline void editConfig(std::function<void(AccountConfig& config)>&& edit) { + inline void editConfig(std::function<void(AccountConfig& config)>&& edit) + { std::lock_guard<std::recursive_mutex> lock(configurationMutex_); edit(*config_); saveConfig(); @@ -125,7 +129,8 @@ public: virtual void saveConfig() const; - void setAccountDetails(const std::map<std::string, std::string>& details) { + void setAccountDetails(const std::map<std::string, std::string>& details) + { std::lock_guard<std::recursive_mutex> lock(configurationMutex_); if (not config_) config_ = buildConfig(); @@ -134,7 +139,8 @@ public: saveConfig(); } - std::map<std::string, std::string> getAccountDetails() const { + std::map<std::string, std::string> getAccountDetails() const + { std::lock_guard<std::recursive_mutex> lock(configurationMutex_); return config().toMap(); } @@ -186,6 +192,7 @@ public: * @return a token to query the message status */ virtual uint64_t sendTextMessage(const std::string& /*to*/, + const std::string& /*deviceId*/, const std::map<std::string, std::string>& /*payloads*/, uint64_t /*refreshToken*/ = 0, bool /*onlyConnected*/ = false) diff --git a/src/connectivity/connectionmanager.cpp b/src/connectivity/connectionmanager.cpp index 9457e404caa6e1fd8d002d09a1df5680eec46bf5..61f507f75218f478dbdd4999ba0249327120a97c 100644 --- a/src/connectivity/connectionmanager.cpp +++ b/src/connectivity/connectionmanager.cpp @@ -60,7 +60,6 @@ struct ConnectionInfo std::function<void(bool)> onConnected_; std::unique_ptr<asio::steady_timer> waitForAnswer_ {}; - }; class ConnectionManager::Impl : public std::enable_shared_from_this<ConnectionManager::Impl> @@ -344,12 +343,17 @@ ConnectionManager::Impl::connectDeviceStartIce( } info->onConnected_ = std::move(onConnected); - info->waitForAnswer_ = std::make_unique<asio::steady_timer>(*Manager::instance().ioContext(), std::chrono::steady_clock::now() + DHT_MSG_TIMEOUT); - info->waitForAnswer_->async_wait(std::bind(&ConnectionManager::Impl::onResponse, this, std::placeholders::_1, deviceId, vid)); + info->waitForAnswer_ = std::make_unique<asio::steady_timer>(*Manager::instance().ioContext(), + std::chrono::steady_clock::now() + + DHT_MSG_TIMEOUT); + info->waitForAnswer_->async_wait( + std::bind(&ConnectionManager::Impl::onResponse, this, std::placeholders::_1, deviceId, vid)); } void -ConnectionManager::Impl::onResponse(const asio::error_code& ec, const DeviceId& deviceId, const dht::Value::Id& vid) +ConnectionManager::Impl::onResponse(const asio::error_code& ec, + const DeviceId& deviceId, + const dht::Value::Id& vid) { if (ec == asio::error::operation_aborted) return; @@ -384,7 +388,6 @@ ConnectionManager::Impl::onResponse(const asio::error_code& ec, const DeviceId& info->onConnected_(true); } - bool ConnectionManager::Impl::connectDeviceOnNegoDone( const DeviceId& deviceId, @@ -706,7 +709,11 @@ ConnectionManager::Impl::onPeerResponse(const PeerConnectionRequest& req) info->responseReceived_ = true; info->response_ = std::move(req); info->waitForAnswer_->expires_at(std::chrono::steady_clock::now()); - info->waitForAnswer_->async_wait(std::bind(&ConnectionManager::Impl::onResponse, this, std::placeholders::_1, device, req.id)); + info->waitForAnswer_->async_wait(std::bind(&ConnectionManager::Impl::onResponse, + this, + std::placeholders::_1, + device, + req.id)); } else { JAMI_WARN() << account << " respond received, but cannot find request"; } diff --git a/src/connectivity/multiplexed_socket.cpp b/src/connectivity/multiplexed_socket.cpp index 2670f716264fb4571f18468c9f31e8dfb62b927f..3c48f3db2c6bf4392adb7fbf7d1003f25d674d46 100644 --- a/src/connectivity/multiplexed_socket.cpp +++ b/src/connectivity/multiplexed_socket.cpp @@ -127,19 +127,15 @@ public: { auto& channelSocket = sockets[channel]; if (not channelSocket) - channelSocket = std::make_shared<ChannelSocket>(parent_.weak(), - name, - channel, - isInitiator, - [w = parent_.weak(), channel]() { - // Remove socket in another thread to avoid any lock - dht::ThreadPool::io().run([w, channel]() { - if (auto shared = w.lock()) { - shared->eraseChannel(channel); - } - }); - - }); + channelSocket = std::make_shared<ChannelSocket>( + parent_.weak(), name, channel, isInitiator, [w = parent_.weak(), channel]() { + // Remove socket in another thread to avoid any lock + dht::ThreadPool::io().run([w, channel]() { + if (auto shared = w.lock()) { + shared->eraseChannel(channel); + } + }); + }); else { JAMI_WARN("A channel is already present on that socket, accepting " "the request will close the previous one %s", @@ -669,7 +665,11 @@ MultiplexedSocket::monitor() const std::lock_guard<std::mutex> lk(pimpl_->socketsMutex); for (const auto& [_, channel] : pimpl_->sockets) { if (channel) - JAMI_DEBUG("\t\t- Channel {} (count: {}) with name {:s} Initiator: {}", fmt::ptr(channel.get()), channel.use_count(), channel->name(), channel->isInitiator()); + JAMI_DEBUG("\t\t- Channel {} (count: {}) with name {:s} Initiator: {}", + fmt::ptr(channel.get()), + channel.use_count(), + channel->name(), + channel->isInitiator()); } } @@ -791,8 +791,7 @@ ChannelSocketTest::ChannelSocketTest(const DeviceId& deviceId, , ioCtx_(*Manager::instance().ioContext()) {} -ChannelSocketTest::~ChannelSocketTest() -{} +ChannelSocketTest::~ChannelSocketTest() {} void ChannelSocketTest::link(const std::shared_ptr<ChannelSocketTest>& socket1, @@ -925,7 +924,8 @@ ChannelSocket::ChannelSocket(std::weak_ptr<MultiplexedSocket> endpoint, const uint16_t& channel, bool isInitiator, std::function<void()> rmFromMxSockCb) - : pimpl_ {std::make_unique<Impl>(endpoint, name, channel, isInitiator, std::move(rmFromMxSockCb))} + : pimpl_ { + std::make_unique<Impl>(endpoint, name, channel, isInitiator, std::move(rmFromMxSockCb))} {} ChannelSocket::~ChannelSocket() {} diff --git a/src/connectivity/multiplexed_socket.h b/src/connectivity/multiplexed_socket.h index 1b6858b38671cf47cf55a2ddcb447bffcd1d4ce1..a414adf26cf5b9febe2c2aef5df98e3349241907 100644 --- a/src/connectivity/multiplexed_socket.h +++ b/src/connectivity/multiplexed_socket.h @@ -115,10 +115,12 @@ public: * This will close all channels and send a TLS EOF on the main socket. */ void shutdown(); + /** * This will wait that eventLoop is stopped and stop it if necessary */ void join(); + /** * Will trigger that callback when shutdown() is called */ diff --git a/src/im/message_engine.cpp b/src/im/message_engine.cpp index 1f49da6e3b0258ac53569f89cc345f8b9b770be6..d75bcd77b25793e04e33d0dbb7c3fec5a4b24883 100644 --- a/src/im/message_engine.cpp +++ b/src/im/message_engine.cpp @@ -44,6 +44,7 @@ MessageEngine::MessageEngine(SIPAccountBase& acc, const std::string& path) MessageToken MessageEngine::sendMessage(const std::string& to, + const std::string& deviceId, const std::map<std::string, std::string>& payloads, uint64_t refreshToken) { @@ -52,7 +53,8 @@ MessageEngine::sendMessage(const std::string& to, MessageToken token; { std::lock_guard<std::mutex> lock(messagesMutex_); - auto& peerMessages = messages_[to]; + + auto& peerMessages = deviceId.empty() ? messages_[to] : messagesDevices_[deviceId]; auto previousIt = peerMessages.find(refreshToken); if (previousIt != peerMessages.end() && previousIt->second.status != MessageStatus::SENT) { JAMI_DEBUG("[message {:d}] Replace content", refreshToken); @@ -70,18 +72,20 @@ MessageEngine::sendMessage(const std::string& to, } save_(); } - runOnMainThread([this, to]() { retrySend(to); }); + runOnMainThread([this, to, deviceId]() { retrySend(to, true, deviceId); }); return token; } void -MessageEngine::onPeerOnline(const std::string& peer, bool retryOnTimeout) +MessageEngine::onPeerOnline(const std::string& peer, + bool retryOnTimeout, + const std::string& deviceId) { - retrySend(peer, retryOnTimeout); + retrySend(peer, retryOnTimeout, deviceId); } void -MessageEngine::retrySend(const std::string& peer, bool retryOnTimeout) +MessageEngine::retrySend(const std::string& peer, bool retryOnTimeout, const std::string& deviceId) { if (account_.getRegistrationState() != RegistrationState::REGISTERED) return; @@ -94,10 +98,13 @@ MessageEngine::retrySend(const std::string& peer, bool retryOnTimeout) std::vector<PendingMsg> pending {}; { std::lock_guard<std::mutex> lock(messagesMutex_); - auto p = messages_.find(peer); - if (p == messages_.end()) + + auto& m = deviceId.empty() ? messages_ : messagesDevices_; + auto p = m.find(deviceId.empty() ? peer : deviceId); + if (p == m.end()) return; auto& messages = p->second; + for (auto m = messages.begin(); m != messages.end(); ++m) { if (m->second.status == MessageStatus::UNKNOWN || m->second.status == MessageStatus::IDLE) { @@ -118,7 +125,7 @@ MessageEngine::retrySend(const std::string& peer, bool retryOnTimeout) p.to, std::to_string(p.token), (int) libjami::Account::MessageStates::SENDING); - account_.sendMessage(p.to, p.payloads, p.token, retryOnTimeout); + account_.sendMessage(p.to, deviceId, p.payloads, p.token, retryOnTimeout, false); } } @@ -159,15 +166,19 @@ MessageEngine::cancel(MessageToken t) } void -MessageEngine::onMessageSent(const std::string& peer, MessageToken token, bool ok) +MessageEngine::onMessageSent(const std::string& peer, + MessageToken token, + bool ok, + const std::string& deviceId) { JAMI_DEBUG("[message {:d}] Message sent: {:s}", token, ok ? "success"sv : "failure"sv); std::lock_guard<std::mutex> lock(messagesMutex_); - auto p = messages_.find(peer); - if (p == messages_.end()) { - JAMI_DEBUG("[message {:d}] Can't find peer", token); + auto& m = deviceId.empty() ? messages_ : messagesDevices_; + + auto p = m.find(deviceId.empty() ? peer : deviceId); + if (p == m.end()) return; - } + auto f = p->second.find(token); if (f != p->second.end()) { auto emit = f->second.payloads.find("application/im-gitmessage-id") @@ -304,7 +315,8 @@ MessageEngine::save_() const payloads[p.first] = p.second; peerRoot[to_hex_string(m.first)] = std::move(msg); } - if (peerRoot.size() == 0) continue; + if (peerRoot.size() == 0) + continue; root[c.first] = std::move(peerRoot); } @@ -325,14 +337,11 @@ MessageEngine::save_() const writer->write(root, &file); } catch (const std::exception& e) { JAMI_ERROR("[Account {:s}] Couldn't save messages to {:s}: {:s}", - accountID, - path, - e.what()); + accountID, + path, + e.what()); } - JAMI_DEBUG("[Account {:s}] saved {:d} messages to {:s}", - accountID, - root.size(), - path); + JAMI_DEBUG("[Account {:s}] saved {:d} messages to {:s}", accountID, root.size(), path); }); } catch (const std::exception& e) { JAMI_ERR("[Account %s] couldn't save messages to %s: %s", diff --git a/src/im/message_engine.h b/src/im/message_engine.h index 9f93fc80d6e8a7a2e459dd9796daeb49f49d9aa9..80de0d3eab0f92e7994e2d41b9c11720edcc4332 100644 --- a/src/im/message_engine.h +++ b/src/im/message_engine.h @@ -41,7 +41,15 @@ class MessageEngine public: MessageEngine(SIPAccountBase&, const std::string& path); + /** + * Add a message to the engine and try to send it + * @param to Uri of the peer + * @param deviceId (Optional) if we want to send to a specific device + * @param payloads The message + * @param refreshToken The token of the message + */ MessageToken sendMessage(const std::string& to, + const std::string& deviceId, const std::map<std::string, std::string>& payloads, uint64_t refreshToken); @@ -51,14 +59,20 @@ public: bool isSent(MessageToken t) const { return getStatus(t) == MessageStatus::SENT; } - void onMessageSent(const std::string& peer, MessageToken t, bool success); + void onMessageSent(const std::string& peer, + MessageToken t, + bool success, + const std::string& deviceId = {}); + void onMessageDisplayed(const std::string& peer, MessageToken t, bool displayed); /** * @TODO change MessageEngine by a queue, * @NOTE retryOnTimeout is used for failing SIP messages (jamiAccount::sendTextMessage) */ - void onPeerOnline(const std::string& peer, bool retryOnTimeout = true); + void onPeerOnline(const std::string& peer, + bool retryOnTimeout = true, + const std::string& deviceId = {}); /** * Load persisted messages @@ -75,7 +89,10 @@ private: static const std::chrono::minutes RETRY_PERIOD; using clock = std::chrono::steady_clock; - void retrySend(const std::string& peer, bool retryOnTimeout = true); + void retrySend(const std::string& peer, + bool retryOnTimeout = true, + const std::string& deviceId = {}); + void save_() const; struct Message @@ -91,6 +108,8 @@ private: const std::string savePath_; std::map<std::string, std::map<MessageToken, Message>> messages_; + std::map<std::string, std::map<MessageToken, Message>> messagesDevices_; + std::set<MessageToken> sentMessages_; mutable std::mutex messagesMutex_ {}; diff --git a/src/jamidht/conversation.cpp b/src/jamidht/conversation.cpp index f62fc51f418e74a4946dbda215abae2fd0e0f557..dbb736bf93e097270aef763b255ac0b69c4bd2af 100644 --- a/src/jamidht/conversation.cpp +++ b/src/jamidht/conversation.cpp @@ -39,7 +39,7 @@ namespace jami { static const char* const LAST_MODIFIED = "lastModified"; -static const auto jsonBuilder = []{ +static const auto jsonBuilder = [] { Json::StreamWriterBuilder wbuilder; wbuilder["commentStyle"] = "None"; wbuilder["indentation"] = ""; @@ -122,7 +122,7 @@ ConversationRequest::toMap() const class Conversation::Impl { public: - Impl(const std::weak_ptr<JamiAccount>& account, + Impl(const std::shared_ptr<JamiAccount>& account, ConversationMode mode, const std::string& otherMember = "") : repository_(ConversationRepository::createConversation(account, mode, otherMember)) @@ -134,7 +134,7 @@ public: init(); } - Impl(const std::weak_ptr<JamiAccount>& account, const std::string& conversationId) + Impl(const std::shared_ptr<JamiAccount>& account, const std::string& conversationId) : account_(account) { repository_ = std::make_unique<ConversationRepository>(account, conversationId); @@ -144,7 +144,7 @@ public: init(); } - Impl(const std::weak_ptr<JamiAccount>& account, + Impl(const std::shared_ptr<JamiAccount>& account, const std::string& remoteDevice, const std::string& conversationId) : account_(account) @@ -153,10 +153,8 @@ public: remoteDevice, conversationId); if (!repository_) { - if (auto shared = account.lock()) { - emitSignal<libjami::ConversationSignal::OnConversationError>( - shared->getAccountID(), conversationId, EFETCH, "Couldn't clone repository"); - } + emitSignal<libjami::ConversationSignal::OnConversationError>( + account->getAccountID(), conversationId, EFETCH, "Couldn't clone repository"); throw std::logic_error("Couldn't clone repository"); } init(); @@ -171,7 +169,10 @@ public: void init() { if (auto shared = account_.lock()) { + ioContext_ = Manager::instance().ioContext(); + fallbackTimer_ = std::make_unique<asio::steady_timer>(*ioContext_); swarmManager_ = std::make_shared<SwarmManager>(NodeId(shared->currentDeviceId())); + swarmManager_->setMobility(shared->isMobile()); accountId_ = shared->getAccountID(); transferManager_ = std::make_shared<TransferManager>(shared->getAccountID(), repository_->id()); @@ -194,6 +195,27 @@ public: } } + const std::string& toString() const + { + if (fmtStr_.empty()) { + if (repository_->mode() == ConversationMode::ONE_TO_ONE) { + if (auto acc = account_.lock()) { + auto peer = acc->getUsername(); + for (const auto& member : repository_->getInitialMembers()) { + if (member != acc->getUsername()) { + peer = member; + } + } + fmtStr_ = fmt::format("[Conversation (1:1) {}]", peer); + } + } else { + fmtStr_ = fmt::format("[Conversation {}]", repository_->id()); + } + } + return fmtStr_; + } + mutable std::string fmtStr_; + /** * If, for whatever reason, the daemon is stopped while hosting a conference, * we need to announce the end of this call when restarting. @@ -201,7 +223,15 @@ public: */ std::vector<std::string> announceEndedCalls(); - ~Impl() = default; + ~Impl() + { + try { + if (fallbackTimer_) + fallbackTimer_->cancel(); + } catch (const std::exception& e) { + JAMI_ERROR("[Conversation {:s}] {:s}", toString(), e.what()); + } + } std::vector<std::string> refreshActiveCalls(); bool isAdmin() const; @@ -272,11 +302,12 @@ public: auto device = commit.at("device"); std::lock_guard<std::mutex> lk(activeCallsMtx_); auto itActive = std::find_if(activeCalls_.begin(), - activeCalls_.end(), - [&](const auto& value) { - return value.at("id") == confId && value.at("uri") == uri - && value.at("device") == device; - }); + activeCalls_.end(), + [&](const auto& value) { + return value.at("id") == confId + && value.at("uri") == uri + && value.at("device") == device; + }); if (commit.find("duration") == commit.end()) { if (itActive == activeCalls_.end() && !eraseOnly) { JAMI_DEBUG( @@ -300,18 +331,16 @@ public: itActive = activeCalls_.erase(itActive); // Unlikely, but we must ensure that no duplicate exists while (itActive != activeCalls_.end()) { - itActive = std::find_if(itActive, activeCalls_.end(), - [&](const auto& value) { - return value.at("id") == confId && value.at("uri") == uri - && value.at("device") == device; - }); + itActive = std::find_if(itActive, activeCalls_.end(), [&](const auto& value) { + return value.at("id") == confId && value.at("uri") == uri + && value.at("device") == device; + }); if (itActive != activeCalls_.end()) { JAMI_ERROR("Duplicate call found. (This is a bug)"); itActive = activeCalls_.erase(itActive); } } - if (eraseOnly) { JAMI_WARNING("previous swarm:{:s} call finished detected: {:s} on device " "{:s}, account {:s}", @@ -523,7 +552,8 @@ public: void voteUnban(const std::string& contactUri, const std::string_view type, const OnDoneCb& cb); std::vector<std::map<std::string, std::string>> getMembers(bool includeInvited, - bool includeLeft, bool includeBanned) const; + bool includeLeft, + bool includeBanned) const; std::string_view bannedType(const std::string& uri) const { @@ -562,12 +592,15 @@ public: std::vector<std::map<std::string, std::string>> getMembers(bool includeInvited, bool includeLeft) const; - std::shared_ptr<SwarmManager> swarmManager_; std::set<std::string> checkedMembers_; // Store members we tried std::function<void()> bootstrapCb_; +#ifdef LIBJAMI_TESTABLE + std::function<void(std::string, BootstrapStatus)> bootstrapCbTest_; +#endif std::mutex writeMtx_ {}; std::unique_ptr<ConversationRepository> repository_; + std::shared_ptr<SwarmManager> swarmManager_; std::weak_ptr<JamiAccount> account_; std::atomic_bool isRemoving_ {false}; std::vector<std::map<std::string, std::string>> loadMessages(const LogOptions& options); @@ -604,6 +637,12 @@ public: mutable std::vector<std::map<std::string, std::string>> activeCalls_ {}; GitSocketList gitSocketList_ {}; + + // Bootstrap + std::shared_ptr<asio::io_context> ioContext_; + std::unique_ptr<asio::steady_timer> fallbackTimer_; + + bool isMobile {false}; }; bool @@ -719,18 +758,18 @@ Conversation::Impl::loadMessages(const LogOptions& options) return repository_->convCommitToMap(convCommits); } -Conversation::Conversation(const std::weak_ptr<JamiAccount>& account, +Conversation::Conversation(const std::shared_ptr<JamiAccount>& account, ConversationMode mode, const std::string& otherMember) : pimpl_ {new Impl {account, mode, otherMember}} {} -Conversation::Conversation(const std::weak_ptr<JamiAccount>& account, +Conversation::Conversation(const std::shared_ptr<JamiAccount>& account, const std::string& conversationId) : pimpl_ {new Impl {account, conversationId}} {} -Conversation::Conversation(const std::weak_ptr<JamiAccount>& account, +Conversation::Conversation(const std::shared_ptr<JamiAccount>& account, const std::string& remoteDevice, const std::string& conversationId) : pimpl_ {new Impl {account, remoteDevice, conversationId}} @@ -807,11 +846,7 @@ Conversation::gitSocket(const DeviceId& deviceId) const { return pimpl_->gitSocket(deviceId); } -/*bool -Conversation::hasGitSocket(const DeviceId& deviceId) const -{ - return pimpl_->hasGitSocket(deviceId); -}*/ + void Conversation::addGitSocket(const DeviceId& deviceId, const std::shared_ptr<ChannelSocket>& socket) { @@ -828,6 +863,20 @@ void Conversation::removeGitSockets() { pimpl_->gitSocketList_.clear(); + pimpl_->swarmManager_->shutdown(); + pimpl_->checkedMembers_.clear(); +} + +void +Conversation::connectivityChanged() +{ + pimpl_->swarmManager_->maintainBuckets(); +} + +bool +Conversation::hasSwarmChannel(const std::string& deviceId) +{ + return pimpl_->swarmManager_->isConnectedWith(DeviceId(deviceId)); } void @@ -928,7 +977,23 @@ Conversation::removeMember(const std::string& contactUri, bool isDevice, const O JAMI_WARN("Vote solved for %s. %s banned", contactUri.c_str(), isDevice ? "Device" : "Member"); + + const auto nodes = sthis->pimpl_->swarmManager_->getRoutingTable().getAllNodes(); + // Remove nodes from swarmManager + std::vector<NodeId> toRemove; + for (const auto node : nodes) + if (contactUri == sthis->uriFromDevice(node.toString())) + toRemove.emplace_back(node); + sthis->pimpl_->swarmManager_->deleteNode(toRemove); + // Remove git sockets with this member + std::vector<DeviceId> gitToRm; + for (const auto& [deviceId, _] : sthis->pimpl_->gitSocketList_) + if (contactUri == sthis->uriFromDevice(deviceId.toString())) + gitToRm.emplace_back(deviceId); + for (const auto& did: gitToRm) + sthis->removeGitSocket(did); } + sthis->pimpl_->announce(commits); lk.unlock(); cb(!lastId.empty(), lastId); @@ -958,9 +1023,31 @@ Conversation::peersToSyncWith() const s.reserve(nodes.size() + mobiles.size()); s.insert(s.end(), nodes.begin(), nodes.end()); s.insert(s.end(), mobiles.begin(), mobiles.end()); + for (const auto& [deviceId, _] : pimpl_->gitSocketList_) + if (std::find(s.cbegin(), s.cend(), deviceId) == s.cend()) + s.emplace_back(deviceId); return s; } +bool +Conversation::isBoostraped() const +{ + const auto& routingTable = pimpl_->swarmManager_->getRoutingTable(); + return !routingTable.getNodes().empty(); +} + +std::string +Conversation::uriFromDevice(const std::string& deviceId) const +{ + return pimpl_->repository_->uriFromDevice(deviceId); +} + +void +Conversation::monitor() +{ + pimpl_->swarmManager_->getRoutingTable().printRoutingTable(); +} + std::string Conversation::join() { @@ -1280,11 +1367,9 @@ Conversation::sync(const std::string& member, if (auto account = pimpl_->account_.lock()) { // For waiting request, downloadFile for (const auto& wr : dataTransfer()->waitingRequests()) { - auto path = fileutils::get_data_dir() + DIR_SEPARATOR_STR - + account->getAccountID() + DIR_SEPARATOR_STR - + "conversation_data" + DIR_SEPARATOR_STR - + id() + DIR_SEPARATOR_STR - + wr.fileId; + auto path = fileutils::get_data_dir() + DIR_SEPARATOR_STR + account->getAccountID() + + DIR_SEPARATOR_STR + "conversation_data" + DIR_SEPARATOR_STR + id() + + DIR_SEPARATOR_STR + wr.fileId; auto start = fileutils::size(path); if (start < 0) start = 0; @@ -1595,7 +1680,9 @@ Conversation::setMessageDisplayed(const std::string& uri, const std::string& int void Conversation::updateLastDisplayed(const std::map<std::string, std::string>& map) { - auto filePath = fmt::format("{}/{}", pimpl_->conversationDataPath_, ConversationMapKeys::LAST_DISPLAYED); + auto filePath = fmt::format("{}/{}", + pimpl_->conversationDataPath_, + ConversationMapKeys::LAST_DISPLAYED); auto prefs = map; auto itLast = prefs.find(LAST_MODIFIED); if (itLast != prefs.end()) { @@ -1611,7 +1698,7 @@ Conversation::updateLastDisplayed(const std::map<std::string, std::string>& map) prefs.erase(itLast); } - for (const auto& [uri, id]: prefs) + for (const auto& [uri, id] : prefs) setMessageDisplayed(uri, id); } @@ -1670,7 +1757,9 @@ Conversation::displayed() const { try { std::map<std::string, std::string> lastDisplayed; - auto filePath = fmt::format("{}/{}", pimpl_->conversationDataPath_, ConversationMapKeys::LAST_DISPLAYED); + auto filePath = fmt::format("{}/{}", + pimpl_->conversationDataPath_, + ConversationMapKeys::LAST_DISPLAYED); auto file = fileutils::loadFile(filePath); msgpack::object_handle oh = msgpack::unpack((const char*) file.data(), file.size()); oh.get().convert(lastDisplayed); @@ -1681,19 +1770,145 @@ Conversation::displayed() const return {}; } +#ifdef LIBJAMI_TESTABLE +void +Conversation::onBootstrapStatus(const std::function<void(std::string, BootstrapStatus)>& cb) +{ + pimpl_->bootstrapCbTest_ = cb; +} +#endif + +void +Conversation::checkBootstrapMember(const asio::error_code& ec, + std::vector<std::map<std::string, std::string>> members) +{ + if (ec == asio::error::operation_aborted + or pimpl_->swarmManager_->getRoutingTable().getNodes().size() > 0) + return; + // We bootstrap the DRT with devices who already wrote in the repository. + // However, in a conversation, a large number of devices may just watch + // the conversation, but never write any message. + auto acc = pimpl_->account_.lock(); + std::string uri; + while (!members.empty()) { + auto member = members.back(); + members.pop_back(); + auto& uri = member.at("uri"); + if (uri != acc->getUsername() + && pimpl_->checkedMembers_.find(uri) == pimpl_->checkedMembers_.end()) + break; + } + // If members is empty, we finished the fallback un-successfully + if (members.empty() && uri.empty()) { + JAMI_WARNING("{}[SwarmManager {}] Bootstrap: Fallback failed. Wait for remote connections.", + pimpl_->toString(), + fmt::ptr(pimpl_->swarmManager_.get())); +#ifdef LIBJAMI_TESTABLE + if (pimpl_->bootstrapCbTest_) + pimpl_->bootstrapCbTest_(id(), BootstrapStatus::FAILED); +#endif + return; + } + + // Fallback, check devices of a member (we didn't check yet) in the conversation + pimpl_->checkedMembers_.emplace(uri); + auto devices = std::make_shared<std::vector<NodeId>>(); + acc->forEachDevice( + dht::InfoHash(uri), + [w = weak(), devices](const std::shared_ptr<dht::crypto::PublicKey>& dev) { + // Test if already sent + if (auto sthis = w.lock()) { + if (!sthis->pimpl_->swarmManager_->getRoutingTable().hasKnownNode(dev->getLongId())) + devices->emplace_back(dev->getLongId()); + } + }, + [w = weak(), devices, members = std::move(members), uri](bool ok) { + auto sthis = w.lock(); + if (!sthis) + return; + if (ok && devices->size() != 0) { +#ifdef LIBJAMI_TESTABLE + if (sthis->pimpl_->bootstrapCbTest_) + sthis->pimpl_->bootstrapCbTest_(sthis->id(), BootstrapStatus::FALLBACK); +#endif + JAMI_WARNING("{}[SwarmManager {}] Bootstrap: Fallback with member: {}", + sthis->pimpl_->toString(), + fmt::ptr(sthis->pimpl_->swarmManager_.get()), + uri); + sthis->pimpl_->swarmManager_->setKnownNodes(*devices); + } else { + // Check next member + sthis->pimpl_->fallbackTimer_->expires_at(std::chrono::steady_clock::now()); + sthis->pimpl_->fallbackTimer_->async_wait( + std::bind(&Conversation::checkBootstrapMember, + sthis.get(), + std::placeholders::_1, + std::move(members))); + } + }); +} + void Conversation::bootstrap(std::function<void()> onBootstraped) { if (!pimpl_ || !pimpl_->repository_ || !pimpl_->swarmManager_) return; + // Here, we bootstrap the DRT with devices who already wrote in the conversation + // If this doesn't work, it will try to fallback with checkBootstrapMember + // If it works, the callback onConnectionChanged will be called with ok=true pimpl_->bootstrapCb_ = std::move(onBootstraped); std::vector<DeviceId> devices; for (const auto& m : pimpl_->repository_->devices()) devices.insert(devices.end(), m.second.begin(), m.second.end()); // Add known devices - JAMI_DEBUG("[SwarmManager {}] Bootstrap with {} devices", + if (auto acc = pimpl_->account_.lock()) { + for (const auto& [id, _] : acc->getKnownDevices()) { + devices.emplace_back(id); + } + } + JAMI_DEBUG("{}[SwarmManager {}] Bootstrap with {} devices", + pimpl_->toString(), fmt::ptr(pimpl_->swarmManager_.get()), devices.size()); + // set callback + pimpl_->swarmManager_->onConnectionChanged([w = weak()](bool ok) { + // This will call methods from accounts, so trigger on another thread. + dht::ThreadPool::io().run([w, ok] { + auto sthis = w.lock(); + if (!sthis) + return; + if (ok) { + // Bootstrap succeeded! + sthis->pimpl_->checkedMembers_.clear(); + if (sthis->pimpl_->bootstrapCb_) + sthis->pimpl_->bootstrapCb_(); +#ifdef LIBJAMI_TESTABLE + if (sthis->pimpl_->bootstrapCbTest_) + sthis->pimpl_->bootstrapCbTest_(sthis->id(), BootstrapStatus::SUCCESS); +#endif + return; + } + // Fallback + auto acc = sthis->pimpl_->account_.lock(); + if (!acc) + return; + auto members = sthis->getMembers(false, false); + std::shuffle(members.begin(), members.end(), acc->rand); + // TODO decide a formula + auto timeForBootstrap = std::min(static_cast<size_t>(8), members.size()); + JAMI_DEBUG("{}[SwarmManager {}] Fallback in {} seconds", + sthis->pimpl_->toString(), + fmt::ptr(sthis->pimpl_->swarmManager_.get()), + (20 - timeForBootstrap)); + sthis->pimpl_->fallbackTimer_->expires_at(std::chrono::steady_clock::now() + 20s + - std::chrono::seconds(timeForBootstrap)); + sthis->pimpl_->fallbackTimer_->async_wait(std::bind(&Conversation::checkBootstrapMember, + sthis.get(), + std::placeholders::_1, + std::move(members))); + }); + }); + pimpl_->checkedMembers_.clear(); pimpl_->swarmManager_->setKnownNodes(devices); } diff --git a/src/jamidht/conversation.h b/src/jamidht/conversation.h index 10dd08b451db7ee6a1bdd02d769030e581e11c53..2f32a3b444f95efa2b26e047eece3aa4c7f6537e 100644 --- a/src/jamidht/conversation.h +++ b/src/jamidht/conversation.h @@ -33,6 +33,8 @@ #include <memory> #include <set> +#include <asio.hpp> + namespace jami { namespace ConversationMapKeys { @@ -126,18 +128,32 @@ using NeedSocketCb class Conversation : public std::enable_shared_from_this<Conversation> { public: - Conversation(const std::weak_ptr<JamiAccount>& account, + Conversation(const std::shared_ptr<JamiAccount>& account, ConversationMode mode, const std::string& otherMember = ""); - Conversation(const std::weak_ptr<JamiAccount>& account, const std::string& conversationId = ""); - Conversation(const std::weak_ptr<JamiAccount>& account, + Conversation(const std::shared_ptr<JamiAccount>& account, + const std::string& conversationId = ""); + Conversation(const std::shared_ptr<JamiAccount>& account, const std::string& remoteDevice, const std::string& conversationId); ~Conversation(); + /** + * Print the state of the DRT linked to the conversation + */ + void monitor(); + +#ifdef LIBJAMI_TESTABLE + enum class BootstrapStatus { FAILED, FALLBACK, SUCCESS }; + /** + * Used by the tests to get whenever the DRT is connected/disconnected + */ + void onBootstrapStatus(const std::function<void(std::string, BootstrapStatus)>& cb); +#endif + /** * Bootstrap swarm manager to other peers - * @param onBootstrapped Callback called when connection is successfully established + * @param onBootstraped Callback called when connection is successfully established */ void bootstrap(std::function<void()> onBootstraped); @@ -486,10 +502,26 @@ public: */ std::vector<std::map<std::string, std::string>> currentCalls() const; + /** + * Git operations will need a ChannelSocket for cloning/fetching commits + * Because libgit2 is a C library, we store the pointer in the corresponding conversation + * and the GitTransport will inject to libgit2 whenever needed + */ std::shared_ptr<ChannelSocket> gitSocket(const DeviceId& deviceId) const; void addGitSocket(const DeviceId& deviceId, const std::shared_ptr<ChannelSocket>& socket); void removeGitSocket(const DeviceId& deviceId); void removeGitSockets(); + /** + * Used to avoid multiple connections, we just check if we got a swarm channel with a specific + * device + * @param deviceId + */ + bool hasSwarmChannel(const std::string& deviceId); + + /** + * If we change from one network to one another, we will need to update the state of the connections + */ + void connectivityChanged(); private: std::shared_ptr<Conversation> shared() @@ -509,6 +541,15 @@ private: return std::static_pointer_cast<Conversation const>(shared_from_this()); } + // Private because of weak() + /** + * Used by bootstrap() to launch the fallback + * @param ec + * @param members Members to try to connect + */ + void checkBootstrapMember(const asio::error_code& ec, + std::vector<std::map<std::string, std::string>> members); + class Impl; std::unique_ptr<Impl> pimpl_; }; diff --git a/src/jamidht/conversation_module.cpp b/src/jamidht/conversation_module.cpp index d1d8736aca664e08abe53301674ff00eca514a91..84f0e06cd51634fb0eddd14563a653b4c37cf7ec 100644 --- a/src/jamidht/conversation_module.cpp +++ b/src/jamidht/conversation_module.cpp @@ -898,8 +898,9 @@ ConversationModule::Impl::sendMessageNotification(Conversation& conversation, { std::lock_guard<std::mutex> lk(notSyncedNotificationMtx_); devices = conversation.peersToSyncWith(); - auto members = conversation.memberUris(username_, {}); + auto members = conversation.memberUris(username_, {MemberRole::BANNED}); std::vector<std::string> connectedMembers; + // print all members for (const auto& device : devices) { auto cert = tls::CertificateStore::instance().getCertificate(device.toString()); if (cert && cert->issuer) @@ -1142,10 +1143,10 @@ ConversationModule::loadConversations() auto convFromDetails = getOneToOneConversation(otherUri); if (convFromDetails != repository) { if (convFromDetails.empty()) { - JAMI_ERROR( - "No conversation detected for {} but one exists ({}). Update details", - otherUri, - repository); + JAMI_ERROR("No conversation detected for {} but one exists ({}). " + "Update details", + otherUri, + repository); acc->updateConvForContact(otherUri, convFromDetails, repository); } else { JAMI_ERROR("Multiple conversation detected for {} but ({} & {})", @@ -1292,12 +1293,12 @@ ConversationModule::clearPendingFetch() { if (!pimpl_->pendingConversationsFetch_.empty()) { // Note: This is a fallback. convModule() is kept if account is disabled/re-enabled. - // iOS uses setAccountActive() a lot, and if for some reason the previous pending fetch is - // not erased (callback not called), it will block the new messages as it will not sync. The - // best way to debug this is to get logs from the last ICE connection for syncing the - // conversation. It may have been killed in some un-expected way avoiding to call the - // callbacks. This should never happen, but if it's the case, this will allow new messages - // to be synced correctly. + // iOS uses setAccountActive() a lot, and if for some reason the previous pending fetch + // is not erased (callback not called), it will block the new messages as it will not + // sync. The best way to debug this is to get logs from the last ICE connection for + // syncing the conversation. It may have been killed in some un-expected way avoiding to + // call the callbacks. This should never happen, but if it's the case, this will allow + // new messages to be synced correctly. JAMI_ERR("This is a bug, seems to still fetch to some device on initializing"); pimpl_->pendingConversationsFetch_.clear(); } @@ -1839,7 +1840,8 @@ ConversationModule::onSyncData(const SyncMsg& msg, auto itConv = pimpl_->convInfos_.find(convId); if (itConv != pimpl_->convInfos_.end() && itConv->second.removed) { if (itConv->second.removed > convInfo.created) { - // Only reclone if re-added, else the peer is not synced yet (could be offline before) + // Only reclone if re-added, else the peer is not synced yet (could be + // offline before) continue; } JAMI_DEBUG("Re-add previously removed conversation {:s}", convId); @@ -2020,8 +2022,8 @@ ConversationModule::addConversationMember(const std::string& conversationId, if (it->second->isMember(contactUri, true)) { JAMI_DEBUG("{:s} is already a member of {:s}, resend invite", contactUri, conversationId); - // Note: This should not be necessary, but if for whatever reason the other side didn't join - // we should not forbid new invites + // Note: This should not be necessary, but if for whatever reason the other side didn't + // join we should not forbid new invites auto invite = it->second->generateInvitation(); lk.unlock(); pimpl_->sendMsgCb_(contactUri, {}, std::move(invite), 0); @@ -2524,8 +2526,8 @@ ConversationModule::hostConference(const std::string& conversationId, }); // When conf finished = remove host & commit - // Master call, so when it's stopped, the conference will be stopped (as we use the hold state - // for detaching the call) + // Master call, so when it's stopped, the conference will be stopped (as we use the hold + // state for detaching the call) conf->onShutdown( [w = pimpl_->weak(), accountUri = pimpl_->username_, confId, conversationId, call]( int duration) { diff --git a/src/jamidht/conversation_module.h b/src/jamidht/conversation_module.h index e02fb46d0096c8fd2563cfc9fbf446b49b559900..9b7d68fa586aa2cf1d7af7307ff006ded5ba7953 100644 --- a/src/jamidht/conversation_module.h +++ b/src/jamidht/conversation_module.h @@ -370,8 +370,8 @@ public: */ void setConversationPreferences(const std::string& conversationId, const std::map<std::string, std::string>& prefs); - std::map<std::string, std::string> getConversationPreferences( - const std::string& conversationId, bool includeCreated = false) const; + std::map<std::string, std::string> getConversationPreferences(const std::string& conversationId, + bool includeCreated = false) const; /** * Retrieve all conversation preferences to sync with other devices */ diff --git a/src/jamidht/conversationrepository.cpp b/src/jamidht/conversationrepository.cpp index 7f6174de061d70b542157714fef868844526b732..79909c13d8e55dc23330b7e50ef83dcc76743b8b 100644 --- a/src/jamidht/conversationrepository.cpp +++ b/src/jamidht/conversationrepository.cpp @@ -93,7 +93,8 @@ public: return {std::move(repo), git_repository_free}; } - std::string getDisplayName() const { + std::string getDisplayName() const + { auto shared = account_.lock(); if (!shared) return {}; @@ -2060,7 +2061,6 @@ ConversationRepository::Impl::log(const LogOptions& options) const if (options.skipMerge && git_commit_parentcount(commit.get()) > 1) { return CallbackResult::Skip; } - if ((options.nbOfCommits != 0 && commits.size() == options.nbOfCommits)) return CallbackResult::Break; // Stop logging if (breakLogging) @@ -2455,17 +2455,14 @@ ConversationRepository::Impl::diffStats(const GitDiff& diff) const ////////////////////////////////// std::unique_ptr<ConversationRepository> -ConversationRepository::createConversation(const std::weak_ptr<JamiAccount>& account, +ConversationRepository::createConversation(const std::shared_ptr<JamiAccount>& account, ConversationMode mode, const std::string& otherMember) { - auto shared = account.lock(); - if (!shared) - return {}; // Create temporary directory because we can't know the first hash for now std::uniform_int_distribution<uint64_t> dist {0, std::numeric_limits<uint64_t>::max()}; random_device rdev; - auto conversationsPath = fileutils::get_data_dir() + DIR_SEPARATOR_STR + shared->getAccountID() + auto conversationsPath = fileutils::get_data_dir() + DIR_SEPARATOR_STR + account->getAccountID() + DIR_SEPARATOR_STR + "conversations"; fileutils::check_dir(conversationsPath.c_str()); auto tmpPath = conversationsPath + DIR_SEPARATOR_STR + std::to_string(dist(rdev)); @@ -2483,14 +2480,14 @@ ConversationRepository::createConversation(const std::weak_ptr<JamiAccount>& acc } // Add initial files - if (!add_initial_files(repo, shared, mode, otherMember)) { + if (!add_initial_files(repo, account, mode, otherMember)) { JAMI_ERR("Error when adding initial files"); fileutils::removeAll(tmpPath, true); return {}; } // Commit changes - auto id = initial_commit(repo, shared, mode, otherMember); + auto id = initial_commit(repo, account, mode, otherMember); if (id.empty()) { JAMI_ERR("Couldn't create initial commit in %s", tmpPath.c_str()); fileutils::removeAll(tmpPath, true); @@ -2511,20 +2508,15 @@ ConversationRepository::createConversation(const std::weak_ptr<JamiAccount>& acc } std::unique_ptr<ConversationRepository> -ConversationRepository::cloneConversation(const std::weak_ptr<JamiAccount>& account, +ConversationRepository::cloneConversation(const std::shared_ptr<JamiAccount>& account, const std::string& deviceId, const std::string& conversationId) { - auto shared = account.lock(); - if (!shared) - return {}; - auto conversationsPath = fileutils::get_data_dir() + "/" + shared->getAccountID() + "/" + auto conversationsPath = fileutils::get_data_dir() + "/" + account->getAccountID() + "/" + "conversations"; fileutils::check_dir(conversationsPath.c_str()); auto path = conversationsPath + "/" + conversationId; - git_repository* rep = nullptr; - std::stringstream url; - url << "git://" << deviceId << '/' << conversationId; + auto url = fmt::format("git://{}/{}", deviceId, conversationId); git_clone_options clone_options; git_clone_options_init(&clone_options, GIT_CLONE_OPTIONS_VERSION); @@ -2533,11 +2525,14 @@ ConversationRepository::cloneConversation(const std::weak_ptr<JamiAccount>& acco void*) { // Uncomment to get advancment // if (stats->received_objects % 500 == 0 || stats->received_objects == stats->total_objects) - // JAMI_DEBUG("{}/{} {}kb", stats->received_objects, stats->total_objects, stats->received_bytes/1024); + // JAMI_DEBUG("{}/{} {}kb", stats->received_objects, stats->total_objects, + // stats->received_bytes/1024); // If a pack is more than 256Mb, it's anormal. if (stats->received_bytes > MAX_FETCH_SIZE) { JAMI_ERROR("Abort fetching repository, the fetch is too big: {} bytes ({}/{})", - stats->received_bytes, stats->received_objects, stats->total_objects); + stats->received_bytes, + stats->received_objects, + stats->total_objects); return -1; } return 0; @@ -2549,11 +2544,13 @@ ConversationRepository::cloneConversation(const std::weak_ptr<JamiAccount>& acco fileutils::removeAll(path, true); } - JAMI_INFO("Start clone in %s", path.c_str()); - if (git_clone(&rep, url.str().c_str(), path.c_str(), nullptr) < 0) { - const git_error* err = giterr_last(); - if (err) - JAMI_ERR("Error when retrieving remote conversation: %s %s", err->message, path.c_str()); + JAMI_DEBUG("Start clone of {:s} to {:s}", url, path); + git_repository* rep = nullptr; + if (auto err = git_clone(&rep, url.c_str(), path.c_str(), nullptr)) { + if (const git_error* gerr = giterr_last()) + JAMI_ERROR("Error when retrieving remote conversation: {:s} {:s}", gerr->message, path); + else + JAMI_ERROR("Unknown error {:d} when retrieving remote conversation", err); return nullptr; } git_repository_free(rep); @@ -2930,11 +2927,14 @@ ConversationRepository::fetch(const std::string& remoteDeviceId) fetch_opts.callbacks.transfer_progress = [](const git_indexer_progress* stats, void*) { // Uncomment to get advancment // if (stats->received_objects % 500 == 0 || stats->received_objects == stats->total_objects) - // JAMI_DEBUG("{}/{} {}kb", stats->received_objects, stats->total_objects, stats->received_bytes/1024); + // JAMI_DEBUG("{}/{} {}kb", stats->received_objects, stats->total_objects, + // stats->received_bytes/1024); // If a pack is more than 256Mb, it's anormal. if (stats->received_bytes > MAX_FETCH_SIZE) { JAMI_ERROR("Abort fetching repository, the fetch is too big: {} bytes ({}/{})", - stats->received_bytes, stats->received_objects, stats->total_objects); + stats->received_bytes, + stats->received_objects, + stats->total_objects); return -1; } return 0; @@ -3702,6 +3702,12 @@ ConversationRepository::pinCertificates(bool blocking) } } +std::string +ConversationRepository::uriFromDevice(const std::string& deviceId) const +{ + return pimpl_->uriFromDevice(deviceId); +} + std::string ConversationRepository::updateInfos(const std::map<std::string, std::string>& profile) { @@ -3799,7 +3805,8 @@ ConversationRepository::infos() const std::map<std::string, std::string> result; if (fileutils::isFile(profilePath)) { auto content = fileutils::loadFile(profilePath); - result = ConversationRepository::infosFromVCard(vCard::utils::toMap(std::string_view {(const char*)content.data(), content.size()})); + result = ConversationRepository::infosFromVCard(vCard::utils::toMap( + std::string_view {(const char*) content.data(), content.size()})); } result["mode"] = std::to_string(static_cast<int>(mode())); return result; diff --git a/src/jamidht/conversationrepository.h b/src/jamidht/conversationrepository.h index 0178c7f7ab045213698fa327d31a6bbbd52eb97c..d49777711098eefeb98fd1587ca4c151d06cb728 100644 --- a/src/jamidht/conversationrepository.h +++ b/src/jamidht/conversationrepository.h @@ -59,8 +59,9 @@ struct LogOptions { std::string from {}; std::string to {}; - uint64_t nbOfCommits {0}; // maximum number of commits wanted - bool skipMerge {false}; // Do not include merge commits in the log. Used by the module to get last interaction without potential merges + uint64_t nbOfCommits {0}; // maximum number of commits wanted + bool skipMerge {false}; // Do not include merge commits in the log. Used by the module to get + // last interaction without potential merges bool includeTo {false}; // If we want or not the "to" commit [from-to] or [from-to) bool fastLog {false}; // Do not parse content, used mostly to count bool logIfNotFound {true}; // Add a warning in the log if commit is not found @@ -140,7 +141,7 @@ public: * @return the conversation repository object */ static LIBJAMI_TESTABLE std::unique_ptr<ConversationRepository> createConversation( - const std::weak_ptr<JamiAccount>& account, + const std::shared_ptr<JamiAccount>& account, ConversationMode mode = ConversationMode::INVITES_ONLY, const std::string& otherMember = ""); @@ -153,7 +154,7 @@ public: * @param socket Socket used to clone */ static LIBJAMI_TESTABLE std::unique_ptr<ConversationRepository> cloneConversation( - const std::weak_ptr<JamiAccount>& account, + const std::shared_ptr<JamiAccount>& account, const std::string& deviceId, const std::string& conversationId); @@ -372,6 +373,13 @@ public: * @param blocking if we need to wait that certificates are pinned */ void pinCertificates(bool blocking = false); + /** + * Retrieve the uri from a deviceId + * @note used by swarm manager (peersToSyncWith) + * @param deviceId + * @return corresponding issuer + */ + std::string uriFromDevice(const std::string& deviceId) const; /** * Change repository's infos diff --git a/src/jamidht/jamiaccount.cpp b/src/jamidht/jamiaccount.cpp index 58446e3e746a5a50a4468225e0c40bfe658f2090..ad6e8501273f53d54a8270ffe1db7e16d87ea611 100644 --- a/src/jamidht/jamiaccount.cpp +++ b/src/jamidht/jamiaccount.cpp @@ -1356,7 +1356,6 @@ JamiAccount::getVolatileAccountDetails() const return a; } - #if HAVE_RINGNS void JamiAccount::lookupName(const std::string& name) @@ -1496,8 +1495,8 @@ JamiAccount::registerAsyncOps() const auto& accId = accPtr->getAccountID(); JAMI_LOG("[Account {:s}] DHT UPNP mapping changed to {:s}", - accId, - mapRes->toString(true)); + accId, + mapRes->toString(true)); if (*update) { // Check if we need to update the mapping and the registration. @@ -1512,10 +1511,11 @@ JamiAccount::registerAsyncOps() // Update the mapping and restart the registration. dhtMap.updateFrom(mapRes); - JAMI_WARNING("[Account {:s}] Allocated port changed to {}. Restarting the " - "registration", - accId, - accPtr->dhtPortUsed()); + JAMI_WARNING( + "[Account {:s}] Allocated port changed to {}. Restarting the " + "registration", + accId, + accPtr->dhtPortUsed()); accPtr->dht_->connectivityChanged(); @@ -1529,14 +1529,15 @@ JamiAccount::registerAsyncOps() // Set connection info and load the account. if (mapRes->getState() == upnp::MappingState::OPEN) { dhtMap.updateFrom(mapRes); - JAMI_LOG("[Account {:s}] Mapping {:s} successfully allocated: starting the DHT", - accId, - dhtMap.toString()); - } else { - JAMI_WARNING( - "[Account {:s}] Mapping request is in {:s} state: starting the DHT anyway", + JAMI_LOG( + "[Account {:s}] Mapping {:s} successfully allocated: starting the DHT", accId, - mapRes->getStateStr()); + dhtMap.toString()); + } else { + JAMI_WARNING("[Account {:s}] Mapping request is in {:s} state: starting " + "the DHT anyway", + accId, + mapRes->getStateStr()); } // Load the account and start the DHT. @@ -1564,7 +1565,8 @@ JamiAccount::doRegister() { std::lock_guard<std::recursive_mutex> lock(configurationMutex_); if (not isUsable()) { - JAMI_WARNING("[Account {:s}] Account must be enabled and active to register, ignoring", getAccountID()); + JAMI_WARNING("[Account {:s}] Account must be enabled and active to register, ignoring", + getAccountID()); return; } @@ -1816,11 +1818,14 @@ JamiAccount::doRegister_() config.proxy_server = proxyServerCached_; if (not config.proxy_server.empty()) { - JAMI_LOG("[Account {}] using proxy server {}", - getAccountID(), - config.proxy_server); + JAMI_LOG("[Account {}] using proxy server {}", getAccountID(), config.proxy_server); if (not config.push_token.empty()) { - JAMI_LOG("[Account %s] using push notifications with platform: {}, topic: {}, token: {}", getAccountID(), config.push_platform, config.push_topic, config.push_token); + JAMI_LOG( + "[Account %s] using push notifications with platform: {}, topic: {}, token: {}", + getAccountID(), + config.push_platform, + config.push_topic, + config.push_token); } } @@ -1939,6 +1944,14 @@ JamiAccount::doRegister_() }, [this] { deviceAnnounced_ = true; + + // Bootstrap at the end to avoid to be long to load. + dht::ThreadPool::io().run([w = weak()] { + if (auto shared = w.lock()) { + std::lock_guard<std::recursive_mutex> lock(shared->configurationMutex_); + shared->convModule()->bootstrap(); + } + }); emitSignal<libjami::ConfigurationSignal::VolatileDetailsChanged>( accountID_, getVolatileAccountDetails()); }); @@ -2023,12 +2036,13 @@ JamiAccount::doRegister_() // Check if pull from banned device if (convModule()->isBannedDevice(conversationId, remoteDevice)) { - JAMI_WARNING("[Account {:s}] Git server requested for conversation {:s}, but the " - "device is " - "unauthorized ({:s}) ", - getAccountID(), - conversationId, - remoteDevice); + JAMI_WARNING( + "[Account {:s}] Git server requested for conversation {:s}, but the " + "device is " + "unauthorized ({:s}) ", + getAccountID(), + conversationId, + remoteDevice); channel->shutdown(); return; } @@ -2039,12 +2053,13 @@ JamiAccount::doRegister_() // So it's not the server socket return; } - JAMI_WARNING("[Account {:s}] Git server requested for conversation {:s}, device {:s}, " - "channel {}", - accountID_, - conversationId, - deviceId.toString(), - channel->channel()); + JAMI_WARNING( + "[Account {:s}] Git server requested for conversation {:s}, device {:s}, " + "channel {}", + accountID_, + conversationId, + deviceId.toString(), + channel->channel()); auto gs = std::make_unique<GitServer>(accountID_, conversationId, channel); gs->setOnFetched( [w = weak(), conversationId, deviceId](const std::string& commit) { @@ -2142,11 +2157,12 @@ JamiAccount::convModule() shared->syncModule()->syncWithConnected(syncMsg); }); }, - [this](auto&& uri, auto&& msg, auto token = 0) { + [this](auto&& uri, auto&& device, auto&& msg, auto token = 0) { // No need to retrigger, sendTextMessage will call // messageEngine_.sendMessage, already retriggering on // main thread. - return sendTextMessage(uri, msg, token); + auto deviceId = device ? device.toString() : ""; + return sendTextMessage(uri, deviceId, msg, token); }, [this](const auto& convId, const auto& deviceId, auto cb, const auto& type) { runOnMainThread([w = weak(), convId, deviceId, cb = std::move(cb), type] { @@ -2186,16 +2202,41 @@ JamiAccount::convModule() }); }, [this](const auto& convId, const auto& deviceId, auto&& cb, const auto& connectionType) { - std::lock_guard<std::mutex> lkCM(connManagerMtx_); - if (!connectionManager_) { - Manager::instance().ioContext()->post([cb = std::move(cb)] { cb({}); }); - return; - } - connectionManager_->connectDevice(DeviceId(deviceId), - fmt::format("swarm://{}", convId), - [cb = std::move( - cb)](std::shared_ptr<ChannelSocket> socket, - const DeviceId&) { cb(socket); }); + runOnMainThread([w = weak(), convId, deviceId, cb, connectionType] { + auto shared = w.lock(); + if (!shared) + return; + std::lock_guard<std::mutex> lkCM(shared->connManagerMtx_); + if (!shared->connectionManager_) { + Manager::instance().ioContext()->post([cb] { cb({}); }); + return; + } + if (!shared->connectionManager_->isConnecting(DeviceId(deviceId), fmt::format("swarm://{}", convId))) { + shared->connectionManager_->connectDevice( + DeviceId(deviceId), + fmt::format("swarm://{}", convId), + [w, cb](std::shared_ptr<ChannelSocket> socket, + const DeviceId& deviceId) { + if (socket) { + auto shared = w.lock(); + if (!shared) + return; + auto remoteCert = socket->peerCertificate(); + auto uri = remoteCert->issuer->getId().toString(); + std::unique_lock<std::mutex> lk(shared->sipConnsMtx_); + // Verify that the connection is not already cached + SipConnectionKey key(uri, deviceId.toString()); + auto it = shared->sipConns_.find(key); + if (it == shared->sipConns_.end()) { + lk.unlock(); + shared->requestSIPConnection(uri, deviceId, ""); + } + } + + cb(socket); + }); + } + }); }, [this](auto&& convId, auto&& contactUri, bool accept) { // NOTE: do not reschedule as the conversation's requests @@ -2328,6 +2369,7 @@ JamiAccount::connectivityChanged() // nothing to do return; } + convModule()->connectivityChanged(); dht_->connectivityChanged(); { std::lock_guard<std::mutex> lkCM(connManagerMtx_); @@ -2911,7 +2953,7 @@ JamiAccount::declineConversationRequest(const std::string& conversationId) { auto peerId = convModule()->peerFromConversationRequest(conversationId); convModule()->declineConversationRequest(conversationId); - if (!peerId.empty()) { + if (!peerId.empty()) { std::lock_guard<std::recursive_mutex> lock(configurationMutex_); if (auto info = accountManager_->getInfo()) { // Verify if we have a trust request with this peer + convId @@ -2919,7 +2961,9 @@ JamiAccount::declineConversationRequest(const std::string& conversationId) if (req.find(libjami::Account::TrustRequest::CONVERSATIONID) != req.end() && req.at(libjami::Account::TrustRequest::CONVERSATIONID) == conversationId) { accountManager_->discardTrustRequest(peerId); - JAMI_DEBUG("[Account {:s}] declined trust request with {:s}", getAccountID(), peerId); + JAMI_DEBUG("[Account {:s}] declined trust request with {:s}", + getAccountID(), + peerId); } } } @@ -2969,6 +3013,7 @@ JamiAccount::forEachDevice(const dht::InfoHash& to, uint64_t JamiAccount::sendTextMessage(const std::string& to, + const std::string& deviceId, const std::map<std::string, std::string>& payloads, uint64_t refreshToken, bool onlyConnected) @@ -2983,18 +3028,19 @@ JamiAccount::sendTextMessage(const std::string& to, try { toUri = parseJamiUri(to); } catch (...) { - JAMI_ERR("Failed to send a text message due to an invalid URI %s", to.c_str()); + JAMI_ERROR("Failed to send a text message due to an invalid URI {}", to); return 0; } if (payloads.size() != 1) { - JAMI_ERR("Multi-part im is not supported yet by JamiAccount"); + JAMI_ERROR("Multi-part im is not supported yet by JamiAccount"); return 0; } - return SIPAccountBase::sendTextMessage(toUri, payloads, refreshToken, onlyConnected); + return SIPAccountBase::sendTextMessage(toUri, deviceId, payloads, refreshToken, onlyConnected); } void JamiAccount::sendMessage(const std::string& to, + const std::string& deviceId, const std::map<std::string, std::string>& payloads, uint64_t token, bool retryOnTimeout, @@ -3006,7 +3052,7 @@ JamiAccount::sendMessage(const std::string& to, } catch (...) { JAMI_ERR("Failed to send a text message due to an invalid URI %s", to.c_str()); if (!onlyConnected) - messageEngine_.onMessageSent(to, token, false); + messageEngine_.onMessageSent(to, token, false, deviceId); return; } if (payloads.size() != 1) { @@ -3018,9 +3064,6 @@ JamiAccount::sendMessage(const std::string& to, return; } - auto toH = dht::InfoHash(toUri); - auto now = clock::to_time_t(clock::now()); - auto confirm = std::make_shared<PendingConfirmation>(); if (onlyConnected) { confirm->replied = true; @@ -3035,6 +3078,10 @@ JamiAccount::sendMessage(const std::string& to, ++it; continue; } + if (!deviceId.empty() && key.second.toString() != deviceId) { + ++it; + continue; + } auto& conn = value.back(); auto& channel = conn.channel; @@ -3042,7 +3089,7 @@ JamiAccount::sendMessage(const std::string& to, auto ctx = std::make_unique<TextMessageCtx>(); ctx->acc = weak(); ctx->to = to; - ctx->deviceId = key.second; + ctx->deviceId = DeviceId(deviceId); ctx->id = token; ctx->onlyConnected = onlyConnected; ctx->retryOnTimeout = retryOnTimeout; @@ -3070,14 +3117,14 @@ JamiAccount::sendMessage(const std::string& to, }); if (!res) { if (!onlyConnected) - messageEngine_.onMessageSent(to, token, false); + messageEngine_.onMessageSent(to, token, false, deviceId); ++it; continue; } } catch (const std::runtime_error& ex) { JAMI_WARN("%s", ex.what()); if (!onlyConnected) - messageEngine_.onMessageSent(to, token, false); + messageEngine_.onMessageSent(to, token, false, deviceId); ++it; // Remove connection in incorrect state shutdownSIPConnection(channel, to, key.second); @@ -3086,137 +3133,153 @@ JamiAccount::sendMessage(const std::string& to, devices->emplace(key.second); ++it; + if (key.second.toString() == deviceId) { + return; + } } lk.unlock(); if (onlyConnected) return; - // Find listening devices for this account - accountManager_->forEachDevice( - toH, - [this, confirm, to, token, payloads, now, devices]( - const std::shared_ptr<dht::crypto::PublicKey>& dev) { - // Test if already sent - auto deviceId = dev->getLongId(); - if (devices->find(deviceId) != devices->end()) { - return; - } - if (deviceId.toString() == currentDeviceId()) { - devices->emplace(deviceId); - return; - } + if (deviceId.empty()) { + auto toH = dht::InfoHash(toUri); + // Find listening devices for this account + accountManager_->forEachDevice( + toH, + [this, confirm, to, token, payloads, devices]( + const std::shared_ptr<dht::crypto::PublicKey>& dev) { + // Test if already sent + auto deviceId = dev->getLongId(); + if (devices->find(deviceId) != devices->end()) { + return; + } + if (deviceId.toString() == currentDeviceId()) { + devices->emplace(deviceId); + return; + } - // Else, ask for a channel and send a DHT message - auto payload_type = payloads.cbegin()->first; - requestSIPConnection(to, deviceId, payload_type); - { - std::lock_guard<std::mutex> lock(messageMutex_); - sentMessages_[token].to.emplace(deviceId); - } + // Else, ask for a channel and send a DHT message + auto payload_type = payloads.cbegin()->first; + requestSIPConnection(to, deviceId, payload_type); + { + std::lock_guard<std::mutex> lock(messageMutex_); + sentMessages_[token].to.emplace(deviceId); + } - auto h = dht::InfoHash::get("inbox:" + dev->getId().toString()); - std::lock_guard<std::mutex> l(confirm->lock); - auto list_token - = dht_->listen<dht::ImMessage>(h, [this, to, token, confirm](dht::ImMessage&& msg) { - // check expected message confirmation - if (msg.id != token) - return true; - - { - std::lock_guard<std::mutex> lock(messageMutex_); - auto e = sentMessages_.find(msg.id); - if (e == sentMessages_.end() - or e->second.to.find(msg.owner->getLongId()) == e->second.to.end()) { - JAMI_DBG() << "[Account " << getAccountID() << "] [message " << token - << "] Message not found"; - return true; - } - sentMessages_.erase(e); - JAMI_DBG() << "[Account " << getAccountID() << "] [message " << token - << "] Received text message reply"; - - // add treated message - auto res = treatedMessages_.emplace(to_hex_string(msg.id)); - if (!res.second) - return true; - } - saveTreatedMessages(); - - // report message as confirmed received - { - std::lock_guard<std::mutex> l(confirm->lock); - for (auto& t : confirm->listenTokens) - dht_->cancelListen(t.first, std::move(t.second)); - confirm->listenTokens.clear(); - confirm->replied = true; - } - messageEngine_.onMessageSent(to, token, true); - return false; - }); - confirm->listenTokens.emplace(h, std::move(list_token)); - dht_->putEncrypted(h, - dev, - dht::ImMessage(token, - std::string(payloads.begin()->first), - std::string(payloads.begin()->second), - now), - [this, to, token, confirm, h](bool ok) { - JAMI_DBG() - << "[Account " << getAccountID() << "] [message " << token - << "] Put encrypted " << (ok ? "ok" : "failed"); - if (not ok && connectionManager_ /* Check if not joining */) { - std::unique_lock<std::mutex> l(confirm->lock); - auto lt = confirm->listenTokens.find(h); - if (lt != confirm->listenTokens.end()) { - std::shared_future<size_t> tok = std::move(lt->second); - confirm->listenTokens.erase(lt); - dht_->cancelListen(h, tok); - } - if (confirm->listenTokens.empty() and not confirm->replied) { - l.unlock(); - messageEngine_.onMessageSent(to, token, false); + auto h = dht::InfoHash::get("inbox:" + dev->getId().toString()); + std::lock_guard<std::mutex> l(confirm->lock); + auto list_token = dht_->listen< + dht::ImMessage>(h, [this, to, token, confirm](dht::ImMessage&& msg) { + // check expected message confirmation + if (msg.id != token) + return true; + + { + std::lock_guard<std::mutex> lock(messageMutex_); + auto e = sentMessages_.find(msg.id); + if (e == sentMessages_.end() + or e->second.to.find(msg.owner->getLongId()) == e->second.to.end()) { + JAMI_DBG() << "[Account " << getAccountID() << "] [message " << token + << "] Message not found"; + return true; + } + sentMessages_.erase(e); + JAMI_DBG() << "[Account " << getAccountID() << "] [message " << token + << "] Received text message reply"; + + // add treated message + auto res = treatedMessages_.emplace(to_hex_string(msg.id)); + if (!res.second) + return true; + } + saveTreatedMessages(); + + // report message as confirmed received + { + std::lock_guard<std::mutex> l(confirm->lock); + for (auto& t : confirm->listenTokens) + dht_->cancelListen(t.first, std::move(t.second)); + confirm->listenTokens.clear(); + confirm->replied = true; + } + messageEngine_.onMessageSent(to, token, true); + return false; + }); + confirm->listenTokens.emplace(h, std::move(list_token)); + auto now = clock::to_time_t(clock::now()); + dht_->putEncrypted(h, + dev, + dht::ImMessage(token, + std::string(payloads.begin()->first), + std::string(payloads.begin()->second), + now), + [this, to, token, confirm, h](bool ok) { + JAMI_DBG() + << "[Account " << getAccountID() << "] [message " + << token << "] Put encrypted " << (ok ? "ok" : "failed"); + if (not ok + && connectionManager_ /* Check if not joining */) { + std::unique_lock<std::mutex> l(confirm->lock); + auto lt = confirm->listenTokens.find(h); + if (lt != confirm->listenTokens.end()) { + std::shared_future<size_t> tok = std::move( + lt->second); + confirm->listenTokens.erase(lt); + dht_->cancelListen(h, tok); + } + if (confirm->listenTokens.empty() + and not confirm->replied) { + l.unlock(); + messageEngine_.onMessageSent(to, token, false); + } } - } - }); + }); - JAMI_DBG() << "[Account " << getAccountID() << "] [message " << token - << "] Sending message for device " << deviceId.toString(); - }, - [this, to, token, devices, confirm](bool ok) { - if (devices->size() == 1 && devices->begin()->toString() == currentDeviceId()) { - // Current user only have devices, so no message are sent - { - std::lock_guard<std::mutex> l(confirm->lock); - for (auto& t : confirm->listenTokens) - dht_->cancelListen(t.first, std::move(t.second)); - confirm->listenTokens.clear(); - confirm->replied = true; + JAMI_DBG() << "[Account " << getAccountID() << "] [message " << token + << "] Sending message for device " << deviceId.toString(); + }, + [this, to, token, devices, confirm](bool ok) { + if (devices->size() == 1 && devices->begin()->toString() == currentDeviceId()) { + // Current user only have devices, so no message are sent + { + std::lock_guard<std::mutex> l(confirm->lock); + for (auto& t : confirm->listenTokens) + dht_->cancelListen(t.first, std::move(t.second)); + confirm->listenTokens.clear(); + confirm->replied = true; + } + messageEngine_.onMessageSent(to, token, true); + } else if (not ok) { + messageEngine_.onMessageSent(to, token, false); } - messageEngine_.onMessageSent(to, token, true); - } else if (not ok) { - messageEngine_.onMessageSent(to, token, false); - } - }); + }); - // Timeout cleanup - Manager::instance().scheduleTaskIn( - [w = weak(), confirm, to, token]() { - std::unique_lock<std::mutex> l(confirm->lock); - if (not confirm->replied) { - if (auto this_ = w.lock()) { - JAMI_DBG() << "[Account " << this_->getAccountID() << "] [message " << token - << "] Timeout"; - for (auto& t : confirm->listenTokens) - this_->dht_->cancelListen(t.first, std::move(t.second)); - confirm->listenTokens.clear(); - confirm->replied = true; - l.unlock(); - this_->messageEngine_.onMessageSent(to, token, false); + // Timeout cleanup + Manager::instance().scheduleTaskIn( + [w = weak(), confirm, to, token]() { + std::unique_lock<std::mutex> l(confirm->lock); + if (not confirm->replied) { + if (auto this_ = w.lock()) { + JAMI_DBG() << "[Account " << this_->getAccountID() << "] [message " << token + << "] Timeout"; + for (auto& t : confirm->listenTokens) + this_->dht_->cancelListen(t.first, std::move(t.second)); + confirm->listenTokens.clear(); + confirm->replied = true; + l.unlock(); + this_->messageEngine_.onMessageSent(to, token, false); + } } - } - }, - std::chrono::minutes(1)); + }, + std::chrono::minutes(1)); + + } else { + // Set message as not sent in order to be re-triggered + messageEngine_.onMessageSent(to, token, false, deviceId); + auto payload_type = payloads.cbegin()->first; + requestSIPConnection(to, DeviceId(deviceId), payload_type); + } } void @@ -3227,7 +3290,7 @@ JamiAccount::onSIPMessageSent(const std::shared_ptr<TextMessageCtx>& ctx, int co ctx->confirmation->replied = true; l.unlock(); if (!ctx->onlyConnected) - messageEngine_.onMessageSent(ctx->to, ctx->id, true); + messageEngine_.onMessageSent(ctx->to, ctx->id, true, ctx->deviceId.toString()); } else { // Note: This can be called from pjsip's eventloop while // sipConnsMtx_ is locked. So we should retrigger the shutdown. @@ -3245,7 +3308,7 @@ JamiAccount::onSIPMessageSent(const std::shared_ptr<TextMessageCtx>& ctx, int co // After closing sockets with that peer, we try to re-connect to // that peer one time. if (ctx->retryOnTimeout) - messageEngine_.onPeerOnline(ctx->to, false); + messageEngine_.onPeerOnline(ctx->to, false, ctx->deviceId.toString()); } } @@ -3344,7 +3407,8 @@ JamiAccount::setPushNotificationTopic(const std::string& topic) } bool -JamiAccount::setPushNotificationConfig(const std::map<std::string, std::string>& data) { +JamiAccount::setPushNotificationConfig(const std::map<std::string, std::string>& data) +{ if (SIPAccountBase::setPushNotificationConfig(data)) { if (dht_) { dht_->setPushNotificationPlatform(config_->platform); @@ -3356,7 +3420,6 @@ JamiAccount::setPushNotificationConfig(const std::map<std::string, std::string>& return false; } - /** * To be called by clients with relevant data when a push notification is received. */ @@ -3471,16 +3534,16 @@ JamiAccount::sendInstantMessage(const std::string& convId, const std::map<std::string, std::string>& msg) { auto members = convModule()->getConversationMembers(convId); - if (members.empty()) { + if (convId.empty() && members.empty()) { // TODO remove, it's for old API for contacts - sendTextMessage(convId, msg); + sendTextMessage(convId, "", msg); return; } for (const auto& m : members) { const auto& uri = m.at("uri"); auto token = std::uniform_int_distribution<uint64_t> {1, JAMI_ID_MAX_VAL}(rand); // Announce to all members that a new message is sent - sendMessage(uri, msg, token, false, true); + sendMessage(uri, "", msg, token, false, true); } } @@ -3497,13 +3560,16 @@ JamiAccount::handleMessage(const std::string& from, const std::pair<std::string, return false; } - JAMI_WARN("Received indication for new commit available in conversation %s", - json["id"].asString().c_str()); - - convModule()->onNewCommit(from, - json["deviceId"].asString(), - json["id"].asString(), - json["commit"].asString()); + std::string deviceId = json["deviceId"].asString(); + std::string id = json["id"].asString(); + std::string commit = json["commit"].asString(); + // onNewCommit will do heavy stuff like fetching, avoid to block SIP socket + dht::ThreadPool::io().run([w = weak(), from, deviceId, id, commit] { + if (auto shared = w.lock()) { + if (auto cm = shared->convModule()) + cm->onNewCommit(from, deviceId, id, commit); + } + }); return true; } else if (m.first == MIME_TYPE_INVITE) { convModule()->onNeedConversationRequest(from, m.second); @@ -3682,7 +3748,9 @@ JamiAccount::requestSIPConnection(const std::string& peerId, } void -JamiAccount::sendProfile(const std::string& convId, const std::string& peerUri, const std::string& deviceId) +JamiAccount::sendProfile(const std::string& convId, + const std::string& peerUri, + const std::string& deviceId) { if (not fileutils::isFile(profilePath())) return; @@ -3709,7 +3777,9 @@ JamiAccount::sendProfile(const std::string& convId, const std::string& peerUri, } bool -JamiAccount::needToSendProfile(const std::string& peerUri, const std::string& deviceId, const std::string& sha3Sum) +JamiAccount::needToSendProfile(const std::string& peerUri, + const std::string& deviceId, + const std::string& sha3Sum) { std::string previousSha3 {}; auto vCardPath = fmt::format("{}/vcard", cachePath_); @@ -3878,12 +3948,15 @@ JamiAccount::cacheSIPConnection(std::shared_ptr<ChannelSocket>&& socket, // Store the connection connections.emplace_back(SipConnection {sip_tr, socket}); JAMI_WARNING("[Account {:s}] New SIP channel opened with {:s}", - getAccountID(), deviceId.to_c_str()); + getAccountID(), + deviceId.to_c_str()); lk.unlock(); convModule()->syncConversations(peerId, deviceId.toString()); // Retry messages + messageEngine_.onPeerOnline(peerId); + messageEngine_.onPeerOnline(peerId, true, deviceId.toString()); // Connect pending calls forEachPendingCall(deviceId, [&](const auto& pc) { @@ -3946,14 +4019,15 @@ JamiAccount::dataTransfer(const std::string& id) } void -JamiAccount::monitor() const +JamiAccount::monitor() { JAMI_DEBUG("[Account {:s}] Monitor connections", getAccountID()); JAMI_DEBUG("[Account {:s}] Using proxy: {:s}", getAccountID(), proxyServerCached_); - std::lock_guard<std::mutex> lkCM(connManagerMtx_); + convModule()->monitor(); + /*std::lock_guard<std::mutex> lkCM(connManagerMtx_); if (connectionManager_) - connectionManager_->monitor(); + connectionManager_->monitor();*/ } void @@ -4032,27 +4106,40 @@ JamiAccount::transferFile(const std::string& conversationId, std::lock_guard<std::mutex> lkCM(connManagerMtx_); if (!connectionManager_) return; - connectionManager_->connectDevice( - DeviceId(deviceId), - channelName, - [this, conversationId, path = std::move(path), fileId, interactionId, start, end, onFinished = std::move(onFinished)]( - std::shared_ptr<ChannelSocket> socket, const DeviceId&) { - if (!socket) - return; - dht::ThreadPool::io().run([w = weak(), - path = std::move(path), - socket = std::move(socket), - conversationId = std::move(conversationId), - fileId, - interactionId, - start, - end, - onFinished = std::move(onFinished)] { - if (auto shared = w.lock()) - if (auto dt = shared->dataTransfer(conversationId)) - dt->transferFile(socket, fileId, interactionId, path, start, end, std::move(onFinished)); - }); - }); + connectionManager_ + ->connectDevice(DeviceId(deviceId), + channelName, + [this, + conversationId, + path = std::move(path), + fileId, + interactionId, + start, + end, + onFinished = std::move(onFinished)](std::shared_ptr<ChannelSocket> socket, + const DeviceId&) { + if (!socket) + return; + dht::ThreadPool::io().run([w = weak(), + path = std::move(path), + socket = std::move(socket), + conversationId = std::move(conversationId), + fileId, + interactionId, + start, + end, + onFinished = std::move(onFinished)] { + if (auto shared = w.lock()) + if (auto dt = shared->dataTransfer(conversationId)) + dt->transferFile(socket, + fileId, + interactionId, + path, + start, + end, + std::move(onFinished)); + }); + }); } void diff --git a/src/jamidht/jamiaccount.h b/src/jamidht/jamiaccount.h index a2b62d3656b6ba09c297b934218a4204e4342131..021697ebbc544ca524d358d858f1c155d08d57a7 100644 --- a/src/jamidht/jamiaccount.h +++ b/src/jamidht/jamiaccount.h @@ -311,11 +311,14 @@ public: void sendTrustRequest(const std::string& to, const std::vector<uint8_t>& payload); void sendMessage(const std::string& to, + const std::string& deviceId, const std::map<std::string, std::string>& payloads, uint64_t id, bool retryOnTimeout = true, bool onlyConnected = false) override; + uint64_t sendTextMessage(const std::string& to, + const std::string& deviceId, const std::map<std::string, std::string>& payloads, uint64_t refreshToken = 0, bool onlyConnected = false) override; @@ -460,7 +463,7 @@ public: bool handleMessage(const std::string& from, const std::pair<std::string, std::string>& message) override; - void monitor() const; + void monitor(); // File transfer void sendFile(const std::string& conversationId, @@ -511,14 +514,18 @@ public: * @param sha3Sum SHA3 hash of the profile */ // Note: when swarm will be merged, this can be moved in transferManager - bool needToSendProfile(const std::string& peerUri, const std::string& deviceId, const std::string& sha3Sum); + bool needToSendProfile(const std::string& peerUri, + const std::string& deviceId, + const std::string& sha3Sum); /** * Send Profile via cached SIP connection * @param convId Conversation's identifier (can be empty for self profile on sync) * @param peerUri Uri that will receive the profile * @param deviceId Device that will receive the profile */ - void sendProfile(const std::string& convId, const std::string& peerUri, const std::string& deviceId); + void sendProfile(const std::string& convId, + const std::string& peerUri, + const std::string& deviceId); /** * Send profile via cached SIP connection * @param peerUri Uri that will receive the profile @@ -572,14 +579,22 @@ public: void handleIncomingConversationCall(const std::string& callId, const std::string& destination); /** - * The DRT component is composed on some special nodes, that are usually present but not connected. - * This kind of node corresponds to devices with push notifications & proxy and are + * The DRT component is composed on some special nodes, that are usually present but not + * connected. This kind of node corresponds to devices with push notifications & proxy and are * stored in the mobile nodes */ - bool isMobile() const { + bool isMobile() const + { return config().proxyEnabled and not config().deviceKey.empty(); } +#ifdef LIBJAMI_TESTABLE + std::map<Uri::Scheme, std::unique_ptr<ChannelHandlerInterface>>& channelHandlers() + { + return channelHandlers_; + }; +#endif + private: NON_COPYABLE(JamiAccount); diff --git a/src/jamidht/swarm/routing_table.cpp b/src/jamidht/swarm/routing_table.cpp index c3dc468511bcb276c92b1d54e881a9cdb42b486f..7f88bd0e368cebff95df19daf2c10ec5b0b73406 100644 --- a/src/jamidht/swarm/routing_table.cpp +++ b/src/jamidht/swarm/routing_table.cpp @@ -494,6 +494,35 @@ RoutingTable::contains(const std::list<Bucket>::iterator& bucket, const NodeId& || NodeId::cmp(nodeId, std::next(bucket)->getLowerLimit()) < 0); } +std::vector<NodeId> +RoutingTable::getAllNodes() const +{ + std::vector<NodeId> ret; + for (const auto& b : buckets) { + const auto& nodes = b.getNodeIds(); + const auto& knownNodes = b.getKnownNodes(); + const auto& mobileNodes = b.getMobileNodes(); + const auto& connectingNodes = b.getConnectingNodes(); + + ret.insert(ret.end(), nodes.begin(), nodes.end()); + ret.insert(ret.end(), knownNodes.begin(), knownNodes.end()); + ret.insert(ret.end(), mobileNodes.begin(), mobileNodes.end()); + ret.insert(ret.end(), connectingNodes.begin(), connectingNodes.end()); + } + return ret; +} + +void +RoutingTable::deleteNode(const NodeId& nodeId) +{ + auto bucket = findBucket(nodeId); + shutdownNode(nodeId); + bucket->removeNode(nodeId); + bucket->removeConnectingNode(nodeId); + bucket->removeKnownNode(nodeId); + bucket->removeMobileNode(nodeId); +} + NodeId RoutingTable::middle(std::list<Bucket>::iterator& it) const { diff --git a/src/jamidht/swarm/routing_table.h b/src/jamidht/swarm/routing_table.h index c20d5a9cb54417de9ee40ca5545f9e780ddc0018..3265b050d46b6c93d2f95589bcb6ab388787898c 100644 --- a/src/jamidht/swarm/routing_table.h +++ b/src/jamidht/swarm/routing_table.h @@ -60,7 +60,7 @@ class Bucket { public: - static constexpr int BUCKET_MAX_SIZE = 4; + static constexpr int BUCKET_MAX_SIZE = 2; Bucket() = delete; Bucket(const Bucket&) = delete; @@ -525,6 +525,16 @@ public: */ bool contains(const std::list<Bucket>::iterator& it, const NodeId& nodeId) const; + /** + * Return every node from each bucket + */ + std::vector<NodeId> getAllNodes() const; + + /** + * Delete node from every table in bucket + */ + void deleteNode(const NodeId& nodeId); + private: RoutingTable(const RoutingTable&) = delete; RoutingTable& operator=(const RoutingTable&) = delete; diff --git a/src/jamidht/swarm/swarm_manager.cpp b/src/jamidht/swarm/swarm_manager.cpp index c6d52a5954d771bfb6bcff646d8e997b881e3169..e0e56d05d258917e1496bb1e8aa3df9c7841ee1b 100644 --- a/src/jamidht/swarm/swarm_manager.cpp +++ b/src/jamidht/swarm/swarm_manager.cpp @@ -1,5 +1,6 @@ /* * Copyright (C) 2023 Savoir-faire Linux Inc. + * * Author: Fadi Shehadeh <fadi.shehadeh@savoirfairelinux.com> * * This program is free software; you can redistribute it and/or modify @@ -13,11 +14,10 @@ * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License - * along with this program. If not, see <https://www.gnu.org/licenses/>. + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include <iostream> - #include "swarm_manager.h" #include "connectivity/multiplexed_socket.h" #include <opendht/thread_pool.h> @@ -37,48 +37,91 @@ SwarmManager::SwarmManager(const NodeId& id) SwarmManager::~SwarmManager() { - shutdown(); + if (!isShutdown_) + shutdown(); } void -SwarmManager::removeNode(const NodeId& nodeId) +SwarmManager::setKnownNodes(const std::vector<NodeId>& known_nodes) { + isShutdown_ = false; { std::lock_guard<std::mutex> lock(mutex); - removeNodeInternal(nodeId); + for (const auto& NodeId : known_nodes) + addKnownNodes(std::move(NodeId)); } maintainBuckets(); } void -SwarmManager::removeNodeInternal(const NodeId& nodeId) +SwarmManager::setMobileNodes(const std::vector<NodeId>& mobile_nodes) { - routing_table.removeNode(nodeId); + { + std::lock_guard<std::mutex> lock(mutex); + for (const auto& nodeId : mobile_nodes) + addMobileNodes(nodeId); + } } void -SwarmManager::setKnownNodes(const std::vector<NodeId>& known_nodes) +SwarmManager::addChannel(const std::shared_ptr<ChannelSocketInterface>& channel) { - isShutdown_ = false; - { - std::lock_guard<std::mutex> lock(mutex); - for (const auto& NodeId : known_nodes) - addKnownNodes(std::move(NodeId)); + // JAMI_WARNING("[SwarmManager {}] addChannel! with {}", fmt::ptr(this), channel->deviceId().to_view()); + if (channel) { + auto emit = false; + { + std::lock_guard<std::mutex> lock(mutex); + emit = routing_table.findBucket(getId())->getNodeIds().size() == 0; + auto bucket = routing_table.findBucket(channel->deviceId()); + if (routing_table.addNode(channel, bucket)) { + std::error_code ec; + resetNodeExpiry(ec, channel, id_); + } + } + receiveMessage(channel); + if (emit && onConnectionChanged_) { + // If it's the first channel we add, we're now connected! + JAMI_DEBUG("[SwarmManager {}] Bootstrap: Connected!", fmt::ptr(this)); + onConnectionChanged_(true); + } } - maintainBuckets(); } void -SwarmManager::setMobileNodes(const std::vector<NodeId>& mobile_nodes) +SwarmManager::removeNode(const NodeId& nodeId) { { std::lock_guard<std::mutex> lock(mutex); - for (const auto& nodeId : mobile_nodes) - addMobileNodes(nodeId); + removeNodeInternal(nodeId); } maintainBuckets(); } +void +SwarmManager::changeMobility(const NodeId& nodeId, bool isMobile) +{ + std::lock_guard<std::mutex> lock(mutex); + auto bucket = routing_table.findBucket(nodeId); + bucket->changeMobility(nodeId, isMobile); +} + +bool +SwarmManager::isConnectedWith(const NodeId& deviceId) +{ + return routing_table.hasNode(deviceId); +} + +void +SwarmManager::shutdown() +{ + if (isShutdown_) { + return; + } + isShutdown_ = true; + std::lock_guard<std::mutex> lock(mutex); + routing_table.shutdownAllNodes(); +} + void SwarmManager::addKnownNodes(const NodeId& nodeId) { @@ -95,6 +138,31 @@ SwarmManager::addMobileNodes(const NodeId& nodeId) } } +void +SwarmManager::maintainBuckets() +{ + std::set<NodeId> nodes; + std::unique_lock<std::mutex> lock(mutex); + auto& buckets = routing_table.getBuckets(); + for (auto it = buckets.begin(); it != buckets.end(); ++it) { + auto& bucket = *it; + bool myBucket = routing_table.contains(it, id_); + auto connecting_nodes = myBucket ? bucket.getConnectingNodesSize() + : bucket.getConnectingNodesSize() + bucket.getNodesSize(); + if (connecting_nodes < Bucket::BUCKET_MAX_SIZE) { + auto nodesToTry = bucket.getKnownNodesRandom(Bucket::BUCKET_MAX_SIZE - connecting_nodes, + rd); + for (auto& node : nodesToTry) + routing_table.addConnectingNode(node); + + nodes.insert(nodesToTry.begin(), nodesToTry.end()); + } + } + lock.unlock(); + for (auto& node : nodes) + tryConnect(node); +} + void SwarmManager::sendRequest(const std::shared_ptr<ChannelSocketInterface>& socket, NodeId& nodeId, @@ -135,17 +203,21 @@ SwarmManager::sendAnswer(const std::shared_ptr<ChannelSocketInterface>& socket, msg.is_mobile = isMobile_; msg.response = std::move(toResponse); - msgpack::sbuffer buffer((size_t) 1024); + msgpack::sbuffer buffer((size_t) 60000); msgpack::packer<msgpack::sbuffer> pk(&buffer); pk.pack(msg); std::error_code ec; + socket->write(reinterpret_cast<const unsigned char*>(buffer.data()), buffer.size(), ec); if (ec) { JAMI_ERROR("{}", ec.message()); return; } } + + else { + } } void @@ -170,7 +242,7 @@ SwarmManager::receiveMessage(const std::shared_ptr<ChannelSocketInterface>& sock auto shared = w.lock(); auto socket = wsocket.lock(); if (!shared || !socket) - return 0lu; + return size_t {0}; try { Message msg; @@ -179,9 +251,10 @@ SwarmManager::receiveMessage(const std::shared_ptr<ChannelSocketInterface>& sock if (msg.is_mobile) shared->changeMobility(socket->deviceId(), msg.is_mobile); - if (msg.request) + if (msg.request) { shared->sendAnswer(socket, msg); - else if (msg.response) { + + } else if (msg.response) { shared->setKnownNodes(msg.response->nodes); shared->setMobileNodes(msg.response->mobile_nodes); } @@ -204,33 +277,35 @@ SwarmManager::receiveMessage(const std::shared_ptr<ChannelSocketInterface>& sock } void -SwarmManager::maintainBuckets() +SwarmManager::resetNodeExpiry(const asio::error_code& ec, + const std::shared_ptr<ChannelSocketInterface>& socket, + NodeId node) { - std::set<NodeId> nodes; - std::unique_lock<std::mutex> lock(mutex); - auto& buckets = routing_table.getBuckets(); - for (auto it = buckets.begin(); it != buckets.end(); ++it) { - auto& bucket = *it; - bool myBucket = routing_table.contains(it, id_); - auto connecting_nodes = myBucket ? bucket.getConnectingNodesSize() - : bucket.getConnectingNodesSize() + bucket.getNodesSize(); - if (connecting_nodes < Bucket::BUCKET_MAX_SIZE) { - auto nodesToTry = bucket.getKnownNodesRandom(Bucket::BUCKET_MAX_SIZE - connecting_nodes, - rd); - for (auto& node : nodesToTry) - routing_table.addConnectingNode(node); + NodeId idToFind; + std::list<Bucket>::iterator bucket; - nodes.insert(nodesToTry.begin(), nodesToTry.end()); - } + if (ec == asio::error::operation_aborted) + return; + + if (!node) { + bucket = routing_table.findBucket(socket->deviceId()); + idToFind = bucket->randomId(rd); + } else { + bucket = routing_table.findBucket(node); + idToFind = node; + } + + sendRequest(socket, idToFind, Query::FIND, Bucket::BUCKET_MAX_SIZE); + + if (!node) { + auto& nodeTimer = bucket->getNodeTimer(socket); + nodeTimer.expires_after(FIND_PERIOD); + nodeTimer.async_wait(std::bind(&jami::SwarmManager::resetNodeExpiry, + shared_from_this(), + std::placeholders::_1, + socket, + NodeId {})); } - lock.unlock(); - for (auto& node : nodes) - tryConnect(node); -} -bool -SwarmManager::hasChannel(const NodeId& deviceId) -{ - return routing_table.hasNode(deviceId); } void @@ -263,71 +338,32 @@ SwarmManager::tryConnect(const NodeId& nodeId) } void -SwarmManager::addChannel(std::shared_ptr<ChannelSocketInterface> channel) +SwarmManager::removeNodeInternal(const NodeId& nodeId) { - auto emit = false; - { - std::lock_guard<std::mutex> lock(mutex); - emit = routing_table.findBucket(getId())->getNodeIds().size() == 0; - auto bucket = routing_table.findBucket(channel->deviceId()); - if (routing_table.addNode(channel, bucket)) { - std::error_code ec; - resetNodeExpiry(ec, channel, id_); - } - } - receiveMessage(channel); - if (emit && onConnectionChanged_) { - // If it's the first channel we add, we're now connected! - JAMI_DEBUG("[SwarmManager {}] Bootstrap: Connected!", fmt::ptr(this)); - onConnectionChanged_(true); - } + routing_table.removeNode(nodeId); } -void -SwarmManager::resetNodeExpiry(const asio::error_code& ec, - const std::shared_ptr<ChannelSocketInterface>& socket, - NodeId node) +std::vector<NodeId> +SwarmManager::getAllNodes() const { - NodeId idToFind; - std::list<Bucket>::iterator bucket; - - if (ec == asio::error::operation_aborted) - return; - - if (!node) { - bucket = routing_table.findBucket(socket->deviceId()); - idToFind = bucket->randomId(rd); - } else { - bucket = routing_table.findBucket(node); - idToFind = node; - } - - sendRequest(socket, idToFind, Query::FIND, Bucket::BUCKET_MAX_SIZE); + std::lock_guard<std::mutex> lock(mutex); + std::vector<NodeId> nodes; + const auto& rtNodes = routing_table.getAllNodes(); + nodes.insert(nodes.end(), rtNodes.begin(), rtNodes.end()); - if (!node) { - auto& nodeTimer = bucket->getNodeTimer(socket); - nodeTimer.expires_after(FIND_PERIOD); - nodeTimer.async_wait(std::bind(&jami::SwarmManager::resetNodeExpiry, - shared_from_this(), - std::placeholders::_1, - socket, - NodeId {})); - } + return nodes; } void -SwarmManager::changeMobility(const NodeId& nodeId, bool isMobile) +SwarmManager::deleteNode(std::vector<NodeId> nodes) { - std::lock_guard<std::mutex> lock(mutex); - auto bucket = routing_table.findBucket(nodeId); - bucket->changeMobility(nodeId, isMobile); + { + std::lock_guard<std::mutex> lock(mutex); + for (const auto& node : nodes) { + routing_table.deleteNode(node); + } + } + maintainBuckets(); } -void -SwarmManager::shutdown() -{ - isShutdown_ = true; - std::lock_guard<std::mutex> lock(mutex); - routing_table.shutdownAllNodes(); -} } // namespace jami diff --git a/src/jamidht/swarm/swarm_manager.h b/src/jamidht/swarm/swarm_manager.h index df1d247226a0b0f209e7bc2a55cf4df9391a5563..50f58cc74c209547492771eb9e76de290ec90264 100644 --- a/src/jamidht/swarm/swarm_manager.h +++ b/src/jamidht/swarm/swarm_manager.h @@ -17,6 +17,7 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ + #pragma once #include "routing_table.h" @@ -41,52 +42,107 @@ public: NeedSocketCb needSocketCb_; - std::weak_ptr<SwarmManager> weak() - { - return std::static_pointer_cast<SwarmManager>(shared_from_this()); - } + std::weak_ptr<SwarmManager> weak() { return weak_from_this(); } + /** + * Get swarm manager id + * @return NodeId + */ const NodeId& getId() const { return id_; } /** - * Add list of nodes to the known nodes list - * @param vector<NodeId>& known_nodes + * Set list of nodes to the routing table known_nodes + * @param known_nodes */ void setKnownNodes(const std::vector<NodeId>& known_nodes); /** - * Add list of nodes to the mobile nodes list - * @param vector<NodeId>& mobile_nodes + * Set list of nodes to the routing table mobile_nodes + * @param mobile_nodes */ void setMobileNodes(const std::vector<NodeId>& mobile_nodes); /** * Add channel to routing table - * @param shared_ptr<ChannelSocketInterface>& channel + * @param channel */ - void addChannel(std::shared_ptr<ChannelSocketInterface> channel); + void addChannel(const std::shared_ptr<ChannelSocketInterface>& channel); + /** + * Remove channel from routing table + * @param channel + */ void removeNode(const NodeId& nodeId); + + /** + * Change mobility of specific node + * @param nodeId + * @param isMobile + */ void changeMobility(const NodeId& nodeId, bool isMobile); - /** For testing */ + /** + * get all nodes from the different tables in bucket + */ + std::vector<NodeId> getAllNodes() const; + + /** + * Delete nodes from the different tables in bucket + */ + void deleteNode(std::vector<NodeId> nodes); + + // For tests + + /** + * Get routing table + * @return RoutingTable + */ RoutingTable& getRoutingTable() { return routing_table; }; + + /** + * Get buckets of routing table + * @return buckets list + */ std::list<Bucket>& getBuckets() { return routing_table.getBuckets(); }; + /** + * Shutdown swarm manager + */ void shutdown(); + /** + * Display swarm manager info + */ void display() { JAMI_DEBUG("SwarmManager {:s} has {:d} nodes in table [P = {}]", getId().to_c_str(), routing_table.getRoutingTableNodeCount(), isMobile_); + // print nodes of routingtable + for (auto& bucket : routing_table.getBuckets()) { + for (auto& node : bucket.getNodes()) { + JAMI_DEBUG("Node {:s}", node.first.toString()); + } + } } + /* + * Callback for connection changed + * @param cb + */ void onConnectionChanged(OnConnectionChanged cb) { onConnectionChanged_ = std::move(cb); } + /** + * Set mobility of swarm manager + * @param isMobile + */ void setMobility(bool isMobile) { isMobile_ = isMobile; } + /** + * Get mobility of swarm manager + * @return true if mobile, false if not + */ bool isMobile() const { return isMobile_; } /** @@ -94,27 +150,38 @@ public: */ void maintainBuckets(); - bool hasChannel(const NodeId& deviceId); + /** + * Check if we're connected with a specific device + * @param deviceId + * @return true if connected, false if not + */ + bool isConnectedWith(const NodeId& deviceId); + + /** + * Check if swarm manager is shutdown + * @return true if shutdown, false if not + */ + bool isShutdown() { return isShutdown_; }; private: /** * Add node to the known_nodes list - * @param NodeId nodeId + * @param nodeId */ void addKnownNodes(const NodeId& nodeId); /** * Add node to the mobile_Nodes list - * @param NodeId nodeId + * @param nodeId */ void addMobileNodes(const NodeId& nodeId); /** * Send nodes request to fill known_nodes list - * @param shared_ptr<ChannelSocketInterface>& socket - * @param NodeId& nodeId - * @param Query q - * @param int numberNodes + * @param socket + * @param nodeId + * @param q + * @param numberNodes */ void sendRequest(const std::shared_ptr<ChannelSocketInterface>& socket, NodeId& nodeId, @@ -123,22 +190,22 @@ private: /** * Send answer to request - * @param std::shared_ptr<ChannelSocketInterface>& socket - * @param Message msg + * @param socket + * @param msg */ void sendAnswer(const std::shared_ptr<ChannelSocketInterface>& socket, const Message& msg_); /** * Interpret received message - * @param std::shared_ptr<ChannelSocketInterface>& socket + * @param socket */ void receiveMessage(const std::shared_ptr<ChannelSocketInterface>& socket); /** - * Add list of nodes to the known nodes list - * @param asio::error_code& ec - * @param shared_ptr<ChannelSocketInterface>& socket - * @param NodeId node + * Reset node's timer expiry + * @param ec + * @param socket + * @param node */ void resetNodeExpiry(const asio::error_code& ec, const std::shared_ptr<ChannelSocketInterface>& socket, @@ -146,10 +213,14 @@ private: /** * Try to establich connexion with specific node - * @param NodeId nodeId + * @param nodeId */ void tryConnect(const NodeId& nodeId); + /** + * Remove node from routing table + * @param nodeId + */ void removeNodeInternal(const NodeId& nodeId); const NodeId id_; diff --git a/src/manager.cpp b/src/manager.cpp index cf9d3efaf2436d9fc513385784a6ae2277ee6ff5..279458fccdd094b8a34fc502acbfaeefe52858d5 100644 --- a/src/manager.cpp +++ b/src/manager.cpp @@ -679,12 +679,7 @@ Manager::ManagerPimpl::bindCallToConference(Call& call, Conference& conf) if (call.isConferenceParticipant()) base_.detachParticipant(callId); - - - JAMI_DEBUG("[call:{}] bind to conference {} (callState={})", - callId, - confId, - state); + JAMI_DEBUG("[call:{}] bind to conference {} (callState={})", callId, confId, state); base_.getRingBufferPool().unBindAll(callId); @@ -702,9 +697,7 @@ Manager::ManagerPimpl::bindCallToConference(Call& call, Conference& conf) conf.bindParticipant(callId); base_.answerCall(call); } else - JAMI_WARNING("[call:{}] call state {} not recognized for conference", - callId, - state); + JAMI_WARNING("[call:{}] call state {} not recognized for conference", callId, state); } //============================================================================== @@ -2918,10 +2911,10 @@ Manager::sendTextMessage(const std::string& accountID, if (pluginChatManager.hasHandlers()) { auto cm = std::make_shared<JamiMessage>(accountID, to, false, payloads, fromPlugin); pluginChatManager.publishMessage(cm); - return acc->sendTextMessage(cm->peerId, cm->data, 0, onlyConnected); + return acc->sendTextMessage(cm->peerId, "", cm->data, 0, onlyConnected); } else #endif // ENABLE_PLUGIN - return acc->sendTextMessage(to, payloads, 0, onlyConnected); + return acc->sendTextMessage(to, "", payloads, 0, onlyConnected); } catch (const std::exception& e) { JAMI_ERR("Exception during text message sending: %s", e.what()); } @@ -3051,10 +3044,9 @@ Manager::createSinkClients( } if (participant.w && participant.h && !participant.videoMuted) { auto currentSink = getSinkClient(sinkId); - if (!accountId.empty() && - currentSink && - string_remove_suffix(participant.uri, '@') == getAccount(accountId)->getUsername() && - participant.device == getAccount<JamiAccount>(accountId)->currentDeviceId()) { + if (!accountId.empty() && currentSink + && string_remove_suffix(participant.uri, '@') == getAccount(accountId)->getUsername() + && participant.device == getAccount<JamiAccount>(accountId)->currentDeviceId()) { // This is a local sink that must already exist continue; } diff --git a/src/manager.h b/src/manager.h index e75ac960bae6f9b2d9917355ec9f9c392e285238..735273be6b8e49a460ab90cedc2a3ae004298a84 100644 --- a/src/manager.h +++ b/src/manager.h @@ -818,11 +818,12 @@ public: * @param videoStream the the VideoFrameActiveWriter to which the sinks should be attached * @param sinksMap A map between sink ids and the respective shared pointer. */ - void createSinkClients(const std::string& callId, - const ConfInfo& infos, - const std::vector<std::shared_ptr<video::VideoFrameActiveWriter>>& videoStreams, - std::map<std::string, std::shared_ptr<video::SinkClient>>& sinksMap, - const std::string& accountId = ""); + void createSinkClients( + const std::string& callId, + const ConfInfo& infos, + const std::vector<std::shared_ptr<video::VideoFrameActiveWriter>>& videoStreams, + std::map<std::string, std::shared_ptr<video::SinkClient>>& sinksMap, + const std::string& accountId = ""); /** * Return an existing SinkClient instance as a shared_ptr associated to the given identifier. @@ -839,7 +840,7 @@ public: AccountFactory accountFactory; std::vector<libjami::Message> getLastMessages(const std::string& accountID, - const uint64_t& base_timestamp); + const uint64_t& base_timestamp); SIPVoIPLink& sipVoIPLink() const; #ifdef ENABLE_PLUGIN diff --git a/src/meson.build b/src/meson.build index 8cee032ff124d16be62e66759460e9dccebd7476..a1e503aeba8724d3818928d2034066941ee9137b 100644 --- a/src/meson.build +++ b/src/meson.build @@ -55,6 +55,10 @@ libjami_sources = files( 'jamidht/sync_channel_handler.cpp', 'jamidht/sync_module.cpp', 'jamidht/transfer_channel_handler.cpp', + 'jamidht/swarm/routing_table.cpp', + 'jamidht/swarm/swarm_manager.cpp', + 'jamidht/swarm/swarm_protocol.cpp', + 'jamidht/swarm/swarm_channel_handler.cpp', 'media/audio/audio-processing/null_audio_processor.cpp', 'media/audio/sound/audiofile.cpp', 'media/audio/sound/dtmf.cpp', diff --git a/src/sip/sipaccount.cpp b/src/sip/sipaccount.cpp index 38287eaf09602e5895fc8150e46c2aa610c73c04..08c005b892dd82703275ce67648caa9b6845966f 100644 --- a/src/sip/sipaccount.cpp +++ b/src/sip/sipaccount.cpp @@ -268,8 +268,8 @@ SIPAccount::onTransportStateChanged(pjsip_transport_state state, { pj_status_t currentStatus = transportStatus_; JAMI_DEBUG("Transport state changed to {:s} for account {:s}!", - SipTransport::stateToStr(state), - accountID_); + SipTransport::stateToStr(state), + accountID_); if (!SipTransport::isAlive(state)) { if (info) { transportStatus_ = info->status; @@ -301,7 +301,8 @@ SIPAccount::setTransport(const std::shared_ptr<SipTransport>& t) return; if (transport_) { JAMI_DEBUG("Removing old transport [{}] from account", fmt::ptr(transport_.get())); - // NOTE: do not call destroyRegistrationInfo() there as we must call the registration callback if needed + // NOTE: do not call destroyRegistrationInfo() there as we must call the registration + // callback if needed if (regc_) pjsip_regc_release_transport(regc_); transport_->removeStateListener(reinterpret_cast<uintptr_t>(this)); @@ -482,9 +483,10 @@ SIPAccount::mapPortUPnP() or mapRes->getState() == upnp::MappingState::IN_PROGRESS; auto newPort = success ? mapRes->getExternalPort() : accPtr->config().publishedPort; if (not success and not accPtr->isRegistered()) { - JAMI_WARNING("[Account {:s}] Failed to open port {}: registering SIP account anyway", - accPtr->getAccountID(), - oldPort); + JAMI_WARNING( + "[Account {:s}] Failed to open port {}: registering SIP account anyway", + accPtr->getAccountID(), + oldPort); accPtr->doRegister1_(); return; } @@ -492,12 +494,13 @@ SIPAccount::mapPortUPnP() or (accPtr->getRegistrationState() != RegistrationState::REGISTERED)) { if (not accPtr->isRegistered()) JAMI_WARNING("[Account {:s}] SIP port {} opened: registering SIP account", - accPtr->getAccountID(), - newPort); + accPtr->getAccountID(), + newPort); else - JAMI_WARNING("[Account {:s}] SIP port changed to {}: re-registering SIP account", - accPtr->getAccountID(), - newPort); + JAMI_WARNING( + "[Account {:s}] SIP port changed to {}: re-registering SIP account", + accPtr->getAccountID(), + newPort); accPtr->publishedPortUsed_ = newPort; } else { accPtr->connectivityChanged(); @@ -528,7 +531,8 @@ SIPAccount::setPushNotificationToken(const std::string& pushDeviceToken) } bool -SIPAccount::setPushNotificationConfig(const std::map<std::string, std::string>& data) { +SIPAccount::setPushNotificationConfig(const std::map<std::string, std::string>& data) +{ if (SIPAccountBase::setPushNotificationConfig(data)) { if (config().enabled) doUnregister([&](bool /* transport_free */) { doRegister(); }); @@ -1795,6 +1799,7 @@ static pjsip_accept_hdr* im_create_accept(pj_pool_t *pool) void SIPAccount::sendMessage(const std::string& to, + const std::string&, const std::map<std::string, std::string>& payloads, uint64_t id, bool, diff --git a/src/sip/sipaccount.h b/src/sip/sipaccount.h index 4caf32fb181625a11e168f1745f5743f83ed245f..a29f7db724a5801667c303c87e901143fb19d84e 100644 --- a/src/sip/sipaccount.h +++ b/src/sip/sipaccount.h @@ -81,17 +81,19 @@ public: ~SIPAccount() noexcept; - const SipAccountConfig& config() const { + const SipAccountConfig& config() const + { return *static_cast<const SipAccountConfig*>(&Account::config()); } - std::unique_ptr<AccountConfig> buildConfig() const override { + std::unique_ptr<AccountConfig> buildConfig() const override + { return std::make_unique<SipAccountConfig>(getAccountID()); } - inline void editConfig(std::function<void(SipAccountConfig& conf)>&& edit) { - Account::editConfig([&](AccountConfig& conf) { - edit(*static_cast<SipAccountConfig*>(&conf)); - }); + inline void editConfig(std::function<void(SipAccountConfig& conf)>&& edit) + { + Account::editConfig( + [&](AccountConfig& conf) { edit(*static_cast<SipAccountConfig*>(&conf)); }); } std::string_view getAccountType() const override { return ACCOUNT_TYPE; } @@ -396,6 +398,7 @@ public: void onRegister(pjsip_regc_cbparam* param); virtual void sendMessage(const std::string& to, + const std::string& deviceId, const std::map<std::string, std::string>& payloads, uint64_t id, bool retryOnTimeout = true, @@ -412,7 +415,10 @@ public: IpAddr createBindingAddress(); void setActiveCodecs(const std::vector<unsigned>& list) override; - bool isSrtpEnabled() const override { return config().srtpKeyExchange != KeyExchangeProtocol::NONE; } + bool isSrtpEnabled() const override + { + return config().srtpKeyExchange != KeyExchangeProtocol::NONE; + } bool setPushNotificationToken(const std::string& pushDeviceToken = "") override; bool setPushNotificationConfig(const std::map<std::string, std::string>& data) override; diff --git a/src/sip/sipaccountbase.h b/src/sip/sipaccountbase.h index d05de5d886189ce5545a0c88c170f2f1ba41de95..92acd001492fd75e9dbb0c8dc4b0e5c7c374a1be 100644 --- a/src/sip/sipaccountbase.h +++ b/src/sip/sipaccountbase.h @@ -175,6 +175,7 @@ public: IceTransportOptions getIceOptions() const noexcept; virtual void sendMessage(const std::string& to, + const std::string& deviceId, const std::map<std::string, std::string>& payloads, uint64_t id, bool retryOnTimeout = true, @@ -182,16 +183,17 @@ public: = 0; virtual uint64_t sendTextMessage(const std::string& to, + const std::string& deviceId, const std::map<std::string, std::string>& payloads, uint64_t refreshToken = 0, bool onlyConnected = false) override { if (onlyConnected) { auto token = std::uniform_int_distribution<uint64_t> {1, JAMI_ID_MAX_VAL}(rand); - sendMessage(to, payloads, token, false, true); + sendMessage(to, deviceId, payloads, token, false, true); return token; } - return messageEngine_.sendMessage(to, payloads, refreshToken); + return messageEngine_.sendMessage(to, deviceId, payloads, refreshToken); } im::MessageStatus getMessageStatus(uint64_t id) const override diff --git a/test/unitTest/Makefile.am b/test/unitTest/Makefile.am index 03719f78ae3f967d7a062ca8599d6f9f7b9b5b85..03e97dcd6e3cc3dd830a055aeacf70a2339635b5 100644 --- a/test/unitTest/Makefile.am +++ b/test/unitTest/Makefile.am @@ -243,4 +243,22 @@ ut_plugins_SOURCES = plugins/plugins.cpp common.cpp check_PROGRAMS += ut_routing_table ut_routing_table_SOURCES = swarm/routing_table.cpp +# +# Bootstrap +# +check_PROGRAMS += ut_bootstrap +ut_bootstrap_SOURCES = swarm/bootstrap.cpp common.cpp + +# +# SwarmConversation +# +check_PROGRAMS += ut_swarm_conversation +ut_swarm_conversation_SOURCES = swarm/swarm_conversation.cpp common.cpp + +# +# SwarmSpreadTest +# +check_PROGRAMS += ut_swarm_spread +ut_swarm_spread_SOURCES = swarm/swarm_spread.cpp common.cpp + TESTS = $(check_PROGRAMS) diff --git a/test/unitTest/actors/account_list.yml b/test/unitTest/actors/account_list.yml new file mode 100644 index 0000000000000000000000000000000000000000..be6c387f85e50b8bd56e0c6450dc6c0fa968568b --- /dev/null +++ b/test/unitTest/actors/account_list.yml @@ -0,0 +1,112 @@ +default-account: + type: RING + upnpEnabled: "true" + archivePassword: "" + archivePIN: "" + archivePath: "" + +accounts: + + alice: + displayName: ALICE + alias: ALICE + + bob: + displayName: BOB + alias: BOB + + charlie: + displayName: CHARLIE + alias: CHARLIE + + dave: + displayName: DAVE + alias: DAVE + + emily: + displayName: EMILY + alias: EMILY + + frank: + displayName: FRANK + alias: FRANK + + greg: + displayName: GREG + alias: GREG + + holly: + displayName: HOLLY + alias: HOLLY + + ian: + displayName: IAN + alias: IAN + + jenna: + displayName: JENNA + alias: JENNA + + kevin: + displayName: KEVIN + alias: KEVIN + + lucy: + displayName: LUCY + alias: LUCY + + mike: + displayName: MIKE + alias: MIKE + + nora: + displayName: NORA + alias: NORA + + olivia: + displayName: OLIVIA + alias: OLIVIA + + pete: + displayName: PETE + alias: PETE + + quinn: + displayName: QUINN + alias: QUINN + + rachel: + displayName: RACHEL + alias: RACHEL + + sam: + displayName: SAM + alias: SAM + + tom: + displayName: TOM + alias: TOM + + uma: + displayName: UMA + alias: UMA + + victor: + displayName: VICTOR + alias: VICTOR + + wendy: + displayName: WENDY + alias: WENDY + + xander: + displayName: XANDER + alias: XANDER + + yvonne: + displayName: YVONNE + alias: YVONNE + + zoe: + displayName: ZOE + alias: ZOE \ No newline at end of file diff --git a/test/unitTest/conversation/conversation.cpp b/test/unitTest/conversation/conversation.cpp index 4bebdf2d0159ec8cc3ccb91989976c2bd4cdccc8..a3fe35c2d85e0ed787065b2d8639db484b219445 100644 --- a/test/unitTest/conversation/conversation.cpp +++ b/test/unitTest/conversation/conversation.cpp @@ -1409,8 +1409,8 @@ ConversationTest::testSetMessageDisplayedAfterClone() std::unique_lock<std::mutex> lk {mtx}; std::condition_variable cv; std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers; - bool requestReceived = false, memberMessageGenerated = false, - msgDisplayed = false, aliceRegistered = false; + bool requestReceived = false, memberMessageGenerated = false, msgDisplayed = false, + aliceRegistered = false; confHandlers.insert( libjami::exportable_callback<libjami::ConversationSignal::ConversationRequestReceived>( [&](const std::string& /*accountId*/, @@ -1504,9 +1504,6 @@ ConversationTest::testSetMessageDisplayedAfterClone() }) != membersInfos.end()); - - - libjami::unregisterSignalHandlers(); } @@ -2010,7 +2007,7 @@ ConversationTest::testVoteNoBadFile() std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers; bool conversationReady = false, requestReceived = false, memberMessageGenerated = false, voteMessageGenerated = false, messageBobReceived = false, messageCarlaReceived = false, - carlaConnected = true; + carlaConnected = false; ; confHandlers.insert( libjami::exportable_callback<libjami::ConversationSignal::ConversationRequestReceived>( @@ -3535,15 +3532,12 @@ ConversationTest::testConversationPreferencesBeforeClone() aliceAccount->sendTrustRequest(bobUri, {}); CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return requestReceived; })); libjami::acceptConversationRequest(bobId, convId); - CPPUNIT_ASSERT( - cv.wait_for(lk, 30s, [&]() { return conversationReadyBob; })); + CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return conversationReadyBob; })); // Set preferences Manager::instance().sendRegister(aliceId, false); libjami::setConversationPreferences(bobId, convId, {{"foo", "bar"}, {"bar", "foo"}}); - CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { - return preferencesBob.size() == 2; - })); + CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return preferencesBob.size() == 2; })); CPPUNIT_ASSERT(preferencesBob["foo"] == "bar" && preferencesBob["bar"] == "foo"); // Bob2 should sync preferences @@ -3556,7 +3550,9 @@ ConversationTest::testConversationPreferencesBeforeClone() details[ConfProperties::ARCHIVE_PIN] = ""; details[ConfProperties::ARCHIVE_PATH] = bobArchive; bob2Id = Manager::instance().addAccount(details); - CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bob2Started && conversationReadyBob2 && !preferencesBob2.empty(); })); + CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { + return bob2Started && conversationReadyBob2 && !preferencesBob2.empty(); + })); CPPUNIT_ASSERT(preferencesBob2["foo"] == "bar" && preferencesBob2["bar"] == "foo"); } diff --git a/test/unitTest/conversation/conversationMembersEvent.cpp b/test/unitTest/conversation/conversationMembersEvent.cpp index 6fe4f3042e6bf92f74ebcd98ecce9ed52e7fa832..418a864042bd554ba2acfa52c2b9a61207b6fa1a 100644 --- a/test/unitTest/conversation/conversationMembersEvent.cpp +++ b/test/unitTest/conversation/conversationMembersEvent.cpp @@ -430,6 +430,7 @@ ConversationMembersEventTest::testMemberAddedNoBadFile() generateFakeInvite(aliceAccount, convId, bobUri); // Generate conv request aliceAccount->sendTextMessage(bobUri, + std::string(bobAccount->currentDeviceId()), {{"application/invite+json", "{\"conversationId\":\"" + convId + "\"}"}}); CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return requestReceived; })); diff --git a/test/unitTest/conversation/conversationRequest.cpp b/test/unitTest/conversation/conversationRequest.cpp index 71d853dbf550ba28a996b2871fdbba83417132dc..408914e96540260b7c684ef30d03fb5b538affe1 100644 --- a/test/unitTest/conversation/conversationRequest.cpp +++ b/test/unitTest/conversation/conversationRequest.cpp @@ -1401,8 +1401,9 @@ ConversationRequestTest::testRemoveContactRemoveTrustRequest() // First, Alice adds Bob aliceAccount->addContact(bobUri); aliceAccount->sendTrustRequest(bobUri, {}); - CPPUNIT_ASSERT(cv.wait_for(lk, 5s, [&]() { return !convId.empty(); })); - CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return requestB1Received && requestB2Received; })); + CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { + return !convId.empty() && requestB1Received && requestB2Received; + })); // Bob1 accepts, both device should get it CPPUNIT_ASSERT(bobAccount->acceptTrustRequest(aliceUri)); diff --git a/test/unitTest/conversationRepository/conversationRepository.cpp b/test/unitTest/conversationRepository/conversationRepository.cpp index 861c2c6e65696db668151b4f3269857a3ebb397c..c181dccb15c32dfe319b57b7fcc4e3345fd053ad 100644 --- a/test/unitTest/conversationRepository/conversationRepository.cpp +++ b/test/unitTest/conversationRepository/conversationRepository.cpp @@ -52,7 +52,8 @@ public: ConversationRepositoryTest() { // Init daemon - libjami::init(libjami::InitFlag(libjami::LIBJAMI_FLAG_DEBUG | libjami::LIBJAMI_FLAG_CONSOLE_LOG)); + libjami::init( + libjami::InitFlag(libjami::LIBJAMI_FLAG_DEBUG | libjami::LIBJAMI_FLAG_CONSOLE_LOG)); if (not Manager::instance().initialized) CPPUNIT_ASSERT(libjami::start("jami-sample.yml")); } @@ -66,16 +67,11 @@ public: private: void testCreateRepository(); - void testCloneViaChannelSocket(); void testAddSomeMessages(); void testLogMessages(); - void testFetch(); void testMerge(); void testFFMerge(); void testDiff(); - // NOTE: Just for debug. the test is a bit complex to write - // due to the clone/fetch verifications (initial commits, size). - // void testCloneHugeRepo(); void testMergeProfileWithConflict(); @@ -90,15 +86,12 @@ private: CPPUNIT_TEST_SUITE(ConversationRepositoryTest); CPPUNIT_TEST(testCreateRepository); - CPPUNIT_TEST(testCloneViaChannelSocket); CPPUNIT_TEST(testAddSomeMessages); CPPUNIT_TEST(testLogMessages); - CPPUNIT_TEST(testFetch); CPPUNIT_TEST(testMerge); CPPUNIT_TEST(testFFMerge); CPPUNIT_TEST(testDiff); CPPUNIT_TEST(testMergeProfileWithConflict); - // CPPUNIT_TEST(testCloneHugeRepo); CPPUNIT_TEST_SUITE_END(); }; @@ -127,7 +120,7 @@ ConversationRepositoryTest::testCreateRepository() auto aliceDeviceId = DeviceId(std::string(aliceAccount->currentDeviceId())); auto uri = aliceAccount->getUsername(); - auto repository = ConversationRepository::createConversation(aliceAccount->weak()); + auto repository = ConversationRepository::createConversation(aliceAccount); // Assert that repository exists CPPUNIT_ASSERT(repository != nullptr); @@ -179,135 +172,11 @@ ConversationRepositoryTest::testCreateRepository() CPPUNIT_ASSERT(deviceCrtStr == deviceCert); } -void -ConversationRepositoryTest::testCloneViaChannelSocket() -{ - auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId); - auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId); - auto aliceDeviceId = DeviceId(std::string(aliceAccount->currentDeviceId())); - auto uri = aliceAccount->getUsername(); - auto bobDeviceId = DeviceId(std::string(bobAccount->currentDeviceId())); - - bobAccount->connectionManager().onICERequest([](const DeviceId&) { return true; }); - aliceAccount->connectionManager().onICERequest([](const DeviceId&) { return true; }); - auto repository = ConversationRepository::createConversation(aliceAccount->weak()); - auto repoPath = fileutils::get_data_dir() + DIR_SEPARATOR_STR + aliceAccount->getAccountID() - + DIR_SEPARATOR_STR + "conversations" + DIR_SEPARATOR_STR + repository->id(); - auto clonedPath = fileutils::get_data_dir() + DIR_SEPARATOR_STR + bobAccount->getAccountID() - + DIR_SEPARATOR_STR + "conversations" + DIR_SEPARATOR_STR + repository->id(); - - std::mutex mtx; - std::unique_lock<std::mutex> lk {mtx}; - std::condition_variable rcv, scv; - bool successfullyConnected = false; - bool successfullyReceive = false; - bool receiverConnected = false; - std::shared_ptr<ChannelSocket> channelSocket = nullptr; - std::shared_ptr<ChannelSocket> sendSocket = nullptr; - - bobAccount->connectionManager().onChannelRequest( - [&successfullyReceive](const std::shared_ptr<dht::crypto::Certificate>&, - const std::string& name) { - successfullyReceive = name == "git://*"; - return true; - }); - - aliceAccount->connectionManager().onChannelRequest( - [&successfullyReceive](const std::shared_ptr<dht::crypto::Certificate>&, - const std::string&) { return true; }); - - bobAccount->connectionManager().onConnectionReady( - [&](const DeviceId&, const std::string& name, std::shared_ptr<ChannelSocket> socket) { - receiverConnected = socket && (name == "git://*"); - channelSocket = socket; - rcv.notify_one(); - }); - - aliceAccount->connectionManager().connectDevice(bobDeviceId, - "git://*", - [&](std::shared_ptr<ChannelSocket> socket, - const DeviceId&) { - if (socket) { - successfullyConnected = true; - sendSocket = socket; - } - scv.notify_one(); - }); - - rcv.wait_for(lk, std::chrono::seconds(10)); - scv.wait_for(lk, std::chrono::seconds(10)); - CPPUNIT_ASSERT(successfullyReceive); - CPPUNIT_ASSERT(successfullyConnected); - CPPUNIT_ASSERT(receiverConnected); - - bobAccount->addGitSocket(aliceDeviceId, repository->id(), channelSocket); - GitServer gs(aliceId, repository->id(), sendSocket); - - auto cloned = ConversationRepository::cloneConversation(bobAccount->weak(), - aliceDeviceId.toString(), - repository->id()); - gs.stop(); - - CPPUNIT_ASSERT(cloned != nullptr); - CPPUNIT_ASSERT(fileutils::isDirectory(clonedPath)); - - // Assert that first commit is signed by alice - git_repository* repo; - CPPUNIT_ASSERT(git_repository_open(&repo, clonedPath.c_str()) == 0); - - // 1. Verify that last commit is correctly signed by alice - git_oid commit_id; - CPPUNIT_ASSERT(git_reference_name_to_id(&commit_id, repo, "HEAD") == 0); - - git_buf signature = {}, signed_data = {}; - git_commit_extract_signature(&signature, &signed_data, repo, &commit_id, "signature"); - auto pk = base64::decode(std::string(signature.ptr, signature.ptr + signature.size)); - auto data = std::vector<uint8_t>(signed_data.ptr, signed_data.ptr + signed_data.size); - git_repository_free(repo); - - CPPUNIT_ASSERT(aliceAccount->identity().second->getPublicKey().checkSignature(data, pk)); - - // 2. Check created files - auto CRLsPath = clonedPath + DIR_SEPARATOR_STR + "CRLs" + DIR_SEPARATOR_STR - + aliceDeviceId.toString(); - CPPUNIT_ASSERT(fileutils::isDirectory(clonedPath)); - - auto adminCrt = clonedPath + DIR_SEPARATOR_STR + "admins" + DIR_SEPARATOR_STR + uri + ".crt"; - CPPUNIT_ASSERT(fileutils::isFile(adminCrt)); - - auto crt = std::ifstream(adminCrt); - std::string adminCrtStr((std::istreambuf_iterator<char>(crt)), std::istreambuf_iterator<char>()); - - auto cert = aliceAccount->identity().second; - auto deviceCert = cert->toString(false); - auto parentCert = cert->issuer->toString(true); - - CPPUNIT_ASSERT(adminCrtStr == parentCert); - - auto deviceCrt = clonedPath + DIR_SEPARATOR_STR + "devices" + DIR_SEPARATOR_STR - + aliceDeviceId.toString() + ".crt"; - CPPUNIT_ASSERT(fileutils::isFile(deviceCrt)); - - crt = std::ifstream(deviceCrt); - std::string deviceCrtStr((std::istreambuf_iterator<char>(crt)), - std::istreambuf_iterator<char>()); - - CPPUNIT_ASSERT(deviceCrtStr == deviceCert); - - // Check cloned messages - auto messages = cloned->log(); - CPPUNIT_ASSERT(messages.size() == 1); - CPPUNIT_ASSERT(messages[0].id == repository->id()); - CPPUNIT_ASSERT( - aliceAccount->identity().second->getPublicKey().checkSignature(messages[0].signed_content, - messages[0].signature)); -} - void ConversationRepositoryTest::testAddSomeMessages() { auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId); - auto repository = ConversationRepository::createConversation(aliceAccount->weak()); + auto repository = ConversationRepository::createConversation(aliceAccount); auto id1 = repository->commitMessage("Commit 1"); auto id2 = repository->commitMessage("Commit 2"); @@ -346,7 +215,7 @@ void ConversationRepositoryTest::testLogMessages() { auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId); - auto repository = ConversationRepository::createConversation(aliceAccount->weak()); + auto repository = ConversationRepository::createConversation(aliceAccount); auto id1 = repository->commitMessage("Commit 1"); auto id2 = repository->commitMessage("Commit 2"); @@ -371,135 +240,6 @@ ConversationRepositoryTest::testLogMessages() CPPUNIT_ASSERT(messages[0].id == repository->id()); } -void -ConversationRepositoryTest::testFetch() -{ - auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId); - auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId); - auto aliceDeviceId = DeviceId(std::string(aliceAccount->currentDeviceId())); - auto bobDeviceId = DeviceId(std::string(bobAccount->currentDeviceId())); - - bobAccount->connectionManager().onICERequest([](const DeviceId&) { return true; }); - aliceAccount->connectionManager().onICERequest([](const DeviceId&) { return true; }); - auto repository = ConversationRepository::createConversation(aliceAccount->weak()); - auto repoPath = fileutils::get_data_dir() + DIR_SEPARATOR_STR + aliceAccount->getAccountID() - + DIR_SEPARATOR_STR + "conversations" + DIR_SEPARATOR_STR + repository->id(); - auto clonedPath = fileutils::get_data_dir() + DIR_SEPARATOR_STR + bobAccount->getAccountID() - + DIR_SEPARATOR_STR + "conversations" + DIR_SEPARATOR_STR + repository->id(); - - std::mutex mtx; - std::unique_lock<std::mutex> lk {mtx}; - std::condition_variable rcv, scv, ccv; - bool successfullyConnected = false; - bool successfullyReceive = false; - bool receiverConnected = false; - std::shared_ptr<ChannelSocket> channelSocket = nullptr; - std::shared_ptr<ChannelSocket> sendSocket = nullptr; - - bobAccount->connectionManager().onChannelRequest( - [&](const std::shared_ptr<dht::crypto::Certificate>&, const std::string& name) { - successfullyReceive = name == "git://*"; - ccv.notify_one(); - return true; - }); - - aliceAccount->connectionManager().onChannelRequest( - [&](const std::shared_ptr<dht::crypto::Certificate>&, const std::string&) { return true; }); - - bobAccount->connectionManager().onConnectionReady( - [&](const DeviceId&, const std::string& name, std::shared_ptr<ChannelSocket> socket) { - receiverConnected = socket && (name == "git://*"); - channelSocket = socket; - rcv.notify_one(); - }); - - aliceAccount->connectionManager().connectDevice(bobDeviceId, - "git://*", - [&](std::shared_ptr<ChannelSocket> socket, - const DeviceId&) { - if (socket) { - successfullyConnected = true; - sendSocket = socket; - } - scv.notify_one(); - }); - - rcv.wait_for(lk, std::chrono::seconds(10)); - scv.wait_for(lk, std::chrono::seconds(10)); - CPPUNIT_ASSERT(successfullyReceive); - CPPUNIT_ASSERT(successfullyConnected); - CPPUNIT_ASSERT(receiverConnected); - CPPUNIT_ASSERT(repository != nullptr); - - bobAccount->addGitSocket(aliceDeviceId, repository->id(), channelSocket); - GitServer gs(aliceId, repository->id(), sendSocket); - - // Clone repository - auto id1 = repository->commitMessage("Commit 1"); - - auto cloned = ConversationRepository::cloneConversation(bobAccount->weak(), - aliceDeviceId.toString(), - repository->id()); - gs.stop(); - bobAccount->removeGitSocket(aliceDeviceId, repository->id()); - - // Add some new messages to fetch - auto id2 = repository->commitMessage("Commit 2"); - auto id3 = repository->commitMessage("Commit 3"); - - // Open a new channel to simulate the fact that we are later - aliceAccount->connectionManager().connectDevice(bobDeviceId, - "git://*", - [&](std::shared_ptr<ChannelSocket> socket, - const DeviceId&) { - if (socket) { - successfullyConnected = true; - sendSocket = socket; - } - scv.notify_one(); - }); - - rcv.wait_for(lk, std::chrono::seconds(10)); - scv.wait_for(lk, std::chrono::seconds(10)); - ccv.wait_for(lk, std::chrono::seconds(10)); - bobAccount->addGitSocket(aliceDeviceId, repository->id(), channelSocket); - GitServer gs2(aliceId, repository->id(), sendSocket); - - CPPUNIT_ASSERT(cloned->fetch(aliceDeviceId.toString())); - CPPUNIT_ASSERT(id3 == cloned->remoteHead(aliceDeviceId.toString())); - - gs2.stop(); - bobAccount->removeGitSocket(aliceDeviceId, repository->id()); - - auto messages = cloned->log({id3}); - CPPUNIT_ASSERT(messages.size() == 4 /* 3 + initial */); - CPPUNIT_ASSERT(messages[0].id == id3); - CPPUNIT_ASSERT(messages[0].parents.front() == id2); - CPPUNIT_ASSERT(messages[0].commit_msg == "Commit 3"); - CPPUNIT_ASSERT(messages[0].author.name == messages[3].author.name); - CPPUNIT_ASSERT(messages[0].author.email == messages[3].author.email); - CPPUNIT_ASSERT(messages[1].id == id2); - CPPUNIT_ASSERT(messages[1].parents.front() == id1); - CPPUNIT_ASSERT(messages[1].commit_msg == "Commit 2"); - CPPUNIT_ASSERT(messages[1].author.name == messages[3].author.name); - CPPUNIT_ASSERT(messages[1].author.email == messages[3].author.email); - CPPUNIT_ASSERT(messages[2].id == id1); - CPPUNIT_ASSERT(messages[2].commit_msg == "Commit 1"); - CPPUNIT_ASSERT(messages[2].author.name == messages[3].author.name); - CPPUNIT_ASSERT(messages[2].author.email == messages[3].author.email); - CPPUNIT_ASSERT(messages[2].parents.front() == repository->id()); - // Check sig - CPPUNIT_ASSERT( - aliceAccount->identity().second->getPublicKey().checkSignature(messages[0].signed_content, - messages[0].signature)); - CPPUNIT_ASSERT( - aliceAccount->identity().second->getPublicKey().checkSignature(messages[1].signed_content, - messages[1].signature)); - CPPUNIT_ASSERT( - aliceAccount->identity().second->getPublicKey().checkSignature(messages[2].signed_content, - messages[2].signature)); -} - std::string ConversationRepositoryTest::addCommit(git_repository* repo, const std::shared_ptr<JamiAccount> account, @@ -617,7 +357,7 @@ void ConversationRepositoryTest::testMerge() { auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId); - auto repository = ConversationRepository::createConversation(aliceAccount->weak()); + auto repository = ConversationRepository::createConversation(aliceAccount); // Assert that repository exists CPPUNIT_ASSERT(repository != nullptr); @@ -652,7 +392,7 @@ void ConversationRepositoryTest::testFFMerge() { auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId); - auto repository = ConversationRepository::createConversation(aliceAccount->weak()); + auto repository = ConversationRepository::createConversation(aliceAccount); // Assert that repository exists CPPUNIT_ASSERT(repository != nullptr); @@ -689,7 +429,7 @@ ConversationRepositoryTest::testDiff() auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId); auto aliceDeviceId = DeviceId(std::string(aliceAccount->currentDeviceId())); auto uri = aliceAccount->getUsername(); - auto repository = ConversationRepository::createConversation(aliceAccount->weak()); + auto repository = ConversationRepository::createConversation(aliceAccount); auto id1 = repository->commitMessage("Commit 1"); auto id2 = repository->commitMessage("Commit 2"); @@ -708,7 +448,7 @@ void ConversationRepositoryTest::testMergeProfileWithConflict() { auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId); - auto repository = ConversationRepository::createConversation(aliceAccount->weak()); + auto repository = ConversationRepository::createConversation(aliceAccount); // Assert that repository exists CPPUNIT_ASSERT(repository != nullptr); @@ -763,89 +503,6 @@ ConversationRepositoryTest::testMergeProfileWithConflict() CPPUNIT_ASSERT(repository->log().size() == 5 /* Initial, add, modify 1, modify 2, merge */); } -/* -void -ConversationRepositoryTest::testCloneHugeRepo() -{ - auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId); - auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId); - auto aliceDeviceId = DeviceId(std::string(aliceAccount->currentDeviceId())); - auto uri = aliceAccount->getUsername(); - auto bobDeviceId = DeviceId(std::string(bobAccount->currentDeviceId())); - - bobAccount->connectionManager().onICERequest([](const DeviceId&) { return true; }); - aliceAccount->connectionManager().onICERequest([](const DeviceId&) { return true; }); - - auto convPath = fileutils::get_data_dir() + DIR_SEPARATOR_STR + aliceAccount->getAccountID() - + DIR_SEPARATOR_STR + "conversations"; - fileutils::recursive_mkdir(convPath); - const auto copyOptions = std::filesystem::copy_options::overwrite_existing | -std::filesystem::copy_options::recursive; auto repoPath = convPath + DIR_SEPARATOR_STR + -"8d3be095ebff73be1c43f193d02407b946d7895d"; std::filesystem::copy("/home/amarok/daemon/", repoPath, -copyOptions); - - auto repository = ConversationRepository(aliceAccount->weak(), -"8d3be095ebff73be1c43f193d02407b946d7895d"); auto clonedPath = fileutils::get_data_dir() + -DIR_SEPARATOR_STR + bobAccount->getAccountID() - + DIR_SEPARATOR_STR + "conversations" + DIR_SEPARATOR_STR + repository.id(); - - std::mutex mtx; - std::unique_lock<std::mutex> lk {mtx}; - std::condition_variable rcv, scv; - bool successfullyConnected = false; - bool successfullyReceive = false; - bool receiverConnected = false; - std::shared_ptr<ChannelSocket> channelSocket = nullptr; - std::shared_ptr<ChannelSocket> sendSocket = nullptr; - - bobAccount->connectionManager().onChannelRequest( - [&successfullyReceive](const std::shared_ptr<dht::crypto::Certificate>&, const std::string& -name) { successfullyReceive = name == "git://*"; return true; - }); - - aliceAccount->connectionManager().onChannelRequest( - [&successfullyReceive](const std::shared_ptr<dht::crypto::Certificate>&, const std::string& -name) { return true; }); - - bobAccount->connectionManager().onConnectionReady( - [&](const DeviceId&, const std::string& name, std::shared_ptr<ChannelSocket> socket) { - receiverConnected = socket && (name == "git://*"); - channelSocket = socket; - rcv.notify_one(); - }); - - aliceAccount->connectionManager().connectDevice(bobDeviceId, - "git://*", - [&](std::shared_ptr<ChannelSocket> socket, - const DeviceId&) { - if (socket) { - successfullyConnected = true; - sendSocket = socket; - } - scv.notify_one(); - }); - - ; - scv.wait_for(lk, std::chrono::seconds(10)); - CPPUNIT_ASSERT(rcv.wait_for(lk, std::chrono::seconds(10), [&] { return receiverConnected; })); - CPPUNIT_ASSERT(scv.wait_for(lk, std::chrono::seconds(10), [&] { return successfullyConnected; -})); CPPUNIT_ASSERT(successfullyReceive); - - bobAccount->addGitSocket(aliceDeviceId, repository.id(), channelSocket); - GitServer gs(aliceId, repository.id(), sendSocket); - - JAMI_ERR("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"); - auto cloned = ConversationRepository::cloneConversation(bobAccount->weak(), - aliceDeviceId, - repository.id()); - gs.stop(); - JAMI_ERR("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ END CLONE"); - - CPPUNIT_ASSERT(cloned != nullptr); - CPPUNIT_ASSERT(fileutils::isDirectory(clonedPath)); -} -*/ - } // namespace test } // namespace jami diff --git a/test/unitTest/fileTransfer/fileTransfer.cpp b/test/unitTest/fileTransfer/fileTransfer.cpp index 2f4b08bd9b55b61c40cb67fdefc96dc0ebd2f657..e8cd6c1500d4e991a5bbb56912b21b0ba0d4fc50 100644 --- a/test/unitTest/fileTransfer/fileTransfer.cpp +++ b/test/unitTest/fileTransfer/fileTransfer.cpp @@ -37,6 +37,7 @@ using namespace std::literals::chrono_literals; using namespace libjami::Account; +using namespace std::literals::chrono_literals; namespace jami { namespace test { @@ -218,9 +219,7 @@ FileTransferTest::testConversationFileTransfer() libjami::acceptConversationRequest(bobId, convId); libjami::acceptConversationRequest(carlaId, convId); - cv.wait_for(lk, 30s, [&]() { - return conversationReady == 3 && memberJoined == 2; - }); + cv.wait_for(lk, 30s, [&]() { return conversationReady == 3 && memberJoined == 2; }); // Send file std::ofstream sendFile(sendPath); @@ -230,15 +229,12 @@ FileTransferTest::testConversationFileTransfer() libjami::sendFile(aliceId, convId, sendPath, "SEND", ""); - CPPUNIT_ASSERT(cv.wait_for(lk, 45s, [&]() { - return !tidBob.empty() && !tidCarla.empty(); - })); + CPPUNIT_ASSERT(cv.wait_for(lk, 45s, [&]() { return !tidBob.empty() && !tidCarla.empty(); })); libjami::downloadFile(bobId, convId, iidBob, tidBob, recvPath); libjami::downloadFile(carlaId, convId, iidCarla, tidCarla, recv2Path); - CPPUNIT_ASSERT( - cv.wait_for(lk, 45s, [&]() { return finished.size() == 3; })); + CPPUNIT_ASSERT(cv.wait_for(lk, 45s, [&]() { return finished.size() == 3; })); libjami::unregisterSignalHandlers(); } @@ -313,9 +309,7 @@ FileTransferTest::testFileTransferInConversation() CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return requestReceived; })); libjami::acceptConversationRequest(bobId, convId); - CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { - return conversationReady && bobJoined; - })); + CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return conversationReady && bobJoined; })); // Create file to send std::ofstream sendFile(sendPath); @@ -330,9 +324,7 @@ FileTransferTest::testFileTransferInConversation() transferAFinished = false; transferBFinished = false; libjami::downloadFile(bobId, convId, iidBob, tidBob, recvPath); - CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { - return transferAFinished && transferBFinished; - })); + CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return transferAFinished && transferBFinished; })); libjami::unregisterSignalHandlers(); std::this_thread::sleep_for(5s); @@ -408,9 +400,7 @@ FileTransferTest::testVcfFileTransferInConversation() CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return requestReceived; })); libjami::acceptConversationRequest(bobId, convId); - CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { - return conversationReady && bobJoined; - })); + CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return conversationReady && bobJoined; })); // Create file to send std::ofstream sendFile(sendPath); @@ -425,9 +415,7 @@ FileTransferTest::testVcfFileTransferInConversation() transferAFinished = false; transferBFinished = false; libjami::downloadFile(bobId, convId, iidBob, tidBob, recvPath); - CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { - return transferAFinished && transferBFinished; - })); + CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return transferAFinished && transferBFinished; })); libjami::unregisterSignalHandlers(); std::this_thread::sleep_for(5s); @@ -512,9 +500,7 @@ FileTransferTest::testBadSha3sumOut() CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return requestReceived; })); libjami::acceptConversationRequest(bobId, convId); - CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { - return conversationReady && memberJoin; - })); + CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return conversationReady && memberJoin; })); // Create file to send std::ofstream sendFile(sendPath); @@ -537,9 +523,7 @@ FileTransferTest::testBadSha3sumOut() libjami::downloadFile(bobId, convId, iid, mid, recvPath); // The file transfer will not be sent as modified - CPPUNIT_ASSERT(!cv.wait_for(lk, 30s, [&]() { - return transferAFinished || transferBFinished; - })); + CPPUNIT_ASSERT(!cv.wait_for(lk, 30s, [&]() { return transferAFinished || transferBFinished; })); libjami::unregisterSignalHandlers(); } @@ -623,9 +607,7 @@ FileTransferTest::testBadSha3sumIn() CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return requestReceived; })); libjami::acceptConversationRequest(bobId, convId); - CPPUNIT_ASSERT(cv.wait_for(lk, 60s, [&]() { - return conversationReady && memberJoin; - })); + CPPUNIT_ASSERT(cv.wait_for(lk, 60s, [&]() { return conversationReady && memberJoin; })); // Create file to send std::ofstream sendFile(sendPath); @@ -738,23 +720,18 @@ FileTransferTest::testAskToMultipleParticipants() libjami::addConversationMember(aliceId, convId, bobUri); CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return requestReceived; })); + requestReceived = false; + libjami::addConversationMember(aliceId, convId, carlaUri); + CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return requestReceived; })); libjami::acceptConversationRequest(bobId, convId); - CPPUNIT_ASSERT(cv.wait_for(lk, 60s, [&]() { - return conversationReady && memberJoin; - })); + CPPUNIT_ASSERT(cv.wait_for(lk, 60s, [&]() { return conversationReady && memberJoin; })); - requestReceived = false; conversationReady = false; memberJoin = false; - libjami::addConversationMember(aliceId, convId, carlaUri); - CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return requestReceived; })); - libjami::acceptConversationRequest(carlaId, convId); - CPPUNIT_ASSERT(cv.wait_for(lk, 60s, [&]() { - return conversationReady && memberJoin; - })); + CPPUNIT_ASSERT(cv.wait_for(lk, 60s, [&]() { return conversationReady && memberJoin; })); // Create file to send std::ofstream sendFile(sendPath); @@ -764,9 +741,7 @@ FileTransferTest::testAskToMultipleParticipants() libjami::sendFile(aliceId, convId, sendPath, "SEND", ""); - CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { - return !bobTid.empty() && !carlaTid.empty(); - })); + CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return !bobTid.empty() && !carlaTid.empty(); })); transferCFinished = false; libjami::downloadFile(carlaId, convId, iidCarla, carlaTid, recv2Path); @@ -853,9 +828,7 @@ FileTransferTest::testCancelInTransfer() CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return requestReceived; })); libjami::acceptConversationRequest(bobId, convId); - CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { - return conversationReady && bobJoined; - })); + CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return conversationReady && bobJoined; })); // Create file to send std::ofstream sendFile(sendPath); @@ -949,9 +922,7 @@ FileTransferTest::testTransferInfo() CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return requestReceived; })); libjami::acceptConversationRequest(bobId, convId); - CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { - return conversationReady && bobJoined; - })); + CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return conversationReady && bobJoined; })); // Create file to send std::ofstream sendFile(sendPath); @@ -973,9 +944,7 @@ FileTransferTest::testTransferInfo() transferAFinished = false; transferBFinished = false; libjami::downloadFile(bobId, convId, iidBob, tidBob, recvPath); - CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { - return transferAFinished && transferBFinished; - })); + CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return transferAFinished && transferBFinished; })); CPPUNIT_ASSERT(libjami::fileTransferInfo(bobId, convId, tidBob, path, totalSize, bytesProgress) == libjami::DataTransferError::success); @@ -1019,17 +988,16 @@ FileTransferTest::testRemoveHardLink() conversationReady = true; })); auto conversationRemoved = false; - confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationRemoved>( - [&](const std::string& /*accountId*/, const std::string& /* conversationId */) { - conversationRemoved = true; - })); + confHandlers.insert( + libjami::exportable_callback<libjami::ConversationSignal::ConversationRemoved>( + [&](const std::string& /*accountId*/, const std::string& /* conversationId */) { + conversationRemoved = true; + })); libjami::registerSignalHandlers(confHandlers); auto convId = libjami::startConversation(aliceId); - CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { - return conversationReady; - })); + CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return conversationReady; })); // Send file std::ofstream sendFile(sendPath); @@ -1040,15 +1008,11 @@ FileTransferTest::testRemoveHardLink() libjami::sendFile(aliceId, convId, sendPath, std::filesystem::absolute("SEND"), ""); messageReceived = false; - CPPUNIT_ASSERT(cv.wait_for(lk, 45s, [&]() { - return messageReceived; - })); + CPPUNIT_ASSERT(cv.wait_for(lk, 45s, [&]() { return messageReceived; })); CPPUNIT_ASSERT(libjami::removeConversation(aliceId, convId)); - CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { - return conversationRemoved; - })); + CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return conversationRemoved; })); auto content = fileutils::loadTextFile(sendPath); CPPUNIT_ASSERT(content.find("AAA") != std::string::npos); diff --git a/test/unitTest/swarm/bootstrap.cpp b/test/unitTest/swarm/bootstrap.cpp new file mode 100644 index 0000000000000000000000000000000000000000..3c5c9b65e8fed236b94f5cd99dde2620aabe23aa --- /dev/null +++ b/test/unitTest/swarm/bootstrap.cpp @@ -0,0 +1,392 @@ +/* + * Copyright (C) 2023 Savoir-faire Linux Inc. + * Author: Sébastien Blin <sebastien.blin@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include <cppunit/TestAssert.h> +#include <cppunit/TestFixture.h> +#include <cppunit/extensions/HelperMacros.h> + +#include <condition_variable> +#include <msgpack.hpp> +#include <filesystem> + +#include "../../test_runner.h" +#include "account_const.h" +#include "common.h" +#include "conversation_interface.h" +#include "fileutils.h" +#include "jami.h" +#include "jamidht/conversation.h" +#include "jamidht/jamiaccount.h" +#include "jamidht/swarm/swarm_channel_handler.h" +#include "manager.h" + +using namespace std::string_literals; +using namespace std::literals::chrono_literals; +using namespace libjami::Account; + +namespace jami { + +struct ConvInfoTest +{ + std::string accountId; + std::string convId; + bool requestReceived = false; + bool conversationReady = false; + std::vector<std::map<std::string, std::string>> messages; + Conversation::BootstrapStatus bootstrap {Conversation::BootstrapStatus::FAILED}; +}; + +namespace test { + +class BootstrapTest : public CppUnit::TestFixture +{ +public: + ~BootstrapTest() { libjami::fini(); } + static std::string name() { return "Bootstrap"; } + void setUp(); + void tearDown(); + + ConvInfoTest aliceData; + ConvInfoTest bobData; + ConvInfoTest bob2Data; + ConvInfoTest carlaData; + + std::mutex mtx; + std::condition_variable cv; + +private: + void connectSignals(); + + void testBootstrapOk(); + void testBootstrapFailed(); + void testBootstrapNeverNewDevice(); + void testBootstrapCompat(); + + CPPUNIT_TEST_SUITE(BootstrapTest); + CPPUNIT_TEST(testBootstrapOk); + CPPUNIT_TEST(testBootstrapFailed); + CPPUNIT_TEST(testBootstrapNeverNewDevice); + CPPUNIT_TEST(testBootstrapCompat); + CPPUNIT_TEST_SUITE_END(); +}; + +CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(BootstrapTest, BootstrapTest::name()); + +void +BootstrapTest::setUp() +{ + aliceData = {}; + bobData = {}; + bob2Data = {}; + carlaData = {}; + + // Init daemon + libjami::init( + libjami::InitFlag(libjami::LIBJAMI_FLAG_DEBUG | libjami::LIBJAMI_FLAG_CONSOLE_LOG)); + if (not Manager::instance().initialized) + CPPUNIT_ASSERT(libjami::start("jami-sample.yml")); + + auto actors = load_actors("actors/alice-bob-carla.yml"); + aliceData.accountId = actors["alice"]; + bobData.accountId = actors["bob"]; + carlaData.accountId = actors["carla"]; + + Manager::instance().sendRegister(carlaData.accountId, false); + wait_for_announcement_of({aliceData.accountId, bobData.accountId}); + connectSignals(); +} + +void +BootstrapTest::tearDown() +{ + libjami::unregisterSignalHandlers(); + auto bobArchive = std::filesystem::current_path().string() + "/bob.gz"; + std::remove(bobArchive.c_str()); + + if (bob2Data.accountId.empty()) { + wait_for_removal_of({aliceData.accountId, bobData.accountId, carlaData.accountId}); + } else { + wait_for_removal_of( + {aliceData.accountId, bobData.accountId, carlaData.accountId, bob2Data.accountId}); + } +} + +void +BootstrapTest::connectSignals() +{ + std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers; + confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>( + [&](const std::string& accountId, const std::string& convId) { + if (accountId == aliceData.accountId) { + aliceData.convId = convId; + aliceData.conversationReady = true; + } else if (accountId == bobData.accountId) { + bobData.convId = convId; + bobData.conversationReady = true; + } else if (accountId == bob2Data.accountId) { + bob2Data.convId = convId; + bob2Data.conversationReady = true; + } else if (accountId == carlaData.accountId) { + carlaData.convId = convId; + carlaData.conversationReady = true; + } + cv.notify_one(); + })); + confHandlers.insert( + libjami::exportable_callback<libjami::ConversationSignal::ConversationRequestReceived>( + [&](const std::string& accountId, + const std::string& /* conversationId */, + std::map<std::string, std::string> /*metadatas*/) { + if (accountId == aliceData.accountId) { + aliceData.requestReceived = true; + } else if (accountId == bobData.accountId) { + bobData.requestReceived = true; + } else if (accountId == bob2Data.accountId) { + bob2Data.requestReceived = true; + } else if (accountId == carlaData.accountId) { + carlaData.requestReceived = true; + } + cv.notify_one(); + })); + confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::MessageReceived>( + [&](const std::string& accountId, + const std::string& /*conversationId*/, + std::map<std::string, std::string> message) { + if (accountId == aliceData.accountId) { + aliceData.messages.emplace_back(message); + } else if (accountId == bobData.accountId) { + bobData.messages.emplace_back(message); + } else if (accountId == bob2Data.accountId) { + bob2Data.messages.emplace_back(message); + } else if (accountId == carlaData.accountId) { + carlaData.messages.emplace_back(message); + } + cv.notify_one(); + })); + libjami::registerSignalHandlers(confHandlers); + + // Link callback for convModule() + auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceData.accountId); + auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobData.accountId); + auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaData.accountId); + + aliceAccount->convModule()->onBootstrapStatus( + [&](std::string /*convId*/, Conversation::BootstrapStatus status) { + aliceData.bootstrap = status; + cv.notify_one(); + }); + bobAccount->convModule()->onBootstrapStatus( + [&](std::string /*convId*/, Conversation::BootstrapStatus status) { + bobData.bootstrap = status; + cv.notify_one(); + }); + carlaAccount->convModule()->onBootstrapStatus( + [&](std::string /*convId*/, Conversation::BootstrapStatus status) { + carlaData.bootstrap = status; + cv.notify_one(); + }); +} + +void +BootstrapTest::testBootstrapOk() +{ + auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceData.accountId); + auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobData.accountId); + auto bobUri = bobAccount->getUsername(); + + aliceAccount->convModule()->onBootstrapStatus( + [&](std::string /*convId*/, Conversation::BootstrapStatus status) { + aliceData.bootstrap = status; + cv.notify_one(); + }); + + std::unique_lock<std::mutex> lk {mtx}; + auto convId = libjami::startConversation(aliceData.accountId); + + libjami::addConversationMember(aliceData.accountId, convId, bobUri); + CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; })); + + auto aliceMsgSize = aliceData.messages.size(); + libjami::acceptConversationRequest(bobData.accountId, convId); + CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { + return bobData.conversationReady && aliceData.messages.size() == aliceMsgSize + 1 + && aliceData.bootstrap == Conversation::BootstrapStatus::SUCCESS + && bobData.bootstrap == Conversation::BootstrapStatus::SUCCESS; + })); +} + +void +BootstrapTest::testBootstrapFailed() +{ + auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceData.accountId); + auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobData.accountId); + auto bobUri = bobAccount->getUsername(); + + aliceAccount->convModule()->onBootstrapStatus( + [&](std::string /*convId*/, Conversation::BootstrapStatus status) { + aliceData.bootstrap = status; + cv.notify_one(); + }); + + std::unique_lock<std::mutex> lk {mtx}; + auto convId = libjami::startConversation(aliceData.accountId); + + libjami::addConversationMember(aliceData.accountId, convId, bobUri); + CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; })); + + auto aliceMsgSize = aliceData.messages.size(); + libjami::acceptConversationRequest(bobData.accountId, convId); + CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { + return bobData.conversationReady && aliceData.messages.size() == aliceMsgSize + 1 + && aliceData.bootstrap == Conversation::BootstrapStatus::SUCCESS + && bobData.bootstrap == Conversation::BootstrapStatus::SUCCESS; + })); + + // Now bob goes offline, it should disconnect alice + Manager::instance().sendRegister(bobData.accountId, false); + // Alice will try to maintain before failing (so will take 30secs to fail) + CPPUNIT_ASSERT(cv.wait_for(lk, 60s, [&]() { + return aliceData.bootstrap == Conversation::BootstrapStatus::FAILED; + })); +} + +void +BootstrapTest::testBootstrapNeverNewDevice() +{ + auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceData.accountId); + auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobData.accountId); + auto bobUri = bobAccount->getUsername(); + + aliceAccount->convModule()->onBootstrapStatus( + [&](std::string /*convId*/, Conversation::BootstrapStatus status) { + aliceData.bootstrap = status; + cv.notify_one(); + }); + + std::unique_lock<std::mutex> lk {mtx}; + auto convId = libjami::startConversation(aliceData.accountId); + + libjami::addConversationMember(aliceData.accountId, convId, bobUri); + CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; })); + + auto aliceMsgSize = aliceData.messages.size(); + libjami::acceptConversationRequest(bobData.accountId, convId); + CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { + return bobData.conversationReady && aliceData.messages.size() == aliceMsgSize + 1 + && aliceData.bootstrap == Conversation::BootstrapStatus::SUCCESS + && bobData.bootstrap == Conversation::BootstrapStatus::SUCCESS; + })); + + // Alice offline + Manager::instance().sendRegister(aliceData.accountId, false); + // Bob will try to maintain before failing (so will take 30secs to fail) + CPPUNIT_ASSERT(cv.wait_for(lk, 60s, [&]() { + return bobData.bootstrap == Conversation::BootstrapStatus::FAILED; + })); + + // Create bob2 + auto bobArchive = std::filesystem::current_path().string() + "/bob.gz"; + std::remove(bobArchive.c_str()); + bobAccount->exportArchive(bobArchive); + std::map<std::string, std::string> details = libjami::getAccountTemplate("RING"); + details[ConfProperties::TYPE] = "RING"; + details[ConfProperties::DISPLAYNAME] = "BOB2"; + details[ConfProperties::ALIAS] = "BOB2"; + details[ConfProperties::UPNP_ENABLED] = "true"; + details[ConfProperties::ARCHIVE_PASSWORD] = ""; + details[ConfProperties::ARCHIVE_PIN] = ""; + details[ConfProperties::ARCHIVE_PATH] = bobArchive; + bob2Data.accountId = Manager::instance().addAccount(details); + auto bob2Account = Manager::instance().getAccount<JamiAccount>(bob2Data.accountId); + + std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers; + bool bob2Connected = false; + confHandlers.insert( + libjami::exportable_callback<libjami::ConfigurationSignal::VolatileDetailsChanged>( + [&](const std::string&, const std::map<std::string, std::string>&) { + auto details = bob2Account->getVolatileAccountDetails(); + auto daemonStatus = details[libjami::Account::ConfProperties::Registration::STATUS]; + if (daemonStatus != "UNREGISTERED") + bob2Connected = true; + cv.notify_one(); + })); + libjami::registerSignalHandlers(confHandlers); + + CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bob2Connected; })); + bob2Account->convModule()->onBootstrapStatus( + [&](std::string /*convId*/, Conversation::BootstrapStatus status) { + bob2Data.bootstrap = status; + cv.notify_one(); + }); + + // Disconnect bob2, to create a valid conv betwen Alice and Bob1 + CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { + return bobData.bootstrap == Conversation::BootstrapStatus::SUCCESS + && bob2Data.bootstrap == Conversation::BootstrapStatus::SUCCESS; + })); + + // Bob offline + Manager::instance().sendRegister(bobData.accountId, false); + // Bob2 will try to maintain before failing (so will take 30secs to fail) + CPPUNIT_ASSERT(cv.wait_for(lk, 60s, [&]() { + return bob2Data.bootstrap == Conversation::BootstrapStatus::FAILED; + })); + + // Alice bootstrap should go to fallback (because bob2 never wrote into the conversation) & Connected + Manager::instance().sendRegister(aliceData.accountId, true); + // Wait for announcement, ICE fallback + delay + CPPUNIT_ASSERT(cv.wait_for(lk, 60s, [&]() { + return aliceData.bootstrap == Conversation::BootstrapStatus::SUCCESS; + })); +} + +void +BootstrapTest::testBootstrapCompat() +{ + auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceData.accountId); + auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobData.accountId); + auto bobUri = bobAccount->getUsername(); + + dynamic_cast<SwarmChannelHandler*>(aliceAccount->channelHandlers()[Uri::Scheme::SWARM].get()) + ->disableSwarmManager + = true; + + std::unique_lock<std::mutex> lk {mtx}; + auto convId = libjami::startConversation(aliceData.accountId); + + libjami::addConversationMember(aliceData.accountId, convId, bobUri); + CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; })); + + auto aliceMsgSize = aliceData.messages.size(); + libjami::acceptConversationRequest(bobData.accountId, convId); + CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { + return bobData.conversationReady && aliceData.messages.size() == aliceMsgSize + 1; + })); + + auto bobMsgSize = bobData.messages.size(); + libjami::sendMessage(aliceData.accountId, convId, "hi"s, ""); + cv.wait_for(lk, 30s, [&]() { + return bobData.messages.size() == bobMsgSize + 1 + && bobData.bootstrap == Conversation::BootstrapStatus::FAILED; + }); +} + +} // namespace test +} // namespace jami + +RING_TEST_RUNNER(jami::test::BootstrapTest::name()) diff --git a/test/unitTest/swarm/routing_table.cpp b/test/unitTest/swarm/routing_table.cpp index f031fe7f1aed1bbff491afd64d76c2c6abd08e98..e67c5c44e1f0ef1d659b421fb1c305d437e03f6b 100644 --- a/test/unitTest/swarm/routing_table.cpp +++ b/test/unitTest/swarm/routing_table.cpp @@ -1297,7 +1297,7 @@ RoutingTableTest::testSwarmManagersWMobileModes() } } - sleep(5); + sleep(10); { if (!swarmManagers.empty()) { @@ -1306,7 +1306,7 @@ RoutingTableTest::testSwarmManagersWMobileModes() } } - sleep(4); + sleep(10); CPPUNIT_ASSERT_EQUAL_MESSAGE("Supposed to be equal", swarmManagers.size(), diff --git a/test/unitTest/swarm/swarm_conversation.cpp b/test/unitTest/swarm/swarm_conversation.cpp new file mode 100644 index 0000000000000000000000000000000000000000..c94dac82db9a5ff08f5f33782521dcdbf5f86f16 --- /dev/null +++ b/test/unitTest/swarm/swarm_conversation.cpp @@ -0,0 +1,223 @@ +/* + * Copyright (C) 2023 Savoir-faire Linux Inc. + * + * Author: Fadi Shehadeh <fadi.shehadeh@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <cppunit/TestAssert.h> +#include <cppunit/TestFixture.h> +#include <cppunit/extensions/HelperMacros.h> + +#include <condition_variable> +#include <filesystem> +#include <string> + +#include "jami.h" +#include "../common.h" +#include "jamidht/swarm/swarm_manager.h" +#include "connectivity/multiplexed_socket.h" + +#include "connectivity/peer_connection.h" +#include <opendht/thread_pool.h> + +#include "../../test_runner.h" +#include "account_const.h" +#include "conversation/conversationcommon.h" +#include "manager.h" + +using namespace dht; +using NodeId = dht::PkId; +using namespace std::literals::chrono_literals; + +namespace jami { +namespace test { + +struct ConvData +{ + std::string id {}; + bool requestReceived {false}; + bool needsHost {false}; + bool conferenceChanged {false}; + bool conferenceRemoved {false}; + std::string hostState {}; + std::vector<std::map<std::string, std::string>> messages {}; +}; + +class SwarmConversationTest : public CppUnit::TestFixture +{ +public: + ~SwarmConversationTest(); + static std::string name() { return "SwarmConversationTest"; } + void setUp(); + + std::map<std::string, ConvData> accountMap; + std::vector<std::string> accountIds; + + std::mutex mtx; + std::unique_lock<std::mutex> lk {mtx}; + std::condition_variable cv; + +private: + void connectSignals(); + + std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers; + + void testSendMessage(); + + CPPUNIT_TEST_SUITE(SwarmConversationTest); + CPPUNIT_TEST(testSendMessage); + CPPUNIT_TEST_SUITE_END(); +}; + +CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(SwarmConversationTest, SwarmConversationTest::name()); + +void +SwarmConversationTest::setUp() +{ + libjami::init( + libjami::InitFlag(libjami::LIBJAMI_FLAG_DEBUG | libjami::LIBJAMI_FLAG_CONSOLE_LOG)); + if (not Manager::instance().initialized) + CPPUNIT_ASSERT(libjami::start("jami-sample.yml")); + + auto actors = load_actors("actors/account_list.yml"); + + for (const auto& act : actors) { + auto id = act.second; + accountIds.emplace_back(id); + std::cout << act.second << std::endl; + accountMap.insert({id, {}}); + } + + wait_for_announcement_of(accountIds, 60s); +} + +SwarmConversationTest::~SwarmConversationTest() +{ + wait_for_removal_of(accountIds); + libjami::fini(); +} + +void +SwarmConversationTest::connectSignals() +{ + std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers; + + confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>( + [&](const std::string& accountId, const std::string& conversationId) { + for (const auto& accId : accountIds) { + if (accountId == accId) { + accountMap[accId].id = conversationId; + } + } + cv.notify_one(); + })); + confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::MessageReceived>( + [&](const std::string& accountId, + const std::string& conversationId, + std::map<std::string, std::string> message) { + for (const auto& accId : accountIds) { + if (accountId == accId && accountMap[accId].id == conversationId) { + accountMap[accId].messages.emplace_back(message); + } + } + cv.notify_one(); + })); + confHandlers.insert( + libjami::exportable_callback<libjami::ConversationSignal::ConversationRequestReceived>( + [&](const std::string& accountId, + const std::string&, + std::map<std::string, std::string>) { + for (const auto& accId : accountIds) { + if (accountId == accId) { + accountMap[accId].requestReceived = true; + } + } + cv.notify_one(); + })); + + libjami::registerSignalHandlers(confHandlers); +} + +void +SwarmConversationTest::testSendMessage() +{ + std::map<std::string, std::shared_ptr<JamiAccount>> jamiAccounts; + + for (const auto& accId : accountIds) { + jamiAccounts.insert({accId, Manager::instance().getAccount<JamiAccount>(accId)}); + std::cout << "created account for: " << accId << std::endl; + } + + std::mutex mtx; + std::unique_lock<std::mutex> lk {mtx}; + std::condition_variable cv; + + connectSignals(); + + auto aliceId = jamiAccounts.begin()->first; + auto convId = libjami::startConversation(aliceId); + + std::cout << "started conversation: " << convId << std::endl; + + for (auto it = std::next(jamiAccounts.begin()); it != jamiAccounts.end(); ++it) { + auto userUri = it->second->getUsername(); + std::cout << "adding member: " << userUri << std::endl; + libjami::addConversationMember(aliceId, convId, userUri); + + CPPUNIT_ASSERT_EQUAL_MESSAGE("ERROR", true, cv.wait_for(lk, 40s, [&]() { + return accountMap[it->first].requestReceived == true; + })); + + libjami::acceptConversationRequest(it->first, convId); + std::this_thread::sleep_for(std::chrono::seconds(10)); + } + + std::cout << "waiting for conversation ready" << std::endl; + for (size_t i = 1; i < accountIds.size(); i++) { + CPPUNIT_ASSERT( + cv.wait_for(lk, 40s, [&]() { return accountMap[accountIds.at(i)].id == convId; })); + } + std::cout << "messages size " << accountMap[accountIds.at(0)].messages.size() << std::endl; + + CPPUNIT_ASSERT( + cv.wait_for(lk, 70s, [&]() { return accountMap[accountIds.at(0)].messages.size() >= 2; })); + + libjami::sendMessage(aliceId, convId, "hi"s, ""); + + for (size_t i = 1; i < accountIds.size(); i++) { + std::cout << "COUNTER: " << i << " messages size " + << accountMap[accountIds.at(i)].messages.size() << std::endl; + + if (accountMap[accountIds.at(i)].messages.size() >= 105) { + for (const auto& msg : accountMap[accountIds.at(i)].messages) { + std::cout << "Message id: " << msg.at("id") << " type: " << msg.at("type") + << std::endl; + } + } + + CPPUNIT_ASSERT(cv.wait_for(lk, 40s, [&]() { + return accountMap[accountIds.at(i)].messages.size() >= 1; + })); + } + + libjami::unregisterSignalHandlers(); +} + +} // namespace test +} // namespace jami + +RING_TEST_RUNNER(jami::test::SwarmConversationTest::name()) diff --git a/test/unitTest/swarm/swarm_spread.cpp b/test/unitTest/swarm/swarm_spread.cpp index 51c0fc65d8fb8ab8a5bac999b60f87f8bb37978d..c5a0e934d756e628a3a367b683ecdae6d5bef23a 100644 --- a/test/unitTest/swarm/swarm_spread.cpp +++ b/test/unitTest/swarm/swarm_spread.cpp @@ -45,10 +45,10 @@ using NodeId = dht::PkId; namespace jami { namespace test { -constexpr size_t nNodes = 5000; +constexpr size_t nNodes = 10; constexpr size_t BOOTSTRAP_SIZE = 2; -auto time = 300s; +auto time = 30s; int TOTAL_HOPS = 0; int moyenne = 0; @@ -200,7 +200,7 @@ SwarmMessageSpread::sendMessage(const std::shared_ptr<ChannelSocketInterface>& s dht::ThreadPool::io().run([socket, buffer = std::move(buffer)] { std::error_code ec; - std::this_thread::sleep_for(std::chrono::milliseconds(50)); + // std::this_thread::sleep_for(std::chrono::milliseconds(50)); socket->write(reinterpret_cast<const unsigned char*>(buffer->data()), buffer->size(), ec); }); @@ -302,7 +302,7 @@ SwarmMessageSpread::needSocketCallBack(const std::shared_ptr<SwarmManager>& sm) ChannelSocketTest::link(cstMe.second, cstRemote.second); receiveMessage(myId, cstMe.second); receiveMessage(node, cstRemote.second); - std::this_thread::sleep_for(std::chrono::seconds(5)); + // std::this_thread::sleep_for(std::chrono::seconds(5)); ChannelSocketTest::link(cstMe.first, cstRemote.first); smRemote->addChannel(cstRemote.first); onSocket(cstMe.first);