LCOV - code coverage report
Current view: top level - foo/src/jamidht - conversationrepository.h (source / functions) Hit Total Coverage
Test: jami-coverage-filtered.info Lines: 16 18 88.9 %
Date: 2026-01-22 10:39:23 Functions: 8 8 100.0 %

          Line data    Source code
       1             : /*
       2             :  *  Copyright (C) 2004-2026 Savoir-faire Linux Inc.
       3             :  *
       4             :  *  This program is free software: you can redistribute it and/or modify
       5             :  *  it under the terms of the GNU General Public License as published by
       6             :  *  the Free Software Foundation, either version 3 of the License, or
       7             :  *  (at your option) any later version.
       8             :  *
       9             :  *  This program is distributed in the hope that it will be useful,
      10             :  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
      11             :  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
      12             :  *  GNU General Public License for more details.
      13             :  *
      14             :  *  You should have received a copy of the GNU General Public License
      15             :  *  along with this program. If not, see <https://www.gnu.org/licenses/>.
      16             :  */
      17             : #pragma once
      18             : #include "def.h"
      19             : #include "vcard.h"
      20             : #include "git_def.h"
      21             : 
      22             : #include <opendht/default_types.h>
      23             : 
      24             : #include <optional>
      25             : #include <memory>
      26             : #include <string>
      27             : #include <vector>
      28             : 
      29             : namespace jami {
      30             : 
      31             : using DeviceId = dht::PkId;
      32             : 
      33             : constexpr auto EFETCH = 1;
      34             : constexpr auto EINVALIDMODE = 2;
      35             : constexpr auto EVALIDFETCH = 3;
      36             : constexpr auto EUNAUTHORIZED = 4;
      37             : constexpr auto ECOMMIT = 5;
      38             : 
      39             : class JamiAccount;
      40             : 
      41             : struct LogOptions
      42             : {
      43             :     std::string from {};
      44             :     std::string to {};
      45             :     uint64_t nbOfCommits {0};  // maximum number of commits wanted
      46             :     bool skipMerge {false};    // Do not include merge commits in the log. Used by the module to get
      47             :                                // last interaction without potential merges
      48             :     bool includeTo {false};    // If we want or not the "to" commit [from-to] or [from-to)
      49             :     bool fastLog {false};      // Do not parse content, used mostly to count
      50             :     bool logIfNotFound {true}; // Add a warning in the log if commit is not found
      51             : 
      52             :     std::string authorUri {}; // filter commits from author
      53             : };
      54             : 
      55             : struct Filter
      56             : {
      57             :     std::string author;
      58             :     std::string lastId;
      59             :     std::string regexSearch;
      60             :     std::string type;
      61             :     int64_t after {0};
      62             :     int64_t before {0};
      63             :     uint32_t maxResult {0};
      64             :     bool caseSensitive {false};
      65             : };
      66             : 
      67             : struct GitAuthor
      68             : {
      69             :     std::string name {};
      70             :     std::string email {};
      71             : };
      72             : 
      73             : enum class ConversationMode : int { ONE_TO_ONE = 0, ADMIN_INVITES_ONLY, INVITES_ONLY, PUBLIC };
      74             : 
      75             : struct ConversationCommit
      76             : {
      77             :     std::string id {};
      78             :     std::vector<std::string> parents {};
      79             :     GitAuthor author {};
      80             :     std::vector<uint8_t> signed_content {};
      81             :     std::vector<uint8_t> signature {};
      82             :     std::string commit_msg {};
      83             :     std::string linearized_parent {};
      84             :     int64_t timestamp {0};
      85             : };
      86             : 
      87             : enum class MemberRole { ADMIN = 0, MEMBER, INVITED, BANNED, LEFT };
      88             : 
      89             : namespace MemberPath {
      90             : 
      91             : static const std::filesystem::path ADMINS {"admins"};
      92             : static const std::filesystem::path MEMBERS {"members"};
      93             : static const std::filesystem::path INVITED {"invited"};
      94             : static const std::filesystem::path BANNED {"banned"};
      95             : static const std::filesystem::path DEVICES {"devices"};
      96             : 
      97             : } // namespace MemberPath
      98             : 
      99             : struct ConversationMember
     100             : {
     101             :     std::string uri;
     102             :     MemberRole role;
     103             : 
     104         710 :     std::map<std::string, std::string> map() const
     105             :     {
     106         710 :         std::string rolestr;
     107         710 :         if (role == MemberRole::ADMIN) {
     108         427 :             rolestr = "admin";
     109         283 :         } else if (role == MemberRole::MEMBER) {
     110         234 :             rolestr = "member";
     111          49 :         } else if (role == MemberRole::INVITED) {
     112          42 :             rolestr = "invited";
     113           7 :         } else if (role == MemberRole::BANNED) {
     114           7 :             rolestr = "banned";
     115           0 :         } else if (role == MemberRole::LEFT) {
     116           0 :             rolestr = "left"; // For one to one
     117             :         }
     118             : 
     119             :         return {
     120         710 :             {"uri",  uri    },
     121             :             {"role", rolestr}
     122        2840 :         };
     123         710 :     }
     124       13993 :     MSGPACK_DEFINE(uri, role)
     125             : };
     126             : 
     127             : enum class CallbackResult { Skip, Break, Ok };
     128             : 
     129             : using PreConditionCb = std::function<CallbackResult(const std::string&, const GitAuthor&, const GitCommit&)>;
     130             : using PostConditionCb = std::function<bool(const std::string&, const GitAuthor&, ConversationCommit&)>;
     131             : using OnMembersChanged = std::function<void(const std::set<std::string>&)>;
     132             : 
     133             : /**
     134             :  * This class gives access to the git repository that represents the conversation
     135             :  */
     136             : class LIBJAMI_TEST_EXPORT ConversationRepository
     137             : {
     138             : public:
     139             : #ifdef LIBJAMI_TEST
     140             :     static bool DISABLE_RESET; // Some tests inject bad files so resetHard() will break the test
     141             : #endif
     142             :     /**
     143             :      * Creates a new repository, with initial files, where the first commit hash is the conversation id
     144             :      * @param account       The related account
     145             :      * @param mode          The wanted mode
     146             :      * @param otherMember   The other uri
     147             :      * @return  the conversation repository object
     148             :      */
     149             :     static LIBJAMI_TEST_EXPORT std::unique_ptr<ConversationRepository> createConversation(
     150             :         const std::shared_ptr<JamiAccount>& account,
     151             :         ConversationMode mode = ConversationMode::INVITES_ONLY,
     152             :         const std::string& otherMember = "");
     153             : 
     154             :     /**
     155             :      * Clones a conversation on a remote device
     156             :      * @note This will use the socket registered for the conversation with JamiAccount::addGitSocket()
     157             :      * @param account           The account getting the conversation
     158             :      * @param deviceId          Remote device
     159             :      * @param conversationId    Conversation to clone
     160             :      */
     161             :     static LIBJAMI_TEST_EXPORT std::pair<std::unique_ptr<ConversationRepository>, std::vector<ConversationCommit>>
     162             :     cloneConversation(const std::shared_ptr<JamiAccount>& account,
     163             :                       const std::string& deviceId,
     164             :                       const std::string& conversationId);
     165             : 
     166             :     /**
     167             :      * Open a conversation repository for an account and an id
     168             :      * @param account       The related account
     169             :      * @param id            The conversation id
     170             :      */
     171             :     ConversationRepository(const std::shared_ptr<JamiAccount>& account, const std::string& id);
     172             :     ~ConversationRepository();
     173             : 
     174             :     /**
     175             :      * Write the certificate in /members and commit the change
     176             :      * @param uri    Member to add
     177             :      * @return the commit id if successful
     178             :      */
     179             :     std::string addMember(const std::string& uri);
     180             : 
     181             :     /**
     182             :      * Fetch a remote repository via the given socket
     183             :      * @note This will use the socket registered for the conversation with JamiAccount::addGitSocket()
     184             :      * @note will create a remote identified by the deviceId
     185             :      * @param remoteDeviceId    Remote device id to fetch
     186             :      * @return if the operation was successful
     187             :      */
     188             :     bool fetch(const std::string& remoteDeviceId);
     189             : 
     190             :     /**
     191             :      * Merge the history of the conversation with another peer
     192             :      * @param uri                    The peer uri
     193             :      * @param disconnectFromPeerCb   Callback to disconnect from peer when banning
     194             :      * @return                       A vector of media maps representing the merged history
     195             :      */
     196             :     std::vector<std::map<std::string, std::string>> mergeHistory(
     197             :         const std::string& uri, std::function<void(const std::string&)>&& disconnectFromPeerCb = {});
     198             : 
     199             :     /**
     200             :      * Retrieve remote head. Can be useful after a fetch operation
     201             :      * @param remoteDeviceId        The remote name
     202             :      * @param branch                Remote branch to check (default: main)
     203             :      * @return the commit id pointed
     204             :      */
     205             :     std::string remoteHead(const std::string& remoteDeviceId, const std::string& branch = "main") const;
     206             : 
     207             :     /**
     208             :      * Return the conversation id
     209             :      */
     210             :     const std::string& id() const;
     211             : 
     212             :     /**
     213             :      * Add a new commit to the conversation
     214             :      * @param msg           The commit message of the commit
     215             :      * @param verifyDevice  If we need to validate that certificates are correct (used for testing)
     216             :      * @return <empty> on failure, else the message id
     217             :      */
     218             :     std::string commitMessage(const std::string& msg, bool verifyDevice = true);
     219             : 
     220             :     std::vector<std::string> commitMessages(const std::vector<std::string>& msgs);
     221             : 
     222             :     /**
     223             :      * Amend a commit message
     224             :      * @param id      The commit to amend
     225             :      * @param msg     The commit message of the commit
     226             :      * @return <empty> on failure, else the message id
     227             :      */
     228             :     std::string amend(const std::string& id, const std::string& msg);
     229             : 
     230             :     /**
     231             :      * Get commits depending on the options we pass
     232             :      * @return a list of commits
     233             :      */
     234             :     std::vector<ConversationCommit> log(const LogOptions& options = {}) const;
     235             :     void log(PreConditionCb&& preCondition,
     236             :              std::function<void(ConversationCommit&&)>&& emplaceCb,
     237             :              PostConditionCb&& postCondition,
     238             :              const std::string& from = "",
     239             :              bool logIfNotFound = true) const;
     240             :     std::optional<ConversationCommit> getCommit(const std::string& commitId, bool logIfNotFound = true) const;
     241             : 
     242             :     /**
     243             :      * Get parent via topological + date sort in branch main of a commit
     244             :      * @param commitId      id to choice
     245             :      */
     246             :     std::optional<std::string> linearizedParent(const std::string& commitId) const;
     247             : 
     248             :     /**
     249             :      * Merge another branch into the main branch
     250             :      * @param merge_id      The reference to merge
     251             :      * @param force         Should be false, skip validateDevice() ; used for test purpose
     252             :      * @return a pair containing if the merge was successful and the merge commit id
     253             :      * generated if one (can be a fast forward merge without commit)
     254             :      */
     255             :     std::pair<bool, std::string> merge(const std::string& merge_id, bool force = false);
     256             : 
     257             :     /**
     258             :      * Get current diff stats between two commits
     259             :      * @param oldId     Old commit
     260             :      * @param newId     Recent commit (empty value will compare to the empty repository)
     261             :      * @note "HEAD" is also accepted as parameter for newId
     262             :      * @return diff stats
     263             :      */
     264             :     std::string diffStats(const std::string& newId, const std::string& oldId = "") const;
     265             : 
     266             :     /**
     267             :      * Get changed files from a git diff
     268             :      * @param diffStats     The stats to analyze
     269             :      * @return get the changed files from a git diff
     270             :      */
     271             :     static std::vector<std::string> changedFiles(std::string_view diffStats);
     272             : 
     273             :     /**
     274             :      * Join a repository
     275             :      * @return commit Id
     276             :      */
     277             :     std::string join();
     278             : 
     279             :     /**
     280             :      * Erase self from repository
     281             :      * @return commit Id
     282             :      */
     283             :     std::string leave();
     284             : 
     285             :     /**
     286             :      * Erase repository
     287             :      */
     288             :     void erase();
     289             : 
     290             :     /**
     291             :      * Get conversation's mode
     292             :      * @return the mode
     293             :      */
     294             :     ConversationMode mode() const;
     295             : 
     296             :     /**
     297             :      * The voting system is divided in two parts. The voting phase where
     298             :      * admins can decide an action (such as kicking someone)
     299             :      * and the resolving phase, when > 50% of the admins voted, we can
     300             :      * considered the vote as finished
     301             :      */
     302             :     /**
     303             :      * Add a vote to kick a device or a user
     304             :      * @param uri       identified of the user/device
     305             :      * @param type      device, members, admins or invited
     306             :      * @return the commit id or empty if failed
     307             :      */
     308             :     std::string voteKick(const std::string& uri, const std::string& type);
     309             :     /**
     310             :      * Add a vote to re-add a user
     311             :      * @param uri       identified of the user
     312             :      * @param type      device, members, admins or invited
     313             :      * @return the commit id or empty if failed
     314             :      */
     315             :     std::string voteUnban(const std::string& uri, const std::string_view type);
     316             :     /**
     317             :      * Validate if a vote is finished
     318             :      * @param uri       identified of the user/device
     319             :      * @param type      device, members, admins or invited
     320             :      * @param voteType  "ban" or "unban"
     321             :      * @return the commit id or empty if failed
     322             :      */
     323             :     std::string resolveVote(const std::string& uri, const std::string_view type, const std::string& voteType);
     324             : 
     325             :     /**
     326             :      * Validate a fetch with remote device
     327             :      * @param remotedevice
     328             :      * @return the validated commits and if an error occurs
     329             :      */
     330             :     std::pair<std::vector<ConversationCommit>, bool> validFetch(const std::string& remoteDevice) const;
     331             : 
     332             :     /**
     333             :      * Validate a clone
     334             :      * @return the validated commits and false if an error occurs
     335             :      */
     336             :     std::pair<std::vector<ConversationCommit>, bool> validClone() const;
     337             : 
     338             :     /**
     339             :      * Verify the signature against the given commit
     340             :      * @param userDevice    the email of the sender (i.e. their device's public key)
     341             :      * @param commitId      the id of the commit
     342             :      */
     343             :     bool isValidUserAtCommit(const std::string& userDevice,
     344             :                              const std::string& commitId,
     345             :                              const git_buf& sig,
     346             :                              const git_buf& sig_data) const;
     347             : 
     348             :     /**
     349             :      * Validate that commits are not malformed
     350             :      * @param commitsToValidate     the list of commits
     351             :      */
     352             :     bool validCommits(const std::vector<ConversationCommit>& commitsToValidate) const;
     353             : 
     354             :     /**
     355             :      * Delete branch with remote
     356             :      * @param remoteDevice
     357             :      */
     358             :     void removeBranchWith(const std::string& remoteDevice);
     359             : 
     360             :     /**
     361             :      * One to one util, get initial members
     362             :      * @return initial members
     363             :      */
     364             :     std::vector<std::string> getInitialMembers() const;
     365             : 
     366             :     /**
     367             :      * Get conversation's members
     368             :      * @return members
     369             :      */
     370             :     std::vector<ConversationMember> members() const;
     371             : 
     372             :     /**
     373             :      * Get conversation's devices
     374             :      * @param ignoreExpired     If we want to ignore expired devices
     375             :      * @return members
     376             :      */
     377             :     std::map<std::string, std::vector<DeviceId>> devices(bool ignoreExpired = true) const;
     378             : 
     379             :     /**
     380             :      * @param filter           If we want to remove one member
     381             :      * @param filteredRoles    If we want to ignore some roles
     382             :      * @return members' uris
     383             :      */
     384             :     std::set<std::string> memberUris(std::string_view filter, const std::set<MemberRole>& filteredRoles) const;
     385             : 
     386             :     /**
     387             :      * To use after a merge with member's events, refresh members knowledge
     388             :      */
     389             :     void refreshMembers() const;
     390             : 
     391             :     void onMembersChanged(OnMembersChanged&& cb);
     392             : 
     393             :     /**
     394             :      * Because conversations can contains non contacts certificates, this methods
     395             :      * loads certificates in conversations into the cert store
     396             :      * @param blocking      if we need to wait that certificates are pinned
     397             :      */
     398             :     void pinCertificates(bool blocking = false);
     399             :     /**
     400             :      * Retrieve the uri from a deviceId
     401             :      * @note used by swarm manager (peersToSyncWith)
     402             :      * @param deviceId
     403             :      * @return corresponding issuer
     404             :      */
     405             :     std::string uriFromDevice(const std::string& deviceId) const;
     406             : 
     407             :     /**
     408             :      * Change repository's infos
     409             :      * @param map       New infos (supported keys: title, description, avatar)
     410             :      * @return the commit id
     411             :      */
     412             :     std::string updateInfos(const std::map<std::string, std::string>& map);
     413             : 
     414             :     /**
     415             :      * Retrieve current infos (title, description, avatar, mode)
     416             :      * @return infos
     417             :      */
     418             :     std::map<std::string, std::string> infos() const;
     419             :     static std::map<std::string, std::string> infosFromVCard(vCard::utils::VCardData&& details);
     420             : 
     421             :     /**
     422             :      * Convert ConversationCommit to MapStringString for the client
     423             :      */
     424             :     std::vector<std::map<std::string, std::string>> convCommitsToMap(
     425             :         const std::vector<ConversationCommit>& commits) const;
     426             :     std::optional<std::map<std::string, std::string>> convCommitToMap(const ConversationCommit& commit) const;
     427             : 
     428             :     /**
     429             :      * Get current HEAD hash
     430             :      */
     431             :     std::string getHead() const;
     432             : 
     433             : private:
     434             :     ConversationRepository() = delete;
     435             :     class Impl;
     436             :     std::unique_ptr<Impl> pimpl_;
     437             : };
     438             : 
     439             : } // namespace jami
     440       13992 : MSGPACK_ADD_ENUM(jami::MemberRole);
     441        3264 : MSGPACK_ADD_ENUM(jami::ConversationMode);

Generated by: LCOV version 1.14