LCOV - code coverage report
Current view: top level - src/jamidht - jamiaccount.h (source / functions) Coverage Total Hit
Test: jami-coverage-filtered.info Lines: 90.0 % 40 36
Test Date: 2026-11-09 10:41:14 Functions: 91.7 % 24 22

            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              : 
      19              : #include "def.h"
      20              : #ifdef HAVE_CONFIG_H
      21              : #include "config.h"
      22              : #endif
      23              : 
      24              : #include "sip/sipaccountbase.h"
      25              : #include "jami/datatransfer_interface.h"
      26              : #include "jamidht/conversation.h"
      27              : #include "data_transfer.h"
      28              : #include "uri.h"
      29              : #include "jamiaccount_config.h"
      30              : 
      31              : #include "noncopyable.h"
      32              : #include "gitserver.h"
      33              : #include "channel_handler.h"
      34              : #include "conversation_module.h"
      35              : #include "sync_module.h"
      36              : #include "conversationrepository.h"
      37              : #include "namedirectory.h"
      38              : 
      39              : #include <dhtnet/diffie-hellman.h>
      40              : #include <dhtnet/tls_session.h>
      41              : #include <dhtnet/multiplexed_socket.h>
      42              : #include <dhtnet/certstore.h>
      43              : #include <dhtnet/connectionmanager.h>
      44              : #include <dhtnet/upnp/mapping.h>
      45              : #include <dhtnet/ip_utils.h>
      46              : #include <dhtnet/fileutils.h>
      47              : 
      48              : #include <opendht/dhtrunner.h>
      49              : #include <opendht/default_types.h>
      50              : #include <opendht/dht_proxy_server.h>
      51              : 
      52              : #include <pjsip/sip_types.h>
      53              : #include <json/json.h>
      54              : 
      55              : #include <chrono>
      56              : #include <functional>
      57              : #include <future>
      58              : #include <list>
      59              : #include <map>
      60              : #include <optional>
      61              : #include <vector>
      62              : #include <filesystem>
      63              : #include <shared_mutex>
      64              : 
      65              : namespace dev {
      66              : template<unsigned N>
      67              : class FixedHash;
      68              : using h160 = FixedHash<20>;
      69              : using Address = h160;
      70              : } // namespace dev
      71              : 
      72              : namespace jami {
      73              : 
      74              : class IceTransport;
      75              : struct Contact;
      76              : struct AccountArchive;
      77              : class DhtPeerConnector;
      78              : class AccountManager;
      79              : struct AccountInfo;
      80              : class SipTransport;
      81              : class ChanneledOutgoingTransfer;
      82              : class SyncModule;
      83              : class PresenceManager;
      84              : namespace test {
      85              : class Account_factoryTest;
      86              : }
      87              : class CollaborativeEditing;
      88              : 
      89              : using SipConnectionKey = std::pair<std::string /* uri */, DeviceId>;
      90              : 
      91              : static constexpr const char MIME_TYPE_IM_COMPOSING[] {"application/im-iscomposing+xml"};
      92              : 
      93              : /**
      94              :  * @brief Ring Account is build on top of SIPAccountBase and uses DHT to handle call connectivity.
      95              :  */
      96              : class JamiAccount : public SIPAccountBase
      97              : {
      98              : public:
      99              :     constexpr static auto ACCOUNT_TYPE = ACCOUNT_TYPE_JAMI;
     100              :     constexpr static const std::pair<uint16_t, uint16_t> DHT_PORT_RANGE {4000, 8888};
     101              :     constexpr static int ICE_STREAMS_COUNT {1};
     102              :     constexpr static int ICE_COMP_COUNT_PER_STREAM {1};
     103              : 
     104         4594 :     std::string_view getAccountType() const override { return ACCOUNT_TYPE; }
     105              : 
     106         9916 :     std::shared_ptr<JamiAccount> shared() { return std::static_pointer_cast<JamiAccount>(shared_from_this()); }
     107              :     std::shared_ptr<JamiAccount const> shared() const
     108              :     {
     109              :         return std::static_pointer_cast<JamiAccount const>(shared_from_this());
     110              :     }
     111        72440 :     std::weak_ptr<JamiAccount> weak() { return std::static_pointer_cast<JamiAccount>(shared_from_this()); }
     112              :     std::weak_ptr<JamiAccount const> weak() const
     113              :     {
     114              :         return std::static_pointer_cast<JamiAccount const>(shared_from_this());
     115              :     }
     116              : 
     117        31881 :     const JamiAccountConfig& config() const { return *static_cast<const JamiAccountConfig*>(&Account::config()); }
     118              : 
     119         1033 :     JamiAccountConfig::Credentials consumeConfigCredentials()
     120              :     {
     121         1033 :         auto* conf = static_cast<JamiAccountConfig*>(config_.get());
     122         1033 :         return std::move(conf->credentials);
     123              :     }
     124              : 
     125              :     void loadConfig() override;
     126              : 
     127              :     /**
     128              :      * Constructor
     129              :      * @param accountID The account identifier
     130              :      */
     131              :     JamiAccount(const std::string& accountId);
     132              : 
     133              :     ~JamiAccount() noexcept;
     134              : 
     135              :     /**
     136              :      * Retrieve volatile details such as recent registration errors
     137              :      * @return std::map< std::string, std::string > The account volatile details
     138              :      */
     139              :     virtual std::map<std::string, std::string> getVolatileAccountDetails() const override;
     140              : 
     141         1011 :     std::unique_ptr<AccountConfig> buildConfig() const override
     142              :     {
     143         1011 :         return std::make_unique<JamiAccountConfig>(getAccountID(), idPath_);
     144              :     }
     145              : 
     146              :     /**
     147              :      * Adds an account id to the list of accounts to track on the DHT for
     148              :      * buddy presence.
     149              :      *
     150              :      * @param buddy_id  The buddy id.
     151              :      */
     152              :     void trackBuddyPresence(const std::string& buddy_id, bool track);
     153              : 
     154              :     /**
     155              :      * Tells for each tracked account id if it has been seen online so far
     156              :      * in the last DeviceAnnouncement::TYPE.expiration minutes.
     157              :      *
     158              :      * @return map of buddy_uri to bool (online or not)
     159              :      */
     160              :     std::map<std::string, bool> getTrackedBuddyPresence() const;
     161              : 
     162              :     void setActiveCodecs(const std::vector<unsigned>& list) override;
     163              : 
     164              :     /**
     165              :      * Connect to the DHT.
     166              :      */
     167              :     void doRegister() override;
     168              : 
     169              :     /**
     170              :      * Disconnect from the DHT.
     171              :      */
     172              :     void doUnregister(bool forceShutdownConnections = false) override;
     173              : 
     174              :     /**
     175              :      * Set the registration state of the specified link
     176              :      * @param state The registration state of underlying VoIPLink
     177              :      */
     178              :     void setRegistrationState(RegistrationState state, int detail_code = 0, const std::string& detail_str = {}) override;
     179              : 
     180              :     /**
     181              :      * @return pj_str_t "From" uri based on account information.
     182              :      * From RFC3261: "The To header field first and foremost specifies the desired
     183              :      * logical" recipient of the request, or the address-of-record of the
     184              :      * user or resource that is the target of this request. [...]  As such, it is
     185              :      * very important that the From URI not contain IP addresses or the FQDN
     186              :      * of the host on which the UA is running, since these are not logical
     187              :      * names."
     188              :      */
     189              :     std::string getFromUri() const override;
     190              : 
     191              :     /**
     192              :      * This method adds the correct scheme, hostname and append
     193              :      * the ;transport= parameter at the end of the uri, in accordance with RFC3261.
     194              :      * It is expected that "port" is present in the internal hostname_.
     195              :      *
     196              :      * @return pj_str_t "To" uri based on @param username
     197              :      * @param username A string formatted as : "username"
     198              :      */
     199              :     std::string getToUri(const std::string& username) const override;
     200              : 
     201              :     /**
     202              :      * In the current version, "srv" uri is obtained in the preformated
     203              :      * way: hostname:port. This method adds the correct scheme and append
     204              :      * the ;transport= parameter at the end of the uri, in accordance with RFC3261.
     205              :      *
     206              :      * @return pj_str_t "server" uri based on @param hostPort
     207              :      * @param hostPort A string formatted as : "hostname:port"
     208              :      */
     209              :     std::string getServerUri() const { return ""; };
     210              : 
     211              :     void setIsComposing(const std::string& conversationUri, bool isWriting) override;
     212              : 
     213              :     bool setMessageDisplayed(const std::string& conversationUri, const std::string& messageId, int status) override;
     214              : 
     215              :     /**
     216              :      * Get the contact header for
     217              :      * @return The contact header based on account information
     218              :      */
     219              :     std::string getContactHeader(const std::shared_ptr<SipTransport>& sipTransport);
     220              : 
     221              :     /* Returns true if the username and/or hostname match this account */
     222              :     MatchRank matches(std::string_view username, std::string_view hostname) const override;
     223              : 
     224              :     /**
     225              :      * Create outgoing SIPCall.
     226              :      * @note Accepts several urls:
     227              :      *          + jami:uri for calling someone
     228              :      *          + swarm:id for calling a group (will host or join if an active call is detected)
     229              :      *          + rdv:id/uri/device/confId to join a specific conference hosted on (uri, device)
     230              :      * @param[in] toUrl The address to call
     231              :      * @param[in] mediaList list of medias
     232              :      * @return A shared pointer on the created call.
     233              :      */
     234              :     std::shared_ptr<Call> newOutgoingCall(std::string_view toUrl,
     235              :                                           const std::vector<libjami::MediaMap>& mediaList) override;
     236              : 
     237              :     /**
     238              :      * Create incoming SIPCall.
     239              :      * @param[in] from The origin of the call
     240              :      * @param mediaList A list of media
     241              :      * @param sipTr: SIP Transport
     242              :      * @return A shared pointer on the created call.
     243              :      */
     244              :     std::shared_ptr<SIPCall> newIncomingCall(const std::string& from,
     245              :                                              const std::vector<libjami::MediaMap>& mediaList,
     246              :                                              const std::shared_ptr<SipTransport>& sipTr = {}) override;
     247              : 
     248              :     void onTextMessage(const std::string& id,
     249              :                        const std::string& from,
     250              :                        const std::shared_ptr<dht::crypto::Certificate>& peerCert,
     251              :                        const std::map<std::string, std::string>& payloads) override;
     252              :     void loadConversation(const std::string& convId);
     253              : 
     254            0 :     virtual bool isTlsEnabled() const override { return true; }
     255          480 :     bool isSrtpEnabled() const override { return true; }
     256              : 
     257              :     bool setCertificateStatus(const std::string& cert_id, dhtnet::tls::TrustStore::PermissionStatus status);
     258              :     bool setCertificateStatus(const std::shared_ptr<crypto::Certificate>& cert,
     259              :                               dhtnet::tls::TrustStore::PermissionStatus status,
     260              :                               bool local = true);
     261              :     std::vector<std::string> getCertificatesByStatus(dhtnet::tls::TrustStore::PermissionStatus status);
     262              : 
     263              :     bool findCertificate(const std::string& id);
     264              :     bool findCertificate(const dht::InfoHash& h,
     265              :                          std::function<void(const std::shared_ptr<dht::crypto::Certificate>&)>&& cb = {});
     266              :     bool findCertificate(const dht::PkId& h,
     267              :                          std::function<void(const std::shared_ptr<dht::crypto::Certificate>&)>&& cb = {});
     268              : 
     269              :     /* contact requests */
     270              :     std::vector<std::map<std::string, std::string>> getTrustRequests() const;
     271              :     // Note: includeConversation used for compatibility test. Do not change
     272              :     bool acceptTrustRequest(const std::string& from, bool includeConversation = true);
     273              :     bool discardTrustRequest(const std::string& from);
     274              :     void declineConversationRequest(const std::string& conversationId);
     275              : 
     276              :     /**
     277              :      * Add contact to the account contact list.
     278              :      * Set confirmed if we know the contact also added us.
     279              :      */
     280              :     void addContact(const std::string& uri, bool confirmed = false);
     281              :     void removeContact(const std::string& uri, bool banned = true);
     282              :     std::vector<std::map<std::string, std::string>> getContacts(bool includeRemoved = false) const;
     283              : 
     284              :     /**
     285              :      * Obtain the list of known devices for this account, with metadata such as
     286              :      * the device label and the time of the last received device sync.
     287              :      * @return a vector of maps with keys "id", "label" and (optionally) "lastSync"
     288              :      *         (seconds since epoch).
     289              :      */
     290              :     std::vector<std::map<std::string, std::string>> getDeviceList() const;
     291              : 
     292              :     ///
     293              :     /// Obtain details about one account contact in serializable form.
     294              :     ///
     295              :     std::map<std::string, std::string> getContactDetails(const std::string& uri) const;
     296              :     std::optional<Contact> getContactInfo(const std::string& uri) const;
     297              : 
     298              :     void sendTrustRequest(const std::string& to, const std::vector<uint8_t>& payload);
     299              :     void sendMessage(const std::string& to,
     300              :                      const std::string& deviceId,
     301              :                      const std::map<std::string, std::string>& payloads,
     302              :                      uint64_t id,
     303              :                      bool retryOnTimeout = true,
     304              :                      bool onlyConnected = false) override;
     305              : 
     306              :     uint64_t sendTextMessage(const std::string& to,
     307              :                              const std::string& deviceId,
     308              :                              const std::map<std::string, std::string>& payloads,
     309              :                              uint64_t refreshToken = 0,
     310              :                              bool onlyConnected = false) override;
     311              :     void sendInstantMessage(const std::string& convId, const std::map<std::string, std::string>& msg);
     312              : 
     313              :     /**
     314              :      * Create and return ICE options.
     315              :      */
     316              :     dhtnet::IceTransportOptions getIceOptions() const override;
     317              :     void getIceOptions(std::function<void(dhtnet::IceTransportOptions&&)> cb) const;
     318              :     dhtnet::IpAddr getPublishedIpAddress(uint16_t family = PF_UNSPEC) const override;
     319              : 
     320              :     /* Devices - existing device */
     321              :     /**
     322              :      * Initiates the process of adding a new device to this account
     323              :      * @param uriProvided The URI provided by the new device to be added
     324              :      * @return A positive operation ID if successful, or a negative value indicating an AddDeviceError:
     325              :      *         - INVALID_URI (-1): The provided URI is invalid
     326              :      *         - ALREADY_LINKING (-2): A device linking operation is already in progress
     327              :      *         - GENERIC (-3): A generic error occurred during the process
     328              :      */
     329              :     int32_t addDevice(const std::string& uriProvided);
     330              :     bool cancelAddDevice(uint32_t op_token);
     331              :     bool confirmAddDevice(uint32_t op_token);
     332              :     /* Devices - new device */
     333              :     bool provideAccountAuthentication(const std::string& credentialsFromUser, const std::string& scheme);
     334              : 
     335              :     /**
     336              :      * Export the archive to a file
     337              :      * @param destinationPath
     338              :      * @param (optional) password, if not provided, will update the contacts only if the archive
     339              :      * doesn't have a password
     340              :      * @return if the archive was exported
     341              :      */
     342              :     bool exportArchive(const std::string& destinationPath,
     343              :                        std::string_view scheme = {},
     344              :                        const std::string& password = {});
     345              :     bool revokeDevice(const std::string& device, std::string_view scheme = {}, const std::string& password = {});
     346              :     std::map<std::string, std::string> getKnownDevices() const;
     347              : 
     348              :     bool isPasswordValid(const std::string& password);
     349              :     std::vector<uint8_t> getPasswordKey(const std::string& password);
     350              : 
     351              :     bool changeArchivePassword(const std::string& password_old, const std::string& password_new);
     352              : 
     353              :     void connectivityChanged() override;
     354              :     void networkInterfaceChanged();
     355              :     bool requestCallRecovery(const std::shared_ptr<SIPCall>& call, bool newNetwork = false);
     356              : 
     357              :     // overloaded methods
     358              :     void flush() override;
     359              : 
     360              :     bool lookupName(const std::string& name);
     361              :     bool lookupAddress(const std::string& address);
     362              :     void registerName(const std::string& name, const std::string& scheme, const std::string& password);
     363              :     bool searchUser(const std::string& nameQuery);
     364              : 
     365              :     /// \return true if the given DHT message identifier has been treated
     366              :     /// \note if message has not been treated yet this method store this id and returns true at
     367              :     /// further calls
     368              :     bool isMessageTreated(dht::Value::Id id);
     369              : 
     370          893 :     std::shared_ptr<dht::DhtRunner> dht() { return dht_; }
     371              : 
     372         5596 :     const dht::crypto::Identity& identity() const { return id_; }
     373              : 
     374         6467 :     PresenceManager* presenceManager() const { return presenceManager_.get(); }
     375              : 
     376              :     void forEachDevice(const dht::InfoHash& to,
     377              :                        std::function<void(const std::shared_ptr<dht::crypto::PublicKey>&)>&& op,
     378              :                        std::function<void(bool)>&& end = {});
     379              : 
     380              :     bool setPushNotificationToken(const std::string& pushDeviceToken = "") override;
     381              :     bool setPushNotificationTopic(const std::string& topic) override;
     382              :     bool setPushNotificationConfig(const std::map<std::string, std::string>& data) override;
     383              : 
     384              :     /**
     385              :      * To be called by clients with relevant data when a push notification is received.
     386              :      */
     387              :     void pushNotificationReceived(const std::string& from, const std::map<std::string, std::string>& data);
     388              : 
     389              :     std::string getUserUri() const override;
     390              : 
     391              :     /**
     392              :      * Get last messages (should be used to retrieve messages when launching the client)
     393              :      * @param base_timestamp
     394              :      */
     395              :     std::vector<libjami::Message> getLastMessages(const uint64_t& base_timestamp) override;
     396              : 
     397              :     /**
     398              :      * Start Publish the Jami Account onto the Network
     399              :      */
     400              :     void startAccountPublish();
     401              : 
     402              :     /**
     403              :      * Start Discovery the Jami Account from the Network
     404              :      */
     405              :     void startAccountDiscovery();
     406              : 
     407              :     void saveConfig() const override;
     408              : 
     409         1024 :     inline void editConfig(std::function<void(JamiAccountConfig& conf)>&& edit)
     410              :     {
     411         2048 :         Account::editConfig([&](AccountConfig& conf) { edit(*static_cast<JamiAccountConfig*>(&conf)); });
     412         1024 :     }
     413              : 
     414              :     /**
     415              :      * Get current discovered peers account id and display name
     416              :      */
     417              :     std::map<std::string, std::string> getNearbyPeers() const override;
     418              : 
     419              :     void sendProfileToPeers();
     420              : 
     421              :     /**
     422              :      * Update the profile vcard and send it to peers
     423              :      * @param displayName Current or new display name
     424              :      * @param avatar Current or new avatar
     425              :      * @param flag  0 for path to avatar, 1 for base64 avatar
     426              :      */
     427              :     void updateProfile(const std::string& displayName,
     428              :                        const std::string& avatar,
     429              :                        const std::string& fileType,
     430              :                        const std::string& botOwner,
     431              :                        int32_t flag) override;
     432              : 
     433              : #ifdef LIBJAMI_TEST
     434            1 :     dhtnet::ConnectionManager& connectionManager() { return *connectionManager_; }
     435              : 
     436              :     /**
     437              :      * Only used for tests, disable sha3sum verification for transfers.
     438              :      * @param newValue
     439              :      */
     440              :     void noSha3sumVerification(bool newValue);
     441              : 
     442            2 :     void publishPresence(bool newValue) { publishPresence_ = newValue; }
     443              : #endif
     444              : 
     445              :     /**
     446              :      * This should be called before flushing the account.
     447              :      * ConnectionManager needs the account to exists
     448              :      */
     449              :     void shutdownConnections();
     450              : 
     451              :     std::string_view currentDeviceId() const;
     452              : 
     453              :     // Received a new commit notification
     454              : 
     455              :     bool handleMessage(const std::shared_ptr<dht::crypto::Certificate>& cert,
     456              :                        const std::string& from,
     457              :                        const std::pair<std::string, std::string>& message) override;
     458              : 
     459              :     void monitor();
     460              :     // conversationId optional
     461              :     std::vector<std::map<std::string, std::string>> getConnectionList(const std::string& conversationId = "");
     462              :     std::vector<std::map<std::string, std::string>> getConversationConnectivity(const std::string& conversationId);
     463              :     std::vector<std::map<std::string, std::string>> getConversationTrackedMembers(const std::string& conversationId);
     464              :     std::vector<std::map<std::string, std::string>> getChannelList(const std::string& connectionId);
     465              : 
     466              :     // File transfer
     467              :     void sendFile(const std::string& conversationId,
     468              :                   const std::filesystem::path& path,
     469              :                   const std::string& name,
     470              :                   const std::string& replyTo);
     471              : 
     472              :     void transferFile(const std::string& conversationId,
     473              :                       const std::string& path,
     474              :                       const std::string& deviceId,
     475              :                       const std::string& fileId,
     476              :                       const std::string& interactionId,
     477              :                       size_t start = 0,
     478              :                       size_t end = 0,
     479              :                       const std::string& sha3Sum = "",
     480              :                       uint64_t lastWriteTime = 0,
     481              :                       std::function<void()> onFinished = {});
     482              : 
     483              :     void askForFileChannel(const std::string& conversationId,
     484              :                            const std::string& deviceId,
     485              :                            const std::string& interactionId,
     486              :                            const std::string& fileId,
     487              :                            size_t start = 0,
     488              :                            size_t end = 0);
     489              : 
     490              :     void askForProfile(const std::string& conversationId, const std::string& deviceId, const std::string& memberUri);
     491              : 
     492              :     /**
     493              :      * Retrieve linked transfer manager
     494              :      * @param id    conversationId or empty for fallback
     495              :      * @return linked transfer manager
     496              :      */
     497              :     std::shared_ptr<TransferManager> dataTransfer(const std::string& id = "");
     498              : 
     499              :     /**
     500              :      *   Used to get the instance of the ConversationModule class which is
     501              :      *  responsible for managing conversations and messages between users.
     502              :      * @param noCreate    whether or not to create a new instance
     503              :      * @return conversationModule instance
     504              :      */
     505              :     ConversationModule* convModule(bool noCreation = false);
     506              :     SyncModule* syncModule();
     507              : 
     508              :     /**
     509              :      * Retrieve the (lazily created) collaborative editing manager, which handles
     510              :      * real-time shared text documents inside this account's conversations.
     511              :      */
     512              :     std::shared_ptr<CollaborativeEditing> collaborativeEditing();
     513              : 
     514              :     /**
     515              :      * Check (via the cache) if we need to send our profile to a specific device
     516              :      * @param peerUri       Uri that will receive the profile
     517              :      * @param deviceId      Device that will receive the profile
     518              :      * @param sha3Sum       SHA3 hash of the profile
     519              :      */
     520              :     // Note: when swarm will be merged, this can be moved in transferManager
     521              :     bool needToSendProfile(const std::string& peerUri, const std::string& deviceId, const std::string& sha3Sum);
     522              :     /**
     523              :      * Send Profile via cached SIP connection
     524              :      * @param convId        Conversation's identifier (can be empty for self profile on sync)
     525              :      * @param peerUri       Uri that will receive the profile
     526              :      * @param deviceId      Device that will receive the profile
     527              :      */
     528              :     void sendProfile(const std::string& convId, const std::string& peerUri, const std::string& deviceId);
     529              :     /**
     530              :      * Send profile via cached SIP connection
     531              :      * @param peerUri       Uri that will receive the profile
     532              :      * @param deviceId      Device that will receive the profile
     533              :      */
     534              :     void sendProfile(const std::string& peerUri, const std::string& deviceId);
     535              :     /**
     536              :      * Clear sent profiles (because of a removed contact or new trust request)
     537              :      * @param peerUri       Uri used to clear cache
     538              :      */
     539              :     void clearProfileCache(const std::string& peerUri);
     540              : 
     541              :     std::filesystem::path profilePath() const;
     542              : 
     543        64873 :     const std::shared_ptr<AccountManager>& accountManager() { return accountManager_; }
     544              : 
     545              :     bool sha3SumVerify() const;
     546              : 
     547              :     /**
     548              :      * Change certificate's validity period
     549              :      * @param pwd       Password for the archive
     550              :      * @param id        Certificate to update ({} for updating the whole chain)
     551              :      * @param validity  New validity
     552              :      * @note forceReloadAccount may be necessary to retrigger the migration
     553              :      */
     554              :     bool setValidity(std::string_view scheme, const std::string& pwd, const dht::InfoHash& id, int64_t validity);
     555              :     /**
     556              :      * Try to reload the account to force the identity to be updated
     557              :      */
     558              :     void forceReloadAccount();
     559              : 
     560              :     void reloadContacts();
     561              : 
     562              :     /**
     563              :      * Make sure appdata/contacts.yml contains correct information
     564              :      * @param removedConv   The current removed conversations
     565              :      */
     566              :     void unlinkConversations(const std::set<std::string>& removedConv);
     567              : 
     568              :     bool isValidAccountDevice(const dht::crypto::Certificate& cert) const;
     569              : 
     570              :     /**
     571              :      * Join incoming call to hosted conference
     572              :      * @param callId        The call to join
     573              :      * @param destination   conversation/uri/device/confId to join
     574              :      */
     575              :     void handleIncomingConversationCall(const std::string& callId, const std::string& destination);
     576              : 
     577              :     /**
     578              :      * The DRT component is composed on some special nodes, that are usually present but not
     579              :      * connected. This kind of node corresponds to devices with push notifications & proxy and are
     580              :      * stored in the mobile nodes
     581              :      */
     582          907 :     bool isMobile() const { return config().proxyEnabled and not config().deviceKey.empty(); }
     583              : 
     584              : #ifdef LIBJAMI_TEST
     585            1 :     std::map<Uri::Scheme, std::unique_ptr<ChannelHandlerInterface>>& channelHandlers() { return channelHandlers_; };
     586              : #endif
     587              : 
     588              :     /**
     589              :      * Ask a device for a realtime channel on a collaborative document.
     590              :      * A refusal (the peer does not have the document open) needs nothing done,
     591              :      * and an accepted socket is handed to CollaborativeEditing by the handler's
     592              :      * onReady, so no callback is taken here.
     593              :      * @param deviceId      The device to dial
     594              :      * @param documentId    The document (repository) id
     595              :      */
     596              :     void connectYdocDevice(const DeviceId& deviceId, const std::string& documentId);
     597              : 
     598        36508 :     dhtnet::tls::CertificateStore& certStore() const { return *certStore_; }
     599              : 
     600              :     /// Returns true if `peerAccountUri` is an active
     601              :     /// contact of this account.
     602              :     bool isContact(const std::string& peerAccountUri) const;
     603              : 
     604         1379 :     class ServiceManager& serviceManager() { return *serviceManager_; }
     605              :     const class ServiceManager& serviceManager() const { return *serviceManager_; }
     606            3 :     bool hasServiceManager() const { return serviceManager_ != nullptr; }
     607              : 
     608              :     /* Service-exposure high-level API. Implemented in jamiaccount.cpp. */
     609              : 
     610              :     /// Send a discovery query to every known device of `peerUri` and return a
     611              :     /// monotonically-increasing request id. Responses are delivered through the
     612              :     /// `libjami::ServiceSignal::PeerServicesReceived` signal with this id as
     613              :     /// first argument.
     614              :     uint32_t queryPeerServices(const std::string& peerUri);
     615              : 
     616              :     /// Open a TCP-tunnel listener on 127.0.0.1:`localPort` (0 = pick a free
     617              :     /// port) that forwards each accepted connection to `serviceId` on
     618              :     /// `peerUri`'s `deviceId`. Returns a tunnel id, or an empty string on
     619              :     /// failure. `serviceName` is purely informational.
     620              :     std::string openServiceTunnel(const std::string& peerUri,
     621              :                                   const std::string& deviceId,
     622              :                                   const std::string& serviceId,
     623              :                                   const std::string& serviceName,
     624              :                                   uint16_t localPort);
     625              : 
     626              :     /// Close a tunnel previously created with openServiceTunnel.
     627              :     bool closeServiceTunnel(const std::string& tunnelId);
     628              : 
     629              :     /// Server-side: shutdown every active inbound tunnel currently serving
     630              :     /// `serviceId`. Called when a local exposed service is removed or
     631              :     /// disabled so that already-established peer connections are torn down.
     632              :     void closeServerTunnelsForService(const std::string& serviceId);
     633              : 
     634              :     /// Snapshot of currently active client tunnels for this account.
     635              :     std::vector<std::map<std::string, std::string>> getActiveServiceTunnels() const;
     636              :     /**
     637              :      * Check if a Device is connected
     638              :      * @param deviceId
     639              :      * @return true if connected
     640              :      */
     641              :     bool isConnectedWith(const DeviceId& deviceId) const;
     642              : 
     643              :     /**
     644              :      * Send a presence note
     645              :      * @param note
     646              :      */
     647              :     void sendPresenceNote(const std::string& note);
     648              : 
     649              : private:
     650              :     NON_COPYABLE(JamiAccount);
     651              : 
     652              :     using clock = std::chrono::system_clock;
     653              :     using time_point = clock::time_point;
     654              : 
     655              :     /**
     656              :      * Private structures
     657              :      */
     658              :     struct PendingCall;
     659              :     struct PendingMessage;
     660              :     struct DiscoveredPeer;
     661              :     class SendMessageContext;
     662              : 
     663            0 :     inline std::string getProxyConfigKey() const
     664              :     {
     665            0 :         const auto& conf = config();
     666            0 :         return dht::InfoHash::get(conf.proxyServer + conf.proxyListUrl).toString();
     667              :     }
     668              : 
     669              :     void scheduleAccountReady() const;
     670              :     AccountManager::OnChangeCallback setupAccountCallbacks();
     671              : 
     672              :     void onContactAdded(const std::string& uri, bool confirmed);
     673              :     void onContactRemoved(const std::string& uri, bool banned);
     674              :     void hangupCallsWith(const std::string& uri);
     675              :     void onIncomingTrustRequest(const std::string& uri,
     676              :                                 const std::string& conversationId,
     677              :                                 const std::vector<uint8_t>& payload,
     678              :                                 TimePoint received,
     679              :                                 TimePoint invited = {});
     680              :     void onKnownDevicesChanged(const std::map<DeviceId, KnownDevice>& devices);
     681              :     void onConversationRequestAccepted(const std::string& conversationId, const std::string& deviceId);
     682              :     void onContactConfirmed(const std::string& uri, const std::string& convFromReq);
     683              : 
     684              :     void conversationNeedsSyncing(std::shared_ptr<SyncMsg>&& syncMsg);
     685              :     uint64_t conversationSendMessage(const std::string& uri,
     686              :                                      const DeviceId& device,
     687              :                                      const std::map<std::string, std::string>& msg,
     688              :                                      uint64_t token = 0);
     689              :     void onConversationNeedSocket(const std::string& convId,
     690              :                                   const std::string& deviceId,
     691              :                                   ChannelCb&& cb,
     692              :                                   const std::string& type,
     693              :                                   bool noNewSocket = false);
     694              :     void onConversationNeedSwarmSocket(const std::string& convId,
     695              :                                        const std::string& deviceId,
     696              :                                        ChannelCb&& cb,
     697              :                                        const std::string& type,
     698              :                                        bool noNewSocket = false);
     699              :     void conversationOneToOneReceive(const std::string& convId, const std::string& from);
     700              : 
     701              :     std::unique_ptr<AccountManager::AccountCredentials> buildAccountCredentials(
     702              :         const JamiAccountConfig& conf,
     703              :         const dht::crypto::Identity& id,
     704              :         const std::string& archive_password_scheme,
     705              :         const std::string& archive_password,
     706              :         const std::string& archive_path,
     707              :         bool& migrating,
     708              :         bool& hasPassword);
     709              : 
     710              :     void onAuthenticationSuccess(bool migrating,
     711              :                                  bool hasPassword,
     712              :                                  const AccountInfo& info,
     713              :                                  const std::map<std::string, std::string>& configMap,
     714              :                                  std::string&& receipt,
     715              :                                  std::vector<uint8_t>&& receiptSignature);
     716              : 
     717              :     static void onAuthenticationError(const std::weak_ptr<JamiAccount>& w,
     718              :                                       bool hadIdentity,
     719              :                                       bool migrating,
     720              :                                       std::string accountId,
     721              :                                       AccountManager::AuthError error,
     722              :                                       const std::string& message);
     723              : 
     724              :     void onPeerConnected(const std::string& peerId, bool connected);
     725              : 
     726              :     void doRegister_();
     727              : 
     728              :     void lookupRegisteredName(const std::string& regName, const NameDirectory::Response& response);
     729              :     dht::DhtRunner::Config initDhtConfig(const JamiAccountConfig& conf);
     730              :     dht::DhtRunner::Context initDhtContext();
     731              :     void onAccountDeviceFound(const std::shared_ptr<dht::crypto::Certificate>& crt);
     732              :     void onAccountDeviceAnnounced();
     733              : 
     734              :     /**
     735              :      * Open a sync connection to one of our account's devices. This wakes the
     736              :      * device up, so it must only be called when there is something new to
     737              :      * synchronize with it (see SyncModule::needsSync / onSyncListChanged).
     738              :      */
     739              :     void connectSyncDevice(const DeviceId& deviceId);
     740              : 
     741              :     /**
     742              :      * React to a contact or conversation-list change (local or learned from a
     743              :      * peer): bump the local sync version, then (re)connect to and push the new
     744              :      * state to the account's other devices that are not up to date. Offline
     745              :      * devices are reached on their next presence announcement.
     746              :      */
     747              :     void onSyncListChanged();
     748              : 
     749              :     bool onICERequest(const DeviceId& deviceId);
     750              : 
     751              :     /**
     752              :      * Pin (or unpin) the account's organization certificate authority in the
     753              :      * trust store according to the allowPeersFromTrusted setting.
     754              :      */
     755              :     void updateTrustedCa();
     756              : 
     757              :     bool onChannelRequest(const std::shared_ptr<dht::crypto::Certificate>& cert, const std::string& name);
     758              :     void onNewDeviceConnection(const std::shared_ptr<dht::crypto::Certificate>& cert);
     759              :     void onConnectionReady(const DeviceId& deviceId,
     760              :                            const std::string& name,
     761              :                            std::shared_ptr<dhtnet::ChannelSocket> channel);
     762              : 
     763         1016 :     const dht::ValueType USER_PROFILE_TYPE = {9, "User profile", std::chrono::hours(24 * 7)};
     764              : 
     765              :     void startOutgoingCall(const std::shared_ptr<SIPCall>& call, const std::string& toUri);
     766              : 
     767              :     void onConnectedOutgoingCall(const std::shared_ptr<SIPCall>& call, const std::string& to_id, dhtnet::IpAddr target);
     768              : 
     769              :     /**
     770              :      * Start a SIP Call
     771              :      * @param call  The current call
     772              :      * @return true if all is correct
     773              :      */
     774              :     bool SIPStartCall(SIPCall& call, const dhtnet::IpAddr& target);
     775              : 
     776              :     /**
     777              :      * Update tracking info when buddy appears offline.
     778              :      */
     779              :     void onTrackedBuddyOffline(const std::string&);
     780              : 
     781              :     /**
     782              :      * Update tracking info when buddy appears offline.
     783              :      */
     784              :     void onTrackedBuddyOnline(const std::string&);
     785              : 
     786              :     /**
     787              :      * Maps require port via UPnP and other async ops
     788              :      */
     789              :     void registerAsyncOps();
     790              :     /**
     791              :      * Add port mapping callback function.
     792              :      */
     793              :     void onPortMappingAdded(uint16_t port_used, bool success);
     794              :     void forEachPendingCall(const DeviceId& deviceId, const std::function<void(const std::shared_ptr<SIPCall>&)>& cb);
     795              : 
     796              :     void loadAccount(const std::string& archive_password_scheme = {},
     797              :                      const std::string& archive_password = {},
     798              :                      const std::string& archive_path = {});
     799              : 
     800              :     std::vector<std::string> loadBootstrap() const;
     801              : 
     802              :     static std::pair<std::string, std::string> saveIdentity(const dht::crypto::Identity& id,
     803              :                                                             const std::filesystem::path& path,
     804              :                                                             const std::string& name);
     805              : 
     806              :     void replyToIncomingIceMsg(const std::shared_ptr<SIPCall>&,
     807              :                                const std::shared_ptr<IceTransport>&,
     808              :                                const std::shared_ptr<IceTransport>&,
     809              :                                const dht::IceCandidates&,
     810              :                                const std::shared_ptr<dht::crypto::Certificate>& from_cert,
     811              :                                const dht::InfoHash& from);
     812              : 
     813              :     void loadCachedUrl(const std::string& url,
     814              :                        const std::filesystem::path& cachePath,
     815              :                        const std::chrono::seconds& cacheDuration,
     816              :                        const std::function<void(const dht::http::Response& response)>& cb);
     817              : 
     818              :     std::string getDhtProxyServer(const std::string& serverList);
     819              :     void loadCachedProxyServer(std::function<void(const std::string&)> cb);
     820              : 
     821              :     void newOutgoingCallHelper(const std::shared_ptr<SIPCall>& call, const Uri& uri);
     822              :     std::shared_ptr<SIPCall> newSwarmOutgoingCallHelper(const Uri& uri, const std::vector<libjami::MediaMap>& mediaList);
     823              :     std::shared_ptr<SIPCall> createSubCall(const std::shared_ptr<SIPCall>& mainCall);
     824              : 
     825              :     std::filesystem::path cachePath_ {};
     826              :     std::filesystem::path dataPath_ {};
     827              : 
     828              :     mutable std::mutex registeredNameMutex_;
     829              :     std::string registeredName_;
     830              : 
     831          911 :     bool setRegisteredName(const std::string& name)
     832              :     {
     833          911 :         std::lock_guard<std::mutex> lock(registeredNameMutex_);
     834          911 :         if (registeredName_ != name) {
     835            1 :             registeredName_ = name;
     836            1 :             return true;
     837              :         }
     838          910 :         return false;
     839          911 :     }
     840         6423 :     std::string getRegisteredName() const
     841              :     {
     842         6423 :         std::lock_guard<std::mutex> lock(registeredNameMutex_);
     843        12846 :         return registeredName_;
     844         6423 :     }
     845              : 
     846              :     std::shared_ptr<dht::Logger> logger_;
     847              :     std::shared_ptr<dhtnet::tls::CertificateStore> certStore_;
     848              : 
     849              :     std::unique_ptr<class ServiceManager> serviceManager_;
     850              : 
     851              :     /// Per-request state for an in-flight queryPeerServices() call. Defined
     852              :     /// out-of-line in jamiaccount.cpp so callers don't need to pull in asio
     853              :     /// just to compile this header.
     854              :     struct PendingSvcQuery;
     855              : 
     856              :     /// Emit the terminal `PeerServicesReceived` signal for `requestId` with
     857              :     /// the given status (matching `libjami::ServiceSignal::PeerServicesStatus`)
     858              :     /// and JSON payload, then drop the request from the pending tables.
     859              :     void finalizeSvcQuery(uint32_t requestId, int status, const std::string& servicesJson);
     860              : 
     861              :     /// Build the JSON array describing `peerUri`'s cached services, tagging each
     862              :     /// entry's "available" flag from the presence system (a device is available
     863              :     /// when it is currently announced online). `forceAvailableDevice`, when
     864              :     /// non-null, is always flagged available -- used right after a device
     865              :     /// answers a discovery query, before its DHT presence announcement has been
     866              :     /// observed. Returns an empty string when the peer has no cached services.
     867              :     std::string buildPeerServicesJson(const std::string& peerUri, const DeviceId* forceAvailableDevice = nullptr);
     868              : 
     869              :     mutable std::mutex pendingSvcQueriesMtx_;
     870              :     std::map<uint32_t, std::shared_ptr<PendingSvcQuery>> pendingSvcQueries_;
     871              : 
     872              :     std::shared_ptr<dht::DhtRunner> dht_ {};
     873              :     std::shared_ptr<AccountManager> accountManager_;
     874              :     dht::crypto::Identity id_ {};
     875              : 
     876              :     std::shared_ptr<dht::DhtProxyServer> dhtProxyServer_;
     877              : 
     878              :     mutable std::mutex messageMutex_ {};
     879              :     std::map<dht::Value::Id, PendingMessage> sentMessages_;
     880              :     dhtnet::fileutils::IdList treatedMessages_;
     881              : 
     882              :     /* tracked buddies presence */
     883              :     std::unique_ptr<PresenceManager> presenceManager_;
     884              :     uint64_t presenceListenerToken_ {0};
     885              :     uint64_t svcPresenceListenerToken_ {0};
     886              : 
     887              :     std::atomic_int syncCnt_ {0};
     888              : 
     889              :     /**
     890              :      * DHT port actually used.
     891              :      * This holds the actual DHT port, which might different from the port
     892              :      * set in the configuration. This can be the case if UPnP is used.
     893              :      */
     894              :     in_port_t dhtPortUsed()
     895              :     {
     896              :         return (upnpCtrl_ and dhtUpnpMapping_.isValid()) ? dhtUpnpMapping_.getExternalPort() : config().dhtPort;
     897              :     }
     898              : 
     899              :     /**
     900              :      * Release the DHT UPnP mapping, if one is currently held, and reset it so that
     901              :      * a subsequent registration requests a new one.
     902              :      */
     903              :     void releaseDhtUpnpMapping();
     904              : 
     905              :     friend class test::Account_factoryTest;
     906              : 
     907              :     /* Current UPNP mapping */
     908              :     dhtnet::upnp::Mapping dhtUpnpMapping_ {dhtnet::upnp::PortType::UDP};
     909              : 
     910              :     /**
     911              :      * Proxy
     912              :      */
     913              :     std::string proxyServerCached_ {};
     914              : 
     915              :     /**
     916              :      * Optional: via_addr construct from received parameters
     917              :      */
     918              :     pjsip_host_port via_addr_ {};
     919              : 
     920              :     pjsip_transport* via_tp_ {nullptr};
     921              : 
     922              :     /** ConnectionManager is thread-safe.
     923              :      * The shared mutex protects the pointer while allowing
     924              :      * multiple threads to access the ConnectionManager concurrently  */
     925              :     mutable std::shared_mutex connManagerMtx_ {};
     926              :     std::unique_ptr<dhtnet::ConnectionManager> connectionManager_;
     927              : 
     928              :     virtual void updateUpnpController() override;
     929              : 
     930              :     std::mutex discoveryMapMtx_;
     931              :     std::shared_ptr<dht::PeerDiscovery> peerDiscovery_;
     932              :     std::map<dht::InfoHash, DiscoveredPeer> discoveredPeers_;
     933              :     std::map<std::string, std::string> discoveredPeerMap_;
     934              : 
     935              :     std::set<std::shared_ptr<dht::http::Request>> requests_;
     936              : 
     937              :     mutable std::mutex sipConnsMtx_ {};
     938              :     struct SipConnection
     939              :     {
     940              :         std::shared_ptr<SipTransport> transport;
     941              :         // Needs to keep track of that channel to access underlying ICE
     942              :         // information, as the SipTransport use a generic transport
     943              :         std::shared_ptr<dhtnet::ChannelSocket> channel;
     944              :     };
     945              :     // NOTE: here we use a vector to avoid race conditions. In fact the contact
     946              :     // can ask for a SIP channel when we are creating a new SIP Channel with this
     947              :     // peer too.
     948              :     std::map<SipConnectionKey, std::vector<SipConnection>> sipConns_;
     949              : 
     950              :     std::mutex pendingCallsMutex_;
     951              :     std::map<DeviceId, std::vector<std::shared_ptr<SIPCall>>> pendingCalls_;
     952              : 
     953              :     std::mutex onConnectionClosedMtx_ {};
     954              :     std::map<DeviceId, std::function<void(const DeviceId&, bool)>> onConnectionClosed_ {};
     955              :     /**
     956              :      * onConnectionClosed contains callbacks that need to be called if a sub call is failing
     957              :      * @param deviceId      The device we are calling
     958              :      * @param eraseDummy    Erase the dummy call (a temporary subcall that must be stop when we will
     959              :      * not create new subcalls)
     960              :      */
     961              :     void callConnectionClosed(const DeviceId& deviceId, bool eraseDummy);
     962              : 
     963              :     /**
     964              :      * Ask a device to open a channeled SIP socket
     965              :      * @param peerId             The contact who owns the device
     966              :      * @param deviceId           The device to ask
     967              :      * @param forceNewConnection If we want a new SIP connection
     968              :      * @param pc                 A pending call to stop if the request fails
     969              :      * @note triggers cacheSIPConnection
     970              :      */
     971              :     void requestSIPConnection(const std::string& peerId,
     972              :                               const DeviceId& deviceId,
     973              :                               const std::string& connectionType,
     974              :                               bool forceNewConnection = false,
     975              :                               const std::shared_ptr<SIPCall>& pc = {});
     976              :     /**
     977              :      * Ask a device for a SIP channel on a new socket, as the connected
     978              :      * sockets may be stale after a network change.
     979              :      * @note triggers cacheSIPConnection on success
     980              :      */
     981              :     void requestRecoverySIPConnection(const DeviceId& deviceId,
     982              :                                       const std::string& connectionType,
     983              :                                       std::function<void(const std::shared_ptr<dhtnet::ChannelSocket>&)>&& onResult);
     984              :     /**
     985              :      * Store a new SIP connection into sipConnections_
     986              :      * @param channel   The new sip channel
     987              :      * @param peerId    The contact who owns the device
     988              :      * @param deviceId  Device linked to that transport
     989              :      */
     990              :     void cacheSIPConnection(std::shared_ptr<dhtnet::ChannelSocket>&& channel,
     991              :                             const std::string& peerId,
     992              :                             const DeviceId& deviceId);
     993              :     /**
     994              :      * Shutdown a SIP connection
     995              :      * @param channel   The channel to close
     996              :      * @param peerId    The contact who owns the device
     997              :      * @param deviceId  Device linked to that transport
     998              :      */
     999              :     void shutdownSIPConnection(const std::shared_ptr<dhtnet::ChannelSocket>& channel,
    1000              :                                const std::string& peerId,
    1001              :                                const DeviceId& deviceId);
    1002              : 
    1003              :     void requestMessageConnection(const std::string& peerId,
    1004              :                                   const DeviceId& deviceId,
    1005              :                                   const std::string& connectionType);
    1006              : 
    1007              :     /**
    1008              :      * Called once per message channel to a device, whichever side opened it.
    1009              :      */
    1010              :     void onMessageChannelReady(const std::string& peerId, const DeviceId& deviceId);
    1011              : 
    1012              :     // File transfers
    1013              :     std::mutex transfersMtx_ {};
    1014              :     std::set<std::string> incomingFileTransfers_ {};
    1015              : 
    1016              :     void onMessageSent(
    1017              :         const std::string& to, uint64_t id, const std::string& deviceId, bool success, bool onlyConnected, bool retry);
    1018              : 
    1019              :     std::mutex gitServersMtx_ {};
    1020              :     std::map<dht::Value::Id, std::unique_ptr<GitServer>> gitServers_ {};
    1021              : 
    1022              :     //// File transfer (for profiles)
    1023              :     std::shared_ptr<TransferManager> nonSwarmTransferManager_;
    1024              : 
    1025              :     std::atomic_bool deviceAnnounced_ {false};
    1026              : 
    1027              :     // Debounce timer coalescing bursts of contact/conversation-list changes
    1028              :     // (e.g. during initial sync) into a single sync-propagation pass.
    1029              :     std::mutex syncListChangedMtx_;
    1030              :     std::shared_ptr<asio::steady_timer> syncListChangedTimer_;
    1031              :     std::atomic_bool noSha3sumVerification_ {false};
    1032              : 
    1033              :     bool publishPresence_ {true};
    1034              : 
    1035              :     std::map<Uri::Scheme, std::unique_ptr<ChannelHandlerInterface>> channelHandlers_ {};
    1036              : 
    1037              :     std::unique_ptr<ConversationModule> convModule_;
    1038              :     std::mutex moduleMtx_;
    1039              :     std::unique_ptr<SyncModule> syncModule_;
    1040              :     std::shared_ptr<CollaborativeEditing> collaborativeEditing_;
    1041              : 
    1042              :     std::mutex rdvMtx_;
    1043              : 
    1044              :     int dhtBoundPort_ {0};
    1045              : 
    1046              :     void initConnectionManager();
    1047              : 
    1048              :     enum class PresenceState : int { DISCONNECTED = 0, AVAILABLE, CONNECTED };
    1049              :     std::map<std::string, PresenceState> presenceState_;
    1050              :     mutable std::mutex presenceStateMtx_;
    1051              :     std::string presenceNote_;
    1052              : };
    1053              : 
    1054              : static inline std::ostream&
    1055              : operator<<(std::ostream& os, const JamiAccount& acc)
    1056              : {
    1057              :     os << "[Account " << acc.getAccountID() << "] ";
    1058              :     return os;
    1059              : }
    1060              : 
    1061              : } // namespace jami
        

Generated by: LCOV version 2.0-1