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