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
|