Ring Daemon
Loading...
Searching...
No Matches
sipaccountbase.cpp
Go to the documentation of this file.
1/*
2 * Copyright (C) 2004-2026 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 "sip/sipaccountbase.h"
19#include "sip/sipvoiplink.h"
20
21#include "account_schema.h"
22#include "manager.h"
23#include "client/jami_signal.h"
24#include "fileutils.h"
27
28#ifdef ENABLE_PLUGIN
30#include "plugin/streamdata.h"
31#endif
32
33#include <dhtnet/ice_transport.h>
34#include <dhtnet/ice_transport_factory.h>
35
36#include <fmt/core.h>
37#include <json/json.h>
38#pragma GCC diagnostic push
39#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
40#include <yaml-cpp/yaml.h>
41#pragma GCC diagnostic pop
42
43#include <type_traits>
44#include <ctime>
45
46using namespace std::literals;
47
48namespace jami {
49
52 , messageEngine_(*this, (fileutils::get_cache_dir() / getAccountID() / "messages").string())
53 , link_(Manager::instance().sipVoIPLink())
54{}
55
57
58bool
60 const pj_str_t* contact,
61 const pj_str_t* to,
62 const pj_str_t* target,
66{
67 JAMI_DBG("Creating SIP dialog: \n"
68 "From: %s\n"
69 "Contact: %s\n"
70 "To: %s\n",
71 from->ptr,
72 contact->ptr,
73 to->ptr);
74
75 if (target) {
76 JAMI_DBG("Target: %s", target->ptr);
77 } else {
78 JAMI_DBG("No target provided, using 'to' as target");
79 }
80
81 auto status = pjsip_dlg_create_uac(pjsip_ua_instance(), from, contact, to, target, dlg);
82 if (status != PJ_SUCCESS) {
83 JAMI_ERR("Unable to create SIP dialogs for user agent client when calling %s %d", to->ptr, status);
84 return false;
85 }
86
87 auto* dialog = *dlg;
88
89 {
90 // lock dialog until invite session creation; this one will own the dialog after
92
93 // Append "Subject: Phone Call" header
94 constexpr auto subj_hdr_name = sip_utils::CONST_PJ_STR("Subject");
95 auto* subj_hdr = reinterpret_cast<pjsip_hdr*>(
96 pjsip_parse_hdr(dialog->pool, &subj_hdr_name, const_cast<char*>("Phone call"), 10, nullptr));
98
100 JAMI_ERR("Unable to create invite session for user agent client");
101 return false;
102 }
103 }
104
105 return true;
106}
107
108void
110{
111 // Class base method
113 dhtnet::fileutils::remove(fileutils::get_cache_dir() / getAccountID() / "messages");
114}
115
116void
118{
120 const auto& conf = config();
121 dhtnet::IpAddr publishedIp {conf.publishedIp};
122 if (not conf.publishedSameasLocal and publishedIp)
123 setPublishedAddress(publishedIp);
124 dhtnet::TurnTransportParams turnParams;
125 turnParams.domain = conf.turnServer;
126 turnParams.username = conf.turnServerUserName;
127 turnParams.password = conf.turnServerPwd;
128 turnParams.realm = conf.turnServerRealm;
129 if (!turnCache_) {
131 turnCache_ = std::make_shared<dhtnet::TurnCache>(getAccountID(),
132 cachePath.string(),
134 Logger::dhtLogger(),
136 conf.turnEnabled);
137 }
138 turnCache_->reconfigure(turnParams, conf.turnEnabled);
139}
140
141std::map<std::string, std::string>
143{
145
146 // replace value from Account for IP2IP
147 if (isIP2IP())
149
150 a.emplace(Conf::CONFIG_TRANSPORT_STATE_CODE, std::to_string(transportStatus_));
152 return a;
153}
154
155void
164
165auto
167{
168 // Note: static arrays are zero-initialized
169 static std::remove_reference<decltype(getPortsReservation())>::type portsInUse;
170 return portsInUse;
171}
172
174SIPAccountBase::getRandomEvenPort(const std::pair<uint16_t, uint16_t>& range) const
175{
176 std::uniform_int_distribution<uint16_t> dist(range.first / 2, range.second / 2);
177 uint16_t result;
178 do {
179 result = 2 * dist(rand);
180 } while (getPortsReservation()[result / 2]);
181 return result;
182}
183
185SIPAccountBase::acquireRandomEvenPort(const std::pair<uint16_t, uint16_t>& range) const
186{
187 std::uniform_int_distribution<uint16_t> dist(range.first / 2, range.second / 2);
188 uint16_t result;
189
190 do {
191 result = 2 * dist(rand);
192 } while (getPortsReservation()[result / 2]);
193
194 getPortsReservation()[result / 2] = true;
195 return result;
196}
197
200{
201 getPortsReservation()[port / 2] = true;
202 return port;
203}
204
205void
207{
208 getPortsReservation()[port / 2] = false;
209}
210
213{
214 return acquireRandomEvenPort(config().audioPortRange);
215}
216
217#ifdef ENABLE_VIDEO
219SIPAccountBase::generateVideoPort() const
220{
221 return acquireRandomEvenPort(config().videoPortRange);
222}
223#endif
224
225dhtnet::IceTransportOptions
227{
228 dhtnet::IceTransportOptions opts;
229 opts.upnpEnable = getUPnPActive();
230 opts.upnpContext = upnpCtrl_ ? upnpCtrl_->upnpContext() : nullptr;
232
233 if (config().turnEnabled && turnCache_) {
234 auto turnAddr = turnCache_->getResolvedTurn();
235 if (turnAddr != std::nullopt) {
236 opts.turnServers.emplace_back(dhtnet::TurnServerInfo()
237 .setUri(turnAddr->toString(true))
238 .setUsername(config().turnServerUserName)
239 .setPassword(config().turnServerPwd)
240 .setRealm(config().turnServerRealm));
241 }
242 // NOTE: first test with ipv6 turn was not concluant and resulted in multiple
243 // co issues. So this needs some debug. for now just disable
244 // if (cacheTurnV6_ && *cacheTurnV6_) {
245 // opts.turnServers.emplace_back(TurnServerInfo()
246 // .setUri(cacheTurnV6_->toString(true))
247 // .setUsername(turnServerUserName_)
248 // .setPassword(turnServerPwd_)
249 // .setRealm(turnServerRealm_));
250 //}
251 }
252 return opts;
253}
254
255void
256SIPAccountBase::onTextMessage(const std::string& id,
257 const std::string& from,
258 const std::shared_ptr<dht::crypto::Certificate>& peerCert,
259 const std::map<std::string, std::string>& payloads)
260{
261 JAMI_LOG("[Account {}] [peer {}] Text message received from {}, {:d} part(s)",
263 peerCert ? peerCert->getLongId().to_view() : ""sv,
264 from,
265 payloads.size());
266 for (const auto& m : payloads) {
267 if (!utf8_validate(m.first))
268 return;
269 if (!utf8_validate(m.second)) {
270 JAMI_WARNING("[Account {}] Dropping invalid message with MIME type {}", accountID_, m.first);
271 return;
272 }
273 if (handleMessage(peerCert, from, m))
274 return;
275 }
276
277#ifdef ENABLE_PLUGIN
278 auto& pluginChatManager = Manager::instance().getJamiPluginManager().getChatServicesManager();
279 if (pluginChatManager.hasHandlers()) {
280 pluginChatManager.publishMessage(std::make_shared<JamiMessage>(accountID_, from, true, payloads, false));
281 }
282#endif
284
285 libjami::Message message;
286 message.from = from;
287 message.payloads = payloads;
288 message.received = std::time(nullptr);
289 std::lock_guard lck(mutexLastMessages_);
290 lastMessages_.emplace_back(std::move(message));
292 lastMessages_.pop_front();
293 }
294}
295
296dhtnet::IpAddr
298{
299 if (family == AF_INET)
300 return publishedIp_[0];
301 if (family == AF_INET6)
302 return publishedIp_[1];
303
305
306 // If family is not set, prefere IPv4 if available. It's more
307 // likely to succeed behind NAT.
308 if (publishedIp_[0])
309 return publishedIp_[0];
310 if (publishedIp_[1])
311 return publishedIp_[1];
312 return {};
313}
314
315void
317{
318 if (ip_addr.getFamily() == AF_INET) {
320 } else {
322 }
323}
324
325std::vector<libjami::Message>
327{
328 std::lock_guard lck(mutexLastMessages_);
329 auto it = lastMessages_.begin();
330 size_t num = lastMessages_.size();
331 while (it != lastMessages_.end() and it->received <= base_timestamp) {
332 num--;
333 ++it;
334 }
335 if (num == 0)
336 return {};
337 return {it, lastMessages_.end()};
338}
339
340std::vector<MediaAttribute>
342{
343 std::vector<MediaAttribute> mediaList;
344 bool secure = isSrtpEnabled();
345 // Add audio and DTMF events
346 mediaList.emplace_back(
348
349#ifdef ENABLE_VIDEO
350 // Add video if allowed.
352 mediaList.emplace_back(
354 }
355#endif
356 return mediaList;
357}
358} // namespace jami
Account specific keys/constants that must be shared in daemon and clients.
virtual void flush()
This method is called to request removal of possible account traces on the system,...
Definition account.h:424
const std::string & getAccountID() const
Get the account ID.
Definition account.h:149
virtual void setRegistrationState(RegistrationState state, int detail_code=0, const std::string &detail_str={})
Set the registration state of the specified link.
Definition account.cpp:103
const std::string accountID_
Account ID are assign in constructor and shall not changed.
Definition account.h:453
RegistrationState registrationState_
Definition account.h:468
std::mt19937_64 rand
Random generator engine Logical account state shall never rely on the state of the random generator.
Definition account.h:334
virtual std::map< std::string, std::string > getVolatileAccountDetails() const
Definition account.cpp:174
virtual bool isIP2IP() const
Returns true if this is the IP2IP account.
Definition account.h:158
virtual bool handleMessage(const std::shared_ptr< dht::crypto::Certificate > &, const std::string &, const std::pair< std::string, std::string > &)
Definition account.h:341
bool getUPnPActive() const
returns whether or not UPnP is enabled and active ie: if it is able to make port mappings
Definition account.cpp:300
bool isVideoEnabled() const
Definition account.h:277
virtual void loadConfig()
Load the settings in this account.
Definition account.cpp:148
std::shared_ptr< dhtnet::upnp::Controller > upnpCtrl_
Definition account.h:493
Manager (controller) of daemon.
Definition manager.h:66
static LIBJAMI_TEST_EXPORT Manager & instance()
Definition manager.cpp:694
std::shared_ptr< asio::io_context > ioContext() const
Definition manager.cpp:1734
const std::shared_ptr< dhtnet::IceTransportFactory > & getIceTransportFactory()
Definition manager.cpp:3209
static constexpr size_t MAX_WAITING_MESSAGES_SIZE
static void releasePort(uint16_t port) noexcept
dhtnet::IpAddr publishedIp_[2]
Published IPv4/IPv6 addresses, used only if defined by the user in account configuration.
const SipAccountBaseConfig & config() const
pj_status_t transportStatus_
virtual bool isSrtpEnabled() const =0
virtual void setRegistrationState(RegistrationState state, int code=0, const std::string &detail_str={}) override
Set the registration state of the specified link.
static uint16_t acquirePort(uint16_t port)
uint16_t acquireRandomEvenPort(const std::pair< uint16_t, uint16_t > &range) const
std::deque< libjami::Message > lastMessages_
std::mutex mutexLastMessages_
The deamon can be launched without any client (or with a non ready client) Like call and file transfe...
SIPAccountBase(const std::string &accountID)
Constructor.
virtual ~SIPAccountBase() noexcept
virtual std::map< std::string, std::string > getVolatileAccountDetails() const override
Retrieve volatile details such as recent registration errors.
virtual void onTextMessage(const std::string &id, const std::string &from, const std::shared_ptr< dht::crypto::Certificate > &peerCert, const std::map< std::string, std::string > &payloads)
std::vector< MediaAttribute > createDefaultMediaList(bool addVideo, bool hold=false)
uint16_t generateAudioPort() const
Socket port generators for media Note: given ports are application wide, a port is unable to be given...
std::vector< libjami::Message > getLastMessages(const uint64_t &base_timestamp) override
uint16_t getRandomEvenPort(const std::pair< uint16_t, uint16_t > &range) const
virtual dhtnet::IpAddr getPublishedIpAddress(uint16_t family=PF_UNSPEC) const
std::string transportError_
im::MessageEngine messageEngine_
bool CreateClientDialogAndInvite(const pj_str_t *from, const pj_str_t *contact, const pj_str_t *to, const pj_str_t *target, const pjmedia_sdp_session *local_sdp, pjsip_dialog **dlg, pjsip_inv_session **inv)
Create UAC attached dialog and invite session.
void setPublishedAddress(const dhtnet::IpAddr &ip_addr)
static std::array< bool, HALF_MAX_PORT > & getPortsReservation() noexcept
void loadConfig() override
Load the settings in this account.
virtual void flush() override
This method is called to request removal of possible account traces on the system,...
virtual dhtnet::IceTransportOptions getIceOptions() const
std::shared_ptr< dhtnet::TurnCache > turnCache_
void load()
Load persisted messages.
void save() const
Persist messages.
#define JAMI_ERR(...)
Definition logger.h:230
#define JAMI_DBG(...)
Definition logger.h:228
#define JAMI_WARNING(formatstr,...)
Definition logger.h:242
#define JAMI_LOG(formatstr,...)
Definition logger.h:237
static const char *const CONFIG_ACCOUNT_REGISTRATION_STATUS
static const char *const CONFIG_TRANSPORT_STATE_CODE
static const char *const CONFIG_TRANSPORT_STATE_DESC
const std::filesystem::path & get_cache_dir()
constexpr std::string_view DEFAULT_VIDEO_STREAMID
Definition sip_utils.h:144
constexpr std::string_view DEFAULT_AUDIO_STREAMID
Definition sip_utils.h:145
constexpr const pj_str_t CONST_PJ_STR(T(&a)[N]) noexcept
Definition sip_utils.h:87
bool utf8_validate(std::string_view str)
utf8_validate:
void emitSignal(Args... args)
Definition jami_signal.h:64
RegistrationState
Contains all the Registration states for an account can be in.
@ MEDIA_AUDIO
Definition media_codec.h:46
@ MEDIA_VIDEO
Definition media_codec.h:47
std::map< std::string, std::string > payloads