-
Alexandre Savard authoredAlexandre Savard authored
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
sipvoiplink.cpp 138.85 KiB
/*
* Copyright (C) 2004, 2005, 2006, 2009, 2008, 2009, 2010 Savoir-Faire Linux Inc.
*
* Author: Emmanuel Milou <emmanuel.milou@savoirfairelinux.com>
* Author: Yun Liu <yun.liu@savoirfairelinux.com>
* Author: Pierre-Luc Bacon <pierre-luc.bacon@savoirfairelinux.com>
* Author: Alexandre Savard <alexandre.savard@savoirfairelinux.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
* Additional permission under GNU GPL version 3 section 7:
*
* If you modify this program, or any covered work, by linking or
* combining it with the OpenSSL project's OpenSSL library (or a
* modified version of that library), containing parts covered by the
* terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
* grants you additional permission to convey the resulting work.
* Corresponding Source for a non-source form of such a combination
* shall include the source code for the parts of OpenSSL used as well
* as that of the covered work.
*/
#include "sipvoiplink.h"
#include "manager.h"
#include "sip/sdp.h"
#include "sipcall.h"
#include "sipaccount.h"
#include "eventthread.h"
#include "SdesNegotiator.h"
#include "dbus/dbusmanager.h"
#include "dbus/callmanager.h"
#include "im/InstantMessaging.h"
#include "pjsip/sip_endpoint.h"
#include "pjsip/sip_transport_tls.h"
#include "pjsip/sip_transport_tls.h"
#include "pjsip/sip_uri.h"
#include <pjnath.h>
#include <netinet/in.h>
#include <arpa/nameser.h>
#include <resolv.h>
#include <istream>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <linux/if.h>
#include <map>
#define CAN_REINVITE 1
using namespace sfl;
static char * invitationStateMap[] = {
(char*) "PJSIP_INV_STATE_NULL",
(char*) "PJSIP_INV_STATE_CALLING",
(char*) "PJSIP_INV_STATE_INCOMING",
(char*) "PJSIP_INV_STATE_EARLY",
(char*) "PJSIP_INV_STATE_CONNECTING",
(char*) "PJSIP_INV_STATE_CONFIRMED",
(char*) "PJSIP_INV_STATE_DISCONNECTED"
};
static char * transactionStateMap[] = {
(char*) "PJSIP_TSX_STATE_NULL" ,
(char*) "PJSIP_TSX_STATE_CALLING",
(char*) "PJSIP_TSX_STATE_TRYING",
(char*) "PJSIP_TSX_STATE_PROCEEDING",
(char*) "PJSIP_TSX_STATE_COMPLETED",
(char*) "PJSIP_TSX_STATE_CONFIRMED",
(char*) "PJSIP_TSX_STATE_TERMINATED",
(char*) "PJSIP_TSX_STATE_DESTROYED",
(char*) "PJSIP_TSX_STATE_MAX"
};
struct result {
pj_status_t status;
pjsip_server_addresses servers;
};
/** The default transport (5060) */
pjsip_transport *_localUDPTransport = NULL;
/** The local tls listener */
pjsip_tpfactory *_localTlsListener = NULL;
/** A map to retreive SFLphone internal call id
* Given a SIP call ID (usefull for transaction sucha as transfer)*/
std::map<std::string, CallID> transferCallID;
const pj_str_t STR_USER_AGENT = { (char*) "User-Agent", 10 };
/**************** EXTERN VARIABLES AND FUNCTIONS (callbacks) **************************/
/*
* Retrieve the SDP of the peer contained in the offer
*
* @param rdata The request data
* @param r_sdp The pjmedia_sdp_media to stock the remote SDP
*/
void get_remote_sdp_from_offer (pjsip_rx_data *rdata, pjmedia_sdp_session** r_sdp);
int getModId();
/**
* Set audio (SDP) configuration for a call
* localport, localip, localexternalport
* @param call a SIPCall valid pointer
* @return bool True
*/
bool setCallAudioLocal (SIPCall* call, std::string localIP);
void handle_incoming_options (pjsip_rx_data *rxdata);
std::string fetch_header_value (pjsip_msg *msg, std::string field);
std::string getLocalAddressAssociatedToAccount (AccountID id);
/*
* The global pool factory
*/
pj_caching_pool _cp;
/*
* The pool to allocate memory
*/
pj_pool_t *_pool;
/*
* The SIP endpoint
*/
pjsip_endpoint *_endpt;
/*
* The SIP module
*/
pjsip_module _mod_ua;
/*
* Thread related
*/
pj_thread_t *thread;
pj_thread_desc desc;
/*
* Url hook instance
*/
UrlHook *urlhook;
/**
* Get the number of voicemail waiting in a SIP message
*/
void set_voicemail_info (AccountID account, pjsip_msg_body *body);
// Documentated from the PJSIP Developer's Guide, available on the pjsip website/
pj_bool_t stun_sock_on_status (pj_stun_sock *stun_sock, pj_stun_sock_op op, pj_status_t status);
pj_bool_t stun_sock_on_rx_data (pj_stun_sock *stun_sock, void *pkt, unsigned pkt_len, const pj_sockaddr_t *src_addr, unsigned addr_len);
/*
* Session callback
* Called when the invite session state has changed.
*
* @param inv A pointer on a pjsip_inv_session structure
* @param e A pointer on a pjsip_event structure
*/
void call_on_state_changed (pjsip_inv_session *inv, pjsip_event *e);
/*
* Session callback
* Called after SDP offer/answer session has completed.
*
* @param inv A pointer on a pjsip_inv_session structure
* @param status A pj_status_t structure
*/
void call_on_media_update (pjsip_inv_session *inv, pj_status_t status UNUSED);
/*
* Called when the invite usage module has created a new dialog and invite
* because of forked outgoing request.
*
* @param inv A pointer on a pjsip_inv_session structure
* @param e A pointer on a pjsip_event structure
*/
void call_on_forked (pjsip_inv_session *inv, pjsip_event *e);
/*
* Session callback
* Called whenever any transactions within the session has changed their state.
* Useful to monitor the progress of an outgoing request.
*
* @param inv A pointer on a pjsip_inv_session structure
* @param tsx A pointer on a pjsip_transaction structure
* @param e A pointer on a pjsip_event structure
*/
void call_on_tsx_changed (pjsip_inv_session *inv, pjsip_transaction *tsx, pjsip_event *e);
void on_rx_offer (pjsip_inv_session *inv, const pjmedia_sdp_session *offer);
void on_create_offer (pjsip_inv_session *inv, pjmedia_sdp_session **p_offer);
/*
* Registration callback
*/
void regc_cb (struct pjsip_regc_cbparam *param);
/*
* DNS Callback used in workaround for bug #1852
*/
static void dns_cb (pj_status_t status, void *token, const struct pjsip_server_addresses *addr);
/*
* Called to handle incoming requests outside dialogs
* @param rdata
* @return pj_bool_t
*/
pj_bool_t mod_on_rx_request (pjsip_rx_data *rdata);
/*
* Called to handle incoming response
* @param rdata
* @return pj_bool_t
*/
pj_bool_t mod_on_rx_response (pjsip_rx_data *rdata UNUSED) ;
/**
* Send an ACK message inside a transaction. PJSIP send automatically, non-2xx ACK response.
* ACK for a 2xx response must be send using this method.
*/
static void sendAck (pjsip_dialog *dlg, pjsip_rx_data *rdata);
/*
* Transfer callbacks
*/
void xfer_func_cb (pjsip_evsub *sub, pjsip_event *event);
void xfer_svr_cb (pjsip_evsub *sub, pjsip_event *event);
void onCallTransfered (pjsip_inv_session *inv, pjsip_rx_data *rdata);
/*************************************************************************************************/
SIPVoIPLink* SIPVoIPLink::_instance = NULL;
SIPVoIPLink::SIPVoIPLink (const AccountID& accountID)
: VoIPLink (accountID)
, _nbTryListenAddr (2) // number of times to try to start SIP listener
, _regPort (atoi (DEFAULT_SIP_PORT))
, _clients (0)
{
_debug ("SIPVOIPLINK");
// to get random number for RANDOM_PORT
srand (time (NULL));
urlhook = new UrlHook ();
/* Start pjsip initialization step */
init();
}
SIPVoIPLink::~SIPVoIPLink()
{
_debug ("UserAgent: SIPVoIPLink destructor called");
terminate();
}
SIPVoIPLink* SIPVoIPLink::instance (const AccountID& id)
{
if (!_instance) {
_debug ("UserAgent: Create new SIPVoIPLink instance");
_instance = new SIPVoIPLink (id);
}
return _instance;
}
void SIPVoIPLink::decrementClients (void)
{
_clients--;
if (_clients == 0) {
_debug ("UserAgent: No SIP account anymore, terminate SIPVoIPLink");
// terminate();
delete SIPVoIPLink::_instance;
}
}
bool SIPVoIPLink::init()
{
if (initDone())
return false;
// TODO This port should be the one configured for the IP profile
// and not the global one
_regPort = Manager::instance().getLocalIp2IpPort();
/* Instanciate the C++ thread */
_evThread = new EventThread (this);
/* Initialize the pjsip library */
pjsip_init();
initDone (true);
return true;
}
void
SIPVoIPLink::terminate()
{
_debug ("UserAgent: Terminating SIPVoIPLink");
if (_evThread) {
_debug ("UserAgent: Deleting sip eventThread");
delete _evThread;
_evThread = NULL;
}
/* Clean shutdown of pjsip library */
if (initDone()) {
_debug ("UserAgent: Shutting down PJSIP");
pjsip_shutdown();
}
initDone (false);
}
void
SIPVoIPLink::terminateSIPCall()
{
ost::MutexLock m (_callMapMutex);
CallMap::iterator iter = _callMap.begin();
SIPCall *call;
while (iter != _callMap.end()) {
call = dynamic_cast<SIPCall*> (iter->second);
if (call) {
// terminate the sip call
delete call;
call = 0;
}
iter++;
}
_callMap.clear();
}
void
SIPVoIPLink::terminateOneCall (const CallID& id)
{
_debug ("UserAgent: Terminate call %s", id.c_str());
SIPCall *call = getSIPCall (id);
if (call) {
// terminate the sip call
delete call;
call = 0;
}
}
void get_remote_sdp_from_offer (pjsip_rx_data *rdata, pjmedia_sdp_session** r_sdp)
{
pjmedia_sdp_session *sdp;
pjsip_msg *msg;
pjsip_msg_body *body;
// Get the message
msg = rdata->msg_info.msg;
// Get the body message
body = msg->body;
// Parse the remote request to get the sdp session
if (body) {
pjmedia_sdp_parse (rdata->tp_info.pool, (char*) body->data, body->len, &sdp);
*r_sdp = sdp;
}
else
*r_sdp = NULL;
}
std::string SIPVoIPLink::getInterfaceAddrFromName (std::string ifaceName)
{
struct ifreq ifr;
int fd;
int err;
struct sockaddr_in *saddr_in;
struct in_addr *addr_in;
if ( (fd = socket (AF_INET, SOCK_DGRAM,0)) < 0)
_error ("UserAgent: Error: could not open socket");
memset (&ifr, 0, sizeof (struct ifreq));
strcpy (ifr.ifr_name, ifaceName.c_str());
ifr.ifr_addr.sa_family = AF_INET;
if ( (err = ioctl (fd, SIOCGIFADDR, &ifr)) < 0)
_debug ("UserAgent: Use default interface (0.0.0.0)");
saddr_in = (struct sockaddr_in *) &ifr.ifr_addr;
addr_in = & (saddr_in->sin_addr);
std::string addr (pj_inet_ntoa (* ( (pj_in_addr*) addr_in)));
close (fd);
return addr;
}
std::string SIPVoIPLink::get_useragent_name (const AccountID& id)
{
/*
useragent << PROGNAME << "/" << SFLPHONED_VERSION;
return useragent.str();
*/
SIPAccount *account = (SIPAccount *) Manager::instance().getAccount (id);
std::ostringstream useragent;
useragent << account->getUseragent();
if (useragent.str() == "sflphone" || useragent.str() == "")
useragent << "/" << SFLPHONED_VERSION;
return useragent.str ();
}
void
SIPVoIPLink::getEvent()
{
// We have to register the external thread so it could access the pjsip frameworks
if (!pj_thread_is_registered())
pj_thread_register (NULL, desc, &thread);
// PJSIP polling
pj_time_val timeout = {0, 10};
pjsip_endpt_handle_events (_endpt, &timeout);
}
int SIPVoIPLink::sendRegister (AccountID id)
{
int expire_value;
pj_status_t status;
pj_str_t useragent;
pjsip_tx_data *tdata;
pjsip_host_info destination;
std::string tmp, hostname, username, password;
SIPAccount *account = NULL;
pjsip_regc *regc;
pjsip_generic_string_hdr *h;
pjsip_hdr hdr_list;
account = dynamic_cast<SIPAccount *> (Manager::instance().getAccount (id));
if (account == NULL) {
_debug ("UserAgent: In sendRegister: account is null");
return false;
}
// Resolve hostname here and keep its
// IP address for the whole time the
// account is connected. This was a
// workaround meant to help issue
// #1852 that we hope should be fixed
// soon.
if (account->isResolveOnce()) {
struct result result;
destination.type = PJSIP_TRANSPORT_UNSPECIFIED;
destination.flag = pjsip_transport_get_flag_from_type (PJSIP_TRANSPORT_UNSPECIFIED);
destination.addr.host = pj_str (const_cast<char*> ( (account->getHostname()).c_str()));
destination.addr.port = 0;
result.status = 0x12345678;
pjsip_endpt_resolve (_endpt, _pool, &destination, &result, &dns_cb);
/* The following magic number and construct are inspired from dns_test.c
* in test-pjsip directory.
*/
while (result.status == 0x12345678) {
pj_time_val timeout = { 1, 0 };
pjsip_endpt_handle_events (_endpt, &timeout);
_debug ("status : %d", result.status);
}
if (result.status != PJ_SUCCESS) {
_debug ("Failed to resolve hostname only once."
" Default resolver will be used on"
" hostname for all requests.");
} else {
_debug ("%d servers where obtained from name resolution.", result.servers.count);
char addr_buf[80];
pj_sockaddr_print ( (pj_sockaddr_t*) &result.servers.entry[0].addr, addr_buf, sizeof (addr_buf), 3);
account->setHostname (addr_buf);
}
}
// Create SIP transport or get existent SIP transport from internal map
// according to account settings, if the transport could not be created but
// one is already set in account, use this one (most likely this is the
// transport we tried to create)
acquireTransport (account->getAccountID());
if (account->getAccountTransport()) {
_debug ("Acquire transport in account registration: %s %s (refcnt=%d)",
account->getAccountTransport()->obj_name,
account->getAccountTransport()->info,
(int) pj_atomic_get (account->getAccountTransport()->ref_cnt));
}
_mutexSIP.enterMutex();
// Get the client registration information for this particular account
regc = account->getRegistrationInfo();
account->setRegister (true);
// Set the expire value of the message from the config file
istringstream stream (account->getRegistrationExpire());
stream >> expire_value;
if (!expire_value) {
expire_value = PJSIP_REGC_EXPIRATION_NOT_SPECIFIED;
}
// Update the state of the voip link
account->setRegistrationState (Trying);
// Create the registration according to the account ID
status = pjsip_regc_create (_endpt, (void*) account, ®c_cb, ®c);
if (status != PJ_SUCCESS) {
_debug ("UserAgent: Unable to create regc.");
_mutexSIP.leaveMutex();
return false;
}
// Creates URI
std::string fromUri = account->getFromUri();
std::string srvUri = account->getServerUri();
std::string address = findLocalAddressFromUri (srvUri, account->getAccountTransport ());
int port = findLocalPortFromUri (srvUri, account->getAccountTransport ());
std::stringstream ss;
std::string portStr;
ss << port;
ss >> portStr;
std::string contactUri = account->getContactHeader (address, portStr);
_debug ("UserAgent: sendRegister: fromUri: %s serverUri: %s contactUri: %s",
fromUri.c_str(),
srvUri.c_str(),
contactUri.c_str());
pj_str_t pjFrom;
pj_cstr (&pjFrom, fromUri.c_str());
pj_str_t pjContact;
pj_cstr (&pjContact, contactUri.c_str());
pj_str_t pjSrv;
pj_cstr (&pjSrv, srvUri.c_str());
// Initializes registration
// Set Route for registration passing throught one or several proxies
// status = pjsip_regc_init (regc, &pjSrv, &pjFrom, &pjFrom, 1, &pjContact, expire_value);
status = pjsip_regc_init (regc, &pjSrv, &pjFrom, &pjFrom, 1, &pjContact, expire_value);
if (! (account->getServiceRoute().empty())) {
_error ("UserAgent: Set Service-Route with %s", account->getServiceRoute().c_str());
pjsip_route_hdr *route_set = pjsip_route_hdr_create (_pool);
pjsip_route_hdr *routing = pjsip_route_hdr_create (_pool);
pjsip_sip_uri *url = pjsip_sip_uri_create (_pool, 0);
routing->name_addr.uri = (pjsip_uri*) url;
pj_strdup2 (_pool, &url->host, account->getServiceRoute().c_str());
pj_list_push_back (&route_set, pjsip_hdr_clone (_pool, routing));
pjsip_regc_set_route_set (regc, route_set);
}
if (status != PJ_SUCCESS) {
_debug ("UserAgent: Unable to initialize account %d in sendRegister", status);
_mutexSIP.leaveMutex();
return false;
}
pjsip_cred_info *cred = account->getCredInfo();
int credential_count = account->getCredentialCount();
_debug ("UserAgent: setting %d credentials in sendRegister", credential_count);
pjsip_regc_set_credentials (regc, credential_count, cred);
// Add User-Agent Header
pj_list_init (&hdr_list);
useragent = pj_str ( (char*) get_useragent_name (id).c_str());
h = pjsip_generic_string_hdr_create (_pool, &STR_USER_AGENT, &useragent);
pj_list_push_back (&hdr_list, (pjsip_hdr*) h);
// pj_list_push_back (&hdr_list, (pjsip_hdr*) routing);
pjsip_regc_add_headers (regc, &hdr_list);
status = pjsip_regc_register (regc, PJ_TRUE, &tdata);
if (status != PJ_SUCCESS) {
_debug ("UserAgent: Unable to register regc.");
_mutexSIP.leaveMutex();
return false;
}
pjsip_tpselector *tp;
init_transport_selector (account->getAccountTransport (), &tp);
// pjsip_regc_set_transport increments transport ref count by one
status = pjsip_regc_set_transport (regc, tp);
if (account->getAccountTransport()) {
// decrease transport's ref count, counter icrementation is
// managed when acquiring transport
pjsip_transport_dec_ref (account->getAccountTransport ());
_debug ("UserAgent: After setting the transport in account registration using transport: %s %s (refcnt=%d)",
account->getAccountTransport()->obj_name,
account->getAccountTransport()->info,
(int) pj_atomic_get (account->getAccountTransport()->ref_cnt));
}
if (status != PJ_SUCCESS) {
_debug ("UserAgent: Unable to set transport.");
_mutexSIP.leaveMutex ();
return false;
}
// Send registration request
// pjsip_regc_send increment the transport ref count by one,
status = pjsip_regc_send (regc, tdata);
if (account->getAccountTransport()) {
// Decrease transport's ref count, since coresponding reference counter decrementation
// is performed in pjsip_regc_destroy. This function is never called in SFLphone as the
// regc data structure is permanently associated to the account at first registration.
pjsip_transport_dec_ref (account->getAccountTransport ());
}
if (status != PJ_SUCCESS) {
_debug ("UserAgent: Unable to send regc request.");
_mutexSIP.leaveMutex();
return false;
}
_mutexSIP.leaveMutex();
account->setRegistrationInfo (regc);
if (account->getAccountTransport()) {
_debug ("Sent account registration using transport: %s %s (refcnt=%d)",
account->getAccountTransport()->obj_name,
account->getAccountTransport()->info,
(int) pj_atomic_get (account->getAccountTransport()->ref_cnt));
}
return true;
}
int
SIPVoIPLink::sendUnregister (AccountID id)
{
pj_status_t status = 0;
pjsip_tx_data *tdata = NULL;
SIPAccount *account;
pjsip_regc *regc;
account = dynamic_cast<SIPAccount *> (Manager::instance().getAccount (id));
regc = account->getRegistrationInfo();
// If an transport is attached to this account, detach it and decrease reference counter
if (account->getAccountTransport()) {
_debug ("Sent account unregistration using transport: %s %s (refcnt=%d)",
account->getAccountTransport()->obj_name,
account->getAccountTransport()->info,
(int) pj_atomic_get (account->getAccountTransport()->ref_cnt));
}
// This may occurs if account failed to register and is in state INVALID
if (!account->isRegister()) {
account->setRegistrationState (Unregistered);
return true;
}
if (regc) {
status = pjsip_regc_unregister (regc, &tdata);
if (status != PJ_SUCCESS) {
_debug ("UserAgent: Unable to unregister regc.");
return false;
}
status = pjsip_regc_send (regc, tdata);
if (status != PJ_SUCCESS) {
_debug ("UserAgent: Unable to send regc request.");
return false;
}
} else {
_debug ("UserAgent: regc is null!");
return false;
}
// account->setRegistrationInfo(regc);
account->setRegister (false);
return true;
}
Call*
SIPVoIPLink::newOutgoingCall (const CallID& id, const std::string& toUrl)
{
SIPAccount * account = NULL;
pj_status_t status;
std::string localAddr, addrSdp;
_debug ("UserAgent: New outgoing call %s to %s", id.c_str(), toUrl.c_str());
SIPCall* call = new SIPCall (id, Call::Outgoing, _pool);
if (call) {
account = dynamic_cast<SIPAccount *> (Manager::instance().getAccount (Manager::instance().getAccountFromCall (id)));
if (account == NULL) {
_error ("UserAgent: Error: Could not retrieving account to make call with");
call->setConnectionState (Call::Disconnected);
call->setState (Call::Error);
delete call;
call=0;
return call;
}
std::string toUri = account->getToUri (toUrl);
call->setPeerNumber (toUri);
localAddr = getInterfaceAddrFromName (account->getLocalInterface ());
_debug ("UserAgent: Local address for call: %s", localAddr.c_str());
if (localAddr == "0.0.0.0")
loadSIPLocalIP (&localAddr);
setCallAudioLocal (call, localAddr);
// May use the published address as well
account->isStunEnabled () ? addrSdp = account->getPublishedAddress () : addrSdp = getInterfaceAddrFromName (account->getLocalInterface ());
if (addrSdp == "0.0.0.0")
loadSIPLocalIP (&addrSdp);
try {
_info ("UserAgent: Creating new rtp session");
call->getAudioRtp()->initAudioRtpConfig (call);
call->getAudioRtp()->initAudioRtpSession (call);
} catch (...) {
_error ("UserAgent: Error: Failed to create rtp thread from newOutGoingCall");
}
// init file name according to peer phone number
call->initRecFileName (toUrl);
_debug ("UserAgent: Try to make a call to: %s with call ID: %s", toUrl.data(), id.data());
// Building the local SDP offer
call->getLocalSDP()->set_ip_address (addrSdp);
status = call->getLocalSDP()->create_initial_offer (account->getActiveCodecs ());
if (status != PJ_SUCCESS) {
delete call;
call=0;
return call;
}
if (SIPOutgoingInvite (call)) {
call->setConnectionState (Call::Progressing);
call->setState (Call::Active);
addCall (call);
} else {
delete call;
call = 0;
}
}
return call;
}
bool
SIPVoIPLink::answer (const CallID& id)
{
pj_status_t status;
pjsip_tx_data *tdata;
pjsip_inv_session *inv_session;
_debug ("UserAgent: Answering call %s", id.c_str());
SIPCall *call = getSIPCall (id);
if (call==0) {
_debug ("UserAgent: SIPCall %s doesn't exists while answering", id.c_str());
return false;
}
Sdp *local_sdp = call->getLocalSDP();
inv_session = call->getInvSession();
status = local_sdp->start_negociation ();
if (status == PJ_SUCCESS) {
_debug ("SIPVoIPLink: UserAgent: SDP Negociation success! : call %s ", call->getCallId().c_str());
// Create and send a 200(OK) response
status = pjsip_inv_answer (inv_session, PJSIP_SC_OK, NULL, NULL, &tdata);
PJ_ASSERT_RETURN (status == PJ_SUCCESS, 1);
status = pjsip_inv_send_msg (inv_session, tdata);
PJ_ASSERT_RETURN (status == PJ_SUCCESS, 1);
call->setConnectionState (Call::Connected);
call->setState (Call::Active);
return true;
} else {
// Create and send a 488/Not acceptable here
// because the SDP negociation failed
status = pjsip_inv_answer (inv_session, PJSIP_SC_NOT_ACCEPTABLE_HERE, NULL, NULL,
&tdata);
PJ_ASSERT_RETURN (status == PJ_SUCCESS, 1);
status = pjsip_inv_send_msg (inv_session, tdata);
PJ_ASSERT_RETURN (status == PJ_SUCCESS, 1);
// Terminate the call
_debug ("SIPVoIPLink: UserAgent: SDP Negociation failed, terminate call %s ", call->getCallId().c_str());
if (call->getAudioRtp())
call->getAudioRtp()->stop ();
terminateOneCall (call->getCallId());
removeCall (call->getCallId());
return false;
}
}
bool
SIPVoIPLink::hangup (const CallID& id)
{
pj_status_t status;
pjsip_tx_data *tdata = NULL;
SIPCall* call;
call = getSIPCall (id);
if (call==0) {
_debug ("! SIP Error: Call doesn't exist");
return false;
}
// User hangup current call. Notify peer
status = pjsip_inv_end_session (call->getInvSession(), 404, NULL, &tdata);
if (status != PJ_SUCCESS)
return false;
if (tdata == NULL)
return true;
// _debug("Some tdata info: %",);
status = pjsip_inv_send_msg (call->getInvSession(), tdata);
if (status != PJ_SUCCESS)
return false;
call->getInvSession()->mod_data[getModId() ] = NULL;
// Release RTP thread
if (Manager::instance().isCurrentCall (id)) {
_debug ("* SIP Info: Stopping AudioRTP for hangup");
call->getAudioRtp()->stop();
}
terminateOneCall (id);
removeCall (id);
return true;
}
bool
SIPVoIPLink::peerHungup (const CallID& id)
{
pj_status_t status;
pjsip_tx_data *tdata = NULL;
SIPCall* call;
_info ("UserAgent: Peer hungup");
call = getSIPCall (id);
if (call==0) {
_warn ("UserAgent: Call doesn't exist");
return false;
}
// User hangup current call. Notify peer
status = pjsip_inv_end_session (call->getInvSession(), 404, NULL, &tdata);
if (status != PJ_SUCCESS)
return false;
if (tdata == NULL)
return true;
status = pjsip_inv_send_msg (call->getInvSession(), tdata);
if (status != PJ_SUCCESS)
return false;
call->getInvSession()->mod_data[getModId() ] = NULL;
// Release RTP thread
if (Manager::instance().isCurrentCall (id)) {
_debug ("UserAgent: Stopping AudioRTP for hangup");
call->getAudioRtp()->stop();
}
terminateOneCall (id);
removeCall (id);
return true;
}
bool
SIPVoIPLink::cancel (const CallID& id)
{
_info ("UserAgent: Cancel call %s", id.c_str());
SIPCall* call = getSIPCall (id);
if (!call) {
_warn ("UserAgent: Error: Call doesn't exist");
return false;
}
terminateOneCall (id);
removeCall (id);
return true;
}
bool
SIPVoIPLink::onhold (const CallID& id)
{
pj_status_t status;
SIPCall* call;
call = getSIPCall (id);
if (call==0) {
_debug ("! SIP Error: call doesn't exist");
return false;
}
// Stop sound
call->setAudioStart (false);
call->setState (Call::Hold);
_debug ("* SIP Info: Stopping AudioRTP for onhold action");
call->getAudioRtp()->stop();
/* Create re-INVITE with new offer */
status = inv_session_reinvite (call, "sendonly");
if (status != PJ_SUCCESS)
return false;
return true;
}
bool
SIPVoIPLink::sendTextMessage (sfl::InstantMessaging *module, const std::string& callID, const std::string& message, const std::string& from)
{
_debug ("SipVoipLink: Send text message to %s, from %s", callID.c_str(), from.c_str());
SIPCall *call = getSIPCall (callID);
pj_status_t status = !PJ_SUCCESS;
if (call) {
std::string formatedFrom = from;
// add double quotes for xml formating
formatedFrom.insert (0,"\"");
formatedFrom.append ("\"");
/* Send IM message */
sfl::InstantMessaging::UriList list;
sfl::InstantMessaging::UriEntry entry;
entry[sfl::IM_XML_URI] = std::string (formatedFrom);
list.push_front (entry);
std::string formatedMessage = module->appendUriList (message, list);
status = module->send_sip_message (call->getInvSession (), (CallID&) callID, formatedMessage);
} else {
/* Notify the client of an error */
/*Manager::instance ().incomingMessage ( "",
"sflphoned",
"Unable to send a message outside a call.");*/
}
return status;
}
int SIPVoIPLink::inv_session_reinvite (SIPCall *call, std::string direction)
{
pj_status_t status;
pjsip_tx_data *tdata;
pjmedia_sdp_session *local_sdp;
pjmedia_sdp_attr *attr;
local_sdp = call->getLocalSDP()->get_local_sdp_session();
if (local_sdp == NULL) {
_debug ("SIP: Error: unable to find local sdp");
return !PJ_SUCCESS;
}
// Reinvite only if connected
// Build the local SDP offer
// TODO Restore Re-Invite
// status = call->getLocalSDP()->create_initial_offer();
// if (status != PJ_SUCCESS)
// return 1; // !PJ_SUCCESS
pjmedia_sdp_media_remove_all_attr (local_sdp->media[0], "sendrecv");
pjmedia_sdp_media_remove_all_attr (local_sdp->media[0], "sendonly");
attr = pjmedia_sdp_attr_create (_pool, direction.c_str(), NULL);
pjmedia_sdp_media_add_attr (local_sdp->media[0], attr);
// Build the reinvite request
status = pjsip_inv_reinvite (call->getInvSession(), NULL,
local_sdp, &tdata);
if (status != PJ_SUCCESS)
return 1; // !PJ_SUCCESS
// Send it
status = pjsip_inv_send_msg (call->getInvSession(), tdata);
if (status != PJ_SUCCESS)
return 1; // !PJ_SUCCESS
return PJ_SUCCESS;
}
bool
SIPVoIPLink::offhold (const CallID& id)
{
SIPCall *call;
pj_status_t status;
call = getSIPCall (id);
if (call==0) {
_debug ("! SIP Error: Call doesn't exist");
return false;
}
try {
call->getAudioRtp()->initAudioRtpConfig (call);
call->getAudioRtp()->initAudioRtpSession (call);
} catch (...) {
_debug ("! SIP Failure: Unable to create RTP Session (%s:%d)", __FILE__, __LINE__);
}
/* Create re-INVITE with new offer */
status = inv_session_reinvite (call, "sendrecv");
if (status != PJ_SUCCESS)
return false;
call->setState (Call::Active);
return true;
}
bool
SIPVoIPLink::transfer (const CallID& id, const std::string& to)
{
SIPCall *call;
std::string tmp_to;
pjsip_evsub *sub;
pjsip_tx_data *tdata;
struct pjsip_evsub_user xfer_cb;
pj_status_t status;
AccountID account_id;
SIPAccount * account = NULL;
call = getSIPCall (id);
call->stopRecording();
account_id = Manager::instance().getAccountFromCall (id);
account = dynamic_cast<SIPAccount *> (Manager::instance().getAccount (account_id));
if (!account) {
_error ("UserAgent: Error: Transfer account is null. Returning.");
return false;
}
if (!call) {
_error ("UserAgent: Error: Call doesn't exist");
return false;
}
std::string dest;
pj_str_t pjDest;
if (to.find ("@") == std::string::npos) {
dest = account->getToUri (to);
pj_cstr (&pjDest, dest.c_str());
}
_info ("UserAgent: Transfering to %s", dest.c_str());
/* Create xfer client subscription. */
pj_bzero (&xfer_cb, sizeof (xfer_cb));
xfer_cb.on_evsub_state = &xfer_func_cb;
status = pjsip_xfer_create_uac (call->getInvSession()->dlg, &xfer_cb, &sub);
if (status != PJ_SUCCESS) {
_warn ("UserAgent: Unable to create xfer -- %d", status);
return false;
}
/* Associate this voiplink of call with the client subscription
* We can not just associate call with the client subscription
* because after this function, we can no find the cooresponding
* voiplink from the call any more. But the voiplink is useful!
*/
pjsip_evsub_set_mod_data (sub, getModId(), this);
/*
* Create REFER request.
*/
status = pjsip_xfer_initiate (sub, &pjDest, &tdata);
if (status != PJ_SUCCESS) {
_error ("UserAgent: Unable to create REFER request -- %d", status);
return false;
}
// Put SIP call id in map in order to retrieve call during transfer callback
std::string callidtransfer (call->getInvSession()->dlg->call_id->id.ptr, call->getInvSession()->dlg->call_id->id.slen);
_debug ("%s", callidtransfer.c_str());
transferCallID.insert (std::pair<std::string, CallID> (callidtransfer, call->getCallId()));
/* Send. */
status = pjsip_xfer_send_request (sub, tdata);
if (status != PJ_SUCCESS) {
_error ("UserAgent: Unable to send REFER request -- %d", status);
return false;
}
return true;
}
bool SIPVoIPLink::transferStep2 (SIPCall* call)
{
// TODO is this the best way to proceed?
Manager::instance().peerHungupCall (call->getCallId());
return true;
}
bool
SIPVoIPLink::refuse (const CallID& id)
{
SIPCall *call;
pj_status_t status;
pjsip_tx_data *tdata;
_debug ("UserAgent: Refuse call %s", id.c_str());
call = getSIPCall (id);
if (call==0) {
_error ("UserAgent: Error: Call doesn't exist");
return false;
}
// can't refuse outgoing call or connected
if (!call->isIncoming() || call->getConnectionState() == Call::Connected) {
_debug ("UserAgent: Call %s is not in state incoming, or is already answered");
return false;
}
// User refuse current call. Notify peer
status = pjsip_inv_end_session (call->getInvSession(), PJSIP_SC_DECLINE, NULL, &tdata); //603
if (status != PJ_SUCCESS)
return false;
status = pjsip_inv_send_msg (call->getInvSession(), tdata);
if (status != PJ_SUCCESS)
return false;
call->getInvSession()->mod_data[getModId() ] = NULL;
removeCall (id);
terminateOneCall (id);
_debug ("UserAgent: Refuse call completed");
return true;
}
std::string
SIPVoIPLink::getCurrentCodecName()
{
SIPCall *call;
AudioCodec *ac = NULL;
std::string name = "";
call = getSIPCall (Manager::instance().getCurrentCallId());
if (call)
ac = call->getLocalSDP()->get_session_media();
if (ac)
name = ac->getCodecName();
return name;
}
bool
SIPVoIPLink::carryingDTMFdigits (const CallID& id, char code)
{
SIPCall *call = getSIPCall (id);
if (!call) {
_error ("UserAgent: Error: Call doesn't exist while sending DTMF");
return false;
}
AccountID accountID = Manager::instance().getAccountFromCall (id);
SIPAccount *account = static_cast<SIPAccount *> (Manager::instance().getAccount (accountID));
if (!account) {
_error ("UserAgent: Error: Account not found while sending DTMF");
return false;
}
DtmfType type = account->getDtmfType();
if (type == OVERRTP)
dtmfOverRtp (call, code);
else if (type == SIPINFO)
dtmfSipInfo (call, code);
else {
_error ("UserAgent: Error: Dtmf type does not exist");
return false;
}
return true;
}
bool
SIPVoIPLink::dtmfSipInfo (SIPCall *call, char code)
{
int duration;
const int body_len = 1000;
char *dtmf_body;
pj_status_t status;
pjsip_tx_data *tdata;
pj_str_t methodName, content;
pjsip_method method;
pjsip_media_type ctype;
duration = Manager::instance().voipPreferences.getPulseLength();
dtmf_body = new char[body_len];
snprintf (dtmf_body, body_len - 1, "Signal=%c\r\nDuration=%d\r\n", code, duration);
pj_strdup2 (_pool, &methodName, "INFO");
pjsip_method_init_np (&method, &methodName);
/* Create request message. */
status = pjsip_dlg_create_request (call->getInvSession()->dlg, &method, -1, &tdata);
if (status != PJ_SUCCESS) {
_debug ("UserAgent: Unable to create INFO request -- %d", status);
return false;
}
/* Get MIME type */
pj_strdup2 (_pool, &ctype.type, "application");
pj_strdup2 (_pool, &ctype.subtype, "dtmf-relay");
/* Create "application/dtmf-relay" message body. */
pj_strdup2 (_pool, &content, dtmf_body);
tdata->msg->body = pjsip_msg_body_create (tdata->pool, &ctype.type, &ctype.subtype, &content);
if (tdata->msg->body == NULL) {
_debug ("UserAgent: Unable to create msg body!");
pjsip_tx_data_dec_ref (tdata);
return false;
}
/* Send the request. */
status = pjsip_dlg_send_request (call->getInvSession()->dlg, tdata, getModId(), NULL);
if (status != PJ_SUCCESS) {
_debug ("UserAgent: Unable to send MESSAGE request -- %d", status);
return false;
}
return true;
}
bool
SIPVoIPLink::dtmfOverRtp (SIPCall* call, char code)
{
call->getAudioRtp()->sendDtmfDigit (atoi (&code));
return true;
}
bool
SIPVoIPLink::SIPOutgoingInvite (SIPCall* call)
{
// If no SIP proxy setting for direct call with only IP address
if (!SIPStartCall (call, "")) {
_debug ("! SIP Failure: call not started");
return false;
}
return true;
}
bool
SIPVoIPLink::SIPStartCall (SIPCall* call, const std::string& subject UNUSED)
{
pj_status_t status;
pjsip_inv_session *inv;
pjsip_dialog *dialog;
pjsip_tx_data *tdata;
AccountID id;
_debug ("UserAgent: Start sip call");
if (call == NULL)
return false;
id = Manager::instance().getAccountFromCall (call->getCallId());
// Get the basic information about the callee account
SIPAccount * account = NULL;
account = dynamic_cast<SIPAccount *> (Manager::instance().getAccount (id));
if (account == NULL) {
_debug ("UserAgent: Error: Account is null in SIPStartCall");
return false;
}
// Creates URI
std::string fromUri;
std::string toUri;
std::string contactUri;
fromUri = account->getFromUri();
toUri = call->getPeerNumber(); // expecting a fully well formed sip uri
std::string address = findLocalAddressFromUri (toUri, account->getAccountTransport ());
int port = findLocalPortFromUri (toUri, account->getAccountTransport ());
std::stringstream ss;
std::string portStr;
ss << port;
ss >> portStr;
contactUri = account->getContactHeader (address, portStr);
_debug ("UserAgent: FROM uri: %s, TO uri: %s, CONTACT uri: %s",
fromUri.c_str(),
toUri.c_str(),
contactUri.c_str());
pj_str_t pjFrom;
pj_cstr (&pjFrom, fromUri.c_str());
pj_str_t pjContact;
pj_cstr (&pjContact, contactUri.c_str());
pj_str_t pjTo;
pj_cstr (&pjTo, toUri.c_str());
// Create the dialog (UAC)
status = pjsip_dlg_create_uac (pjsip_ua_instance(), &pjFrom,
&pjContact,
&pjTo,
NULL,
&dialog);
if (status != PJ_SUCCESS) {
_error ("UserAgent: Error: UAC creation failed");
return false;
}
// Create the invite session for this call
status = pjsip_inv_create_uac (dialog, call->getLocalSDP()->get_local_sdp_session(), 0, &inv);
if (! (account->getServiceRoute().empty())) {
_error ("UserAgent: Set Service-Route with %s", account->getServiceRoute().c_str());
pjsip_route_hdr *route_set = pjsip_route_hdr_create (_pool);
pjsip_route_hdr *routing = pjsip_route_hdr_create (_pool);
pjsip_sip_uri *url = pjsip_sip_uri_create (_pool, 0);
routing->name_addr.uri = (pjsip_uri*) url;
pj_strdup2 (_pool, &url->host, account->getServiceRoute().c_str());
pj_list_push_back (&route_set, pjsip_hdr_clone (_pool, routing));
pjsip_dlg_set_route_set (dialog, route_set);
}
PJ_ASSERT_RETURN (status == PJ_SUCCESS, false);
// Set auth information
pjsip_auth_clt_set_credentials (&dialog->auth_sess, 1, account->getCredInfo());
// Associate current call in the invite session
inv->mod_data[getModId() ] = call;
status = pjsip_inv_invite (inv, &tdata);
PJ_ASSERT_RETURN (status == PJ_SUCCESS, false);
// Associate current invite session in the call
call->setInvSession (inv);
// Set the appropriate transport
pjsip_tpselector *tp;
init_transport_selector (account->getAccountTransport (), &tp);
// increment transport's ref count by one
status = pjsip_dlg_set_transport (dialog, tp);
// decrement transport's ref count
// pjsip_transport_dec_ref(account->getAccountTransport());
status = pjsip_inv_send_msg (inv, tdata);
if (status != PJ_SUCCESS) {
_error ("UserAgent: Error: failed to send invite");
return false;
}
if (account->getAccountTransport()) {
_debug ("UserAgent: Sent invite request using transport: %s %s (refcnt=%d)",
account->getAccountTransport()->obj_name,
account->getAccountTransport()->info,
(int) pj_atomic_get (account->getAccountTransport()->ref_cnt));
}
return true;
}
void
SIPVoIPLink::SIPCallServerFailure (SIPCall *call)
{
if (call != 0) {
_error ("UserAgent: Error: Server error!");
CallID id = call->getCallId();
Manager::instance().callFailure (id);
terminateOneCall (id);
removeCall (id);
if (call->getAudioRtp ())
call->getAudioRtp()->stop();
}
}
void
SIPVoIPLink::SIPCallClosed (SIPCall *call)
{
_info ("UserAgent: Closing call");
if (!call) {
_warn ("UserAgent: Error: CAll pointer is NULL\n");
return;
}
CallID id = call->getCallId();
if (Manager::instance().isCurrentCall (id)) {
call->setAudioStart (false);
_debug ("UserAgent: Stopping AudioRTP when closing");
call->getAudioRtp()->stop();
}
Manager::instance().peerHungupCall (id);
terminateOneCall (id);
removeCall (id);
}
void
SIPVoIPLink::SIPCallReleased (SIPCall *call)
{
if (!call) {
return;
}
// if we are here.. something when wrong before...
_debug ("UserAgent: SIP call release");
CallID id = call->getCallId();
Manager::instance().callFailure (id);
terminateOneCall (id);
removeCall (id);
}
void
SIPVoIPLink::SIPCallAnswered (SIPCall *call, pjsip_rx_data *rdata UNUSED)
{
_info ("UserAgent: SIP call answered");
if (!call) {
_warn ("UserAgent: Error: SIP failure, unknown call");
return;
}
if (call->getConnectionState() != Call::Connected) {
_debug ("UserAgent: Update call state , id = %s", call->getCallId().c_str());
call->setConnectionState (Call::Connected);
call->setState (Call::Active);
Manager::instance().peerAnsweredCall (call->getCallId());
} else {
_debug ("UserAgent: Answering call (on/off hold to send ACK)");
}
}
SIPCall*
SIPVoIPLink::getSIPCall (const CallID& id)
{
Call* call = getCall (id);
if (call) {
return dynamic_cast<SIPCall*> (call);
}
return NULL;
}
bool SIPVoIPLink::new_ip_to_ip_call (const CallID& id, const std::string& to)
{
SIPCall *call;
pj_status_t status;
pjsip_dialog *dialog;
pjsip_inv_session *inv;
pjsip_tx_data *tdata;
std::string localAddress, addrSdp;
_debug ("UserAgent: New IP2IP call %s to %s", id.c_str(), to.c_str());
/* Create the call */
call = new SIPCall (id, Call::Outgoing, _pool);
if (call) {
call->setCallConfiguration (Call::IPtoIP);
// Init recfile name using to uri
call->initRecFileName (to);
SIPAccount * account = NULL;
account = dynamic_cast<SIPAccount *> (Manager::instance().getAccount (IP2IP_PROFILE));
if (account == NULL) {
_error ("UserAgent: Error: Account %s is null. Returning", IP2IP_PROFILE);
return !PJ_SUCCESS;
}
// Set the local address
localAddress = getInterfaceAddrFromName (account->getLocalInterface ());
// Set SDP parameters - Set to local
addrSdp = localAddress;
// If local address bound to ANY, reslove it using PJSIP
if (localAddress == "0.0.0.0") {
loadSIPLocalIP (&localAddress);
}
_debug ("UserAgent: Local Address for IP2IP call: %s", localAddress.c_str());
// Local address to appear in SDP
if (addrSdp == "0.0.0.0") {
addrSdp = localAddress;
}
_debug ("UserAgent: Media Address for IP2IP call: %s", localAddress.c_str());
// Set local address for RTP media
setCallAudioLocal (call, localAddress);
std::string toUri = account->getToUri (to);
call->setPeerNumber (toUri);
_debug ("UserAgent: TO uri for IP2IP call: %s", toUri.c_str());
// Building the local SDP offer
// call->getLocalSDP()->set_ip_address (addrSdp);
// call->getLocalSDP()->create_initial_offer (account->getActiveCodecs ());
// Audio Rtp Session must be initialized before creating initial offer in SDP session
// since SDES require crypto attribute.
try {
call->getAudioRtp()->initAudioRtpConfig (call);
call->getAudioRtp()->initAudioRtpSession (call);
} catch (...) {
_debug ("UserAgent: Unable to create RTP Session in new IP2IP call (%s:%d)", __FILE__, __LINE__);
}
// Building the local SDP offer
call->getLocalSDP()->set_ip_address (addrSdp);
call->getLocalSDP()->create_initial_offer (account->getActiveCodecs ());
// Init TLS transport if enabled
if (account->isTlsEnabled()) {
_debug ("UserAgent: TLS enabled for IP2IP calls");
int at = toUri.find ("@");
int trns = toUri.find (";transport");
std::string remoteAddr = toUri.substr (at+1, trns-at-1);
if (toUri.find ("sips:") != 1) {
_debug ("UserAgent: Error \"sips\" scheme required for TLS call");
return false;
}
if (createTlsTransport (account->getAccountID(), remoteAddr) != PJ_SUCCESS)
return false;
}
// If no transport already set, use the default one created at pjsip initialization
if (account->getAccountTransport() == NULL) {
_debug ("UserAgent: No transport for this account, using the default one");
account->setAccountTransport (_localUDPTransport);
}
_debug ("UserAgent: Local port %i for IP2IP call", account->getLocalPort());
_debug ("UserAgent: Local address in sdp %s for IP2IP call", localAddress.c_str());
// Create URI
std::string fromUri = account->getFromUri();
std::string address = findLocalAddressFromUri (toUri, account->getAccountTransport());
int port = findLocalPortFromUri (toUri, account->getAccountTransport());
std::stringstream ss;
std::string portStr;
ss << port;
ss >> portStr;
std::string contactUri = account->getContactHeader (address, portStr);
_debug ("UserAgent: FROM uri: %s, TO uri: %s, CONTACT uri: %s",
fromUri.c_str(), toUri.c_str(), contactUri.c_str());
pj_str_t pjFrom;
pj_cstr (&pjFrom, fromUri.c_str());
pj_str_t pjTo;
pj_cstr (&pjTo, toUri.c_str());
pj_str_t pjContact;
pj_cstr (&pjContact, contactUri.c_str());
// Create the dialog (UAC)
// (Parameters are "strduped" inside this function)
_debug ("UserAgent: Creating dialog for this call");
status = pjsip_dlg_create_uac (pjsip_ua_instance(), &pjFrom, &pjContact, &pjTo, NULL, &dialog);
PJ_ASSERT_RETURN (status == PJ_SUCCESS, false);
// Create the invite session for this call
_debug ("UserAgent: Creating invite session for this call");
status = pjsip_inv_create_uac (dialog, call->getLocalSDP()->get_local_sdp_session(), 0, &inv);
PJ_ASSERT_RETURN (status == PJ_SUCCESS, false);
if (! (account->getServiceRoute().empty())) {
_error ("UserAgent: Set Service-Route with %s", account->getServiceRoute().c_str());
pjsip_route_hdr *route_set = pjsip_route_hdr_create (_pool);
pjsip_route_hdr *routing = pjsip_route_hdr_create (_pool);
pjsip_sip_uri *url = pjsip_sip_uri_create (_pool, 0);
routing->name_addr.uri = (pjsip_uri*) url;
pj_strdup2 (_pool, &url->host, account->getServiceRoute().c_str());
pj_list_push_back (&route_set, pjsip_hdr_clone (_pool, routing));
pjsip_dlg_set_route_set (dialog, route_set);
}
// Set the appropriate transport
pjsip_tpselector *tp;
init_transport_selector (account->getAccountTransport(), &tp);
if (!account->getAccountTransport()) {
_error ("UserAgent: Error: Transport is NULL in IP2IP call");
}
// set_transport methods increment transport's ref_count
status = pjsip_dlg_set_transport (dialog, tp);
// decrement transport's ref count
// pjsip_transport_dec_ref(account->getAccountTransport());
if (status != PJ_SUCCESS) {
_error ("UserAgent: Error: Failed to set the transport for an IP2IP call");
return status;
}
// Associate current call in the invite session
inv->mod_data[getModId() ] = call;
status = pjsip_inv_invite (inv, &tdata);
PJ_ASSERT_RETURN (status == PJ_SUCCESS, false);
// Associate current invite session in the call
call->setInvSession (inv);
status = pjsip_inv_send_msg (inv, tdata);
if (status != PJ_SUCCESS) {
delete call;
call = 0;
return false;
}
call->setConnectionState (Call::Progressing);
call->setState (Call::Active);
addCall (call);
return true;
} else
return false;
}
///////////////////////////////////////////////////////////////////////////////
// Private functions
///////////////////////////////////////////////////////////////////////////////
bool get_dns_server_addresses (std::vector<std::string> *servers)
{
int server_count, i;
std::vector<std::string> nameservers;
struct sockaddr_in current_server;
in_addr address;
// Read configuration files
if (res_init () != 0) {
_debug ("UserAgent: Resolver initialization failed");
return false;
}
server_count = _res.nscount;
for (i=0; i<server_count; i++) {
current_server = (struct sockaddr_in) _res.nsaddr_list[i];
address = current_server.sin_addr;
nameservers.push_back (inet_ntoa (address));
}
*servers = nameservers;
return true;
}
pj_status_t SIPVoIPLink::enable_dns_srv_resolver (pjsip_endpoint *endpt, pj_dns_resolver **p_resv)
{
pj_status_t status;
pj_dns_resolver *resv;
std::vector <std::string> dns_servers;
int scount, i;
_debug ("UserAgent: Enable DNS SRV resolver");
// Create the DNS resolver instance
status = pjsip_endpt_create_resolver (endpt, &resv);
if (status != PJ_SUCCESS) {
_error ("UserAgent: Error: Creating the DNS resolver instance");
return status;
}
if (!get_dns_server_addresses (&dns_servers)) {
_error ("UserAgent: Error: while fetching DNS information");
return -1;
}
// Build the nameservers list needed by pjsip
if ( (scount = dns_servers.size ()) <= 0) {
_warn ("UserAgent: No server detected while fetching DNS information, stop dns resolution");
return 0;
}
pj_str_t nameservers[scount];
for (i = 0; i<scount; i++) {
_debug ("UserAgent: Server: %s", (char *) dns_servers[i].c_str());
nameservers[i] = pj_str ( (char *) dns_servers[i].c_str());
}
// Update the name servers for the DNS resolver
status = pj_dns_resolver_set_ns (resv, scount, nameservers, NULL);
if (status != PJ_SUCCESS) {
_debug ("UserAgent: Error updating the name servers for the DNS resolver");
return status;
}
// Set the DNS resolver instance of the SIP resolver engine
status = pjsip_endpt_set_resolver (endpt, resv);
if (status != PJ_SUCCESS) {
_debug ("UserAgent: Error setting the DNS resolver instance of the SIP resolver engine");
return status;
}
*p_resv = resv;
return PJ_SUCCESS;
}
bool SIPVoIPLink::pjsip_init()
{
pj_status_t status;
pjsip_inv_callback inv_cb;
pj_str_t accepted;
std::string name_mod;
pj_dns_resolver *p_resv;
std::string addr;
name_mod = "sflphone";
_debug ("pjsip_init");
// Init PJLIB: must be called before any call to the pjsip library
status = pj_init();
// Use pjsip macros for sanity check
PJ_ASSERT_RETURN (status == PJ_SUCCESS, 1);
// Init PJLIB-UTIL library
status = pjlib_util_init();
PJ_ASSERT_RETURN (status == PJ_SUCCESS, 1);
// Set the pjsip log level
pj_log_set_level (PJ_LOG_LEVEL);
// Init PJNATH
status = pjnath_init();
PJ_ASSERT_RETURN (status == PJ_SUCCESS, 1);
// Create a pool factory to allocate memory
pj_caching_pool_init (&_cp, &pj_pool_factory_default_policy, 0);
// Create memory pool for application.
_pool = pj_pool_create (&_cp.factory, "sflphone", 4000, 4000, NULL);
if (!_pool) {
_debug ("UserAgent: Could not initialize memory pool");
return PJ_ENOMEM;
}
// Create the SIP endpoint
status = pjsip_endpt_create (&_cp.factory, pj_gethostname()->ptr, &_endpt);
PJ_ASSERT_RETURN (status == PJ_SUCCESS, 1);
if (!loadSIPLocalIP (&addr)) {
_debug ("UserAgent: Unable to determine network capabilities");
return false;
}
// Initialize default UDP transport according to
// IP to IP settings (most likely using port 5060)
createDefaultSipUdpTransport();
// Call this method to create TLS listener
createDefaultSipTlsListener();
// Initialize transaction layer
status = pjsip_tsx_layer_init_module (_endpt);
PJ_ASSERT_RETURN (status == PJ_SUCCESS, 1);
// Initialize UA layer module
status = pjsip_ua_init_module (_endpt, NULL);
PJ_ASSERT_RETURN (status == PJ_SUCCESS, 1);
// Initialize Replaces support. See the Replaces specification in RFC 3891
status = pjsip_replaces_init_module (_endpt);
PJ_ASSERT_RETURN (status == PJ_SUCCESS, 1);
// Initialize 100rel support
status = pjsip_100rel_init_module (_endpt);
PJ_ASSERT_RETURN (status == PJ_SUCCESS, 1);
// Initialize and register sflphone module
_mod_ua.name = pj_str ( (char*) name_mod.c_str());
_mod_ua.id = -1;
_mod_ua.priority = PJSIP_MOD_PRIORITY_APPLICATION;
_mod_ua.on_rx_request = &mod_on_rx_request;
_mod_ua.on_rx_response = &mod_on_rx_response;
status = pjsip_endpt_register_module (_endpt, &_mod_ua);
PJ_ASSERT_RETURN (status == PJ_SUCCESS, 1);
// Init the event subscription module.
// It extends PJSIP by supporting SUBSCRIBE and NOTIFY methods
status = pjsip_evsub_init_module (_endpt);
PJ_ASSERT_RETURN (status == PJ_SUCCESS, 1);
// Init xfer/REFER module
status = pjsip_xfer_init_module (_endpt);
PJ_ASSERT_RETURN (status == PJ_SUCCESS, 1);
status = enable_dns_srv_resolver (_endpt, &p_resv);
PJ_ASSERT_RETURN (status == PJ_SUCCESS, 1);
// Init the callback for INVITE session:
pj_bzero (&inv_cb, sizeof (inv_cb));
inv_cb.on_state_changed = &call_on_state_changed;
inv_cb.on_new_session = &call_on_forked;
inv_cb.on_media_update = &call_on_media_update;
inv_cb.on_tsx_state_changed = &call_on_tsx_changed;
inv_cb.on_rx_offer = &on_rx_offer;
inv_cb.on_create_offer = &on_create_offer;
// Initialize session invite module
status = pjsip_inv_usage_init (_endpt, &inv_cb);
PJ_ASSERT_RETURN (status == PJ_SUCCESS, 1);
_debug ("UserAgent: VOIP callbacks initialized");
// Add endpoint capabilities (INFO, OPTIONS, etc) for this UA
pj_str_t allowed[] = { { (char*) "INFO", 4}, { (char*) "REGISTER", 8}, { (char*) "OPTIONS", 7}, { (char*) "MESSAGE", 7 } }; // //{"INVITE", 6}, {"ACK",3}, {"BYE",3}, {"CANCEL",6}
accepted = pj_str ( (char*) "application/sdp");
// Register supported methods
pjsip_endpt_add_capability (_endpt, &_mod_ua, PJSIP_H_ALLOW, NULL, PJ_ARRAY_SIZE (allowed), allowed);
const pj_str_t STR_MIME_TEXT_PLAIN = { (char*) "text/plain", 10 };
pjsip_endpt_add_capability (_endpt, &_mod_ua, PJSIP_H_ACCEPT, NULL, 1, &STR_MIME_TEXT_PLAIN);
// Register "application/sdp" in ACCEPT header
pjsip_endpt_add_capability (_endpt, &_mod_ua, PJSIP_H_ACCEPT, NULL, 1, &accepted);
_debug ("UserAgent: pjsip version %s for %s initialized", pj_get_version(), PJ_OS_NAME);
// Create the secondary thread to poll sip events
_evThread->start();
/* Done! */
return PJ_SUCCESS;
}
pj_status_t SIPVoIPLink::stunServerResolve (AccountID id)
{
pj_str_t stunServer;
pj_uint16_t stunPort;
pj_stun_sock_cb stun_sock_cb;
pj_stun_sock *stun_sock;
pj_stun_config stunCfg;
pj_status_t status;
// Fetch the account information from the config file
SIPAccount * account = NULL;
account = dynamic_cast<SIPAccount *> (Manager::instance().getAccount (id));
if (account == NULL) {
_debug ("stunServerResolve: Account is null. Returning");
return !PJ_SUCCESS;
}
// Get the STUN server name and port
stunServer = account->getStunServerName ();
stunPort = account->getStunPort ();
// Initialize STUN configuration
pj_stun_config_init (&stunCfg, &_cp.factory, 0, pjsip_endpt_get_ioqueue (_endpt), pjsip_endpt_get_timer_heap (_endpt));
status = PJ_EPENDING;
pj_bzero (&stun_sock_cb, sizeof (stun_sock_cb));
stun_sock_cb.on_rx_data = &stun_sock_on_rx_data;
stun_sock_cb.on_status = &stun_sock_on_status;
status = pj_stun_sock_create (&stunCfg, "stunresolve", pj_AF_INET(), &stun_sock_cb, NULL, NULL, &stun_sock);
if (status != PJ_SUCCESS) {
char errmsg[PJ_ERR_MSG_SIZE];
pj_strerror (status, errmsg, sizeof (errmsg));
_debug ("Error creating STUN socket for %.*s: %s", (int) stunServer.slen, stunServer.ptr, errmsg);
return status;
}
status = pj_stun_sock_start (stun_sock, &stunServer, stunPort, NULL);
if (status != PJ_SUCCESS) {
char errmsg[PJ_ERR_MSG_SIZE];
pj_strerror (status, errmsg, sizeof (errmsg));
_debug ("Error starting STUN socket for %.*s: %s", (int) stunServer.slen, stunServer.ptr, errmsg);
pj_stun_sock_destroy (stun_sock);
stun_sock = NULL;
return status;
}
return status;
}
bool SIPVoIPLink::acquireTransport (const AccountID& accountID)
{
SIPAccount* account = dynamic_cast<SIPAccount *> (Manager::instance().getAccount (accountID));
if (!account)
return false;
// If an account is already bound to this account, decrease its reference
// as it is going to change. If the same transport is selected, reference
// counter will be increased
if (account->getAccountTransport()) {
_debug ("pjsip_transport_dec_ref in acquireTransport");
pjsip_transport_dec_ref (account->getAccountTransport());
}
// Try to create a new transport in case the settings for this account
// are different than one defined for already created ones
// If TLS is enabled, TLS connection is automatically handled when sending account registration
// However, for any other sip transaction, we must create TLS connection
if (createSipTransport (accountID)) {
return true;
}
// A transport is already created on this port, use it
else {
_debug ("Could not create a new transport (%s)", account->getTransportMapKey().c_str());
_debug ("Searching transport (%s) in transport map", account->getTransportMapKey().c_str());
// Could not create new transport, this transport may already exists
SipTransportMap::iterator transport;
transport = _transportMap.find (account->getTransportMapKey());
if (transport != _transportMap.end()) {
// Transport already exist, use it for this account
_debug ("Found transport (%s) in transport map", account->getTransportMapKey().c_str());
pjsip_transport* tr = transport->second;
// Set transport to be used for transaction involving this account
account->setAccountTransport (tr);
// Increment newly associated transport reference counter
// If the account is shutdowning, time is automatically canceled
pjsip_transport_add_ref (tr);
return true;
} else {
// Transport could not either be created, socket not available
_debug ("Did not find transport (%s) in transport map", account->getTransportMapKey().c_str());
account->setAccountTransport (_localUDPTransport);
std::string localHostName (_localUDPTransport->local_name.host.ptr, _localUDPTransport->local_name.host.slen);
_debug ("Use default one instead (%s:%i)", localHostName.c_str(), _localUDPTransport->local_name.port);
// account->setLocalAddress(localHostName);
account->setLocalPort (_localUDPTransport->local_name.port);
// Transport could not either be created or found in the map, socket not available
return false;
}
}
}
bool SIPVoIPLink::createDefaultSipUdpTransport()
{
int errPjsip = 0;
// Retrieve Direct IP Calls settings.
SIPAccount * account = NULL;
// Use IP2IP_PROFILE to init default udp transport settings
account = dynamic_cast<SIPAccount *> (Manager::instance().getAccount (IP2IP_PROFILE));
// Create a UDP listener meant for all accounts for which TLS was not enabled
// Cannot acquireTransport since default UDP transport must be created regardless of TLS
errPjsip = createUdpTransport (IP2IP_PROFILE);
if (account && (errPjsip == PJ_SUCCESS)) {
// Store transport in transport map
addTransportToMap (account->getTransportMapKey(), account->getAccountTransport());
// if account is not NULL, use IP2IP trasport as default one
_localUDPTransport = account->getAccountTransport();
}
// If the above UDP server
// could not be created, then give it another try
// on a random sip port
else if (errPjsip != PJ_SUCCESS) {
_debug ("UserAgent: Could not initialize SIP listener on port %d", _regPort);
_regPort = RANDOM_SIP_PORT;
_debug ("UserAgent: Trying to initialize SIP listener on port %d", _regPort);
// If no AccountID specified, pointer to transport is stored in _localUDPTransport
errPjsip = createUdpTransport();
if (errPjsip != PJ_SUCCESS) {
_debug ("UserAgent: Fail to initialize SIP listener on port %d", _regPort);
return false;
}
}
return true;
}
void SIPVoIPLink::createDefaultSipTlsListener()
{
SIPAccount * account = NULL;
account = dynamic_cast<SIPAccount *> (Manager::instance().getAccount (IP2IP_PROFILE));
if (account->isTlsEnabled()) {
createTlsListener (IP2IP_PROFILE);
}
}
void SIPVoIPLink::createTlsListener (const AccountID& accountID)
{
pjsip_tpfactory *tls;
pj_sockaddr_in local_addr;
pjsip_host_port a_name;
pj_status_t status;
pj_status_t success;
_debug ("Create TLS listener");
/* Grab the tls settings, populated
* from configuration file.
*/
SIPAccount * account = NULL;
account = dynamic_cast<SIPAccount *> (Manager::instance().getAccount (accountID));
if (account == NULL) {
_debug ("UserAgent: Account is null while creating TLS default listener. Returning");
// return !PJ_SUCCESS;
}
// Init local address for this listener to be bound (ADDR_ANY on port 5061).
pj_sockaddr_in_init (&local_addr, 0, 0);
pj_uint16_t localTlsPort = account->getTlsListenerPort();
local_addr.sin_port = pj_htons (localTlsPort);
pj_str_t pjAddress;
pj_cstr (&pjAddress, PJ_INADDR_ANY);
success = pj_sockaddr_in_set_str_addr (&local_addr, &pjAddress);
// Init published address for this listener (Local IP address on port 5061)
std::string publishedAddress;
loadSIPLocalIP (&publishedAddress);
pj_bzero (&a_name, sizeof (pjsip_host_port));
pj_cstr (&a_name.host, publishedAddress.c_str());
a_name.port = account->getTlsListenerPort();
/* Get TLS settings. Expected to be filled */
pjsip_tls_setting * tls_setting = account->getTlsSetting();
_debug ("UserAgent: TLS transport to be initialized with published address %.*s,"
" published port %d,\n local address %.*s, local port %d",
(int) a_name.host.slen, a_name.host.ptr,
(int) a_name.port, pjAddress.slen, pjAddress.ptr, (int) localTlsPort);
status = pjsip_tls_transport_start (_endpt, tls_setting, &local_addr, &a_name, 1, &tls);
if (status != PJ_SUCCESS) {
_debug ("UserAgent: Error creating SIP TLS listener (%d)", status);
} else {
_localTlsListener = tls;
}
// return PJ_SUCCESS;
}
bool SIPVoIPLink::createSipTransport (AccountID id)
{
SIPAccount* account = dynamic_cast<SIPAccount *> (Manager::instance().getAccount (id));
if (!account)
return false;
pj_status_t status;
if (account->isTlsEnabled()) {
if (_localTlsListener == NULL)
createTlsListener (id);
// Parse remote address to establish connection
std::string remoteSipUri = account->getServerUri();
int sips = remoteSipUri.find ("<sips:") + 6;
int trns = remoteSipUri.find (";transport");
std::string remoteAddr = remoteSipUri.substr (sips, trns-sips);
// Nothing to do, TLS listener already created at pjsip's startup and TLS connection
// is automatically handled in pjsip when sending registration messages.
if (createTlsTransport (id, remoteAddr) != PJ_SUCCESS)
return false;
return true;
} else {
// Launch a new UDP listener/transport, using the published address
if (account->isStunEnabled ()) {
status = createAlternateUdpTransport (id);
if (status != PJ_SUCCESS) {
_debug ("Failed to init UDP transport with STUN published address for account %s", id.c_str());
return false;
}
} else {
status = createUdpTransport (id);
if (status != PJ_SUCCESS) {
_debug ("Failed to initialize UDP transport for account %s", id.c_str());
return false;
} else {
// If transport successfully created, store it in the internal map.
// STUN aware transport are account specific and should not be stored in map.
// TLS transport is ephemeral and is managed by PJSIP, should not be stored either.
addTransportToMap (account->getTransportMapKey(), account->getAccountTransport());
}
}
}
return true;
}
bool SIPVoIPLink::addTransportToMap (std::string key, pjsip_transport* transport)
{
SipTransportMap::iterator iter_transport;
iter_transport = _transportMap.find (key);
// old transport in transport map, erase it
if (iter_transport != _transportMap.end()) {
_transportMap.erase (iter_transport);
}
_debug ("UserAgent: Storing newly created transport in map using key %s", key.c_str());
_transportMap.insert (pair<std::string, pjsip_transport*> (key, transport));
return true;
}
int SIPVoIPLink::createUdpTransport (AccountID id)
{
pj_status_t status;
pj_sockaddr_in bound_addr;
pjsip_host_port a_name;
// char tmpIP[32];
pjsip_transport *transport;
std::string listeningAddress = "0.0.0.0";
int listeningPort = _regPort;
/* Use my local address as default value */
if (!loadSIPLocalIP (&listeningAddress))
return !PJ_SUCCESS;
_debug ("UserAgent: Create UDP transport for account \"%s\"", id.c_str());
/*
* Retrieve the account information
*/
SIPAccount * account = NULL;
// if account id is not specified, init _localUDPTransport
if (id != "") {
account = dynamic_cast<SIPAccount *> (Manager::instance().getAccount (id));
}
// Set information to the local address and port
if (account == NULL) {
_debug ("UserAgent: Account with id \"%s\" is null in createUdpTransport.", id.c_str());
} else {
// We are trying to initialize a UDP transport available for all local accounts and direct IP calls
_debug ("UserAgent: found account %s in map", account->getAccountID().c_str());
if (account->getLocalInterface () != "default") {
listeningAddress = getInterfaceAddrFromName (account->getLocalInterface());
}
listeningPort = account->getLocalPort ();
}
pj_memset (&bound_addr, 0, sizeof (bound_addr));
pj_str_t temporary_address;
if (account && account->getLocalInterface () == "default") {
// Init bound address to ANY
bound_addr.sin_addr.s_addr = pj_htonl (PJ_INADDR_ANY);
loadSIPLocalIP (&listeningAddress);
} else {
// bind this account to a specific interface
pj_strdup2 (_pool, &temporary_address, listeningAddress.c_str());
bound_addr.sin_addr = pj_inet_addr (&temporary_address);
}
bound_addr.sin_port = pj_htons ( (pj_uint16_t) listeningPort);
bound_addr.sin_family = PJ_AF_INET;
pj_bzero (bound_addr.sin_zero, sizeof (bound_addr.sin_zero));
// Create UDP-Server (default port: 5060)
// Use here either the local information or the published address
if (account && !account->getPublishedSameasLocal ()) {
// Set the listening address to the published address
listeningAddress = account->getPublishedAddress ();
// Set the listening port to the published port
listeningPort = account->getPublishedPort ();
_debug ("UserAgent: Creating UDP transport published %s:%i", listeningAddress.c_str (), listeningPort);
}
// We must specify this here to avoid the IP2IP_PROFILE
// to create a transport with name 0.0.0.0 to appear in the via header
if (id == IP2IP_PROFILE)
loadSIPLocalIP (&listeningAddress);
if (listeningAddress == "" || listeningPort == 0) {
_error ("UserAgent: Error invalid address for new udp transport");
return !PJ_SUCCESS;
}
//strcpy (tmpIP, listeningAddress.data());
/* Init published name */
pj_bzero (&a_name, sizeof (pjsip_host_port));
pj_cstr (&a_name.host, listeningAddress.c_str());
a_name.port = listeningPort;
//pj_strdup2 (_pool, &a_name.host, tmpIP);
//a_name.port = (pj_uint16_t) listeningPort;
status = pjsip_udp_transport_start (_endpt, &bound_addr, &a_name, 1, &transport);
// Print info from transport manager associated to endpoint
pjsip_tpmgr * tpmgr = pjsip_endpt_get_tpmgr (_endpt);
pjsip_tpmgr_dump_transports (tpmgr);
if (status != PJ_SUCCESS) {
_debug ("UserAgent: (%d) Unable to start UDP transport on %s:%d", status, listeningAddress.data(), listeningPort);
return status;
} else {
_debug ("UserAgent: UDP transport initialized successfully on %s:%d", listeningAddress.c_str (), listeningPort);
if (account == NULL) {
_debug ("UserAgent: Use transport as local UDP server");
_localUDPTransport = transport;
} else {
_debug ("UserAgent: bind transport to account %s", account->getAccountID().c_str());
account->setAccountTransport (transport);
}
}
return PJ_SUCCESS;
}
std::string SIPVoIPLink::findLocalAddressFromUri (const std::string& uri, pjsip_transport *transport)
{
pj_str_t localAddress;
pjsip_transport_type_e transportType;
pjsip_tpselector *tp_sel;
_debug ("SIP: Find local address from URI");
// Find the transport that must be used with the given uri
pj_str_t tmp;
pj_strdup2_with_null (_pool, &tmp, uri.c_str());
pjsip_uri * genericUri = NULL;
genericUri = pjsip_parse_uri (_pool, tmp.ptr, tmp.slen, 0);
pj_str_t pjMachineName;
pj_strdup (_pool, &pjMachineName, pj_gethostname());
std::string machineName (pjMachineName.ptr, pjMachineName.slen);
if (genericUri == NULL) {
_warn ("SIP: generic URI is NULL in findLocalAddressFromUri");
return machineName;
}
pjsip_sip_uri * sip_uri = NULL;
sip_uri = (pjsip_sip_uri*) pjsip_uri_get_uri (genericUri);
if (sip_uri == NULL) {
_warn ("SIP: Invalid uri in findLocalAddressFromURI");
return machineName;
}
if (PJSIP_URI_SCHEME_IS_SIPS (sip_uri)) {
transportType = PJSIP_TRANSPORT_TLS;
} else {
if (transport == NULL) {
_warn ("SIP: Transport is NULL in findLocalAddressFromUri. Try the local UDP transport");
transport = _localUDPTransport;
}
transportType = PJSIP_TRANSPORT_UDP;
}
// Get the transport manager associated with
// this endpoint
pjsip_tpmgr * tpmgr = NULL;
tpmgr = pjsip_endpt_get_tpmgr (_endpt);
if (tpmgr == NULL) {
_warn ("SIP: Unexpected: Cannot get tpmgr from endpoint.");
return machineName;
}
// Find the local address (and port) based on the registered
// transports and the transport type
int port;
pj_status_t status;
/* Init the transport selector */
//_debug ("Transport ID: %s", transport->obj_name);
if (transportType == PJSIP_TRANSPORT_UDP) {
status = init_transport_selector (transport, &tp_sel);
if (status == PJ_SUCCESS) {
status = pjsip_tpmgr_find_local_addr (tpmgr, _pool, transportType, tp_sel, &localAddress, &port);
} else {
status = pjsip_tpmgr_find_local_addr (tpmgr, _pool, transportType, NULL, &localAddress, &port);
}
} else {
status = pjsip_tpmgr_find_local_addr (tpmgr, _pool, transportType, NULL, &localAddress, &port);
}
if (status != PJ_SUCCESS) {
_debug ("SIP: Failed to find local address from transport");
return machineName;
}
std::string localaddr (localAddress.ptr, localAddress.slen);
if (localaddr == "0.0.0.0")
loadSIPLocalIP (&localaddr);
_debug ("SIP: Local address discovered from attached transport: %s", localaddr.c_str());
return localaddr;
}
pj_status_t SIPVoIPLink::init_transport_selector (pjsip_transport *transport, pjsip_tpselector **tp_sel)
{
pjsip_tpselector *tp;
if (transport != NULL) {
tp = (pjsip_tpselector *) pj_pool_zalloc (_pool, sizeof (pjsip_tpselector));
tp->type = PJSIP_TPSELECTOR_TRANSPORT;
tp->u.transport = transport;
*tp_sel = tp;
return PJ_SUCCESS;
}
return !PJ_SUCCESS;
}
int SIPVoIPLink::findLocalPortFromUri (const std::string& uri, pjsip_transport *transport)
{
pj_str_t localAddress;
pjsip_transport_type_e transportType;
int port;
pjsip_tpselector *tp_sel;
// Find the transport that must be used with the given uri
pj_str_t tmp;
pj_strdup2_with_null (_pool, &tmp, uri.c_str());
pjsip_uri * genericUri = NULL;
genericUri = pjsip_parse_uri (_pool, tmp.ptr, tmp.slen, 0);
if (genericUri == NULL) {
_debug ("UserAgent: genericUri is NULL in findLocalPortFromUri");
return atoi (DEFAULT_SIP_PORT);
}
pjsip_sip_uri * sip_uri = NULL;
sip_uri = (pjsip_sip_uri*) pjsip_uri_get_uri (genericUri);
if (sip_uri == NULL) {
_debug ("UserAgent: Invalid uri in findLocalAddressFromTransport");
return atoi (DEFAULT_SIP_PORT);
}
if (PJSIP_URI_SCHEME_IS_SIPS (sip_uri)) {
transportType = PJSIP_TRANSPORT_TLS;
port = atoi (DEFAULT_SIP_TLS_PORT);
} else {
if (transport == NULL) {
_debug ("UserAgent: transport is NULL in findLocalPortFromUri - Try the local UDP transport");
transport = _localUDPTransport;
}
transportType = PJSIP_TRANSPORT_UDP;
port = atoi (DEFAULT_SIP_PORT);
}
// Get the transport manager associated with
// this endpoint
pjsip_tpmgr * tpmgr = NULL;
tpmgr = pjsip_endpt_get_tpmgr (_endpt);
if (tpmgr == NULL) {
_debug ("UserAgent: unexpected, cannot get tpmgr from endpoint.");
return port;
}
// Find the local address (and port) based on the registered
// transports and the transport type
/* Init the transport selector */
pj_status_t status;
if (transportType == PJSIP_TRANSPORT_UDP) {
_debug ("UserAgent: transport ID: %s", transport->obj_name);
status = init_transport_selector (transport, &tp_sel);
if (status == PJ_SUCCESS)
status = pjsip_tpmgr_find_local_addr (tpmgr, _pool, transportType, tp_sel, &localAddress, &port);
else
status = pjsip_tpmgr_find_local_addr (tpmgr, _pool, transportType, NULL, &localAddress, &port);
} else
status = pjsip_tpmgr_find_local_addr (tpmgr, _pool, transportType, NULL, &localAddress, &port);
if (status != PJ_SUCCESS) {
_debug ("UserAgent: failed to find local address from transport");
}
_debug ("UserAgent: local port discovered from attached transport: %i", port);
return port;
}
pj_status_t SIPVoIPLink::createTlsTransport (const AccountID& accountID, std::string remoteAddr)
{
pj_status_t success;
_debug ("Create TLS transport for account %s\n", accountID.c_str());
// Retrieve the account information
SIPAccount * account = dynamic_cast<SIPAccount *> (Manager::instance().getAccount (accountID));
if (!account) {
_debug ("UserAgent: Account is NULL when creating TLS connection, returning");
return !PJ_SUCCESS;
}
pj_sockaddr_in rem_addr;
pj_str_t remote;
pj_cstr (&remote, remoteAddr.c_str());
pj_sockaddr_in_init (&rem_addr, &remote, (pj_uint16_t) 5061);
// Update TLS settings for account registration using the default listeners
// Pjsip does not allow to create multiple listener
// pjsip_tpmgr *mgr = pjsip_endpt_get_tpmgr(_endpt);
// pjsip_tls_listener_update_settings(_endpt, _pool, mgr, _localTlsListener, account->getTlsSetting());
// Create a new TLS connection from TLS listener
pjsip_transport *tls;
success = pjsip_endpt_acquire_transport (_endpt, PJSIP_TRANSPORT_TLS, &rem_addr, sizeof (rem_addr), NULL, &tls);
if (success != PJ_SUCCESS)
_debug ("UserAgent: Error could not create TLS transport");
else
account->setAccountTransport (tls);
return success;
}
pj_status_t SIPVoIPLink::createAlternateUdpTransport (AccountID id)
{
pj_sockaddr_in boundAddr;
pjsip_host_port a_name;
pj_status_t status;
pj_str_t stunServer;
pj_uint16_t stunPort;
pj_sockaddr_in pub_addr;
pj_sock_t sock;
std::string listeningAddress = "";
int listeningPort;
_debug ("UserAgent: Create Alternate UDP transport");
/*
* Retrieve the account information
*/
SIPAccount * account = NULL;
account = dynamic_cast<SIPAccount *> (Manager::instance().getAccount (id));
if (account == NULL) {
_error ("UserAgent: Error: Account is null. Returning");
return !PJ_SUCCESS;
}
stunServer = account->getStunServerName ();
stunPort = account->getStunPort ();
status = stunServerResolve (id);
if (status != PJ_SUCCESS) {
_error ("UserAgent: Error: Resolving STUN server: %i", status);
return status;
}
// Init socket
sock = PJ_INVALID_SOCKET;
_debug ("UserAgent: Initializing IPv4 socket on %s:%i", stunServer.ptr, stunPort);
status = pj_sockaddr_in_init (&boundAddr, &stunServer, 0);
if (status != PJ_SUCCESS) {
_debug ("UserAgent: Error: Initializing IPv4 socket on %s:%i", stunServer.ptr, stunPort);
return status;
}
// Create and bind the socket
status = pj_sock_socket (pj_AF_INET(), pj_SOCK_DGRAM(), 0, &sock);
if (status != PJ_SUCCESS) {
_debug ("UserAgent: Error: Unable to create or bind socket (%d)", status);
return status;
}
// Query the mapped IP address and port on the 'outside' of the NAT
status = pjstun_get_mapped_addr (&_cp.factory, 1, &sock, &stunServer, stunPort, &stunServer, stunPort, &pub_addr);
if (status != PJ_SUCCESS) {
_debug ("UserAgwent: Error: Contacting STUN server (%d)", status);
pj_sock_close (sock);
return status;
}
_debug ("UserAgent: Firewall address : %s:%d", pj_inet_ntoa (pub_addr.sin_addr), pj_ntohs (pub_addr.sin_port));
a_name.host = pj_str (pj_inet_ntoa (pub_addr.sin_addr));
a_name.port = pj_ntohs (pub_addr.sin_port);
listeningAddress = std::string (a_name.host.ptr);
listeningPort = (int) a_name.port;
// Set the address to be used in SDP
account->setPublishedAddress (listeningAddress);
account->setPublishedPort (listeningPort);
// Create the UDP transport
pjsip_transport *transport;
status = pjsip_udp_transport_attach2 (_endpt, PJSIP_TRANSPORT_UDP, sock, &a_name, 1, &transport);
if (status != PJ_SUCCESS) {
_debug ("UserAgent: Error: Creating alternate SIP UDP listener (%d)", status);
return status;
}
_debug ("UserAgent: UDP Transport successfully created on %s:%i", listeningAddress.c_str (), listeningPort);
account->setAccountTransport (transport);
if (transport) {
_debug ("UserAgent: Initial ref count: %s %s (refcnt=%i)", transport->obj_name, transport->info,
(int) pj_atomic_get (transport->ref_cnt));
pj_sockaddr *addr = (pj_sockaddr*) & (transport->key.rem_addr);
static char str[PJ_INET6_ADDRSTRLEN];
pj_inet_ntop ( ( (const pj_sockaddr*) addr)->addr.sa_family, pj_sockaddr_get_addr (addr), str, sizeof (str));
_debug ("UserAgent: KEY: %s:%d",str, pj_sockaddr_get_port ( (const pj_sockaddr*) & (transport->key.rem_addr)));
}
pjsip_tpmgr * tpmgr = pjsip_endpt_get_tpmgr (_endpt);
pjsip_tpmgr_dump_transports (tpmgr);
return PJ_SUCCESS;
}
void SIPVoIPLink::shutdownSipTransport (const AccountID& accountID)
{
_debug ("UserAgent: Shutdown Sip Transport");
SIPAccount* account = dynamic_cast<SIPAccount *> (Manager::instance().getAccount (accountID));
if (!account)
return;
if (account->getAccountTransport()) {
_debug ("Transport bound to account, decrease ref count");
// decrease reference count added by pjsip_regc_send
// PJSIP's IDLE timer is set if counter reach 0
// there is still problems when account registration fails, so comment it for now
// status = pjsip_transport_dec_ref(account->getAccountTransport());
// detach transport from this account
account->setAccountTransport (NULL);
}
}
bool SIPVoIPLink::loadSIPLocalIP (std::string *addr)
{
bool returnValue = true;
std::string localAddress = "127.0.0.1";
pj_sockaddr ip_addr;
if (pj_gethostip (pj_AF_INET(), &ip_addr) != PJ_SUCCESS) {
// Update the registration state if no network capabilities found
_debug ("UserAgent: Get host ip failed!");
returnValue = false;
} else {
localAddress = std::string (pj_inet_ntoa (ip_addr.ipv4.sin_addr));
_debug ("UserAgent: Checking network, local IP address: %s", localAddress.data());
}
*addr = localAddress;
return returnValue;
}
void SIPVoIPLink::busy_sleep (unsigned msec)
{
#if defined(PJ_SYMBIAN) && PJ_SYMBIAN != 0
/* Ideally we shouldn't call pj_thread_sleep() and rather
* CActiveScheduler::WaitForAnyRequest() here, but that will
* drag in Symbian header and it doesn't look pretty.
*/
pj_thread_sleep (msec);
#else
pj_time_val timeout, now, tv;
pj_gettimeofday (&timeout);
timeout.msec += msec;
pj_time_val_normalize (&timeout);
tv.sec = 0;
tv.msec = 10;
pj_time_val_normalize (&tv);
do {
pjsip_endpt_handle_events (_endpt, &tv);
pj_gettimeofday (&now);
} while (PJ_TIME_VAL_LT (now, timeout));
#endif
}
bool SIPVoIPLink::pjsip_shutdown (void)
{
if (_endpt) {
_debug ("UserAgent: Shutting down...");
busy_sleep (1000);
}
pj_thread_join (thread);
pj_thread_destroy (thread);
thread = NULL;
/* Destroy endpoint. */
if (_endpt) {
pjsip_endpt_destroy (_endpt);
_endpt = NULL;
}
/* Destroy pool and pool factory. */
if (_pool) {
pj_pool_release (_pool);
_pool = NULL;
pj_caching_pool_destroy (&_cp);
}
/* Shutdown PJLIB */
pj_shutdown();
_debug ("UserAgent: Shutted down successfully");
/* Done. */
return true;
}
int getModId()
{
return _mod_ua.id;
}
static void dns_cb (pj_status_t status, void *token, const struct pjsip_server_addresses *addr)
{
struct result * result = (struct result*) token;
result->status = status;
if (status == PJ_SUCCESS) {
pj_memcpy (&result->servers, addr, sizeof (*addr));
}
}
void set_voicemail_info (AccountID account, pjsip_msg_body *body)
{
int voicemail = 0, pos_begin, pos_end;
std::string voice_str = "Voice-Message: ";
std::string delimiter = "/";
std::string msg_body, voicemail_str;
_debug ("UserAgent: checking the voice message!");
// The voicemail message is formated like that:
// Voice-Message: 1/0 . 1 is the number we want to retrieve in this case
// We get the notification body
msg_body = (char*) body->data;
// We need the position of the first character of the string voice_str
pos_begin = msg_body.find (voice_str);
// We need the position of the delimiter
pos_end = msg_body.find (delimiter);
// So our voicemail number between the both index
try {
voicemail_str = msg_body.substr (pos_begin + voice_str.length(), pos_end - (pos_begin + voice_str.length()));
std::cout << "voicemail number : " << voicemail_str << std::endl;
voicemail = atoi (voicemail_str.c_str());
} catch (std::out_of_range& e) {
std::cerr << e.what() << std::endl;
}
// We need now to notify the manager
if (voicemail != 0)
Manager::instance().startVoiceMessageNotification (account, voicemail);
}
void SIPVoIPLink::handle_reinvite (SIPCall *call UNUSED)
{
_debug ("UserAgent: Handle reinvite");
}
// This callback is called when the invite session state has changed
void call_on_state_changed (pjsip_inv_session *inv, pjsip_event *e)
{
_debug ("UserAgent: Call state changed to %s", invitationStateMap[inv->state]);
pjsip_rx_data *rdata;
/* Retrieve the call information */
SIPCall * call = NULL;
call = reinterpret_cast<SIPCall*> (inv->mod_data[_mod_ua.id]);
if (call == NULL) {
_error ("UserAgent: Error: Call is NULL in call state changed callback");
return;
} else {
}
//Retrieve the body message
rdata = e->body.tsx_state.src.rdata;
// If the call is a direct IP-to-IP call
AccountID accId;
SIPVoIPLink * link = NULL;
if (call->getCallConfiguration () == Call::IPtoIP) {
link = SIPVoIPLink::instance ("");
} else {
accId = Manager::instance().getAccountFromCall (call->getCallId());
link = dynamic_cast<SIPVoIPLink *> (Manager::instance().getAccountLink (accId));
}
if (link == NULL) {
_error ("UserAgent: Error: Link is NULL in call state changed callback");
return;
}
/*
pjsip_hdr *allow_header = NULL;
std::string *allowed_options = NULL;
char header_buffer[500];
if (e->body.tsx_state.src.rdata->msg_info.msg)
allow_header = (pjsip_hdr *) pjsip_msg_find_hdr (e->body.tsx_state.src.rdata->msg_info.msg, PJSIP_H_ALLOW, NULL);
if (allow_header) {
allowed_options = new std::string (allow_header->name.ptr, allow_header->name.slen);
allow_header->vptr->print_on (allow_header, header_buffer, 5000);
std::string theHeader (header_buffer);
}
if (allowed_options)
delete allowed_options;
*/
// If this is an outgoing INVITE that was created because of
// REFER/transfer, send NOTIFY to transferer.
if (call->getXferSub() && e->type==PJSIP_EVENT_TSX_STATE) {
int st_code = -1;
pjsip_evsub_state ev_state = PJSIP_EVSUB_STATE_ACTIVE;
switch (call->getInvSession()->state) {
case PJSIP_INV_STATE_NULL:
case PJSIP_INV_STATE_CALLING:
/* Do nothing */
break;
case PJSIP_INV_STATE_EARLY:
case PJSIP_INV_STATE_CONNECTING:
st_code = e->body.tsx_state.tsx->status_code;
ev_state = PJSIP_EVSUB_STATE_ACTIVE;
break;
case PJSIP_INV_STATE_CONFIRMED:
/* When state is confirmed, send the final 200/OK and terminate
* subscription.
*/
st_code = e->body.tsx_state.tsx->status_code;
ev_state = PJSIP_EVSUB_STATE_TERMINATED;
break;
case PJSIP_INV_STATE_DISCONNECTED:
st_code = e->body.tsx_state.tsx->status_code;
ev_state = PJSIP_EVSUB_STATE_TERMINATED;
break;
case PJSIP_INV_STATE_INCOMING:
/* Nothing to do. Just to keep gcc from complaining about
* unused enums.
*/
break;
}
if (st_code != -1) {
pjsip_tx_data *tdata;
pj_status_t status;
status = pjsip_xfer_notify (call->getXferSub(), ev_state, st_code, NULL, &tdata);
if (status != PJ_SUCCESS) {
_debug ("UserAgent: Unable to create NOTIFY -- %d", status);
} else {
status = pjsip_xfer_send_request (call->getXferSub(), tdata);
if (status != PJ_SUCCESS) {
_debug ("UserAgent: Unable to send NOTIFY -- %d", status);
}
}
}
return;
}
if (inv->state != PJSIP_INV_STATE_CONFIRMED) {
// Update UI with the current status code and description
//pjsip_transaction * tsx
pjsip_transaction * tsx = NULL;
tsx = e->body.tsx_state.tsx;
int statusCode = 404;
if (tsx != NULL) {
statusCode = tsx->status_code;
}
const pj_str_t * description = pjsip_get_status_text (statusCode);
if (statusCode) {
// test wether or not dbus manager is instantiated, if not no need to notify the client
if (Manager::instance().getDbusManager())
DBusManager::instance().getCallManager()->sipCallStateChanged (call->getCallId(), std::string (description->ptr, description->slen), statusCode);
}
}
// The call is ringing - We need to handle this case only on outgoing call
if (inv->state == PJSIP_INV_STATE_EARLY && e->body.tsx_state.tsx->role == PJSIP_ROLE_UAC) {
call->setConnectionState (Call::Ringing);
Manager::instance().peerRingingCall (call->getCallId());
}
// After we sent or received a ACK - The connection is established
else if (inv->state == PJSIP_INV_STATE_CONFIRMED) {
link->SIPCallAnswered (call, rdata);
} else if (inv->state == PJSIP_INV_STATE_DISCONNECTED) {
_debug ("UserAgent: State: %s. Cause: %.*s", invitationStateMap[inv->state], (int) inv->cause_text.slen, inv->cause_text.ptr);
accId = Manager::instance().getAccountFromCall (call->getCallId());
link = dynamic_cast<SIPVoIPLink *> (Manager::instance().getAccountLink (accId));
// Make sure link is valid
assert (link);
switch (inv->cause) {
// The call terminates normally - BYE / CANCEL
case PJSIP_SC_OK:
case PJSIP_SC_REQUEST_TERMINATED:
link->SIPCallClosed (call);
break;
case PJSIP_SC_DECLINE:
_debug ("UserAgent: Call %s is declined", call->getCallId().c_str());
if (inv->role == PJSIP_ROLE_UAC)
link->SIPCallServerFailure (call);
break;
case PJSIP_SC_NOT_FOUND: /* peer not found */
case PJSIP_SC_REQUEST_TIMEOUT: /* request timeout */
case PJSIP_SC_NOT_ACCEPTABLE_HERE: /* no compatible codecs */
case PJSIP_SC_NOT_ACCEPTABLE_ANYWHERE:
case PJSIP_SC_UNSUPPORTED_MEDIA_TYPE:
case PJSIP_SC_UNAUTHORIZED:
case PJSIP_SC_FORBIDDEN:
case PJSIP_SC_REQUEST_PENDING:
case PJSIP_SC_ADDRESS_INCOMPLETE:
link->SIPCallServerFailure (call);
break;
default:
link->SIPCallServerFailure (call);
_error ("UserAgent: Unhandled call state. This is probably a bug.");
break;
}
}
}
// This callback is called after SDP offer/answer session has completed.
void call_on_media_update (pjsip_inv_session *inv, pj_status_t status)
{
_debug ("UserAgent: Call media update");
const pjmedia_sdp_session *local_sdp;
const pjmedia_sdp_session *remote_sdp;
SIPVoIPLink * link = NULL;
SIPCall * call;
call = reinterpret_cast<SIPCall *> (inv->mod_data[getModId() ]);
if (!call) {
_debug ("UserAgent: Call declined by peer, SDP negociation stopped");
return;
}
link = dynamic_cast<SIPVoIPLink *> (Manager::instance().getAccountLink (AccountNULL));
if (link == NULL) {
_warn ("UserAgent: Error: Failed to get sip link");
return;
}
if (status != PJ_SUCCESS) {
_warn ("UserAgent: Error: while negotiating the offer");
link->hangup (call->getCallId());
Manager::instance().callFailure (call->getCallId());
return;
}
if (!inv->neg) {
return;
}
// Get the new sdp, result of the negotiation
pjmedia_sdp_neg_get_active_local (inv->neg, &local_sdp);
pjmedia_sdp_neg_get_active_remote (inv->neg, &remote_sdp);
// Clean the resulting sdp offer to create a new one (in case of a reinvite)
call->getLocalSDP()->clean_session_media();
// Set the fresh negotiated one, no matter if that was an offer or answer.
// The local sdp is updated in case of an answer, even if the remote sdp
// is kept internally.
call->getLocalSDP()->set_negotiated_sdp (local_sdp);
// Set remote ip / port
call->getLocalSDP()->set_media_transport_info_from_remote_sdp (remote_sdp);
try {
call->getAudioRtp()->updateDestinationIpAddress();
} catch (...) {
}
// Get the crypto attribute containing srtp's cryptographic context (keys, cipher)
CryptoOffer crypto_offer;
call->getLocalSDP()->get_remote_sdp_crypto_from_offer (remote_sdp, crypto_offer);
bool nego_success = false;
if (!crypto_offer.empty()) {
_debug ("UserAgent: Crypto attribute in SDP, init SRTP session");
// init local cryptografic capabilities for negotiation
std::vector<sfl::CryptoSuiteDefinition>localCapabilities;
for (int i = 0; i < 3; i++) {
localCapabilities.push_back (sfl::CryptoSuites[i]);
}
sfl::SdesNegotiator sdesnego (localCapabilities, crypto_offer);
if (sdesnego.negotiate()) {
_debug ("UserAgent: SDES negociation successfull \n");
nego_success = true;
_debug ("UserAgent: Set remote cryptographic context\n");
try {
call->getAudioRtp()->setRemoteCryptoInfo (sdesnego);
} catch (...) {}
DBusManager::instance().getCallManager()->secureSdesOn (call->getCallId());
} else {
DBusManager::instance().getCallManager()->secureSdesOff (call->getCallId());
}
}
// We did not found any crypto context for this media
if (!nego_success && call->getAudioRtp()->getAudioRtpType() == sfl::Sdes) {
// We did not found any crypto context for this media
// @TODO if SRTPONLY, CallFail
_debug ("UserAgent: Did not found any crypto or negociation failed but Sdes enabled");
call->getAudioRtp()->stop();
call->getAudioRtp()->setSrtpEnabled (false);
// if RTPFALLBACK, change RTP session
AccountID accountID = Manager::instance().getAccountFromCall (call->getCallId());
SIPAccount *account = (SIPAccount *) Manager::instance().getAccount (accountID);
if (account->getSrtpFallback())
call->getAudioRtp()->initAudioRtpSession (call);
}
if (nego_success && call->getAudioRtp()->getAudioRtpType() != sfl::Sdes) {
// We found a crypto context for this media but Sdes is not
// enabled for this call, make a try using RTP only...
_debug ("UserAgent: SDES not initialized for this call\n");
}
Sdp *sdpSession = call->getLocalSDP();
if (!sdpSession)
return;
AudioCodec *sessionMedia = sdpSession->get_session_media();
if (!sessionMedia)
return;
AudioCodecType pl = (AudioCodecType) sessionMedia->getPayload();
AudioCodec* audiocodec = Manager::instance().getCodecDescriptorMap().instantiateCodec (pl);
if (audiocodec == NULL)
_error ("UserAgent: No audiocodec found");
try {
call->setAudioStart (true);
call->getAudioRtp()->start (audiocodec);
} catch (exception& rtpException) {
_error ("UserAgent: Error: %s", rtpException.what());
}
}
void call_on_forked (pjsip_inv_session *inv UNUSED, pjsip_event *e UNUSED)
{
}
void call_on_tsx_changed (pjsip_inv_session *inv UNUSED, pjsip_transaction *tsx, pjsip_event *e)
{
assert (tsx);
_debug ("UserAgent: Transaction changed to state %s", transactionStateMap[tsx->state]);
pjsip_rx_data* r_data;
pjsip_tx_data* t_data;
if (tsx->role==PJSIP_ROLE_UAS && tsx->state==PJSIP_TSX_STATE_TRYING &&
pjsip_method_cmp (&tsx->method, &pjsip_refer_method) ==0) {
/** Handle the refer method **/
onCallTransfered (inv, e->body.tsx_state.src.rdata);
} else if (tsx->role==PJSIP_ROLE_UAS && tsx->state==PJSIP_TSX_STATE_TRYING) {
if (e && e->body.rx_msg.rdata) {
_debug ("Event");
r_data = e->body.rx_msg.rdata;
if (r_data && r_data->msg_info.msg->line.req.method.id == PJSIP_OTHER_METHOD) {
_debug ("R_data");
std::string method_info = "INFO";
std::string method_notify = "NOTIFY";
std::string request = pjsip_rx_data_get_info (r_data);
_debug ("UserAgent: %s", request.c_str());
if (request.find (method_notify) != (size_t)-1) {
}
// Must reply 200 OK on SIP INFO request
else if (request.find (method_info) != (size_t)-1) {
pjsip_dlg_create_response (inv->dlg, r_data, PJSIP_SC_OK, NULL, &t_data);
pjsip_dlg_send_response (inv->dlg, tsx, t_data);
}
}
}
// Incoming TEXT message
if (e && e->body.tsx_state.src.rdata) {
// sender of this message
std::string from;
// Get the message inside the transaction
r_data = e->body.tsx_state.src.rdata;
std::string formatedMessage = (char*) r_data->msg_info.msg->body->data;
// Try to determine who is the recipient of the message
SIPCall *call = reinterpret_cast<SIPCall *> (inv->mod_data[getModId() ]);
if (!call) {
_debug ("Incoming TEXT message: Can't find the recipient of the message");
return;
}
// Respond with a 200/OK
pjsip_dlg_create_response (inv->dlg, r_data, PJSIP_SC_OK, NULL, &t_data);
pjsip_dlg_send_response (inv->dlg, tsx, t_data);
std::string message;
std::string urilist;
sfl::InstantMessaging::UriList list;
sfl::InstantMessaging *module = Manager::instance().getInstantMessageModule();
try {
// retrive message from formated text
message = module->findTextMessage (formatedMessage);
// retreive the recipient-list of this message
urilist = module->findTextUriList (formatedMessage);
// parse the recipient list xml
list = module->parseXmlUriList (urilist);
// If no item present in the list, peer is considered as the sender
if (list.empty()) {
from = call->getPeerNumber ();
} else {
sfl::InstantMessaging::UriEntry entry = list.front();
sfl::InstantMessaging::UriEntry::iterator iterAttr = entry.find (IM_XML_URI);
if (iterAttr->second != "Me")
from = iterAttr->second;
else
from = call->getPeerNumber ();
}
} catch (sfl::InstantMessageException &e) {
_error ("SipVoipLink: %s", e.what());
message = "";
from = call->getPeerNumber ();
}
// strip < and > characters in case of an IP address
std::string stripped;
if (from[0] == '<' && from[from.size()-1] == '>')
stripped = from.substr (1, from.size()-2);
else
stripped = from;
// Pass through the instant messaging module if needed
// Right now, it does do anything.
// And notify the clients
Manager::instance ().incomingMessage (call->getCallId (), stripped, module->receive (message, stripped, call->getCallId ()));
}
}
}
void regc_cb (struct pjsip_regc_cbparam *param)
{
SIPAccount * account = NULL;
account = static_cast<SIPAccount *> (param->token);
if (account == NULL) {
_debug ("Account is NULL in regc_cb.");
return;
}
assert (param);
const pj_str_t * description = pjsip_get_status_text (param->code);
if (param->code && description) {
//std::string descriptionprint(description->ptr, description->slen);
//_debug("Received client registration callback wiht code: %i, %s\n", param->code, descriptionprint.c_str());
DBusManager::instance().getCallManager()->registrationStateChanged (account->getAccountID(), std::string (description->ptr, description->slen), param->code);
std::pair<int, std::string> details (param->code, std::string (description->ptr, description->slen));
// there a race condition for this ressource when closing the application
if (account)
account->setRegistrationStateDetailed (details);
}
if (param->status == PJ_SUCCESS) {
if (param->code < 0 || param->code >= 300) {
/* Sometimes, the status is OK, but we still failed.
* So checking the code for real result
*/
_debug ("UserAgent: The error is: %d", param->code);
switch (param->code) {
case 606:
account->setRegistrationState (ErrorConfStun);
break;
case 503:
case 408:
account->setRegistrationState (ErrorHost);
break;
case 401:
case 403:
case 404:
account->setRegistrationState (ErrorAuth);
break;
case 423: { // Expiration Interval Too Brief
int expire_value;
std::istringstream stream (account->getRegistrationExpire());
stream >> expire_value;
std::stringstream out;
out << (expire_value * 2);
std::string s = out.str();
account->setRegistrationExpire (s);
account->registerVoIPLink();
}
break;
default:
account->setRegistrationState (Error);
break;
}
account->setRegister (false);
// shutdown this transport since useless
// if(account->getAccountTransport() != _localUDPTransport) {
SIPVoIPLink::instance ("")->shutdownSipTransport (account->getAccountID());
//}
} else {
// Registration/Unregistration is success
if (account->isRegister())
account->setRegistrationState (Registered);
else {
account->setRegistrationState (Unregistered);
account->setRegister (false);
SIPVoIPLink::instance ("")->shutdownSipTransport (account->getAccountID());
// pjsip_regc_destroy(param->regc);
// account->setRegistrationInfo(NULL);
}
}
} else {
account->setRegistrationState (ErrorAuth);
account->setRegister (false);
SIPVoIPLink::instance ("")->shutdownSipTransport (account->getAccountID());
}
}
// Optional function to be called to process incoming request message.
pj_bool_t
mod_on_rx_request (pjsip_rx_data *rdata)
{
pj_status_t status;
pj_str_t reason;
unsigned options = 0;
pjsip_dialog* dialog;
pjsip_tx_data *tdata;
AccountID account_id;
pjsip_uri *uri;
pjsip_sip_uri *sip_uri;
std::string userName, server, displayName;
SIPVoIPLink *link;
CallID id;
SIPCall* call;
pjsip_inv_session *inv;
SIPAccount *account;
pjmedia_sdp_session *r_sdp;
// pjsip_generic_string_hdr* hdr;
// voicemail part
std::string method_name;
std::string request;
_info ("UserAgent: Transaction REQUEST received using transport: %s %s (refcnt=%d)",
rdata->tp_info.transport->obj_name,
rdata->tp_info.transport->info,
(int) pj_atomic_get (rdata->tp_info.transport->ref_cnt));
// No need to go any further on incoming ACK
if (rdata->msg_info.msg->line.req.method.id == PJSIP_ACK_METHOD && pjsip_rdata_get_dlg (rdata) != NULL) {
_info ("UserAgent: received an ACK");
return true;
}
/* First, let's got the username and server name from the invite.
* We will use them to detect which account is the callee.
*/
uri = rdata->msg_info.to->uri;
sip_uri = (pjsip_sip_uri *) pjsip_uri_get_uri (uri);
userName = std::string (sip_uri->user.ptr, sip_uri->user.slen);
server = std::string (sip_uri->host.ptr, sip_uri->host.slen);
// Get the account id of callee from username and server
account_id = Manager::instance().getAccountIdFromNameAndServer (userName, server);
/* If we don't find any account to receive the call */
if (account_id == AccountNULL) {
_debug ("UserAgent: Username %s doesn't match any account, using IP2IP!",userName.c_str());
}
/* Get the voip link associated to the incoming call */
/* The account must before have been associated to the call in ManagerImpl */
link = dynamic_cast<SIPVoIPLink *> (Manager::instance().getAccountLink (account_id));
/* If we can't find any voIP link to handle the incoming call */
if (!link) {
_warn ("UserAgent: Error: cannot retrieve the voiplink from the account ID...");
pj_strdup2 (_pool, &reason, "ERROR: cannot retrieve the voip link from account");
pjsip_endpt_respond_stateless (_endpt, rdata, PJSIP_SC_INTERNAL_SERVER_ERROR,
&reason, NULL, NULL);
return true;
return false;
}
// Parse the display name from "From" header
char* from_header = strstr (rdata->msg_info.msg_buf, "From: ");
if (from_header) {
std::string temp (from_header);
int begin_displayName = temp.find ("\"") + 1;
int end_displayName = temp.rfind ("\"");
displayName = temp.substr (begin_displayName, end_displayName - begin_displayName);
if (displayName.size() > 25) {
displayName = std::string ("");
}
} else {
displayName = std::string ("");
}
_debug ("UserAgent: The receiver is: %s@%s", userName.data(), server.data());
_debug ("UserAgent: The callee account is %s", account_id.c_str());
/* Now, it is the time to find the information of the caller */
uri = rdata->msg_info.from->uri;
sip_uri = (pjsip_sip_uri *) pjsip_uri_get_uri (uri);
// Store the peer number
char tmp[PJSIP_MAX_URL_SIZE];
int length = pjsip_uri_print (PJSIP_URI_IN_FROMTO_HDR,
sip_uri, tmp, PJSIP_MAX_URL_SIZE);
std::string peerNumber (tmp, length);
//Remove sip: prefix
size_t found = peerNumber.find ("sip:");
if (found!=std::string::npos)
peerNumber.erase (found, found+4);
found = peerNumber.find ("@");
if (found!=std::string::npos)
peerNumber.erase (found);
_debug ("UserAgent: Peer number: %s", peerNumber.c_str());
// Get the server voicemail notification
// Catch the NOTIFY message
if (rdata->msg_info.msg->line.req.method.id == PJSIP_OTHER_METHOD) {
method_name = "NOTIFY";
// Retrieve all the message. Should contains only the method name but ...
request = rdata->msg_info.msg->line.req.method.name.ptr;
// Check if the message is a notification
if (request.find (method_name) != (size_t)-1) {
/* Notify the right account */
set_voicemail_info (account_id, rdata->msg_info.msg->body);
request.find (method_name);
}
pjsip_endpt_respond_stateless (_endpt, rdata, PJSIP_SC_OK, NULL, NULL, NULL);
return true;
}
// Handle an OPTIONS message
if (rdata->msg_info.msg->line.req.method.id == PJSIP_OPTIONS_METHOD) {
handle_incoming_options (rdata);
return true;
}
// Respond statelessly any non-INVITE requests with 500
if (rdata->msg_info.msg->line.req.method.id != PJSIP_INVITE_METHOD) {
if (rdata->msg_info.msg->line.req.method.id != PJSIP_ACK_METHOD) {
pj_strdup2 (_pool, &reason, "user agent unable to handle this request ");
pjsip_endpt_respond_stateless (_endpt, rdata, PJSIP_SC_METHOD_NOT_ALLOWED,
&reason, NULL, NULL);
return true;
}
}
account = dynamic_cast<SIPAccount *> (Manager::instance().getAccount (account_id));
get_remote_sdp_from_offer (rdata, &r_sdp);
if (account->getActiveCodecs().empty()) {
_warn ("UserAgent: Error: No active codec");
pj_strdup2 (_pool, &reason, "no active codec");
pjsip_endpt_respond_stateless (_endpt, rdata, PJSIP_SC_NOT_ACCEPTABLE_HERE ,
&reason, NULL, NULL);
return true;
}
// Verify that we can handle the request
status = pjsip_inv_verify_request (rdata, &options, NULL, NULL, _endpt, NULL);
if (status != PJ_SUCCESS) {
pj_strdup2 (_pool, &reason, "user agent unable to handle this INVITE");
pjsip_endpt_respond_stateless (_endpt, rdata, PJSIP_SC_METHOD_NOT_ALLOWED,
&reason, NULL, NULL);
return true;
}
/******************************************* URL HOOK *********************************************/
if (Manager::instance().hookPreference.getSipEnabled()) {
_debug ("UserAgent: Set sip url hooks");
std::string header_value;
header_value = fetch_header_value (rdata->msg_info.msg,
Manager::instance().hookPreference.getUrlSipField());
if (header_value.size () < header_value.max_size()) {
if (header_value!="") {
urlhook->addAction (header_value,
Manager::instance().hookPreference.getUrlCommand());
}
} else
throw length_error ("UserAgent: Url exceeds std::string max_size");
}
/************************************************************************************************/
_info ("UserAgent: Create a new call");
// Generate a new call ID for the incoming call!
id = Manager::instance().getNewCallID();
call = new SIPCall (id, Call::Incoming, _pool);
// If an error occured at the call creation
if (!call) {
_warn ("UserAgent: Error: Unable to create an incoming call");
pj_strdup2 (_pool, &reason, "unable to create an incoming call");
pjsip_endpt_respond_stateless (_endpt, rdata, PJSIP_SC_INTERNAL_SERVER_ERROR,
&reason, NULL, NULL);
return false;
}
std::string addrToUse, addrSdp ="0.0.0.0";
pjsip_tpselector *tp;
if (account != NULL) {
// May use the published address as well
addrToUse = SIPVoIPLink::instance ("")->getInterfaceAddrFromName (account->getLocalInterface ());
account->isStunEnabled () ? addrSdp = account->getPublishedAddress () : addrSdp = addrToUse;
// Set the appropriate transport to have the right VIA header
link->init_transport_selector (account->getAccountTransport (), &tp);
if (account->getAccountTransport()) {
_debug ("UserAgent: SIP transport for this account: %s %s (refcnt=%i)",
account->getAccountTransport()->obj_name,
account->getAccountTransport()->info,
(int) pj_atomic_get (account->getAccountTransport()->ref_cnt));
}
}
if (addrToUse == "0.0.0.0") {
link->loadSIPLocalIP (&addrToUse);
}
if (addrSdp == "0.0.0.0") {
addrSdp = addrToUse;
}
call->setConnectionState (Call::Progressing);
call->setPeerNumber (peerNumber);
call->setDisplayName (displayName);
call->initRecFileName (peerNumber);
_debug ("UserAgent: DisplayName: %s", displayName.c_str());
// Have to do some stuff with the SDP
// Set the codec map, IP, peer number and so on... for the SIPCall object
setCallAudioLocal (call, addrToUse);
// We retrieve the remote sdp offer in the rdata struct to begin the negociation
call->getLocalSDP()->set_ip_address (addrSdp);
try {
call->getAudioRtp()->initAudioRtpConfig (call);
call->getAudioRtp()->initAudioRtpSession (call);
} catch (...) {
_warn ("UserAgent: Error: Failed to create rtp thread from answer");
}
status = call->getLocalSDP()->receiving_initial_offer (r_sdp, account->getActiveCodecs ());
if (status!=PJ_SUCCESS) {
delete call;
call = NULL;
_warn ("UserAgent: fail in receiving initial offer");
pj_strdup2 (_pool, &reason, "fail in receiving initial offer");
pjsip_endpt_respond_stateless (_endpt, rdata, PJSIP_SC_INTERNAL_SERVER_ERROR,
&reason, NULL, NULL);
return false;
}
/* Create the local dialog (UAS) */
status = pjsip_dlg_create_uas (pjsip_ua_instance(), rdata, NULL, &dialog);
if (status != PJ_SUCCESS) {
delete call;
call = NULL;
_warn ("UserAgent: Error: Failed to create uas dialog");
pj_strdup2 (_pool, &reason, "fail to create uas dialog");
pjsip_endpt_respond_stateless (_endpt, rdata, PJSIP_SC_INTERNAL_SERVER_ERROR,
&reason, NULL, NULL);
return false;
}
// Specify media capability during invite session creation
status = pjsip_inv_create_uas (dialog, rdata, call->getLocalSDP()->get_local_sdp_session(), 0, &inv);
// Explicitly set the transport, set_transport methods increment transport's reference counter
status = pjsip_dlg_set_transport (dialog, tp);
PJ_ASSERT_RETURN (status == PJ_SUCCESS, 1);
// Associate the call in the invite session
inv->mod_data[_mod_ua.id] = call;
// Send a 180 Ringing response
_info ("UserAgent: Send a 180 Ringing response");
status = pjsip_inv_initial_answer (inv, rdata, PJSIP_SC_RINGING, NULL, NULL, &tdata);
PJ_ASSERT_RETURN (status == PJ_SUCCESS, 1);
status = pjsip_inv_send_msg (inv, tdata);
PJ_ASSERT_RETURN (status == PJ_SUCCESS, 1);
// Associate invite session to the current call
call->setInvSession (inv);
// Update the connection state
call->setConnectionState (Call::Ringing);
_debug ("UserAgent: Add call to account link");
if (Manager::instance().incomingCall (call, account_id)) {
// Add this call to the callAccountMap in ManagerImpl
Manager::instance().getAccountLink (account_id)->addCall (call);
} else {
// Fail to notify UI
delete call;
call = NULL;
_warn ("UserAgent: Fail to notify UI!");
pj_strdup2 (_pool, &reason, "fail to notify ui");
pjsip_endpt_respond_stateless (_endpt, rdata, PJSIP_SC_INTERNAL_SERVER_ERROR,
&reason, NULL, NULL);
return false;
}
/* Done */
return true;
}
pj_bool_t mod_on_rx_response (pjsip_rx_data *rdata)
{
_info ("UserAgent: Transaction response using transport: %s %s (refcnt=%d)",
rdata->tp_info.transport->obj_name,
rdata->tp_info.transport->info,
(int) pj_atomic_get (rdata->tp_info.transport->ref_cnt));
pjsip_dialog *dlg;
dlg = pjsip_rdata_get_dlg (rdata);
if (dlg != NULL) {
pjsip_transaction *tsx = pjsip_rdata_get_tsx (rdata);
if (tsx != NULL && tsx->method.id == PJSIP_INVITE_METHOD) {
if (tsx->status_code < 200) {
_info ("UserAgent: Received provisional response");
} else if (tsx->status_code >= 300) {
_warn ("UserAgent: Dialog failed");
// pjsip_dlg_dec_session(dlg);
// ACK for non-2xx final response is sent by transaction.
} else {
_info ("UserAgent: Received 200 OK response");
sendAck (dlg, rdata);
}
}
}
return PJ_SUCCESS;
}
static void sendAck (pjsip_dialog *dlg, pjsip_rx_data *rdata)
{
pjsip_tx_data *tdata;
// Create ACK request
pjsip_dlg_create_request (dlg, &pjsip_ack_method, rdata->msg_info.cseq->cseq, &tdata);
pjsip_dlg_send_request (dlg, tdata,-1, NULL);
}
void onCallTransfered (pjsip_inv_session *inv, pjsip_rx_data *rdata)
{
pj_status_t status;
pjsip_tx_data *tdata;
SIPCall *existing_call;
const pj_str_t str_refer_to = { (char*) "Refer-To", 8};
const pj_str_t str_refer_sub = { (char*) "Refer-Sub", 9 };
const pj_str_t str_ref_by = { (char*) "Referred-By", 11 };
pjsip_generic_string_hdr *refer_to;
pjsip_generic_string_hdr *refer_sub;
pjsip_hdr *ref_by_hdr;
pj_bool_t no_refer_sub = PJ_FALSE;
char *uri;
std::string tmp;
pjsip_status_code code;
pjsip_evsub *sub;
existing_call = (SIPCall *) inv->mod_data[_mod_ua.id];
/* Find the Refer-To header */
refer_to = (pjsip_generic_string_hdr*)
pjsip_msg_find_hdr_by_name (rdata->msg_info.msg, &str_refer_to, NULL);
if (refer_to == NULL) {
/* Invalid Request.
* No Refer-To header!
*/
_debug ("UserAgent: Received REFER without Refer-To header!");
pjsip_dlg_respond (inv->dlg, rdata, 400, NULL, NULL, NULL);
return;
}
/* Find optional Refer-Sub header */
refer_sub = (pjsip_generic_string_hdr*)
pjsip_msg_find_hdr_by_name (rdata->msg_info.msg, &str_refer_sub, NULL);
if (refer_sub) {
if (!pj_strnicmp2 (&refer_sub->hvalue, "true", 4) ==0)
no_refer_sub = PJ_TRUE;
}
/* Find optional Referred-By header (to be copied onto outgoing INVITE
* request.
*/
ref_by_hdr = (pjsip_hdr*)
pjsip_msg_find_hdr_by_name (rdata->msg_info.msg, &str_ref_by, NULL);
/* Notify callback */
code = PJSIP_SC_ACCEPTED;
_debug ("UserAgent: Call to %.*s is being transfered to %.*s",
(int) inv->dlg->remote.info_str.slen,
inv->dlg->remote.info_str.ptr,
(int) refer_to->hvalue.slen,
refer_to->hvalue.ptr);
if (no_refer_sub) {
/*
* Always answer with 2xx.
*/
pjsip_tx_data *tdata;
const pj_str_t str_false = { (char*) "false", 5};
pjsip_hdr *hdr;
status = pjsip_dlg_create_response (inv->dlg, rdata, code, NULL,
&tdata);
if (status != PJ_SUCCESS) {
_debug ("UserAgent: Unable to create 2xx response to REFER -- %d", status);
return;
}
/* Add Refer-Sub header */
hdr = (pjsip_hdr*)
pjsip_generic_string_hdr_create (tdata->pool, &str_refer_sub,
&str_false);
pjsip_msg_add_hdr (tdata->msg, hdr);
/* Send answer */
status = pjsip_dlg_send_response (inv->dlg, pjsip_rdata_get_tsx (rdata),
tdata);
if (status != PJ_SUCCESS) {
_debug ("UserAgent: Unable to create 2xx response to REFER -- %d", status);
return;
}
/* Don't have subscription */
sub = NULL;
} else {
struct pjsip_evsub_user xfer_cb;
pjsip_hdr hdr_list;
/* Init callback */
pj_bzero (&xfer_cb, sizeof (xfer_cb));
xfer_cb.on_evsub_state = &xfer_svr_cb;
/* Init addiTHIS_FILE, THIS_FILE, tional header list to be sent with REFER response */
pj_list_init (&hdr_list);
/* Create transferee event subscription */
status = pjsip_xfer_create_uas (inv->dlg, &xfer_cb, rdata, &sub);
if (status != PJ_SUCCESS) {
_debug ("UserAgent: Unable to create xfer uas -- %d", status);
pjsip_dlg_respond (inv->dlg, rdata, 500, NULL, NULL, NULL);
return;
}
/* If there's Refer-Sub header and the value is "true", send back
* Refer-Sub in the response with value "true" too.
*/
if (refer_sub) {
const pj_str_t str_true = { (char*) "true", 4 };
pjsip_hdr *hdr;
hdr = (pjsip_hdr*)
pjsip_generic_string_hdr_create (inv->dlg->pool,
&str_refer_sub,
&str_true);
pj_list_push_back (&hdr_list, hdr);
}
/* Accept the REFER request, send 2xx. */
pjsip_xfer_accept (sub, rdata, code, &hdr_list);
/* Create initial NOTIFY request */
status = pjsip_xfer_notify (sub, PJSIP_EVSUB_STATE_ACTIVE,
100, NULL, &tdata);
if (status != PJ_SUCCESS) {
_debug ("UserAgent: Unable to create NOTIFY to REFER -- %d", status);
return;
}
/* Send initial NOTIFY request */
status = pjsip_xfer_send_request (sub, tdata);
if (status != PJ_SUCCESS) {
_debug ("UserAgent: Unable to send NOTIFY to REFER -- %d", status);
return;
}
}
/* We're cheating here.
* We need to get a null terminated string from a pj_str_t.
* So grab the pointer from the hvalue and NULL terminate it, knowing
* that the NULL position will be occupied by a newline.
*/
uri = refer_to->hvalue.ptr;
uri[refer_to->hvalue.slen] = '\0';
/* Now make the outgoing call. */
tmp = std::string (uri);
if (existing_call == NULL) {
_debug ("UserAgent: Call doesn't exist!");
return;
}
AccountID accId = Manager::instance().getAccountFromCall (existing_call->getCallId());
CallID newCallId = Manager::instance().getNewCallID();
if (!Manager::instance().outgoingCall (accId, newCallId, tmp)) {
/* Notify xferer about the error (if we have subscription) */
if (sub) {
status = pjsip_xfer_notify (sub, PJSIP_EVSUB_STATE_TERMINATED,
500, NULL, &tdata);
if (status != PJ_SUCCESS) {
_debug ("UserAgent: Unable to create NOTIFY to REFER -- %d", status);
return;
}
status = pjsip_xfer_send_request (sub, tdata);
if (status != PJ_SUCCESS) {
_debug ("UserAgent: Unable to send NOTIFY to REFER -- %d", status);
return;
}
}
return;
}
SIPCall* newCall = 0;
SIPVoIPLink *link = dynamic_cast<SIPVoIPLink *> (Manager::instance().getAccountLink (accId));
if (link) {
newCall = dynamic_cast<SIPCall *> (link->getCall (newCallId));
if (!newCall) {
_debug ("UserAgent: can not find the call from sipvoiplink!");
return;
}
}
if (sub) {
/* Put the server subscription in inv_data.
* Subsequent state changed in pjsua_inv_on_state_changed() will be
* reported back to the server subscription.
*/
newCall->setXferSub (sub);
/* Put the invite_data in the subscription. */
pjsip_evsub_set_mod_data (sub, _mod_ua.id,
newCall);
}
}
void xfer_func_cb (pjsip_evsub *sub, pjsip_event *event)
{
PJ_UNUSED_ARG (event);
/*
* When subscription is accepted (got 200/OK to REFER), check if
* subscription suppressed.
*/
if (pjsip_evsub_get_state (sub) == PJSIP_EVSUB_STATE_ACCEPTED) {
_debug ("UserAgent: Transfer received, waiting for notifications. ");
}
/*
* On incoming NOTIFY, notify application about call transfer progress.
*/
else if (pjsip_evsub_get_state (sub) == PJSIP_EVSUB_STATE_ACTIVE ||
pjsip_evsub_get_state (sub) == PJSIP_EVSUB_STATE_TERMINATED) {
pjsip_msg *msg;
pjsip_msg_body *body;
pjsip_status_line status_line;
pj_bool_t is_last;
pj_bool_t cont;
pj_status_t status;
SIPVoIPLink *link = reinterpret_cast<SIPVoIPLink *> (pjsip_evsub_get_mod_data (sub, _mod_ua.id));
/* When subscription is terminated, clear the xfer_sub member of
* the inv_data.
*/
if (pjsip_evsub_get_state (sub) == PJSIP_EVSUB_STATE_TERMINATED) {
pjsip_evsub_set_mod_data (sub, _mod_ua.id, NULL);
_debug ("UserAgent: Xfer client subscription terminated");
}
/* Application is not interested with call progress status */
if (!link || !event) {
_warn ("UserAgent: Either link or event is empty in transfer callback");
return;
}
pjsip_rx_data* r_data = event->body.rx_msg.rdata;
std::string method_notify = "NOTIFY";
std::string request = pjsip_rx_data_get_info (r_data);
/* This better be a NOTIFY request */
if (r_data->msg_info.msg->line.req.method.id == PJSIP_OTHER_METHOD &&
request.find (method_notify) != (size_t)-1) {
/* Check if there's body */
msg = r_data->msg_info.msg;
body = msg->body;
if (!body) {
_warn ("UserAgent: Warning! Received NOTIFY without message body");
return;
}
/* Check for appropriate content */
if (pj_stricmp2 (&body->content_type.type, "message") != 0 ||
pj_stricmp2 (&body->content_type.subtype, "sipfrag") != 0) {
_warn ("UserAgent: Warning! Received NOTIFY without message/sipfrag content");
return;
}
/* Try to parse the content */
status = pjsip_parse_status_line ( (char*) body->data, body->len, &status_line);
if (status != PJ_SUCCESS) {
_warn ("UserAgent: Warning! Received NOTIFY with invalid message/sipfrag content");
return;
}
} else {
_error ("UserAgent: Error: Set code to 500 during transfer");
status_line.code = 500;
status_line.reason = *pjsip_get_status_text (500);
}
// Get call coresponding to this transaction
std::string transferID (r_data->msg_info.cid->id.ptr, r_data->msg_info.cid->id.slen);
std::map<std::string, CallID>::iterator it = transferCallID.find (transferID);
CallID cid = it->second;
SIPCall *call = dynamic_cast<SIPCall *> (link->getCall (cid));
if (!call) {
_warn ("UserAgent: Call with id %s doesn't exit!", cid.c_str());
return;
}
/* Notify application */
is_last = (pjsip_evsub_get_state (sub) ==PJSIP_EVSUB_STATE_TERMINATED);
cont = !is_last;
_debug ("UserAgent: Notification status line: %d", status_line.code);
if (status_line.code/100 == 2) {
_debug ("UserAgent: Received 200 OK on call transfered, stop call!");
pjsip_tx_data *tdata;
status = pjsip_inv_end_session (call->getInvSession(), PJSIP_SC_GONE, NULL, &tdata);
if (status != PJ_SUCCESS) {
_debug ("UserAgent: Fail to create end session msg!");
} else {
status = pjsip_inv_send_msg (call->getInvSession(), tdata);
if (status != PJ_SUCCESS)
_debug ("UserAgent: Fail to send end session msg!");
}
link->transferStep2 (call);
cont = PJ_FALSE;
}
if (!cont) {
pjsip_evsub_set_mod_data (sub, _mod_ua.id, NULL);
}
}
}
void xfer_svr_cb (pjsip_evsub *sub, pjsip_event *event)
{
PJ_UNUSED_ARG (event);
/*
* When subscription is terminated, clear the xfer_sub member of
* the inv_data.
*/
if (pjsip_evsub_get_state (sub) == PJSIP_EVSUB_STATE_TERMINATED) {
SIPCall *call;
call = (SIPCall*) pjsip_evsub_get_mod_data (sub, _mod_ua.id);
if (!call)
return;
pjsip_evsub_set_mod_data (sub, _mod_ua.id, NULL);
call->setXferSub (NULL);
_debug ("UserAgent: Xfer server subscription terminated");
}
}
void on_rx_offer (pjsip_inv_session *inv, const pjmedia_sdp_session *offer UNUSED)
{
_info ("UserAgent: Received SDP offer");
#ifdef CAN_REINVITE
_debug ("UserAgent: %s (%d): on_rx_offer REINVITE", __FILE__, __LINE__);
SIPCall *call;
pj_status_t status;
AccountID accId;
SIPVoIPLink *link;
call = (SIPCall*) inv->mod_data[getModId() ];
if (!call)
return;
accId = Manager::instance().getAccountFromCall (call->getCallId());
link = dynamic_cast<SIPVoIPLink *> (Manager::instance().getAccountLink (accId));
// call->getLocalSDP()->receiving_initial_offer ( (pjmedia_sdp_session*) offer, account->getActiveCodecs ());
status=pjsip_inv_set_sdp_answer (call->getInvSession(), call->getLocalSDP()->get_local_sdp_session());
if (link)
link->handle_reinvite (call);
#endif
}
void on_create_offer (pjsip_inv_session *inv, pjmedia_sdp_session **p_offer)
{
_info ("UserAgent: Create new SDP offer");
/* Retrieve the call information */
SIPCall * call = NULL;
call = reinterpret_cast<SIPCall*> (inv->mod_data[_mod_ua.id]);
CallID callid = call->getCallId();
AccountID accountid = Manager::instance().getAccountFromCall (callid);
SIPAccount *account = dynamic_cast<SIPAccount *> (Manager::instance().getAccount (accountid));
SIPVoIPLink *link = dynamic_cast<SIPVoIPLink *> (Manager::instance().getAccountLink (accountid));
// Set the local address
std::string localAddress = link->getInterfaceAddrFromName (account->getLocalInterface ());
// Set SDP parameters - Set to local
std::string addrSdp = localAddress;
_debug ("UserAgent: Local Address for IP2IP call: %s", localAddress.c_str());
// If local address bound to ANY, reslove it using PJSIP
if (localAddress == "0.0.0.0") {
link->loadSIPLocalIP (&localAddress);
}
// Local address to appear in SDP
if (addrSdp == "0.0.0.0") {
addrSdp = localAddress;
}
// Set local address for RTP media
setCallAudioLocal (call, localAddress);
// Building the local SDP offer
call->getLocalSDP()->set_ip_address (addrSdp);
call->getLocalSDP()->create_initial_offer (account->getActiveCodecs());
*p_offer = call->getLocalSDP()->get_local_sdp_session();
}
void handle_incoming_options (pjsip_rx_data *rdata)
{
pjsip_tx_data *tdata;
pjsip_response_addr res_addr;
const pjsip_hdr *cap_hdr;
pj_status_t status;
/* Create basic response. */
status = pjsip_endpt_create_response (_endpt, rdata, PJSIP_SC_OK, NULL, &tdata);
if (status != PJ_SUCCESS) {
return;
}
/* Add Allow header */
cap_hdr = pjsip_endpt_get_capability (_endpt, PJSIP_H_ALLOW, NULL);
if (cap_hdr) {
pjsip_msg_add_hdr (tdata->msg, (pjsip_hdr*) pjsip_hdr_clone (tdata->pool, cap_hdr));
}
/* Add Accept header */
cap_hdr = pjsip_endpt_get_capability (_endpt, PJSIP_H_ACCEPT, NULL);
if (cap_hdr) {
pjsip_msg_add_hdr (tdata->msg, (pjsip_hdr*) pjsip_hdr_clone (tdata->pool, cap_hdr));
}
/* Add Supported header */
cap_hdr = pjsip_endpt_get_capability (_endpt, PJSIP_H_SUPPORTED, NULL);
if (cap_hdr) {
pjsip_msg_add_hdr (tdata->msg, (pjsip_hdr*) pjsip_hdr_clone (tdata->pool, cap_hdr));
}
/* Add Allow-Events header from the evsub module */
cap_hdr = pjsip_evsub_get_allow_events_hdr (NULL);
if (cap_hdr) {
pjsip_msg_add_hdr (tdata->msg, (pjsip_hdr*) pjsip_hdr_clone (tdata->pool, cap_hdr));
}
/* Send response statelessly */
pjsip_get_response_addr (tdata->pool, rdata, &res_addr);
status = pjsip_endpt_send_response (_endpt, &res_addr, tdata, NULL, NULL);
if (status != PJ_SUCCESS)
pjsip_tx_data_dec_ref (tdata);
}
/*****************************************************************************************************************/
bool setCallAudioLocal (SIPCall* call, std::string localIP)
{
SIPAccount *account = NULL;
if (call) {
account = dynamic_cast<SIPAccount *> (Manager::instance().getAccount (Manager::instance().getAccountFromCall (call->getCallId ())));
// Setting Audio
unsigned int callLocalAudioPort = RANDOM_LOCAL_PORT;
unsigned int callLocalExternAudioPort = callLocalAudioPort;
if (account->isStunEnabled ()) {
// If use Stun server
callLocalExternAudioPort = account->getStunPort ();
//localIP = account->getPublishedAddress ();
}
_debug (" Setting local ip address: %s", localIP.c_str());
_debug (" Setting local audio port to: %d", callLocalAudioPort);
_debug (" Setting local audio port (external) to: %d", callLocalExternAudioPort);
// Set local audio port for SIPCall(id)
call->setLocalIp (localIP);
call->setLocalAudioPort (callLocalAudioPort);
call->setLocalExternAudioPort (callLocalExternAudioPort);
call->getLocalSDP()->attribute_port_to_all_media (callLocalExternAudioPort);
return true;
}
return false;
}
std::string fetch_header_value (pjsip_msg *msg, std::string field)
{
pj_str_t name;
pjsip_generic_string_hdr * hdr;
std::string value, url;
size_t pos;
std::cout << "fetch header value" << std::endl;
/* Convert the field name into pjsip type */
name = pj_str ( (char*) field.c_str());
/* Get the header value and convert into string*/
hdr = (pjsip_generic_string_hdr*) pjsip_msg_find_hdr_by_name (msg, &name, NULL);
if (!hdr)
return "";
value = hdr->hvalue.ptr;
if ( (pos=value.find ("\n")) == std::string::npos) {
return "";
}
url = value.substr (0, pos);
return url;
}
std::vector<std::string> SIPVoIPLink::getAllIpInterface (void)
{
pj_sockaddr addrList[16];
unsigned int addrCnt = PJ_ARRAY_SIZE (addrList);
pj_status_t success;
success = pj_enum_ip_interface (pj_AF_INET(), &addrCnt, addrList);
std::vector<std::string> ifaceList;
if (success != PJ_SUCCESS) {
return ifaceList;
}
_debug ("Detecting available interfaces...");
int i;
for (i = 0; i < (int) addrCnt; i++) {
char tmpAddr[PJ_INET_ADDRSTRLEN];
pj_sockaddr_print (&addrList[i], tmpAddr, sizeof (tmpAddr), 0);
ifaceList.push_back (std::string (tmpAddr));
_debug ("Local interface %s", tmpAddr);
}
return ifaceList;
}
int get_iface_list (struct ifconf *ifconf)
{
int sock, rval;
if ( (sock = socket (AF_INET,SOCK_STREAM,0)) < 0)
_debug ("get_iface_list error could not open socket\n");
if ( (rval = ioctl (sock, SIOCGIFCONF , (char*) ifconf)) < 0)
_debug ("get_iface_list error ioctl(SIOGIFCONF)\n");
close (sock);
return rval;
}
std::vector<std::string> SIPVoIPLink::getAllIpInterfaceByName (void)
{
std::vector<std::string> ifaceList;
static struct ifreq ifreqs[20];
struct ifconf ifconf;
int nifaces;
// add the default
ifaceList.push_back (std::string ("default"));
memset (&ifconf,0,sizeof (ifconf));
ifconf.ifc_buf = (char*) (ifreqs);
ifconf.ifc_len = sizeof (ifreqs);
if (get_iface_list (&ifconf) < 0)
_debug ("getAllIpInterfaceByName error could not get interface list\n");
nifaces = ifconf.ifc_len/sizeof (struct ifreq);
_debug ("Interfaces (count = %d):\n", nifaces);
for (int i = 0; i < nifaces; i++) {
_debug (" %s ", ifreqs[i].ifr_name);
ifaceList.push_back (std::string (ifreqs[i].ifr_name));
printf (" %s\n", getInterfaceAddrFromName (std::string (ifreqs[i].ifr_name)).c_str());
}
return ifaceList;
}
pj_bool_t stun_sock_on_status (pj_stun_sock *stun_sock UNUSED, pj_stun_sock_op op UNUSED, pj_status_t status)
{
if (status == PJ_SUCCESS)
return PJ_TRUE;
else
return PJ_FALSE;
}
pj_bool_t stun_sock_on_rx_data (pj_stun_sock *stun_sock UNUSED, void *pkt UNUSED, unsigned pkt_len UNUSED, const pj_sockaddr_t *src_addr UNUSED, unsigned addr_len UNUSED)
{
return PJ_TRUE;
}
std::string getLocalAddressAssociatedToAccount (AccountID id)
{
SIPAccount *account = NULL;
pj_sockaddr_in local_addr_ipv4;
pjsip_transport *tspt;
std::string localAddr;
pj_str_t tmp;
account = dynamic_cast<SIPAccount *> (Manager::instance().getAccount (id));
// Set the local address
if (account != NULL && account->getAccountTransport ()) {
tspt = account->getAccountTransport ();
if (tspt != NULL) {
local_addr_ipv4 = tspt->local_addr.ipv4;
} else {
_debug ("In getLocalAddressAssociatedToAccount: transport is null");
local_addr_ipv4 = _localUDPTransport->local_addr.ipv4;
}
} else {
_debug ("In getLocalAddressAssociatedToAccount: account is null");
local_addr_ipv4 = _localUDPTransport->local_addr.ipv4;
}
_debug ("slbvasjklbvaskbvaskvbaskvaskvbsdfk: %i", local_addr_ipv4.sin_addr.s_addr);
tmp = pj_str (pj_inet_ntoa (local_addr_ipv4.sin_addr));
localAddr = std::string (tmp.ptr);
_debug ("slbvasjklbvaskbvaskvbaskvaskvbsdfk: %s", localAddr.c_str());
return localAddr;
}