79#include <dhtnet/ice_transport.h>
80#include <dhtnet/ice_transport_factory.h>
81#include <dhtnet/upnp/upnp_control.h>
82#include <dhtnet/multiplexed_socket.h>
83#include <dhtnet/certstore.h>
85#include <opendht/thread_pool.h>
86#include <opendht/peer_discovery.h>
87#include <opendht/http.h>
89#include <yaml-cpp/yaml.h>
99#include <initializer_list>
104#include <system_error>
106using namespace std::placeholders;
134#define CASE_STATE(X) \
135 case Migration::State::X: \
172 std::chrono::steady_clock::time_point
start;
185 std::set<DeviceId>
to;
204 "(https?://)?([\\w\\.\\-_\\~]+)(:(\\d+)|:\\[(.+)-(.+)\\])?");
214 if (
dhtf != std::string_view::npos) {
218 if (
dhtf != std::string_view::npos) {
234 if (
sufix.length() < 40)
235 throw std::invalid_argument(
"ID must be a Jami infohash");
237 const std::string_view
toUri =
sufix.substr(0, 40);
239 throw std::invalid_argument(
"ID must be a Jami infohash");
243static constexpr std::string_view
246 return status == dht::NodeStatus::Connected
248 : (status == dht::NodeStatus::Connecting ?
"connecting"sv :
"disconnected"sv);
253 , cachePath_(fileutils::get_cache_dir() / accountId)
254 , dataPath_(cachePath_ /
"values")
258 , connectionManager_ {}
259 , nonSwarmTransferManager_()
275 std::lock_guard
lk(gitServersMtx_);
281 std::lock_guard
lk(connManagerMtx_);
283 dht::ThreadPool::io().run([
conMgr = std::make_shared<
decltype(connectionManager_)>(
284 std::move(connectionManager_))] {});
285 connectionManager_.reset();
286 channelHandlers_.clear();
289 convModule_->shutdownConnections();
292 std::lock_guard
lk(sipConnsMtx_);
302 dhtnet::fileutils::removeAll(cachePath_);
303 dhtnet::fileutils::removeAll(dataPath_);
304 dhtnet::fileutils::removeAll(
idPath_,
true);
307std::shared_ptr<SIPCall>
309 const std::vector<libjami::MediaMap>&
mediaList,
310 const std::shared_ptr<SipTransport>&
sipTransp)
319 call->setPeerNumber(from);
326 JAMI_ERR(
"newIncomingCall: unable to find matching call for %s", from.c_str());
337 return newSwarmOutgoingCallHelper(uri,
mediaList);
341 std::shared_ptr<SIPCall> call;
347 JAMI_WARN(
"Media list is empty, setting a default list");
357 std::shared_lock
lkCM(connManagerMtx_);
358 if (!connectionManager_)
361 connectionManager_->getIceOptions([call, w =
weak(), uri = std::move(uri)](
auto&&
opts) {
362 if (call->isIceEnabled()) {
363 if (not call->createIceMediaTransport(false)
364 or not call->initIceMediaTransport(true,
365 std::forward<dhtnet::IceTransportOptions>(opts))) {
372 JAMI_DBG() <<
"New outgoing call with " << uri.toString();
373 call->setPeerNumber(uri.authority());
374 call->setPeerUri(uri.toString());
376 shared->newOutgoingCallHelper(call, uri);
383JamiAccount::newOutgoingCallHelper(
const std::shared_ptr<SIPCall>& call,
const Uri& uri)
385 JAMI_DBG() <<
this <<
"Calling peer " << uri.authority();
387 startOutgoingCall(call, uri.authority());
391 NameDirectory::lookupUri(suffix,
393 [wthis_ = weak(), call](
const std::string& regName,
394 const std::string& address,
395 NameDirectory::Response response) {
398 runOnMainThread([wthis_, regName, address, response, call]() {
399 if (response != NameDirectory::Response::found) {
400 call->onFailure(EINVAL);
403 if (
auto sthis = wthis_.lock()) {
405 sthis->startOutgoingCall(call, regName);
407 call->onFailure(ENOENT);
415 call->onFailure(ENOENT);
420std::shared_ptr<SIPCall>
421JamiAccount::newSwarmOutgoingCallHelper(
const Uri& uri,
422 const std::vector<libjami::MediaMap>& mediaList)
424 JAMI_DEBUG(
"[Account {}] Calling conversation {}", getAccountID(), uri.authority());
425 return convModule()->call(
428 [
this, uri](
const auto& accountUri,
const auto& deviceId,
const auto& call) {
431 std::unique_lock lkSipConn(sipConnsMtx_);
432 for (auto& [key, value] : sipConns_) {
433 if (key.first != accountUri || key.second != deviceId)
437 auto& sipConn = value.back();
439 if (!sipConn.channel) {
441 "A SIP transport exists without Channel, this is a bug. Please report");
445 auto transport = sipConn.transport;
446 if (!transport or !sipConn.channel)
448 call->setState(Call::ConnectionState::PROGRESSING);
450 auto remoted_address = sipConn.channel->getRemoteAddress();
452 onConnectedOutgoingCall(call, uri.authority(), remoted_address);
454 } catch (const VoipLinkException&) {
465 std::lock_guard lkP(pendingCallsMutex_);
466 pendingCalls_[deviceId].emplace_back(call);
470 auto type = call->hasVideo() ?
"videoCall" :
"audioCall";
471 JAMI_WARNING(
"[call {}] No channeled socket with this peer. Send request",
473 requestSIPConnection(accountUri, deviceId, type,
true, call);
478JamiAccount::handleIncomingConversationCall(
const std::string& callId,
479 const std::string& destination)
482 if (split.size() != 4)
484 auto conversationId = std::string(split[0]);
485 auto accountUri = std::string(split[1]);
486 auto deviceId = std::string(split[2]);
487 auto confId = std::string(split[3]);
489 if (getUsername() != accountUri || currentDeviceId() != deviceId)
493 std::lock_guard lk(rdvMtx_);
494 auto isNotHosting = !convModule()->isHosting(conversationId, confId);
496 auto currentCalls = convModule()->getActiveCalls(conversationId);
497 if (!currentCalls.empty()) {
498 confId = currentCalls[0][
"id"];
499 isNotHosting =
false;
502 JAMI_DEBUG(
"No active call to join, create conference");
505 auto preferences = convModule()->getConversationPreferences(conversationId);
507#if defined(__ANDROID__) || defined(__APPLE__)
511 auto itPref = preferences.find(ConversationPreferences::HOST_CONFERENCES);
512 if (itPref != preferences.end()) {
513 canHost = itPref->second == TRUE_STR;
516 auto call = getCall(callId);
522 if (isNotHosting && !canHost) {
523 JAMI_DEBUG(
"Request for hosting a conference declined");
524 Manager::instance().hangupCall(getAccountID(), callId);
534 std::shared_ptr<Conference> conf;
535 std::vector<libjami::MediaMap> currentMediaList;
537 conf = getConference(confId);
539 JAMI_ERROR(
"Conference {} not found", confId);
542 auto hostMedias = conf->currentMediaList();
543 auto sipCall = std::dynamic_pointer_cast<SIPCall>(call);
544 if (hostMedias.empty()) {
545 currentMediaList = MediaAttribute::mediaAttributesToMediaMaps(
546 createDefaultMediaList(call->hasVideo(),
true));
547 }
else if (hostMedias.size() < sipCall->getRtpSessionList().size()) {
551 currentMediaList = hostMedias;
564 for (
const auto& m : conf->currentMediaList()) {
568 currentMediaList.emplace_back(m);
569 }
else if (call->hasVideo()
574 currentMediaList.emplace_back(m);
580 Manager::instance().answerCall(*call, currentMediaList);
583 JAMI_DEBUG(
"Creating conference for swarm {} with ID {}", conversationId, confId);
585 convModule()->hostConference(conversationId, confId, callId);
587 JAMI_DEBUG(
"Adding participant {} for swarm {} with ID {}", callId, conversationId, confId);
588 Manager::instance().addAudio(*call);
589 conf->addSubCall(callId);
590 emitSignal<libjami::CallSignal::ConferenceChanged>(getAccountID(),
592 conf->getStateStr());
596std::shared_ptr<SIPCall>
597JamiAccount::createSubCall(
const std::shared_ptr<SIPCall>& mainCall)
599 auto mediaList = MediaAttribute::mediaAttributesToMediaMaps(mainCall->getMediaAttributeList());
600 return Manager::instance().callFactory.newSipCall(shared(), Call::CallType::OUTGOING, mediaList);
604JamiAccount::startOutgoingCall(
const std::shared_ptr<SIPCall>& call,
const std::string& toUri)
606 if (not accountManager_ or not dht_) {
607 call->onFailure(ENETDOWN);
614 call->setState(Call::ConnectionState::TRYING);
615 std::weak_ptr<SIPCall> wCall = call;
618 accountManager_->lookupAddress(toUri,
619 [wCall](
const std::string& regName,
620 const std::string& address,
621 const NameDirectory::Response& response) {
622 if (response == NameDirectory::Response::found)
623 if (
auto call = wCall.lock()) {
624 call->setPeerRegisteredName(regName);
629 dht::InfoHash peer_account(toUri);
632 std::set<DeviceId> devices;
633 std::unique_lock lkSipConn(sipConnsMtx_);
636 auto dummyCall = createSubCall(call);
638 call->addSubCall(*dummyCall);
639 dummyCall->setIceMedia(call->getIceMedia());
641 [
this, wCall, toUri, dummyCall = std::move(dummyCall)](
const DeviceId& deviceId,
646 dummyCall->onFailure(
static_cast<int>(std::errc::no_such_device_or_address));
649 auto call = wCall.lock();
652 auto state = call->getConnectionState();
653 if (state != Call::ConnectionState::PROGRESSING
654 and state != Call::ConnectionState::TRYING)
657 auto dev_call = createSubCall(call);
658 dev_call->setPeerNumber(call->getPeerNumber());
659 dev_call->setState(Call::ConnectionState::TRYING);
660 call->addStateListener(
661 [w = weak(), deviceId](Call::CallState, Call::ConnectionState state,
int) {
662 if (state != Call::ConnectionState::PROGRESSING
663 and state != Call::ConnectionState::TRYING) {
664 if (
auto shared = w.lock())
665 shared->callConnectionClosed(deviceId,
true);
670 call->addSubCall(*dev_call);
671 dev_call->setIceMedia(call->getIceMedia());
673 std::lock_guard lk(pendingCallsMutex_);
674 pendingCalls_[deviceId].emplace_back(dev_call);
677 JAMI_WARNING(
"[call {}] No channeled socket with this peer. Send request",
680 auto type = call->hasVideo() ?
"videoCall" :
"audioCall";
681 requestSIPConnection(toUri, deviceId, type,
true, dev_call);
684 std::vector<std::shared_ptr<dhtnet::ChannelSocket>> channels;
685 for (
auto& [key, value] : sipConns_) {
686 if (key.first != toUri)
690 auto& sipConn = value.back();
692 if (!sipConn.channel) {
693 JAMI_WARNING(
"A SIP transport exists without Channel, this is a bug. Please report");
697 auto transport = sipConn.transport;
698 auto remote_address = sipConn.channel->getRemoteAddress();
699 if (!transport or !remote_address)
702 channels.emplace_back(sipConn.channel);
704 JAMI_WARNING(
"[call {}] A channeled socket is detected with this peer.", call->getCallId());
706 auto dev_call = createSubCall(call);
707 dev_call->setPeerNumber(call->getPeerNumber());
708 dev_call->setSipTransport(transport, getContactHeader(transport));
709 call->addSubCall(*dev_call);
710 dev_call->setIceMedia(call->getIceMedia());
716 dev_call->setState(Call::ConnectionState::PROGRESSING);
719 std::lock_guard lk(onConnectionClosedMtx_);
720 onConnectionClosed_[key.second] = sendRequest;
723 call->addStateListener(
724 [w = weak(), deviceId = key.second](Call::CallState, Call::ConnectionState state,
int) {
725 if (state != Call::ConnectionState::PROGRESSING
726 and state != Call::ConnectionState::TRYING) {
727 if (auto shared = w.lock())
728 shared->callConnectionClosed(deviceId, true);
735 onConnectedOutgoingCall(dev_call, toUri, remote_address);
736 }
catch (
const VoipLinkException&) {
744 devices.emplace(key.second);
750 for (
const auto& channel : channels)
751 channel->sendBeacon();
754 accountManager_->forEachDevice(
756 [
this, devices = std::move(devices), sendRequest](
757 const std::shared_ptr<dht::crypto::PublicKey>&
dev) {
759 auto deviceId =
dev->getLongId();
760 if (devices.find(deviceId) != devices.end())
763 std::lock_guard lk(onConnectionClosedMtx_);
764 onConnectionClosed_[deviceId] = sendRequest;
766 sendRequest(deviceId,
false);
770 if (
auto call = wCall.lock()) {
771 JAMI_WARNING(
"[call:{}] No devices found", call->getCallId());
773 if (call->getConnectionState() == Call::ConnectionState::TRYING)
774 call->onFailure(
static_cast<int>(std::errc::no_such_device_or_address));
781JamiAccount::onConnectedOutgoingCall(
const std::shared_ptr<SIPCall>& call,
782 const std::string& to_id,
783 dhtnet::IpAddr target)
787 JAMI_LOG(
"[call:{}] Outgoing call connected to {}", call->getCallId(), to_id);
789 const auto localAddress = dhtnet::ip_utils::getInterfaceAddr(getLocalInterface(),
792 dhtnet::IpAddr addrSdp = getPublishedSameasLocal()
794 : connectionManager_->getPublishedIpAddress(target.getFamily());
798 addrSdp = localAddress;
801 auto& sdp = call->getSDP();
803 sdp.setPublishedIP(addrSdp);
805 auto mediaAttrList = call->getMediaAttributeList();
806 if (mediaAttrList.empty()) {
807 JAMI_ERROR(
"[call:{}] No media. Abort!", call->getCallId());
811 if (not sdp.createOffer(mediaAttrList)) {
812 JAMI_ERROR(
"[call:{}] Unable to send outgoing INVITE request for new call",
823 dht::ThreadPool::io().run([w = weak(), call = std::move(call), target] {
824 auto account = w.lock();
828 if (not account->SIPStartCall(*call, target)) {
829 JAMI_ERROR(
"[call:{}] Unable to send outgoing INVITE request for new call",
836JamiAccount::SIPStartCall(SIPCall& call,
const dhtnet::IpAddr& target)
838 JAMI_LOG(
"[call:{}] Start SIP call", call.getCallId());
840 if (call.isIceEnabled())
841 call.addLocalIceAttributes();
843 std::string toUri(getToUri(call.getPeerNumber() +
"@"
844 + target.toString(
true)));
846 pj_str_t pjTo = sip_utils::CONST_PJ_STR(toUri);
849 std::string from(getFromUri());
850 pj_str_t pjFrom = sip_utils::CONST_PJ_STR(from);
852 std::string targetStr = getToUri(target.toString(
true));
853 pj_str_t pjTarget = sip_utils::CONST_PJ_STR(targetStr);
855 auto contact = call.getContactHeader();
856 auto pjContact = sip_utils::CONST_PJ_STR(contact);
858 JAMI_LOG(
"[call:{}] Contact header: {} / {} -> {} / {}",
865 auto local_sdp = call.getSDP().getLocalSdpSession();
866 pjsip_dialog* dialog {
nullptr};
867 pjsip_inv_session* inv {
nullptr};
868 if (!CreateClientDialogAndInvite(&pjFrom, &pjContact, &pjTo, &pjTarget, local_sdp, &dialog, &inv))
871 inv->mod_data[link_.getModId()] = &call;
872 call.setInviteSession(inv);
874 pjsip_tx_data* tdata;
876 if (pjsip_inv_invite(call.inviteSession_.get(), &tdata) != PJ_SUCCESS) {
877 JAMI_ERROR(
"[call:{}] Unable to initialize invite", call.getCallId());
881 pjsip_tpselector tp_sel;
882 tp_sel.type = PJSIP_TPSELECTOR_TRANSPORT;
883 if (!call.getTransport()) {
884 JAMI_ERROR(
"[call:{}] Unable to get transport", call.getCallId());
887 tp_sel.u.transport = call.getTransport()->get();
888 if (pjsip_dlg_set_transport(dialog, &tp_sel) != PJ_SUCCESS) {
889 JAMI_ERROR(
"[call:{}] Unable to associate transport for invite session dialog",
894 JAMI_LOG(
"[call:{}] Sending SIP invite", call.getCallId());
897 sip_utils::addUserAgentHeader(getUserAgentName(), tdata);
899 if (pjsip_inv_send_msg(call.inviteSession_.get(), tdata) != PJ_SUCCESS) {
900 JAMI_ERROR(
"[call:{}] Unable to send invite message", call.getCallId());
904 call.setState(Call::CallState::ACTIVE, Call::ConnectionState::PROGRESSING);
909JamiAccount::saveConfig()
const
912 YAML::Emitter accountOut;
913 config().serialize(accountOut);
914 auto accountConfig = config().path /
"config.yml";
915 std::lock_guard lock(dhtnet::fileutils::getFileLock(accountConfig));
916 std::ofstream fout(accountConfig);
917 fout.write(accountOut.c_str(), accountOut.size());
918 JAMI_LOG(
"Saved account config to {}", accountConfig);
919 }
catch (
const std::exception& e) {
920 JAMI_ERROR(
"Error saving account config: {}", e.what());
925JamiAccount::loadConfig()
927 SIPAccountBase::loadConfig();
928 registeredName_ = config().registeredName;
930 accountManager_->setAccountDeviceName(config().deviceName);
931 if (connectionManager_) {
932 if (
auto c = connectionManager_->getConfig()) {
934 c->upnpEnabled = config().upnpEnabled;
935 c->turnEnabled = config().turnEnabled;
936 c->turnServer = config().turnServer;
937 c->turnServerUserName = config().turnServerUserName;
938 c->turnServerPwd = config().turnServerPwd;
939 c->turnServerRealm = config().turnServerRealm;
942 if (config().proxyEnabled) {
944 auto str = fileutils::loadCacheTextFile(cachePath_ /
"dhtproxy",
945 std::chrono::hours(24 * 14));
947 if (json::parse(str, root)) {
948 proxyServerCached_ = root[getProxyConfigKey()].asString();
950 }
catch (
const std::exception& e) {
951 JAMI_LOG(
"[Account {}] Unable to load proxy URL from cache: {}",
954 proxyServerCached_.clear();
957 proxyServerCached_.clear();
959 std::filesystem::remove(cachePath_ /
"dhtproxy", ec);
961 auto credentials = consumeConfigCredentials();
962 loadAccount(credentials.archive_password_scheme,
963 credentials.archive_password,
964 credentials.archive_pin,
965 credentials.archive_path);
969JamiAccount::changeArchivePassword(
const std::string& password_old,
const std::string& password_new)
972 if (!accountManager_->changePassword(password_old, password_new)) {
973 JAMI_ERROR(
"[Account {}] Unable to change archive password", getAccountID());
979 }
catch (
const std::exception& ex) {
980 JAMI_ERROR(
"[Account {}] Unable to change archive password: {}", getAccountID(), ex.what());
981 if (password_old.empty()) {
983 emitSignal<libjami::ConfigurationSignal::AccountDetailsChanged>(getAccountID(),
984 getAccountDetails());
988 if (password_old != password_new)
989 emitSignal<libjami::ConfigurationSignal::AccountDetailsChanged>(getAccountID(),
990 getAccountDetails());
995JamiAccount::isPasswordValid(
const std::string& password)
997 return accountManager_ and accountManager_->isPasswordValid(password);
1001JamiAccount::getPasswordKey(
const std::string& password)
1003 return accountManager_ ? accountManager_->getPasswordKey(password) : std::vector<uint8_t>();
1007JamiAccount::provideAccountAuthentication(
const std::string& credentialsFromUser,
1008 const std::string& scheme)
1010 if (
auto manager = std::dynamic_pointer_cast<ArchiveAccountManager>(accountManager_)) {
1011 return manager->provideAccountAuthentication(credentialsFromUser, scheme);
1013 JAMI_ERR(
"[LinkDevice] Invalid AccountManager instance while providing current account "
1019JamiAccount::addDevice(
const std::string& uriProvided)
1021 JAMI_LOG(
"[LinkDevice] JamiAccount::addDevice({}, {})", getAccountID(), uriProvided);
1022 if (not accountManager_) {
1023 JAMI_ERR(
"[LinkDevice] Invalid AccountManager instance while adding a device.");
1024 return static_cast<int32_t
>(AccountManager::AddDeviceError::GENERIC);
1026 auto authHandler = channelHandlers_.find(Uri::Scheme::AUTH);
1027 if (authHandler == channelHandlers_.end())
1028 return static_cast<int32_t
>(AccountManager::AddDeviceError::GENERIC);
1029 return accountManager_->addDevice(uriProvided,
1030 config().archiveHasPassword
1031 ? fileutils::ARCHIVE_AUTH_SCHEME_PASSWORD
1032 : fileutils::ARCHIVE_AUTH_SCHEME_NONE,
1037JamiAccount::cancelAddDevice(uint32_t op_token)
1039 if (!accountManager_)
1041 return accountManager_->cancelAddDevice(op_token);
1045JamiAccount::confirmAddDevice(uint32_t op_token)
1047 if (!accountManager_)
1049 return accountManager_->confirmAddDevice(op_token);
1053JamiAccount::exportArchive(
const std::string& destinationPath,
1054 std::string_view scheme,
1055 const std::string& password)
1058 return manager->exportArchive(destinationPath, scheme, password);
1064JamiAccount::setValidity(std::string_view scheme,
1065 const std::string& pwd,
1066 const dht::InfoHash&
id,
1070 if (manager->setValidity(scheme, pwd, id_,
id, validity)) {
1079JamiAccount::forceReloadAccount()
1089JamiAccount::unlinkConversations(
const std::set<std::string>& removed)
1091 std::lock_guard lock(configurationMutex_);
1092 if (
auto info = accountManager_->getInfo()) {
1093 auto contacts = info->contacts->getContacts();
1094 for (
auto& [
id, c] : contacts) {
1095 if (removed.find(c.conversationId) != removed.end()) {
1096 info->contacts->updateConversation(
id,
"");
1098 "[Account {}] Detected removed conversation ({}) in contact details for {}",
1108JamiAccount::isValidAccountDevice(
const dht::crypto::Certificate& cert)
const
1110 if (accountManager_) {
1111 if (
auto info = accountManager_->getInfo()) {
1113 return info->contacts->isValidAccountDevice(cert).isValid();
1120JamiAccount::revokeDevice(
const std::string& device,
1121 std::string_view scheme,
1122 const std::string& password)
1124 if (not accountManager_)
1126 return accountManager_->revokeDevice(
1128 emitSignal<libjami::ConfigurationSignal::DeviceRevocationEnded>(getAccountID(),
1136std::pair<std::string, std::string>
1137JamiAccount::saveIdentity(
const dht::crypto::Identity
id,
1138 const std::filesystem::path& path,
1139 const std::string& name)
1141 auto names = std::make_pair(name +
".key", name +
".crt");
1143 fileutils::saveFile(path / names.first,
id.first->serialize(), 0600);
1145 fileutils::saveFile(path / names.second,
id.second->getPacked(), 0600);
1151JamiAccount::loadAccount(
const std::string& archive_password_scheme,
1152 const std::string& archive_password,
1153 const std::string& archive_pin,
1154 const std::string& archive_path)
1156 if (registrationState_ == RegistrationState::INITIALIZING)
1159 JAMI_DEBUG(
"[Account {:s}] Loading account", getAccountID());
1160 AccountManager::OnChangeCallback callbacks {
1161 [
this](
const std::string& uri,
bool confirmed) {
1165 dht::ThreadPool::io().run([w = weak(), uri, confirmed] {
1166 if (
auto shared = w.lock()) {
1167 if (auto cm = shared->convModule(true)) {
1168 auto activeConv = cm->getOneToOneConversation(uri);
1169 if (!activeConv.empty())
1170 cm->bootstrap(activeConv);
1172 emitSignal<libjami::ConfigurationSignal::ContactAdded>(shared->getAccountID(),
1179 [
this](
const std::string& uri,
bool banned) {
1182 dht::ThreadPool::io().run([w = weak(), uri, banned] {
1183 if (
auto shared = w.lock()) {
1185 if (auto convModule = shared->convModule(true))
1186 convModule->removeContact(uri, banned);
1190 if (shared->connectionManager_ && uri != shared->getUsername()) {
1191 shared->connectionManager_->closeConnectionsWith(uri);
1194 emitSignal<libjami::ConfigurationSignal::ContactRemoved>(shared->getAccountID(),
1200 [
this](
const std::string& uri,
1201 const std::string& conversationId,
1202 const std::vector<uint8_t>& payload,
1206 dht::ThreadPool::io().run([w = weak(), uri, conversationId, payload, received] {
1207 if (
auto shared = w.lock()) {
1208 shared->clearProfileCache(uri);
1209 if (conversationId.empty()) {
1211 emitSignal<libjami::ConfigurationSignal::IncomingTrustRequest>(
1212 shared->getAccountID(), conversationId, uri, payload, received);
1216 if (
auto cm = shared->convModule(
true)) {
1217 auto activeConv = cm->getOneToOneConversation(uri);
1218 if (activeConv != conversationId)
1219 cm->onTrustRequest(uri, conversationId, payload, received);
1224 [
this](
const std::map<DeviceId, KnownDevice>& devices) {
1225 std::map<std::string, std::string> ids;
1226 for (
auto& d : devices) {
1227 auto id = d.first.toString();
1228 auto label = d.second.name.empty() ?
id.substr(0, 8) : d.second.name;
1229 ids.emplace(std::move(
id), std::move(label));
1231 runOnMainThread([
id = getAccountID(), devices = std::move(ids)] {
1232 emitSignal<libjami::ConfigurationSignal::KnownDevicesChanged>(
id, devices);
1235 [
this](
const std::string& conversationId,
const std::string& deviceId) {
1239 if (
auto cm = convModule(
true))
1240 cm->acceptConversationRequest(conversationId, deviceId);
1242 [
this](
const std::string& uri,
const std::string& convFromReq) {
1243 dht::ThreadPool::io().run([w = weak(), convFromReq, uri] {
1244 if (
auto shared = w.lock()) {
1245 auto cm = shared->convModule(true);
1247 auto requestPath = shared->cachePath_ /
"requests" / uri;
1248 dhtnet::fileutils::remove(requestPath);
1269 const auto& conf = config();
1271 auto oldIdentity = id_.first ? id_.first->getPublicKey().getLongId() :
DeviceId();
1272 if (conf.managerUri.empty()) {
1273 accountManager_ = std::make_shared<ArchiveAccountManager>(
1277 [
this](DeviceSync&& syncData) {
1278 if (
auto sm = syncModule()) {
1279 auto syncDataPtr = std::make_shared<SyncMsg>();
1280 syncDataPtr->ds = std::move(syncData);
1281 sm->syncWithConnected(syncDataPtr);
1284 conf.archivePath.empty() ?
"archive.gz" : conf.archivePath,
1287 accountManager_ = std::make_shared<ServerAccountManager>(getAccountID(),
1293 auto id = accountManager_->loadIdentity(conf.tlsCertificateFile,
1294 conf.tlsPrivateKeyFile,
1297 if (
auto info = accountManager_->useIdentity(
id,
1299 conf.receiptSignature,
1300 conf.managerUsername,
1303 id_ = std::move(
id);
1304 config_->username = info->accountId;
1305 JAMI_WARNING(
"[Account {:s}] Loaded account identity", getAccountID());
1306 if (info->identity.first->getPublicKey().getLongId() != oldIdentity) {
1307 JAMI_WARNING(
"[Account {:s}] Identity changed", getAccountID());
1309 std::lock_guard lk(moduleMtx_);
1310 convModule_.reset();
1316 convModule()->setAccountManager(accountManager_);
1318 if (not isEnabled()) {
1319 setRegistrationState(RegistrationState::UNREGISTERED);
1321 convModule()->loadConversations();
1322 }
else if (isEnabled()) {
1323 JAMI_WARNING(
"[Account {}] useIdentity failed!", getAccountID());
1324 if (not conf.managerUri.empty() and archive_password.empty()) {
1325 Migration::setState(accountID_, Migration::State::INVALID);
1326 setRegistrationState(RegistrationState::ERROR_NEED_MIGRATION);
1330 bool migrating = registrationState_ == RegistrationState::ERROR_NEED_MIGRATION;
1331 setRegistrationState(RegistrationState::INITIALIZING);
1332 auto fDeviceKey = dht::ThreadPool::computation()
1333 .getShared<std::shared_ptr<dht::crypto::PrivateKey>>([]() {
1334 return std::make_shared<dht::crypto::PrivateKey>(
1335 dht::crypto::PrivateKey::generate());
1338 std::unique_ptr<AccountManager::AccountCredentials> creds;
1339 if (conf.managerUri.empty()) {
1340 auto acreds = std::make_unique<ArchiveAccountManager::ArchiveAccountCredentials>();
1341 auto archivePath = fileutils::getFullPath(idPath_, conf.archivePath);
1344 if (!archive_path.empty()) {
1346 acreds->scheme =
"file";
1347 acreds->uri = archive_path;
1348 }
else if (!conf.archive_url.empty() && conf.archive_url ==
"jami-auth") {
1350 JAMI_DEBUG(
"[Account {}] [LinkDevice] scheme p2p & uri {}", getAccountID(), conf.archive_url);
1351 acreds->scheme =
"p2p";
1352 acreds->uri = conf.archive_url;
1353 }
else if (!archive_pin.empty()) {
1355 acreds->scheme =
"dht";
1356 acreds->uri = archive_pin;
1357 acreds->dhtBootstrap = loadBootstrap();
1358 acreds->dhtPort = dhtPortUsed();
1359 }
else if (std::filesystem::is_regular_file(archivePath)) {
1361 acreds->scheme =
"local";
1362 acreds->uri = archivePath.string();
1363 acreds->updateIdentity = id;
1366 creds = std::move(acreds);
1368 auto screds = std::make_unique<ServerAccountManager::ServerAccountCredentials>();
1369 screds->username = conf.managerUsername;
1370 creds = std::move(screds);
1372 creds->password = archive_password;
1373 bool hasPassword = !archive_password.empty();
1374 if (hasPassword && archive_password_scheme.empty())
1375 creds->password_scheme = fileutils::ARCHIVE_AUTH_SCHEME_PASSWORD;
1377 creds->password_scheme = archive_password_scheme;
1382 fmt::ptr(accountManager_));
1384 accountManager_->initAuthentication(
1386 ip_utils::getDeviceName(),
1391 hasPassword](
const AccountInfo& info,
1392 const std::map<std::string, std::string>& config,
1393 std::string&& receipt,
1394 std::vector<uint8_t>&& receipt_signature) {
1395 auto sthis = w.lock();
1398 JAMI_LOG(
"[Account {}] Auth success! Device: {}", getAccountID(), info.deviceId);
1400 dhtnet::fileutils::check_dir(idPath_, 0700);
1402 auto id = info.identity;
1403 editConfig([&](JamiAccountConfig& conf) {
1404 std::tie(conf.tlsPrivateKeyFile, conf.tlsCertificateFile)
1405 = saveIdentity(
id, idPath_, DEVICE_ID_PATH);
1406 conf.tlsPassword = {};
1407 auto passwordIt = config.find(
1409 if (passwordIt != config.end() && !passwordIt->second.empty()) {
1410 conf.archiveHasPassword = passwordIt->second ==
"true";
1412 conf.archiveHasPassword = hasPassword;
1415 if (not conf.managerUri.empty()) {
1416 conf.registeredName = conf.managerUsername;
1418 registeredName_ = conf.managerUsername;
1421 conf.username = info.accountId;
1422 conf.deviceName = accountManager_->getAccountDeviceName();
1424 auto nameServerIt = config.find(
1426 if (nameServerIt != config.end() && !nameServerIt->second.empty()) {
1427 conf.nameServer = nameServerIt->second;
1429 auto displayNameIt = config.find(
1431 if (displayNameIt != config.end() && !displayNameIt->second.empty()) {
1432 conf.displayName = displayNameIt->second;
1434 conf.receipt = std::move(receipt);
1435 conf.receiptSignature = std::move(receipt_signature);
1436 conf.fromMap(config);
1438 id_ = std::move(
id);
1440 std::lock_guard lk(moduleMtx_);
1441 convModule_.reset();
1444 Migration::setState(getAccountID(), Migration::State::SUCCESS);
1446 if (not info.photo.empty() or not config_->displayName.empty())
1447 emitSignal<libjami::ConfigurationSignal::AccountProfileReceived>(
1448 getAccountID(), config_->displayName, info.photo);
1449 setRegistrationState(RegistrationState::UNREGISTERED);
1454 accountId = getAccountID(),
1455 migrating](AccountManager::AuthError error,
const std::string& message) {
1456 JAMI_WARNING(
"[Account {}] Auth error: {} {}", accountId, (
int) error, message);
1457 if ((
id.first || migrating)
1458 && error == AccountManager::AuthError::INVALID_ARGUMENTS) {
1461 Migration::setState(accountId, Migration::State::INVALID);
1462 if (
auto acc = w.lock())
1463 acc->setRegistrationState(RegistrationState::ERROR_NEED_MIGRATION);
1466 if (
auto acc = w.lock())
1467 acc->setRegistrationState(RegistrationState::ERROR_GENERIC);
1468 runOnMainThread([accountId = std::move(accountId)] {
1469 Manager::instance().removeAccount(accountId,
true);
1475 }
catch (
const std::exception& e) {
1476 JAMI_WARNING(
"[Account {}] Error loading account: {}", getAccountID(), e.what());
1477 accountManager_.reset();
1478 setRegistrationState(RegistrationState::ERROR_GENERIC);
1482std::map<std::string, std::string>
1483JamiAccount::getVolatileAccountDetails()
const
1485 auto a = SIPAccountBase::getVolatileAccountDetails();
1488 auto registeredName = getRegisteredName();
1489 if (not registeredName.empty())
1495 deviceAnnounced_ ? TRUE_STR : FALSE_STR);
1496 if (accountManager_) {
1497 if (
auto info = accountManager_->getInfo()) {
1506JamiAccount::lookupName(
const std::string& name)
1508 std::lock_guard lock(configurationMutex_);
1509 if (accountManager_)
1510 accountManager_->lookupUri(name,
1511 config().nameServer,
1512 [acc = getAccountID(), name](
const std::string& regName,
1513 const std::string& address,
1515 emitSignal<libjami::ConfigurationSignal::RegisteredNameFound>(
1516 acc, name, (
int) response, address, regName);
1521JamiAccount::lookupAddress(
const std::string& addr)
1523 std::lock_guard lock(configurationMutex_);
1524 auto acc = getAccountID();
1525 if (accountManager_)
1526 accountManager_->lookupAddress(
1528 [acc, addr](
const std::string& regName,
1529 const std::string& address,
1530 NameDirectory::Response response) {
1531 emitSignal<libjami::ConfigurationSignal::RegisteredNameFound>(acc,
1540JamiAccount::registerName(
const std::string& name,
1541 const std::string& scheme,
1542 const std::string& password)
1544 std::lock_guard lock(configurationMutex_);
1545 if (accountManager_)
1546 accountManager_->registerName(
1550 [acc = getAccountID(), name, w = weak()](NameDirectory::RegistrationResponse response,
1551 const std::string& regName) {
1552 auto res = (int) std::min(response, NameDirectory::RegistrationResponse::error);
1553 if (response == NameDirectory::RegistrationResponse::success) {
1554 if (
auto this_ = w.lock()) {
1555 if (this_->setRegisteredName(regName)) {
1556 this_->editConfig([&](JamiAccountConfig& config) {
1557 config.registeredName = regName;
1559 emitSignal<libjami::ConfigurationSignal::VolatileDetailsChanged>(
1560 this_->accountID_, this_->getVolatileAccountDetails());
1564 emitSignal<libjami::ConfigurationSignal::NameRegistrationEnded>(acc, res, name);
1570JamiAccount::searchUser(
const std::string& query)
1572 if (accountManager_)
1573 return accountManager_->searchUser(
1586JamiAccount::forEachPendingCall(
const DeviceId& deviceId,
1587 const std::function<
void(
const std::shared_ptr<SIPCall>&)>& cb)
1589 std::vector<std::shared_ptr<SIPCall>> pc;
1591 std::lock_guard lk(pendingCallsMutex_);
1592 pc = std::move(pendingCalls_[deviceId]);
1594 for (
const auto& pendingCall : pc) {
1600JamiAccount::registerAsyncOps()
1602 auto onLoad = [
this, loaded = std::make_shared<std::atomic_uint>()] {
1603 if (++(*loaded) == 2u) {
1604 runOnMainThread([w = weak()] {
1605 if (
auto s = w.lock()) {
1606 std::lock_guard lock(s->configurationMutex_);
1613 loadCachedProxyServer([onLoad](
const std::string&) { onLoad(); });
1616 JAMI_LOG(
"[Account {:s}] UPnP: attempting to map ports", getAccountID());
1619 if (dhtUpnpMapping_.isValid()) {
1620 upnpCtrl_->releaseMapping(dhtUpnpMapping_);
1623 dhtUpnpMapping_.enableAutoUpdate(
true);
1626 dhtUpnpMapping_.setNotifyCallback([w = weak(),
1628 update = std::make_shared<bool>(
false)](
1629 dhtnet::upnp::Mapping::sharedPtr_t mapRes) {
1630 if (
auto accPtr = w.lock()) {
1631 auto& dhtMap = accPtr->dhtUpnpMapping_;
1632 const auto& accId = accPtr->getAccountID();
1634 JAMI_LOG(
"[Account {:s}] DHT UPnP mapping changed to {:s}",
1636 mapRes->toString(true));
1640 if (dhtMap.getMapKey() != mapRes->getMapKey()
1641 or dhtMap.getState() != mapRes->getState()) {
1645 if (mapRes->getState() == dhtnet::upnp::MappingState::OPEN
1646 or (mapRes->getState() == dhtnet::upnp::MappingState::FAILED
1647 and dhtMap.getState() == dhtnet::upnp::MappingState::OPEN)) {
1649 dhtMap.updateFrom(mapRes);
1652 "[Account {:s}] Allocated port changed to {}. Restarting the "
1655 accPtr->dhtPortUsed());
1657 accPtr->dht_->connectivityChanged();
1661 dhtMap.updateFrom(mapRes);
1667 if (mapRes->getState() == dhtnet::upnp::MappingState::OPEN) {
1668 dhtMap.updateFrom(mapRes);
1670 "[Account {:s}] Mapping {:s} successfully allocated: starting the DHT",
1674 JAMI_WARNING(
"[Account {:s}] Mapping request is in {:s} state: starting "
1677 mapRes->getStateStr());
1687 auto map = upnpCtrl_->reserveMapping(dhtUpnpMapping_);
1701JamiAccount::doRegister()
1703 std::lock_guard lock(configurationMutex_);
1704 if (not isUsable()) {
1705 JAMI_WARNING(
"[Account {:s}] Account must be enabled and active to register, ignoring",
1710 JAMI_LOG(
"[Account {:s}] Starting account…", getAccountID());
1715 if (registrationState_ == RegistrationState::INITIALIZING
1716 || registrationState_ == RegistrationState::ERROR_NEED_MIGRATION)
1720 setRegistrationState(RegistrationState::TRYING);
1722 if (upnpCtrl_ or 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}",
1753 JAMI_LOG(
"[Account {:s}] {:s} presence for {:s}",
1755 track ?
"Track" :
"Untrack",
1758 auto h = dht::InfoHash(buddyUri);
1759 std::unique_lock lock(buddyInfoMtx);
1761 auto buddy = trackedBuddies_.emplace(h,
BuddyInfo {h});
1763 trackPresence(buddy.first->first, buddy.first->second);
1765 auto it = presenceState_.find(buddyUri);
1766 if (it != presenceState_.end() && it->second != PresenceState::DISCONNECTED) {
1768 emitSignal<libjami::PresenceSignal::NewBuddyNotification>(getAccountID(),
1770 static_cast<int>(it->second),
1774 auto buddy = trackedBuddies_.find(h);
1775 if (buddy != trackedBuddies_.end()) {
1776 if (
auto dht = dht_)
1777 if (
dht->isRunning())
1778 dht->cancelListen(h, std::move(buddy->second.listenToken));
1779 trackedBuddies_.erase(buddy);
1785JamiAccount::trackPresence(
const dht::InfoHash& h, BuddyInfo& buddy)
1788 if (not
dht or not
dht->isRunning()) {
1792 =
dht->listen<DeviceAnnouncement>(h, [
this, h](DeviceAnnouncement&&
dev,
bool expired) {
1793 bool wasConnected, isConnected;
1795 std::lock_guard lock(buddyInfoMtx);
1796 auto buddy = trackedBuddies_.find(h);
1797 if (buddy == trackedBuddies_.end())
1799 wasConnected = buddy->second.devices_cnt > 0;
1801 --buddy->second.devices_cnt;
1803 ++buddy->second.devices_cnt;
1804 isConnected = buddy->second.devices_cnt > 0;
1808 runOnMainThread([w = weak(), h,
dev, expired, isConnected, wasConnected]() {
1809 auto sthis = w.lock();
1814 sthis->messageEngine_.onPeerOnline(h.toString());
1816 if (isConnected and not wasConnected) {
1817 sthis->onTrackedBuddyOnline(h);
1818 }
else if (not isConnected and wasConnected) {
1819 sthis->onTrackedBuddyOffline(h);
1827std::map<std::string, bool>
1828JamiAccount::getTrackedBuddyPresence()
const
1830 std::lock_guard lock(buddyInfoMtx);
1831 std::map<std::string, bool> presence_info;
1832 for (
const auto& buddy_info_p : trackedBuddies_)
1833 presence_info.emplace(buddy_info_p.first.toString(), buddy_info_p.second.devices_cnt > 0);
1834 return presence_info;
1838JamiAccount::onTrackedBuddyOnline(
const dht::InfoHash& contactId)
1840 std::string id(contactId.toString());
1841 JAMI_DEBUG(
"[Account {:s}] Buddy {} online", getAccountID(),
id);
1842 auto& state = presenceState_[id];
1843 if (state < PresenceState::AVAILABLE) {
1844 state = PresenceState::AVAILABLE;
1845 emitSignal<libjami::PresenceSignal::NewBuddyNotification>(getAccountID(),
1848 PresenceState::AVAILABLE),
1852 if (
auto details = getContactInfo(
id)) {
1853 if (!details->confirmed) {
1854 auto convId = convModule()->getOneToOneConversation(
id);
1859 std::lock_guard lock(configurationMutex_);
1860 if (accountManager_) {
1862 auto requestPath = cachePath_ /
"requests" / id;
1863 std::vector<uint8_t> payload;
1865 payload = fileutils::loadFile(requestPath);
1868 if (payload.size() >= 64000) {
1870 "[Account {:s}] Trust request for contact {:s} is too big, reset payload",
1875 accountManager_->sendTrustRequest(
id, convId, payload);
1882JamiAccount::onTrackedBuddyOffline(
const dht::InfoHash& contactId)
1884 auto id = contactId.toString();
1885 JAMI_DEBUG(
"[Account {:s}] Buddy {} offline", getAccountID(),
id);
1886 auto& state = presenceState_[id];
1887 if (state > PresenceState::DISCONNECTED) {
1888 if (state == PresenceState::CONNECTED) {
1889 JAMI_WARNING(
"[Account {:s}] Buddy {} is not present on the DHT, but P2P connected",
1894 state = PresenceState::DISCONNECTED;
1895 emitSignal<libjami::PresenceSignal::NewBuddyNotification>(getAccountID(),
1898 PresenceState::DISCONNECTED),
1904JamiAccount::doRegister_()
1906 if (registrationState_ != RegistrationState::TRYING) {
1907 JAMI_ERROR(
"[Account {}] Already registered", getAccountID());
1911 JAMI_DEBUG(
"[Account {}] Starting account…", getAccountID());
1912 const auto& conf = config();
1915 if (not accountManager_ or not accountManager_->getInfo())
1916 throw std::runtime_error(
"No identity configured for this account.");
1918 if (dht_->isRunning()) {
1919 JAMI_ERROR(
"[Account {}] DHT already running (stopping it first).", getAccountID());
1923 convModule()->clearPendingFetch();
1927 accountManager_->lookupAddress(
1928 accountManager_->getInfo()->accountId,
1929 [w = weak()](
const std::string& regName,
1930 const std::string& address,
1931 const NameDirectory::Response& response) {
1932 if (
auto this_ = w.lock()) {
1933 if (response == NameDirectory::Response::found
1934 or response == NameDirectory::Response::notFound) {
1935 const auto& nameResult = response == NameDirectory::Response::found
1938 if (this_->setRegisteredName(nameResult)) {
1939 this_->editConfig([&](JamiAccountConfig& config) {
1940 config.registeredName = nameResult;
1942 emitSignal<libjami::ConfigurationSignal::VolatileDetailsChanged>(
1943 this_->accountID_, this_->getVolatileAccountDetails());
1950 dht::DhtRunner::Config config {};
1951 config.dht_config.node_config.network = 0;
1952 config.dht_config.node_config.maintain_storage =
false;
1953 config.dht_config.node_config.persist_path = (cachePath_ /
"dhtstate").
string();
1954 config.dht_config.id = id_;
1955 config.dht_config.cert_cache_all =
true;
1956 config.push_node_id = getAccountID();
1957 config.push_token = conf.deviceKey;
1958 config.push_topic = conf.notificationTopic;
1959 config.push_platform = conf.platform;
1961 config.threaded =
true;
1962 config.peer_discovery = conf.dhtPeerDiscovery;
1963 config.peer_publish = conf.dhtPeerDiscovery;
1964 if (conf.proxyEnabled)
1965 config.proxy_server = proxyServerCached_;
1967 if (not config.proxy_server.empty()) {
1968 JAMI_LOG(
"[Account {}] Using proxy server {}", getAccountID(), config.proxy_server);
1969 if (not config.push_token.empty()) {
1971 "[Account {}] using push notifications with platform: {}, topic: {}, token: {}",
1973 config.push_platform,
1980 if (conf.accountPeerDiscovery or conf.accountPublish) {
1981 peerDiscovery_ = std::make_shared<dht::PeerDiscovery>();
1982 if (conf.accountPeerDiscovery) {
1983 JAMI_LOG(
"[Account {}] Starting Jami account discovery…", getAccountID());
1984 startAccountDiscovery();
1986 if (conf.accountPublish)
1987 startAccountPublish();
1989 dht::DhtRunner::Context context {};
1990 context.peerDiscovery = peerDiscovery_;
1991 context.rng = std::make_unique<std::mt19937_64>(dht::crypto::getDerivedRandomEngine(rand));
1993 auto dht_log_level = Manager::instance().dhtLogLevel;
1994 if (dht_log_level > 0) {
1995 context.logger = Logger::dhtLogger();
1997 context.certificateStore = [&](
const dht::InfoHash& pk_id) {
1998 std::vector<std::shared_ptr<dht::crypto::Certificate>> ret;
1999 if (
auto cert = certStore().getCertificate(pk_id.toString()))
2000 ret.emplace_back(std::move(cert));
2001 JAMI_LOG(
"Query for local certificate store: {}: {} found.",
2007 context.statusChangedCallback = [
this](dht::NodeStatus s4, dht::NodeStatus s6) {
2008 JAMI_LOG(
"[Account {}] DHT status: IPv4 {}; IPv6 {}",
2013 auto newStatus = std::max(s4, s6);
2014 switch (newStatus) {
2015 case dht::NodeStatus::Connecting:
2016 state = RegistrationState::TRYING;
2018 case dht::NodeStatus::Connected:
2019 state = RegistrationState::REGISTERED;
2021 case dht::NodeStatus::Disconnected:
2022 state = RegistrationState::UNREGISTERED;
2025 state = RegistrationState::ERROR_GENERIC;
2029 setRegistrationState(state);
2031 context.identityAnnouncedCb = [
this](
bool ok) {
2034 accountManager_->startSync(
2035 [
this](
const std::shared_ptr<dht::crypto::Certificate>& crt) {
2039 auto deviceId = crt->getLongId().toString();
2040 if (accountManager_->getInfo()->deviceId == deviceId)
2043 dht::ThreadPool::io().run([w = weak(), deviceId, crt] {
2044 auto shared = w.lock();
2045 if (!shared)
return;
2046 std::unique_lock lk(shared->connManagerMtx_);
2047 shared->initConnectionManager();
2049 std::shared_lock slk(shared->connManagerMtx_);
2053 if (!shared->connectionManager_)
2055 shared->requestMessageConnection(shared->getUsername(), crt->getLongId(),
"sync");
2056 if (!shared->syncModule()->isConnected(crt->getLongId())) {
2057 shared->channelHandlers_[Uri::Scheme::SYNC]
2058 ->connect(crt->getLongId(),
2060 [](std::shared_ptr<dhtnet::ChannelSocket> socket,
2061 const DeviceId& deviceId) {});
2068 deviceAnnounced_ =
true;
2071 dht::ThreadPool::io().run([w = weak()] {
2072 if (
auto shared = w.lock())
2073 shared->convModule()->bootstrap();
2075 emitSignal<libjami::ConfigurationSignal::VolatileDetailsChanged>(
2082 dht_->run(dhtPortUsed(), config, std::move(context));
2084 for (
const auto& bootstrap : loadBootstrap())
2085 dht_->bootstrap(bootstrap);
2087 dhtBoundPort_ = dht_->getBoundPort();
2089 accountManager_->setDht(dht_);
2091 std::unique_lock lkCM(connManagerMtx_);
2092 initConnectionManager();
2093 connectionManager_->onDhtConnected(*accountManager_->getInfo()->devicePk);
2094 connectionManager_->onICERequest([
this](
const DeviceId& deviceId) {
2095 std::promise<bool>
accept;
2096 std::future<bool> fut =
accept.get_future();
2097 accountManager_->findCertificate(
2098 deviceId, [
this, &accept](
const std::shared_ptr<dht::crypto::Certificate>& cert) {
2103 dht::InfoHash peer_account_id;
2104 auto res = accountManager_->onPeerCertificate(cert,
2105 this->config().dhtPublicInCalls,
2107 JAMI_LOG(
"[Account {}] [device {}] {} ICE request from {}",
2110 res ?
"Accepting" :
"Discarding",
2115 auto result = fut.get();
2118 connectionManager_->onChannelRequest(
2119 [
this](
const std::shared_ptr<dht::crypto::Certificate>& cert,
const std::string& name) {
2120 JAMI_LOG(
"[Account {}] [device {}] New channel requested: '{}'",
2125 if (this->config().turnEnabled && turnCache_) {
2126 auto addr = turnCache_->getResolvedTurn();
2127 if (addr == std::nullopt) {
2131 turnCache_->refresh();
2135 auto uri = Uri(name);
2136 std::shared_lock lk(connManagerMtx_);
2137 auto itHandler = channelHandlers_.find(uri.scheme());
2138 if (itHandler != channelHandlers_.end() && itHandler->second)
2139 return itHandler->second->onRequest(cert, name);
2140 return name ==
"sip";
2142 connectionManager_->onConnectionReady([
this](
const DeviceId& deviceId,
2143 const std::string& name,
2144 std::shared_ptr<dhtnet::ChannelSocket> channel) {
2146 auto cert = channel->peerCertificate();
2147 if (!cert || !cert->issuer)
2149 auto peerId = cert->issuer->getId().toString();
2151 if (accountManager()->getCertificateStatus(peerId)
2152 == dhtnet::tls::TrustStore::PermissionStatus::BANNED) {
2153 channel->shutdown();
2156 if (name ==
"sip") {
2157 cacheSIPConnection(std::move(channel), peerId, deviceId);
2158 }
else if (name.find(
"git://") == 0) {
2159 auto sep = name.find_last_of(
'/');
2160 auto conversationId = name.substr(sep + 1);
2161 auto remoteDevice = name.substr(6, sep - 6);
2163 if (channel->isInitiator()) {
2169 if (convModule()->isBanned(conversationId, remoteDevice)) {
2171 "[Account {:s}] [Conversation {}] Git server requested, but the "
2172 "device is unauthorized ({:s}) ",
2176 channel->shutdown();
2180 auto sock = convModule()->gitSocket(deviceId.toString(), conversationId);
2181 if (sock == channel) {
2187 "[Account {:s}] [Conversation {}] [device {}] Git server requested",
2190 deviceId.toString());
2191 auto gs = std::make_unique<GitServer>(accountID_, conversationId, channel);
2192 syncCnt_.fetch_add(1);
2193 gs->setOnFetched([w = weak(), conversationId, deviceId](
2194 const std::string& commit) {
2195 dht::ThreadPool::computation().run([w, conversationId, deviceId, commit]() {
2196 if (
auto shared = w.lock()) {
2197 shared->convModule()->setFetched(conversationId,
2198 deviceId.toString(),
2200 if (shared->syncCnt_.fetch_sub(1) == 1) {
2201 emitSignal<libjami::ConversationSignal::ConversationCloned>(
2202 shared->getAccountID().c_str());
2207 const dht::Value::Id serverId =
ValueIdDist()(rand);
2209 std::lock_guard lk(gitServersMtx_);
2210 gitServers_[serverId] = std::move(gs);
2212 channel->onShutdown([w = weak(), serverId]() {
2214 runOnMainThread([serverId, w]() {
2215 if (
auto sthis = w.lock()) {
2216 std::lock_guard lk(sthis->gitServersMtx_);
2217 sthis->gitServers_.erase(serverId);
2223 std::shared_lock lk(connManagerMtx_);
2224 auto uri = Uri(name);
2225 auto itHandler = channelHandlers_.find(uri.scheme());
2226 if (itHandler != channelHandlers_.end() && itHandler->second)
2227 itHandler->second->onReady(cert, name, std::move(channel));
2233 if (!conf.managerUri.empty() && accountManager_) {
2234 dynamic_cast<ServerAccountManager*
>(accountManager_.get())->onNeedsMigration([
this]() {
2235 editConfig([&](JamiAccountConfig& conf) {
2236 conf.receipt.clear();
2237 conf.receiptSignature.clear();
2239 Migration::setState(accountID_, Migration::State::INVALID);
2240 setRegistrationState(RegistrationState::ERROR_NEED_MIGRATION);
2242 dynamic_cast<ServerAccountManager*
>(accountManager_.get())
2243 ->syncBlueprintConfig([
this](
const std::map<std::string, std::string>& config) {
2244 editConfig([&](JamiAccountConfig& conf) { conf.fromMap(config); });
2245 emitSignal<libjami::ConfigurationSignal::AccountDetailsChanged>(
2250 std::lock_guard lock(buddyInfoMtx);
2251 for (
auto& buddy : trackedBuddies_) {
2252 buddy.second.devices_cnt = 0;
2253 trackPresence(buddy.first, buddy.second);
2255 }
catch (
const std::exception& e) {
2256 JAMI_ERR(
"Error registering DHT account: %s", e.what());
2257 setRegistrationState(RegistrationState::ERROR_GENERIC);
2262JamiAccount::convModule(
bool noCreation)
2265 return convModule_.get();
2266 if (!accountManager() || currentDeviceId() ==
"") {
2267 JAMI_ERROR(
"[Account {}] Calling convModule() with an uninitialized account",
2271 std::unique_lock lock(configurationMutex_);
2272 std::lock_guard lk(moduleMtx_);
2274 convModule_ = std::make_unique<ConversationModule>(
2277 [
this](
auto&& syncMsg) {
2278 dht::ThreadPool::computation().run([w = weak(), syncMsg] {
2279 if (
auto shared = w.lock()) {
2280 auto& config = shared->config();
2283 if (!config.managerUri.empty() && !syncMsg)
2284 if (auto am = shared->accountManager())
2286 if (auto sm = shared->syncModule())
2287 sm->syncWithConnected(syncMsg);
2291 [
this](
auto&& uri,
auto&& device,
auto&& msg,
auto token = 0) {
2295 auto deviceId = device ? device.toString() :
"";
2296 return sendTextMessage(uri, deviceId, msg, token);
2298 [
this](
const auto& convId,
const auto& deviceId,
auto cb,
const auto& type) {
2299 dht::ThreadPool::io().run([w = weak(), convId, deviceId, cb = std::move(cb), type] {
2300 auto shared = w.lock();
2303 if (
auto socket = shared->convModule()->gitSocket(deviceId, convId)) {
2310 std::shared_lock lkCM(shared->connManagerMtx_);
2311 if (!shared->connectionManager_) {
2317 shared->connectionManager_->connectDevice(
2319 fmt::format(
"git://{}/{}", deviceId, convId),
2322 convId](std::shared_ptr<dhtnet::ChannelSocket> socket,
const DeviceId&) {
2323 dht::ThreadPool::io().run([w,
2325 socket = std::move(socket),
2328 socket->onShutdown([w, deviceId = socket->deviceId(), convId] {
2329 dht::ThreadPool::io().run([w, deviceId, convId] {
2330 if (auto shared = w.lock())
2331 shared->convModule()
2332 ->removeGitSocket(deviceId.toString(), convId);
2346 [
this](
const auto& convId,
const auto& deviceId,
auto&& cb,
const auto& connectionType) {
2347 dht::ThreadPool::io().run([w = weak(),
2352 auto shared = w.lock();
2355 auto cm = shared->convModule();
2356 std::shared_lock lkCM(shared->connManagerMtx_);
2357 if (!shared->connectionManager_ || !cm || cm->isBanned(convId, deviceId)) {
2358 asio::post(*Manager::instance().ioContext(), [cb = std::move(cb)] { cb({}); });
2361 if (!shared->connectionManager_->isConnecting(
DeviceId(deviceId),
2362 fmt::format(
"swarm://{}",
2364 shared->connectionManager_->connectDevice(
2366 fmt::format(
"swarm://{}", convId),
2367 [w, cb = std::move(cb)](std::shared_ptr<dhtnet::ChannelSocket> socket,
2369 dht::ThreadPool::io().run([w,
2371 socket = std::move(socket),
2374 auto shared = w.lock();
2377 auto remoteCert = socket->peerCertificate();
2378 auto uri = remoteCert->issuer->getId().
toString();
2379 if (shared->accountManager()->getCertificateStatus(uri)
2380 == dhtnet::tls::TrustStore::PermissionStatus::BANNED) {
2384 shared->requestMessageConnection(uri, deviceId,
"");
2392 [
this](
auto&& convId,
auto&& from) {
2394 ->findCertificate(dht::InfoHash(from),
2395 [
this, from, convId](
2396 const std::shared_ptr<dht::crypto::Certificate>& cert) {
2397 auto info = accountManager_->getInfo();
2400 info->contacts->onTrustRequest(dht::InfoHash(from),
2401 cert->getSharedPublicKey(),
2408 autoLoadConversations_);
2410 return convModule_.get();
2414JamiAccount::syncModule()
2416 if (!accountManager() || currentDeviceId() ==
"") {
2417 JAMI_ERR() <<
"Calling syncModule() with an uninitialized account.";
2420 std::lock_guard lk(moduleMtx_);
2422 syncModule_ = std::make_unique<SyncModule>(weak());
2423 return syncModule_.get();
2427JamiAccount::onTextMessage(
const std::string&
id,
2428 const std::string& from,
2429 const std::shared_ptr<dht::crypto::Certificate>& peerCert,
2430 const std::map<std::string, std::string>& payloads)
2434 SIPAccountBase::onTextMessage(
id, fromUri, peerCert, payloads);
2440JamiAccount::loadConversation(
const std::string& convId)
2442 if (
auto cm = convModule(
true))
2443 cm->loadSingleConversation(convId);
2447JamiAccount::doUnregister(
bool forceShutdownConnections)
2449 std::unique_lock lock(configurationMutex_);
2450 if (registrationState_ >= RegistrationState::ERROR_GENERIC) {
2455 std::condition_variable cv;
2456 bool shutdown_complete {
false};
2458 if (peerDiscovery_) {
2463 JAMI_WARN(
"[Account %s] Unregistering account %p", getAccountID().c_str(),
this);
2466 JAMI_WARN(
"[Account %s] DHT shutdown complete", getAccountID().c_str());
2467 std::lock_guard lock(mtx);
2468 shutdown_complete =
true;
2474 std::lock_guard lk(pendingCallsMutex_);
2475 pendingCalls_.clear();
2481 if (not isEnabled() || forceShutdownConnections)
2482 shutdownConnections();
2485 if (upnpCtrl_ and dhtUpnpMapping_.isValid()) {
2486 upnpCtrl_->releaseMapping(dhtUpnpMapping_);
2490 std::unique_lock lock(mtx);
2491 cv.wait(lock, [&] {
return shutdown_complete; });
2494 setRegistrationState(RegistrationState::UNREGISTERED);
2507 const std::string& detail_str)
2509 if (registrationState_ != state) {
2510 if (state == RegistrationState::REGISTERED) {
2511 JAMI_WARNING(
"[Account {}] Connected", getAccountID());
2512 turnCache_->refresh();
2513 if (connectionManager_)
2514 connectionManager_->storeActiveIpAddress();
2515 }
else if (state == RegistrationState::TRYING) {
2516 JAMI_WARNING(
"[Account {}] Connecting…", getAccountID());
2518 deviceAnnounced_ =
false;
2519 JAMI_WARNING(
"[Account {}] Disconnected", getAccountID());
2523 Account::setRegistrationState(state, detail_code, detail_str);
2527JamiAccount::reloadContacts()
2529 accountManager_->reloadContacts();
2533JamiAccount::connectivityChanged()
2536 if (not isUsable()) {
2541 if (
auto cm = convModule())
2542 cm->connectivityChanged();
2543 dht_->connectivityChanged();
2545 std::shared_lock lkCM(connManagerMtx_);
2546 if (connectionManager_) {
2547 connectionManager_->connectivityChanged();
2549 connectionManager_->setPublishedAddress({});
2555JamiAccount::findCertificate(
2556 const dht::InfoHash& h,
2557 std::function<
void(
const std::shared_ptr<dht::crypto::Certificate>&)>&& cb)
2559 if (accountManager_)
2560 return accountManager_->findCertificate(h, std::move(cb));
2565JamiAccount::findCertificate(
2566 const dht::PkId&
id, std::function<
void(
const std::shared_ptr<dht::crypto::Certificate>&)>&& cb)
2568 if (accountManager_)
2569 return accountManager_->findCertificate(
id, std::move(cb));
2574JamiAccount::findCertificate(
const std::string& crt_id)
2576 if (accountManager_)
2577 return accountManager_->findCertificate(dht::InfoHash(crt_id));
2582JamiAccount::setCertificateStatus(
const std::string& cert_id,
2583 dhtnet::tls::TrustStore::PermissionStatus status)
2585 bool done = accountManager_ ? accountManager_->setCertificateStatus(cert_id, status) :
false;
2587 findCertificate(cert_id);
2588 emitSignal<libjami::ConfigurationSignal::CertificateStateChanged>(
2589 getAccountID(), cert_id, dhtnet::tls::TrustStore::statusToStr(status));
2595JamiAccount::setCertificateStatus(
const std::shared_ptr<crypto::Certificate>& cert,
2596 dhtnet::tls::TrustStore::PermissionStatus status,
2599 bool done = accountManager_ ? accountManager_->setCertificateStatus(cert, status, local)
2602 findCertificate(cert->getId().toString());
2603 emitSignal<libjami::ConfigurationSignal::CertificateStateChanged>(
2604 getAccountID(), cert->getId().toString(), dhtnet::tls::TrustStore::statusToStr(status));
2609std::vector<std::string>
2610JamiAccount::getCertificatesByStatus(dhtnet::tls::TrustStore::PermissionStatus status)
2612 if (accountManager_)
2613 return accountManager_->getCertificatesByStatus(status);
2618JamiAccount::isMessageTreated(dht::Value::Id
id)
2620 std::lock_guard lock(messageMutex_);
2621 return !treatedMessages_.add(
id);
2625JamiAccount::sha3SumVerify()
const
2627 return !noSha3sumVerification_;
2632JamiAccount::noSha3sumVerification(
bool newValue)
2634 noSha3sumVerification_ = newValue;
2638std::map<std::string, std::string>
2639JamiAccount::getKnownDevices()
const
2641 std::lock_guard lock(configurationMutex_);
2642 if (not accountManager_ or not accountManager_->getInfo())
2644 std::map<std::string, std::string> ids;
2645 for (
const auto& d : accountManager_->getKnownDevices()) {
2646 auto id = d.first.toString();
2647 auto label = d.second.name.empty() ?
id.substr(0, 8) : d.second.name;
2648 ids.emplace(std::move(
id), std::move(label));
2654JamiAccount::loadCachedUrl(
const std::string& url,
2655 const std::filesystem::path& cachePath,
2656 const std::chrono::seconds& cacheDuration,
2657 std::function<
void(
const dht::http::Response& response)> cb)
2659 dht::ThreadPool::io().run([cb, url, cachePath, cacheDuration, w = weak()]() {
2663 std::lock_guard lk(dhtnet::fileutils::getFileLock(cachePath));
2664 data = fileutils::loadCacheTextFile(cachePath, cacheDuration);
2666 dht::http::Response ret;
2667 ret.body = std::move(data);
2668 ret.status_code = 200;
2670 }
catch (
const std::exception& e) {
2671 JAMI_LOG(
"Failed to load '{}' from '{}': {}", url, cachePath, e.what());
2673 if (
auto sthis = w.lock()) {
2674 auto req = std::make_shared<dht::http::Request>(
2675 *Manager::instance().ioContext(),
2677 [cb, cachePath, w](
const dht::http::Response& response) {
2678 if (response.status_code == 200) {
2680 std::lock_guard lk(dhtnet::fileutils::getFileLock(cachePath));
2681 fileutils::saveFile(cachePath,
2682 (const uint8_t*) response.body.data(),
2683 response.body.size(),
2685 JAMI_LOG(
"Cached result to '{}'", cachePath);
2686 } catch (
const std::exception& ex) {
2694 if (std::filesystem::exists(cachePath)) {
2695 JAMI_WARNING(
"Failed to download URL, using cached data");
2699 dhtnet::fileutils::getFileLock(cachePath));
2700 data = fileutils::loadTextFile(cachePath);
2702 dht::http::Response ret;
2703 ret.body = std::move(data);
2704 ret.status_code = 200;
2707 throw std::runtime_error(
"No cached data");
2712 if (
auto req = response.request.lock())
2713 if (
auto sthis = w.lock())
2714 sthis->requests_.erase(req);
2716 sthis->requests_.emplace(req);
2724JamiAccount::loadCachedProxyServer(std::function<
void(
const std::string& proxy)> cb)
2726 const auto& conf = config();
2727 if (conf.proxyEnabled and proxyServerCached_.empty()) {
2728 JAMI_DEBUG(
"[Account {:s}] Loading DHT proxy URL: {:s}", getAccountID(), conf.proxyListUrl);
2729 if (conf.proxyListUrl.empty() or not conf.proxyListEnabled) {
2730 cb(getDhtProxyServer(conf.proxyServer));
2732 loadCachedUrl(conf.proxyListUrl,
2733 cachePath_ /
"dhtproxylist",
2734 std::chrono::hours(24 * 3),
2735 [w = weak(), cb = std::move(cb)](
const dht::http::Response& response) {
2736 if (
auto sthis = w.lock()) {
2737 if (response.status_code == 200) {
2738 cb(sthis->getDhtProxyServer(response.body));
2740 cb(sthis->getDhtProxyServer(sthis->config().proxyServer));
2746 cb(proxyServerCached_);
2751JamiAccount::getDhtProxyServer(
const std::string& serverList)
2753 if (proxyServerCached_.empty()) {
2754 std::vector<std::string> proxys;
2756 std::sregex_iterator begin = {serverList.begin(), serverList.end(),
PROXY_REGEX}, end;
2757 for (
auto it = begin; it != end; ++it) {
2759 if (match[5].matched and match[6].matched) {
2761 auto start = std::stoi(match[5]), end = std::stoi(match[6]);
2762 for (
auto p = start; p <= end; p++)
2763 proxys.emplace_back(match[1].str() + match[2].str() +
":"
2764 + std::to_string(p));
2766 JAMI_WARN(
"Malformed proxy, ignore it");
2770 proxys.emplace_back(match[0].str());
2776 auto randIt = proxys.begin();
2777 std::advance(randIt,
2778 std::uniform_int_distribution<unsigned long>(0, proxys.size() - 1)(rand));
2779 proxyServerCached_ = *randIt;
2781 dhtnet::fileutils::check_dir(cachePath_, 0700);
2782 auto proxyCachePath = cachePath_ /
"dhtproxy";
2783 std::ofstream file(proxyCachePath);
2784 JAMI_DEBUG(
"Cache DHT proxy server: {}", proxyServerCached_);
2785 Json::Value node(Json::objectValue);
2786 node[getProxyConfigKey()] = proxyServerCached_;
2790 JAMI_WARNING(
"Unable to write into {}", proxyCachePath);
2792 return proxyServerCached_;
2796JamiAccount::matches(std::string_view userName, std::string_view server)
const
2798 if (not accountManager_ or not accountManager_->getInfo())
2799 return MatchRank::NONE;
2801 if (userName == accountManager_->getInfo()->accountId
2802 || server == accountManager_->getInfo()->accountId
2803 || userName == accountManager_->getInfo()->deviceId) {
2804 JAMI_LOG(
"Matching account ID in request with username {}", userName);
2805 return MatchRank::FULL;
2807 return MatchRank::NONE;
2812JamiAccount::getFromUri()
const
2814 const std::string uri =
"<sip:" + accountManager_->getInfo()->accountId +
"@ring.dht>";
2815 if (not config().displayName.empty())
2816 return "\"" + config().displayName +
"\" " + uri;
2821JamiAccount::getToUri(
const std::string& to)
const
2825 return fmt::format(
"<sips:{};transport=tls>", username);
2829getDisplayed(
const std::string& conversationId,
const std::string& messageId)
2833 "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n"
2834 "<imdn><message-id>{}</message-id>\n"
2836 "<display-notification><status><displayed/></status></display-notification>\n"
2839 conversationId.empty() ?
"" :
"<conversation>" + conversationId +
"</conversation>");
2846 return fmt::format(
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
2847 "<presence xmlns=\"urn:ietf:params:xml:ns:pidf\">\n"
2850 " <basic>{}</basic>\n"
2858JamiAccount::setIsComposing(
const std::string& conversationUri,
bool isWriting)
2860 Uri uri(conversationUri);
2861 std::string conversationId = {};
2862 if (uri.
scheme() == Uri::Scheme::SWARM) {
2868 if (
auto cm = convModule(
true)) {
2869 if (
auto typer = cm->getTypers(conversationId)) {
2871 typer->addTyper(getUsername(),
true);
2873 typer->removeTyper(getUsername(),
true);
2879JamiAccount::setMessageDisplayed(
const std::string& conversationUri,
2880 const std::string& messageId,
2883 Uri uri(conversationUri);
2884 std::string conversationId = {};
2885 if (uri.
scheme() == Uri::Scheme::SWARM)
2888 && isReadReceiptEnabled();
2889 if (!conversationId.empty())
2890 sendMessage &= convModule()->onMessageDisplayed(getUsername(), conversationId, messageId);
2893 {{MIME_TYPE_IMDN, getDisplayed(conversationId, messageId)}});
2898JamiAccount::getContactHeader(
const std::shared_ptr<SipTransport>& sipTransport)
2900 if (sipTransport and sipTransport->get() !=
nullptr) {
2901 auto transport = sipTransport->get();
2904 bool reliable = transport->flag & PJSIP_TRANSPORT_RELIABLE;
2905 return fmt::format(
"\"{}\" <sips:{}{}{};transport={}>",
2906 config().displayName,
2907 id_.second->getId().toString(),
2908 address.empty() ?
"" :
"@",
2910 reliable ?
"tls" :
"dtls");
2912 JAMI_ERR(
"getContactHeader: no SIP transport provided");
2913 return fmt::format(
"\"{}\" <sips:{}@ring.dht>",
2914 config().displayName,
2915 id_.second->getId().toString());
2920JamiAccount::addContact(
const std::string& uri,
bool confirmed)
2922 dht::InfoHash h(uri);
2924 JAMI_ERROR(
"addContact: invalid contact URI");
2927 auto conversation = convModule()->getOneToOneConversation(uri);
2928 if (!confirmed && conversation.empty())
2929 conversation = convModule()->startConversation(ConversationMode::ONE_TO_ONE, h);
2930 std::unique_lock lock(configurationMutex_);
2931 if (accountManager_)
2932 accountManager_->addContact(h, confirmed, conversation);
2934 JAMI_WARNING(
"[Account {}] addContact: account not loaded", getAccountID());
2938JamiAccount::removeContact(
const std::string& uri,
bool ban)
2940 std::lock_guard lock(configurationMutex_);
2941 if (accountManager_)
2942 accountManager_->removeContact(uri, ban);
2944 JAMI_WARNING(
"[Account {}] removeContact: account not loaded", getAccountID());
2947std::map<std::string, std::string>
2948JamiAccount::getContactDetails(
const std::string& uri)
const
2950 std::lock_guard lock(configurationMutex_);
2951 return accountManager_ ? accountManager_->getContactDetails(uri)
2952 : std::map<std::string, std::string> {};
2955std::optional<Contact>
2956JamiAccount::getContactInfo(
const std::string& uri)
const
2958 std::lock_guard lock(configurationMutex_);
2959 return accountManager_ ? accountManager_->getContactInfo(uri) : std::nullopt;
2962std::vector<std::map<std::string, std::string>>
2963JamiAccount::getContacts(
bool includeRemoved)
const
2965 std::lock_guard lock(configurationMutex_);
2966 if (not accountManager_)
2968 return accountManager_->getContacts(includeRemoved);
2973std::vector<std::map<std::string, std::string>>
2974JamiAccount::getTrustRequests()
const
2976 std::lock_guard lock(configurationMutex_);
2977 return accountManager_ ? accountManager_->getTrustRequests()
2978 : std::vector<std::map<std::string, std::string>> {};
2982JamiAccount::acceptTrustRequest(
const std::string& from,
bool includeConversation)
2984 dht::InfoHash h(from);
2986 JAMI_ERROR(
"addContact: invalid contact URI");
2989 std::unique_lock lock(configurationMutex_);
2990 if (accountManager_) {
2991 if (!accountManager_->acceptTrustRequest(from, includeConversation)) {
2994 return accountManager_->addContact(h,
true);
2998 JAMI_WARNING(
"[Account {}] acceptTrustRequest: account not loaded", getAccountID());
3003JamiAccount::discardTrustRequest(
const std::string& from)
3006 auto requests = getTrustRequests();
3007 for (
const auto& req : requests) {
3009 convModule()->declineConversationRequest(
3015 std::lock_guard lock(configurationMutex_);
3016 if (accountManager_)
3017 return accountManager_->discardTrustRequest(from);
3018 JAMI_WARNING(
"[Account {:s}] discardTrustRequest: account not loaded", getAccountID());
3023JamiAccount::declineConversationRequest(
const std::string& conversationId)
3025 auto peerId = convModule()->peerFromConversationRequest(conversationId);
3026 convModule()->declineConversationRequest(conversationId);
3027 if (!peerId.empty()) {
3028 std::lock_guard lock(configurationMutex_);
3029 if (
auto info = accountManager_->getInfo()) {
3031 auto req = info->contacts->getTrustRequest(dht::InfoHash(peerId));
3034 accountManager_->discardTrustRequest(peerId);
3035 JAMI_DEBUG(
"[Account {:s}] Declined trust request with {:s}",
3044JamiAccount::sendTrustRequest(
const std::string& to,
const std::vector<uint8_t>& payload)
3046 dht::InfoHash h(to);
3048 JAMI_ERROR(
"addContact: invalid contact URI");
3052 auto requestPath = cachePath_ /
"requests";
3053 dhtnet::fileutils::recursive_mkdir(requestPath, 0700);
3054 auto cachedFile = requestPath / to;
3055 std::ofstream req(cachedFile, std::ios::trunc | std::ios::binary);
3056 if (!req.is_open()) {
3057 JAMI_ERROR(
"Unable to write data to {}", cachedFile);
3061 if (not payload.empty()) {
3062 req.write(
reinterpret_cast<const char*
>(payload.data()), payload.size());
3065 if (payload.size() >= 64000) {
3066 JAMI_WARN() <<
"Trust request is too big. Remove payload";
3069 auto conversation = convModule()->getOneToOneConversation(to);
3070 if (conversation.empty())
3071 conversation = convModule()->startConversation(ConversationMode::ONE_TO_ONE, h);
3072 if (not conversation.empty()) {
3073 std::lock_guard lock(configurationMutex_);
3074 if (accountManager_)
3075 accountManager_->sendTrustRequest(to,
3077 payload.size() >= 64000 ? std::vector<uint8_t> {}
3080 JAMI_WARNING(
"[Account {}] sendTrustRequest: account not loaded", getAccountID());
3082 JAMI_WARNING(
"[Account {}] sendTrustRequest: account not loaded", getAccountID());
3086JamiAccount::forEachDevice(
const dht::InfoHash& to,
3087 std::function<
void(
const std::shared_ptr<dht::crypto::PublicKey>&)>&& op,
3088 std::function<
void(
bool)>&& end)
3090 accountManager_->forEachDevice(to, std::move(op), std::move(end));
3094JamiAccount::sendTextMessage(
const std::string& to,
3095 const std::string& deviceId,
3096 const std::map<std::string, std::string>& payloads,
3097 uint64_t refreshToken,
3101 if (uri.
scheme() == Uri::Scheme::SWARM) {
3102 sendInstantMessage(uri.
authority(), payloads);
3110 JAMI_ERROR(
"Failed to send a text message due to an invalid URI {}", to);
3113 if (payloads.size() != 1) {
3114 JAMI_ERROR(
"Multi-part im is not supported yet by JamiAccount");
3117 return SIPAccountBase::sendTextMessage(toUri, deviceId, payloads, refreshToken, onlyConnected);
3121JamiAccount::sendMessage(
const std::string& to,
3122 const std::string& deviceId,
3123 const std::map<std::string, std::string>& payloads,
3125 bool retryOnTimeout,
3132 JAMI_ERROR(
"[Account {}] Failed to send a text message due to an invalid URI {}",
3136 messageEngine_.onMessageSent(to, token,
false, deviceId);
3139 if (payloads.size() != 1) {
3140 JAMI_ERROR(
"Multi-part im is not supported");
3142 messageEngine_.onMessageSent(toUri, token,
false, deviceId);
3147 std::shared_lock clk(connManagerMtx_);
3149 channelHandlers_[Uri::Scheme::MESSAGE].get());
3153 messageEngine_.onMessageSent(to, token,
false, deviceId);
3160 class SendMessageContext {
3162 using OnComplete = std::function<void(
bool,
bool)>;
3163 SendMessageContext(OnComplete onComplete) : onComplete(std::move(onComplete)) {}
3166 std::lock_guard lk(mtx);
3167 return devices.insert(device).second;
3171 std::unique_lock lk(mtx);
3176 bool complete(
const DeviceId& device,
bool success) {
3177 std::unique_lock lk(mtx);
3178 if (devices.erase(device) == 0)
3186 bool empty()
const {
3187 std::lock_guard lk(mtx);
3188 return devices.empty();
3190 bool pending(
const DeviceId& device)
const {
3191 std::lock_guard lk(mtx);
3192 return devices.find(device) != devices.end();
3195 mutable std::mutex mtx;
3196 OnComplete onComplete;
3197 std::set<DeviceId> devices;
3198 unsigned completeCount = 0;
3199 unsigned successCount = 0;
3200 bool started {
false};
3202 void checkComplete(std::unique_lock<std::mutex>& lk) {
3203 if (started && (devices.empty() || successCount)) {
3205 auto cb = std::move(onComplete);
3208 cb(successCount != 0, completeCount != 0);
3213 auto devices = std::make_shared<SendMessageContext>([
3220 ](
bool success,
bool sent) {
3221 if (
auto acc = w.lock())
3222 acc->onMessageSent(to, token, deviceId, success, onlyConnected, sent && retryOnTimeout);
3225 struct TextMessageCtx {
3226 std::weak_ptr<JamiAccount> acc;
3229 std::shared_ptr<SendMessageContext> devices;
3230 std::shared_ptr<dhtnet::ChannelSocket> sipChannel;
3233 auto completed = [w = weak(), to, devices](
const DeviceId& device, std::shared_ptr<dhtnet::ChannelSocket> conn,
bool success) {
3235 if (
auto acc = w.lock()) {
3236 std::shared_lock clk(acc->connManagerMtx_);
3238 acc->channelHandlers_[Uri::Scheme::MESSAGE].get())) {
3239 handler->closeChannel(to, device, conn);
3242 devices->complete(device, success);
3245 const auto& payload = *payloads.begin();
3246 auto msg = std::make_shared<MessageChannelHandler::Message>();
3248 msg->t = payload.first;
3249 msg->c = payload.second;
3251 if (deviceId.empty()) {
3252 auto conns = handler->getChannels(toUri);
3254 for (
const auto& conn : conns) {
3255 auto connDevice = conn->deviceId();
3256 if (!devices->add(connDevice))
3258 dht::ThreadPool::io().run([completed, connDevice, conn, msg] {
3259 completed(connDevice, conn, MessageChannelHandler::sendMessage(conn, *msg));
3263 if (
auto conn = handler->getChannel(toUri, device)) {
3265 devices->add(device);
3266 dht::ThreadPool::io().run([completed, device, conn, msg] {
3267 completed(device, conn, MessageChannelHandler::sendMessage(conn, *msg));
3276 std::unique_lock lk(sipConnsMtx_);
3277 for (
auto& [key, value] : sipConns_) {
3278 if (key.first != to or value.empty())
3280 if (!deviceId.empty() && key.second != device)
3282 if (!devices->add(key.second))
3285 auto& conn = value.back();
3286 auto& channel = conn.channel;
3289 auto ctx = std::make_unique<TextMessageCtx>();
3292 ctx->deviceId = key.second;
3293 ctx->devices = devices;
3294 ctx->sipChannel = channel;
3297 auto res = sendSIPMessage(conn,
3302 [](
void* token, pjsip_event* event) {
3303 if (auto c = std::shared_ptr<TextMessageCtx>{(TextMessageCtx*) token})
3306 code = event->body.tsx_state.tsx->status_code
3308 bool success = code == PJSIP_SC_OK;
3312 JAMI_WARNING(
"Timeout when send a message, close current connection");
3313 if (auto acc = c->acc.lock())
3314 acc->shutdownSIPConnection(c->sipChannel,
3318 c->devices->complete(c->deviceId, code == PJSIP_SC_OK);
3322 devices->complete(key.second,
false);
3325 }
catch (
const std::runtime_error& ex) {
3328 shutdownSIPConnection(channel, to, key.second);
3329 devices->complete(key.second,
false);
3333 if (key.second == device) {
3347 auto extractIdFromJson = [](
const std::string& jsonData) -> std::string {
3349 if (json::parse(jsonData, parsed)) {
3350 auto value = parsed.get(
"id", Json::nullValue);
3351 if (value && value.isString()) {
3352 return value.asString();
3355 JAMI_WARNING(
"Unable to parse jsonData to get conversation ID");
3361 auto payload_type = msg->t;
3362 if (payload_type == MIME_TYPE_GIT) {
3363 std::string
id = extractIdFromJson(msg->c);
3365 payload_type +=
"/" + id;
3369 if (deviceId.empty()) {
3370 auto toH = dht::InfoHash(toUri);
3372 accountManager_->forEachDevice(toH,
3377 currentDevice =
DeviceId(currentDeviceId())](
3378 const std::shared_ptr<dht::crypto::PublicKey>&
dev) {
3380 auto deviceId =
dev->getLongId();
3381 if (deviceId == currentDevice
3382 || devices->pending(deviceId)) {
3387 requestMessageConnection(to, deviceId, payload_type);
3390 requestMessageConnection(to, device, payload_type);
3395JamiAccount::onMessageSent(
const std::string& to, uint64_t
id,
const std::string& deviceId,
bool success,
bool onlyConnected,
bool retry)
3398 messageEngine_.onMessageSent(to,
3405 messageEngine_.onPeerOnline(to, deviceId);
3409dhtnet::IceTransportOptions
3410JamiAccount::getIceOptions()
const
3412 return connectionManager_->getIceOptions();
3416JamiAccount::getIceOptions(std::function<
void(dhtnet::IceTransportOptions&&)> cb)
const
3418 return connectionManager_->getIceOptions(std::move(cb));
3422JamiAccount::getPublishedIpAddress(uint16_t family)
const
3424 return connectionManager_->getPublishedIpAddress(family);
3428JamiAccount::setPushNotificationToken(
const std::string& token)
3430 if (SIPAccountBase::setPushNotificationToken(token)) {
3431 JAMI_WARNING(
"[Account {:s}] setPushNotificationToken: {:s}", getAccountID(), token);
3433 dht_->setPushNotificationToken(token);
3440JamiAccount::setPushNotificationTopic(
const std::string& topic)
3442 if (SIPAccountBase::setPushNotificationTopic(topic)) {
3444 dht_->setPushNotificationTopic(topic);
3451JamiAccount::setPushNotificationConfig(
const std::map<std::string, std::string>& data)
3453 if (SIPAccountBase::setPushNotificationConfig(data)) {
3455 dht_->setPushNotificationPlatform(config_->platform);
3456 dht_->setPushNotificationTopic(config_->notificationTopic);
3457 dht_->setPushNotificationToken(config_->deviceKey);
3468JamiAccount::pushNotificationReceived(
const std::string& from,
3469 const std::map<std::string, std::string>& data)
3471 auto ret_future = dht_->pushNotificationReceived(data);
3472 dht::ThreadPool::computation().run([
id = getAccountID(), ret_future = ret_future.share()] {
3473 JAMI_WARNING(
"[Account {:s}] pushNotificationReceived: {}", id, (uint8_t) ret_future.get());
3478JamiAccount::getUserUri()
const
3481 if (not registeredName_.empty())
3487std::vector<libjami::Message>
3488JamiAccount::getLastMessages(
const uint64_t& base_timestamp)
3490 return SIPAccountBase::getLastMessages(base_timestamp);
3494JamiAccount::startAccountPublish()
3497 info_pub.
accountId = dht::InfoHash(accountManager_->getInfo()->accountId);
3503JamiAccount::startAccountDiscovery()
3505 auto id = dht::InfoHash(accountManager_->getInfo()->accountId);
3508 std::lock_guard lc(discoveryMapMtx_);
3510 if (v.accountId !=
id) {
3512 auto& dp = discoveredPeers_[v.accountId];
3513 dp.displayName = v.displayName;
3514 discoveredPeerMap_[v.accountId.toString()] = v.displayName;
3515 if (dp.cleanupTask) {
3516 dp.cleanupTask->cancel();
3519 JAMI_LOG(
"Account discovered: {}: {}", v.displayName, v.accountId.to_c_str());
3521 emitSignal<libjami::PresenceSignal::NearbyPeerNotification>(getAccountID(),
3527 dp.cleanupTask = Manager::instance().scheduler().scheduleIn(
3528 [w = weak(), p = v.accountId, a = v.displayName] {
3529 if (auto this_ = w.lock()) {
3531 std::lock_guard lc(this_->discoveryMapMtx_);
3532 this_->discoveredPeers_.erase(p);
3533 this_->discoveredPeerMap_.erase(p.toString());
3536 emitSignal<libjami::PresenceSignal::NearbyPeerNotification>(
3537 this_->getAccountID(), p.toString(), 1, a);
3539 JAMI_INFO(
"Account removed from discovery list: %s", a.c_str());
3546std::map<std::string, std::string>
3547JamiAccount::getNearbyPeers()
const
3549 return discoveredPeerMap_;
3553JamiAccount::sendProfileToPeers()
3555 if (!connectionManager_)
3557 std::set<std::string> peers;
3558 const auto& accountUri = accountManager_->getInfo()->
accountId;
3560 for (
const auto& connection : connectionManager_->getConnectionList()) {
3561 const auto& device = connection.at(
"device");
3562 const auto& peer = connection.at(
"peer");
3563 if (!peers.emplace(peer).second)
3565 if (peer == accountUri) {
3566 sendProfile(
"", accountUri, device);
3569 const auto& conversationId = convModule()->getOneToOneConversation(peer);
3570 if (!conversationId.empty()) {
3571 sendProfile(conversationId, peer, device);
3577JamiAccount::updateProfile(
const std::string& displayName,
3578 const std::string& avatar,
3579 const std::string& fileType,
3584 const auto& accountUri = accountManager_->getInfo()->accountId;
3585 const auto& path = profilePath();
3586 const auto& profiles = idPath_ /
"profiles";
3589 if (!std::filesystem::exists(profiles)) {
3590 std::filesystem::create_directories(profiles);
3592 }
catch (
const std::exception& e) {
3593 JAMI_ERROR(
"Failed to create profiles directory: {}", e.what());
3597 const auto& vCardPath = profiles / fmt::format(
"{}.vcf", base64::encode(accountUri));
3599 auto profile = getProfileVcard();
3600 if (profile.empty()) {
3604 profile[
"FN"] = displayName;
3606 emitSignal<libjami::ConfigurationSignal::AccountDetailsChanged>(getAccountID(),
3607 getAccountDetails());
3609 if (!fileType.empty()) {
3610 const std::string& key =
"PHOTO;ENCODING=BASE64;TYPE=" + fileType;
3613 const auto& avatarPath = std::filesystem::path(avatar);
3614 if (std::filesystem::exists(avatarPath)) {
3616 profile[key] = base64::encode(fileutils::loadFile(avatarPath));
3617 }
catch (
const std::exception& e) {
3618 JAMI_ERROR(
"Failed to load avatar: {}", e.what());
3621 }
else if (flag == 1) {
3623 profile[key] = avatar;
3630 std::filesystem::path tmpPath = vCardPath.string() +
".tmp";
3631 std::ofstream file(tmpPath);
3632 if (file.is_open()) {
3635 std::filesystem::rename(tmpPath, vCardPath);
3636 fileutils::createFileLink(path, vCardPath,
true);
3637 emitSignal<libjami::ConfigurationSignal::ProfileReceived>(getAccountID(),
3640 sendProfileToPeers();
3642 JAMI_ERROR(
"Unable to open file for writing: {}", tmpPath.string());
3644 }
catch (
const std::exception& e) {
3645 JAMI_ERROR(
"Error writing profile: {}", e.what());
3650JamiAccount::setActiveCodecs(
const std::vector<unsigned>& list)
3652 Account::setActiveCodecs(list);
3654 setCodecActive(AV_CODEC_ID_OPUS);
3656 setCodecActive(AV_CODEC_ID_HEVC);
3657 setCodecActive(AV_CODEC_ID_H264);
3658 setCodecActive(AV_CODEC_ID_VP8);
3660 config_->activeCodecs = getActiveCodecs(
MEDIA_ALL);
3664JamiAccount::sendInstantMessage(
const std::string& convId,
3665 const std::map<std::string, std::string>& msg)
3667 auto members = convModule()->getConversationMembers(convId);
3668 if (convId.empty() && members.empty()) {
3670 sendTextMessage(convId,
"", msg);
3673 for (
const auto& m : members) {
3674 const auto& uri = m.at(
"uri");
3675 auto token = std::uniform_int_distribution<uint64_t> {1, JAMI_ID_MAX_VAL}(rand);
3677 sendMessage(uri,
"", msg, token,
false,
true);
3682JamiAccount::handleMessage(
const std::shared_ptr<dht::crypto::Certificate>& cert,
const std::string& from,
const std::pair<std::string, std::string>& m)
3684 if (not cert or not cert->issuer)
3687 if (cert->issuer->getId().to_view() != from) {
3688 JAMI_WARNING(
"[Account {}] [device {}] handleMessage: invalid author {}", getAccountID(), cert->issuer->getId().to_view(), from);
3691 if (m.first == MIME_TYPE_GIT) {
3693 if (!json::parse(m.second, json)) {
3698 dht::ThreadPool::io().run([
3701 deviceId = json[
"deviceId"].asString(),
3702 id = json[
"id"].asString(),
3703 commit = json[
"commit"].asString()
3705 if (
auto shared = w.lock()) {
3706 if (auto cm = shared->convModule())
3707 cm->fetchNewCommits(from, deviceId, id, commit);
3711 }
else if (m.first == MIME_TYPE_INVITE) {
3712 convModule()->onNeedConversationRequest(from, m.second);
3716 if (!json::parse(m.second, json)) {
3719 convModule()->onConversationRequest(from, json);
3721 }
else if (m.first == MIME_TYPE_IM_COMPOSING) {
3723 static const std::regex COMPOSING_REGEX(
"<state>\\s*(\\w+)\\s*<\\/state>");
3724 std::smatch matched_pattern;
3726 bool isComposing {
false};
3727 if (matched_pattern.ready() && !matched_pattern.empty() && matched_pattern[1].matched) {
3728 isComposing = matched_pattern[1] ==
"active";
3730 static const std::regex CONVID_REGEX(
"<conversation>\\s*(\\w+)\\s*<\\/conversation>");
3732 std::string conversationId =
"";
3733 if (matched_pattern.ready() && !matched_pattern.empty() && matched_pattern[1].matched) {
3734 conversationId = matched_pattern[1];
3736 if (!conversationId.empty()) {
3737 if (
auto cm = convModule(
true)) {
3738 if (
auto typer = cm->getTypers(conversationId)) {
3740 typer->addTyper(from);
3742 typer->removeTyper(from);
3747 }
catch (
const std::exception& e) {
3748 JAMI_WARNING(
"Error parsing composing state: {}", e.what());
3752 static const std::regex IMDN_MSG_ID_REGEX(
"<message-id>\\s*(\\w+)\\s*<\\/message-id>");
3753 std::smatch matched_pattern;
3756 std::string messageId;
3757 if (matched_pattern.ready() && !matched_pattern.empty() && matched_pattern[1].matched) {
3758 messageId = matched_pattern[1];
3760 JAMI_WARNING(
"Message displayed: unable to parse message ID");
3764 static const std::regex STATUS_REGEX(
"<status>\\s*<(\\w+)\\/>\\s*<\\/status>");
3766 bool isDisplayed {
false};
3767 if (matched_pattern.ready() && !matched_pattern.empty() && matched_pattern[1].matched) {
3768 isDisplayed = matched_pattern[1] ==
"displayed";
3770 JAMI_WARNING(
"Message displayed: unable to parse status");
3774 static const std::regex CONVID_REGEX(
"<conversation>\\s*(\\w+)\\s*<\\/conversation>");
3776 std::string conversationId =
"";
3777 if (matched_pattern.ready() && !matched_pattern.empty() && matched_pattern[1].matched) {
3778 conversationId = matched_pattern[1];
3781 if (!isReadReceiptEnabled())
3784 if (convModule()->onMessageDisplayed(from, conversationId, messageId)) {
3785 JAMI_DEBUG(
"[message {}] Displayed by peer", messageId);
3786 emitSignal<libjami::ConfigurationSignal::AccountMessageStatusChanged>(
3795 }
catch (
const std::exception& e) {
3796 JAMI_ERROR(
"Error parsing display notification: {}", e.what());
3799 std::smatch matched_pattern;
3800 static const std::regex BASIC_REGEX(
"<basic>([\\w\\s]+)<\\/basic>");
3802 std::string customStatus {};
3803 if (matched_pattern.ready() && !matched_pattern.empty() && matched_pattern[1].matched) {
3804 customStatus = matched_pattern[1];
3805 emitSignal<libjami::PresenceSignal::NewBuddyNotification>(getAccountID(),
3808 PresenceState::CONNECTED),
3820JamiAccount::callConnectionClosed(
const DeviceId& deviceId,
bool eraseDummy)
3822 std::function<void(
const DeviceId&,
bool)> cb;
3824 std::lock_guard lk(onConnectionClosedMtx_);
3825 auto it = onConnectionClosed_.find(deviceId);
3826 if (it != onConnectionClosed_.end()) {
3828 cb = std::move(it->second);
3829 onConnectionClosed_.erase(it);
3837 dht::ThreadPool::io().run(
3838 [w = weak(), cb = std::move(cb),
id = deviceId, erase = std::move(eraseDummy)] {
3839 if (
auto acc = w.lock()) {
3847JamiAccount::requestMessageConnection(
const std::string& peerId,
3848 const DeviceId& deviceId,
3849 const std::string& connectionType)
3851 std::shared_lock lk(connManagerMtx_);
3852 auto* handler =
static_cast<MessageChannelHandler*
>(
3853 channelHandlers_[Uri::Scheme::MESSAGE].get());
3857 if (
auto connected = handler->getChannel(peerId, deviceId)) {
3861 auto connected = handler->getChannels(peerId);
3862 if (!connected.empty()) {
3869 [w = weak(), peerId](std::shared_ptr<dhtnet::ChannelSocket> socket,
3872 dht::ThreadPool::io().run([w, peerId, deviceId] {
3873 if (
auto acc = w.lock()) {
3874 acc->messageEngine_.onPeerOnline(peerId);
3875 acc->messageEngine_.onPeerOnline(peerId, deviceId.toString(), true);
3876 if (!acc->presenceNote_.empty()) {
3878 auto token = std::uniform_int_distribution<uint64_t> {1, JAMI_ID_MAX_VAL}(acc->rand);
3879 std::map<std::string, std::string> msg = {
3880 {MIME_TYPE_PIDF, getPIDF(acc->presenceNote_)}
3882 acc->sendMessage(peerId, deviceId.toString(), msg, token, false, true);
3884 acc->convModule()->syncConversations(peerId, deviceId.toString());
3892JamiAccount::requestSIPConnection(
const std::string& peerId,
3893 const DeviceId& deviceId,
3894 const std::string& connectionType,
3895 bool forceNewConnection,
3896 const std::shared_ptr<SIPCall>& pc)
3898 if (peerId == getUsername()) {
3899 if (!syncModule()->isConnected(deviceId))
3900 channelHandlers_[Uri::Scheme::SYNC]
3903 [](std::shared_ptr<dhtnet::ChannelSocket> socket,
3904 const DeviceId& deviceId) {});
3907 JAMI_LOG(
"[Account {}] Request SIP connection to peer {} on device {}",
3913 std::lock_guard lk(sipConnsMtx_);
3914 auto id = std::make_pair(peerId, deviceId);
3916 if (sipConns_.find(
id) != sipConns_.end()) {
3917 JAMI_LOG(
"[Account {}] A SIP connection with {} already exists", getAccountID(), deviceId);
3921 std::shared_lock lkCM(connManagerMtx_);
3922 if (!connectionManager_)
3927 if (!forceNewConnection && connectionManager_->isConnecting(deviceId,
"sip")) {
3928 JAMI_LOG(
"[Account {}] Already connecting to {}", getAccountID(), deviceId);
3931 JAMI_LOG(
"[Account {}] Ask {} for a new SIP channel", getAccountID(), deviceId);
3932 connectionManager_->connectDevice(
3937 pc = std::move(pc)](std::shared_ptr<dhtnet::ChannelSocket> socket,
const DeviceId&) {
3940 auto shared = w.lock();
3946 shared->callConnectionClosed(
id.second,
true);
3956JamiAccount::isConnectedWith(
const DeviceId& deviceId)
const
3958 std::shared_lock lkCM(connManagerMtx_);
3959 if (connectionManager_)
3960 return connectionManager_->isConnected(deviceId);
3965JamiAccount::sendPresenceNote(
const std::string& note)
3967 if (
auto info = accountManager_->getInfo()) {
3968 if (!info || !info->contacts)
3970 presenceNote_ = note;
3971 auto contacts = info->contacts->getContacts();
3972 std::vector<std::pair<std::string, DeviceId>> keys;
3974 std::shared_lock lkCM(connManagerMtx_);
3976 channelHandlers_[Uri::Scheme::MESSAGE].get());
3979 for (
const auto& contact : contacts) {
3980 auto peerId = contact.first.toString();
3982 for (
const auto& channel : channels) {
3983 keys.emplace_back(peerId, channel->deviceId());
3987 auto token = std::uniform_int_distribution<uint64_t> {1, JAMI_ID_MAX_VAL}(rand);
3989 for (
auto& key : keys) {
3990 sendMessage(key.first, key.second.toString(), msg, token,
false,
true);
3996JamiAccount::sendProfile(
const std::string& convId,
3997 const std::string& peerUri,
3998 const std::string& deviceId)
4000 auto accProfilePath = profilePath();
4001 if (not std::filesystem::is_regular_file(accProfilePath))
4003 auto currentSha3 = fileutils::sha3File(accProfilePath);
4005 if (not needToSendProfile(peerUri, deviceId, currentSha3)) {
4006 JAMI_DEBUG(
"[Account {}] [device {}] Peer {} already got an up-to-date vCard", getAccountID(), deviceId, peerUri);
4010 transferFile(convId,
4011 accProfilePath.string(),
4018 fileutils::lastWriteTimeInSeconds(accProfilePath),
4019 [accId = getAccountID(), peerUri, deviceId]() {
4021 auto sendDir = fileutils::get_cache_dir() / accId /
"vcard" / peerUri;
4022 auto path = sendDir / deviceId;
4023 dhtnet::fileutils::recursive_mkdir(sendDir);
4024 std::lock_guard lock(dhtnet::fileutils::getFileLock(path));
4025 if (std::filesystem::is_regular_file(path))
4027 std::ofstream p(path);
4032JamiAccount::needToSendProfile(
const std::string& peerUri,
4033 const std::string& deviceId,
4034 const std::string& sha3Sum)
4036 std::string previousSha3 {};
4037 auto vCardPath = cachePath_ /
"vcard";
4038 auto sha3Path = vCardPath /
"sha3";
4039 dhtnet::fileutils::check_dir(vCardPath, 0700);
4041 previousSha3 = fileutils::loadTextFile(sha3Path);
4043 fileutils::saveFile(sha3Path, (
const uint8_t*) sha3Sum.data(), sha3Sum.size(), 0600);
4046 if (sha3Sum != previousSha3) {
4048 dhtnet::fileutils::removeAll(vCardPath,
true);
4049 dhtnet::fileutils::check_dir(vCardPath, 0700);
4050 fileutils::saveFile(sha3Path, (
const uint8_t*) sha3Sum.data(), sha3Sum.size(), 0600);
4053 auto peerPath = vCardPath / peerUri;
4054 dhtnet::fileutils::recursive_mkdir(peerPath);
4055 return not std::filesystem::is_regular_file(peerPath / deviceId);
4059JamiAccount::sendSIPMessage(SipConnection& conn,
4060 const std::string& to,
4063 const std::map<std::string, std::string>& data,
4064 pjsip_endpt_send_callback cb)
4066 auto transport = conn.transport;
4067 auto channel = conn.channel;
4069 throw std::runtime_error(
4070 "A SIP transport exists without Channel, this is a bug. Please report");
4071 auto remote_address = channel->getRemoteAddress();
4072 if (!remote_address)
4077 auto toURI = getToUri(fmt::format(
"{}@{}", to, remote_address.toString(
true)));
4078 std::string from = getFromUri();
4081 constexpr pjsip_method msg_method = {PJSIP_OTHER_METHOD,
4082 sip_utils::CONST_PJ_STR(sip_utils::SIP_METHODS::MESSAGE)};
4083 pj_str_t pjFrom = sip_utils::CONST_PJ_STR(from);
4084 pj_str_t pjTo = sip_utils::CONST_PJ_STR(toURI);
4087 pjsip_tx_data* tdata =
nullptr;
4088 pj_status_t status = pjsip_endpt_create_request(link_.getEndpoint(),
4098 if (status != PJ_SUCCESS) {
4099 JAMI_ERROR(
"Unable to create request: {}", sip_utils::sip_strerror(status));
4105 constexpr auto key = sip_utils::CONST_PJ_STR(
"Date");
4107 auto time = std::time(
nullptr);
4108 auto date = std::ctime(&time);
4110 *std::remove(date, date + strlen(date),
'\n') =
'\0';
4113 hdr =
reinterpret_cast<pjsip_hdr*
>(
4114 pjsip_date_hdr_create(tdata->pool, &key, pj_cstr(&date_str, date)));
4115 pjsip_msg_add_hdr(tdata->msg, hdr);
4119 auto pjMessageId = sip_utils::CONST_PJ_STR(token_str);
4120 hdr =
reinterpret_cast<pjsip_hdr*
>(
4121 pjsip_generic_string_hdr_create(tdata->pool, &STR_MESSAGE_ID, &pjMessageId));
4122 pjsip_msg_add_hdr(tdata->msg, hdr);
4125 sip_utils::addUserAgentHeader(getUserAgentName(), tdata);
4128 const pjsip_tpselector tp_sel = SIPVoIPLink::getTransportSelector(transport->get());
4129 status = pjsip_tx_data_set_transport(tdata, &tp_sel);
4130 if (status != PJ_SUCCESS) {
4131 JAMI_ERROR(
"Unable to create request: {}", sip_utils::sip_strerror(status));
4134 im::fillPJSIPMessageBody(*tdata, data);
4137 dht::ThreadPool::io().run([w = weak(), tdata, ctx, cb = std::move(cb)] {
4138 auto shared = w.lock();
4141 auto status = pjsip_endpt_send_request(shared->link_.getEndpoint(), tdata, -1, ctx, cb);
4142 if (status != PJ_SUCCESS)
4143 JAMI_ERROR(
"Unable to send request: {}", sip_utils::sip_strerror(status));
4149JamiAccount::clearProfileCache(
const std::string& peerUri)
4152 std::filesystem::remove_all(cachePath_ /
"vcard" / peerUri, ec);
4155std::filesystem::path
4156JamiAccount::profilePath()
const
4158 return idPath_ /
"profile.vcf";
4162JamiAccount::cacheSIPConnection(std::shared_ptr<dhtnet::ChannelSocket>&& socket,
4163 const std::string& peerId,
4166 std::unique_lock lk(sipConnsMtx_);
4169 auto& connections = sipConns_[key];
4170 auto conn = std::find_if(connections.begin(), connections.end(), [&](
const auto& v) {
4171 return v.channel == socket;
4173 if (conn != connections.end()) {
4174 JAMI_WARNING(
"[Account {}] Channel socket already cached with this peer", getAccountID());
4179 auto onShutdown = [w = weak(), peerId, key, socket]() {
4180 dht::ThreadPool::io().run([w = std::move(w), peerId, key, socket] {
4181 auto shared = w.lock();
4184 shared->shutdownSIPConnection(socket, key.first, key.second);
4188 shared->callConnectionClosed(key.second,
false);
4191 auto sip_tr = link_.sipTransportBroker->getChanneledTransport(shared(),
4193 std::move(onShutdown));
4199 connections.emplace_back(SipConnection {sip_tr, socket});
4200 JAMI_WARNING(
"[Account {:s}] [device {}] New SIP channel opened", getAccountID(), deviceId);
4204 messageEngine_.onPeerOnline(peerId);
4205 messageEngine_.onPeerOnline(peerId, deviceId.toString(),
true);
4208 forEachPendingCall(deviceId, [&](
const auto& pc) {
4209 if (pc->getConnectionState() != Call::ConnectionState::TRYING
4210 and pc->getConnectionState() != Call::ConnectionState::PROGRESSING)
4212 pc->setSipTransport(sip_tr, getContactHeader(sip_tr));
4213 pc->setState(Call::ConnectionState::PROGRESSING);
4214 if (
auto remote_address = socket->getRemoteAddress()) {
4216 onConnectedOutgoingCall(pc, peerId, remote_address);
4217 }
catch (
const VoipLinkException&) {
4229JamiAccount::shutdownSIPConnection(
const std::shared_ptr<dhtnet::ChannelSocket>& channel,
4230 const std::string& peerId,
4231 const DeviceId& deviceId)
4233 std::unique_lock lk(sipConnsMtx_);
4235 auto it = sipConns_.find(key);
4236 if (it != sipConns_.end()) {
4237 auto& conns = it->second;
4238 conns.erase(std::remove_if(conns.begin(),
4240 [&](
auto v) { return v.channel == channel; }),
4242 if (conns.empty()) {
4243 sipConns_.erase(it);
4249 channel->shutdown();
4253JamiAccount::currentDeviceId()
const
4255 if (!accountManager_ or not accountManager_->getInfo())
4257 return accountManager_->getInfo()->deviceId;
4260std::shared_ptr<TransferManager>
4261JamiAccount::dataTransfer(
const std::string&
id)
4264 return nonSwarmTransferManager_;
4265 return convModule()->dataTransfer(
id);
4269JamiAccount::monitor()
4271 JAMI_DEBUG(
"[Account {:s}] Monitor connections", getAccountID());
4272 JAMI_DEBUG(
"[Account {:s}] Using proxy: {:s}", getAccountID(), proxyServerCached_);
4274 if (
auto cm = convModule())
4276 std::shared_lock lkCM(connManagerMtx_);
4277 if (connectionManager_)
4278 connectionManager_->monitor();
4281std::vector<std::map<std::string, std::string>>
4282JamiAccount::getConnectionList(
const std::string& conversationId)
4284 std::shared_lock lkCM(connManagerMtx_);
4285 if (connectionManager_ && conversationId.empty()) {
4286 return connectionManager_->getConnectionList();
4287 }
else if (connectionManager_ && convModule_) {
4288 std::vector<std::map<std::string, std::string>> connectionList;
4289 if (
auto conv = convModule_->getConversation(conversationId)) {
4290 for (
const auto& deviceId : conv->getDeviceIdList()) {
4291 auto connections = connectionManager_->getConnectionList(deviceId);
4292 connectionList.reserve(connectionList.size() + connections.size());
4293 std::move(connections.begin(),
4295 std::back_inserter(connectionList));
4298 return connectionList;
4304std::vector<std::map<std::string, std::string>>
4305JamiAccount::getChannelList(
const std::string& connectionId)
4307 std::shared_lock lkCM(connManagerMtx_);
4308 if (!connectionManager_)
4310 return connectionManager_->getChannelList(connectionId);
4314JamiAccount::sendFile(
const std::string& conversationId,
4315 const std::filesystem::path& path,
4316 const std::string& name,
4317 const std::string& replyTo)
4319 if (!std::filesystem::is_regular_file(path)) {
4326 dht::ThreadPool::computation().run([w = weak(), conversationId, path, name, replyTo]() {
4327 if (
auto shared = w.lock()) {
4329 auto tid = jami::generateUID(shared->rand);
4330 value[
"tid"] = std::to_string(tid);
4331 value[
"displayName"] = name.empty() ? path.filename().string() : name;
4332 value[
"totalSize"] = std::to_string(fileutils::size(path));
4333 value[
"sha3sum"] = fileutils::sha3File(path);
4334 value[
"type"] =
"application/data-transfer+json";
4336 shared->convModule()->sendMessage(
4341 [accId = shared->getAccountID(), conversationId, tid, path](
4342 const std::string& commitId) {
4344 auto filelinkPath = fileutils::get_data_dir() / accId /
"conversation_data"
4345 / conversationId / (commitId +
"_" + std::to_string(tid));
4346 filelinkPath += path.extension();
4347 if (path != filelinkPath && !std::filesystem::is_symlink(filelinkPath)) {
4348 if (!fileutils::createFileLink(filelinkPath, path, true)) {
4350 "Unable to create symlink for file transfer {} - {}. Copy file",
4354 auto success = std::filesystem::copy_file(path, filelinkPath, ec);
4355 if (ec || !success) {
4356 JAMI_ERROR(
"Unable to copy file for file transfer {} - {}",
4362 emitSignal<libjami::DataTransferSignal::DataTransferEvent>(
4367 uint32_t(libjami::DataTransferEventCode::invalid));
4372 emitSignal<libjami::DataTransferSignal::DataTransferEvent>(
4377 uint32_t(libjami::DataTransferEventCode::created));
4380 emitSignal<libjami::DataTransferSignal::DataTransferEvent>(
4385 uint32_t(libjami::DataTransferEventCode::created));
4394JamiAccount::transferFile(
const std::string& conversationId,
4395 const std::string& path,
4396 const std::string& deviceId,
4397 const std::string& fileId,
4398 const std::string& interactionId,
4401 const std::string& sha3Sum,
4402 uint64_t lastWriteTime,
4403 std::function<
void()> onFinished)
4405 std::string modified;
4406 if (lastWriteTime != 0) {
4407 modified = fmt::format(
"&modified={}", lastWriteTime);
4409 auto fid = fileId ==
"profile.vcf" ? fmt::format(
"profile.vcf?sha3={}{}", sha3Sum, modified)
4411 auto channelName = conversationId.empty() ? fmt::format(
"{}profile.vcf?sha3={}{}",
4412 DATA_TRANSFER_SCHEME,
4415 : fmt::format(
"{}{}/{}/{}",
4416 DATA_TRANSFER_SCHEME,
4420 std::shared_lock lkCM(connManagerMtx_);
4421 if (!connectionManager_)
4424 ->connectDevice(
DeviceId(deviceId),
4428 path = std::move(path),
4433 onFinished = std::move(
4434 onFinished)](std::shared_ptr<dhtnet::ChannelSocket> socket,
4438 dht::ThreadPool::io().run([w = weak(),
4439 path = std::move(path),
4440 socket = std::move(socket),
4441 conversationId = std::move(conversationId),
4446 onFinished = std::move(onFinished)] {
4447 if (
auto shared = w.lock())
4448 if (
auto dt = shared->dataTransfer(conversationId))
4449 dt->transferFile(socket,
4455 std::move(onFinished));
4461JamiAccount::askForFileChannel(
const std::string& conversationId,
4462 const std::string& deviceId,
4463 const std::string& interactionId,
4464 const std::string& fileId,
4468 auto tryDevice = [=](
const auto& did) {
4469 std::shared_lock lkCM(connManagerMtx_);
4470 if (!connectionManager_)
4473 auto channelName = fmt::format(
"{}{}/{}/{}",
4474 DATA_TRANSFER_SCHEME,
4478 if (start != 0 || end != 0) {
4479 channelName += fmt::format(
"?start={}&end={}", start, end);
4483 connectionManager_->connectDevice(
4490 start](std::shared_ptr<dhtnet::ChannelSocket> channel,
const DeviceId&) {
4493 dht::ThreadPool::io().run([w, conversationId, channel, fileId, interactionId, start] {
4494 auto shared = w.lock();
4497 auto dt = shared->dataTransfer(conversationId);
4500 if (interactionId.empty())
4501 dt->onIncomingProfile(channel);
4503 dt->onIncomingFileTransfer(fileId, channel, start);
4509 if (!deviceId.empty()) {
4515 for (
const auto& m : convModule()->getConversationMembers(conversationId)) {
4516 accountManager_->forEachDevice(dht::InfoHash(m.at(
"uri")), [
4518 ](
const std::shared_ptr<dht::crypto::PublicKey>&
dev) {
4519 tryDevice(
dev->getLongId());
4526JamiAccount::askForProfile(
const std::string& conversationId,
4527 const std::string& deviceId,
4528 const std::string& memberUri)
4530 std::shared_lock lkCM(connManagerMtx_);
4531 if (!connectionManager_)
4534 auto channelName = fmt::format(
"{}{}/profile/{}.vcf",
4535 DATA_TRANSFER_SCHEME,
4540 connectionManager_->connectDevice(
4543 [
this, conversationId](std::shared_ptr<dhtnet::ChannelSocket> channel,
const DeviceId&) {
4546 dht::ThreadPool::io().run([w = weak(), conversationId, channel] {
4547 if (
auto shared = w.lock())
4548 if (
auto dt = shared->dataTransfer(conversationId))
4549 dt->onIncomingProfile(channel);
4556JamiAccount::onPeerConnected(
const std::string& peerId,
bool connected)
4558 std::unique_lock lock(buddyInfoMtx);
4559 auto& state = presenceState_[peerId];
4560 auto it = trackedBuddies_.find(dht::InfoHash(peerId));
4561 auto isOnline = it != trackedBuddies_.end() && it->second.devices_cnt > 0;
4562 auto newState = connected ? PresenceState::CONNECTED : (isOnline ? PresenceState::AVAILABLE : PresenceState::DISCONNECTED);
4563 if (state != newState) {
4566 emitSignal<libjami::PresenceSignal::NewBuddyNotification>(getAccountID(),
4568 static_cast<int>(newState),
4574JamiAccount::initConnectionManager()
4576 if (!nonSwarmTransferManager_)
4577 nonSwarmTransferManager_
4578 = std::make_shared<TransferManager>(accountID_,
4581 dht::crypto::getDerivedRandomEngine(rand));
4582 if (!connectionManager_) {
4583 auto connectionManagerConfig = std::make_shared<dhtnet::ConnectionManager::Config>();
4584 connectionManagerConfig->ioContext = Manager::instance().ioContext();
4585 connectionManagerConfig->dht =
dht();
4586 connectionManagerConfig->certStore = certStore_;
4587 connectionManagerConfig->id = identity();
4588 connectionManagerConfig->upnpCtrl = upnpCtrl_;
4589 connectionManagerConfig->turnServer = config().turnServer;
4590 connectionManagerConfig->upnpEnabled = config().upnpEnabled;
4591 connectionManagerConfig->turnServerUserName = config().turnServerUserName;
4592 connectionManagerConfig->turnServerPwd = config().turnServerPwd;
4593 connectionManagerConfig->turnServerRealm = config().turnServerRealm;
4594 connectionManagerConfig->turnEnabled = config().turnEnabled;
4595 connectionManagerConfig->cachePath = cachePath_;
4596 connectionManagerConfig->logger = Logger::dhtLogger();
4597 connectionManagerConfig->factory = Manager::instance().getIceTransportFactory();
4598 connectionManagerConfig->turnCache = turnCache_;
4599 connectionManagerConfig->rng = std::make_unique<std::mt19937_64>(
4600 dht::crypto::getDerivedRandomEngine(rand));
4601 connectionManager_ = std::make_unique<dhtnet::ConnectionManager>(connectionManagerConfig);
4602 channelHandlers_[Uri::Scheme::SWARM]
4603 = std::make_unique<SwarmChannelHandler>(shared(), *connectionManager_.get());
4604 channelHandlers_[Uri::Scheme::GIT]
4605 = std::make_unique<ConversationChannelHandler>(shared(), *connectionManager_.get());
4606 channelHandlers_[Uri::Scheme::SYNC]
4607 = std::make_unique<SyncChannelHandler>(shared(), *connectionManager_.get());
4608 channelHandlers_[Uri::Scheme::DATA_TRANSFER]
4609 = std::make_unique<TransferChannelHandler>(shared(), *connectionManager_.get());
4610 channelHandlers_[Uri::Scheme::MESSAGE]
4611 = std::make_unique<MessageChannelHandler>(*connectionManager_.get(),
4612 [
this](
const auto& cert, std::string& type,
const std::string& content) {
4613 onTextMessage(
"", cert->issuer->getId().toString(), cert, {{type, content}});
4615 [w = weak()](
const std::string& peer,
bool connected) {
4616 asio::post(*Manager::instance().ioContext(), [w, peer, connected] {
4617 if (
auto acc = w.lock())
4618 acc->onPeerConnected(peer, connected);
4621 channelHandlers_[Uri::Scheme::AUTH]
4622 = std::make_unique<AuthChannelHandler>(shared(), *connectionManager_.get());
4625 connectionManager_->oniOSConnected([&](
const std::string& connType, dht::InfoHash peer_h) {
4626 if ((connType ==
"videoCall" || connType ==
"audioCall")
4628 bool hasVideo = connType ==
"videoCall";
4629 emitSignal<libjami::ConversationSignal::CallConnectionRequest>(
"",
4641JamiAccount::updateUpnpController()
4643 Account::updateUpnpController();
4644 if (connectionManager_) {
4645 auto config = connectionManager_->getConfig();
4647 config->upnpCtrl = upnpCtrl_;
Account specific keys/constants that must be shared in daemon and clients.
const std::string & getAccountID() const
Get the account ID.
std::filesystem::path idPath_
path to account
bool isVideoEnabled() const
Manages channels for syncing informations.
std::shared_ptr< SIPCall > newSipCall(const std::shared_ptr< SIPAccountBase > &account, Call::CallType type, const std::vector< libjami::MediaMap > &mediaList)
Create a new call instance.
std::shared_ptr< Call > newOutgoingCall(std::string_view toUrl, const std::vector< libjami::MediaMap > &mediaList) override
Create outgoing SIPCall.
std::string getContactHeader(const std::shared_ptr< SipTransport > &sipTransport)
Get the contact header for.
std::shared_ptr< SIPCall > newIncomingCall(const std::string &from, const std::vector< libjami::MediaMap > &mediaList, const std::shared_ptr< SipTransport > &sipTr={}) override
Create incoming SIPCall.
void shutdownConnections()
This should be called before flushing the account.
void flush() override
This method is called to request removal of possible account traces on the system,...
std::shared_ptr< JamiAccount > shared()
JamiAccount(const std::string &accountId)
Constructor.
std::weak_ptr< JamiAccount > weak()
Level-driven logging class that support printf and C++ stream logging fashions.
static LIBJAMI_TEST_EXPORT Manager & instance()
static bool syncOnRegister
Manages channels for exchanging messages between peers.
std::vector< std::shared_ptr< dhtnet::ChannelSocket > > getChannels(const std::string &peer) const
std::vector< std::map< std::string, std::string > > SearchResult
std::vector< MediaAttribute > createDefaultMediaList(bool addVideo, bool onHold=false)
virtual void flush() override
This method is called to request removal of possible account traces on the system,...
const std::string & authority() const
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)
std::string toString(const Json::Value &jsonVal)
constexpr const pj_str_t CONST_PJ_STR(T(&a)[N]) noexcept
RegistrationState
Contains all the Registration states for an account can be in.
static const std::string PEER_DISCOVERY_JAMI_SERVICE
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
std::string to_hex_string(uint64_t id)
static const auto PROXY_REGEX
bool getline(std::string_view &str, std::string_view &line, char delim='\n')
Similar to @getline_full but skips empty results.
constexpr pj_str_t STR_MESSAGE_ID
const std::string & userAgent()
std::string_view stripPrefix(std::string_view toUrl)
static constexpr const char *const RING_URI_PREFIX
static constexpr const char *const JAMI_URI_PREFIX
static constexpr const char MIME_TYPE_IMDN[]
static constexpr std::string_view dhtStatusStr(dht::NodeStatus status)
std::string getDisplayed(const std::string &conversationId, const std::string &messageId)
std::string_view parseJamiUri(std::string_view toUrl)
static constexpr const char DEVICE_ID_PATH[]
std::string getPIDF(const std::string ¬e)
static constexpr const char MIME_TYPE_INVITE_JSON[]
static constexpr const char MIME_TYPE_PIDF[]
std::vector< std::string_view > split_string(std::string_view str, char delim)
void string_replace(std::string &str, const std::string &from, const std::string &to)
static constexpr const char URI[]
static constexpr const char ARCHIVE_HAS_PASSWORD[]
static constexpr const char PROXY_SERVER[]
static constexpr const char DEVICE_ID[]
static constexpr const char DISPLAYNAME[]
static constexpr const char CONVERSATIONID[]
static constexpr const char FROM[]
static constexpr const char OFF_CALL[]
static constexpr const char DEVICE_ANNOUNCED[]
static constexpr const char DHT_BOUND_PORT[]
static constexpr const char REGISTERED_NAME[]
LIBJAMI_PUBLIC bool start(const std::filesystem::path &config_file={}) noexcept
Start asynchronously daemon created by init().
bool accept(const std::string &accountId, const std::string &callId)
bool setCertificateStatus(const std::string &accountId, const std::string &certId, const std::string &ststr)
std::map< std::string, std::string > getAccountDetails(const std::string &accountId)
std::map< std::string, std::string > getVolatileAccountDetails(const std::string &accountId)
bool regex_search(string_view sv, svmatch &m, const regex &e, regex_constants::match_flag_type flags=regex_constants::match_default)
std::map< std::string, std::string > initVcard()
std::string toString(const std::map< std::string, std::string > &vCard)
void removeByKey(std::map< std::string, std::string > &vCard, std::string_view key)
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::future< size_t > listenToken
BuddyInfo(dht::InfoHash id)
std::shared_ptr< Task > cleanupTask
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
std::weak_ptr< SIPAccount > acc
AbstractSIPTransport * self