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
243static constexpr std::string_view
244dhtStatusStr(dht::NodeStatus status)
245{
246 return status == dht::NodeStatus::Connected
247 ? "connected"sv
248 : (status == dht::NodeStatus::Connecting ? "connecting"sv : "disconnected"sv);
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 // Following replay logic could incorrectly be triggered for conversations
1250 // already fetched and used by other devices, so it is disabled for now.
1251 /*if (!convFromReq.empty()) {
1252 auto oldConv = cm->getOneToOneConversation(uri);
1253 // If we previously removed the contact, and re-add it, we may
1254 // receive a convId different from the request. In that case,
1255 // we need to remove the current conversation and clone the old
1256 // one (given by convFromReq).
1257 // TODO: In the future, we may want to re-commit the messages we
1258 // may have send in the request we sent.
1259 if (oldConv != convFromReq
1260 && cm->updateConvForContact(uri, oldConv, convFromReq)) {
1261 cm->initReplay(oldConv, convFromReq);
1262 cm->cloneConversationFrom(convFromReq, uri, oldConv);
1263 }
1264 }*/
1265 }
1266 });
1267 }};
1268
1269 const auto& conf = config();
1270 try {
1271 auto oldIdentity = id_.first ? id_.first->getPublicKey().getLongId() : DeviceId();
1272 if (conf.managerUri.empty()) {
1273 accountManager_ = std::make_shared<ArchiveAccountManager>(
1274 getAccountID(),
1275 getPath(),
1276 [this]() { return getAccountDetails(); },
1277 [this](DeviceSync&& syncData) {
1278 if (auto sm = syncModule()) {
1279 auto syncDataPtr = std::make_shared<SyncMsg>();
1280 syncDataPtr->ds = std::move(syncData);
1281 sm->syncWithConnected(syncDataPtr);
1282 }
1283 },
1284 conf.archivePath.empty() ? "archive.gz" : conf.archivePath,
1285 conf.nameServer);
1286 } else {
1287 accountManager_ = std::make_shared<ServerAccountManager>(getAccountID(),
1288 getPath(),
1289 conf.managerUri,
1290 conf.nameServer);
1291 }
1292
1293 auto id = accountManager_->loadIdentity(conf.tlsCertificateFile,
1294 conf.tlsPrivateKeyFile,
1295 conf.tlsPassword);
1296
1297 if (auto info = accountManager_->useIdentity(id,
1298 conf.receipt,
1299 conf.receiptSignature,
1300 conf.managerUsername,
1301 callbacks)) {
1302 // normal loading path
1303 id_ = std::move(id);
1304 config_->username = info->accountId;
1305 JAMI_WARNING("[Account {:s}] Loaded account identity", getAccountID());
1306 if (info->identity.first->getPublicKey().getLongId() != oldIdentity) {
1307 JAMI_WARNING("[Account {:s}] Identity changed", getAccountID());
1308 {
1309 std::lock_guard lk(moduleMtx_);
1310 convModule_.reset();
1311 }
1312 convModule(); // convModule must absolutely be initialized in
1313 // both branches of the if statement here in order
1314 // for it to exist for subsequent use.
1315 } else {
1316 convModule()->setAccountManager(accountManager_);
1317 }
1318 if (not isEnabled()) {
1319 setRegistrationState(RegistrationState::UNREGISTERED);
1320 }
1321 convModule()->loadConversations();
1322 } else if (isEnabled()) {
1323 JAMI_WARNING("[Account {}] useIdentity failed!", getAccountID());
1324 if (not conf.managerUri.empty() and archive_password.empty()) {
1325 Migration::setState(accountID_, Migration::State::INVALID);
1326 setRegistrationState(RegistrationState::ERROR_NEED_MIGRATION);
1327 return;
1328 }
1329
1330 bool migrating = registrationState_ == RegistrationState::ERROR_NEED_MIGRATION;
1331 setRegistrationState(RegistrationState::INITIALIZING);
1332 auto fDeviceKey = dht::ThreadPool::computation()
1333 .getShared<std::shared_ptr<dht::crypto::PrivateKey>>([]() {
1334 return std::make_shared<dht::crypto::PrivateKey>(
1335 dht::crypto::PrivateKey::generate());
1336 });
1337
1338 std::unique_ptr<AccountManager::AccountCredentials> creds;
1339 if (conf.managerUri.empty()) {
1340 auto acreds = std::make_unique<ArchiveAccountManager::ArchiveAccountCredentials>();
1341 auto archivePath = fileutils::getFullPath(idPath_, conf.archivePath);
1342 // bool hasArchive = std::filesystem::is_regular_file(archivePath);
1343
1344 if (!archive_path.empty()) {
1345 // Importing external archive
1346 acreds->scheme = "file";
1347 acreds->uri = archive_path;
1348 } else if (!conf.archive_url.empty() && conf.archive_url == "jami-auth") {
1349 // Importing over a Peer2Peer TLS connection with DHT as DNS
1350 JAMI_DEBUG("[Account {}] [LinkDevice] scheme p2p & uri {}", getAccountID(), conf.archive_url);
1351 acreds->scheme = "p2p";
1352 acreds->uri = conf.archive_url;
1353 } else if (!archive_pin.empty()) {
1354 // Importing from DHT
1355 acreds->scheme = "dht";
1356 acreds->uri = archive_pin;
1357 acreds->dhtBootstrap = loadBootstrap();
1358 acreds->dhtPort = dhtPortUsed();
1359 } else if (std::filesystem::is_regular_file(archivePath)) {
1360 // Migrating local account
1361 acreds->scheme = "local";
1362 acreds->uri = archivePath.string();
1363 acreds->updateIdentity = id;
1364 migrating = true;
1365 }
1366 creds = std::move(acreds);
1367 } else {
1368 auto screds = std::make_unique<ServerAccountManager::ServerAccountCredentials>();
1369 screds->username = conf.managerUsername;
1370 creds = std::move(screds);
1371 }
1372 creds->password = archive_password;
1373 bool hasPassword = !archive_password.empty();
1374 if (hasPassword && archive_password_scheme.empty())
1375 creds->password_scheme = fileutils::ARCHIVE_AUTH_SCHEME_PASSWORD;
1376 else
1377 creds->password_scheme = archive_password_scheme;
1378
1379 JAMI_WARNING("[Account {}] initAuthentication {} {}",
1380 getAccountID(),
1381 fmt::ptr(this),
1382 fmt::ptr(accountManager_));
1383
1384 accountManager_->initAuthentication(
1385 fDeviceKey,
1386 ip_utils::getDeviceName(),
1387 std::move(creds),
1388 [w = weak(),
1389 this,
1390 migrating,
1391 hasPassword](const AccountInfo& info,
1392 const std::map<std::string, std::string>& config,
1393 std::string&& receipt,
1394 std::vector<uint8_t>&& receipt_signature) {
1395 auto sthis = w.lock();
1396 if (not sthis)
1397 return;
1398 JAMI_LOG("[Account {}] Auth success! Device: {}", getAccountID(), info.deviceId);
1399
1400 dhtnet::fileutils::check_dir(idPath_, 0700);
1401
1402 auto id = info.identity;
1403 editConfig([&](JamiAccountConfig& conf) {
1404 std::tie(conf.tlsPrivateKeyFile, conf.tlsCertificateFile)
1405 = saveIdentity(id, idPath_, DEVICE_ID_PATH);
1406 conf.tlsPassword = {};
1407 auto passwordIt = config.find(
1409 if (passwordIt != config.end() && !passwordIt->second.empty()) {
1410 conf.archiveHasPassword = passwordIt->second == "true";
1411 } else {
1412 conf.archiveHasPassword = hasPassword;
1413 }
1414
1415 if (not conf.managerUri.empty()) {
1416 conf.registeredName = conf.managerUsername;
1417#if HAVE_RINGNS
1418 registeredName_ = conf.managerUsername;
1419#endif
1420 }
1421 conf.username = info.accountId;
1422 conf.deviceName = accountManager_->getAccountDeviceName();
1423
1424 auto nameServerIt = config.find(
1426 if (nameServerIt != config.end() && !nameServerIt->second.empty()) {
1427 conf.nameServer = nameServerIt->second;
1428 }
1429 auto displayNameIt = config.find(
1431 if (displayNameIt != config.end() && !displayNameIt->second.empty()) {
1432 conf.displayName = displayNameIt->second;
1433 }
1434 conf.receipt = std::move(receipt);
1435 conf.receiptSignature = std::move(receipt_signature);
1436 conf.fromMap(config);
1437 });
1438 id_ = std::move(id);
1439 {
1440 std::lock_guard lk(moduleMtx_);
1441 convModule_.reset();
1442 }
1443 if (migrating) {
1444 Migration::setState(getAccountID(), Migration::State::SUCCESS);
1445 }
1446 if (not info.photo.empty() or not config_->displayName.empty())
1447 emitSignal<libjami::ConfigurationSignal::AccountProfileReceived>(
1448 getAccountID(), config_->displayName, info.photo);
1449 setRegistrationState(RegistrationState::UNREGISTERED);
1450 doRegister();
1451 },
1452 [w = weak(),
1453 id,
1454 accountId = getAccountID(),
1455 migrating](AccountManager::AuthError error, const std::string& message) {
1456 JAMI_WARNING("[Account {}] Auth error: {} {}", accountId, (int) error, message);
1457 if ((id.first || migrating)
1458 && error == AccountManager::AuthError::INVALID_ARGUMENTS) {
1459 // In cast of a migration or manager connexion failure stop the migration
1460 // and block the account
1461 Migration::setState(accountId, Migration::State::INVALID);
1462 if (auto acc = w.lock())
1463 acc->setRegistrationState(RegistrationState::ERROR_NEED_MIGRATION);
1464 } else {
1465 // In case of a DHT or backup import failure, just remove the account
1466 if (auto acc = w.lock())
1467 acc->setRegistrationState(RegistrationState::ERROR_GENERIC);
1468 runOnMainThread([accountId = std::move(accountId)] {
1469 Manager::instance().removeAccount(accountId, true);
1470 });
1471 }
1472 },
1473 callbacks);
1474 }
1475 } catch (const std::exception& e) {
1476 JAMI_WARNING("[Account {}] Error loading account: {}", getAccountID(), e.what());
1477 accountManager_.reset();
1478 setRegistrationState(RegistrationState::ERROR_GENERIC);
1479 }
1480}
1481
1482std::map<std::string, std::string>
1483JamiAccount::getVolatileAccountDetails() const
1484{
1485 auto a = SIPAccountBase::getVolatileAccountDetails();
1487#if HAVE_RINGNS
1488 auto registeredName = getRegisteredName();
1489 if (not registeredName.empty())
1491#endif
1492 a.emplace(libjami::Account::ConfProperties::PROXY_SERVER, proxyServerCached_);
1493 a.emplace(libjami::Account::VolatileProperties::DHT_BOUND_PORT, std::to_string(dhtBoundPort_));
1495 deviceAnnounced_ ? TRUE_STR : FALSE_STR);
1496 if (accountManager_) {
1497 if (auto info = accountManager_->getInfo()) {
1498 a.emplace(libjami::Account::ConfProperties::DEVICE_ID, info->deviceId);
1499 }
1500 }
1501 return a;
1502}
1503
1504#if HAVE_RINGNS
1505void
1506JamiAccount::lookupName(const std::string& name)
1507{
1508 std::lock_guard lock(configurationMutex_);
1509 if (accountManager_)
1510 accountManager_->lookupUri(name,
1511 config().nameServer,
1512 [acc = getAccountID(), name](const std::string& regName,
1513 const std::string& address,
1514 NameDirectory::Response response) {
1515 emitSignal<libjami::ConfigurationSignal::RegisteredNameFound>(
1516 acc, name, (int) response, address, regName);
1517 });
1518}
1519
1520void
1521JamiAccount::lookupAddress(const std::string& addr)
1522{
1523 std::lock_guard lock(configurationMutex_);
1524 auto acc = getAccountID();
1525 if (accountManager_)
1526 accountManager_->lookupAddress(
1527 addr,
1528 [acc, addr](const std::string& regName,
1529 const std::string& address,
1530 NameDirectory::Response response) {
1531 emitSignal<libjami::ConfigurationSignal::RegisteredNameFound>(acc,
1532 addr,
1533 (int) response,
1534 address,
1535 regName);
1536 });
1537}
1538
1539void
1540JamiAccount::registerName(const std::string& name,
1541 const std::string& scheme,
1542 const std::string& password)
1543{
1544 std::lock_guard lock(configurationMutex_);
1545 if (accountManager_)
1546 accountManager_->registerName(
1547 name,
1548 scheme,
1549 password,
1550 [acc = getAccountID(), name, w = weak()](NameDirectory::RegistrationResponse response,
1551 const std::string& regName) {
1552 auto res = (int) std::min(response, NameDirectory::RegistrationResponse::error);
1553 if (response == NameDirectory::RegistrationResponse::success) {
1554 if (auto this_ = w.lock()) {
1555 if (this_->setRegisteredName(regName)) {
1556 this_->editConfig([&](JamiAccountConfig& config) {
1557 config.registeredName = regName;
1558 });
1559 emitSignal<libjami::ConfigurationSignal::VolatileDetailsChanged>(
1560 this_->accountID_, this_->getVolatileAccountDetails());
1561 }
1562 }
1563 }
1564 emitSignal<libjami::ConfigurationSignal::NameRegistrationEnded>(acc, res, name);
1565 });
1566}
1567#endif
1568
1569bool
1570JamiAccount::searchUser(const std::string& query)
1571{
1572 if (accountManager_)
1573 return accountManager_->searchUser(
1574 query,
1575 [acc = getAccountID(), query](const jami::NameDirectory::SearchResult& result,
1578 (int) response,
1579 query,
1580 result);
1581 });
1582 return false;
1583}
1584
1585void
1586JamiAccount::forEachPendingCall(const DeviceId& deviceId,
1587 const std::function<void(const std::shared_ptr<SIPCall>&)>& cb)
1588{
1589 std::vector<std::shared_ptr<SIPCall>> pc;
1590 {
1591 std::lock_guard lk(pendingCallsMutex_);
1592 pc = std::move(pendingCalls_[deviceId]);
1593 }
1594 for (const auto& pendingCall : pc) {
1595 cb(pendingCall);
1596 }
1597}
1598
1599void
1600JamiAccount::registerAsyncOps()
1601{
1602 auto onLoad = [this, loaded = std::make_shared<std::atomic_uint>()] {
1603 if (++(*loaded) == 2u) {
1604 runOnMainThread([w = weak()] {
1605 if (auto s = w.lock()) {
1606 std::lock_guard lock(s->configurationMutex_);
1607 s->doRegister_();
1608 }
1609 });
1610 }
1611 };
1612
1613 loadCachedProxyServer([onLoad](const std::string&) { onLoad(); });
1614
1615 if (upnpCtrl_) {
1616 JAMI_LOG("[Account {:s}] UPnP: attempting to map ports", getAccountID());
1617
1618 // Release current mapping if any.
1619 if (dhtUpnpMapping_.isValid()) {
1620 upnpCtrl_->releaseMapping(dhtUpnpMapping_);
1621 }
1622
1623 dhtUpnpMapping_.enableAutoUpdate(true);
1624
1625 // Set the notify callback.
1626 dhtUpnpMapping_.setNotifyCallback([w = weak(),
1627 onLoad,
1628 update = std::make_shared<bool>(false)](
1629 dhtnet::upnp::Mapping::sharedPtr_t mapRes) {
1630 if (auto accPtr = w.lock()) {
1631 auto& dhtMap = accPtr->dhtUpnpMapping_;
1632 const auto& accId = accPtr->getAccountID();
1633
1634 JAMI_LOG("[Account {:s}] DHT UPnP mapping changed to {:s}",
1635 accId,
1636 mapRes->toString(true));
1637
1638 if (*update) {
1639 // Check if we need to update the mapping and the registration.
1640 if (dhtMap.getMapKey() != mapRes->getMapKey()
1641 or dhtMap.getState() != mapRes->getState()) {
1642 // The connectivity must be restarted, if either:
1643 // - the state changed to "OPEN",
1644 // - the state changed to "FAILED" and the mapping was in use.
1645 if (mapRes->getState() == dhtnet::upnp::MappingState::OPEN
1646 or (mapRes->getState() == dhtnet::upnp::MappingState::FAILED
1647 and dhtMap.getState() == dhtnet::upnp::MappingState::OPEN)) {
1648 // Update the mapping and restart the registration.
1649 dhtMap.updateFrom(mapRes);
1650
1651 JAMI_WARNING(
1652 "[Account {:s}] Allocated port changed to {}. Restarting the "
1653 "registration",
1654 accId,
1655 accPtr->dhtPortUsed());
1656
1657 accPtr->dht_->connectivityChanged();
1658
1659 } else {
1660 // Only update the mapping.
1661 dhtMap.updateFrom(mapRes);
1662 }
1663 }
1664 } else {
1665 *update = true;
1666 // Set connection info and load the account.
1667 if (mapRes->getState() == dhtnet::upnp::MappingState::OPEN) {
1668 dhtMap.updateFrom(mapRes);
1669 JAMI_LOG(
1670 "[Account {:s}] Mapping {:s} successfully allocated: starting the DHT",
1671 accId,
1672 dhtMap.toString());
1673 } else {
1674 JAMI_WARNING("[Account {:s}] Mapping request is in {:s} state: starting "
1675 "the DHT anyway",
1676 accId,
1677 mapRes->getStateStr());
1678 }
1679
1680 // Load the account and start the DHT.
1681 onLoad();
1682 }
1683 }
1684 });
1685
1686 // Request the mapping.
1687 auto map = upnpCtrl_->reserveMapping(dhtUpnpMapping_);
1688 // The returned mapping is invalid. Load the account now since
1689 // we may never receive the callback.
1690 if (not map) {
1691 onLoad();
1692 }
1693 } else {
1694 // No UPNP. Load the account and start the DHT. The local DHT
1695 // might not be reachable for peers if we are behind a NAT.
1696 onLoad();
1697 }
1698}
1699
1700void
1701JamiAccount::doRegister()
1702{
1703 std::lock_guard lock(configurationMutex_);
1704 if (not isUsable()) {
1705 JAMI_WARNING("[Account {:s}] Account must be enabled and active to register, ignoring",
1706 getAccountID());
1707 return;
1708 }
1709
1710 JAMI_LOG("[Account {:s}] Starting account…", getAccountID());
1711
1712 // invalid state transitions:
1713 // INITIALIZING: generating/loading certificates, unable to register
1714 // NEED_MIGRATION: old account detected, user needs to migrate
1715 if (registrationState_ == RegistrationState::INITIALIZING
1716 || registrationState_ == RegistrationState::ERROR_NEED_MIGRATION)
1717 return;
1718
1719 convModule(); // Init conv module before passing in trying
1720 setRegistrationState(RegistrationState::TRYING);
1721 /* if UPnP is enabled, then wait for IGD to complete registration */
1722 if (upnpCtrl_ or proxyServerCached_.empty()) {
1723 registerAsyncOps();
1724 } else {
1725 doRegister_();
1726 }
1727}
1728
1729std::vector<std::string>
1730JamiAccount::loadBootstrap() const
1731{
1732 std::vector<std::string> bootstrap;
1733 std::string_view stream(config().hostname), node_addr;
1734 while (jami::getline(stream, node_addr, ';'))
1735 bootstrap.emplace_back(node_addr);
1736 for (const auto& b : bootstrap)
1737 JAMI_DBG("[Account %s] Bootstrap node: %s", getAccountID().c_str(), b.c_str());
1738 return bootstrap;
1739}
1740
1741void
1742JamiAccount::trackBuddyPresence(const std::string& buddy_id, bool track)
1743{
1744 std::string buddyUri;
1745 try {
1746 buddyUri = parseJamiUri(buddy_id);
1747 } catch (...) {
1748 JAMI_ERROR("[Account {:s}] Failed to track presence: invalid URI {:s}",
1749 getAccountID(),
1750 buddy_id);
1751 return;
1752 }
1753 JAMI_LOG("[Account {:s}] {:s} presence for {:s}",
1754 getAccountID(),
1755 track ? "Track" : "Untrack",
1756 buddy_id);
1757
1758 auto h = dht::InfoHash(buddyUri);
1759 std::unique_lock lock(buddyInfoMtx);
1760 if (track) {
1761 auto buddy = trackedBuddies_.emplace(h, BuddyInfo {h});
1762 if (buddy.second) {
1763 trackPresence(buddy.first->first, buddy.first->second);
1764 }
1765 auto it = presenceState_.find(buddyUri);
1766 if (it != presenceState_.end() && it->second != PresenceState::DISCONNECTED) {
1767 lock.unlock();
1768 emitSignal<libjami::PresenceSignal::NewBuddyNotification>(getAccountID(),
1769 buddyUri,
1770 static_cast<int>(it->second),
1771 "");
1772 }
1773 } else {
1774 auto buddy = trackedBuddies_.find(h);
1775 if (buddy != trackedBuddies_.end()) {
1776 if (auto dht = dht_)
1777 if (dht->isRunning())
1778 dht->cancelListen(h, std::move(buddy->second.listenToken));
1779 trackedBuddies_.erase(buddy);
1780 }
1781 }
1782}
1783
1784void
1785JamiAccount::trackPresence(const dht::InfoHash& h, BuddyInfo& buddy)
1786{
1787 auto dht = dht_;
1788 if (not dht or not dht->isRunning()) {
1789 return;
1790 }
1791 buddy.listenToken
1792 = dht->listen<DeviceAnnouncement>(h, [this, h](DeviceAnnouncement&& dev, bool expired) {
1793 bool wasConnected, isConnected;
1794 {
1795 std::lock_guard lock(buddyInfoMtx);
1796 auto buddy = trackedBuddies_.find(h);
1797 if (buddy == trackedBuddies_.end())
1798 return true;
1799 wasConnected = buddy->second.devices_cnt > 0;
1800 if (expired)
1801 --buddy->second.devices_cnt;
1802 else
1803 ++buddy->second.devices_cnt;
1804 isConnected = buddy->second.devices_cnt > 0;
1805 }
1806 // NOTE: the rest can use configurationMtx_, that can be locked during unregister so
1807 // do not retrigger on dht
1808 runOnMainThread([w = weak(), h, dev, expired, isConnected, wasConnected]() {
1809 auto sthis = w.lock();
1810 if (!sthis)
1811 return;
1812 if (not expired) {
1813 // Retry messages every time a new device announce its presence
1814 sthis->messageEngine_.onPeerOnline(h.toString());
1815 }
1816 if (isConnected and not wasConnected) {
1817 sthis->onTrackedBuddyOnline(h);
1818 } else if (not isConnected and wasConnected) {
1819 sthis->onTrackedBuddyOffline(h);
1820 }
1821 });
1822
1823 return true;
1824 });
1825}
1826
1827std::map<std::string, bool>
1828JamiAccount::getTrackedBuddyPresence() const
1829{
1830 std::lock_guard lock(buddyInfoMtx);
1831 std::map<std::string, bool> presence_info;
1832 for (const auto& buddy_info_p : trackedBuddies_)
1833 presence_info.emplace(buddy_info_p.first.toString(), buddy_info_p.second.devices_cnt > 0);
1834 return presence_info;
1835}
1836
1837void
1838JamiAccount::onTrackedBuddyOnline(const dht::InfoHash& contactId)
1839{
1840 std::string id(contactId.toString());
1841 JAMI_DEBUG("[Account {:s}] Buddy {} online", getAccountID(), id);
1842 auto& state = presenceState_[id];
1843 if (state < PresenceState::AVAILABLE) {
1844 state = PresenceState::AVAILABLE;
1845 emitSignal<libjami::PresenceSignal::NewBuddyNotification>(getAccountID(),
1846 id,
1847 static_cast<int>(
1848 PresenceState::AVAILABLE),
1849 "");
1850 }
1851
1852 if (auto details = getContactInfo(id)) {
1853 if (!details->confirmed) {
1854 auto convId = convModule()->getOneToOneConversation(id);
1855 if (convId.empty())
1856 return;
1857 // In this case, the TrustRequest was sent but never confirmed (cause the contact was
1858 // offline maybe) To avoid the contact to never receive the conv request, retry there
1859 std::lock_guard lock(configurationMutex_);
1860 if (accountManager_) {
1861 // Retrieve cached payload for trust request.
1862 auto requestPath = cachePath_ / "requests" / id;
1863 std::vector<uint8_t> payload;
1864 try {
1865 payload = fileutils::loadFile(requestPath);
1866 } catch (...) {
1867 }
1868 if (payload.size() >= 64000) {
1870 "[Account {:s}] Trust request for contact {:s} is too big, reset payload",
1871 getAccountID(),
1872 id);
1873 payload.clear();
1874 }
1875 accountManager_->sendTrustRequest(id, convId, payload);
1876 }
1877 }
1878 }
1879}
1880
1881void
1882JamiAccount::onTrackedBuddyOffline(const dht::InfoHash& contactId)
1883{
1884 auto id = contactId.toString();
1885 JAMI_DEBUG("[Account {:s}] Buddy {} offline", getAccountID(), id);
1886 auto& state = presenceState_[id];
1887 if (state > PresenceState::DISCONNECTED) {
1888 if (state == PresenceState::CONNECTED) {
1889 JAMI_WARNING("[Account {:s}] Buddy {} is not present on the DHT, but P2P connected",
1890 getAccountID(),
1891 id);
1892 return;
1893 }
1894 state = PresenceState::DISCONNECTED;
1895 emitSignal<libjami::PresenceSignal::NewBuddyNotification>(getAccountID(),
1896 id,
1897 static_cast<int>(
1898 PresenceState::DISCONNECTED),
1899 "");
1900 }
1901}
1902
1903void
1904JamiAccount::doRegister_()
1905{
1906 if (registrationState_ != RegistrationState::TRYING) {
1907 JAMI_ERROR("[Account {}] Already registered", getAccountID());
1908 return;
1909 }
1910
1911 JAMI_DEBUG("[Account {}] Starting account…", getAccountID());
1912 const auto& conf = config();
1913
1914 try {
1915 if (not accountManager_ or not accountManager_->getInfo())
1916 throw std::runtime_error("No identity configured for this account.");
1917
1918 if (dht_->isRunning()) {
1919 JAMI_ERROR("[Account {}] DHT already running (stopping it first).", getAccountID());
1920 dht_->join();
1921 }
1922
1923 convModule()->clearPendingFetch();
1924
1925#if HAVE_RINGNS
1926 // Look for registered name
1927 accountManager_->lookupAddress(
1928 accountManager_->getInfo()->accountId,
1929 [w = weak()](const std::string& regName,
1930 const std::string& address,
1931 const NameDirectory::Response& response) {
1932 if (auto this_ = w.lock()) {
1933 if (response == NameDirectory::Response::found
1934 or response == NameDirectory::Response::notFound) {
1935 const auto& nameResult = response == NameDirectory::Response::found
1936 ? regName
1937 : "";
1938 if (this_->setRegisteredName(nameResult)) {
1939 this_->editConfig([&](JamiAccountConfig& config) {
1940 config.registeredName = nameResult;
1941 });
1942 emitSignal<libjami::ConfigurationSignal::VolatileDetailsChanged>(
1943 this_->accountID_, this_->getVolatileAccountDetails());
1944 }
1945 }
1946 }
1947 });
1948#endif
1949
1950 dht::DhtRunner::Config config {};
1951 config.dht_config.node_config.network = 0;
1952 config.dht_config.node_config.maintain_storage = false;
1953 config.dht_config.node_config.persist_path = (cachePath_ / "dhtstate").string();
1954 config.dht_config.id = id_;
1955 config.dht_config.cert_cache_all = true;
1956 config.push_node_id = getAccountID();
1957 config.push_token = conf.deviceKey;
1958 config.push_topic = conf.notificationTopic;
1959 config.push_platform = conf.platform;
1960 config.proxy_user_agent = jami::userAgent();
1961 config.threaded = true;
1962 config.peer_discovery = conf.dhtPeerDiscovery;
1963 config.peer_publish = conf.dhtPeerDiscovery;
1964 if (conf.proxyEnabled)
1965 config.proxy_server = proxyServerCached_;
1966
1967 if (not config.proxy_server.empty()) {
1968 JAMI_LOG("[Account {}] Using proxy server {}", getAccountID(), config.proxy_server);
1969 if (not config.push_token.empty()) {
1970 JAMI_LOG(
1971 "[Account {}] using push notifications with platform: {}, topic: {}, token: {}",
1972 getAccountID(),
1973 config.push_platform,
1974 config.push_topic,
1975 config.push_token);
1976 }
1977 }
1978
1979 // check if dht peer service is enabled
1980 if (conf.accountPeerDiscovery or conf.accountPublish) {
1981 peerDiscovery_ = std::make_shared<dht::PeerDiscovery>();
1982 if (conf.accountPeerDiscovery) {
1983 JAMI_LOG("[Account {}] Starting Jami account discovery…", getAccountID());
1984 startAccountDiscovery();
1985 }
1986 if (conf.accountPublish)
1987 startAccountPublish();
1988 }
1989 dht::DhtRunner::Context context {};
1990 context.peerDiscovery = peerDiscovery_;
1991 context.rng = std::make_unique<std::mt19937_64>(dht::crypto::getDerivedRandomEngine(rand));
1992
1993 auto dht_log_level = Manager::instance().dhtLogLevel;
1994 if (dht_log_level > 0) {
1995 context.logger = Logger::dhtLogger();
1996 }
1997 context.certificateStore = [&](const dht::InfoHash& pk_id) {
1998 std::vector<std::shared_ptr<dht::crypto::Certificate>> ret;
1999 if (auto cert = certStore().getCertificate(pk_id.toString()))
2000 ret.emplace_back(std::move(cert));
2001 JAMI_LOG("Query for local certificate store: {}: {} found.",
2002 pk_id.toString(),
2003 ret.size());
2004 return ret;
2005 };
2006
2007 context.statusChangedCallback = [this](dht::NodeStatus s4, dht::NodeStatus s6) {
2008 JAMI_LOG("[Account {}] DHT status: IPv4 {}; IPv6 {}",
2009 getAccountID(),
2010 dhtStatusStr(s4),
2011 dhtStatusStr(s6));
2012 RegistrationState state;
2013 auto newStatus = std::max(s4, s6);
2014 switch (newStatus) {
2015 case dht::NodeStatus::Connecting:
2016 state = RegistrationState::TRYING;
2017 break;
2018 case dht::NodeStatus::Connected:
2019 state = RegistrationState::REGISTERED;
2020 break;
2021 case dht::NodeStatus::Disconnected:
2022 state = RegistrationState::UNREGISTERED;
2023 break;
2024 default:
2025 state = RegistrationState::ERROR_GENERIC;
2026 break;
2027 }
2028
2029 setRegistrationState(state);
2030 };
2031 context.identityAnnouncedCb = [this](bool ok) {
2032 if (!ok)
2033 return;
2034 accountManager_->startSync(
2035 [this](const std::shared_ptr<dht::crypto::Certificate>& crt) {
2036 if (jami::Manager::instance().syncOnRegister) {
2037 if (!crt)
2038 return;
2039 auto deviceId = crt->getLongId().toString();
2040 if (accountManager_->getInfo()->deviceId == deviceId)
2041 return;
2042
2043 dht::ThreadPool::io().run([w = weak(), deviceId, crt] {
2044 auto shared = w.lock();
2045 if (!shared) return;
2046 std::unique_lock lk(shared->connManagerMtx_);
2047 shared->initConnectionManager();
2048 lk.unlock();
2049 std::shared_lock slk(shared->connManagerMtx_);
2050 // NOTE: connectionManager_ and channelHandlers_ get initialized at the
2051 // same time and are both protected by connManagerMtx_, so this check
2052 // ensures that the access to channelHandlers_ below is valid.
2053 if (!shared->connectionManager_)
2054 return;
2055 shared->requestMessageConnection(shared->getUsername(), crt->getLongId(), "sync");
2056 if (!shared->syncModule()->isConnected(crt->getLongId())) {
2057 shared->channelHandlers_[Uri::Scheme::SYNC]
2058 ->connect(crt->getLongId(),
2059 "",
2060 [](std::shared_ptr<dhtnet::ChannelSocket> socket,
2061 const DeviceId& deviceId) {});
2062 }
2063 });
2064 }
2065 },
2066 [this] {
2068 deviceAnnounced_ = true;
2069
2070 // Bootstrap at the end to avoid to be long to load.
2071 dht::ThreadPool::io().run([w = weak()] {
2072 if (auto shared = w.lock())
2073 shared->convModule()->bootstrap();
2074 });
2075 emitSignal<libjami::ConfigurationSignal::VolatileDetailsChanged>(
2076 accountID_, getVolatileAccountDetails());
2077 }
2078 },
2079 publishPresence_);
2080 };
2081
2082 dht_->run(dhtPortUsed(), config, std::move(context));
2083
2084 for (const auto& bootstrap : loadBootstrap())
2085 dht_->bootstrap(bootstrap);
2086
2087 dhtBoundPort_ = dht_->getBoundPort();
2088
2089 accountManager_->setDht(dht_);
2090
2091 std::unique_lock lkCM(connManagerMtx_);
2092 initConnectionManager();
2093 connectionManager_->onDhtConnected(*accountManager_->getInfo()->devicePk);
2094 connectionManager_->onICERequest([this](const DeviceId& deviceId) {
2095 std::promise<bool> accept;
2096 std::future<bool> fut = accept.get_future();
2097 accountManager_->findCertificate(
2098 deviceId, [this, &accept](const std::shared_ptr<dht::crypto::Certificate>& cert) {
2099 if (!cert) {
2100 accept.set_value(false);
2101 return;
2102 }
2103 dht::InfoHash peer_account_id;
2104 auto res = accountManager_->onPeerCertificate(cert,
2105 this->config().dhtPublicInCalls,
2106 peer_account_id);
2107 JAMI_LOG("[Account {}] [device {}] {} ICE request from {}",
2108 getAccountID(),
2109 cert->getLongId(),
2110 res ? "Accepting" : "Discarding",
2111 peer_account_id);
2112 accept.set_value(res);
2113 });
2114 fut.wait();
2115 auto result = fut.get();
2116 return result;
2117 });
2118 connectionManager_->onChannelRequest(
2119 [this](const std::shared_ptr<dht::crypto::Certificate>& cert, const std::string& name) {
2120 JAMI_LOG("[Account {}] [device {}] New channel requested: '{}'",
2121 getAccountID(),
2122 cert->getLongId(),
2123 name);
2124
2125 if (this->config().turnEnabled && turnCache_) {
2126 auto addr = turnCache_->getResolvedTurn();
2127 if (addr == std::nullopt) {
2128 // If TURN is enabled, but no TURN cached, there can be a temporary
2129 // resolution error to solve. Sometimes, a connectivity change is not
2130 // enough, so even if this case is really rare, it should be easy to avoid.
2131 turnCache_->refresh();
2132 }
2133 }
2134
2135 auto uri = Uri(name);
2136 std::shared_lock lk(connManagerMtx_);
2137 auto itHandler = channelHandlers_.find(uri.scheme());
2138 if (itHandler != channelHandlers_.end() && itHandler->second)
2139 return itHandler->second->onRequest(cert, name);
2140 return name == "sip";
2141 });
2142 connectionManager_->onConnectionReady([this](const DeviceId& deviceId,
2143 const std::string& name,
2144 std::shared_ptr<dhtnet::ChannelSocket> channel) {
2145 if (channel) {
2146 auto cert = channel->peerCertificate();
2147 if (!cert || !cert->issuer)
2148 return;
2149 auto peerId = cert->issuer->getId().toString();
2150 // A connection request can be sent just before member is banned and this must be ignored.
2151 if (accountManager()->getCertificateStatus(peerId)
2152 == dhtnet::tls::TrustStore::PermissionStatus::BANNED) {
2153 channel->shutdown();
2154 return;
2155 }
2156 if (name == "sip") {
2157 cacheSIPConnection(std::move(channel), peerId, deviceId);
2158 } else if (name.find("git://") == 0) {
2159 auto sep = name.find_last_of('/');
2160 auto conversationId = name.substr(sep + 1);
2161 auto remoteDevice = name.substr(6, sep - 6);
2162
2163 if (channel->isInitiator()) {
2164 // Check if wanted remote is our side (git://remoteDevice/conversationId)
2165 return;
2166 }
2167
2168 // Check if pull from banned device
2169 if (convModule()->isBanned(conversationId, remoteDevice)) {
2171 "[Account {:s}] [Conversation {}] Git server requested, but the "
2172 "device is unauthorized ({:s}) ",
2173 getAccountID(),
2174 conversationId,
2175 remoteDevice);
2176 channel->shutdown();
2177 return;
2178 }
2179
2180 auto sock = convModule()->gitSocket(deviceId.toString(), conversationId);
2181 if (sock == channel) {
2182 // The onConnectionReady is already used as client (for retrieving messages)
2183 // So it's not the server socket
2184 return;
2185 }
2186 JAMI_LOG(
2187 "[Account {:s}] [Conversation {}] [device {}] Git server requested",
2188 accountID_,
2189 conversationId,
2190 deviceId.toString());
2191 auto gs = std::make_unique<GitServer>(accountID_, conversationId, channel);
2192 syncCnt_.fetch_add(1);
2193 gs->setOnFetched([w = weak(), conversationId, deviceId](
2194 const std::string& commit) {
2195 dht::ThreadPool::computation().run([w, conversationId, deviceId, commit]() {
2196 if (auto shared = w.lock()) {
2197 shared->convModule()->setFetched(conversationId,
2198 deviceId.toString(),
2199 commit);
2200 if (shared->syncCnt_.fetch_sub(1) == 1) {
2201 emitSignal<libjami::ConversationSignal::ConversationCloned>(
2202 shared->getAccountID().c_str());
2203 }
2204 }
2205 });
2206 });
2207 const dht::Value::Id serverId = ValueIdDist()(rand);
2208 {
2209 std::lock_guard lk(gitServersMtx_);
2210 gitServers_[serverId] = std::move(gs);
2211 }
2212 channel->onShutdown([w = weak(), serverId]() {
2213 // Run on main thread to avoid to be in mxSock's eventLoop
2214 runOnMainThread([serverId, w]() {
2215 if (auto sthis = w.lock()) {
2216 std::lock_guard lk(sthis->gitServersMtx_);
2217 sthis->gitServers_.erase(serverId);
2218 }
2219 });
2220 });
2221 } else {
2222 // TODO move git://
2223 std::shared_lock lk(connManagerMtx_);
2224 auto uri = Uri(name);
2225 auto itHandler = channelHandlers_.find(uri.scheme());
2226 if (itHandler != channelHandlers_.end() && itHandler->second)
2227 itHandler->second->onReady(cert, name, std::move(channel));
2228 }
2229 }
2230 });
2231 lkCM.unlock();
2232
2233 if (!conf.managerUri.empty() && accountManager_) {
2234 dynamic_cast<ServerAccountManager*>(accountManager_.get())->onNeedsMigration([this]() {
2235 editConfig([&](JamiAccountConfig& conf) {
2236 conf.receipt.clear();
2237 conf.receiptSignature.clear();
2238 });
2239 Migration::setState(accountID_, Migration::State::INVALID);
2240 setRegistrationState(RegistrationState::ERROR_NEED_MIGRATION);
2241 });
2242 dynamic_cast<ServerAccountManager*>(accountManager_.get())
2243 ->syncBlueprintConfig([this](const std::map<std::string, std::string>& config) {
2244 editConfig([&](JamiAccountConfig& conf) { conf.fromMap(config); });
2245 emitSignal<libjami::ConfigurationSignal::AccountDetailsChanged>(
2246 getAccountID(), getAccountDetails());
2247 });
2248 }
2249
2250 std::lock_guard lock(buddyInfoMtx);
2251 for (auto& buddy : trackedBuddies_) {
2252 buddy.second.devices_cnt = 0;
2253 trackPresence(buddy.first, buddy.second);
2254 }
2255 } catch (const std::exception& e) {
2256 JAMI_ERR("Error registering DHT account: %s", e.what());
2257 setRegistrationState(RegistrationState::ERROR_GENERIC);
2258 }
2259}
2260
2261ConversationModule*
2262JamiAccount::convModule(bool noCreation)
2263{
2264 if (noCreation)
2265 return convModule_.get();
2266 if (!accountManager() || currentDeviceId() == "") {
2267 JAMI_ERROR("[Account {}] Calling convModule() with an uninitialized account",
2268 getAccountID());
2269 return nullptr;
2270 }
2271 std::unique_lock lock(configurationMutex_);
2272 std::lock_guard lk(moduleMtx_);
2273 if (!convModule_) {
2274 convModule_ = std::make_unique<ConversationModule>(
2275 shared(),
2276 accountManager_,
2277 [this](auto&& syncMsg) {
2278 dht::ThreadPool::computation().run([w = weak(), syncMsg] {
2279 if (auto shared = w.lock()) {
2280 auto& config = shared->config();
2281 // For JAMS account, we must update the server
2282 // for now, only sync with the JAMS server for changes to the conversation list
2283 if (!config.managerUri.empty() && !syncMsg)
2284 if (auto am = shared->accountManager())
2285 am->syncDevices();
2286 if (auto sm = shared->syncModule())
2287 sm->syncWithConnected(syncMsg);
2288 }
2289 });
2290 },
2291 [this](auto&& uri, auto&& device, auto&& msg, auto token = 0) {
2292 // No need to retrigger, sendTextMessage will call
2293 // messageEngine_.sendMessage, already retriggering on
2294 // main thread.
2295 auto deviceId = device ? device.toString() : "";
2296 return sendTextMessage(uri, deviceId, msg, token);
2297 },
2298 [this](const auto& convId, const auto& deviceId, auto cb, const auto& type) {
2299 dht::ThreadPool::io().run([w = weak(), convId, deviceId, cb = std::move(cb), type] {
2300 auto shared = w.lock();
2301 if (!shared)
2302 return;
2303 if (auto socket = shared->convModule()->gitSocket(deviceId, convId)) {
2304 if (!cb(socket))
2305 socket->shutdown();
2306 else
2307 cb({});
2308 return;
2309 }
2310 std::shared_lock lkCM(shared->connManagerMtx_);
2311 if (!shared->connectionManager_) {
2312 lkCM.unlock();
2313 cb({});
2314 return;
2315 }
2316
2317 shared->connectionManager_->connectDevice(
2318 DeviceId(deviceId),
2319 fmt::format("git://{}/{}", deviceId, convId),
2320 [w,
2321 cb = std::move(cb),
2322 convId](std::shared_ptr<dhtnet::ChannelSocket> socket, const DeviceId&) {
2323 dht::ThreadPool::io().run([w,
2324 cb = std::move(cb),
2325 socket = std::move(socket),
2326 convId] {
2327 if (socket) {
2328 socket->onShutdown([w, deviceId = socket->deviceId(), convId] {
2329 dht::ThreadPool::io().run([w, deviceId, convId] {
2330 if (auto shared = w.lock())
2331 shared->convModule()
2332 ->removeGitSocket(deviceId.toString(), convId);
2333 });
2334 });
2335 if (!cb(socket))
2336 socket->shutdown();
2337 } else
2338 cb({});
2339 });
2340 },
2341 false,
2342 false,
2343 type);
2344 });
2345 },
2346 [this](const auto& convId, const auto& deviceId, auto&& cb, const auto& connectionType) {
2347 dht::ThreadPool::io().run([w = weak(),
2348 convId,
2349 deviceId,
2350 cb = std::move(cb),
2351 connectionType] {
2352 auto shared = w.lock();
2353 if (!shared)
2354 return;
2355 auto cm = shared->convModule();
2356 std::shared_lock lkCM(shared->connManagerMtx_);
2357 if (!shared->connectionManager_ || !cm || cm->isBanned(convId, deviceId)) {
2358 asio::post(*Manager::instance().ioContext(), [cb = std::move(cb)] { cb({}); });
2359 return;
2360 }
2361 if (!shared->connectionManager_->isConnecting(DeviceId(deviceId),
2362 fmt::format("swarm://{}",
2363 convId))) {
2364 shared->connectionManager_->connectDevice(
2365 DeviceId(deviceId),
2366 fmt::format("swarm://{}", convId),
2367 [w, cb = std::move(cb)](std::shared_ptr<dhtnet::ChannelSocket> socket,
2368 const DeviceId& deviceId) {
2369 dht::ThreadPool::io().run([w,
2370 cb = std::move(cb),
2371 socket = std::move(socket),
2372 deviceId] {
2373 if (socket) {
2374 auto shared = w.lock();
2375 if (!shared)
2376 return;
2377 auto remoteCert = socket->peerCertificate();
2378 auto uri = remoteCert->issuer->getId().toString();
2379 if (shared->accountManager()->getCertificateStatus(uri)
2380 == dhtnet::tls::TrustStore::PermissionStatus::BANNED) {
2381 cb(nullptr);
2382 return;
2383 }
2384 shared->requestMessageConnection(uri, deviceId, "");
2385 }
2386 cb(socket);
2387 });
2388 });
2389 }
2390 });
2391 },
2392 [this](auto&& convId, auto&& from) {
2393 accountManager_
2394 ->findCertificate(dht::InfoHash(from),
2395 [this, from, convId](
2396 const std::shared_ptr<dht::crypto::Certificate>& cert) {
2397 auto info = accountManager_->getInfo();
2398 if (!cert || !info)
2399 return;
2400 info->contacts->onTrustRequest(dht::InfoHash(from),
2401 cert->getSharedPublicKey(),
2402 time(nullptr),
2403 false,
2404 convId,
2405 {});
2406 });
2407 },
2408 autoLoadConversations_);
2409 }
2410 return convModule_.get();
2411}
2412
2413SyncModule*
2414JamiAccount::syncModule()
2415{
2416 if (!accountManager() || currentDeviceId() == "") {
2417 JAMI_ERR() << "Calling syncModule() with an uninitialized account.";
2418 return nullptr;
2419 }
2420 std::lock_guard lk(moduleMtx_);
2421 if (!syncModule_)
2422 syncModule_ = std::make_unique<SyncModule>(weak());
2423 return syncModule_.get();
2424}
2425
2426void
2427JamiAccount::onTextMessage(const std::string& id,
2428 const std::string& from,
2429 const std::shared_ptr<dht::crypto::Certificate>& peerCert,
2430 const std::map<std::string, std::string>& payloads)
2431{
2432 try {
2433 const std::string fromUri {parseJamiUri(from)};
2434 SIPAccountBase::onTextMessage(id, fromUri, peerCert, payloads);
2435 } catch (...) {
2436 }
2437}
2438
2439void
2440JamiAccount::loadConversation(const std::string& convId)
2441{
2442 if (auto cm = convModule(true))
2443 cm->loadSingleConversation(convId);
2444}
2445
2446void
2447JamiAccount::doUnregister(bool forceShutdownConnections)
2448{
2449 std::unique_lock lock(configurationMutex_);
2450 if (registrationState_ >= RegistrationState::ERROR_GENERIC) {
2451 return;
2452 }
2453
2454 std::mutex mtx;
2455 std::condition_variable cv;
2456 bool shutdown_complete {false};
2457
2458 if (peerDiscovery_) {
2459 peerDiscovery_->stopPublish(PEER_DISCOVERY_JAMI_SERVICE);
2460 peerDiscovery_->stopDiscovery(PEER_DISCOVERY_JAMI_SERVICE);
2461 }
2462
2463 JAMI_WARN("[Account %s] Unregistering account %p", getAccountID().c_str(), this);
2464 dht_->shutdown(
2465 [&] {
2466 JAMI_WARN("[Account %s] DHT shutdown complete", getAccountID().c_str());
2467 std::lock_guard lock(mtx);
2468 shutdown_complete = true;
2469 cv.notify_all();
2470 },
2471 true);
2472
2473 {
2474 std::lock_guard lk(pendingCallsMutex_);
2475 pendingCalls_.clear();
2476 }
2477
2478 // Stop all current P2P connections if account is disabled
2479 // or if explicitly requested by the caller.
2480 // NOTE: Leaving the connections open is useful when changing an account's config.
2481 if (not isEnabled() || forceShutdownConnections)
2482 shutdownConnections();
2483
2484 // Release current UPnP mapping if any.
2485 if (upnpCtrl_ and dhtUpnpMapping_.isValid()) {
2486 upnpCtrl_->releaseMapping(dhtUpnpMapping_);
2487 }
2488
2489 {
2490 std::unique_lock lock(mtx);
2491 cv.wait(lock, [&] { return shutdown_complete; });
2492 }
2493 dht_->join();
2494 setRegistrationState(RegistrationState::UNREGISTERED);
2495
2496 lock.unlock();
2497
2498#ifdef ENABLE_PLUGIN
2499 jami::Manager::instance().getJamiPluginManager().getChatServicesManager().cleanChatSubjects(
2500 getAccountID());
2501#endif
2502}
2503
2504void
2505JamiAccount::setRegistrationState(RegistrationState state,
2506 int detail_code,
2507 const std::string& detail_str)
2508{
2509 if (registrationState_ != state) {
2510 if (state == RegistrationState::REGISTERED) {
2511 JAMI_WARNING("[Account {}] Connected", getAccountID());
2512 turnCache_->refresh();
2513 if (connectionManager_)
2514 connectionManager_->storeActiveIpAddress();
2515 } else if (state == RegistrationState::TRYING) {
2516 JAMI_WARNING("[Account {}] Connecting…", getAccountID());
2517 } else {
2518 deviceAnnounced_ = false;
2519 JAMI_WARNING("[Account {}] Disconnected", getAccountID());
2520 }
2521 }
2522 // Update registrationState_ & emit signals
2523 Account::setRegistrationState(state, detail_code, detail_str);
2524}
2525
2526void
2527JamiAccount::reloadContacts()
2528{
2529 accountManager_->reloadContacts();
2530}
2531
2532void
2533JamiAccount::connectivityChanged()
2534{
2535 JAMI_WARN("connectivityChanged");
2536 if (not isUsable()) {
2537 // nothing to do
2538 return;
2539 }
2540
2541 if (auto cm = convModule())
2542 cm->connectivityChanged();
2543 dht_->connectivityChanged();
2544 {
2545 std::shared_lock lkCM(connManagerMtx_);
2546 if (connectionManager_) {
2547 connectionManager_->connectivityChanged();
2548 // reset cache
2549 connectionManager_->setPublishedAddress({});
2550 }
2551 }
2552}
2553
2554bool
2555JamiAccount::findCertificate(
2556 const dht::InfoHash& h,
2557 std::function<void(const std::shared_ptr<dht::crypto::Certificate>&)>&& cb)
2558{
2559 if (accountManager_)
2560 return accountManager_->findCertificate(h, std::move(cb));
2561 return false;
2562}
2563
2564bool
2565JamiAccount::findCertificate(
2566 const dht::PkId& id, std::function<void(const std::shared_ptr<dht::crypto::Certificate>&)>&& cb)
2567{
2568 if (accountManager_)
2569 return accountManager_->findCertificate(id, std::move(cb));
2570 return false;
2571}
2572
2573bool
2574JamiAccount::findCertificate(const std::string& crt_id)
2575{
2576 if (accountManager_)
2577 return accountManager_->findCertificate(dht::InfoHash(crt_id));
2578 return false;
2579}
2580
2581bool
2582JamiAccount::setCertificateStatus(const std::string& cert_id,
2583 dhtnet::tls::TrustStore::PermissionStatus status)
2584{
2585 bool done = accountManager_ ? accountManager_->setCertificateStatus(cert_id, status) : false;
2586 if (done) {
2587 findCertificate(cert_id);
2588 emitSignal<libjami::ConfigurationSignal::CertificateStateChanged>(
2589 getAccountID(), cert_id, dhtnet::tls::TrustStore::statusToStr(status));
2590 }
2591 return done;
2592}
2593
2594bool
2595JamiAccount::setCertificateStatus(const std::shared_ptr<crypto::Certificate>& cert,
2596 dhtnet::tls::TrustStore::PermissionStatus status,
2597 bool local)
2598{
2599 bool done = accountManager_ ? accountManager_->setCertificateStatus(cert, status, local)
2600 : false;
2601 if (done) {
2602 findCertificate(cert->getId().toString());
2603 emitSignal<libjami::ConfigurationSignal::CertificateStateChanged>(
2604 getAccountID(), cert->getId().toString(), dhtnet::tls::TrustStore::statusToStr(status));
2605 }
2606 return done;
2607}
2608
2609std::vector<std::string>
2610JamiAccount::getCertificatesByStatus(dhtnet::tls::TrustStore::PermissionStatus status)
2611{
2612 if (accountManager_)
2613 return accountManager_->getCertificatesByStatus(status);
2614 return {};
2615}
2616
2617bool
2618JamiAccount::isMessageTreated(dht::Value::Id id)
2619{
2620 std::lock_guard lock(messageMutex_);
2621 return !treatedMessages_.add(id);
2622}
2623
2624bool
2625JamiAccount::sha3SumVerify() const
2626{
2627 return !noSha3sumVerification_;
2628}
2629
2630#ifdef LIBJAMI_TEST
2631void
2632JamiAccount::noSha3sumVerification(bool newValue)
2633{
2634 noSha3sumVerification_ = newValue;
2635}
2636#endif
2637
2638std::map<std::string, std::string>
2639JamiAccount::getKnownDevices() const
2640{
2641 std::lock_guard lock(configurationMutex_);
2642 if (not accountManager_ or not accountManager_->getInfo())
2643 return {};
2644 std::map<std::string, std::string> ids;
2645 for (const auto& d : accountManager_->getKnownDevices()) {
2646 auto id = d.first.toString();
2647 auto label = d.second.name.empty() ? id.substr(0, 8) : d.second.name;
2648 ids.emplace(std::move(id), std::move(label));
2649 }
2650 return ids;
2651}
2652
2653void
2654JamiAccount::loadCachedUrl(const std::string& url,
2655 const std::filesystem::path& cachePath,
2656 const std::chrono::seconds& cacheDuration,
2657 std::function<void(const dht::http::Response& response)> cb)
2658{
2659 dht::ThreadPool::io().run([cb, url, cachePath, cacheDuration, w = weak()]() {
2660 try {
2661 std::string data;
2662 {
2663 std::lock_guard lk(dhtnet::fileutils::getFileLock(cachePath));
2664 data = fileutils::loadCacheTextFile(cachePath, cacheDuration);
2665 }
2666 dht::http::Response ret;
2667 ret.body = std::move(data);
2668 ret.status_code = 200;
2669 cb(ret);
2670 } catch (const std::exception& e) {
2671 JAMI_LOG("Failed to load '{}' from '{}': {}", url, cachePath, e.what());
2672
2673 if (auto sthis = w.lock()) {
2674 auto req = std::make_shared<dht::http::Request>(
2675 *Manager::instance().ioContext(),
2676 url,
2677 [cb, cachePath, w](const dht::http::Response& response) {
2678 if (response.status_code == 200) {
2679 try {
2680 std::lock_guard lk(dhtnet::fileutils::getFileLock(cachePath));
2681 fileutils::saveFile(cachePath,
2682 (const uint8_t*) response.body.data(),
2683 response.body.size(),
2684 0600);
2685 JAMI_LOG("Cached result to '{}'", cachePath);
2686 } catch (const std::exception& ex) {
2687 JAMI_WARNING("Failed to save result to '{}': {}",
2688 cachePath,
2689 ex.what());
2690 }
2691 cb(response);
2692 } else {
2693 try {
2694 if (std::filesystem::exists(cachePath)) {
2695 JAMI_WARNING("Failed to download URL, using cached data");
2696 std::string data;
2697 {
2698 std::lock_guard lk(
2699 dhtnet::fileutils::getFileLock(cachePath));
2700 data = fileutils::loadTextFile(cachePath);
2701 }
2702 dht::http::Response ret;
2703 ret.body = std::move(data);
2704 ret.status_code = 200;
2705 cb(ret);
2706 } else
2707 throw std::runtime_error("No cached data");
2708 } catch (...) {
2709 cb(response);
2710 }
2711 }
2712 if (auto req = response.request.lock())
2713 if (auto sthis = w.lock())
2714 sthis->requests_.erase(req);
2715 });
2716 sthis->requests_.emplace(req);
2717 req->send();
2718 }
2719 }
2720 });
2721}
2722
2723void
2724JamiAccount::loadCachedProxyServer(std::function<void(const std::string& proxy)> cb)
2725{
2726 const auto& conf = config();
2727 if (conf.proxyEnabled and proxyServerCached_.empty()) {
2728 JAMI_DEBUG("[Account {:s}] Loading DHT proxy URL: {:s}", getAccountID(), conf.proxyListUrl);
2729 if (conf.proxyListUrl.empty() or not conf.proxyListEnabled) {
2730 cb(getDhtProxyServer(conf.proxyServer));
2731 } else {
2732 loadCachedUrl(conf.proxyListUrl,
2733 cachePath_ / "dhtproxylist",
2734 std::chrono::hours(24 * 3),
2735 [w = weak(), cb = std::move(cb)](const dht::http::Response& response) {
2736 if (auto sthis = w.lock()) {
2737 if (response.status_code == 200) {
2738 cb(sthis->getDhtProxyServer(response.body));
2739 } else {
2740 cb(sthis->getDhtProxyServer(sthis->config().proxyServer));
2741 }
2742 }
2743 });
2744 }
2745 } else {
2746 cb(proxyServerCached_);
2747 }
2748}
2749
2750std::string
2751JamiAccount::getDhtProxyServer(const std::string& serverList)
2752{
2753 if (proxyServerCached_.empty()) {
2754 std::vector<std::string> proxys;
2755 // Split the list of servers
2756 std::sregex_iterator begin = {serverList.begin(), serverList.end(), PROXY_REGEX}, end;
2757 for (auto it = begin; it != end; ++it) {
2758 auto& match = *it;
2759 if (match[5].matched and match[6].matched) {
2760 try {
2761 auto start = std::stoi(match[5]), end = std::stoi(match[6]);
2762 for (auto p = start; p <= end; p++)
2763 proxys.emplace_back(match[1].str() + match[2].str() + ":"
2764 + std::to_string(p));
2765 } catch (...) {
2766 JAMI_WARN("Malformed proxy, ignore it");
2767 continue;
2768 }
2769 } else {
2770 proxys.emplace_back(match[0].str());
2771 }
2772 }
2773 if (proxys.empty())
2774 return {};
2775 // Select one of the list as the current proxy.
2776 auto randIt = proxys.begin();
2777 std::advance(randIt,
2778 std::uniform_int_distribution<unsigned long>(0, proxys.size() - 1)(rand));
2779 proxyServerCached_ = *randIt;
2780 // Cache it!
2781 dhtnet::fileutils::check_dir(cachePath_, 0700);
2782 auto proxyCachePath = cachePath_ / "dhtproxy";
2783 std::ofstream file(proxyCachePath);
2784 JAMI_DEBUG("Cache DHT proxy server: {}", proxyServerCached_);
2785 Json::Value node(Json::objectValue);
2786 node[getProxyConfigKey()] = proxyServerCached_;
2787 if (file.is_open())
2788 file << node;
2789 else
2790 JAMI_WARNING("Unable to write into {}", proxyCachePath);
2791 }
2792 return proxyServerCached_;
2793}
2794
2796JamiAccount::matches(std::string_view userName, std::string_view server) const
2797{
2798 if (not accountManager_ or not accountManager_->getInfo())
2799 return MatchRank::NONE;
2800
2801 if (userName == accountManager_->getInfo()->accountId
2802 || server == accountManager_->getInfo()->accountId
2803 || userName == accountManager_->getInfo()->deviceId) {
2804 JAMI_LOG("Matching account ID in request with username {}", userName);
2805 return MatchRank::FULL;
2806 } else {
2807 return MatchRank::NONE;
2808 }
2809}
2810
2811std::string
2812JamiAccount::getFromUri() const
2813{
2814 const std::string uri = "<sip:" + accountManager_->getInfo()->accountId + "@ring.dht>";
2815 if (not config().displayName.empty())
2816 return "\"" + config().displayName + "\" " + uri;
2817 return uri;
2818}
2819
2820std::string
2821JamiAccount::getToUri(const std::string& to) const
2822{
2823 auto username = to;
2824 string_replace(username, "sip:", "");
2825 return fmt::format("<sips:{};transport=tls>", username);
2826}
2827
2828std::string
2829getDisplayed(const std::string& conversationId, const std::string& messageId)
2830{
2831 // implementing https://tools.ietf.org/rfc/rfc5438.txt
2832 return fmt::format(
2833 "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n"
2834 "<imdn><message-id>{}</message-id>\n"
2835 "{}"
2836 "<display-notification><status><displayed/></status></display-notification>\n"
2837 "</imdn>",
2838 messageId,
2839 conversationId.empty() ? "" : "<conversation>" + conversationId + "</conversation>");
2840}
2841
2842std::string
2843getPIDF(const std::string& note)
2844{
2845 // implementing https://datatracker.ietf.org/doc/html/rfc3863
2846 return fmt::format("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
2847 "<presence xmlns=\"urn:ietf:params:xml:ns:pidf\">\n"
2848 " <tuple>\n"
2849 " <status>\n"
2850 " <basic>{}</basic>\n"
2851 " </status>\n"
2852 " </tuple>\n"
2853 "</presence>",
2854 note);
2855}
2856
2857void
2858JamiAccount::setIsComposing(const std::string& conversationUri, bool isWriting)
2859{
2860 Uri uri(conversationUri);
2861 std::string conversationId = {};
2862 if (uri.scheme() == Uri::Scheme::SWARM) {
2863 conversationId = uri.authority();
2864 } else {
2865 return;
2866 }
2867
2868 if (auto cm = convModule(true)) {
2869 if (auto typer = cm->getTypers(conversationId)) {
2870 if (isWriting)
2871 typer->addTyper(getUsername(), true);
2872 else
2873 typer->removeTyper(getUsername(), true);
2874 }
2875 }
2876}
2877
2878bool
2879JamiAccount::setMessageDisplayed(const std::string& conversationUri,
2880 const std::string& messageId,
2881 int status)
2882{
2883 Uri uri(conversationUri);
2884 std::string conversationId = {};
2885 if (uri.scheme() == Uri::Scheme::SWARM)
2886 conversationId = uri.authority();
2887 auto sendMessage = status == (int) libjami::Account::MessageStates::DISPLAYED
2888 && isReadReceiptEnabled();
2889 if (!conversationId.empty())
2890 sendMessage &= convModule()->onMessageDisplayed(getUsername(), conversationId, messageId);
2891 if (sendMessage)
2892 sendInstantMessage(uri.authority(),
2893 {{MIME_TYPE_IMDN, getDisplayed(conversationId, messageId)}});
2894 return true;
2895}
2896
2897std::string
2898JamiAccount::getContactHeader(const std::shared_ptr<SipTransport>& sipTransport)
2899{
2900 if (sipTransport and sipTransport->get() != nullptr) {
2901 auto transport = sipTransport->get();
2902 auto* td = reinterpret_cast<tls::AbstractSIPTransport::TransportData*>(transport);
2903 auto address = td->self->getLocalAddress().toString(true);
2904 bool reliable = transport->flag & PJSIP_TRANSPORT_RELIABLE;
2905 return fmt::format("\"{}\" <sips:{}{}{};transport={}>",
2906 config().displayName,
2907 id_.second->getId().toString(),
2908 address.empty() ? "" : "@",
2909 address,
2910 reliable ? "tls" : "dtls");
2911 } else {
2912 JAMI_ERR("getContactHeader: no SIP transport provided");
2913 return fmt::format("\"{}\" <sips:{}@ring.dht>",
2914 config().displayName,
2915 id_.second->getId().toString());
2916 }
2917}
2918
2919void
2920JamiAccount::addContact(const std::string& uri, bool confirmed)
2921{
2922 dht::InfoHash h(uri);
2923 if (not h) {
2924 JAMI_ERROR("addContact: invalid contact URI");
2925 return;
2926 }
2927 auto conversation = convModule()->getOneToOneConversation(uri);
2928 if (!confirmed && conversation.empty())
2929 conversation = convModule()->startConversation(ConversationMode::ONE_TO_ONE, h);
2930 std::unique_lock lock(configurationMutex_);
2931 if (accountManager_)
2932 accountManager_->addContact(h, confirmed, conversation);
2933 else
2934 JAMI_WARNING("[Account {}] addContact: account not loaded", getAccountID());
2935}
2936
2937void
2938JamiAccount::removeContact(const std::string& uri, bool ban)
2939{
2940 std::lock_guard lock(configurationMutex_);
2941 if (accountManager_)
2942 accountManager_->removeContact(uri, ban);
2943 else
2944 JAMI_WARNING("[Account {}] removeContact: account not loaded", getAccountID());
2945}
2946
2947std::map<std::string, std::string>
2948JamiAccount::getContactDetails(const std::string& uri) const
2949{
2950 std::lock_guard lock(configurationMutex_);
2951 return accountManager_ ? accountManager_->getContactDetails(uri)
2952 : std::map<std::string, std::string> {};
2953}
2954
2955std::optional<Contact>
2956JamiAccount::getContactInfo(const std::string& uri) const
2957{
2958 std::lock_guard lock(configurationMutex_);
2959 return accountManager_ ? accountManager_->getContactInfo(uri) : std::nullopt;
2960}
2961
2962std::vector<std::map<std::string, std::string>>
2963JamiAccount::getContacts(bool includeRemoved) const
2964{
2965 std::lock_guard lock(configurationMutex_);
2966 if (not accountManager_)
2967 return {};
2968 return accountManager_->getContacts(includeRemoved);
2969}
2970
2971/* trust requests */
2972
2973std::vector<std::map<std::string, std::string>>
2974JamiAccount::getTrustRequests() const
2975{
2976 std::lock_guard lock(configurationMutex_);
2977 return accountManager_ ? accountManager_->getTrustRequests()
2978 : std::vector<std::map<std::string, std::string>> {};
2979}
2980
2981bool
2982JamiAccount::acceptTrustRequest(const std::string& from, bool includeConversation)
2983{
2984 dht::InfoHash h(from);
2985 if (not h) {
2986 JAMI_ERROR("addContact: invalid contact URI");
2987 return false;
2988 }
2989 std::unique_lock lock(configurationMutex_);
2990 if (accountManager_) {
2991 if (!accountManager_->acceptTrustRequest(from, includeConversation)) {
2992 // Note: unused for swarm
2993 // Typically the case where the trust request doesn't exists, only incoming DHT messages
2994 return accountManager_->addContact(h, true);
2995 }
2996 return true;
2997 }
2998 JAMI_WARNING("[Account {}] acceptTrustRequest: account not loaded", getAccountID());
2999 return false;
3000}
3001
3002bool
3003JamiAccount::discardTrustRequest(const std::string& from)
3004{
3005 // Remove 1:1 generated conv requests
3006 auto requests = getTrustRequests();
3007 for (const auto& req : requests) {
3008 if (req.at(libjami::Account::TrustRequest::FROM) == from) {
3009 convModule()->declineConversationRequest(
3011 }
3012 }
3013
3014 // Remove trust request
3015 std::lock_guard lock(configurationMutex_);
3016 if (accountManager_)
3017 return accountManager_->discardTrustRequest(from);
3018 JAMI_WARNING("[Account {:s}] discardTrustRequest: account not loaded", getAccountID());
3019 return false;
3020}
3021
3022void
3023JamiAccount::declineConversationRequest(const std::string& conversationId)
3024{
3025 auto peerId = convModule()->peerFromConversationRequest(conversationId);
3026 convModule()->declineConversationRequest(conversationId);
3027 if (!peerId.empty()) {
3028 std::lock_guard lock(configurationMutex_);
3029 if (auto info = accountManager_->getInfo()) {
3030 // Verify if we have a trust request with this peer + convId
3031 auto req = info->contacts->getTrustRequest(dht::InfoHash(peerId));
3032 if (req.find(libjami::Account::TrustRequest::CONVERSATIONID) != req.end()
3033 && req.at(libjami::Account::TrustRequest::CONVERSATIONID) == conversationId) {
3034 accountManager_->discardTrustRequest(peerId);
3035 JAMI_DEBUG("[Account {:s}] Declined trust request with {:s}",
3036 getAccountID(),
3037 peerId);
3038 }
3039 }
3040 }
3041}
3042
3043void
3044JamiAccount::sendTrustRequest(const std::string& to, const std::vector<uint8_t>& payload)
3045{
3046 dht::InfoHash h(to);
3047 if (not h) {
3048 JAMI_ERROR("addContact: invalid contact URI");
3049 return;
3050 }
3051 // Here we cache payload sent by the client
3052 auto requestPath = cachePath_ / "requests";
3053 dhtnet::fileutils::recursive_mkdir(requestPath, 0700);
3054 auto cachedFile = requestPath / to;
3055 std::ofstream req(cachedFile, std::ios::trunc | std::ios::binary);
3056 if (!req.is_open()) {
3057 JAMI_ERROR("Unable to write data to {}", cachedFile);
3058 return;
3059 }
3060
3061 if (not payload.empty()) {
3062 req.write(reinterpret_cast<const char*>(payload.data()), payload.size());
3063 }
3064
3065 if (payload.size() >= 64000) {
3066 JAMI_WARN() << "Trust request is too big. Remove payload";
3067 }
3068
3069 auto conversation = convModule()->getOneToOneConversation(to);
3070 if (conversation.empty())
3071 conversation = convModule()->startConversation(ConversationMode::ONE_TO_ONE, h);
3072 if (not conversation.empty()) {
3073 std::lock_guard lock(configurationMutex_);
3074 if (accountManager_)
3075 accountManager_->sendTrustRequest(to,
3076 conversation,
3077 payload.size() >= 64000 ? std::vector<uint8_t> {}
3078 : payload);
3079 else
3080 JAMI_WARNING("[Account {}] sendTrustRequest: account not loaded", getAccountID());
3081 } else
3082 JAMI_WARNING("[Account {}] sendTrustRequest: account not loaded", getAccountID());
3083}
3084
3085void
3086JamiAccount::forEachDevice(const dht::InfoHash& to,
3087 std::function<void(const std::shared_ptr<dht::crypto::PublicKey>&)>&& op,
3088 std::function<void(bool)>&& end)
3089{
3090 accountManager_->forEachDevice(to, std::move(op), std::move(end));
3091}
3092
3093uint64_t
3094JamiAccount::sendTextMessage(const std::string& to,
3095 const std::string& deviceId,
3096 const std::map<std::string, std::string>& payloads,
3097 uint64_t refreshToken,
3098 bool onlyConnected)
3099{
3100 Uri uri(to);
3101 if (uri.scheme() == Uri::Scheme::SWARM) {
3102 sendInstantMessage(uri.authority(), payloads);
3103 return 0;
3104 }
3105
3106 std::string toUri;
3107 try {
3108 toUri = parseJamiUri(to);
3109 } catch (...) {
3110 JAMI_ERROR("Failed to send a text message due to an invalid URI {}", to);
3111 return 0;
3112 }
3113 if (payloads.size() != 1) {
3114 JAMI_ERROR("Multi-part im is not supported yet by JamiAccount");
3115 return 0;
3116 }
3117 return SIPAccountBase::sendTextMessage(toUri, deviceId, payloads, refreshToken, onlyConnected);
3118}
3119
3120void
3121JamiAccount::sendMessage(const std::string& to,
3122 const std::string& deviceId,
3123 const std::map<std::string, std::string>& payloads,
3124 uint64_t token,
3125 bool retryOnTimeout,
3126 bool onlyConnected)
3127{
3128 std::string toUri;
3129 try {
3130 toUri = parseJamiUri(to);
3131 } catch (...) {
3132 JAMI_ERROR("[Account {}] Failed to send a text message due to an invalid URI {}",
3133 getAccountID(),
3134 to);
3135 if (!onlyConnected)
3136 messageEngine_.onMessageSent(to, token, false, deviceId);
3137 return;
3138 }
3139 if (payloads.size() != 1) {
3140 JAMI_ERROR("Multi-part im is not supported");
3141 if (!onlyConnected)
3142 messageEngine_.onMessageSent(toUri, token, false, deviceId);
3143 return;
3144 }
3145
3146 // Use the Message channel if available
3147 std::shared_lock clk(connManagerMtx_);
3148 auto* handler = static_cast<MessageChannelHandler*>(
3149 channelHandlers_[Uri::Scheme::MESSAGE].get());
3150 if (!handler) {
3151 clk.unlock();
3152 if (!onlyConnected)
3153 messageEngine_.onMessageSent(to, token, false, deviceId);
3154 return;
3155 }
3156
3160 class SendMessageContext {
3161 public:
3162 using OnComplete = std::function<void(bool, bool)>;
3163 SendMessageContext(OnComplete onComplete) : onComplete(std::move(onComplete)) {}
3165 bool add(const DeviceId& device) {
3166 std::lock_guard lk(mtx);
3167 return devices.insert(device).second;
3168 }
3170 void start() {
3171 std::unique_lock lk(mtx);
3172 started = true;
3173 checkComplete(lk);
3174 }
3176 bool complete(const DeviceId& device, bool success) {
3177 std::unique_lock lk(mtx);
3178 if (devices.erase(device) == 0)
3179 return false;
3180 ++completeCount;
3181 if (success)
3182 ++successCount;
3183 checkComplete(lk);
3184 return true;
3185 }
3186 bool empty() const {
3187 std::lock_guard lk(mtx);
3188 return devices.empty();
3189 }
3190 bool pending(const DeviceId& device) const {
3191 std::lock_guard lk(mtx);
3192 return devices.find(device) != devices.end();
3193 }
3194 private:
3195 mutable std::mutex mtx;
3196 OnComplete onComplete;
3197 std::set<DeviceId> devices;
3198 unsigned completeCount = 0;
3199 unsigned successCount = 0;
3200 bool started {false};
3201
3202 void checkComplete(std::unique_lock<std::mutex>& lk) {
3203 if (started && (devices.empty() || successCount)) {
3204 if (onComplete) {
3205 auto cb = std::move(onComplete);
3206 onComplete = {};
3207 lk.unlock();
3208 cb(successCount != 0, completeCount != 0);
3209 }
3210 }
3211 }
3212 };
3213 auto devices = std::make_shared<SendMessageContext>([
3214 w= weak(),
3215 to,
3216 token,
3217 deviceId,
3218 onlyConnected,
3219 retryOnTimeout
3220 ](bool success, bool sent) {
3221 if (auto acc = w.lock())
3222 acc->onMessageSent(to, token, deviceId, success, onlyConnected, sent && retryOnTimeout);
3223 });
3224
3225 struct TextMessageCtx {
3226 std::weak_ptr<JamiAccount> acc;
3227 std::string peerId;
3228 DeviceId deviceId;
3229 std::shared_ptr<SendMessageContext> devices;
3230 std::shared_ptr<dhtnet::ChannelSocket> sipChannel;
3231 };
3232
3233 auto completed = [w = weak(), to, devices](const DeviceId& device, std::shared_ptr<dhtnet::ChannelSocket> conn, bool success) {
3234 if (!success)
3235 if (auto acc = w.lock()) {
3236 std::shared_lock clk(acc->connManagerMtx_);
3237 if (auto* handler = static_cast<MessageChannelHandler*>(
3238 acc->channelHandlers_[Uri::Scheme::MESSAGE].get())) {
3239 handler->closeChannel(to, device, conn);
3240 }
3241 }
3242 devices->complete(device, success);
3243 };
3244
3245 const auto& payload = *payloads.begin();
3246 auto msg = std::make_shared<MessageChannelHandler::Message>();
3247 msg->id = token;
3248 msg->t = payload.first;
3249 msg->c = payload.second;
3250 auto device = deviceId.empty() ? DeviceId() : DeviceId(deviceId);
3251 if (deviceId.empty()) {
3252 auto conns = handler->getChannels(toUri);
3253 clk.unlock();
3254 for (const auto& conn : conns) {
3255 auto connDevice = conn->deviceId();
3256 if (!devices->add(connDevice))
3257 continue;
3258 dht::ThreadPool::io().run([completed, connDevice, conn, msg] {
3259 completed(connDevice, conn, MessageChannelHandler::sendMessage(conn, *msg));
3260 });
3261 }
3262 } else {
3263 if (auto conn = handler->getChannel(toUri, device)) {
3264 clk.unlock();
3265 devices->add(device);
3266 dht::ThreadPool::io().run([completed, device, conn, msg] {
3267 completed(device, conn, MessageChannelHandler::sendMessage(conn, *msg));
3268 });
3269 devices->start();
3270 return;
3271 }
3272 }
3273 if (clk)
3274 clk.unlock();
3275
3276 std::unique_lock lk(sipConnsMtx_);
3277 for (auto& [key, value] : sipConns_) {
3278 if (key.first != to or value.empty())
3279 continue;
3280 if (!deviceId.empty() && key.second != device)
3281 continue;
3282 if (!devices->add(key.second))
3283 continue;
3284
3285 auto& conn = value.back();
3286 auto& channel = conn.channel;
3287
3288 // Set input token into callback
3289 auto ctx = std::make_unique<TextMessageCtx>();
3290 ctx->acc = weak();
3291 ctx->peerId = to;
3292 ctx->deviceId = key.second;
3293 ctx->devices = devices;
3294 ctx->sipChannel = channel;
3295
3296 try {
3297 auto res = sendSIPMessage(conn,
3298 to,
3299 ctx.release(),
3300 token,
3301 payloads,
3302 [](void* token, pjsip_event* event) {
3303 if (auto c = std::shared_ptr<TextMessageCtx>{(TextMessageCtx*) token})
3304 runOnMainThread([
3305 c = std::move(c),
3306 code = event->body.tsx_state.tsx->status_code
3307 ] {
3308 bool success = code == PJSIP_SC_OK;
3309 // Note: This can be called from PJSIP's eventloop while
3310 // sipConnsMtx_ is locked. So we should retrigger the shutdown.
3311 if (!success) {
3312 JAMI_WARNING("Timeout when send a message, close current connection");
3313 if (auto acc = c->acc.lock())
3314 acc->shutdownSIPConnection(c->sipChannel,
3315 c->peerId,
3316 c->deviceId);
3317 }
3318 c->devices->complete(c->deviceId, code == PJSIP_SC_OK);
3319 });
3320 });
3321 if (!res) {
3322 devices->complete(key.second, false);
3323 continue;
3324 }
3325 } catch (const std::runtime_error& ex) {
3326 JAMI_WARNING("{}", ex.what());
3327 // Remove connection in incorrect state
3328 shutdownSIPConnection(channel, to, key.second);
3329 devices->complete(key.second, false);
3330 continue;
3331 }
3332
3333 if (key.second == device) {
3334 devices->start();
3335 return;
3336 }
3337 }
3338 lk.unlock();
3339 devices->start();
3340
3341 if (onlyConnected)
3342 return;
3343 // We are unable to send the message directly, try connecting
3344
3345 // Get conversation id, which will be used by the iOS notification extension
3346 // to load the conversation.
3347 auto extractIdFromJson = [](const std::string& jsonData) -> std::string {
3348 Json::Value parsed;
3349 if (json::parse(jsonData, parsed)) {
3350 auto value = parsed.get("id", Json::nullValue);
3351 if (value && value.isString()) {
3352 return value.asString();
3353 }
3354 } else {
3355 JAMI_WARNING("Unable to parse jsonData to get conversation ID");
3356 }
3357 return "";
3358 };
3359
3360 // get request type
3361 auto payload_type = msg->t;
3362 if (payload_type == MIME_TYPE_GIT) {
3363 std::string id = extractIdFromJson(msg->c);
3364 if (!id.empty()) {
3365 payload_type += "/" + id;
3366 }
3367 }
3368
3369 if (deviceId.empty()) {
3370 auto toH = dht::InfoHash(toUri);
3371 // Find listening devices for this account
3372 accountManager_->forEachDevice(toH,
3373 [this,
3374 to,
3375 devices,
3376 payload_type,
3377 currentDevice = DeviceId(currentDeviceId())](
3378 const std::shared_ptr<dht::crypto::PublicKey>& dev) {
3379 // Test if already sent
3380 auto deviceId = dev->getLongId();
3381 if (deviceId == currentDevice
3382 || devices->pending(deviceId)) {
3383 return;
3384 }
3385
3386 // Else, ask for a channel to send the message
3387 requestMessageConnection(to, deviceId, payload_type);
3388 });
3389 } else {
3390 requestMessageConnection(to, device, payload_type);
3391 }
3392}
3393
3394void
3395JamiAccount::onMessageSent(const std::string& to, uint64_t id, const std::string& deviceId, bool success, bool onlyConnected, bool retry)
3396{
3397 if (!onlyConnected)
3398 messageEngine_.onMessageSent(to,
3399 id,
3400 success,
3401 deviceId);
3402
3403 if (!success) {
3404 if (retry)
3405 messageEngine_.onPeerOnline(to, deviceId);
3406 }
3407}
3408
3409dhtnet::IceTransportOptions
3410JamiAccount::getIceOptions() const
3411{
3412 return connectionManager_->getIceOptions();
3413}
3414
3415void
3416JamiAccount::getIceOptions(std::function<void(dhtnet::IceTransportOptions&&)> cb) const
3417{
3418 return connectionManager_->getIceOptions(std::move(cb));
3419}
3420
3421dhtnet::IpAddr
3422JamiAccount::getPublishedIpAddress(uint16_t family) const
3423{
3424 return connectionManager_->getPublishedIpAddress(family);
3425}
3426
3427bool
3428JamiAccount::setPushNotificationToken(const std::string& token)
3429{
3430 if (SIPAccountBase::setPushNotificationToken(token)) {
3431 JAMI_WARNING("[Account {:s}] setPushNotificationToken: {:s}", getAccountID(), token);
3432 if (dht_)
3433 dht_->setPushNotificationToken(token);
3434 return true;
3435 }
3436 return false;
3437}
3438
3439bool
3440JamiAccount::setPushNotificationTopic(const std::string& topic)
3441{
3442 if (SIPAccountBase::setPushNotificationTopic(topic)) {
3443 if (dht_)
3444 dht_->setPushNotificationTopic(topic);
3445 return true;
3446 }
3447 return false;
3448}
3449
3450bool
3451JamiAccount::setPushNotificationConfig(const std::map<std::string, std::string>& data)
3452{
3453 if (SIPAccountBase::setPushNotificationConfig(data)) {
3454 if (dht_) {
3455 dht_->setPushNotificationPlatform(config_->platform);
3456 dht_->setPushNotificationTopic(config_->notificationTopic);
3457 dht_->setPushNotificationToken(config_->deviceKey);
3458 }
3459 return true;
3460 }
3461 return false;
3462}
3463
3467void
3468JamiAccount::pushNotificationReceived(const std::string& from,
3469 const std::map<std::string, std::string>& data)
3470{
3471 auto ret_future = dht_->pushNotificationReceived(data);
3472 dht::ThreadPool::computation().run([id = getAccountID(), ret_future = ret_future.share()] {
3473 JAMI_WARNING("[Account {:s}] pushNotificationReceived: {}", id, (uint8_t) ret_future.get());
3474 });
3475}
3476
3477std::string
3478JamiAccount::getUserUri() const
3479{
3480#ifdef HAVE_RINGNS
3481 if (not registeredName_.empty())
3482 return JAMI_URI_PREFIX + registeredName_;
3483#endif
3484 return JAMI_URI_PREFIX + config().username;
3485}
3486
3487std::vector<libjami::Message>
3488JamiAccount::getLastMessages(const uint64_t& base_timestamp)
3489{
3490 return SIPAccountBase::getLastMessages(base_timestamp);
3491}
3492
3493void
3494JamiAccount::startAccountPublish()
3495{
3496 AccountPeerInfo info_pub;
3497 info_pub.accountId = dht::InfoHash(accountManager_->getInfo()->accountId);
3498 info_pub.displayName = config().displayName;
3499 peerDiscovery_->startPublish<AccountPeerInfo>(PEER_DISCOVERY_JAMI_SERVICE, info_pub);
3500}
3501
3502void
3503JamiAccount::startAccountDiscovery()
3504{
3505 auto id = dht::InfoHash(accountManager_->getInfo()->accountId);
3506 peerDiscovery_->startDiscovery<AccountPeerInfo>(
3507 PEER_DISCOVERY_JAMI_SERVICE, [this, id](AccountPeerInfo&& v, dht::SockAddr&&) {
3508 std::lock_guard lc(discoveryMapMtx_);
3509 // Make sure that account itself will not be recorded
3510 if (v.accountId != id) {
3511 // Create or find the old one
3512 auto& dp = discoveredPeers_[v.accountId];
3513 dp.displayName = v.displayName;
3514 discoveredPeerMap_[v.accountId.toString()] = v.displayName;
3515 if (dp.cleanupTask) {
3516 dp.cleanupTask->cancel();
3517 } else {
3518 // Avoid repeat reception of same peer
3519 JAMI_LOG("Account discovered: {}: {}", v.displayName, v.accountId.to_c_str());
3520 // Send Added Peer and corrsponding accoundID
3521 emitSignal<libjami::PresenceSignal::NearbyPeerNotification>(getAccountID(),
3522 v.accountId
3523 .toString(),
3524 0,
3525 v.displayName);
3526 }
3527 dp.cleanupTask = Manager::instance().scheduler().scheduleIn(
3528 [w = weak(), p = v.accountId, a = v.displayName] {
3529 if (auto this_ = w.lock()) {
3530 {
3531 std::lock_guard lc(this_->discoveryMapMtx_);
3532 this_->discoveredPeers_.erase(p);
3533 this_->discoveredPeerMap_.erase(p.toString());
3534 }
3535 // Send deleted peer
3536 emitSignal<libjami::PresenceSignal::NearbyPeerNotification>(
3537 this_->getAccountID(), p.toString(), 1, a);
3538 }
3539 JAMI_INFO("Account removed from discovery list: %s", a.c_str());
3540 },
3542 }
3543 });
3544}
3545
3546std::map<std::string, std::string>
3547JamiAccount::getNearbyPeers() const
3548{
3549 return discoveredPeerMap_;
3550}
3551
3552void
3553JamiAccount::sendProfileToPeers()
3554{
3555 if (!connectionManager_)
3556 return;
3557 std::set<std::string> peers;
3558 const auto& accountUri = accountManager_->getInfo()->accountId;
3559 // TODO: avoid using getConnectionList
3560 for (const auto& connection : connectionManager_->getConnectionList()) {
3561 const auto& device = connection.at("device");
3562 const auto& peer = connection.at("peer");
3563 if (!peers.emplace(peer).second)
3564 continue;
3565 if (peer == accountUri) {
3566 sendProfile("", accountUri, device);
3567 continue;
3568 }
3569 const auto& conversationId = convModule()->getOneToOneConversation(peer);
3570 if (!conversationId.empty()) {
3571 sendProfile(conversationId, peer, device);
3572 }
3573 }
3574}
3575
3576void
3577JamiAccount::updateProfile(const std::string& displayName,
3578 const std::string& avatar,
3579 const std::string& fileType,
3580 int32_t flag)
3581{
3582 // if the fileType is empty then only the display name will be upated
3583
3584 const auto& accountUri = accountManager_->getInfo()->accountId;
3585 const auto& path = profilePath();
3586 const auto& profiles = idPath_ / "profiles";
3587
3588 try {
3589 if (!std::filesystem::exists(profiles)) {
3590 std::filesystem::create_directories(profiles);
3591 }
3592 } catch (const std::exception& e) {
3593 JAMI_ERROR("Failed to create profiles directory: {}", e.what());
3594 return;
3595 }
3596
3597 const auto& vCardPath = profiles / fmt::format("{}.vcf", base64::encode(accountUri));
3598
3599 auto profile = getProfileVcard();
3600 if (profile.empty()) {
3601 profile = vCard::utils::initVcard();
3602 }
3603
3604 profile["FN"] = displayName;
3605 editConfig([&](JamiAccountConfig& config) { config.displayName = displayName; });
3606 emitSignal<libjami::ConfigurationSignal::AccountDetailsChanged>(getAccountID(),
3607 getAccountDetails());
3608
3609 if (!fileType.empty()) {
3610 const std::string& key = "PHOTO;ENCODING=BASE64;TYPE=" + fileType;
3611 if (flag == 0) {
3612 vCard::utils::removeByKey(profile, "PHOTO");
3613 const auto& avatarPath = std::filesystem::path(avatar);
3614 if (std::filesystem::exists(avatarPath)) {
3615 try {
3616 profile[key] = base64::encode(fileutils::loadFile(avatarPath));
3617 } catch (const std::exception& e) {
3618 JAMI_ERROR("Failed to load avatar: {}", e.what());
3619 }
3620 }
3621 } else if (flag == 1) {
3622 vCard::utils::removeByKey(profile, "PHOTO");
3623 profile[key] = avatar;
3624 }
3625 }
3626 if (flag == 2) {
3627 vCard::utils::removeByKey(profile, "PHOTO");
3628 }
3629 try {
3630 std::filesystem::path tmpPath = vCardPath.string() + ".tmp";
3631 std::ofstream file(tmpPath);
3632 if (file.is_open()) {
3633 file << vCard::utils::toString(profile);
3634 file.close();
3635 std::filesystem::rename(tmpPath, vCardPath);
3636 fileutils::createFileLink(path, vCardPath, true);
3637 emitSignal<libjami::ConfigurationSignal::ProfileReceived>(getAccountID(),
3638 accountUri,
3639 path.string());
3640 sendProfileToPeers();
3641 } else {
3642 JAMI_ERROR("Unable to open file for writing: {}", tmpPath.string());
3643 }
3644 } catch (const std::exception& e) {
3645 JAMI_ERROR("Error writing profile: {}", e.what());
3646 }
3647}
3648
3649void
3650JamiAccount::setActiveCodecs(const std::vector<unsigned>& list)
3651{
3652 Account::setActiveCodecs(list);
3653 if (!hasActiveCodec(MEDIA_AUDIO))
3654 setCodecActive(AV_CODEC_ID_OPUS);
3655 if (!hasActiveCodec(MEDIA_VIDEO)) {
3656 setCodecActive(AV_CODEC_ID_HEVC);
3657 setCodecActive(AV_CODEC_ID_H264);
3658 setCodecActive(AV_CODEC_ID_VP8);
3659 }
3660 config_->activeCodecs = getActiveCodecs(MEDIA_ALL);
3661}
3662
3663void
3664JamiAccount::sendInstantMessage(const std::string& convId,
3665 const std::map<std::string, std::string>& msg)
3666{
3667 auto members = convModule()->getConversationMembers(convId);
3668 if (convId.empty() && members.empty()) {
3669 // TODO remove, it's for old API for contacts
3670 sendTextMessage(convId, "", msg);
3671 return;
3672 }
3673 for (const auto& m : members) {
3674 const auto& uri = m.at("uri");
3675 auto token = std::uniform_int_distribution<uint64_t> {1, JAMI_ID_MAX_VAL}(rand);
3676 // Announce to all members that a new message is sent
3677 sendMessage(uri, "", msg, token, false, true);
3678 }
3679}
3680
3681bool
3682JamiAccount::handleMessage(const std::shared_ptr<dht::crypto::Certificate>& cert, const std::string& from, const std::pair<std::string, std::string>& m)
3683{
3684 if (not cert or not cert->issuer)
3685 return true; // stop processing message
3686
3687 if (cert->issuer->getId().to_view() != from) {
3688 JAMI_WARNING("[Account {}] [device {}] handleMessage: invalid author {}", getAccountID(), cert->issuer->getId().to_view(), from);
3689 return true;
3690 }
3691 if (m.first == MIME_TYPE_GIT) {
3692 Json::Value json;
3693 if (!json::parse(m.second, json)) {
3694 return true;
3695 }
3696
3697 // fetchNewCommits will do heavy stuff like fetching, avoid to block SIP socket
3698 dht::ThreadPool::io().run([
3699 w = weak(),
3700 from,
3701 deviceId = json["deviceId"].asString(),
3702 id = json["id"].asString(),
3703 commit = json["commit"].asString()
3704 ] {
3705 if (auto shared = w.lock()) {
3706 if (auto cm = shared->convModule())
3707 cm->fetchNewCommits(from, deviceId, id, commit);
3708 }
3709 });
3710 return true;
3711 } else if (m.first == MIME_TYPE_INVITE) {
3712 convModule()->onNeedConversationRequest(from, m.second);
3713 return true;
3714 } else if (m.first == MIME_TYPE_INVITE_JSON) {
3715 Json::Value json;
3716 if (!json::parse(m.second, json)) {
3717 return true;
3718 }
3719 convModule()->onConversationRequest(from, json);
3720 return true;
3721 } else if (m.first == MIME_TYPE_IM_COMPOSING) {
3722 try {
3723 static const std::regex COMPOSING_REGEX("<state>\\s*(\\w+)\\s*<\\/state>");
3724 std::smatch matched_pattern;
3725 std::regex_search(m.second, matched_pattern, COMPOSING_REGEX);
3726 bool isComposing {false};
3727 if (matched_pattern.ready() && !matched_pattern.empty() && matched_pattern[1].matched) {
3728 isComposing = matched_pattern[1] == "active";
3729 }
3730 static const std::regex CONVID_REGEX("<conversation>\\s*(\\w+)\\s*<\\/conversation>");
3731 std::regex_search(m.second, matched_pattern, CONVID_REGEX);
3732 std::string conversationId = "";
3733 if (matched_pattern.ready() && !matched_pattern.empty() && matched_pattern[1].matched) {
3734 conversationId = matched_pattern[1];
3735 }
3736 if (!conversationId.empty()) {
3737 if (auto cm = convModule(true)) {
3738 if (auto typer = cm->getTypers(conversationId)) {
3739 if (isComposing)
3740 typer->addTyper(from);
3741 else
3742 typer->removeTyper(from);
3743 }
3744 }
3745 }
3746 return true;
3747 } catch (const std::exception& e) {
3748 JAMI_WARNING("Error parsing composing state: {}", e.what());
3749 }
3750 } else if (m.first == MIME_TYPE_IMDN) {
3751 try {
3752 static const std::regex IMDN_MSG_ID_REGEX("<message-id>\\s*(\\w+)\\s*<\\/message-id>");
3753 std::smatch matched_pattern;
3754
3755 std::regex_search(m.second, matched_pattern, IMDN_MSG_ID_REGEX);
3756 std::string messageId;
3757 if (matched_pattern.ready() && !matched_pattern.empty() && matched_pattern[1].matched) {
3758 messageId = matched_pattern[1];
3759 } else {
3760 JAMI_WARNING("Message displayed: unable to parse message ID");
3761 return true;
3762 }
3763
3764 static const std::regex STATUS_REGEX("<status>\\s*<(\\w+)\\/>\\s*<\\/status>");
3765 std::regex_search(m.second, matched_pattern, STATUS_REGEX);
3766 bool isDisplayed {false};
3767 if (matched_pattern.ready() && !matched_pattern.empty() && matched_pattern[1].matched) {
3768 isDisplayed = matched_pattern[1] == "displayed";
3769 } else {
3770 JAMI_WARNING("Message displayed: unable to parse status");
3771 return true;
3772 }
3773
3774 static const std::regex CONVID_REGEX("<conversation>\\s*(\\w+)\\s*<\\/conversation>");
3775 std::regex_search(m.second, matched_pattern, CONVID_REGEX);
3776 std::string conversationId = "";
3777 if (matched_pattern.ready() && !matched_pattern.empty() && matched_pattern[1].matched) {
3778 conversationId = matched_pattern[1];
3779 }
3780
3781 if (!isReadReceiptEnabled())
3782 return true;
3783 if (isDisplayed) {
3784 if (convModule()->onMessageDisplayed(from, conversationId, messageId)) {
3785 JAMI_DEBUG("[message {}] Displayed by peer", messageId);
3786 emitSignal<libjami::ConfigurationSignal::AccountMessageStatusChanged>(
3787 accountID_,
3788 conversationId,
3789 from,
3790 messageId,
3792 }
3793 }
3794 return true;
3795 } catch (const std::exception& e) {
3796 JAMI_ERROR("Error parsing display notification: {}", e.what());
3797 }
3798 } else if (m.first == MIME_TYPE_PIDF) {
3799 std::smatch matched_pattern;
3800 static const std::regex BASIC_REGEX("<basic>([\\w\\s]+)<\\/basic>");
3801 std::regex_search(m.second, matched_pattern, BASIC_REGEX);
3802 std::string customStatus {};
3803 if (matched_pattern.ready() && !matched_pattern.empty() && matched_pattern[1].matched) {
3804 customStatus = matched_pattern[1];
3805 emitSignal<libjami::PresenceSignal::NewBuddyNotification>(getAccountID(),
3806 from,
3807 static_cast<int>(
3808 PresenceState::CONNECTED),
3809 customStatus);
3810 } else {
3811 JAMI_WARNING("Presence: unable to parse status");
3812 }
3813 return true;
3814 }
3815
3816 return false;
3817}
3818
3819void
3820JamiAccount::callConnectionClosed(const DeviceId& deviceId, bool eraseDummy)
3821{
3822 std::function<void(const DeviceId&, bool)> cb;
3823 {
3824 std::lock_guard lk(onConnectionClosedMtx_);
3825 auto it = onConnectionClosed_.find(deviceId);
3826 if (it != onConnectionClosed_.end()) {
3827 if (eraseDummy) {
3828 cb = std::move(it->second);
3829 onConnectionClosed_.erase(it);
3830 } else {
3831 // In this case a new subcall is created and the callback
3832 // will be re-called once with eraseDummy = true
3833 cb = it->second;
3834 }
3835 }
3836 }
3837 dht::ThreadPool::io().run(
3838 [w = weak(), cb = std::move(cb), id = deviceId, erase = std::move(eraseDummy)] {
3839 if (auto acc = w.lock()) {
3840 if (cb)
3841 cb(id, erase);
3842 }
3843 });
3844}
3845
3846void
3847JamiAccount::requestMessageConnection(const std::string& peerId,
3848 const DeviceId& deviceId,
3849 const std::string& connectionType)
3850{
3851 std::shared_lock lk(connManagerMtx_);
3852 auto* handler = static_cast<MessageChannelHandler*>(
3853 channelHandlers_[Uri::Scheme::MESSAGE].get());
3854 if (!handler)
3855 return;
3856 if (deviceId) {
3857 if (auto connected = handler->getChannel(peerId, deviceId)) {
3858 return;
3859 }
3860 } else {
3861 auto connected = handler->getChannels(peerId);
3862 if (!connected.empty()) {
3863 return;
3864 }
3865 }
3866 handler->connect(
3867 deviceId,
3868 "",
3869 [w = weak(), peerId](std::shared_ptr<dhtnet::ChannelSocket> socket,
3870 const DeviceId& deviceId) {
3871 if (socket)
3872 dht::ThreadPool::io().run([w, peerId, deviceId] {
3873 if (auto acc = w.lock()) {
3874 acc->messageEngine_.onPeerOnline(peerId);
3875 acc->messageEngine_.onPeerOnline(peerId, deviceId.toString(), true);
3876 if (!acc->presenceNote_.empty()) {
3877 // If a presence note is set, send it to this device.
3878 auto token = std::uniform_int_distribution<uint64_t> {1, JAMI_ID_MAX_VAL}(acc->rand);
3879 std::map<std::string, std::string> msg = {
3880 {MIME_TYPE_PIDF, getPIDF(acc->presenceNote_)}
3881 };
3882 acc->sendMessage(peerId, deviceId.toString(), msg, token, false, true);
3883 }
3884 acc->convModule()->syncConversations(peerId, deviceId.toString());
3885 }
3886 });
3887 },
3888 connectionType);
3889}
3890
3891void
3892JamiAccount::requestSIPConnection(const std::string& peerId,
3893 const DeviceId& deviceId,
3894 const std::string& connectionType,
3895 bool forceNewConnection,
3896 const std::shared_ptr<SIPCall>& pc)
3897{
3898 if (peerId == getUsername()) {
3899 if (!syncModule()->isConnected(deviceId))
3900 channelHandlers_[Uri::Scheme::SYNC]
3901 ->connect(deviceId,
3902 "",
3903 [](std::shared_ptr<dhtnet::ChannelSocket> socket,
3904 const DeviceId& deviceId) {});
3905 }
3906
3907 JAMI_LOG("[Account {}] Request SIP connection to peer {} on device {}",
3908 getAccountID(),
3909 peerId,
3910 deviceId);
3911
3912 // If a connection already exists or is in progress, no need to do this
3913 std::lock_guard lk(sipConnsMtx_);
3914 auto id = std::make_pair(peerId, deviceId);
3915
3916 if (sipConns_.find(id) != sipConns_.end()) {
3917 JAMI_LOG("[Account {}] A SIP connection with {} already exists", getAccountID(), deviceId);
3918 return;
3919 }
3920 // If not present, create it
3921 std::shared_lock lkCM(connManagerMtx_);
3922 if (!connectionManager_)
3923 return;
3924 // Note, Even if we send 50 "sip" request, the connectionManager_ will only use one socket.
3925 // however, this will still ask for multiple channels, so only ask
3926 // if there is no pending request
3927 if (!forceNewConnection && connectionManager_->isConnecting(deviceId, "sip")) {
3928 JAMI_LOG("[Account {}] Already connecting to {}", getAccountID(), deviceId);
3929 return;
3930 }
3931 JAMI_LOG("[Account {}] Ask {} for a new SIP channel", getAccountID(), deviceId);
3932 connectionManager_->connectDevice(
3933 deviceId,
3934 "sip",
3935 [w = weak(),
3936 id = std::move(id),
3937 pc = std::move(pc)](std::shared_ptr<dhtnet::ChannelSocket> socket, const DeviceId&) {
3938 if (socket)
3939 return;
3940 auto shared = w.lock();
3941 if (!shared)
3942 return;
3943 // If this is triggered, this means that the
3944 // connectDevice didn't get any response from the DHT.
3945 // Stop searching pending call.
3946 shared->callConnectionClosed(id.second, true);
3947 if (pc)
3948 pc->onFailure();
3949 },
3950 false,
3951 forceNewConnection,
3952 connectionType);
3953}
3954
3955bool
3956JamiAccount::isConnectedWith(const DeviceId& deviceId) const
3957{
3958 std::shared_lock lkCM(connManagerMtx_);
3959 if (connectionManager_)
3960 return connectionManager_->isConnected(deviceId);
3961 return false;
3962}
3963
3964void
3965JamiAccount::sendPresenceNote(const std::string& note)
3966{
3967 if (auto info = accountManager_->getInfo()) {
3968 if (!info || !info->contacts)
3969 return;
3970 presenceNote_ = note;
3971 auto contacts = info->contacts->getContacts();
3972 std::vector<std::pair<std::string, DeviceId>> keys;
3973 {
3974 std::shared_lock lkCM(connManagerMtx_);
3975 auto* handler = static_cast<MessageChannelHandler*>(
3976 channelHandlers_[Uri::Scheme::MESSAGE].get());
3977 if (!handler)
3978 return;
3979 for (const auto& contact : contacts) {
3980 auto peerId = contact.first.toString();
3981 auto channels = handler->getChannels(peerId);
3982 for (const auto& channel : channels) {
3983 keys.emplace_back(peerId, channel->deviceId());
3984 }
3985 }
3986 }
3987 auto token = std::uniform_int_distribution<uint64_t> {1, JAMI_ID_MAX_VAL}(rand);
3988 std::map<std::string, std::string> msg = {{MIME_TYPE_PIDF, getPIDF(presenceNote_)}};
3989 for (auto& key : keys) {
3990 sendMessage(key.first, key.second.toString(), msg, token, false, true);
3991 }
3992 }
3993}
3994
3995void
3996JamiAccount::sendProfile(const std::string& convId,
3997 const std::string& peerUri,
3998 const std::string& deviceId)
3999{
4000 auto accProfilePath = profilePath();
4001 if (not std::filesystem::is_regular_file(accProfilePath))
4002 return;
4003 auto currentSha3 = fileutils::sha3File(accProfilePath);
4004 // VCard sync for peerUri
4005 if (not needToSendProfile(peerUri, deviceId, currentSha3)) {
4006 JAMI_DEBUG("[Account {}] [device {}] Peer {} already got an up-to-date vCard", getAccountID(), deviceId, peerUri);
4007 return;
4008 }
4009 // We need a new channel
4010 transferFile(convId,
4011 accProfilePath.string(),
4012 deviceId,
4013 "profile.vcf",
4014 "",
4015 0,
4016 0,
4017 currentSha3,
4018 fileutils::lastWriteTimeInSeconds(accProfilePath),
4019 [accId = getAccountID(), peerUri, deviceId]() {
4020 // Mark the VCard as sent
4021 auto sendDir = fileutils::get_cache_dir() / accId / "vcard" / peerUri;
4022 auto path = sendDir / deviceId;
4023 dhtnet::fileutils::recursive_mkdir(sendDir);
4024 std::lock_guard lock(dhtnet::fileutils::getFileLock(path));
4025 if (std::filesystem::is_regular_file(path))
4026 return;
4027 std::ofstream p(path);
4028 });
4029}
4030
4031bool
4032JamiAccount::needToSendProfile(const std::string& peerUri,
4033 const std::string& deviceId,
4034 const std::string& sha3Sum)
4035{
4036 std::string previousSha3 {};
4037 auto vCardPath = cachePath_ / "vcard";
4038 auto sha3Path = vCardPath / "sha3";
4039 dhtnet::fileutils::check_dir(vCardPath, 0700);
4040 try {
4041 previousSha3 = fileutils::loadTextFile(sha3Path);
4042 } catch (...) {
4043 fileutils::saveFile(sha3Path, (const uint8_t*) sha3Sum.data(), sha3Sum.size(), 0600);
4044 return true;
4045 }
4046 if (sha3Sum != previousSha3) {
4047 // Incorrect sha3 stored. Update it
4048 dhtnet::fileutils::removeAll(vCardPath, true);
4049 dhtnet::fileutils::check_dir(vCardPath, 0700);
4050 fileutils::saveFile(sha3Path, (const uint8_t*) sha3Sum.data(), sha3Sum.size(), 0600);
4051 return true;
4052 }
4053 auto peerPath = vCardPath / peerUri;
4054 dhtnet::fileutils::recursive_mkdir(peerPath);
4055 return not std::filesystem::is_regular_file(peerPath / deviceId);
4056}
4057
4058bool
4059JamiAccount::sendSIPMessage(SipConnection& conn,
4060 const std::string& to,
4061 void* ctx,
4062 uint64_t token,
4063 const std::map<std::string, std::string>& data,
4064 pjsip_endpt_send_callback cb)
4065{
4066 auto transport = conn.transport;
4067 auto channel = conn.channel;
4068 if (!channel)
4069 throw std::runtime_error(
4070 "A SIP transport exists without Channel, this is a bug. Please report");
4071 auto remote_address = channel->getRemoteAddress();
4072 if (!remote_address)
4073 return false;
4074
4075 // Build SIP Message
4076 // "deviceID@IP"
4077 auto toURI = getToUri(fmt::format("{}@{}", to, remote_address.toString(true)));
4078 std::string from = getFromUri();
4079
4080 // Build SIP message
4081 constexpr pjsip_method msg_method = {PJSIP_OTHER_METHOD,
4082 sip_utils::CONST_PJ_STR(sip_utils::SIP_METHODS::MESSAGE)};
4083 pj_str_t pjFrom = sip_utils::CONST_PJ_STR(from);
4084 pj_str_t pjTo = sip_utils::CONST_PJ_STR(toURI);
4085
4086 // Create request.
4087 pjsip_tx_data* tdata = nullptr;
4088 pj_status_t status = pjsip_endpt_create_request(link_.getEndpoint(),
4089 &msg_method,
4090 &pjTo,
4091 &pjFrom,
4092 &pjTo,
4093 nullptr,
4094 nullptr,
4095 -1,
4096 nullptr,
4097 &tdata);
4098 if (status != PJ_SUCCESS) {
4099 JAMI_ERROR("Unable to create request: {}", sip_utils::sip_strerror(status));
4100 return false;
4101 }
4102
4103 // Add Date Header.
4104 pj_str_t date_str;
4105 constexpr auto key = sip_utils::CONST_PJ_STR("Date");
4106 pjsip_hdr* hdr;
4107 auto time = std::time(nullptr);
4108 auto date = std::ctime(&time);
4109 // the erase-remove idiom for a cstring, removes _all_ new lines with in date
4110 *std::remove(date, date + strlen(date), '\n') = '\0';
4111
4112 // Add Header
4113 hdr = reinterpret_cast<pjsip_hdr*>(
4114 pjsip_date_hdr_create(tdata->pool, &key, pj_cstr(&date_str, date)));
4115 pjsip_msg_add_hdr(tdata->msg, hdr);
4116
4117 // https://tools.ietf.org/html/rfc5438#section-6.3
4118 auto token_str = to_hex_string(token);
4119 auto pjMessageId = sip_utils::CONST_PJ_STR(token_str);
4120 hdr = reinterpret_cast<pjsip_hdr*>(
4121 pjsip_generic_string_hdr_create(tdata->pool, &STR_MESSAGE_ID, &pjMessageId));
4122 pjsip_msg_add_hdr(tdata->msg, hdr);
4123
4124 // Add user-agent header
4125 sip_utils::addUserAgentHeader(getUserAgentName(), tdata);
4126
4127 // Init tdata
4128 const pjsip_tpselector tp_sel = SIPVoIPLink::getTransportSelector(transport->get());
4129 status = pjsip_tx_data_set_transport(tdata, &tp_sel);
4130 if (status != PJ_SUCCESS) {
4131 JAMI_ERROR("Unable to create request: {}", sip_utils::sip_strerror(status));
4132 return false;
4133 }
4134 im::fillPJSIPMessageBody(*tdata, data);
4135
4136 // Because pjsip_endpt_send_request can take quite some time, move it in a io thread to avoid to block
4137 dht::ThreadPool::io().run([w = weak(), tdata, ctx, cb = std::move(cb)] {
4138 auto shared = w.lock();
4139 if (!shared)
4140 return;
4141 auto status = pjsip_endpt_send_request(shared->link_.getEndpoint(), tdata, -1, ctx, cb);
4142 if (status != PJ_SUCCESS)
4143 JAMI_ERROR("Unable to send request: {}", sip_utils::sip_strerror(status));
4144 });
4145 return true;
4146}
4147
4148void
4149JamiAccount::clearProfileCache(const std::string& peerUri)
4150{
4151 std::error_code ec;
4152 std::filesystem::remove_all(cachePath_ / "vcard" / peerUri, ec);
4153}
4154
4155std::filesystem::path
4156JamiAccount::profilePath() const
4157{
4158 return idPath_ / "profile.vcf";
4159}
4160
4161void
4162JamiAccount::cacheSIPConnection(std::shared_ptr<dhtnet::ChannelSocket>&& socket,
4163 const std::string& peerId,
4164 const DeviceId& deviceId)
4165{
4166 std::unique_lock lk(sipConnsMtx_);
4167 // Verify that the connection is not already cached
4168 SipConnectionKey key(peerId, deviceId);
4169 auto& connections = sipConns_[key];
4170 auto conn = std::find_if(connections.begin(), connections.end(), [&](const auto& v) {
4171 return v.channel == socket;
4172 });
4173 if (conn != connections.end()) {
4174 JAMI_WARNING("[Account {}] Channel socket already cached with this peer", getAccountID());
4175 return;
4176 }
4177
4178 // Convert to SIP transport
4179 auto onShutdown = [w = weak(), peerId, key, socket]() {
4180 dht::ThreadPool::io().run([w = std::move(w), peerId, key, socket] {
4181 auto shared = w.lock();
4182 if (!shared)
4183 return;
4184 shared->shutdownSIPConnection(socket, key.first, key.second);
4185 // The connection can be closed during the SIP initialization, so
4186 // if this happens, the request should be re-sent to ask for a new
4187 // SIP channel to make the call pass through
4188 shared->callConnectionClosed(key.second, false);
4189 });
4190 };
4191 auto sip_tr = link_.sipTransportBroker->getChanneledTransport(shared(),
4192 socket,
4193 std::move(onShutdown));
4194 if (!sip_tr) {
4195 JAMI_ERROR("No channeled transport found");
4196 return;
4197 }
4198 // Store the connection
4199 connections.emplace_back(SipConnection {sip_tr, socket});
4200 JAMI_WARNING("[Account {:s}] [device {}] New SIP channel opened", getAccountID(), deviceId);
4201 lk.unlock();
4202
4203 // Retry messages
4204 messageEngine_.onPeerOnline(peerId);
4205 messageEngine_.onPeerOnline(peerId, deviceId.toString(), true);
4206
4207 // Connect pending calls
4208 forEachPendingCall(deviceId, [&](const auto& pc) {
4209 if (pc->getConnectionState() != Call::ConnectionState::TRYING
4210 and pc->getConnectionState() != Call::ConnectionState::PROGRESSING)
4211 return;
4212 pc->setSipTransport(sip_tr, getContactHeader(sip_tr));
4213 pc->setState(Call::ConnectionState::PROGRESSING);
4214 if (auto remote_address = socket->getRemoteAddress()) {
4215 try {
4216 onConnectedOutgoingCall(pc, peerId, remote_address);
4217 } catch (const VoipLinkException&) {
4218 // In this case, the main scenario is that SIPStartCall failed because
4219 // the ICE is dead and the TLS session didn't send any packet on that dead
4220 // link (connectivity change, killed by the os, etc)
4221 // Here, we don't need to do anything, the TLS will fail and will delete
4222 // the cached transport
4223 }
4224 }
4225 });
4226}
4227
4228void
4229JamiAccount::shutdownSIPConnection(const std::shared_ptr<dhtnet::ChannelSocket>& channel,
4230 const std::string& peerId,
4231 const DeviceId& deviceId)
4232{
4233 std::unique_lock lk(sipConnsMtx_);
4234 SipConnectionKey key(peerId, deviceId);
4235 auto it = sipConns_.find(key);
4236 if (it != sipConns_.end()) {
4237 auto& conns = it->second;
4238 conns.erase(std::remove_if(conns.begin(),
4239 conns.end(),
4240 [&](auto v) { return v.channel == channel; }),
4241 conns.end());
4242 if (conns.empty()) {
4243 sipConns_.erase(it);
4244 }
4245 }
4246 lk.unlock();
4247 // Shutdown after removal to let the callbacks do stuff if needed
4248 if (channel)
4249 channel->shutdown();
4250}
4251
4252std::string_view
4253JamiAccount::currentDeviceId() const
4254{
4255 if (!accountManager_ or not accountManager_->getInfo())
4256 return {};
4257 return accountManager_->getInfo()->deviceId;
4258}
4259
4260std::shared_ptr<TransferManager>
4261JamiAccount::dataTransfer(const std::string& id)
4262{
4263 if (id.empty())
4264 return nonSwarmTransferManager_;
4265 return convModule()->dataTransfer(id);
4266}
4267
4268void
4269JamiAccount::monitor()
4270{
4271 JAMI_DEBUG("[Account {:s}] Monitor connections", getAccountID());
4272 JAMI_DEBUG("[Account {:s}] Using proxy: {:s}", getAccountID(), proxyServerCached_);
4273
4274 if (auto cm = convModule())
4275 cm->monitor();
4276 std::shared_lock lkCM(connManagerMtx_);
4277 if (connectionManager_)
4278 connectionManager_->monitor();
4279}
4280
4281std::vector<std::map<std::string, std::string>>
4282JamiAccount::getConnectionList(const std::string& conversationId)
4283{
4284 std::shared_lock lkCM(connManagerMtx_);
4285 if (connectionManager_ && conversationId.empty()) {
4286 return connectionManager_->getConnectionList();
4287 } else if (connectionManager_ && convModule_) {
4288 std::vector<std::map<std::string, std::string>> connectionList;
4289 if (auto conv = convModule_->getConversation(conversationId)) {
4290 for (const auto& deviceId : conv->getDeviceIdList()) {
4291 auto connections = connectionManager_->getConnectionList(deviceId);
4292 connectionList.reserve(connectionList.size() + connections.size());
4293 std::move(connections.begin(),
4294 connections.end(),
4295 std::back_inserter(connectionList));
4296 }
4297 }
4298 return connectionList;
4299 } else {
4300 return {};
4301 }
4302}
4303
4304std::vector<std::map<std::string, std::string>>
4305JamiAccount::getChannelList(const std::string& connectionId)
4306{
4307 std::shared_lock lkCM(connManagerMtx_);
4308 if (!connectionManager_)
4309 return {};
4310 return connectionManager_->getChannelList(connectionId);
4311}
4312
4313void
4314JamiAccount::sendFile(const std::string& conversationId,
4315 const std::filesystem::path& path,
4316 const std::string& name,
4317 const std::string& replyTo)
4318{
4319 if (!std::filesystem::is_regular_file(path)) {
4320 JAMI_ERROR("Invalid filename '{}'", path);
4321 return;
4322 }
4323 // NOTE: this sendMessage is in a computation thread because
4324 // sha3sum can take quite some time to computer if the user decide
4325 // to send a big file
4326 dht::ThreadPool::computation().run([w = weak(), conversationId, path, name, replyTo]() {
4327 if (auto shared = w.lock()) {
4328 Json::Value value;
4329 auto tid = jami::generateUID(shared->rand);
4330 value["tid"] = std::to_string(tid);
4331 value["displayName"] = name.empty() ? path.filename().string() : name;
4332 value["totalSize"] = std::to_string(fileutils::size(path));
4333 value["sha3sum"] = fileutils::sha3File(path);
4334 value["type"] = "application/data-transfer+json";
4335
4336 shared->convModule()->sendMessage(
4337 conversationId,
4338 std::move(value),
4339 replyTo,
4340 true,
4341 [accId = shared->getAccountID(), conversationId, tid, path](
4342 const std::string& commitId) {
4343 // Create a symlink to answer to re-ask
4344 auto filelinkPath = fileutils::get_data_dir() / accId / "conversation_data"
4345 / conversationId / (commitId + "_" + std::to_string(tid));
4346 filelinkPath += path.extension();
4347 if (path != filelinkPath && !std::filesystem::is_symlink(filelinkPath)) {
4348 if (!fileutils::createFileLink(filelinkPath, path, true)) {
4349 JAMI_WARNING(
4350 "Unable to create symlink for file transfer {} - {}. Copy file",
4351 filelinkPath,
4352 path);
4353 std::error_code ec;
4354 auto success = std::filesystem::copy_file(path, filelinkPath, ec);
4355 if (ec || !success) {
4356 JAMI_ERROR("Unable to copy file for file transfer {} - {}",
4357 filelinkPath,
4358 path);
4359 // Signal to notify clients that the operation failed.
4360 // The fileId field sends the filePath.
4361 // libjami::DataTransferEventCode::unsupported (2) is unused elsewhere.
4362 emitSignal<libjami::DataTransferSignal::DataTransferEvent>(
4363 accId,
4364 conversationId,
4365 commitId,
4366 path.u8string(),
4367 uint32_t(libjami::DataTransferEventCode::invalid));
4368 } else {
4369 // Signal to notify clients that the file is copied and can be
4370 // safely deleted. The fileId field sends the filePath.
4371 // libjami::DataTransferEventCode::created (1) is unused elsewhere.
4372 emitSignal<libjami::DataTransferSignal::DataTransferEvent>(
4373 accId,
4374 conversationId,
4375 commitId,
4376 path.u8string(),
4377 uint32_t(libjami::DataTransferEventCode::created));
4378 }
4379 } else {
4380 emitSignal<libjami::DataTransferSignal::DataTransferEvent>(
4381 accId,
4382 conversationId,
4383 commitId,
4384 path.u8string(),
4385 uint32_t(libjami::DataTransferEventCode::created));
4386 }
4387 }
4388 });
4389 }
4390 });
4391}
4392
4393void
4394JamiAccount::transferFile(const std::string& conversationId,
4395 const std::string& path,
4396 const std::string& deviceId,
4397 const std::string& fileId,
4398 const std::string& interactionId,
4399 size_t start,
4400 size_t end,
4401 const std::string& sha3Sum,
4402 uint64_t lastWriteTime,
4403 std::function<void()> onFinished)
4404{
4405 std::string modified;
4406 if (lastWriteTime != 0) {
4407 modified = fmt::format("&modified={}", lastWriteTime);
4408 }
4409 auto fid = fileId == "profile.vcf" ? fmt::format("profile.vcf?sha3={}{}", sha3Sum, modified)
4410 : fileId;
4411 auto channelName = conversationId.empty() ? fmt::format("{}profile.vcf?sha3={}{}",
4412 DATA_TRANSFER_SCHEME,
4413 sha3Sum,
4414 modified)
4415 : fmt::format("{}{}/{}/{}",
4416 DATA_TRANSFER_SCHEME,
4417 conversationId,
4418 currentDeviceId(),
4419 fid);
4420 std::shared_lock lkCM(connManagerMtx_);
4421 if (!connectionManager_)
4422 return;
4423 connectionManager_
4424 ->connectDevice(DeviceId(deviceId),
4425 channelName,
4426 [this,
4427 conversationId,
4428 path = std::move(path),
4429 fileId,
4430 interactionId,
4431 start,
4432 end,
4433 onFinished = std::move(
4434 onFinished)](std::shared_ptr<dhtnet::ChannelSocket> socket,
4435 const DeviceId&) {
4436 if (!socket)
4437 return;
4438 dht::ThreadPool::io().run([w = weak(),
4439 path = std::move(path),
4440 socket = std::move(socket),
4441 conversationId = std::move(conversationId),
4442 fileId,
4443 interactionId,
4444 start,
4445 end,
4446 onFinished = std::move(onFinished)] {
4447 if (auto shared = w.lock())
4448 if (auto dt = shared->dataTransfer(conversationId))
4449 dt->transferFile(socket,
4450 fileId,
4451 interactionId,
4452 path,
4453 start,
4454 end,
4455 std::move(onFinished));
4456 });
4457 });
4458}
4459
4460void
4461JamiAccount::askForFileChannel(const std::string& conversationId,
4462 const std::string& deviceId,
4463 const std::string& interactionId,
4464 const std::string& fileId,
4465 size_t start,
4466 size_t end)
4467{
4468 auto tryDevice = [=](const auto& did) {
4469 std::shared_lock lkCM(connManagerMtx_);
4470 if (!connectionManager_)
4471 return;
4472
4473 auto channelName = fmt::format("{}{}/{}/{}",
4474 DATA_TRANSFER_SCHEME,
4475 conversationId,
4476 currentDeviceId(),
4477 fileId);
4478 if (start != 0 || end != 0) {
4479 channelName += fmt::format("?start={}&end={}", start, end);
4480 }
4481 // We can avoid to negotiate new sessions, as the file notif
4482 // probably came from an online device or last connected device.
4483 connectionManager_->connectDevice(
4484 did,
4485 channelName,
4486 [w = weak(),
4487 conversationId,
4488 fileId,
4489 interactionId,
4490 start](std::shared_ptr<dhtnet::ChannelSocket> channel, const DeviceId&) {
4491 if (!channel)
4492 return;
4493 dht::ThreadPool::io().run([w, conversationId, channel, fileId, interactionId, start] {
4494 auto shared = w.lock();
4495 if (!shared)
4496 return;
4497 auto dt = shared->dataTransfer(conversationId);
4498 if (!dt)
4499 return;
4500 if (interactionId.empty())
4501 dt->onIncomingProfile(channel);
4502 else
4503 dt->onIncomingFileTransfer(fileId, channel, start);
4504 });
4505 },
4506 false);
4507 };
4508
4509 if (!deviceId.empty()) {
4510 // Only ask for device
4511 tryDevice(DeviceId(deviceId));
4512 } else {
4513 // Only ask for connected devices. For others we will attempt
4514 // with new peer online
4515 for (const auto& m : convModule()->getConversationMembers(conversationId)) {
4516 accountManager_->forEachDevice(dht::InfoHash(m.at("uri")), [
4517 tryDevice
4518 ](const std::shared_ptr<dht::crypto::PublicKey>& dev) {
4519 tryDevice(dev->getLongId());
4520 });
4521 }
4522 }
4523}
4524
4525void
4526JamiAccount::askForProfile(const std::string& conversationId,
4527 const std::string& deviceId,
4528 const std::string& memberUri)
4529{
4530 std::shared_lock lkCM(connManagerMtx_);
4531 if (!connectionManager_)
4532 return;
4533
4534 auto channelName = fmt::format("{}{}/profile/{}.vcf",
4535 DATA_TRANSFER_SCHEME,
4536 conversationId,
4537 memberUri);
4538 // We can avoid to negotiate new sessions, as the file notif
4539 // probably came from an online device or last connected device.
4540 connectionManager_->connectDevice(
4541 DeviceId(deviceId),
4542 channelName,
4543 [this, conversationId](std::shared_ptr<dhtnet::ChannelSocket> channel, const DeviceId&) {
4544 if (!channel)
4545 return;
4546 dht::ThreadPool::io().run([w = weak(), conversationId, channel] {
4547 if (auto shared = w.lock())
4548 if (auto dt = shared->dataTransfer(conversationId))
4549 dt->onIncomingProfile(channel);
4550 });
4551 },
4552 false);
4553}
4554
4555void
4556JamiAccount::onPeerConnected(const std::string& peerId, bool connected)
4557{
4558 std::unique_lock lock(buddyInfoMtx);
4559 auto& state = presenceState_[peerId];
4560 auto it = trackedBuddies_.find(dht::InfoHash(peerId));
4561 auto isOnline = it != trackedBuddies_.end() && it->second.devices_cnt > 0;
4562 auto newState = connected ? PresenceState::CONNECTED : (isOnline ? PresenceState::AVAILABLE : PresenceState::DISCONNECTED);
4563 if (state != newState) {
4564 state = newState;
4565 lock.unlock();
4566 emitSignal<libjami::PresenceSignal::NewBuddyNotification>(getAccountID(),
4567 peerId,
4568 static_cast<int>(newState),
4569 "");
4570 }
4571}
4572
4573void
4574JamiAccount::initConnectionManager()
4575{
4576 if (!nonSwarmTransferManager_)
4577 nonSwarmTransferManager_
4578 = std::make_shared<TransferManager>(accountID_,
4579 config().username,
4580 "",
4581 dht::crypto::getDerivedRandomEngine(rand));
4582 if (!connectionManager_) {
4583 auto connectionManagerConfig = std::make_shared<dhtnet::ConnectionManager::Config>();
4584 connectionManagerConfig->ioContext = Manager::instance().ioContext();
4585 connectionManagerConfig->dht = dht();
4586 connectionManagerConfig->certStore = certStore_;
4587 connectionManagerConfig->id = identity();
4588 connectionManagerConfig->upnpCtrl = upnpCtrl_;
4589 connectionManagerConfig->turnServer = config().turnServer;
4590 connectionManagerConfig->upnpEnabled = config().upnpEnabled;
4591 connectionManagerConfig->turnServerUserName = config().turnServerUserName;
4592 connectionManagerConfig->turnServerPwd = config().turnServerPwd;
4593 connectionManagerConfig->turnServerRealm = config().turnServerRealm;
4594 connectionManagerConfig->turnEnabled = config().turnEnabled;
4595 connectionManagerConfig->cachePath = cachePath_;
4596 connectionManagerConfig->logger = Logger::dhtLogger();
4597 connectionManagerConfig->factory = Manager::instance().getIceTransportFactory();
4598 connectionManagerConfig->turnCache = turnCache_;
4599 connectionManagerConfig->rng = std::make_unique<std::mt19937_64>(
4600 dht::crypto::getDerivedRandomEngine(rand));
4601 connectionManager_ = std::make_unique<dhtnet::ConnectionManager>(connectionManagerConfig);
4602 channelHandlers_[Uri::Scheme::SWARM]
4603 = std::make_unique<SwarmChannelHandler>(shared(), *connectionManager_.get());
4604 channelHandlers_[Uri::Scheme::GIT]
4605 = std::make_unique<ConversationChannelHandler>(shared(), *connectionManager_.get());
4606 channelHandlers_[Uri::Scheme::SYNC]
4607 = std::make_unique<SyncChannelHandler>(shared(), *connectionManager_.get());
4608 channelHandlers_[Uri::Scheme::DATA_TRANSFER]
4609 = std::make_unique<TransferChannelHandler>(shared(), *connectionManager_.get());
4610 channelHandlers_[Uri::Scheme::MESSAGE]
4611 = std::make_unique<MessageChannelHandler>(*connectionManager_.get(),
4612 [this](const auto& cert, std::string& type, const std::string& content) {
4613 onTextMessage("", cert->issuer->getId().toString(), cert, {{type, content}});
4614 },
4615 [w = weak()](const std::string& peer, bool connected) {
4616 asio::post(*Manager::instance().ioContext(), [w, peer, connected] {
4617 if (auto acc = w.lock())
4618 acc->onPeerConnected(peer, connected);
4619 });
4620 });
4621 channelHandlers_[Uri::Scheme::AUTH]
4622 = std::make_unique<AuthChannelHandler>(shared(), *connectionManager_.get());
4623
4624#if TARGET_OS_IOS
4625 connectionManager_->oniOSConnected([&](const std::string& connType, dht::InfoHash peer_h) {
4626 if ((connType == "videoCall" || connType == "audioCall")
4627 && jami::Manager::instance().isIOSExtension) {
4628 bool hasVideo = connType == "videoCall";
4629 emitSignal<libjami::ConversationSignal::CallConnectionRequest>("",
4630 peer_h.toString(),
4631 hasVideo);
4632 return true;
4633 }
4634 return false;
4635 });
4636#endif
4637 }
4638}
4639
4640void
4641JamiAccount::updateUpnpController()
4642{
4643 Account::updateUpnpController();
4644 if (connectionManager_) {
4645 auto config = connectionManager_->getConfig();
4646 if (config)
4647 config->upnpCtrl = upnpCtrl_;
4648 }
4649}
4650
4651} // 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
static constexpr std::string_view dhtStatusStr(dht::NodeStatus status)
std::string getDisplayed(const std::string &conversationId, const std::string &messageId)
std::string_view parseJamiUri(std::string_view toUrl)
static 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