80#include <dhtnet/ice_transport.h>
81#include <dhtnet/ice_transport_factory.h>
82#include <dhtnet/upnp/upnp_control.h>
83#include <dhtnet/multiplexed_socket.h>
84#include <dhtnet/certstore.h>
86#include <opendht/thread_pool.h>
87#include <opendht/peer_discovery.h>
88#include <opendht/http.h>
90#include <yaml-cpp/yaml.h>
91#include <fmt/format.h>
102#include <initializer_list>
107#include <system_error>
110using namespace std::placeholders;
138#define CASE_STATE(X) \
139 case Migration::State::X: \
159 std::chrono::steady_clock::time_point
start;
172 std::set<DeviceId>
to;
196 : onComplete(
std::
move(onComplete))
201 std::lock_guard
lk(mtx);
202 return devices.insert(device).second;
207 std::unique_lock
lk(mtx);
214 std::unique_lock
lk(mtx);
215 if (devices.erase(device) == 0)
225 std::lock_guard
lk(mtx);
226 return devices.empty();
230 std::lock_guard
lk(mtx);
231 return devices.find(device) != devices.end();
235 mutable std::mutex mtx;
237 std::set<DeviceId> devices;
238 unsigned completeCount = 0;
239 unsigned successCount = 0;
240 bool started {
false};
242 void checkComplete(std::unique_lock<std::mutex>&
lk)
244 if (started && (devices.empty() || successCount)) {
246 auto cb = std::move(onComplete);
247 auto success = successCount != 0;
259static const auto PROXY_REGEX = std::regex(
"(https?://)?([\\w\\.\\-_\\~]+)(:(\\d+)|:\\[(.+)-(.+)\\])?");
269 if (
dhtf != std::string_view::npos) {
273 if (
dhtf != std::string_view::npos) {
289 if (
sufix.length() < 40)
290 throw std::invalid_argument(
"Not a valid Jami URI: " +
toUrl);
292 const std::string_view
toUri =
sufix.substr(0, 40);
294 throw std::invalid_argument(
"Not a valid Jami URI: " +
toUrl);
298static constexpr std::string_view
301 return status == dht::NodeStatus::Connected
303 : (status == dht::NodeStatus::Connecting ?
"connecting"sv :
"disconnected"sv);
308 , cachePath_(fileutils::get_cache_dir() / accountId)
309 , dataPath_(cachePath_ /
"values")
310 , logger_(
Logger::dhtLogger(fmt::format(
"Account {}", accountId)))
315 , connectionManager_ {}
316 , nonSwarmTransferManager_()
318 presenceListenerToken_ = presenceManager_->addListener([
this](
const std::string& uri,
bool online) {
320 if (
auto sthis = w.lock()) {
322 sthis->onTrackedBuddyOnline(uri);
323 sthis->messageEngine_.onPeerOnline(uri);
325 sthis->onTrackedBuddyOffline(uri);
332JamiAccount::~JamiAccount() noexcept
339JamiAccount::shutdownConnections()
341 JAMI_DBG(
"[Account %s] Shutdown connections", getAccountID().c_str());
343 decltype(gitServers_) gservers;
345 std::lock_guard lk(gitServersMtx_);
346 gservers = std::move(gitServers_);
348 for (
auto& [_id, gs] : gservers)
351 std::lock_guard lk(connManagerMtx_);
353 dht::ThreadPool::io().run(
354 [conMgr = std::make_shared<
decltype(connectionManager_)>(std::move(connectionManager_))] {});
355 connectionManager_.reset();
356 channelHandlers_.clear();
359 convModule_->shutdownConnections();
362 std::lock_guard lk(sipConnsMtx_);
370 SIPAccountBase::flush();
372 dhtnet::fileutils::removeAll(cachePath_);
373 dhtnet::fileutils::removeAll(dataPath_);
374 dhtnet::fileutils::removeAll(idPath_,
true);
377std::shared_ptr<SIPCall>
378JamiAccount::newIncomingCall(
const std::string& from,
379 const std::vector<libjami::MediaMap>& mediaList,
380 const std::shared_ptr<SipTransport>& sipTransp)
382 JAMI_DEBUG(
"New incoming call from {:s} with {:d} media", from, mediaList.size());
385 auto call = Manager::instance().callFactory.newSipCall(shared(), Call::CallType::INCOMING, mediaList);
387 call->setPeerNumber(from);
389 call->setSipTransport(sipTransp, getContactHeader(sipTransp));
394 JAMI_ERR(
"newIncomingCall: unable to find matching call for %s", from.c_str());
399JamiAccount::newOutgoingCall(std::string_view toUrl,
const std::vector<libjami::MediaMap>& mediaList)
401 auto uri =
Uri(toUrl);
402 if (uri.scheme() == Uri::Scheme::SWARM || uri.scheme() == Uri::Scheme::RENDEZVOUS) {
405 return newSwarmOutgoingCallHelper(uri, mediaList);
408 auto& manager = Manager::instance();
409 std::shared_ptr<SIPCall> call;
412 if (not mediaList.empty()) {
413 call = manager.callFactory.newSipCall(shared(), Call::CallType::OUTGOING, mediaList);
415 JAMI_WARN(
"Media list is empty, setting a default list");
416 call = manager.callFactory.newSipCall(shared(),
417 Call::CallType::OUTGOING,
418 MediaAttribute::mediaAttributesToMediaMaps(
419 createDefaultMediaList(isVideoEnabled())));
425 std::shared_lock lkCM(connManagerMtx_);
426 if (!connectionManager_)
429 connectionManager_->getIceOptions([call, w = weak(), uri = std::move(uri)](
auto&& opts) {
430 if (call->isIceEnabled()) {
431 if (not call->createIceMediaTransport(false)
432 or not call->initIceMediaTransport(true, std::forward<dhtnet::IceTransportOptions>(opts))) {
436 auto shared = w.lock();
439 JAMI_DBG() <<
"New outgoing call with " << uri.toString();
440 call->setPeerNumber(uri.authority());
441 call->setPeerUri(uri.toString());
443 shared->newOutgoingCallHelper(call, uri);
450JamiAccount::newOutgoingCallHelper(
const std::shared_ptr<SIPCall>& call,
const Uri& uri)
452 JAMI_LOG(
"[Account {}] Calling peer {}", getAccountID(), uri.authority());
454 startOutgoingCall(call, uri.authority());
455 }
catch (
const std::invalid_argument&) {
457 NameDirectory::lookupUri(suffix,
459 [wthis_ = weak(), call](
const std::string& regName,
460 const std::string& address,
461 NameDirectory::Response response) {
464 runOnMainThread([wthis_, regName, address, response, call]() {
465 if (response != NameDirectory::Response::found) {
466 call->onFailure(PJSIP_SC_NOT_FOUND);
469 if (
auto sthis = wthis_.lock()) {
471 sthis->startOutgoingCall(call, address);
472 } catch (
const std::invalid_argument&) {
473 call->onFailure(PJSIP_SC_NOT_FOUND);
476 call->onFailure(PJSIP_SC_SERVICE_UNAVAILABLE);
483std::shared_ptr<SIPCall>
484JamiAccount::newSwarmOutgoingCallHelper(
const Uri& uri,
const std::vector<libjami::MediaMap>& mediaList)
486 JAMI_DEBUG(
"[Account {}] Calling conversation {}", getAccountID(), uri.authority());
488 ->call(uri.authority(), mediaList, [
this, uri](
const auto& accountUri,
const auto& deviceId,
const auto& call) {
491 std::unique_lock lkSipConn(sipConnsMtx_);
492 for (auto& [key, value] : sipConns_) {
493 if (key.first != accountUri || key.second != deviceId)
497 auto& sipConn = value.back();
499 if (!sipConn.channel) {
500 JAMI_WARN(
"A SIP transport exists without Channel, this is a bug. Please report");
504 auto transport = sipConn.transport;
505 if (!transport or !sipConn.channel)
507 call->setState(Call::ConnectionState::PROGRESSING);
509 auto remoted_address = sipConn.channel->getRemoteAddress();
511 onConnectedOutgoingCall(call, uri.authority(), remoted_address);
513 } catch (const VoipLinkException&) {
524 std::lock_guard lkP(pendingCallsMutex_);
525 pendingCalls_[deviceId].emplace_back(call);
529 auto type = call->hasVideo() ?
"videoCall" :
"audioCall";
530 JAMI_WARNING(
"[call {}] No channeled socket with this peer. Send request", call->getCallId());
531 requestSIPConnection(accountUri, deviceId, type,
true, call);
536JamiAccount::handleIncomingConversationCall(
const std::string& callId,
const std::string& destination)
539 if (split.size() != 4)
541 auto conversationId = std::string(split[0]);
542 auto accountUri = std::string(split[1]);
543 auto deviceId = std::string(split[2]);
544 auto confId = std::string(split[3]);
546 if (getUsername() != accountUri || currentDeviceId() != deviceId)
550 std::lock_guard lk(rdvMtx_);
551 auto isNotHosting = !convModule()->isHosting(conversationId, confId);
553 auto currentCalls = convModule()->getActiveCalls(conversationId);
554 if (!currentCalls.empty()) {
555 confId = currentCalls[0][
"id"];
556 isNotHosting =
false;
559 JAMI_DEBUG(
"No active call to join, create conference");
562 auto preferences = convModule()->getConversationPreferences(conversationId);
564#if defined(__ANDROID__) || defined(__APPLE__)
568 auto itPref = preferences.find(ConversationPreferences::HOST_CONFERENCES);
569 if (itPref != preferences.end()) {
570 canHost = itPref->second == TRUE_STR;
573 auto call = getCall(callId);
579 if (isNotHosting && !canHost) {
580 JAMI_DEBUG(
"Request for hosting a conference declined");
581 Manager::instance().hangupCall(getAccountID(), callId);
591 std::shared_ptr<Conference> conf;
592 std::vector<libjami::MediaMap> currentMediaList;
594 conf = getConference(confId);
596 JAMI_ERROR(
"[conf:{}] Conference not found", confId);
599 auto hostMedias = conf->currentMediaList();
600 auto sipCall = std::dynamic_pointer_cast<SIPCall>(call);
601 if (hostMedias.empty()) {
602 currentMediaList = MediaAttribute::mediaAttributesToMediaMaps(
603 createDefaultMediaList(call->hasVideo(),
true));
604 }
else if (hostMedias.size() < sipCall->getRtpSessionList().size()) {
608 currentMediaList = hostMedias;
609 currentMediaList.push_back(
616 bool hasVideo =
false;
618 const auto rtpSessions = sipCall->getRtpSessionList();
619 hasVideo = std::any_of(rtpSessions.begin(), rtpSessions.end(), [](
const auto& session) {
620 return session && session->getMediaType() == MediaType::MEDIA_VIDEO;
628 for (
const auto& m : conf->currentMediaList()) {
631 currentMediaList.emplace_back(m);
635 currentMediaList.emplace_back(m);
641 Manager::instance().acceptCall(*call, currentMediaList);
644 JAMI_DEBUG(
"Creating conference for swarm {} with ID {}", conversationId, confId);
646 convModule()->hostConference(conversationId, confId, callId);
648 JAMI_DEBUG(
"Adding participant {} for swarm {} with ID {}", callId, conversationId, confId);
649 Manager::instance().addAudio(*call);
650 conf->addSubCall(callId);
651 emitSignal<libjami::CallSignal::ConferenceChanged>(getAccountID(), conf->getConfId(), conf->getStateStr());
655std::shared_ptr<SIPCall>
656JamiAccount::createSubCall(
const std::shared_ptr<SIPCall>& mainCall)
658 auto mediaList = MediaAttribute::mediaAttributesToMediaMaps(mainCall->getMediaAttributeList());
659 return Manager::instance().callFactory.newSipCall(shared(), Call::CallType::OUTGOING, mediaList);
663JamiAccount::startOutgoingCall(
const std::shared_ptr<SIPCall>& call,
const std::string& toUri)
665 if (not accountManager_ or not dht_) {
666 call->onFailure(PJSIP_SC_SERVICE_UNAVAILABLE);
673 call->setState(Call::ConnectionState::TRYING);
674 std::weak_ptr<SIPCall> wCall = call;
676 accountManager_->lookupAddress(toUri,
677 [wCall](
const std::string& regName,
679 const NameDirectory::Response& response) {
680 if (response == NameDirectory::Response::found)
681 if (
auto call = wCall.lock()) {
682 call->setPeerRegisteredName(regName);
686 dht::InfoHash peer_account(toUri);
688 throw std::invalid_argument(
"Invalid peer account: " + toUri);
692 std::set<DeviceId> devices;
693 std::unique_lock lkSipConn(sipConnsMtx_);
696 auto dummyCall = createSubCall(call);
698 call->addSubCall(*dummyCall);
699 dummyCall->setIceMedia(call->getIceMedia());
700 auto sendRequest = [
this, wCall, toUri, dummyCall = std::move(dummyCall)](
const DeviceId& deviceId,
705 dummyCall->onFailure(PJSIP_SC_TEMPORARILY_UNAVAILABLE);
708 auto call = wCall.lock();
711 auto state = call->getConnectionState();
712 if (state != Call::ConnectionState::PROGRESSING and state != Call::ConnectionState::TRYING)
715 auto dev_call = createSubCall(call);
716 dev_call->setPeerNumber(call->getPeerNumber());
717 dev_call->setState(Call::ConnectionState::TRYING);
718 call->addStateListener([w = weak(), deviceId](Call::CallState, Call::ConnectionState state,
int) {
719 if (state != Call::ConnectionState::PROGRESSING and state != Call::ConnectionState::TRYING) {
720 if (
auto shared = w.lock())
721 shared->callConnectionClosed(deviceId,
true);
726 call->addSubCall(*dev_call);
727 dev_call->setIceMedia(call->getIceMedia());
729 std::lock_guard lk(pendingCallsMutex_);
730 pendingCalls_[deviceId].emplace_back(dev_call);
733 JAMI_WARNING(
"[call {}] No channeled socket with this peer. Send request", call->getCallId());
735 const auto* type = call->hasVideo() ?
"videoCall" :
"audioCall";
736 requestSIPConnection(toUri, deviceId, type,
true, dev_call);
739 std::vector<std::shared_ptr<dhtnet::ChannelSocket>> channels;
740 for (
auto& [key, value] : sipConns_) {
741 if (key.first != toUri)
745 auto& sipConn = value.back();
747 if (!sipConn.channel) {
748 JAMI_WARNING(
"A SIP transport exists without Channel, this is a bug. Please report");
752 auto transport = sipConn.transport;
753 auto remote_address = sipConn.channel->getRemoteAddress();
754 if (!transport or !remote_address)
757 channels.emplace_back(sipConn.channel);
759 JAMI_WARNING(
"[call {}] A channeled socket is detected with this peer.", call->getCallId());
761 auto dev_call = createSubCall(call);
762 dev_call->setPeerNumber(call->getPeerNumber());
763 dev_call->setSipTransport(transport, getContactHeader(transport));
764 call->addSubCall(*dev_call);
765 dev_call->setIceMedia(call->getIceMedia());
771 dev_call->setState(Call::ConnectionState::PROGRESSING);
774 std::lock_guard lk(onConnectionClosedMtx_);
775 onConnectionClosed_[key.second] = sendRequest;
778 call->addStateListener([w = weak(), deviceId = key.second](Call::CallState, Call::ConnectionState state,
int) {
779 if (state != Call::ConnectionState::PROGRESSING and state != Call::ConnectionState::TRYING) {
780 if (auto shared = w.lock())
781 shared->callConnectionClosed(deviceId, true);
788 onConnectedOutgoingCall(dev_call, toUri, remote_address);
789 }
catch (
const VoipLinkException&) {
797 devices.emplace(key.second);
803 for (
const auto& channel : channels)
804 channel->sendBeacon();
807 accountManager_->forEachDevice(
809 [
this, devices = std::move(devices), sendRequest](
const std::shared_ptr<dht::crypto::PublicKey>&
dev) {
811 auto deviceId =
dev->getLongId();
812 if (devices.find(deviceId) != devices.end())
815 std::lock_guard lk(onConnectionClosedMtx_);
816 onConnectionClosed_[deviceId] = sendRequest;
818 sendRequest(deviceId,
false);
822 if (
auto call = wCall.lock()) {
823 JAMI_WARNING(
"[call:{}] No devices found", call->getCallId());
825 if (call->getConnectionState() == Call::ConnectionState::TRYING)
826 call->onFailure(PJSIP_SC_TEMPORARILY_UNAVAILABLE);
833JamiAccount::onConnectedOutgoingCall(
const std::shared_ptr<SIPCall>& call,
834 const std::string& to_id,
835 dhtnet::IpAddr target)
839 JAMI_LOG(
"[call:{}] Outgoing call connected to {}", call->getCallId(), to_id);
841 const auto localAddress = dhtnet::ip_utils::getInterfaceAddr(getLocalInterface(), target.getFamily());
843 dhtnet::IpAddr addrSdp = getPublishedSameasLocal() ? localAddress
844 : connectionManager_->getPublishedIpAddress(target.getFamily());
848 addrSdp = localAddress;
851 auto& sdp = call->getSDP();
853 sdp.setPublishedIP(addrSdp);
855 auto mediaAttrList = call->getMediaAttributeList();
856 if (mediaAttrList.empty()) {
857 JAMI_ERROR(
"[call:{}] No media. Abort!", call->getCallId());
861 if (not sdp.createOffer(mediaAttrList)) {
862 JAMI_ERROR(
"[call:{}] Unable to send outgoing INVITE request for new call", call->getCallId());
872 dht::ThreadPool::io().run([w = weak(), call = std::move(call), target] {
873 auto account = w.lock();
877 if (not account->SIPStartCall(*call, target)) {
878 JAMI_ERROR(
"[call:{}] Unable to send outgoing INVITE request for new call", call->getCallId());
884JamiAccount::SIPStartCall(SIPCall& call,
const dhtnet::IpAddr& target)
886 JAMI_LOG(
"[call:{}] Start SIP call", call.getCallId());
888 if (call.isIceEnabled())
889 call.addLocalIceAttributes();
892 getToUri(call.getPeerNumber() +
"@" + target.toString(
true)));
894 pj_str_t pjTo = sip_utils::CONST_PJ_STR(toUri);
897 std::string from(getFromUri());
898 pj_str_t pjFrom = sip_utils::CONST_PJ_STR(from);
900 std::string targetStr = getToUri(target.toString(
true));
901 pj_str_t pjTarget = sip_utils::CONST_PJ_STR(targetStr);
903 auto contact = call.getContactHeader();
904 auto pjContact = sip_utils::CONST_PJ_STR(contact);
906 JAMI_LOG(
"[call:{}] Contact header: {} / {} -> {} / {}", call.getCallId(), contact, from, toUri, targetStr);
908 auto* local_sdp = call.getSDP().getLocalSdpSession();
909 pjsip_dialog* dialog {
nullptr};
910 pjsip_inv_session* inv {
nullptr};
911 if (!CreateClientDialogAndInvite(&pjFrom, &pjContact, &pjTo, &pjTarget, local_sdp, &dialog, &inv))
914 inv->mod_data[link_.getModId()] = &call;
915 call.setInviteSession(inv);
917 pjsip_tx_data* tdata;
919 if (pjsip_inv_invite(call.inviteSession_.get(), &tdata) != PJ_SUCCESS) {
920 JAMI_ERROR(
"[call:{}] Unable to initialize invite", call.getCallId());
924 pjsip_tpselector tp_sel;
925 tp_sel.type = PJSIP_TPSELECTOR_TRANSPORT;
926 if (!call.getTransport()) {
927 JAMI_ERROR(
"[call:{}] Unable to get transport", call.getCallId());
930 tp_sel.u.transport = call.getTransport()->get();
931 if (pjsip_dlg_set_transport(dialog, &tp_sel) != PJ_SUCCESS) {
932 JAMI_ERROR(
"[call:{}] Unable to associate transport for invite session dialog", call.getCallId());
936 JAMI_LOG(
"[call:{}] Sending SIP invite", call.getCallId());
939 sip_utils::addUserAgentHeader(getUserAgentName(), tdata);
941 if (pjsip_inv_send_msg(call.inviteSession_.get(), tdata) != PJ_SUCCESS) {
942 JAMI_ERROR(
"[call:{}] Unable to send invite message", call.getCallId());
946 call.setState(Call::CallState::ACTIVE, Call::ConnectionState::PROGRESSING);
951JamiAccount::saveConfig()
const
954 YAML::Emitter accountOut;
955 config().serialize(accountOut);
956 auto accountConfig = config().path /
"config.yml";
957 std::lock_guard lock(dhtnet::fileutils::getFileLock(accountConfig));
958 std::ofstream fout(accountConfig);
959 fout.write(accountOut.c_str(),
static_cast<std::streamsize
>(accountOut.size()));
960 JAMI_LOG(
"Saved account config to {}", accountConfig);
961 }
catch (
const std::exception& e) {
962 JAMI_ERROR(
"Error saving account config: {}", e.what());
967JamiAccount::loadConfig()
969 SIPAccountBase::loadConfig();
970 registeredName_ = config().registeredName;
972 accountManager_->setAccountDeviceName(config().deviceName);
973 if (connectionManager_) {
974 if (
auto c = connectionManager_->getConfig()) {
976 c->upnpEnabled = config().upnpEnabled;
977 c->turnEnabled = config().turnEnabled;
978 c->turnServer = config().turnServer;
979 c->turnServerUserName = config().turnServerUserName;
980 c->turnServerPwd = config().turnServerPwd;
981 c->turnServerRealm = config().turnServerRealm;
984 if (config().proxyEnabled) {
986 auto str = fileutils::loadCacheTextFile(cachePath_ /
"dhtproxy", std::chrono::hours(24 * 14));
988 if (json::parse(str, root)) {
989 proxyServerCached_ = root[getProxyConfigKey()].asString();
991 }
catch (
const std::exception& e) {
992 JAMI_LOG(
"[Account {}] Unable to load proxy URL from cache: {}", getAccountID(), e.what());
993 proxyServerCached_.clear();
996 proxyServerCached_.clear();
998 std::filesystem::remove(cachePath_ /
"dhtproxy", ec);
1000 if (not config().dhtProxyServerEnabled) {
1001 dhtProxyServer_.reset();
1003 auto credentials = consumeConfigCredentials();
1004 loadAccount(credentials.archive_password_scheme, credentials.archive_password, credentials.archive_path);
1008JamiAccount::changeArchivePassword(
const std::string& password_old,
const std::string& password_new)
1011 if (!accountManager_->changePassword(password_old, password_new)) {
1012 JAMI_ERROR(
"[Account {}] Unable to change archive password", getAccountID());
1016 }
catch (
const std::exception& ex) {
1017 JAMI_ERROR(
"[Account {}] Unable to change archive password: {}", getAccountID(), ex.what());
1018 if (password_old.empty()) {
1020 emitSignal<libjami::ConfigurationSignal::AccountDetailsChanged>(getAccountID(), getAccountDetails());
1024 if (password_old != password_new)
1025 emitSignal<libjami::ConfigurationSignal::AccountDetailsChanged>(getAccountID(), getAccountDetails());
1030JamiAccount::isPasswordValid(
const std::string& password)
1032 return accountManager_ and accountManager_->isPasswordValid(password);
1036JamiAccount::getPasswordKey(
const std::string& password)
1038 return accountManager_ ? accountManager_->getPasswordKey(password) : std::vector<uint8_t>();
1042JamiAccount::provideAccountAuthentication(
const std::string& credentialsFromUser,
const std::string& scheme)
1044 if (
auto manager = std::dynamic_pointer_cast<ArchiveAccountManager>(accountManager_)) {
1045 return manager->provideAccountAuthentication(credentialsFromUser, scheme);
1047 JAMI_ERR(
"[LinkDevice] Invalid AccountManager instance while providing current account "
1053JamiAccount::addDevice(
const std::string& uriProvided)
1055 JAMI_LOG(
"[LinkDevice] JamiAccount::addDevice({}, {})", getAccountID(), uriProvided);
1056 if (not accountManager_) {
1057 JAMI_ERR(
"[LinkDevice] Invalid AccountManager instance while adding a device.");
1058 return static_cast<int32_t
>(AccountManager::AddDeviceError::GENERIC);
1060 auto authHandler = channelHandlers_.find(Uri::Scheme::AUTH);
1061 if (authHandler == channelHandlers_.end())
1062 return static_cast<int32_t
>(AccountManager::AddDeviceError::GENERIC);
1063 return accountManager_->addDevice(uriProvided,
1064 config().archiveHasPassword ? fileutils::ARCHIVE_AUTH_SCHEME_PASSWORD
1065 : fileutils::ARCHIVE_AUTH_SCHEME_NONE,
1070JamiAccount::cancelAddDevice(uint32_t op_token)
1072 if (!accountManager_)
1074 return accountManager_->cancelAddDevice(op_token);
1078JamiAccount::confirmAddDevice(uint32_t op_token)
1080 if (!accountManager_)
1082 return accountManager_->confirmAddDevice(op_token);
1086JamiAccount::exportArchive(
const std::string& destinationPath, std::string_view scheme,
const std::string& password)
1089 return manager->exportArchive(destinationPath, scheme, password);
1095JamiAccount::setValidity(std::string_view scheme,
const std::string& pwd,
const dht::InfoHash&
id, int64_t validity)
1098 if (manager->setValidity(scheme, pwd, id_,
id, validity)) {
1107JamiAccount::forceReloadAccount()
1117JamiAccount::unlinkConversations(
const std::set<std::string>& removed)
1119 std::lock_guard lock(configurationMutex_);
1120 if (
const auto* info = accountManager_->getInfo()) {
1121 auto contacts = info->contacts->getContacts();
1122 for (
auto& [
id, c] : contacts) {
1123 if (removed.find(c.conversationId) != removed.end()) {
1124 info->contacts->updateConversation(
id,
"");
1125 JAMI_WARNING(
"[Account {}] Detected removed conversation ({}) in contact details for {}",
1135JamiAccount::isValidAccountDevice(
const dht::crypto::Certificate& cert)
const
1137 if (accountManager_) {
1138 if (
const auto* info = accountManager_->getInfo()) {
1140 return info->contacts->isValidAccountDevice(cert).isValid();
1147JamiAccount::revokeDevice(
const std::string& device, std::string_view scheme,
const std::string& password)
1149 if (not accountManager_)
1151 return accountManager_
1153 emitSignal<libjami::ConfigurationSignal::DeviceRevocationEnded>(getAccountID(),
1155 static_cast<int>(result));
1160std::pair<std::string, std::string>
1161JamiAccount::saveIdentity(
const dht::crypto::Identity&
id,
const std::filesystem::path& path,
const std::string& name)
1163 auto names = std::make_pair(name +
".key", name +
".crt");
1165 fileutils::saveFile(path / names.first,
id.first->serialize(), 0600);
1167 fileutils::saveFile(path / names.second,
id.second->getPacked(), 0600);
1172JamiAccount::scheduleAccountReady()
const
1174 const auto accountId = getAccountID();
1175 runOnMainThread([accountId] { Manager::instance().markAccountReady(accountId); });
1178AccountManager::OnChangeCallback
1179JamiAccount::setupAccountCallbacks()
1181 return AccountManager::OnChangeCallback {
1182 [
this](
const std::string& uri,
bool confirmed) { onContactAdded(uri, confirmed); },
1183 [
this](
const std::string& uri,
bool banned) { onContactRemoved(uri, banned); },
1184 [
this](
const std::string& uri,
1185 const std::string& conversationId,
1186 const std::vector<uint8_t>& payload,
1187 time_t received) { onIncomingTrustRequest(uri, conversationId, payload, received); },
1188 [
this](
const std::map<DeviceId, KnownDevice>& devices) { onKnownDevicesChanged(devices); },
1189 [
this](
const std::string& conversationId,
const std::string& deviceId) {
1190 onConversationRequestAccepted(conversationId, deviceId);
1192 [
this](
const std::string& uri,
const std::string& convFromReq) { onContactConfirmed(uri, convFromReq); }};
1196JamiAccount::onContactAdded(
const std::string& uri,
bool confirmed)
1201 dht::ThreadPool::io().run([w = weak(), uri, confirmed] {
1202 if (
auto shared = w.lock()) {
1203 if (auto* cm = shared->convModule(true)) {
1204 auto activeConv = cm->getOneToOneConversation(uri);
1205 if (!activeConv.empty())
1206 cm->bootstrap(activeConv);
1208 emitSignal<libjami::ConfigurationSignal::ContactAdded>(shared->getAccountID(), uri, confirmed);
1215JamiAccount::onContactRemoved(
const std::string& uri,
bool banned)
1219 dht::ThreadPool::io().run([w = weak(), uri, banned] {
1220 if (
auto shared = w.lock()) {
1222 if (auto* convModule = shared->convModule(true))
1223 convModule->removeContact(uri, banned);
1227 if (shared->connectionManager_ && uri != shared->getUsername()) {
1228 shared->connectionManager_->closeConnectionsWith(uri);
1231 emitSignal<libjami::ConfigurationSignal::ContactRemoved>(shared->getAccountID(), uri, banned);
1237JamiAccount::onIncomingTrustRequest(
const std::string& uri,
1238 const std::string& conversationId,
1239 const std::vector<uint8_t>& payload,
1244 dht::ThreadPool::io().run([w = weak(), uri, conversationId, payload, received] {
1245 if (
auto shared = w.lock()) {
1246 shared->clearProfileCache(uri);
1247 if (conversationId.empty()) {
1249 emitSignal<libjami::ConfigurationSignal::IncomingTrustRequest>(shared->getAccountID(),
1257 if (
auto* cm = shared->convModule(
true)) {
1258 auto activeConv = cm->getOneToOneConversation(uri);
1259 if (activeConv != conversationId)
1260 cm->onTrustRequest(uri, conversationId, payload, received);
1267JamiAccount::onKnownDevicesChanged(
const std::map<DeviceId, KnownDevice>& devices)
1269 std::map<std::string, std::string> ids;
1270 for (
auto& d : devices) {
1271 auto id = d.first.toString();
1272 auto label = d.second.name.empty() ?
id.substr(0, 8) : d.second.name;
1273 ids.emplace(std::move(
id), std::move(label));
1275 runOnMainThread([
id = getAccountID(), devices = std::move(ids)] {
1276 emitSignal<libjami::ConfigurationSignal::KnownDevicesChanged>(
id, devices);
1281JamiAccount::onConversationRequestAccepted(
const std::string& conversationId,
const std::string& deviceId)
1286 if (
auto* cm = convModule(
true))
1287 cm->acceptConversationRequest(conversationId, deviceId);
1291JamiAccount::onContactConfirmed(
const std::string& uri,
const std::string& convFromReq)
1293 dht::ThreadPool::io().run([w = weak(), convFromReq, uri] {
1294 if (
auto shared = w.lock()) {
1295 shared->convModule(true);
1297 auto requestPath = shared->cachePath_ /
"requests" / uri;
1298 dhtnet::fileutils::remove(requestPath);
1303std::unique_ptr<AccountManager::AccountCredentials>
1304JamiAccount::buildAccountCredentials(
const JamiAccountConfig& conf,
1305 const dht::crypto::Identity&
id,
1306 const std::string& archive_password_scheme,
1307 const std::string& archive_password,
1308 const std::string& archive_path,
1312 std::unique_ptr<AccountManager::AccountCredentials> creds;
1314 if (conf.managerUri.empty()) {
1315 auto acreds = std::make_unique<ArchiveAccountManager::ArchiveAccountCredentials>();
1316 auto archivePath = fileutils::getFullPath(idPath_, conf.archivePath);
1318 if (!archive_path.empty()) {
1319 acreds->scheme =
"file";
1320 acreds->uri = archive_path;
1321 }
else if (!conf.archive_url.empty() && conf.archive_url ==
"jami-auth") {
1322 JAMI_DEBUG(
"[Account {}] [LinkDevice] scheme p2p & uri {}", getAccountID(), conf.archive_url);
1323 acreds->scheme =
"p2p";
1324 acreds->uri = conf.archive_url;
1325 }
else if (std::filesystem::is_regular_file(archivePath)) {
1326 acreds->scheme =
"local";
1327 acreds->uri = archivePath.string();
1328 acreds->updateIdentity = id;
1332 creds = std::move(acreds);
1334 auto screds = std::make_unique<ServerAccountManager::ServerAccountCredentials>();
1335 screds->username = conf.managerUsername;
1336 creds = std::move(screds);
1339 creds->password = archive_password;
1340 hasPassword = !archive_password.empty();
1341 creds->password_scheme = (hasPassword && archive_password_scheme.empty()) ? fileutils::ARCHIVE_AUTH_SCHEME_PASSWORD
1342 : archive_password_scheme;
1348JamiAccount::onAuthenticationSuccess(
bool migrating,
1350 const AccountInfo& info,
1351 const std::map<std::string, std::string>& configMap,
1352 std::string&& receipt,
1353 std::vector<uint8_t>&& receiptSignature)
1355 JAMI_LOG(
"[Account {}] Auth success! Device: {}", getAccountID(), info.deviceId);
1357 dhtnet::fileutils::check_dir(idPath_, 0700);
1359 auto id = info.identity;
1360 editConfig([&](JamiAccountConfig& conf) {
1361 std::tie(conf.tlsPrivateKeyFile, conf.tlsCertificateFile) = saveIdentity(
id, idPath_, DEVICE_ID_PATH);
1362 conf.tlsPassword = {};
1365 conf.archiveHasPassword = (passwordIt != configMap.end() && !passwordIt->second.empty())
1366 ? passwordIt->second ==
"true"
1369 if (not conf.managerUri.empty()) {
1370 conf.registeredName = conf.managerUsername;
1371 registeredName_ = conf.managerUsername;
1374 conf.username = info.accountId;
1375 conf.deviceName = accountManager_->getAccountDeviceName();
1378 if (nameServerIt != configMap.end() && !nameServerIt->second.empty())
1379 conf.nameServer = nameServerIt->second;
1382 if (displayNameIt != configMap.end() && !displayNameIt->second.empty())
1383 conf.displayName = displayNameIt->second;
1385 conf.receipt = std::move(receipt);
1386 conf.receiptSignature = std::move(receiptSignature);
1387 conf.fromMap(configMap);
1390 id_ = std::move(
id);
1392 std::lock_guard lk(moduleMtx_);
1393 convModule_.reset();
1397 Migration::setState(getAccountID(), Migration::State::SUCCESS);
1399 setRegistrationState(RegistrationState::UNREGISTERED);
1401 if (!info.photo.empty() || !info.displayName.empty()) {
1403 auto newProfile = vCard::utils::initVcard();
1404 newProfile[std::string(vCard::Property::FORMATTED_NAME)] = info.displayName;
1405 newProfile[std::string(vCard::Property::PHOTO)] = info.photo;
1407 const auto& profiles = idPath_ /
"profiles";
1408 const auto& vCardPath = profiles / fmt::format(
"{}.vcf", base64::encode(info.accountId));
1409 vCard::utils::save(newProfile, vCardPath, profilePath());
1411 runOnMainThread([w = weak(),
id = info.accountId, vCardPath] {
1412 if (auto shared = w.lock()) {
1413 emitSignal<libjami::ConfigurationSignal::ProfileReceived>(shared->getAccountID(),
1415 vCardPath.string());
1418 }
catch (
const std::exception& e) {
1419 JAMI_WARNING(
"[Account {}] Unable to save profile after authentication: {}", getAccountID(), e.what());
1424 scheduleAccountReady();
1428JamiAccount::onAuthenticationError(
const std::weak_ptr<JamiAccount>& w,
1431 std::string accountId,
1432 AccountManager::AuthError error,
1433 const std::string& message)
1435 JAMI_WARNING(
"[Account {}] Auth error: {} {}", accountId, (
int) error, message);
1437 if ((hadIdentity || migrating) && error == AccountManager::AuthError::INVALID_ARGUMENTS) {
1438 Migration::setState(accountId, Migration::State::INVALID);
1439 if (
auto acc = w.lock())
1440 acc->setRegistrationState(RegistrationState::ERROR_NEED_MIGRATION);
1444 if (
auto acc = w.lock())
1445 acc->setRegistrationState(RegistrationState::ERROR_GENERIC);
1447 runOnMainThread([accountId = std::move(accountId)] { Manager::instance().removeAccount(accountId,
true); });
1452JamiAccount::loadAccount(
const std::string& archive_password_scheme,
1453 const std::string& archive_password,
1454 const std::string& archive_path)
1456 if (registrationState_ == RegistrationState::INITIALIZING)
1459 JAMI_DEBUG(
"[Account {:s}] Loading account", getAccountID());
1461 const auto scheduleAccountReady = [accountId = getAccountID()] {
1462 runOnMainThread([accountId] {
1463 auto& manager = Manager::instance();
1464 manager.markAccountReady(accountId);
1468 const auto& conf = config();
1469 auto callbacks = setupAccountCallbacks();
1472 auto oldIdentity = id_.first ? id_.first->getPublicKey().getLongId() :
DeviceId();
1474 if (conf.managerUri.empty()) {
1475 accountManager_ = std::make_shared<ArchiveAccountManager>(
1479 [
this](DeviceSync&& syncData) {
1480 if (
auto* sm = syncModule()) {
1481 auto syncDataPtr = std::make_shared<SyncMsg>();
1482 syncDataPtr->ds = std::move(syncData);
1483 sm->syncWithConnected(syncDataPtr);
1486 conf.archivePath.empty() ?
"archive.gz" : conf.archivePath,
1489 accountManager_ = std::make_shared<ServerAccountManager>(getAccountID(),
1495 auto id = accountManager_->loadIdentity(conf.tlsCertificateFile, conf.tlsPrivateKeyFile, conf.tlsPassword);
1497 if (
const auto* info
1498 = accountManager_->useIdentity(
id, conf.receipt, conf.receiptSignature, conf.managerUsername, callbacks)) {
1499 id_ = std::move(
id);
1500 config_->username = info->accountId;
1501 JAMI_WARNING(
"[Account {:s}] Loaded account identity", getAccountID());
1503 if (info->identity.first->getPublicKey().getLongId() != oldIdentity) {
1504 JAMI_WARNING(
"[Account {:s}] Identity changed", getAccountID());
1506 std::lock_guard lk(moduleMtx_);
1507 convModule_.reset();
1511 convModule()->setAccountManager(accountManager_);
1514 convModule()->initPresence();
1515 if (not isEnabled())
1516 setRegistrationState(RegistrationState::UNREGISTERED);
1518 scheduleAccountReady();
1525 JAMI_WARNING(
"[Account {}] useIdentity failed!", getAccountID());
1527 if (not conf.managerUri.empty() && archive_password.empty()) {
1528 Migration::setState(accountID_, Migration::State::INVALID);
1529 setRegistrationState(RegistrationState::ERROR_NEED_MIGRATION);
1533 bool migrating = registrationState_ == RegistrationState::ERROR_NEED_MIGRATION;
1534 setRegistrationState(RegistrationState::INITIALIZING);
1536 auto fDeviceKey = dht::ThreadPool::computation().getShared<std::shared_ptr<dht::crypto::PrivateKey>>(
1537 []() {
return std::make_shared<dht::crypto::PrivateKey>(dht::crypto::PrivateKey::generate()); });
1539 bool hasPassword =
false;
1540 auto creds = buildAccountCredentials(conf,
1542 archive_password_scheme,
1548 JAMI_WARNING(
"[Account {}] initAuthentication {}", getAccountID(), fmt::ptr(
this));
1550 const bool hadIdentity =
static_cast<bool>(
id.first);
1551 accountManager_->initAuthentication(
1553 ip_utils::getDeviceName(),
1555 [w = weak(), migrating, hasPassword](
const AccountInfo& info,
1556 const std::map<std::string, std::string>& configMap,
1557 std::string&& receipt,
1558 std::vector<uint8_t>&& receiptSignature) {
1559 if (
auto self = w.lock())
1560 self->onAuthenticationSuccess(migrating,
1565 std::move(receiptSignature));
1567 [w = weak(), hadIdentity, accountId = getAccountID(), migrating](AccountManager::AuthError error,
1568 const std::string& message) {
1569 JamiAccount::onAuthenticationError(w, hadIdentity, migrating, accountId, error, message);
1572 }
catch (
const std::exception& e) {
1573 JAMI_WARNING(
"[Account {}] Error loading account: {}", getAccountID(), e.what());
1574 accountManager_.reset();
1575 setRegistrationState(RegistrationState::ERROR_GENERIC);
1579std::map<std::string, std::string>
1580JamiAccount::getVolatileAccountDetails()
const
1582 auto a = SIPAccountBase::getVolatileAccountDetails();
1584 auto registeredName = getRegisteredName();
1585 if (not registeredName.empty())
1590 if (accountManager_) {
1591 if (
const auto* info = accountManager_->getInfo()) {
1599JamiAccount::lookupName(
const std::string& name)
1601 std::lock_guard lock(configurationMutex_);
1602 if (accountManager_)
1603 accountManager_->lookupUri(name,
1604 config().nameServer,
1605 [acc = getAccountID(), name](
const std::string& regName,
1606 const std::string& address,
1608 emitSignal<libjami::ConfigurationSignal::RegisteredNameFound>(acc,
1617JamiAccount::lookupAddress(
const std::string& addr)
1619 std::lock_guard lock(configurationMutex_);
1620 auto acc = getAccountID();
1621 if (accountManager_)
1622 accountManager_->lookupAddress(addr,
1623 [acc, addr](
const std::string& regName,
1624 const std::string& address,
1626 emitSignal<libjami::ConfigurationSignal::RegisteredNameFound>(acc,
1635JamiAccount::registerName(
const std::string& name,
const std::string& scheme,
const std::string& password)
1637 std::lock_guard lock(configurationMutex_);
1638 if (accountManager_)
1640 ->registerName(name,
1644 const std::string& regName) {
1645 auto res = (int) std::min(response, NameDirectory::RegistrationResponse::error);
1646 if (response == NameDirectory::RegistrationResponse::success) {
1647 if (
auto this_ = w.lock()) {
1648 if (this_->setRegisteredName(regName)) {
1651 emitSignal<libjami::ConfigurationSignal::VolatileDetailsChanged>(
1652 this_->accountID_, this_->getVolatileAccountDetails());
1656 emitSignal<libjami::ConfigurationSignal::NameRegistrationEnded>(acc, res, name);
1661JamiAccount::searchUser(
const std::string& query)
1663 if (accountManager_)
1664 return accountManager_
1677JamiAccount::forEachPendingCall(
const DeviceId& deviceId,
const std::function<
void(
const std::shared_ptr<SIPCall>&)>& cb)
1679 std::vector<std::shared_ptr<SIPCall>> pc;
1681 std::lock_guard lk(pendingCallsMutex_);
1682 pc = std::move(pendingCalls_[deviceId]);
1684 for (
const auto& pendingCall : pc) {
1690JamiAccount::registerAsyncOps()
1692 loadCachedProxyServer([w = weak()](
const std::string&) {
1693 runOnMainThread([w] {
1694 if (
auto s = w.lock()) {
1695 std::lock_guard lock(s->configurationMutex_);
1703JamiAccount::doRegister()
1705 std::lock_guard lock(configurationMutex_);
1706 if (not isUsable()) {
1707 JAMI_WARNING(
"[Account {:s}] Account must be enabled and active to register, ignoring", getAccountID());
1711 JAMI_LOG(
"[Account {:s}] Starting account…", getAccountID());
1716 if (registrationState_ == RegistrationState::INITIALIZING
1717 || registrationState_ == RegistrationState::ERROR_NEED_MIGRATION)
1721 setRegistrationState(RegistrationState::TRYING);
1722 if (proxyServerCached_.empty()) {
1729std::vector<std::string>
1730JamiAccount::loadBootstrap()
const
1732 std::vector<std::string> bootstrap;
1733 std::string_view stream(config().hostname), 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());
1742JamiAccount::trackBuddyPresence(
const std::string& buddy_id,
bool track)
1744 std::string buddyUri;
1748 JAMI_ERROR(
"[Account {:s}] Failed to track presence: invalid URI {:s}", getAccountID(), buddy_id);
1751 JAMI_LOG(
"[Account {:s}] {:s} presence for {:s}", getAccountID(), track ?
"Track" :
"Untrack", buddy_id);
1753 if (!presenceManager_)
1757 presenceManager_->trackBuddy(buddyUri);
1758 std::lock_guard lock(presenceStateMtx_);
1759 auto it = presenceState_.find(buddyUri);
1760 if (it != presenceState_.end() && it->second != PresenceState::DISCONNECTED) {
1761 emitSignal<libjami::PresenceSignal::NewBuddyNotification>(getAccountID(),
1763 static_cast<int>(it->second),
1767 presenceManager_->untrackBuddy(buddyUri);
1771std::map<std::string, bool>
1772JamiAccount::getTrackedBuddyPresence()
const
1774 if (!presenceManager_)
1776 return presenceManager_->getTrackedBuddyPresence();
1780JamiAccount::onTrackedBuddyOnline(
const std::string& contactId)
1782 JAMI_DEBUG(
"[Account {:s}] Buddy {} online", getAccountID(), contactId);
1783 std::lock_guard lock(presenceStateMtx_);
1784 auto& state = presenceState_[contactId];
1785 if (state < PresenceState::AVAILABLE) {
1786 state = PresenceState::AVAILABLE;
1787 emitSignal<libjami::PresenceSignal::NewBuddyNotification>(getAccountID(),
1789 static_cast<int>(PresenceState::AVAILABLE),
1793 if (
auto details = getContactInfo(contactId)) {
1794 if (!details->confirmed) {
1795 auto convId = convModule()->getOneToOneConversation(contactId);
1800 std::lock_guard lock(configurationMutex_);
1801 if (accountManager_) {
1803 auto requestPath = cachePath_ /
"requests" / contactId;
1804 std::vector<uint8_t> payload;
1806 payload = fileutils::loadFile(requestPath);
1809 if (payload.size() >= 64000) {
1810 JAMI_WARNING(
"[Account {:s}] Trust request for contact {:s} is too big, reset payload",
1815 accountManager_->sendTrustRequest(contactId, convId, payload);
1822JamiAccount::onTrackedBuddyOffline(
const std::string& contactId)
1824 JAMI_DEBUG(
"[Account {:s}] Buddy {} offline", getAccountID(), contactId);
1825 std::lock_guard lock(presenceStateMtx_);
1826 auto& state = presenceState_[contactId];
1827 if (state > PresenceState::DISCONNECTED) {
1828 if (state == PresenceState::CONNECTED) {
1829 JAMI_WARNING(
"[Account {:s}] Buddy {} is not present on the DHT, but P2P connected",
1834 state = PresenceState::DISCONNECTED;
1835 emitSignal<libjami::PresenceSignal::NewBuddyNotification>(getAccountID(),
1837 static_cast<int>(PresenceState::DISCONNECTED),
1843JamiAccount::doRegister_()
1845 if (registrationState_ != RegistrationState::TRYING) {
1846 JAMI_ERROR(
"[Account {}] Already registered", getAccountID());
1850 JAMI_DEBUG(
"[Account {}] Starting account…", getAccountID());
1851 const auto& conf = config();
1854 if (not accountManager_ or not accountManager_->getInfo())
1855 throw std::runtime_error(
"No identity configured for this account.");
1857 if (dht_->isRunning()) {
1858 JAMI_ERROR(
"[Account {}] DHT already running (stopping it first).", getAccountID());
1862 convModule()->clearPendingFetch();
1865 accountManager_->lookupAddress(accountManager_->getInfo()->accountId,
1866 [w = weak()](
const std::string& regName,
1867 const std::string& ,
1868 const NameDirectory::Response& response) {
1869 if (
auto this_ = w.lock())
1870 this_->lookupRegisteredName(regName, response);
1873 dht::DhtRunner::Config config = initDhtConfig(conf);
1876 if (conf.accountPeerDiscovery or conf.accountPublish) {
1877 peerDiscovery_ = std::make_shared<dht::PeerDiscovery>();
1878 if (conf.accountPeerDiscovery) {
1879 JAMI_LOG(
"[Account {}] Starting Jami account discovery…", getAccountID());
1880 startAccountDiscovery();
1882 if (conf.accountPublish)
1883 startAccountPublish();
1886 dht::DhtRunner::Context context = initDhtContext();
1888 dht_->run(conf.dhtPort, config, std::move(context));
1889 dhtBoundPort_ = dht_->getBoundPort();
1894 JAMI_LOG(
"[Account {:s}] UPnP: requesting mapping for DHT port {}", getAccountID(), dhtBoundPort_);
1896 if (dhtUpnpMapping_.isValid()) {
1897 upnpCtrl_->releaseMapping(dhtUpnpMapping_);
1900 dhtUpnpMapping_.enableAutoUpdate(
true);
1902 dhtnet::upnp::Mapping desired(dhtnet::upnp::PortType::UDP, dhtBoundPort_, dhtBoundPort_);
1903 dhtUpnpMapping_.updateFrom(desired);
1905 dhtUpnpMapping_.setNotifyCallback([w = weak()](
const dhtnet::upnp::Mapping::sharedPtr_t& mapRes) {
1906 if (
auto accPtr = w.lock()) {
1907 auto& dhtMap = accPtr->dhtUpnpMapping_;
1908 const auto& accId = accPtr->getAccountID();
1910 JAMI_LOG(
"[Account {:s}] DHT UPnP mapping changed to {:s}", accId, mapRes->toString(true));
1912 if (dhtMap.getMapKey() != mapRes->getMapKey() or dhtMap.getState() != mapRes->getState()) {
1913 dhtMap.updateFrom(mapRes);
1914 if (mapRes->getState() == dhtnet::upnp::MappingState::OPEN) {
1915 JAMI_LOG(
"[Account {:s}] Mapping {:s} successfully allocated", accId, dhtMap.toString());
1916 accPtr->dht_->connectivityChanged();
1917 } else if (mapRes->getState() == dhtnet::upnp::MappingState::FAILED) {
1918 JAMI_WARNING(
"[Account {:s}] UPnP mapping failed", accId);
1921 dhtMap.updateFrom(mapRes);
1926 upnpCtrl_->reserveMapping(dhtUpnpMapping_);
1929 for (
const auto& bootstrap : loadBootstrap())
1930 dht_->bootstrap(bootstrap);
1932 accountManager_->setDht(dht_);
1934 if (conf.dhtProxyServerEnabled) {
1935 dht::ProxyServerConfig proxyConfig;
1936 proxyConfig.port = conf.dhtProxyServerPort;
1937 proxyConfig.identity = id_;
1938 dhtProxyServer_ = std::make_shared<dht::DhtProxyServer>(dht_, proxyConfig);
1940 dhtProxyServer_.reset();
1943 std::unique_lock lkCM(connManagerMtx_);
1944 initConnectionManager();
1945 connectionManager_->dhtStarted();
1946 connectionManager_->onICERequest([
this](
const DeviceId& deviceId) {
return onICERequest(deviceId); });
1947 connectionManager_->onChannelRequest([
this](
const std::shared_ptr<dht::crypto::Certificate>& cert,
1948 const std::string& name) {
return onChannelRequest(cert, name); });
1949 connectionManager_->onConnectionReady(
1950 [
this](
const DeviceId& deviceId,
const std::string& name, std::shared_ptr<dhtnet::ChannelSocket> channel) {
1951 onConnectionReady(deviceId, name, std::move(channel));
1955 if (!conf.managerUri.empty() && accountManager_) {
1956 dynamic_cast<ServerAccountManager*
>(accountManager_.get())->onNeedsMigration([
this]() {
1957 editConfig([&](JamiAccountConfig& conf) {
1958 conf.receipt.clear();
1959 conf.receiptSignature.clear();
1961 Migration::setState(accountID_, Migration::State::INVALID);
1962 setRegistrationState(RegistrationState::ERROR_NEED_MIGRATION);
1964 dynamic_cast<ServerAccountManager*
>(accountManager_.get())
1965 ->syncBlueprintConfig([
this](
const std::map<std::string, std::string>& config) {
1966 editConfig([&](JamiAccountConfig& conf) { conf.fromMap(config); });
1967 emitSignal<libjami::ConfigurationSignal::AccountDetailsChanged>(getAccountID(),
getAccountDetails());
1971 if (presenceManager_)
1972 presenceManager_->refresh();
1973 }
catch (
const std::exception& e) {
1974 JAMI_ERR(
"Error registering DHT account: %s", e.what());
1975 setRegistrationState(RegistrationState::ERROR_GENERIC);
1980JamiAccount::lookupRegisteredName(
const std::string& regName,
const NameDirectory::Response& response)
1982 if (response == NameDirectory::Response::found or response == NameDirectory::Response::notFound) {
1983 const auto& nameResult = response == NameDirectory::Response::found ? regName :
"";
1984 if (setRegisteredName(nameResult)) {
1985 editConfig([&](JamiAccountConfig& config) { config.registeredName = nameResult; });
1991dht::DhtRunner::Config
1992JamiAccount::initDhtConfig(
const JamiAccountConfig& conf)
1994 dht::DhtRunner::Config config {};
1995 config.dht_config.node_config.network = 0;
1996 config.dht_config.node_config.maintain_storage =
false;
1997 config.dht_config.node_config.persist_path = (cachePath_ /
"dhtstate").
string();
1998 config.dht_config.id = id_;
1999 config.dht_config.cert_cache_all =
true;
2000 config.push_node_id = getAccountID();
2001 config.push_token = conf.deviceKey;
2002 config.push_topic = conf.notificationTopic;
2003 config.push_platform = conf.platform;
2005 config.threaded =
true;
2006 config.peer_discovery = conf.dhtPeerDiscovery;
2007 config.peer_publish = conf.dhtPeerDiscovery;
2008 if (conf.proxyEnabled)
2009 config.proxy_server = proxyServerCached_;
2011 if (not config.proxy_server.empty()) {
2012 JAMI_LOG(
"[Account {}] Using proxy server {}", getAccountID(), config.proxy_server);
2013 if (not config.push_token.empty()) {
2014 JAMI_LOG(
"[Account {}] using push notifications with platform: {}, topic: {}, token: {}",
2016 config.push_platform,
2024dht::DhtRunner::Context
2025JamiAccount::initDhtContext()
2027 dht::DhtRunner::Context context {};
2028 context.peerDiscovery = peerDiscovery_;
2029 context.rng = std::make_unique<std::mt19937_64>(dht::crypto::getDerivedRandomEngine(rand));
2031 auto dht_log_level = Manager::instance().dhtLogLevel;
2032 if (dht_log_level > 0) {
2033 context.logger = logger_;
2036 context.certificateStore = [&](
const dht::InfoHash& pk_id) {
2037 std::vector<std::shared_ptr<dht::crypto::Certificate>> ret;
2038 if (
auto cert = certStore().getCertificate(pk_id.toString()))
2039 ret.emplace_back(std::move(cert));
2040 JAMI_LOG(
"[Account {}] Query for local certificate store: {}: {} found.",
2047 context.certificateStorePkId = [&](
const DeviceId& pk_id) {
2048 std::vector<std::shared_ptr<dht::crypto::Certificate>> ret;
2049 if (
auto cert = certStore().getCertificate(pk_id.toString()))
2050 ret.emplace_back(std::move(cert));
2051 JAMI_LOG(
"[Account {}] Query for local certificate store: {}: {} found.",
2058 context.statusChangedCallback = [
this](dht::NodeStatus s4, dht::NodeStatus s6) {
2061 auto newStatus = std::max(s4, s6);
2062 switch (newStatus) {
2063 case dht::NodeStatus::Connecting:
2064 state = RegistrationState::TRYING;
2066 case dht::NodeStatus::Connected:
2067 state = RegistrationState::REGISTERED;
2069 case dht::NodeStatus::Disconnected:
2070 state = RegistrationState::UNREGISTERED;
2073 state = RegistrationState::ERROR_GENERIC;
2077 setRegistrationState(state);
2080 context.identityAnnouncedCb = [
this](
bool ok) {
2082 JAMI_ERROR(
"[Account {}] Identity announcement failed", getAccountID());
2085 JAMI_WARNING(
"[Account {}] Identity announcement succeeded", getAccountID());
2087 ->startSync([
this](
const std::shared_ptr<dht::crypto::Certificate>& crt) { onAccountDeviceFound(crt); },
2088 [
this] { onAccountDeviceAnnounced(); },
2096JamiAccount::onAccountDeviceFound(
const std::shared_ptr<dht::crypto::Certificate>& crt)
2101 auto deviceId = crt->getLongId().toString();
2102 if (accountManager_->getInfo()->deviceId == deviceId)
2105 dht::ThreadPool::io().run([w = weak(), deviceId, crt] {
2106 auto shared = w.lock();
2109 std::unique_lock lk(shared->connManagerMtx_);
2110 shared->initConnectionManager();
2112 std::shared_lock slk(shared->connManagerMtx_);
2116 if (!shared->connectionManager_)
2118 shared->requestMessageConnection(shared->getUsername(), crt->getLongId(),
"sync");
2119 if (!shared->syncModule()->isConnected(crt->getLongId())) {
2120 shared->channelHandlers_[Uri::Scheme::SYNC]
2121 ->connect(crt->getLongId(),
2123 [](
const std::shared_ptr<dhtnet::ChannelSocket>& ,
2124 const DeviceId& ) {});
2131JamiAccount::onAccountDeviceAnnounced()
2134 deviceAnnounced_ =
true;
2137 dht::ThreadPool::io().run([w = weak()] {
2138 if (
auto shared = w.lock())
2139 shared->convModule()->bootstrap();
2146JamiAccount::onICERequest(
const DeviceId& deviceId)
2148 std::promise<bool>
accept;
2149 std::future<bool> fut =
accept.get_future();
2150 accountManager_->findCertificate(deviceId, [
this, &accept](
const std::shared_ptr<dht::crypto::Certificate>& cert) {
2155 dht::InfoHash peer_account_id;
2156 auto res = accountManager_->onPeerCertificate(cert, this->config().dhtPublicInCalls, peer_account_id);
2157 JAMI_LOG(
"[Account {}] [device {}] {} ICE request from {}",
2160 res ?
"Accepting" :
"Discarding",
2165 auto result = fut.get();
2170JamiAccount::onChannelRequest(
const std::shared_ptr<dht::crypto::Certificate>& cert,
const std::string& name)
2172 JAMI_LOG(
"[Account {}] [device {}] New channel requested: '{}'", getAccountID(), cert->getLongId(), name);
2174 if (this->config().turnEnabled && turnCache_) {
2175 auto addr = turnCache_->getResolvedTurn();
2176 if (addr == std::nullopt) {
2180 turnCache_->refresh();
2184 auto uri = Uri(name);
2185 std::shared_lock lk(connManagerMtx_);
2186 auto itHandler = channelHandlers_.find(uri.scheme());
2187 if (itHandler != channelHandlers_.end() && itHandler->second)
2188 return itHandler->second->onRequest(cert, name);
2189 return name ==
"sip";
2193JamiAccount::onConnectionReady(
const DeviceId& deviceId,
2194 const std::string& name,
2195 std::shared_ptr<dhtnet::ChannelSocket> channel)
2198 auto cert = channel->peerCertificate();
2199 if (!cert || !cert->issuer)
2201 auto peerId = cert->issuer->getId().toString();
2203 if (accountManager()->getCertificateStatus(peerId) == dhtnet::tls::TrustStore::PermissionStatus::BANNED) {
2204 channel->shutdown();
2207 if (name ==
"sip") {
2208 cacheSIPConnection(std::move(channel), peerId, deviceId);
2209 }
else if (name.find(
"git://") == 0) {
2210 auto sep = name.find_last_of(
'/');
2211 auto conversationId = name.substr(sep + 1);
2212 auto remoteDevice = name.substr(6, sep - 6);
2214 if (channel->isInitiator()) {
2220 if (convModule()->isBanned(conversationId, remoteDevice)) {
2221 JAMI_WARNING(
"[Account {:s}] [Conversation {}] Git server requested, but the "
2222 "device is unauthorized ({:s}) ",
2226 channel->shutdown();
2230 auto sock = convModule()->gitSocket(deviceId.toString(), conversationId);
2231 if (sock == channel) {
2236 JAMI_LOG(
"[Account {:s}] [Conversation {}] [device {}] Git server requested",
2239 deviceId.toString());
2240 auto gs = std::make_unique<GitServer>(accountID_, conversationId, channel);
2241 syncCnt_.fetch_add(1);
2242 gs->setOnFetched([w = weak(), conversationId, deviceId](
const std::string& commit) {
2243 dht::ThreadPool::computation().run([w, conversationId, deviceId, commit]() {
2244 if (
auto shared = w.lock()) {
2245 shared->convModule()->setFetched(conversationId, deviceId.toString(), commit);
2246 if (shared->syncCnt_.fetch_sub(1) == 1) {
2247 emitSignal<libjami::ConversationSignal::ConversationCloned>(shared->getAccountID().c_str());
2252 const dht::Value::Id serverId =
ValueIdDist()(rand);
2254 std::lock_guard lk(gitServersMtx_);
2255 gitServers_[serverId] = std::move(gs);
2257 channel->onShutdown([w = weak(), serverId](
const std::error_code&) {
2259 runOnMainThread([serverId, w]() {
2260 if (
auto sthis = w.lock()) {
2261 std::lock_guard lk(sthis->gitServersMtx_);
2262 sthis->gitServers_.erase(serverId);
2268 std::shared_lock lk(connManagerMtx_);
2269 auto uri = Uri(name);
2270 auto itHandler = channelHandlers_.find(uri.scheme());
2271 if (itHandler != channelHandlers_.end() && itHandler->second)
2272 itHandler->second->onReady(cert, name, std::move(channel));
2278JamiAccount::conversationNeedsSyncing(std::shared_ptr<SyncMsg>&& syncMsg)
2280 dht::ThreadPool::computation().run([w = weak(), syncMsg] {
2281 if (
auto shared = w.lock()) {
2282 const auto& config = shared->config();
2285 if (!config.managerUri.empty() && !syncMsg)
2286 if (auto am = shared->accountManager())
2288 if (auto* sm = shared->syncModule())
2289 sm->syncWithConnected(syncMsg);
2295JamiAccount::conversationSendMessage(
const std::string& uri,
2296 const DeviceId& device,
2297 const std::map<std::string, std::string>& msg,
2303 auto deviceId = device ? device.toString() :
"";
2308JamiAccount::onConversationNeedSocket(
const std::string& convId,
2309 const std::string& deviceId,
2311 const std::string& type)
2313 dht::ThreadPool::io().run([w = weak(), convId, deviceId, cb = std::move(cb), type] {
2314 auto shared = w.lock();
2317 if (
auto socket = shared->convModule()->gitSocket(deviceId, convId)) {
2324 std::shared_lock lkCM(shared->connManagerMtx_);
2325 if (!shared->connectionManager_) {
2331 shared->connectionManager_->connectDevice(
2333 fmt::format(
"git://{}/{}", deviceId, convId),
2334 [w, cb = std::move(cb), convId](std::shared_ptr<dhtnet::ChannelSocket> socket,
const DeviceId&) {
2335 dht::ThreadPool::io().run([w, cb = std::move(cb), socket = std::move(socket), convId] {
2337 socket->onShutdown([w, deviceId = socket->deviceId(), convId](
const std::error_code&) {
2338 dht::ThreadPool::io().run([w, deviceId, convId] {
2339 if (auto shared = w.lock())
2340 shared->convModule()->removeGitSocket(deviceId.toString(), convId);
2356JamiAccount::onConversationNeedSwarmSocket(
const std::string& convId,
2357 const std::string& deviceId,
2359 const std::string& type)
2361 dht::ThreadPool::io().run([w = weak(), convId, deviceId, cb = std::forward<ChannelCb&&>(cb), type] {
2362 auto shared = w.lock();
2365 auto* cm = shared->convModule();
2366 std::shared_lock lkCM(shared->connManagerMtx_);
2367 if (!shared->connectionManager_ || !cm || cm->isBanned(convId, deviceId)) {
2368 asio::post(*Manager::instance().ioContext(), [cb = std::move(cb)] { cb({}); });
2372 auto swarmUri = fmt::format(
"swarm://{}", convId);
2373 if (!shared->connectionManager_->isConnecting(device, swarmUri)) {
2374 shared->connectionManager_->connectDevice(
2379 wam = std::weak_ptr(shared->accountManager())](std::shared_ptr<dhtnet::ChannelSocket> socket,
2380 const DeviceId& deviceId) {
2381 dht::ThreadPool::io().run([w, wam, cb = std::move(cb), socket = std::move(socket), deviceId] {
2383 auto shared = w.lock();
2384 auto am = wam.lock();
2385 auto remoteCert = socket->peerCertificate();
2386 if (!remoteCert || !remoteCert->issuer) {
2390 auto uri = remoteCert->issuer->getId().toString();
2392 || am->getCertificateStatus(uri) == dhtnet::tls::TrustStore::PermissionStatus::BANNED) {
2396 shared->requestMessageConnection(uri, deviceId,
"");
2406JamiAccount::conversationOneToOneReceive(
const std::string& convId,
const std::string& from)
2408 accountManager_->findCertificate(dht::InfoHash(from),
2409 [
this, from, convId](
const std::shared_ptr<dht::crypto::Certificate>& cert) {
2410 const auto* info = accountManager_->getInfo();
2413 info->contacts->onTrustRequest(dht::InfoHash(from),
2414 cert->getSharedPublicKey(),
2423JamiAccount::convModule(
bool noCreation)
2426 return convModule_.get();
2427 if (!accountManager() || currentDeviceId() ==
"") {
2428 JAMI_ERROR(
"[Account {}] Calling convModule() with an uninitialized account", getAccountID());
2431 std::unique_lock lock(configurationMutex_);
2432 std::lock_guard lk(moduleMtx_);
2434 convModule_ = std::make_unique<ConversationModule>(
2437 [
this](
auto&& syncMsg) { conversationNeedsSyncing(std::forward<std::shared_ptr<SyncMsg>>(syncMsg)); },
2438 [
this](
auto&& uri,
auto&& device,
auto&& msg,
auto token = 0) {
2439 return conversationSendMessage(uri, device, msg, token);
2441 [
this](
const auto& convId,
const auto& deviceId,
auto&& cb,
const auto& connectionType) {
2442 onConversationNeedSocket(convId, deviceId, std::forward<
decltype(cb)>(cb), connectionType);
2444 [
this](
const auto& convId,
const auto& deviceId,
auto&& cb,
const auto& connectionType) {
2445 onConversationNeedSwarmSocket(convId, deviceId, std::forward<
decltype(cb)>(cb), connectionType);
2447 [
this](
const auto& convId,
const auto& from) { conversationOneToOneReceive(convId, from); },
2448 autoLoadConversations_);
2450 return convModule_.get();
2454JamiAccount::syncModule()
2456 if (!accountManager() || currentDeviceId() ==
"") {
2457 JAMI_ERR() <<
"Calling syncModule() with an uninitialized account.";
2460 std::lock_guard lk(moduleMtx_);
2462 syncModule_ = std::make_unique<SyncModule>(shared());
2463 return syncModule_.get();
2467JamiAccount::onTextMessage(
const std::string&
id,
2468 const std::string& from,
2469 const std::shared_ptr<dht::crypto::Certificate>& peerCert,
2470 const std::map<std::string, std::string>& payloads)
2474 SIPAccountBase::onTextMessage(
id, fromUri, peerCert, payloads);
2480JamiAccount::loadConversation(
const std::string& convId)
2482 if (
auto* cm = convModule(
true))
2483 cm->loadSingleConversation(convId);
2487JamiAccount::doUnregister(
bool forceShutdownConnections)
2489 std::unique_lock lock(configurationMutex_);
2490 if (registrationState_ >= RegistrationState::ERROR_GENERIC) {
2495 std::condition_variable cv;
2496 bool shutdown_complete {
false};
2498 if (peerDiscovery_) {
2503 JAMI_WARN(
"[Account %s] Unregistering account %p", getAccountID().c_str(),
this);
2506 JAMI_WARN(
"[Account %s] DHT shutdown complete", getAccountID().c_str());
2507 std::lock_guard lock(mtx);
2508 shutdown_complete =
true;
2514 std::lock_guard lk(pendingCallsMutex_);
2515 pendingCalls_.clear();
2521 if (not isEnabled() || forceShutdownConnections)
2522 shutdownConnections();
2525 if (upnpCtrl_ and dhtUpnpMapping_.isValid()) {
2526 upnpCtrl_->releaseMapping(dhtUpnpMapping_);
2530 std::unique_lock lock(mtx);
2531 cv.wait(lock, [&] {
return shutdown_complete; });
2534 setRegistrationState(RegistrationState::UNREGISTERED);
2544JamiAccount::setRegistrationState(
RegistrationState state,
int detail_code,
const std::string& detail_str)
2546 if (registrationState_ != state) {
2547 if (state == RegistrationState::REGISTERED) {
2548 JAMI_WARNING(
"[Account {}] Connected", getAccountID());
2549 turnCache_->refresh();
2550 if (connectionManager_)
2551 connectionManager_->storeActiveIpAddress();
2552 }
else if (state == RegistrationState::TRYING) {
2553 JAMI_WARNING(
"[Account {}] Connecting…", getAccountID());
2555 deviceAnnounced_ =
false;
2556 JAMI_WARNING(
"[Account {}] Disconnected", getAccountID());
2560 Account::setRegistrationState(state, detail_code, detail_str);
2564JamiAccount::reloadContacts()
2566 accountManager_->reloadContacts();
2570JamiAccount::connectivityChanged()
2572 if (not isUsable()) {
2576 JAMI_WARNING(
"[{}] connectivityChanged", getAccountID());
2578 if (
auto* cm = convModule())
2579 cm->connectivityChanged();
2580 dht_->connectivityChanged();
2582 std::shared_lock lkCM(connManagerMtx_);
2583 if (connectionManager_) {
2584 connectionManager_->connectivityChanged();
2586 connectionManager_->setPublishedAddress({});
2592JamiAccount::findCertificate(
const dht::InfoHash& h,
2593 std::function<
void(
const std::shared_ptr<dht::crypto::Certificate>&)>&& cb)
2595 if (accountManager_)
2596 return accountManager_->findCertificate(h, std::move(cb));
2601JamiAccount::findCertificate(
const dht::PkId&
id,
2602 std::function<
void(
const std::shared_ptr<dht::crypto::Certificate>&)>&& cb)
2604 if (accountManager_)
2605 return accountManager_->findCertificate(
id, std::move(cb));
2610JamiAccount::findCertificate(
const std::string& crt_id)
2612 if (accountManager_)
2613 return accountManager_->findCertificate(dht::InfoHash(crt_id));
2618JamiAccount::setCertificateStatus(
const std::string& cert_id, dhtnet::tls::TrustStore::PermissionStatus status)
2620 bool done = accountManager_ ? accountManager_->setCertificateStatus(cert_id, status) :
false;
2622 findCertificate(cert_id);
2623 emitSignal<libjami::ConfigurationSignal::CertificateStateChanged>(getAccountID(),
2625 dhtnet::tls::TrustStore::statusToStr(status));
2631JamiAccount::setCertificateStatus(
const std::shared_ptr<crypto::Certificate>& cert,
2632 dhtnet::tls::TrustStore::PermissionStatus status,
2635 bool done = accountManager_ ? accountManager_->setCertificateStatus(cert, status, local) :
false;
2637 findCertificate(cert->getLongId().toString());
2638 emitSignal<libjami::ConfigurationSignal::CertificateStateChanged>(getAccountID(),
2639 cert->getLongId().toString(),
2640 dhtnet::tls::TrustStore::statusToStr(status));
2645std::vector<std::string>
2646JamiAccount::getCertificatesByStatus(dhtnet::tls::TrustStore::PermissionStatus status)
2648 if (accountManager_)
2649 return accountManager_->getCertificatesByStatus(status);
2654JamiAccount::isMessageTreated(dht::Value::Id
id)
2656 std::lock_guard lock(messageMutex_);
2657 return !treatedMessages_.add(
id);
2661JamiAccount::sha3SumVerify()
const
2663 return !noSha3sumVerification_;
2668JamiAccount::noSha3sumVerification(
bool newValue)
2670 noSha3sumVerification_ = newValue;
2674std::map<std::string, std::string>
2675JamiAccount::getKnownDevices()
const
2677 std::lock_guard lock(configurationMutex_);
2678 if (not accountManager_ or not accountManager_->getInfo())
2680 std::map<std::string, std::string> ids;
2681 for (
const auto& d : accountManager_->getKnownDevices()) {
2682 auto id = d.first.toString();
2683 auto label = d.second.name.empty() ?
id.substr(0, 8) : d.second.name;
2684 ids.emplace(std::move(
id), std::move(label));
2690JamiAccount::loadCachedUrl(
const std::string& url,
2691 const std::filesystem::path& cachePath,
2692 const std::chrono::seconds& cacheDuration,
2693 const std::function<
void(
const dht::http::Response& response)>& cb)
2695 dht::ThreadPool::io().run([cb, url, cachePath, cacheDuration, w = weak()]() {
2699 std::lock_guard lk(dhtnet::fileutils::getFileLock(cachePath));
2700 data = fileutils::loadCacheTextFile(cachePath, cacheDuration);
2702 dht::http::Response ret;
2703 ret.body = std::move(data);
2704 ret.status_code = 200;
2706 }
catch (
const std::exception& e) {
2707 JAMI_LOG(
"Failed to load '{}' from '{}': {}", url, cachePath, e.what());
2709 if (
auto sthis = w.lock()) {
2710 auto req = std::make_shared<dht::http::Request>(
2711 *Manager::instance().ioContext(), url, [cb, cachePath, w](
const dht::http::Response& response) {
2712 if (response.status_code == 200) {
2714 std::lock_guard lk(dhtnet::fileutils::getFileLock(cachePath));
2715 fileutils::saveFile(cachePath,
2716 (const uint8_t*) response.body.data(),
2717 response.body.size(),
2719 JAMI_LOG(
"Cached result to '{}'", cachePath);
2720 } catch (
const std::exception& ex) {
2721 JAMI_WARNING(
"Failed to save result to '{}': {}", cachePath, ex.what());
2726 if (std::filesystem::exists(cachePath)) {
2727 JAMI_WARNING(
"Failed to download URL, using cached data");
2730 std::lock_guard lk(dhtnet::fileutils::getFileLock(cachePath));
2731 data = fileutils::loadTextFile(cachePath);
2733 dht::http::Response ret;
2734 ret.body = std::move(data);
2735 ret.status_code = 200;
2738 throw std::runtime_error(
"No cached data");
2743 if (
auto req = response.request.lock())
2744 if (
auto sthis = w.lock())
2745 sthis->requests_.erase(req);
2747 sthis->requests_.emplace(req);
2755JamiAccount::loadCachedProxyServer(std::function<
void(
const std::string& proxy)> cb)
2757 const auto& conf = config();
2758 if (conf.proxyEnabled and proxyServerCached_.empty()) {
2759 JAMI_DEBUG(
"[Account {:s}] Loading DHT proxy URL: {:s}", getAccountID(), conf.proxyListUrl);
2760 if (conf.proxyListUrl.empty() or not conf.proxyListEnabled) {
2761 cb(getDhtProxyServer(conf.proxyServer));
2763 loadCachedUrl(conf.proxyListUrl,
2764 cachePath_ /
"dhtproxylist",
2765 std::chrono::hours(24 * 3),
2766 [w = weak(), cb = std::move(cb)](
const dht::http::Response& response) {
2767 if (
auto sthis = w.lock()) {
2768 if (response.status_code == 200) {
2769 cb(sthis->getDhtProxyServer(response.body));
2771 cb(sthis->getDhtProxyServer(sthis->config().proxyServer));
2777 cb(proxyServerCached_);
2782JamiAccount::getDhtProxyServer(
const std::string& serverList)
2784 if (proxyServerCached_.empty()) {
2785 std::vector<std::string> proxys;
2787 std::sregex_iterator begin = {serverList.begin(), serverList.end(),
PROXY_REGEX}, end;
2788 for (
auto it = begin; it != end; ++it) {
2790 if (match[5].matched and match[6].matched) {
2792 auto start = std::stoi(match[5]), end = std::stoi(match[6]);
2793 for (
auto p = start; p <= end; p++)
2794 proxys.emplace_back(match[1].str() + match[2].str() +
":" + std::to_string(p));
2796 JAMI_WARN(
"Malformed proxy, ignore it");
2800 proxys.emplace_back(match[0].str());
2806 auto randIt = proxys.begin();
2807 std::advance(randIt, std::uniform_int_distribution<unsigned long>(0, proxys.size() - 1)(rand));
2808 proxyServerCached_ = *randIt;
2810 dhtnet::fileutils::check_dir(cachePath_, 0700);
2811 auto proxyCachePath = cachePath_ /
"dhtproxy";
2812 std::ofstream file(proxyCachePath);
2813 JAMI_DEBUG(
"Cache DHT proxy server: {}", proxyServerCached_);
2814 Json::Value node(Json::objectValue);
2815 node[getProxyConfigKey()] = proxyServerCached_;
2819 JAMI_WARNING(
"Unable to write into {}", proxyCachePath);
2821 return proxyServerCached_;
2825JamiAccount::matches(std::string_view userName, std::string_view server)
const
2827 if (not accountManager_ or not accountManager_->getInfo())
2828 return MatchRank::NONE;
2830 if (userName == accountManager_->getInfo()->accountId || server == accountManager_->getInfo()->accountId
2831 || userName == accountManager_->getInfo()->deviceId) {
2832 JAMI_LOG(
"Matching account ID in request with username {}", userName);
2833 return MatchRank::FULL;
2835 return MatchRank::NONE;
2840JamiAccount::getFromUri()
const
2842 std::string uri =
"<sip:" + accountManager_->getInfo()->accountId +
"@ring.dht>";
2843 if (not config().displayName.empty())
2844 return "\"" + config().displayName +
"\" " + uri;
2849JamiAccount::getToUri(
const std::string& to)
const
2853 return fmt::format(
"<sips:{};transport=tls>", username);
2857getDisplayed(
const std::string& conversationId,
const std::string& messageId)
2860 return fmt::format(
"<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n"
2861 "<imdn><message-id>{}</message-id>\n"
2863 "<display-notification><status><displayed/></status></display-notification>\n"
2866 conversationId.empty() ?
"" :
"<conversation>" + conversationId +
"</conversation>");
2873 return fmt::format(
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
2874 "<presence xmlns=\"urn:ietf:params:xml:ns:pidf\">\n"
2877 " <basic>{}</basic>\n"
2885JamiAccount::setIsComposing(
const std::string& conversationUri,
bool isWriting)
2887 Uri uri(conversationUri);
2888 std::string conversationId = {};
2889 if (uri.
scheme() == Uri::Scheme::SWARM) {
2895 if (
auto* cm = convModule(
true)) {
2896 if (
auto typer = cm->getTypers(conversationId)) {
2898 typer->addTyper(getUsername(),
true);
2900 typer->removeTyper(getUsername(),
true);
2906JamiAccount::setMessageDisplayed(
const std::string& conversationUri,
const std::string& messageId,
int status)
2908 Uri uri(conversationUri);
2909 std::string conversationId = {};
2910 if (uri.
scheme() == Uri::Scheme::SWARM)
2913 if (!conversationId.empty())
2914 sendMessage &= convModule()->onMessageDisplayed(getUsername(), conversationId, messageId);
2916 sendInstantMessage(uri.
authority(), {{MIME_TYPE_IMDN, getDisplayed(conversationId, messageId)}});
2921JamiAccount::getContactHeader(
const std::shared_ptr<SipTransport>& sipTransport)
2923 if (sipTransport and sipTransport->get() !=
nullptr) {
2924 auto* transport = sipTransport->get();
2927 bool reliable = transport->flag & PJSIP_TRANSPORT_RELIABLE;
2928 return fmt::format(
"\"{}\" <sips:{}{}{};transport={}>",
2929 config().displayName,
2930 id_.second->getId().toString(),
2931 address.empty() ?
"" :
"@",
2933 reliable ?
"tls" :
"dtls");
2935 JAMI_ERR(
"getContactHeader: no SIP transport provided");
2936 return fmt::format(
"\"{}\" <sips:{}@ring.dht>", config().displayName, id_.second->getId().toString());
2941JamiAccount::addContact(
const std::string& uri,
bool confirmed)
2943 dht::InfoHash h(uri);
2945 JAMI_ERROR(
"addContact: invalid contact URI");
2948 auto conversation = convModule()->getOneToOneConversation(uri);
2949 if (!confirmed && conversation.empty())
2950 conversation = convModule()->startConversation(ConversationMode::ONE_TO_ONE, h);
2951 std::unique_lock lock(configurationMutex_);
2952 if (accountManager_)
2953 accountManager_->addContact(h, confirmed, conversation);
2955 JAMI_WARNING(
"[Account {}] addContact: account not loaded", getAccountID());
2959JamiAccount::removeContact(
const std::string& uri,
bool ban)
2961 std::lock_guard lock(configurationMutex_);
2962 if (accountManager_)
2963 accountManager_->removeContact(uri, ban);
2965 JAMI_WARNING(
"[Account {}] removeContact: account not loaded", getAccountID());
2968std::map<std::string, std::string>
2969JamiAccount::getContactDetails(
const std::string& uri)
const
2971 std::lock_guard lock(configurationMutex_);
2972 return accountManager_ ? accountManager_->getContactDetails(uri) : std::map<std::string, std::string> {};
2975std::optional<Contact>
2976JamiAccount::getContactInfo(
const std::string& uri)
const
2978 std::lock_guard lock(configurationMutex_);
2979 return accountManager_ ? accountManager_->getContactInfo(uri) : std::nullopt;
2982std::vector<std::map<std::string, std::string>>
2983JamiAccount::getContacts(
bool includeRemoved)
const
2985 std::lock_guard lock(configurationMutex_);
2986 if (not accountManager_)
2988 const auto& contacts = accountManager_->getContacts(includeRemoved);
2989 std::vector<std::map<std::string, std::string>> ret;
2990 ret.reserve(contacts.size());
2991 for (
const auto& c : contacts) {
2992 auto details = c.second.toMap();
2993 if (not details.empty()) {
2994 details[
"id"] = c.first.toString();
2995 ret.emplace_back(std::move(details));
3003std::vector<std::map<std::string, std::string>>
3004JamiAccount::getTrustRequests()
const
3006 std::lock_guard lock(configurationMutex_);
3007 return accountManager_ ? accountManager_->getTrustRequests() : std::vector<std::map<std::string, std::string>> {};
3011JamiAccount::acceptTrustRequest(
const std::string& from,
bool includeConversation)
3013 dht::InfoHash h(from);
3015 JAMI_ERROR(
"addContact: invalid contact URI");
3018 std::unique_lock lock(configurationMutex_);
3019 if (accountManager_) {
3020 if (!accountManager_->acceptTrustRequest(from, includeConversation)) {
3023 return accountManager_->addContact(h,
true);
3027 JAMI_WARNING(
"[Account {}] acceptTrustRequest: account not loaded", getAccountID());
3032JamiAccount::discardTrustRequest(
const std::string& from)
3035 auto requests = getTrustRequests();
3036 for (
const auto& req : requests) {
3043 std::lock_guard lock(configurationMutex_);
3044 if (accountManager_)
3045 return accountManager_->discardTrustRequest(from);
3046 JAMI_WARNING(
"[Account {:s}] discardTrustRequest: account not loaded", getAccountID());
3051JamiAccount::declineConversationRequest(
const std::string& conversationId)
3053 auto peerId = convModule()->peerFromConversationRequest(conversationId);
3054 convModule()->declineConversationRequest(conversationId);
3055 if (!peerId.empty()) {
3056 std::lock_guard lock(configurationMutex_);
3057 if (
const auto* info = accountManager_->getInfo()) {
3059 auto req = info->contacts->getTrustRequest(dht::InfoHash(peerId));
3062 accountManager_->discardTrustRequest(peerId);
3063 JAMI_DEBUG(
"[Account {:s}] Declined trust request with {:s}", getAccountID(), peerId);
3070JamiAccount::sendTrustRequest(
const std::string& to,
const std::vector<uint8_t>& payload)
3072 dht::InfoHash h(to);
3074 JAMI_ERROR(
"addContact: invalid contact URI");
3078 auto requestPath = cachePath_ /
"requests";
3079 dhtnet::fileutils::recursive_mkdir(requestPath, 0700);
3080 auto cachedFile = requestPath / to;
3081 std::ofstream req(cachedFile, std::ios::trunc | std::ios::binary);
3082 if (!req.is_open()) {
3083 JAMI_ERROR(
"Unable to write data to {}", cachedFile);
3087 if (not payload.empty()) {
3088 req.write(
reinterpret_cast<const char*
>(payload.data()),
static_cast<std::streamsize
>(payload.size()));
3091 if (payload.size() >= 64000) {
3092 JAMI_WARN() <<
"Trust request is too big. Remove payload";
3095 auto conversation = convModule()->getOneToOneConversation(to);
3096 if (conversation.empty())
3097 conversation = convModule()->startConversation(ConversationMode::ONE_TO_ONE, h);
3098 if (not conversation.empty()) {
3099 std::lock_guard lock(configurationMutex_);
3100 if (accountManager_)
3101 accountManager_->sendTrustRequest(to,
3103 payload.size() >= 64000 ? std::vector<uint8_t> {} : payload);
3105 JAMI_WARNING(
"[Account {}] sendTrustRequest: account not loaded", getAccountID());
3107 JAMI_WARNING(
"[Account {}] sendTrustRequest: account not loaded", getAccountID());
3111JamiAccount::forEachDevice(
const dht::InfoHash& to,
3112 std::function<
void(
const std::shared_ptr<dht::crypto::PublicKey>&)>&& op,
3113 std::function<
void(
bool)>&& end)
3115 accountManager_->forEachDevice(to, std::move(op), std::move(end));
3119JamiAccount::sendTextMessage(
const std::string& to,
3120 const std::string& deviceId,
3121 const std::map<std::string, std::string>& payloads,
3122 uint64_t refreshToken,
3126 if (uri.
scheme() == Uri::Scheme::SWARM) {
3127 sendInstantMessage(uri.
authority(), payloads);
3135 JAMI_ERROR(
"Failed to send a text message due to an invalid URI {}", to);
3138 if (payloads.size() != 1) {
3139 JAMI_ERROR(
"Multi-part im is not supported yet by JamiAccount");
3142 return SIPAccountBase::sendTextMessage(toUri, deviceId, payloads, refreshToken, onlyConnected);
3146JamiAccount::sendMessage(
const std::string& to,
3147 const std::string& deviceId,
3148 const std::map<std::string, std::string>& payloads,
3150 bool retryOnTimeout,
3157 JAMI_ERROR(
"[Account {}] Failed to send a text message due to an invalid URI {}", getAccountID(), to);
3159 messageEngine_.onMessageSent(to, token,
false, deviceId);
3162 if (payloads.size() != 1) {
3163 JAMI_ERROR(
"Multi-part im is not supported");
3165 messageEngine_.onMessageSent(toUri, token,
false, deviceId);
3170 std::shared_lock clk(connManagerMtx_);
3175 messageEngine_.onMessageSent(to, token,
false, deviceId);
3179 auto devices = std::make_shared<SendMessageContext>(
3180 [w = weak(), to, token, deviceId, onlyConnected, retryOnTimeout](
bool success,
bool sent) {
3181 if (
auto acc = w.lock())
3182 acc->onMessageSent(to, token, deviceId, success, onlyConnected, sent && retryOnTimeout);
3185 auto completed = [w = weak(), to, devices](
const DeviceId& device,
3186 const std::shared_ptr<dhtnet::ChannelSocket>& conn,
3189 if (
auto acc = w.lock()) {
3190 std::shared_lock clk(acc->connManagerMtx_);
3192 acc->channelHandlers_[Uri::Scheme::MESSAGE].get())) {
3193 handler->closeChannel(to, device, conn);
3196 devices->complete(device, success);
3199 const auto& payload = *payloads.begin();
3200 auto msg = std::make_shared<MessageChannelHandler::Message>();
3202 msg->t = payload.first;
3203 msg->c = payload.second;
3205 if (deviceId.empty()) {
3206 auto conns = handler->getChannels(toUri);
3208 for (
const auto& conn : conns) {
3209 auto connDevice = conn->deviceId();
3210 if (!devices->add(connDevice))
3212 dht::ThreadPool::io().run([completed, connDevice, conn, msg] {
3213 completed(connDevice, conn, MessageChannelHandler::sendMessage(conn, *msg));
3217 if (
auto conn = handler->getChannel(toUri, device)) {
3219 devices->add(device);
3220 dht::ThreadPool::io().run([completed, device, conn, msg] {
3221 completed(device, conn, MessageChannelHandler::sendMessage(conn, *msg));
3238 auto extractIdFromJson = [](
const std::string& jsonData) -> std::string {
3240 if (json::parse(jsonData, parsed)) {
3241 auto value = parsed.get(
"id", Json::nullValue);
3242 if (value && value.isString()) {
3243 return value.asString();
3246 JAMI_WARNING(
"Unable to parse jsonData to get conversation ID");
3252 auto payload_type = msg->t;
3253 if (payload_type == MIME_TYPE_GIT) {
3254 std::string
id = extractIdFromJson(msg->c);
3256 payload_type +=
"/" + id;
3260 if (deviceId.empty()) {
3261 auto toH = dht::InfoHash(toUri);
3263 accountManager_->forEachDevice(toH,
3264 [
this, to, devices, payload_type, currentDevice =
DeviceId(currentDeviceId())](
3265 const std::shared_ptr<dht::crypto::PublicKey>&
dev) {
3267 auto deviceId =
dev->getLongId();
3268 if (deviceId == currentDevice || devices->pending(deviceId)) {
3273 dht::ThreadPool::io().run([
this, to, deviceId, payload_type]() {
3274 requestMessageConnection(to, deviceId, payload_type);
3278 requestMessageConnection(to, device, payload_type);
3283JamiAccount::onMessageSent(
3284 const std::string& to, uint64_t
id,
const std::string& deviceId,
bool success,
bool onlyConnected,
bool retry)
3287 messageEngine_.onMessageSent(to,
id, success, deviceId);
3291 messageEngine_.onPeerOnline(to, deviceId);
3295dhtnet::IceTransportOptions
3296JamiAccount::getIceOptions()
const
3298 return connectionManager_->getIceOptions();
3302JamiAccount::getIceOptions(std::function<
void(dhtnet::IceTransportOptions&&)> cb)
const
3304 return connectionManager_->getIceOptions(std::move(cb));
3308JamiAccount::getPublishedIpAddress(uint16_t family)
const
3310 return connectionManager_->getPublishedIpAddress(family);
3314JamiAccount::setPushNotificationToken(
const std::string& token)
3316 if (SIPAccountBase::setPushNotificationToken(token)) {
3317 JAMI_WARNING(
"[Account {:s}] setPushNotificationToken: {:s}", getAccountID(), token);
3319 dht_->setPushNotificationToken(token);
3326JamiAccount::setPushNotificationTopic(
const std::string& topic)
3328 if (SIPAccountBase::setPushNotificationTopic(topic)) {
3330 dht_->setPushNotificationTopic(topic);
3337JamiAccount::setPushNotificationConfig(
const std::map<std::string, std::string>& data)
3339 if (SIPAccountBase::setPushNotificationConfig(data)) {
3341 dht_->setPushNotificationPlatform(config_->platform);
3342 dht_->setPushNotificationTopic(config_->notificationTopic);
3343 dht_->setPushNotificationToken(config_->deviceKey);
3354JamiAccount::pushNotificationReceived(
const std::string& ,
const std::map<std::string, std::string>& data)
3356 auto ret_future = dht_->pushNotificationReceived(data);
3357 dht::ThreadPool::computation().run([
id = getAccountID(), ret_future = ret_future.share()] {
3358 JAMI_WARNING(
"[Account {:s}] pushNotificationReceived: {}", id, (uint8_t) ret_future.get());
3363JamiAccount::getUserUri()
const
3365 if (not registeredName_.empty())
3370std::vector<libjami::Message>
3371JamiAccount::getLastMessages(
const uint64_t& base_timestamp)
3373 return SIPAccountBase::getLastMessages(base_timestamp);
3377JamiAccount::startAccountPublish()
3380 info_pub.
accountId = dht::InfoHash(accountManager_->getInfo()->accountId);
3386JamiAccount::startAccountDiscovery()
3388 auto id = dht::InfoHash(accountManager_->getInfo()->accountId);
3391 std::lock_guard lc(discoveryMapMtx_);
3393 if (v.accountId !=
id) {
3395 auto& dp = discoveredPeers_[v.accountId];
3396 dp.displayName = v.displayName;
3397 discoveredPeerMap_[v.accountId.toString()] = v.displayName;
3398 if (!dp.cleanupTimer) {
3400 JAMI_LOG(
"Account discovered: {}: {}", v.displayName, v.accountId.to_c_str());
3402 emitSignal<libjami::PresenceSignal::NearbyPeerNotification>(getAccountID(),
3403 v.accountId.toString(),
3406 dp.cleanupTimer = std::make_unique<asio::steady_timer>(*Manager::instance().ioContext(),
3410 dp.cleanupTimer->async_wait(
3411 [w = weak(), p = v.accountId, a = v.displayName](
const asio::error_code& ec) {
3414 if (auto this_ = w.lock()) {
3416 std::lock_guard lc(this_->discoveryMapMtx_);
3417 this_->discoveredPeers_.erase(p);
3418 this_->discoveredPeerMap_.erase(p.toString());
3421 emitSignal<libjami::PresenceSignal::NearbyPeerNotification>(this_->getAccountID(),
3426 JAMI_INFO(
"Account removed from discovery list: %s", a.c_str());
3432std::map<std::string, std::string>
3433JamiAccount::getNearbyPeers()
const
3435 return discoveredPeerMap_;
3439JamiAccount::sendProfileToPeers()
3441 if (!connectionManager_)
3443 std::set<std::string> peers;
3444 const auto& accountUri = accountManager_->getInfo()->
accountId;
3446 for (
const auto& connection : connectionManager_->getConnectionList()) {
3447 const auto& device = connection.at(
"device");
3448 const auto& peer = connection.at(
"peer");
3449 if (!peers.emplace(peer).second)
3451 if (peer == accountUri) {
3452 sendProfile(
"", accountUri, device);
3455 const auto& conversationId = convModule()->getOneToOneConversation(peer);
3456 if (!conversationId.empty()) {
3457 sendProfile(conversationId, peer, device);
3463JamiAccount::updateProfile(
const std::string& displayName,
3464 const std::string& avatar,
3465 const std::string& fileType,
3470 const auto& accountUri = accountManager_->getInfo()->accountId;
3471 const auto& path = profilePath();
3472 const auto& profiles = idPath_ /
"profiles";
3475 if (!std::filesystem::exists(profiles)) {
3476 std::filesystem::create_directories(profiles);
3478 }
catch (
const std::exception& e) {
3479 JAMI_ERROR(
"Failed to create profiles directory: {}", e.what());
3483 const auto& vCardPath = profiles / fmt::format(
"{}.vcf", base64::encode(accountUri));
3485 auto profile = getProfileVcard();
3486 if (profile.empty()) {
3487 profile = vCard::utils::initVcard();
3490 profile[std::string(vCard::Property::FORMATTED_NAME)] = displayName;
3492 emitSignal<libjami::ConfigurationSignal::AccountDetailsChanged>(getAccountID(), getAccountDetails());
3494 if (!fileType.empty()) {
3495 const std::string& key =
"PHOTO;ENCODING=BASE64;TYPE=" + fileType;
3497 vCard::utils::removeByKey(profile, vCard::Property::PHOTO);
3498 const auto& avatarPath = std::filesystem::path(avatar);
3499 if (std::filesystem::exists(avatarPath)) {
3501 profile[key] = base64::encode(fileutils::loadFile(avatarPath));
3502 }
catch (
const std::exception& e) {
3503 JAMI_ERROR(
"Failed to load avatar: {}", e.what());
3506 }
else if (flag == 1) {
3507 vCard::utils::removeByKey(profile, vCard::Property::PHOTO);
3508 profile[key] = avatar;
3512 vCard::utils::removeByKey(profile, vCard::Property::PHOTO);
3515 vCard::utils::save(profile, vCardPath, path);
3516 emitSignal<libjami::ConfigurationSignal::ProfileReceived>(getAccountID(), accountUri, path.string());
3520 std::filesystem::remove_all(cachePath_ /
"vcard", ec);
3521 sendProfileToPeers();
3522 }
catch (
const std::exception& e) {
3523 JAMI_ERROR(
"Error writing profile: {}", e.what());
3528JamiAccount::setActiveCodecs(
const std::vector<unsigned>& list)
3530 Account::setActiveCodecs(list);
3532 setCodecActive(AV_CODEC_ID_OPUS);
3534 setCodecActive(AV_CODEC_ID_HEVC);
3535 setCodecActive(AV_CODEC_ID_H264);
3536 setCodecActive(AV_CODEC_ID_VP8);
3538 config_->activeCodecs = getActiveCodecs(
MEDIA_ALL);
3542JamiAccount::sendInstantMessage(
const std::string& convId,
const std::map<std::string, std::string>& msg)
3544 auto members = convModule()->getConversationMembers(convId);
3545 if (convId.empty() && members.empty()) {
3547 sendTextMessage(convId,
"", msg);
3550 for (
const auto& m : members) {
3551 const auto& uri = m.at(
"uri");
3552 auto token = std::uniform_int_distribution<uint64_t> {1, JAMI_ID_MAX_VAL}(rand);
3554 sendMessage(uri,
"", msg, token,
false,
true);
3559JamiAccount::handleMessage(
const std::shared_ptr<dht::crypto::Certificate>& cert,
3560 const std::string& from,
3561 const std::pair<std::string, std::string>& m)
3563 if (not cert or not cert->issuer)
3566 if (cert->issuer->getId().to_view() != from) {
3567 JAMI_WARNING(
"[Account {}] [device {}] handleMessage: invalid author {}",
3569 cert->issuer->getId().to_view(),
3573 if (m.first == MIME_TYPE_GIT) {
3575 if (!json::parse(m.second, json)) {
3580 dht::ThreadPool::io().run([w = weak(),
3582 deviceId = json[
"deviceId"].asString(),
3583 id = json[
"id"].asString(),
3584 commit = json[
"commit"].asString()] {
3585 if (
auto shared = w.lock()) {
3586 if (auto* cm = shared->convModule())
3587 cm->fetchNewCommits(from, deviceId, id, commit);
3591 }
else if (m.first == MIME_TYPE_INVITE) {
3592 convModule()->onNeedConversationRequest(from, m.second);
3596 if (!json::parse(m.second, json)) {
3599 convModule()->onConversationRequest(from, json);
3601 }
else if (m.first == MIME_TYPE_IM_COMPOSING) {
3603 static const std::regex COMPOSING_REGEX(
"<state>\\s*(\\w+)\\s*<\\/state>");
3604 std::smatch matched_pattern;
3606 bool isComposing {
false};
3607 if (matched_pattern.ready() && !matched_pattern.empty() && matched_pattern[1].matched) {
3608 isComposing = matched_pattern[1] ==
"active";
3610 static const std::regex CONVID_REGEX(
"<conversation>\\s*(\\w+)\\s*<\\/conversation>");
3612 std::string conversationId =
"";
3613 if (matched_pattern.ready() && !matched_pattern.empty() && matched_pattern[1].matched) {
3614 conversationId = matched_pattern[1];
3616 if (!conversationId.empty()) {
3617 if (
auto* cm = convModule(
true)) {
3618 if (
auto typer = cm->getTypers(conversationId)) {
3620 typer->addTyper(from);
3622 typer->removeTyper(from);
3627 }
catch (
const std::exception& e) {
3628 JAMI_WARNING(
"Error parsing composing state: {}", e.what());
3632 static const std::regex IMDN_MSG_ID_REGEX(
"<message-id>\\s*(\\w+)\\s*<\\/message-id>");
3633 std::smatch matched_pattern;
3636 std::string messageId;
3637 if (matched_pattern.ready() && !matched_pattern.empty() && matched_pattern[1].matched) {
3638 messageId = matched_pattern[1];
3640 JAMI_WARNING(
"Message displayed: unable to parse message ID");
3644 static const std::regex STATUS_REGEX(
"<status>\\s*<(\\w+)\\/>\\s*<\\/status>");
3646 bool isDisplayed {
false};
3647 if (matched_pattern.ready() && !matched_pattern.empty() && matched_pattern[1].matched) {
3648 isDisplayed = matched_pattern[1] ==
"displayed";
3650 JAMI_WARNING(
"Message displayed: unable to parse status");
3654 static const std::regex CONVID_REGEX(
"<conversation>\\s*(\\w+)\\s*<\\/conversation>");
3656 std::string conversationId =
"";
3657 if (matched_pattern.ready() && !matched_pattern.empty() && matched_pattern[1].matched) {
3658 conversationId = matched_pattern[1];
3661 if (!isReadReceiptEnabled())
3664 if (convModule()->onMessageDisplayed(from, conversationId, messageId)) {
3665 JAMI_DEBUG(
"[message {}] Displayed by peer", messageId);
3666 emitSignal<libjami::ConfigurationSignal::AccountMessageStatusChanged>(
3675 }
catch (
const std::exception& e) {
3676 JAMI_ERROR(
"Error parsing display notification: {}", e.what());
3679 std::smatch matched_pattern;
3680 static const std::regex BASIC_REGEX(
"<basic>([\\w\\s]+)<\\/basic>");
3682 std::string customStatus {};
3683 if (matched_pattern.ready() && !matched_pattern.empty() && matched_pattern[1].matched) {
3684 customStatus = matched_pattern[1];
3685 emitSignal<libjami::PresenceSignal::NewBuddyNotification>(getAccountID(),
3687 static_cast<int>(PresenceState::CONNECTED),
3699JamiAccount::callConnectionClosed(
const DeviceId& deviceId,
bool eraseDummy)
3701 std::function<void(
const DeviceId&,
bool)> cb;
3703 std::lock_guard lk(onConnectionClosedMtx_);
3704 auto it = onConnectionClosed_.find(deviceId);
3705 if (it != onConnectionClosed_.end()) {
3707 cb = std::move(it->second);
3708 onConnectionClosed_.erase(it);
3716 dht::ThreadPool::io().run([w = weak(), cb = std::move(cb),
id = deviceId, erase = std::move(eraseDummy)] {
3717 if (
auto acc = w.lock()) {
3725JamiAccount::requestMessageConnection(
const std::string& peerId,
3726 const DeviceId& deviceId,
3727 const std::string& connectionType)
3729 std::shared_lock lk(connManagerMtx_);
3730 auto* handler =
static_cast<MessageChannelHandler*
>(channelHandlers_[Uri::Scheme::MESSAGE].get());
3734 if (
auto connected = handler->getChannel(peerId, deviceId)) {
3738 auto connected = handler->getChannels(peerId);
3739 if (!connected.empty()) {
3746 [w = weak(), peerId](
const std::shared_ptr<dhtnet::ChannelSocket>& socket,
const DeviceId& deviceId) {
3748 dht::ThreadPool::io().run([w, peerId, deviceId] {
3749 if (
auto acc = w.lock()) {
3750 acc->messageEngine_.onPeerOnline(peerId);
3751 acc->messageEngine_.onPeerOnline(peerId, deviceId.toString(), true);
3752 if (!acc->presenceNote_.empty()) {
3754 auto token = std::uniform_int_distribution<uint64_t> {1, JAMI_ID_MAX_VAL}(acc->rand);
3755 std::map<std::string, std::string> msg = {{MIME_TYPE_PIDF, getPIDF(acc->presenceNote_)}};
3756 acc->sendMessage(peerId, deviceId.toString(), msg, token, false, true);
3758 acc->convModule()->syncConversations(peerId, deviceId.toString());
3766JamiAccount::requestSIPConnection(
const std::string& peerId,
3767 const DeviceId& deviceId,
3768 const std::string& connectionType,
3769 bool forceNewConnection,
3770 const std::shared_ptr<SIPCall>& pc)
3772 if (peerId == getUsername()) {
3773 if (!syncModule()->isConnected(deviceId))
3774 channelHandlers_[Uri::Scheme::SYNC]->connect(deviceId,
3776 [](
const std::shared_ptr<dhtnet::ChannelSocket>& ,
3777 const DeviceId& ) {});
3780 JAMI_LOG(
"[Account {}] Request SIP connection to peer {} on device {}", getAccountID(), peerId, deviceId);
3783 std::lock_guard lk(sipConnsMtx_);
3784 auto id = std::make_pair(peerId, deviceId);
3786 if (sipConns_.find(
id) != sipConns_.end()) {
3787 JAMI_LOG(
"[Account {}] A SIP connection with {} already exists", getAccountID(), deviceId);
3791 std::shared_lock lkCM(connManagerMtx_);
3792 if (!connectionManager_)
3797 if (!forceNewConnection && connectionManager_->isConnecting(deviceId,
"sip")) {
3798 JAMI_LOG(
"[Account {}] Already connecting to {}", getAccountID(), deviceId);
3801 JAMI_LOG(
"[Account {}] Ask {} for a new SIP channel", getAccountID(), deviceId);
3802 dhtnet::ConnectDeviceOptions options;
3803 options.noNewSocket =
false;
3804 options.forceNewSocket = forceNewConnection;
3805 options.connType = connectionType;
3806 options.channelTimeout = 3s;
3807 connectionManager_->connectDevice(
3810 [w = weak(),
id = std::move(
id), pc = std::move(pc)](
const std::shared_ptr<dhtnet::ChannelSocket>& socket,
3814 auto shared = w.lock();
3820 shared->callConnectionClosed(
id.second,
true);
3822 pc->onFailure(PJSIP_SC_TEMPORARILY_UNAVAILABLE);
3828JamiAccount::isConnectedWith(
const DeviceId& deviceId)
const
3830 std::shared_lock lkCM(connManagerMtx_);
3831 if (connectionManager_)
3832 return connectionManager_->isConnected(deviceId);
3837JamiAccount::sendPresenceNote(
const std::string& note)
3839 if (
const auto* info = accountManager_->getInfo()) {
3840 if (!info || !info->contacts)
3842 presenceNote_ = note;
3843 auto contacts = info->contacts->getContacts();
3844 std::vector<std::pair<std::string, DeviceId>> keys;
3846 std::shared_lock lkCM(connManagerMtx_);
3850 for (
const auto& contact : contacts) {
3851 auto peerId = contact.first.toString();
3853 for (
const auto& channel : channels) {
3854 keys.emplace_back(peerId, channel->deviceId());
3858 auto token = std::uniform_int_distribution<uint64_t> {1, JAMI_ID_MAX_VAL}(rand);
3860 for (
auto& key : keys) {
3861 sendMessage(key.first, key.second.toString(), msg, token,
false,
true);
3867JamiAccount::sendProfile(
const std::string& convId,
const std::string& peerUri,
const std::string& deviceId)
3869 auto accProfilePath = profilePath();
3870 if (not std::filesystem::is_regular_file(accProfilePath))
3872 auto currentSha3 = fileutils::sha3File(accProfilePath);
3874 if (not needToSendProfile(peerUri, deviceId, currentSha3)) {
3875 JAMI_DEBUG(
"[Account {}] [device {}] Peer {} already got an up-to-date vCard",
3882 transferFile(convId,
3883 accProfilePath.string(),
3890 fileutils::lastWriteTimeInSeconds(accProfilePath),
3891 [accId = getAccountID(), peerUri, deviceId]() {
3893 auto sendDir = fileutils::get_cache_dir() / accId /
"vcard" / peerUri;
3894 auto path = sendDir / deviceId;
3895 dhtnet::fileutils::recursive_mkdir(sendDir);
3896 std::lock_guard lock(dhtnet::fileutils::getFileLock(path));
3897 if (std::filesystem::is_regular_file(path))
3899 std::ofstream p(path);
3904JamiAccount::needToSendProfile(
const std::string& peerUri,
const std::string& deviceId,
const std::string& sha3Sum)
3906 std::string previousSha3 {};
3907 auto vCardPath = cachePath_ /
"vcard";
3908 auto sha3Path = vCardPath /
"sha3";
3909 dhtnet::fileutils::check_dir(vCardPath, 0700);
3911 previousSha3 = fileutils::loadTextFile(sha3Path);
3913 fileutils::saveFile(sha3Path, (
const uint8_t*) sha3Sum.data(), sha3Sum.size(), 0600);
3916 if (sha3Sum != previousSha3) {
3918 dhtnet::fileutils::removeAll(vCardPath,
true);
3919 dhtnet::fileutils::check_dir(vCardPath, 0700);
3920 fileutils::saveFile(sha3Path, (
const uint8_t*) sha3Sum.data(), sha3Sum.size(), 0600);
3923 auto peerPath = vCardPath / peerUri;
3924 dhtnet::fileutils::recursive_mkdir(peerPath);
3925 return not std::filesystem::is_regular_file(peerPath / deviceId);
3929JamiAccount::clearProfileCache(
const std::string& peerUri)
3932 std::filesystem::remove_all(cachePath_ /
"vcard" / peerUri, ec);
3935std::filesystem::path
3936JamiAccount::profilePath()
const
3938 return idPath_ /
"profile.vcf";
3942JamiAccount::cacheSIPConnection(std::shared_ptr<dhtnet::ChannelSocket>&& socket,
3943 const std::string& peerId,
3946 std::unique_lock lk(sipConnsMtx_);
3949 auto& connections = sipConns_[key];
3950 auto conn = std::find_if(connections.begin(), connections.end(), [&](
const auto& v) { return v.channel == socket; });
3951 if (conn != connections.end()) {
3952 JAMI_WARNING(
"[Account {}] Channel socket already cached with this peer", getAccountID());
3957 auto onShutdown = [w = weak(), peerId, key, socket]() {
3958 dht::ThreadPool::io().run([w = std::move(w), peerId, key, socket] {
3959 auto shared = w.lock();
3962 shared->shutdownSIPConnection(socket, key.first, key.second);
3966 shared->callConnectionClosed(key.second,
false);
3969 auto sip_tr = link_.sipTransportBroker->getChanneledTransport(shared(), socket, std::move(onShutdown));
3975 connections.emplace_back(SipConnection {sip_tr, socket});
3976 JAMI_WARNING(
"[Account {:s}] [device {}] New SIP channel opened", getAccountID(), deviceId);
3980 messageEngine_.onPeerOnline(peerId);
3981 messageEngine_.onPeerOnline(peerId, deviceId.toString(),
true);
3984 forEachPendingCall(deviceId, [&](
const auto& pc) {
3985 if (pc->getConnectionState() != Call::ConnectionState::TRYING
3986 and pc->getConnectionState() != Call::ConnectionState::PROGRESSING)
3988 pc->setSipTransport(sip_tr, getContactHeader(sip_tr));
3989 pc->setState(Call::ConnectionState::PROGRESSING);
3990 if (
auto remote_address = socket->getRemoteAddress()) {
3992 onConnectedOutgoingCall(pc, peerId, remote_address);
3993 }
catch (
const VoipLinkException&) {
4005JamiAccount::shutdownSIPConnection(
const std::shared_ptr<dhtnet::ChannelSocket>& channel,
4006 const std::string& peerId,
4007 const DeviceId& deviceId)
4009 std::unique_lock lk(sipConnsMtx_);
4011 auto it = sipConns_.find(key);
4012 if (it != sipConns_.end()) {
4013 auto& conns = it->second;
4014 conns.erase(std::remove_if(conns.begin(), conns.end(), [&](
auto v) { return v.channel == channel; }),
4016 if (conns.empty()) {
4017 sipConns_.erase(it);
4023 channel->shutdown();
4027JamiAccount::currentDeviceId()
const
4029 if (!accountManager_ or not accountManager_->getInfo())
4031 return accountManager_->getInfo()->deviceId;
4034std::shared_ptr<TransferManager>
4035JamiAccount::dataTransfer(
const std::string&
id)
4038 return nonSwarmTransferManager_;
4039 if (
auto* cm = convModule())
4040 return cm->dataTransfer(
id);
4045JamiAccount::monitor()
4047 JAMI_DEBUG(
"[Account {:s}] Monitor connections", getAccountID());
4048 JAMI_DEBUG(
"[Account {:s}] Using proxy: {:s}", getAccountID(), proxyServerCached_);
4050 if (
auto* cm = convModule())
4052 std::shared_lock lkCM(connManagerMtx_);
4053 if (connectionManager_)
4054 connectionManager_->monitor();
4057std::vector<std::map<std::string, std::string>>
4058JamiAccount::getConnectionList(
const std::string& conversationId)
4060 std::shared_lock lkCM(connManagerMtx_);
4061 if (connectionManager_ && conversationId.empty()) {
4062 return connectionManager_->getConnectionList();
4063 }
else if (connectionManager_ && convModule_) {
4064 std::vector<std::map<std::string, std::string>> connectionList;
4065 if (
auto conv = convModule_->getConversation(conversationId)) {
4066 for (
const auto& deviceId : conv->getDeviceIdList()) {
4067 auto connections = connectionManager_->getConnectionList(deviceId);
4068 connectionList.reserve(connectionList.size() + connections.size());
4069 std::move(connections.begin(), connections.end(), std::back_inserter(connectionList));
4072 return connectionList;
4078std::vector<std::map<std::string, std::string>>
4079JamiAccount::getConversationConnectivity(
const std::string& conversationId)
4081 std::shared_lock lkCM(connManagerMtx_);
4083 if (
auto conv = convModule_->getConversation(conversationId)) {
4084 return conv->getConnectivity();
4090std::vector<std::map<std::string, std::string>>
4091JamiAccount::getConversationTrackedMembers(
const std::string& conversationId)
4093 std::shared_lock lkCM(connManagerMtx_);
4095 if (
auto conv = convModule_->getConversation(conversationId)) {
4096 return conv->getTrackedMembers();
4102std::vector<std::map<std::string, std::string>>
4103JamiAccount::getChannelList(
const std::string& connectionId)
4105 std::shared_lock lkCM(connManagerMtx_);
4106 if (!connectionManager_)
4108 return connectionManager_->getChannelList(connectionId);
4112JamiAccount::sendFile(
const std::string& conversationId,
4113 const std::filesystem::path& path,
4114 const std::string& name,
4115 const std::string& replyTo)
4118 if (!std::filesystem::is_regular_file(path, ec)) {
4120 emitSignal<libjami::ConversationSignal::OnConversationError>(getAccountID(),
4123 "Invalid filename.");
4127 auto fileSize = std::filesystem::file_size(path, ec);
4128 if (ec || fileSize ==
static_cast<decltype(fileSize)
>(-1)) {
4129 JAMI_ERROR(
"Negative file size, user probably doesn't have the appropriate permissions for '{}'", path);
4130 emitSignal<libjami::ConversationSignal::OnConversationError>(
4134 "Negative file size, could be due to insufficient file permissions.");
4141 dht::ThreadPool::computation().run([w = weak(), conversationId, path, name, fileSize, replyTo]() {
4142 if (
auto shared = w.lock()) {
4144 auto tid = jami::generateUID(shared->rand);
4145 auto displayName = name.empty() ? path.filename().string() : name;
4146 value[
"tid"] = std::to_string(tid);
4147 value[
"displayName"] = displayName;
4148 value[
"totalSize"] = std::to_string(fileSize);
4149 value[
"sha3sum"] = fileutils::sha3File(path);
4150 value[
"type"] =
"application/data-transfer+json";
4152 shared->convModule()->sendMessage(
4157 [accId = shared->getAccountID(), conversationId, tid, displayName, path](const std::string& commitId) {
4159 auto filelinkPath = fileutils::get_data_dir() / accId /
"conversation_data" / conversationId
4160 / getFileId(commitId, std::to_string(tid), displayName);
4161 if (path != filelinkPath && !std::filesystem::is_symlink(filelinkPath)) {
4162 if (!fileutils::createFileLink(filelinkPath, path, true)) {
4163 JAMI_WARNING(
"Unable to create symlink for file transfer {} - {}. Copy file",
4167 auto success = std::filesystem::copy_file(path, filelinkPath, ec);
4168 if (ec || !success) {
4169 JAMI_ERROR(
"Unable to copy file for file transfer {} - {}", filelinkPath, path);
4173 emitSignal<libjami::DataTransferSignal::DataTransferEvent>(
4178 uint32_t(libjami::DataTransferEventCode::invalid));
4183 emitSignal<libjami::DataTransferSignal::DataTransferEvent>(
4188 uint32_t(libjami::DataTransferEventCode::created));
4191 emitSignal<libjami::DataTransferSignal::DataTransferEvent>(
4196 uint32_t(libjami::DataTransferEventCode::created));
4205JamiAccount::transferFile(
const std::string& conversationId,
4206 const std::string& path,
4207 const std::string& deviceId,
4208 const std::string& fileId,
4209 const std::string& interactionId,
4212 const std::string& sha3Sum,
4213 uint64_t lastWriteTime,
4214 std::function<
void()> onFinished)
4216 std::string modified;
4217 if (lastWriteTime != 0) {
4218 modified = fmt::format(
"&modified={}", lastWriteTime);
4220 auto fid = fileId ==
"profile.vcf" ? fmt::format(
"profile.vcf?sha3={}{}", sha3Sum, modified) : fileId;
4221 auto channelName = conversationId.empty()
4222 ? fmt::format(
"{}profile.vcf?sha3={}{}", DATA_TRANSFER_SCHEME, sha3Sum, modified)
4223 : fmt::format(
"{}{}/{}/{}", DATA_TRANSFER_SCHEME, conversationId, currentDeviceId(), fid);
4224 std::shared_lock lkCM(connManagerMtx_);
4225 if (!connectionManager_)
4227 connectionManager_->connectDevice(
4232 path = std::move(path),
4237 onFinished = std::move(onFinished)](std::shared_ptr<dhtnet::ChannelSocket> socket,
const DeviceId&) {
4240 dht::ThreadPool::io().run([w = weak(),
4241 path = std::move(path),
4242 socket = std::move(socket),
4243 conversationId = std::move(conversationId),
4248 onFinished = std::move(onFinished)] {
4249 if (
auto shared = w.lock())
4250 if (
auto dt = shared->dataTransfer(conversationId))
4251 dt->transferFile(socket, fileId, interactionId, path, start, end, std::move(onFinished));
4257JamiAccount::askForFileChannel(
const std::string& conversationId,
4258 const std::string& deviceId,
4259 const std::string& interactionId,
4260 const std::string& fileId,
4264 auto tryDevice = [=](
const auto& did) {
4265 std::shared_lock lkCM(connManagerMtx_);
4266 if (!connectionManager_)
4269 auto channelName = fmt::format(
"{}{}/{}/{}", DATA_TRANSFER_SCHEME, conversationId, currentDeviceId(), fileId);
4270 if (start != 0 || end != 0) {
4271 channelName += fmt::format(
"?start={}&end={}", start, end);
4275 connectionManager_->connectDevice(
4282 start](
const std::shared_ptr<dhtnet::ChannelSocket>& channel,
const DeviceId&) {
4285 dht::ThreadPool::io().run([w, conversationId, channel, fileId, interactionId, start] {
4286 auto shared = w.lock();
4289 auto dt = shared->dataTransfer(conversationId);
4292 if (interactionId.empty())
4293 dt->onIncomingProfile(channel);
4295 dt->onIncomingFileTransfer(fileId, channel, start);
4301 if (!deviceId.empty()) {
4307 for (
const auto& m : convModule()->getConversationMembers(conversationId)) {
4308 accountManager_->forEachDevice(dht::InfoHash(m.at(
"uri")),
4309 [tryDevice](
const std::shared_ptr<dht::crypto::PublicKey>&
dev) {
4310 tryDevice(
dev->getLongId());
4317JamiAccount::askForProfile(
const std::string& conversationId,
const std::string& deviceId,
const std::string& memberUri)
4319 std::shared_lock lkCM(connManagerMtx_);
4320 if (!connectionManager_)
4323 auto channelName = fmt::format(
"{}{}/profile/{}.vcf", DATA_TRANSFER_SCHEME, conversationId, memberUri);
4326 connectionManager_->connectDevice(
4329 [
this, conversationId](
const std::shared_ptr<dhtnet::ChannelSocket>& channel,
const DeviceId&) {
4332 dht::ThreadPool::io().run([w = weak(), conversationId, channel] {
4333 if (
auto shared = w.lock())
4334 if (
auto dt = shared->dataTransfer(conversationId))
4335 dt->onIncomingProfile(channel);
4342JamiAccount::onPeerConnected(
const std::string& peerId,
bool connected)
4344 auto isOnline = presenceManager_ && presenceManager_->isOnline(peerId);
4345 auto newState = connected ? PresenceState::CONNECTED
4346 : (isOnline ? PresenceState::AVAILABLE : PresenceState::DISCONNECTED);
4348 runOnMainThread([w = weak(), peerId, newState] {
4349 if (
auto sthis = w.lock()) {
4350 std::lock_guard lock(sthis->presenceStateMtx_);
4351 auto& state = sthis->presenceState_[peerId];
4352 if (state != newState) {
4354 emitSignal<libjami::PresenceSignal::NewBuddyNotification>(sthis->getAccountID(),
4356 static_cast<int>(newState),
4364JamiAccount::initConnectionManager()
4366 if (!nonSwarmTransferManager_)
4367 nonSwarmTransferManager_ = std::make_shared<TransferManager>(accountID_,
4370 dht::crypto::getDerivedRandomEngine(rand));
4371 if (!connectionManager_) {
4372 auto connectionManagerConfig = std::make_shared<dhtnet::ConnectionManager::Config>();
4373 connectionManagerConfig->ioContext = Manager::instance().ioContext();
4374 connectionManagerConfig->dht =
dht();
4375 connectionManagerConfig->certStore = certStore_;
4376 connectionManagerConfig->id = identity();
4377 connectionManagerConfig->upnpCtrl = upnpCtrl_;
4378 connectionManagerConfig->turnServer = config().turnServer;
4379 connectionManagerConfig->upnpEnabled = config().upnpEnabled;
4380 connectionManagerConfig->turnServerUserName = config().turnServerUserName;
4381 connectionManagerConfig->turnServerPwd = config().turnServerPwd;
4382 connectionManagerConfig->turnServerRealm = config().turnServerRealm;
4383 connectionManagerConfig->turnEnabled = config().turnEnabled;
4384 connectionManagerConfig->cachePath = cachePath_;
4385 if (Manager::instance().dhtnetLogLevel > 0) {
4386 connectionManagerConfig->logger = logger_;
4388 connectionManagerConfig->factory = Manager::instance().getIceTransportFactory();
4389 connectionManagerConfig->turnCache = turnCache_;
4390 connectionManagerConfig->rng = std::make_unique<std::mt19937_64>(dht::crypto::getDerivedRandomEngine(rand));
4391 connectionManagerConfig->legacyMode = dhtnet::LegacyMode::Supported;
4392 connectionManager_ = std::make_unique<dhtnet::ConnectionManager>(connectionManagerConfig);
4393 channelHandlers_[Uri::Scheme::SWARM] = std::make_unique<SwarmChannelHandler>(shared(),
4394 *connectionManager_.get());
4395 channelHandlers_[Uri::Scheme::GIT] = std::make_unique<ConversationChannelHandler>(shared(),
4396 *connectionManager_.get());
4397 channelHandlers_[Uri::Scheme::SYNC] = std::make_unique<SyncChannelHandler>(shared(), *connectionManager_.get());
4398 channelHandlers_[Uri::Scheme::DATA_TRANSFER]
4399 = std::make_unique<TransferChannelHandler>(shared(), *connectionManager_.get());
4400 channelHandlers_[Uri::Scheme::MESSAGE] = std::make_unique<MessageChannelHandler>(
4401 *connectionManager_.get(),
4402 [
this](
const auto& cert, std::string& type,
const std::string& content) {
4403 onTextMessage(
"", cert->issuer->getId().toString(), cert, {{type, content}});
4405 [w = weak()](
const std::string& peer,
bool connected) {
4406 asio::post(*Manager::instance().ioContext(), [w, peer, connected] {
4407 if (
auto acc = w.lock())
4408 acc->onPeerConnected(peer, connected);
4411 channelHandlers_[Uri::Scheme::AUTH] = std::make_unique<AuthChannelHandler>(shared(), *connectionManager_.get());
4414 connectionManager_->oniOSConnected([&](
const std::string& connType, dht::InfoHash peer_h) {
4416 bool hasVideo = connType ==
"videoCall";
4417 emitSignal<libjami::ConversationSignal::CallConnectionRequest>(
"", peer_h.toString(), hasVideo);
4427JamiAccount::updateUpnpController()
4429 Account::updateUpnpController();
4430 if (connectionManager_) {
4431 auto config = connectionManager_->getConfig();
4433 config->upnpCtrl = upnpCtrl_;
Account specific keys/constants that must be shared in daemon and clients.
Manages channels for syncing informations.
Track sending state for a single message to one or more devices.
void start()
Call after all messages are sent.
bool complete(const DeviceId &device, bool success)
Complete pending message for device.
std::function< void(bool, bool)> OnComplete
SendMessageContext(OnComplete onComplete)
bool pending(const DeviceId &device) const
bool add(const DeviceId &device)
Track new pending message for device.
JamiAccount(const std::string &accountId)
Constructor.
std::weak_ptr< JamiAccount > weak()
Level-driven logging class that support printf and C++ stream logging fashions.
static LIBJAMI_TEST_EXPORT Manager & instance()
Manages channels for exchanging messages between peers.
std::vector< std::shared_ptr< dhtnet::ChannelSocket > > getChannels(const std::string &peer) const
std::vector< std::map< std::string, std::string > > SearchResult
const std::string & authority() const
virtual dhtnet::IpAddr getLocalAddress() const =0
#define JAMI_ERROR(formatstr,...)
#define JAMI_DEBUG(formatstr,...)
#define JAMI_WARNING(formatstr,...)
#define JAMI_LOG(formatstr,...)
void setState(const std::string &accountID, const State migrationState)
std::string mapStateNumberToString(const State migrationState)
constexpr const pj_str_t CONST_PJ_STR(T(&a)[N]) noexcept
const constexpr auto PEER_DISCOVERY_EXPIRATION
std::pair< std::string, DeviceId > SipConnectionKey
static constexpr auto TREATED_PATH
void emitSignal(Args... args)
std::uniform_int_distribution< dht::Value::Id > ValueIdDist
static const auto PROXY_REGEX
constexpr auto EVALIDFETCH
bool getline(std::string_view &str, std::string_view &line, char delim='\n')
Similar to @getline_full but skips empty results.
constexpr pj_str_t STR_MESSAGE_ID
const std::string & userAgent()
std::string_view stripPrefix(std::string_view toUrl)
RegistrationState
Contains all the Registration states for an account can be in.
static constexpr const char MIME_TYPE_IMDN[]
static constexpr std::string_view dhtStatusStr(dht::NodeStatus status)
std::string getDisplayed(const std::string &conversationId, const std::string &messageId)
std::string_view parseJamiUri(std::string_view toUrl)
static const constexpr std::string_view PEER_DISCOVERY_JAMI_SERVICE
static const constexpr std::string_view RING_URI_PREFIX
static constexpr const char DEVICE_ID_PATH[]
std::string getPIDF(const std::string ¬e)
static constexpr const char MIME_TYPE_INVITE_JSON[]
static const constexpr std::string_view JAMI_URI_PREFIX
static constexpr const char MIME_TYPE_PIDF[]
std::vector< std::string_view > split_string(std::string_view str, char delim)
void string_replace(std::string &str, const std::string &from, const std::string &to)
static void runOnMainThread(Callback &&cb)
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 OFF_CALL[]
static constexpr const char DEVICE_ANNOUNCED[]
static constexpr const char DHT_BOUND_PORT[]
static constexpr const char REGISTERED_NAME[]
bool accept(const std::string &accountId, const std::string &callId)
bool setCertificateStatus(const std::string &accountId, const std::string &certId, const std::string &ststr)
std::map< std::string, std::string > getAccountDetails(const std::string &accountId)
std::map< std::string, std::string > getVolatileAccountDetails(const std::string &accountId)
void sendTextMessage(const std::string &accountId, const std::string &callId, const std::map< std::string, std::string > &messages, const std::string &from, bool isMixed)
bool start(const std::filesystem::path &config_file) noexcept
Start asynchronously daemon created by init().
bool regex_search(string_view sv, svmatch &m, const regex &e, regex_constants::match_flag_type flags=regex_constants::match_default)
SIPCall are SIP implementation of a normal Call.
Specific VoIPLink for SIP (SIP core for incoming and outgoing events).
std::string displayName
Display name when calling.
std::vector< uint8_t > receiptSignature
std::string registeredName
std::unique_ptr< asio::steady_timer > cleanupTimer
std::future< size_t > listen_key
std::chrono::steady_clock::time_point start
dht::InfoHash from_account
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
AbstractSIPTransport * self