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");
246 return status == dht::NodeStatus::Connected
248 : (status == dht::NodeStatus::Connecting ?
"connecting" :
"disconnected");
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);
1249 if (!convFromReq.empty()) {
1250 auto oldConv = cm->getOneToOneConversation(uri);
1257 if (oldConv != convFromReq
1258 && cm->updateConvForContact(uri, oldConv, convFromReq)) {
1259 cm->initReplay(oldConv, convFromReq);
1260 cm->cloneConversationFrom(convFromReq, uri, oldConv);
1267 const auto& conf = config();
1269 auto oldIdentity = id_.first ? id_.first->getPublicKey().getLongId() :
DeviceId();
1270 if (conf.managerUri.empty()) {
1271 accountManager_ = std::make_shared<ArchiveAccountManager>(
1275 conf.archivePath.empty() ?
"archive.gz" : conf.archivePath,
1278 accountManager_ = std::make_shared<ServerAccountManager>(getAccountID(),
1284 auto id = accountManager_->loadIdentity(conf.tlsCertificateFile,
1285 conf.tlsPrivateKeyFile,
1288 if (
auto info = accountManager_->useIdentity(
id,
1290 conf.receiptSignature,
1291 conf.managerUsername,
1294 id_ = std::move(
id);
1295 config_->username = info->accountId;
1296 JAMI_WARNING(
"[Account {:s}] Loaded account identity", getAccountID());
1297 if (info->identity.first->getPublicKey().getLongId() != oldIdentity) {
1298 JAMI_WARNING(
"[Account {:s}] Identity changed", getAccountID());
1300 std::lock_guard lk(moduleMtx_);
1301 convModule_.reset();
1307 convModule()->setAccountManager(accountManager_);
1309 if (not isEnabled()) {
1310 setRegistrationState(RegistrationState::UNREGISTERED);
1312 convModule()->loadConversations();
1313 }
else if (isEnabled()) {
1314 JAMI_WARNING(
"[Account {}] useIdentity failed!", getAccountID());
1315 if (not conf.managerUri.empty() and archive_password.empty()) {
1316 Migration::setState(accountID_, Migration::State::INVALID);
1317 setRegistrationState(RegistrationState::ERROR_NEED_MIGRATION);
1321 bool migrating = registrationState_ == RegistrationState::ERROR_NEED_MIGRATION;
1322 setRegistrationState(RegistrationState::INITIALIZING);
1323 auto fDeviceKey = dht::ThreadPool::computation()
1324 .getShared<std::shared_ptr<dht::crypto::PrivateKey>>([]() {
1325 return std::make_shared<dht::crypto::PrivateKey>(
1326 dht::crypto::PrivateKey::generate());
1329 std::unique_ptr<AccountManager::AccountCredentials> creds;
1330 if (conf.managerUri.empty()) {
1331 auto acreds = std::make_unique<ArchiveAccountManager::ArchiveAccountCredentials>();
1332 auto archivePath = fileutils::getFullPath(idPath_, conf.archivePath);
1335 if (!archive_path.empty()) {
1337 acreds->scheme =
"file";
1338 acreds->uri = archive_path;
1339 }
else if (!conf.archive_url.empty() && conf.archive_url ==
"jami-auth") {
1341 JAMI_DEBUG(
"[JamiAccount] [LinkDevice] scheme p2p & uri {}", conf.archive_url);
1342 acreds->scheme =
"p2p";
1343 acreds->uri = conf.archive_url;
1344 }
else if (!archive_pin.empty()) {
1346 acreds->scheme =
"dht";
1347 acreds->uri = archive_pin;
1348 acreds->dhtBootstrap = loadBootstrap();
1349 acreds->dhtPort = dhtPortUsed();
1350 }
else if (std::filesystem::is_regular_file(archivePath)) {
1352 acreds->scheme =
"local";
1353 acreds->uri = archivePath.string();
1354 acreds->updateIdentity = id;
1357 creds = std::move(acreds);
1359 auto screds = std::make_unique<ServerAccountManager::ServerAccountCredentials>();
1360 screds->username = conf.managerUsername;
1361 creds = std::move(screds);
1363 creds->password = archive_password;
1364 bool hasPassword = !archive_password.empty();
1365 if (hasPassword && archive_password_scheme.empty())
1366 creds->password_scheme = fileutils::ARCHIVE_AUTH_SCHEME_PASSWORD;
1368 creds->password_scheme = archive_password_scheme;
1373 fmt::ptr(accountManager_));
1375 accountManager_->initAuthentication(
1377 ip_utils::getDeviceName(),
1382 hasPassword](
const AccountInfo& info,
1383 const std::map<std::string, std::string>& config,
1384 std::string&& receipt,
1385 std::vector<uint8_t>&& receipt_signature) {
1386 auto sthis = w.lock();
1389 JAMI_LOG(
"[Account {}] Auth success! Device: {}", getAccountID(), info.deviceId);
1391 dhtnet::fileutils::check_dir(idPath_, 0700);
1393 auto id = info.identity;
1394 editConfig([&](JamiAccountConfig& conf) {
1395 std::tie(conf.tlsPrivateKeyFile, conf.tlsCertificateFile)
1396 = saveIdentity(
id, idPath_, DEVICE_ID_PATH);
1397 conf.tlsPassword = {};
1398 auto passwordIt = config.find(
1400 if (passwordIt != config.end() && !passwordIt->second.empty()) {
1401 conf.archiveHasPassword = passwordIt->second ==
"true";
1403 conf.archiveHasPassword = hasPassword;
1406 if (not conf.managerUri.empty()) {
1407 conf.registeredName = conf.managerUsername;
1408 registeredName_ = conf.managerUsername;
1410 conf.username = info.accountId;
1411 conf.deviceName = accountManager_->getAccountDeviceName();
1413 auto nameServerIt = config.find(
1415 if (nameServerIt != config.end() && !nameServerIt->second.empty()) {
1416 conf.nameServer = nameServerIt->second;
1418 auto displayNameIt = config.find(
1420 if (displayNameIt != config.end() && !displayNameIt->second.empty()) {
1421 conf.displayName = displayNameIt->second;
1423 conf.receipt = std::move(receipt);
1424 conf.receiptSignature = std::move(receipt_signature);
1425 conf.fromMap(config);
1427 id_ = std::move(
id);
1429 std::lock_guard lk(moduleMtx_);
1430 convModule_.reset();
1433 Migration::setState(getAccountID(), Migration::State::SUCCESS);
1435 if (not info.photo.empty() or not config_->displayName.empty())
1436 emitSignal<libjami::ConfigurationSignal::AccountProfileReceived>(
1437 getAccountID(), config_->displayName, info.photo);
1438 setRegistrationState(RegistrationState::UNREGISTERED);
1443 accountId = getAccountID(),
1444 migrating](AccountManager::AuthError error,
const std::string& message) {
1445 JAMI_WARNING(
"[Account {}] Auth error: {} {}", accountId, (
int) error, message);
1446 if ((
id.first || migrating)
1447 && error == AccountManager::AuthError::INVALID_ARGUMENTS) {
1450 Migration::setState(accountId, Migration::State::INVALID);
1451 if (
auto acc = w.lock())
1452 acc->setRegistrationState(RegistrationState::ERROR_NEED_MIGRATION);
1455 if (
auto acc = w.lock())
1456 acc->setRegistrationState(RegistrationState::ERROR_GENERIC);
1457 runOnMainThread([accountId = std::move(accountId)] {
1458 Manager::instance().removeAccount(accountId,
true);
1464 }
catch (
const std::exception& e) {
1465 JAMI_WARNING(
"[Account {}] Error loading account: {}", getAccountID(), e.what());
1466 accountManager_.reset();
1467 setRegistrationState(RegistrationState::ERROR_GENERIC);
1471std::map<std::string, std::string>
1472JamiAccount::getVolatileAccountDetails()
const
1474 auto a = SIPAccountBase::getVolatileAccountDetails();
1477 auto registeredName = getRegisteredName();
1478 if (not registeredName.empty())
1484 deviceAnnounced_ ? TRUE_STR : FALSE_STR);
1485 if (accountManager_) {
1486 if (
auto info = accountManager_->getInfo()) {
1495JamiAccount::lookupName(
const std::string& name)
1497 std::lock_guard lock(configurationMutex_);
1498 if (accountManager_)
1499 accountManager_->lookupUri(name,
1500 config().nameServer,
1501 [acc = getAccountID(), name](
const std::string& regName,
1502 const std::string& address,
1504 emitSignal<libjami::ConfigurationSignal::RegisteredNameFound>(
1505 acc, name, (
int) response, address, regName);
1510JamiAccount::lookupAddress(
const std::string& addr)
1512 std::lock_guard lock(configurationMutex_);
1513 auto acc = getAccountID();
1514 if (accountManager_)
1515 accountManager_->lookupAddress(
1517 [acc, addr](
const std::string& regName,
1518 const std::string& address,
1519 NameDirectory::Response response) {
1520 emitSignal<libjami::ConfigurationSignal::RegisteredNameFound>(acc,
1529JamiAccount::registerName(
const std::string& name,
1530 const std::string& scheme,
1531 const std::string& password)
1533 std::lock_guard lock(configurationMutex_);
1534 if (accountManager_)
1535 accountManager_->registerName(
1539 [acc = getAccountID(), name, w = weak()](NameDirectory::RegistrationResponse response,
1540 const std::string& regName) {
1541 auto res = (int) std::min(response, NameDirectory::RegistrationResponse::error);
1542 if (response == NameDirectory::RegistrationResponse::success) {
1543 if (
auto this_ = w.lock()) {
1544 if (this_->setRegisteredName(regName)) {
1545 this_->editConfig([&](JamiAccountConfig& config) {
1546 config.registeredName = regName;
1548 emitSignal<libjami::ConfigurationSignal::VolatileDetailsChanged>(
1549 this_->accountID_, this_->getVolatileAccountDetails());
1553 emitSignal<libjami::ConfigurationSignal::NameRegistrationEnded>(acc, res, name);
1559JamiAccount::searchUser(
const std::string& query)
1561 if (accountManager_)
1562 return accountManager_->searchUser(
1575JamiAccount::forEachPendingCall(
const DeviceId& deviceId,
1576 const std::function<
void(
const std::shared_ptr<SIPCall>&)>& cb)
1578 std::vector<std::shared_ptr<SIPCall>> pc;
1580 std::lock_guard lk(pendingCallsMutex_);
1581 pc = std::move(pendingCalls_[deviceId]);
1583 for (
const auto& pendingCall : pc) {
1589JamiAccount::registerAsyncOps()
1591 auto onLoad = [
this, loaded = std::make_shared<std::atomic_uint>()] {
1592 if (++(*loaded) == 2u) {
1593 runOnMainThread([w = weak()] {
1594 if (
auto s = w.lock()) {
1595 std::lock_guard lock(s->configurationMutex_);
1602 loadCachedProxyServer([onLoad](
const std::string&) { onLoad(); });
1605 JAMI_LOG(
"[Account {:s}] UPnP: attempting to map ports", getAccountID());
1608 if (dhtUpnpMapping_.isValid()) {
1609 upnpCtrl_->releaseMapping(dhtUpnpMapping_);
1612 dhtUpnpMapping_.enableAutoUpdate(
true);
1615 dhtUpnpMapping_.setNotifyCallback([w = weak(),
1617 update = std::make_shared<bool>(
false)](
1618 dhtnet::upnp::Mapping::sharedPtr_t mapRes) {
1619 if (
auto accPtr = w.lock()) {
1620 auto& dhtMap = accPtr->dhtUpnpMapping_;
1621 const auto& accId = accPtr->getAccountID();
1623 JAMI_LOG(
"[Account {:s}] DHT UPnP mapping changed to {:s}",
1625 mapRes->toString(true));
1629 if (dhtMap.getMapKey() != mapRes->getMapKey()
1630 or dhtMap.getState() != mapRes->getState()) {
1634 if (mapRes->getState() == dhtnet::upnp::MappingState::OPEN
1635 or (mapRes->getState() == dhtnet::upnp::MappingState::FAILED
1636 and dhtMap.getState() == dhtnet::upnp::MappingState::OPEN)) {
1638 dhtMap.updateFrom(mapRes);
1641 "[Account {:s}] Allocated port changed to {}. Restarting the "
1644 accPtr->dhtPortUsed());
1646 accPtr->dht_->connectivityChanged();
1650 dhtMap.updateFrom(mapRes);
1656 if (mapRes->getState() == dhtnet::upnp::MappingState::OPEN) {
1657 dhtMap.updateFrom(mapRes);
1659 "[Account {:s}] Mapping {:s} successfully allocated: starting the DHT",
1663 JAMI_WARNING(
"[Account {:s}] Mapping request is in {:s} state: starting "
1666 mapRes->getStateStr());
1676 auto map = upnpCtrl_->reserveMapping(dhtUpnpMapping_);
1690JamiAccount::doRegister()
1692 std::lock_guard lock(configurationMutex_);
1693 if (not isUsable()) {
1694 JAMI_WARNING(
"[Account {:s}] Account must be enabled and active to register, ignoring",
1699 JAMI_LOG(
"[Account {:s}] Starting account…", getAccountID());
1704 if (registrationState_ == RegistrationState::INITIALIZING
1705 || registrationState_ == RegistrationState::ERROR_NEED_MIGRATION)
1709 setRegistrationState(RegistrationState::TRYING);
1711 if (upnpCtrl_ or proxyServerCached_.empty()) {
1718std::vector<std::string>
1719JamiAccount::loadBootstrap()
const
1721 std::vector<std::string> bootstrap;
1722 std::string_view stream(config().hostname), node_addr;
1724 bootstrap.emplace_back(node_addr);
1725 for (
const auto& b : bootstrap)
1726 JAMI_DBG(
"[Account %s] Bootstrap node: %s", getAccountID().c_str(), b.c_str());
1731JamiAccount::trackBuddyPresence(
const std::string& buddy_id,
bool track)
1733 std::string buddyUri;
1737 JAMI_ERROR(
"[Account {:s}] Failed to track presence: invalid URI {:s}",
1742 JAMI_LOG(
"[Account {:s}] {:s} presence for {:s}",
1744 track ?
"Track" :
"Untrack",
1747 auto h = dht::InfoHash(buddyUri);
1748 std::unique_lock lock(buddyInfoMtx);
1750 auto buddy = trackedBuddies_.emplace(h,
BuddyInfo {h});
1752 trackPresence(buddy.first->first, buddy.first->second);
1754 auto it = presenceState_.find(buddyUri);
1755 if (it != presenceState_.end() && it->second != PresenceState::DISCONNECTED) {
1757 emitSignal<libjami::PresenceSignal::NewBuddyNotification>(getAccountID(),
1759 static_cast<int>(it->second),
1763 auto buddy = trackedBuddies_.find(h);
1764 if (buddy != trackedBuddies_.end()) {
1765 if (
auto dht = dht_)
1766 if (
dht->isRunning())
1767 dht->cancelListen(h, std::move(buddy->second.listenToken));
1768 trackedBuddies_.erase(buddy);
1774JamiAccount::trackPresence(
const dht::InfoHash& h, BuddyInfo& buddy)
1777 if (not
dht or not
dht->isRunning()) {
1781 =
dht->listen<DeviceAnnouncement>(h, [
this, h](DeviceAnnouncement&&
dev,
bool expired) {
1782 bool wasConnected, isConnected;
1784 std::lock_guard lock(buddyInfoMtx);
1785 auto buddy = trackedBuddies_.find(h);
1786 if (buddy == trackedBuddies_.end())
1788 wasConnected = buddy->second.devices_cnt > 0;
1790 --buddy->second.devices_cnt;
1792 ++buddy->second.devices_cnt;
1793 isConnected = buddy->second.devices_cnt > 0;
1797 runOnMainThread([w = weak(), h,
dev, expired, isConnected, wasConnected]() {
1798 auto sthis = w.lock();
1803 sthis->messageEngine_.onPeerOnline(h.toString());
1805 if (isConnected and not wasConnected) {
1806 sthis->onTrackedBuddyOnline(h);
1807 }
else if (not isConnected and wasConnected) {
1808 sthis->onTrackedBuddyOffline(h);
1816std::map<std::string, bool>
1817JamiAccount::getTrackedBuddyPresence()
const
1819 std::lock_guard lock(buddyInfoMtx);
1820 std::map<std::string, bool> presence_info;
1821 for (
const auto& buddy_info_p : trackedBuddies_)
1822 presence_info.emplace(buddy_info_p.first.toString(), buddy_info_p.second.devices_cnt > 0);
1823 return presence_info;
1827JamiAccount::onTrackedBuddyOnline(
const dht::InfoHash& contactId)
1829 std::string id(contactId.toString());
1830 JAMI_DEBUG(
"[Account {:s}] Buddy {} online", getAccountID(),
id);
1831 auto& state = presenceState_[id];
1832 if (state < PresenceState::AVAILABLE) {
1833 state = PresenceState::AVAILABLE;
1834 emitSignal<libjami::PresenceSignal::NewBuddyNotification>(getAccountID(),
1837 PresenceState::AVAILABLE),
1841 if (
auto details = getContactInfo(
id)) {
1842 if (!details->confirmed) {
1843 auto convId = convModule()->getOneToOneConversation(
id);
1848 std::lock_guard lock(configurationMutex_);
1849 if (accountManager_) {
1851 auto requestPath = cachePath_ /
"requests" / id;
1852 std::vector<uint8_t> payload;
1854 payload = fileutils::loadFile(requestPath);
1857 if (payload.size() >= 64000) {
1859 "[Account {:s}] Trust request for contact {:s} is too big, reset payload",
1864 accountManager_->sendTrustRequest(
id, convId, payload);
1871JamiAccount::onTrackedBuddyOffline(
const dht::InfoHash& contactId)
1873 auto id = contactId.toString();
1874 JAMI_DEBUG(
"[Account {:s}] Buddy {} offline", getAccountID(),
id);
1875 auto& state = presenceState_[id];
1876 if (state > PresenceState::DISCONNECTED) {
1877 if (state == PresenceState::CONNECTED) {
1878 JAMI_WARNING(
"[Account {:s}] Buddy {} is not present on the DHT, but P2P connected",
1883 state = PresenceState::DISCONNECTED;
1884 emitSignal<libjami::PresenceSignal::NewBuddyNotification>(getAccountID(),
1887 PresenceState::DISCONNECTED),
1893JamiAccount::doRegister_()
1895 if (registrationState_ != RegistrationState::TRYING) {
1896 JAMI_ERROR(
"[Account {}] Already registered", getAccountID());
1900 JAMI_DEBUG(
"[Account {}] Starting account…", getAccountID());
1901 const auto& conf = config();
1904 if (not accountManager_ or not accountManager_->getInfo())
1905 throw std::runtime_error(
"No identity configured for this account.");
1907 if (dht_->isRunning()) {
1908 JAMI_ERROR(
"[Account {}] DHT already running (stopping it first).", getAccountID());
1912 convModule()->clearPendingFetch();
1916 accountManager_->lookupAddress(
1917 accountManager_->getInfo()->accountId,
1918 [w = weak()](
const std::string& regName,
1919 const std::string& address,
1920 const NameDirectory::Response& response) {
1921 if (
auto this_ = w.lock()) {
1922 if (response == NameDirectory::Response::found
1923 or response == NameDirectory::Response::notFound) {
1924 const auto& nameResult = response == NameDirectory::Response::found
1927 if (this_->setRegisteredName(nameResult)) {
1928 this_->editConfig([&](JamiAccountConfig& config) {
1929 config.registeredName = nameResult;
1931 emitSignal<libjami::ConfigurationSignal::VolatileDetailsChanged>(
1932 this_->accountID_, this_->getVolatileAccountDetails());
1939 dht::DhtRunner::Config config {};
1940 config.dht_config.node_config.network = 0;
1941 config.dht_config.node_config.maintain_storage =
false;
1942 config.dht_config.node_config.persist_path = (cachePath_ /
"dhtstate").
string();
1943 config.dht_config.id = id_;
1944 config.dht_config.cert_cache_all =
true;
1945 config.push_node_id = getAccountID();
1946 config.push_token = conf.deviceKey;
1947 config.push_topic = conf.notificationTopic;
1948 config.push_platform = conf.platform;
1950 config.threaded =
true;
1951 config.peer_discovery = conf.dhtPeerDiscovery;
1952 config.peer_publish = conf.dhtPeerDiscovery;
1953 if (conf.proxyEnabled)
1954 config.proxy_server = proxyServerCached_;
1956 if (not config.proxy_server.empty()) {
1957 JAMI_LOG(
"[Account {}] Using proxy server {}", getAccountID(), config.proxy_server);
1958 if (not config.push_token.empty()) {
1960 "[Account {}] using push notifications with platform: {}, topic: {}, token: {}",
1962 config.push_platform,
1969 if (conf.accountPeerDiscovery or conf.accountPublish) {
1970 peerDiscovery_ = std::make_shared<dht::PeerDiscovery>();
1971 if (conf.accountPeerDiscovery) {
1972 JAMI_LOG(
"[Account {}] Starting Jami account discovery…", getAccountID());
1973 startAccountDiscovery();
1975 if (conf.accountPublish)
1976 startAccountPublish();
1978 dht::DhtRunner::Context context {};
1979 context.peerDiscovery = peerDiscovery_;
1980 context.rng = std::make_unique<std::mt19937_64>(dht::crypto::getDerivedRandomEngine(rand));
1982 auto dht_log_level = Manager::instance().dhtLogLevel;
1983 if (dht_log_level > 0) {
1984 context.logger = Logger::dhtLogger();
1986 context.certificateStore = [&](
const dht::InfoHash& pk_id) {
1987 std::vector<std::shared_ptr<dht::crypto::Certificate>> ret;
1988 if (
auto cert = certStore().getCertificate(pk_id.toString()))
1989 ret.emplace_back(std::move(cert));
1990 JAMI_LOG(
"Query for local certificate store: {}: {} found.",
1996 context.statusChangedCallback = [
this](dht::NodeStatus s4, dht::NodeStatus s6) {
1997 JAMI_DBG(
"[Account %s] DHT status: IPv4 %s; IPv6 %s",
1998 getAccountID().c_str(),
2002 auto newStatus = std::max(s4, s6);
2003 switch (newStatus) {
2004 case dht::NodeStatus::Connecting:
2005 state = RegistrationState::TRYING;
2007 case dht::NodeStatus::Connected:
2008 state = RegistrationState::REGISTERED;
2010 case dht::NodeStatus::Disconnected:
2011 state = RegistrationState::UNREGISTERED;
2014 state = RegistrationState::ERROR_GENERIC;
2018 setRegistrationState(state);
2020 context.identityAnnouncedCb = [
this](
bool ok) {
2023 accountManager_->startSync(
2024 [
this](
const std::shared_ptr<dht::crypto::Certificate>& crt) {
2028 auto deviceId = crt->getLongId().toString();
2029 if (accountManager_->getInfo()->deviceId == deviceId)
2032 dht::ThreadPool::io().run([w = weak(), deviceId, crt] {
2033 auto shared = w.lock();
2034 if (!shared)
return;
2035 std::unique_lock lk(shared->connManagerMtx_);
2036 shared->initConnectionManager();
2038 std::shared_lock slk(shared->connManagerMtx_);
2042 if (!shared->connectionManager_)
2044 shared->requestMessageConnection(shared->getUsername(), crt->getLongId(),
"sync");
2045 if (!shared->syncModule()->isConnected(crt->getLongId())) {
2046 shared->channelHandlers_[Uri::Scheme::SYNC]
2047 ->connect(crt->getLongId(),
2049 [](std::shared_ptr<dhtnet::ChannelSocket> socket,
2050 const DeviceId& deviceId) {});
2057 deviceAnnounced_ =
true;
2060 dht::ThreadPool::io().run([w = weak()] {
2061 if (
auto shared = w.lock())
2062 shared->convModule()->bootstrap();
2064 emitSignal<libjami::ConfigurationSignal::VolatileDetailsChanged>(
2071 dht_->run(dhtPortUsed(), config, std::move(context));
2073 for (
const auto& bootstrap : loadBootstrap())
2074 dht_->bootstrap(bootstrap);
2076 dhtBoundPort_ = dht_->getBoundPort();
2078 accountManager_->setDht(dht_);
2080 std::unique_lock lkCM(connManagerMtx_);
2081 initConnectionManager();
2082 connectionManager_->onDhtConnected(*accountManager_->getInfo()->devicePk);
2083 connectionManager_->onICERequest([
this](
const DeviceId& deviceId) {
2084 std::promise<bool>
accept;
2085 std::future<bool> fut =
accept.get_future();
2086 accountManager_->findCertificate(
2087 deviceId, [
this, &accept](
const std::shared_ptr<dht::crypto::Certificate>& cert) {
2092 dht::InfoHash peer_account_id;
2093 auto res = accountManager_->onPeerCertificate(cert,
2094 this->config().dhtPublicInCalls,
2096 JAMI_LOG(
"[Account {}] [device {}] {} ICE request from {}",
2099 res ?
"Accepting" :
"Discarding",
2104 auto result = fut.get();
2107 connectionManager_->onChannelRequest(
2108 [
this](
const std::shared_ptr<dht::crypto::Certificate>& cert,
const std::string& name) {
2109 JAMI_LOG(
"[Account {}] [device {}] New channel requested: '{}'",
2114 if (this->config().turnEnabled && turnCache_) {
2115 auto addr = turnCache_->getResolvedTurn();
2116 if (addr == std::nullopt) {
2120 turnCache_->refresh();
2124 auto uri = Uri(name);
2125 std::shared_lock lk(connManagerMtx_);
2126 auto itHandler = channelHandlers_.find(uri.scheme());
2127 if (itHandler != channelHandlers_.end() && itHandler->second)
2128 return itHandler->second->onRequest(cert, name);
2129 return name ==
"sip";
2131 connectionManager_->onConnectionReady([
this](
const DeviceId& deviceId,
2132 const std::string& name,
2133 std::shared_ptr<dhtnet::ChannelSocket> channel) {
2135 auto cert = channel->peerCertificate();
2136 if (!cert || !cert->issuer)
2138 auto peerId = cert->issuer->getId().toString();
2140 if (accountManager()->getCertificateStatus(peerId)
2141 == dhtnet::tls::TrustStore::PermissionStatus::BANNED) {
2142 channel->shutdown();
2145 if (name ==
"sip") {
2146 cacheSIPConnection(std::move(channel), peerId, deviceId);
2147 }
else if (name.find(
"git://") == 0) {
2148 auto sep = name.find_last_of(
'/');
2149 auto conversationId = name.substr(sep + 1);
2150 auto remoteDevice = name.substr(6, sep - 6);
2152 if (channel->isInitiator()) {
2158 if (convModule()->isBanned(conversationId, remoteDevice)) {
2160 "[Account {:s}] [Conversation {}] Git server requested, but the "
2161 "device is unauthorized ({:s}) ",
2165 channel->shutdown();
2169 auto sock = convModule()->gitSocket(deviceId.toString(), conversationId);
2170 if (sock == channel) {
2176 "[Account {:s}] [Conversation {}] [device {}] Git server requested",
2179 deviceId.toString());
2180 auto gs = std::make_unique<GitServer>(accountID_, conversationId, channel);
2181 syncCnt_.fetch_add(1);
2182 gs->setOnFetched([w = weak(), conversationId, deviceId](
2183 const std::string& commit) {
2184 dht::ThreadPool::computation().run([w, conversationId, deviceId, commit]() {
2185 if (
auto shared = w.lock()) {
2186 shared->convModule()->setFetched(conversationId,
2187 deviceId.toString(),
2189 if (shared->syncCnt_.fetch_sub(1) == 1) {
2190 emitSignal<libjami::ConversationSignal::ConversationCloned>(
2191 shared->getAccountID().c_str());
2196 const dht::Value::Id serverId =
ValueIdDist()(rand);
2198 std::lock_guard lk(gitServersMtx_);
2199 gitServers_[serverId] = std::move(gs);
2201 channel->onShutdown([w = weak(), serverId]() {
2203 runOnMainThread([serverId, w]() {
2204 if (
auto sthis = w.lock()) {
2205 std::lock_guard lk(sthis->gitServersMtx_);
2206 sthis->gitServers_.erase(serverId);
2212 std::shared_lock lk(connManagerMtx_);
2213 auto uri = Uri(name);
2214 auto itHandler = channelHandlers_.find(uri.scheme());
2215 if (itHandler != channelHandlers_.end() && itHandler->second)
2216 itHandler->second->onReady(cert, name, std::move(channel));
2222 if (!conf.managerUri.empty() && accountManager_) {
2223 dynamic_cast<ServerAccountManager*
>(accountManager_.get())->onNeedsMigration([
this]() {
2224 editConfig([&](JamiAccountConfig& conf) {
2225 conf.receipt.clear();
2226 conf.receiptSignature.clear();
2228 Migration::setState(accountID_, Migration::State::INVALID);
2229 setRegistrationState(RegistrationState::ERROR_NEED_MIGRATION);
2231 dynamic_cast<ServerAccountManager*
>(accountManager_.get())
2232 ->syncBlueprintConfig([
this](
const std::map<std::string, std::string>& config) {
2233 editConfig([&](JamiAccountConfig& conf) { conf.fromMap(config); });
2234 emitSignal<libjami::ConfigurationSignal::AccountDetailsChanged>(
2239 std::lock_guard lock(buddyInfoMtx);
2240 for (
auto& buddy : trackedBuddies_) {
2241 buddy.second.devices_cnt = 0;
2242 trackPresence(buddy.first, buddy.second);
2244 }
catch (
const std::exception& e) {
2245 JAMI_ERR(
"Error registering DHT account: %s", e.what());
2246 setRegistrationState(RegistrationState::ERROR_GENERIC);
2251JamiAccount::convModule(
bool noCreation)
2254 return convModule_.get();
2255 if (!accountManager() || currentDeviceId() ==
"") {
2256 JAMI_ERROR(
"[Account {}] Calling convModule() with an uninitialized account",
2260 std::unique_lock<std::recursive_mutex> lock(configurationMutex_);
2261 std::lock_guard lk(moduleMtx_);
2263 convModule_ = std::make_unique<ConversationModule>(
2266 [
this](
auto&& syncMsg) {
2267 dht::ThreadPool::io().run([w = weak(), syncMsg] {
2268 if (
auto shared = w.lock()) {
2269 auto& config = shared->config();
2272 if (!config.managerUri.empty() && !syncMsg)
2273 if (auto am = shared->accountManager())
2275 if (auto sm = shared->syncModule())
2276 sm->syncWithConnected(syncMsg);
2280 [
this](
auto&& uri,
auto&& device,
auto&& msg,
auto token = 0) {
2284 auto deviceId = device ? device.toString() :
"";
2285 return sendTextMessage(uri, deviceId, msg, token);
2287 [
this](
const auto& convId,
const auto& deviceId,
auto cb,
const auto& type) {
2288 dht::ThreadPool::io().run([w = weak(), convId, deviceId, cb = std::move(cb), type] {
2289 auto shared = w.lock();
2292 if (
auto socket = shared->convModule()->gitSocket(deviceId, convId)) {
2299 std::shared_lock lkCM(shared->connManagerMtx_);
2300 if (!shared->connectionManager_) {
2306 shared->connectionManager_->connectDevice(
2308 fmt::format(
"git://{}/{}", deviceId, convId),
2311 convId](std::shared_ptr<dhtnet::ChannelSocket> socket,
const DeviceId&) {
2312 dht::ThreadPool::io().run([w,
2314 socket = std::move(socket),
2317 socket->onShutdown([w, deviceId = socket->deviceId(), convId] {
2318 dht::ThreadPool::io().run([w, deviceId, convId] {
2319 if (auto shared = w.lock())
2320 shared->convModule()
2321 ->removeGitSocket(deviceId.toString(), convId);
2335 [
this](
const auto& convId,
const auto& deviceId,
auto&& cb,
const auto& connectionType) {
2336 dht::ThreadPool::io().run([w = weak(),
2341 auto shared = w.lock();
2344 auto cm = shared->convModule();
2345 std::shared_lock lkCM(shared->connManagerMtx_);
2346 if (!shared->connectionManager_ || !cm || cm->isBanned(convId, deviceId)) {
2347 asio::post(*Manager::instance().ioContext(), [cb = std::move(cb)] { cb({}); });
2350 if (!shared->connectionManager_->isConnecting(
DeviceId(deviceId),
2351 fmt::format(
"swarm://{}",
2353 shared->connectionManager_->connectDevice(
2355 fmt::format(
"swarm://{}", convId),
2356 [w, cb = std::move(cb)](std::shared_ptr<dhtnet::ChannelSocket> socket,
2358 dht::ThreadPool::io().run([w,
2360 socket = std::move(socket),
2363 auto shared = w.lock();
2366 auto remoteCert = socket->peerCertificate();
2367 auto uri = remoteCert->issuer->getId().
toString();
2368 if (shared->accountManager()->getCertificateStatus(uri)
2369 == dhtnet::tls::TrustStore::PermissionStatus::BANNED) {
2373 shared->requestMessageConnection(uri, deviceId,
"");
2381 [
this](
auto&& convId,
auto&& from) {
2383 ->findCertificate(dht::InfoHash(from),
2384 [
this, from, convId](
2385 const std::shared_ptr<dht::crypto::Certificate>& cert) {
2386 auto info = accountManager_->getInfo();
2389 info->contacts->onTrustRequest(dht::InfoHash(from),
2390 cert->getSharedPublicKey(),
2397 autoLoadConversations_);
2399 return convModule_.get();
2403JamiAccount::syncModule()
2405 if (!accountManager() || currentDeviceId() ==
"") {
2406 JAMI_ERR() <<
"Calling syncModule() with an uninitialized account.";
2409 std::lock_guard lk(moduleMtx_);
2411 syncModule_ = std::make_unique<SyncModule>(weak());
2412 return syncModule_.get();
2416JamiAccount::onTextMessage(
const std::string&
id,
2417 const std::string& from,
2418 const std::shared_ptr<dht::crypto::Certificate>& peerCert,
2419 const std::map<std::string, std::string>& payloads)
2423 SIPAccountBase::onTextMessage(
id, fromUri, peerCert, payloads);
2429JamiAccount::loadConversation(
const std::string& convId)
2431 if (
auto cm = convModule(
true))
2432 cm->loadSingleConversation(convId);
2436JamiAccount::doUnregister(
bool forceShutdownConnections)
2438 std::unique_lock<std::recursive_mutex> lock(configurationMutex_);
2439 if (registrationState_ >= RegistrationState::ERROR_GENERIC) {
2444 std::condition_variable cv;
2445 bool shutdown_complete {
false};
2447 if (peerDiscovery_) {
2452 JAMI_WARN(
"[Account %s] Unregistering account %p", getAccountID().c_str(),
this);
2455 JAMI_WARN(
"[Account %s] DHT shutdown complete", getAccountID().c_str());
2456 std::lock_guard lock(mtx);
2457 shutdown_complete =
true;
2463 std::lock_guard lk(pendingCallsMutex_);
2464 pendingCalls_.clear();
2470 if (not isEnabled() || forceShutdownConnections)
2471 shutdownConnections();
2474 if (upnpCtrl_ and dhtUpnpMapping_.isValid()) {
2475 upnpCtrl_->releaseMapping(dhtUpnpMapping_);
2479 std::unique_lock lock(mtx);
2480 cv.wait(lock, [&] {
return shutdown_complete; });
2483 setRegistrationState(RegistrationState::UNREGISTERED);
2496 const std::string& detail_str)
2498 if (registrationState_ != state) {
2499 if (state == RegistrationState::REGISTERED) {
2500 JAMI_WARNING(
"[Account {}] Connected", getAccountID());
2501 turnCache_->refresh();
2502 if (connectionManager_)
2503 connectionManager_->storeActiveIpAddress();
2504 }
else if (state == RegistrationState::TRYING) {
2505 JAMI_WARNING(
"[Account {}] Connecting…", getAccountID());
2507 deviceAnnounced_ =
false;
2508 JAMI_WARNING(
"[Account {}] Disconnected", getAccountID());
2512 Account::setRegistrationState(state, detail_code, detail_str);
2516JamiAccount::reloadContacts()
2518 accountManager_->reloadContacts();
2522JamiAccount::connectivityChanged()
2525 if (not isUsable()) {
2530 if (
auto cm = convModule())
2531 cm->connectivityChanged();
2532 dht_->connectivityChanged();
2534 std::shared_lock lkCM(connManagerMtx_);
2535 if (connectionManager_) {
2536 connectionManager_->connectivityChanged();
2538 connectionManager_->setPublishedAddress({});
2544JamiAccount::findCertificate(
2545 const dht::InfoHash& h,
2546 std::function<
void(
const std::shared_ptr<dht::crypto::Certificate>&)>&& cb)
2548 if (accountManager_)
2549 return accountManager_->findCertificate(h, std::move(cb));
2554JamiAccount::findCertificate(
2555 const dht::PkId&
id, std::function<
void(
const std::shared_ptr<dht::crypto::Certificate>&)>&& cb)
2557 if (accountManager_)
2558 return accountManager_->findCertificate(
id, std::move(cb));
2563JamiAccount::findCertificate(
const std::string& crt_id)
2565 if (accountManager_)
2566 return accountManager_->findCertificate(dht::InfoHash(crt_id));
2571JamiAccount::setCertificateStatus(
const std::string& cert_id,
2572 dhtnet::tls::TrustStore::PermissionStatus status)
2574 bool done = accountManager_ ? accountManager_->setCertificateStatus(cert_id, status) :
false;
2576 findCertificate(cert_id);
2577 emitSignal<libjami::ConfigurationSignal::CertificateStateChanged>(
2578 getAccountID(), cert_id, dhtnet::tls::TrustStore::statusToStr(status));
2584JamiAccount::setCertificateStatus(
const std::shared_ptr<crypto::Certificate>& cert,
2585 dhtnet::tls::TrustStore::PermissionStatus status,
2588 bool done = accountManager_ ? accountManager_->setCertificateStatus(cert, status, local)
2591 findCertificate(cert->getId().toString());
2592 emitSignal<libjami::ConfigurationSignal::CertificateStateChanged>(
2593 getAccountID(), cert->getId().toString(), dhtnet::tls::TrustStore::statusToStr(status));
2598std::vector<std::string>
2599JamiAccount::getCertificatesByStatus(dhtnet::tls::TrustStore::PermissionStatus status)
2601 if (accountManager_)
2602 return accountManager_->getCertificatesByStatus(status);
2607JamiAccount::isMessageTreated(dht::Value::Id
id)
2609 std::lock_guard lock(messageMutex_);
2610 return !treatedMessages_.add(
id);
2614JamiAccount::sha3SumVerify()
const
2616 return !noSha3sumVerification_;
2621JamiAccount::noSha3sumVerification(
bool newValue)
2623 noSha3sumVerification_ = newValue;
2627std::map<std::string, std::string>
2628JamiAccount::getKnownDevices()
const
2630 std::lock_guard lock(configurationMutex_);
2631 if (not accountManager_ or not accountManager_->getInfo())
2633 std::map<std::string, std::string> ids;
2634 for (
const auto& d : accountManager_->getKnownDevices()) {
2635 auto id = d.first.toString();
2636 auto label = d.second.name.empty() ?
id.substr(0, 8) : d.second.name;
2637 ids.emplace(std::move(
id), std::move(label));
2643JamiAccount::loadCachedUrl(
const std::string& url,
2644 const std::filesystem::path& cachePath,
2645 const std::chrono::seconds& cacheDuration,
2646 std::function<
void(
const dht::http::Response& response)> cb)
2648 dht::ThreadPool::io().run([cb, url, cachePath, cacheDuration, w = weak()]() {
2652 std::lock_guard lk(dhtnet::fileutils::getFileLock(cachePath));
2653 data = fileutils::loadCacheTextFile(cachePath, cacheDuration);
2655 dht::http::Response ret;
2656 ret.body = std::move(data);
2657 ret.status_code = 200;
2659 }
catch (
const std::exception& e) {
2660 JAMI_LOG(
"Failed to load '{}' from '{}': {}", url, cachePath, e.what());
2662 if (
auto sthis = w.lock()) {
2663 auto req = std::make_shared<dht::http::Request>(
2664 *Manager::instance().ioContext(),
2666 [cb, cachePath, w](
const dht::http::Response& response) {
2667 if (response.status_code == 200) {
2669 std::lock_guard lk(dhtnet::fileutils::getFileLock(cachePath));
2670 fileutils::saveFile(cachePath,
2671 (const uint8_t*) response.body.data(),
2672 response.body.size(),
2674 JAMI_LOG(
"Cached result to '{}'", cachePath);
2675 } catch (
const std::exception& ex) {
2683 if (std::filesystem::exists(cachePath)) {
2684 JAMI_WARNING(
"Failed to download URL, using cached data");
2688 dhtnet::fileutils::getFileLock(cachePath));
2689 data = fileutils::loadTextFile(cachePath);
2691 dht::http::Response ret;
2692 ret.body = std::move(data);
2693 ret.status_code = 200;
2696 throw std::runtime_error(
"No cached data");
2701 if (
auto req = response.request.lock())
2702 if (
auto sthis = w.lock())
2703 sthis->requests_.erase(req);
2705 sthis->requests_.emplace(req);
2713JamiAccount::loadCachedProxyServer(std::function<
void(
const std::string& proxy)> cb)
2715 const auto& conf = config();
2716 if (conf.proxyEnabled and proxyServerCached_.empty()) {
2717 JAMI_DEBUG(
"[Account {:s}] Loading DHT proxy URL: {:s}", getAccountID(), conf.proxyListUrl);
2718 if (conf.proxyListUrl.empty() or not conf.proxyListEnabled) {
2719 cb(getDhtProxyServer(conf.proxyServer));
2721 loadCachedUrl(conf.proxyListUrl,
2722 cachePath_ /
"dhtproxylist",
2723 std::chrono::hours(24 * 3),
2724 [w = weak(), cb = std::move(cb)](
const dht::http::Response& response) {
2725 if (
auto sthis = w.lock()) {
2726 if (response.status_code == 200) {
2727 cb(sthis->getDhtProxyServer(response.body));
2729 cb(sthis->getDhtProxyServer(sthis->config().proxyServer));
2735 cb(proxyServerCached_);
2740JamiAccount::getDhtProxyServer(
const std::string& serverList)
2742 if (proxyServerCached_.empty()) {
2743 std::vector<std::string> proxys;
2745 std::sregex_iterator begin = {serverList.begin(), serverList.end(),
PROXY_REGEX}, end;
2746 for (
auto it = begin; it != end; ++it) {
2748 if (match[5].matched and match[6].matched) {
2750 auto start = std::stoi(match[5]), end = std::stoi(match[6]);
2751 for (
auto p = start; p <= end; p++)
2752 proxys.emplace_back(match[1].str() + match[2].str() +
":"
2753 + std::to_string(p));
2755 JAMI_WARN(
"Malformed proxy, ignore it");
2759 proxys.emplace_back(match[0].str());
2765 auto randIt = proxys.begin();
2766 std::advance(randIt,
2767 std::uniform_int_distribution<unsigned long>(0, proxys.size() - 1)(rand));
2768 proxyServerCached_ = *randIt;
2770 dhtnet::fileutils::check_dir(cachePath_, 0700);
2771 auto proxyCachePath = cachePath_ /
"dhtproxy";
2772 std::ofstream file(proxyCachePath);
2773 JAMI_DEBUG(
"Cache DHT proxy server: {}", proxyServerCached_);
2774 Json::Value node(Json::objectValue);
2775 node[getProxyConfigKey()] = proxyServerCached_;
2779 JAMI_WARNING(
"Unable to write into {}", proxyCachePath);
2781 return proxyServerCached_;
2785JamiAccount::matches(std::string_view userName, std::string_view server)
const
2787 if (not accountManager_ or not accountManager_->getInfo())
2788 return MatchRank::NONE;
2790 if (userName == accountManager_->getInfo()->accountId
2791 || server == accountManager_->getInfo()->accountId
2792 || userName == accountManager_->getInfo()->deviceId) {
2793 JAMI_LOG(
"Matching account ID in request with username {}", userName);
2794 return MatchRank::FULL;
2796 return MatchRank::NONE;
2801JamiAccount::getFromUri()
const
2803 const std::string uri =
"<sip:" + accountManager_->getInfo()->accountId +
"@ring.dht>";
2804 if (not config().displayName.empty())
2805 return "\"" + config().displayName +
"\" " + uri;
2810JamiAccount::getToUri(
const std::string& to)
const
2814 return fmt::format(
"<sips:{};transport=tls>", username);
2818getDisplayed(
const std::string& conversationId,
const std::string& messageId)
2822 "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n"
2823 "<imdn><message-id>{}</message-id>\n"
2825 "<display-notification><status><displayed/></status></display-notification>\n"
2828 conversationId.empty() ?
"" :
"<conversation>" + conversationId +
"</conversation>");
2835 return fmt::format(
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
2836 "<presence xmlns=\"urn:ietf:params:xml:ns:pidf\">\n"
2839 " <basic>{}</basic>\n"
2847JamiAccount::setIsComposing(
const std::string& conversationUri,
bool isWriting)
2849 Uri uri(conversationUri);
2850 std::string conversationId = {};
2851 if (uri.
scheme() == Uri::Scheme::SWARM) {
2857 if (
auto cm = convModule(
true)) {
2858 if (
auto typer = cm->getTypers(conversationId)) {
2860 typer->addTyper(getUsername(),
true);
2862 typer->removeTyper(getUsername(),
true);
2868JamiAccount::setMessageDisplayed(
const std::string& conversationUri,
2869 const std::string& messageId,
2872 Uri uri(conversationUri);
2873 std::string conversationId = {};
2874 if (uri.
scheme() == Uri::Scheme::SWARM)
2877 && isReadReceiptEnabled();
2878 if (!conversationId.empty())
2879 sendMessage &= convModule()->onMessageDisplayed(getUsername(), conversationId, messageId);
2882 {{MIME_TYPE_IMDN, getDisplayed(conversationId, messageId)}});
2887JamiAccount::getContactHeader(
const std::shared_ptr<SipTransport>& sipTransport)
2889 if (sipTransport and sipTransport->get() !=
nullptr) {
2890 auto transport = sipTransport->get();
2893 bool reliable = transport->flag & PJSIP_TRANSPORT_RELIABLE;
2894 return fmt::format(
"\"{}\" <sips:{}{}{};transport={}>",
2895 config().displayName,
2896 id_.second->getId().toString(),
2897 address.empty() ?
"" :
"@",
2899 reliable ?
"tls" :
"dtls");
2901 JAMI_ERR(
"getContactHeader: no SIP transport provided");
2902 return fmt::format(
"\"{}\" <sips:{}@ring.dht>",
2903 config().displayName,
2904 id_.second->getId().toString());
2909JamiAccount::addContact(
const std::string& uri,
bool confirmed)
2911 dht::InfoHash h(uri);
2913 JAMI_ERROR(
"addContact: invalid contact URI");
2916 auto conversation = convModule()->getOneToOneConversation(uri);
2917 if (!confirmed && conversation.empty())
2918 conversation = convModule()->startConversation(ConversationMode::ONE_TO_ONE, h);
2919 std::unique_lock<std::recursive_mutex> lock(configurationMutex_);
2920 if (accountManager_)
2921 accountManager_->addContact(h, confirmed, conversation);
2923 JAMI_WARNING(
"[Account {}] addContact: account not loaded", getAccountID());
2927JamiAccount::removeContact(
const std::string& uri,
bool ban)
2929 std::lock_guard lock(configurationMutex_);
2930 if (accountManager_)
2931 accountManager_->removeContact(uri, ban);
2933 JAMI_WARNING(
"[Account {}] removeContact: account not loaded", getAccountID());
2936std::map<std::string, std::string>
2937JamiAccount::getContactDetails(
const std::string& uri)
const
2939 std::lock_guard lock(configurationMutex_);
2940 return accountManager_ ? accountManager_->getContactDetails(uri)
2941 : std::map<std::string, std::string> {};
2944std::optional<Contact>
2945JamiAccount::getContactInfo(
const std::string& uri)
const
2947 std::lock_guard lock(configurationMutex_);
2948 return accountManager_ ? accountManager_->getContactInfo(uri) : std::nullopt;
2951std::vector<std::map<std::string, std::string>>
2952JamiAccount::getContacts(
bool includeRemoved)
const
2954 std::lock_guard lock(configurationMutex_);
2955 if (not accountManager_)
2957 return accountManager_->getContacts(includeRemoved);
2962std::vector<std::map<std::string, std::string>>
2963JamiAccount::getTrustRequests()
const
2965 std::lock_guard lock(configurationMutex_);
2966 return accountManager_ ? accountManager_->getTrustRequests()
2967 : std::vector<std::map<std::string, std::string>> {};
2971JamiAccount::acceptTrustRequest(
const std::string& from,
bool includeConversation)
2973 dht::InfoHash h(from);
2975 JAMI_ERROR(
"addContact: invalid contact URI");
2978 std::unique_lock<std::recursive_mutex> lock(configurationMutex_);
2979 if (accountManager_) {
2980 if (!accountManager_->acceptTrustRequest(from, includeConversation)) {
2983 return accountManager_->addContact(h,
true);
2987 JAMI_WARNING(
"[Account {}] acceptTrustRequest: account not loaded", getAccountID());
2992JamiAccount::discardTrustRequest(
const std::string& from)
2995 auto requests = getTrustRequests();
2996 for (
const auto& req : requests) {
2998 convModule()->declineConversationRequest(
3004 std::lock_guard lock(configurationMutex_);
3005 if (accountManager_)
3006 return accountManager_->discardTrustRequest(from);
3007 JAMI_WARNING(
"[Account {:s}] discardTrustRequest: account not loaded", getAccountID());
3012JamiAccount::declineConversationRequest(
const std::string& conversationId)
3014 auto peerId = convModule()->peerFromConversationRequest(conversationId);
3015 convModule()->declineConversationRequest(conversationId);
3016 if (!peerId.empty()) {
3017 std::lock_guard lock(configurationMutex_);
3018 if (
auto info = accountManager_->getInfo()) {
3020 auto req = info->contacts->getTrustRequest(dht::InfoHash(peerId));
3023 accountManager_->discardTrustRequest(peerId);
3024 JAMI_DEBUG(
"[Account {:s}] Declined trust request with {:s}",
3033JamiAccount::sendTrustRequest(
const std::string& to,
const std::vector<uint8_t>& payload)
3035 dht::InfoHash h(to);
3037 JAMI_ERROR(
"addContact: invalid contact URI");
3041 auto requestPath = cachePath_ /
"requests";
3042 dhtnet::fileutils::recursive_mkdir(requestPath, 0700);
3043 auto cachedFile = requestPath / to;
3044 std::ofstream req(cachedFile, std::ios::trunc | std::ios::binary);
3045 if (!req.is_open()) {
3046 JAMI_ERROR(
"Unable to write data to {}", cachedFile);
3050 if (not payload.empty()) {
3051 req.write(
reinterpret_cast<const char*
>(payload.data()), payload.size());
3054 if (payload.size() >= 64000) {
3055 JAMI_WARN() <<
"Trust request is too big. Remove payload";
3058 auto conversation = convModule()->getOneToOneConversation(to);
3059 if (conversation.empty())
3060 conversation = convModule()->startConversation(ConversationMode::ONE_TO_ONE, h);
3061 if (not conversation.empty()) {
3062 std::lock_guard lock(configurationMutex_);
3063 if (accountManager_)
3064 accountManager_->sendTrustRequest(to,
3066 payload.size() >= 64000 ? std::vector<uint8_t> {}
3069 JAMI_WARNING(
"[Account {}] sendTrustRequest: account not loaded", getAccountID());
3071 JAMI_WARNING(
"[Account {}] sendTrustRequest: account not loaded", getAccountID());
3075JamiAccount::forEachDevice(
const dht::InfoHash& to,
3076 std::function<
void(
const std::shared_ptr<dht::crypto::PublicKey>&)>&& op,
3077 std::function<
void(
bool)>&& end)
3079 accountManager_->forEachDevice(to, std::move(op), std::move(end));
3083JamiAccount::sendTextMessage(
const std::string& to,
3084 const std::string& deviceId,
3085 const std::map<std::string, std::string>& payloads,
3086 uint64_t refreshToken,
3090 if (uri.
scheme() == Uri::Scheme::SWARM) {
3091 sendInstantMessage(uri.
authority(), payloads);
3099 JAMI_ERROR(
"Failed to send a text message due to an invalid URI {}", to);
3102 if (payloads.size() != 1) {
3103 JAMI_ERROR(
"Multi-part im is not supported yet by JamiAccount");
3106 return SIPAccountBase::sendTextMessage(toUri, deviceId, payloads, refreshToken, onlyConnected);
3110JamiAccount::sendMessage(
const std::string& to,
3111 const std::string& deviceId,
3112 const std::map<std::string, std::string>& payloads,
3114 bool retryOnTimeout,
3121 JAMI_ERROR(
"[Account {}] Failed to send a text message due to an invalid URI {}",
3125 messageEngine_.onMessageSent(to, token,
false, deviceId);
3128 if (payloads.size() != 1) {
3129 JAMI_ERROR(
"Multi-part im is not supported");
3131 messageEngine_.onMessageSent(toUri, token,
false, deviceId);
3136 std::shared_lock clk(connManagerMtx_);
3138 channelHandlers_[Uri::Scheme::MESSAGE].get());
3142 messageEngine_.onMessageSent(to, token,
false, deviceId);
3149 class SendMessageContext {
3151 using OnComplete = std::function<void(
bool,
bool)>;
3152 SendMessageContext(OnComplete onComplete) : onComplete(std::move(onComplete)) {}
3155 std::lock_guard lk(mtx);
3156 return devices.insert(device).second;
3160 std::unique_lock lk(mtx);
3165 bool complete(
const DeviceId& device,
bool success) {
3166 std::unique_lock lk(mtx);
3167 if (devices.erase(device) == 0)
3175 bool empty()
const {
3176 std::lock_guard lk(mtx);
3177 return devices.empty();
3179 bool pending(
const DeviceId& device)
const {
3180 std::lock_guard lk(mtx);
3181 return devices.find(device) != devices.end();
3184 mutable std::mutex mtx;
3185 OnComplete onComplete;
3186 std::set<DeviceId> devices;
3187 unsigned completeCount = 0;
3188 unsigned successCount = 0;
3189 bool started {
false};
3191 void checkComplete(std::unique_lock<std::mutex>& lk) {
3192 if (started && (devices.empty() || successCount)) {
3194 auto cb = std::move(onComplete);
3197 cb(successCount != 0, completeCount != 0);
3202 auto devices = std::make_shared<SendMessageContext>([
3209 ](
bool success,
bool sent) {
3210 if (
auto acc = w.lock())
3211 acc->onMessageSent(to, token, deviceId, success, onlyConnected, sent && retryOnTimeout);
3214 struct TextMessageCtx {
3215 std::weak_ptr<JamiAccount> acc;
3218 std::shared_ptr<SendMessageContext> devices;
3219 std::shared_ptr<dhtnet::ChannelSocket> sipChannel;
3222 auto completed = [w = weak(), to, devices](
const DeviceId& device, std::shared_ptr<dhtnet::ChannelSocket> conn,
bool success) {
3224 if (
auto acc = w.lock()) {
3225 std::shared_lock clk(acc->connManagerMtx_);
3227 acc->channelHandlers_[Uri::Scheme::MESSAGE].get())) {
3228 handler->closeChannel(to, device, conn);
3231 devices->complete(device, success);
3234 const auto& payload = *payloads.begin();
3235 auto msg = std::make_shared<MessageChannelHandler::Message>();
3237 msg->t = payload.first;
3238 msg->c = payload.second;
3240 if (deviceId.empty()) {
3241 auto conns = handler->getChannels(toUri);
3243 for (
const auto& conn : conns) {
3244 auto connDevice = conn->deviceId();
3245 if (!devices->add(connDevice))
3247 dht::ThreadPool::io().run([completed, connDevice, conn, msg] {
3248 completed(connDevice, conn, MessageChannelHandler::sendMessage(conn, *msg));
3252 if (
auto conn = handler->getChannel(toUri, device)) {
3254 devices->add(device);
3255 dht::ThreadPool::io().run([completed, device, conn, msg] {
3256 completed(device, conn, MessageChannelHandler::sendMessage(conn, *msg));
3265 std::unique_lock lk(sipConnsMtx_);
3266 for (
auto& [key, value] : sipConns_) {
3267 if (key.first != to or value.empty())
3269 if (!deviceId.empty() && key.second != device)
3271 if (!devices->add(key.second))
3274 auto& conn = value.back();
3275 auto& channel = conn.channel;
3278 auto ctx = std::make_unique<TextMessageCtx>();
3281 ctx->deviceId = key.second;
3282 ctx->devices = devices;
3283 ctx->sipChannel = channel;
3286 auto res = sendSIPMessage(conn,
3291 [](
void* token, pjsip_event* event) {
3292 if (auto c = std::shared_ptr<TextMessageCtx>{(TextMessageCtx*) token})
3295 code = event->body.tsx_state.tsx->status_code
3297 bool success = code == PJSIP_SC_OK;
3301 JAMI_WARNING(
"Timeout when send a message, close current connection");
3302 if (auto acc = c->acc.lock())
3303 acc->shutdownSIPConnection(c->sipChannel,
3307 c->devices->complete(c->deviceId, code == PJSIP_SC_OK);
3311 devices->complete(key.second,
false);
3314 }
catch (
const std::runtime_error& ex) {
3317 shutdownSIPConnection(channel, to, key.second);
3318 devices->complete(key.second,
false);
3322 if (key.second == device) {
3336 auto extractIdFromJson = [](
const std::string& jsonData) -> std::string {
3338 if (json::parse(jsonData, parsed)) {
3339 auto value = parsed.get(
"id", Json::nullValue);
3340 if (value && value.isString()) {
3341 return value.asString();
3344 JAMI_WARNING(
"Unable to parse jsonData to get conversation ID");
3350 auto payload_type = msg->t;
3351 if (payload_type == MIME_TYPE_GIT) {
3352 std::string
id = extractIdFromJson(msg->c);
3354 payload_type +=
"/" + id;
3358 if (deviceId.empty()) {
3359 auto toH = dht::InfoHash(toUri);
3361 accountManager_->forEachDevice(toH,
3366 currentDevice =
DeviceId(currentDeviceId())](
3367 const std::shared_ptr<dht::crypto::PublicKey>&
dev) {
3369 auto deviceId =
dev->getLongId();
3370 if (deviceId == currentDevice
3371 || devices->pending(deviceId)) {
3376 requestMessageConnection(to, deviceId, payload_type);
3379 requestMessageConnection(to, device, payload_type);
3384JamiAccount::onMessageSent(
const std::string& to, uint64_t
id,
const std::string& deviceId,
bool success,
bool onlyConnected,
bool retry)
3387 messageEngine_.onMessageSent(to,
3394 messageEngine_.onPeerOnline(to, deviceId);
3398dhtnet::IceTransportOptions
3399JamiAccount::getIceOptions()
const
3401 return connectionManager_->getIceOptions();
3405JamiAccount::getIceOptions(std::function<
void(dhtnet::IceTransportOptions&&)> cb)
const
3407 return connectionManager_->getIceOptions(std::move(cb));
3411JamiAccount::getPublishedIpAddress(uint16_t family)
const
3413 return connectionManager_->getPublishedIpAddress(family);
3417JamiAccount::setPushNotificationToken(
const std::string& token)
3419 if (SIPAccountBase::setPushNotificationToken(token)) {
3420 JAMI_WARNING(
"[Account {:s}] setPushNotificationToken: {:s}", getAccountID(), token);
3422 dht_->setPushNotificationToken(token);
3429JamiAccount::setPushNotificationTopic(
const std::string& topic)
3431 if (SIPAccountBase::setPushNotificationTopic(topic)) {
3433 dht_->setPushNotificationTopic(topic);
3440JamiAccount::setPushNotificationConfig(
const std::map<std::string, std::string>& data)
3442 if (SIPAccountBase::setPushNotificationConfig(data)) {
3444 dht_->setPushNotificationPlatform(config_->platform);
3445 dht_->setPushNotificationTopic(config_->notificationTopic);
3446 dht_->setPushNotificationToken(config_->deviceKey);
3457JamiAccount::pushNotificationReceived(
const std::string& from,
3458 const std::map<std::string, std::string>& data)
3460 auto ret_future = dht_->pushNotificationReceived(data);
3461 dht::ThreadPool::computation().run([
id = getAccountID(), ret_future = ret_future.share()] {
3462 JAMI_WARNING(
"[Account {:s}] pushNotificationReceived: {}", id, (uint8_t) ret_future.get());
3467JamiAccount::getUserUri()
const
3470 if (not registeredName_.empty())
3476std::vector<libjami::Message>
3477JamiAccount::getLastMessages(
const uint64_t& base_timestamp)
3479 return SIPAccountBase::getLastMessages(base_timestamp);
3483JamiAccount::startAccountPublish()
3486 info_pub.
accountId = dht::InfoHash(accountManager_->getInfo()->accountId);
3492JamiAccount::startAccountDiscovery()
3494 auto id = dht::InfoHash(accountManager_->getInfo()->accountId);
3497 std::lock_guard lc(discoveryMapMtx_);
3499 if (v.accountId !=
id) {
3501 auto& dp = discoveredPeers_[v.accountId];
3502 dp.displayName = v.displayName;
3503 discoveredPeerMap_[v.accountId.toString()] = v.displayName;
3504 if (dp.cleanupTask) {
3505 dp.cleanupTask->cancel();
3508 JAMI_LOG(
"Account discovered: {}: {}", v.displayName, v.accountId.to_c_str());
3510 emitSignal<libjami::PresenceSignal::NearbyPeerNotification>(getAccountID(),
3516 dp.cleanupTask = Manager::instance().scheduler().scheduleIn(
3517 [w = weak(), p = v.accountId, a = v.displayName] {
3518 if (auto this_ = w.lock()) {
3520 std::lock_guard lc(this_->discoveryMapMtx_);
3521 this_->discoveredPeers_.erase(p);
3522 this_->discoveredPeerMap_.erase(p.toString());
3525 emitSignal<libjami::PresenceSignal::NearbyPeerNotification>(
3526 this_->getAccountID(), p.toString(), 1, a);
3528 JAMI_INFO(
"Account removed from discovery list: %s", a.c_str());
3535std::map<std::string, std::string>
3536JamiAccount::getNearbyPeers()
const
3538 return discoveredPeerMap_;
3542JamiAccount::sendProfileToPeers()
3544 if (!connectionManager_)
3546 std::set<std::string> peers;
3547 const auto& accountUri = accountManager_->getInfo()->
accountId;
3549 for (
const auto& connection : connectionManager_->getConnectionList()) {
3550 const auto& device = connection.at(
"device");
3551 const auto& peer = connection.at(
"peer");
3552 if (!peers.emplace(peer).second)
3554 if (peer == accountUri) {
3555 sendProfile(
"", accountUri, device);
3558 const auto& conversationId = convModule()->getOneToOneConversation(peer);
3559 if (!conversationId.empty()) {
3560 sendProfile(conversationId, peer, device);
3566JamiAccount::updateProfile(
const std::string& displayName,
3567 const std::string& avatar,
3568 const std::string& fileType,
3573 const auto& accountUri = accountManager_->getInfo()->accountId;
3574 const auto& path = profilePath();
3575 const auto& profiles = idPath_ /
"profiles";
3578 if (!std::filesystem::exists(profiles)) {
3579 std::filesystem::create_directories(profiles);
3581 }
catch (
const std::exception& e) {
3582 JAMI_ERROR(
"Failed to create profiles directory: {}", e.what());
3586 const auto& vCardPath = profiles / fmt::format(
"{}.vcf", base64::encode(accountUri));
3588 auto profile = getProfileVcard();
3589 if (profile.empty()) {
3593 profile[
"FN"] = displayName;
3595 emitSignal<libjami::ConfigurationSignal::AccountDetailsChanged>(getAccountID(),
3596 getAccountDetails());
3598 if (!fileType.empty()) {
3599 const std::string& key =
"PHOTO;ENCODING=BASE64;TYPE=" + fileType;
3602 const auto& avatarPath = std::filesystem::path(avatar);
3603 if (std::filesystem::exists(avatarPath)) {
3605 profile[key] = base64::encode(fileutils::loadFile(avatarPath));
3606 }
catch (
const std::exception& e) {
3607 JAMI_ERROR(
"Failed to load avatar: {}", e.what());
3610 }
else if (flag == 1) {
3612 profile[key] = avatar;
3619 std::filesystem::path tmpPath = vCardPath.string() +
".tmp";
3620 std::ofstream file(tmpPath);
3621 if (file.is_open()) {
3624 std::filesystem::rename(tmpPath, vCardPath);
3625 fileutils::createFileLink(path, vCardPath,
true);
3626 emitSignal<libjami::ConfigurationSignal::ProfileReceived>(getAccountID(),
3629 sendProfileToPeers();
3631 JAMI_ERROR(
"Unable to open file for writing: {}", tmpPath.string());
3633 }
catch (
const std::exception& e) {
3634 JAMI_ERROR(
"Error writing profile: {}", e.what());
3639JamiAccount::setActiveCodecs(
const std::vector<unsigned>& list)
3641 Account::setActiveCodecs(list);
3643 setCodecActive(AV_CODEC_ID_OPUS);
3645 setCodecActive(AV_CODEC_ID_HEVC);
3646 setCodecActive(AV_CODEC_ID_H264);
3647 setCodecActive(AV_CODEC_ID_VP8);
3649 config_->activeCodecs = getActiveCodecs(
MEDIA_ALL);
3653JamiAccount::sendInstantMessage(
const std::string& convId,
3654 const std::map<std::string, std::string>& msg)
3656 auto members = convModule()->getConversationMembers(convId);
3657 if (convId.empty() && members.empty()) {
3659 sendTextMessage(convId,
"", msg);
3662 for (
const auto& m : members) {
3663 const auto& uri = m.at(
"uri");
3664 auto token = std::uniform_int_distribution<uint64_t> {1, JAMI_ID_MAX_VAL}(rand);
3666 sendMessage(uri,
"", msg, token,
false,
true);
3671JamiAccount::handleMessage(
const std::shared_ptr<dht::crypto::Certificate>& cert,
const std::string& from,
const std::pair<std::string, std::string>& m)
3673 if (not cert or not cert->issuer)
3676 if (cert->issuer->getId().to_view() != from) {
3677 JAMI_WARNING(
"[Account {}] [device {}] handleMessage: invalid author {}", getAccountID(), cert->issuer->getId().to_view(), from);
3680 if (m.first == MIME_TYPE_GIT) {
3682 if (!json::parse(m.second, json)) {
3687 dht::ThreadPool::io().run([
3690 deviceId = json[
"deviceId"].asString(),
3691 id = json[
"id"].asString(),
3692 commit = json[
"commit"].asString()
3694 if (
auto shared = w.lock()) {
3695 if (auto cm = shared->convModule())
3696 cm->fetchNewCommits(from, deviceId, id, commit);
3700 }
else if (m.first == MIME_TYPE_INVITE) {
3701 convModule()->onNeedConversationRequest(from, m.second);
3705 if (!json::parse(m.second, json)) {
3708 convModule()->onConversationRequest(from, json);
3710 }
else if (m.first == MIME_TYPE_IM_COMPOSING) {
3712 static const std::regex COMPOSING_REGEX(
"<state>\\s*(\\w+)\\s*<\\/state>");
3713 std::smatch matched_pattern;
3715 bool isComposing {
false};
3716 if (matched_pattern.ready() && !matched_pattern.empty() && matched_pattern[1].matched) {
3717 isComposing = matched_pattern[1] ==
"active";
3719 static const std::regex CONVID_REGEX(
"<conversation>\\s*(\\w+)\\s*<\\/conversation>");
3721 std::string conversationId =
"";
3722 if (matched_pattern.ready() && !matched_pattern.empty() && matched_pattern[1].matched) {
3723 conversationId = matched_pattern[1];
3725 if (!conversationId.empty()) {
3726 if (
auto cm = convModule(
true)) {
3727 if (
auto typer = cm->getTypers(conversationId)) {
3729 typer->addTyper(from);
3731 typer->removeTyper(from);
3736 }
catch (
const std::exception& e) {
3737 JAMI_WARNING(
"Error parsing composing state: {}", e.what());
3741 static const std::regex IMDN_MSG_ID_REGEX(
"<message-id>\\s*(\\w+)\\s*<\\/message-id>");
3742 std::smatch matched_pattern;
3745 std::string messageId;
3746 if (matched_pattern.ready() && !matched_pattern.empty() && matched_pattern[1].matched) {
3747 messageId = matched_pattern[1];
3749 JAMI_WARNING(
"Message displayed: unable to parse message ID");
3753 static const std::regex STATUS_REGEX(
"<status>\\s*<(\\w+)\\/>\\s*<\\/status>");
3755 bool isDisplayed {
false};
3756 if (matched_pattern.ready() && !matched_pattern.empty() && matched_pattern[1].matched) {
3757 isDisplayed = matched_pattern[1] ==
"displayed";
3759 JAMI_WARNING(
"Message displayed: unable to parse status");
3763 static const std::regex CONVID_REGEX(
"<conversation>\\s*(\\w+)\\s*<\\/conversation>");
3765 std::string conversationId =
"";
3766 if (matched_pattern.ready() && !matched_pattern.empty() && matched_pattern[1].matched) {
3767 conversationId = matched_pattern[1];
3770 if (!isReadReceiptEnabled())
3773 if (convModule()->onMessageDisplayed(from, conversationId, messageId)) {
3774 JAMI_DEBUG(
"[message {}] Displayed by peer", messageId);
3775 emitSignal<libjami::ConfigurationSignal::AccountMessageStatusChanged>(
3784 }
catch (
const std::exception& e) {
3785 JAMI_ERROR(
"Error parsing display notification: {}", e.what());
3788 std::smatch matched_pattern;
3789 static const std::regex BASIC_REGEX(
"<basic>([\\w\\s]+)<\\/basic>");
3791 std::string customStatus {};
3792 if (matched_pattern.ready() && !matched_pattern.empty() && matched_pattern[1].matched) {
3793 customStatus = matched_pattern[1];
3794 emitSignal<libjami::PresenceSignal::NewBuddyNotification>(getAccountID(),
3797 PresenceState::CONNECTED),
3809JamiAccount::callConnectionClosed(
const DeviceId& deviceId,
bool eraseDummy)
3811 std::function<void(
const DeviceId&,
bool)> cb;
3813 std::lock_guard lk(onConnectionClosedMtx_);
3814 auto it = onConnectionClosed_.find(deviceId);
3815 if (it != onConnectionClosed_.end()) {
3817 cb = std::move(it->second);
3818 onConnectionClosed_.erase(it);
3826 dht::ThreadPool::io().run(
3827 [w = weak(), cb = std::move(cb),
id = deviceId, erase = std::move(eraseDummy)] {
3828 if (
auto acc = w.lock()) {
3836JamiAccount::requestMessageConnection(
const std::string& peerId,
3837 const DeviceId& deviceId,
3838 const std::string& connectionType)
3840 std::shared_lock lk(connManagerMtx_);
3841 auto* handler =
static_cast<MessageChannelHandler*
>(
3842 channelHandlers_[Uri::Scheme::MESSAGE].get());
3846 if (
auto connected = handler->getChannel(peerId, deviceId)) {
3850 auto connected = handler->getChannels(peerId);
3851 if (!connected.empty()) {
3858 [w = weak(), peerId](std::shared_ptr<dhtnet::ChannelSocket> socket,
3861 dht::ThreadPool::io().run([w, peerId, deviceId] {
3862 if (
auto acc = w.lock()) {
3863 acc->messageEngine_.onPeerOnline(peerId);
3864 acc->messageEngine_.onPeerOnline(peerId, deviceId.toString(), true);
3865 if (!acc->presenceNote_.empty()) {
3867 auto token = std::uniform_int_distribution<uint64_t> {1, JAMI_ID_MAX_VAL}(acc->rand);
3868 std::map<std::string, std::string> msg = {
3869 {MIME_TYPE_PIDF, getPIDF(acc->presenceNote_)}
3871 acc->sendMessage(peerId, deviceId.toString(), msg, token, false, true);
3873 acc->convModule()->syncConversations(peerId, deviceId.toString());
3881JamiAccount::requestSIPConnection(
const std::string& peerId,
3882 const DeviceId& deviceId,
3883 const std::string& connectionType,
3884 bool forceNewConnection,
3885 const std::shared_ptr<SIPCall>& pc)
3887 if (peerId == getUsername()) {
3888 if (!syncModule()->isConnected(deviceId))
3889 channelHandlers_[Uri::Scheme::SYNC]
3892 [](std::shared_ptr<dhtnet::ChannelSocket> socket,
3893 const DeviceId& deviceId) {});
3896 JAMI_LOG(
"[Account {}] Request SIP connection to peer {} on device {}",
3902 std::lock_guard lk(sipConnsMtx_);
3903 auto id = std::make_pair(peerId, deviceId);
3905 if (sipConns_.find(
id) != sipConns_.end()) {
3906 JAMI_LOG(
"[Account {}] A SIP connection with {} already exists", getAccountID(), deviceId);
3910 std::shared_lock lkCM(connManagerMtx_);
3911 if (!connectionManager_)
3916 if (!forceNewConnection && connectionManager_->isConnecting(deviceId,
"sip")) {
3917 JAMI_LOG(
"[Account {}] Already connecting to {}", getAccountID(), deviceId);
3920 JAMI_LOG(
"[Account {}] Ask {} for a new SIP channel", getAccountID(), deviceId);
3921 connectionManager_->connectDevice(
3926 pc = std::move(pc)](std::shared_ptr<dhtnet::ChannelSocket> socket,
const DeviceId&) {
3929 auto shared = w.lock();
3935 shared->callConnectionClosed(
id.second,
true);
3945JamiAccount::isConnectedWith(
const DeviceId& deviceId)
const
3947 std::shared_lock lkCM(connManagerMtx_);
3948 if (connectionManager_)
3949 return connectionManager_->isConnected(deviceId);
3954JamiAccount::sendPresenceNote(
const std::string& note)
3956 if (
auto info = accountManager_->getInfo()) {
3957 if (!info || !info->contacts)
3959 presenceNote_ = note;
3960 auto contacts = info->contacts->getContacts();
3961 std::vector<std::pair<std::string, DeviceId>> keys;
3963 std::shared_lock lkCM(connManagerMtx_);
3965 channelHandlers_[Uri::Scheme::MESSAGE].get());
3968 for (
const auto& contact : contacts) {
3969 auto peerId = contact.first.toString();
3971 for (
const auto& channel : channels) {
3972 keys.emplace_back(peerId, channel->deviceId());
3976 auto token = std::uniform_int_distribution<uint64_t> {1, JAMI_ID_MAX_VAL}(rand);
3978 for (
auto& key : keys) {
3979 sendMessage(key.first, key.second.toString(), msg, token,
false,
true);
3985JamiAccount::sendProfile(
const std::string& convId,
3986 const std::string& peerUri,
3987 const std::string& deviceId)
3989 auto accProfilePath = profilePath();
3990 if (not std::filesystem::is_regular_file(accProfilePath))
3992 auto currentSha3 = fileutils::sha3File(accProfilePath);
3994 if (not needToSendProfile(peerUri, deviceId, currentSha3)) {
3995 JAMI_DEBUG(
"[Account {}] [device {}] Peer {} already got an up-to-date vCard", getAccountID(), deviceId, peerUri);
3999 transferFile(convId,
4000 accProfilePath.string(),
4007 fileutils::lastWriteTimeInSeconds(accProfilePath),
4008 [accId = getAccountID(), peerUri, deviceId]() {
4010 auto sendDir = fileutils::get_cache_dir() / accId /
"vcard" / peerUri;
4011 auto path = sendDir / deviceId;
4012 dhtnet::fileutils::recursive_mkdir(sendDir);
4013 std::lock_guard lock(dhtnet::fileutils::getFileLock(path));
4014 if (std::filesystem::is_regular_file(path))
4016 std::ofstream p(path);
4021JamiAccount::needToSendProfile(
const std::string& peerUri,
4022 const std::string& deviceId,
4023 const std::string& sha3Sum)
4025 std::string previousSha3 {};
4026 auto vCardPath = cachePath_ /
"vcard";
4027 auto sha3Path = vCardPath /
"sha3";
4028 dhtnet::fileutils::check_dir(vCardPath, 0700);
4030 previousSha3 = fileutils::loadTextFile(sha3Path);
4032 fileutils::saveFile(sha3Path, (
const uint8_t*) sha3Sum.data(), sha3Sum.size(), 0600);
4035 if (sha3Sum != previousSha3) {
4037 dhtnet::fileutils::removeAll(vCardPath,
true);
4038 dhtnet::fileutils::check_dir(vCardPath, 0700);
4039 fileutils::saveFile(sha3Path, (
const uint8_t*) sha3Sum.data(), sha3Sum.size(), 0600);
4042 auto peerPath = vCardPath / peerUri;
4043 dhtnet::fileutils::recursive_mkdir(peerPath);
4044 return not std::filesystem::is_regular_file(peerPath / deviceId);
4048JamiAccount::sendSIPMessage(SipConnection& conn,
4049 const std::string& to,
4052 const std::map<std::string, std::string>& data,
4053 pjsip_endpt_send_callback cb)
4055 auto transport = conn.transport;
4056 auto channel = conn.channel;
4058 throw std::runtime_error(
4059 "A SIP transport exists without Channel, this is a bug. Please report");
4060 auto remote_address = channel->getRemoteAddress();
4061 if (!remote_address)
4066 auto toURI = getToUri(fmt::format(
"{}@{}", to, remote_address.toString(
true)));
4067 std::string from = getFromUri();
4070 constexpr pjsip_method msg_method = {PJSIP_OTHER_METHOD,
4071 sip_utils::CONST_PJ_STR(sip_utils::SIP_METHODS::MESSAGE)};
4072 pj_str_t pjFrom = sip_utils::CONST_PJ_STR(from);
4073 pj_str_t pjTo = sip_utils::CONST_PJ_STR(toURI);
4076 pjsip_tx_data* tdata =
nullptr;
4077 pj_status_t status = pjsip_endpt_create_request(link_.getEndpoint(),
4087 if (status != PJ_SUCCESS) {
4088 JAMI_ERROR(
"Unable to create request: {}", sip_utils::sip_strerror(status));
4094 constexpr auto key = sip_utils::CONST_PJ_STR(
"Date");
4096 auto time = std::time(
nullptr);
4097 auto date = std::ctime(&time);
4099 *std::remove(date, date + strlen(date),
'\n') =
'\0';
4102 hdr =
reinterpret_cast<pjsip_hdr*
>(
4103 pjsip_date_hdr_create(tdata->pool, &key, pj_cstr(&date_str, date)));
4104 pjsip_msg_add_hdr(tdata->msg, hdr);
4108 auto pjMessageId = sip_utils::CONST_PJ_STR(token_str);
4109 hdr =
reinterpret_cast<pjsip_hdr*
>(
4110 pjsip_generic_string_hdr_create(tdata->pool, &STR_MESSAGE_ID, &pjMessageId));
4111 pjsip_msg_add_hdr(tdata->msg, hdr);
4114 sip_utils::addUserAgentHeader(getUserAgentName(), tdata);
4117 const pjsip_tpselector tp_sel = SIPVoIPLink::getTransportSelector(transport->get());
4118 status = pjsip_tx_data_set_transport(tdata, &tp_sel);
4119 if (status != PJ_SUCCESS) {
4120 JAMI_ERROR(
"Unable to create request: {}", sip_utils::sip_strerror(status));
4123 im::fillPJSIPMessageBody(*tdata, data);
4126 dht::ThreadPool::io().run([w = weak(), tdata, ctx, cb = std::move(cb)] {
4127 auto shared = w.lock();
4130 auto status = pjsip_endpt_send_request(shared->link_.getEndpoint(), tdata, -1, ctx, cb);
4131 if (status != PJ_SUCCESS)
4132 JAMI_ERROR(
"Unable to send request: {}", sip_utils::sip_strerror(status));
4138JamiAccount::clearProfileCache(
const std::string& peerUri)
4141 std::filesystem::remove_all(cachePath_ /
"vcard" / peerUri, ec);
4144std::filesystem::path
4145JamiAccount::profilePath()
const
4147 return idPath_ /
"profile.vcf";
4151JamiAccount::cacheSIPConnection(std::shared_ptr<dhtnet::ChannelSocket>&& socket,
4152 const std::string& peerId,
4155 std::unique_lock lk(sipConnsMtx_);
4158 auto& connections = sipConns_[key];
4159 auto conn = std::find_if(connections.begin(), connections.end(), [&](
const auto& v) {
4160 return v.channel == socket;
4162 if (conn != connections.end()) {
4163 JAMI_WARNING(
"[Account {}] Channel socket already cached with this peer", getAccountID());
4168 auto onShutdown = [w = weak(), peerId, key, socket]() {
4169 dht::ThreadPool::io().run([w = std::move(w), peerId, key, socket] {
4170 auto shared = w.lock();
4173 shared->shutdownSIPConnection(socket, key.first, key.second);
4177 shared->callConnectionClosed(key.second,
false);
4180 auto sip_tr = link_.sipTransportBroker->getChanneledTransport(shared(),
4182 std::move(onShutdown));
4188 connections.emplace_back(SipConnection {sip_tr, socket});
4189 JAMI_WARNING(
"[Account {:s}] [device {}] New SIP channel opened", getAccountID(), deviceId);
4193 messageEngine_.onPeerOnline(peerId);
4194 messageEngine_.onPeerOnline(peerId, deviceId.toString(),
true);
4197 forEachPendingCall(deviceId, [&](
const auto& pc) {
4198 if (pc->getConnectionState() != Call::ConnectionState::TRYING
4199 and pc->getConnectionState() != Call::ConnectionState::PROGRESSING)
4201 pc->setSipTransport(sip_tr, getContactHeader(sip_tr));
4202 pc->setState(Call::ConnectionState::PROGRESSING);
4203 if (
auto remote_address = socket->getRemoteAddress()) {
4205 onConnectedOutgoingCall(pc, peerId, remote_address);
4206 }
catch (
const VoipLinkException&) {
4218JamiAccount::shutdownSIPConnection(
const std::shared_ptr<dhtnet::ChannelSocket>& channel,
4219 const std::string& peerId,
4220 const DeviceId& deviceId)
4222 std::unique_lock lk(sipConnsMtx_);
4224 auto it = sipConns_.find(key);
4225 if (it != sipConns_.end()) {
4226 auto& conns = it->second;
4227 conns.erase(std::remove_if(conns.begin(),
4229 [&](
auto v) { return v.channel == channel; }),
4231 if (conns.empty()) {
4232 sipConns_.erase(it);
4238 channel->shutdown();
4242JamiAccount::currentDeviceId()
const
4244 if (!accountManager_ or not accountManager_->getInfo())
4246 return accountManager_->getInfo()->deviceId;
4249std::shared_ptr<TransferManager>
4250JamiAccount::dataTransfer(
const std::string&
id)
4253 return nonSwarmTransferManager_;
4254 return convModule()->dataTransfer(
id);
4258JamiAccount::monitor()
4260 JAMI_DEBUG(
"[Account {:s}] Monitor connections", getAccountID());
4261 JAMI_DEBUG(
"[Account {:s}] Using proxy: {:s}", getAccountID(), proxyServerCached_);
4263 if (
auto cm = convModule())
4265 std::shared_lock lkCM(connManagerMtx_);
4266 if (connectionManager_)
4267 connectionManager_->monitor();
4270std::vector<std::map<std::string, std::string>>
4271JamiAccount::getConnectionList(
const std::string& conversationId)
4273 std::shared_lock lkCM(connManagerMtx_);
4274 if (connectionManager_ && conversationId.empty()) {
4275 return connectionManager_->getConnectionList();
4276 }
else if (connectionManager_ && convModule_) {
4277 std::vector<std::map<std::string, std::string>> connectionList;
4278 if (
auto conv = convModule_->getConversation(conversationId)) {
4279 for (
const auto& deviceId : conv->getDeviceIdList()) {
4280 auto connections = connectionManager_->getConnectionList(deviceId);
4281 connectionList.reserve(connectionList.size() + connections.size());
4282 std::move(connections.begin(),
4284 std::back_inserter(connectionList));
4287 return connectionList;
4293std::vector<std::map<std::string, std::string>>
4294JamiAccount::getChannelList(
const std::string& connectionId)
4296 std::shared_lock lkCM(connManagerMtx_);
4297 if (!connectionManager_)
4299 return connectionManager_->getChannelList(connectionId);
4303JamiAccount::sendFile(
const std::string& conversationId,
4304 const std::filesystem::path& path,
4305 const std::string& name,
4306 const std::string& replyTo)
4308 if (!std::filesystem::is_regular_file(path)) {
4315 dht::ThreadPool::computation().run([w = weak(), conversationId, path, name, replyTo]() {
4316 if (
auto shared = w.lock()) {
4318 auto tid = jami::generateUID(shared->rand);
4319 value[
"tid"] = std::to_string(tid);
4320 value[
"displayName"] = name.empty() ? path.filename().string() : name;
4321 value[
"totalSize"] = std::to_string(fileutils::size(path));
4322 value[
"sha3sum"] = fileutils::sha3File(path);
4323 value[
"type"] =
"application/data-transfer+json";
4325 shared->convModule()->sendMessage(
4330 [accId = shared->getAccountID(), conversationId, tid, path](
4331 const std::string& commitId) {
4333 auto filelinkPath = fileutils::get_data_dir() / accId /
"conversation_data"
4334 / conversationId / (commitId +
"_" + std::to_string(tid));
4335 filelinkPath += path.extension();
4336 if (path != filelinkPath && !std::filesystem::is_symlink(filelinkPath)) {
4337 if (!fileutils::createFileLink(filelinkPath, path, true)) {
4339 "Unable to create symlink for file transfer {} - {}. Copy file",
4343 auto success = std::filesystem::copy_file(path, filelinkPath, ec);
4344 if (ec || !success) {
4345 JAMI_ERROR(
"Unable to copy file for file transfer {} - {}",
4351 emitSignal<libjami::DataTransferSignal::DataTransferEvent>(
4356 uint32_t(libjami::DataTransferEventCode::invalid));
4361 emitSignal<libjami::DataTransferSignal::DataTransferEvent>(
4366 uint32_t(libjami::DataTransferEventCode::created));
4369 emitSignal<libjami::DataTransferSignal::DataTransferEvent>(
4374 uint32_t(libjami::DataTransferEventCode::created));
4383JamiAccount::transferFile(
const std::string& conversationId,
4384 const std::string& path,
4385 const std::string& deviceId,
4386 const std::string& fileId,
4387 const std::string& interactionId,
4390 const std::string& sha3Sum,
4391 uint64_t lastWriteTime,
4392 std::function<
void()> onFinished)
4394 std::string modified;
4395 if (lastWriteTime != 0) {
4396 modified = fmt::format(
"&modified={}", lastWriteTime);
4398 auto fid = fileId ==
"profile.vcf" ? fmt::format(
"profile.vcf?sha3={}{}", sha3Sum, modified)
4400 auto channelName = conversationId.empty() ? fmt::format(
"{}profile.vcf?sha3={}{}",
4401 DATA_TRANSFER_SCHEME,
4404 : fmt::format(
"{}{}/{}/{}",
4405 DATA_TRANSFER_SCHEME,
4409 std::shared_lock lkCM(connManagerMtx_);
4410 if (!connectionManager_)
4413 ->connectDevice(
DeviceId(deviceId),
4417 path = std::move(path),
4422 onFinished = std::move(
4423 onFinished)](std::shared_ptr<dhtnet::ChannelSocket> socket,
4427 dht::ThreadPool::io().run([w = weak(),
4428 path = std::move(path),
4429 socket = std::move(socket),
4430 conversationId = std::move(conversationId),
4435 onFinished = std::move(onFinished)] {
4436 if (
auto shared = w.lock())
4437 if (
auto dt = shared->dataTransfer(conversationId))
4438 dt->transferFile(socket,
4444 std::move(onFinished));
4450JamiAccount::askForFileChannel(
const std::string& conversationId,
4451 const std::string& deviceId,
4452 const std::string& interactionId,
4453 const std::string& fileId,
4457 auto tryDevice = [=](
const auto& did) {
4458 std::shared_lock lkCM(connManagerMtx_);
4459 if (!connectionManager_)
4462 auto channelName = fmt::format(
"{}{}/{}/{}",
4463 DATA_TRANSFER_SCHEME,
4467 if (start != 0 || end != 0) {
4468 channelName += fmt::format(
"?start={}&end={}", start, end);
4472 connectionManager_->connectDevice(
4479 start](std::shared_ptr<dhtnet::ChannelSocket> channel,
const DeviceId&) {
4482 dht::ThreadPool::io().run([w, conversationId, channel, fileId, interactionId, start] {
4483 auto shared = w.lock();
4486 auto dt = shared->dataTransfer(conversationId);
4489 if (interactionId.empty())
4490 dt->onIncomingProfile(channel);
4492 dt->onIncomingFileTransfer(fileId, channel, start);
4498 if (!deviceId.empty()) {
4504 for (
const auto& m : convModule()->getConversationMembers(conversationId)) {
4505 accountManager_->forEachDevice(dht::InfoHash(m.at(
"uri")), [
4507 ](
const std::shared_ptr<dht::crypto::PublicKey>&
dev) {
4508 tryDevice(
dev->getLongId());
4515JamiAccount::askForProfile(
const std::string& conversationId,
4516 const std::string& deviceId,
4517 const std::string& memberUri)
4519 std::shared_lock lkCM(connManagerMtx_);
4520 if (!connectionManager_)
4523 auto channelName = fmt::format(
"{}{}/profile/{}.vcf",
4524 DATA_TRANSFER_SCHEME,
4529 connectionManager_->connectDevice(
4532 [
this, conversationId](std::shared_ptr<dhtnet::ChannelSocket> channel,
const DeviceId&) {
4535 dht::ThreadPool::io().run([w = weak(), conversationId, channel] {
4536 if (
auto shared = w.lock())
4537 if (
auto dt = shared->dataTransfer(conversationId))
4538 dt->onIncomingProfile(channel);
4545JamiAccount::onPeerConnected(
const std::string& peerId,
bool connected)
4547 std::unique_lock lock(buddyInfoMtx);
4548 auto& state = presenceState_[peerId];
4549 auto it = trackedBuddies_.find(dht::InfoHash(peerId));
4550 auto isOnline = it != trackedBuddies_.end() && it->second.devices_cnt > 0;
4551 auto newState = connected ? PresenceState::CONNECTED : (isOnline ? PresenceState::AVAILABLE : PresenceState::DISCONNECTED);
4552 if (state != newState) {
4555 emitSignal<libjami::PresenceSignal::NewBuddyNotification>(getAccountID(),
4557 static_cast<int>(newState),
4563JamiAccount::initConnectionManager()
4565 if (!nonSwarmTransferManager_)
4566 nonSwarmTransferManager_
4567 = std::make_shared<TransferManager>(accountID_,
4570 dht::crypto::getDerivedRandomEngine(rand));
4571 if (!connectionManager_) {
4572 auto connectionManagerConfig = std::make_shared<dhtnet::ConnectionManager::Config>();
4573 connectionManagerConfig->ioContext = Manager::instance().ioContext();
4574 connectionManagerConfig->dht =
dht();
4575 connectionManagerConfig->certStore = certStore_;
4576 connectionManagerConfig->id = identity();
4577 connectionManagerConfig->upnpCtrl = upnpCtrl_;
4578 connectionManagerConfig->turnServer = config().turnServer;
4579 connectionManagerConfig->upnpEnabled = config().upnpEnabled;
4580 connectionManagerConfig->turnServerUserName = config().turnServerUserName;
4581 connectionManagerConfig->turnServerPwd = config().turnServerPwd;
4582 connectionManagerConfig->turnServerRealm = config().turnServerRealm;
4583 connectionManagerConfig->turnEnabled = config().turnEnabled;
4584 connectionManagerConfig->cachePath = cachePath_;
4585 connectionManagerConfig->logger = Logger::dhtLogger();
4586 connectionManagerConfig->factory = Manager::instance().getIceTransportFactory();
4587 connectionManagerConfig->turnCache = turnCache_;
4588 connectionManagerConfig->rng = std::make_unique<std::mt19937_64>(
4589 dht::crypto::getDerivedRandomEngine(rand));
4590 connectionManager_ = std::make_unique<dhtnet::ConnectionManager>(connectionManagerConfig);
4591 channelHandlers_[Uri::Scheme::SWARM]
4592 = std::make_unique<SwarmChannelHandler>(shared(), *connectionManager_.get());
4593 channelHandlers_[Uri::Scheme::GIT]
4594 = std::make_unique<ConversationChannelHandler>(shared(), *connectionManager_.get());
4595 channelHandlers_[Uri::Scheme::SYNC]
4596 = std::make_unique<SyncChannelHandler>(shared(), *connectionManager_.get());
4597 channelHandlers_[Uri::Scheme::DATA_TRANSFER]
4598 = std::make_unique<TransferChannelHandler>(shared(), *connectionManager_.get());
4599 channelHandlers_[Uri::Scheme::MESSAGE]
4600 = std::make_unique<MessageChannelHandler>(*connectionManager_.get(),
4601 [
this](
const auto& cert, std::string& type,
const std::string& content) {
4602 onTextMessage(
"", cert->issuer->getId().toString(), cert, {{type, content}});
4604 [w = weak()](
const std::string& peer,
bool connected) {
4605 asio::post(*Manager::instance().ioContext(), [w, peer, connected] {
4606 if (
auto acc = w.lock())
4607 acc->onPeerConnected(peer, connected);
4610 channelHandlers_[Uri::Scheme::AUTH]
4611 = std::make_unique<AuthChannelHandler>(shared(), *connectionManager_.get());
4614 connectionManager_->oniOSConnected([&](
const std::string& connType, dht::InfoHash peer_h) {
4615 if ((connType ==
"videoCall" || connType ==
"audioCall")
4617 bool hasVideo = connType ==
"videoCall";
4618 emitSignal<libjami::ConversationSignal::CallConnectionRequest>(
"",
4630JamiAccount::updateUpnpController()
4632 Account::updateUpnpController();
4633 if (connectionManager_) {
4634 auto config = connectionManager_->getConfig();
4636 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[]
std::string getDisplayed(const std::string &conversationId, const std::string &messageId)
std::string_view parseJamiUri(std::string_view toUrl)
constexpr const char * dhtStatusStr(dht::NodeStatus status)
static constexpr const char DEVICE_ID_PATH[]
std::string getPIDF(const std::string ¬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