47#include <dhtnet/ip_utils.h>
48#include <opendht/thread_pool.h>
50#include <pjsip/sip_endpoint.h>
51#include <pjsip/sip_uri.h>
53#include <pjsip-simple/presence.h>
54#include <pjsip-simple/publish.h>
57#if PJ_VERSION_NUM < (2 << 24 | 10 << 16)
58#error "Unsupported PJSIP version (requires version 2.10+)"
95#ifdef DEBUG_SIP_REQUEST_MSG
106 JAMI_INFO(
"Processing in-dialog option request");
108 JAMI_ERR(
"Failed to create in-dialog response for option request");
112 JAMI_INFO(
"Processing out-of-dialog option request");
114 JAMI_ERR(
"Failed to create out-of-dialog response for option request");
119#define ADD_HDR(hdr) \
121 const pjsip_hdr* cap_hdr = hdr; \
123 pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*) pjsip_hdr_clone(tdata->pool, cap_hdr)); \
125#define ADD_CAP(cap) ADD_HDR(pjsip_endpt_get_capability(endpt_, cap, NULL));
134 JAMI_ERR(
"Failed to send in-dialog response for option request");
138 JAMI_INFO(
"Sent in-dialog response for option request");
147 JAMI_ERR(
"Failed to send out-of-dialog response for option request");
151 JAMI_INFO(
"Sent out-of-dialog response for option request");
170 if (
tsx->status_code / 100 == 2) {
177 if (
rdata->msg_info.cseq) {
201 JAMI_ERR(
"Transaction has been created for this request, send response "
202 "statefully instead");
211 using wt = std::weak_ptr<T>;
212 return !weak.owner_before(
wt {}) && !
wt {}.owner_before(weak);
234 JAMI_ERR(
"Missing From, To or Via fields");
267 rdata->tp_info.transport);
269 std::shared_ptr<SIPAccountBase>
account;
271 const auto&
waccount = transport ? transport->getAccount() : std::weak_ptr<SIPAccountBase> {};
279 if (
not(transport = std::static_pointer_cast<SIPAccount>(
account)->getTransport())) {
280 JAMI_ERR(
"No suitable transport to answer this call.");
283 JAMI_WARN(
"Using transport from account.");
286 JAMI_ERR(
"Dropping SIP request: account is expired.");
296 if (body
and body->data) {
297 std::string_view
body_view(
static_cast<char*
>(body->data), body->len);
299 if (
pos != std::string_view::npos) {
305 "Voice-Message: %d/%d (%d/",
324 if (payloads.size() > 0) {
339 id = std::string(
msgId->hvalue.ptr,
msgId->hvalue.slen);
341 if (
not id.empty()) {
345 auto acc = std::dynamic_pointer_cast<JamiAccount>(
account);
346 if (acc
and acc->isMessageTreated(
intid))
351 account->onTextMessage(
id,
peerNumber, transport->getTlsInfos().peerCert, payloads);
368 JAMI_INFO(
"Received a SIP INVITE request");
376 JAMI_WARN(
"Failed to parse the SDP in offer");
390 JAMI_ERR(
"Unable to verify INVITE request in secure dialog.");
414 call->toUsername(std::string(toUsername));
421 if (
account->getUPnPActive()) {
424 :
account->getPublishedIpAddress();
427 ?
account->getPublishedIpAddress()
428 : dhtnet::ip_utils::getInterfaceAddr(
account->getLocalInterface(),
family);
448 call->getSDP().setPublishedIP(
addrSdp);
453 if (
r_sdp !=
nullptr) {
454 call->getSDP().setReceivedOffer(
r_sdp);
473 JAMI_ERR(
"Unable to set transport for dialog");
485 JAMI_ERR(
"Call invite is not initialized");
494 call->setInviteSession(
inv);
501 JAMI_ERR(
"Something wrong with Replaces request.");
526 JAMI_ERR(
"Unable to create answer TRYING");
534 JAMI_ERR(
"Unable to send msg TRYING");
542 JAMI_ERR(
"Unable to create answer RINGING");
548 JAMI_ERR(
"Unable to send msg RINGING");
580 broker->transportStateChanged(
tp, state, info);
582 JAMI_ERR(
"SIPVoIPLink with invalid SipTransportBroker");
616 if ((ret) != PJ_SUCCESS) \
617 throw VoipLinkException(#ret " failed"); \
627 auto ns = dhtnet::ip_utils::getLocalNameservers();
628 if (
not ns.empty()) {
631 for (
unsigned i = 0,
n =
ns.size();
i <
n;
i++) {
658 JAMI_WARN(
"Error setting PJSIP DNS resolver: %s",
731 sipThread_ = std::thread([
this] {
744 JAMI_DBG(
"Shutting down SIPVoIPLink@%p…",
this);
762 JAMI_DBG(
"SIPVoIPLink@%p shutdown successfully completed",
this);
765std::shared_ptr<SIPAccountBase>
768 std::string_view
fromUri)
const
770 JAMI_LOG(
"username = {}, server = {}, from = {}",
776 std::shared_ptr<SIPAccountBase> result;
787 }
else if (match >
best) {
805 JAMI_ERR(
"pjsip_endpt_handle_events failed with error %s",
812 JAMI_DEBUG(
"Register new keepalive timer {:d} with delay {:d}", timer.id,
delay.sec);
822 JAMI_ERR(
"Unable to schedule new timer in pjsip endpoint");
826 JAMI_ERR(
"Invalid timer or delay entry");
830 JAMI_ERR(
"Invalid timer entry, maybe already scheduled");
845static std::shared_ptr<SIPCall>
849 return std::static_pointer_cast<SIPCall>(
call_ptr->shared_from_this());
856 if (
inv ==
nullptr or ev ==
nullptr) {
866 JAMI_WARN(
"[call:%s] INVITE@%p state changed to %d (%s): unexpected event type %d",
867 call->getCallId().c_str(),
875 decltype(pjsip_transaction::status_code)
status_code = 0;
878 const auto tsx =
ev->body.tsx_state.tsx;
882 JAMI_LOG(
"[call:{}] INVITE@{:p} state changed to {:d} ({}): cause={:d}, tsx@{:p} status {:d} ({:s})",
892 JAMI_LOG(
"[call:{}] INVITE@{:p} state changed to {:d} ({:s}): cause={:d} (TX_MSG)",
903 rdata =
ev->body.tsx_state.src.rdata;
905 if (
rdata !=
nullptr) {
909 call->setPeerAllowMethods(std::move(
methods));
913 switch (
inv->state) {
916 call->onPeerRinging();
925 switch (
inv->cause) {
946 call->onFailure(
inv->cause);
979 if (
auto const&
account = call->getAccount().lock()) {
980 call->onReceiveOfferIn200OK(
param->offer);
991 if (
auto const&
account = call->getAccount().lock()) {
1008 auto account = call->getSIPAccount();
1014 if (
account->isEmptyOffersEnabled()) {
1016 JAMI_DBG(
"Client requested to send an empty offer (no SDP)");
1022 if (
auto dlg =
inv->dlg) {
1024 if (
auto tr =
dlg->tp_sel.u.transport)
1025 family =
tr->local_addr.addr.sa_family;
1027 if (
auto tr =
dlg->tp_sel.u.listener)
1028 family =
tr->local_addr.addr.sa_family;
1033 dhtnet::IpAddr address;
1034 if (
account->getUPnPActive()) {
1036 address =
account->getPublishedSameasLocal() ?
account->getUPnPIpAddress()
1037 :
account->getPublishedIpAddress();
1046 auto&
sdp = call->getSDP();
1047 sdp.setPublishedIP(address);
1050 auto const&
mediaList = call->getMediaAttributeList();
1055 JAMI_DBG(
"Creating a SDP offer using the following media:");
1057 JAMI_DBG(
"[call %s] Media %s", call->getCallId().c_str(),
media.toString(
true).c_str());
1072 JAMI_ERR(
"Active remote not present");
1077 JAMI_ERR(
"Invalid remote SDP session");
1090 JAMI_ERR(
"Active local not present");
1095 JAMI_ERR(
"Invalid local SDP session");
1110 JAMI_DBG(
"[call:%s] INVITE@%p media update: status %d", call->getCallId().c_str(),
inv, status);
1118 JAMI_WARN(
"[call:%s] SDP offer failed, reason %d", call->getCallId().c_str(),
reason);
1129 auto&
sdp = call->getSDP();
1140 call->onMediaNegotiationComplete();
1158 auto body_msg = std::string_view((
char*) body->data, (
size_t) body->len);
1172 static const std::regex
STREAMID_REGEX(
"<stream_id>([0-9]+)</stream_id>");
1178 }
catch (
const std::exception&
e) {
1179 JAMI_WARN(
"Error parsing stream index: %s",
e.what());
1196 while (rotation <= -180)
1198 while (rotation > 180)
1200 JAMI_WARN(
"Rotate video %d deg.", rotation);
1204 }
catch (
const std::exception&
e) {
1205 JAMI_WARN(
"Error parsing angle: %s",
e.what());
1210 static const std::regex
REC_REGEX(
"recording_state=([0-1])");
1218 }
catch (
const std::exception&
e) {
1219 JAMI_WARN(
"Error parsing state remote recording: %s",
e.what());
1224 static const std::regex
REC_REGEX(
"mute_state=([0-1])");
1232 }
catch (
const std::exception&
e) {
1233 JAMI_WARN(
"Error parsing state remote mute: %s",
e.what());
1238 static const std::regex
REC_REGEX(
"voice_activity=([0-1])");
1246 }
catch (
const std::exception&
e) {
1247 JAMI_WARN(
"Error parsing state remote voice: %s",
e.what());
1264 JAMI_WARN(
"[call:%s] Attempting to transfer to %s", callId.c_str(),
refer_to.c_str());
1271 }
catch (
const std::exception&
e) {
1272 JAMI_ERR(
"[call:%s] SIP transfer failed: %s", callId.c_str(),
e.what());
1294 if (
static_cast<void*
>(
refer_to->next) ==
static_cast<void*
>(&
msg->hdr)
1303 JAMI_ERR(
"[call:%s] REFER: too many Refer-To headers", call.
getCallId().c_str());
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",
1341#ifdef DEBUG_SIP_REQUEST_MSG
1351 const auto rdata =
event->body.tsx_state.src.rdata;
1353 JAMI_ERROR(
"[INVITE:{:p}] SIP RX request without rx data", fmt::ptr(
inv));
1357 const auto msg =
rdata->msg_info.msg;
1359 JAMI_ERROR(
"[INVITE:{:p}] SIP RX request without msg", fmt::ptr(
inv));
1365 JAMI_LOG(
"[INVITE:{:p}] RX SIP method {:d} ({:s})",
1367 (
int)
msg->line.req.method.id,
1370#ifdef DEBUG_SIP_REQUEST_MSG
1388 call->onTextMessage(std::move(
m));
1393#ifdef DEBUG_SIP_REQUEST_MSG
1400 const auto rdata =
event->body.tsx_state.src.rdata;
1401 if (
rdata ==
nullptr or rdata->msg_info.msg ==
nullptr)
1404 const auto msg =
rdata->msg_info.msg;
1409 switch (
msg->line.status.code) {
1418 JAMI_LOG(
"[INVITE:{:p}] SIP RX response: reason {:s}, status code {:d} {:s}",
1421 msg->line.status.code,
1437 if (
inv ==
nullptr) {
1451 std::lock_guard
lk(mutex_);
1452 cbMap_.emplace(key, std::move(
cb));
1457 std::lock_guard
lk(mutex_);
1458 auto it = cbMap_.find(key);
1459 if (
it != cbMap_.end()) {
1460 it->second(status, addr);
1467 std::map<uintptr_t, ResolveCallback> cbMap_;
1470static SafeResolveCallbackMap&
1503 const auto n = name.rfind(
':');
1504 if (
n != std::string::npos) {
1505 port = std::atoi(name.c_str() +
n + 1);
1511 JAMI_DBG(
"Attempt to resolve '%s' (port: %u)", name.c_str(),
port);
1519 const auto token = std::hash<std::string>()(name + std::to_string(type));
1524 JAMI_WARN(
"Unable to resolve \"%s\" using pjsip_endpt_resolve, attempting getaddrinfo.",
1526 dht::ThreadPool::io().run([=,
cb = std::move(
cb)]() {
1527 auto ips = dhtnet::ip_utils::getAddrList(name.c_str());
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);
1537 }
catch (
const std::exception&
e) {
1538 JAMI_ERR(
"Error resolving address: %s",
e.what());
1546#define RETURN_IF_NULL(A, ...) \
1547 if ((A) == NULL) { \
1548 JAMI_WARN(__VA_ARGS__); \
1552#define RETURN_FALSE_IF_NULL(A, ...) \
1553 if ((A) == NULL) { \
1554 JAMI_WARN(__VA_ARGS__); \
1561 const std::string&
host,
1573 "Transport is NULL in findLocalAddress, using local address %s :%d",
1580 "Transport manager is NULL in findLocalAddress, using local address %s :%d",
1590 JAMI_WARN(
"Unable to retrieve local address and port from transport, using %s :%d",
1628 "Transport is NULL in findLocalAddress, using local address %s:%u",
1632 JAMI_DBG(
"STUN mapping of '%s:%u'", addr.c_str(),
port);
1646 JAMI_ERROR(
"No response from STUN server {:s}",
1651 JAMI_ERR(
"Different mapped addresses are returned by servers.");
1661 JAMI_WARNING(
"Error from STUN server {:s}, using source address",
1666#undef RETURN_IF_NULL
1667#undef RETURN_FALSE_IF_NULL
const std::string & getCallId() const
Return a reference on the call id.
std::string getAccountId() const
std::vector< std::shared_ptr< T > > getAllAccounts() const
Get a list of account pointers of type T (baseclass Account)
std::shared_ptr< Call > newOutgoingCall(std::string_view toUrl, const std::string &accountId, const std::vector< libjami::MediaMap > &mediaList)
Create a new outgoing call.
static LIBJAMI_TEST_EXPORT Manager & instance()
void incomingCall(const std::string &accountId, Call &call)
Handle incoming call and notify user.
SIPVoIPLink & sipVoIPLink() const
bool hangupCall(const std::string &accountId, const std::string &callId)
Functions which occur with a user's action Hangup the call.
static pjsip_module mod_presence_server
static constexpr auto ACCOUNT_TYPE
std::vector< MediaAttribute > getMediaAttributeList() const override
void sendKeyframe(int streamIdx=-1) override
void peerMuted(bool state, int streamIdx) override
void peerVoice(bool state) override
void peerRecording(bool state) override
pjsip_endpoint * getEndpoint()
void cancelKeepAliveTimer(pj_timer_entry &timer)
Abort currently registered timer.
bool findLocalAddressFromSTUN(pjsip_transport *transport, pj_str_t *stunServerName, int stunPort, std::string &address, pj_uint16_t &port) const
std::shared_ptr< SIPAccountBase > guessAccount(std::string_view userName, std::string_view server, std::string_view fromUri) const
Guess the account related to an incoming SIP call.
void handleEvents()
Event listener.
static pjsip_tpselector getTransportSelector(pjsip_transport *transport)
Initialize the transport selector.
void shutdown()
Destroy structures.
pj_caching_pool * getCachingPool() noexcept
std::function< void(std::vector< dhtnet::IpAddr >)> SrvResolveCallback
std::unique_ptr< SipTransportBroker > sipTransportBroker
Instance that maintain and manage transport (UDP, TLS)
void findLocalAddressFromTransport(pjsip_transport *transport, pjsip_transport_type_e transportType, const std::string &host, std::string &address, pj_uint16_t &port) const
Get the correct address to use (ie advertised) from a uri.
pj_pool_t * getPool() noexcept
void resolveSrvName(const std::string &name, pjsip_transport_type_e type, SrvResolveCallback &&cb)
static void createSDPOffer(pjsip_inv_session *inv)
void registerKeepAliveTimer(pj_timer_entry &timer, pj_time_val &delay)
Register a new keepalive registration timer to this endpoint.
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)
static void printSession(const pjmedia_sdp_session *session, const char *header, SdpDirection direction)
Log the given session.
Manages the transports and receive callbacks from PJSIP.
#define JAMI_ERROR(formatstr,...)
#define JAMI_DEBUG(formatstr,...)
#define JAMI_WARNING(formatstr,...)
#define JAMI_LOG(formatstr,...)
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
constexpr std::string_view INFO
constexpr std::string_view MESSAGE
constexpr std::string_view PUBLISH
constexpr std::string_view REFER
constexpr std::string_view NOTIFY
static constexpr int DEFAULT_SIP_PORT
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
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
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)
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)
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)
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.
#define RETURN_FALSE_IF_NULL(A,...)
#define RETURN_IF_NULL(A,...)
Specific VoIPLink for SIP (SIP core for incoming and outgoing events).