Ring Daemon
Loading...
Searching...
No Matches
sipvoiplink.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/sipvoiplink.h"
19
20#ifdef HAVE_CONFIG_H
21#include "config.h"
22#endif
23
24#include "sdp.h"
25#include "sip/sipcall.h"
26#include "sip/sipaccount.h"
27
28#include "jamidht/jamiaccount.h"
29
30#include "manager.h"
31
33
34#include "pres_sub_server.h"
35
37#include "string_utils.h"
38#include "logger.h"
39
40#include <dhtnet/ip_utils.h>
41#include <opendht/thread_pool.h>
42
43#include <pjsip/sip_endpoint.h>
44#include <pjsip/sip_uri.h>
45
46#include <pjsip-simple/presence.h>
47#include <pjsip-simple/publish.h>
48
49// Only PJSIP 2.10 is supported.
50#if PJ_VERSION_NUM < (2 << 24 | 10 << 16)
51#error "Unsupported PJSIP version (requires version 2.10+)"
52#endif
53
54#include <cstddef>
55#include <regex>
56
57namespace jami {
58
60
61/**************** EXTERN VARIABLES AND FUNCTIONS (callbacks) **************************/
62
65
69// Called when an SDP offer is found in answer. This will occur
70// when we send an empty invite (no SDP). In this case, we should
71// expect an offer in a the 200 OK message
73// Called when a re-invite is received
75// Called to request an SDP offer if the peer sent an invite or
76// a re-invite with no SDP. In this, we must provide an offer in
77// the answer (200 OK) if we accept the call
79// Called to report media (SDP) negotiation result
81
82static std::shared_ptr<SIPCall> getCallFromInvite(pjsip_inv_session* inv);
83#ifdef DEBUG_SIP_REQUEST_MSG
85#endif
86
87static pj_bool_t
89{
91
93 if (dlg) {
94 JAMI_INFO("Processing in-dialog option request");
96 JAMI_ERR("Failed to create in-dialog response for option request");
97 return PJ_FALSE;
98 }
99 } else {
100 JAMI_INFO("Processing out-of-dialog option request");
102 JAMI_ERR("Failed to create out-of-dialog response for option request");
103 return PJ_FALSE;
104 }
105 }
106
107#define ADD_HDR(hdr) \
108 do { \
109 const pjsip_hdr* cap_hdr = hdr; \
110 if (cap_hdr) \
111 pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*) pjsip_hdr_clone(tdata->pool, cap_hdr)); \
112 } while (0)
113#define ADD_CAP(cap) ADD_HDR(pjsip_endpt_get_capability(endpt_, cap, NULL));
114
119
120 if (dlg) {
122 JAMI_ERR("Failed to send in-dialog response for option request");
123 return PJ_FALSE;
124 }
125
126 JAMI_INFO("Sent in-dialog response for option request");
127 return PJ_TRUE;
128 }
129
132
135 JAMI_ERR("Failed to send out-of-dialog response for option request");
136 return PJ_FALSE;
137 }
138
139 JAMI_INFO("Sent out-of-dialog response for option request");
140 return PJ_TRUE;
141}
142
143// return PJ_FALSE so that eventually other modules will handle these requests
144// TODO: move Voicemail to separate module
145static pj_bool_t
147{
149
150 if (!dlg)
151 return PJ_FALSE;
152
154
155 if (!tsx or tsx->method.id != PJSIP_INVITE_METHOD)
156 return PJ_FALSE;
157
158 if (tsx->status_code / 100 == 2) {
164
165 if (rdata->msg_info.cseq) {
166 pjsip_dlg_create_request(dlg, &pjsip_ack_method, rdata->msg_info.cseq->cseq, &tdata);
168 }
169 }
170
171 return PJ_FALSE;
172}
173
174static pj_status_t
177 int st_code,
178 const pj_str_t* st_text,
179 const pjsip_hdr* hdr_list,
180 const pjsip_msg_body* body)
181{
182 /* Check that no UAS transaction has been created for this request.
183 * If UAS transaction has been created for this request, application
184 * MUST send the response statefully using that transaction.
185 */
187 return pjsip_endpt_respond_stateless(endpt, rdata, st_code, st_text, hdr_list, body);
188 else
189 JAMI_ERR("Transaction has been created for this request, send response "
190 "statefully instead");
191
192 return !PJ_SUCCESS;
193}
194
195template<typename T>
196static bool
197is_uninitialized(std::weak_ptr<T> const& weak)
198{
199 using wt = std::weak_ptr<T>;
200 return !weak.owner_before(wt {}) && !wt {}.owner_before(weak);
201}
202
203static pj_bool_t
205{
206 if (!rdata or !rdata->msg_info.msg) {
207 JAMI_ERR("rx_data is NULL");
208 return PJ_FALSE;
209 }
210
211 pjsip_method* method = &rdata->msg_info.msg->line.req.method;
212
213 if (!method) {
214 JAMI_ERR("method is NULL");
215 return PJ_FALSE;
216 }
217
219 return PJ_FALSE;
220
221 if (!rdata->msg_info.to or !rdata->msg_info.from or !rdata->msg_info.via) {
222 JAMI_ERR("Missing From, To or Via fields");
223 return PJ_FALSE;
224 }
225
226 auto* const sip_to_uri = reinterpret_cast<pjsip_sip_uri*>(pjsip_uri_get_uri(rdata->msg_info.to->uri));
227 auto* const sip_from_uri = reinterpret_cast<pjsip_sip_uri*>(pjsip_uri_get_uri(rdata->msg_info.from->uri));
228 const pjsip_host_port& sip_via = rdata->msg_info.via->sent_by;
229
230 if (!sip_to_uri or !sip_from_uri or !sip_via.host.ptr) {
231 JAMI_ERR("NULL URI");
232 return PJ_FALSE;
233 }
234
235 std::string_view toUsername(sip_to_uri->user.ptr, sip_to_uri->user.slen);
236 std::string_view toHost(sip_to_uri->host.ptr, sip_to_uri->host.slen);
237 std::string_view viaHostname(sip_via.host.ptr, sip_via.host.slen);
238 const std::string_view remote_user(sip_from_uri->user.ptr, sip_from_uri->user.slen);
239 const std::string_view remote_hostname(sip_from_uri->host.ptr, sip_from_uri->host.slen);
240 std::string peerNumber;
241 if (not remote_user.empty() and not remote_hostname.empty())
243 else {
247 }
248
249 auto transport = Manager::instance().sipVoIPLink().sipTransportBroker->addTransport(rdata->tp_info.transport);
250
251 std::shared_ptr<SIPAccountBase> account;
252 // If transport account is default-constructed, guessing account is allowed
253 const auto& waccount = transport ? transport->getAccount() : std::weak_ptr<SIPAccountBase> {};
256 if (not account)
257 return PJ_FALSE;
258 if (not transport and account->getAccountType() == SIPAccount::ACCOUNT_TYPE) {
259 if (not(transport = std::static_pointer_cast<SIPAccount>(account)->getTransport())) {
260 JAMI_ERR("No suitable transport to answer this call.");
261 return PJ_FALSE;
262 }
263 JAMI_WARN("Using transport from account.");
264 }
265 } else if (!(account = waccount.lock())) {
266 JAMI_ERR("Dropping SIP request: account is expired.");
267 return PJ_FALSE;
268 }
269
270 pjsip_msg_body* body = rdata->msg_info.msg->body;
271
272 if (method->id == PJSIP_OTHER_METHOD) {
273 std::string_view request = sip_utils::as_view(method->name);
274
275 if (request.find(sip_utils::SIP_METHODS::NOTIFY) != std::string_view::npos) {
276 if (body and body->data) {
277 std::string_view body_view(static_cast<char*>(body->data), body->len);
278 auto pos = body_view.find("Voice-Message: ");
279 if (pos != std::string_view::npos) {
280 int newCount {0};
281 int oldCount {0};
282 int urgentCount {0};
283 std::string sp(body_view.substr(pos));
284 int ret = sscanf(sp.c_str(), "Voice-Message: %d/%d (%d/", &newCount, &oldCount, &urgentCount);
285
286 // According to rfc3842
287 // urgent messages are optional
288 if (ret >= 2)
290 newCount,
291 oldCount,
293 }
294 }
295 } else if (request.find(sip_utils::SIP_METHODS::MESSAGE) != std::string_view::npos) {
296 // Reply 200 immediately (RFC 3428, ch. 7)
297 try_respond_stateless(endpt_, rdata, PJSIP_SC_OK, nullptr, nullptr, nullptr);
298 // Process message content in case of multi-part body
299 auto payloads = im::parseSipMessage(rdata->msg_info.msg);
300 if (payloads.size() > 0) {
301 constexpr pj_str_t STR_MESSAGE_ID = jami::sip_utils::CONST_PJ_STR("Message-ID");
304 nullptr);
305 std::string id = {};
306 if (!msgId) {
307 // Supports IMDN message format https://tools.ietf.org/html/rfc5438#section-7.1.1.3
308 constexpr pj_str_t STR_IMDN_MESSAGE_ID = jami::sip_utils::CONST_PJ_STR("imdn.Message-ID");
311 nullptr);
312 }
313 if (msgId)
314 id = std::string(msgId->hvalue.ptr, msgId->hvalue.slen);
315
316 if (not id.empty()) {
317 try {
318 // Mark message as treated
319 auto intid = from_hex_string(id);
320 auto acc = std::dynamic_pointer_cast<JamiAccount>(account);
321 if (acc and acc->isMessageTreated(intid))
322 return PJ_FALSE;
323 } catch (const std::exception& e) {
324 JAMI_WARNING("[Account {}] Couldn't treat message {}: {}",
325 account->getAccountID(),
326 from_hex_string(id),
327 e.what());
328 }
329 }
330 account->onTextMessage(id, peerNumber, transport->getTlsInfos().peerCert, payloads);
331 }
332 return PJ_FALSE;
333 }
334
336
337 return PJ_FALSE;
338 } else if (method->id == PJSIP_OPTIONS_METHOD) {
340 } else if (method->id != PJSIP_INVITE_METHOD && method->id != PJSIP_ACK_METHOD) {
342 return PJ_FALSE;
343 }
344
345 if (method->id == PJSIP_INVITE_METHOD) {
346 // Log headers of received INVITE
347 JAMI_INFO("Received a SIP INVITE request");
348 sip_utils::logMessageHeaders(&rdata->msg_info.msg->hdr);
349 }
350
351 pjmedia_sdp_session* r_sdp {nullptr};
352 if (body) {
353 if (pjmedia_sdp_parse(rdata->tp_info.pool, (char*) body->data, body->len, &r_sdp) != PJ_SUCCESS) {
354 JAMI_WARN("Failed to parse the SDP in offer");
355 r_sdp = nullptr;
356 }
357 }
358
359 if (not account->hasActiveCodec(MEDIA_AUDIO)) {
361 return PJ_FALSE;
362 }
363
364 // Verify that we can handle the request
365 unsigned options = 0;
366
368 JAMI_ERR("Unable to verify INVITE request in secure dialog.");
370 return PJ_FALSE;
371 }
372
373 // Build the initial media using the remote offer.
375
376 // To enable video, it must be enabled in the remote and locally (i.e. in the account)
377 for (auto& media : localMediaList) {
378 if (media.type_ == MediaType::MEDIA_VIDEO) {
379 media.enabled_ &= account->isVideoEnabled();
380 }
381 }
382
383 auto call = account->newIncomingCall(std::string(remote_user),
385 transport);
386 if (!call) {
387 return PJ_FALSE;
388 }
389
390 call->setPeerUaVersion(sip_utils::getPeerUserAgent(rdata));
391 // The username can be used to join specific calls in conversations
392 call->toUsername(std::string(toUsername));
393
394 // FIXME: for now, use the same address family as the SIP transport
396
397 dhtnet::IpAddr addrSdp;
398 if (account->getUPnPActive()) {
399 /* use UPnP addr, or published addr if its set */
400 addrSdp = account->getPublishedSameasLocal() ? account->getUPnPIpAddress() : account->getPublishedIpAddress();
401 } else {
402 addrSdp = account->isStunEnabled() or (not account->getPublishedSameasLocal())
403 ? account->getPublishedIpAddress()
404 : dhtnet::ip_utils::getInterfaceAddr(account->getLocalInterface(), family);
405 }
406
407 /* fallback on local address */
408 if (not addrSdp)
409 addrSdp = dhtnet::ip_utils::getInterfaceAddr(account->getLocalInterface(), family);
410
411 // Try to obtain display name from From: header first, fallback on Contact:
413 if (peerDisplayName.empty()) {
414 if (const auto* hdr = static_cast<const pjsip_contact_hdr*>(
415 pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_CONTACT, nullptr))) {
417 }
418 }
419
420 call->setPeerNumber(peerNumber);
421 call->setPeerUri(account->getToUri(peerNumber));
422 call->setPeerDisplayName(peerDisplayName);
424 call->getSDP().setPublishedIP(addrSdp);
425 call->setPeerAllowMethods(sip_utils::getPeerAllowMethods(rdata));
426
427 // Set the temporary media list. Might change when we receive
428 // the accept from the client.
429 if (r_sdp != nullptr) {
430 call->getSDP().setReceivedOffer(r_sdp);
431 }
432
433 pjsip_dialog* dialog = nullptr;
435 JAMI_ERR("Unable to create UAS");
436 call.reset();
438 return PJ_FALSE;
439 }
440
443 JAMI_ERR("Unable to set transport for dialog");
444 if (dialog)
446 return PJ_FALSE;
447 }
448
449 pjsip_inv_session* inv = nullptr;
450 // Create UAS for the invite.
451 // The SDP is not set here, it will be done when the call is
452 // accepted and the media attributes of the answer are known.
454 if (!inv) {
455 JAMI_ERR("Call invite is not initialized");
457 return PJ_FALSE;
458 }
459
460 // dialog is now owned by invite
462
463 inv->mod_data[mod_ua_.id] = call.get();
464 call->setInviteSession(inv);
465
466 // Check whether Replaces header is present in the request and process accordingly.
468 pjsip_tx_data* response;
469
471 JAMI_ERR("Something wrong with Replaces request.");
472 call.reset();
473
474 // Something wrong with the Replaces header.
475 if (response) {
477 pjsip_get_response_addr(response->pool, rdata, &res_addr);
479 } else {
481 }
482
483 return PJ_FALSE;
484 }
485
486 // Check if call has been transferred
487 pjsip_tx_data* tdata = nullptr;
488
489 if (pjsip_inv_initial_answer(call->inviteSession_.get(), rdata, PJSIP_SC_TRYING, NULL, NULL, &tdata) != PJ_SUCCESS) {
490 JAMI_ERR("Unable to create answer TRYING");
491 return PJ_FALSE;
492 }
493
494 // Add user-agent header
495 sip_utils::addUserAgentHeader(account->getUserAgentName(), tdata);
496
497 if (pjsip_inv_send_msg(call->inviteSession_.get(), tdata) != PJ_SUCCESS) {
498 JAMI_ERR("Unable to send msg TRYING");
499 return PJ_FALSE;
500 }
501
502 call->setState(Call::ConnectionState::TRYING);
503
504 if (pjsip_inv_answer(call->inviteSession_.get(), PJSIP_SC_RINGING, NULL, NULL, &tdata) != PJ_SUCCESS) {
505 JAMI_ERR("Unable to create answer RINGING");
506 return PJ_FALSE;
507 }
508
509 sip_utils::addContactHeader(call->getContactHeader(), tdata);
510 if (pjsip_inv_send_msg(call->inviteSession_.get(), tdata) != PJ_SUCCESS) {
511 JAMI_ERR("Unable to send msg RINGING");
512 return PJ_FALSE;
513 }
514
515 call->setState(Call::ConnectionState::RINGING);
516
517 Manager::instance().incomingCall(account->getAccountID(), *call);
518
519 if (replaced_dlg) {
520 // Get the INVITE session associated with the replaced dialog.
522
523 // Disconnect the "replaced" INVITE session.
526 }
527
528 // Close call at application level
530 replacedCall->hangup(PJSIP_SC_OK);
531 }
532
533 return PJ_FALSE;
534}
535
536static void
538{
539 if (auto& broker = Manager::instance().sipVoIPLink().sipTransportBroker)
540 broker->transportStateChanged(tp, state, info);
541 else
542 JAMI_ERR("SIPVoIPLink with invalid SipTransportBroker");
543}
544
545/*************************************************************************************************/
546
549{
550 return endpt_;
551}
552
555{
556 return &mod_ua_;
557}
558
561{
562 return pool_.get();
563}
564
567{
568 return &cp_;
569}
570
572 : pool_(nullptr)
573{
574#define TRY(ret) \
575 do { \
576 if ((ret) != PJ_SUCCESS) \
577 throw VoipLinkException(#ret " failed"); \
578 } while (0)
579
581 pool_.reset(pj_pool_create(&cp_.factory, PACKAGE, static_cast<long>(64) * 1024, 4096, nullptr));
582 if (!pool_)
583 throw VoipLinkException("UserAgent: Unable to initialize memory pool");
584
585 TRY(pjsip_endpt_create(&cp_.factory, pj_gethostname()->ptr, &endpt_));
586
587 auto ns = dhtnet::ip_utils::getLocalNameservers();
588 if (not ns.empty()) {
589 std::vector<pj_str_t> dns_nameservers(ns.size());
590 std::vector<pj_uint16_t> dns_ports(ns.size());
591 for (unsigned i = 0, n = ns.size(); i < n; i++) {
592 char hbuf[NI_MAXHOST];
593 if (auto ret
594 = getnameinfo((sockaddr*) &ns[i], ns[i].getLength(), hbuf, sizeof(hbuf), nullptr, 0, NI_NUMERICHOST)) {
595 JAMI_WARN("Error printing SIP nameserver: %s", gai_strerror(ret));
596 } else {
597 JAMI_DBG("Using SIP nameserver: %s", hbuf);
598 pj_strdup2(pool_.get(), &dns_nameservers[i], hbuf);
599 dns_ports[i] = ns[i].getPort();
600 }
601 }
604 JAMI_WARN("Error creating SIP DNS resolver: %s", sip_utils::sip_strerror(ret).c_str());
605 } else {
607 dns_nameservers.size(),
608 dns_nameservers.data(),
609 dns_ports.data())) {
610 JAMI_WARN("Error setting SIP DNS servers: %s", sip_utils::sip_strerror(ret).c_str());
611 } else {
613 JAMI_WARN("Error setting PJSIP DNS resolver: %s", sip_utils::sip_strerror(ret).c_str());
614 }
615 }
616 }
617 }
618
620
622 if (status != PJ_SUCCESS)
623 JAMI_ERR("Unable to set transport callback: %s", sip_utils::sip_strerror(status).c_str());
624
627 TRY(pjsip_replaces_init_module(endpt_)); // See the Replaces specification in RFC 3891
629
630 // Initialize and register ring module
632 mod_ua_.id = -1;
634 mod_ua_.on_rx_request = &transaction_request_cb;
635 mod_ua_.on_rx_response = &transaction_response_cb;
637
640
641 // presence/publish management
644
645 static const pjsip_inv_callback inv_cb = {
649 nullptr /* on_rx_offer */,
654 nullptr /* on_send_ack */,
655 nullptr /* on_redirected */,
656 };
658
659 static constexpr pj_str_t allowed[] = {
660 CONST_PJ_STR(sip_utils::SIP_METHODS::INFO),
664 };
665
667
668 static constexpr pj_str_t text_plain = CONST_PJ_STR("text/plain");
670
671 static constexpr pj_str_t accepted = CONST_PJ_STR("application/sdp");
673
674 static constexpr pj_str_t iscomposing = CONST_PJ_STR("application/im-iscomposing+xml");
676
678#undef TRY
679
680 sipThread_ = std::thread([this] {
681 while (running_)
682 handleEvents();
683 });
684
685 JAMI_DBG("SIPVoIPLink@%p", this);
686}
687
689
690void
692{
693 JAMI_DBG("Shutting down SIPVoIPLink@%p…", this);
694 // Remaining calls should not happen as possible upper callbacks
695 // may be called and another instance of SIPVoIPLink can be re-created!
696
697 if (not Manager::instance().callFactory.empty(Call::LinkType::SIP))
698 JAMI_ERR("%zu SIP calls remains!", Manager::instance().callFactory.callCount(Call::LinkType::SIP));
699
700 sipTransportBroker->shutdown();
702
703 running_ = false;
704 sipThread_.join();
706 pool_.reset();
708 sipTransportBroker.reset();
709
710 JAMI_DBG("SIPVoIPLink@%p shutdown successfully completed", this);
711}
712
713std::shared_ptr<SIPAccountBase>
714SIPVoIPLink::guessAccount(std::string_view userName, std::string_view server, std::string_view fromUri) const
715{
716 JAMI_LOG("username = {}, server = {}, from = {}", userName, server, fromUri);
717 // Attempt to find the account id from username and server name by full match
718
719 std::shared_ptr<SIPAccountBase> result;
720 std::shared_ptr<SIPAccountBase> IP2IPAccount;
722
723 // SIP accounts
724 for (const auto& account : Manager::instance().getAllAccounts<SIPAccount>()) {
725 const MatchRank match(account->matches(userName, server));
726
727 // return right away if this is a full match
728 if (match == MatchRank::FULL) {
729 return account;
730 } else if (match > best) {
731 best = match;
732 result = account;
733 } else if (!IP2IPAccount && account->isIP2IP()) {
734 // Allow IP2IP calls if an account exists for this type of calls
736 }
737 }
738
739 return result ? result : IP2IPAccount;
740}
741
742// Called from EventThread::run (not main thread)
743void
745{
746 const pj_time_val timeout = {1, 0};
747 if (auto ret = pjsip_endpt_handle_events(endpt_, &timeout))
748 JAMI_ERR("pjsip_endpt_handle_events failed with error %s", sip_utils::sip_strerror(ret).c_str());
749}
750
751void
753{
754 JAMI_DEBUG("Register new keepalive timer {:d} with delay {:d}", timer.id, delay.sec);
755
756 if (timer.id == -1)
757 JAMI_WARN("Timer already scheduled");
758
759 switch (pjsip_endpt_schedule_timer(endpt_, &timer, &delay)) {
760 case PJ_SUCCESS:
761 break;
762
763 default:
764 JAMI_ERR("Unable to schedule new timer in pjsip endpoint");
765
766 /* fallthrough */
767 case PJ_EINVAL:
768 JAMI_ERR("Invalid timer or delay entry");
769 break;
770
771 case PJ_EINVALIDOP:
772 JAMI_ERR("Invalid timer entry, maybe already scheduled");
773 break;
774 }
775}
776
777void
782
784// Private functions
786
787static std::shared_ptr<SIPCall>
789{
790 if (auto* call_ptr = static_cast<SIPCall*>(inv->mod_data[mod_ua_.id]))
791 return std::static_pointer_cast<SIPCall>(call_ptr->shared_from_this());
792 return nullptr;
793}
794
795static void
797{
798 if (inv == nullptr or ev == nullptr) {
799 throw VoipLinkException("Unexpected null pointer");
800 }
801
802 auto call = getCallFromInvite(inv);
803 if (not call)
804 return;
805
806 if (ev->type != PJSIP_EVENT_TSX_STATE and ev->type != PJSIP_EVENT_TX_MSG and ev->type != PJSIP_EVENT_RX_MSG) {
807 JAMI_WARN("[call:%s] INVITE@%p state changed to %d (%s): unexpected event type %d",
808 call->getCallId().c_str(),
809 inv,
810 inv->state,
812 ev->type);
813 return;
814 }
815
816 decltype(pjsip_transaction::status_code) status_code = 0;
817
818 if (ev->type == PJSIP_EVENT_TSX_STATE) {
819 auto* const tsx = ev->body.tsx_state.tsx;
820 status_code = tsx ? tsx->status_code : PJSIP_SC_NOT_FOUND;
821 const pj_str_t* description = pjsip_get_status_text(status_code);
822
823 JAMI_LOG("[call:{}] INVITE@{:p} state changed to {:d} ({}): cause={:d}, tsx@{:p} status {:d} ({:s})",
824 call->getCallId(),
825 fmt::ptr(inv),
826 (int) inv->state,
828 (int) inv->cause,
829 fmt::ptr(tsx),
831 sip_utils::as_view(*description));
832 } else if (ev->type == PJSIP_EVENT_TX_MSG) {
833 JAMI_LOG("[call:{}] INVITE@{:p} state changed to {:d} ({:s}): cause={:d} (TX_MSG)",
834 call->getCallId(),
835 fmt::ptr(inv),
836 (int) inv->state,
838 (int) inv->cause);
839 }
840 pjsip_rx_data* rdata {nullptr};
841 if (ev->type == PJSIP_EVENT_RX_MSG) {
842 rdata = ev->body.rx_msg.rdata;
843 } else if (ev->type == PJSIP_EVENT_TSX_STATE and ev->body.tsx_state.type == PJSIP_EVENT_RX_MSG) {
844 rdata = ev->body.tsx_state.src.rdata;
845 }
846 if (rdata != nullptr) {
847 call->setPeerUaVersion(sip_utils::getPeerUserAgent(rdata));
849 if (not methods.empty()) {
850 call->setPeerAllowMethods(std::move(methods));
851 }
852 }
853
854 switch (inv->state) {
857 call->onPeerRinging();
858 break;
859
861 // After we sent or received a ACK - The connection is established
862 call->onAnswered();
863 break;
864
866 switch (inv->cause) {
867 // When a peer's device replies busy
869 call->onBusyHere();
870 break;
871 // When the peer manually refuse the call
872 case PJSIP_SC_DECLINE:
874 if (inv->role != PJSIP_ROLE_UAC)
875 break;
876 // close call
877 call->onClosed();
878 break;
879 // The call terminates normally - BYE / CANCEL
880 case PJSIP_SC_OK:
882 call->onClosed();
883 break;
884
885 // Error/unhandled conditions
886 default:
887 call->onFailure(inv->cause);
888 break;
889 }
890 break;
891
892 default:
893 break;
894 }
895}
896
897static void
899{
900 if (not param or not param->offer) {
901 JAMI_ERR("Invalid offer");
902 return;
903 }
904
905 auto call = getCallFromInvite(inv);
906 if (not call)
907 return;
908
909 // This callback is called whenever a new media offer is found in a
910 // SIP message, typically in a re-invite and in a '200 OK' (as a
911 // response to an empty invite).
912 // Here we only handle the second case. The first case is handled
913 // in reinvite_received_cb.
914 if (inv->cause != PJSIP_SC_OK) {
915 // Silently ignore if it's not a '200 OK'
916 return;
917 }
918
919 if (auto call = getCallFromInvite(inv)) {
920 if (auto const& account = call->getAccount().lock()) {
921 call->onReceiveOfferIn200OK(param->offer);
922 }
923 }
924}
925
926static pj_status_t
928{
929 if (!offer)
930 return !PJ_SUCCESS;
931 if (auto call = getCallFromInvite(inv)) {
932 if (auto const& account = call->getAccount().lock()) {
933 return call->onReceiveReinvite(offer, rdata);
934 }
935 }
936
937 // Return success if there is no matching call. The re-invite
938 // should be ignored.
939 return PJ_SUCCESS;
940}
941
942static void
944{
945 auto call = getCallFromInvite(inv);
946 if (not call)
947 return;
948
949 auto account = call->getSIPAccount();
950 if (not account) {
951 JAMI_ERR("No account detected");
952 return;
953 }
954
955 if (account->isEmptyOffersEnabled()) {
956 // Skip if the client wants to send an empty offer.
957 JAMI_DBG("Client requested to send an empty offer (no SDP)");
958 return;
959 }
960
961 auto family = pj_AF_INET();
962 // FIXME: for now, use the same address family as the SIP transport
963 if (auto* dlg = inv->dlg) {
964 if (dlg->tp_sel.type == PJSIP_TPSELECTOR_TRANSPORT) {
965 if (auto* tr = dlg->tp_sel.u.transport)
966 family = tr->local_addr.addr.sa_family;
967 } else if (dlg->tp_sel.type == PJSIP_TPSELECTOR_TRANSPORT) {
968 if (auto* tr = dlg->tp_sel.u.listener)
969 family = tr->local_addr.addr.sa_family;
970 }
971 }
972 auto ifaceAddr = dhtnet::ip_utils::getInterfaceAddr(account->getLocalInterface(), family);
973
974 dhtnet::IpAddr address;
975 if (account->getUPnPActive()) {
976 /* use UPnP addr, or published addr if it's set */
977 address = account->getPublishedSameasLocal() ? account->getUPnPIpAddress() : account->getPublishedIpAddress();
978 } else {
979 address = account->getPublishedSameasLocal() ? ifaceAddr : account->getPublishedIpAddress();
980 }
981
982 /* fallback on local address */
983 if (not address)
984 address = ifaceAddr;
985
986 auto& sdp = call->getSDP();
987 sdp.setPublishedIP(address);
988
989 // This list should be provided by the client. Kept for backward compatibility.
990 auto const& mediaList = call->getMediaAttributeList();
991 if (mediaList.empty()) {
992 throw VoipLinkException("Unexpected empty media attribute list");
993 }
994
995 JAMI_DBG("Creating a SDP offer using the following media:");
996 for (auto const& media : mediaList) {
997 JAMI_DBG("[call %s] Media %s", call->getCallId().c_str(), media.toString(true).c_str());
998 }
999
1000 const bool created = sdp.createOffer(mediaList);
1001
1002 if (created and p_offer != nullptr)
1003 *p_offer = sdp.getLocalSdpSession();
1004}
1005
1006static const pjmedia_sdp_session*
1008{
1010
1012 JAMI_ERR("Active remote not present");
1013 return nullptr;
1014 }
1015
1017 JAMI_ERR("Invalid remote SDP session");
1018 return nullptr;
1019 }
1020
1021 return sdp_session;
1022}
1023
1024static const pjmedia_sdp_session*
1026{
1028
1030 JAMI_ERR("Active local not present");
1031 return nullptr;
1032 }
1033
1035 JAMI_ERR("Invalid local SDP session");
1036 return nullptr;
1037 }
1038
1039 return sdp_session;
1040}
1041
1042// This callback is called after SDP offer/answer session has completed.
1043static void
1045{
1046 auto call = getCallFromInvite(inv);
1047 if (not call)
1048 return;
1049
1050 JAMI_DBG("[call:%s] INVITE@%p media update: status %d", call->getCallId().c_str(), inv, status);
1051
1052 if (status != PJ_SUCCESS) {
1053 const int reason = inv->state != PJSIP_INV_STATE_NULL and inv->state != PJSIP_INV_STATE_CONFIRMED
1055 : 0;
1056
1057 JAMI_WARN("[call:%s] SDP offer failed, reason %d", call->getCallId().c_str(), reason);
1058
1059 call->hangup(reason);
1060 return;
1061 }
1062
1063 // Fetch SDP data from request
1064 const auto* const localSDP = get_active_local_sdp(inv);
1065 const auto* const remoteSDP = get_active_remote_sdp(inv);
1066
1067 // Update our SDP manager
1068 auto& sdp = call->getSDP();
1069 sdp.setActiveLocalSdpSession(localSDP);
1070 if (localSDP != nullptr) {
1071 Sdp::printSession(localSDP, "Local active session:", sdp.getSdpDirection());
1072 }
1073
1074 sdp.setActiveRemoteSdpSession(remoteSDP);
1075 if (remoteSDP != nullptr) {
1076 Sdp::printSession(remoteSDP, "Remote active session:", sdp.getSdpDirection());
1077 }
1078
1079 call->onMediaNegotiationComplete();
1080}
1081
1082static void
1085
1086static bool
1088{
1089 /*
1090 * Incoming INFO request for media control.
1091 */
1092 constexpr pj_str_t STR_APPLICATION = CONST_PJ_STR("application");
1093 constexpr pj_str_t STR_MEDIA_CONTROL_XML = CONST_PJ_STR("media_control+xml");
1094
1095 if (body->len and pj_stricmp(&body->content_type.type, &STR_APPLICATION) == 0
1096 and pj_stricmp(&body->content_type.subtype, &STR_MEDIA_CONTROL_XML) == 0) {
1097 auto body_msg = std::string_view((char*) body->data, (size_t) body->len);
1098
1099 /* Apply and answer the INFO request */
1100 static constexpr auto PICT_FAST_UPDATE = "picture_fast_update"sv;
1101 static constexpr auto STREAM_ID = "stream_id"sv;
1102 static constexpr auto DEVICE_ORIENTATION = "device_orientation"sv;
1103 static constexpr auto RECORDING_STATE = "recording_state"sv;
1104 static constexpr auto MUTE_STATE = "mute_state"sv;
1105 static constexpr auto VOICE_ACTIVITY = "voice_activity"sv;
1106
1107 int streamIdx = -1;
1108 if (body_msg.find(STREAM_ID) != std::string_view::npos) {
1109 // Note: here we use the index of the RTP stream, not its label!
1110 // Indeed, both sides will have different labels as they have different call IDs
1111 static const std::regex STREAMID_REGEX("<stream_id>([0-9]+)</stream_id>");
1114 if (matched_pattern.ready() && !matched_pattern.empty() && matched_pattern[1].matched) {
1115 try {
1116 streamIdx = std::stoi(matched_pattern[1]);
1117 } catch (const std::exception& e) {
1118 JAMI_WARN("Error parsing stream index: %s", e.what());
1119 }
1120 }
1121 }
1122
1123 if (body_msg.find(PICT_FAST_UPDATE) != std::string_view::npos) {
1124 call.sendKeyframe(streamIdx);
1125 return true;
1126 } else if (body_msg.find(DEVICE_ORIENTATION) != std::string_view::npos) {
1127 static const std::regex ORIENTATION_REGEX("device_orientation=([-+]?[0-9]+)");
1128
1131
1132 if (matched_pattern.ready() && !matched_pattern.empty() && matched_pattern[1].matched) {
1133 try {
1134 int rotation = -std::stoi(matched_pattern[1]);
1135 while (rotation <= -180)
1136 rotation += 360;
1137 while (rotation > 180)
1138 rotation -= 360;
1139 JAMI_WARN("Rotate video %d deg.", rotation);
1140#ifdef ENABLE_VIDEO
1141 call.setRotation(streamIdx, rotation);
1142#endif
1143 } catch (const std::exception& e) {
1144 JAMI_WARN("Error parsing angle: %s", e.what());
1145 }
1146 return true;
1147 }
1148 } else if (body_msg.find(RECORDING_STATE) != std::string_view::npos) {
1149 static const std::regex REC_REGEX("recording_state=([0-1])");
1152
1153 if (matched_pattern.ready() && !matched_pattern.empty() && matched_pattern[1].matched) {
1154 try {
1155 bool state = std::stoi(matched_pattern[1]);
1156 call.peerRecording(state);
1157 } catch (const std::exception& e) {
1158 JAMI_WARN("Error parsing state remote recording: %s", e.what());
1159 }
1160 return true;
1161 }
1162 } else if (body_msg.find(MUTE_STATE) != std::string_view::npos) {
1163 static const std::regex REC_REGEX("mute_state=([0-1])");
1166
1167 if (matched_pattern.ready() && !matched_pattern.empty() && matched_pattern[1].matched) {
1168 try {
1169 bool state = std::stoi(matched_pattern[1]);
1170 call.peerMuted(state, streamIdx);
1171 } catch (const std::exception& e) {
1172 JAMI_WARN("Error parsing state remote mute: %s", e.what());
1173 }
1174 return true;
1175 }
1176 } else if (body_msg.find(VOICE_ACTIVITY) != std::string_view::npos) {
1177 static const std::regex REC_REGEX("voice_activity=([0-1])");
1180
1181 if (matched_pattern.ready() && !matched_pattern.empty() && matched_pattern[1].matched) {
1182 try {
1183 bool state = std::stoi(matched_pattern[1]);
1184 call.peerVoice(state);
1185 } catch (const std::exception& e) {
1186 JAMI_WARN("Error parsing state remote voice: %s", e.what());
1187 }
1188 return true;
1189 }
1190 }
1191 }
1192
1193 return false;
1194}
1195
1199static bool
1200transferCall(SIPCall& call, const std::string& refer_to)
1201{
1202 const auto& callId = call.getCallId();
1203 JAMI_WARN("[call:%s] Attempting to transfer to %s", callId.c_str(), refer_to.c_str());
1204 try {
1206 call.getAccountId(),
1208 Manager::instance().hangupCall(call.getAccountId(), callId);
1209 } catch (const std::exception& e) {
1210 JAMI_ERR("[call:%s] SIP transfer failed: %s", callId.c_str(), e.what());
1211 return false;
1212 }
1213 return true;
1214}
1215
1216static void
1218{
1219 const auto ret = pjsip_dlg_respond(inv->dlg, rdata, status_code, nullptr, nullptr, nullptr);
1220 if (ret != PJ_SUCCESS)
1221 JAMI_WARN("SIP: Failed to reply %d to request", status_code);
1222}
1223
1224static void
1226{
1227 static constexpr pj_str_t str_refer_to = CONST_PJ_STR("Refer-To");
1228
1229 if (auto* refer_to = static_cast<pjsip_generic_string_hdr*>(
1231 // RFC 3515, 2.4.2: reply bad request if no or too many refer-to header.
1232 if (static_cast<void*>(refer_to->next) == static_cast<void*>(&msg->hdr)
1235 transferCall(call, std::string(refer_to->hvalue.ptr, refer_to->hvalue.slen));
1236
1237 // RFC 3515, 2.4.4: we MUST handle the processing using NOTIFY msgs
1238 // But your current design doesn't permit that
1239 return;
1240 } else
1241 JAMI_ERR("[call:%s] REFER: too many Refer-To headers", call.getCallId().c_str());
1242 } else
1243 JAMI_ERR("[call:%s] REFER: no Refer-To header", call.getCallId().c_str());
1244
1246}
1247
1248static void
1254
1255static void
1257{
1258 if (!msg->body)
1259 return;
1260
1261 const std::string bodyText {static_cast<char*>(msg->body->data), msg->body->len};
1262 JAMI_DBG("[call:%s] NOTIFY body start - %p\n%s\n[call:%s] NOTIFY body end - %p",
1263 call.getCallId().c_str(),
1264 msg->body,
1265 bodyText.c_str(),
1266 call.getCallId().c_str(),
1267 msg->body);
1268
1269 // TODO
1270}
1271
1272static void
1274{
1275 auto call = getCallFromInvite(inv);
1276 if (not call)
1277 return;
1278
1279#ifdef DEBUG_SIP_REQUEST_MSG
1281#endif
1282
1283 // We process here only incoming request message
1284 if (tsx->role != PJSIP_ROLE_UAS or tsx->state != PJSIP_TSX_STATE_TRYING
1285 or event->body.tsx_state.type != PJSIP_EVENT_RX_MSG) {
1286 return;
1287 }
1288
1289 auto* const rdata = event->body.tsx_state.src.rdata;
1290 if (!rdata) {
1291 JAMI_ERROR("[INVITE:{:p}] SIP RX request without rx data", fmt::ptr(inv));
1292 return;
1293 }
1294
1295 auto* const msg = rdata->msg_info.msg;
1296 if (msg->type != PJSIP_REQUEST_MSG) {
1297 JAMI_ERROR("[INVITE:{:p}] SIP RX request without msg", fmt::ptr(inv));
1298 return;
1299 }
1300
1301 // Using method name to dispatch
1302 auto methodName = sip_utils::as_view(msg->line.req.method.name);
1303 JAMI_LOG("[INVITE:{:p}] RX SIP method {:d} ({:s})", fmt::ptr(inv), (int) msg->line.req.method.id, methodName);
1304
1305#ifdef DEBUG_SIP_REQUEST_MSG
1306 char msgbuf[1000];
1307 auto msgsize = pjsip_msg_print(msg, msgbuf, sizeof msgbuf);
1308 if (msgsize > 0)
1309 JAMI_LOG("{:s}", std::string_view(msgbuf, msgsize));
1310#endif // DEBUG_SIP_REQUEST_MSG
1311
1313 onRequestRefer(inv, rdata, msg, *call);
1315 onRequestInfo(inv, rdata, msg, *call);
1317 onRequestNotify(inv, rdata, msg, *call);
1321 if (msg->body)
1322 runOnMainThread([call, m = im::parseSipMessage(msg)]() mutable { call->onTextMessage(std::move(m)); });
1323 }
1324}
1325
1326#ifdef DEBUG_SIP_REQUEST_MSG
1327static void
1329{
1330 if (event->body.tsx_state.type != PJSIP_EVENT_RX_MSG)
1331 return;
1332
1333 const auto rdata = event->body.tsx_state.src.rdata;
1334 if (rdata == nullptr or rdata->msg_info.msg == nullptr)
1335 return;
1336
1337 const auto msg = rdata->msg_info.msg;
1338 if (msg->type != PJSIP_RESPONSE_MSG)
1339 return;
1340
1341 // Only handle the following responses
1342 switch (msg->line.status.code) {
1343 case PJSIP_SC_TRYING:
1344 case PJSIP_SC_RINGING:
1345 case PJSIP_SC_OK:
1346 break;
1347 default:
1348 return;
1349 }
1350
1351 JAMI_LOG("[INVITE:{:p}] SIP RX response: reason {:s}, status code {:d} {:s}",
1352 fmt::ptr(inv),
1353 sip_utils::as_view(msg->line.status.reason),
1354 msg->line.status.code,
1355 sip_utils::as_view(*pjsip_get_status_text(msg->line.status.code)));
1356
1358}
1359#endif
1360
1361int
1363{
1364 return mod_ua_.id;
1365}
1366
1367void
1369{
1370 if (inv == nullptr) {
1371 throw VoipLinkException("Invite session is unable to be null");
1372 }
1373 sdp_create_offer_cb(inv, nullptr);
1374}
1375
1376// Thread-safe DNS resolver callback mapping
1378{
1379public:
1381
1383 {
1384 std::lock_guard lk(mutex_);
1385 cbMap_.emplace(key, std::move(cb));
1386 }
1387
1389 {
1390 std::lock_guard lk(mutex_);
1391 auto it = cbMap_.find(key);
1392 if (it != cbMap_.end()) {
1393 it->second(status, addr);
1394 cbMap_.erase(it);
1395 }
1396 }
1397
1398private:
1399 std::mutex mutex_;
1400 std::map<uintptr_t, ResolveCallback> cbMap_;
1401};
1402
1403static SafeResolveCallbackMap&
1405{
1406 static SafeResolveCallbackMap map;
1407 return map;
1408}
1409
1410static void
1411resolver_callback(pj_status_t status, void* token, const struct pjsip_server_addresses* addr)
1412{
1413 getResolveCallbackMap().process((uintptr_t) token, status, addr);
1414}
1415
1416void
1418{
1419 // PJSIP limits hostname to be longer than PJ_MAX_HOSTNAME.
1420 // But, resolver prefix the given name by a string like "_sip._udp."
1421 // causing a check against PJ_MAX_HOSTNAME to be useless.
1422 // It's not easy to pre-determinate as it's implementation dependent.
1423 // So we just choose a security marge enough for most cases, preventing a crash later
1424 // in the call of pjsip_endpt_resolve().
1425 if (name.length() > (PJ_MAX_HOSTNAME - 12)) {
1426 JAMI_ERR("Hostname is too long");
1427 cb({});
1428 return;
1429 }
1430
1431 // extract port if name is in form "server:port"
1432 int port;
1434 const auto n = name.rfind(':');
1435 if (n != std::string::npos) {
1436 port = std::atoi(name.c_str() + n + 1);
1437 name_size = static_cast<pj_ssize_t>(n);
1438 } else {
1439 port = 0;
1440 name_size = static_cast<pj_ssize_t>(name.size());
1441 }
1442 JAMI_DBG("Attempt to resolve '%s' (port: %u)", name.c_str(), port);
1443
1445 /*.flag = */ 0,
1446 /*.type = */ type,
1447 /*.addr = */ {{(char*) name.c_str(), name_size}, port},
1448 };
1449
1450 const auto token = std::hash<std::string>()(name + std::to_string(type));
1452 .registerCallback(token, [=, cb = std::move(cb)](pj_status_t s, const pjsip_server_addresses* r) {
1453 try {
1454 if (s != PJ_SUCCESS || !r) {
1455 JAMI_WARN("Unable to resolve \"%s\" using pjsip_endpt_resolve, attempting getaddrinfo.",
1456 name.c_str());
1457 dht::ThreadPool::io().run([=, cb = std::move(cb)]() {
1458 auto ips = dhtnet::ip_utils::getAddrList(name.c_str());
1459 runOnMainThread(std::bind(cb, std::move(ips)));
1460 });
1461 } else {
1462 std::vector<dhtnet::IpAddr> ips;
1463 ips.reserve(r->count);
1464 for (unsigned i = 0; i < r->count; i++)
1465 ips.push_back(r->entry[i].addr);
1466 cb(ips);
1467 }
1468 } catch (const std::exception& e) {
1469 JAMI_ERR("Error resolving address: %s", e.what());
1470 cb({});
1471 }
1472 });
1473
1474 pjsip_endpt_resolve(endpt_, pool_.get(), &host_info, (void*) token, resolver_callback);
1475}
1476
1477#define RETURN_IF_NULL(A, ...) \
1478 if ((A) == NULL) { \
1479 JAMI_WARN(__VA_ARGS__); \
1480 return; \
1481 }
1482
1483#define RETURN_FALSE_IF_NULL(A, ...) \
1484 if ((A) == NULL) { \
1485 JAMI_WARN(__VA_ARGS__); \
1486 return false; \
1487 }
1488
1489void
1492 const std::string& host,
1493 std::string& addr,
1494 pj_uint16_t& port) const
1495{
1496 // Initialize the SIP port with the default SIP port
1498
1499 // Initialize the SIP address with the hostname
1501
1502 // Update address and port with active transport
1503 RETURN_IF_NULL(transport, "Transport is NULL in findLocalAddress, using local address %s :%d", addr.c_str(), port);
1504
1505 // get the transport manager associated with the SIP enpoint
1508 "Transport manager is NULL in findLocalAddress, using local address %s :%d",
1509 addr.c_str(),
1510 port);
1511
1512 const pj_str_t pjstring(CONST_PJ_STR(host));
1513
1514 auto tp_sel = getTransportSelector(transport);
1515 pjsip_tpmgr_fla2_param param = {transportType, &tp_sel, pjstring, PJ_FALSE, {nullptr, 0}, 0, nullptr};
1516 if (pjsip_tpmgr_find_local_addr2(tpmgr, pool_.get(), &param) != PJ_SUCCESS) {
1517 JAMI_WARN("Unable to retrieve local address and port from transport, using %s :%d", addr.c_str(), port);
1518 return;
1519 }
1520
1521 // Update local address based on the transport type
1522 addr = sip_utils::as_view(param.ret_addr);
1523
1524 // Determine the local port based on transport information
1525 port = param.ret_port;
1526}
1527
1528bool
1530 pjsip_transport* transport, pj_str_t* stunServerName, int stunPort, std::string& addr, pj_uint16_t& port) const
1531{
1532 // WARN: this code use pjstun_get_mapped_addr2 that works
1533 // in IPv4 only.
1534 // WARN: this function is blocking (network request).
1535
1536 // Initialize the sip port with the default SIP port
1538
1539 // Get Local IP address
1540 auto localIp = dhtnet::ip_utils::getLocalAddr(pj_AF_INET());
1541 if (not localIp) {
1542 JAMI_WARN("Failed to find local IP");
1543 return false;
1544 }
1545
1546 addr = localIp.toString();
1547
1548 // Update address and port with active transport
1549 RETURN_FALSE_IF_NULL(transport,
1550 "Transport is NULL in findLocalAddress, using local address %s:%u",
1551 addr.c_str(),
1552 port);
1553
1554 JAMI_DBG("STUN mapping of '%s:%u'", addr.c_str(), port);
1555
1560
1561 switch (stunStatus) {
1563 JAMI_ERROR("No response from STUN server {:s}", sip_utils::as_view(*stunServerName));
1564 return false;
1565
1567 JAMI_ERR("Different mapped addresses are returned by servers.");
1568 return false;
1569
1570 case PJ_SUCCESS:
1572 addr = dhtnet::IpAddr((const sockaddr_in&) mapped_addr).toString(true);
1573 JAMI_DEBUG("STUN server {:s} replied '{}'", sip_utils::as_view(*stunServerName), addr);
1574 return true;
1575
1576 default: // use given address, silent any not handled error
1577 JAMI_WARNING("Error from STUN server {:s}, using source address", sip_utils::as_view(*stunServerName));
1578 return false;
1579 }
1580}
1581#undef RETURN_IF_NULL
1582#undef RETURN_FALSE_IF_NULL
1583} // namespace jami
#define TRY(call, error)
const std::string & getCallId() const
Return a reference on the call id.
Definition call.h:111
std::string getAccountId() const
Definition call.cpp:144
std::vector< std::shared_ptr< T > > getAllAccounts() const
Get a list of account pointers of type T (baseclass Account)
Definition manager.h:763
std::shared_ptr< Call > newOutgoingCall(std::string_view toUrl, const std::string &accountId, const std::vector< libjami::MediaMap > &mediaList)
Create a new outgoing call.
Definition manager.cpp:3087
static LIBJAMI_TEST_EXPORT Manager & instance()
Definition manager.cpp:694
void incomingCall(const std::string &accountId, Call &call)
Handle incoming call and notify user.
Definition manager.cpp:1881
SIPVoIPLink & sipVoIPLink() const
Definition manager.cpp:3229
bool hangupCall(const std::string &accountId, const std::string &callId)
Functions which occur with a user's action Hangup the call.
Definition manager.cpp:1158
static std::vector< libjami::MediaMap > mediaAttributesToMediaMaps(const std::vector< MediaAttribute > &mediaAttrList)
static pjsip_module mod_presence_server
static constexpr auto ACCOUNT_TYPE
Definition sipaccount.h:49
std::vector< MediaAttribute > getMediaAttributeList() const override
Definition sipcall.cpp:2548
void sendKeyframe(int streamIdx=-1) override
Definition sipcall.cpp:1543
void peerMuted(bool state, int streamIdx) override
Definition sipcall.cpp:3591
void peerVoice(bool state) override
Definition sipcall.cpp:3614
void peerRecording(bool state) override
Definition sipcall.cpp:3574
void registerCallback(uintptr_t key, ResolveCallback &&cb)
void process(uintptr_t key, pj_status_t status, const pjsip_server_addresses *addr)
std::function< void(pj_status_t, const pjsip_server_addresses *)> ResolveCallback
static std::vector< MediaAttribute > getMediaAttributeListFromSdp(const pjmedia_sdp_session *sdpSession, bool ignoreDisabled=false)
Definition sdp.cpp:974
static void printSession(const pjmedia_sdp_session *session, const char *header, SdpDirection direction)
Log the given session.
Definition sdp.cpp:436
Manages the transports and receive callbacks from PJSIP.
#define JAMI_ERR(...)
Definition logger.h:230
#define JAMI_ERROR(formatstr,...)
Definition logger.h:243
#define JAMI_DBG(...)
Definition logger.h:228
#define JAMI_DEBUG(formatstr,...)
Definition logger.h:238
#define JAMI_WARN(...)
Definition logger.h:229
#define JAMI_WARNING(formatstr,...)
Definition logger.h:242
#define JAMI_LOG(formatstr,...)
Definition logger.h:237
#define JAMI_INFO(...)
Definition logger.h:227
std::map< std::string, std::string > parseSipMessage(const pjsip_msg *msg)
Parses given SIP message into a map where the key is the contents of the Content-Type header (along w...
constexpr std::string_view OPTIONS
Definition sip_utils.h:45
constexpr std::string_view INFO
Definition sip_utils.h:44
constexpr std::string_view MESSAGE
Definition sip_utils.h:43
constexpr std::string_view PUBLISH
Definition sip_utils.h:46
constexpr std::string_view REFER
Definition sip_utils.h:47
constexpr std::string_view NOTIFY
Definition sip_utils.h:48
static constexpr int DEFAULT_SIP_PORT
Definition sip_utils.h:51
void logMessageHeaders(const pjsip_hdr *hdr_list)
void addUserAgentHeader(const std::string &userAgent, pjsip_tx_data *tdata)
void addContactHeader(const std::string &contactHdr, pjsip_tx_data *tdata)
std::string_view stripSipUriPrefix(std::string_view sipUri)
std::string sip_strerror(pj_status_t code)
constexpr std::string_view as_view(const pj_str_t &str) noexcept
Definition sip_utils.h:105
std::string_view getPeerUserAgent(const pjsip_rx_data *rdata)
std::vector< std::string > getPeerAllowMethods(const pjsip_rx_data *rdata)
std::string parseDisplayName(const pjsip_name_addr *sip_name_addr)
constexpr const pj_str_t CONST_PJ_STR(T(&a)[N]) noexcept
Definition sip_utils.h:87
static pjsip_module mod_ua_
static void resolver_callback(pj_status_t status, void *token, const struct pjsip_server_addresses *addr)
void emitSignal(Args... args)
Definition jami_signal.h:64
static void on_rx_offer2(pjsip_inv_session *inv, struct pjsip_inv_on_rx_offer_cb_param *param)
static std::shared_ptr< SIPCall > getCallFromInvite(pjsip_inv_session *inv)
static void invite_session_state_changed_cb(pjsip_inv_session *inv, pjsip_event *e)
static void onRequestRefer(pjsip_inv_session *inv, pjsip_rx_data *rdata, pjsip_msg *msg, SIPCall &call)
static SafeResolveCallbackMap & getResolveCallbackMap()
static const pjmedia_sdp_session * get_active_local_sdp(pjsip_inv_session *inv)
static pjsip_endpoint * endpt_
static void replyToRequest(pjsip_inv_session *inv, pjsip_rx_data *rdata, int status_code)
constexpr pj_str_t STR_MESSAGE_ID
uint64_t from_hex_string(const std::string &str)
static void onRequestInfo(pjsip_inv_session *inv, pjsip_rx_data *rdata, pjsip_msg *msg, SIPCall &call)
static void sdp_media_update_cb(pjsip_inv_session *inv, pj_status_t status)
static void outgoing_request_forked_cb(pjsip_inv_session *inv, pjsip_event *e)
static pj_bool_t transaction_request_cb(pjsip_rx_data *rdata)
static void sdp_create_offer_cb(pjsip_inv_session *inv, pjmedia_sdp_session **p_offer)
static pj_bool_t transaction_response_cb(pjsip_rx_data *rdata)
static bool handleMediaControl(SIPCall &call, pjsip_msg_body *body)
static pj_bool_t handleIncomingOptions(pjsip_rx_data *rdata)
static void tp_state_callback(pjsip_transport *tp, pjsip_transport_state state, const pjsip_transport_state_info *info)
static void transaction_state_changed_cb(pjsip_inv_session *inv, pjsip_transaction *tsx, pjsip_event *e)
@ MEDIA_AUDIO
Definition media_codec.h:46
@ MEDIA_VIDEO
Definition media_codec.h:47
static bool transferCall(SIPCall &call, const std::string &refer_to)
Helper function to process refer function on call transfer.
static pj_status_t try_respond_stateless(pjsip_endpoint *endpt, pjsip_rx_data *rdata, int st_code, const pj_str_t *st_text, const pjsip_hdr *hdr_list, const pjsip_msg_body *body)
static void onRequestNotify(pjsip_inv_session *, pjsip_rx_data *, pjsip_msg *msg, SIPCall &call)
static const pjmedia_sdp_session * get_active_remote_sdp(pjsip_inv_session *inv)
static pj_status_t reinvite_received_cb(pjsip_inv_session *inv, const pjmedia_sdp_session *offer, pjsip_rx_data *rdata)
static void runOnMainThread(Callback &&cb)
Definition manager.h:930
static bool is_uninitialized(std::weak_ptr< T > const &weak)
match_results< string_view::const_iterator > svmatch
bool regex_search(string_view sv, svmatch &m, const regex &e, regex_constants::match_flag_type flags=regex_constants::match_default)
A SIP Account specify SIP specific functions and object = SIPCall/SIPVoIPLink)
SIPCall are SIP implementation of a normal Call.