Files
openbts/SIP/SIPBase.cpp

1548 lines
58 KiB
C++

/**@file SIP Base -- SIP IETF RFC-3261, RTP IETF RFC-3550. */
/*
* Copyright 2008, 2009, 2010 Free Software Foundation, Inc.
* Copyright 2011, 2012, 2014 Range Networks, Inc.
*
* This software is distributed under multiple licenses;
* see the COPYING file in the main directory for licensing
* information for this specific distribuion.
*
* This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details.
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.
*/
#define LOG_GROUP LogGroup::SIP // Can set Log.Level.SIP for debugging
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include <sys/types.h>
#include <semaphore.h>
#include <ortp/telephonyevents.h>
#include <config.h>
#include <Logger.h>
#include <Timeval.h>
#include <GSMConfig.h>
#include <ControlTransfer.h>
#include <GSMCommon.h>
#include <GSMLogicalChannel.h>
#include <Reporting.h>
#include <Globals.h>
#include <L3TranEntry.h> // For HandoverEntry
#include "SIPMessage.h"
#include "SIPParse.h" // For SipParam
#include "SIPBase.h"
#include "SIPDialog.h"
#include "SIP2Interface.h"
#include "SIPUtility.h"
#undef WARNING
int gCountRtpSessions = 0;
int gCountRtpSockets = 0;
int gCountSipDialogs = 0;
namespace SIP {
using namespace std;
using namespace Control;
const bool rtpUseRealTime = true; // Enables a bug fix for the RTP library.
bool gPeerIsBuggySmqueue = true;
// These need to be declared here instead of in the header because of interaction with InterthreadQueue.h.
SipEngine::~SipEngine() {}
SipEngine::SipEngine() { mTranId = 0; }
void SipBaseProtected::_define_vtable() {}
void DialogMessage::_define_vtable() {}
string makeUriWithTag(string username, string ip, string tag)
{
return format("<sip:%s@%s>;tag=%s",username,ip,tag);
}
string makeUri(string username, string ip, unsigned port)
{
if (port) {
return format("sip:%s@%s:%u",username,ip,port);
} else {
return format("sip:%s@%s",username,ip);
}
}
static int get_rtp_tev_type(char dtmf){
switch (dtmf){
case '1': return TEV_DTMF_1;
case '2': return TEV_DTMF_2;
case '3': return TEV_DTMF_3;
case '4': return TEV_DTMF_4;
case '5': return TEV_DTMF_5;
case '6': return TEV_DTMF_6;
case '7': return TEV_DTMF_7;
case '8': return TEV_DTMF_8;
case '9': return TEV_DTMF_9;
case '0': return TEV_DTMF_0;
case '*': return TEV_DTMF_STAR;
case '#': return TEV_DTMF_POUND;
case 'a':
case 'A': return TEV_DTMF_A;
case 'B':
case 'b': return TEV_DTMF_B;
case 'C':
case 'c': return TEV_DTMF_C;
case 'D':
case 'd': return TEV_DTMF_D;
case '!': return TEV_FLASH;
default:
LOG(WARNING) << "Bad dtmf: " << dtmf;
return -1;
}
}
const char* SipStateString(SipState s)
{
switch(s)
{
case SSNullState: return "NullState";
case SSTimeout: return "Timeout";
case Starting: return "Starting";
case Proceeding: return "Proceeding";
case Ringing: return "Ringing";
case Connecting: return "Connecting";
case Active: return "Active";
case SSFail: return "SSFail";
case MOCBusy: return "MOCBusy";
case MODClearing: return "MODClearing";
case MODCanceling: return "MODCanceling";
case MODError: return "MODError";
case MTDClearing: return "MTDClearing";
case MTDCanceling: return "MTDCanceling";
case Canceled: return "Canceled";
case Cleared: return "Cleared";
//case SipRegister: return "SipRegister";
//case SipUnregister: return "SipUnregister";
case MOSMSSubmit: return "SMS-Submit";
case HandoverInbound: return "HandoverInbound";
//case HandoverInboundReferred: return "HandoverInboundReferred";
case HandoverOutbound: return "HandoverOutbound";
//default: return NULL;
}
return NULL;
}
ostream& operator<<(ostream& os, SipState s)
{
const char* str = SipStateString(s);
if (str) os << str;
else os << "?" << ((int)s) << "?";
return os;
}
// Should we cancel a transaction based on the SIP state?
bool SipBaseProtected::sipIsStuck() const
{
switch (getDialogType()) {
case SIPDTUndefined:
LOG(ERR) << "undefined dialog type?"; return false;
case SIPDTRegister:
case SIPDTUnregister:
return false; // The registrar dialogs are immortal.
default:
break; // Call and Message dialogs should either proceed to Active or eventually die on their own.
}
unsigned age = mStateAge.elapsed();
// These ages were copied from the old pre-l3-rewrite code in TransactionTable.cpp
switch (getSipState()) {
case Active:
return false; // Things are copascetic. Let this SIP dialog run.
case SSFail:
case HandoverInbound:
case Proceeding:
case Canceled:
case Cleared:
// Stuck in these states longer than 30 seconds? Grounds for terminating the transaction.
return age > 30*1000;
default:
// Stuck in any other state longer than 180 seconds? Grounds for terminating the transaction.
return age > 180*1000;
}
}
void SipRtp::rtpStop()
{
if (mSession) {
RtpSession *save = mSession;
mSession = NULL; // Prevent rxFrame and txFrame from using it, which is overkill because we dont call this until the state is not Active.
rtp_session_destroy(save);
gCountRtpSessions--;
gCountRtpSockets--;
}
}
void SipRtp::rtpInit()
{
mSession = NULL;
mTxTime = 0;
mRxTime = 0;
mRxRealTime = 0;
mTxRealTime = 0;
mDTMF = 0;
mDTMFDuration = 0;
mDTMFEnding = 0;
mRTPPort = 0; //to make sure noise doesn't magically equal a valid RTP port
}
DialogStateVars::DialogStateVars()
{
// generate a tag now.
// (pat) It identifies our side of the SIP dialog to the peer.
// It is placed in the to-tag for both responses and requests.
// For responses we munge the saved INVITE immediately so all responses use this to-tag.
//mLocalTag=make_tag();
// set our CSeq in case we need one
mLocalCSeq = gSipInterface.nextInviteCSeqNum(); // formerly: random()%600;
}
string DialogStateVars::dsToString() const
{
ostringstream ss;
ss << LOGVARM(mCallId);
ss << LOGVARM(mLocalHeader.value())<<LOGVARM(mRemoteHeader.value());
ss << LOGVARM(mLocalCSeq) << LOGVARM(mRouteSet);
ss << LOGVAR2("proxy",mProxy.ipToText());
return ss.str();
}
static unsigned gDialogId = 1;
// (pat) This constructor preforms only base initialization.
// Final initialization is in the dialog constructor.
// For MO the final init of mRemote... is in sendMessage or sendInvite, which is called from the dialog constructor.
void SipBase::SipBaseInit(DialogType wDialogType, string proxy, const char * proxyProvenance) // , TranEntryId wTranId)
{
//mTranId = 0;
mDialogId = gDialogId++;
mLastResponse = NULL;
mInvite = NULL;
mDialogType = wDialogType;
//mTranId = wTranId;
if (! mProxy.ipSet(proxy,proxyProvenance)) {
LOG(ALERT) << "cannot resolve IP address for " << proxy << sbText();
}
}
SipBase::~SipBase()
{
if (mInvite) { delete mInvite; }
mInvite = NULL;
gCountSipDialogs--;
}
void SipRtp::rtpText(std::ostringstream&os) const
{
os <<LOGVARM(mTxTime) << LOGVARM(mRxTime);
//os <<LOGVAR(mRTPRemIP);
os <<LOGVAR(mRTPPort);
os <<LOGVARM(mCodec);
// warning: The unbelievably stupid << sends the mDTMS char verbatim, and it is 0, which prematurely terminates the string.
unsigned dtmf = mDTMF;
os <<LOGVAR(dtmf) <<LOGVARM(mDTMFDuration)<<LOGVARM(mDTMFStartTime);
}
void SipBase::sbText(std::ostringstream&os, bool verbose) const
{
os <<" SipBase(";
//os <<LOGVARM(mTranId);
os <<LOGVAR2("localUsername",sipLocalUsername()) << LOGVAR2("remoteUsername",sipRemoteUsername());
os <<LOGVARM(mInviteViaBranch);
os << " DialogStateVars=(" << dsToString() << ")";
// Add the first line of the last response.
if (mLastResponse) { os << LOGVARM(mLastResponse); }
if (IS_LOG_LEVEL(DEBUG)) verbose = true;
if (verbose) {
rtpText(os);
//os << "proxy=(" << mProxy.ipToText() << ")";
}
os <<LOGVAR2("SipState",getSipState());
os <<LOGVARM(mDialogType);
os <<")";
}
string SipBase::sbText() const
{
std::ostringstream ss;
sbText(ss);
return ss.str();
}
void SipBase::saveInviteOrMessage(const SipMessage *INVITE, bool /*mine*/)
{
mInvite = new SipMessage();
*mInvite = *INVITE;
}
// This can only be called for MO responses: MOC, MOD.
void SipBase::saveMOResponse(SipMessage *response)
{
// This is used only for sdp, and only for the invite response, so we should just save that...
mLastResponse = new SipMessage();
*mLastResponse = *response;
// TODO: Multipart messages.
if (response->msmContentType == "application/sdp") {
mSdpAnswer = response->msmBody;
}
// The To: is the remote party and might have a new tag.
// (pat) But only for a response to one of our requests.
dsSetRemoteHeader(&response->msmTo);
if (response->msmCSeqMethod == "INVITE") {
// Handle the Contact.
// If the INVITE is canceled early we get a 487 error which does not have a Contact.
// TODO: Handle the routeset.
string uristr;
if (! response->msmContactValue.empty() && crackUri(response->msmContactValue,NULL,&uristr,NULL)) {
SipUri uri(uristr);
mProxy.ipSet(uri.uriHostAndPort(), "INVITE Contact");
}
}
}
void DialogStateVars::dsSetLocalMO(const FullMobileId &msid, bool addTag)
{
// We dont really have to check for collisions; our ids were vanishingly unlikely to collide
// before and now the time is encoded into it as well.
//string callid;
//do { callid = globallyUniqueId(""); } while (gSipInterface.findDialogsByCallId(callid,false));
dsSetCallId(globallyUniqueId(""));
string username;
{
// IMSI gets prefixed with "IMSI" to form a SIP username
username = msid.fmidUsername();
}
dsSetLocalUri(makeUri(username,localIPAndPort()));
//mSipUsername = username;
LOG(DEBUG) << "set user MO"<<LOGVAR(msid)<<LOGVAR(username)<<LOGVARM(mCallId) << dsToString();
if (addTag) {
mLocalHeader.setTag(make_tag());
}
}
// We always need a local tag, but handover adds its own.
void DialogStateVars::dsSetLocalHeaderMT(SipPreposition *toheader, bool addTag)
{
LOG(DEBUG);
// We will parse the contact to see if there is already a tag, which would be an error.
mLocalHeader = *toheader;
if (addTag) {
if (!mLocalHeader.mTag.empty()) {
LOG(ERR) <<"MT Dialog initiation had a pre-existing to-tag:"<<toheader;
// Now what? Keep the tag I guess.
// The dialog is probably impossible.
} else {
mLocalHeader.setTag(make_tag());
}
}
}
void writePrivateHeaders(SipMessage *msg, const L3LogicalChannel *l3chan)
{
// P-PHY-Info
// This is a non-standard private header in OpenBTS.
// TODO: If we add the MSC params to this, especially L3TI, the SIP message will completely encapsulate handover.
// TA=<timing advance> TE=<TA error> UpRSSI=<uplink RSSI> TxPwr=<MS tx power> DnRSSIdBm=<downlink RSSI>
// Get the values.
if (l3chan) {
char phy_info[200];
// (pat) TODO: This is really cheating.
const GSM::L2LogicalChannel *chan = l3chan->getL2Channel();
MSPhysReportInfo *phys = chan->getPhysInfo();
snprintf(phy_info,200,"OpenBTS; TA=%d TE=%f UpRSSI=%f TxPwr=%d DnRSSIdBm=%d time=%9.3lf",
phys->actualMSTiming(), phys->timingError(),
phys->RSSI(), phys->actualMSPower(),
chan->measurementResults().RXLEV_FULL_SERVING_CELL_dBm(),
phys->timestamp());
static const string cPhyInfoString("P-PHY-Info");
msg->smAddHeader(cPhyInfoString,phy_info);
}
// P-Access-Network-Info
// See 3GPP 24.229 7.2. This is a genuine specified header.
char cgi_3gpp[256];
snprintf(cgi_3gpp,256,"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"));
static const string cAccessNetworkInfoString("P-Access-Network-Info");
msg->smAddHeader(cAccessNetworkInfoString, cgi_3gpp);
// FIXME -- Use the subscriber registry to look up the E.164
// and make a second P-Preferred-Identity header.
}
// Like this: Note no via branch, but registrar added one.
// REGISTER sip:127.0.0.1 SIP/2.0^M // this is the proxy IP
// Via: SIP/2.0/UDP 127.0.0.1:5062;branch^M
// From: IMSI001010002220002 <sip:IMSI001010002220002@127.0.0.1>;tag=vzmikpkffdomsjvb^M
// To: IMSI001010002220002 <sip:IMSI001010002220002@127.0.0.1>^M
// Call-ID: 196117285@127.0.0.1^M
// CSeq: 226 REGISTER^M
// Contact: <sip:IMSI001010002220002@127.0.0.1:5062>;expires=5400^M
// User-Agent: OpenBTS 3.0TRUNK Build Date May 3 2013^M
// Max-Forwards: 70^M
// P-PHY-Info: OpenBTS; TA=1 TE=0.392578 UpRSSI=-26.000000 TxPwr=33 DnRSSIdBm=-111^M
// P-Access-Network-Info: 3GPP-GERAN; cgi-3gpp=0010103ef000a^M
// P-Preferred-Identity: <sip:IMSI001010002220002@127.0.0.1:5060>^M
// Content-Length: 0^M
//
// SIP/2.0 401 Unauthorized^M
// Via: SIP/2.0/UDP localhost:5064;branch=1;received=string_address@foo.bar^M
// Via: SIP/2.0/UDP 127.0.0.1:5062;branch^M
// From: IMSI001010002220002 <sip:IMSI001010002220002@127.0.0.1>;tag=vzmikpkffdomsjvb^M
// To: IMSI001010002220002 <sip:IMSI001010002220002@127.0.0.1>^M
// Call-ID: 196117285@127.0.0.1^M
// CSeq: 226 REGISTER^M
// Contact: <sip:IMSI001010002220002@127.0.0.1:5062>;expires=5400^M
// WWW-Authenticate: Digest nonce=972ed867f7284fca8670e44a71f97040^M
// User-agent: OpenBTS 3.0TRUNK Build Date May 3 2013^M
// Max-forwards: 70^M
// P-phy-info: OpenBTS; TA=1 TE=0.392578 UpRSSI=-26.000000 TxPwr=33 DnRSSIdBm=-111^M
// P-access-network-info: 3GPP-GERAN; cgi-3gpp=0010103ef000a^M
// P-preferred-identity: <sip:IMSI001010002220002@127.0.0.1:5060>^M
// Content-Length: 0^M
//
// REGISTER sip:127.0.0.1 SIP/2.0^M
// Via: SIP/2.0/UDP 127.0.0.1:5062;branch^M
// From: IMSI001010002220002 <sip:IMSI001010002220002@127.0.0.1>;tag=yyourwrzymenfffa^M
// To: IMSI001010002220002 <sip:IMSI001010002220002@127.0.0.1>^M
// Call-ID: 2140400952@127.0.0.1^M
// CSeq: 447 REGISTER^M
// Contact: <sip:IMSI001010002220002@127.0.0.1:5062>;expires=5400^M
// Authorization: Digest, nonce=83775658971b9dc469ad88c3c3d5250b, uri=001010002220002, response=4f9eb260^M
// User-Agent: OpenBTS 3.0TRUNK Build Date May 3 2013^M
// Max-Forwards: 70^M
// P-PHY-Info: OpenBTS; TA=1 TE=0.423828 UpRSSI=-63.000000 TxPwr=13 DnRSSIdBm=-48^M
// P-Access-Network-Info: 3GPP-GERAN; cgi-3gpp=0010103ef000a^M
// P-Preferred-Identity: <sip:IMSI001010002220002@127.0.0.1:5060>^M
// Content-Length: 0^M
// We wrap our REGISTER messages inside a dialog object, even though it is technically not a dialog.
SipMessage *SipBase::makeRegisterMsg(DialogType wMethod, const L3LogicalChannel* chan, string RAND, const FullMobileId &msid, const char *SRES)
{
// TODO: We need to make a transaction here...
static const string registerStr("REGISTER");
// The request URI is special for REGISTER;
string reqUri = string("sip:") + proxyIP();
// We formerly allocated a new Dialog for each REGISTER message so the IMSI was stashed in the dialog, and localSipUri() worked.
//SipPreposition myUri(localSipUri());
string username = msid.fmidUsername();
// RFC3261 is somewhat contradictory on the From-tag and To-tag.
// The documentation for 'from' says the from-tag is always included.
// The examples in 24.1 show a From-tag but no To-tag.
// The To-tag includes the optional <>, and Paul at null team incorrectly thought the <> were required,
// so we will include them as that appears to be common practice.
string myUriString;
string authUri;
string authUsername;
string realm = gConfig.getStr("SIP.Realm");
if (realm.length() > 0) {
authUri = string("sip:") + realm;
authUsername = string("IMSI") + msid.mImsi;
myUriString = makeUri(username,realm,0);
} else {
myUriString = makeUri(username,dsPeer()->mipName,0); // The port, if any, is already in mipName.
}
//string fromUriString = makeUriWithTag(username,dsPeer()->mipName,make_tag()); // The port, if any, is already in mipName.
SipPreposition toHeader("",myUriString,"");
SipPreposition fromHeader("",myUriString,make_tag());
dsNextCSeq(); // Advance CSeqNum.
SipMessage *msg = makeRequest(registerStr,reqUri,username,&toHeader,&fromHeader,make_branch());
// Replace the Contact header so we can set the expires option. What a botched up spec.
// Replace the P-Preferred-Identity
unsigned expires;
if (wMethod == SIPDTRegister ) {
expires = 60*gConfig.getNum("SIP.RegistrationPeriod");
if (SRES && strlen(SRES)) {
if (realm.length() > 0) {
string response = makeResponse(authUsername, realm, SRES, registerStr, authUri, RAND);
msg->msmAuthorizationValue = format("Digest realm=\"%s\", username=\"%s\", nonce=\"%s\", uri=\"%s\", response=\"%s\", algorithm=MD5 ",
realm.c_str(), authUsername.c_str(), RAND.c_str(), authUri.c_str(), response.c_str());
} else {
msg->msmAuthorizationValue = format("Digest, nonce=%s, uri=%s, response=%s",RAND.c_str(),msid.mImsi.c_str(),SRES);
}
}
} else if (wMethod == SIPDTUnregister ) {
expires = 0;
} else { assert(0); }
// We use myURI instead of localContact because the SIPDialog for REGISTER is shared by all REGISTER
// users and does not contain the personal info for this user.
//msg->msmContactValue = format("<%s>;expires=%u",myUriString.c_str(),expires);
msg->msmContactValue = localContact(username,expires);
writePrivateHeaders(msg,chan);
return msg;
}
string SipBase::makeSDPOffer()
{
SdpInfo sdp;
sdp.sdpInitOffer(this);
return sdp.sdpValue();
//return makeSDP("0","0");
}
// mCodec is an implicit parameter, consisting of the chosen codec.
string SipBase::makeSDPAnswer()
{
SdpInfo answer;
answer.sdpInitOffer(this);
mSdpAnswer = answer.sdpValue();
return mSdpAnswer;
}
// A request inside an invite is ACK, CANCEL, BYE, INFO, or re-INVITE.
// The only target-refresh-request is re-INVITE, which can change the "Dialog State".
// For ACK sec 17.1.1.3 the To must equal the To of the response being acked, which specifically contains a tag.
// ACK contains only one via == top via of original request with matching branch.
// TODO: ACK must copy route header fields from INVITE.
// sec 9.1 For CANCEL, all fields must == INVITE except the method. Note CSeq must match too. Must have single via
// matching the [invite] request being cancelled, with matching branch. TODO: Must copy route header from request.
// 15.1.1: BYE is a new request within a dialog as per section 12.2.
// transaction constructed as per with a new tag, branch, etc.
// It should not include CONTACT because it is non-target-refresh.
// Implicit parameters from SipBase: mCallId, mRemoteUsername, mRemoteDomain, mCSeq, etc.
SipMessage *SipBase::makeRequest(string method,string requestUri, string whoami, SipPreposition*toHeader, SipPreposition*fromHeader, string branch)
{
SipMessage *invite = new SipMessage();
invite->msmReqMethod = method;
invite->msmCallId = this->mCallId;
//string toContact = makeUri(mRemoteUsername,mRemoteDomain); // dialed_number@remote_ip
//string fromContact = makeUri(mSipUsername,localIP());
//invite->msmReqUri = makeUri(mRemoteUsername,mRemoteDomain);
invite->msmReqUri = requestUri;
invite->smAddViaBranch(this,branch);
invite->msmCSeqMethod = invite->msmReqMethod;
invite->msmCSeqNum = mLocalCSeq; // We dont need to advance for an initial request; caller advances if necessary.
string realm = gConfig.getStr("SIP.Realm");
if (realm.length() > 0) {
string tousername = toHeader->uriUsername();
string fromusername = fromHeader->uriUsername();
string toTag = toHeader->getTag();
string fromTag = fromHeader->getTag();
string toUriString = makeUri(tousername ,realm,0);
string fromUriString = makeUri(fromusername ,realm,0);
invite->msmTo = SipPreposition("",toUriString, toTag );
invite->msmFrom = SipPreposition("",fromUriString, fromTag );
} else {
invite->msmTo = *toHeader;
invite->msmFrom = *fromHeader;
}
invite->msmContactValue = localContact(whoami);
string prefid = this->preferredIdentity(whoami);
static const string cPreferredIdentityString("P-Preferred-Identity");
invite->smAddHeader(cPreferredIdentityString,prefid);
// The caller has not added the content yet: saveInviteOrMessage(invite,true);
return invite;
}
// This is an *initial* request only, for INVITE or MESSAGE.
// This version generates the request from the values in the DialogStateVars.
SipMessage *SipBase::makeInitialRequest(string method)
{
string requestUri = dsRemoteURI();
this->mInviteViaBranch = make_branch();
return makeRequest(method,requestUri,sipLocalUsername(),&mRemoteHeader,&mLocalHeader,this->mInviteViaBranch);
}
void SipMOInviteClientTransactionLayer::MOUssdSendINVITE(string ussd, const L3LogicalChannel *chan)
{
static const char* xmlUssdTemplate =
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
"<ussd-data>\n"
" <language>en</language>\n"
" <ussd-string>%s</ussd-string>\n"
"</ussd-data>\n";
LOG(INFO) << "user " << sipLocalUsername() << " state " << getSipState() <<sbText();
static const string cInviteStr("INVITE");
SipMessage *invite = makeInitialRequest(cInviteStr);
// This is dumber than snot. We have to put in a dummy sdp with port 0.
mRTPPort = 0;
invite->smAddBody(string("application/sdp"),makeSDPOffer());
// Add RFC-4119 geolocation XML to content area, if available.
// TODO: This makes it a multipart message, needs to be tested.
string xml = format(xmlUssdTemplate,ussd);
invite->smAddBody(string("application/vnd.3gpp.ussd+xml"),xml);
writePrivateHeaders(invite,chan);
moWriteLowSide(invite);
LOG(DEBUG) <<LOGVAR(invite);
delete invite;
setSipState(Starting);
}
//old args: const char * calledUser, const char * calledDomain, short rtpPort, Control::CodecSet codec,
void SipMOInviteClientTransactionLayer::MOCSendINVITE(const L3LogicalChannel *chan)
{
static const char* xmlGeoprivTemplate =
"<?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";
LOG(INFO) << "user " << sipLocalUsername() << " state " << getSipState() <<sbText();
#if PAT_TEST_SIP_DIRECT
// (pat 7-23-2013): This code has eroded beyond recoverability...
//bool directBtsConnection = false;
// exten => _X.,1,Set(Name=${ODBC_SQL(select dial from dialdata_table where exten=\"${EXTEN}\")})
// exten => _X.,n,GotoIf($["${Name}"=""] ?other-lines,${EXTEN},1)
// exten => _X.,n,Set(IPAddr=${ODBC_SQL(select ipaddr from sip_buddies where username=\"${Name}\")})
// exten => _X.,n,GotoIf($["${IPAddr}"=""] ?other-lines,${EXTEN},1)
// exten => _X.,n,Set(Port=${ODBC_SQL(select port from sip_buddies where username=\"${Name}\")})
// exten => _X.,n,GotoIf($["${Port}"!=""] ?dialNum)
// exten => _X.,n,Set(Port=5062) ; Port was not set, so set to default. Gets around bug in subscriberRegistry
// exten => _X.,n(dialNum),Dial(SIP/${Name}@${IPAddr}:${Port})
if (gConfig.getStr("SIP.Proxy.Mode") == string("direct")) {
// Is this IMSI registered directly on a BTS?
string remoteIMSI = gSubscriberRegistry.getIMSI(wCalledUsername);
string remoteIPStr, remotePortStr;
if (remoteIMSI != "") {
remoteIPStr = gSubscriberRegistry.imsiGet(remoteIMSI,"ipaddr");
remotePortStr = gSubscriberRegistry.imsiGet(remoteIMSI,"port");
unsigned remotePort = (remotePortStr != "") ? atoi(remotePortStr.c_str()) : 0;
LOG(DEBUG) << "BTS direct test: "<<wCalledUsername
<< format(" -> SIP/%s@%s:%s",remoteIMSI.c_str(),remoteIPStr.c_str(),remotePortStr.c_str()) <<sbText();
if (remoteIPStr != "" && remotePort) {
//directBTSConnection = true;
mRemoteUsername = remoteIMSI;
mProxyIP = remoteIPStr;
mProxyPort = remotePort;
LOG(INFO) << "Calling BTS direct: "<<wCalledUsername
<< format(" -> SIP/%s@%s:%u",mRemoteUsername.c_str(),mProxyIP.c_str(),mProxyPort) <<sbText();
}
}
}
//mRemoteUsername = "IMSI001690000000002"; // pats iphone
#endif
LOG(DEBUG) <<sbText();
static const string cInviteStr("INVITE");
SipMessage *invite = makeInitialRequest(cInviteStr);
invite->smAddBody(string("application/sdp"),makeSDPOffer());
string username = sipLocalUsername();
WATCH("MOC imsi="<<username);
if (username.substr(0,4) == "IMSI") {
string pAssociatedUri, pAssertedIdentity;
gTMSITable.getSipIdentities(username.substr(4),pAssociatedUri,pAssertedIdentity);
WATCH("MOC"<<LOGVAR(username)<<LOGVAR(pAssociatedUri)<<LOGVAR(pAssertedIdentity));
if (pAssociatedUri.size()) {
invite->smAddHeader("P-Associated-URI",pAssociatedUri);
}
if (pAssertedIdentity.size()) {
invite->smAddHeader("P-Asserted-Identity",pAssertedIdentity);
}
}
writePrivateHeaders(invite,chan);
moWriteLowSide(invite);
delete invite;
setSipState(Starting);
};
void SipMOInviteClientTransactionLayer::handleSMSResponse(SipMessage *sipmsg)
{
// There are only three cases, and here they are:
stopTimers();
int code = sipmsg->smGetCode();
if (code < 200) {
// (pat) 11-26 This was wrong: mTimerBF.set(64*T1);
return; // Ignore 100 Trying.
}
// Smqueue incorrectly requires us to add a from-tag, and sends a 400 error if we dont.
// So if we get a 400 error, see if the user agent is blank (which is what smqueue does, yate adds a correct user-agent)
// then make an attempt to redeliver the message with a from-tag.
// What a botch-up.
if (code == 400) {
string useragent = sipmsg->msmHeaders.paramFind("User-Agent");
if (useragent == "") {
LOG(INFO) << "Message delivery failed; no user-agent specified. Assuming smqueue and resending MESSAGE";
gPeerIsBuggySmqueue = true;
// Resend the message, and this time it will add the from-tag.
if (dgGetDialog()->MOSMSRetry()) { return; }
}
}
dialogPushState((code/100)*100 == 200 ? Cleared : SSFail,code);
mTimerK.setOnce(T4); // 17.1.2.2: Timer K Controls when the dialog is destroyed.
}
void SipInviteServerTransactionLayerBase::SipMTCancel(SipMessage *sipmsg)
{
assert(sipmsg->isCANCEL());
SipMessageReply cancelok(sipmsg,200,string("OK"),this);
sipWrite(&cancelok);
if (dsPeer()->ipIsReliableTransport()) {
// It no longer matters whether we use Canceled or MTDCanceling state, and we should get rid of some states.
dialogPushState(Canceled,0);
} else {
dialogPushState(MTDCanceling,0);
setTimerJ();
}
}
void SipInviteServerTransactionLayerBase::SipMTBye(SipMessage *sipmsg)
{
assert(sipmsg->isBYE());
gReports.incr("OpenBTS.SIP.BYE.In");
SipMessageReply byeok(sipmsg,200,string("OK"),this);
sipWrite(&byeok);
if (dsPeer()->ipIsReliableTransport()) {
dialogPushState(Cleared,0);
} else {
dialogPushState(MTDClearing,0);
setTimerJ();
}
}
// Incoming message from SIPInterface.
void SipMOInviteClientTransactionLayer::MOWriteHighSide(SipMessage *sipmsg)
{
int code = sipmsg->smGetCode();
LOG(DEBUG) <<LOGVAR(code) <<LOGVAR(dgIsInvite());
if (code == 0) { // It is a SIP Request. Switch based on the method.
if (sipmsg->isCANCEL()) {
mTimerBF.stop();
// I dont think the peer is supposed to do this, but lets cancel it:
SipMTCancel(sipmsg);
} else if (sipmsg->isBYE()) {
// A BYE should not be sent by the peer until dialog established, and we are supposed to send 405 error.
// but instead we are going to quietly terminate the dialog anyway.
SipMTBye(sipmsg);
} else {
// Not expecting any others.
// Must send 405 error.
LOG(WARNING)<<"SIP Message ignored:"<<sipmsg;
SipMessageReply oops(sipmsg,405,string("Method Not Allowed"),this);
sipWrite(&oops);
}
} else { // It is a SIP reply.
saveMOResponse(sipmsg); LOG(DEBUG)<<"saveResponse"<<sipmsg;
if (dgIsInvite()) { // It is INVITE
stopTimers(); // Yes we stop the timers for every possible case. TimerBF will be restarted when handleInviteResponse sends the reply.
handleInviteResponse(code,true);
} else { // It is a MESSAGE
handleSMSResponse(sipmsg);
}
}
}
// Outgoing message.
void SipMOInviteClientTransactionLayer::moWriteLowSide(SipMessage *sipmsg)
{
if (sipmsg->isINVITE() || sipmsg->isMESSAGE()) { // It cant be anything else.
if (!dsPeer()->ipIsReliableTransport()) { mTimerAE.set(2*T1); }
mTimerBF.setOnce(64*T1); // RFC3261 17.1.2.2.2 Timer F
// This assert is not true in the weird case where we resend an SMS message.
// We should not be using this code for MESSAGE in the first place - it should be a TU.
//assert(mInvite == 0);
saveInviteOrMessage(sipmsg,true);
}
sipWrite(sipmsg);
}
// (pat) Counter-intuitively, the "ACK" is a SIP Request, not a SIP Response.
// Therefore its first line includes a Request-URI, and the request-uri is also placed in the "To" field.
// RFC2234 13.1: "The procedure for sending this ACK depends on the type of response.
// For final responses between 300 and 699, the ACK processing is done in the transaction layer and follows
// one set of rules (See Section 17). For 2xx responses, the ACK is generated by the UAC core. (section 13)"
// 17.1.1.3: "The ACK request constructed by the client transaction MUST contain
// values for the Call-ID, From, and Request-URI that are equal to the
// values of those header fields in the request passed to the transport
// by the client transaction (call this the "original request"). The To
// header field in the ACK MUST equal the To header field in the
// response being acknowledged, and therefore will usually differ from
// the To header field in the original request by the addition of the
// tag parameter. The ACK MUST contain a single Via header field, and
// this MUST be equal to the top Via header field of the original
// request. The CSeq header field in the ACK MUST contain the same
// value for the sequence number as was present in the original request,
// but the method parameter MUST be equal to "ACK".
//
void SipMOInviteClientTransactionLayer::MOCSendACK()
{
assert(! mTimerAE.isActive() && ! mTimerBF.isActive());
LOG(INFO) << sbText();
//LOG(INFO) << "user " << mSipUsername << " state " << getSipState() <<sbText();
static const string cAckstr("ACK");
SipMessageAckOrCancel ack(cAckstr,mInvite);
ack.msmTo = *dsRequestToHeader(); // Must get the updated to-tag.
sipWrite(&ack);
// we dont care mTimerD.set(T4);
}
void SipMOInviteClientTransactionLayer::MOSMSSendMESSAGE(const string &messageText, const string &contentType)
{
LOG(INFO) << "SIP send to " << dsRequestToHeader() <<" MESSAGE " << messageText <<sbText();
assert(mDialogType == SIPDTMOSMS);
gReports.incr("OpenBTS.SIP.MESSAGE.Out");
static const string cMessagestr("MESSAGE");
SipMessage *msg = makeInitialRequest(cMessagestr);
msg->smAddBody(contentType,messageText);
moWriteLowSide(msg);
delete msg;
setSipState(MOSMSSubmit);
}
// This is a temporary routine to work around bugs in smqueue.
// Resend the message with changes (gPeerIsBuggySmqueue, set/reset by the caller) to see if it works any better.
bool SipMOInviteClientTransactionLayer::MOSMSRetry()
{
LOG(INFO);
SipDialog *oldDialog = dgGetDialog();
FullMobileId fromMsId(oldDialog->sipLocalUsername());
SipDialog *newDialog = SipDialog::newSipDialogMOSMS(
mTranId,
fromMsId, // caller imsi
oldDialog->sipRemoteUsername(),
oldDialog->smsBody,
oldDialog->smsContentType);
gNewTransactionTable.ttSetDialog(oldDialog->mTranId,newDialog);
return true; // success
//dialog->mLocalCSeq = gSipInterface.nextInviteCSeqNum(); // formerly: random()%600;
//dialog->dsSetCallId(globallyUniqueId(""));
//dialog->mLocalHeader.setTag(gPeerIsBuggySmqueue ? make_tag() : "");
//dialog->MOSMSSendMESSAGE(mInvite->msmBody,mInvite->msmContentType);
}
// Return TRUE to remove the dialog.
bool SipMOInviteClientTransactionLayer::moPeriodicService()
{
if (mTimerAE.expired()) { // Resend timer.
// The HANDOVER is inbound, but the invite is outbound like MOC.
if (getSipState() == Starting || getSipState() == HandoverInbound || getSipState() == MOSMSSubmit) {
sipWrite(getInvite());
mTimerAE.setDouble();
} else {
mTimerAE.stop();
}
} else if (mTimerBF.expired() || mTimerD.expired()) { // Dialog killer timer.
// Whoops. No response from peer.
stopTimers();
dialogPushState(SSFail,0);
return true;
} else if (mTimerK.expired()) { // Normal exit delay to absorb resends.
stopTimers();
if (! dgIsInvite()) { return true; } // It is a SIP MESSAGE.
} else if (sipIsFinished()) {
// If one of the kill timers is active, wait for it to expire, otherwise kill now.
return (mTimerBF.isActive() || mTimerD.isActive() || mTimerK.isActive()) ? false : true;
}
return false;
}
string SipMOInviteClientTransactionLayer::motlText() const
{
ostringstream os;
os <<LOGVAR2("Timers: AE",mTimerAE) <<LOGVAR2("BF",mTimerBF) <<LOGVAR2("K",mTimerK) <<LOGVAR2("D",mTimerD);
return os.str();
}
SipState SipBase::MODSendCANCEL()
{
LOG(INFO) << sbText();
setSipState(MODCanceling); // (pat) MOD sent a cancel, see forceSIPClearing.
SipMOCancelTU *cancelTU = new SipMOCancelTU(dynamic_cast<SipDialog*>(this));
cancelTU->sctStart();
return getSipState();
}
// Type is RtpCallback, but args determined by rtp_signal_table_emit2
extern "C" {
void ourRtpTimestampJumpCallback(RtpSession *session, unsigned long timestamp,unsigned long dialogid)
{
SipBase *dialog = gSipInterface.dmFindDialogByRtp(session);
if (dialog) {
LOG(NOTICE) << "RTP timestamp jump"<<LOGVAR(timestamp)<<LOGVAR(dialogid)<<dialog;
// This is called from the same thread that is calling rxFrame or txFrame, so no problem.
if (dialog->mRxTime) {
rtp_session_resync(session);
dialog->mRxTime = 0;
dialog->mRxRealTime = 0;
}
} else {
LOG(ALERT) << "RTP timestamp jump, but no dialog"<<LOGVAR(dialogid);
}
}
};
void SipRtp::initRTP1(const char *d_ip_addr, unsigned d_port, unsigned dialogId)
{
LOG(DEBUG) << LOGVAR(d_ip_addr)<<LOGVAR(d_port)<<sbText();
if(mSession == NULL) {
mSession = rtp_session_new(RTP_SESSION_SENDRECV);
gCountRtpSessions++;
}
bool rfc2833 = gConfig.getBool("SIP.DTMF.RFC2833");
if (rfc2833) {
RtpProfile* profile = rtp_session_get_send_profile(mSession);
int index = gConfig.getNum("SIP.DTMF.RFC2833.PayloadType");
// (pat) The &payload_type_telephone_event comes from the RTP library 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);
}
// 8-6-2013: I tried turning scheduling mode to FALSE, but it didnt seem to work at all.
// There is an interesting problem when you dial 2600 since the rxFrame is blocking on its own txFrame,
// a paradox that somehow doesnt hang.
if (rtpUseRealTime) {
// Disable blocking and scheduling in the RTP library block.
rtp_session_set_blocking_mode(mSession, FALSE);
rtp_session_set_scheduling_mode(mSession, FALSE);
} else {
// Let the RTP library block.
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);
// (pat added) The last argument is user payload data that is passed to ourRtpTimestampJumpCallback()
// I was going to use the dialogId but decided to look up the dialog by mSession.
rtp_session_signal_connect(mSession,"timestamp_jump",(RtpCallback)ourRtpTimestampJumpCallback,dialogId);
gCountRtpSockets++;
#ifdef ORTP_NEW_API
rtp_session_set_local_addr(mSession, "0.0.0.0", mRTPPort, -1);
#else
rtp_session_set_local_addr(mSession, "0.0.0.0", mRTPPort);
#endif
rtp_session_set_remote_addr(mSession, d_ip_addr, d_port);
WATCHF("*** initRTP local=%d remote=%s %d\n",mRTPPort,d_ip_addr,d_port);
int speechBuffer = gConfig.getNum("GSM.SpeechBuffer");
// The RTP library sets the default here to 5000, but I think the cost of resynchronization is very
// low and we should do it more often to reduce horrendous sound quality that otherwise occurs
// when there is extraneous delay in the received frames. Such delay occurs due to the transmitter
// using the TCH for FACCH, for in-call-SMS, and other reasons.
// The horrendous sound I suspect is caused by improper computation of the rxframe number internally.
// I'm setting it down such that we will resynchronize whenever we get more than one frame off, where one frame = 160.
// Update: That worked great as long as there was no jump, but
// there seems to be a bug in the RTP library that the time jump limit must exceed the buffered amount
// or it constantly tries to resync.
// Update: When the timestamp jump occurs there is a very noticeable silence presumably while the jitter
// buffer is reloaded, so instead of setting this low, we will leave it high (it must be set to
// something to handle the negative timestamp jump after handover) and go back to setting rxTime from the actual time.
//unsigned jump_limit = max(300,((speechBuffer+159+160) / 160));
unsigned jump_limit = 5000;
LOG(DEBUG)<<LOGVAR(jump_limit);
rtp_session_set_time_jump_limit(mSession,jump_limit);
if (speechBuffer == 0) {
// (pat) Special case value turns off the rtp jitter compensation entirely.
// There is some intrinsic buffering both in GSML1FEC and between us and the transceiver.
rtp_session_enable_jitter_buffer(mSession,FALSE);
} else if (speechBuffer == 1) {
// (pat) The default is adaptive speech buffer, so we just do nothing in this case.
} else {
// (pat 8-6-2013) The RTP adaptive jitter buffer does not work well with OpenBTS because it assumes
// the source stream is continuous in time, but it is not in our case. When FACCH is used or there
// is some other drop out, the lost time appears to be added to the total delay through the de-jitter algorithm.
// At least some phones seem to turn the TCH off completely during an in-call SMS.
// So lets turn it off. This code was copied from rtp_session_init().
// (pat update) The RTP library goes south if you set the jitter buffer size over about 300 msecs,
// so heck with it - just use the adaptive algorithm. It is mostly fixed now anyway.
{
//int packets = speechBuffer/20 + 10; // Number of 20 msec packets needed for buffering, plus some slop.
JBParameters jbp;
jbp.min_size=RTP_DEFAULT_JITTER_TIME; // Not used by RTP code but we are setting it anyway.
jbp.nom_size=speechBuffer; // Original default was 80 msecs.
jbp.max_size=-1; // Not used by RTP code.
// The max_packets must be large enough to cover the speech buffer size plus a lot of slop, not sure how much.
jbp.max_packets= 100;/* maximum number of packet allowed to be queued */
jbp.adaptive=FALSE;
rtp_session_enable_jitter_buffer(mSession,TRUE); // redundant, this is the default.
rtp_session_set_jitter_buffer_params(mSession,&jbp);
}
}
// Check for event support.
int code = rtp_session_telephone_events_supported(mSession);
if (code == -1) {
if (rfc2833) { LOG(CRIT) << "RTP session does not support selected DTMF method RFC-2833" <<sbText(); }
else { LOG(CRIT) << "RTP session does not support telephone events" <<sbText(); }
}
}
void SipBase::initRTP()
{
SdpInfo sdp;
sdp.sdpParse(getSdpRemote().c_str());
initRTP1(sdp.sdpHost.c_str(),sdp.sdpRtpPort,mDialogId);
}
void SipBase::MTCInitRTP()
{
initRTP();
}
void SipBase::MOCInitRTP()
{
initRTP();
}
bool SipRtp::txDtmf()
{
ScopedLock lock(mRtpLock);
//true means start
bool start = (mDTMFDuration == 0);
mblk_t *m = rtp_session_create_telephone_event_packet(mSession,start);
if (!m) {
// (pat) Failed because, and I quote: "the rtp session cannot support telephony event (because the rtp profile
// it is bound to does not include a telephony event payload type)."
LOG(DEBUG) << "fail";
return false;
}
// (pat) Max RTP event duration is 8 seconds, so if we exceed that turn it off. Back it off a little (5 packets, 100ms) because
// we also need to send the three end packets and it is not clear to me if the DTMFDuration should be incremented or not
// during the end packets, but we are incrementing.
// (8 * 50 * 160) - (5 * 160) = 63200.
if (!mDTMFEnding && mDTMFDuration >= 63200) {
mDTMFEnding = 1;
}
//volume 10 for some magic reason, arg 3 is true to send an end packet.
// (pat) The magic reason is that the spec says DTMF tones below a certain dB should be ignored by the receiver, which is dumber than snot.
int code = rtp_session_add_telephone_event(mSession,m,get_rtp_tev_type(mDTMF),!!mDTMFEnding,10,mDTMFDuration);
int bytes = rtp_session_sendm_with_ts(mSession,m,mDTMFStartTime);
LOG(DEBUG) <<LOGVAR(start) <<LOGVAR(mDTMF) <<LOGVAR(mDTMFEnding) <<LOGVAR(mDTMFDuration) <<LOGVAR(code) <<LOGVAR(bytes);
// (pat) There is a buglet that this time would be incorrect if we flushed some RTP packets.
mDTMFDuration += 160;
if (mDTMFEnding) {
// We need to send the end packet three times.
if (mDTMFEnding++ >= 3) {
mDTMFEnding = 0;
mDTMF = 0;
}
}
return (!code && bytes > 0);
}
// Return true if ok, false on failure.
bool SipRtp::startDTMF(char key)
{
LOG (DEBUG) << key <<sbText();
if (getSipState()!=Active) return false;
if (get_rtp_tev_type(key) < 0){
LOG(WARNING) << "DTMF (using RFC-2833) sent invalid key, numeric value="<<(int) key;
return false;
}
// (pat) It is ok to start a new DTMF before the old one ended.
mDTMF = key;
mDTMFDuration = 0;
mDTMFStartTime = mTxTime;
mDTMFEnding = 0;
if (! txDtmf()) {
// Error? Turn off DTMF sending.
LOG(WARNING) << "DTMF RFC-2833 failed on start." <<sbText();
mDTMF = 0;
return false;
}
return true;
}
void SipRtp::stopDTMF()
{
//false means not start
/***
mblk_t *m = rtp_session_create_telephone_event_packet(mSession,false);
//volume 10 for some magic reason, end is true
int code = rtp_session_add_telephone_event(mSession,m,get_rtp_tev_type(mDTMF),true,10,mDTMFDuration);
int bytes = rtp_session_sendm_with_ts(mSession,m,mDTMFStartTime);
mDTMFDuration += 160;
LOG (DEBUG) << "DTMF RFC-2833 sending " << mDTMF << " " << mDTMFDuration <<sbText();
// Turn it off if there's an error.
if (code || bytes <= 0)
***/
if (!mDTMF) {
LOG(WARNING) << "stop DTMF command received after DTMF ended.";
return;
}
mDTMFEnding = 1;
if (! txDtmf()) {
LOG(ERR) << "DTMF RFC-2833 failed at end" <<sbText();
mDTMF = 0;
}
}
// send frame in the uplink direction.
// The gsm bit rate is 13500 and clock rate is 8000. Time is 20ms or 50 packets/sec.
// The 160 that we send is divided by payload->clock_rate in rtp_session_ts_to_time to yield 20ms.
void SipRtp::txFrame(GSM::AudioFrame* frame, unsigned numFlushed)
{
if(getSipState()!=Active) return;
ScopedLock lock(mRtpLock);
// HACK -- Hardcoded for GSM/8000.
// FIXME: Make this work for multiple vocoder types. (pat) fixed, but codec is still hard-coded in initRTP1.
int nbytes = frame->sizeBytes();
// (pat 8-2013) Our send stream is discontinous. After a discontinuity, the sound degrades.
// I think this is caused by bugs in the RTP library.
mTxTime += (numFlushed+1)*160;
int result = rtp_session_send_with_ts(mSession, frame->begin(), nbytes, mTxTime);
LOG(DEBUG) << LOGVAR(mTxTime) <<LOGVAR(result);
// (pat) The result is the number of bytes sent over the network, which includes nbytes data + 12 bytes of RTP header.
if (result < nbytes || result != 33+12) {
LOG(DEBUG) << "rtp_session_send_with_ts("<<nbytes<<") returned "<<result <<sbText();
}
if (mDTMF) {
//false means not start
/*****
mblk_t *m = rtp_session_create_telephone_event_packet(mSession,false);
//volume 10 for some magic reason, false means not end
int code = rtp_session_add_telephone_event(mSession,m,get_rtp_tev_type(mDTMF),false,10,mDTMFDuration);
int bytes = rtp_session_sendm_with_ts(mSession,m,mDTMFStartTime);
mDTMFDuration += 160;
//LOG (DEBUG) << "DTMF RFC-2833 sending " << mDTMF << " " << mDTMFDuration <<sbText();
if (code || bytes <=0)
***/
if (! txDtmf())
{
LOG(ERR) << "DTMF RFC-2833 failed after start." <<sbText();
mDTMF=0; // Turn it off if there's an error.
}
}
}
GSM::AudioFrame *SipRtp::rxFrame()
{
if(getSipState()!=Active) {
LOG(DEBUG) <<"skip"<<LOGVAR(getSipState());
return 0;
}
int more = 0;
// The buffer size is:
// For GSM, 260 bits + 4 bit header is 33 bytes.
// For TFO (3GPP 28.062 5.2.2) is 40 bytes.
// For AMR 'compact transport format', variable size, max is 244 bits + 10 bit header??
// (pat) We will not support TFO for years, if ever.
// TODO: Make the rxFrame buffer big enough (160?) for G.711. But we dont support that yet.
const int maxsize = 33;
// (pat 8-2013) I tried rtp_session_get_current_recv_ts but it just doesnt work; returns garbage.
// It is frightening how much we depend on the extremely buggy RTP library.
//uint32_t suggestedRxTime = rtp_session_get_current_recv_ts(mSession);
// (pat 8-2013) The RTP library has a scheduling mode and a blocking mode whereby it blocks the thread
// until the specified time has elapsed, which is 20ms.
// But after a discontinuity in the send data stream the RTP scheduler seems to get confused and rxFrame
// returns 0 for long periods of time.
// A discontinuity can be artificially generated at the time of writing using an in-call-SMS,
// which currently blocks TCH while the current thread runs SACCH, a bug to be fixed,
// however discontinuities could occur for other reasons so the code should not break when one occurs.
// I added this code controlled by rtpUseRealTime so we could handle the real elapsed time ourselves and not block.
// This code below suffers no such instability, so we are using it. Someday if we switch RTP libraries,
// someone can try turning off rtpUseRealTime - but you have to TEST DISCONTINUITIES, which is hard,
// so dont just turn this off, try a test call, and pronounce it fixed.
if (rtpUseRealTime) {
struct timeval tv;
gettimeofday(&tv,NULL);
uint64_t realTime = (uint64_t)tv.tv_sec * (1000 * 1000) + (uint64_t)tv.tv_usec; // time in usecs.
if (mRxRealTime == 0) {
// First time, init for next pass through...
devassert(mRxTime == 0);
mRxRealTime = realTime;
} else {
uint64_t delay = realTime - mRxRealTime; // total elapsed time in usecs.
uint64_t delayInFrames = delay / (1000 * 20); // 20ms per frame // elapsed time in frames.
unsigned proposedRxTime = delayInFrames * 160; // elapsed time in RTP 'time' units. (160 / 8000 bitrate == 20 msecs)
LOG(DEBUG) << format("realTime=%.3f delay=%.3f delayInFrames=%u RxTime=%u proposed=%u",
realTime/1e6,delay/1e6,(unsigned)delayInFrames,mRxTime,proposedRxTime);
if (proposedRxTime <= mRxTime) {
LOG(DEBUG) <<"skip, insufficient time passed";
return NULL;
}
// We will snag the next frame with a number equal or higher than this. If there are none available, we get NULL.
// When there is a discontinuity of missing frames, we get a bunch of NULL frames during the discontinuity, then
// things pick up normally again.
mRxTime += 160;
}
}
GSM::AudioFrame *result = new GSM::AudioFrame(maxsize); // Make it big enough for anything we might support.
int ret = rtp_session_recv_with_ts(mSession, result->begin(), maxsize, mRxTime, &more);
// (pat) You MUST increase rxTime even if rtp_session_recv... returns 0.
// This looks like a bug in the RTP lib to me, specifically, here:
// Excerpt from rtpsession.c: rtp_session_recvm_with_ts():
// // prevent reading from the sockets when two consecutives calls for a same timestamp*/
// if (user_ts==session->rtp.rcv_last_app_ts)
// read_socket=FALSE;
// The bug is the above should also check the qempty() flag.
// It should only manifest when blocking mode is off but we had it on when I thought I saw troubles.
// I tried incrementing by just 1 when ret is 0, but that resulted in no sound at all.
if (!rtpUseRealTime) { mRxTime += 160; }
//LOG(DEBUG) << "rtp_session_recv returns("<<LOGVAR(mRxTime)<<LOGVAR(more)<<")="<<LOGVAR(ret) <<sbText();
devassert(ret <= maxsize); // Check for bugs in rtp library or Audio type setup.
LOG(DEBUG) <<LOGVAR(getSipState())<<LOGVAR(mRxTime)<<LOGVAR(ret);
if (ret <= 0) { delete result; return NULL; }
result->setSizeBits(ret * 8);
// (pat) Added warning; you could get ALOT of these:
// Update: It is not that the frame is over-sized, it is that there is another frame already in the queue.
//if (more) { LOG(WARNING) << "Incoming RTP frame over-sized, extra ignored." <<sbText(); }
if (more) { LOG(WARNING) << "Incoming RTP lagging behind clock." <<sbText(); }
return result;
}
bool SipBase::dgIsInvite() const
{
SipMessage *invite = getInvite();
return invite && invite->isINVITE();
}
// Are these the same dialogs?
bool SipBase::sameDialog(SipDialog *other)
{
if (this->callId() != other->callId()) { return false; }
if (this->dsLocalTag() != other->dsLocalTag()) { return false; }
if (this->dsRemoteTag() != other->dsRemoteTag()) { return false; }
// Good enough.
return true;
}
// Does this incoming message want to be processed by this dialog?
// NOTE: This is temporary until we fully support SipTransactions, in which case this will be
// used only for in-dialog requests, and in that case both to- and from-tags must match.
// This may not be correct for group calls - we may want to search each of several possible
// matching dialogs for the transaction matching the via-branch.
bool SipBase::matchMessage(SipMessage *msg)
{
// The caller already checked the callid, but lets do it again in case someone modifies the code later.
if (msg->smGetCallId() != this->callId()) { return false; }
// This code could be simplified by combining logic, but I left it verbose for clarity
if (msg->isRequest()) {
// If it is a request sent by the peer, the remote tag must match. Both empty is ok.
if (msg->smGetRemoteTag() != this->dsRemoteTag()) { return false; }
// The local tag in the message is either empty (meaning the peer has not received it yet) or matches.
string msgLocalTag = msg->smGetLocalTag();
if (! msgLocalTag.empty()) {
if (msgLocalTag != this->dsLocalTag()) { return false; }
}
} else {
// If it is a reply, it means the original request was sent by us. The local tags must match. Both empty is ok.
if (msg->smGetLocalTag() != this->dsLocalTag()) { return false; }
// The remote tag in the dialog is either empty (has not been set yet, will probably be set by this message), or matches.
string remoteTag = dsRemoteTag();
if (! remoteTag.empty()) {
if (remoteTag != msg->smGetRemoteTag()) { return false; }
}
}
return true; // Good enough.
}
// 17.2.3 tells how to match requests to server transactions, but that does not apply to this.
/* return true if this is exactly the same invite (not a re-invite) as the one we have stored */
bool SipBase::sameInviteOrMessage(SipMessage * msg)
{
ScopedLock lock(mDialogLock,__FILE__,__LINE__); // probably unnecessary.
assert(getInvite());
if (NULL == msg){
LOG(NOTICE) << "trying to compare empty message" <<sbText();
return false;
}
// We are assuming that the callids match.
// Otherwise, this would not have been called.
// FIXME -- Check callids and assrt if they down match.
// So we just check the CSeq.
// FIXME -- Check all of the pointers along these chains and log ERR if anthing is missing.
return sameMsg(msg,getInvite());
}
// Only the SipMTInviteServerTransactionLayer and SipMOInviteClientTransactionLayer are allowed to call
// the underlying sipWrite method directly for the invite transactions.
void SipBase::sipWrite(SipMessage *sipmsg)
{
if (!mProxy.mipValid) {
LOG(ERR) << "Attempt to write to invalid proxy ignored, address:"<<mProxy.mipName;
return;
}
gSipInterface.siWrite(&mProxy.mipSockAddr,sipmsg);
}
static string encodeSpaces(string str)
{
char *buf = (char*)alloca(str.size()+2), *bp;
const char *sp = str.c_str(), *esp = str.c_str() + str.size();
for (bp = buf; sp < esp; bp++, sp++) {
*bp = (*sp == ' ') ? '\t' : *sp;
}
return string(buf,str.size()); // we did not copy the trailing nul so must specify size.
}
// This message is created on BTS1 to send to BTS2.
string SipBase::dsHandoverMessage(string peer) const
{
SipMessage *msg = new SipMessageHandoverRefer(this,peer);
string str = msg->smGenerate();
//LOG(DEBUG) <<LOGVAR(str);
// We are temporarily sending this over the peering interface in a string of space-separated parameters.
// So to get rid of the spaces, replace them with tabs.
string result= encodeSpaces(str);
//LOG(DEBUG) <<LOGVAR(result);
return result;
}
SipDialog *SipBase::dgGetDialog()
{
return dynamic_cast<SipDialog*>(this);
}
void SipMTInviteServerTransactionLayer::MTCSendTrying()
{
SipMessage *invite = getInvite();
if (invite==NULL) {
setSipState(SSFail);
gReports.incr("OpenBTS.SIP.Failed.Local");
}
LOG(INFO) << sbText();
if (getSipState()==SSFail) return;
SipMessageReply trying(invite,100,string("Trying"),this);
mtWriteLowSide(&trying);
setSipState(Proceeding);
}
void SipMTInviteServerTransactionLayer::MTSMSSendTrying()
{
MTCSendTrying();
}
void SipMTInviteServerTransactionLayer::MTCSendRinging()
{
if (getSipState()==SSFail) return;
SipMessage *invite = getInvite();
LOG(INFO) <<sbText();
assert(invite);
LOG(DEBUG) << "send ringing" <<sbText();
SipMessageReply ringing(invite,180,string("Ringing"),this);
mtWriteLowSide(&ringing);
setSipState(Proceeding);
}
void SipMTInviteServerTransactionLayer::MTCSendOK(CodecSet wCodec, const L3LogicalChannel *chan)
{
if (getSipState()==SSFail) { devassert(0); }
SipMessage *invite = getInvite();
gReports.incr("OpenBTS.SIP.INVITE-OK.Out");
mRTPPort = allocateRTPPorts();
mCodec = wCodec;
LOG(INFO) <<sbText();
SipMessageReply ok(invite,200,string("OK"),this);
ok.smAddBody(string("application/sdp"),makeSDPAnswer()); // TODO: This should be a reply to the originating SDP offer.
writePrivateHeaders(&ok,chan);
mtWriteLowSide(&ok);
setSipState(Connecting);
// In RFC-3261 the Transaction Layer no longer handles timers after the OK is sent.
// The Transport Layer alone is not capabable of sending the 200 OK reliably because then the
// INVITE server transaction ends, and the INVITE client transaction no longer resends INVITEs after
// receiving a provisional response. Rather, the way that would end up being handled is by starting a new
// INVITE transaction, which is totally not what we want to do. So we will push out the 2xx OK
// until we get the ACK. Doesnt matter for reliable transports.
if (dgIsInvite()) { setTimerG(); }
setTimerH();
}
string SipMTInviteServerTransactionLayer::mttlText() const
{
ostringstream ss;
ss << LOGVAR2("Timers: G",mTimerG) <<LOGVAR2("H",mTimerH) <<LOGVAR2("J",mTimerJ);
return ss.str();
}
// Doesnt seem like messages need the private headers.
void SipMTInviteServerTransactionLayer::MTSMSReply(int code, const char *explanation) // , const L3LogicalChannel *chan)
{
LOG(INFO) <<sbText();
// If this operation was initiated from the CLI, there was no MESSAGE
if (mInvite) { // It is a MESSAGE in this case, not an INVITE
//2-2014: the reply to MESSAGE must include the to-field, so we pass the dialog to SIpMessageReply
SipMessageReply reply(mInvite,code,string(explanation),this); // previous: NULL);
sipWrite(&reply);
} else {
LOG(INFO) << "clearing CLI-generated transaction" <<sbText();
}
setSipState(code == 200 ? Cleared : SSFail);
}
// This can only be used for early errors before we get the ACK.
void SipMTInviteServerTransactionLayer::MTCEarlyError(int code, const char*reason) // The message must be 300-699.
{
LOG(DEBUG);
// TODO: What if we were already ACKed?
setSipState(MODError);
SipMessageReply reply(getInvite(),code,string(reason),this);
mtWriteLowSide(&reply);
if (dgIsInvite()) { setTimerG(); }
setTimerH();
}
// This is called for the second and subsequent received INVITEs as well as the ACK.
// We send the current response, whatever it is.
void SipMTInviteServerTransactionLayer::MTWriteHighSide(SipMessage *sipmsg) { // Incoming message from SIPInterface.
LOG(DEBUG);
SipState state = getSipState();
if (sipmsg->smGetCode() == 0) {
if (sipmsg->isINVITE()) {
if (mtLastResponse.smIsEmpty()) { MTCSendTrying(); }
else { sipWrite(&mtLastResponse); }
} else if (sipmsg->isACK()) {
if (state == SSNullState || state == Proceeding || state == Connecting) {
dialogPushState(Active,0);
gSipInterface.dmAddLocalTag(dgGetDialog());
} else {
// We could be failed or canceled, so ignore the ACK.
}
stopTimers(); // happiness
// The spec shows a short Timer I being started here, but all it does is specify a time
// when the dialog will stop absorbing additional ACKS, thus suppressing error messages.
// Well, how about if we just never throw an error for that? Done.
} else if (sipmsg->isCANCEL()) {
SipMTCancel(sipmsg);
} else if (sipmsg->isBYE()) { // TODO: This should not happen.
SipMTBye(sipmsg);
} else if (sipmsg->isMESSAGE()) {
// It is a duplicate MESSAGE. Resend the current response.
if (mtLastResponse.smIsEmpty()) {
if (! gConfig.getBool("SIP.RFC3428.NoTrying")) { MTSMSSendTrying(); } // Otherwise just ignore the duplicate MESSAGE.
} else {
sipWrite(&mtLastResponse);
}
} else {
// Not expecting any others. Must send 405 error.
LOG(WARNING)<<"SIP Message ignored:"<<sipmsg;
SipMessageReply oops(sipmsg,405,string("Method Not Allowed"),this);
sipWrite(&oops);
}
} else {
LOG(WARNING)<<"SIP Message ignored:"<<sipmsg;
}
}
// Return TRUE to remove the dialog.
bool SipMTInviteServerTransactionLayer::mtPeriodicService()
{
if (mTimerG.expired()) { // Resend timer.
if (getSipState() == SSFail || getSipState() == Active) {
sipWrite(mLastResponse);
mTimerG.setDouble(T2); // Will send again later.
} else {
// This could happen if a CANCEL started before the ACK was received.
// Not sure what to do - I think we will let the CANCEL take precedence, so stop sending this response.
mTimerG.stop();
}
} else if (mTimerJ.expired() || mTimerH.expired()) { // Dialog killer timers.
stopTimers(); // probably redundant.
// Time to destroy the Dialog.
if (dgIsInvite()) {
// Whoops. No ACK received. Notify L3 and remove the dialog
dialogPushState(SSFail,0);
} else {
// No need to notify, just remove the dialog.
}
return true; // Stop the dialog now. It will be deleted by the periodic service loop after the associated L3 transaction ends.
} else if (sipIsFinished()) {
// If one of the kill timers is active, wait for it to expire, otherwise kill now.
return (mTimerJ.isActive() || mTimerH.isActive()) ? false : true;
}
return false;
}
};
// vim: ts=4 sw=4