OpenBTS/SIP/SIPEngine.cpp
Kurtis Heimerl 8d0a9799ef Fixes #734 in public, , as well as rearchitects a number of SIP
control flows. Basically, there was a small bug where I forgot to listen
 for an OK, which caused transactions to stick around too long and fuck
 everything up. That was quickly fixed, but my compulsion required me to
 reorganize the code to avoid such errors in the future. I implemented
 another new feature as well, we now respond with a 480 Temporarily
 Unavailable message (rather than cancel) when canceling an INCOMING call.
 The prior behavior was incorrect.

git-svn-id: http://wush.net/svn/range/software/public/openbts/trunk@3264 19bc5d8c-e614-43d4-8b26-e1612bc8e597
2012-03-05 03:53:57 +00:00

1173 lines
30 KiB
C++

/**@file SIP Call Control -- SIP IETF RFC-3261, RTP IETF RFC-3550. */
/*
* Copyright 2008, 2009, 2010, 2011 Free Software Foundation, Inc.
* Copyright 2011 Range Networks, Inc.
*
* This software is distributed under the terms of the GNU Affero Public License.
* See the COPYING file in the main directory for details.
*
* This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero 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 Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include <sys/types.h>
#include <semaphore.h>
#include <ortp/telephonyevents.h>
#include <Logger.h>
#include <Timeval.h>
#include <GSMConfig.h>
#include <ControlCommon.h>
#include <GSMCommon.h>
#include "SIPInterface.h"
#include "SIPUtility.h"
#include "SIPMessage.h"
#include "SIPEngine.h"
#undef WARNING
using namespace std;
using namespace SIP;
using namespace Control;
const char* SIP::SIPStateString(SIPState s)
{
switch(s)
{
case NullState: return "Null";
case Timeout: return "Timeout";
case Starting: return "Starting";
case Proceeding: return "Proceeding";
case Ringing: return "Ringing";
case Connecting: return "Connecting";
case Active: return "Active";
case Fail: return "Fail";
case Busy: return "Busy";
case MODClearing: return "MODClearing";
case MODCanceling: return "MODCanceling";
case MTDClearing: return "MTDClearing";
case MTDCanceling: return "MTDCanceling";
case Canceled: return "Canceled";
case Cleared: return "Cleared";
case MessageSubmit: return "SMS-Submit";
default: return NULL;
}
}
ostream& SIP::operator<<(ostream& os, SIPState s)
{
const char* str = SIPStateString(s);
if (str) os << str;
else os << "?" << s << "?";
return os;
}
SIPEngine::SIPEngine(const char* proxy, const char* IMSI)
:mCSeq(random()%1000),
mMyToFromHeader(NULL), mRemoteToFromHeader(NULL),
mCallIDHeader(NULL),
mSIPPort(gConfig.getNum("SIP.Local.Port")),
mSIPIP(gConfig.getStr("SIP.Local.IP")),
mINVITE(NULL), mLastResponse(NULL), mBYE(NULL),
mCANCEL(NULL), mSession(NULL), mTxTime(0), mRxTime(0),
mState(NullState),
mDTMF('\0'),mDTMFDuration(0)
{
assert(proxy);
if (IMSI) user(IMSI);
if (!resolveAddress(&mProxyAddr,proxy)) {
LOG(ALERT) << "cannot resolve IP address for " << proxy;
return;
}
char host[256];
const char* ret = inet_ntop(AF_INET,&(mProxyAddr.sin_addr),host,255);
if (!ret) {
LOG(ALERT) << "cannot translate proxy IP address";
return;
}
mProxyIP = string(host);
mProxyPort = ntohs(mProxyAddr.sin_port);
// generate a tag now
char tmp[50];
make_tag(tmp);
mMyTag=tmp;
// set our CSeq in case we need one
mCSeq = random()%600;
//to make sure noise doesn't magically equal a valid RTP port
mRTPPort = 0;
}
SIPEngine::~SIPEngine()
{
if (mINVITE!=NULL) osip_message_free(mINVITE);
if (mLastResponse!=NULL) osip_message_free(mLastResponse);
if (mBYE!=NULL) osip_message_free(mBYE);
if (mCANCEL!=NULL) osip_message_free(mCANCEL);
// FIXME -- Do we need to dispose of the RtpSesion *mSesison?
}
void SIPEngine::saveINVITE(const osip_message_t *INVITE, bool mine)
{
// Instead of cloning, why not just keep the old one?
// Because that doesn't work in all calling contexts.
// This simplifies the call-handling logic.
if (mINVITE!=NULL) osip_message_free(mINVITE);
osip_message_clone(INVITE,&mINVITE);
// #238-private
if (mINVITE==NULL){
LOG(ALERT) << "Message cloning failed, skipping this message.";
return;
}
mCallIDHeader = mINVITE->call_id;
// If this our own INVITE? Did we initiate the transaciton?
if (mine) {
mMyToFromHeader = mINVITE->from;
mRemoteToFromHeader = mINVITE->to;
return;
}
// It's not our own. The From: is the remote party.
mMyToFromHeader = mINVITE->to;
mRemoteToFromHeader = mINVITE->from;
// We need to set our tag, too.
osip_from_set_tag(mMyToFromHeader, strdup(mMyTag.c_str()));
}
void SIPEngine::saveResponse(osip_message_t *response)
{
if (mLastResponse!=NULL) osip_message_free(mLastResponse);
osip_message_clone(response,&mLastResponse);
// The To: is the remote party and might have an new tag.
mRemoteToFromHeader = mLastResponse->to;
}
void SIPEngine::saveBYE(const osip_message_t *BYE, bool mine)
{
// Instead of cloning, why not just keep the old one?
// Because that doesn't work in all calling contexts.
// This simplifies the call-handling logic.
if (mBYE!=NULL) osip_message_free(mBYE);
osip_message_clone(BYE,&mBYE);
}
void SIPEngine::saveCANCEL(const osip_message_t *CANCEL, bool mine)
{
// Instead of cloning, why not just keep the old one?
// Because that doesn't work in all calling contexts.
// This simplifies the call-handling logic.
if (mCANCEL!=NULL) osip_message_free(mCANCEL);
osip_message_clone(CANCEL,&mCANCEL);
}
/* we're going to figure if the from field is us or not */
bool SIPEngine::instigator()
{
assert(mINVITE);
osip_uri_t * from_uri = mINVITE->from->url;
return (!strncmp(from_uri->username,mSIPUsername.c_str(),15) &&
!strncmp(from_uri->host, mSIPIP.c_str(), 30));
}
void SIPEngine::user( const char * IMSI )
{
LOG(DEBUG) << "IMSI=" << IMSI;
unsigned id = random();
char tmp[20];
sprintf(tmp, "%u", id);
mCallID = tmp;
// IMSI gets prefixed with "IMSI" to form a SIP username
mSIPUsername = string("IMSI") + IMSI;
}
void SIPEngine::user( const char * wCallID, const char * IMSI, const char *origID, const char *origHost)
{
LOG(DEBUG) << "IMSI=" << IMSI << " " << wCallID << " " << origID << "@" << origHost;
mSIPUsername = string("IMSI") + IMSI;
mCallID = string(wCallID);
mRemoteUsername = string(origID);
mRemoteDomain = string(origHost);
}
bool SIPEngine::Register( Method wMethod )
{
LOG(INFO) << "user " << mSIPUsername << " state " << mState << " " << wMethod << " callID " << mCallID;
// Before start, need to add mCallID
gSIPInterface.addCall(mCallID);
// Initial configuration for sip message.
// Make a new from tag and new branch.
// make new mCSeq.
// Generate SIP Message
// Either a register or unregister. Only difference
// is expiration period.
osip_message_t * reg;
if (wMethod == SIPRegister ){
reg = sip_register( mSIPUsername.c_str(),
60*gConfig.getNum("SIP.RegistrationPeriod"),
mSIPPort, mSIPIP.c_str(),
mProxyIP.c_str(), mMyTag.c_str(),
mViaBranch.c_str(), mCallID.c_str(), mCSeq
);
} else if (wMethod == SIPUnregister ) {
reg = sip_register( mSIPUsername.c_str(),
0,
mSIPPort, mSIPIP.c_str(),
mProxyIP.c_str(), mMyTag.c_str(),
mViaBranch.c_str(), mCallID.c_str(), mCSeq
);
} else { assert(0); }
LOG(DEBUG) << "writing registration " << reg;
gSIPInterface.write(&mProxyAddr,reg);
bool success = false;
osip_message_t *msg = NULL;
Timeval timeout(gConfig.getNum("SIP.Timer.F"));
while (!timeout.passed()) {
try {
// SIPInterface::read will throw SIPTIimeout if it times out.
// It should not return NULL.
msg = gSIPInterface.read(mCallID, gConfig.getNum("SIP.Timer.E"));
} catch (SIPTimeout) {
// send again
gSIPInterface.write(&mProxyAddr,reg);
continue;
}
assert(msg);
int status = msg->status_code;
LOG(INFO) << "received status " << msg->status_code << " " << msg->reason_phrase;
// specific status
if (status==200) {
LOG(INFO) << "REGISTER success";
success = true;
break;
}
if (status==401) {
LOG(INFO) << "REGISTER fail -- unauthorized";
break;
}
if (status==404) {
LOG(INFO) << "REGISTER fail -- not found";
break;
}
if (status>=200) {
LOG(NOTICE) << "REGISTER unexpected response " << status;
break;
}
}
if (!msg) {
LOG(ALERT) << "SIP REGISTER timed out; is the registration server " << mProxyIP << ":" << mProxyPort << " OK?";
throw SIPTimeout();
}
osip_message_free(reg);
osip_message_free(msg);
gSIPInterface.removeCall(mCallID);
return success;
}
const char* geoprivTemplate =
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
"<presence xmlns=\"urn:ietf:params:xml:ns:pidf\"\n"
"xmlns:gp=\"urn:ietf:params:xml:ns:pidf:geopriv10\"\n"
"xmlns:gml=\"urn:opengis:specification:gml:schema-xsd:feature:v3.0\"\n"
"entity=\"pres:%s@%s\">\n"
"<tuple id=\"1\">\n"
"<status>\n"
"<gp:geopriv>\n"
"<gp:location-info>\n"
"<gml:location>\n"
"<gml:Point gml:id=\"point1\" srsName=\"epsg:4326\">\n"
"<gml:coordinates>%s</gml:coordinates>\n"
"</gml:Point>\n"
"</gml:location>\n"
"</gp:location-info>\n"
"<gp:usage-rules>\n"
"<gp:retransmission-allowed>no</gp:retransmission-allowed>\n"
"</gp:usage-rules>\n"
"</gp:geopriv>\n"
"</status>\n"
"</tuple>\n"
"</presence>\n";
SIPState SIPEngine::SOSSendINVITE(short wRtp_port, unsigned wCodec)
{
LOG(INFO) << "user " << mSIPUsername << " state " << mState;
// Before start, need to add mCallID
gSIPInterface.addCall(mCallID);
// Set Invite params.
// new CSEQ and codec
char tmp[50];
make_branch(tmp);
mViaBranch = tmp;
mCodec = wCodec;
mCSeq++;
mRemoteDomain = gConfig.getStr("Control.Emergency.Destination.Host");
mRemoteUsername = gConfig.getStr("Control.Emergency.Destination.User");
mRTPPort= wRtp_port;
LOG(DEBUG) << "To: " << mRemoteUsername << "@" << mRemoteDomain;
LOG(DEBUG) << "From: " << mSIPUsername << "@" << mSIPIP;
osip_message_t *invite;
if (gConfig.defines("Control.Emergency.RFC5031")) {
invite = sip_invite5031(
mRTPPort, mSIPUsername.c_str(),
mSIPPort, mSIPIP.c_str(), mProxyIP.c_str(),
mMyTag.c_str(), mViaBranch.c_str(), mCallID.c_str(), mCSeq, mCodec);
} else {
invite = sip_invite(
mRemoteUsername.c_str(), mRTPPort, mSIPUsername.c_str(),
mSIPPort, mSIPIP.c_str(), mProxyIP.c_str(),
mMyTag.c_str(), mViaBranch.c_str(), mCallID.c_str(), mCSeq, mCodec);
}
// Add IMS emergency call headers with osip_message_set_header.
// P-Access-Network-Info
// See 3GPP 24.229 7.2.
char cgi_3gpp[50];
sprintf(cgi_3gpp,"3GPP-GERAN; cgi-3gpp=%s%s%04x%04x",
gConfig.getStr("GSM.Identity.MCC").c_str(),gConfig.getStr("GSM.Identity.MNC").c_str(),
(unsigned)gConfig.getNum("GSM.Identity.LAC"),(unsigned)gConfig.getNum("GSM.Identity.CI"));
osip_message_set_header(invite,"P-Access-Network-Info",cgi_3gpp);
// P-Preferred-Identity
// See RFC-3325.
char pref_id[50];
sprintf(pref_id,"<sip:%s@%s>",
mSIPUsername.c_str(),
gConfig.getStr("Control.Emergency.GatewaySwitch").c_str());
osip_message_set_header(invite,"P-Preferred-Identity",pref_id);
// FIXME -- Use the subscriber registry to look up the E.164
// and make a second P-Preferred-Identity header.
// Add RFC-4119 geolocation XML to content area, if available.
if (gConfig.defines("Control.Emergency.Geolocation")) {
char xml[strlen(geoprivTemplate) + 100];
sprintf(xml,geoprivTemplate,
mSIPUsername.c_str(), gConfig.getStr("Control.Emergency.GatewaySwitch").c_str(),
gConfig.getStr("Control.Emergency.Geolocation").c_str());
osip_message_set_content_type(invite, strdup("application/pidf+xml"));
char tmp[20];
sprintf(tmp,"%u",strlen(xml));
osip_message_set_content_length(invite, strdup(tmp));
osip_message_set_body(invite,xml,strlen(xml));
}
// Send Invite to Asterisk.
gSIPInterface.write(&mProxyAddr,invite);
saveINVITE(invite,true);
osip_message_free(invite);
mState = Starting;
return mState;
};
SIPState SIPEngine::MOCSendINVITE( const char * wCalledUsername,
const char * wCalledDomain , short wRtp_port, unsigned wCodec)
{
LOG(INFO) << "user " << mSIPUsername << " state " << mState;
// Before start, need to add mCallID
gSIPInterface.addCall(mCallID);
// Set Invite params.
// new CSEQ and codec
char tmp[50];
make_branch(tmp);
mViaBranch = tmp;
mCodec = wCodec;
mCSeq++;
mRemoteUsername = wCalledUsername;
mRemoteDomain = wCalledDomain;
mRTPPort= wRtp_port;
LOG(DEBUG) << "mRemoteUsername=" << mRemoteUsername;
LOG(DEBUG) << "mSIPUsername=" << mSIPUsername;
osip_message_t * invite = sip_invite(
mRemoteUsername.c_str(), mRTPPort, mSIPUsername.c_str(),
mSIPPort, mSIPIP.c_str(), mProxyIP.c_str(),
mMyTag.c_str(), mViaBranch.c_str(), mCallID.c_str(), mCSeq, mCodec);
// P-Access-Network-Info
// See 3GPP 24.229 7.2.
char cgi_3gpp[50];
sprintf(cgi_3gpp,"3GPP-GERAN; cgi-3gpp=%s%s%04x%04x",
gConfig.getStr("GSM.Identity.MCC").c_str(),gConfig.getStr("GSM.Identity.MNC").c_str(),
(unsigned)gConfig.getNum("GSM.Identity.LAC"),(unsigned)gConfig.getNum("GSM.Identity.CI"));
osip_message_set_header(invite,"P-Access-Network-Info",cgi_3gpp);
// Send Invite.
gSIPInterface.write(&mProxyAddr,invite);
saveINVITE(invite,true);
osip_message_free(invite);
mState = Starting;
return mState;
};
SIPState SIPEngine::MOCResendINVITE()
{
assert(mINVITE);
LOG(INFO) << "user " << mSIPUsername << " state " << mState;
gSIPInterface.write(&mProxyAddr,mINVITE);
return mState;
}
SIPState SIPEngine::MOCWaitForOK()
{
LOG(INFO) << "user " << mSIPUsername << " state " << mState;
osip_message_t * msg;
// Read off the fifo. if time out will
// clean up and return false.
try {
msg = gSIPInterface.read(mCallID, gConfig.getNum("SIP.Timer.A"));
}
catch (SIPTimeout& e) {
LOG(DEBUG) << "timeout";
mState = Timeout;
return mState;
}
int status = msg->status_code;
LOG(DEBUG) << "received status " << status;
saveResponse(msg);
switch (status) {
case 100: // Trying
case 183: // Progress
mState = Proceeding;
break;
case 180: // Ringing
mState = Ringing;
break;
case 200: // OK
// Save the response and update the state,
// but the ACK doesn't happen until the call connects.
mState = Active;
break;
// Anything 400 or above terminates the call, so we ACK.
// FIXME -- It would be nice to save more information about the
// specific failure cause.
case 486:
case 503:
mState = Busy;
MOCSendACK();
break;
default:
LOG(NOTICE) << "unhandled status code " << status;
mState = Fail;
MOCSendACK();
}
osip_message_free(msg);
LOG(DEBUG) << " new state: " << mState;
return mState;
}
//this isn't working right now -kurtis
SIPState SIPEngine::MOCSendACK()
{
assert(mLastResponse);
// new branch
char tmp[50];
make_branch(tmp);
mViaBranch = tmp;
LOG(INFO) << "user " << mSIPUsername << " state " << mState;
osip_message_t* ack = sip_ack( mRemoteDomain.c_str(),
mRemoteUsername.c_str(),
mSIPUsername.c_str(),
mSIPPort, mSIPIP.c_str(), mProxyIP.c_str(),
mMyToFromHeader, mRemoteToFromHeader,
mViaBranch.c_str(), mCallIDHeader, mCSeq
);
gSIPInterface.write(&mProxyAddr,ack);
osip_message_free(ack);
return mState;
}
SIPState SIPEngine::MODSendBYE()
{
LOG(INFO) << "user " << mSIPUsername << " state " << mState;
assert(mINVITE);
char tmp[50];
make_branch(tmp);
mViaBranch = tmp;
mCSeq++;
osip_message_t * bye = sip_bye(mRemoteDomain.c_str(), mRemoteUsername.c_str(),
mSIPUsername.c_str(),
mSIPPort, mSIPIP.c_str(), mProxyIP.c_str(), mProxyPort,
mMyToFromHeader, mRemoteToFromHeader,
mViaBranch.c_str(), mCallIDHeader, mCSeq );
gSIPInterface.write(&mProxyAddr,bye);
saveBYE(bye,true);
osip_message_free(bye);
mState = MODClearing;
return mState;
}
SIPState SIPEngine::MODSendUnavail()
{
LOG(INFO) << "user " << mSIPUsername << " state " << mState;
assert(mINVITE);
osip_message_t * unavail = sip_temporarily_unavailable(mINVITE, mSIPIP.c_str(),
mSIPUsername.c_str(), mSIPPort);
gSIPInterface.write(&mProxyAddr,unavail);
osip_message_free(unavail);
mState = Canceled;
return mState;
}
SIPState SIPEngine::MODSendCANCEL()
{
LOG(INFO) << "user " << mSIPUsername << " state " << mState;
assert(mINVITE);
osip_message_t * cancel = sip_cancel(mINVITE, mSIPIP.c_str(),
mSIPUsername.c_str(), mSIPPort);
gSIPInterface.write(&mProxyAddr,cancel);
saveCANCEL(cancel, true);
osip_message_free(cancel);
mState = MODCanceling;
return mState;
}
SIPState SIPEngine::MODResendBYE()
{
LOG(INFO) << "user " << mSIPUsername << " state " << mState;
assert(mState==MODClearing);
assert(mBYE);
gSIPInterface.write(&mProxyAddr,mBYE);
return mState;
}
SIPState SIPEngine::MODResendCANCEL()
{
LOG(INFO) << "user " << mSIPUsername << " state " << mState;
assert(mState==MODCanceling);
assert(mCANCEL);
gSIPInterface.write(&mProxyAddr,mCANCEL);
return mState;
}
/* there shouldn't be any more communications on this fifo, but we might
get a 487 RequestTerminated. We only need to respond and move on -kurtis */
SIPState SIPEngine::MODWaitFor487()
{
LOG(INFO) << "user " << mSIPUsername << " state " << mState;
osip_message_t * msg;
try {
msg = gSIPInterface.read(mCallID, gConfig.getNum("SIP.Timer.E"));
}
catch (SIPTimeout& e) {
LOG(NOTICE) << "487 Timeout";
return mState;
}
//ok, message arrived
if (msg->status_code != 487){
LOG(WARNING) << "unexpected " << msg->status_code <<
" response to CANCEL, from proxy " << mProxyIP << ":" << mProxyPort;
return mState;
} else {
osip_message_t* ack = sip_ack( mRemoteDomain.c_str(),
mRemoteUsername.c_str(),
mSIPUsername.c_str(),
mSIPPort, mSIPIP.c_str(), mProxyIP.c_str(),
mMyToFromHeader, mRemoteToFromHeader,
mViaBranch.c_str(), mCallIDHeader, mCSeq
);
gSIPInterface.write(&mProxyAddr,ack);
osip_message_free(ack);
return mState;
}
}
SIPState SIPEngine::MODWaitForBYEOK()
{
LOG(INFO) << "user " << mSIPUsername << " state " << mState;
bool responded = false;
Timeval timeout(gConfig.getNum("SIP.Timer.F"));
while (!timeout.passed()) {
try {
osip_message_t * ok = gSIPInterface.read(mCallID, gConfig.getNum("SIP.Timer.E"));
responded = true;
unsigned code = ok->status_code;
saveResponse(ok);
osip_message_free(ok);
if (code!=200) {
LOG(WARNING) << "unexpected " << code << " response to BYE, from proxy " << mProxyIP << ":" << mProxyPort << ". Assuming other end has cleared";
}
break;
}
catch (SIPTimeout& e) {
LOG(NOTICE) << "response timeout, resending BYE";
MODResendBYE();
}
}
if (!responded) { LOG(ALERT) << "lost contact with proxy " << mProxyIP << ":" << mProxyPort; }
mState = Cleared;
return mState;
}
SIPState SIPEngine::MODWaitForCANCELOK()
{
LOG(INFO) << "user " << mSIPUsername << " state " << mState;
bool responded = false;
Timeval timeout(gConfig.getNum("SIP.Timer.F"));
while (!timeout.passed()) {
try {
osip_message_t * ok = gSIPInterface.read(mCallID, gConfig.getNum("SIP.Timer.E"));
responded = true;
unsigned code = ok->status_code;
saveResponse(ok);
osip_message_free(ok);
if (code!=200) {
LOG(WARNING) << "unexpected " << code << " response to CANCEL, from proxy " << mProxyIP << ":" << mProxyPort << ". Assuming other end has cleared";
}
break;
}
catch (SIPTimeout& e) {
LOG(NOTICE) << "response timeout, resending CANCEL";
MODResendCANCEL();
}
}
if (!responded) { LOG(ALERT) << "lost contact with proxy " << mProxyIP << ":" << mProxyPort; }
mState = Canceled;
return mState;
}
SIPState SIPEngine::MTDCheckBYE()
{
//LOG(DEBUG) << "user " << mSIPUsername << " state " << mState;
// If the call is not active, there should be nothing to check.
if (mState!=Active) return mState;
// Need to check size of osip_message_t* fifo,
// so need to get fifo pointer and get size.
// HACK -- reach deep inside to get damn thing
int fifoSize = gSIPInterface.fifoSize(mCallID);
// Size of -1 means the FIFO does not exist.
// Treat the call as cleared.
if (fifoSize==-1) {
LOG(NOTICE) << "MTDCheckBYE attempt to check BYE on non-existant SIP FIFO";
mState=Cleared;
return mState;
}
// If no messages, there is no change in state.
if (fifoSize==0) return mState;
osip_message_t * msg = gSIPInterface.read(mCallID);
if (msg->sip_method) {
if (strcmp(msg->sip_method,"BYE")==0) {
LOG(DEBUG) << "found msg="<<msg->sip_method;
saveBYE(msg,false);
mState = MTDClearing;
}
//repeated ACK, send OK
//pretty sure this never happens, but someone else left a fixme before... -kurtis
if (strcmp(msg->sip_method,"ACK")==0) {
LOG(DEBUG) << "Not responding to repeated ACK. FIXME";
}
}
//repeated OK, send ack
//MOC because that's the only time we ACK
if (msg->status_code==200){
LOG(DEBUG) << "Repeated OK, resending ACK";
MOCSendACK();
}
osip_message_free(msg);
return mState;
}
SIPState SIPEngine::MTDSendBYEOK()
{
LOG(INFO) << "user " << mSIPUsername << " state " << mState;
assert(mBYE);
osip_message_t * okay = sip_b_okay(mBYE);
gSIPInterface.write(&mProxyAddr,okay);
osip_message_free(okay);
mState = Cleared;
return mState;
}
SIPState SIPEngine::MTDSendCANCELOK()
{
LOG(INFO) << "user " << mSIPUsername << " state " << mState;
assert(mCANCEL);
osip_message_t * okay = sip_b_okay(mCANCEL);
gSIPInterface.write(&mProxyAddr,okay);
osip_message_free(okay);
mState = Canceled;
return mState;
}
SIPState SIPEngine::MTCSendTrying()
{
LOG(INFO) << "user " << mSIPUsername << " state " << mState;
if (mINVITE==NULL) mState=Fail;
if (mState==Fail) return mState;
osip_message_t * trying = sip_trying(mINVITE, mSIPUsername.c_str(), mProxyIP.c_str());
gSIPInterface.write(&mProxyAddr,trying);
osip_message_free(trying);
mState=Proceeding;
return mState;
}
SIPState SIPEngine::MTCSendRinging()
{
LOG(INFO) << "user " << mSIPUsername << " state " << mState;
assert(mINVITE);
LOG(DEBUG) << "send ringing";
osip_message_t * ringing = sip_ringing(mINVITE,
mSIPUsername.c_str(), mProxyIP.c_str());
gSIPInterface.write(&mProxyAddr,ringing);
osip_message_free(ringing);
mState = Proceeding;
return mState;
}
SIPState SIPEngine::MTCSendOK( short wRTPPort, unsigned wCodec )
{
LOG(INFO) << "user " << mSIPUsername << " state " << mState;
assert(mINVITE);
mRTPPort = wRTPPort;
mCodec = wCodec;
LOG(DEBUG) << "port=" << wRTPPort << " codec=" << mCodec;
// Form ack from invite and new parameters.
osip_message_t * okay = sip_okay_sdp(mINVITE, mSIPUsername.c_str(),
mSIPIP.c_str(), mSIPPort, mRTPPort, mCodec);
gSIPInterface.write(&mProxyAddr,okay);
osip_message_free(okay);
mState=Connecting;
return mState;
}
SIPState SIPEngine::MTCWaitForACK()
{
// wait for ack,set this to timeout of
// of call channel. If want a longer timeout
// period, need to split into 2 handle situation
// like MOC where this fxn if called multiple times.
LOG(INFO) << "user " << mSIPUsername << " state " << mState;
osip_message_t * ack;
// FIXME -- This is supposed to retransmit BYE on timer I.
try {
ack = gSIPInterface.read(mCallID, gConfig.getNum("SIP.Timer.H"));
}
catch (SIPTimeout& e) {
LOG(NOTICE) << "timeout";
mState = Timeout;
return mState;
}
catch (SIPError& e) {
LOG(NOTICE) << "read error";
mState = Fail;
return mState;
}
if (ack->sip_method==NULL) {
LOG(NOTICE) << "SIP message with no method, status " << ack->status_code;
mState = Fail;
osip_message_free(ack);
return mState;
}
LOG(INFO) << "received sip_method="<<ack->sip_method;
// check for duplicated INVITE
if( strcmp(ack->sip_method,"INVITE") == 0){
LOG(NOTICE) << "received duplicate INVITE";
}
// check for the ACK
else if( strcmp(ack->sip_method,"ACK") == 0){
LOG(INFO) << "received ACK";
mState=Active;
}
// check for the CANCEL
else if( strcmp(ack->sip_method,"CANCEL") == 0){
LOG(INFO) << "received CANCEL";
saveCANCEL(ack, false);
mState=MTDCanceling;
}
// check for strays
else {
LOG(NOTICE) << "unexpected Message "<<ack->sip_method;
mState = Fail;
}
osip_message_free(ack);
return mState;
}
SIPState SIPEngine::MTCCheckForCancel()
{
// wait for ack,set this to timeout of
// of call channel. If want a longer timeout
// period, need to split into 2 handle situation
// like MOC where this fxn if called multiple times.
LOG(INFO) << "user " << mSIPUsername << " state " << mState;
osip_message_t * msg;
try {
//block for very small amount of time
msg = gSIPInterface.read(mCallID,1);
}
catch (SIPTimeout& e) {
return mState;
}
catch (SIPError& e) {
LOG(NOTICE) << "read error";
mState = Fail;
return mState;
}
if (msg->sip_method==NULL) {
LOG(NOTICE) << "SIP message with no method, status " << msg->status_code;
mState = Fail;
osip_message_free(msg);
return mState;
}
LOG(INFO) << "received sip_method=" << msg->sip_method;
// check for duplicated INVITE
if (strcmp(msg->sip_method,"INVITE")==0) {
LOG(NOTICE) << "received duplicate INVITE";
}
// check for the CANCEL
else if (strcmp(msg->sip_method,"CANCEL")==0) {
LOG(INFO) << "received CANCEL";
saveCANCEL(msg, false);
mState=MTDCanceling;
}
// check for strays
else {
LOG(NOTICE) << "unexpected Message " << msg->sip_method;
mState = Fail;
}
osip_message_free(msg);
return mState;
}
void SIPEngine::InitRTP(const osip_message_t * msg )
{
if(mSession == NULL)
mSession = rtp_session_new(RTP_SESSION_SENDRECV);
bool rfc2833 = gConfig.defines("SIP.DTMF.RFC2833");
if (rfc2833) {
RtpProfile* profile = rtp_session_get_send_profile(mSession);
int index = gConfig.getNum("SIP.DTMF.RFC2833.PayloadType");
rtp_profile_set_payload(profile,index,&payload_type_telephone_event);
// Do we really need this next line?
rtp_session_set_send_profile(mSession,profile);
}
rtp_session_set_blocking_mode(mSession, TRUE);
rtp_session_set_scheduling_mode(mSession, TRUE);
rtp_session_set_connected_mode(mSession, TRUE);
rtp_session_set_symmetric_rtp(mSession, TRUE);
// Hardcode RTP session type to GSM full rate (GSM 06.10).
// FIXME -- Make this work for multiple vocoder types.
rtp_session_set_payload_type(mSession, 3);
char d_ip_addr[20];
char d_port[10];
get_rtp_params(msg, d_port, d_ip_addr);
LOG(DEBUG) << "IP="<<d_ip_addr<<" "<<d_port<<" "<<mRTPPort;
rtp_session_set_local_addr(mSession, "0.0.0.0", mRTPPort );
rtp_session_set_remote_addr(mSession, d_ip_addr, atoi(d_port));
// Check for event support.
int code = rtp_session_telephone_events_supported(mSession);
if (code == -1) {
if (rfc2833) { LOG(ALERT) << "RTP session does not support selected DTMF method RFC-2833"; }
else { LOG(WARNING) << "RTP session does not support telephone events"; }
}
}
void SIPEngine::MTCInitRTP()
{
assert(mINVITE);
InitRTP(mINVITE);
}
void SIPEngine::MOCInitRTP()
{
assert(mLastResponse);
InitRTP(mLastResponse);
}
bool SIPEngine::startDTMF(char key)
{
LOG (DEBUG) << key;
if (mState!=Active) return false;
mDTMF = key;
mDTMFDuration = 0;
mDTMFStartTime = mTxTime;
int code = rtp_session_send_dtmf2(mSession,mDTMF,mDTMFStartTime,mDTMFDuration);
mDTMFDuration += 160;
if (!code) return true;
// Error? Turn off DTMF sending.
LOG(WARNING) << "DTMF RFC-2833 failed on start.";
mDTMF = '\0';
return false;
}
void SIPEngine::stopDTMF()
{
mDTMF='\0';
}
void SIPEngine::txFrame(unsigned char* frame )
{
if(mState!=Active) return;
// HACK -- Hardcoded for GSM/8000.
// FIXME -- Make this work for multiple vocoder types.
rtp_session_send_with_ts(mSession, frame, 33, mTxTime);
mTxTime += 160;
if (mDTMF) {
// Any RFC-2833 action?
int code = rtp_session_send_dtmf2(mSession,mDTMF,mDTMFStartTime,mDTMFDuration);
mDTMFDuration += 160;
LOG (DEBUG) << "DTMF RFC-2833 sending " << mDTMF << " " << mDTMFDuration;
// Turn it off if there's an error.
if (code) {
LOG(ERR) << "DTMF RFC-2833 failed after start.";
mDTMF='\0';
}
}
}
int SIPEngine::rxFrame(unsigned char* frame)
{
if(mState!=Active) return 0;
int more;
int ret=0;
// HACK -- Hardcoded for GSM/8000.
// FIXME -- Make this work for multiple vocoder types.
ret = rtp_session_recv_with_ts(mSession, frame, 33, mRxTime, &more);
mRxTime += 160;
return ret;
}
SIPState SIPEngine::MOSMSSendMESSAGE(const char * wCalledUsername,
const char * wCalledDomain , const char *messageText, const char *contentType)
{
LOG(DEBUG) << "mState=" << mState;
LOG(INFO) << "SIP send to " << wCalledUsername << "@" << wCalledDomain << " MESSAGE " << messageText;
// Before start, need to add mCallID
gSIPInterface.addCall(mCallID);
// Set MESSAGE params.
char tmp[50];
make_branch(tmp);
mViaBranch = tmp;
mCSeq++;
mRemoteUsername = wCalledUsername;
mRemoteDomain = wCalledDomain;
osip_message_t * message = sip_message(
mRemoteUsername.c_str(), mSIPUsername.c_str(),
mSIPPort, mSIPIP.c_str(), mProxyIP.c_str(),
mMyTag.c_str(), mViaBranch.c_str(), mCallID.c_str(), mCSeq,
messageText, contentType);
// Send Invite to the SIP proxy.
gSIPInterface.write(&mProxyAddr,message);
saveINVITE(message,true);
osip_message_free(message);
mState = MessageSubmit;
return mState;
};
SIPState SIPEngine::MOSMSWaitForSubmit()
{
LOG(INFO) << "user " << mSIPUsername << " state " << mState;
try {
osip_message_t * ok = gSIPInterface.read(mCallID, gConfig.getNum("SIP.Timer.A"));
// That should never return NULL.
assert(ok);
if((ok->status_code==200) || (ok->status_code==202) ) {
mState = Cleared;
LOG(INFO) << "successful";
}
osip_message_free(ok);
}
catch (SIPTimeout& e) {
LOG(ALERT) << "SIP MESSAGE timed out; is the SMS server " << mProxyIP << ":" << mProxyPort << " OK?";
mState = Fail;
}
return mState;
}
SIPState SIPEngine::MTSMSSendOK()
{
LOG(INFO) << "user " << mSIPUsername << " state " << mState;
// If this operation was initiated from the CLI, there was no INVITE.
if (!mINVITE) {
LOG(INFO) << "clearing CLI-generated transaction";
mState=Cleared;
return mState;
}
// Form ack from invite and new parameters.
osip_message_t * okay = sip_okay(mINVITE, mSIPUsername.c_str(),
mSIPIP.c_str(), mSIPPort);
gSIPInterface.write(&mProxyAddr,okay);
osip_message_free(okay);
mState=Cleared;
return mState;
}
bool SIPEngine::sendINFOAndWaitForOK(unsigned wInfo)
{
LOG(INFO) << "user " << mSIPUsername << " state " << mState;
char tmp[50];
make_branch(tmp);
mViaBranch = tmp;
mCSeq++;
osip_message_t * info = sip_info( wInfo,
mRemoteUsername.c_str(), mRTPPort, mSIPUsername.c_str(),
mSIPPort, mSIPIP.c_str(), mProxyIP.c_str(),
mMyTag.c_str(), mViaBranch.c_str(), mCallIDHeader, mCSeq);
gSIPInterface.write(&mProxyAddr,info);
osip_message_free(info);
try {
// This will timeout on failure. It will not return NULL.
osip_message_t *msg = gSIPInterface.read(mCallID, gConfig.getNum("SIP.Timer.A"));
LOG(DEBUG) << "received status " << msg->status_code << " " << msg->reason_phrase;
bool retVal = (msg->status_code==200);
osip_message_free(msg);
if (!retVal) LOG(CRIT) << "DTMF RFC-2967 failed.";
return retVal;
}
catch (SIPTimeout& e) {
LOG(NOTICE) << "timeout";
return false;
}
};
// vim: ts=4 sw=4