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 :
18 : #ifdef HAVE_CONFIG_H
19 : #include "config.h"
20 : #endif
21 :
22 : #include "jamiaccount.h"
23 :
24 : #include "logger.h"
25 :
26 : #include "accountarchive.h"
27 : #include "jami_contact.h"
28 : #include "configkeys.h"
29 : #include "contact_list.h"
30 : #include "archive_account_manager.h"
31 : #include "server_account_manager.h"
32 : #include "jamidht/channeled_transport.h"
33 : #include "conversation_channel_handler.h"
34 : #include "sync_channel_handler.h"
35 : #include "message_channel_handler.h"
36 : #include "auth_channel_handler.h"
37 : #include "transfer_channel_handler.h"
38 : #include "swarm/swarm_channel_handler.h"
39 : #include "jami/media_const.h"
40 :
41 : #include "sip/sdp.h"
42 : #include "sip/sipvoiplink.h"
43 : #include "sip/sipcall.h"
44 : #include "sip/siptransport.h"
45 : #include "connectivity/sip_utils.h"
46 :
47 : #include "uri.h"
48 :
49 : #include "client/jami_signal.h"
50 : #include "jami/call_const.h"
51 : #include "jami/account_const.h"
52 :
53 : #include "system_codec_container.h"
54 :
55 : #include "account_schema.h"
56 : #include "manager.h"
57 : #include "connectivity/utf8_utils.h"
58 : #include "connectivity/ip_utils.h"
59 :
60 : #ifdef ENABLE_PLUGIN
61 : #include "plugin/jamipluginmanager.h"
62 : #include "plugin/chatservicesmanager.h"
63 : #endif
64 :
65 : #ifdef ENABLE_VIDEO
66 : #include "libav_utils.h"
67 : #endif
68 : #include "fileutils.h"
69 : #include "string_utils.h"
70 : #include "archiver.h"
71 : #include "data_transfer.h"
72 : #include "json_utils.h"
73 :
74 : #include "libdevcrypto/Common.h"
75 : #include "base64.h"
76 : #include "vcard.h"
77 : #include "im/instant_messaging.h"
78 :
79 : #include <dhtnet/ice_transport.h>
80 : #include <dhtnet/ice_transport_factory.h>
81 : #include <dhtnet/upnp/upnp_control.h>
82 : #include <dhtnet/multiplexed_socket.h>
83 : #include <dhtnet/certstore.h>
84 :
85 : #include <opendht/thread_pool.h>
86 : #include <opendht/peer_discovery.h>
87 : #include <opendht/http.h>
88 :
89 : #include <yaml-cpp/yaml.h>
90 : #include <fmt/format.h>
91 :
92 : #include <unistd.h>
93 :
94 : #include <algorithm>
95 : #include <array>
96 : #include <cctype>
97 : #include <charconv>
98 : #include <cinttypes>
99 : #include <cstdarg>
100 : #include <fstream>
101 : #include <initializer_list>
102 : #include <memory>
103 : #include <regex>
104 : #include <sstream>
105 : #include <string>
106 : #include <system_error>
107 : #include <utility>
108 :
109 : using namespace std::placeholders;
110 :
111 : namespace jami {
112 :
113 : constexpr pj_str_t STR_MESSAGE_ID = jami::sip_utils::CONST_PJ_STR("Message-ID");
114 : static constexpr const char MIME_TYPE_IMDN[] {"message/imdn+xml"};
115 : static constexpr const char MIME_TYPE_PIDF[] {"application/pidf+xml"};
116 : static constexpr const char MIME_TYPE_INVITE_JSON[] {"application/invite+json"};
117 : static constexpr const char DEVICE_ID_PATH[] {"ring_device"};
118 : static constexpr auto TREATED_PATH = "treatedImMessages"sv;
119 :
120 : struct VCardMessageCtx
121 : {
122 : std::shared_ptr<std::atomic_int> success;
123 : int total;
124 : std::string path;
125 : };
126 :
127 : namespace Migration {
128 :
129 : enum class State { // Contains all the Migration states
130 : SUCCESS,
131 : INVALID
132 : };
133 :
134 : std::string
135 4 : mapStateNumberToString(const State migrationState)
136 : {
137 : #define CASE_STATE(X) \
138 : case Migration::State::X: \
139 : return #X
140 :
141 4 : switch (migrationState) {
142 0 : CASE_STATE(INVALID);
143 4 : CASE_STATE(SUCCESS);
144 : }
145 0 : return {};
146 : }
147 :
148 : void
149 4 : setState(const std::string& accountID, const State migrationState)
150 : {
151 4 : emitSignal<libjami::ConfigurationSignal::MigrationEnded>(accountID, mapStateNumberToString(migrationState));
152 4 : }
153 :
154 : } // namespace Migration
155 :
156 : struct JamiAccount::BuddyInfo
157 : {
158 : /* the buddy id */
159 : dht::InfoHash id;
160 :
161 : /* number of devices connected on the DHT */
162 : uint32_t devices_cnt {};
163 :
164 : /* The disposable object to update buddy info */
165 : std::future<size_t> listenToken;
166 :
167 34 : BuddyInfo(dht::InfoHash id)
168 34 : : id(id)
169 34 : {}
170 : };
171 :
172 : struct JamiAccount::PendingCall
173 : {
174 : std::chrono::steady_clock::time_point start;
175 : std::shared_ptr<IceTransport> ice_sp;
176 : std::shared_ptr<IceTransport> ice_tcp_sp;
177 : std::weak_ptr<SIPCall> call;
178 : std::future<size_t> listen_key;
179 : dht::InfoHash call_key;
180 : dht::InfoHash from;
181 : dht::InfoHash from_account;
182 : std::shared_ptr<dht::crypto::Certificate> from_cert;
183 : };
184 :
185 : struct JamiAccount::PendingMessage
186 : {
187 : std::set<DeviceId> to;
188 : };
189 :
190 : struct AccountPeerInfo
191 : {
192 : dht::InfoHash accountId;
193 : std::string displayName;
194 0 : MSGPACK_DEFINE(accountId, displayName)
195 : };
196 :
197 : struct JamiAccount::DiscoveredPeer
198 : {
199 : std::string displayName;
200 : std::unique_ptr<asio::steady_timer> cleanupTimer;
201 : };
202 :
203 : /**
204 : * Track sending state for a single message to one or more devices.
205 : */
206 : class JamiAccount::SendMessageContext
207 : {
208 : public:
209 : using OnComplete = std::function<void(bool, bool)>;
210 14734 : SendMessageContext(OnComplete onComplete)
211 14734 : : onComplete(std::move(onComplete))
212 14734 : {}
213 : /** Track new pending message for device */
214 13051 : bool add(const DeviceId& device)
215 : {
216 13051 : std::lock_guard lk(mtx);
217 26102 : return devices.insert(device).second;
218 13050 : }
219 : /** Call after all messages are sent */
220 14734 : void start()
221 : {
222 14734 : std::unique_lock lk(mtx);
223 14734 : started = true;
224 14734 : checkComplete(lk);
225 14734 : }
226 : /** Complete pending message for device */
227 12813 : bool complete(const DeviceId& device, bool success)
228 : {
229 12813 : std::unique_lock lk(mtx);
230 12815 : if (devices.erase(device) == 0)
231 0 : return false;
232 12812 : ++completeCount;
233 12812 : if (success)
234 12811 : ++successCount;
235 12812 : checkComplete(lk);
236 12815 : return true;
237 12815 : }
238 : bool empty() const
239 : {
240 : std::lock_guard lk(mtx);
241 : return devices.empty();
242 : }
243 2960 : bool pending(const DeviceId& device) const
244 : {
245 2960 : std::lock_guard lk(mtx);
246 5921 : return devices.find(device) != devices.end();
247 2961 : }
248 :
249 : private:
250 : mutable std::mutex mtx;
251 : OnComplete onComplete;
252 : std::set<DeviceId> devices;
253 : unsigned completeCount = 0;
254 : unsigned successCount = 0;
255 : bool started {false};
256 :
257 27545 : void checkComplete(std::unique_lock<std::mutex>& lk)
258 : {
259 27545 : if (started && (devices.empty() || successCount)) {
260 14731 : if (onComplete) {
261 14730 : auto cb = std::move(onComplete);
262 14730 : auto success = successCount != 0;
263 14730 : auto complete = completeCount != 0;
264 14730 : onComplete = {};
265 14730 : lk.unlock();
266 14731 : cb(success, complete);
267 14732 : }
268 : }
269 27547 : }
270 : };
271 :
272 : static const constexpr std::string_view RING_URI_PREFIX = "ring:";
273 : static const constexpr std::string_view JAMI_URI_PREFIX = "jami:";
274 : static const auto PROXY_REGEX = std::regex("(https?://)?([\\w\\.\\-_\\~]+)(:(\\d+)|:\\[(.+)-(.+)\\])?");
275 : static const constexpr std::string_view PEER_DISCOVERY_JAMI_SERVICE = "jami";
276 : const constexpr auto PEER_DISCOVERY_EXPIRATION = std::chrono::minutes(1);
277 :
278 : using ValueIdDist = std::uniform_int_distribution<dht::Value::Id>;
279 :
280 : std::string_view
281 41568 : stripPrefix(std::string_view toUrl)
282 : {
283 41568 : auto dhtf = toUrl.find(RING_URI_PREFIX);
284 41583 : if (dhtf != std::string_view::npos) {
285 0 : dhtf += RING_URI_PREFIX.size();
286 : } else {
287 41583 : dhtf = toUrl.find(JAMI_URI_PREFIX);
288 41589 : if (dhtf != std::string_view::npos) {
289 0 : dhtf += JAMI_URI_PREFIX.size();
290 : } else {
291 41589 : dhtf = toUrl.find("sips:");
292 41572 : dhtf = (dhtf == std::string_view::npos) ? 0 : dhtf + 5;
293 : }
294 : }
295 41572 : while (dhtf < toUrl.length() && toUrl[dhtf] == '/')
296 0 : dhtf++;
297 41574 : return toUrl.substr(dhtf);
298 : }
299 :
300 : std::string_view
301 41548 : parseJamiUri(std::string_view toUrl)
302 : {
303 41548 : auto sufix = stripPrefix(toUrl);
304 41578 : if (sufix.length() < 40)
305 0 : throw std::invalid_argument("Not a valid Jami URI: " + toUrl);
306 :
307 41578 : const std::string_view toUri = sufix.substr(0, 40);
308 41581 : if (std::find_if_not(toUri.cbegin(), toUri.cend(), ::isxdigit) != toUri.cend())
309 1 : throw std::invalid_argument("Not a valid Jami URI: " + toUrl);
310 41604 : return toUri;
311 : }
312 :
313 : static constexpr std::string_view
314 3952 : dhtStatusStr(dht::NodeStatus status)
315 : {
316 : return status == dht::NodeStatus::Connected
317 2009 : ? "connected"sv
318 3952 : : (status == dht::NodeStatus::Connecting ? "connecting"sv : "disconnected"sv);
319 : }
320 :
321 776 : JamiAccount::JamiAccount(const std::string& accountId)
322 : : SIPAccountBase(accountId)
323 776 : , cachePath_(fileutils::get_cache_dir() / accountId)
324 776 : , dataPath_(cachePath_ / "values")
325 1552 : , logger_(Logger::dhtLogger(fmt::format("Account {}", accountId)))
326 776 : , certStore_ {std::make_shared<dhtnet::tls::CertificateStore>(idPath_, logger_)}
327 776 : , dht_(std::make_shared<dht::DhtRunner>())
328 776 : , treatedMessages_(cachePath_ / TREATED_PATH)
329 776 : , connectionManager_ {}
330 3880 : , nonSwarmTransferManager_()
331 776 : {}
332 :
333 1552 : JamiAccount::~JamiAccount() noexcept
334 : {
335 776 : if (dht_)
336 776 : dht_->join();
337 776 : }
338 :
339 : void
340 798 : JamiAccount::shutdownConnections()
341 : {
342 798 : JAMI_DBG("[Account %s] Shutdown connections", getAccountID().c_str());
343 :
344 798 : decltype(gitServers_) gservers;
345 : {
346 798 : std::lock_guard lk(gitServersMtx_);
347 798 : gservers = std::move(gitServers_);
348 798 : }
349 1199 : for (auto& [_id, gs] : gservers)
350 401 : gs->stop();
351 : {
352 798 : std::lock_guard lk(connManagerMtx_);
353 : // Just move destruction on another thread.
354 1596 : dht::ThreadPool::io().run(
355 798 : [conMgr = std::make_shared<decltype(connectionManager_)>(std::move(connectionManager_))] {});
356 798 : connectionManager_.reset();
357 798 : channelHandlers_.clear();
358 798 : }
359 798 : if (convModule_) {
360 693 : convModule_->shutdownConnections();
361 : }
362 :
363 798 : std::lock_guard lk(sipConnsMtx_);
364 798 : sipConns_.clear();
365 798 : }
366 :
367 : void
368 771 : JamiAccount::flush()
369 : {
370 : // Class base method
371 771 : SIPAccountBase::flush();
372 :
373 771 : dhtnet::fileutils::removeAll(cachePath_);
374 771 : dhtnet::fileutils::removeAll(dataPath_);
375 771 : dhtnet::fileutils::removeAll(idPath_, true);
376 771 : }
377 :
378 : std::shared_ptr<SIPCall>
379 90 : JamiAccount::newIncomingCall(const std::string& from,
380 : const std::vector<libjami::MediaMap>& mediaList,
381 : const std::shared_ptr<SipTransport>& sipTransp)
382 : {
383 360 : JAMI_DEBUG("New incoming call from {:s} with {:d} media", from, mediaList.size());
384 :
385 90 : if (sipTransp) {
386 180 : auto call = Manager::instance().callFactory.newSipCall(shared(), Call::CallType::INCOMING, mediaList);
387 90 : call->setPeerUri(JAMI_URI_PREFIX + from);
388 90 : call->setPeerNumber(from);
389 :
390 90 : call->setSipTransport(sipTransp, getContactHeader(sipTransp));
391 :
392 90 : return call;
393 90 : }
394 :
395 0 : JAMI_ERR("newIncomingCall: unable to find matching call for %s", from.c_str());
396 0 : return nullptr;
397 : }
398 :
399 : std::shared_ptr<Call>
400 104 : JamiAccount::newOutgoingCall(std::string_view toUrl, const std::vector<libjami::MediaMap>& mediaList)
401 : {
402 104 : auto uri = Uri(toUrl);
403 104 : if (uri.scheme() == Uri::Scheme::SWARM || uri.scheme() == Uri::Scheme::RENDEZVOUS) {
404 : // NOTE: In this case newOutgoingCall can act as "resumeConference" and just attach the
405 : // host to the current hosted conference. So, no call will be returned in that case.
406 22 : return newSwarmOutgoingCallHelper(uri, mediaList);
407 : }
408 :
409 82 : auto& manager = Manager::instance();
410 82 : std::shared_ptr<SIPCall> call;
411 :
412 : // SIP allows sending empty invites, this use case is not used with Jami accounts.
413 82 : if (not mediaList.empty()) {
414 27 : call = manager.callFactory.newSipCall(shared(), Call::CallType::OUTGOING, mediaList);
415 : } else {
416 55 : JAMI_WARN("Media list is empty, setting a default list");
417 110 : call = manager.callFactory.newSipCall(shared(),
418 : Call::CallType::OUTGOING,
419 110 : MediaAttribute::mediaAttributesToMediaMaps(
420 165 : createDefaultMediaList(isVideoEnabled())));
421 : }
422 :
423 82 : if (not call)
424 0 : return {};
425 :
426 82 : std::shared_lock lkCM(connManagerMtx_);
427 82 : if (!connectionManager_)
428 0 : return {};
429 :
430 82 : connectionManager_->getIceOptions([call, w = weak(), uri = std::move(uri)](auto&& opts) {
431 82 : if (call->isIceEnabled()) {
432 82 : if (not call->createIceMediaTransport(false)
433 164 : or not call->initIceMediaTransport(true, std::forward<dhtnet::IceTransportOptions>(opts))) {
434 0 : return;
435 : }
436 : }
437 82 : auto shared = w.lock();
438 82 : if (!shared)
439 0 : return;
440 82 : JAMI_DBG() << "New outgoing call with " << uri.toString();
441 82 : call->setPeerNumber(uri.authority());
442 82 : call->setPeerUri(uri.toString());
443 :
444 82 : shared->newOutgoingCallHelper(call, uri);
445 82 : });
446 :
447 82 : return call;
448 104 : }
449 :
450 : void
451 82 : JamiAccount::newOutgoingCallHelper(const std::shared_ptr<SIPCall>& call, const Uri& uri)
452 : {
453 328 : JAMI_LOG("[Account {}] Calling peer {}", getAccountID(), uri.authority());
454 : try {
455 82 : startOutgoingCall(call, uri.authority());
456 0 : } catch (const std::invalid_argument&) {
457 0 : auto suffix = stripPrefix(uri.toString());
458 0 : NameDirectory::lookupUri(suffix,
459 0 : config().nameServer,
460 0 : [wthis_ = weak(), call](const std::string& regName,
461 : const std::string& address,
462 : NameDirectory::Response response) {
463 : // we may run inside an unknown thread, but following code must
464 : // be called in main thread
465 0 : runOnMainThread([wthis_, regName, address, response, call]() {
466 0 : if (response != NameDirectory::Response::found) {
467 0 : call->onFailure(PJSIP_SC_NOT_FOUND);
468 0 : return;
469 : }
470 0 : if (auto sthis = wthis_.lock()) {
471 : try {
472 0 : sthis->startOutgoingCall(call, address);
473 0 : } catch (const std::invalid_argument&) {
474 0 : call->onFailure(PJSIP_SC_NOT_FOUND);
475 0 : }
476 : } else {
477 0 : call->onFailure(PJSIP_SC_SERVICE_UNAVAILABLE);
478 0 : }
479 : });
480 0 : });
481 0 : }
482 82 : }
483 :
484 : std::shared_ptr<SIPCall>
485 22 : JamiAccount::newSwarmOutgoingCallHelper(const Uri& uri, const std::vector<libjami::MediaMap>& mediaList)
486 : {
487 88 : JAMI_DEBUG("[Account {}] Calling conversation {}", getAccountID(), uri.authority());
488 : return convModule()
489 66 : ->call(uri.authority(), mediaList, [this, uri](const auto& accountUri, const auto& deviceId, const auto& call) {
490 11 : if (!call)
491 0 : return;
492 11 : std::unique_lock lkSipConn(sipConnsMtx_);
493 12 : for (auto& [key, value] : sipConns_) {
494 1 : if (key.first != accountUri || key.second != deviceId)
495 1 : continue;
496 0 : if (value.empty())
497 0 : continue;
498 0 : auto& sipConn = value.back();
499 :
500 0 : if (!sipConn.channel) {
501 0 : JAMI_WARN("A SIP transport exists without Channel, this is a bug. Please report");
502 0 : continue;
503 : }
504 :
505 0 : auto transport = sipConn.transport;
506 0 : if (!transport or !sipConn.channel)
507 0 : continue;
508 0 : call->setState(Call::ConnectionState::PROGRESSING);
509 :
510 0 : auto remoted_address = sipConn.channel->getRemoteAddress();
511 : try {
512 0 : onConnectedOutgoingCall(call, uri.authority(), remoted_address);
513 0 : return;
514 0 : } catch (const VoipLinkException&) {
515 : // In this case, the main scenario is that SIPStartCall failed because
516 : // the ICE is dead and the TLS session didn't send any packet on that dead
517 : // link (connectivity change, killed by the operating system, etc)
518 : // Here, we don't need to do anything, the TLS will fail and will delete
519 : // the cached transport
520 0 : continue;
521 : }
522 : }
523 11 : lkSipConn.unlock();
524 : {
525 11 : std::lock_guard lkP(pendingCallsMutex_);
526 11 : pendingCalls_[deviceId].emplace_back(call);
527 11 : }
528 :
529 : // Else, ask for a channel (for future calls/text messages)
530 11 : auto type = call->hasVideo() ? "videoCall" : "audioCall";
531 44 : JAMI_WARNING("[call {}] No channeled socket with this peer. Send request", call->getCallId());
532 11 : requestSIPConnection(accountUri, deviceId, type, true, call);
533 33 : });
534 : }
535 :
536 : void
537 10 : JamiAccount::handleIncomingConversationCall(const std::string& callId, const std::string& destination)
538 : {
539 10 : auto split = jami::split_string(destination, '/');
540 10 : if (split.size() != 4)
541 0 : return;
542 10 : auto conversationId = std::string(split[0]);
543 10 : auto accountUri = std::string(split[1]);
544 10 : auto deviceId = std::string(split[2]);
545 10 : auto confId = std::string(split[3]);
546 :
547 10 : if (getUsername() != accountUri || currentDeviceId() != deviceId)
548 0 : return;
549 :
550 : // Avoid concurrent checks in this part
551 10 : std::lock_guard lk(rdvMtx_);
552 10 : auto isNotHosting = !convModule()->isHosting(conversationId, confId);
553 10 : if (confId == "0") {
554 1 : auto currentCalls = convModule()->getActiveCalls(conversationId);
555 1 : if (!currentCalls.empty()) {
556 0 : confId = currentCalls[0]["id"];
557 0 : isNotHosting = false;
558 : } else {
559 1 : confId = callId;
560 4 : JAMI_DEBUG("No active call to join, create conference");
561 : }
562 1 : }
563 10 : auto preferences = convModule()->getConversationPreferences(conversationId);
564 10 : auto canHost = true;
565 : #if defined(__ANDROID__) || defined(__APPLE__)
566 : // By default, mobile devices SHOULD NOT host conferences.
567 : canHost = false;
568 : #endif
569 10 : auto itPref = preferences.find(ConversationPreferences::HOST_CONFERENCES);
570 10 : if (itPref != preferences.end()) {
571 0 : canHost = itPref->second == TRUE_STR;
572 : }
573 :
574 10 : auto call = getCall(callId);
575 10 : if (!call) {
576 0 : JAMI_ERROR("Call {} not found", callId);
577 0 : return;
578 : }
579 :
580 10 : if (isNotHosting && !canHost) {
581 0 : JAMI_DEBUG("Request for hosting a conference declined");
582 0 : Manager::instance().hangupCall(getAccountID(), callId);
583 0 : return;
584 : }
585 : // Due to the fact that in a conference, the host is not the one who
586 : // provides the initial sdp offer, the following block of code is responsible
587 : // for handling the medialist that the host will form his response with.
588 : // We always want the hosts response to be the same length as that of the
589 : // peer who is asking to join (providing the offer). A priori though the peer
590 : // doesn't know what active media streams the host will have so we deal with the
591 : // possible cases here.
592 10 : std::shared_ptr<Conference> conf;
593 10 : std::vector<libjami::MediaMap> currentMediaList;
594 10 : if (!isNotHosting) {
595 7 : conf = getConference(confId);
596 7 : if (!conf) {
597 0 : JAMI_ERROR("[conf:{}] Conference not found", confId);
598 0 : return;
599 : }
600 7 : auto hostMedias = conf->currentMediaList();
601 7 : auto sipCall = std::dynamic_pointer_cast<SIPCall>(call);
602 7 : if (hostMedias.empty()) {
603 0 : currentMediaList = MediaAttribute::mediaAttributesToMediaMaps(
604 0 : createDefaultMediaList(call->hasVideo(), true));
605 7 : } else if (hostMedias.size() < sipCall->getRtpSessionList().size()) {
606 : // First case: host has less media streams than the other person is joining
607 : // with. We need to add video media to the host before accepting the offer
608 : // This can happen if we host an audio call and someone joins with video
609 0 : currentMediaList = hostMedias;
610 0 : currentMediaList.push_back(
611 : {{libjami::Media::MediaAttributeKey::MEDIA_TYPE, libjami::Media::MediaAttributeValue::VIDEO},
612 : {libjami::Media::MediaAttributeKey::ENABLED, TRUE_STR},
613 : {libjami::Media::MediaAttributeKey::MUTED, TRUE_STR},
614 : {libjami::Media::MediaAttributeKey::SOURCE, ""},
615 : {libjami::Media::MediaAttributeKey::LABEL, "video_0"}});
616 : } else {
617 7 : bool hasVideo = false;
618 7 : if (sipCall) {
619 7 : const auto rtpSessions = sipCall->getRtpSessionList();
620 7 : hasVideo = std::any_of(rtpSessions.begin(), rtpSessions.end(), [](const auto& session) {
621 13 : return session && session->getMediaType() == MediaType::MEDIA_VIDEO;
622 : });
623 7 : }
624 : // The second case is that the host has the same or more media
625 : // streams than the person joining. In this case we match all their
626 : // medias to form our offer. They will then potentially join the call without seeing
627 : // seeing all of our medias. For now we deal with this by calling a
628 : // requestmediachange once they've joined.
629 14 : for (const auto& m : conf->currentMediaList()) {
630 : // We only expect to have 1 audio stream, add it.
631 13 : if (m.at(libjami::Media::MediaAttributeKey::MEDIA_TYPE) == libjami::Media::MediaAttributeValue::AUDIO) {
632 7 : currentMediaList.emplace_back(m);
633 6 : } else if (hasVideo
634 12 : && m.at(libjami::Media::MediaAttributeKey::MEDIA_TYPE)
635 6 : == libjami::Media::MediaAttributeValue::VIDEO) {
636 6 : currentMediaList.emplace_back(m);
637 6 : break;
638 : }
639 7 : }
640 : }
641 7 : }
642 10 : Manager::instance().acceptCall(*call, currentMediaList);
643 :
644 10 : if (isNotHosting) {
645 12 : JAMI_DEBUG("Creating conference for swarm {} with ID {}", conversationId, confId);
646 : // Create conference and host it.
647 3 : convModule()->hostConference(conversationId, confId, callId);
648 : } else {
649 28 : JAMI_DEBUG("Adding participant {} for swarm {} with ID {}", callId, conversationId, confId);
650 7 : Manager::instance().addAudio(*call);
651 7 : conf->addSubCall(callId);
652 7 : emitSignal<libjami::CallSignal::ConferenceChanged>(getAccountID(), conf->getConfId(), conf->getStateStr());
653 : }
654 10 : }
655 :
656 : std::shared_ptr<SIPCall>
657 164 : JamiAccount::createSubCall(const std::shared_ptr<SIPCall>& mainCall)
658 : {
659 164 : auto mediaList = MediaAttribute::mediaAttributesToMediaMaps(mainCall->getMediaAttributeList());
660 328 : return Manager::instance().callFactory.newSipCall(shared(), Call::CallType::OUTGOING, mediaList);
661 164 : }
662 :
663 : void
664 82 : JamiAccount::startOutgoingCall(const std::shared_ptr<SIPCall>& call, const std::string& toUri)
665 : {
666 82 : if (not accountManager_ or not dht_) {
667 0 : call->onFailure(PJSIP_SC_SERVICE_UNAVAILABLE);
668 0 : return;
669 : }
670 :
671 : // TODO: for now, we automatically trust all explicitly called peers
672 82 : setCertificateStatus(toUri, dhtnet::tls::TrustStore::PermissionStatus::ALLOWED);
673 :
674 82 : call->setState(Call::ConnectionState::TRYING);
675 82 : std::weak_ptr<SIPCall> wCall = call;
676 :
677 82 : accountManager_->lookupAddress(toUri,
678 82 : [wCall](const std::string& regName,
679 : const std::string& /*address*/,
680 : const NameDirectory::Response& response) {
681 82 : if (response == NameDirectory::Response::found)
682 1 : if (auto call = wCall.lock()) {
683 1 : call->setPeerRegisteredName(regName);
684 1 : }
685 82 : });
686 :
687 82 : dht::InfoHash peer_account(toUri);
688 82 : if (!peer_account) {
689 0 : throw std::invalid_argument("Invalid peer account: " + toUri);
690 : }
691 :
692 : // Call connected devices
693 82 : std::set<DeviceId> devices;
694 82 : std::unique_lock lkSipConn(sipConnsMtx_);
695 : // NOTE: dummyCall is a call used to avoid to mark the call as failed if the
696 : // cached connection is failing with ICE (close event still not detected).
697 82 : auto dummyCall = createSubCall(call);
698 :
699 82 : call->addSubCall(*dummyCall);
700 82 : dummyCall->setIceMedia(call->getIceMedia());
701 82 : auto sendRequest = [this, wCall, toUri, dummyCall = std::move(dummyCall)](const DeviceId& deviceId,
702 390 : bool eraseDummy) {
703 160 : if (eraseDummy) {
704 : // Mark the temp call as failed to stop the main call if necessary
705 82 : if (dummyCall)
706 82 : dummyCall->onFailure(PJSIP_SC_TEMPORARILY_UNAVAILABLE);
707 82 : return;
708 : }
709 78 : auto call = wCall.lock();
710 78 : if (not call)
711 0 : return;
712 78 : auto state = call->getConnectionState();
713 78 : if (state != Call::ConnectionState::PROGRESSING and state != Call::ConnectionState::TRYING)
714 0 : return;
715 :
716 78 : auto dev_call = createSubCall(call);
717 78 : dev_call->setPeerNumber(call->getPeerNumber());
718 78 : dev_call->setState(Call::ConnectionState::TRYING);
719 78 : call->addStateListener([w = weak(), deviceId](Call::CallState, Call::ConnectionState state, int) {
720 230 : if (state != Call::ConnectionState::PROGRESSING and state != Call::ConnectionState::TRYING) {
721 78 : if (auto shared = w.lock())
722 78 : shared->callConnectionClosed(deviceId, true);
723 78 : return false;
724 : }
725 152 : return true;
726 : });
727 78 : call->addSubCall(*dev_call);
728 78 : dev_call->setIceMedia(call->getIceMedia());
729 : {
730 78 : std::lock_guard lk(pendingCallsMutex_);
731 78 : pendingCalls_[deviceId].emplace_back(dev_call);
732 78 : }
733 :
734 312 : JAMI_WARNING("[call {}] No channeled socket with this peer. Send request", call->getCallId());
735 : // Else, ask for a channel (for future calls/text messages)
736 78 : const auto* type = call->hasVideo() ? "videoCall" : "audioCall";
737 78 : requestSIPConnection(toUri, deviceId, type, true, dev_call);
738 242 : };
739 :
740 82 : std::vector<std::shared_ptr<dhtnet::ChannelSocket>> channels;
741 132 : for (auto& [key, value] : sipConns_) {
742 50 : if (key.first != toUri)
743 46 : continue;
744 4 : if (value.empty())
745 0 : continue;
746 4 : auto& sipConn = value.back();
747 :
748 4 : if (!sipConn.channel) {
749 0 : JAMI_WARNING("A SIP transport exists without Channel, this is a bug. Please report");
750 0 : continue;
751 0 : }
752 :
753 4 : auto transport = sipConn.transport;
754 4 : auto remote_address = sipConn.channel->getRemoteAddress();
755 4 : if (!transport or !remote_address)
756 0 : continue;
757 :
758 4 : channels.emplace_back(sipConn.channel);
759 :
760 16 : JAMI_WARNING("[call {}] A channeled socket is detected with this peer.", call->getCallId());
761 :
762 4 : auto dev_call = createSubCall(call);
763 4 : dev_call->setPeerNumber(call->getPeerNumber());
764 4 : dev_call->setSipTransport(transport, getContactHeader(transport));
765 4 : call->addSubCall(*dev_call);
766 4 : dev_call->setIceMedia(call->getIceMedia());
767 :
768 : // Set the call in PROGRESSING State because the ICE session
769 : // is already ready. Note that this line should be after
770 : // addSubcall() to change the state of the main call
771 : // and avoid to get an active call in a TRYING state.
772 4 : dev_call->setState(Call::ConnectionState::PROGRESSING);
773 :
774 : {
775 4 : std::lock_guard lk(onConnectionClosedMtx_);
776 4 : onConnectionClosed_[key.second] = sendRequest;
777 4 : }
778 :
779 4 : call->addStateListener([w = weak(), deviceId = key.second](Call::CallState, Call::ConnectionState state, int) {
780 12 : if (state != Call::ConnectionState::PROGRESSING and state != Call::ConnectionState::TRYING) {
781 4 : if (auto shared = w.lock())
782 4 : shared->callConnectionClosed(deviceId, true);
783 4 : return false;
784 : }
785 8 : return true;
786 : });
787 :
788 : try {
789 4 : onConnectedOutgoingCall(dev_call, toUri, remote_address);
790 0 : } catch (const VoipLinkException&) {
791 : // In this case, the main scenario is that SIPStartCall failed because
792 : // the ICE is dead and the TLS session didn't send any packet on that dead
793 : // link (connectivity change, killed by the os, etc)
794 : // Here, we don't need to do anything, the TLS will fail and will delete
795 : // the cached transport
796 0 : continue;
797 0 : }
798 4 : devices.emplace(key.second);
799 4 : }
800 :
801 82 : lkSipConn.unlock();
802 : // Note: Send beacon can destroy the socket (if storing last occurence of shared_ptr)
803 : // causing sipConn to be destroyed. So, do it while sipConns_ not locked.
804 86 : for (const auto& channel : channels)
805 4 : channel->sendBeacon();
806 :
807 : // Find listening devices for this account
808 164 : accountManager_->forEachDevice(
809 : peer_account,
810 82 : [this, devices = std::move(devices), sendRequest](const std::shared_ptr<dht::crypto::PublicKey>& dev) {
811 : // Test if already sent via a SIP transport
812 82 : auto deviceId = dev->getLongId();
813 82 : if (devices.find(deviceId) != devices.end())
814 4 : return;
815 : {
816 78 : std::lock_guard lk(onConnectionClosedMtx_);
817 78 : onConnectionClosed_[deviceId] = sendRequest;
818 78 : }
819 78 : sendRequest(deviceId, false);
820 : },
821 82 : [wCall](bool ok) {
822 82 : if (not ok) {
823 1 : if (auto call = wCall.lock()) {
824 4 : JAMI_WARNING("[call:{}] No devices found", call->getCallId());
825 : // Note: if a P2P connection exists, the call will be at least in CONNECTING
826 1 : if (call->getConnectionState() == Call::ConnectionState::TRYING)
827 1 : call->onFailure(PJSIP_SC_TEMPORARILY_UNAVAILABLE);
828 1 : }
829 : }
830 82 : });
831 82 : }
832 :
833 : void
834 90 : JamiAccount::onConnectedOutgoingCall(const std::shared_ptr<SIPCall>& call,
835 : const std::string& to_id,
836 : dhtnet::IpAddr target)
837 : {
838 90 : if (!call)
839 0 : return;
840 360 : JAMI_LOG("[call:{}] Outgoing call connected to {}", call->getCallId(), to_id);
841 :
842 90 : const auto localAddress = dhtnet::ip_utils::getInterfaceAddr(getLocalInterface(), target.getFamily());
843 :
844 90 : dhtnet::IpAddr addrSdp = getPublishedSameasLocal() ? localAddress
845 90 : : connectionManager_->getPublishedIpAddress(target.getFamily());
846 :
847 : // fallback on local address
848 90 : if (not addrSdp)
849 0 : addrSdp = localAddress;
850 :
851 : // Building the local SDP offer
852 90 : auto& sdp = call->getSDP();
853 :
854 90 : sdp.setPublishedIP(addrSdp);
855 :
856 90 : auto mediaAttrList = call->getMediaAttributeList();
857 90 : if (mediaAttrList.empty()) {
858 0 : JAMI_ERROR("[call:{}] No media. Abort!", call->getCallId());
859 0 : return;
860 : }
861 :
862 90 : if (not sdp.createOffer(mediaAttrList)) {
863 0 : JAMI_ERROR("[call:{}] Unable to send outgoing INVITE request for new call", call->getCallId());
864 0 : return;
865 : }
866 :
867 : // Note: pj_ice_strans_create can call onComplete in the same thread
868 : // This means that iceMutex_ in IceTransport can be locked when onInitDone is called
869 : // So, we need to run the call creation in the main thread
870 : // Also, we do not directly call SIPStartCall before receiving onInitDone, because
871 : // there is an inside waitForInitialization that can block the thread.
872 : // Note: avoid runMainThread as SIPStartCall use transportMutex
873 90 : dht::ThreadPool::io().run([w = weak(), call = std::move(call), target] {
874 90 : auto account = w.lock();
875 90 : if (not account)
876 0 : return;
877 :
878 90 : if (not account->SIPStartCall(*call, target)) {
879 0 : JAMI_ERROR("[call:{}] Unable to send outgoing INVITE request for new call", call->getCallId());
880 : }
881 90 : });
882 90 : }
883 :
884 : bool
885 90 : JamiAccount::SIPStartCall(SIPCall& call, const dhtnet::IpAddr& target)
886 : {
887 360 : JAMI_LOG("[call:{}] Start SIP call", call.getCallId());
888 :
889 90 : if (call.isIceEnabled())
890 90 : call.addLocalIceAttributes();
891 :
892 : std::string toUri(
893 180 : getToUri(call.getPeerNumber() + "@" + target.toString(true))); // expecting a fully well formed sip uri
894 :
895 90 : pj_str_t pjTo = sip_utils::CONST_PJ_STR(toUri);
896 :
897 : // Create the from header
898 90 : std::string from(getFromUri());
899 90 : pj_str_t pjFrom = sip_utils::CONST_PJ_STR(from);
900 :
901 90 : std::string targetStr = getToUri(target.toString(true));
902 90 : pj_str_t pjTarget = sip_utils::CONST_PJ_STR(targetStr);
903 :
904 90 : auto contact = call.getContactHeader();
905 90 : auto pjContact = sip_utils::CONST_PJ_STR(contact);
906 :
907 360 : JAMI_LOG("[call:{}] Contact header: {} / {} -> {} / {}", call.getCallId(), contact, from, toUri, targetStr);
908 :
909 90 : auto* local_sdp = call.getSDP().getLocalSdpSession();
910 90 : pjsip_dialog* dialog {nullptr};
911 90 : pjsip_inv_session* inv {nullptr};
912 90 : if (!CreateClientDialogAndInvite(&pjFrom, &pjContact, &pjTo, &pjTarget, local_sdp, &dialog, &inv))
913 0 : return false;
914 :
915 90 : inv->mod_data[link_.getModId()] = &call;
916 90 : call.setInviteSession(inv);
917 :
918 : pjsip_tx_data* tdata;
919 :
920 90 : if (pjsip_inv_invite(call.inviteSession_.get(), &tdata) != PJ_SUCCESS) {
921 0 : JAMI_ERROR("[call:{}] Unable to initialize invite", call.getCallId());
922 0 : return false;
923 : }
924 :
925 : pjsip_tpselector tp_sel;
926 90 : tp_sel.type = PJSIP_TPSELECTOR_TRANSPORT;
927 90 : if (!call.getTransport()) {
928 0 : JAMI_ERROR("[call:{}] Unable to get transport", call.getCallId());
929 0 : return false;
930 : }
931 90 : tp_sel.u.transport = call.getTransport()->get();
932 90 : if (pjsip_dlg_set_transport(dialog, &tp_sel) != PJ_SUCCESS) {
933 0 : JAMI_ERROR("[call:{}] Unable to associate transport for invite session dialog", call.getCallId());
934 0 : return false;
935 : }
936 :
937 360 : JAMI_LOG("[call:{}] Sending SIP invite", call.getCallId());
938 :
939 : // Add user-agent header
940 90 : sip_utils::addUserAgentHeader(getUserAgentName(), tdata);
941 :
942 90 : if (pjsip_inv_send_msg(call.inviteSession_.get(), tdata) != PJ_SUCCESS) {
943 0 : JAMI_ERROR("[call:{}] Unable to send invite message", call.getCallId());
944 0 : return false;
945 : }
946 :
947 90 : call.setState(Call::CallState::ACTIVE, Call::ConnectionState::PROGRESSING);
948 90 : return true;
949 90 : }
950 :
951 : void
952 2557 : JamiAccount::saveConfig() const
953 : {
954 : try {
955 2557 : YAML::Emitter accountOut;
956 2557 : config().serialize(accountOut);
957 2557 : auto accountConfig = config().path / "config.yml";
958 2557 : std::lock_guard lock(dhtnet::fileutils::getFileLock(accountConfig));
959 2557 : std::ofstream fout(accountConfig);
960 2557 : fout.write(accountOut.c_str(), static_cast<std::streamsize>(accountOut.size()));
961 10228 : JAMI_LOG("Saved account config to {}", accountConfig);
962 2557 : } catch (const std::exception& e) {
963 0 : JAMI_ERROR("Error saving account config: {}", e.what());
964 0 : }
965 2557 : }
966 :
967 : void
968 791 : JamiAccount::loadConfig()
969 : {
970 791 : SIPAccountBase::loadConfig();
971 791 : registeredName_ = config().registeredName;
972 791 : if (accountManager_)
973 20 : accountManager_->setAccountDeviceName(config().deviceName);
974 791 : if (connectionManager_) {
975 16 : if (auto c = connectionManager_->getConfig()) {
976 : // Update connectionManager's config
977 16 : c->upnpEnabled = config().upnpEnabled;
978 16 : c->turnEnabled = config().turnEnabled;
979 16 : c->turnServer = config().turnServer;
980 16 : c->turnServerUserName = config().turnServerUserName;
981 16 : c->turnServerPwd = config().turnServerPwd;
982 16 : c->turnServerRealm = config().turnServerRealm;
983 16 : }
984 : }
985 791 : if (config().proxyEnabled) {
986 : try {
987 0 : auto str = fileutils::loadCacheTextFile(cachePath_ / "dhtproxy", std::chrono::hours(24 * 14));
988 0 : Json::Value root;
989 0 : if (json::parse(str, root)) {
990 0 : proxyServerCached_ = root[getProxyConfigKey()].asString();
991 : }
992 0 : } catch (const std::exception& e) {
993 0 : JAMI_LOG("[Account {}] Unable to load proxy URL from cache: {}", getAccountID(), e.what());
994 0 : proxyServerCached_.clear();
995 0 : }
996 : } else {
997 791 : proxyServerCached_.clear();
998 791 : std::error_code ec;
999 791 : std::filesystem::remove(cachePath_ / "dhtproxy", ec);
1000 : }
1001 791 : if (not config().dhtProxyServerEnabled) {
1002 791 : dhtProxyServer_.reset();
1003 : }
1004 791 : auto credentials = consumeConfigCredentials();
1005 791 : loadAccount(credentials.archive_password_scheme, credentials.archive_password, credentials.archive_path);
1006 791 : }
1007 :
1008 : bool
1009 7 : JamiAccount::changeArchivePassword(const std::string& password_old, const std::string& password_new)
1010 : {
1011 : try {
1012 7 : if (!accountManager_->changePassword(password_old, password_new)) {
1013 8 : JAMI_ERROR("[Account {}] Unable to change archive password", getAccountID());
1014 2 : return false;
1015 : }
1016 10 : editConfig([&](JamiAccountConfig& config) { config.archiveHasPassword = not password_new.empty(); });
1017 0 : } catch (const std::exception& ex) {
1018 0 : JAMI_ERROR("[Account {}] Unable to change archive password: {}", getAccountID(), ex.what());
1019 0 : if (password_old.empty()) {
1020 0 : editConfig([&](JamiAccountConfig& config) { config.archiveHasPassword = true; });
1021 0 : emitSignal<libjami::ConfigurationSignal::AccountDetailsChanged>(getAccountID(), getAccountDetails());
1022 : }
1023 0 : return false;
1024 0 : }
1025 5 : if (password_old != password_new)
1026 5 : emitSignal<libjami::ConfigurationSignal::AccountDetailsChanged>(getAccountID(), getAccountDetails());
1027 5 : return true;
1028 : }
1029 :
1030 : bool
1031 3 : JamiAccount::isPasswordValid(const std::string& password)
1032 : {
1033 3 : return accountManager_ and accountManager_->isPasswordValid(password);
1034 : }
1035 :
1036 : std::vector<uint8_t>
1037 0 : JamiAccount::getPasswordKey(const std::string& password)
1038 : {
1039 0 : return accountManager_ ? accountManager_->getPasswordKey(password) : std::vector<uint8_t>();
1040 : }
1041 :
1042 : bool
1043 7 : JamiAccount::provideAccountAuthentication(const std::string& credentialsFromUser, const std::string& scheme)
1044 : {
1045 7 : if (auto manager = std::dynamic_pointer_cast<ArchiveAccountManager>(accountManager_)) {
1046 7 : return manager->provideAccountAuthentication(credentialsFromUser, scheme);
1047 7 : }
1048 0 : JAMI_ERR("[LinkDevice] Invalid AccountManager instance while providing current account "
1049 : "authentication.");
1050 0 : return false;
1051 : }
1052 :
1053 : int32_t
1054 5 : JamiAccount::addDevice(const std::string& uriProvided)
1055 : {
1056 20 : JAMI_LOG("[LinkDevice] JamiAccount::addDevice({}, {})", getAccountID(), uriProvided);
1057 5 : if (not accountManager_) {
1058 0 : JAMI_ERR("[LinkDevice] Invalid AccountManager instance while adding a device.");
1059 0 : return static_cast<int32_t>(AccountManager::AddDeviceError::GENERIC);
1060 : }
1061 5 : auto authHandler = channelHandlers_.find(Uri::Scheme::AUTH);
1062 5 : if (authHandler == channelHandlers_.end())
1063 0 : return static_cast<int32_t>(AccountManager::AddDeviceError::GENERIC);
1064 10 : return accountManager_->addDevice(uriProvided,
1065 5 : config().archiveHasPassword ? fileutils::ARCHIVE_AUTH_SCHEME_PASSWORD
1066 : : fileutils::ARCHIVE_AUTH_SCHEME_NONE,
1067 10 : (AuthChannelHandler*) authHandler->second.get());
1068 : }
1069 :
1070 : bool
1071 0 : JamiAccount::cancelAddDevice(uint32_t op_token)
1072 : {
1073 0 : if (!accountManager_)
1074 0 : return false;
1075 0 : return accountManager_->cancelAddDevice(op_token);
1076 : }
1077 :
1078 : bool
1079 4 : JamiAccount::confirmAddDevice(uint32_t op_token)
1080 : {
1081 4 : if (!accountManager_)
1082 0 : return false;
1083 4 : return accountManager_->confirmAddDevice(op_token);
1084 : }
1085 :
1086 : bool
1087 38 : JamiAccount::exportArchive(const std::string& destinationPath, std::string_view scheme, const std::string& password)
1088 : {
1089 38 : if (auto* manager = dynamic_cast<ArchiveAccountManager*>(accountManager_.get())) {
1090 38 : return manager->exportArchive(destinationPath, scheme, password);
1091 : }
1092 0 : return false;
1093 : }
1094 :
1095 : bool
1096 2 : JamiAccount::setValidity(std::string_view scheme, const std::string& pwd, const dht::InfoHash& id, int64_t validity)
1097 : {
1098 2 : if (auto* manager = dynamic_cast<ArchiveAccountManager*>(accountManager_.get())) {
1099 2 : if (manager->setValidity(scheme, pwd, id_, id, validity)) {
1100 2 : saveIdentity(id_, idPath_, DEVICE_ID_PATH);
1101 2 : return true;
1102 : }
1103 : }
1104 0 : return false;
1105 : }
1106 :
1107 : void
1108 4 : JamiAccount::forceReloadAccount()
1109 : {
1110 4 : editConfig([&](JamiAccountConfig& conf) {
1111 4 : conf.receipt.clear();
1112 4 : conf.receiptSignature.clear();
1113 4 : });
1114 4 : loadAccount();
1115 4 : }
1116 :
1117 : void
1118 2 : JamiAccount::unlinkConversations(const std::set<std::string>& removed)
1119 : {
1120 2 : std::lock_guard lock(configurationMutex_);
1121 2 : if (const auto* info = accountManager_->getInfo()) {
1122 2 : auto contacts = info->contacts->getContacts();
1123 4 : for (auto& [id, c] : contacts) {
1124 2 : if (removed.find(c.conversationId) != removed.end()) {
1125 1 : info->contacts->updateConversation(id, "");
1126 4 : JAMI_WARNING("[Account {}] Detected removed conversation ({}) in contact details for {}",
1127 : getAccountID(),
1128 : c.conversationId,
1129 : id.toString());
1130 : }
1131 : }
1132 2 : }
1133 2 : }
1134 :
1135 : bool
1136 1052 : JamiAccount::isValidAccountDevice(const dht::crypto::Certificate& cert) const
1137 : {
1138 1052 : if (accountManager_) {
1139 1052 : if (const auto* info = accountManager_->getInfo()) {
1140 1052 : if (info->contacts)
1141 1052 : return info->contacts->isValidAccountDevice(cert).isValid();
1142 : }
1143 : }
1144 0 : return false;
1145 : }
1146 :
1147 : bool
1148 3 : JamiAccount::revokeDevice(const std::string& device, std::string_view scheme, const std::string& password)
1149 : {
1150 3 : if (not accountManager_)
1151 0 : return false;
1152 3 : return accountManager_
1153 6 : ->revokeDevice(device, scheme, password, [this, device](AccountManager::RevokeDeviceResult result) {
1154 3 : emitSignal<libjami::ConfigurationSignal::DeviceRevocationEnded>(getAccountID(),
1155 3 : device,
1156 : static_cast<int>(result));
1157 6 : });
1158 : return true;
1159 : }
1160 :
1161 : std::pair<std::string, std::string>
1162 775 : JamiAccount::saveIdentity(const dht::crypto::Identity& id, const std::filesystem::path& path, const std::string& name)
1163 : {
1164 1550 : auto names = std::make_pair(name + ".key", name + ".crt");
1165 775 : if (id.first)
1166 775 : fileutils::saveFile(path / names.first, id.first->serialize(), 0600);
1167 775 : if (id.second)
1168 775 : fileutils::saveFile(path / names.second, id.second->getPacked(), 0600);
1169 775 : return names;
1170 0 : }
1171 :
1172 : void
1173 773 : JamiAccount::scheduleAccountReady() const
1174 : {
1175 773 : const auto accountId = getAccountID();
1176 1546 : runOnMainThread([accountId] { Manager::instance().markAccountReady(accountId); });
1177 773 : }
1178 :
1179 : AccountManager::OnChangeCallback
1180 791 : JamiAccount::setupAccountCallbacks()
1181 : {
1182 : return AccountManager::OnChangeCallback {
1183 164 : [this](const std::string& uri, bool confirmed) { onContactAdded(uri, confirmed); },
1184 23 : [this](const std::string& uri, bool banned) { onContactRemoved(uri, banned); },
1185 84 : [this](const std::string& uri,
1186 : const std::string& conversationId,
1187 : const std::vector<uint8_t>& payload,
1188 84 : time_t received) { onIncomingTrustRequest(uri, conversationId, payload, received); },
1189 2929 : [this](const std::map<DeviceId, KnownDevice>& devices) { onKnownDevicesChanged(devices); },
1190 55 : [this](const std::string& conversationId, const std::string& deviceId) {
1191 55 : onConversationRequestAccepted(conversationId, deviceId);
1192 55 : },
1193 831 : [this](const std::string& uri, const std::string& convFromReq) { onContactConfirmed(uri, convFromReq); }};
1194 : }
1195 :
1196 : void
1197 164 : JamiAccount::onContactAdded(const std::string& uri, bool confirmed)
1198 : {
1199 164 : if (!id_.first)
1200 3 : return;
1201 161 : if (jami::Manager::instance().syncOnRegister) {
1202 161 : dht::ThreadPool::io().run([w = weak(), uri, confirmed] {
1203 161 : if (auto shared = w.lock()) {
1204 161 : if (auto* cm = shared->convModule(true)) {
1205 161 : auto activeConv = cm->getOneToOneConversation(uri);
1206 161 : if (!activeConv.empty())
1207 161 : cm->bootstrap(activeConv);
1208 161 : }
1209 161 : emitSignal<libjami::ConfigurationSignal::ContactAdded>(shared->getAccountID(), uri, confirmed);
1210 161 : }
1211 161 : });
1212 : }
1213 : }
1214 :
1215 : void
1216 23 : JamiAccount::onContactRemoved(const std::string& uri, bool banned)
1217 : {
1218 23 : if (!id_.first)
1219 0 : return;
1220 23 : dht::ThreadPool::io().run([w = weak(), uri, banned] {
1221 23 : if (auto shared = w.lock()) {
1222 : // Erase linked conversation's requests
1223 23 : if (auto* convModule = shared->convModule(true))
1224 23 : convModule->removeContact(uri, banned);
1225 : // Remove current connections with contact
1226 : // Note: if contact is ourself, we don't close the connection
1227 : // because it's used for syncing other conversations.
1228 23 : if (shared->connectionManager_ && uri != shared->getUsername()) {
1229 22 : shared->connectionManager_->closeConnectionsWith(uri);
1230 : }
1231 : // Update client.
1232 23 : emitSignal<libjami::ConfigurationSignal::ContactRemoved>(shared->getAccountID(), uri, banned);
1233 23 : }
1234 23 : });
1235 : }
1236 :
1237 : void
1238 84 : JamiAccount::onIncomingTrustRequest(const std::string& uri,
1239 : const std::string& conversationId,
1240 : const std::vector<uint8_t>& payload,
1241 : time_t received)
1242 : {
1243 84 : if (!id_.first)
1244 0 : return;
1245 84 : dht::ThreadPool::io().run([w = weak(), uri, conversationId, payload, received] {
1246 84 : if (auto shared = w.lock()) {
1247 84 : shared->clearProfileCache(uri);
1248 84 : if (conversationId.empty()) {
1249 : // Old path
1250 0 : emitSignal<libjami::ConfigurationSignal::IncomingTrustRequest>(shared->getAccountID(),
1251 0 : conversationId,
1252 0 : uri,
1253 0 : payload,
1254 : received);
1255 0 : return;
1256 : }
1257 : // Here account can be initializing
1258 84 : if (auto* cm = shared->convModule(true)) {
1259 84 : auto activeConv = cm->getOneToOneConversation(uri);
1260 84 : if (activeConv != conversationId)
1261 76 : cm->onTrustRequest(uri, conversationId, payload, received);
1262 84 : }
1263 84 : }
1264 : });
1265 : }
1266 :
1267 : void
1268 2929 : JamiAccount::onKnownDevicesChanged(const std::map<DeviceId, KnownDevice>& devices)
1269 : {
1270 2929 : std::map<std::string, std::string> ids;
1271 1010997 : for (auto& d : devices) {
1272 1008068 : auto id = d.first.toString();
1273 1008068 : auto label = d.second.name.empty() ? id.substr(0, 8) : d.second.name;
1274 1008068 : ids.emplace(std::move(id), std::move(label));
1275 1008068 : }
1276 2929 : runOnMainThread([id = getAccountID(), devices = std::move(ids)] {
1277 2929 : emitSignal<libjami::ConfigurationSignal::KnownDevicesChanged>(id, devices);
1278 2929 : });
1279 2929 : }
1280 :
1281 : void
1282 55 : JamiAccount::onConversationRequestAccepted(const std::string& conversationId, const std::string& deviceId)
1283 : {
1284 : // Note: Do not retrigger on another thread. This has to be done
1285 : // at the same time of acceptTrustRequest a synced state between TrustRequest
1286 : // and convRequests.
1287 55 : if (auto* cm = convModule(true))
1288 55 : cm->acceptConversationRequest(conversationId, deviceId);
1289 55 : }
1290 :
1291 : void
1292 40 : JamiAccount::onContactConfirmed(const std::string& uri, const std::string& convFromReq)
1293 : {
1294 40 : dht::ThreadPool::io().run([w = weak(), convFromReq, uri] {
1295 40 : if (auto shared = w.lock()) {
1296 40 : shared->convModule(true);
1297 : // Remove cached payload if there is one
1298 80 : auto requestPath = shared->cachePath_ / "requests" / uri;
1299 40 : dhtnet::fileutils::remove(requestPath);
1300 80 : }
1301 40 : });
1302 40 : }
1303 :
1304 : std::unique_ptr<AccountManager::AccountCredentials>
1305 775 : JamiAccount::buildAccountCredentials(const JamiAccountConfig& conf,
1306 : const dht::crypto::Identity& id,
1307 : const std::string& archive_password_scheme,
1308 : const std::string& archive_password,
1309 : const std::string& archive_path,
1310 : bool& migrating,
1311 : bool& hasPassword)
1312 : {
1313 775 : std::unique_ptr<AccountManager::AccountCredentials> creds;
1314 :
1315 775 : if (conf.managerUri.empty()) {
1316 775 : auto acreds = std::make_unique<ArchiveAccountManager::ArchiveAccountCredentials>();
1317 775 : auto archivePath = fileutils::getFullPath(idPath_, conf.archivePath);
1318 :
1319 775 : if (!archive_path.empty()) {
1320 39 : acreds->scheme = "file";
1321 39 : acreds->uri = archive_path;
1322 736 : } else if (!conf.archive_url.empty() && conf.archive_url == "jami-auth") {
1323 20 : JAMI_DEBUG("[Account {}] [LinkDevice] scheme p2p & uri {}", getAccountID(), conf.archive_url);
1324 5 : acreds->scheme = "p2p";
1325 5 : acreds->uri = conf.archive_url;
1326 731 : } else if (std::filesystem::is_regular_file(archivePath)) {
1327 4 : acreds->scheme = "local";
1328 4 : acreds->uri = archivePath.string();
1329 4 : acreds->updateIdentity = id;
1330 4 : migrating = true;
1331 : }
1332 :
1333 775 : creds = std::move(acreds);
1334 775 : } else {
1335 0 : auto screds = std::make_unique<ServerAccountManager::ServerAccountCredentials>();
1336 0 : screds->username = conf.managerUsername;
1337 0 : creds = std::move(screds);
1338 0 : }
1339 :
1340 775 : creds->password = archive_password;
1341 775 : hasPassword = !archive_password.empty();
1342 1540 : creds->password_scheme = (hasPassword && archive_password_scheme.empty()) ? fileutils::ARCHIVE_AUTH_SCHEME_PASSWORD
1343 1540 : : archive_password_scheme;
1344 :
1345 775 : return creds;
1346 0 : }
1347 :
1348 : void
1349 773 : JamiAccount::onAuthenticationSuccess(bool migrating,
1350 : bool hasPassword,
1351 : const AccountInfo& info,
1352 : const std::map<std::string, std::string>& configMap,
1353 : std::string&& receipt,
1354 : std::vector<uint8_t>&& receiptSignature)
1355 : {
1356 3092 : JAMI_LOG("[Account {}] Auth success! Device: {}", getAccountID(), info.deviceId);
1357 :
1358 773 : dhtnet::fileutils::check_dir(idPath_, 0700);
1359 :
1360 773 : auto id = info.identity;
1361 773 : editConfig([&](JamiAccountConfig& conf) {
1362 773 : std::tie(conf.tlsPrivateKeyFile, conf.tlsCertificateFile) = saveIdentity(id, idPath_, DEVICE_ID_PATH);
1363 773 : conf.tlsPassword = {};
1364 :
1365 773 : auto passwordIt = configMap.find(libjami::Account::ConfProperties::ARCHIVE_HAS_PASSWORD);
1366 1546 : conf.archiveHasPassword = (passwordIt != configMap.end() && !passwordIt->second.empty())
1367 1546 : ? passwordIt->second == "true"
1368 0 : : hasPassword;
1369 :
1370 773 : if (not conf.managerUri.empty()) {
1371 0 : conf.registeredName = conf.managerUsername;
1372 0 : registeredName_ = conf.managerUsername;
1373 : }
1374 :
1375 773 : conf.username = info.accountId;
1376 773 : conf.deviceName = accountManager_->getAccountDeviceName();
1377 :
1378 773 : auto nameServerIt = configMap.find(libjami::Account::ConfProperties::Nameserver::URI);
1379 773 : if (nameServerIt != configMap.end() && !nameServerIt->second.empty())
1380 0 : conf.nameServer = nameServerIt->second;
1381 :
1382 773 : auto displayNameIt = configMap.find(libjami::Account::ConfProperties::DISPLAYNAME);
1383 773 : if (displayNameIt != configMap.end() && !displayNameIt->second.empty())
1384 41 : conf.displayName = displayNameIt->second;
1385 :
1386 773 : conf.receipt = std::move(receipt);
1387 773 : conf.receiptSignature = std::move(receiptSignature);
1388 773 : conf.fromMap(configMap);
1389 773 : });
1390 :
1391 773 : id_ = std::move(id);
1392 : {
1393 773 : std::lock_guard lk(moduleMtx_);
1394 773 : convModule_.reset();
1395 773 : }
1396 :
1397 773 : if (migrating)
1398 4 : Migration::setState(getAccountID(), Migration::State::SUCCESS);
1399 :
1400 773 : setRegistrationState(RegistrationState::UNREGISTERED);
1401 :
1402 773 : if (!info.photo.empty() || !info.displayName.empty()) {
1403 : try {
1404 0 : auto newProfile = vCard::utils::initVcard();
1405 0 : newProfile[std::string(vCard::Property::FORMATTED_NAME)] = info.displayName;
1406 0 : newProfile[std::string(vCard::Property::PHOTO)] = info.photo;
1407 :
1408 0 : const auto& profiles = idPath_ / "profiles";
1409 0 : const auto& vCardPath = profiles / fmt::format("{}.vcf", base64::encode(info.accountId));
1410 0 : vCard::utils::save(newProfile, vCardPath, profilePath());
1411 :
1412 0 : runOnMainThread([w = weak(), id = info.accountId, vCardPath] {
1413 0 : if (auto shared = w.lock()) {
1414 0 : emitSignal<libjami::ConfigurationSignal::ProfileReceived>(shared->getAccountID(),
1415 0 : id,
1416 0 : vCardPath.string());
1417 0 : }
1418 0 : });
1419 0 : } catch (const std::exception& e) {
1420 0 : JAMI_WARNING("[Account {}] Unable to save profile after authentication: {}", getAccountID(), e.what());
1421 0 : }
1422 : }
1423 :
1424 773 : doRegister();
1425 773 : scheduleAccountReady();
1426 773 : }
1427 :
1428 : void
1429 0 : JamiAccount::onAuthenticationError(const std::weak_ptr<JamiAccount>& w,
1430 : bool hadIdentity,
1431 : bool migrating,
1432 : std::string accountId,
1433 : AccountManager::AuthError error,
1434 : const std::string& message)
1435 : {
1436 0 : JAMI_WARNING("[Account {}] Auth error: {} {}", accountId, (int) error, message);
1437 :
1438 0 : if ((hadIdentity || migrating) && error == AccountManager::AuthError::INVALID_ARGUMENTS) {
1439 0 : Migration::setState(accountId, Migration::State::INVALID);
1440 0 : if (auto acc = w.lock())
1441 0 : acc->setRegistrationState(RegistrationState::ERROR_NEED_MIGRATION);
1442 0 : return;
1443 : }
1444 :
1445 0 : if (auto acc = w.lock())
1446 0 : acc->setRegistrationState(RegistrationState::ERROR_GENERIC);
1447 :
1448 0 : runOnMainThread([accountId = std::move(accountId)] { Manager::instance().removeAccount(accountId, true); });
1449 : }
1450 :
1451 : // must be called while configurationMutex_ is locked
1452 : void
1453 795 : JamiAccount::loadAccount(const std::string& archive_password_scheme,
1454 : const std::string& archive_password,
1455 : const std::string& archive_path)
1456 : {
1457 795 : if (registrationState_ == RegistrationState::INITIALIZING)
1458 20 : return;
1459 :
1460 3164 : JAMI_DEBUG("[Account {:s}] Loading account", getAccountID());
1461 :
1462 791 : const auto scheduleAccountReady = [accountId = getAccountID()] {
1463 16 : runOnMainThread([accountId] {
1464 16 : auto& manager = Manager::instance();
1465 16 : manager.markAccountReady(accountId);
1466 16 : });
1467 807 : };
1468 :
1469 791 : const auto& conf = config();
1470 791 : auto callbacks = setupAccountCallbacks();
1471 :
1472 : try {
1473 791 : auto oldIdentity = id_.first ? id_.first->getPublicKey().getLongId() : DeviceId();
1474 :
1475 791 : if (conf.managerUri.empty()) {
1476 2373 : accountManager_ = std::make_shared<ArchiveAccountManager>(
1477 791 : getAccountID(),
1478 : getPath(),
1479 42 : [this]() { return getAccountDetails(); },
1480 812 : [this](DeviceSync&& syncData) {
1481 812 : if (auto* sm = syncModule()) {
1482 812 : auto syncDataPtr = std::make_shared<SyncMsg>();
1483 812 : syncDataPtr->ds = std::move(syncData);
1484 812 : sm->syncWithConnected(syncDataPtr);
1485 812 : }
1486 812 : },
1487 1582 : conf.archivePath.empty() ? "archive.gz" : conf.archivePath,
1488 1582 : conf.nameServer);
1489 : } else {
1490 0 : accountManager_ = std::make_shared<ServerAccountManager>(getAccountID(),
1491 : getPath(),
1492 0 : conf.managerUri,
1493 0 : conf.nameServer);
1494 : }
1495 :
1496 791 : auto id = accountManager_->loadIdentity(conf.tlsCertificateFile, conf.tlsPrivateKeyFile, conf.tlsPassword);
1497 :
1498 791 : if (const auto* info
1499 791 : = accountManager_->useIdentity(id, conf.receipt, conf.receiptSignature, conf.managerUsername, callbacks)) {
1500 16 : id_ = std::move(id);
1501 16 : config_->username = info->accountId;
1502 64 : JAMI_WARNING("[Account {:s}] Loaded account identity", getAccountID());
1503 :
1504 16 : if (info->identity.first->getPublicKey().getLongId() != oldIdentity) {
1505 0 : JAMI_WARNING("[Account {:s}] Identity changed", getAccountID());
1506 : {
1507 0 : std::lock_guard lk(moduleMtx_);
1508 0 : convModule_.reset();
1509 0 : }
1510 0 : convModule();
1511 : } else {
1512 16 : convModule()->setAccountManager(accountManager_);
1513 : }
1514 :
1515 16 : if (not isEnabled())
1516 0 : setRegistrationState(RegistrationState::UNREGISTERED);
1517 :
1518 16 : scheduleAccountReady();
1519 16 : return;
1520 : }
1521 :
1522 775 : if (!isEnabled())
1523 0 : return;
1524 :
1525 3100 : JAMI_WARNING("[Account {}] useIdentity failed!", getAccountID());
1526 :
1527 775 : if (not conf.managerUri.empty() && archive_password.empty()) {
1528 0 : Migration::setState(accountID_, Migration::State::INVALID);
1529 0 : setRegistrationState(RegistrationState::ERROR_NEED_MIGRATION);
1530 0 : return;
1531 : }
1532 :
1533 775 : bool migrating = registrationState_ == RegistrationState::ERROR_NEED_MIGRATION;
1534 775 : setRegistrationState(RegistrationState::INITIALIZING);
1535 :
1536 775 : auto fDeviceKey = dht::ThreadPool::computation().getShared<std::shared_ptr<dht::crypto::PrivateKey>>(
1537 2325 : []() { return std::make_shared<dht::crypto::PrivateKey>(dht::crypto::PrivateKey::generate()); });
1538 :
1539 775 : bool hasPassword = false;
1540 : auto creds = buildAccountCredentials(conf,
1541 : id,
1542 : archive_password_scheme,
1543 : archive_password,
1544 : archive_path,
1545 : migrating,
1546 775 : hasPassword);
1547 :
1548 3100 : JAMI_WARNING("[Account {}] initAuthentication {}", getAccountID(), fmt::ptr(this));
1549 :
1550 775 : const bool hadIdentity = static_cast<bool>(id.first);
1551 3875 : accountManager_->initAuthentication(
1552 : fDeviceKey,
1553 1550 : ip_utils::getDeviceName(),
1554 775 : std::move(creds),
1555 773 : [w = weak(), migrating, hasPassword](const AccountInfo& info,
1556 : const std::map<std::string, std::string>& configMap,
1557 : std::string&& receipt,
1558 773 : std::vector<uint8_t>&& receiptSignature) {
1559 773 : if (auto self = w.lock())
1560 1546 : self->onAuthenticationSuccess(migrating,
1561 : hasPassword,
1562 : info,
1563 : configMap,
1564 773 : std::move(receipt),
1565 1546 : std::move(receiptSignature));
1566 773 : },
1567 775 : [w = weak(), hadIdentity, accountId = getAccountID(), migrating](AccountManager::AuthError error,
1568 0 : const std::string& message) {
1569 0 : JamiAccount::onAuthenticationError(w, hadIdentity, migrating, accountId, error, message);
1570 0 : },
1571 : callbacks);
1572 791 : } catch (const std::exception& e) {
1573 0 : JAMI_WARNING("[Account {}] Error loading account: {}", getAccountID(), e.what());
1574 0 : accountManager_.reset();
1575 0 : setRegistrationState(RegistrationState::ERROR_GENERIC);
1576 0 : }
1577 807 : }
1578 :
1579 : std::map<std::string, std::string>
1580 4598 : JamiAccount::getVolatileAccountDetails() const
1581 : {
1582 4598 : auto a = SIPAccountBase::getVolatileAccountDetails();
1583 4598 : a.emplace(libjami::Account::VolatileProperties::InstantMessaging::OFF_CALL, TRUE_STR);
1584 4598 : auto registeredName = getRegisteredName();
1585 4598 : if (not registeredName.empty())
1586 3 : a.emplace(libjami::Account::VolatileProperties::REGISTERED_NAME, registeredName);
1587 4598 : a.emplace(libjami::Account::ConfProperties::PROXY_SERVER, proxyServerCached_);
1588 4598 : a.emplace(libjami::Account::VolatileProperties::DHT_BOUND_PORT, std::to_string(dhtBoundPort_));
1589 4598 : a.emplace(libjami::Account::VolatileProperties::DEVICE_ANNOUNCED, deviceAnnounced_ ? TRUE_STR : FALSE_STR);
1590 4598 : if (accountManager_) {
1591 4598 : if (const auto* info = accountManager_->getInfo()) {
1592 3777 : a.emplace(libjami::Account::ConfProperties::DEVICE_ID, info->deviceId);
1593 : }
1594 : }
1595 9196 : return a;
1596 4598 : }
1597 :
1598 : void
1599 3 : JamiAccount::lookupName(const std::string& name)
1600 : {
1601 3 : std::lock_guard lock(configurationMutex_);
1602 3 : if (accountManager_)
1603 9 : accountManager_->lookupUri(name,
1604 3 : config().nameServer,
1605 3 : [acc = getAccountID(), name](const std::string& regName,
1606 : const std::string& address,
1607 : NameDirectory::Response response) {
1608 6 : emitSignal<libjami::ConfigurationSignal::RegisteredNameFound>(acc,
1609 3 : name,
1610 : (int) response,
1611 : address,
1612 : regName);
1613 3 : });
1614 3 : }
1615 :
1616 : void
1617 3 : JamiAccount::lookupAddress(const std::string& addr)
1618 : {
1619 3 : std::lock_guard lock(configurationMutex_);
1620 3 : auto acc = getAccountID();
1621 3 : if (accountManager_)
1622 3 : accountManager_->lookupAddress(addr,
1623 3 : [acc, addr](const std::string& regName,
1624 : const std::string& address,
1625 : NameDirectory::Response response) {
1626 6 : emitSignal<libjami::ConfigurationSignal::RegisteredNameFound>(acc,
1627 3 : addr,
1628 : (int) response,
1629 : address,
1630 : regName);
1631 3 : });
1632 3 : }
1633 :
1634 : void
1635 1 : JamiAccount::registerName(const std::string& name, const std::string& scheme, const std::string& password)
1636 : {
1637 1 : std::lock_guard lock(configurationMutex_);
1638 1 : if (accountManager_)
1639 1 : accountManager_
1640 2 : ->registerName(name,
1641 : scheme,
1642 : password,
1643 1 : [acc = getAccountID(), name, w = weak()](NameDirectory::RegistrationResponse response,
1644 : const std::string& regName) {
1645 1 : auto res = (int) std::min(response, NameDirectory::RegistrationResponse::error);
1646 1 : if (response == NameDirectory::RegistrationResponse::success) {
1647 1 : if (auto this_ = w.lock()) {
1648 1 : if (this_->setRegisteredName(regName)) {
1649 1 : this_->editConfig(
1650 1 : [&](JamiAccountConfig& config) { config.registeredName = regName; });
1651 1 : emitSignal<libjami::ConfigurationSignal::VolatileDetailsChanged>(
1652 2 : this_->accountID_, this_->getVolatileAccountDetails());
1653 : }
1654 1 : }
1655 : }
1656 1 : emitSignal<libjami::ConfigurationSignal::NameRegistrationEnded>(acc, res, name);
1657 1 : });
1658 1 : }
1659 :
1660 : bool
1661 0 : JamiAccount::searchUser(const std::string& query)
1662 : {
1663 0 : if (accountManager_)
1664 0 : return accountManager_
1665 0 : ->searchUser(query,
1666 0 : [acc = getAccountID(), query](const jami::NameDirectory::SearchResult& result,
1667 : jami::NameDirectory::Response response) {
1668 0 : jami::emitSignal<libjami::ConfigurationSignal::UserSearchEnded>(acc,
1669 : (int) response,
1670 0 : query,
1671 : result);
1672 0 : });
1673 0 : return false;
1674 : }
1675 :
1676 : void
1677 174 : JamiAccount::forEachPendingCall(const DeviceId& deviceId, const std::function<void(const std::shared_ptr<SIPCall>&)>& cb)
1678 : {
1679 174 : std::vector<std::shared_ptr<SIPCall>> pc;
1680 : {
1681 173 : std::lock_guard lk(pendingCallsMutex_);
1682 174 : pc = std::move(pendingCalls_[deviceId]);
1683 173 : }
1684 259 : for (const auto& pendingCall : pc) {
1685 86 : cb(pendingCall);
1686 : }
1687 174 : }
1688 :
1689 : void
1690 692 : JamiAccount::registerAsyncOps()
1691 : {
1692 692 : loadCachedProxyServer([w = weak()](const std::string&) {
1693 692 : runOnMainThread([w] {
1694 692 : if (auto s = w.lock()) {
1695 692 : std::lock_guard lock(s->configurationMutex_);
1696 692 : s->doRegister_();
1697 1384 : }
1698 692 : });
1699 692 : });
1700 692 : }
1701 :
1702 : void
1703 1603 : JamiAccount::doRegister()
1704 : {
1705 1603 : std::lock_guard lock(configurationMutex_);
1706 1603 : if (not isUsable()) {
1707 560 : JAMI_WARNING("[Account {:s}] Account must be enabled and active to register, ignoring", getAccountID());
1708 140 : return;
1709 : }
1710 :
1711 5852 : JAMI_LOG("[Account {:s}] Starting account…", getAccountID());
1712 :
1713 : // invalid state transitions:
1714 : // INITIALIZING: generating/loading certificates, unable to register
1715 : // NEED_MIGRATION: old account detected, user needs to migrate
1716 1463 : if (registrationState_ == RegistrationState::INITIALIZING
1717 692 : || registrationState_ == RegistrationState::ERROR_NEED_MIGRATION)
1718 771 : return;
1719 :
1720 692 : convModule(); // Init conv module before passing in trying
1721 692 : setRegistrationState(RegistrationState::TRYING);
1722 692 : if (proxyServerCached_.empty()) {
1723 692 : registerAsyncOps();
1724 : } else {
1725 0 : doRegister_();
1726 : }
1727 1603 : }
1728 :
1729 : std::vector<std::string>
1730 692 : JamiAccount::loadBootstrap() const
1731 : {
1732 692 : std::vector<std::string> bootstrap;
1733 692 : std::string_view stream(config().hostname), node_addr;
1734 1384 : while (jami::getline(stream, node_addr, ';'))
1735 692 : bootstrap.emplace_back(node_addr);
1736 1384 : for (const auto& b : bootstrap)
1737 692 : JAMI_DBG("[Account %s] Bootstrap node: %s", getAccountID().c_str(), b.c_str());
1738 1384 : return bootstrap;
1739 0 : }
1740 :
1741 : void
1742 34 : JamiAccount::trackBuddyPresence(const std::string& buddy_id, bool track)
1743 : {
1744 34 : std::string buddyUri;
1745 : try {
1746 34 : buddyUri = parseJamiUri(buddy_id);
1747 0 : } catch (...) {
1748 0 : JAMI_ERROR("[Account {:s}] Failed to track presence: invalid URI {:s}", getAccountID(), buddy_id);
1749 0 : return;
1750 0 : }
1751 136 : JAMI_LOG("[Account {:s}] {:s} presence for {:s}", getAccountID(), track ? "Track" : "Untrack", buddy_id);
1752 :
1753 34 : auto h = dht::InfoHash(buddyUri);
1754 34 : std::unique_lock lock(buddyInfoMtx);
1755 34 : if (track) {
1756 34 : auto buddy = trackedBuddies_.emplace(h, BuddyInfo {h});
1757 34 : if (buddy.second) {
1758 34 : trackPresence(buddy.first->first, buddy.first->second);
1759 : }
1760 34 : auto it = presenceState_.find(buddyUri);
1761 34 : if (it != presenceState_.end() && it->second != PresenceState::DISCONNECTED) {
1762 1 : lock.unlock();
1763 1 : emitSignal<libjami::PresenceSignal::NewBuddyNotification>(getAccountID(),
1764 : buddyUri,
1765 1 : static_cast<int>(it->second),
1766 : "");
1767 : }
1768 : } else {
1769 0 : auto buddy = trackedBuddies_.find(h);
1770 0 : if (buddy != trackedBuddies_.end()) {
1771 0 : if (auto dht = dht_)
1772 0 : if (dht->isRunning())
1773 0 : dht->cancelListen(h, std::move(buddy->second.listenToken));
1774 0 : trackedBuddies_.erase(buddy);
1775 : }
1776 : }
1777 34 : }
1778 :
1779 : void
1780 35 : JamiAccount::trackPresence(const dht::InfoHash& h, BuddyInfo& buddy)
1781 : {
1782 35 : auto dht = dht_;
1783 35 : if (not dht or not dht->isRunning()) {
1784 1 : return;
1785 : }
1786 68 : buddy.listenToken = dht->listen<DeviceAnnouncement>(h, [this, h](DeviceAnnouncement&& dev, bool expired) {
1787 : bool wasConnected, isConnected;
1788 : {
1789 66 : std::lock_guard lock(buddyInfoMtx);
1790 66 : auto buddy = trackedBuddies_.find(h);
1791 66 : if (buddy == trackedBuddies_.end())
1792 0 : return true;
1793 66 : wasConnected = buddy->second.devices_cnt > 0;
1794 66 : if (expired)
1795 33 : --buddy->second.devices_cnt;
1796 : else
1797 33 : ++buddy->second.devices_cnt;
1798 66 : isConnected = buddy->second.devices_cnt > 0;
1799 66 : }
1800 : // NOTE: the rest can use configurationMtx_, that can be locked during unregister so
1801 : // do not retrigger on dht
1802 66 : runOnMainThread([w = weak(), h, dev, expired, isConnected, wasConnected]() {
1803 66 : auto sthis = w.lock();
1804 66 : if (!sthis)
1805 0 : return;
1806 66 : if (not expired) {
1807 : // Retry messages every time a new device announce its presence
1808 33 : sthis->messageEngine_.onPeerOnline(h.toString());
1809 : }
1810 66 : if (isConnected and not wasConnected) {
1811 32 : sthis->onTrackedBuddyOnline(h);
1812 34 : } else if (not isConnected and wasConnected) {
1813 32 : sthis->onTrackedBuddyOffline(h);
1814 : }
1815 66 : });
1816 :
1817 66 : return true;
1818 34 : });
1819 35 : }
1820 :
1821 : std::map<std::string, bool>
1822 2 : JamiAccount::getTrackedBuddyPresence() const
1823 : {
1824 2 : std::lock_guard lock(buddyInfoMtx);
1825 2 : std::map<std::string, bool> presence_info;
1826 4 : for (const auto& buddy_info_p : trackedBuddies_)
1827 2 : presence_info.emplace(buddy_info_p.first.toString(), buddy_info_p.second.devices_cnt > 0);
1828 4 : return presence_info;
1829 2 : }
1830 :
1831 : void
1832 32 : JamiAccount::onTrackedBuddyOnline(const dht::InfoHash& contactId)
1833 : {
1834 32 : std::string id(contactId.toString());
1835 128 : JAMI_DEBUG("[Account {:s}] Buddy {} online", getAccountID(), id);
1836 32 : auto& state = presenceState_[id];
1837 32 : if (state < PresenceState::AVAILABLE) {
1838 31 : state = PresenceState::AVAILABLE;
1839 31 : emitSignal<libjami::PresenceSignal::NewBuddyNotification>(getAccountID(),
1840 : id,
1841 : static_cast<int>(PresenceState::AVAILABLE),
1842 : "");
1843 : }
1844 :
1845 32 : if (auto details = getContactInfo(id)) {
1846 5 : if (!details->confirmed) {
1847 5 : auto convId = convModule()->getOneToOneConversation(id);
1848 5 : if (convId.empty())
1849 0 : return;
1850 : // In this case, the TrustRequest was sent but never confirmed (cause the contact was
1851 : // offline maybe) To avoid the contact to never receive the conv request, retry there
1852 5 : std::lock_guard lock(configurationMutex_);
1853 5 : if (accountManager_) {
1854 : // Retrieve cached payload for trust request.
1855 10 : auto requestPath = cachePath_ / "requests" / id;
1856 5 : std::vector<uint8_t> payload;
1857 : try {
1858 8 : payload = fileutils::loadFile(requestPath);
1859 3 : } catch (...) {
1860 3 : }
1861 5 : if (payload.size() >= 64000) {
1862 0 : JAMI_WARNING("[Account {:s}] Trust request for contact {:s} is too big, reset payload",
1863 : getAccountID(),
1864 : id);
1865 0 : payload.clear();
1866 : }
1867 5 : accountManager_->sendTrustRequest(id, convId, payload);
1868 5 : }
1869 5 : }
1870 32 : }
1871 32 : }
1872 :
1873 : void
1874 32 : JamiAccount::onTrackedBuddyOffline(const dht::InfoHash& contactId)
1875 : {
1876 32 : auto id = contactId.toString();
1877 128 : JAMI_DEBUG("[Account {:s}] Buddy {} offline", getAccountID(), id);
1878 32 : auto& state = presenceState_[id];
1879 32 : if (state > PresenceState::DISCONNECTED) {
1880 32 : if (state == PresenceState::CONNECTED) {
1881 0 : JAMI_WARNING("[Account {:s}] Buddy {} is not present on the DHT, but P2P connected", getAccountID(), id);
1882 0 : return;
1883 : }
1884 32 : state = PresenceState::DISCONNECTED;
1885 32 : emitSignal<libjami::PresenceSignal::NewBuddyNotification>(getAccountID(),
1886 : id,
1887 : static_cast<int>(PresenceState::DISCONNECTED),
1888 : "");
1889 : }
1890 32 : }
1891 :
1892 : void
1893 692 : JamiAccount::doRegister_()
1894 : {
1895 692 : if (registrationState_ != RegistrationState::TRYING) {
1896 0 : JAMI_ERROR("[Account {}] Already registered", getAccountID());
1897 0 : return;
1898 : }
1899 :
1900 2768 : JAMI_DEBUG("[Account {}] Starting account…", getAccountID());
1901 692 : const auto& conf = config();
1902 :
1903 : try {
1904 692 : if (not accountManager_ or not accountManager_->getInfo())
1905 0 : throw std::runtime_error("No identity configured for this account.");
1906 :
1907 692 : if (dht_->isRunning()) {
1908 8 : JAMI_ERROR("[Account {}] DHT already running (stopping it first).", getAccountID());
1909 2 : dht_->join();
1910 : }
1911 :
1912 692 : convModule()->clearPendingFetch();
1913 :
1914 : // Look for registered name
1915 692 : accountManager_->lookupAddress(accountManager_->getInfo()->accountId,
1916 692 : [w = weak()](const std::string& regName,
1917 : const std::string& /*address*/,
1918 : const NameDirectory::Response& response) {
1919 692 : if (auto this_ = w.lock())
1920 692 : this_->lookupRegisteredName(regName, response);
1921 692 : });
1922 :
1923 692 : dht::DhtRunner::Config config = initDhtConfig(conf);
1924 :
1925 : // check if dht peer service is enabled
1926 692 : if (conf.accountPeerDiscovery or conf.accountPublish) {
1927 0 : peerDiscovery_ = std::make_shared<dht::PeerDiscovery>();
1928 0 : if (conf.accountPeerDiscovery) {
1929 0 : JAMI_LOG("[Account {}] Starting Jami account discovery…", getAccountID());
1930 0 : startAccountDiscovery();
1931 : }
1932 0 : if (conf.accountPublish)
1933 0 : startAccountPublish();
1934 : }
1935 :
1936 692 : dht::DhtRunner::Context context = initDhtContext();
1937 :
1938 692 : dht_->run(conf.dhtPort, config, std::move(context));
1939 692 : dhtBoundPort_ = dht_->getBoundPort();
1940 :
1941 : // Now that the DHT is running and we know the actual bound port,
1942 : // request a UPnP mapping for it.
1943 692 : if (upnpCtrl_) {
1944 2672 : JAMI_LOG("[Account {:s}] UPnP: requesting mapping for DHT port {}", getAccountID(), dhtBoundPort_);
1945 :
1946 668 : if (dhtUpnpMapping_.isValid()) {
1947 0 : upnpCtrl_->releaseMapping(dhtUpnpMapping_);
1948 : }
1949 :
1950 668 : dhtUpnpMapping_.enableAutoUpdate(true);
1951 :
1952 668 : dhtnet::upnp::Mapping desired(dhtnet::upnp::PortType::UDP, dhtBoundPort_, dhtBoundPort_);
1953 668 : dhtUpnpMapping_.updateFrom(desired);
1954 :
1955 668 : dhtUpnpMapping_.setNotifyCallback([w = weak()](const dhtnet::upnp::Mapping::sharedPtr_t& mapRes) {
1956 650 : if (auto accPtr = w.lock()) {
1957 650 : auto& dhtMap = accPtr->dhtUpnpMapping_;
1958 650 : const auto& accId = accPtr->getAccountID();
1959 :
1960 2600 : JAMI_LOG("[Account {:s}] DHT UPnP mapping changed to {:s}", accId, mapRes->toString(true));
1961 :
1962 650 : if (dhtMap.getMapKey() != mapRes->getMapKey() or dhtMap.getState() != mapRes->getState()) {
1963 637 : dhtMap.updateFrom(mapRes);
1964 637 : if (mapRes->getState() == dhtnet::upnp::MappingState::OPEN) {
1965 0 : JAMI_LOG("[Account {:s}] Mapping {:s} successfully allocated", accId, dhtMap.toString());
1966 0 : accPtr->dht_->connectivityChanged();
1967 637 : } else if (mapRes->getState() == dhtnet::upnp::MappingState::FAILED) {
1968 2548 : JAMI_WARNING("[Account {:s}] UPnP mapping failed", accId);
1969 : }
1970 : } else {
1971 13 : dhtMap.updateFrom(mapRes);
1972 : }
1973 650 : }
1974 650 : });
1975 :
1976 668 : upnpCtrl_->reserveMapping(dhtUpnpMapping_);
1977 668 : }
1978 :
1979 1384 : for (const auto& bootstrap : loadBootstrap())
1980 1384 : dht_->bootstrap(bootstrap);
1981 :
1982 692 : accountManager_->setDht(dht_);
1983 :
1984 692 : if (conf.dhtProxyServerEnabled) {
1985 0 : dht::ProxyServerConfig proxyConfig;
1986 0 : proxyConfig.port = conf.dhtProxyServerPort;
1987 0 : proxyConfig.identity = id_;
1988 0 : dhtProxyServer_ = std::make_shared<dht::DhtProxyServer>(dht_, proxyConfig);
1989 0 : } else {
1990 692 : dhtProxyServer_.reset();
1991 : }
1992 :
1993 692 : std::unique_lock lkCM(connManagerMtx_);
1994 692 : initConnectionManager();
1995 692 : connectionManager_->dhtStarted();
1996 1461 : connectionManager_->onICERequest([this](const DeviceId& deviceId) { return onICERequest(deviceId); });
1997 692 : connectionManager_->onChannelRequest([this](const std::shared_ptr<dht::crypto::Certificate>& cert,
1998 2221 : const std::string& name) { return onChannelRequest(cert, name); });
1999 692 : connectionManager_->onConnectionReady(
2000 4223 : [this](const DeviceId& deviceId, const std::string& name, std::shared_ptr<dhtnet::ChannelSocket> channel) {
2001 4223 : onConnectionReady(deviceId, name, std::move(channel));
2002 4222 : });
2003 692 : lkCM.unlock();
2004 :
2005 692 : if (!conf.managerUri.empty() && accountManager_) {
2006 0 : dynamic_cast<ServerAccountManager*>(accountManager_.get())->onNeedsMigration([this]() {
2007 0 : editConfig([&](JamiAccountConfig& conf) {
2008 0 : conf.receipt.clear();
2009 0 : conf.receiptSignature.clear();
2010 0 : });
2011 0 : Migration::setState(accountID_, Migration::State::INVALID);
2012 0 : setRegistrationState(RegistrationState::ERROR_NEED_MIGRATION);
2013 0 : });
2014 0 : dynamic_cast<ServerAccountManager*>(accountManager_.get())
2015 0 : ->syncBlueprintConfig([this](const std::map<std::string, std::string>& config) {
2016 0 : editConfig([&](JamiAccountConfig& conf) { conf.fromMap(config); });
2017 0 : emitSignal<libjami::ConfigurationSignal::AccountDetailsChanged>(getAccountID(), getAccountDetails());
2018 0 : });
2019 : }
2020 :
2021 692 : std::lock_guard lock(buddyInfoMtx);
2022 693 : for (auto& buddy : trackedBuddies_) {
2023 1 : buddy.second.devices_cnt = 0;
2024 1 : trackPresence(buddy.first, buddy.second);
2025 : }
2026 692 : } catch (const std::exception& e) {
2027 0 : JAMI_ERR("Error registering DHT account: %s", e.what());
2028 0 : setRegistrationState(RegistrationState::ERROR_GENERIC);
2029 0 : }
2030 : }
2031 :
2032 : void
2033 690 : JamiAccount::lookupRegisteredName(const std::string& regName, const NameDirectory::Response& response)
2034 : {
2035 690 : if (response == NameDirectory::Response::found or response == NameDirectory::Response::notFound) {
2036 690 : const auto& nameResult = response == NameDirectory::Response::found ? regName : "";
2037 690 : if (setRegisteredName(nameResult)) {
2038 0 : editConfig([&](JamiAccountConfig& config) { config.registeredName = nameResult; });
2039 0 : emitSignal<libjami::ConfigurationSignal::VolatileDetailsChanged>(accountID_, getVolatileAccountDetails());
2040 : }
2041 690 : }
2042 690 : }
2043 :
2044 : dht::DhtRunner::Config
2045 692 : JamiAccount::initDhtConfig(const JamiAccountConfig& conf)
2046 : {
2047 692 : dht::DhtRunner::Config config {};
2048 692 : config.dht_config.node_config.network = 0;
2049 692 : config.dht_config.node_config.maintain_storage = false;
2050 692 : config.dht_config.node_config.persist_path = (cachePath_ / "dhtstate").string();
2051 692 : config.dht_config.id = id_;
2052 692 : config.dht_config.cert_cache_all = true;
2053 692 : config.push_node_id = getAccountID();
2054 692 : config.push_token = conf.deviceKey;
2055 692 : config.push_topic = conf.notificationTopic;
2056 692 : config.push_platform = conf.platform;
2057 692 : config.proxy_user_agent = jami::userAgent();
2058 692 : config.threaded = true;
2059 692 : config.peer_discovery = conf.dhtPeerDiscovery;
2060 692 : config.peer_publish = conf.dhtPeerDiscovery;
2061 692 : if (conf.proxyEnabled)
2062 0 : config.proxy_server = proxyServerCached_;
2063 :
2064 692 : if (not config.proxy_server.empty()) {
2065 0 : JAMI_LOG("[Account {}] Using proxy server {}", getAccountID(), config.proxy_server);
2066 0 : if (not config.push_token.empty()) {
2067 0 : JAMI_LOG("[Account {}] using push notifications with platform: {}, topic: {}, token: {}",
2068 : getAccountID(),
2069 : config.push_platform,
2070 : config.push_topic,
2071 : config.push_token);
2072 : }
2073 : }
2074 692 : return config;
2075 0 : }
2076 :
2077 : dht::DhtRunner::Context
2078 692 : JamiAccount::initDhtContext()
2079 : {
2080 692 : dht::DhtRunner::Context context {};
2081 692 : context.peerDiscovery = peerDiscovery_;
2082 692 : context.rng = std::make_unique<std::mt19937_64>(dht::crypto::getDerivedRandomEngine(rand));
2083 :
2084 692 : auto dht_log_level = Manager::instance().dhtLogLevel;
2085 692 : if (dht_log_level > 0) {
2086 0 : context.logger = logger_;
2087 : }
2088 :
2089 75 : context.certificateStore = [&](const dht::InfoHash& pk_id) {
2090 75 : std::vector<std::shared_ptr<dht::crypto::Certificate>> ret;
2091 150 : if (auto cert = certStore().getCertificate(pk_id.toString()))
2092 75 : ret.emplace_back(std::move(cert));
2093 300 : JAMI_LOG("[Account {}] Query for local certificate store: {}: {} found.",
2094 : getAccountID(),
2095 : pk_id.toString(),
2096 : ret.size());
2097 75 : return ret;
2098 692 : };
2099 :
2100 1322 : context.certificateStorePkId = [&](const DeviceId& pk_id) {
2101 1322 : std::vector<std::shared_ptr<dht::crypto::Certificate>> ret;
2102 2644 : if (auto cert = certStore().getCertificate(pk_id.toString()))
2103 1322 : ret.emplace_back(std::move(cert));
2104 5288 : JAMI_LOG("[Account {}] Query for local certificate store: {}: {} found.",
2105 : getAccountID(),
2106 : pk_id.toString(),
2107 : ret.size());
2108 1322 : return ret;
2109 692 : };
2110 :
2111 5928 : context.statusChangedCallback = [this](dht::NodeStatus s4, dht::NodeStatus s6) {
2112 7904 : JAMI_LOG("[Account {}] DHT status: IPv4 {}; IPv6 {}", getAccountID(), dhtStatusStr(s4), dhtStatusStr(s6));
2113 : RegistrationState state;
2114 1976 : auto newStatus = std::max(s4, s6);
2115 1976 : switch (newStatus) {
2116 667 : case dht::NodeStatus::Connecting:
2117 667 : state = RegistrationState::TRYING;
2118 667 : break;
2119 1309 : case dht::NodeStatus::Connected:
2120 1309 : state = RegistrationState::REGISTERED;
2121 1309 : break;
2122 0 : case dht::NodeStatus::Disconnected:
2123 0 : state = RegistrationState::UNREGISTERED;
2124 0 : break;
2125 0 : default:
2126 0 : state = RegistrationState::ERROR_GENERIC;
2127 0 : break;
2128 : }
2129 :
2130 1976 : setRegistrationState(state);
2131 2668 : };
2132 :
2133 3390 : context.identityAnnouncedCb = [this](bool ok) {
2134 687 : if (!ok) {
2135 60 : JAMI_ERROR("[Account {}] Identity announcement failed", getAccountID());
2136 15 : return;
2137 : }
2138 2688 : JAMI_WARNING("[Account {}] Identity announcement succeeded", getAccountID());
2139 672 : accountManager_
2140 2096 : ->startSync([this](const std::shared_ptr<dht::crypto::Certificate>& crt) { onAccountDeviceFound(crt); },
2141 671 : [this] { onAccountDeviceAnnounced(); },
2142 672 : publishPresence_);
2143 692 : };
2144 :
2145 692 : return context;
2146 0 : }
2147 :
2148 : void
2149 752 : JamiAccount::onAccountDeviceFound(const std::shared_ptr<dht::crypto::Certificate>& crt)
2150 : {
2151 752 : if (jami::Manager::instance().syncOnRegister) {
2152 752 : if (!crt)
2153 678 : return;
2154 752 : auto deviceId = crt->getLongId().toString();
2155 752 : if (accountManager_->getInfo()->deviceId == deviceId)
2156 678 : return;
2157 :
2158 74 : dht::ThreadPool::io().run([w = weak(), deviceId, crt] {
2159 74 : auto shared = w.lock();
2160 74 : if (!shared)
2161 0 : return;
2162 74 : std::unique_lock lk(shared->connManagerMtx_);
2163 74 : shared->initConnectionManager();
2164 74 : lk.unlock();
2165 74 : std::shared_lock slk(shared->connManagerMtx_);
2166 : // NOTE: connectionManager_ and channelHandlers_ get initialized at the
2167 : // same time and are both protected by connManagerMtx_, so this check
2168 : // ensures that the access to channelHandlers_ below is valid.
2169 74 : if (!shared->connectionManager_)
2170 0 : return;
2171 74 : shared->requestMessageConnection(shared->getUsername(), crt->getLongId(), "sync");
2172 74 : if (!shared->syncModule()->isConnected(crt->getLongId())) {
2173 72 : shared->channelHandlers_[Uri::Scheme::SYNC]
2174 72 : ->connect(crt->getLongId(),
2175 : "",
2176 72 : [](const std::shared_ptr<dhtnet::ChannelSocket>& /*socket*/,
2177 72 : const DeviceId& /*deviceId*/) {});
2178 : }
2179 74 : });
2180 752 : }
2181 : }
2182 :
2183 : void
2184 671 : JamiAccount::onAccountDeviceAnnounced()
2185 : {
2186 671 : if (jami::Manager::instance().syncOnRegister) {
2187 671 : deviceAnnounced_ = true;
2188 :
2189 : // Bootstrap at the end to avoid to be long to load.
2190 671 : dht::ThreadPool::io().run([w = weak()] {
2191 671 : if (auto shared = w.lock())
2192 671 : shared->convModule()->bootstrap();
2193 671 : });
2194 671 : emitSignal<libjami::ConfigurationSignal::VolatileDetailsChanged>(accountID_, getVolatileAccountDetails());
2195 : }
2196 671 : }
2197 :
2198 : bool
2199 769 : JamiAccount::onICERequest(const DeviceId& deviceId)
2200 : {
2201 769 : std::promise<bool> accept;
2202 769 : std::future<bool> fut = accept.get_future();
2203 769 : accountManager_->findCertificate(deviceId, [this, &accept](const std::shared_ptr<dht::crypto::Certificate>& cert) {
2204 769 : if (!cert) {
2205 0 : accept.set_value(false);
2206 0 : return;
2207 : }
2208 769 : dht::InfoHash peer_account_id;
2209 769 : auto res = accountManager_->onPeerCertificate(cert, this->config().dhtPublicInCalls, peer_account_id);
2210 3076 : JAMI_LOG("[Account {}] [device {}] {} ICE request from {}",
2211 : getAccountID(),
2212 : cert->getLongId(),
2213 : res ? "Accepting" : "Discarding",
2214 : peer_account_id);
2215 769 : accept.set_value(res);
2216 : });
2217 769 : fut.wait();
2218 769 : auto result = fut.get();
2219 769 : return result;
2220 769 : }
2221 :
2222 : bool
2223 2221 : JamiAccount::onChannelRequest(const std::shared_ptr<dht::crypto::Certificate>& cert, const std::string& name)
2224 : {
2225 8878 : JAMI_LOG("[Account {}] [device {}] New channel requested: '{}'", getAccountID(), cert->getLongId(), name);
2226 :
2227 2220 : if (this->config().turnEnabled && turnCache_) {
2228 2221 : auto addr = turnCache_->getResolvedTurn();
2229 2221 : if (addr == std::nullopt) {
2230 : // If TURN is enabled, but no TURN cached, there can be a temporary
2231 : // resolution error to solve. Sometimes, a connectivity change is not
2232 : // enough, so even if this case is really rare, it should be easy to avoid.
2233 34 : turnCache_->refresh();
2234 : }
2235 : }
2236 :
2237 2221 : auto uri = Uri(name);
2238 2217 : std::shared_lock lk(connManagerMtx_);
2239 2218 : auto itHandler = channelHandlers_.find(uri.scheme());
2240 2220 : if (itHandler != channelHandlers_.end() && itHandler->second)
2241 2110 : return itHandler->second->onRequest(cert, name);
2242 109 : return name == "sip";
2243 2220 : }
2244 :
2245 : void
2246 4224 : JamiAccount::onConnectionReady(const DeviceId& deviceId,
2247 : const std::string& name,
2248 : std::shared_ptr<dhtnet::ChannelSocket> channel)
2249 : {
2250 4224 : if (channel) {
2251 4225 : auto cert = channel->peerCertificate();
2252 4224 : if (!cert || !cert->issuer)
2253 0 : return;
2254 4225 : auto peerId = cert->issuer->getId().toString();
2255 : // A connection request can be sent just before member is banned and this must be ignored.
2256 4223 : if (accountManager()->getCertificateStatus(peerId) == dhtnet::tls::TrustStore::PermissionStatus::BANNED) {
2257 60 : channel->shutdown();
2258 60 : return;
2259 : }
2260 4165 : if (name == "sip") {
2261 174 : cacheSIPConnection(std::move(channel), peerId, deviceId);
2262 3990 : } else if (name.find("git://") == 0) {
2263 1670 : auto sep = name.find_last_of('/');
2264 1670 : auto conversationId = name.substr(sep + 1);
2265 1670 : auto remoteDevice = name.substr(6, sep - 6);
2266 :
2267 1670 : if (channel->isInitiator()) {
2268 : // Check if wanted remote is our side (git://remoteDevice/conversationId)
2269 824 : return;
2270 : }
2271 :
2272 : // Check if pull from banned device
2273 846 : if (convModule()->isBanned(conversationId, remoteDevice)) {
2274 0 : JAMI_WARNING("[Account {:s}] [Conversation {}] Git server requested, but the "
2275 : "device is unauthorized ({:s}) ",
2276 : getAccountID(),
2277 : conversationId,
2278 : remoteDevice);
2279 0 : channel->shutdown();
2280 0 : return;
2281 : }
2282 :
2283 846 : auto sock = convModule()->gitSocket(deviceId.toString(), conversationId);
2284 846 : if (sock == channel) {
2285 : // The onConnectionReady is already used as client (for retrieving messages)
2286 : // So it's not the server socket
2287 0 : return;
2288 : }
2289 3384 : JAMI_LOG("[Account {:s}] [Conversation {}] [device {}] Git server requested",
2290 : accountID_,
2291 : conversationId,
2292 : deviceId.toString());
2293 846 : auto gs = std::make_unique<GitServer>(accountID_, conversationId, channel);
2294 846 : syncCnt_.fetch_add(1);
2295 846 : gs->setOnFetched([w = weak(), conversationId, deviceId](const std::string& commit) {
2296 1117 : dht::ThreadPool::computation().run([w, conversationId, deviceId, commit]() {
2297 1117 : if (auto shared = w.lock()) {
2298 1117 : shared->convModule()->setFetched(conversationId, deviceId.toString(), commit);
2299 2234 : if (shared->syncCnt_.fetch_sub(1) == 1) {
2300 308 : emitSignal<libjami::ConversationSignal::ConversationCloned>(shared->getAccountID().c_str());
2301 : }
2302 1117 : }
2303 1117 : });
2304 1117 : });
2305 846 : const dht::Value::Id serverId = ValueIdDist()(rand);
2306 : {
2307 846 : std::lock_guard lk(gitServersMtx_);
2308 846 : gitServers_[serverId] = std::move(gs);
2309 846 : }
2310 846 : channel->onShutdown([w = weak(), serverId](const std::error_code&) {
2311 : // Run on main thread to avoid to be in mxSock's eventLoop
2312 846 : runOnMainThread([serverId, w]() {
2313 846 : if (auto sthis = w.lock()) {
2314 846 : std::lock_guard lk(sthis->gitServersMtx_);
2315 846 : sthis->gitServers_.erase(serverId);
2316 1692 : }
2317 846 : });
2318 846 : });
2319 2494 : } else {
2320 : // TODO move git://
2321 2320 : std::shared_lock lk(connManagerMtx_);
2322 2320 : auto uri = Uri(name);
2323 2318 : auto itHandler = channelHandlers_.find(uri.scheme());
2324 2320 : if (itHandler != channelHandlers_.end() && itHandler->second)
2325 2312 : itHandler->second->onReady(cert, name, std::move(channel));
2326 2320 : }
2327 5108 : }
2328 : }
2329 :
2330 : void
2331 1564 : JamiAccount::conversationNeedsSyncing(std::shared_ptr<SyncMsg>&& syncMsg)
2332 : {
2333 1564 : dht::ThreadPool::computation().run([w = weak(), syncMsg] {
2334 1564 : if (auto shared = w.lock()) {
2335 1564 : const auto& config = shared->config();
2336 : // For JAMS account, we must update the server
2337 : // for now, only sync with the JAMS server for changes to the conversation list
2338 1564 : if (!config.managerUri.empty() && !syncMsg)
2339 0 : if (auto am = shared->accountManager())
2340 0 : am->syncDevices();
2341 1564 : if (auto* sm = shared->syncModule())
2342 1564 : sm->syncWithConnected(syncMsg);
2343 1564 : }
2344 1564 : });
2345 1564 : }
2346 :
2347 : uint64_t
2348 14004 : JamiAccount::conversationSendMessage(const std::string& uri,
2349 : const DeviceId& device,
2350 : const std::map<std::string, std::string>& msg,
2351 : uint64_t token)
2352 : {
2353 : // No need to retrigger, sendTextMessage will call
2354 : // messageEngine_.sendMessage, already retriggering on
2355 : // main thread.
2356 14004 : auto deviceId = device ? device.toString() : "";
2357 28002 : return sendTextMessage(uri, deviceId, msg, token);
2358 14005 : }
2359 :
2360 : void
2361 2318 : JamiAccount::onConversationNeedSocket(const std::string& convId,
2362 : const std::string& deviceId,
2363 : ChannelCb&& cb,
2364 : const std::string& type)
2365 : {
2366 2318 : dht::ThreadPool::io().run([w = weak(), convId, deviceId, cb = std::move(cb), type] {
2367 2318 : auto shared = w.lock();
2368 2318 : if (!shared)
2369 0 : return;
2370 2318 : if (auto socket = shared->convModule()->gitSocket(deviceId, convId)) {
2371 1399 : if (!cb(socket))
2372 0 : socket->shutdown();
2373 : else
2374 1400 : cb({});
2375 1400 : return;
2376 2318 : }
2377 918 : std::shared_lock lkCM(shared->connManagerMtx_);
2378 918 : if (!shared->connectionManager_) {
2379 11 : lkCM.unlock();
2380 11 : cb({});
2381 11 : return;
2382 : }
2383 :
2384 4535 : shared->connectionManager_->connectDevice(
2385 907 : DeviceId(deviceId),
2386 2721 : fmt::format("git://{}/{}", deviceId, convId),
2387 1814 : [w, cb = std::move(cb), convId](std::shared_ptr<dhtnet::ChannelSocket> socket, const DeviceId&) {
2388 906 : dht::ThreadPool::io().run([w, cb = std::move(cb), socket = std::move(socket), convId] {
2389 906 : if (socket) {
2390 844 : socket->onShutdown([w, deviceId = socket->deviceId(), convId](const std::error_code&) {
2391 844 : dht::ThreadPool::io().run([w, deviceId, convId] {
2392 844 : if (auto shared = w.lock())
2393 844 : shared->convModule()->removeGitSocket(deviceId.toString(), convId);
2394 844 : });
2395 844 : });
2396 844 : if (!cb(socket))
2397 1 : socket->shutdown();
2398 : } else
2399 62 : cb({});
2400 906 : });
2401 906 : },
2402 : false,
2403 : false,
2404 907 : type);
2405 2329 : });
2406 2318 : }
2407 :
2408 : void
2409 733 : JamiAccount::onConversationNeedSwarmSocket(const std::string& convId,
2410 : const std::string& deviceId,
2411 : ChannelCb&& cb,
2412 : const std::string& type)
2413 : {
2414 733 : dht::ThreadPool::io().run([w = weak(), convId, deviceId, cb = std::forward<ChannelCb&&>(cb), type] {
2415 732 : auto shared = w.lock();
2416 731 : if (!shared)
2417 0 : return;
2418 731 : auto* cm = shared->convModule();
2419 732 : std::shared_lock lkCM(shared->connManagerMtx_);
2420 732 : if (!shared->connectionManager_ || !cm || cm->isBanned(convId, deviceId)) {
2421 80 : asio::post(*Manager::instance().ioContext(), [cb = std::move(cb)] { cb({}); });
2422 40 : return;
2423 : }
2424 693 : DeviceId device(deviceId);
2425 693 : auto swarmUri = fmt::format("swarm://{}", convId);
2426 691 : if (!shared->connectionManager_->isConnecting(device, swarmUri)) {
2427 1324 : shared->connectionManager_->connectDevice(
2428 : device,
2429 : swarmUri,
2430 660 : [w,
2431 662 : cb = std::move(cb),
2432 : wam = std::weak_ptr(shared->accountManager())](std::shared_ptr<dhtnet::ChannelSocket> socket,
2433 : const DeviceId& deviceId) {
2434 662 : dht::ThreadPool::io().run([w, wam, cb = std::move(cb), socket = std::move(socket), deviceId] {
2435 660 : if (socket) {
2436 350 : auto shared = w.lock();
2437 350 : auto am = wam.lock();
2438 350 : auto remoteCert = socket->peerCertificate();
2439 350 : if (!remoteCert || !remoteCert->issuer) {
2440 0 : cb(nullptr);
2441 0 : return;
2442 : }
2443 350 : auto uri = remoteCert->issuer->getId().toString();
2444 700 : if (!shared || !am
2445 700 : || am->getCertificateStatus(uri) == dhtnet::tls::TrustStore::PermissionStatus::BANNED) {
2446 0 : cb(nullptr);
2447 0 : return;
2448 : }
2449 350 : shared->requestMessageConnection(uri, deviceId, "");
2450 350 : }
2451 660 : cb(socket);
2452 : });
2453 662 : });
2454 : }
2455 773 : });
2456 733 : }
2457 :
2458 : void
2459 1 : JamiAccount::conversationOneToOneReceive(const std::string& convId, const std::string& from)
2460 : {
2461 1 : accountManager_->findCertificate(dht::InfoHash(from),
2462 1 : [this, from, convId](const std::shared_ptr<dht::crypto::Certificate>& cert) {
2463 1 : const auto* info = accountManager_->getInfo();
2464 1 : if (!cert || !info)
2465 0 : return;
2466 2 : info->contacts->onTrustRequest(dht::InfoHash(from),
2467 : cert->getSharedPublicKey(),
2468 : time(nullptr),
2469 : false,
2470 1 : convId,
2471 : {});
2472 : });
2473 1 : }
2474 :
2475 : ConversationModule*
2476 28370 : JamiAccount::convModule(bool noCreation)
2477 : {
2478 28370 : if (noCreation)
2479 5680 : return convModule_.get();
2480 22690 : if (!accountManager() || currentDeviceId() == "") {
2481 0 : JAMI_ERROR("[Account {}] Calling convModule() with an uninitialized account", getAccountID());
2482 0 : return nullptr;
2483 : }
2484 22686 : std::unique_lock lock(configurationMutex_);
2485 22690 : std::lock_guard lk(moduleMtx_);
2486 22689 : if (!convModule_) {
2487 1336 : convModule_ = std::make_unique<ConversationModule>(
2488 668 : shared(),
2489 668 : accountManager_,
2490 1564 : [this](auto&& syncMsg) { conversationNeedsSyncing(std::forward<std::shared_ptr<SyncMsg>>(syncMsg)); },
2491 13998 : [this](auto&& uri, auto&& device, auto&& msg, auto token = 0) {
2492 13998 : return conversationSendMessage(uri, device, msg, token);
2493 : },
2494 2318 : [this](const auto& convId, const auto& deviceId, auto&& cb, const auto& connectionType) {
2495 2318 : onConversationNeedSocket(convId, deviceId, std::forward<decltype(cb)>(cb), connectionType);
2496 2318 : },
2497 733 : [this](const auto& convId, const auto& deviceId, auto&& cb, const auto& connectionType) {
2498 733 : onConversationNeedSwarmSocket(convId, deviceId, std::forward<decltype(cb)>(cb), connectionType);
2499 733 : },
2500 1 : [this](const auto& convId, const auto& from) { conversationOneToOneReceive(convId, from); },
2501 1336 : autoLoadConversations_);
2502 : }
2503 22691 : return convModule_.get();
2504 22693 : }
2505 :
2506 : SyncModule*
2507 2586 : JamiAccount::syncModule()
2508 : {
2509 2586 : if (!accountManager() || currentDeviceId() == "") {
2510 0 : JAMI_ERR() << "Calling syncModule() with an uninitialized account.";
2511 0 : return nullptr;
2512 : }
2513 2586 : std::lock_guard lk(moduleMtx_);
2514 2586 : if (!syncModule_)
2515 656 : syncModule_ = std::make_unique<SyncModule>(shared());
2516 2586 : return syncModule_.get();
2517 2586 : }
2518 :
2519 : void
2520 12805 : JamiAccount::onTextMessage(const std::string& id,
2521 : const std::string& from,
2522 : const std::shared_ptr<dht::crypto::Certificate>& peerCert,
2523 : const std::map<std::string, std::string>& payloads)
2524 : {
2525 : try {
2526 12805 : const std::string fromUri {parseJamiUri(from)};
2527 12802 : SIPAccountBase::onTextMessage(id, fromUri, peerCert, payloads);
2528 12807 : } catch (...) {
2529 0 : }
2530 12807 : }
2531 :
2532 : void
2533 0 : JamiAccount::loadConversation(const std::string& convId)
2534 : {
2535 0 : if (auto* cm = convModule(true))
2536 0 : cm->loadSingleConversation(convId);
2537 0 : }
2538 :
2539 : void
2540 956 : JamiAccount::doUnregister(bool forceShutdownConnections)
2541 : {
2542 956 : std::unique_lock lock(configurationMutex_);
2543 956 : if (registrationState_ >= RegistrationState::ERROR_GENERIC) {
2544 142 : return;
2545 : }
2546 :
2547 814 : std::mutex mtx;
2548 814 : std::condition_variable cv;
2549 814 : bool shutdown_complete {false};
2550 :
2551 814 : if (peerDiscovery_) {
2552 0 : peerDiscovery_->stopPublish(PEER_DISCOVERY_JAMI_SERVICE);
2553 0 : peerDiscovery_->stopDiscovery(PEER_DISCOVERY_JAMI_SERVICE);
2554 : }
2555 :
2556 814 : JAMI_WARN("[Account %s] Unregistering account %p", getAccountID().c_str(), this);
2557 814 : dht_->shutdown(
2558 814 : [&] {
2559 814 : JAMI_WARN("[Account %s] DHT shutdown complete", getAccountID().c_str());
2560 814 : std::lock_guard lock(mtx);
2561 814 : shutdown_complete = true;
2562 814 : cv.notify_all();
2563 814 : },
2564 : true);
2565 :
2566 : {
2567 814 : std::lock_guard lk(pendingCallsMutex_);
2568 814 : pendingCalls_.clear();
2569 814 : }
2570 :
2571 : // Stop all current P2P connections if account is disabled
2572 : // or if explicitly requested by the caller.
2573 : // NOTE: Leaving the connections open is useful when changing an account's config.
2574 814 : if (not isEnabled() || forceShutdownConnections)
2575 798 : shutdownConnections();
2576 :
2577 : // Release current UPnP mapping if any.
2578 814 : if (upnpCtrl_ and dhtUpnpMapping_.isValid()) {
2579 0 : upnpCtrl_->releaseMapping(dhtUpnpMapping_);
2580 : }
2581 :
2582 : {
2583 814 : std::unique_lock lock(mtx);
2584 2314 : cv.wait(lock, [&] { return shutdown_complete; });
2585 814 : }
2586 814 : dht_->join();
2587 814 : setRegistrationState(RegistrationState::UNREGISTERED);
2588 :
2589 814 : lock.unlock();
2590 :
2591 : #ifdef ENABLE_PLUGIN
2592 814 : jami::Manager::instance().getJamiPluginManager().getChatServicesManager().cleanChatSubjects(getAccountID());
2593 : #endif
2594 956 : }
2595 :
2596 : void
2597 5030 : JamiAccount::setRegistrationState(RegistrationState state, int detail_code, const std::string& detail_str)
2598 : {
2599 5030 : if (registrationState_ != state) {
2600 3614 : if (state == RegistrationState::REGISTERED) {
2601 2752 : JAMI_WARNING("[Account {}] Connected", getAccountID());
2602 688 : turnCache_->refresh();
2603 688 : if (connectionManager_)
2604 680 : connectionManager_->storeActiveIpAddress();
2605 2926 : } else if (state == RegistrationState::TRYING) {
2606 2768 : JAMI_WARNING("[Account {}] Connecting…", getAccountID());
2607 : } else {
2608 2234 : deviceAnnounced_ = false;
2609 8935 : JAMI_WARNING("[Account {}] Disconnected", getAccountID());
2610 : }
2611 : }
2612 : // Update registrationState_ & emit signals
2613 5030 : Account::setRegistrationState(state, detail_code, detail_str);
2614 5030 : }
2615 :
2616 : void
2617 0 : JamiAccount::reloadContacts()
2618 : {
2619 0 : accountManager_->reloadContacts();
2620 0 : }
2621 :
2622 : void
2623 0 : JamiAccount::connectivityChanged()
2624 : {
2625 0 : if (not isUsable()) {
2626 : // nothing to do
2627 0 : return;
2628 : }
2629 0 : JAMI_WARNING("[{}] connectivityChanged", getAccountID());
2630 :
2631 0 : if (auto* cm = convModule())
2632 0 : cm->connectivityChanged();
2633 0 : dht_->connectivityChanged();
2634 : {
2635 0 : std::shared_lock lkCM(connManagerMtx_);
2636 0 : if (connectionManager_) {
2637 0 : connectionManager_->connectivityChanged();
2638 : // reset cache
2639 0 : connectionManager_->setPublishedAddress({});
2640 : }
2641 0 : }
2642 : }
2643 :
2644 : bool
2645 0 : JamiAccount::findCertificate(const dht::InfoHash& h,
2646 : std::function<void(const std::shared_ptr<dht::crypto::Certificate>&)>&& cb)
2647 : {
2648 0 : if (accountManager_)
2649 0 : return accountManager_->findCertificate(h, std::move(cb));
2650 0 : return false;
2651 : }
2652 :
2653 : bool
2654 0 : JamiAccount::findCertificate(const dht::PkId& id,
2655 : std::function<void(const std::shared_ptr<dht::crypto::Certificate>&)>&& cb)
2656 : {
2657 0 : if (accountManager_)
2658 0 : return accountManager_->findCertificate(id, std::move(cb));
2659 0 : return false;
2660 : }
2661 :
2662 : bool
2663 79 : JamiAccount::findCertificate(const std::string& crt_id)
2664 : {
2665 79 : if (accountManager_)
2666 79 : return accountManager_->findCertificate(dht::InfoHash(crt_id));
2667 0 : return false;
2668 : }
2669 :
2670 : bool
2671 82 : JamiAccount::setCertificateStatus(const std::string& cert_id, dhtnet::tls::TrustStore::PermissionStatus status)
2672 : {
2673 82 : bool done = accountManager_ ? accountManager_->setCertificateStatus(cert_id, status) : false;
2674 82 : if (done) {
2675 79 : findCertificate(cert_id);
2676 79 : emitSignal<libjami::ConfigurationSignal::CertificateStateChanged>(getAccountID(),
2677 : cert_id,
2678 : dhtnet::tls::TrustStore::statusToStr(status));
2679 : }
2680 82 : return done;
2681 : }
2682 :
2683 : bool
2684 0 : JamiAccount::setCertificateStatus(const std::shared_ptr<crypto::Certificate>& cert,
2685 : dhtnet::tls::TrustStore::PermissionStatus status,
2686 : bool local)
2687 : {
2688 0 : bool done = accountManager_ ? accountManager_->setCertificateStatus(cert, status, local) : false;
2689 0 : if (done) {
2690 0 : findCertificate(cert->getLongId().toString());
2691 0 : emitSignal<libjami::ConfigurationSignal::CertificateStateChanged>(getAccountID(),
2692 0 : cert->getLongId().toString(),
2693 : dhtnet::tls::TrustStore::statusToStr(status));
2694 : }
2695 0 : return done;
2696 : }
2697 :
2698 : std::vector<std::string>
2699 0 : JamiAccount::getCertificatesByStatus(dhtnet::tls::TrustStore::PermissionStatus status)
2700 : {
2701 0 : if (accountManager_)
2702 0 : return accountManager_->getCertificatesByStatus(status);
2703 0 : return {};
2704 : }
2705 :
2706 : bool
2707 0 : JamiAccount::isMessageTreated(dht::Value::Id id)
2708 : {
2709 0 : std::lock_guard lock(messageMutex_);
2710 0 : return !treatedMessages_.add(id);
2711 0 : }
2712 :
2713 : bool
2714 14 : JamiAccount::sha3SumVerify() const
2715 : {
2716 14 : return !noSha3sumVerification_;
2717 : }
2718 :
2719 : #ifdef LIBJAMI_TEST
2720 : void
2721 1 : JamiAccount::noSha3sumVerification(bool newValue)
2722 : {
2723 1 : noSha3sumVerification_ = newValue;
2724 1 : }
2725 : #endif
2726 :
2727 : std::map<std::string, std::string>
2728 186 : JamiAccount::getKnownDevices() const
2729 : {
2730 186 : std::lock_guard lock(configurationMutex_);
2731 186 : if (not accountManager_ or not accountManager_->getInfo())
2732 0 : return {};
2733 186 : std::map<std::string, std::string> ids;
2734 1375 : for (const auto& d : accountManager_->getKnownDevices()) {
2735 1189 : auto id = d.first.toString();
2736 1189 : auto label = d.second.name.empty() ? id.substr(0, 8) : d.second.name;
2737 1189 : ids.emplace(std::move(id), std::move(label));
2738 1189 : }
2739 186 : return ids;
2740 186 : }
2741 :
2742 : void
2743 0 : JamiAccount::loadCachedUrl(const std::string& url,
2744 : const std::filesystem::path& cachePath,
2745 : const std::chrono::seconds& cacheDuration,
2746 : const std::function<void(const dht::http::Response& response)>& cb)
2747 : {
2748 0 : dht::ThreadPool::io().run([cb, url, cachePath, cacheDuration, w = weak()]() {
2749 : try {
2750 0 : std::string data;
2751 : {
2752 0 : std::lock_guard lk(dhtnet::fileutils::getFileLock(cachePath));
2753 0 : data = fileutils::loadCacheTextFile(cachePath, cacheDuration);
2754 0 : }
2755 0 : dht::http::Response ret;
2756 0 : ret.body = std::move(data);
2757 0 : ret.status_code = 200;
2758 0 : cb(ret);
2759 0 : } catch (const std::exception& e) {
2760 0 : JAMI_LOG("Failed to load '{}' from '{}': {}", url, cachePath, e.what());
2761 :
2762 0 : if (auto sthis = w.lock()) {
2763 : auto req = std::make_shared<dht::http::Request>(
2764 0 : *Manager::instance().ioContext(), url, [cb, cachePath, w](const dht::http::Response& response) {
2765 0 : if (response.status_code == 200) {
2766 : try {
2767 0 : std::lock_guard lk(dhtnet::fileutils::getFileLock(cachePath));
2768 0 : fileutils::saveFile(cachePath,
2769 0 : (const uint8_t*) response.body.data(),
2770 : response.body.size(),
2771 : 0600);
2772 0 : JAMI_LOG("Cached result to '{}'", cachePath);
2773 0 : } catch (const std::exception& ex) {
2774 0 : JAMI_WARNING("Failed to save result to '{}': {}", cachePath, ex.what());
2775 0 : }
2776 0 : cb(response);
2777 : } else {
2778 : try {
2779 0 : if (std::filesystem::exists(cachePath)) {
2780 0 : JAMI_WARNING("Failed to download URL, using cached data");
2781 0 : std::string data;
2782 : {
2783 0 : std::lock_guard lk(dhtnet::fileutils::getFileLock(cachePath));
2784 0 : data = fileutils::loadTextFile(cachePath);
2785 0 : }
2786 0 : dht::http::Response ret;
2787 0 : ret.body = std::move(data);
2788 0 : ret.status_code = 200;
2789 0 : cb(ret);
2790 0 : } else
2791 0 : throw std::runtime_error("No cached data");
2792 0 : } catch (...) {
2793 0 : cb(response);
2794 0 : }
2795 : }
2796 0 : if (auto req = response.request.lock())
2797 0 : if (auto sthis = w.lock())
2798 0 : sthis->requests_.erase(req);
2799 0 : });
2800 0 : sthis->requests_.emplace(req);
2801 0 : req->send();
2802 0 : }
2803 0 : }
2804 0 : });
2805 0 : }
2806 :
2807 : void
2808 692 : JamiAccount::loadCachedProxyServer(std::function<void(const std::string& proxy)> cb)
2809 : {
2810 692 : const auto& conf = config();
2811 692 : if (conf.proxyEnabled and proxyServerCached_.empty()) {
2812 0 : JAMI_DEBUG("[Account {:s}] Loading DHT proxy URL: {:s}", getAccountID(), conf.proxyListUrl);
2813 0 : if (conf.proxyListUrl.empty() or not conf.proxyListEnabled) {
2814 0 : cb(getDhtProxyServer(conf.proxyServer));
2815 : } else {
2816 0 : loadCachedUrl(conf.proxyListUrl,
2817 0 : cachePath_ / "dhtproxylist",
2818 0 : std::chrono::hours(24 * 3),
2819 0 : [w = weak(), cb = std::move(cb)](const dht::http::Response& response) {
2820 0 : if (auto sthis = w.lock()) {
2821 0 : if (response.status_code == 200) {
2822 0 : cb(sthis->getDhtProxyServer(response.body));
2823 : } else {
2824 0 : cb(sthis->getDhtProxyServer(sthis->config().proxyServer));
2825 : }
2826 0 : }
2827 0 : });
2828 : }
2829 : } else {
2830 692 : cb(proxyServerCached_);
2831 : }
2832 692 : }
2833 :
2834 : std::string
2835 0 : JamiAccount::getDhtProxyServer(const std::string& serverList)
2836 : {
2837 0 : if (proxyServerCached_.empty()) {
2838 0 : std::vector<std::string> proxys;
2839 : // Split the list of servers
2840 0 : std::sregex_iterator begin = {serverList.begin(), serverList.end(), PROXY_REGEX}, end;
2841 0 : for (auto it = begin; it != end; ++it) {
2842 0 : auto& match = *it;
2843 0 : if (match[5].matched and match[6].matched) {
2844 : try {
2845 0 : auto start = std::stoi(match[5]), end = std::stoi(match[6]);
2846 0 : for (auto p = start; p <= end; p++)
2847 0 : proxys.emplace_back(match[1].str() + match[2].str() + ":" + std::to_string(p));
2848 0 : } catch (...) {
2849 0 : JAMI_WARN("Malformed proxy, ignore it");
2850 0 : continue;
2851 0 : }
2852 : } else {
2853 0 : proxys.emplace_back(match[0].str());
2854 : }
2855 0 : }
2856 0 : if (proxys.empty())
2857 0 : return {};
2858 : // Select one of the list as the current proxy.
2859 0 : auto randIt = proxys.begin();
2860 0 : std::advance(randIt, std::uniform_int_distribution<unsigned long>(0, proxys.size() - 1)(rand));
2861 0 : proxyServerCached_ = *randIt;
2862 : // Cache it!
2863 0 : dhtnet::fileutils::check_dir(cachePath_, 0700);
2864 0 : auto proxyCachePath = cachePath_ / "dhtproxy";
2865 0 : std::ofstream file(proxyCachePath);
2866 0 : JAMI_DEBUG("Cache DHT proxy server: {}", proxyServerCached_);
2867 0 : Json::Value node(Json::objectValue);
2868 0 : node[getProxyConfigKey()] = proxyServerCached_;
2869 0 : if (file.is_open())
2870 0 : file << node;
2871 : else
2872 0 : JAMI_WARNING("Unable to write into {}", proxyCachePath);
2873 0 : }
2874 0 : return proxyServerCached_;
2875 : }
2876 :
2877 : MatchRank
2878 0 : JamiAccount::matches(std::string_view userName, std::string_view server) const
2879 : {
2880 0 : if (not accountManager_ or not accountManager_->getInfo())
2881 0 : return MatchRank::NONE;
2882 :
2883 0 : if (userName == accountManager_->getInfo()->accountId || server == accountManager_->getInfo()->accountId
2884 0 : || userName == accountManager_->getInfo()->deviceId) {
2885 0 : JAMI_LOG("Matching account ID in request with username {}", userName);
2886 0 : return MatchRank::FULL;
2887 : } else {
2888 0 : return MatchRank::NONE;
2889 : }
2890 : }
2891 :
2892 : std::string
2893 327 : JamiAccount::getFromUri() const
2894 : {
2895 327 : std::string uri = "<sip:" + accountManager_->getInfo()->accountId + "@ring.dht>";
2896 329 : if (not config().displayName.empty())
2897 655 : return "\"" + config().displayName + "\" " + uri;
2898 0 : return uri;
2899 328 : }
2900 :
2901 : std::string
2902 271 : JamiAccount::getToUri(const std::string& to) const
2903 : {
2904 271 : auto username = to;
2905 271 : string_replace(username, "sip:", "");
2906 542 : return fmt::format("<sips:{};transport=tls>", username);
2907 271 : }
2908 :
2909 : std::string
2910 7 : getDisplayed(const std::string& conversationId, const std::string& messageId)
2911 : {
2912 : // implementing https://tools.ietf.org/rfc/rfc5438.txt
2913 : return fmt::format("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n"
2914 : "<imdn><message-id>{}</message-id>\n"
2915 : "{}"
2916 : "<display-notification><status><displayed/></status></display-notification>\n"
2917 : "</imdn>",
2918 : messageId,
2919 14 : conversationId.empty() ? "" : "<conversation>" + conversationId + "</conversation>");
2920 : }
2921 :
2922 : std::string
2923 6 : getPIDF(const std::string& note)
2924 : {
2925 : // implementing https://datatracker.ietf.org/doc/html/rfc3863
2926 : return fmt::format("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
2927 : "<presence xmlns=\"urn:ietf:params:xml:ns:pidf\">\n"
2928 : " <tuple>\n"
2929 : " <status>\n"
2930 : " <basic>{}</basic>\n"
2931 : " </status>\n"
2932 : " </tuple>\n"
2933 : "</presence>",
2934 12 : note);
2935 : }
2936 :
2937 : void
2938 5 : JamiAccount::setIsComposing(const std::string& conversationUri, bool isWriting)
2939 : {
2940 5 : Uri uri(conversationUri);
2941 5 : std::string conversationId = {};
2942 5 : if (uri.scheme() == Uri::Scheme::SWARM) {
2943 5 : conversationId = uri.authority();
2944 : } else {
2945 0 : return;
2946 : }
2947 :
2948 5 : if (auto* cm = convModule(true)) {
2949 5 : if (auto typer = cm->getTypers(conversationId)) {
2950 5 : if (isWriting)
2951 4 : typer->addTyper(getUsername(), true);
2952 : else
2953 1 : typer->removeTyper(getUsername(), true);
2954 5 : }
2955 : }
2956 5 : }
2957 :
2958 : bool
2959 9 : JamiAccount::setMessageDisplayed(const std::string& conversationUri, const std::string& messageId, int status)
2960 : {
2961 9 : Uri uri(conversationUri);
2962 9 : std::string conversationId = {};
2963 9 : if (uri.scheme() == Uri::Scheme::SWARM)
2964 9 : conversationId = uri.authority();
2965 9 : auto sendMessage = status == (int) libjami::Account::MessageStates::DISPLAYED && isReadReceiptEnabled();
2966 9 : if (!conversationId.empty())
2967 9 : sendMessage &= convModule()->onMessageDisplayed(getUsername(), conversationId, messageId);
2968 9 : if (sendMessage)
2969 14 : sendInstantMessage(uri.authority(), {{MIME_TYPE_IMDN, getDisplayed(conversationId, messageId)}});
2970 9 : return true;
2971 9 : }
2972 :
2973 : std::string
2974 180 : JamiAccount::getContactHeader(const std::shared_ptr<SipTransport>& sipTransport)
2975 : {
2976 180 : if (sipTransport and sipTransport->get() != nullptr) {
2977 180 : auto* transport = sipTransport->get();
2978 180 : auto* td = reinterpret_cast<tls::AbstractSIPTransport::TransportData*>(transport);
2979 180 : auto address = td->self->getLocalAddress().toString(true);
2980 180 : bool reliable = transport->flag & PJSIP_TRANSPORT_RELIABLE;
2981 : return fmt::format("\"{}\" <sips:{}{}{};transport={}>",
2982 180 : config().displayName,
2983 180 : id_.second->getId().toString(),
2984 180 : address.empty() ? "" : "@",
2985 : address,
2986 540 : reliable ? "tls" : "dtls");
2987 180 : } else {
2988 0 : JAMI_ERR("getContactHeader: no SIP transport provided");
2989 0 : return fmt::format("\"{}\" <sips:{}@ring.dht>", config().displayName, id_.second->getId().toString());
2990 : }
2991 : }
2992 :
2993 : void
2994 67 : JamiAccount::addContact(const std::string& uri, bool confirmed)
2995 : {
2996 67 : dht::InfoHash h(uri);
2997 67 : if (not h) {
2998 4 : JAMI_ERROR("addContact: invalid contact URI");
2999 1 : return;
3000 : }
3001 66 : auto conversation = convModule()->getOneToOneConversation(uri);
3002 66 : if (!confirmed && conversation.empty())
3003 66 : conversation = convModule()->startConversation(ConversationMode::ONE_TO_ONE, h);
3004 66 : std::unique_lock lock(configurationMutex_);
3005 66 : if (accountManager_)
3006 66 : accountManager_->addContact(h, confirmed, conversation);
3007 : else
3008 0 : JAMI_WARNING("[Account {}] addContact: account not loaded", getAccountID());
3009 66 : }
3010 :
3011 : void
3012 19 : JamiAccount::removeContact(const std::string& uri, bool ban)
3013 : {
3014 19 : std::lock_guard lock(configurationMutex_);
3015 19 : if (accountManager_)
3016 19 : accountManager_->removeContact(uri, ban);
3017 : else
3018 0 : JAMI_WARNING("[Account {}] removeContact: account not loaded", getAccountID());
3019 19 : }
3020 :
3021 : std::map<std::string, std::string>
3022 8 : JamiAccount::getContactDetails(const std::string& uri) const
3023 : {
3024 8 : std::lock_guard lock(configurationMutex_);
3025 16 : return accountManager_ ? accountManager_->getContactDetails(uri) : std::map<std::string, std::string> {};
3026 8 : }
3027 :
3028 : std::optional<Contact>
3029 32 : JamiAccount::getContactInfo(const std::string& uri) const
3030 : {
3031 32 : std::lock_guard lock(configurationMutex_);
3032 64 : return accountManager_ ? accountManager_->getContactInfo(uri) : std::nullopt;
3033 32 : }
3034 :
3035 : std::vector<std::map<std::string, std::string>>
3036 1 : JamiAccount::getContacts(bool includeRemoved) const
3037 : {
3038 1 : std::lock_guard lock(configurationMutex_);
3039 1 : if (not accountManager_)
3040 0 : return {};
3041 1 : const auto& contacts = accountManager_->getContacts(includeRemoved);
3042 1 : std::vector<std::map<std::string, std::string>> ret;
3043 1 : ret.reserve(contacts.size());
3044 2 : for (const auto& c : contacts) {
3045 1 : auto details = c.second.toMap();
3046 1 : if (not details.empty()) {
3047 1 : details["id"] = c.first.toString();
3048 1 : ret.emplace_back(std::move(details));
3049 : }
3050 1 : }
3051 1 : return ret;
3052 1 : }
3053 :
3054 : /* trust requests */
3055 :
3056 : std::vector<std::map<std::string, std::string>>
3057 701 : JamiAccount::getTrustRequests() const
3058 : {
3059 701 : std::lock_guard lock(configurationMutex_);
3060 1402 : return accountManager_ ? accountManager_->getTrustRequests() : std::vector<std::map<std::string, std::string>> {};
3061 701 : }
3062 :
3063 : bool
3064 28 : JamiAccount::acceptTrustRequest(const std::string& from, bool includeConversation)
3065 : {
3066 28 : dht::InfoHash h(from);
3067 28 : if (not h) {
3068 0 : JAMI_ERROR("addContact: invalid contact URI");
3069 0 : return false;
3070 : }
3071 28 : std::unique_lock lock(configurationMutex_);
3072 28 : if (accountManager_) {
3073 28 : if (!accountManager_->acceptTrustRequest(from, includeConversation)) {
3074 : // Note: unused for swarm
3075 : // Typically the case where the trust request doesn't exists, only incoming DHT messages
3076 0 : return accountManager_->addContact(h, true);
3077 : }
3078 28 : return true;
3079 : }
3080 0 : JAMI_WARNING("[Account {}] acceptTrustRequest: account not loaded", getAccountID());
3081 0 : return false;
3082 28 : }
3083 :
3084 : bool
3085 2 : JamiAccount::discardTrustRequest(const std::string& from)
3086 : {
3087 : // Remove 1:1 generated conv requests
3088 2 : auto requests = getTrustRequests();
3089 4 : for (const auto& req : requests) {
3090 2 : if (req.at(libjami::Account::TrustRequest::FROM) == from) {
3091 2 : convModule()->declineConversationRequest(req.at(libjami::Account::TrustRequest::CONVERSATIONID));
3092 : }
3093 : }
3094 :
3095 : // Remove trust request
3096 2 : std::lock_guard lock(configurationMutex_);
3097 2 : if (accountManager_)
3098 2 : return accountManager_->discardTrustRequest(from);
3099 0 : JAMI_WARNING("[Account {:s}] discardTrustRequest: account not loaded", getAccountID());
3100 0 : return false;
3101 2 : }
3102 :
3103 : void
3104 3 : JamiAccount::declineConversationRequest(const std::string& conversationId)
3105 : {
3106 3 : auto peerId = convModule()->peerFromConversationRequest(conversationId);
3107 3 : convModule()->declineConversationRequest(conversationId);
3108 3 : if (!peerId.empty()) {
3109 3 : std::lock_guard lock(configurationMutex_);
3110 3 : if (const auto* info = accountManager_->getInfo()) {
3111 : // Verify if we have a trust request with this peer + convId
3112 3 : auto req = info->contacts->getTrustRequest(dht::InfoHash(peerId));
3113 6 : if (req.find(libjami::Account::TrustRequest::CONVERSATIONID) != req.end()
3114 6 : && req.at(libjami::Account::TrustRequest::CONVERSATIONID) == conversationId) {
3115 1 : accountManager_->discardTrustRequest(peerId);
3116 4 : JAMI_DEBUG("[Account {:s}] Declined trust request with {:s}", getAccountID(), peerId);
3117 : }
3118 3 : }
3119 3 : }
3120 3 : }
3121 :
3122 : void
3123 57 : JamiAccount::sendTrustRequest(const std::string& to, const std::vector<uint8_t>& payload)
3124 : {
3125 57 : dht::InfoHash h(to);
3126 57 : if (not h) {
3127 0 : JAMI_ERROR("addContact: invalid contact URI");
3128 0 : return;
3129 : }
3130 : // Here we cache payload sent by the client
3131 57 : auto requestPath = cachePath_ / "requests";
3132 57 : dhtnet::fileutils::recursive_mkdir(requestPath, 0700);
3133 57 : auto cachedFile = requestPath / to;
3134 57 : std::ofstream req(cachedFile, std::ios::trunc | std::ios::binary);
3135 57 : if (!req.is_open()) {
3136 0 : JAMI_ERROR("Unable to write data to {}", cachedFile);
3137 0 : return;
3138 : }
3139 :
3140 57 : if (not payload.empty()) {
3141 3 : req.write(reinterpret_cast<const char*>(payload.data()), static_cast<std::streamsize>(payload.size()));
3142 : }
3143 :
3144 57 : if (payload.size() >= 64000) {
3145 1 : JAMI_WARN() << "Trust request is too big. Remove payload";
3146 : }
3147 :
3148 57 : auto conversation = convModule()->getOneToOneConversation(to);
3149 57 : if (conversation.empty())
3150 0 : conversation = convModule()->startConversation(ConversationMode::ONE_TO_ONE, h);
3151 57 : if (not conversation.empty()) {
3152 57 : std::lock_guard lock(configurationMutex_);
3153 57 : if (accountManager_)
3154 114 : accountManager_->sendTrustRequest(to,
3155 : conversation,
3156 114 : payload.size() >= 64000 ? std::vector<uint8_t> {} : payload);
3157 : else
3158 0 : JAMI_WARNING("[Account {}] sendTrustRequest: account not loaded", getAccountID());
3159 57 : } else
3160 0 : JAMI_WARNING("[Account {}] sendTrustRequest: account not loaded", getAccountID());
3161 57 : }
3162 :
3163 : void
3164 289 : JamiAccount::forEachDevice(const dht::InfoHash& to,
3165 : std::function<void(const std::shared_ptr<dht::crypto::PublicKey>&)>&& op,
3166 : std::function<void(bool)>&& end)
3167 : {
3168 289 : accountManager_->forEachDevice(to, std::move(op), std::move(end));
3169 289 : }
3170 :
3171 : uint64_t
3172 13996 : JamiAccount::sendTextMessage(const std::string& to,
3173 : const std::string& deviceId,
3174 : const std::map<std::string, std::string>& payloads,
3175 : uint64_t refreshToken,
3176 : bool onlyConnected)
3177 : {
3178 13996 : Uri uri(to);
3179 13989 : if (uri.scheme() == Uri::Scheme::SWARM) {
3180 0 : sendInstantMessage(uri.authority(), payloads);
3181 0 : return 0;
3182 : }
3183 :
3184 13989 : std::string toUri;
3185 : try {
3186 13987 : toUri = parseJamiUri(to);
3187 0 : } catch (...) {
3188 0 : JAMI_ERROR("Failed to send a text message due to an invalid URI {}", to);
3189 0 : return 0;
3190 0 : }
3191 13985 : if (payloads.size() != 1) {
3192 0 : JAMI_ERROR("Multi-part im is not supported yet by JamiAccount");
3193 0 : return 0;
3194 : }
3195 13991 : return SIPAccountBase::sendTextMessage(toUri, deviceId, payloads, refreshToken, onlyConnected);
3196 14008 : }
3197 :
3198 : void
3199 14766 : JamiAccount::sendMessage(const std::string& to,
3200 : const std::string& deviceId,
3201 : const std::map<std::string, std::string>& payloads,
3202 : uint64_t token,
3203 : bool retryOnTimeout,
3204 : bool onlyConnected)
3205 : {
3206 14766 : std::string toUri;
3207 : try {
3208 14766 : toUri = parseJamiUri(to);
3209 0 : } catch (...) {
3210 0 : JAMI_ERROR("[Account {}] Failed to send a text message due to an invalid URI {}", getAccountID(), to);
3211 0 : if (!onlyConnected)
3212 0 : messageEngine_.onMessageSent(to, token, false, deviceId);
3213 0 : return;
3214 0 : }
3215 14766 : if (payloads.size() != 1) {
3216 0 : JAMI_ERROR("Multi-part im is not supported");
3217 0 : if (!onlyConnected)
3218 0 : messageEngine_.onMessageSent(toUri, token, false, deviceId);
3219 0 : return;
3220 : }
3221 :
3222 : // Use the Message channel if available
3223 14766 : std::shared_lock clk(connManagerMtx_);
3224 14766 : auto* handler = static_cast<MessageChannelHandler*>(channelHandlers_[Uri::Scheme::MESSAGE].get());
3225 14766 : if (!handler) {
3226 32 : clk.unlock();
3227 32 : if (!onlyConnected)
3228 32 : messageEngine_.onMessageSent(to, token, false, deviceId);
3229 32 : return;
3230 : }
3231 :
3232 : auto devices = std::make_shared<SendMessageContext>(
3233 42268 : [w = weak(), to, token, deviceId, onlyConnected, retryOnTimeout](bool success, bool sent) {
3234 14730 : if (auto acc = w.lock())
3235 14730 : acc->onMessageSent(to, token, deviceId, success, onlyConnected, sent && retryOnTimeout);
3236 29468 : });
3237 :
3238 12814 : auto completed = [w = weak(), to, devices](const DeviceId& device,
3239 : const std::shared_ptr<dhtnet::ChannelSocket>& conn,
3240 : bool success) {
3241 12814 : if (!success)
3242 1 : if (auto acc = w.lock()) {
3243 1 : std::shared_lock clk(acc->connManagerMtx_);
3244 1 : if (auto* handler = static_cast<MessageChannelHandler*>(
3245 1 : acc->channelHandlers_[Uri::Scheme::MESSAGE].get())) {
3246 1 : handler->closeChannel(to, device, conn);
3247 : }
3248 2 : }
3249 12814 : devices->complete(device, success);
3250 27549 : };
3251 :
3252 14734 : const auto& payload = *payloads.begin();
3253 14733 : auto msg = std::make_shared<MessageChannelHandler::Message>();
3254 14733 : msg->id = token;
3255 14734 : msg->t = payload.first;
3256 14734 : msg->c = payload.second;
3257 14734 : auto device = deviceId.empty() ? DeviceId() : DeviceId(deviceId);
3258 14734 : if (deviceId.empty()) {
3259 3595 : auto conns = handler->getChannels(toUri);
3260 3595 : clk.unlock();
3261 5560 : for (const auto& conn : conns) {
3262 1965 : auto connDevice = conn->deviceId();
3263 1966 : if (!devices->add(connDevice))
3264 237 : continue;
3265 1730 : dht::ThreadPool::io().run([completed, connDevice, conn, msg] {
3266 1730 : completed(connDevice, conn, MessageChannelHandler::sendMessage(conn, *msg));
3267 1730 : });
3268 : }
3269 3595 : } else {
3270 11139 : if (auto conn = handler->getChannel(toUri, device)) {
3271 11085 : clk.unlock();
3272 11085 : devices->add(device);
3273 11085 : dht::ThreadPool::io().run([completed, device, conn, msg] {
3274 11085 : completed(device, conn, MessageChannelHandler::sendMessage(conn, *msg));
3275 11085 : });
3276 11085 : devices->start();
3277 11085 : return;
3278 11139 : }
3279 : }
3280 3649 : if (clk)
3281 54 : clk.unlock();
3282 :
3283 3649 : devices->start();
3284 :
3285 3649 : if (onlyConnected)
3286 22 : return;
3287 : // We are unable to send the message directly, try connecting
3288 :
3289 : // Get conversation id, which will be used by the iOS notification extension
3290 : // to load the conversation.
3291 3042 : auto extractIdFromJson = [](const std::string& jsonData) -> std::string {
3292 3042 : Json::Value parsed;
3293 3042 : if (json::parse(jsonData, parsed)) {
3294 3043 : auto value = parsed.get("id", Json::nullValue);
3295 3043 : if (value && value.isString()) {
3296 3043 : return value.asString();
3297 : }
3298 3043 : } else {
3299 0 : JAMI_WARNING("Unable to parse jsonData to get conversation ID");
3300 : }
3301 0 : return "";
3302 3043 : };
3303 :
3304 : // get request type
3305 3627 : auto payload_type = msg->t;
3306 3627 : if (payload_type == MIME_TYPE_GIT) {
3307 3043 : std::string id = extractIdFromJson(msg->c);
3308 3043 : if (!id.empty()) {
3309 3043 : payload_type += "/" + id;
3310 : }
3311 3043 : }
3312 :
3313 3627 : if (deviceId.empty()) {
3314 3573 : auto toH = dht::InfoHash(toUri);
3315 : // Find listening devices for this account
3316 3573 : accountManager_->forEachDevice(toH,
3317 3584 : [this, to, devices, payload_type, currentDevice = DeviceId(currentDeviceId())](
3318 2960 : const std::shared_ptr<dht::crypto::PublicKey>& dev) {
3319 : // Test if already sent
3320 3584 : auto deviceId = dev->getLongId();
3321 3584 : if (deviceId == currentDevice || devices->pending(deviceId)) {
3322 624 : return;
3323 : }
3324 :
3325 : // Else, ask for a channel to send the message
3326 2961 : dht::ThreadPool::io().run([this, to, deviceId, payload_type]() {
3327 2961 : requestMessageConnection(to, deviceId, payload_type);
3328 2961 : });
3329 : });
3330 : } else {
3331 54 : requestMessageConnection(to, device, payload_type);
3332 : }
3333 59226 : }
3334 :
3335 : void
3336 14730 : JamiAccount::onMessageSent(
3337 : const std::string& to, uint64_t id, const std::string& deviceId, bool success, bool onlyConnected, bool retry)
3338 : {
3339 14730 : if (!onlyConnected)
3340 14703 : messageEngine_.onMessageSent(to, id, success, deviceId);
3341 :
3342 14734 : if (!success) {
3343 1921 : if (retry)
3344 1 : messageEngine_.onPeerOnline(to, deviceId);
3345 : }
3346 14734 : }
3347 :
3348 : dhtnet::IceTransportOptions
3349 118 : JamiAccount::getIceOptions() const
3350 : {
3351 118 : return connectionManager_->getIceOptions();
3352 : }
3353 :
3354 : void
3355 11 : JamiAccount::getIceOptions(std::function<void(dhtnet::IceTransportOptions&&)> cb) const
3356 : {
3357 11 : return connectionManager_->getIceOptions(std::move(cb));
3358 : }
3359 :
3360 : dhtnet::IpAddr
3361 99 : JamiAccount::getPublishedIpAddress(uint16_t family) const
3362 : {
3363 99 : return connectionManager_->getPublishedIpAddress(family);
3364 : }
3365 :
3366 : bool
3367 0 : JamiAccount::setPushNotificationToken(const std::string& token)
3368 : {
3369 0 : if (SIPAccountBase::setPushNotificationToken(token)) {
3370 0 : JAMI_WARNING("[Account {:s}] setPushNotificationToken: {:s}", getAccountID(), token);
3371 0 : if (dht_)
3372 0 : dht_->setPushNotificationToken(token);
3373 0 : return true;
3374 : }
3375 0 : return false;
3376 : }
3377 :
3378 : bool
3379 0 : JamiAccount::setPushNotificationTopic(const std::string& topic)
3380 : {
3381 0 : if (SIPAccountBase::setPushNotificationTopic(topic)) {
3382 0 : if (dht_)
3383 0 : dht_->setPushNotificationTopic(topic);
3384 0 : return true;
3385 : }
3386 0 : return false;
3387 : }
3388 :
3389 : bool
3390 0 : JamiAccount::setPushNotificationConfig(const std::map<std::string, std::string>& data)
3391 : {
3392 0 : if (SIPAccountBase::setPushNotificationConfig(data)) {
3393 0 : if (dht_) {
3394 0 : dht_->setPushNotificationPlatform(config_->platform);
3395 0 : dht_->setPushNotificationTopic(config_->notificationTopic);
3396 0 : dht_->setPushNotificationToken(config_->deviceKey);
3397 : }
3398 0 : return true;
3399 : }
3400 0 : return false;
3401 : }
3402 :
3403 : /**
3404 : * To be called by clients with relevant data when a push notification is received.
3405 : */
3406 : void
3407 0 : JamiAccount::pushNotificationReceived(const std::string& /*from*/, const std::map<std::string, std::string>& data)
3408 : {
3409 0 : auto ret_future = dht_->pushNotificationReceived(data);
3410 0 : dht::ThreadPool::computation().run([id = getAccountID(), ret_future = ret_future.share()] {
3411 0 : JAMI_WARNING("[Account {:s}] pushNotificationReceived: {}", id, (uint8_t) ret_future.get());
3412 0 : });
3413 0 : }
3414 :
3415 : std::string
3416 1 : JamiAccount::getUserUri() const
3417 : {
3418 1 : if (not registeredName_.empty())
3419 0 : return JAMI_URI_PREFIX + registeredName_;
3420 1 : return JAMI_URI_PREFIX + config().username;
3421 : }
3422 :
3423 : std::vector<libjami::Message>
3424 0 : JamiAccount::getLastMessages(const uint64_t& base_timestamp)
3425 : {
3426 0 : return SIPAccountBase::getLastMessages(base_timestamp);
3427 : }
3428 :
3429 : void
3430 0 : JamiAccount::startAccountPublish()
3431 : {
3432 0 : AccountPeerInfo info_pub;
3433 0 : info_pub.accountId = dht::InfoHash(accountManager_->getInfo()->accountId);
3434 0 : info_pub.displayName = config().displayName;
3435 0 : peerDiscovery_->startPublish<AccountPeerInfo>(PEER_DISCOVERY_JAMI_SERVICE, info_pub);
3436 0 : }
3437 :
3438 : void
3439 0 : JamiAccount::startAccountDiscovery()
3440 : {
3441 0 : auto id = dht::InfoHash(accountManager_->getInfo()->accountId);
3442 0 : peerDiscovery_
3443 0 : ->startDiscovery<AccountPeerInfo>(PEER_DISCOVERY_JAMI_SERVICE, [this, id](AccountPeerInfo&& v, dht::SockAddr&&) {
3444 0 : std::lock_guard lc(discoveryMapMtx_);
3445 : // Make sure that account itself will not be recorded
3446 0 : if (v.accountId != id) {
3447 : // Create or find the old one
3448 0 : auto& dp = discoveredPeers_[v.accountId];
3449 0 : dp.displayName = v.displayName;
3450 0 : discoveredPeerMap_[v.accountId.toString()] = v.displayName;
3451 0 : if (!dp.cleanupTimer) {
3452 : // Avoid repeat reception of same peer
3453 0 : JAMI_LOG("Account discovered: {}: {}", v.displayName, v.accountId.to_c_str());
3454 : // Send Added Peer and corrsponding accoundID
3455 0 : emitSignal<libjami::PresenceSignal::NearbyPeerNotification>(getAccountID(),
3456 0 : v.accountId.toString(),
3457 : 0,
3458 0 : v.displayName);
3459 0 : dp.cleanupTimer = std::make_unique<asio::steady_timer>(*Manager::instance().ioContext(),
3460 0 : PEER_DISCOVERY_EXPIRATION);
3461 : }
3462 0 : dp.cleanupTimer->expires_after(PEER_DISCOVERY_EXPIRATION);
3463 0 : dp.cleanupTimer->async_wait(
3464 0 : [w = weak(), p = v.accountId, a = v.displayName](const asio::error_code& ec) {
3465 0 : if (ec)
3466 0 : return;
3467 0 : if (auto this_ = w.lock()) {
3468 : {
3469 0 : std::lock_guard lc(this_->discoveryMapMtx_);
3470 0 : this_->discoveredPeers_.erase(p);
3471 0 : this_->discoveredPeerMap_.erase(p.toString());
3472 0 : }
3473 : // Send deleted peer
3474 0 : emitSignal<libjami::PresenceSignal::NearbyPeerNotification>(this_->getAccountID(),
3475 0 : p.toString(),
3476 : 1,
3477 0 : a);
3478 0 : }
3479 0 : JAMI_INFO("Account removed from discovery list: %s", a.c_str());
3480 : });
3481 : }
3482 0 : });
3483 0 : }
3484 :
3485 : std::map<std::string, std::string>
3486 0 : JamiAccount::getNearbyPeers() const
3487 : {
3488 0 : return discoveredPeerMap_;
3489 : }
3490 :
3491 : void
3492 0 : JamiAccount::sendProfileToPeers()
3493 : {
3494 0 : if (!connectionManager_)
3495 0 : return;
3496 0 : std::set<std::string> peers;
3497 0 : const auto& accountUri = accountManager_->getInfo()->accountId;
3498 : // TODO: avoid using getConnectionList
3499 0 : for (const auto& connection : connectionManager_->getConnectionList()) {
3500 0 : const auto& device = connection.at("device");
3501 0 : const auto& peer = connection.at("peer");
3502 0 : if (!peers.emplace(peer).second)
3503 0 : continue;
3504 0 : if (peer == accountUri) {
3505 0 : sendProfile("", accountUri, device);
3506 0 : continue;
3507 : }
3508 0 : const auto& conversationId = convModule()->getOneToOneConversation(peer);
3509 0 : if (!conversationId.empty()) {
3510 0 : sendProfile(conversationId, peer, device);
3511 : }
3512 0 : }
3513 0 : }
3514 :
3515 : void
3516 0 : JamiAccount::updateProfile(const std::string& displayName,
3517 : const std::string& avatar,
3518 : const std::string& fileType,
3519 : int32_t flag)
3520 : {
3521 : // if the fileType is empty then only the display name will be upated
3522 :
3523 0 : const auto& accountUri = accountManager_->getInfo()->accountId;
3524 0 : const auto& path = profilePath();
3525 0 : const auto& profiles = idPath_ / "profiles";
3526 :
3527 : try {
3528 0 : if (!std::filesystem::exists(profiles)) {
3529 0 : std::filesystem::create_directories(profiles);
3530 : }
3531 0 : } catch (const std::exception& e) {
3532 0 : JAMI_ERROR("Failed to create profiles directory: {}", e.what());
3533 0 : return;
3534 0 : }
3535 :
3536 0 : const auto& vCardPath = profiles / fmt::format("{}.vcf", base64::encode(accountUri));
3537 :
3538 0 : auto profile = getProfileVcard();
3539 0 : if (profile.empty()) {
3540 0 : profile = vCard::utils::initVcard();
3541 : }
3542 :
3543 0 : profile[std::string(vCard::Property::FORMATTED_NAME)] = displayName;
3544 0 : editConfig([&](JamiAccountConfig& config) { config.displayName = displayName; });
3545 0 : emitSignal<libjami::ConfigurationSignal::AccountDetailsChanged>(getAccountID(), getAccountDetails());
3546 :
3547 0 : if (!fileType.empty()) {
3548 0 : const std::string& key = "PHOTO;ENCODING=BASE64;TYPE=" + fileType;
3549 0 : if (flag == 0) {
3550 0 : vCard::utils::removeByKey(profile, vCard::Property::PHOTO);
3551 0 : const auto& avatarPath = std::filesystem::path(avatar);
3552 0 : if (std::filesystem::exists(avatarPath)) {
3553 : try {
3554 0 : profile[key] = base64::encode(fileutils::loadFile(avatarPath));
3555 0 : } catch (const std::exception& e) {
3556 0 : JAMI_ERROR("Failed to load avatar: {}", e.what());
3557 0 : }
3558 : }
3559 0 : } else if (flag == 1) {
3560 0 : vCard::utils::removeByKey(profile, vCard::Property::PHOTO);
3561 0 : profile[key] = avatar;
3562 : }
3563 0 : }
3564 0 : if (flag == 2) {
3565 0 : vCard::utils::removeByKey(profile, vCard::Property::PHOTO);
3566 : }
3567 : try {
3568 0 : vCard::utils::save(profile, vCardPath, path);
3569 0 : emitSignal<libjami::ConfigurationSignal::ProfileReceived>(getAccountID(), accountUri, path.string());
3570 :
3571 : // Delete all profile sent markers:
3572 0 : std::error_code ec;
3573 0 : std::filesystem::remove_all(cachePath_ / "vcard", ec);
3574 0 : sendProfileToPeers();
3575 0 : } catch (const std::exception& e) {
3576 0 : JAMI_ERROR("Error writing profile: {}", e.what());
3577 0 : }
3578 0 : }
3579 :
3580 : void
3581 791 : JamiAccount::setActiveCodecs(const std::vector<unsigned>& list)
3582 : {
3583 791 : Account::setActiveCodecs(list);
3584 791 : if (!hasActiveCodec(MEDIA_AUDIO))
3585 771 : setCodecActive(AV_CODEC_ID_OPUS);
3586 791 : if (!hasActiveCodec(MEDIA_VIDEO)) {
3587 771 : setCodecActive(AV_CODEC_ID_HEVC);
3588 771 : setCodecActive(AV_CODEC_ID_H264);
3589 771 : setCodecActive(AV_CODEC_ID_VP8);
3590 : }
3591 791 : config_->activeCodecs = getActiveCodecs(MEDIA_ALL);
3592 791 : }
3593 :
3594 : void
3595 11 : JamiAccount::sendInstantMessage(const std::string& convId, const std::map<std::string, std::string>& msg)
3596 : {
3597 11 : auto members = convModule()->getConversationMembers(convId);
3598 11 : if (convId.empty() && members.empty()) {
3599 : // TODO remove, it's for old API for contacts
3600 0 : sendTextMessage(convId, "", msg);
3601 0 : return;
3602 : }
3603 33 : for (const auto& m : members) {
3604 22 : const auto& uri = m.at("uri");
3605 22 : auto token = std::uniform_int_distribution<uint64_t> {1, JAMI_ID_MAX_VAL}(rand);
3606 : // Announce to all members that a new message is sent
3607 22 : sendMessage(uri, "", msg, token, false, true);
3608 : }
3609 11 : }
3610 :
3611 : bool
3612 12806 : JamiAccount::handleMessage(const std::shared_ptr<dht::crypto::Certificate>& cert,
3613 : const std::string& from,
3614 : const std::pair<std::string, std::string>& m)
3615 : {
3616 12806 : if (not cert or not cert->issuer)
3617 0 : return true; // stop processing message
3618 :
3619 12808 : if (cert->issuer->getId().to_view() != from) {
3620 0 : JAMI_WARNING("[Account {}] [device {}] handleMessage: invalid author {}",
3621 : getAccountID(),
3622 : cert->issuer->getId().to_view(),
3623 : from);
3624 0 : return true;
3625 : }
3626 12806 : if (m.first == MIME_TYPE_GIT) {
3627 12393 : Json::Value json;
3628 12394 : if (!json::parse(m.second, json)) {
3629 0 : return true;
3630 : }
3631 :
3632 : // fetchNewCommits will do heavy stuff like fetching, avoid to block SIP socket
3633 24779 : dht::ThreadPool::io().run([w = weak(),
3634 : from,
3635 12389 : deviceId = json["deviceId"].asString(),
3636 12391 : id = json["id"].asString(),
3637 12394 : commit = json["commit"].asString()] {
3638 12392 : if (auto shared = w.lock()) {
3639 12392 : if (auto* cm = shared->convModule())
3640 12392 : cm->fetchNewCommits(from, deviceId, id, commit);
3641 12392 : }
3642 12389 : });
3643 12394 : return true;
3644 12808 : } else if (m.first == MIME_TYPE_INVITE) {
3645 136 : convModule()->onNeedConversationRequest(from, m.second);
3646 136 : return true;
3647 278 : } else if (m.first == MIME_TYPE_INVITE_JSON) {
3648 260 : Json::Value json;
3649 260 : if (!json::parse(m.second, json)) {
3650 0 : return true;
3651 : }
3652 260 : convModule()->onConversationRequest(from, json);
3653 260 : return true;
3654 278 : } else if (m.first == MIME_TYPE_IM_COMPOSING) {
3655 : try {
3656 4 : static const std::regex COMPOSING_REGEX("<state>\\s*(\\w+)\\s*<\\/state>");
3657 4 : std::smatch matched_pattern;
3658 4 : std::regex_search(m.second, matched_pattern, COMPOSING_REGEX);
3659 4 : bool isComposing {false};
3660 4 : if (matched_pattern.ready() && !matched_pattern.empty() && matched_pattern[1].matched) {
3661 4 : isComposing = matched_pattern[1] == "active";
3662 : }
3663 4 : static const std::regex CONVID_REGEX("<conversation>\\s*(\\w+)\\s*<\\/conversation>");
3664 4 : std::regex_search(m.second, matched_pattern, CONVID_REGEX);
3665 4 : std::string conversationId = "";
3666 4 : if (matched_pattern.ready() && !matched_pattern.empty() && matched_pattern[1].matched) {
3667 4 : conversationId = matched_pattern[1];
3668 : }
3669 4 : if (!conversationId.empty()) {
3670 4 : if (auto* cm = convModule(true)) {
3671 4 : if (auto typer = cm->getTypers(conversationId)) {
3672 4 : if (isComposing)
3673 3 : typer->addTyper(from);
3674 : else
3675 1 : typer->removeTyper(from);
3676 4 : }
3677 : }
3678 : }
3679 4 : return true;
3680 4 : } catch (const std::exception& e) {
3681 0 : JAMI_WARNING("Error parsing composing state: {}", e.what());
3682 0 : }
3683 14 : } else if (m.first == MIME_TYPE_IMDN) {
3684 : try {
3685 9 : static const std::regex IMDN_MSG_ID_REGEX("<message-id>\\s*(\\w+)\\s*<\\/message-id>");
3686 9 : std::smatch matched_pattern;
3687 :
3688 9 : std::regex_search(m.second, matched_pattern, IMDN_MSG_ID_REGEX);
3689 9 : std::string messageId;
3690 9 : if (matched_pattern.ready() && !matched_pattern.empty() && matched_pattern[1].matched) {
3691 9 : messageId = matched_pattern[1];
3692 : } else {
3693 0 : JAMI_WARNING("Message displayed: unable to parse message ID");
3694 0 : return true;
3695 : }
3696 :
3697 9 : static const std::regex STATUS_REGEX("<status>\\s*<(\\w+)\\/>\\s*<\\/status>");
3698 9 : std::regex_search(m.second, matched_pattern, STATUS_REGEX);
3699 9 : bool isDisplayed {false};
3700 9 : if (matched_pattern.ready() && !matched_pattern.empty() && matched_pattern[1].matched) {
3701 9 : isDisplayed = matched_pattern[1] == "displayed";
3702 : } else {
3703 0 : JAMI_WARNING("Message displayed: unable to parse status");
3704 0 : return true;
3705 : }
3706 :
3707 9 : static const std::regex CONVID_REGEX("<conversation>\\s*(\\w+)\\s*<\\/conversation>");
3708 9 : std::regex_search(m.second, matched_pattern, CONVID_REGEX);
3709 9 : std::string conversationId = "";
3710 9 : if (matched_pattern.ready() && !matched_pattern.empty() && matched_pattern[1].matched) {
3711 9 : conversationId = matched_pattern[1];
3712 : }
3713 :
3714 9 : if (!isReadReceiptEnabled())
3715 0 : return true;
3716 9 : if (isDisplayed) {
3717 9 : if (convModule()->onMessageDisplayed(from, conversationId, messageId)) {
3718 32 : JAMI_DEBUG("[message {}] Displayed by peer", messageId);
3719 16 : emitSignal<libjami::ConfigurationSignal::AccountMessageStatusChanged>(
3720 8 : accountID_,
3721 : conversationId,
3722 : from,
3723 : messageId,
3724 : static_cast<int>(libjami::Account::MessageStates::DISPLAYED));
3725 : }
3726 : }
3727 9 : return true;
3728 9 : } catch (const std::exception& e) {
3729 0 : JAMI_ERROR("Error parsing display notification: {}", e.what());
3730 0 : }
3731 5 : } else if (m.first == MIME_TYPE_PIDF) {
3732 5 : std::smatch matched_pattern;
3733 5 : static const std::regex BASIC_REGEX("<basic>([\\w\\s]+)<\\/basic>");
3734 5 : std::regex_search(m.second, matched_pattern, BASIC_REGEX);
3735 5 : std::string customStatus {};
3736 5 : if (matched_pattern.ready() && !matched_pattern.empty() && matched_pattern[1].matched) {
3737 5 : customStatus = matched_pattern[1];
3738 5 : emitSignal<libjami::PresenceSignal::NewBuddyNotification>(getAccountID(),
3739 : from,
3740 : static_cast<int>(PresenceState::CONNECTED),
3741 : customStatus);
3742 : } else {
3743 0 : JAMI_WARNING("Presence: unable to parse status");
3744 : }
3745 4 : return true;
3746 4 : }
3747 :
3748 0 : return false;
3749 : }
3750 :
3751 : void
3752 259 : JamiAccount::callConnectionClosed(const DeviceId& deviceId, bool eraseDummy)
3753 : {
3754 259 : std::function<void(const DeviceId&, bool)> cb;
3755 : {
3756 259 : std::lock_guard lk(onConnectionClosedMtx_);
3757 259 : auto it = onConnectionClosed_.find(deviceId);
3758 259 : if (it != onConnectionClosed_.end()) {
3759 82 : if (eraseDummy) {
3760 82 : cb = std::move(it->second);
3761 82 : onConnectionClosed_.erase(it);
3762 : } else {
3763 : // In this case a new subcall is created and the callback
3764 : // will be re-called once with eraseDummy = true
3765 0 : cb = it->second;
3766 : }
3767 : }
3768 259 : }
3769 259 : dht::ThreadPool::io().run([w = weak(), cb = std::move(cb), id = deviceId, erase = std::move(eraseDummy)] {
3770 259 : if (auto acc = w.lock()) {
3771 259 : if (cb)
3772 82 : cb(id, erase);
3773 259 : }
3774 259 : });
3775 259 : }
3776 :
3777 : void
3778 3439 : JamiAccount::requestMessageConnection(const std::string& peerId,
3779 : const DeviceId& deviceId,
3780 : const std::string& connectionType)
3781 : {
3782 3439 : std::shared_lock lk(connManagerMtx_);
3783 3439 : auto* handler = static_cast<MessageChannelHandler*>(channelHandlers_[Uri::Scheme::MESSAGE].get());
3784 3439 : if (!handler)
3785 1 : return;
3786 3438 : if (deviceId) {
3787 3438 : if (auto connected = handler->getChannel(peerId, deviceId)) {
3788 2016 : return;
3789 3438 : }
3790 : } else {
3791 0 : auto connected = handler->getChannels(peerId);
3792 0 : if (!connected.empty()) {
3793 0 : return;
3794 : }
3795 0 : }
3796 1422 : handler->connect(
3797 : deviceId,
3798 : "",
3799 781 : [w = weak(), peerId](const std::shared_ptr<dhtnet::ChannelSocket>& socket, const DeviceId& deviceId) {
3800 781 : if (socket)
3801 691 : dht::ThreadPool::io().run([w, peerId, deviceId] {
3802 690 : if (auto acc = w.lock()) {
3803 691 : acc->messageEngine_.onPeerOnline(peerId);
3804 690 : acc->messageEngine_.onPeerOnline(peerId, deviceId.toString(), true);
3805 691 : if (!acc->presenceNote_.empty()) {
3806 : // If a presence note is set, send it to this device.
3807 3 : auto token = std::uniform_int_distribution<uint64_t> {1, JAMI_ID_MAX_VAL}(acc->rand);
3808 12 : std::map<std::string, std::string> msg = {{MIME_TYPE_PIDF, getPIDF(acc->presenceNote_)}};
3809 3 : acc->sendMessage(peerId, deviceId.toString(), msg, token, false, true);
3810 3 : }
3811 691 : acc->convModule()->syncConversations(peerId, deviceId.toString());
3812 691 : }
3813 691 : });
3814 782 : },
3815 : connectionType);
3816 3439 : }
3817 :
3818 : void
3819 89 : JamiAccount::requestSIPConnection(const std::string& peerId,
3820 : const DeviceId& deviceId,
3821 : const std::string& connectionType,
3822 : bool forceNewConnection,
3823 : const std::shared_ptr<SIPCall>& pc)
3824 : {
3825 89 : if (peerId == getUsername()) {
3826 0 : if (!syncModule()->isConnected(deviceId))
3827 0 : channelHandlers_[Uri::Scheme::SYNC]->connect(deviceId,
3828 : "",
3829 0 : [](const std::shared_ptr<dhtnet::ChannelSocket>& /*socket*/,
3830 0 : const DeviceId& /*deviceId*/) {});
3831 : }
3832 :
3833 356 : JAMI_LOG("[Account {}] Request SIP connection to peer {} on device {}", getAccountID(), peerId, deviceId);
3834 :
3835 : // If a connection already exists or is in progress, no need to do this
3836 89 : std::lock_guard lk(sipConnsMtx_);
3837 89 : auto id = std::make_pair(peerId, deviceId);
3838 :
3839 89 : if (sipConns_.find(id) != sipConns_.end()) {
3840 0 : JAMI_LOG("[Account {}] A SIP connection with {} already exists", getAccountID(), deviceId);
3841 0 : return;
3842 : }
3843 : // If not present, create it
3844 89 : std::shared_lock lkCM(connManagerMtx_);
3845 89 : if (!connectionManager_)
3846 0 : return;
3847 : // Note, Even if we send 50 "sip" request, the connectionManager_ will only use one socket.
3848 : // however, this will still ask for multiple channels, so only ask
3849 : // if there is no pending request
3850 89 : if (!forceNewConnection && connectionManager_->isConnecting(deviceId, "sip")) {
3851 0 : JAMI_LOG("[Account {}] Already connecting to {}", getAccountID(), deviceId);
3852 0 : return;
3853 : }
3854 356 : JAMI_LOG("[Account {}] Ask {} for a new SIP channel", getAccountID(), deviceId);
3855 89 : dhtnet::ConnectDeviceOptions options;
3856 89 : options.noNewSocket = false;
3857 89 : options.forceNewSocket = forceNewConnection;
3858 89 : options.connType = connectionType;
3859 89 : options.channelTimeout = 3s;
3860 267 : connectionManager_->connectDevice(
3861 : deviceId,
3862 : "sip",
3863 178 : [w = weak(), id = std::move(id), pc = std::move(pc)](const std::shared_ptr<dhtnet::ChannelSocket>& socket,
3864 : const DeviceId&) {
3865 89 : if (socket)
3866 86 : return;
3867 3 : auto shared = w.lock();
3868 3 : if (!shared)
3869 0 : return;
3870 : // If this is triggered, this means that the
3871 : // connectDevice didn't get any response from the DHT.
3872 : // Stop searching pending call.
3873 3 : shared->callConnectionClosed(id.second, true);
3874 3 : if (pc)
3875 3 : pc->onFailure(PJSIP_SC_TEMPORARILY_UNAVAILABLE);
3876 3 : },
3877 : options);
3878 89 : }
3879 :
3880 : bool
3881 2629 : JamiAccount::isConnectedWith(const DeviceId& deviceId) const
3882 : {
3883 2629 : std::shared_lock lkCM(connManagerMtx_);
3884 2629 : if (connectionManager_)
3885 2627 : return connectionManager_->isConnected(deviceId);
3886 2 : return false;
3887 2629 : }
3888 :
3889 : void
3890 3 : JamiAccount::sendPresenceNote(const std::string& note)
3891 : {
3892 3 : if (const auto* info = accountManager_->getInfo()) {
3893 3 : if (!info || !info->contacts)
3894 0 : return;
3895 3 : presenceNote_ = note;
3896 3 : auto contacts = info->contacts->getContacts();
3897 3 : std::vector<std::pair<std::string, DeviceId>> keys;
3898 : {
3899 3 : std::shared_lock lkCM(connManagerMtx_);
3900 3 : auto* handler = static_cast<MessageChannelHandler*>(channelHandlers_[Uri::Scheme::MESSAGE].get());
3901 3 : if (!handler)
3902 0 : return;
3903 5 : for (const auto& contact : contacts) {
3904 2 : auto peerId = contact.first.toString();
3905 2 : auto channels = handler->getChannels(peerId);
3906 4 : for (const auto& channel : channels) {
3907 2 : keys.emplace_back(peerId, channel->deviceId());
3908 : }
3909 2 : }
3910 3 : }
3911 3 : auto token = std::uniform_int_distribution<uint64_t> {1, JAMI_ID_MAX_VAL}(rand);
3912 12 : std::map<std::string, std::string> msg = {{MIME_TYPE_PIDF, getPIDF(presenceNote_)}};
3913 5 : for (auto& key : keys) {
3914 2 : sendMessage(key.first, key.second.toString(), msg, token, false, true);
3915 : }
3916 3 : }
3917 : }
3918 :
3919 : void
3920 835 : JamiAccount::sendProfile(const std::string& convId, const std::string& peerUri, const std::string& deviceId)
3921 : {
3922 835 : auto accProfilePath = profilePath();
3923 835 : if (not std::filesystem::is_regular_file(accProfilePath))
3924 827 : return;
3925 8 : auto currentSha3 = fileutils::sha3File(accProfilePath);
3926 : // VCard sync for peerUri
3927 8 : if (not needToSendProfile(peerUri, deviceId, currentSha3)) {
3928 0 : JAMI_DEBUG("[Account {}] [device {}] Peer {} already got an up-to-date vCard",
3929 : getAccountID(),
3930 : deviceId,
3931 : peerUri);
3932 0 : return;
3933 : }
3934 : // We need a new channel
3935 24 : transferFile(convId,
3936 16 : accProfilePath.string(),
3937 : deviceId,
3938 : "profile.vcf",
3939 : "",
3940 : 0,
3941 : 0,
3942 : currentSha3,
3943 : fileutils::lastWriteTimeInSeconds(accProfilePath),
3944 8 : [accId = getAccountID(), peerUri, deviceId]() {
3945 : // Mark the VCard as sent
3946 12 : auto sendDir = fileutils::get_cache_dir() / accId / "vcard" / peerUri;
3947 6 : auto path = sendDir / deviceId;
3948 6 : dhtnet::fileutils::recursive_mkdir(sendDir);
3949 6 : std::lock_guard lock(dhtnet::fileutils::getFileLock(path));
3950 6 : if (std::filesystem::is_regular_file(path))
3951 0 : return;
3952 6 : std::ofstream p(path);
3953 6 : });
3954 835 : }
3955 :
3956 : bool
3957 8 : JamiAccount::needToSendProfile(const std::string& peerUri, const std::string& deviceId, const std::string& sha3Sum)
3958 : {
3959 8 : std::string previousSha3 {};
3960 7 : auto vCardPath = cachePath_ / "vcard";
3961 7 : auto sha3Path = vCardPath / "sha3";
3962 8 : dhtnet::fileutils::check_dir(vCardPath, 0700);
3963 : try {
3964 11 : previousSha3 = fileutils::loadTextFile(sha3Path);
3965 3 : } catch (...) {
3966 3 : fileutils::saveFile(sha3Path, (const uint8_t*) sha3Sum.data(), sha3Sum.size(), 0600);
3967 3 : return true;
3968 3 : }
3969 5 : if (sha3Sum != previousSha3) {
3970 : // Incorrect sha3 stored. Update it
3971 0 : dhtnet::fileutils::removeAll(vCardPath, true);
3972 0 : dhtnet::fileutils::check_dir(vCardPath, 0700);
3973 0 : fileutils::saveFile(sha3Path, (const uint8_t*) sha3Sum.data(), sha3Sum.size(), 0600);
3974 0 : return true;
3975 : }
3976 5 : auto peerPath = vCardPath / peerUri;
3977 5 : dhtnet::fileutils::recursive_mkdir(peerPath);
3978 5 : return not std::filesystem::is_regular_file(peerPath / deviceId);
3979 8 : }
3980 :
3981 : void
3982 84 : JamiAccount::clearProfileCache(const std::string& peerUri)
3983 : {
3984 84 : std::error_code ec;
3985 84 : std::filesystem::remove_all(cachePath_ / "vcard" / peerUri, ec);
3986 84 : }
3987 :
3988 : std::filesystem::path
3989 837 : JamiAccount::profilePath() const
3990 : {
3991 837 : return idPath_ / "profile.vcf";
3992 : }
3993 :
3994 : void
3995 174 : JamiAccount::cacheSIPConnection(std::shared_ptr<dhtnet::ChannelSocket>&& socket,
3996 : const std::string& peerId,
3997 : const DeviceId& deviceId)
3998 : {
3999 174 : std::unique_lock lk(sipConnsMtx_);
4000 : // Verify that the connection is not already cached
4001 174 : SipConnectionKey key(peerId, deviceId);
4002 174 : auto& connections = sipConns_[key];
4003 176 : auto conn = std::find_if(connections.begin(), connections.end(), [&](const auto& v) { return v.channel == socket; });
4004 174 : if (conn != connections.end()) {
4005 0 : JAMI_WARNING("[Account {}] Channel socket already cached with this peer", getAccountID());
4006 0 : return;
4007 : }
4008 :
4009 : // Convert to SIP transport
4010 174 : auto onShutdown = [w = weak(), peerId, key, socket]() {
4011 174 : dht::ThreadPool::io().run([w = std::move(w), peerId, key, socket] {
4012 174 : auto shared = w.lock();
4013 174 : if (!shared)
4014 0 : return;
4015 174 : shared->shutdownSIPConnection(socket, key.first, key.second);
4016 : // The connection can be closed during the SIP initialization, so
4017 : // if this happens, the request should be re-sent to ask for a new
4018 : // SIP channel to make the call pass through
4019 174 : shared->callConnectionClosed(key.second, false);
4020 174 : });
4021 348 : };
4022 348 : auto sip_tr = link_.sipTransportBroker->getChanneledTransport(shared(), socket, std::move(onShutdown));
4023 174 : if (!sip_tr) {
4024 0 : JAMI_ERROR("No channeled transport found");
4025 0 : return;
4026 : }
4027 : // Store the connection
4028 174 : connections.emplace_back(SipConnection {sip_tr, socket});
4029 696 : JAMI_WARNING("[Account {:s}] [device {}] New SIP channel opened", getAccountID(), deviceId);
4030 174 : lk.unlock();
4031 :
4032 : // Retry messages
4033 174 : messageEngine_.onPeerOnline(peerId);
4034 174 : messageEngine_.onPeerOnline(peerId, deviceId.toString(), true);
4035 :
4036 : // Connect pending calls
4037 174 : forEachPendingCall(deviceId, [&](const auto& pc) {
4038 86 : if (pc->getConnectionState() != Call::ConnectionState::TRYING
4039 86 : and pc->getConnectionState() != Call::ConnectionState::PROGRESSING)
4040 0 : return;
4041 86 : pc->setSipTransport(sip_tr, getContactHeader(sip_tr));
4042 86 : pc->setState(Call::ConnectionState::PROGRESSING);
4043 86 : if (auto remote_address = socket->getRemoteAddress()) {
4044 : try {
4045 86 : onConnectedOutgoingCall(pc, peerId, remote_address);
4046 0 : } catch (const VoipLinkException&) {
4047 : // In this case, the main scenario is that SIPStartCall failed because
4048 : // the ICE is dead and the TLS session didn't send any packet on that dead
4049 : // link (connectivity change, killed by the os, etc)
4050 : // Here, we don't need to do anything, the TLS will fail and will delete
4051 : // the cached transport
4052 : }
4053 : }
4054 : });
4055 174 : }
4056 :
4057 : void
4058 174 : JamiAccount::shutdownSIPConnection(const std::shared_ptr<dhtnet::ChannelSocket>& channel,
4059 : const std::string& peerId,
4060 : const DeviceId& deviceId)
4061 : {
4062 174 : std::unique_lock lk(sipConnsMtx_);
4063 174 : SipConnectionKey key(peerId, deviceId);
4064 174 : auto it = sipConns_.find(key);
4065 174 : if (it != sipConns_.end()) {
4066 90 : auto& conns = it->second;
4067 181 : conns.erase(std::remove_if(conns.begin(), conns.end(), [&](auto v) { return v.channel == channel; }),
4068 90 : conns.end());
4069 90 : if (conns.empty()) {
4070 89 : sipConns_.erase(it);
4071 : }
4072 : }
4073 174 : lk.unlock();
4074 : // Shutdown after removal to let the callbacks do stuff if needed
4075 174 : if (channel)
4076 174 : channel->shutdown();
4077 174 : }
4078 :
4079 : std::string_view
4080 30701 : JamiAccount::currentDeviceId() const
4081 : {
4082 30701 : if (!accountManager_ or not accountManager_->getInfo())
4083 0 : return {};
4084 30699 : return accountManager_->getInfo()->deviceId;
4085 : }
4086 :
4087 : std::shared_ptr<TransferManager>
4088 153 : JamiAccount::dataTransfer(const std::string& id)
4089 : {
4090 153 : if (id.empty())
4091 68 : return nonSwarmTransferManager_;
4092 85 : if (auto* cm = convModule())
4093 85 : return cm->dataTransfer(id);
4094 0 : return {};
4095 : }
4096 :
4097 : void
4098 0 : JamiAccount::monitor()
4099 : {
4100 0 : JAMI_DEBUG("[Account {:s}] Monitor connections", getAccountID());
4101 0 : JAMI_DEBUG("[Account {:s}] Using proxy: {:s}", getAccountID(), proxyServerCached_);
4102 :
4103 0 : if (auto* cm = convModule())
4104 0 : cm->monitor();
4105 0 : std::shared_lock lkCM(connManagerMtx_);
4106 0 : if (connectionManager_)
4107 0 : connectionManager_->monitor();
4108 0 : }
4109 :
4110 : std::vector<std::map<std::string, std::string>>
4111 0 : JamiAccount::getConnectionList(const std::string& conversationId)
4112 : {
4113 0 : std::shared_lock lkCM(connManagerMtx_);
4114 0 : if (connectionManager_ && conversationId.empty()) {
4115 0 : return connectionManager_->getConnectionList();
4116 0 : } else if (connectionManager_ && convModule_) {
4117 0 : std::vector<std::map<std::string, std::string>> connectionList;
4118 0 : if (auto conv = convModule_->getConversation(conversationId)) {
4119 0 : for (const auto& deviceId : conv->getDeviceIdList()) {
4120 0 : auto connections = connectionManager_->getConnectionList(deviceId);
4121 0 : connectionList.reserve(connectionList.size() + connections.size());
4122 0 : std::move(connections.begin(), connections.end(), std::back_inserter(connectionList));
4123 0 : }
4124 0 : }
4125 0 : return connectionList;
4126 0 : } else {
4127 0 : return {};
4128 : }
4129 0 : }
4130 :
4131 : std::vector<std::map<std::string, std::string>>
4132 0 : JamiAccount::getChannelList(const std::string& connectionId)
4133 : {
4134 0 : std::shared_lock lkCM(connManagerMtx_);
4135 0 : if (!connectionManager_)
4136 0 : return {};
4137 0 : return connectionManager_->getChannelList(connectionId);
4138 0 : }
4139 :
4140 : void
4141 14 : JamiAccount::sendFile(const std::string& conversationId,
4142 : const std::filesystem::path& path,
4143 : const std::string& name,
4144 : const std::string& replyTo)
4145 : {
4146 14 : std::error_code ec;
4147 14 : if (!std::filesystem::is_regular_file(path, ec)) {
4148 0 : JAMI_ERROR("Invalid filename '{}'", path);
4149 0 : emitSignal<libjami::ConversationSignal::OnConversationError>(getAccountID(),
4150 : conversationId,
4151 : EVALIDFETCH,
4152 : "Invalid filename.");
4153 0 : return;
4154 : }
4155 :
4156 14 : auto fileSize = std::filesystem::file_size(path, ec);
4157 14 : if (ec || fileSize == static_cast<decltype(fileSize)>(-1)) {
4158 0 : JAMI_ERROR("Negative file size, user probably doesn't have the appropriate permissions for '{}'", path);
4159 0 : emitSignal<libjami::ConversationSignal::OnConversationError>(
4160 0 : getAccountID(),
4161 : conversationId,
4162 : EVALIDFETCH,
4163 : "Negative file size, could be due to insufficient file permissions.");
4164 0 : return;
4165 : }
4166 :
4167 : // NOTE: this sendMessage is in a computation thread because
4168 : // sha3sum can take quite some time to computer if the user decide
4169 : // to send a big file
4170 14 : dht::ThreadPool::computation().run([w = weak(), conversationId, path, name, fileSize, replyTo]() {
4171 14 : if (auto shared = w.lock()) {
4172 14 : Json::Value value;
4173 14 : auto tid = jami::generateUID(shared->rand);
4174 14 : auto displayName = name.empty() ? path.filename().string() : name;
4175 14 : value["tid"] = std::to_string(tid);
4176 14 : value["displayName"] = displayName;
4177 14 : value["totalSize"] = std::to_string(fileSize);
4178 14 : value["sha3sum"] = fileutils::sha3File(path);
4179 14 : value["type"] = "application/data-transfer+json";
4180 :
4181 70 : shared->convModule()->sendMessage(
4182 14 : conversationId,
4183 14 : std::move(value),
4184 14 : replyTo,
4185 : true,
4186 42 : [accId = shared->getAccountID(), conversationId, tid, displayName, path](const std::string& commitId) {
4187 : // Create a symlink to answer to re-ask
4188 28 : auto filelinkPath = fileutils::get_data_dir() / accId / "conversation_data" / conversationId
4189 42 : / getFileId(commitId, std::to_string(tid), displayName);
4190 14 : if (path != filelinkPath && !std::filesystem::is_symlink(filelinkPath)) {
4191 14 : if (!fileutils::createFileLink(filelinkPath, path, true)) {
4192 0 : JAMI_WARNING("Unable to create symlink for file transfer {} - {}. Copy file",
4193 : filelinkPath,
4194 : path);
4195 0 : std::error_code ec;
4196 0 : auto success = std::filesystem::copy_file(path, filelinkPath, ec);
4197 0 : if (ec || !success) {
4198 0 : JAMI_ERROR("Unable to copy file for file transfer {} - {}", filelinkPath, path);
4199 : // Signal to notify clients that the operation failed.
4200 : // The fileId field sends the filePath.
4201 : // libjami::DataTransferEventCode::unsupported (2) is unused elsewhere.
4202 0 : emitSignal<libjami::DataTransferSignal::DataTransferEvent>(
4203 0 : accId,
4204 0 : conversationId,
4205 : commitId,
4206 0 : path.string(),
4207 : uint32_t(libjami::DataTransferEventCode::invalid));
4208 : } else {
4209 : // Signal to notify clients that the file is copied and can be
4210 : // safely deleted. The fileId field sends the filePath.
4211 : // libjami::DataTransferEventCode::created (1) is unused elsewhere.
4212 0 : emitSignal<libjami::DataTransferSignal::DataTransferEvent>(
4213 0 : accId,
4214 0 : conversationId,
4215 : commitId,
4216 0 : path.string(),
4217 : uint32_t(libjami::DataTransferEventCode::created));
4218 : }
4219 : } else {
4220 28 : emitSignal<libjami::DataTransferSignal::DataTransferEvent>(
4221 14 : accId,
4222 14 : conversationId,
4223 : commitId,
4224 28 : path.string(),
4225 : uint32_t(libjami::DataTransferEventCode::created));
4226 : }
4227 : }
4228 14 : });
4229 28 : }
4230 14 : });
4231 : }
4232 :
4233 : void
4234 8 : JamiAccount::transferFile(const std::string& conversationId,
4235 : const std::string& path,
4236 : const std::string& deviceId,
4237 : const std::string& fileId,
4238 : const std::string& interactionId,
4239 : size_t start,
4240 : size_t end,
4241 : const std::string& sha3Sum,
4242 : uint64_t lastWriteTime,
4243 : std::function<void()> onFinished)
4244 : {
4245 8 : std::string modified;
4246 8 : if (lastWriteTime != 0) {
4247 16 : modified = fmt::format("&modified={}", lastWriteTime);
4248 : }
4249 16 : auto fid = fileId == "profile.vcf" ? fmt::format("profile.vcf?sha3={}{}", sha3Sum, modified) : fileId;
4250 8 : auto channelName = conversationId.empty()
4251 : ? fmt::format("{}profile.vcf?sha3={}{}", DATA_TRANSFER_SCHEME, sha3Sum, modified)
4252 16 : : fmt::format("{}{}/{}/{}", DATA_TRANSFER_SCHEME, conversationId, currentDeviceId(), fid);
4253 8 : std::shared_lock lkCM(connManagerMtx_);
4254 8 : if (!connectionManager_)
4255 0 : return;
4256 40 : connectionManager_->connectDevice(
4257 16 : DeviceId(deviceId),
4258 : channelName,
4259 8 : [this,
4260 : conversationId,
4261 8 : path = std::move(path),
4262 : fileId,
4263 : interactionId,
4264 : start,
4265 : end,
4266 8 : onFinished = std::move(onFinished)](std::shared_ptr<dhtnet::ChannelSocket> socket, const DeviceId&) {
4267 8 : if (!socket)
4268 0 : return;
4269 40 : dht::ThreadPool::io().run([w = weak(),
4270 8 : path = std::move(path),
4271 8 : socket = std::move(socket),
4272 8 : conversationId = std::move(conversationId),
4273 8 : fileId,
4274 8 : interactionId,
4275 : start,
4276 : end,
4277 8 : onFinished = std::move(onFinished)] {
4278 8 : if (auto shared = w.lock())
4279 8 : if (auto dt = shared->dataTransfer(conversationId))
4280 16 : dt->transferFile(socket, fileId, interactionId, path, start, end, std::move(onFinished));
4281 8 : });
4282 : });
4283 8 : }
4284 :
4285 : void
4286 13 : JamiAccount::askForFileChannel(const std::string& conversationId,
4287 : const std::string& deviceId,
4288 : const std::string& interactionId,
4289 : const std::string& fileId,
4290 : size_t start,
4291 : size_t end)
4292 : {
4293 28 : auto tryDevice = [=](const auto& did) {
4294 28 : std::shared_lock lkCM(connManagerMtx_);
4295 28 : if (!connectionManager_)
4296 0 : return;
4297 :
4298 28 : auto channelName = fmt::format("{}{}/{}/{}", DATA_TRANSFER_SCHEME, conversationId, currentDeviceId(), fileId);
4299 28 : if (start != 0 || end != 0) {
4300 6 : channelName += fmt::format("?start={}&end={}", start, end);
4301 : }
4302 : // We can avoid to negotiate new sessions, as the file notif
4303 : // probably came from an online device or last connected device.
4304 56 : connectionManager_->connectDevice(
4305 : did,
4306 : channelName,
4307 40 : [w = weak(),
4308 28 : conversationId,
4309 28 : fileId,
4310 28 : interactionId,
4311 : start](const std::shared_ptr<dhtnet::ChannelSocket>& channel, const DeviceId&) {
4312 28 : if (!channel)
4313 16 : return;
4314 12 : dht::ThreadPool::io().run([w, conversationId, channel, fileId, interactionId, start] {
4315 12 : auto shared = w.lock();
4316 12 : if (!shared)
4317 0 : return;
4318 12 : auto dt = shared->dataTransfer(conversationId);
4319 12 : if (!dt)
4320 0 : return;
4321 12 : if (interactionId.empty())
4322 0 : dt->onIncomingProfile(channel);
4323 : else
4324 12 : dt->onIncomingFileTransfer(fileId, channel, start);
4325 12 : });
4326 : },
4327 : false);
4328 41 : };
4329 :
4330 13 : if (!deviceId.empty()) {
4331 : // Only ask for device
4332 1 : tryDevice(DeviceId(deviceId));
4333 : } else {
4334 : // Only ask for connected devices. For others we will attempt
4335 : // with new peer online
4336 40 : for (const auto& m : convModule()->getConversationMembers(conversationId)) {
4337 28 : accountManager_->forEachDevice(dht::InfoHash(m.at("uri")),
4338 27 : [tryDevice](const std::shared_ptr<dht::crypto::PublicKey>& dev) {
4339 27 : tryDevice(dev->getLongId());
4340 27 : });
4341 12 : }
4342 : }
4343 13 : }
4344 :
4345 : void
4346 57 : JamiAccount::askForProfile(const std::string& conversationId, const std::string& deviceId, const std::string& memberUri)
4347 : {
4348 57 : std::shared_lock lkCM(connManagerMtx_);
4349 57 : if (!connectionManager_)
4350 0 : return;
4351 :
4352 57 : auto channelName = fmt::format("{}{}/profile/{}.vcf", DATA_TRANSFER_SCHEME, conversationId, memberUri);
4353 : // We can avoid to negotiate new sessions, as the file notif
4354 : // probably came from an online device or last connected device.
4355 171 : connectionManager_->connectDevice(
4356 114 : DeviceId(deviceId),
4357 : channelName,
4358 105 : [this, conversationId](const std::shared_ptr<dhtnet::ChannelSocket>& channel, const DeviceId&) {
4359 57 : if (!channel)
4360 9 : return;
4361 48 : dht::ThreadPool::io().run([w = weak(), conversationId, channel] {
4362 48 : if (auto shared = w.lock())
4363 48 : if (auto dt = shared->dataTransfer(conversationId))
4364 96 : dt->onIncomingProfile(channel);
4365 48 : });
4366 : },
4367 : false);
4368 57 : }
4369 :
4370 : void
4371 2178 : JamiAccount::onPeerConnected(const std::string& peerId, bool connected)
4372 : {
4373 2178 : std::unique_lock lock(buddyInfoMtx);
4374 2178 : auto& state = presenceState_[peerId];
4375 2178 : auto it = trackedBuddies_.find(dht::InfoHash(peerId));
4376 2178 : auto isOnline = it != trackedBuddies_.end() && it->second.devices_cnt > 0;
4377 3266 : auto newState = connected ? PresenceState::CONNECTED
4378 1088 : : (isOnline ? PresenceState::AVAILABLE : PresenceState::DISCONNECTED);
4379 2178 : if (state != newState) {
4380 2178 : state = newState;
4381 2178 : lock.unlock();
4382 2178 : emitSignal<libjami::PresenceSignal::NewBuddyNotification>(getAccountID(),
4383 : peerId,
4384 : static_cast<int>(newState),
4385 : "");
4386 : }
4387 2178 : }
4388 :
4389 : void
4390 766 : JamiAccount::initConnectionManager()
4391 : {
4392 766 : if (!nonSwarmTransferManager_)
4393 659 : nonSwarmTransferManager_ = std::make_shared<TransferManager>(accountID_,
4394 659 : config().username,
4395 : "",
4396 1977 : dht::crypto::getDerivedRandomEngine(rand));
4397 766 : if (!connectionManager_) {
4398 670 : auto connectionManagerConfig = std::make_shared<dhtnet::ConnectionManager::Config>();
4399 670 : connectionManagerConfig->ioContext = Manager::instance().ioContext();
4400 670 : connectionManagerConfig->dht = dht();
4401 670 : connectionManagerConfig->certStore = certStore_;
4402 670 : connectionManagerConfig->id = identity();
4403 670 : connectionManagerConfig->upnpCtrl = upnpCtrl_;
4404 670 : connectionManagerConfig->turnServer = config().turnServer;
4405 670 : connectionManagerConfig->upnpEnabled = config().upnpEnabled;
4406 670 : connectionManagerConfig->turnServerUserName = config().turnServerUserName;
4407 670 : connectionManagerConfig->turnServerPwd = config().turnServerPwd;
4408 670 : connectionManagerConfig->turnServerRealm = config().turnServerRealm;
4409 670 : connectionManagerConfig->turnEnabled = config().turnEnabled;
4410 670 : connectionManagerConfig->cachePath = cachePath_;
4411 670 : if (Manager::instance().dhtnetLogLevel > 0) {
4412 0 : connectionManagerConfig->logger = logger_;
4413 : }
4414 670 : connectionManagerConfig->factory = Manager::instance().getIceTransportFactory();
4415 670 : connectionManagerConfig->turnCache = turnCache_;
4416 670 : connectionManagerConfig->rng = std::make_unique<std::mt19937_64>(dht::crypto::getDerivedRandomEngine(rand));
4417 670 : connectionManagerConfig->legacyMode = dhtnet::LegacyMode::Supported;
4418 670 : connectionManager_ = std::make_unique<dhtnet::ConnectionManager>(connectionManagerConfig);
4419 1340 : channelHandlers_[Uri::Scheme::SWARM] = std::make_unique<SwarmChannelHandler>(shared(),
4420 1340 : *connectionManager_.get());
4421 1340 : channelHandlers_[Uri::Scheme::GIT] = std::make_unique<ConversationChannelHandler>(shared(),
4422 1340 : *connectionManager_.get());
4423 670 : channelHandlers_[Uri::Scheme::SYNC] = std::make_unique<SyncChannelHandler>(shared(), *connectionManager_.get());
4424 670 : channelHandlers_[Uri::Scheme::DATA_TRANSFER]
4425 1340 : = std::make_unique<TransferChannelHandler>(shared(), *connectionManager_.get());
4426 2010 : channelHandlers_[Uri::Scheme::MESSAGE] = std::make_unique<MessageChannelHandler>(
4427 670 : *connectionManager_.get(),
4428 12805 : [this](const auto& cert, std::string& type, const std::string& content) {
4429 25610 : onTextMessage("", cert->issuer->getId().toString(), cert, {{type, content}});
4430 12808 : },
4431 2180 : [w = weak()](const std::string& peer, bool connected) {
4432 2180 : asio::post(*Manager::instance().ioContext(), [w, peer, connected] {
4433 2180 : if (auto acc = w.lock())
4434 2180 : acc->onPeerConnected(peer, connected);
4435 2180 : });
4436 2850 : });
4437 670 : channelHandlers_[Uri::Scheme::AUTH] = std::make_unique<AuthChannelHandler>(shared(), *connectionManager_.get());
4438 :
4439 : #if TARGET_OS_IOS
4440 : connectionManager_->oniOSConnected([&](const std::string& connType, dht::InfoHash peer_h) {
4441 : if ((connType == "videoCall" || connType == "audioCall") && jami::Manager::instance().isIOSExtension) {
4442 : bool hasVideo = connType == "videoCall";
4443 : emitSignal<libjami::ConversationSignal::CallConnectionRequest>("", peer_h.toString(), hasVideo);
4444 : return true;
4445 : }
4446 : return false;
4447 : });
4448 : #endif
4449 670 : }
4450 766 : }
4451 :
4452 : void
4453 1003 : JamiAccount::updateUpnpController()
4454 : {
4455 1003 : Account::updateUpnpController();
4456 1003 : if (connectionManager_) {
4457 47 : auto config = connectionManager_->getConfig();
4458 47 : if (config)
4459 47 : config->upnpCtrl = upnpCtrl_;
4460 47 : }
4461 1003 : }
4462 :
4463 : } // namespace jami
|