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