Project 'savoirfairelinux/ring-client-windows' was moved to 'savoirfairelinux/jami-client-windows'. Please update any links and bookmarks that may still have the old path.
Select Git revision
smartlistmodel.h
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
sipvoiplink.cpp 46.18 KiB
/*
* Copyright (C) 2004-2006 Savoir-Faire Linux inc.
* Author: Yan Morin <yan.morin@savoirfairelinux.com>
* Author : Laurielle Lea <laurielle.lea@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 2 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.
*/
#include "sipvoiplink.h"
#include "eventthread.h"
#include "sipcall.h"
#include <sstream> // for ostringstream
#include "manager.h"
#include "user_cfg.h" // SIGNALISATION / PULSE #define
// for listener
#define DEFAULT_SIP_PORT 5060
#define RANDOM_SIP_PORT rand() % 64000 + 1024
#define RANDOM_LOCAL_PORT ((rand() % 27250) + 5250)*2
#define EXOSIP_ERROR_NO 0
#define EXOSIP_ERROR_STD -1
#define EXOSIP_ERROR_BUILDING -2
// for registration
#define EXPIRES_VALUE 180
// 1XX responses
#define DIALOG_ESTABLISHED 101
// see: osip_const.h
// FOR VOICE Message handling
#define VOICE_MSG "Voice-Message"
#define LENGTH_VOICE_MSG 15
// need for hold/unhold
#define INVITE_METHOD "INVITE"
SIPVoIPLink::SIPVoIPLink(const AccountID& accountID)
: VoIPLink(accountID), _localExternAddress("")
{
_evThread = new EventThread(this);
_nMsgVoicemail = 0;
_eXosipRegID = EXOSIP_ERROR_STD;
_eXosipStarted = false;
_nbTryListenAddr = 2; // number of times to try to start SIP listener
_localExternPort = 0;
// to get random number for RANDOM_PORT
srand (time(NULL));
}
SIPVoIPLink::~SIPVoIPLink()
{
delete _evThread; _evThread = 0;
terminate();
}
bool
SIPVoIPLink::init()
{
if (!_eXosipStarted) {
if (0 != eXosip_init()) {
_debug("Could not initialize eXosip\n");
return false;
}
_eXosipStarted = true;
// check networking capabilities
if ( !checkNetwork() ) {
_debug("SIP FAILURE: Unable to determine network capabilities\n");
return false;
}
// if we useStun and we failed to receive something on port 5060, we try a random port
// If use STUN server, firewall address setup
int errExosip = 0;
int port = DEFAULT_SIP_PORT;
int iTry = 1; // try number..
do {
if (_useStun && !Manager::instance().behindNat(port)) {
port = RANDOM_SIP_PORT;
if (!Manager::instance().behindNat(port)) {
_debug("SIP FAILURE: Unable to check NAT setting\n");
return false; // hoho we can't use the random sip port too...
}
}
// second parameter, NULL is "::" for ipv6 and "0.0.0.0" for ipv4, we can put INADDR_ANY
errExosip = eXosip_listen_addr(IPPROTO_UDP, INADDR_ANY, port, AF_INET, 0);
if (errExosip != 0) {
_debug("SIP ERROR: [%d/%d] could not initialize SIP listener on port %d\n", iTry, _nbTryListenAddr, port);
port = RANDOM_SIP_PORT;
}
} while ( errExosip != 0 && iTry < _nbTryListenAddr );
if ( errExosip != 0 ) { // we didn't succeeded
_debug("SIP FAILURE: SIP failed to listen on port %d\n", port);
return false;
}
_localPort = port;
_debug("SIP Init: listening on port %d\n", port);
if (_useStun) {
// This method is used to replace contact address with the public address of your NAT
// it should be call after eXosip_listen_addr
// set by last behindNat() call (ish)...
_localExternAddress = Manager::instance().getFirewallAddress();
_localExternPort = Manager::instance().getFirewallPort();
eXosip_masquerade_contact(_localExternAddress.data(), _localExternPort);
} else {
_localExternAddress = _localIPAddress;
_localExternPort = _localPort;
}
// Set user agent
std::string tmp = std::string(PROGNAME_GLOBAL) + "/" + std::string(SFLPHONED_VERSION);
eXosip_set_user_agent(tmp.data());
_debug("SIP Init: starting loop thread (SIP events)\n");
_evThread->start();
}
return _eXosipStarted;
}
void
SIPVoIPLink::terminate()
{
terminateSIPCall();
if (_eXosipStarted) {
eXosip_quit();
_eXosipStarted = 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) {
// Release SIP stack.
eXosip_lock();
eXosip_call_terminate(call->getCid(), call->getDid() );
eXosip_unlock();
delete call; call = 0;
}
iter++;
}
_callMap.clear();
}
bool
SIPVoIPLink::checkNetwork()
{
// Set IP address
return loadSIPLocalIP();
}
bool
SIPVoIPLink::loadSIPLocalIP()
{
bool returnValue = true;
if (_localIPAddress == "127.0.0.1") {
char* myIPAddress = new char[65];
if (eXosip_guess_localip(AF_INET, myIPAddress, 64) == EXOSIP_ERROR_STD) {
returnValue = false;
} else {
_localIPAddress = std::string(myIPAddress);
_debug("Checking network, setting local IP address to: %s\n", myIPAddress);
}
delete [] myIPAddress; myIPAddress = NULL;
}
return returnValue;
}
void
SIPVoIPLink::getEvent()
{
eXosip_event_t* event = eXosip_event_wait(0, 50);
eXosip_lock();
eXosip_automatic_action();
eXosip_unlock();
if (event == NULL) {
return;
}
_debug("SIP Event: [cdt=%4d:%4d:%4d] type=#%03d %s \n", event->cid, event->did, event->tid, event->type, event->textinfo);
switch (event->type) {
/* REGISTER related events */
case EXOSIP_REGISTRATION_NEW: /** 00 < announce new registration. */
_debug("EXOSIP_REGISTRATION_NEW event is not implemented\n");
break;
case EXOSIP_REGISTRATION_SUCCESS: /** 01 < user is successfully registred. */
Manager::instance().registrationSucceed(getAccountID());
break;
case EXOSIP_REGISTRATION_FAILURE: /** 02 < user is not registred. */
Manager::instance().registrationFailed(getAccountID());
break;
case EXOSIP_REGISTRATION_REFRESHED: /** 03 < registration has been refreshed. */
_debug("EXOSIP_REGISTRATION_REFRESHED event is not implemented\n");
break;
case EXOSIP_REGISTRATION_TERMINATED: /** 04 < UA is not registred any more. */
_debug("EXOSIP_REGISTRATION_TERMINATED event is not implmeneted\n");
break;
/* INVITE related events within calls */
case EXOSIP_CALL_INVITE: /** 05 < announce a new call */
SIPCallInvite(event);
break;
case EXOSIP_CALL_REINVITE: /** 06 < announce a new INVITE within call */
SIPCallReinvite(event);
break;
case EXOSIP_CALL_NOANSWER: /** 07 < announce no answer within the timeout */
_debug("EXOSIP_CALL_NOANSWER event is not implemented\n");
break;
case EXOSIP_CALL_PROCEEDING: /** 08 < announce processing by a remote app */
_debug("EXOSIP_CALL_NOANSWER event is not implemented\n");
break;
case EXOSIP_CALL_RINGING: /** 09 < announce ringback */
SIPCallRinging(event);
break;
case EXOSIP_CALL_ANSWERED: /** 10 < announce start of call */
SIPCallAnswered(event);
break;
case EXOSIP_CALL_REDIRECTED: /** 11 < announce a redirection */
_debug("EXOSIP_CALL_REDIRECTED event is not implemented\n");
break;
case EXOSIP_CALL_REQUESTFAILURE: /** 12 < announce a request failure */
SIPCallRequestFailure(event);
break;
case EXOSIP_CALL_SERVERFAILURE: /** 13 < announce a server failure */
SIPCallServerFailure(event);
break;
case EXOSIP_CALL_GLOBALFAILURE: /** 14 < announce a global failure */
SIPCallServerFailure(event);
break;
case EXOSIP_CALL_ACK: /** 15 < ACK received for 200ok to INVITE */
SIPCallAck(event);
break;
case EXOSIP_CALL_CANCELLED: /** 16 < announce that call has been cancelled */
case EXOSIP_CALL_TIMEOUT: /** 17 < announce that call has failed */
Manager::instance().displayError("EXOSIP Call Error not implemented yet");
break;
/* request related events within calls (except INVITE) */
case EXOSIP_CALL_MESSAGE_NEW: /** 18 < announce new incoming MESSAGE. */
SIPCallMessageNew(event);
break;
case EXOSIP_CALL_MESSAGE_PROCEEDING: /** 19 < announce a 1xx for MESSAGE. */
case EXOSIP_CALL_MESSAGE_ANSWERED: /** 20 < announce a 200ok */
// 200 OK
case EXOSIP_CALL_MESSAGE_REDIRECTED: /** 21 < announce a failure. */
case EXOSIP_CALL_MESSAGE_REQUESTFAILURE: /** 22 < announce a failure. */
case EXOSIP_CALL_MESSAGE_SERVERFAILURE: /** 23 < announce a failure. */
case EXOSIP_CALL_MESSAGE_GLOBALFAILURE: /** 24 < announce a failure. */
Manager::instance().displayError("EXOSIP Call Message not implemented yet");
break;
case EXOSIP_CALL_CLOSED: /** 25 < a BYE was received for this call */
SIPCallClosed(event);
break;
/* for both UAS & UAC events */
case EXOSIP_CALL_RELEASED: /** 26 < call context is cleared. */
SIPCallReleased(event);
break;
/* response received for request outside calls */
case EXOSIP_MESSAGE_NEW: /** 27 < announce new incoming MESSAGE. */
if (event->request == NULL) { break; }
SIPMessageNew(event);
break;
case EXOSIP_MESSAGE_PROCEEDING: /** 28 < announce a 1xx for MESSAGE. */
case EXOSIP_MESSAGE_ANSWERED: /** 29 < announce a 200ok */
case EXOSIP_MESSAGE_REDIRECTED: /** 30 < announce a failure. */
Manager::instance().displayError("EXOSIP Message not implemented yet");
break;
case EXOSIP_MESSAGE_REQUESTFAILURE: /** 31 < announce a failure. */
if (event->response !=0 && event->response->status_code == SIP_METHOD_NOT_ALLOWED) {
Manager::instance().incomingMessage(getAccountID(), "Message are not allowed");
} else {
Manager::instance().displayError("EXOSIP_MESSAGE_REQUESTFAILURE not implemented yet");
}
break;
case EXOSIP_MESSAGE_SERVERFAILURE: /** 32 < announce a failure. */
case EXOSIP_MESSAGE_GLOBALFAILURE: /** 33 < announce a failure. */
Manager::instance().displayError("EXOSIP Message not implemented yet");
break;
/* Presence and Instant Messaging */
case EXOSIP_SUBSCRIPTION_UPDATE: /** 34 < announce incoming SUBSCRIBE. */
case EXOSIP_SUBSCRIPTION_CLOSED: /** 35 < announce end of subscription. */
Manager::instance().displayError("EXOSIP Subscription not implemented yet");
break;
case EXOSIP_SUBSCRIPTION_NOANSWER: /** 37 < announce no answer */
case EXOSIP_SUBSCRIPTION_PROCEEDING: /** 38 < announce a 1xx */
Manager::instance().displayError("EXOSIP Subscription resposne not implemented yet");
break;
case EXOSIP_SUBSCRIPTION_ANSWERED: /** 39 < announce a 200ok */
eXosip_lock();
eXosip_automatic_action();
eXosip_unlock();
break;
case EXOSIP_SUBSCRIPTION_REDIRECTED: /** 40 < announce a redirection */
case EXOSIP_SUBSCRIPTION_REQUESTFAILURE: /** 41 < announce a request failure */
case EXOSIP_SUBSCRIPTION_SERVERFAILURE: /** 42 < announce a server failure */
case EXOSIP_SUBSCRIPTION_GLOBALFAILURE: /** 43 < announce a global failure */
case EXOSIP_SUBSCRIPTION_NOTIFY: /** 44 < announce new NOTIFY request */
case EXOSIP_SUBSCRIPTION_RELEASED: /** 45 < call context is cleared. */
Manager::instance().displayError("EXOSIP Subscription resposne not implemented yet");
break;
case EXOSIP_IN_SUBSCRIPTION_NEW: /** 46 < announce new incoming SUBSCRIBE.*/
case EXOSIP_IN_SUBSCRIPTION_RELEASED: /** 47 < announce end of subscription. */
Manager::instance().displayError("EXOSIP Subscription not implemented yet");
break;
case EXOSIP_EVENT_COUNT: /** 48 < MAX number of events */
break;
}
eXosip_event_free(event);
}
bool
SIPVoIPLink::setRegister()
{
if (_eXosipRegID != EXOSIP_ERROR_STD) {
Manager::instance().displayError("SIP Error: Registration already sent. Try to unregister");
return false;
}
std::string hostname = getHostName();
if (hostname.empty()) {
Manager::instance().displayConfigError("Fill host part field");
return false;
}
if (_userpart.empty()) {
Manager::instance().displayConfigError("Fill user part field");
return false;
}
std::string proxy = "sip:" + _proxy;
hostname = "sip:" + hostname;
std::string from = SIPFromHeader(_userpart, getHostName());
osip_message_t *reg = NULL;
eXosip_lock();
if (!_proxy.empty()) {
_debug("SIP Register: From: %s to %s\n", from.data(), proxy.data());
_eXosipRegID = eXosip_register_build_initial_register(from.data(),
proxy.data(), NULL, EXPIRES_VALUE, ®);
} else {
_debug("SIP Register: From: %s to %s\n", from.data(), hostname.data());
_eXosipRegID = eXosip_register_build_initial_register(from.data(),
hostname.data(), NULL, EXPIRES_VALUE, ®);
}
eXosip_unlock();
if (_eXosipRegID < EXOSIP_ERROR_NO ) {
return false;
}
if (!sendSIPAuthentification()) {
_debug("SIP Register: No authentication\n");
return false;
}
osip_message_set_header (reg, "Event", "Registration");
osip_message_set_header (reg, "Allow-Events", "presence");
eXosip_lock();
int eXosipErr = eXosip_register_send_register(_eXosipRegID, reg);
if (eXosipErr == EXOSIP_ERROR_BUILDING) {
_debug("SIP Failure: Cannot build registration, check the setup\n");
eXosip_unlock();
return false;
}
if (eXosipErr == EXOSIP_ERROR_STD) {
_debug("SIP Failure: Registration sending failed\n");
eXosip_unlock();
return false;
}
eXosip_unlock();
return true;
}
std::string
SIPVoIPLink::SIPFromHeader(const std::string& userpart, const std::string& hostpart)
{
return ("\"" + getFullName() + "\"" + " <sip:" + userpart + "@" + hostpart + ">");
}
bool
SIPVoIPLink::sendSIPAuthentification()
{
std::string login = _authname;
if (login.empty()) {
login = _userpart;
}
if (login.empty()) {
Manager::instance().displayConfigError("Fill authentification name");
return false;
}
if (_password.empty()) {
Manager::instance().displayConfigError("Fill password field");
return false;
}
eXosip_lock();
int returnValue = eXosip_add_authentication_info(login.data(), login.data(), _password.data(), NULL, NULL);
eXosip_unlock();
return (returnValue != EXOSIP_ERROR_STD ? true : false);
}
bool
SIPVoIPLink::setUnregister()
{
if ( _eXosipRegID == EXOSIP_ERROR_STD) return false;
int eXosipErr = EXOSIP_ERROR_NO;
osip_message_t *reg = NULL;
eXosip_lock();
eXosipErr = eXosip_register_build_register (_eXosipRegID, 0, ®);
eXosip_unlock();
if (eXosipErr != EXOSIP_ERROR_NO) {
_debug("SIP Failure: Unable to build registration for setUnregister");
return false;
}
eXosip_lock();
_debug("< Sending REGISTER (expire=0)\n");
eXosipErr = eXosip_register_send_register (_eXosipRegID, reg);
if (eXosipErr == EXOSIP_ERROR_BUILDING) {
_debug("SIP Failure: Cannot build registration (unregister), check the setup\n");
eXosip_unlock();
return false;
}
if (eXosipErr == EXOSIP_ERROR_STD) {
_debug("SIP Failure: Unable to send registration (unregister)\n");
}
eXosip_unlock();
_eXosipRegID = EXOSIP_ERROR_STD;
return true;
}
Call*
SIPVoIPLink::newOutgoingCall(const CallID& id, const std::string& toUrl)
{
SIPCall* call = new SIPCall(id, Call::Outgoing);
if (call) {
call->setPeerNumber(toUrl);
// we have to add the codec before using it in SIPOutgoingInvite...
call->setCodecMap(Manager::instance().getCodecDescriptorMap());
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)
{
_debug("SIP Action: start answering\n");
SIPCall* call = getSIPCall(id);
if (call==0) {
_debug("SIP Failure: SIPCall doesn't exists\n");
return false;
}
// Send 180 RINGING
_debug("< Send 180 Ringing\n");
eXosip_lock ();
eXosip_call_send_answer(call->getTid(), SIP_RINGING, NULL);
eXosip_unlock ();
call->setConnectionState(Call::Ringing);
// Send 200 OK
osip_message_t *answerMessage = NULL;
eXosip_lock();
_debug("< Building 200 OK\n");
int i = eXosip_call_build_answer(call->getTid(), SIP_OK, &answerMessage);
if (i != 0) {
_debug("< Send 400 Bad Request\n");
eXosip_call_send_answer (call->getTid(), SIP_BAD_REQUEST, NULL);
} else {
// use exosip, bug locked
i = 0;
sdp_message_t *remote_sdp = eXosip_get_remote_sdp(call->getDid());
if (remote_sdp!=NULL) {
i = call->sdp_complete_message(remote_sdp, answerMessage);
if (i!=0) {
osip_message_free(answerMessage);
}
sdp_message_free(remote_sdp);
}
if (i != 0) {
_debug("< Send 415 Unsupported Media Type\n");
eXosip_call_send_answer (call->getTid(), SIP_UNSUPPORTED_MEDIA_TYPE, NULL);
} else {
_debug("< Send 200 OK\n");
eXosip_call_send_answer (call->getTid(), SIP_OK, answerMessage);
}
}
eXosip_unlock();
if(i==0) {
// Incoming call is answered, start the sound channel.
_debug(" Starting AudioRTP\n");
if (_audiortp.createNewSession(call) >= 0) {
call->setAudioStart(true);
call->setConnectionState(Call::Connected);
call->setState(Call::Active);
return true;
} else {
_debug("FATAL: Unable to start sound when answering %s/%d\n", __FILE__, __LINE__);
}
}
removeCall(call->getCallId());
return false;
/* int i;
int port;
char tmpbuf[64];
bzero (tmpbuf, 64);
// Get port
snprintf (tmpbuf, 63, "%d", getSipCall(id)->getLocalAudioPort());
_debug("%10d: Answer call [cid = %d, did = %d]\n", id, getSipCall(id)->getCid(), getSipCall(id)->getDid());
port = getSipCall(id)->getLocalAudioPort();
_debug(" Local audio port: %d\n", port);
*/
}
bool
SIPVoIPLink::hangup(const CallID& id)
{
SIPCall* call = getSIPCall(id);
if (call==0) { _debug("Call doesn't exist\n"); return false; }
_debug("Hang up call %s [cd: %3d %3d]\n", id.data(), call->getCid(), call->getDid());
// Release SIP stack.
eXosip_lock();
eXosip_call_terminate(call->getCid(), call->getDid());
eXosip_unlock();
// Release RTP channels
if (Manager::instance().isCurrentCall(id)) {
_audiortp.closeRtpSession();
}
removeCall(id);
return true;
}
bool
SIPVoIPLink::cancel(const CallID& id)
{
SIPCall* call = getSIPCall(id);
if (call==0) { _debug("Call doesn't exist\n"); return false; }
_debug("Cancel call %s [cid: %3d]\n", id.data(), call->getCid());
// Release SIP stack.
eXosip_lock();
eXosip_call_terminate(call->getCid(), -1);
eXosip_unlock();
removeCall(id);
return true;
}
bool
SIPVoIPLink::onhold(const CallID& id)
{
SIPCall* call = getSIPCall(id);
if (call==0) { _debug("Call doesn't exist\n"); return false; }
int did = call->getDid();
eXosip_lock ();
sdp_message_t *local_sdp = eXosip_get_local_sdp(did);
eXosip_unlock ();
if (local_sdp == NULL) {
_debug("SIP Failure: unable to find local_sdp\n");
return false;
}
// Build INVITE_METHOD for put call on-hold
osip_message_t *invite = NULL;
eXosip_lock ();
int exosipErr = eXosip_call_build_request (did, INVITE_METHOD, &invite);
eXosip_unlock ();
if (exosipErr != 0) {
sdp_message_free(local_sdp);
_debug("SIP Failure: unable to build invite method to hold call\n");
return false;
}
/* add sdp body */
{
char *tmp = NULL;
int i = sdp_hold_call(local_sdp);
if (i != 0) {
sdp_message_free (local_sdp);
osip_message_free (invite);
_debug("SIP Failure: Unable to hold call in SDP\n");
return false;
}
i = sdp_message_to_str(local_sdp, &tmp);
sdp_message_free(local_sdp);
if (i != 0) {
osip_message_free (invite);
osip_free (tmp);
_debug("SIP Failure: Unable to translate sdp message to string\n");
return false;
}
osip_message_set_body (invite, tmp, strlen (tmp));
osip_free (tmp);
osip_message_set_content_type (invite, "application/sdp");
}
// Stop sound
call->setAudioStart(false);
_audiortp.closeRtpSession();
// send request
_debug("< Send on hold request\n");
eXosip_lock ();
exosipErr = eXosip_call_send_request (did, invite);
eXosip_unlock ();
return true;
}
bool
SIPVoIPLink::offhold(const CallID& id)
{
SIPCall* call = getSIPCall(id);
if (call==0) { _debug("Call doesn't exist\n"); return false; }
int did = call->getDid();
eXosip_lock ();
sdp_message_t *local_sdp = eXosip_get_local_sdp(did);
eXosip_unlock ();
if (local_sdp == NULL) {
_debug("SIP Failure: unable to find local_sdp\n");
return false;
}
// Build INVITE_METHOD for put call off-hold
osip_message_t *invite;
eXosip_lock ();
int exosipErr = eXosip_call_build_request (did, INVITE_METHOD, &invite);
eXosip_unlock ();
if (exosipErr != 0) {
sdp_message_free(local_sdp);
return EXOSIP_ERROR_STD;
}
/* add sdp body */
{
char *tmp = NULL;
int i = sdp_off_hold_call (local_sdp);
if (i != 0) {
sdp_message_free (local_sdp);
osip_message_free (invite);
return false;
}
i = sdp_message_to_str (local_sdp, &tmp);
sdp_message_free (local_sdp);
if (i != 0) {
osip_message_free (invite);
osip_free (tmp);
return false;
}
osip_message_set_body (invite, tmp, strlen (tmp));
osip_free (tmp);
osip_message_set_content_type (invite, "application/sdp");
}
// Send request
_debug("< Send off hold request\n");
eXosip_lock ();
exosipErr = eXosip_call_send_request (did, invite);
eXosip_unlock ();
// Enable audio
_debug(" Starting AudioRTP\n");
// it's sure that this is the current call id...
if (_audiortp.createNewSession(call) < 0) {
_debug("SIP Failure: Unable to start sound (%s:%d)\n", __FILE__, __LINE__);
return false;
}
return true;
}
bool
SIPVoIPLink::transfer(const CallID& id, const std::string& to)
{
SIPCall* call = getSIPCall(id);
if (call==0) { _debug("Call doesn't exist\n"); return false; }
std::string tmp_to = SIPToHeader(to);
if (tmp_to.find("@") == std::string::npos) {
tmp_to = tmp_to + "@" + getHostName();
}
osip_message_t *refer;
eXosip_lock();
// Build transfer request
int exosipErr = eXosip_call_build_refer(call->getDid(), (char*)tmp_to.data(), &refer);
if (exosipErr == 0) {
// Send transfer request
exosipErr = eXosip_call_send_request(call->getDid(), refer);
}
eXosip_unlock();
// shall we delete the call?
removeCall(id);
return true;
}
bool
SIPVoIPLink::refuse (const CallID& id)
{
SIPCall* call = getSIPCall(id);
if (call==0) { _debug("Call doesn't exist\n"); return false; }
osip_message_t *answerMessage = NULL;
eXosip_lock();
// not BUSY.. where decline the invitation!
int exosipErr = eXosip_call_build_answer(call->getTid(), SIP_DECLINE, &answerMessage);
if (exosipErr == 0) {
exosipErr = eXosip_call_send_answer(call->getTid(), SIP_DECLINE, answerMessage);
}
eXosip_unlock();
return true;
}
bool
SIPVoIPLink::carryingDTMFdigits(const CallID& id, char code)
{
SIPCall* call = getSIPCall(id);
if (call==0) { _debug("Call doesn't exist\n"); return false; }
int duration = Manager::instance().getConfigInt(SIGNALISATION, PULSE_LENGTH);
osip_message_t *info;
const int body_len = 1000;
int i;
char *dtmf_body = new char[body_len];
eXosip_lock();
// Build info request
i = eXosip_call_build_info(call->getDid(), &info);
if (i == 0) {
snprintf(dtmf_body, body_len - 1, "Signal=%c\r\nDuration=%d\r\n", code, duration);
osip_message_set_content_type (info, "application/dtmf-relay");
osip_message_set_body (info, dtmf_body, strlen (dtmf_body));
// Send info request
i = eXosip_call_send_request(call->getDid(), info);
}
eXosip_unlock();
delete[] dtmf_body; dtmf_body = NULL;
return true;
}
bool
SIPVoIPLink::sendMessage(const std::string& to, const std::string& body)
{
bool returnValue = false;
// fast return
if (body.empty()) {return returnValue; }
osip_message_t* message = 0;
const char* method = "MESSAGE";
std::string sipFrom = getSipFrom();
std::string sipTo = getSipTo(to);
std::string sipRoute = getSipRoute();
if (!SIPCheckUrl(sipFrom)) {
Manager::instance().displayConfigError("Error in source address");
return returnValue;
}
if (!SIPCheckUrl(sipTo)) {
Manager::instance().displayError("Error in destination address");
return returnValue;
}
int eXosipError = EXOSIP_ERROR_STD;
eXosip_lock();
if ( sipRoute.empty() ) {
eXosipError = eXosip_message_build_request(&message, method, sipTo.c_str(), sipFrom.c_str(), NULL);
} else {
eXosipError = eXosip_message_build_request(&message, method, sipTo.c_str(), sipFrom.c_str(), sipRoute.c_str());
}
if (eXosipError == EXOSIP_ERROR_NO) {
// add body
// add message
// src: http://www.atosc.org/pipermail/public/osip/2005-October/006007.html
osip_message_set_expires(message, "120");
osip_message_set_body(message, body.c_str(), body.length());
osip_message_set_content_type(message, "text/plain");
eXosipError = eXosip_message_send_request(message);
if (eXosipError == EXOSIP_ERROR_NO) {
// correctly send the message
returnValue = true;
}
}
eXosip_unlock();
return returnValue;
}
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\n");
return false;
}
return true;
}
bool
SIPVoIPLink::SIPStartCall(SIPCall* call, const std::string& subject)
{
if (!call) return false;
std::string to = getSipTo(call->getPeerNumber());
std::string from = getSipFrom();
std::string route = getSipRoute();
_debug(" From: %s\n", from.data());
_debug(" Route: %s\n", route.data());
if (!SIPCheckUrl(from)) {
_debug("SIP Error: Source address is invalid %s\n", from.data());
Manager::instance().displayConfigError("Error in source address");
return false;
}
if (!SIPCheckUrl(to)) {
Manager::instance().displayErrorText(call->getCallId(), "Error in destination address");
return false;
}
osip_message_t *invite;
eXosip_lock();
int eXosipError = eXosip_call_build_initial_invite (&invite, (char*)to.data(),
(char*)from.data(),
(char*)route.data(),
(char*)subject.data());
if (eXosipError != 0) {
eXosip_unlock();
return false; // error when building the invite
}
setCallAudioLocal(call);
std::ostringstream media_audio;
std::ostringstream rtpmap_attr;
int payload;
int nbChannel;
// Set rtpmap according to the supported codec order
CodecMap map = call->getCodecMap().getMap();
CodecMap::iterator iter = map.begin();
while(iter != map.end()) {
if (iter->second!=0 && iter->second->isActive()) {
payload = iter->first;
// add each payload in the list of payload
media_audio << payload << " ";
rtpmap_attr << "a=rtpmap: " << payload << " " <<
iter->second->getCodecName().data() << "/" << iter->second->getClockRate();
nbChannel = iter->second->getChannel();
if (nbChannel!=1) {
rtpmap_attr << "/" << nbChannel;
}
rtpmap_attr << "\r\n";
}
// go to next codec
iter++;
}
// http://www.antisip.com/documentation/eXosip2/group__howto1__initialize.html
// tell sip if we support SIP extension like 100rel
// osip_message_set_supported (invite, "100rel");
/* add sdp body */
{
char tmp[4096];
snprintf (tmp, 4096,
"v=0\r\n"
"o=SFLphone 0 0 IN IP4 %s\r\n"
"s=call\r\n"
"c=IN IP4 %s\r\n"
"t=0 0\r\n"
"m=audio %d RTP/AVP %s\r\n"
"%s",
_localExternAddress.c_str(), _localExternAddress.c_str(), call->getLocalExternAudioPort(), media_audio.str().c_str(), rtpmap_attr.str().c_str());
// media_audio should be one, two or three numbers?
osip_message_set_body (invite, tmp, strlen (tmp));
osip_message_set_content_type (invite, "application/sdp");
_debug("SDP send: %s", tmp);
}
_debug("> INVITE To <%s>\n", to.data());
int cid = eXosip_call_send_initial_invite(invite);
// Keep the cid in case of cancelling
call->setCid(cid);
if (cid <= 0) {
eXosip_unlock();
return false ;
} else {
_debug("SIP Info: Outgoing callID is %s, cid=%d\n", call->getCallId().data(), cid);
eXosip_call_set_reference (cid, NULL);
}
eXosip_unlock();
return true;
}
/**
* Get the Sip FROM url (add sip:, add @host, etc...)
*/
std::string
SIPVoIPLink::getSipFrom() {
// Form the From header field basis on configuration panel
std::string host = getHostName();
if ( host.empty() ) {
host = _localIPAddress;
}
return SIPFromHeader(_userpart, host);
}
/**
* Get the Sip TO url (add sip:, add @host, etc...)
*/
std::string
SIPVoIPLink::getSipTo(const std::string& to_url) {
// Form the From header field basis on configuration panel
bool isRegistered = (_eXosipRegID == EXOSIP_ERROR_STD) ? false : true;
// add a @host if we are registered and there is no one inside the url
if (to_url.find("@") == std::string::npos && isRegistered) {
std::string host = getHostName();
if(!host.empty()) {
return SIPToHeader(to_url + "@" + host);
}
}
return SIPToHeader(to_url);
}
/**
* Get the sip proxy (add sip: if there is one)
* @return empty string or <sip:proxy;lr> url
*/
std::string
SIPVoIPLink::getSipRoute() {
std::string proxy = _proxy;
if ( !proxy.empty() ) {
proxy = "<sip:" + proxy + ";lr>";
}
return proxy; // return empty
}
std::string
SIPVoIPLink::SIPToHeader(const std::string& to)
{
if (to.find("sip:") == std::string::npos) {
return ("sip:" + to );
} else {
return to;
}
}
bool
SIPVoIPLink::SIPCheckUrl(const std::string& url)
{
int i;
osip_from_t *to;
i = osip_from_init(&to);
if (i != 0) {
_debug("SIP Warning: Cannot initialize osip parser\n");
return false;
}
i = osip_from_parse(to, url.data());
if (i != 0) {
_debug("SIP Warning: Cannot parse url %s\n", url.data());
return false;
}
// Free memory
osip_from_free (to);
return true;
}
bool
SIPVoIPLink::setCallAudioLocal(SIPCall* call)
{
// Setting Audio
unsigned int callLocalAudioPort = RANDOM_LOCAL_PORT;
unsigned int callLocalExternAudioPort = callLocalAudioPort;
if (_useStun) {
// If use Stun server
if (Manager::instance().behindNat(callLocalAudioPort)) {
callLocalExternAudioPort = Manager::instance().getFirewallPort();
}
}
_debug(" Setting local audio port to: %d\n", callLocalAudioPort);
_debug(" Setting local audio port (external) to: %d\n", callLocalExternAudioPort);
// Set local audio port for SIPCall(id)
call->setLocalIp(_localIPAddress);
call->setLocalAudioPort(callLocalAudioPort);
call->setLocalExternAudioPort(callLocalExternAudioPort);
return true;
}
void
SIPVoIPLink::SIPCallInvite(eXosip_event_t *event)
{
_debug("> INVITE (receive)\n");
CallID id = Manager::instance().getNewCallID();
SIPCall* call = new SIPCall(id, Call::Incoming);
if (!call) {
_debug("SIP Failure: unable to create an incoming call");
return;
}
setCallAudioLocal(call);
call->setConnectionState(Call::Progressing);
if (call->SIPCallInvite(event)) {
if (Manager::instance().incomingCall(call, getAccountID())) {
addCall(call);
} else {
delete call; call = 0;
}
} else {
delete call; call = 0;
}
}
void
SIPVoIPLink::SIPCallReinvite(eXosip_event_t *event)
{
_debug("> REINVITE (receive)\n");
SIPCall* call = findSIPCallWithCidDid(event->cid, event->did);
if (call == 0) {
_debug("SIP Failure: unknown call\n");
_debug("< Send 488 Not Acceptable Here");
eXosip_lock();
eXosip_call_send_answer(event->tid, 488, NULL);
eXosip_unlock();
return;
}
if ( call->getCallId() == Manager::instance().getCurrentCallId()) {
// STOP tone
Manager::instance().stopTone();
// STOP old rtp session
_audiortp.closeRtpSession();
call->setAudioStart(false);
}
call->SIPCallReinvite(event);
}
void
SIPVoIPLink::SIPCallRinging(eXosip_event_t *event)
{
SIPCall* call = findSIPCallWithCidDid(event->cid, event->did);
if (!call) {
_debug("SIP Failure: unknown call\n");
return;
}
// we could set the cid/did/tid and get the FROM here...
// but we found the call with the cid/did already, why setting it again?
// call->ringingCall(event);
call->setConnectionState(Call::Ringing);
Manager::instance().peerRingingCall(call->getCallId());
}
void
SIPVoIPLink::SIPCallAnswered(eXosip_event_t *event)
{
SIPCall* call = findSIPCallWithCid(event->cid);
if (!call) {
_debug("SIP Failure: unknown call\n");
return;
}
call->setDid(event->did);
if (call->getConnectionState() != Call::Connected) {
call->SIPCallAnswered(event);
call->SIPCallAnsweredWithoutHold(event);
call->setConnectionState(Call::Connected);
call->setState(Call::Active);
Manager::instance().peerAnsweredCall(call->getCallId());
if (Manager::instance().isCurrentCall(call->getCallId())) {
_debug("SIP: Starting AudioRTP\n");
if ( _audiortp.createNewSession(call) < 0) {
_debug("RTP Failure: unable to create new session\n");
} else {
call->setAudioStart(true);
}
}
} else {
_debug("Answering call (on/off hold to send ACK)\n");
call->SIPCallAnswered(event);
}
}
void
SIPVoIPLink::SIPCallRequestFailure(eXosip_event_t *event)
{
if (!event->response) { return; }
// 404 error
_debug("SIP Event: Request Failure, receive code %d\n", event->response->status_code);
// Handle 4XX errors
switch (event->response->status_code) {
case SIP_PROXY_AUTHENTICATION_REQUIRED:
_debug("SIP Server ask required authentification: logging...\n");
sendSIPAuthentification();
eXosip_lock();
eXosip_automatic_action();
eXosip_unlock();
break;
case SIP_UNAUTHORIZED:
_debug("Request is unauthorized. SIP Server ask authentification: logging...\n");
sendSIPAuthentification();
break;
case SIP_BUSY_HERE: // 486
{
SIPCall* call = findSIPCallWithCid(event->cid);
if (call!=0) {
CallID& id = call->getCallId();
call->setConnectionState(Call::Connected);
call->setState(Call::Busy);
Manager::instance().displayErrorText(id, event->response->reason_phrase);
Manager::instance().callBusy(id);
removeCall(id);
}
}
break;
case SIP_REQUEST_TERMINATED: // 487
break;
default:
/*case SIP_BAD_REQUEST:
case SIP_FORBIDDEN:
case SIP_NOT_FOUND:
case SIP_METHOD_NOT_ALLOWED:
case SIP_406_NOT_ACCEPTABLE:
case SIP_REQ_TIME_OUT:
case SIP_TEMPORARILY_UNAVAILABLE:
case SIP_ADDRESS_INCOMPLETE:
case SIP_NOT_ACCEPTABLE_HERE: // 488 */
// Display error on the screen phone
{
SIPCall* call = findSIPCallWithCid(event->cid);
if (call!=0) {
CallID& id = call->getCallId();
call->setConnectionState(Call::Connected);
call->setState(Call::Error);
Manager::instance().displayErrorText(id, event->response->reason_phrase);
Manager::instance().callFailure(id);
removeCall(id);
}
}
}
}
void
SIPVoIPLink::SIPCallServerFailure(eXosip_event_t *event)
{
if (!event->response) { return; }
switch(event->response->status_code) {
case SIP_SERVICE_UNAVAILABLE: // 500
case SIP_BUSY_EVRYWHERE: // 600
case SIP_DECLINE: // 603
SIPCall* call = findSIPCallWithCid(event->cid);
if (call != 0) {
CallID id = call->getCallId();
Manager::instance().callFailure(id);
removeCall(id);
}
break;
}
}
void
SIPVoIPLink::SIPCallAck(eXosip_event_t *event)
{
SIPCall* call = findSIPCallWithCidDid(event->cid, event->did);
if (!call) { return; }
if (!call->isAudioStarted()) {
if (Manager::instance().isCurrentCall(call->getCallId())) {
_debug(" Starting AudioRTP\n");
if ( _audiortp.createNewSession(call) ) {
call->setAudioStart(true);
}
}
}
}
void
SIPVoIPLink::SIPCallMessageNew(eXosip_event_t *event)
{
if (0 == event->request) return;
_debug("Receive a call message\n");
if (MSG_IS_INFO(event->request)) {
_debug("It's a Request Info\n");
osip_content_type_t* c_t = event->request->content_type;
if (c_t != 0 && c_t->type != 0 && c_t->subtype != 0 ) {
_debug(" Content Type of the message: %s/%s\n", c_t->type, c_t->subtype);
// application/dtmf-relay
if (strcmp(c_t->type, "application") == 0 && strcmp(c_t->subtype, "dtmf-relay") == 0) {
handleDtmfRelay(event);
}
}
}
osip_message_t *answerOKNewMessage;
eXosip_lock();
if ( 0 == eXosip_call_build_answer(event->tid, SIP_OK, &answerOKNewMessage)) {
_debug("< Sending 200 OK\n");
eXosip_call_send_answer(event->tid, SIP_OK, answerOKNewMessage);
} else {
_debug("SIP Failure: Could not sent an OK message\n");
}
eXosip_unlock();
}
void
SIPVoIPLink::SIPCallClosed(eXosip_event_t *event)
{
// it was without did before
SIPCall* call = findSIPCallWithCid(event->cid);
if (!call) { return; }
CallID id = call->getCallId();
call->setDid(event->did);
if (Manager::instance().isCurrentCall(id)) {
call->setAudioStart(false);
_audiortp.closeRtpSession();
}
Manager::instance().peerHungupCall(id);
removeCall(id);
}
void
SIPVoIPLink::SIPCallReleased(eXosip_event_t *event)
{
// do cleanup if exists
// only cid because did is always 0 in these case..
SIPCall* call = findSIPCallWithCid(event->cid);
if (!call) { return; }
// if we are here.. something when wrong before...
CallID id = call->getCallId();
Manager::instance().callFailure(id);
removeCall(id);
}
void
SIPVoIPLink::SIPMessageNew(eXosip_event_t *event)
{
if (MSG_IS_OPTIONS(event->request)) {
// old handling was
// - send 200 OK if call id is not found
// - send nothing if call id is found
eXosip_lock();
eXosip_options_send_answer (event->tid, SIP_OK, NULL);
eXosip_unlock();
}
// Voice message
else if (MSG_IS_NOTIFY(event->request)){
_debug("> NOTIFY Voice message\n");
int ii;
unsigned int pos;
unsigned int pos_slash;
osip_body_t *body = NULL;
// Get the message body
ii = osip_message_get_body(event->request, 0, &body);
if (ii != 0) {
_debug(" Cannot get body in a new EXOSIP_MESSAGE_NEW event\n");
return;
}
// Analyse message body
if (!body || !body->body) {
return;
}
std::string str(body->body);
pos = str.find(VOICE_MSG);
if (pos == std::string::npos) {
// If the string is not found
return;
}
pos_slash = str.find ("/");
std::string nb_msg = str.substr(pos + LENGTH_VOICE_MSG,
pos_slash - (pos + LENGTH_VOICE_MSG));
// Set the number of voice-message
int msgVoicemail = atoi(nb_msg.data());
if (msgVoicemail != 0) {
// If there is at least one voice-message, start notification
Manager::instance().startVoiceMessageNotification(getAccountID(), nb_msg);
} else {
// Stop notification when there is 0 voice message
Manager::instance().stopVoiceMessageNotification(getAccountID());
}
// http://www.jdrosen.net/papers/draft-ietf-simple-im-session-00.txt
} else if (MSG_IS_MESSAGE(event->request)) {
_debug("> MESSAGE received\n");
// osip_content_type_t* osip_message::content_type
osip_content_type_t* c_t = event->request->content_type;
if (c_t != 0 && c_t->type != 0 && c_t->subtype != 0 ) {
_debug(" Content Type of the message: %s/%s\n", c_t->type, c_t->subtype);
osip_body_t *body = NULL;
// Get the message body
if (0 == osip_message_get_body(event->request, 0, &body)) {
_debug(" Body length: %d\n", body->length);
if (body->body!=0 &&
strcmp(c_t->type,"text") == 0 &&
strcmp(c_t->subtype,"plain") == 0
) {
_debug(" Text body: %s\n", body->body);
Manager::instance().incomingMessage(getAccountID(), body->body);
}
}
}
osip_message_t *answerOK;
eXosip_lock();
if ( 0 == eXosip_message_build_answer(event->tid, SIP_OK, &answerOK)) {
_debug("< Sending 200 OK\n");
eXosip_message_send_answer(event->tid, SIP_OK, answerOK);
}
eXosip_unlock();
}
}
SIPCall*
SIPVoIPLink::findSIPCallWithCid(int cid)
{
if (cid < 1) {
_debug("Not enough information for this event\n");
return 0;
}
ost::MutexLock m(_callMapMutex);
SIPCall* call = 0;
CallMap::iterator iter = _callMap.begin();
while(iter != _callMap.end()) {
call = dynamic_cast<SIPCall*>(iter->second);
if (call && call->getCid() == cid) {
return call;
}
iter++;
}
return 0;
}
SIPCall*
SIPVoIPLink::findSIPCallWithCidDid(int cid, int did)
{
if (cid < 1 && did < -1) {
_debug("Not enough information for this event\n");
return 0;
}
ost::MutexLock m(_callMapMutex);
SIPCall* call = 0;
CallMap::iterator iter = _callMap.begin();
while(iter != _callMap.end()) {
call = dynamic_cast<SIPCall*>(iter->second);
if (call && call->getCid() == cid && call->getDid() == did) {
return call;
}
iter++;
}
return 0;
}
SIPCall*
SIPVoIPLink::getSIPCall(const CallID& id)
{
Call* call = getCall(id);
if (call) {
return dynamic_cast<SIPCall*>(call);
}
return 0;
}
/**
* Handle an INFO with application/dtmf-relay content-type
* @param event eXosip Event
*/
bool
SIPVoIPLink::handleDtmfRelay(eXosip_event_t* event) {
SIPCall* call = findSIPCallWithCidDid(event->cid, event->did);
if (call==0) { return false; }
bool returnValue = false;
osip_body_t *body = NULL;
// Get the message body
if (0 == osip_message_get_body(event->request, 0, &body) && body->body != 0 ) {
_debug(" Text body: %s\n", body->body);
std::string dtmfBody(body->body);
unsigned int posStart = 0;
unsigned int posEnd = 0;
std::string signal;
std::string duration;
// search for signal=and duration=
posStart = dtmfBody.find("Signal=");
if (posStart != std::string::npos) {
posStart += strlen("Signal=");
posEnd = dtmfBody.find("\n", posStart);
if (posEnd == std::string::npos) {
posEnd = dtmfBody.length();
}
signal = dtmfBody.substr(posStart, posEnd-posStart+1);
_debug("Signal value: %s\n", signal.c_str());
if (!signal.empty()) {
if (Manager::instance().isCurrentCall(call->getCallId())) {
Manager::instance().playDtmf(signal[0]);
returnValue = true;
}
}
/*
// we receive the duration, but we use our configuration...
posStart = dtmfBody.find("Duration=");
if (posStart != std::string::npos) {
posStart += strlen("Duration=");
posEnd = dtmfBody.find("\n", posStart);
if (posEnd == std::string::npos) {
posEnd = dtmfBody.length();
}
duration = dtmfBody.substr(posStart, posEnd-posStart+1);
_debug("Duration value: %s\n", duration.c_str());
returnValue = true;
}
*/
}
}
return returnValue;
}
///////////////////////////////////////////////////////////////////////////////
// Private functions
///////////////////////////////////////////////////////////////////////////////
int
SIPVoIPLink::sdp_hold_call (sdp_message_t * sdp)
{
int pos;
int pos_media = -1;
char *rcvsnd;
int recv_send = -1;
pos = 0;
rcvsnd = sdp_message_a_att_field_get (sdp, pos_media, pos);
while (rcvsnd != NULL) {
if (rcvsnd != NULL && 0 == strcmp (rcvsnd, "sendonly")) {
recv_send = 0;
} else if (rcvsnd != NULL && (0 == strcmp (rcvsnd, "recvonly")
|| 0 == strcmp (rcvsnd, "sendrecv"))) {
recv_send = 0;
sprintf (rcvsnd, "sendonly");
}
pos++;
rcvsnd = sdp_message_a_att_field_get (sdp, pos_media, pos);
}
pos_media = 0;
while (!sdp_message_endof_media (sdp, pos_media)) {
pos = 0;
rcvsnd = sdp_message_a_att_field_get (sdp, pos_media, pos);
while (rcvsnd != NULL) {
if (rcvsnd != NULL && 0 == strcmp (rcvsnd, "sendonly")) {
recv_send = 0;
} else if (rcvsnd != NULL && (0 == strcmp (rcvsnd, "recvonly")
|| 0 == strcmp (rcvsnd, "sendrecv"))) {
recv_send = 0;
sprintf (rcvsnd, "sendonly");
}
pos++;
rcvsnd = sdp_message_a_att_field_get (sdp, pos_media, pos);
}
pos_media++;
}
if (recv_send == -1) {
/* we need to add a global attribute with a field set to "sendonly" */
sdp_message_a_attribute_add (sdp, -1, osip_strdup ("sendonly"), NULL);
}
return 0;
}
int
SIPVoIPLink::sdp_off_hold_call (sdp_message_t * sdp)
{
int pos;
int pos_media = -1;
char *rcvsnd;
pos = 0;
rcvsnd = sdp_message_a_att_field_get (sdp, pos_media, pos);
while (rcvsnd != NULL) {
if (rcvsnd != NULL && (0 == strcmp (rcvsnd, "sendonly")
|| 0 == strcmp (rcvsnd, "recvonly"))) {
sprintf (rcvsnd, "sendrecv");
}
pos++;
rcvsnd = sdp_message_a_att_field_get (sdp, pos_media, pos);
}
pos_media = 0;
while (!sdp_message_endof_media (sdp, pos_media)) {
pos = 0;
rcvsnd = sdp_message_a_att_field_get (sdp, pos_media, pos);
while (rcvsnd != NULL) {
if (rcvsnd != NULL && (0 == strcmp (rcvsnd, "sendonly")
|| 0 == strcmp (rcvsnd, "recvonly"))) {
sprintf (rcvsnd, "sendrecv");
}
pos++;
rcvsnd = sdp_message_a_att_field_get (sdp, pos_media, pos);
}
pos_media++;
}
return 0;
}