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