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