Line data Source code
1 : /*
2 : * Copyright (C) 2004-2025 Savoir-faire Linux Inc.
3 : *
4 : * This program is free software: you can redistribute it and/or modify
5 : * it under the terms of the GNU General Public License as published by
6 : * the Free Software Foundation, either version 3 of the License, or
7 : * (at your option) any later version.
8 : *
9 : * This program is distributed in the hope that it will be useful,
10 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 : * GNU General Public License for more details.
13 : *
14 : * You should have received a copy of the GNU General Public License
15 : * along with this program. If not, see <https://www.gnu.org/licenses/>.
16 : */
17 :
18 : #include "media/media_codec.h"
19 : #ifdef HAVE_CONFIG_H
20 : #include "config.h"
21 : #endif
22 : #include "account.h"
23 :
24 : #include <algorithm>
25 : #include <iterator>
26 : #include <mutex>
27 : #include <fstream>
28 :
29 : #ifdef ENABLE_VIDEO
30 : #include "libav_utils.h"
31 : #endif
32 :
33 : #include "logger.h"
34 : #include "manager.h"
35 :
36 : #include <opendht/rng.h>
37 :
38 : #include "client/ring_signal.h"
39 : #include "account_schema.h"
40 : #include "jami/account_const.h"
41 : #include "string_utils.h"
42 : #include "fileutils.h"
43 : #include "config/yamlparser.h"
44 : #include "system_codec_container.h"
45 : #include "vcard.h"
46 :
47 : #pragma GCC diagnostic push
48 : #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
49 : #include <yaml-cpp/yaml.h>
50 : #pragma GCC diagnostic pop
51 :
52 : #include "compiler_intrinsics.h"
53 : #include "jami/account_const.h"
54 :
55 : #include <dhtnet/upnp/upnp_control.h>
56 : #include <dhtnet/ip_utils.h>
57 :
58 : #include <fmt/ranges.h>
59 :
60 : using namespace std::literals;
61 :
62 : namespace jami {
63 :
64 : // For portability, do not specify the absolute filename of the ringtone.
65 : // Instead, specify its base name to be looked in
66 : // JAMI_DATADIR/ringtones/, where JAMI_DATADIR is a preprocessor macro denoting
67 : // the data directory prefix that must be set at build time.
68 : const std::string Account::DEFAULT_USER_AGENT = Account::getDefaultUserAgent();
69 :
70 801 : Account::Account(const std::string& accountId)
71 801 : : rand(Manager::instance().getSeededRandomEngine())
72 801 : , accountID_(accountId)
73 801 : , systemCodecContainer_(getSystemCodecContainer())
74 2403 : , idPath_(fileutils::get_data_dir() / accountId)
75 : {
76 : // Initialize the codec order, used when creating a new account
77 801 : loadDefaultCodecs();
78 801 : }
79 :
80 801 : Account::~Account() {}
81 :
82 : void
83 772 : Account::hangupCalls()
84 : {
85 794 : for (const auto& callId : callSet_.getCallIds())
86 794 : Manager::instance().hangupCall(getAccountID(), callId);
87 772 : }
88 :
89 : void
90 1028 : Account::updateUpnpController()
91 : {
92 1028 : std::lock_guard lk {upnp_mtx};
93 :
94 1028 : if (not config().upnpEnabled or not isUsable()) {
95 218 : upnpCtrl_.reset();
96 218 : return;
97 : }
98 :
99 : // UPNP enabled. Create new controller if needed.
100 810 : if (not upnpCtrl_) {
101 792 : upnpCtrl_ = std::make_shared<dhtnet::upnp::Controller>(Manager::instance().upnpContext());
102 792 : if (not upnpCtrl_) {
103 0 : throw std::runtime_error("Failed to create a UPNP Controller instance!");
104 : }
105 : }
106 1028 : }
107 :
108 : void
109 5115 : Account::setRegistrationState(RegistrationState state, int detail_code, const std::string& detail_str)
110 : {
111 5115 : if (state != registrationState_) {
112 3667 : registrationState_ = state;
113 : // Notify the client
114 7334 : runOnMainThread([accountId = accountID_,
115 : state = mapStateNumberToString(registrationState_),
116 : detail_code,
117 : detail_str,
118 3667 : details = getVolatileAccountDetails()] {
119 3663 : emitSignal<libjami::ConfigurationSignal::RegistrationStateChanged>(accountId,
120 3663 : state,
121 : detail_code,
122 3663 : detail_str);
123 :
124 3663 : emitSignal<libjami::ConfigurationSignal::VolatileDetailsChanged>(accountId, details);
125 3663 : });
126 : }
127 5115 : }
128 :
129 : void
130 801 : Account::loadDefaultCodecs()
131 : {
132 : // default codec are system codecs
133 801 : const auto& systemCodecList = systemCodecContainer_->getSystemCodecInfoList();
134 801 : accountCodecInfoList_.clear();
135 801 : accountCodecInfoList_.reserve(systemCodecList.size());
136 10413 : for (const auto& systemCodec : systemCodecList) {
137 : // As defined in SDP RFC, only select a codec if it can encode and decode
138 9612 : if ((systemCodec->codecType & CODEC_ENCODER_DECODER) != CODEC_ENCODER_DECODER)
139 0 : continue;
140 :
141 9612 : if (systemCodec->mediaType & MEDIA_AUDIO) {
142 6408 : accountCodecInfoList_.emplace_back(
143 12816 : std::make_shared<SystemAudioCodecInfo>(*std::static_pointer_cast<SystemAudioCodecInfo>(systemCodec)));
144 : }
145 :
146 9612 : if (systemCodec->mediaType & MEDIA_VIDEO) {
147 3204 : accountCodecInfoList_.emplace_back(
148 6408 : std::make_shared<SystemVideoCodecInfo>(*std::static_pointer_cast<SystemVideoCodecInfo>(systemCodec)));
149 : }
150 : }
151 801 : }
152 :
153 : void
154 816 : Account::loadConfig()
155 : {
156 816 : setActiveCodecs(config_->activeCodecs);
157 :
158 : // Try to get the client-defined resource base directory, if any. If not set, use the default
159 : // JAMI_DATADIR that was set at build time.
160 816 : auto ringtoneDir = fileutils::get_resource_dir_path() / RINGDIR;
161 816 : ringtonePath_ = fileutils::getFullPath(ringtoneDir, config_->ringtonePath);
162 : // If the user defined a custom ringtone, the file may not exists or may not allow access
163 : // In this case, fallback on the default ringtone path
164 816 : std::ifstream f(ringtonePath_);
165 816 : if (not std::filesystem::is_regular_file(ringtonePath_) || !f.is_open()) {
166 3264 : JAMI_WARNING("Ringtone {} is not a valid file", ringtonePath_);
167 816 : config_->ringtonePath = DEFAULT_RINGTONE_PATH;
168 816 : ringtonePath_ = fileutils::getFullPath(ringtoneDir, config_->ringtonePath);
169 : }
170 816 : updateUpnpController();
171 816 : }
172 :
173 : void
174 52 : Account::saveConfig() const
175 : {
176 52 : Manager::instance().saveConfig();
177 52 : }
178 :
179 : std::map<std::string, std::string>
180 4609 : Account::getVolatileAccountDetails() const
181 : {
182 9217 : return {{Conf::CONFIG_ACCOUNT_REGISTRATION_STATUS, mapStateNumberToString(registrationState_)},
183 18433 : {libjami::Account::VolatileProperties::ACTIVE, active_ ? TRUE_STR : FALSE_STR}};
184 : }
185 :
186 : vCard::utils::VCardData
187 0 : Account::getProfileVcard() const
188 : {
189 : try {
190 0 : auto path = idPath_ / "profile.vcf";
191 0 : if (!std::filesystem::exists(path))
192 0 : return {};
193 0 : return vCard::utils::toMap(fileutils::loadTextFile(path));
194 0 : } catch (const std::exception& e) {
195 0 : JAMI_ERROR("Error reading profile: {}", e.what());
196 0 : return {};
197 0 : }
198 : }
199 :
200 : bool
201 1733 : Account::hasActiveCodec(MediaType mediaType) const
202 : {
203 21669 : for (auto& codecIt : accountCodecInfoList_)
204 20077 : if ((codecIt->mediaType & mediaType) && codecIt->isActive)
205 141 : return true;
206 1592 : return false;
207 : }
208 :
209 : void
210 816 : Account::setActiveCodecs(const std::vector<unsigned>& list)
211 : {
212 : // first clear the previously stored codecs
213 : // TODO: mutex to protect isActive
214 816 : setAllCodecsActive(MEDIA_ALL, false);
215 :
216 : // list contains the ordered payload of active codecs picked by the user for this account
217 : // we used the codec vector to save the order.
218 816 : uint16_t order = 1;
219 876 : for (const auto& item : list) {
220 60 : if (auto accCodec = searchCodecById(item, MEDIA_ALL)) {
221 60 : accCodec->isActive = true;
222 60 : accCodec->order = order;
223 60 : ++order;
224 60 : }
225 : }
226 816 : sortCodec();
227 816 : }
228 :
229 : void
230 816 : Account::sortCodec()
231 : {
232 816 : std::sort(std::begin(accountCodecInfoList_),
233 816 : std::end(accountCodecInfoList_),
234 18370 : [](const std::shared_ptr<SystemCodecInfo>& a, const std::shared_ptr<SystemCodecInfo>& b) {
235 18370 : return a->order < b->order;
236 : });
237 816 : }
238 :
239 : std::string
240 8276 : Account::mapStateNumberToString(RegistrationState state)
241 : {
242 : #define CASE_STATE(X) \
243 : case RegistrationState::X: \
244 : return #X
245 :
246 8276 : switch (state) {
247 0 : CASE_STATE(UNLOADED);
248 3019 : CASE_STATE(UNREGISTERED);
249 1466 : CASE_STATE(TRYING);
250 2237 : CASE_STATE(REGISTERED);
251 0 : CASE_STATE(ERROR_GENERIC);
252 0 : CASE_STATE(ERROR_AUTH);
253 0 : CASE_STATE(ERROR_NETWORK);
254 0 : CASE_STATE(ERROR_HOST);
255 0 : CASE_STATE(ERROR_SERVICE_UNAVAILABLE);
256 0 : CASE_STATE(ERROR_NEED_MIGRATION);
257 1554 : CASE_STATE(INITIALIZING);
258 0 : default:
259 0 : return libjami::Account::States::ERROR_GENERIC;
260 : }
261 :
262 : #undef CASE_STATE
263 : }
264 :
265 : std::vector<unsigned>
266 0 : Account::getDefaultCodecsId()
267 : {
268 0 : return getSystemCodecContainer()->getSystemCodecInfoIdList(MEDIA_ALL);
269 : }
270 :
271 : std::map<std::string, std::string>
272 0 : Account::getDefaultCodecDetails(const unsigned& codecId)
273 : {
274 0 : auto codec = jami::getSystemCodecContainer()->searchCodecById(codecId, jami::MEDIA_ALL);
275 0 : if (codec) {
276 0 : if (codec->mediaType & jami::MEDIA_AUDIO) {
277 0 : auto audioCodec = std::static_pointer_cast<jami::SystemAudioCodecInfo>(codec);
278 0 : return audioCodec->getCodecSpecifications();
279 0 : }
280 0 : if (codec->mediaType & jami::MEDIA_VIDEO) {
281 0 : auto videoCodec = std::static_pointer_cast<jami::SystemVideoCodecInfo>(codec);
282 0 : return videoCodec->getCodecSpecifications();
283 0 : }
284 : }
285 0 : return {};
286 0 : }
287 :
288 : /**
289 : * Get the UPnP IP (external router) address.
290 : * If use UPnP is set to false, the address will be empty.
291 : */
292 : dhtnet::IpAddr
293 0 : Account::getUPnPIpAddress() const
294 : {
295 0 : std::lock_guard lk(upnp_mtx);
296 0 : if (upnpCtrl_)
297 0 : return upnpCtrl_->getExternalIP();
298 0 : return {};
299 0 : }
300 :
301 : /**
302 : * returns whether or not UPnP is enabled and active_
303 : * ie: if it is able to make port mappings
304 : */
305 : bool
306 531 : Account::getUPnPActive() const
307 : {
308 531 : std::lock_guard lk(upnp_mtx);
309 531 : if (upnpCtrl_)
310 398 : return upnpCtrl_->isReady();
311 133 : return false;
312 531 : }
313 :
314 : /*
315 : * private account codec searching functions
316 : *
317 : * */
318 : std::shared_ptr<SystemCodecInfo>
319 60 : Account::searchCodecById(unsigned codecId, MediaType mediaType)
320 : {
321 60 : if (mediaType != MEDIA_NONE) {
322 185 : for (auto& codecIt : accountCodecInfoList_) {
323 185 : if ((codecIt->id == codecId) && (codecIt->mediaType & mediaType))
324 60 : return codecIt;
325 : }
326 : }
327 0 : return {};
328 : }
329 :
330 : std::shared_ptr<SystemCodecInfo>
331 0 : Account::searchCodecByName(const std::string& name, MediaType mediaType)
332 : {
333 0 : if (mediaType != MEDIA_NONE) {
334 0 : for (auto& codecIt : accountCodecInfoList_) {
335 0 : if (codecIt->name == name && (codecIt->mediaType & mediaType))
336 0 : return codecIt;
337 : }
338 : }
339 0 : return {};
340 : }
341 :
342 : std::shared_ptr<SystemCodecInfo>
343 0 : Account::searchCodecByPayload(unsigned payload, MediaType mediaType)
344 : {
345 0 : if (mediaType != MEDIA_NONE) {
346 0 : for (auto& codecIt : accountCodecInfoList_) {
347 0 : if ((codecIt->payloadType == payload) && (codecIt->mediaType & mediaType))
348 0 : return codecIt;
349 : }
350 : }
351 0 : return {};
352 : }
353 :
354 : std::vector<unsigned>
355 816 : Account::getActiveCodecs(MediaType mediaType) const
356 : {
357 816 : if (mediaType == MEDIA_NONE)
358 0 : return {};
359 :
360 816 : std::vector<unsigned> idList;
361 10608 : for (auto& codecIt : accountCodecInfoList_) {
362 9792 : if ((codecIt->mediaType & mediaType) && (codecIt->isActive))
363 2664 : idList.push_back(codecIt->id);
364 : }
365 816 : return idList;
366 816 : }
367 :
368 : std::vector<unsigned>
369 0 : Account::getAccountCodecInfoIdList(MediaType mediaType) const
370 : {
371 0 : if (mediaType == MEDIA_NONE)
372 0 : return {};
373 :
374 0 : std::vector<unsigned> idList;
375 0 : for (auto& codecIt : accountCodecInfoList_) {
376 0 : if (codecIt->mediaType & mediaType)
377 0 : idList.push_back(codecIt->id);
378 : }
379 :
380 0 : return idList;
381 0 : }
382 :
383 : void
384 864 : Account::setAllCodecsActive(MediaType mediaType, bool active)
385 : {
386 864 : if (mediaType == MEDIA_NONE)
387 0 : return;
388 11232 : for (auto& codecIt : accountCodecInfoList_) {
389 10368 : if (codecIt->mediaType & mediaType)
390 10080 : codecIt->isActive = active;
391 : }
392 : }
393 :
394 : void
395 3088 : Account::setCodecActive(unsigned codecId)
396 : {
397 40144 : for (auto& codecIt : accountCodecInfoList_) {
398 37056 : if (codecIt->avcodecId == codecId)
399 2316 : codecIt->isActive = true;
400 : }
401 3088 : }
402 :
403 : void
404 0 : Account::setCodecInactive(unsigned codecId)
405 : {
406 0 : for (auto& codecIt : accountCodecInfoList_) {
407 0 : if (codecIt->avcodecId == codecId)
408 0 : codecIt->isActive = false;
409 : }
410 0 : }
411 :
412 : std::vector<std::shared_ptr<SystemCodecInfo>>
413 742 : Account::getActiveAccountCodecInfoList(MediaType mediaType) const
414 : {
415 742 : if (mediaType == MEDIA_NONE)
416 0 : return {};
417 :
418 742 : std::vector<std::shared_ptr<SystemCodecInfo>> accountCodecList;
419 9646 : for (auto& codecIt : accountCodecInfoList_) {
420 8904 : if ((codecIt->mediaType & mediaType) && (codecIt->isActive))
421 1293 : accountCodecList.push_back(codecIt);
422 : }
423 :
424 742 : return accountCodecList;
425 742 : }
426 :
427 : const std::string&
428 446 : Account::getUserAgentName()
429 : {
430 446 : return config_->customUserAgent.empty() ? DEFAULT_USER_AGENT : config_->customUserAgent;
431 : }
432 :
433 : std::string
434 40 : Account::getDefaultUserAgent()
435 : {
436 80 : return fmt::format("{:s} {:s} ({:s})", PACKAGE_NAME, libjami::version(), jami::platform());
437 : }
438 :
439 : void
440 0 : Account::addDefaultModerator(const std::string& uri)
441 : {
442 0 : config_->defaultModerators.insert(uri);
443 0 : }
444 :
445 : void
446 0 : Account::removeDefaultModerator(const std::string& uri)
447 : {
448 0 : config_->defaultModerators.erase(uri);
449 0 : }
450 :
451 : bool
452 811 : Account::meetMinimumRequiredVersion(const std::vector<unsigned>& version,
453 : const std::vector<unsigned>& minRequiredVersion)
454 : {
455 828 : for (size_t i = 0; i < minRequiredVersion.size(); i++) {
456 824 : if (i == version.size() or version[i] < minRequiredVersion[i])
457 5 : return false;
458 819 : if (version[i] > minRequiredVersion[i])
459 802 : return true;
460 : }
461 4 : return true;
462 : }
463 :
464 : bool
465 0 : Account::setPushNotificationConfig(const std::map<std::string, std::string>& data)
466 : {
467 0 : std::lock_guard lock(configurationMutex_);
468 0 : auto platform = data.find("platform");
469 0 : auto topic = data.find("topic");
470 0 : auto token = data.find("token");
471 0 : bool changed = false;
472 0 : if (platform != data.end() && config_->platform != platform->second) {
473 0 : config_->platform = platform->second;
474 0 : changed = true;
475 : }
476 0 : if (topic != data.end() && config_->notificationTopic != topic->second) {
477 0 : config_->notificationTopic = topic->second;
478 0 : changed = true;
479 : }
480 0 : if (token != data.end() && config_->deviceKey != token->second) {
481 0 : config_->deviceKey = token->second;
482 0 : changed = true;
483 : }
484 0 : if (changed)
485 0 : saveConfig();
486 0 : return changed;
487 0 : }
488 :
489 : } // namespace jami
|