From 08ef8dd80d571816259b195b0956a472a40b58ad Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=A9bastien=20Blin?=
 <sebastien.blin@savoirfairelinux.com>
Date: Fri, 20 May 2022 09:17:03 -0400
Subject: [PATCH] swarm: follow user preference for hosting conferences

This avoid mobile phone to host conferences for others unless
explicitly authorized by the user.
Clients can show it via the "hostConference" conversation's preference.

Documentation: https://docs.jami.net/technical/swarm.html#call-in-swarm
GitLab: #312
Change-Id: I9bbd3e394cd1b3bcfd4a3120d9023a5ed686303d
---
 src/jamidht/conversation.h          |   4 +
 src/jamidht/conversation_module.cpp |   4 -
 src/jamidht/jamiaccount.cpp         | 218 +++++++++++++++-------------
 test/unitTest/conversation/call.cpp | 132 ++++++++++++++++-
 4 files changed, 246 insertions(+), 112 deletions(-)

diff --git a/src/jamidht/conversation.h b/src/jamidht/conversation.h
index d5114d232a..70db6921ad 100644
--- a/src/jamidht/conversation.h
+++ b/src/jamidht/conversation.h
@@ -52,6 +52,10 @@ static constexpr const char* CONVERSATIONID = "conversationId";
 static constexpr const char* METADATAS = "metadatas";
 } // namespace ConversationMapKeys
 
+namespace ConversationPreferences {
+static constexpr const char* HOST_CONFERENCES = "hostConferences";
+}
+
 /**
  * A ConversationRequest is a request which corresponds to a trust request, but for conversations
  * It's signed by the sender and contains the members list, the conversationId, and the metadatas
diff --git a/src/jamidht/conversation_module.cpp b/src/jamidht/conversation_module.cpp
index 14611bbca9..43773bad6a 100644
--- a/src/jamidht/conversation_module.cpp
+++ b/src/jamidht/conversation_module.cpp
@@ -2043,13 +2043,9 @@ ConversationModule::call(const std::string& url,
             return;
         }
         conversationId = parameters[0];
-        JAMI_ERR("@@@ %s", conversationId.c_str());
         uri = parameters[1];
-        JAMI_ERR("@@@ %s", uri.c_str());
         deviceId = parameters[2];
-        JAMI_ERR("@@@ %s", deviceId.c_str());
         confId = parameters[3];
-        JAMI_ERR("@@@ %s", confId.c_str());
     }
 
     std::string callUri;
diff --git a/src/jamidht/jamiaccount.cpp b/src/jamidht/jamiaccount.cpp
index 8ef6922fdb..29b020e6a7 100644
--- a/src/jamidht/jamiaccount.cpp
+++ b/src/jamidht/jamiaccount.cpp
@@ -164,7 +164,7 @@ void
 setState(const std::string& accountID, const State migrationState)
 {
     emitSignal<libjami::ConfigurationSignal::MigrationEnded>(accountID,
-                                                           mapStateNumberToString(migrationState));
+                                                             mapStateNumberToString(migrationState));
 }
 
 } // namespace Migration
@@ -532,14 +532,32 @@ JamiAccount::handleIncomingConversationCall(const std::string& callId,
     if (getUsername() != accountUri || currentDeviceId() != deviceId)
         return;
 
+    auto isNotHosting = !convModule()->isHosting(conversationId, confId);
+    auto preferences = convModule()->getConversationPreferences(conversationId);
+    auto canHost = true;
+#if defined(__ANDROID__) || defined(__APPLE__)
+    // By default, mobile devices SHOULD NOT host conferences.
+    canHost = false;
+#endif
+    auto itPref = preferences.find(ConversationPreferences::HOST_CONFERENCES);
+    if (itPref != preferences.end()) {
+        canHost = itPref->second == TRUE_STR;
+    }
+
     auto call = getCall(callId);
     if (!call) {
         JAMI_ERR("Call %s not found", callId.c_str());
         return;
     }
+
+    if (isNotHosting && !canHost) {
+        JAMI_DBG("Request for hosting a conference declined");
+        Manager::instance().hangupCall(getAccountID(), callId);
+        return;
+    }
     Manager::instance().answerCall(*call);
 
-    if (!convModule()->isHosting(conversationId, confId)) {
+    if (isNotHosting) {
         // Create conference and host it.
         convModule()->hostConference(conversationId, confId, callId);
         if (auto conf = getConference(confId))
@@ -904,7 +922,7 @@ JamiAccount::changeArchivePassword(const std::string& password_old, const std::s
             JAMI_ERR("[Account %s] Can't change archive password", getAccountID().c_str());
             return false;
         }
-        editConfig([&](JamiAccountConfig& config){
+        editConfig([&](JamiAccountConfig& config) {
             config.archiveHasPassword = not password_new.empty();
         });
     } catch (const std::exception& ex) {
@@ -912,17 +930,15 @@ JamiAccount::changeArchivePassword(const std::string& password_old, const std::s
                  getAccountID().c_str(),
                  ex.what());
         if (password_old.empty()) {
-            editConfig([&](JamiAccountConfig& config){
-                config.archiveHasPassword = true;
-            });
+            editConfig([&](JamiAccountConfig& config) { config.archiveHasPassword = true; });
             emitSignal<libjami::ConfigurationSignal::AccountDetailsChanged>(getAccountID(),
-                                                                          getAccountDetails());
+                                                                            getAccountDetails());
         }
         return false;
     }
     if (password_old != password_new)
         emitSignal<libjami::ConfigurationSignal::AccountDetailsChanged>(getAccountID(),
-                                                                      getAccountDetails());
+                                                                        getAccountDetails());
     return true;
 }
 
@@ -979,7 +995,7 @@ JamiAccount::setValidity(const std::string& pwd, const dht::InfoHash& id, int64_
 void
 JamiAccount::forceReloadAccount()
 {
-    editConfig([&](JamiAccountConfig& conf){
+    editConfig([&](JamiAccountConfig& conf) {
         conf.receipt.clear();
         conf.receiptSignature.clear();
     });
@@ -1026,8 +1042,9 @@ JamiAccount::revokeDevice(const std::string& password, const std::string& device
     return accountManager_
         ->revokeDevice(password, device, [this, device](AccountManager::RevokeDeviceResult result) {
             emitSignal<libjami::ConfigurationSignal::DeviceRevocationEnded>(getAccountID(),
-                                                                          device,
-                                                                          static_cast<int>(result));
+                                                                            device,
+                                                                            static_cast<int>(
+                                                                                result));
         });
     return true;
 }
@@ -1078,8 +1095,8 @@ JamiAccount::loadAccount(const std::string& archive_password,
                     }
                     // Update client.
                     emitSignal<libjami::ConfigurationSignal::ContactRemoved>(shared->getAccountID(),
-                                                                           uri,
-                                                                           banned);
+                                                                             uri,
+                                                                             banned);
                 }
             });
         },
@@ -1093,10 +1110,10 @@ JamiAccount::loadAccount(const std::string& archive_password,
             if (conversationId.empty()) {
                 // Old path
                 emitSignal<libjami::ConfigurationSignal::IncomingTrustRequest>(getAccountID(),
-                                                                             conversationId,
-                                                                             uri,
-                                                                             payload,
-                                                                             received);
+                                                                               conversationId,
+                                                                               uri,
+                                                                               payload,
+                                                                               received);
                 return;
             }
             // Here account can be initializing
@@ -1232,18 +1249,17 @@ JamiAccount::loadAccount(const std::string& archive_password,
                 ip_utils::getDeviceName(),
                 std::move(creds),
                 [this, migrating, hasPassword](const AccountInfo& info,
-                                  const std::map<std::string, std::string>& config,
-                                  std::string&& receipt,
-                                  std::vector<uint8_t>&& receipt_signature) {
+                                               const std::map<std::string, std::string>& config,
+                                               std::string&& receipt,
+                                               std::vector<uint8_t>&& receipt_signature) {
                     JAMI_INFO("[Account %s] Auth success!", getAccountID().c_str());
 
                     fileutils::check_dir(idPath_.c_str(), 0700);
 
                     auto id = info.identity;
-                    editConfig([&](JamiAccountConfig& conf){
-                        std::tie(conf.tlsPrivateKeyFile, conf.tlsCertificateFile) = saveIdentity(id,
-                                                                                        idPath_,
-                                                                                        DEVICE_ID_PATH);
+                    editConfig([&](JamiAccountConfig& conf) {
+                        std::tie(conf.tlsPrivateKeyFile, conf.tlsCertificateFile)
+                            = saveIdentity(id, idPath_, DEVICE_ID_PATH);
                         conf.tlsPassword = {};
                         conf.archiveHasPassword = hasPassword;
                         if (not conf.managerUri.empty()) {
@@ -1253,11 +1269,13 @@ JamiAccount::loadAccount(const std::string& archive_password,
                         conf.username = info.accountId;
                         conf.deviceName = accountManager_->getAccountDeviceName();
 
-                        auto nameServerIt = config.find(libjami::Account::ConfProperties::RingNS::URI);
+                        auto nameServerIt = config.find(
+                            libjami::Account::ConfProperties::RingNS::URI);
                         if (nameServerIt != config.end() && !nameServerIt->second.empty()) {
                             conf.nameServer = nameServerIt->second;
                         }
-                        auto displayNameIt = config.find(libjami::Account::ConfProperties::DISPLAYNAME);
+                        auto displayNameIt = config.find(
+                            libjami::Account::ConfProperties::DISPLAYNAME);
                         if (displayNameIt != config.end() && !displayNameIt->second.empty()) {
                             conf.displayName = displayNameIt->second;
                         }
@@ -1270,9 +1288,8 @@ JamiAccount::loadAccount(const std::string& archive_password,
                         Migration::setState(getAccountID(), Migration::State::SUCCESS);
                     }
                     if (not info.photo.empty() or not config_->displayName.empty())
-                        emitSignal<libjami::ConfigurationSignal::AccountProfileReceived>(getAccountID(),
-                                                                                       config_->displayName,
-                                                                                       info.photo);
+                        emitSignal<libjami::ConfigurationSignal::AccountProfileReceived>(
+                            getAccountID(), config_->displayName, info.photo);
                     setRegistrationState(RegistrationState::UNREGISTERED);
                     convModule()->loadConversations();
                     doRegister();
@@ -1350,9 +1367,9 @@ JamiAccount::lookupAddress(const std::string& addr)
         accountManager_->lookupAddress(
             addr, [acc, addr](const std::string& result, NameDirectory::Response response) {
                 emitSignal<libjami::ConfigurationSignal::RegisteredNameFound>(acc,
-                                                                            (int) response,
-                                                                            addr,
-                                                                            result);
+                                                                              (int) response,
+                                                                              addr,
+                                                                              result);
             });
 }
 
@@ -1379,9 +1396,8 @@ JamiAccount::registerName(const std::string& password, const std::string& name)
                 if (response == NameDirectory::RegistrationResponse::success) {
                     if (auto this_ = w.lock()) {
                         this_->registeredName_ = name;
-                        this_->editConfig([&](JamiAccountConfig& config){
-                            config.registeredName = name;
-                        });
+                        this_->editConfig(
+                            [&](JamiAccountConfig& config) { config.registeredName = name; });
                         emitSignal<libjami::ConfigurationSignal::VolatileDetailsChanged>(
                             this_->accountID_, this_->getVolatileAccountDetails());
                     }
@@ -1400,9 +1416,9 @@ JamiAccount::searchUser(const std::string& query)
             [acc = getAccountID(), query](const jami::NameDirectory::SearchResult& result,
                                           jami::NameDirectory::Response response) {
                 jami::emitSignal<libjami::ConfigurationSignal::UserSearchEnded>(acc,
-                                                                              (int) response,
-                                                                              query,
-                                                                              result);
+                                                                                (int) response,
+                                                                                query,
+                                                                                result);
             });
     return false;
 }
@@ -1705,9 +1721,9 @@ JamiAccount::onTrackedBuddyOffline(const dht::InfoHash& contactId)
 {
     JAMI_DBG("Buddy %s offline", contactId.toString().c_str());
     emitSignal<libjami::PresenceSignal::NewBuddyNotification>(getAccountID(),
-                                                            contactId.toString(),
-                                                            0,
-                                                            "");
+                                                              contactId.toString(),
+                                                              0,
+                                                              "");
 }
 
 void
@@ -1743,18 +1759,16 @@ JamiAccount::doRegister_()
                     if (response == NameDirectory::Response::found) {
                         if (this_->registeredName_ != result) {
                             this_->registeredName_ = result;
-                            this_->editConfig([&](JamiAccountConfig& config){
-                                config.registeredName = result;
-                            });
+                            this_->editConfig(
+                                [&](JamiAccountConfig& config) { config.registeredName = result; });
                             emitSignal<libjami::ConfigurationSignal::VolatileDetailsChanged>(
                                 this_->accountID_, this_->getVolatileAccountDetails());
                         }
                     } else if (response == NameDirectory::Response::notFound) {
                         if (not this_->registeredName_.empty()) {
                             this_->registeredName_.clear();
-                            this_->editConfig([&](JamiAccountConfig& config){
-                                config.registeredName.clear();
-                            });
+                            this_->editConfig(
+                                [&](JamiAccountConfig& config) { config.registeredName.clear(); });
                             emitSignal<libjami::ConfigurationSignal::VolatileDetailsChanged>(
                                 this_->accountID_, this_->getVolatileAccountDetails());
                         }
@@ -1824,8 +1838,8 @@ JamiAccount::doRegister_()
                 auto now = std::chrono::duration_cast<std::chrono::milliseconds>(
                                std::chrono::steady_clock::now().time_since_epoch())
                                .count();
-                jami::emitSignal<libjami::ConfigurationSignal::MessageSend>(std::to_string(now) + " "
-                                                                          + std::string(tmp));
+                jami::emitSignal<libjami::ConfigurationSignal::MessageSend>(
+                    std::to_string(now) + " " + std::string(tmp));
             };
             context.logger = std::make_shared<dht::Logger>(log_all, log_all, silent);
 #else
@@ -1995,8 +2009,8 @@ JamiAccount::doRegister_()
                     if (isVCard)
                         cb = [peerId, accountId = getAccountID()](const std::string& path) {
                             emitSignal<libjami::ConfigurationSignal::ProfileReceived>(accountId,
-                                                                                    peerId,
-                                                                                    path);
+                                                                                      peerId,
+                                                                                      path);
                         };
 
                     libjami::DataTransferInfo info;
@@ -2174,21 +2188,23 @@ JamiAccount::convModule()
                         cb({});
                         return;
                     }
-                    shared->connectionManager_
-                        ->connectDevice(DeviceId(deviceId),
-                                        "git://" + deviceId + "/" + convId,
-                                        [shared, cb, convId](std::shared_ptr<ChannelSocket> socket,
-                                                             const DeviceId&) {
-                                            if (socket) {
-                                                socket->onShutdown(
-                                                    [shared, deviceId = socket->deviceId(), convId] {
-                                                        shared->removeGitSocket(deviceId, convId);
-                                                    });
-                                                if (!cb(socket))
-                                                    socket->shutdown();
-                                            } else
-                                                cb({});
-                                        }, false, false, type);
+                    shared->connectionManager_->connectDevice(
+                        DeviceId(deviceId),
+                        "git://" + deviceId + "/" + convId,
+                        [shared, cb, convId](std::shared_ptr<ChannelSocket> socket,
+                                             const DeviceId&) {
+                            if (socket) {
+                                socket->onShutdown([shared, deviceId = socket->deviceId(), convId] {
+                                    shared->removeGitSocket(deviceId, convId);
+                                });
+                                if (!cb(socket))
+                                    socket->shutdown();
+                            } else
+                                cb({});
+                        },
+                        false,
+                        false,
+                        type);
                 });
             },
             [this](auto&& convId, auto&& contactUri, bool accept) {
@@ -2347,11 +2363,10 @@ JamiAccount::setCertificateStatus(const std::string& cert_id,
 {
     bool done = accountManager_ ? accountManager_->setCertificateStatus(cert_id, status) : false;
     if (done) {
-        dht_->findCertificate(dht::InfoHash(cert_id), [](const std::shared_ptr<dht::crypto::Certificate>& crt) {});
-        emitSignal<libjami::ConfigurationSignal::CertificateStateChanged>(getAccountID(),
-                                                                        cert_id,
-                                                                        tls::TrustStore::statusToStr(
-                                                                            status));
+        dht_->findCertificate(dht::InfoHash(cert_id),
+                              [](const std::shared_ptr<dht::crypto::Certificate>& crt) {});
+        emitSignal<libjami::ConfigurationSignal::CertificateStateChanged>(
+            getAccountID(), cert_id, tls::TrustStore::statusToStr(status));
     }
     return done;
 }
@@ -2551,9 +2566,7 @@ JamiAccount::loadCachedProxyServer(std::function<void(const std::string& proxy)>
 {
     const auto& conf = config();
     if (conf.proxyEnabled and proxyServerCached_.empty()) {
-        JAMI_DEBUG("[Account {:s}] loading DHT proxy URL: {:s}",
-                 getAccountID(),
-                 conf.proxyListUrl);
+        JAMI_DEBUG("[Account {:s}] loading DHT proxy URL: {:s}", getAccountID(), conf.proxyListUrl);
         if (conf.proxyListUrl.empty()) {
             cb(getDhtProxyServer(conf.proxyServer));
         } else {
@@ -3013,19 +3026,24 @@ JamiAccount::sendMessage(const std::string& to,
         ctx->confirmation = confirm;
 
         try {
-            auto res = sendSIPMessage(
-                conn, to, ctx.release(), token, payloads, [](void* token, pjsip_event* event) {
-                    std::shared_ptr<TextMessageCtx> c {(TextMessageCtx*) token};
-                    auto code = event->body.tsx_state.tsx->status_code;
-                    runOnMainThread([c=std::move(c), code]() {
-                        if (c) {
-                            auto acc = c->acc.lock();
-                            if (not acc)
-                                return;
-                            acc->onSIPMessageSent(std::move(c), code);
-                        }
-                    });
-                });
+            auto res = sendSIPMessage(conn,
+                                      to,
+                                      ctx.release(),
+                                      token,
+                                      payloads,
+                                      [](void* token, pjsip_event* event) {
+                                          std::shared_ptr<TextMessageCtx> c {
+                                              (TextMessageCtx*) token};
+                                          auto code = event->body.tsx_state.tsx->status_code;
+                                          runOnMainThread([c = std::move(c), code]() {
+                                              if (c) {
+                                                  auto acc = c->acc.lock();
+                                                  if (not acc)
+                                                      return;
+                                                  acc->onSIPMessageSent(std::move(c), code);
+                                              }
+                                          });
+                                      });
             if (!res) {
                 if (!onlyConnected)
                     messageEngine_.onMessageSent(to, token, false);
@@ -3163,7 +3181,8 @@ JamiAccount::sendMessage(const std::string& to,
             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";
+                    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();
@@ -3214,9 +3233,9 @@ JamiAccount::onIsComposing(const std::string& conversationId,
     try {
         const std::string fromUri {parseJamiUri(peer)};
         emitSignal<libjami::ConfigurationSignal::ComposingStatusChanged>(accountID_,
-                                                                       conversationId,
-                                                                       peer,
-                                                                       isWriting ? 1 : 0);
+                                                                         conversationId,
+                                                                         peer,
+                                                                         isWriting ? 1 : 0);
     } catch (...) {
         JAMI_ERR("[Account %s] Can't parse URI: %s", getAccountID().c_str(), peer.c_str());
     }
@@ -3379,9 +3398,10 @@ JamiAccount::startAccountDiscovery()
                               v.accountId.to_c_str());
                     // Send Added Peer and corrsponding accoundID
                     emitSignal<libjami::PresenceSignal::NearbyPeerNotification>(getAccountID(),
-                                                                              v.accountId.toString(),
-                                                                              0,
-                                                                              v.displayName);
+                                                                                v.accountId
+                                                                                    .toString(),
+                                                                                0,
+                                                                                v.displayName);
                 }
                 dp.cleanupTask = Manager::instance().scheduler().scheduleIn(
                     [w = weak(), p = v.accountId, a = v.displayName] {
@@ -3726,7 +3746,7 @@ JamiAccount::requestSIPConnection(const std::string& peerId,
         deviceId,
         "sip",
         [w = weak(), id = std::move(id), pc = std::move(pc)](std::shared_ptr<ChannelSocket> socket,
-                                             const DeviceId&) {
+                                                             const DeviceId&) {
             if (socket)
                 return;
             auto shared = w.lock();
@@ -3920,7 +3940,7 @@ JamiAccount::cacheSIPConnection(std::shared_ptr<ChannelSocket>&& socket,
 
     // Convert to SIP transport
     auto onShutdown = [w = weak(), peerId, key, socket]() {
-        runOnMainThread([w=std::move(w), peerId, key, socket] {
+        runOnMainThread([w = std::move(w), peerId, key, socket] {
             auto shared = w.lock();
             if (!shared)
                 return;
@@ -4051,10 +4071,8 @@ JamiAccount::sendFile(const std::string& conversationId,
                               std::move(value),
                               replyTo,
                               true,
-                              [accId = shared->getAccountID(),
-                               conversationId,
-                               tid,
-                               path](const std::string& commitId) {
+                              [accId = shared->getAccountID(), conversationId, tid, path](
+                                  const std::string& commitId) {
                                   // Create a symlink to answer to re-ask
                                   auto filelinkPath = fileutils::get_data_dir() + DIR_SEPARATOR_STR
                                                       + accId + DIR_SEPARATOR_STR
diff --git a/test/unitTest/conversation/call.cpp b/test/unitTest/conversation/call.cpp
index 6596946eaf..d4bed4932e 100644
--- a/test/unitTest/conversation/call.cpp
+++ b/test/unitTest/conversation/call.cpp
@@ -39,6 +39,7 @@ struct ConvData
 {
     std::string id {};
     bool requestReceived {false};
+    bool needsHost {false};
     bool conferenceChanged {false};
     bool conferenceRemoved {false};
     std::string hostState {};
@@ -78,6 +79,8 @@ private:
     void testJoinFinishedCallForbidden();
     void testUsePreference();
     void testJoinWhileActiveCall();
+    void testCallSelfIfDefaultHost();
+    void testNeedsHost();
 
     CPPUNIT_TEST_SUITE(ConversationCallTest);
     CPPUNIT_TEST(testActiveCalls);
@@ -88,6 +91,8 @@ private:
     CPPUNIT_TEST(testJoinFinishedCallForbidden);
     CPPUNIT_TEST(testUsePreference);
     CPPUNIT_TEST(testJoinWhileActiveCall);
+    CPPUNIT_TEST(testCallSelfIfDefaultHost);
+    CPPUNIT_TEST(testNeedsHost);
     CPPUNIT_TEST_SUITE_END();
 };
 
@@ -98,7 +103,7 @@ ConversationCallTest::setUp()
 {
     // Init daemon
     libjami::init(
-        libjami::InitFlag(libjami::libjami_FLAG_DEBUG | libjami::libjami_FLAG_CONSOLE_LOG));
+        libjami::InitFlag(libjami::LIBJAMI_FLAG_DEBUG | libjami::LIBJAMI_FLAG_CONSOLE_LOG));
     if (not Manager::instance().initialized)
         CPPUNIT_ASSERT(libjami::start("jami-sample.yml"));
 
@@ -173,6 +178,18 @@ ConversationCallTest::connectSignals()
                     carlaData_.requestReceived = true;
                 cv.notify_one();
             }));
+    confHandlers.insert(libjami::exportable_callback<libjami::ConfigurationSignal::NeedsHost>(
+        [&](const std::string& accountId, const std::string& /*conversationId*/) {
+            if (accountId == aliceId)
+                aliceData_.needsHost = true;
+            if (accountId == bobId)
+                bobData_.needsHost = true;
+            if (accountId == bob2Id)
+                bob2Data_.needsHost = true;
+            if (accountId == carlaId)
+                carlaData_.needsHost = true;
+            cv.notify_one();
+        }));
     confHandlers.insert(libjami::exportable_callback<libjami::CallSignal::ConferenceChanged>(
         [&](const std::string& accountId, const std::string&, const std::string&) {
             if (accountId == aliceId)
@@ -320,12 +337,12 @@ ConversationCallTest::testActiveCalls3Peers()
         return lastCommitIsCall(aliceData_) && lastCommitIsCall(bobData_)
                && lastCommitIsCall(carlaData_);
     });
-
+    auto confId = bobData_.messages.rbegin()->at("confId");
     auto destination = fmt::format("rdv:{}/{}/{}/{}",
                                    bobData_.id,
                                    bobData_.messages.rbegin()->at("uri"),
                                    bobData_.messages.rbegin()->at("device"),
-                                   bobData_.messages.rbegin()->at("confId"));
+                                   confId);
 
     aliceData_.conferenceChanged = false;
     libjami::placeCallWithMedia(bobId, destination, {});
@@ -339,7 +356,7 @@ ConversationCallTest::testActiveCalls3Peers()
     });
 
     // get 3 participants
-    auto callList = libjami::getParticipantList(aliceId, bobData_.messages.rbegin()->at("confId"));
+    auto callList = libjami::getParticipantList(aliceId, confId);
     CPPUNIT_ASSERT(callList.size() == 3);
 
     // get active calls = 1
@@ -349,7 +366,7 @@ ConversationCallTest::testActiveCalls3Peers()
     aliceData_.messages.clear();
     bobData_.messages.clear();
     carlaData_.messages.clear();
-    Manager::instance().hangupCall(aliceId, callId);
+    Manager::instance().hangupConference(aliceId, confId);
 
     // should get message
     cv.wait_for(lk, 30s, [&]() {
@@ -417,7 +434,7 @@ ConversationCallTest::testRejoinCall()
                                    bobData_.id,
                                    bobData_.messages.rbegin()->at("uri"),
                                    bobData_.messages.rbegin()->at("device"),
-                                   bobData_.messages.rbegin()->at("confId"));
+                                   confId);
 
     aliceData_.conferenceChanged = false;
     auto bobCall = libjami::placeCallWithMedia(bobId, destination, {});
@@ -457,7 +474,7 @@ ConversationCallTest::testRejoinCall()
     aliceData_.messages.clear();
     bobData_.messages.clear();
     carlaData_.messages.clear();
-    Manager::instance().hangupCall(aliceId, callId);
+    Manager::instance().hangupConference(aliceId, confId);
 
     // should get message
     cv.wait_for(lk, 30s, [&]() {
@@ -821,11 +838,110 @@ ConversationCallTest::testJoinWhileActiveCall()
     aliceData_.messages.clear();
     bobData_.messages.clear();
     aliceData_.conferenceChanged = false;
-    Manager::instance().hangupCall(bobId, bobCallId);
+    Manager::instance().hangupCall(aliceId, callId);
 
     CPPUNIT_ASSERT(!cv.wait_for(lk, 10s, [&]() { return aliceData_.conferenceRemoved; }));
 }
 
+void
+ConversationCallTest::testCallSelfIfDefaultHost()
+{
+    enableCarla();
+    connectSignals();
+    auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
+    auto aliceUri = aliceAccount->getUsername();
+    auto aliceDevice = std::string(aliceAccount->currentDeviceId());
+    auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
+    auto bobUri = bobAccount->getUsername();
+    // Start conversation
+    libjami::startConversation(aliceId);
+    cv.wait_for(lk, 30s, [&]() { return !aliceData_.id.empty(); });
+    libjami::addConversationMember(aliceId, aliceData_.id, bobUri);
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData_.requestReceived; }));
+    aliceData_.messages.clear();
+    libjami::acceptConversationRequest(bobId, aliceData_.id);
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
+        return !bobData_.id.empty() && !aliceData_.messages.empty();
+    }));
+    // Update preferences
+    aliceData_.messages.clear();
+    bobData_.messages.clear();
+    auto lastCommitIsProfile = [&](const auto& data) {
+        return !data.messages.empty()
+               && data.messages.rbegin()->at("type") == "application/update-profile";
+    };
+    libjami::updateConversationInfos(aliceId,
+                                     aliceData_.id,
+                                     std::map<std::string, std::string> {
+                                         {"rdvAccount", aliceUri},
+                                         {"rdvDevice", aliceDevice},
+                                     });
+    // should get message
+    cv.wait_for(lk, 30s, [&]() {
+        return lastCommitIsProfile(aliceData_) && lastCommitIsProfile(bobData_);
+    });
+    // start call
+    aliceData_.messages.clear();
+    bobData_.messages.clear();
+    auto callId = libjami::placeCallWithMedia(aliceId, "swarm:" + aliceData_.id, {});
+    auto lastCommitIsCall = [&](const auto& data) {
+        return !data.messages.empty()
+               && data.messages.rbegin()->at("type") == "application/call-history+json";
+    };
+    // should get message
+    cv.wait_for(lk, 30s, [&]() {
+        return lastCommitIsCall(aliceData_) && lastCommitIsCall(bobData_);
+    });
+    auto confId = aliceData_.messages.rbegin()->at("confId");
+    // Alice should be the host
+    CPPUNIT_ASSERT(aliceAccount->getConference(confId));
+    Manager::instance().hangupConference(aliceId, confId);
+}
+void
+ConversationCallTest::testNeedsHost()
+{
+    enableCarla();
+    connectSignals();
+    auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
+    auto aliceUri = aliceAccount->getUsername();
+    auto aliceDevice = std::string(aliceAccount->currentDeviceId());
+    auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
+    auto bobUri = bobAccount->getUsername();
+    // Start conversation
+    libjami::startConversation(aliceId);
+    cv.wait_for(lk, 30s, [&]() { return !aliceData_.id.empty(); });
+    libjami::addConversationMember(aliceId, aliceData_.id, bobUri);
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData_.requestReceived; }));
+    aliceData_.messages.clear();
+    libjami::acceptConversationRequest(bobId, aliceData_.id);
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
+        return !bobData_.id.empty() && !aliceData_.messages.empty();
+    }));
+    // Update preferences
+    aliceData_.messages.clear();
+    bobData_.messages.clear();
+    auto lastCommitIsProfile = [&](const auto& data) {
+        return !data.messages.empty()
+               && data.messages.rbegin()->at("type") == "application/update-profile";
+    };
+    libjami::updateConversationInfos(aliceId,
+                                     aliceData_.id,
+                                     std::map<std::string, std::string> {
+                                         {"rdvAccount", aliceUri},
+                                         {"rdvDevice", aliceDevice},
+                                     });
+    // should get message
+    cv.wait_for(lk, 30s, [&]() {
+        return lastCommitIsProfile(aliceData_) && lastCommitIsProfile(bobData_);
+    });
+    // Disable Host
+    Manager::instance().sendRegister(aliceId, false);
+    // start call
+    auto callId = libjami::placeCallWithMedia(bobId, "swarm:" + aliceData_.id, {});
+    // should get message
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData_.needsHost; }));
+}
+
 } // namespace test
 } // namespace jami
 
-- 
GitLab