LCOV - code coverage report
Current view: top level - test/unitTest/account_archive - migration.cpp (source / functions) Hit Total Coverage
Test: jami-coverage-filtered.info Lines: 224 224 100.0 %
Date: 2024-05-02 09:40:27 Functions: 37 37 100.0 %

          Line data    Source code
       1             : /*
       2             :  *  Copyright (C) 2022-2024 Savoir-faire Linux Inc.
       3             :  *  Author: Sébastien Blin <sebastien.blin@savoirfairelinux.com>
       4             :  *
       5             :  *  This program is free software; you can redistribute it and/or modify
       6             :  *  it under the terms of the GNU General Public License as published by
       7             :  *  the Free Software Foundation; either version 3 of the License, or
       8             :  *  (at your option) any later version.
       9             :  *
      10             :  *  This program is distributed in the hope that it will be useful,
      11             :  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
      12             :  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      13             :  *  GNU General Public License for more details.
      14             :  *
      15             :  *  You should have received a copy of the GNU General Public License
      16             :  *  along with this program. If not, see <https://www.gnu.org/licenses/>.
      17             :  */
      18             : 
      19             : #include <cppunit/TestAssert.h>
      20             : #include <cppunit/TestFixture.h>
      21             : #include <cppunit/extensions/HelperMacros.h>
      22             : 
      23             : #include <condition_variable>
      24             : #include <string>
      25             : #include <filesystem>
      26             : #include <fstream>
      27             : #include <streambuf>
      28             : #include <fmt/format.h>
      29             : 
      30             : #include "manager.h"
      31             : #include "jamidht/accountarchive.h"
      32             : #include "jamidht/jamiaccount.h"
      33             : #include "../../test_runner.h"
      34             : #include "account_const.h"
      35             : #include "common.h"
      36             : #include "fileutils.h"
      37             : 
      38             : using namespace std::string_literals;
      39             : using namespace std::literals::chrono_literals;
      40             : using namespace libjami::Account;
      41             : 
      42             : namespace jami {
      43             : namespace test {
      44             : 
      45             : class MigrationTest : public CppUnit::TestFixture
      46             : {
      47             : public:
      48           3 :     MigrationTest()
      49           3 :     {
      50             :         // Init daemon
      51           3 :         libjami::init(libjami::InitFlag(libjami::LIBJAMI_FLAG_DEBUG | libjami::LIBJAMI_FLAG_CONSOLE_LOG));
      52           3 :         if (not Manager::instance().initialized)
      53           1 :             CPPUNIT_ASSERT(libjami::start("dring-sample.yml"));
      54           3 :     }
      55           6 :     ~MigrationTest() { libjami::fini(); }
      56           2 :     static std::string name() { return "AccountArchive"; }
      57             :     void setUp();
      58             :     void tearDown();
      59             : 
      60             :     std::string aliceId;
      61             :     std::string bobId;
      62             :     std::string bob2Id;
      63             : 
      64             : private:
      65             :     void testLoadExpiredAccount();
      66             :     void testMigrationAfterRevokation();
      67             :     void testExpiredDeviceInSwarm();
      68             : 
      69           2 :     CPPUNIT_TEST_SUITE(MigrationTest);
      70           1 :     CPPUNIT_TEST(testLoadExpiredAccount);
      71           1 :     CPPUNIT_TEST(testMigrationAfterRevokation);
      72           1 :     CPPUNIT_TEST(testExpiredDeviceInSwarm);
      73           4 :     CPPUNIT_TEST_SUITE_END();
      74             : };
      75             : 
      76             : CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(MigrationTest, MigrationTest::name());
      77             : 
      78             : void
      79           3 : MigrationTest::setUp()
      80             : {
      81           3 :     auto actors = load_actors("actors/alice-bob.yml");
      82           3 :     aliceId = actors["alice"];
      83           3 :     bobId = actors["bob"];
      84           3 : }
      85             : 
      86             : void
      87           3 : MigrationTest::tearDown()
      88             : {
      89           6 :     auto bobArchive = std::filesystem::current_path().string() + "/bob.gz";
      90           3 :     std::remove(bobArchive.c_str());
      91             : 
      92           3 :     if (!bob2Id.empty())
      93           4 :         wait_for_removal_of({aliceId, bob2Id, bobId});
      94             :     else
      95           6 :         wait_for_removal_of({aliceId, bobId});
      96           3 : }
      97             : 
      98             : void
      99           1 : MigrationTest::testLoadExpiredAccount()
     100             : {
     101           3 :     wait_for_announcement_of({aliceId, bobId});
     102           1 :     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     103           1 :     auto aliceUri = aliceAccount->getUsername();
     104           1 :     auto aliceDevice = std::string(aliceAccount->currentDeviceId());
     105             : 
     106             :     // Get alice's expiration
     107           2 :     auto archivePath = fileutils::get_data_dir() / aliceAccount->getAccountID() / "archive.gz";
     108           2 :     auto devicePath = fileutils::get_data_dir() / aliceAccount->getAccountID() / "ring_device.crt";
     109           1 :     auto archive = AccountArchive(archivePath);
     110           2 :     auto deviceCert = dht::crypto::Certificate(fileutils::loadFile(devicePath));
     111           1 :     auto deviceExpiration = deviceCert.getExpiration();
     112           1 :     auto accountExpiration = archive.id.second->getExpiration();
     113             : 
     114             :     // Update validity
     115           1 :     CPPUNIT_ASSERT(aliceAccount->setValidity("", "", {}, 9));
     116           1 :     archive = AccountArchive(archivePath);
     117           1 :     deviceCert = dht::crypto::Certificate(fileutils::loadFile(devicePath));
     118           1 :     auto newDeviceExpiration = deviceCert.getExpiration();
     119           1 :     auto newAccountExpiration = archive.id.second->getExpiration();
     120             : 
     121             :     // Check expiration is changed
     122           1 :     CPPUNIT_ASSERT(newDeviceExpiration < deviceExpiration
     123             :                    && newAccountExpiration < accountExpiration);
     124             : 
     125             :     // Sleep and wait that certificate is expired
     126           1 :     std::this_thread::sleep_for(10s);
     127             : 
     128             :     // reload account, check migration signals
     129           1 :     std::mutex mtx;
     130           1 :     std::unique_lock lk {mtx};
     131           1 :     std::condition_variable cv;
     132           1 :     std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
     133           1 :     auto aliceMigrated = false;
     134           1 :     confHandlers.insert(libjami::exportable_callback<libjami::ConfigurationSignal::MigrationEnded>(
     135           1 :         [&](const std::string& accountId, const std::string& state) {
     136           1 :             if (accountId == aliceId && state == "SUCCESS") {
     137           1 :                 aliceMigrated = true;
     138             :             }
     139           1 :             cv.notify_one();
     140           1 :         }));
     141           1 :     libjami::registerSignalHandlers(confHandlers);
     142             : 
     143             :     // Check migration is triggered and expiration updated
     144           1 :     aliceAccount->forceReloadAccount();
     145           3 :     CPPUNIT_ASSERT(cv.wait_for(lk, 15s, [&]() { return aliceMigrated; }));
     146             : 
     147           1 :     archive = AccountArchive(archivePath);
     148           1 :     deviceCert = dht::crypto::Certificate(fileutils::loadFile(devicePath));
     149           1 :     deviceExpiration = deviceCert.getExpiration();
     150           1 :     accountExpiration = archive.id.second->getExpiration();
     151           1 :     CPPUNIT_ASSERT(newDeviceExpiration < deviceExpiration
     152             :                    && newAccountExpiration < accountExpiration);
     153           1 :     CPPUNIT_ASSERT(aliceUri == aliceAccount->getUsername());
     154           1 :     CPPUNIT_ASSERT(aliceDevice == aliceAccount->currentDeviceId());
     155           1 : }
     156             : 
     157             : void
     158           1 : MigrationTest::testMigrationAfterRevokation()
     159             : {
     160           3 :     wait_for_announcement_of({aliceId, bobId});
     161           1 :     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
     162           1 :     auto bobUri = bobAccount->getUsername();
     163             : 
     164             :     // Generate bob2
     165           1 :     std::mutex mtx;
     166           1 :     std::unique_lock lk {mtx};
     167           1 :     std::condition_variable cv;
     168             : 
     169             :     // Add second device for Bob
     170           1 :     std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
     171           2 :     auto bobArchive = std::filesystem::current_path().string() + "/bob.gz";
     172           1 :     std::remove(bobArchive.c_str());
     173           1 :     bobAccount->exportArchive(bobArchive);
     174             : 
     175           2 :     std::map<std::string, std::string> details = libjami::getAccountTemplate("RING");
     176           1 :     details[ConfProperties::TYPE] = "RING";
     177           1 :     details[ConfProperties::DISPLAYNAME] = "BOB2";
     178           1 :     details[ConfProperties::ALIAS] = "BOB2";
     179           1 :     details[ConfProperties::UPNP_ENABLED] = "true";
     180           1 :     details[ConfProperties::ARCHIVE_PASSWORD] = "";
     181           1 :     details[ConfProperties::ARCHIVE_PIN] = "";
     182           1 :     details[ConfProperties::ARCHIVE_PATH] = bobArchive;
     183             : 
     184           1 :     auto deviceRevoked = false;
     185           1 :     confHandlers.insert(
     186           2 :         libjami::exportable_callback<libjami::ConfigurationSignal::DeviceRevocationEnded>(
     187           1 :             [&](const std::string& accountId, const std::string&, int status) {
     188           1 :                 if (accountId == bobId && status == 0)
     189           1 :                     deviceRevoked = true;
     190           1 :                 cv.notify_one();
     191           1 :             }));
     192           1 :     auto bobMigrated = false;
     193           1 :     confHandlers.insert(libjami::exportable_callback<libjami::ConfigurationSignal::MigrationEnded>(
     194           1 :         [&](const std::string& accountId, const std::string& state) {
     195           1 :             if (accountId == bob2Id && state == "SUCCESS") {
     196           1 :                 bobMigrated = true;
     197             :             }
     198           1 :             cv.notify_one();
     199           1 :         }));
     200           1 :     auto knownChanged = false;
     201           1 :     confHandlers.insert(libjami::exportable_callback<libjami::ConfigurationSignal::KnownDevicesChanged>(
     202           6 :         [&](const std::string& accountId, auto devices) {
     203           6 :             if (accountId == bobId && devices.size() == 2)
     204           2 :                 knownChanged = true;
     205           6 :             cv.notify_one();
     206           6 :         }));
     207           1 :     libjami::registerSignalHandlers(confHandlers);
     208             : 
     209           1 :     bob2Id = Manager::instance().addAccount(details);
     210           1 :     auto bob2Account = Manager::instance().getAccount<JamiAccount>(bob2Id);
     211             : 
     212           5 :     CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&]() { return knownChanged; }));
     213             : 
     214             :     // Revoke bob2
     215           1 :     auto bob2Device = std::string(bob2Account->currentDeviceId());
     216           1 :     bobAccount->revokeDevice(bob2Device);
     217           2 :     CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&]() { return deviceRevoked; }));
     218             :     // Note: bob2 will need some seconds to get the revokation list
     219           1 :     std::this_thread::sleep_for(10s);
     220             : 
     221             :     // Check migration is triggered and expiration updated
     222           1 :     bob2Account->forceReloadAccount();
     223           4 :     CPPUNIT_ASSERT(cv.wait_for(lk, 15s, [&]() { return bobMigrated; }));
     224             :     // Because the device was revoked, a new ID must be generated there
     225           1 :     CPPUNIT_ASSERT(bob2Device != bob2Account->currentDeviceId());
     226           1 : }
     227             : 
     228             : void
     229           1 : MigrationTest::testExpiredDeviceInSwarm()
     230             : {
     231           1 :     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     232             : 
     233           1 :     std::mutex mtx;
     234           1 :     std::unique_lock lk {mtx};
     235           1 :     std::condition_variable cv;
     236           1 :     std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
     237           1 :     auto messageBobReceived = 0, messageAliceReceived = 0;
     238           1 :     confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::MessageReceived>(
     239           8 :         [&](const std::string& accountId,
     240             :             const std::string& /* conversationId */,
     241             :             std::map<std::string, std::string> /*message*/) {
     242           8 :             if (accountId == bobId) {
     243           3 :                 messageBobReceived += 1;
     244             :             } else {
     245           5 :                 messageAliceReceived += 1;
     246             :             }
     247           8 :             cv.notify_one();
     248           8 :         }));
     249           1 :     bool requestReceived = false;
     250           1 :     confHandlers.insert(
     251           2 :         libjami::exportable_callback<libjami::ConversationSignal::ConversationRequestReceived>(
     252           1 :             [&](const std::string& /*accountId*/,
     253             :                 const std::string& /* conversationId */,
     254             :                 std::map<std::string, std::string> /*metadatas*/) {
     255           1 :                 requestReceived = true;
     256           1 :                 cv.notify_one();
     257           1 :             }));
     258           1 :     bool conversationReady = false;
     259           1 :     confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
     260           2 :         [&](const std::string& accountId, const std::string& /* conversationId */) {
     261           2 :             if (accountId == bobId) {
     262           1 :                 conversationReady = true;
     263           1 :                 cv.notify_one();
     264             :             }
     265           2 :         }));
     266           1 :     auto aliceMigrated = false;
     267           1 :     confHandlers.insert(libjami::exportable_callback<libjami::ConfigurationSignal::MigrationEnded>(
     268           2 :         [&](const std::string& accountId, const std::string& state) {
     269           2 :             if (accountId == aliceId && state == "SUCCESS") {
     270           2 :                 aliceMigrated = true;
     271             :             }
     272           2 :             cv.notify_one();
     273           2 :         }));
     274           1 :     bool aliceStopped = false, aliceAnnounced = false, aliceRegistered = false, aliceRegistering = false;
     275           1 :     confHandlers.insert(
     276           2 :         libjami::exportable_callback<libjami::ConfigurationSignal::VolatileDetailsChanged>(
     277          20 :             [&](const std::string&, const std::map<std::string, std::string>&) {
     278          20 :                 auto details = aliceAccount->getVolatileAccountDetails();
     279          40 :                 auto daemonStatus = details[libjami::Account::ConfProperties::Registration::STATUS];
     280          20 :                 if (daemonStatus == "UNREGISTERED")
     281           3 :                     aliceStopped = true;
     282          17 :                 else if (daemonStatus == "REGISTERED")
     283          10 :                     aliceRegistered = true;
     284           7 :                 else if (daemonStatus == "TRYING")
     285           5 :                     aliceRegistering = true;
     286          40 :                 auto announced = details[libjami::Account::VolatileProperties::DEVICE_ANNOUNCED];
     287          20 :                 if (announced == "true")
     288           2 :                     aliceAnnounced = true;
     289          20 :                 cv.notify_one();
     290          20 :             }));
     291           1 :     libjami::registerSignalHandlers(confHandlers);
     292             : 
     293             :     // NOTE: We must update certificate before announcing, else, there will be several
     294             :     // certificates on the DHT
     295             : 
     296           4 :     CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&]() { return aliceRegistering; }));
     297           1 :     auto aliceDevice = std::string(aliceAccount->currentDeviceId());
     298           1 :     CPPUNIT_ASSERT(aliceAccount->setValidity("", "", {}, 90));
     299           1 :     auto now = std::chrono::system_clock::now();
     300           1 :     aliceRegistered = false;
     301           1 :     aliceAccount->forceReloadAccount();
     302           6 :     CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&]() { return aliceRegistered; }));
     303           1 :     CPPUNIT_ASSERT(aliceAccount->currentDeviceId() == aliceDevice);
     304             : 
     305           1 :     aliceStopped = false;
     306           1 :     Manager::instance().sendRegister(aliceId, false);
     307           3 :     CPPUNIT_ASSERT(cv.wait_for(lk, 15s, [&]() { return aliceStopped; }));
     308           1 :     aliceAnnounced = false;
     309           1 :     Manager::instance().sendRegister(aliceId, true);
     310           8 :     CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&]() { return aliceAnnounced; }));
     311             : 
     312           1 :     CPPUNIT_ASSERT(aliceAccount->currentDeviceId() == aliceDevice);
     313             : 
     314             :     // Create conversation
     315           1 :     auto convId = libjami::startConversation(aliceId);
     316             : 
     317           1 :     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
     318           1 :     auto bobUri = bobAccount->getUsername();
     319           1 :     libjami::addConversationMember(aliceId, convId, bobUri);
     320           4 :     CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&]() { return requestReceived; }));
     321             : 
     322           1 :     messageAliceReceived = 0;
     323           1 :     libjami::acceptConversationRequest(bobId, convId);
     324           3 :     CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&]() { return conversationReady; }));
     325             : 
     326             :     // Assert that repository exists
     327           2 :     auto repoPath = fileutils::get_data_dir() / bobAccount->getAccountID()
     328           4 :                     / "conversations" / convId;
     329           1 :     CPPUNIT_ASSERT(std::filesystem::is_directory(repoPath));
     330             :     // Wait that alice sees Bob
     331           3 :     cv.wait_for(lk, 20s, [&]() { return messageAliceReceived == 1; });
     332             : 
     333           1 :     messageBobReceived = 0;
     334           1 :     libjami::sendMessage(aliceId, convId, "hi"s, "");
     335           4 :     CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&]() { return messageBobReceived == 1; }));
     336             : 
     337             :     // Wait for certificate to expire
     338           1 :     std::this_thread::sleep_until(now + 100s);
     339             :     // Check migration is triggered and expiration updated
     340           1 :     aliceAnnounced = false;
     341           1 :     aliceAccount->forceReloadAccount();
     342           8 :     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceAnnounced; }));
     343           1 :     CPPUNIT_ASSERT(aliceAccount->currentDeviceId() == aliceDevice);
     344             : 
     345             :     // check that certificate in conversation is expired
     346           3 :     auto devicePath = repoPath / "devices" / fmt::format("{}.crt", aliceAccount->currentDeviceId());
     347           1 :     CPPUNIT_ASSERT(std::filesystem::is_regular_file(devicePath));
     348           2 :     auto cert = dht::crypto::Certificate(fileutils::loadFile(devicePath));
     349           1 :     now = std::chrono::system_clock::now();
     350           1 :     CPPUNIT_ASSERT(cert.getExpiration() < now);
     351             : 
     352             :     // Resend a new message
     353           1 :     messageBobReceived = 0;
     354           1 :     libjami::sendMessage(aliceId, convId, "hi again"s, "");
     355           4 :     CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&]() { return messageBobReceived == 1; }));
     356           1 :     messageAliceReceived = 0;
     357           1 :     libjami::sendMessage(bobId, convId, "hi!"s, "");
     358           4 :     CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&]() { return messageAliceReceived == 1; }));
     359             : 
     360             :     // check that certificate in conversation is updated
     361           1 :     CPPUNIT_ASSERT(std::filesystem::is_regular_file(devicePath));
     362           1 :     cert = dht::crypto::Certificate(fileutils::loadFile(devicePath));
     363           1 :     now = std::chrono::system_clock::now();
     364           1 :     CPPUNIT_ASSERT(cert.getExpiration() > now);
     365             : 
     366             :     // Check same device as begining
     367           1 :     CPPUNIT_ASSERT(aliceAccount->currentDeviceId() == aliceDevice);
     368           1 : }
     369             : 
     370             : } // namespace test
     371             : } // namespace jami
     372             : 
     373           1 : RING_TEST_RUNNER(jami::test::MigrationTest::name())

Generated by: LCOV version 1.14