Ring Daemon 16.0.0
Loading...
Searching...
No Matches
sipvoiplink.cpp
Go to the documentation of this file.
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 "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
35
36#ifdef ENABLE_VIDEO
38#include "client/videomanager.h"
39#endif
40
41#include "pres_sub_server.h"
42
44#include "string_utils.h"
45#include "logger.h"
46
47#include <dhtnet/ip_utils.h>
48#include <opendht/thread_pool.h>
49
50#include <pjsip/sip_endpoint.h>
51#include <pjsip/sip_uri.h>
52
53#include <pjsip-simple/presence.h>
54#include <pjsip-simple/publish.h>
55
56// Only PJSIP 2.10 is supported.
57#if PJ_VERSION_NUM < (2 << 24 | 10 << 16)
58#error "Unsupported PJSIP version (requires version 2.10+)"
59#endif
60
61#include <istream>
62#include <algorithm>
63#include <regex>
64
65namespace jami {
66
68
69/**************** EXTERN VARIABLES AND FUNCTIONS (callbacks) **************************/
70
73
78 pjsip_event* e);
79// Called when an SDP offer is found in answer. This will occur
80// when we send an empty invite (no SDP). In this case, we should
81// expect an offer in a the 200 OK message
83// Called when a re-invite is received
87// Called to request an SDP offer if the peer sent an invite or
88// a re-invite with no SDP. In this, we must provide an offer in
89// the answer (200 OK) if we accept the call
91// Called to report media (SDP) negotiation result
93
94static std::shared_ptr<SIPCall> getCallFromInvite(pjsip_inv_session* inv);
95#ifdef DEBUG_SIP_REQUEST_MSG
97#endif
98
99static pj_bool_t
101{
103
105 if (dlg) {
106 JAMI_INFO("Processing in-dialog option request");
108 JAMI_ERR("Failed to create in-dialog response for option request");
109 return PJ_FALSE;
110 }
111 } else {
112 JAMI_INFO("Processing out-of-dialog option request");
114 JAMI_ERR("Failed to create out-of-dialog response for option request");
115 return PJ_FALSE;
116 }
117 }
118
119#define ADD_HDR(hdr) \
120 do { \
121 const pjsip_hdr* cap_hdr = hdr; \
122 if (cap_hdr) \
123 pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*) pjsip_hdr_clone(tdata->pool, cap_hdr)); \
124 } while (0)
125#define ADD_CAP(cap) ADD_HDR(pjsip_endpt_get_capability(endpt_, cap, NULL));
126
131
132 if (dlg) {
134 JAMI_ERR("Failed to send in-dialog response for option request");
135 return PJ_FALSE;
136 }
137
138 JAMI_INFO("Sent in-dialog response for option request");
139 return PJ_TRUE;
140 }
141
144
147 JAMI_ERR("Failed to send out-of-dialog response for option request");
148 return PJ_FALSE;
149 }
150
151 JAMI_INFO("Sent out-of-dialog response for option request");
152 return PJ_TRUE;
153}
154
155// return PJ_FALSE so that eventually other modules will handle these requests
156// TODO: move Voicemail to separate module
157static pj_bool_t
159{
161
162 if (!dlg)
163 return PJ_FALSE;
164
166
167 if (!tsx or tsx->method.id != PJSIP_INVITE_METHOD)
168 return PJ_FALSE;
169
170 if (tsx->status_code / 100 == 2) {
176
177 if (rdata->msg_info.cseq) {
178 pjsip_dlg_create_request(dlg, &pjsip_ack_method, rdata->msg_info.cseq->cseq, &tdata);
180 }
181 }
182
183 return PJ_FALSE;
184}
185
186static pj_status_t
189 int st_code,
190 const pj_str_t* st_text,
191 const pjsip_hdr* hdr_list,
192 const pjsip_msg_body* body)
193{
194 /* Check that no UAS transaction has been created for this request.
195 * If UAS transaction has been created for this request, application
196 * MUST send the response statefully using that transaction.
197 */
199 return pjsip_endpt_respond_stateless(endpt, rdata, st_code, st_text, hdr_list, body);
200 else
201 JAMI_ERR("Transaction has been created for this request, send response "
202 "statefully instead");
203
204 return !PJ_SUCCESS;
205}
206
207template<typename T>
208bool
209is_uninitialized(std::weak_ptr<T> const& weak)
210{
211 using wt = std::weak_ptr<T>;
212 return !weak.owner_before(wt {}) && !wt {}.owner_before(weak);
213}
214
215static pj_bool_t
217{
218 if (!rdata or !rdata->msg_info.msg) {
219 JAMI_ERR("rx_data is NULL");
220 return PJ_FALSE;
221 }
222
223 pjsip_method* method = &rdata->msg_info.msg->line.req.method;
224
225 if (!method) {
226 JAMI_ERR("method is NULL");
227 return PJ_FALSE;
228 }
229
231 return PJ_FALSE;
232
233 if (!rdata->msg_info.to or !rdata->msg_info.from or !rdata->msg_info.via) {
234 JAMI_ERR("Missing From, To or Via fields");
235 return PJ_FALSE;
236 }
237
238 const auto sip_to_uri = reinterpret_cast<pjsip_sip_uri*>(
239 pjsip_uri_get_uri(rdata->msg_info.to->uri));
240 const auto sip_from_uri = reinterpret_cast<pjsip_sip_uri*>(
241 pjsip_uri_get_uri(rdata->msg_info.from->uri));
242 const pjsip_host_port& sip_via = rdata->msg_info.via->sent_by;
243
244 if (!sip_to_uri or !sip_from_uri or !sip_via.host.ptr) {
245 JAMI_ERR("NULL URI");
246 return PJ_FALSE;
247 }
248
249 std::string_view toUsername(sip_to_uri->user.ptr, sip_to_uri->user.slen);
250 std::string_view toHost(sip_to_uri->host.ptr, sip_to_uri->host.slen);
251 std::string_view viaHostname(sip_via.host.ptr, sip_via.host.slen);
252 const std::string_view remote_user(sip_from_uri->user.ptr, sip_from_uri->user.slen);
253 const std::string_view remote_hostname(sip_from_uri->host.ptr, sip_from_uri->host.slen);
254 std::string peerNumber;
255 if (not remote_user.empty() and not remote_hostname.empty())
257 else {
261 tmp,
264 }
265
266 auto transport = Manager::instance().sipVoIPLink().sipTransportBroker->addTransport(
267 rdata->tp_info.transport);
268
269 std::shared_ptr<SIPAccountBase> account;
270 // If transport account is default-constructed, guessing account is allowed
271 const auto& waccount = transport ? transport->getAccount() : std::weak_ptr<SIPAccountBase> {};
276 if (not account)
277 return PJ_FALSE;
278 if (not transport and account->getAccountType() == SIPAccount::ACCOUNT_TYPE) {
279 if (not(transport = std::static_pointer_cast<SIPAccount>(account)->getTransport())) {
280 JAMI_ERR("No suitable transport to answer this call.");
281 return PJ_FALSE;
282 }
283 JAMI_WARN("Using transport from account.");
284 }
285 } else if (!(account = waccount.lock())) {
286 JAMI_ERR("Dropping SIP request: account is expired.");
287 return PJ_FALSE;
288 }
289
290 pjsip_msg_body* body = rdata->msg_info.msg->body;
291
292 if (method->id == PJSIP_OTHER_METHOD) {
293 std::string_view request = sip_utils::as_view(method->name);
294
295 if (request.find(sip_utils::SIP_METHODS::NOTIFY) != std::string_view::npos) {
296 if (body and body->data) {
297 std::string_view body_view(static_cast<char*>(body->data), body->len);
298 auto pos = body_view.find("Voice-Message: ");
299 if (pos != std::string_view::npos) {
300 int newCount {0};
301 int oldCount {0};
302 int urgentCount {0};
303 std::string sp(body_view.substr(pos));
304 int ret = sscanf(sp.c_str(),
305 "Voice-Message: %d/%d (%d/",
306 &newCount,
307 &oldCount,
308 &urgentCount);
309
310 // According to rfc3842
311 // urgent messages are optional
312 if (ret >= 2)
314 newCount,
315 oldCount,
317 }
318 }
319 } else if (request.find(sip_utils::SIP_METHODS::MESSAGE) != std::string_view::npos) {
320 // Reply 200 immediately (RFC 3428, ch. 7)
321 try_respond_stateless(endpt_, rdata, PJSIP_SC_OK, nullptr, nullptr, nullptr);
322 // Process message content in case of multi-part body
323 auto payloads = im::parseSipMessage(rdata->msg_info.msg);
324 if (payloads.size() > 0) {
325 constexpr pj_str_t STR_MESSAGE_ID = jami::sip_utils::CONST_PJ_STR("Message-ID");
327 pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &STR_MESSAGE_ID, nullptr);
328 std::string id = {};
329 if (!msgId) {
330 // Supports IMDN message format https://tools.ietf.org/html/rfc5438#section-7.1.1.3
332 "imdn.Message-ID");
334 pjsip_msg_find_hdr_by_name(rdata->msg_info.msg,
336 nullptr);
337 }
338 if (msgId)
339 id = std::string(msgId->hvalue.ptr, msgId->hvalue.slen);
340
341 if (not id.empty()) {
342 try {
343 // Mark message as treated
344 auto intid = from_hex_string(id);
345 auto acc = std::dynamic_pointer_cast<JamiAccount>(account);
346 if (acc and acc->isMessageTreated(intid))
347 return PJ_FALSE;
348 } catch (...) {
349 }
350 }
351 account->onTextMessage(id, peerNumber, transport->getTlsInfos().peerCert, payloads);
352 }
353 return PJ_FALSE;
354 }
355
357
358 return PJ_FALSE;
359 } else if (method->id == PJSIP_OPTIONS_METHOD) {
361 } else if (method->id != PJSIP_INVITE_METHOD && method->id != PJSIP_ACK_METHOD) {
363 return PJ_FALSE;
364 }
365
366 if (method->id == PJSIP_INVITE_METHOD) {
367 // Log headers of received INVITE
368 JAMI_INFO("Received a SIP INVITE request");
369 sip_utils::logMessageHeaders(&rdata->msg_info.msg->hdr);
370 }
371
372 pjmedia_sdp_session* r_sdp {nullptr};
373 if (body) {
374 if (pjmedia_sdp_parse(rdata->tp_info.pool, (char*) body->data, body->len, &r_sdp)
375 != PJ_SUCCESS) {
376 JAMI_WARN("Failed to parse the SDP in offer");
377 r_sdp = nullptr;
378 }
379 }
380
381 if (not account->hasActiveCodec(MEDIA_AUDIO)) {
383 return PJ_FALSE;
384 }
385
386 // Verify that we can handle the request
387 unsigned options = 0;
388
390 JAMI_ERR("Unable to verify INVITE request in secure dialog.");
392 return PJ_FALSE;
393 }
394
395 // Build the initial media using the remote offer.
397
398 // To enable video, it must be enabled in the remote and locally (i.e. in the account)
399 for (auto& media : localMediaList) {
400 if (media.type_ == MediaType::MEDIA_VIDEO) {
401 media.enabled_ &= account->isVideoEnabled();
402 }
403 }
404
405 auto call = account->newIncomingCall(std::string(remote_user),
407 transport);
408 if (!call) {
409 return PJ_FALSE;
410 }
411
412 call->setPeerUaVersion(sip_utils::getPeerUserAgent(rdata));
413 // The username can be used to join specific calls in conversations
414 call->toUsername(std::string(toUsername));
415
416 // FIXME: for now, use the same address family as the SIP transport
418 pjsip_transport_get_type_from_flag(transport->get()->flag));
419
420 dhtnet::IpAddr addrSdp;
421 if (account->getUPnPActive()) {
422 /* use UPnP addr, or published addr if its set */
423 addrSdp = account->getPublishedSameasLocal() ? account->getUPnPIpAddress()
424 : account->getPublishedIpAddress();
425 } else {
426 addrSdp = account->isStunEnabled() or (not account->getPublishedSameasLocal())
427 ? account->getPublishedIpAddress()
428 : dhtnet::ip_utils::getInterfaceAddr(account->getLocalInterface(), family);
429 }
430
431 /* fallback on local address */
432 if (not addrSdp)
433 addrSdp = dhtnet::ip_utils::getInterfaceAddr(account->getLocalInterface(), family);
434
435 // Try to obtain display name from From: header first, fallback on Contact:
437 if (peerDisplayName.empty()) {
438 if (auto hdr = static_cast<const pjsip_contact_hdr*>(
439 pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_CONTACT, nullptr))) {
441 }
442 }
443
444 call->setPeerNumber(peerNumber);
445 call->setPeerUri(account->getToUri(peerNumber));
446 call->setPeerDisplayName(peerDisplayName);
448 call->getSDP().setPublishedIP(addrSdp);
449 call->setPeerAllowMethods(sip_utils::getPeerAllowMethods(rdata));
450
451 // Set the temporary media list. Might change when we receive
452 // the accept from the client.
453 if (r_sdp != nullptr) {
454 call->getSDP().setReceivedOffer(r_sdp);
455 }
456
457 pjsip_dialog* dialog = nullptr;
459 != PJ_SUCCESS) {
460 JAMI_ERR("Unable to create UAS");
461 call.reset();
463 rdata,
465 nullptr,
466 nullptr,
467 nullptr);
468 return PJ_FALSE;
469 }
470
473 JAMI_ERR("Unable to set transport for dialog");
474 if (dialog)
476 return PJ_FALSE;
477 }
478
479 pjsip_inv_session* inv = nullptr;
480 // Create UAS for the invite.
481 // The SDP is not set here, it will be done when the call is
482 // accepted and the media attributes of the answer are known.
484 if (!inv) {
485 JAMI_ERR("Call invite is not initialized");
487 return PJ_FALSE;
488 }
489
490 // dialog is now owned by invite
492
493 inv->mod_data[mod_ua_.id] = call.get();
494 call->setInviteSession(inv);
495
496 // Check whether Replaces header is present in the request and process accordingly.
498 pjsip_tx_data* response;
499
501 JAMI_ERR("Something wrong with Replaces request.");
502 call.reset();
503
504 // Something wrong with the Replaces header.
505 if (response) {
507 pjsip_get_response_addr(response->pool, rdata, &res_addr);
509 } else {
511 }
512
513 return PJ_FALSE;
514 }
515
516 // Check if call has been transferred
517 pjsip_tx_data* tdata = nullptr;
518
519 if (pjsip_inv_initial_answer(call->inviteSession_.get(),
520 rdata,
522 NULL,
523 NULL,
524 &tdata)
525 != PJ_SUCCESS) {
526 JAMI_ERR("Unable to create answer TRYING");
527 return PJ_FALSE;
528 }
529
530 // Add user-agent header
531 sip_utils::addUserAgentHeader(account->getUserAgentName(), tdata);
532
533 if (pjsip_inv_send_msg(call->inviteSession_.get(), tdata) != PJ_SUCCESS) {
534 JAMI_ERR("Unable to send msg TRYING");
535 return PJ_FALSE;
536 }
537
538 call->setState(Call::ConnectionState::TRYING);
539
540 if (pjsip_inv_answer(call->inviteSession_.get(), PJSIP_SC_RINGING, NULL, NULL, &tdata)
541 != PJ_SUCCESS) {
542 JAMI_ERR("Unable to create answer RINGING");
543 return PJ_FALSE;
544 }
545
546 sip_utils::addContactHeader(call->getContactHeader(), tdata);
547 if (pjsip_inv_send_msg(call->inviteSession_.get(), tdata) != PJ_SUCCESS) {
548 JAMI_ERR("Unable to send msg RINGING");
549 return PJ_FALSE;
550 }
551
552 call->setState(Call::ConnectionState::RINGING);
553
554 Manager::instance().incomingCall(account->getAccountID(), *call);
555
556 if (replaced_dlg) {
557 // Get the INVITE session associated with the replaced dialog.
559
560 // Disconnect the "replaced" INVITE session.
562 && tdata) {
564 }
565
566 // Close call at application level
568 replacedCall->hangup(PJSIP_SC_OK);
569 }
570
571 return PJ_FALSE;
572}
573
574static void
577 const pjsip_transport_state_info* info)
578{
579 if (auto& broker = Manager::instance().sipVoIPLink().sipTransportBroker)
580 broker->transportStateChanged(tp, state, info);
581 else
582 JAMI_ERR("SIPVoIPLink with invalid SipTransportBroker");
583}
584
585/*************************************************************************************************/
586
589{
590 return endpt_;
591}
592
595{
596 return &mod_ua_;
597}
598
601{
602 return pool_.get();
603}
604
607{
608 return &cp_;
609}
610
612 : pool_(nullptr, pj_pool_release)
613{
614#define TRY(ret) \
615 do { \
616 if ((ret) != PJ_SUCCESS) \
617 throw VoipLinkException(#ret " failed"); \
618 } while (0)
619
621 pool_.reset(pj_pool_create(&cp_.factory, PACKAGE, 64 * 1024, 4096, nullptr));
622 if (!pool_)
623 throw VoipLinkException("UserAgent: Unable to initialize memory pool");
624
625 TRY(pjsip_endpt_create(&cp_.factory, pj_gethostname()->ptr, &endpt_));
626
627 auto ns = dhtnet::ip_utils::getLocalNameservers();
628 if (not ns.empty()) {
629 std::vector<pj_str_t> dns_nameservers(ns.size());
630 std::vector<pj_uint16_t> dns_ports(ns.size());
631 for (unsigned i = 0, n = ns.size(); i < n; i++) {
632 char hbuf[NI_MAXHOST];
633 if (auto ret = getnameinfo((sockaddr*) &ns[i],
634 ns[i].getLength(),
635 hbuf,
636 sizeof(hbuf),
637 nullptr,
638 0,
640 JAMI_WARN("Error printing SIP nameserver: %s", gai_strerror(ret));
641 } else {
642 JAMI_DBG("Using SIP nameserver: %s", hbuf);
643 pj_strdup2(pool_.get(), &dns_nameservers[i], hbuf);
644 dns_ports[i] = ns[i].getPort();
645 }
646 }
649 JAMI_WARN("Error creating SIP DNS resolver: %s", sip_utils::sip_strerror(ret).c_str());
650 } else {
652 dns_nameservers.size(),
653 dns_nameservers.data(),
654 dns_ports.data())) {
655 JAMI_WARN("Error setting SIP DNS servers: %s", sip_utils::sip_strerror(ret).c_str());
656 } else {
658 JAMI_WARN("Error setting PJSIP DNS resolver: %s",
660 }
661 }
662 }
663 }
664
666
668 if (status != PJ_SUCCESS)
669 JAMI_ERR("Unable to set transport callback: %s", sip_utils::sip_strerror(status).c_str());
670
673 TRY(pjsip_replaces_init_module(endpt_)); // See the Replaces specification in RFC 3891
675
676 // Initialize and register ring module
678 mod_ua_.id = -1;
680 mod_ua_.on_rx_request = &transaction_request_cb;
681 mod_ua_.on_rx_response = &transaction_response_cb;
683
686
687 // presence/publish management
690
691 static const pjsip_inv_callback inv_cb = {
695 nullptr /* on_rx_offer */,
700 nullptr /* on_send_ack */,
701 nullptr /* on_redirected */,
702 };
704
705 static constexpr pj_str_t allowed[] = {
706 CONST_PJ_STR(sip_utils::SIP_METHODS::INFO),
710 };
711
713 &mod_ua_,
715 nullptr,
717 allowed);
718
719 static constexpr pj_str_t text_plain = CONST_PJ_STR("text/plain");
721
722 static constexpr pj_str_t accepted = CONST_PJ_STR("application/sdp");
724
725 static constexpr pj_str_t iscomposing = CONST_PJ_STR("application/im-iscomposing+xml");
727
729#undef TRY
730
731 sipThread_ = std::thread([this] {
732 while (running_)
733 handleEvents();
734 });
735
736 JAMI_DBG("SIPVoIPLink@%p", this);
737}
738
740
741void
743{
744 JAMI_DBG("Shutting down SIPVoIPLink@%p…", this);
745 // Remaining calls should not happen as possible upper callbacks
746 // may be called and another instance of SIPVoIPLink can be re-created!
747
748 if (not Manager::instance().callFactory.empty(Call::LinkType::SIP))
749 JAMI_ERR("%zu SIP calls remains!",
750 Manager::instance().callFactory.callCount(Call::LinkType::SIP));
751
752 sipTransportBroker->shutdown();
754
755 running_ = false;
756 sipThread_.join();
758 pool_.reset();
760 sipTransportBroker.reset();
761
762 JAMI_DBG("SIPVoIPLink@%p shutdown successfully completed", this);
763}
764
765std::shared_ptr<SIPAccountBase>
767 std::string_view server,
768 std::string_view fromUri) const
769{
770 JAMI_LOG("username = {}, server = {}, from = {}",
771 userName,
772 server,
773 fromUri);
774 // Attempt to find the account id from username and server name by full match
775
776 std::shared_ptr<SIPAccountBase> result;
777 std::shared_ptr<SIPAccountBase> IP2IPAccount;
779
780 // SIP accounts
781 for (const auto& account : Manager::instance().getAllAccounts<SIPAccount>()) {
782 const MatchRank match(account->matches(userName, server));
783
784 // return right away if this is a full match
785 if (match == MatchRank::FULL) {
786 return account;
787 } else if (match > best) {
788 best = match;
789 result = account;
790 } else if (!IP2IPAccount && account->isIP2IP()) {
791 // Allow IP2IP calls if an account exists for this type of calls
793 }
794 }
795
796 return result ? result : IP2IPAccount;
797}
798
799// Called from EventThread::run (not main thread)
800void
802{
803 const pj_time_val timeout = {1, 0};
804 if (auto ret = pjsip_endpt_handle_events(endpt_, &timeout))
805 JAMI_ERR("pjsip_endpt_handle_events failed with error %s",
807}
808
809void
811{
812 JAMI_DEBUG("Register new keepalive timer {:d} with delay {:d}", timer.id, delay.sec);
813
814 if (timer.id == -1)
815 JAMI_WARN("Timer already scheduled");
816
817 switch (pjsip_endpt_schedule_timer(endpt_, &timer, &delay)) {
818 case PJ_SUCCESS:
819 break;
820
821 default:
822 JAMI_ERR("Unable to schedule new timer in pjsip endpoint");
823
824 /* fallthrough */
825 case PJ_EINVAL:
826 JAMI_ERR("Invalid timer or delay entry");
827 break;
828
829 case PJ_EINVALIDOP:
830 JAMI_ERR("Invalid timer entry, maybe already scheduled");
831 break;
832 }
833}
834
835void
840
842// Private functions
844
845static std::shared_ptr<SIPCall>
847{
848 if (auto call_ptr = static_cast<SIPCall*>(inv->mod_data[mod_ua_.id]))
849 return std::static_pointer_cast<SIPCall>(call_ptr->shared_from_this());
850 return nullptr;
851}
852
853static void
855{
856 if (inv == nullptr or ev == nullptr) {
857 throw VoipLinkException("Unexpected null pointer");
858 }
859
860 auto call = getCallFromInvite(inv);
861 if (not call)
862 return;
863
864 if (ev->type != PJSIP_EVENT_TSX_STATE and ev->type != PJSIP_EVENT_TX_MSG
865 and ev->type != PJSIP_EVENT_RX_MSG) {
866 JAMI_WARN("[call:%s] INVITE@%p state changed to %d (%s): unexpected event type %d",
867 call->getCallId().c_str(),
868 inv,
869 inv->state,
871 ev->type);
872 return;
873 }
874
875 decltype(pjsip_transaction::status_code) status_code = 0;
876
877 if (ev->type == PJSIP_EVENT_TSX_STATE) {
878 const auto tsx = ev->body.tsx_state.tsx;
879 status_code = tsx ? tsx->status_code : PJSIP_SC_NOT_FOUND;
880 const pj_str_t* description = pjsip_get_status_text(status_code);
881
882 JAMI_LOG("[call:{}] INVITE@{:p} state changed to {:d} ({}): cause={:d}, tsx@{:p} status {:d} ({:s})",
883 call->getCallId(),
884 fmt::ptr(inv),
885 (int)inv->state,
887 (int)inv->cause,
888 fmt::ptr(tsx),
890 sip_utils::as_view(*description));
891 } else if (ev->type == PJSIP_EVENT_TX_MSG) {
892 JAMI_LOG("[call:{}] INVITE@{:p} state changed to {:d} ({:s}): cause={:d} (TX_MSG)",
893 call->getCallId(),
894 fmt::ptr(inv),
895 (int)inv->state,
897 (int)inv->cause);
898 }
899 pjsip_rx_data* rdata {nullptr};
900 if (ev->type == PJSIP_EVENT_RX_MSG) {
901 rdata = ev->body.rx_msg.rdata;
902 } else if (ev->type == PJSIP_EVENT_TSX_STATE and ev->body.tsx_state.type == PJSIP_EVENT_RX_MSG) {
903 rdata = ev->body.tsx_state.src.rdata;
904 }
905 if (rdata != nullptr) {
906 call->setPeerUaVersion(sip_utils::getPeerUserAgent(rdata));
908 if (not methods.empty()) {
909 call->setPeerAllowMethods(std::move(methods));
910 }
911 }
912
913 switch (inv->state) {
916 call->onPeerRinging();
917 break;
918
920 // After we sent or received a ACK - The connection is established
921 call->onAnswered();
922 break;
923
925 switch (inv->cause) {
926 // When a peer's device replies busy
928 call->onBusyHere();
929 break;
930 // When the peer manually refuse the call
931 case PJSIP_SC_DECLINE:
933 if (inv->role != PJSIP_ROLE_UAC)
934 break;
935 // close call
936 call->onClosed();
937 break;
938 // The call terminates normally - BYE / CANCEL
939 case PJSIP_SC_OK:
941 call->onClosed();
942 break;
943
944 // Error/unhandled conditions
945 default:
946 call->onFailure(inv->cause);
947 break;
948 }
949 break;
950
951 default:
952 break;
953 }
954}
955
956static void
958{
959 if (not param or not param->offer) {
960 JAMI_ERR("Invalid offer");
961 return;
962 }
963
964 auto call = getCallFromInvite(inv);
965 if (not call)
966 return;
967
968 // This callback is called whenever a new media offer is found in a
969 // SIP message, typically in a re-invite and in a '200 OK' (as a
970 // response to an empty invite).
971 // Here we only handle the second case. The first case is handled
972 // in reinvite_received_cb.
973 if (inv->cause != PJSIP_SC_OK) {
974 // Silently ignore if it's not a '200 OK'
975 return;
976 }
977
978 if (auto call = getCallFromInvite(inv)) {
979 if (auto const& account = call->getAccount().lock()) {
980 call->onReceiveOfferIn200OK(param->offer);
981 }
982 }
983}
984
985static pj_status_t
987{
988 if (!offer)
989 return !PJ_SUCCESS;
990 if (auto call = getCallFromInvite(inv)) {
991 if (auto const& account = call->getAccount().lock()) {
992 return call->onReceiveReinvite(offer, rdata);
993 }
994 }
995
996 // Return success if there is no matching call. The re-invite
997 // should be ignored.
998 return PJ_SUCCESS;
999}
1000
1001static void
1003{
1004 auto call = getCallFromInvite(inv);
1005 if (not call)
1006 return;
1007
1008 auto account = call->getSIPAccount();
1009 if (not account) {
1010 JAMI_ERR("No account detected");
1011 return;
1012 }
1013
1014 if (account->isEmptyOffersEnabled()) {
1015 // Skip if the client wants to send an empty offer.
1016 JAMI_DBG("Client requested to send an empty offer (no SDP)");
1017 return;
1018 }
1019
1020 auto family = pj_AF_INET();
1021 // FIXME: for now, use the same address family as the SIP transport
1022 if (auto dlg = inv->dlg) {
1023 if (dlg->tp_sel.type == PJSIP_TPSELECTOR_TRANSPORT) {
1024 if (auto tr = dlg->tp_sel.u.transport)
1025 family = tr->local_addr.addr.sa_family;
1026 } else if (dlg->tp_sel.type == PJSIP_TPSELECTOR_TRANSPORT) {
1027 if (auto tr = dlg->tp_sel.u.listener)
1028 family = tr->local_addr.addr.sa_family;
1029 }
1030 }
1031 auto ifaceAddr = dhtnet::ip_utils::getInterfaceAddr(account->getLocalInterface(), family);
1032
1033 dhtnet::IpAddr address;
1034 if (account->getUPnPActive()) {
1035 /* use UPnP addr, or published addr if it's set */
1036 address = account->getPublishedSameasLocal() ? account->getUPnPIpAddress()
1037 : account->getPublishedIpAddress();
1038 } else {
1039 address = account->getPublishedSameasLocal() ? ifaceAddr : account->getPublishedIpAddress();
1040 }
1041
1042 /* fallback on local address */
1043 if (not address)
1044 address = ifaceAddr;
1045
1046 auto& sdp = call->getSDP();
1047 sdp.setPublishedIP(address);
1048
1049 // This list should be provided by the client. Kept for backward compatibility.
1050 auto const& mediaList = call->getMediaAttributeList();
1051 if (mediaList.empty()) {
1052 throw VoipLinkException("Unexpected empty media attribute list");
1053 }
1054
1055 JAMI_DBG("Creating a SDP offer using the following media:");
1056 for (auto const& media : mediaList) {
1057 JAMI_DBG("[call %s] Media %s", call->getCallId().c_str(), media.toString(true).c_str());
1058 }
1059
1060 const bool created = sdp.createOffer(mediaList);
1061
1062 if (created and p_offer != nullptr)
1063 *p_offer = sdp.getLocalSdpSession();
1064}
1065
1066static const pjmedia_sdp_session*
1068{
1070
1072 JAMI_ERR("Active remote not present");
1073 return nullptr;
1074 }
1075
1077 JAMI_ERR("Invalid remote SDP session");
1078 return nullptr;
1079 }
1080
1081 return sdp_session;
1082}
1083
1084static const pjmedia_sdp_session*
1086{
1088
1090 JAMI_ERR("Active local not present");
1091 return nullptr;
1092 }
1093
1095 JAMI_ERR("Invalid local SDP session");
1096 return nullptr;
1097 }
1098
1099 return sdp_session;
1100}
1101
1102// This callback is called after SDP offer/answer session has completed.
1103static void
1105{
1106 auto call = getCallFromInvite(inv);
1107 if (not call)
1108 return;
1109
1110 JAMI_DBG("[call:%s] INVITE@%p media update: status %d", call->getCallId().c_str(), inv, status);
1111
1112 if (status != PJ_SUCCESS) {
1113 const int reason = inv->state != PJSIP_INV_STATE_NULL
1116 : 0;
1117
1118 JAMI_WARN("[call:%s] SDP offer failed, reason %d", call->getCallId().c_str(), reason);
1119
1120 call->hangup(reason);
1121 return;
1122 }
1123
1124 // Fetch SDP data from request
1125 const auto localSDP = get_active_local_sdp(inv);
1126 const auto remoteSDP = get_active_remote_sdp(inv);
1127
1128 // Update our SDP manager
1129 auto& sdp = call->getSDP();
1130 sdp.setActiveLocalSdpSession(localSDP);
1131 if (localSDP != nullptr) {
1132 Sdp::printSession(localSDP, "Local active session:", sdp.getSdpDirection());
1133 }
1134
1135 sdp.setActiveRemoteSdpSession(remoteSDP);
1136 if (remoteSDP != nullptr) {
1137 Sdp::printSession(remoteSDP, "Remote active session:", sdp.getSdpDirection());
1138 }
1139
1140 call->onMediaNegotiationComplete();
1141}
1142
1143static void
1146
1147static bool
1149{
1150 /*
1151 * Incoming INFO request for media control.
1152 */
1153 constexpr pj_str_t STR_APPLICATION = CONST_PJ_STR("application");
1154 constexpr pj_str_t STR_MEDIA_CONTROL_XML = CONST_PJ_STR("media_control+xml");
1155
1156 if (body->len and pj_stricmp(&body->content_type.type, &STR_APPLICATION) == 0
1157 and pj_stricmp(&body->content_type.subtype, &STR_MEDIA_CONTROL_XML) == 0) {
1158 auto body_msg = std::string_view((char*) body->data, (size_t) body->len);
1159
1160 /* Apply and answer the INFO request */
1161 static constexpr auto PICT_FAST_UPDATE = "picture_fast_update"sv;
1162 static constexpr auto STREAM_ID = "stream_id"sv;
1163 static constexpr auto DEVICE_ORIENTATION = "device_orientation"sv;
1164 static constexpr auto RECORDING_STATE = "recording_state"sv;
1165 static constexpr auto MUTE_STATE = "mute_state"sv;
1166 static constexpr auto VOICE_ACTIVITY = "voice_activity"sv;
1167
1168 int streamIdx = -1;
1169 if (body_msg.find(STREAM_ID) != std::string_view::npos) {
1170 // Note: here we use the index of the RTP stream, not its label!
1171 // Indeed, both sides will have different labels as they have different call IDs
1172 static const std::regex STREAMID_REGEX("<stream_id>([0-9]+)</stream_id>");
1175 if (matched_pattern.ready() && !matched_pattern.empty() && matched_pattern[1].matched) {
1176 try {
1177 streamIdx = std::stoi(matched_pattern[1]);
1178 } catch (const std::exception& e) {
1179 JAMI_WARN("Error parsing stream index: %s", e.what());
1180 }
1181 }
1182 }
1183
1184 if (body_msg.find(PICT_FAST_UPDATE) != std::string_view::npos) {
1185 call.sendKeyframe(streamIdx);
1186 return true;
1187 } else if (body_msg.find(DEVICE_ORIENTATION) != std::string_view::npos) {
1188 static const std::regex ORIENTATION_REGEX("device_orientation=([-+]?[0-9]+)");
1189
1192
1193 if (matched_pattern.ready() && !matched_pattern.empty() && matched_pattern[1].matched) {
1194 try {
1195 int rotation = -std::stoi(matched_pattern[1]);
1196 while (rotation <= -180)
1197 rotation += 360;
1198 while (rotation > 180)
1199 rotation -= 360;
1200 JAMI_WARN("Rotate video %d deg.", rotation);
1201#ifdef ENABLE_VIDEO
1202 call.setRotation(streamIdx, rotation);
1203#endif
1204 } catch (const std::exception& e) {
1205 JAMI_WARN("Error parsing angle: %s", e.what());
1206 }
1207 return true;
1208 }
1209 } else if (body_msg.find(RECORDING_STATE) != std::string_view::npos) {
1210 static const std::regex REC_REGEX("recording_state=([0-1])");
1213
1214 if (matched_pattern.ready() && !matched_pattern.empty() && matched_pattern[1].matched) {
1215 try {
1216 bool state = std::stoi(matched_pattern[1]);
1217 call.peerRecording(state);
1218 } catch (const std::exception& e) {
1219 JAMI_WARN("Error parsing state remote recording: %s", e.what());
1220 }
1221 return true;
1222 }
1223 } else if (body_msg.find(MUTE_STATE) != std::string_view::npos) {
1224 static const std::regex REC_REGEX("mute_state=([0-1])");
1227
1228 if (matched_pattern.ready() && !matched_pattern.empty() && matched_pattern[1].matched) {
1229 try {
1230 bool state = std::stoi(matched_pattern[1]);
1231 call.peerMuted(state, streamIdx);
1232 } catch (const std::exception& e) {
1233 JAMI_WARN("Error parsing state remote mute: %s", e.what());
1234 }
1235 return true;
1236 }
1237 } else if (body_msg.find(VOICE_ACTIVITY) != std::string_view::npos) {
1238 static const std::regex REC_REGEX("voice_activity=([0-1])");
1241
1242 if (matched_pattern.ready() && !matched_pattern.empty() && matched_pattern[1].matched) {
1243 try {
1244 bool state = std::stoi(matched_pattern[1]);
1245 call.peerVoice(state);
1246 } catch (const std::exception& e) {
1247 JAMI_WARN("Error parsing state remote voice: %s", e.what());
1248 }
1249 return true;
1250 }
1251 }
1252 }
1253
1254 return false;
1255}
1256
1260static bool
1261transferCall(SIPCall& call, const std::string& refer_to)
1262{
1263 const auto& callId = call.getCallId();
1264 JAMI_WARN("[call:%s] Attempting to transfer to %s", callId.c_str(), refer_to.c_str());
1265 try {
1267 call.getAccountId(),
1269 call.getMediaAttributeList()));
1270 Manager::instance().hangupCall(call.getAccountId(), callId);
1271 } catch (const std::exception& e) {
1272 JAMI_ERR("[call:%s] SIP transfer failed: %s", callId.c_str(), e.what());
1273 return false;
1274 }
1275 return true;
1276}
1277
1278static void
1280{
1281 const auto ret = pjsip_dlg_respond(inv->dlg, rdata, status_code, nullptr, nullptr, nullptr);
1282 if (ret != PJ_SUCCESS)
1283 JAMI_WARN("SIP: Failed to reply %d to request", status_code);
1284}
1285
1286static void
1288{
1289 static constexpr pj_str_t str_refer_to = CONST_PJ_STR("Refer-To");
1290
1291 if (auto refer_to = static_cast<pjsip_generic_string_hdr*>(
1293 // RFC 3515, 2.4.2: reply bad request if no or too many refer-to header.
1294 if (static_cast<void*>(refer_to->next) == static_cast<void*>(&msg->hdr)
1297 transferCall(call, std::string(refer_to->hvalue.ptr, refer_to->hvalue.slen));
1298
1299 // RFC 3515, 2.4.4: we MUST handle the processing using NOTIFY msgs
1300 // But your current design doesn't permit that
1301 return;
1302 } else
1303 JAMI_ERR("[call:%s] REFER: too many Refer-To headers", call.getCallId().c_str());
1304 } else
1305 JAMI_ERR("[call:%s] REFER: no Refer-To header", call.getCallId().c_str());
1306
1308}
1309
1310static void
1316
1317static void
1319{
1320 if (!msg->body)
1321 return;
1322
1323 const std::string bodyText {static_cast<char*>(msg->body->data), msg->body->len};
1324 JAMI_DBG("[call:%s] NOTIFY body start - %p\n%s\n[call:%s] NOTIFY body end - %p",
1325 call.getCallId().c_str(),
1326 msg->body,
1327 bodyText.c_str(),
1328 call.getCallId().c_str(),
1329 msg->body);
1330
1331 // TODO
1332}
1333
1334static void
1336{
1337 auto call = getCallFromInvite(inv);
1338 if (not call)
1339 return;
1340
1341#ifdef DEBUG_SIP_REQUEST_MSG
1343#endif
1344
1345 // We process here only incoming request message
1346 if (tsx->role != PJSIP_ROLE_UAS or tsx->state != PJSIP_TSX_STATE_TRYING
1347 or event->body.tsx_state.type != PJSIP_EVENT_RX_MSG) {
1348 return;
1349 }
1350
1351 const auto rdata = event->body.tsx_state.src.rdata;
1352 if (!rdata) {
1353 JAMI_ERROR("[INVITE:{:p}] SIP RX request without rx data", fmt::ptr(inv));
1354 return;
1355 }
1356
1357 const auto msg = rdata->msg_info.msg;
1358 if (msg->type != PJSIP_REQUEST_MSG) {
1359 JAMI_ERROR("[INVITE:{:p}] SIP RX request without msg", fmt::ptr(inv));
1360 return;
1361 }
1362
1363 // Using method name to dispatch
1364 auto methodName = sip_utils::as_view(msg->line.req.method.name);
1365 JAMI_LOG("[INVITE:{:p}] RX SIP method {:d} ({:s})",
1366 fmt::ptr(inv),
1367 (int)msg->line.req.method.id,
1368 methodName);
1369
1370#ifdef DEBUG_SIP_REQUEST_MSG
1371 char msgbuf[1000];
1372 auto msgsize = pjsip_msg_print(msg, msgbuf, sizeof msgbuf);
1373 if (msgsize > 0)
1374 JAMI_LOG("{:s}", std::string_view(msgbuf, msgsize));
1375#endif // DEBUG_SIP_REQUEST_MSG
1376
1378 onRequestRefer(inv, rdata, msg, *call);
1380 onRequestInfo(inv, rdata, msg, *call);
1382 onRequestNotify(inv, rdata, msg, *call);
1386 if (msg->body)
1387 runOnMainThread([call, m = im::parseSipMessage(msg)]() mutable {
1388 call->onTextMessage(std::move(m));
1389 });
1390 }
1391}
1392
1393#ifdef DEBUG_SIP_REQUEST_MSG
1394static void
1396{
1397 if (event->body.tsx_state.type != PJSIP_EVENT_RX_MSG)
1398 return;
1399
1400 const auto rdata = event->body.tsx_state.src.rdata;
1401 if (rdata == nullptr or rdata->msg_info.msg == nullptr)
1402 return;
1403
1404 const auto msg = rdata->msg_info.msg;
1405 if (msg->type != PJSIP_RESPONSE_MSG)
1406 return;
1407
1408 // Only handle the following responses
1409 switch (msg->line.status.code) {
1410 case PJSIP_SC_TRYING:
1411 case PJSIP_SC_RINGING:
1412 case PJSIP_SC_OK:
1413 break;
1414 default:
1415 return;
1416 }
1417
1418 JAMI_LOG("[INVITE:{:p}] SIP RX response: reason {:s}, status code {:d} {:s}",
1419 fmt::ptr(inv),
1420 sip_utils::as_view(msg->line.status.reason),
1421 msg->line.status.code,
1422 sip_utils::as_view(*pjsip_get_status_text(msg->line.status.code)));
1423
1425}
1426#endif
1427
1428int
1430{
1431 return mod_ua_.id;
1432}
1433
1434void
1436{
1437 if (inv == nullptr) {
1438 throw VoipLinkException("Invite session is unable to be null");
1439 }
1440 sdp_create_offer_cb(inv, nullptr);
1441}
1442
1443// Thread-safe DNS resolver callback mapping
1445{
1446public:
1448
1450 {
1451 std::lock_guard lk(mutex_);
1452 cbMap_.emplace(key, std::move(cb));
1453 }
1454
1456 {
1457 std::lock_guard lk(mutex_);
1458 auto it = cbMap_.find(key);
1459 if (it != cbMap_.end()) {
1460 it->second(status, addr);
1461 cbMap_.erase(it);
1462 }
1463 }
1464
1465private:
1466 std::mutex mutex_;
1467 std::map<uintptr_t, ResolveCallback> cbMap_;
1468};
1469
1470static SafeResolveCallbackMap&
1472{
1473 static SafeResolveCallbackMap map;
1474 return map;
1475}
1476
1477static void
1478resolver_callback(pj_status_t status, void* token, const struct pjsip_server_addresses* addr)
1479{
1480 getResolveCallbackMap().process((uintptr_t) token, status, addr);
1481}
1482
1483void
1484SIPVoIPLink::resolveSrvName(const std::string& name,
1487{
1488 // PJSIP limits hostname to be longer than PJ_MAX_HOSTNAME.
1489 // But, resolver prefix the given name by a string like "_sip._udp."
1490 // causing a check against PJ_MAX_HOSTNAME to be useless.
1491 // It's not easy to pre-determinate as it's implementation dependent.
1492 // So we just choose a security marge enough for most cases, preventing a crash later
1493 // in the call of pjsip_endpt_resolve().
1494 if (name.length() > (PJ_MAX_HOSTNAME - 12)) {
1495 JAMI_ERR("Hostname is too long");
1496 cb({});
1497 return;
1498 }
1499
1500 // extract port if name is in form "server:port"
1501 int port;
1503 const auto n = name.rfind(':');
1504 if (n != std::string::npos) {
1505 port = std::atoi(name.c_str() + n + 1);
1506 name_size = n;
1507 } else {
1508 port = 0;
1509 name_size = name.size();
1510 }
1511 JAMI_DBG("Attempt to resolve '%s' (port: %u)", name.c_str(), port);
1512
1514 /*.flag = */ 0,
1515 /*.type = */ type,
1516 /*.addr = */ {{(char*) name.c_str(), name_size}, port},
1517 };
1518
1519 const auto token = std::hash<std::string>()(name + std::to_string(type));
1521 token, [=, cb = std::move(cb)](pj_status_t s, const pjsip_server_addresses* r) {
1522 try {
1523 if (s != PJ_SUCCESS || !r) {
1524 JAMI_WARN("Unable to resolve \"%s\" using pjsip_endpt_resolve, attempting getaddrinfo.",
1525 name.c_str());
1526 dht::ThreadPool::io().run([=, cb = std::move(cb)]() {
1527 auto ips = dhtnet::ip_utils::getAddrList(name.c_str());
1528 runOnMainThread(std::bind(cb, std::move(ips)));
1529 });
1530 } else {
1531 std::vector<dhtnet::IpAddr> ips;
1532 ips.reserve(r->count);
1533 for (unsigned i = 0; i < r->count; i++)
1534 ips.push_back(r->entry[i].addr);
1535 cb(ips);
1536 }
1537 } catch (const std::exception& e) {
1538 JAMI_ERR("Error resolving address: %s", e.what());
1539 cb({});
1540 }
1541 });
1542
1543 pjsip_endpt_resolve(endpt_, pool_.get(), &host_info, (void*) token, resolver_callback);
1544}
1545
1546#define RETURN_IF_NULL(A, ...) \
1547 if ((A) == NULL) { \
1548 JAMI_WARN(__VA_ARGS__); \
1549 return; \
1550 }
1551
1552#define RETURN_FALSE_IF_NULL(A, ...) \
1553 if ((A) == NULL) { \
1554 JAMI_WARN(__VA_ARGS__); \
1555 return false; \
1556 }
1557
1558void
1561 const std::string& host,
1562 std::string& addr,
1563 pj_uint16_t& port) const
1564{
1565 // Initialize the SIP port with the default SIP port
1567
1568 // Initialize the SIP address with the hostname
1570
1571 // Update address and port with active transport
1572 RETURN_IF_NULL(transport,
1573 "Transport is NULL in findLocalAddress, using local address %s :%d",
1574 addr.c_str(),
1575 port);
1576
1577 // get the transport manager associated with the SIP enpoint
1580 "Transport manager is NULL in findLocalAddress, using local address %s :%d",
1581 addr.c_str(),
1582 port);
1583
1584 const pj_str_t pjstring(CONST_PJ_STR(host));
1585
1586 auto tp_sel = getTransportSelector(transport);
1588 = {transportType, &tp_sel, pjstring, PJ_FALSE, {nullptr, 0}, 0, nullptr};
1589 if (pjsip_tpmgr_find_local_addr2(tpmgr, pool_.get(), &param) != PJ_SUCCESS) {
1590 JAMI_WARN("Unable to retrieve local address and port from transport, using %s :%d",
1591 addr.c_str(),
1592 port);
1593 return;
1594 }
1595
1596 // Update local address based on the transport type
1597 addr = sip_utils::as_view(param.ret_addr);
1598
1599 // Determine the local port based on transport information
1600 port = param.ret_port;
1601}
1602
1603bool
1606 int stunPort,
1607 std::string& addr,
1608 pj_uint16_t& port) const
1609{
1610 // WARN: this code use pjstun_get_mapped_addr2 that works
1611 // in IPv4 only.
1612 // WARN: this function is blocking (network request).
1613
1614 // Initialize the sip port with the default SIP port
1616
1617 // Get Local IP address
1618 auto localIp = dhtnet::ip_utils::getLocalAddr(pj_AF_INET());
1619 if (not localIp) {
1620 JAMI_WARN("Failed to find local IP");
1621 return false;
1622 }
1623
1624 addr = localIp.toString();
1625
1626 // Update address and port with active transport
1627 RETURN_FALSE_IF_NULL(transport,
1628 "Transport is NULL in findLocalAddress, using local address %s:%u",
1629 addr.c_str(),
1630 port);
1631
1632 JAMI_DBG("STUN mapping of '%s:%u'", addr.c_str(), port);
1633
1639 &stunOpt,
1640 1,
1641 &sipSocket,
1642 &mapped_addr);
1643
1644 switch (stunStatus) {
1646 JAMI_ERROR("No response from STUN server {:s}",
1648 return false;
1649
1651 JAMI_ERR("Different mapped addresses are returned by servers.");
1652 return false;
1653
1654 case PJ_SUCCESS:
1656 addr = dhtnet::IpAddr((const sockaddr_in&) mapped_addr).toString(true);
1657 JAMI_DEBUG("STUN server {:s} replied '{}'", sip_utils::as_view(*stunServerName), addr);
1658 return true;
1659
1660 default: // use given address, silent any not handled error
1661 JAMI_WARNING("Error from STUN server {:s}, using source address",
1663 return false;
1664 }
1665}
1666#undef RETURN_IF_NULL
1667#undef RETURN_FALSE_IF_NULL
1668} // namespace jami
#define TRY(call, error)
const std::string & getCallId() const
Return a reference on the call id.
Definition call.h:132
std::string getAccountId() const
Definition call.cpp:158
std::vector< std::shared_ptr< T > > getAllAccounts() const
Get a list of account pointers of type T (baseclass Account)
Definition manager.h:731
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:3051
static LIBJAMI_TEST_EXPORT Manager & instance()
Definition manager.cpp:676
void incomingCall(const std::string &accountId, Call &call)
Handle incoming call and notify user.
Definition manager.cpp:1872
SIPVoIPLink & sipVoIPLink() const
Definition manager.cpp:3189
bool hangupCall(const std::string &accountId, const std::string &callId)
Functions which occur with a user's action Hangup the call.
Definition manager.cpp:1142
static std::vector< libjami::MediaMap > mediaAttributesToMediaMaps(std::vector< MediaAttribute > mediaAttrList)
static pjsip_module mod_presence_server
static constexpr auto ACCOUNT_TYPE
Definition sipaccount.h:50
std::vector< MediaAttribute > getMediaAttributeList() const override
Definition sipcall.cpp:2660
void sendKeyframe(int streamIdx=-1) override
Definition sipcall.cpp:1679
void peerMuted(bool state, int streamIdx) override
Definition sipcall.cpp:3712
void peerVoice(bool state) override
Definition sipcall.cpp:3735
void peerRecording(bool state) override
Definition sipcall.cpp:3695
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:1028
static void printSession(const pjmedia_sdp_session *session, const char *header, SdpDirection direction)
Log the given session.
Definition sdp.cpp:462
Manages the transports and receive callbacks from PJSIP.
#define JAMI_ERR(...)
Definition logger.h:218
#define JAMI_ERROR(formatstr,...)
Definition logger.h:228
#define JAMI_DBG(...)
Definition logger.h:216
#define JAMI_DEBUG(formatstr,...)
Definition logger.h:226
#define JAMI_WARN(...)
Definition logger.h:217
#define JAMI_WARNING(formatstr,...)
Definition logger.h:227
#define JAMI_LOG(formatstr,...)
Definition logger.h:225
#define JAMI_INFO(...)
Definition logger.h:215
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:47
constexpr std::string_view INFO
Definition sip_utils.h:46
constexpr std::string_view MESSAGE
Definition sip_utils.h:45
constexpr std::string_view PUBLISH
Definition sip_utils.h:48
constexpr std::string_view REFER
Definition sip_utils.h:49
constexpr std::string_view NOTIFY
Definition sip_utils.h:50
static constexpr int DEFAULT_SIP_PORT
Definition sip_utils.h:53
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:107
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:89
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 ring_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)
@ MEDIA_AUDIO
Definition media_codec.h:47
@ MEDIA_VIDEO
Definition media_codec.h:48
static pj_bool_t transaction_request_cb(pjsip_rx_data *rdata)
bool is_uninitialized(std::weak_ptr< T > const &weak)
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)
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:909
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.