mirror of
https://github.com/RangeNetworks/openbts.git
synced 2025-11-03 21:33:15 +00:00
- prevents totally bogus SDP offers from being made due to conversion kabooms (m=audio 4294935774 RTP/AVP 3) (upstream r8160)
1977 lines
64 KiB
C++
1977 lines
64 KiB
C++
/**@file TransactionTable and related classes. */
|
|
|
|
/*
|
|
* Copyright 2008, 2010 Free Software Foundation, Inc.
|
|
* Copyright 2010 Kestrel Signal Process, 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::Control // Can set Log.Level.Control for debugging
|
|
|
|
#include "ControlCommon.h"
|
|
#include "L3TranEntry.h"
|
|
#include "L3MMLayer.h"
|
|
|
|
#include <GSMLogicalChannel.h>
|
|
#include <GSML3Message.h>
|
|
#include <GSML3CCMessages.h>
|
|
#include <GSML3RRMessages.h>
|
|
#include <GSML3MMMessages.h>
|
|
#include <GSMConfig.h>
|
|
|
|
#include <Peering.h>
|
|
|
|
//#include <SIPEngine.h>
|
|
//#include <SIPInterface.h>
|
|
#include <SIPUtility.h>
|
|
|
|
//#include <CallControl.h>
|
|
|
|
#include <Reporting.h>
|
|
#include <Logger.h>
|
|
#undef WARNING
|
|
|
|
// This is in the global namespace.
|
|
Control::NewTransactionTable gNewTransactionTable;
|
|
Control::StaleTransactionTable gStaleTransactionTable;
|
|
|
|
int gCountTranEntry = 0;
|
|
|
|
namespace Control {
|
|
using namespace std;
|
|
using namespace GSM;
|
|
using namespace SIP;
|
|
|
|
|
|
#if EXTERNAL_TRANSACTION_TABLE
|
|
// (pat) This external transaction table is obsolete and we will not support it any more.
|
|
// The code implementing it has eroded and would not work if enabled.
|
|
// It is retained here until we release version 4 in the remote off-chance that some important customer
|
|
// has built legacy applications that use this, so we can help migrate that customer to something different.
|
|
// This is extremely unlikely, since we have no customers.
|
|
static const char* createNewTransactionTable = {
|
|
"CREATE TABLE IF NOT EXISTS TRANSACTION_TABLE ("
|
|
"ID INTEGER PRIMARY KEY, " // internal transaction ID
|
|
"CHANNEL TEXT DEFAULT NULL," // channel description string (cross-refs CHANNEL_TABLE)
|
|
"CREATED INTEGER NOT NULL, " // Unix time of record creation
|
|
"CHANGED INTEGER NOT NULL, " // time of last state change
|
|
"TYPE TEXT, " // transaction type
|
|
"SUBSCRIBER TEXT, " // IMSI, if known
|
|
"L3TI INTEGER, " // GSM L3 transaction ID, +0x08 if generated by MS
|
|
"SIP_CALLID TEXT, " // SIP-side call id tag
|
|
"SIP_PROXY TEXT, " // SIP proxy IP
|
|
"CALLED TEXT, " // called party number
|
|
"CALLING TEXT, " // calling party number
|
|
"GSMSTATE TEXT, " // GSM/Q.931 state
|
|
"SIPSTATE TEXT " // SIP state
|
|
")"
|
|
};
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
HandoverEntry::HandoverEntry(const TranEntry *tran) :
|
|
mMyTranID(tran->tranID()),
|
|
mHandoverOtherBSTransactionID(0)
|
|
{
|
|
};
|
|
|
|
HandoverEntry *TranEntry::getHandoverEntry(bool create) const // It is not const, but we want C++ to be a happy compiler.
|
|
{
|
|
if (!mHandover && create) { mHandover = new HandoverEntry(this); }
|
|
return mHandover;
|
|
}
|
|
|
|
// class base initialization goes here.
|
|
void TranEntry::TranEntryInit()
|
|
{
|
|
mID = gNewTransactionTable.ttNewID();
|
|
mL3TI = cL3TIInvalid; // Until we know better.
|
|
mDialog = 0;
|
|
mHandover = NULL;
|
|
//mGSMState = CCState::NullState; moved to TranEntryProtected
|
|
mNumSQLTries = gConfig.getNum("Control.NumSQLTries"); // will be increased later by the SOS constructor.
|
|
mContext = NULL;
|
|
//mChannel = NULL;
|
|
//mNextChannel = NULL;
|
|
mMMData = NULL;
|
|
//mRemoved = false; moved to TranEntryProtected
|
|
//initTimers();
|
|
}
|
|
|
|
|
|
//DIG: Debug Start
|
|
#include <execinfo.h>
|
|
//DIG: Debug End
|
|
TranEntry::TranEntry(
|
|
SipDialog *wDialog,
|
|
//const L3MobileIdentity& wSubscriber,
|
|
const L3CMServiceType& wService)
|
|
{
|
|
gCountTranEntry++;
|
|
TranEntryInit();
|
|
if (wDialog) setDialog(wDialog);
|
|
//mSubscriber = wSubscriber;
|
|
mService = wService;
|
|
|
|
//DIG: Debug Start
|
|
if (0) {
|
|
printf("*********************************************************\n");
|
|
printf("*********************************************************\n");
|
|
printf(" CONSTRUCTOR\n");
|
|
printf("TranEntry::TranEntry() called, call stack follows:\n");
|
|
const int elements = 100;
|
|
void *buffer[elements];
|
|
int nptrs = backtrace(buffer, elements);
|
|
char **strings = backtrace_symbols(buffer, nptrs);
|
|
if (strings == NULL) { perror("backtrace_symbols"); }
|
|
else
|
|
{
|
|
for (int j = 0; j < nptrs; j++) printf("%s\n", strings[j]);
|
|
free(strings);
|
|
}
|
|
printf("*********************************************************\n");
|
|
printf("*********************************************************\n");
|
|
}
|
|
//DIG: Debug End
|
|
startTime = time(NULL);
|
|
endTime = 0;
|
|
//gNewTransactionTable.ttAdd(this);
|
|
}
|
|
|
|
// For MO the channel is always known.
|
|
TranEntry *TranEntry::newMO(MMContext *wChan, const GSM::L3CMServiceType& wService)
|
|
{
|
|
//L3MobileIdentity unknownId;
|
|
//TranEntry *result = new TranEntry(proxy,unknownId,wChannel,wService,CCState::NullState);
|
|
TranEntry *result = new TranEntry(NULL,wService); // No SipDialog yet for MO transactions.
|
|
LOG(DEBUG);
|
|
//wChan->chanGetContext(true)->mmConnectTran(result);
|
|
wChan->mmConnectTran(result);
|
|
gNewTransactionTable.ttAdd(result);
|
|
return result;
|
|
}
|
|
|
|
void TranEntry::setDialog(SIP::SipDialog *dialog) { mDialog = dialog; dialog->setTranId(mID); }
|
|
void TranEntry::txFrame(GSM::AudioFrame* frame, unsigned numFlushed) { getDialog()->txFrame(frame,numFlushed); }
|
|
GSM::AudioFrame *TranEntry::rxFrame() { return getDialog()->rxFrame(); } // Crashes if rtp not established.
|
|
|
|
unsigned TranEntry::getRTPPort() const
|
|
{
|
|
if (SipDialog *dialog = getDialog()) { return dialog->RTPPort(); }
|
|
return 0;
|
|
}
|
|
|
|
|
|
TranEntry *TranEntry::newMOSSD(MMContext* wChannel)
|
|
{
|
|
return newMO(wChannel,L3CMServiceType::SupplementaryService);
|
|
}
|
|
|
|
TranEntry *TranEntry::newMOC(MMContext* wChannel, CMServiceTypeCode serviceType)
|
|
{
|
|
assert(serviceType == L3CMServiceType::MobileOriginatedCall);
|
|
return newMO(wChannel,serviceType);
|
|
}
|
|
|
|
TranEntry *TranEntry::newMOSMS(MMContext* wChannel)
|
|
{
|
|
return newMO(wChannel,L3CMServiceType::ShortMessage);
|
|
}
|
|
|
|
TranEntry *TranEntry::newMOMM(MMContext* wChannel)
|
|
{
|
|
return newMO(wChannel,L3CMServiceType::LocationUpdateRequest);
|
|
}
|
|
|
|
// The transaction is created without an assigned channel.
|
|
TranEntry *TranEntry::newMTC(
|
|
SipDialog *dialog,
|
|
const FullMobileId& msid,
|
|
const GSM::L3CMServiceType& wService, // MobileTerminatedCall, FuzzCall, TestCall, or UndefinedType for generic page from CLI.
|
|
const string wCallerId)
|
|
//const L3CallingPartyBCDNumber& wCalling)
|
|
{
|
|
//L3MobileIdentity subscriber(toImsiDigits.c_str());
|
|
//TranEntry *result = new TranEntry(dialog,subscriber, wService);
|
|
TranEntry *result = new TranEntry(dialog, wService);
|
|
result->mSubscriber = msid;
|
|
result->mCalling = GSM::L3CallingPartyBCDNumber(wCallerId.c_str());
|
|
LOG(DEBUG) <<LOGVAR2("callerid",result->mCalling.digits());
|
|
gNewTransactionTable.ttAdd(result);
|
|
return result;
|
|
}
|
|
|
|
|
|
// post-l3-rewrite
|
|
TranEntry *TranEntry::newMTSMS(
|
|
SipDialog *dialog,
|
|
const FullMobileId& msid,
|
|
const L3CallingPartyBCDNumber& wCalling,
|
|
string smsBody, // (pat) The recommendation for C++11 is to pass-by-value parameters that will be copied.
|
|
string smsContentType)
|
|
{
|
|
//TranEntry *result = new TranEntry(dialog,subscriber,GSM::L3CMServiceType::MobileTerminatedShortMessage);
|
|
TranEntry *result = new TranEntry(dialog,GSM::L3CMServiceType::MobileTerminatedShortMessage);
|
|
result->mSubscriber = msid;
|
|
// The the L3TI is assigned when the transaction starts running. If ever.
|
|
result->mCalling = wCalling;
|
|
result->mMessage = smsBody;
|
|
result->mContentType = smsContentType;
|
|
gNewTransactionTable.ttAdd(result);
|
|
return result;
|
|
}
|
|
|
|
|
|
|
|
// Form for inbound handovers.
|
|
TranEntry *TranEntry::newHandover(
|
|
const struct sockaddr_in* peer,
|
|
unsigned wInboundHandoverReference,
|
|
SimpleKeyValue ¶ms,
|
|
L3LogicalChannel *wChannel,
|
|
unsigned wHandoverOtherBSTransactionID)
|
|
{
|
|
MMContext *mmchan = wChannel->chanGetContext(true); // This is where we create the MMContext for a handover.
|
|
//TranEntry *result = new TranEntry(proxy,imsi,wChannel,GSM::L3CMServiceType::HandoverCall,CCState::HandoverInbound);
|
|
// We dont want to open the dialog before receiving the handover.
|
|
// The proxy is not used until the dialog is created so it is no longer a parameter.
|
|
TranEntry *result = newMO(mmchan, GSM::L3CMServiceType::HandoverCall);
|
|
|
|
//TranEntry *result = new TranEntry(NULL, GSM::L3CMServiceType::HandoverCall);
|
|
//wChannel->getContext(true)->mmConnectTran(this);
|
|
//wChannel->chanSetVoiceTran(result); // TODO: This should error check no tran there yet.
|
|
|
|
result->setGSMState(CCState::HandoverInbound);
|
|
const char* IMSI = params.get("IMSI");
|
|
if (IMSI) result->mSubscriber = FullMobileId(IMSI);
|
|
|
|
const char* called = params.get("called");
|
|
if (called) {
|
|
// TODO: Do we need to call setCalled() which will update sql?
|
|
result->mCalled = GSM::L3CalledPartyBCDNumber(called);
|
|
result->mService = GSM::L3CMServiceType::MobileOriginatedCall;
|
|
}
|
|
|
|
const char* calling = params.get("calling");
|
|
if (calling) {
|
|
result->mCalling = GSM::L3CallingPartyBCDNumber(calling);
|
|
result->mService = GSM::L3CMServiceType::MobileTerminatedCall;
|
|
}
|
|
|
|
const char* L3TI = params.get("L3TI");
|
|
if (L3TI) {
|
|
result->mL3TI = strtol(L3TI,NULL,10);
|
|
} else {
|
|
// TODO: And what should l3ti be otherwise?
|
|
result->mL3TI = 7; // (pat) Not sure what this should be if not in inbound handover parameters.
|
|
}
|
|
|
|
const char* codec = params.get("codec");
|
|
// TODO: Is this an RTP codec number or a CodecSet number?
|
|
// Assuming this information came from a peer OpenBTS unit it is our internal CodecSet number.
|
|
if (codec) result->mCodecs = CodecSet((CodecType)atoi(codec));
|
|
|
|
// Set the SIP state.
|
|
//result->mSIP->setSipState(SIP::HandoverInbound);
|
|
|
|
//const char * callId = params.get("CallID");
|
|
//result->mSIP->setCallId(callId);
|
|
|
|
|
|
// This is used for inbound handovers.
|
|
// We are "BS2" in the handover ladder diagram.
|
|
// The message string was formed by the handoverString method.
|
|
result->getHandoverEntry(true)->initHandoverEntry(peer,wInboundHandoverReference,wHandoverOtherBSTransactionID,params);
|
|
|
|
return result;
|
|
}
|
|
|
|
void HandoverEntry::initHandoverEntry(
|
|
const struct sockaddr_in* peer,
|
|
unsigned wInboundHandoverReference,
|
|
unsigned wHandoverOtherBSTransactionID,
|
|
SimpleKeyValue ¶ms)
|
|
{
|
|
// FIXME: This is also in the params. Which do we want to use? (pat) This one.
|
|
mInboundReference = wInboundHandoverReference;
|
|
mHandoverOtherBSTransactionID = wHandoverOtherBSTransactionID;
|
|
|
|
// Save the peer address.
|
|
bcopy(peer,&mInboundPeer,sizeof(mInboundPeer));
|
|
|
|
const char* refer = params.get("REFER");
|
|
if (refer) {
|
|
// We changed spaces to tabs to get the REFER message through the peering interface.
|
|
// Since we are sending it through the SIP parser, it does not matter very much,
|
|
// however the tabs are preserved in a few places, especially the SDP strings,
|
|
// so change all the tabs back to spaces to be safe.
|
|
const char *inp; char *outp, *outbuf = (char*)alloca(strlen(refer)+1);
|
|
for (inp = refer, outp = outbuf; *inp; inp++, outp++) {
|
|
*outp = (*inp == '\t') ? ' ' : *inp;
|
|
}
|
|
*outp = 0;
|
|
mSipReferStr = string(outbuf);
|
|
}
|
|
}
|
|
|
|
|
|
TranEntry::~TranEntry()
|
|
{
|
|
//DIG: Debug Start
|
|
if (0) {
|
|
printf("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n");
|
|
printf("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n");
|
|
printf(" destructor\n");
|
|
printf("TranEntry::~TranEntry() called, call stack follows:\n");
|
|
const int elements = 100;
|
|
void *buffer[elements];
|
|
int nptrs = backtrace(buffer, elements);
|
|
char **strings = backtrace_symbols(buffer, nptrs);
|
|
if (strings == NULL) { perror("backtrace_symbols"); }
|
|
else
|
|
{
|
|
for (int j = 0; j < nptrs; j++) printf("%s\n", strings[j]);
|
|
free(strings);
|
|
}
|
|
printf("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n");
|
|
printf("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n");
|
|
}
|
|
//DIG: Debug End
|
|
gCountTranEntry--;
|
|
// This lock should go out of scope before the object is actually destroyed.
|
|
//ScopedLock lock(mLock,__FILE__,__LINE__);
|
|
|
|
// This is the l3-rewrite stack of procedures running for this transaction.
|
|
while (mProcStack.size()) {
|
|
MachineBase *pb = mProcStack.back();
|
|
mProcStack.pop_back();
|
|
delete pb;
|
|
}
|
|
|
|
// Remove any FIFO from the gPeerInterface.
|
|
gPeerInterface.removeFIFO(tranID());
|
|
|
|
if (mMMData) { delete mMMData; }
|
|
if (mHandover) { delete mHandover; }
|
|
|
|
#if EXTERNAL_TRANSACTION_TABLE
|
|
// Delete the SQL table entry. (pat) There wont be any for LocationUpdating procedure, or transactions that did not run until they got an IMSI.
|
|
char query[100];
|
|
sprintf(query,"DELETE FROM TRANSACTION_TABLE WHERE ID=%u",tranID());
|
|
runQuery(query);
|
|
#endif
|
|
}
|
|
|
|
//bool TranEntryProtected::isRemoved() const
|
|
//{
|
|
// if (mRemoved) {
|
|
// assert(0);
|
|
// return true;
|
|
// }
|
|
// return false;
|
|
//}
|
|
|
|
|
|
bool TranEntryProtected::clearingGSM() const
|
|
{
|
|
//if (isRemoved()) throw RemovedTransaction(tranID());
|
|
//ScopedLock lock(mLock,__FILE__,__LINE__);
|
|
return (mGSMState==CCState::ReleaseRequest) || (mGSMState==CCState::DisconnectIndication);
|
|
}
|
|
|
|
|
|
bool TranEntryProtected::isStuckOrRemoved() const
|
|
{
|
|
//if (mRemoved) return true;
|
|
unsigned age = mStateTimer.elapsed();
|
|
|
|
// 180-second tests
|
|
if (age < 180*1000) return false;
|
|
// Dead if someone requested removal >3 min ago.
|
|
// (pat) Post-l3-rewrite we dont need to wait to delete TranEntrys,
|
|
// because nothing points back to them permanently, only currently running functions, for example,
|
|
// Peering gets a TranEntry pointer and immediately modifies it. One second would be over-kill.
|
|
// But having TranEntrys stick around a while may still be useful for debugging to see them in the CLI,
|
|
// so I did not change this.
|
|
// Any GSM state other than Active for >3 min?
|
|
if (getGSMState() !=CCState::Active) { return true; }
|
|
// Any SIP stte other than active for >3 min?
|
|
//if (lSIPState !=SIP::Active) return true;
|
|
return false;
|
|
}
|
|
|
|
bool TranEntry::deadOrRemoved() const
|
|
{
|
|
//ScopedLock lock(mLock,__FILE__,__LINE__);
|
|
if (isStuckOrRemoved()) {
|
|
LOG(NOTICE)<<"Transaction in state "<<getGSMState() <<" for >3 minutes; "<<*this;
|
|
return true;
|
|
}
|
|
SipDialog *dialog = getDialog();
|
|
if (dialog && dialog->sipIsStuck()) return true;
|
|
return false; // still going
|
|
}
|
|
|
|
//SIP::SipState TranEntry::getSipState() const
|
|
//{
|
|
// if (mDialog) { return mDialog->getSipState(); } // post-l3-rewrite
|
|
// return SIP::NullState;
|
|
//}
|
|
|
|
|
|
void TranEntryProtected::stateText(ostream &os) const
|
|
{
|
|
//if (mRemoved) os << " [removed]";
|
|
os << " GSMState=" << mGSMState; // Dont call getGSMState(), it asserts 0 if the transaction has been removed;
|
|
if (isStuckOrRemoved()) os << " [defunct]";
|
|
}
|
|
|
|
void TranEntryProtected::stateText(unsigned &state, std::string &deleted) const
|
|
{
|
|
state = (unsigned)mGSMState; // Dont call getGSMState(), it asserts 0 if the transaction has been removed;
|
|
if (isStuckOrRemoved()) deleted = " [defunct]";
|
|
else deleted = "";
|
|
}
|
|
|
|
|
|
// Use this for the column headers for the "calls" output
|
|
void TranEntry::header(ostream& os)
|
|
{
|
|
std::string fmtBuf("");
|
|
char buf[BUFSIZ];
|
|
|
|
fmtBuf += TranFmt::lblfmt_Active;
|
|
fmtBuf += TranFmt::lblfmt_TranId;
|
|
fmtBuf += TranFmt::lblfmt_L3TI;
|
|
fmtBuf += TranFmt::lblfmt_Service;
|
|
fmtBuf += TranFmt::lblfmt_To;
|
|
fmtBuf += TranFmt::lblfmt_From;
|
|
fmtBuf += TranFmt::lblfmt_AgeSec;
|
|
fmtBuf += TranFmt::lblfmt_StartTime;
|
|
fmtBuf += TranFmt::lblfmt_EndTime;
|
|
fmtBuf += TranFmt::lblfmt_Message;
|
|
fmtBuf += "\n";
|
|
|
|
snprintf(buf, sizeof(buf)-1, fmtBuf.c_str(),
|
|
"Active",
|
|
"TranId",
|
|
"L3TI",
|
|
"Service",
|
|
"To",
|
|
"From",
|
|
"AgeSec",
|
|
"Start Time",
|
|
"End Time",
|
|
"Message");
|
|
os << buf;
|
|
|
|
snprintf(buf, sizeof(buf)-1, fmtBuf.c_str(),
|
|
"======",
|
|
"==========",
|
|
"=========",
|
|
"=========",
|
|
"================",
|
|
"================",
|
|
"=========",
|
|
"=====================================",
|
|
"=====================================",
|
|
"========================================================");
|
|
os << buf;
|
|
}
|
|
|
|
// Use this for the column data for the "calls" output
|
|
void TranEntry::textTable(ostream& os) const
|
|
{
|
|
std::string fmtBuf("");
|
|
char buf[BUFSIZ];
|
|
|
|
fmtBuf += TranFmt::fmt_Active;
|
|
fmtBuf += TranFmt::fmt_TranId;
|
|
fmtBuf += TranFmt::fmt_L3TI;
|
|
fmtBuf += TranFmt::fmt_Service;
|
|
fmtBuf += TranFmt::fmt_To;
|
|
fmtBuf += TranFmt::fmt_From;
|
|
fmtBuf += TranFmt::fmt_AgeSec;
|
|
fmtBuf += TranFmt::fmt_StartTime;
|
|
if (endTime)
|
|
fmtBuf += TranFmt::fmt_EndTime;
|
|
else
|
|
fmtBuf += TranFmt::fmt_EndTime2;
|
|
fmtBuf += TranFmt::fmt_Message;
|
|
fmtBuf += "\n";
|
|
|
|
struct tm startTm, endTm;
|
|
localtime_r(&startTime, &startTm);
|
|
std::ostringstream svc;
|
|
mService.text(svc);
|
|
const char *psSvc = svc.str().c_str();
|
|
if (endTime)
|
|
{
|
|
localtime_r(&endTime, &endTm);
|
|
snprintf(buf, sizeof(buf)-1, fmtBuf.c_str(),
|
|
endTime == 0 ? "yes" : "no",
|
|
tranID(),
|
|
mL3TI,
|
|
psSvc,
|
|
mCalled.digits()[0] ? mCalled.digits() : "",
|
|
mCalling.digits()[0] ? mCalling.digits() : "",
|
|
(stateAge()+500)/1000,
|
|
startTm.tm_year + 1900, startTm.tm_mon + 1, startTm.tm_mday,
|
|
startTm.tm_hour, startTm.tm_min, startTm.tm_sec,
|
|
startTime,
|
|
endTm.tm_year + 1900, endTm.tm_mon + 1, endTm.tm_mday,
|
|
endTm.tm_hour, endTm.tm_min, endTm.tm_sec,
|
|
endTime,
|
|
mMessage.c_str());
|
|
} else
|
|
{
|
|
snprintf(buf, sizeof(buf)-1, fmtBuf.c_str(),
|
|
endTime == 0 ? "yes" : "no",
|
|
tranID(),
|
|
mL3TI,
|
|
psSvc,
|
|
mCalled.digits()[0] ? mCalled.digits() : "",
|
|
mCalling.digits()[0] ? mCalling.digits() : "",
|
|
(stateAge()+500)/1000,
|
|
startTm.tm_year + 1900, startTm.tm_mon + 1, startTm.tm_mday,
|
|
startTm.tm_hour, startTm.tm_min, startTm.tm_sec,
|
|
startTime,
|
|
"", // no end time
|
|
mMessage.c_str());
|
|
}
|
|
os << buf;
|
|
}
|
|
|
|
// Use this for the column data for the "calls" output
|
|
void TranEntry::text(ostream& os) const
|
|
{
|
|
//ScopedLock lock(mLock,__FILE__,__LINE__);
|
|
if (endTime != 0) return; // don't bother printing one that's complete through this interface
|
|
os << " TranEntry(";
|
|
os <<LOGVAR2("tid",tranID());
|
|
stateText(os);
|
|
if (! isStuckOrRemoved()) {
|
|
// Nothing else I am willing to risk saying about a removed transaction, for fear of trigging an exception.
|
|
if (channel()) os << " chan=(" << *channel() <<")";
|
|
else os << " chan=none";
|
|
os <<LOGVARM(mSubscriber);
|
|
os <<LOGVARM(mL3TI);
|
|
//if (mSIP) {
|
|
// os << " SIP-call-id=" << mSIP->callId();
|
|
// os << " SIP-proxy=" << mSIP->proxyIP() << ":" << mSIP->proxyPort();
|
|
// os << " SIPState=" << mSIP->sipState();
|
|
//}
|
|
os << LOGVARM(mService);
|
|
if (mCalled.digits()[0]) os << " to=" << mCalled.digits();
|
|
if (mCalling.digits()[0]) os << " from=" << mCalling.digits();
|
|
os << " stateAge=(" << (stateAge()+500)/1000 << " sec)";
|
|
if (currentProcedure()) {
|
|
os << " stack=(";
|
|
for (list<MachineBase*>::const_iterator it = mProcStack.begin(); it != mProcStack.end(); it++) {
|
|
(*it)->machText(os);
|
|
}
|
|
os << ")";
|
|
}
|
|
L3TimerList::text(os);
|
|
if (mMessage.size()) os << " message=\"" << mMessage << "\"";
|
|
}
|
|
os << ")";
|
|
}
|
|
|
|
string TranEntry::text() const
|
|
{
|
|
ostringstream os;
|
|
text(os);
|
|
return os.str();
|
|
}
|
|
|
|
ostream& operator<<(ostream& os, const TranEntry& entry)
|
|
{
|
|
entry.text(os);
|
|
return os;
|
|
}
|
|
|
|
ostream& operator<<(ostream& os, const TranEntry* entry)
|
|
{
|
|
if (entry == NULL) { os << "(null TranEntry)"; return os; }
|
|
entry->text(os);
|
|
return os;
|
|
}
|
|
|
|
//void TranEntry::message(const char *wMessage, size_t length)
|
|
//{
|
|
// /*if (length>520) {
|
|
// LOG(NOTICE) << "truncating long message: " << wMessage;
|
|
// length=520;
|
|
// }*/
|
|
// if (isRemoved()) throw RemovedTransaction(tranID());
|
|
// //ScopedLock lock(mLock,__FILE__,__LINE__);
|
|
// //memcpy(mMessage,wMessage,length);
|
|
// //mMessage[length]='\0';
|
|
// mMessage.assign(wMessage, length);
|
|
//}
|
|
//
|
|
//void TranEntry::messageType(const char *wContentType)
|
|
//{
|
|
// if (isRemoved()) throw RemovedTransaction(tranID());
|
|
// //ScopedLock lock(mLock,__FILE__,__LINE__);
|
|
// mContentType.assign(wContentType);
|
|
//}
|
|
|
|
|
|
|
|
#if EXTERNAL_TRANSACTION_TABLE
|
|
void TranEntry::runQuery(const char* query) const
|
|
{
|
|
// Caller should hold mLock and should have already checked isRemoved()..
|
|
for (unsigned i=0; i<mNumSQLTries; i++) {
|
|
if (sqlite3_command(gNewTransactionTable.getDB(),query)) return;
|
|
}
|
|
LOG(ALERT) << "transaction table access failed after " << mNumSQLTries << "attempts. query:" << query << " error: " << sqlite3_errmsg(gNewTransactionTable.getDB());
|
|
}
|
|
#endif
|
|
|
|
|
|
|
|
// (pat) David says: No more external reporting tables.
|
|
//void TranEntry::insertIntoDatabase()
|
|
//{
|
|
// if (mDialog == NULL) { return; } // TODO: We might want to see the LUR transactions, which also do not have an mSIP.
|
|
//
|
|
// // This should be called only from gNewTransactionTable::add.
|
|
// // Caller should hold mLock.
|
|
//
|
|
// ostringstream serviceTypeSS;
|
|
// serviceTypeSS << mService;
|
|
//
|
|
// ostringstream sipStateSS;
|
|
// mPrevSipState = mDialog->getSipState();
|
|
// sipStateSS << mPrevSipState;
|
|
//
|
|
// string subscriber = mSubscriber.fmidUsername();
|
|
//
|
|
// const char* stateString = CCState::callStateString(getGSMState());
|
|
// assert(stateString);
|
|
//
|
|
// // FIXME -- This should be done in a single SQL transaction.
|
|
//
|
|
// char query[500];
|
|
// unsigned now = (unsigned)time(NULL);
|
|
// sprintf(query,"INSERT INTO TRANSACTION_TABLE "
|
|
// "(ID,CREATED,CHANGED,TYPE,SUBSCRIBER,L3TI,CALLED,CALLING,GSMSTATE,SIPSTATE,SIP_CALLID,SIP_PROXY) "
|
|
// "VALUES (%u,%u, %u, '%s','%s', %u,'%s', '%s', '%s', '%s', '%s', '%s')",
|
|
// tranID(),now,now,
|
|
// serviceTypeSS.str().c_str(),
|
|
// subscriber.c_str(),
|
|
// mL3TI,
|
|
// mCalled.digits(),
|
|
// mCalling.digits(),
|
|
// stateString,
|
|
// sipStateSS.str().c_str(),
|
|
// mDialog->callId().c_str(),
|
|
// mDialog->proxyIP().c_str()
|
|
// );
|
|
//
|
|
// runQuery(query);
|
|
//
|
|
// if (!channel()) return;
|
|
// sprintf(query,"UPDATE TRANSACTION_TABLE SET CHANNEL='%s' WHERE ID=%u",
|
|
// channel()->descriptiveString(), tranID());
|
|
// runQuery(query);
|
|
//}
|
|
|
|
|
|
|
|
void TranEntry::setSubscriberImsi(string imsi, bool andAttach)
|
|
{
|
|
mSubscriber.mImsi = imsi;
|
|
// Now that we have an imsi we can hook up the MMUser.
|
|
if (andAttach) {
|
|
gMMLayer.mmAttachByImsi(channel(),imsi);
|
|
}
|
|
}
|
|
|
|
L3LogicalChannel* TranEntry::channel()
|
|
{
|
|
// Dont do this isRemoved test. We use the channel just for LOG messages. The ts will be NULL if the tran is removed.
|
|
//if (isRemoved()) throw RemovedTransaction(tranID());
|
|
MMContext *ts = teGetContext();
|
|
return ts ? ts->tsChannel() : NULL;
|
|
}
|
|
|
|
const L3LogicalChannel* TranEntry::channel() const
|
|
{
|
|
//if (isRemoved()) throw RemovedTransaction(tranID());
|
|
//MMContext *ts = const_cast<TranEntry*>(this)->teGetContext(); // gotta love it.
|
|
MMContext *ts = Unconst(this)->teGetContext(); // gotta love it.
|
|
return ts ? ts->tsChannel() : NULL;
|
|
}
|
|
|
|
//bool TranEntry::isChannelMatch(const L3LogicalChannel *lch)
|
|
//{
|
|
// // The void* compares pointers even if someone defines operator== on L3LogicalChannel.
|
|
// return ((void*)this->channel() == (void*)lch || (void*)this->getL2Channel()->SACCH() == (void*)lch ||
|
|
// (this->mNextChannel && ((void*)this->mNextChannel == (void*)lch || (void*)this->mNextChannel->getL2Channel()->SACCH() == (void*)lch)));
|
|
//}
|
|
|
|
L2LogicalChannel* TranEntry::getL2Channel() const
|
|
{
|
|
L3LogicalChannel *chan = Unconst(channel()); // what a pathetic language
|
|
return chan ? dynamic_cast<L2LogicalChannel*>(chan) : NULL;
|
|
}
|
|
|
|
|
|
// This is used after the channel() is changed from SDCCH to to TCHFACCH just to be safe.
|
|
L3LogicalChannel* TranEntry::getTCHFACCH() {
|
|
devassert(channel()->chtype()==FACCHType); // This is the type returned by the TCHFACCHLogicalChannel, even though it is TCH too.
|
|
return channel();
|
|
}
|
|
|
|
|
|
|
|
unsigned TranEntry::getL3TI() const
|
|
{
|
|
//if (isRemoved()) throw RemovedTransaction(tranID());
|
|
return mL3TI;
|
|
}
|
|
|
|
CallState TranEntryProtected::getGSMState() const
|
|
{
|
|
// Dont throw this for just asking about the GSMState; we do that even while the transaction is being removed,
|
|
// to print it, and etc.
|
|
// if (isRemoved()) throw RemovedTransaction(tranID());
|
|
//ScopedLock lock(mLock,__FILE__,__LINE__); // redundant
|
|
return mGSMState;
|
|
}
|
|
|
|
|
|
void TranEntryProtected::setGSMState(CallState wState)
|
|
{
|
|
//if (wState != CCState::NullState && isRemoved()) throw RemovedTransaction(tranID());
|
|
//ScopedLock lock(mLock,__FILE__,__LINE__);
|
|
mStateTimer.now();
|
|
|
|
mGSMState = wState;
|
|
}
|
|
|
|
SIP::SipState TranEntry::echoSipState(SIP::SipState state) const
|
|
{
|
|
// Caller should hold mLock.
|
|
if (mPrevSipState==state) return state;
|
|
mPrevSipState = state;
|
|
|
|
const char* stateString = SIP::SipStateString(state);
|
|
assert(stateString);
|
|
|
|
#if EXTERNAL_TRANSACTION_TABLE
|
|
unsigned now = time(NULL);
|
|
char query[150];
|
|
sprintf(query,
|
|
"UPDATE TRANSACTION_TABLE SET SIPSTATE='%s',CHANGED=%u WHERE ID=%u",
|
|
stateString,now,tranID());
|
|
runQuery(query);
|
|
#endif
|
|
|
|
return state;
|
|
}
|
|
|
|
|
|
|
|
void TranEntry::setCalled(const L3CalledPartyBCDNumber& wCalled)
|
|
{
|
|
//if (isRemoved()) throw RemovedTransaction(tranID());
|
|
//ScopedLock lock(mLock,__FILE__,__LINE__);
|
|
mCalled = wCalled;
|
|
|
|
#if EXTERNAL_TRANSACTION_TABLE
|
|
char query[151];
|
|
snprintf(query,150,
|
|
"UPDATE TRANSACTION_TABLE SET CALLED='%s' WHERE ID=%u",
|
|
mCalled.digits(), tranID());
|
|
runQuery(query);
|
|
#endif
|
|
}
|
|
|
|
|
|
// Does this ti reported by the MS match this transaction?
|
|
bool TranEntry::matchL3TI(unsigned ti, bool fromMS)
|
|
{
|
|
// Old incorrect way:
|
|
//return l3TISigBits(mL3TI) == l3TISigBits(ti);
|
|
if (fromMS) {
|
|
// If the ti argument came from the MS flip the TI flag.
|
|
if (ti & 0x8) { ti &= ~0x8; } else { ti |= 0x8; }
|
|
}
|
|
return mL3TI == ti;
|
|
}
|
|
|
|
|
|
void TranEntry::setL3TI(unsigned wL3TI)
|
|
{
|
|
//if (isRemoved()) throw RemovedTransaction(tranID());
|
|
//ScopedLock lock(mLock,__FILE__,__LINE__);
|
|
mL3TI = wL3TI;
|
|
|
|
#if EXTERNAL_TRANSACTION_TABLE
|
|
char query[151];
|
|
snprintf(query,150,
|
|
"UPDATE TRANSACTION_TABLE SET L3TI=%u WHERE ID=%u",
|
|
mL3TI, tranID());
|
|
runQuery(query);
|
|
#endif
|
|
}
|
|
|
|
|
|
bool TranEntry::terminationRequested()
|
|
{
|
|
ScopedLock lock(mAnotherLock,__FILE__,__LINE__);
|
|
//if (isRemoved()) throw RemovedTransaction(tranID());
|
|
bool retVal = mTerminationRequested;
|
|
mTerminationRequested = false;
|
|
return retVal;
|
|
}
|
|
|
|
|
|
// The handover is from BS1 to BS2.
|
|
// This is run in BS1 to create the handover string to send to BS2.
|
|
// The string must contain everything about the SIP side of the session.
|
|
// Everything needed to be known about the radio side of the session was transferred as an L3 HandoverCommand.
|
|
string TranEntry::handoverString(string peer) const
|
|
{
|
|
// This string is a set of key-value pairs.
|
|
// It needs to carry all of the information of the GSM Abis Handover Request message,
|
|
// as well as all of the information of the SIP REFER message.
|
|
// We call this as "BS1" in the handover ladder diagram.
|
|
// It is decoded at the other end by a TransactionEnty constructor.
|
|
|
|
//if (isRemoved()) throw RemovedTransaction(tranID());
|
|
//ScopedLock lock(mLock,__FILE__,__LINE__);
|
|
ostringstream os;
|
|
os << tranID();
|
|
os << " IMSI=" << mSubscriber.mImsi;
|
|
// We dont need these.
|
|
//HandoverEntry *handover = getHandoverEntry(true);
|
|
//if (getGSMState()==CCState::HandoverInbound) os << " inbound-ref=" << handover->mInboundReference;
|
|
//if (getGSMState()==CCState::HandoverOutbound) os << " outbound-ref=" << handover->mOutboundReference.value();
|
|
os << " L3TI=" << mL3TI;
|
|
if (mCalled.digits()[0]) os << " called=" << mCalled.digits();
|
|
if (mCalling.digits()[0]) os << " calling=" << mCalling.digits();
|
|
|
|
const SipBase *sip = Unconst(this)->getDialog();
|
|
os << " REFER=" << sip->dsHandoverMessage(peer);
|
|
|
|
// remote ip and port (pat) This is where we send the re-INVITE, but subsequent messages
|
|
// are sent to our proxy IP. This is wrong, but our SIP response routing for other messages is wrong too.
|
|
// RFC3261 section 4 page 16 describes routing as follows:
|
|
// 1. The INVITE is necessarily sent via proxies, which add their own "via" headers.
|
|
// 2. The reply to the INVITE must include the "via" headers so it can get back.
|
|
// 3. Subsequently, if there is a Contact field, all messages bypass the proxies and are sent directly to the Contact.
|
|
// 4. But the proxies might want to see the messages too, so they can add a "required-route" parameter which trumps
|
|
// the "contact" header and specifies that messages are sent there instead. This is called "Loose Routing." What a mess.
|
|
// And I quote: "These procedures separate the destination of the request (present in the Request-URI) from
|
|
// the set of proxies that need to be visited along the way (present in the Route header field)."
|
|
// In contrast, A Strict Router "follows the Route processing rules of RFC 2543 and many prior work in
|
|
// progress versions of this RFC. That rule caused proxies to destroy the contents of the Request-URI
|
|
// when a Route header field was present."
|
|
// 8.1.1.1: Normally the request-URI is equal to the To: field. But if there is a configured proxy (our case)
|
|
// this is called a "pre-existing route set" and we must follow 12.2.1.1 using the request-URI as the
|
|
// remote target URI(???)
|
|
// 12.2: The route-set is immutably defined by the initial INVITE. You can change the remote-URI in a re-INVITE
|
|
// (aka target-refresh-request) but not the route-set.
|
|
// Remote-URI: Intial request remote-URI must == To: field.
|
|
|
|
|
|
// Functional but unused. See comments in SIP::inboundHandoverSendINVITE()
|
|
//os << " RTPState=" <<
|
|
// sip->RTPSession()->rtp.snd_time_offset << "," <<
|
|
// sip->RTPSession()->rtp.snd_ts_offset << "," <<
|
|
// sip->RTPSession()->rtp.snd_rand_offset << "," <<
|
|
// sip->RTPSession()->rtp.snd_last_ts << "," <<
|
|
// sip->RTPSession()->rtp.rcv_time_offset << "," <<
|
|
// sip->RTPSession()->rtp.rcv_ts_offset << "," <<
|
|
// sip->RTPSession()->rtp.rcv_query_ts_offset << "," <<
|
|
// sip->RTPSession()->rtp.rcv_last_ts << "," <<
|
|
// sip->RTPSession()->rtp.rcv_last_app_ts << "," <<
|
|
// sip->RTPSession()->rtp.rcv_last_ret_ts << "," <<
|
|
// sip->RTPSession()->rtp.hwrcv_extseq << "," <<
|
|
// sip->RTPSession()->rtp.hwrcv_seq_at_last_SR << "," <<
|
|
// sip->RTPSession()->rtp.hwrcv_since_last_SR << "," <<
|
|
// sip->RTPSession()->rtp.last_rcv_SR_ts << "," <<
|
|
// sip->RTPSession()->rtp.last_rcv_SR_time.tv_sec << "," << sip->RTPSession()->rtp.last_rcv_SR_time.tv_usec << "," <<
|
|
// sip->RTPSession()->rtp.snd_seq << "," <<
|
|
// sip->RTPSession()->rtp.last_rtcp_report_snt_r << "," <<
|
|
// sip->RTPSession()->rtp.last_rtcp_report_snt_s << "," <<
|
|
// sip->RTPSession()->rtp.rtcp_report_snt_interval << "," <<
|
|
// sip->RTPSession()->rtp.last_rtcp_packet_count << "," <<
|
|
// sip->RTPSession()->rtp.sent_payload_bytes;
|
|
|
|
return os.str();
|
|
}
|
|
|
|
void NewTransactionTable::ttInit()
|
|
{
|
|
//if (! l3rewrite()) return; // Only one of TransactionTable::init or NewTransactionTable::ttInit
|
|
LOG(DEBUG);
|
|
// This assumes the main application uses sdevrandom.
|
|
//mIDCounter = random();
|
|
mIDCounter = 100; // pat changed. 0 is reserved. Start it high enough so it cannot possibly be confused with an L3TI.
|
|
|
|
}
|
|
|
|
|
|
|
|
unsigned NewTransactionTable::ttNewID()
|
|
{
|
|
unsigned iCntr;
|
|
rwLock.wlock();
|
|
iCntr = mIDCounter++;
|
|
rwLock.unlock();
|
|
return iCntr;
|
|
}
|
|
|
|
|
|
void NewTransactionTable::ttAdd(TranEntry* value)
|
|
{
|
|
LOG(DEBUG);
|
|
LOG(INFO) << "new transaction " << *value;
|
|
rwLock.wlock();
|
|
//value->vGetRef();
|
|
value->incRefCnt();
|
|
mTable[value->tranID()]=value;
|
|
rwLock.unlock();
|
|
//DIG: Debug Start
|
|
if (0) {
|
|
printf("^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n");
|
|
printf("^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n");
|
|
printf(" transaction ttAdd\n");
|
|
printf("NewTransactionTable::ttAdd(%p) called, call stack follows:\n", value);
|
|
const int elements = 100;
|
|
void *buffer[elements];
|
|
int nptrs = backtrace(buffer, elements);
|
|
char **strings = backtrace_symbols(buffer, nptrs);
|
|
if (strings == NULL) { perror("backtrace_symbols"); }
|
|
else
|
|
{
|
|
for (int j = 0; j < nptrs; j++) printf("%s\n", strings[j]);
|
|
free(strings);
|
|
}
|
|
printf("^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n");
|
|
printf("^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n");
|
|
}
|
|
//DIG: Debug End
|
|
}
|
|
|
|
|
|
|
|
TranEntry* NewTransactionTable::ttFindById(TranEntryId key)
|
|
{
|
|
// Since this is a log-time operation, we don't screw that up by calling clearDeadEntries.
|
|
|
|
// ID==0 is a non-valid special case.
|
|
LOG(DEBUG) << "by key: " << key;
|
|
assert(key);
|
|
|
|
TranEntry* poEntry = NULL;
|
|
rwLock.rlock();
|
|
NewTransactionMap::iterator itr = mTable.find(key);
|
|
if (itr!=mTable.end())
|
|
if (!itr->second->deadOrRemoved())
|
|
poEntry = itr->second;
|
|
rwLock.unlock();
|
|
return poEntry;
|
|
}
|
|
|
|
|
|
// In l3-rewrite this is called ONLY from teRemove.
|
|
// mark the element as done
|
|
bool NewTransactionTable::ttRemove(TranEntryId key)
|
|
{
|
|
LOG(DEBUG) <<LOGVAR(key);
|
|
// ID==0 is a non-valid special case, and it shouldn't be passed here.
|
|
if (key==0) {
|
|
LOG(ERR) << "called with key==0";
|
|
return false;
|
|
}
|
|
//DIG: Debug Start
|
|
if (0) {
|
|
printf("vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv\n");
|
|
printf("vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv\n");
|
|
printf(" transaction ttRemove\n");
|
|
printf("NewTransactionTable::~ttRemove(%d) called, call stack follows:\n", key);
|
|
const int elements = 100;
|
|
void *buffer[elements];
|
|
int nptrs = backtrace(buffer, elements);
|
|
char **strings = backtrace_symbols(buffer, nptrs);
|
|
if (strings == NULL) { perror("backtrace_symbols"); }
|
|
else
|
|
{
|
|
for (int j = 0; j < nptrs; j++) printf("%s\n", strings[j]);
|
|
free(strings);
|
|
}
|
|
printf("vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv\n");
|
|
printf("vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv\n");
|
|
}
|
|
//DIG: Debug End
|
|
|
|
bool bRet = true;
|
|
rwLock.wlock();
|
|
NewTransactionMap::iterator itr = mTable.find(key);
|
|
if (itr==mTable.end()) bRet = false;
|
|
else
|
|
{
|
|
StaleTranEntry *poEntry = new StaleTranEntry(*(itr->second)); // copy constructor
|
|
poEntry->setEndTime(time(NULL));
|
|
gStaleTransactionTable.ttAdd(poEntry);
|
|
mTable.erase(itr); // erase the original
|
|
}
|
|
rwLock.unlock();
|
|
return bRet;
|
|
}
|
|
|
|
void NewTransactionTable::ttErase(NewTransactionMap::iterator itr)
|
|
{
|
|
//DIG: Debug Start
|
|
if (0) {
|
|
printf("vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv\n");
|
|
printf("vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv\n");
|
|
printf(" transaction ttErase\n");
|
|
printf("NewTransactionTable::ttErase(%p) called, call stack follows:\n", itr->second);
|
|
const int elements = 100;
|
|
void *buffer[elements];
|
|
int nptrs = backtrace(buffer, elements);
|
|
char **strings = backtrace_symbols(buffer, nptrs);
|
|
if (strings == NULL) { perror("backtrace_symbols"); }
|
|
else
|
|
{
|
|
for (int j = 0; j < nptrs; j++) printf("%s\n", strings[j]);
|
|
free(strings);
|
|
}
|
|
printf("vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv\n");
|
|
printf("vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv\n");
|
|
}
|
|
//DIG: Debug End
|
|
|
|
// we are already locked
|
|
mTable.erase(itr); // erase the original
|
|
}
|
|
|
|
// Return true if we found it, or false if not found.
|
|
// This is called from a separate thread, so we set the flag and wait for the service loop to handle it.
|
|
bool NewTransactionTable::ttTerminate(TranEntryId tid)
|
|
{
|
|
bool bRet = true;
|
|
rwLock.rlock();
|
|
NewTransactionMap::iterator itr = mTable.find(tid);
|
|
if (itr==mTable.end()) bRet = false;
|
|
else
|
|
{
|
|
TranEntry *tran = itr->second;
|
|
ScopedLock lock2(tran->mAnotherLock,__FILE__,__LINE__);
|
|
tran->mTerminationRequested = true;
|
|
}
|
|
rwLock.unlock();
|
|
return bRet;
|
|
}
|
|
|
|
// Does the TranEntry referenced by this id still pointer to its SipDialog?
|
|
// We use the TranEntryId so we can delete the TranEntry completely separately from the SipDialog.
|
|
// However, the TranEntry has a pointer to the SipDialog, so we dont delete that until its gone.
|
|
bool NewTransactionTable::ttIsDialogReleased(TranEntryId tid)
|
|
{
|
|
bool bRet = true;
|
|
rwLock.rlock();
|
|
NewTransactionMap::iterator itr = mTable.find(tid);
|
|
if (itr==mTable.end()) bRet = false;
|
|
else bRet = (itr->second->mDialog == 0);
|
|
rwLock.unlock();
|
|
return bRet;
|
|
}
|
|
|
|
// This is only used as a bug work around for the buggy smqueue.
|
|
bool NewTransactionTable::ttSetDialog(TranEntryId tid, SipDialog *dialog)
|
|
{
|
|
bool bRet = true;
|
|
rwLock.rlock();
|
|
NewTransactionMap::iterator itr = mTable.find(tid);
|
|
if (itr==mTable.end()) bRet = false;
|
|
else itr->second->setDialog(dialog);
|
|
rwLock.unlock();
|
|
return bRet;
|
|
}
|
|
|
|
//void NewTransactionTable::clearDeadEntries()
|
|
//{
|
|
// We just cant do this any more because there are pointers to TranEntry in the MMContext or MMUser
|
|
// If we want this functionality it has to be in the MMContext and MMUser.
|
|
//}
|
|
|
|
|
|
//TranEntry* NewTransactionTable::ttFindByLCH(const L3LogicalChannel *chan)
|
|
//{
|
|
// //LOG(DEBUG) << "by channel: " << *chan << " (" << chan << ")";
|
|
//
|
|
// ScopedLock lock(mttLock,__FILE__,__LINE__);
|
|
//
|
|
// // Yes, it's linear time.
|
|
// // Since clearDeadEntries is also linear, do that here, too.
|
|
// clearDeadEntries();
|
|
//
|
|
// // Brute force search.
|
|
// // This search assumes in order by transaction ID.
|
|
// for (NewTransactionMap::iterator itr = mTable.begin(); itr!=mTable.end(); ++itr) {
|
|
// TranEntry *tran = itr->second;
|
|
// if (tran->deadOrRemoved()) continue;
|
|
// if ((void*)tran->channel() == (void*)chan || (void*)tran->mNextChannel == (void*)chan) return tran;
|
|
// }
|
|
// LOG(DEBUG) << "no match for " << *chan << " (" << chan << ")";
|
|
// return NULL; // not found
|
|
//}
|
|
|
|
// Release anything associated with this channel.
|
|
//void NewTransactionTable::ttLostChannel(const L3LogicalChannel *chan)
|
|
//{
|
|
// ScopedLock lock(mttLock,__FILE__,__LINE__);
|
|
//
|
|
// // Yes, it's linear time.
|
|
// // Since clearDeadEntries is also linear, do that here, too.
|
|
// clearDeadEntries();
|
|
//
|
|
// // Brute force search.
|
|
// // This search assumes in order by transaction ID.
|
|
// for (NewTransactionMap::iterator itr = mTable.begin(); itr!=mTable.end(); ++itr) {
|
|
// TranEntry *tran = itr->second;
|
|
// if (tran->deadOrRemoved()) continue;
|
|
// if (tran->isChannelMatch(chan)) {
|
|
// //tran->terminate();
|
|
// tran->teRemove();
|
|
// }
|
|
// }
|
|
// //LOG(DEBUG) << "no match for " << *chan << " (" << chan << ")";
|
|
//}
|
|
|
|
|
|
//TranEntry* NewTransactionTable::ttFindBySACCH(const GSM::SACCHLogicalChannel *chan)
|
|
//{
|
|
// LOG(DEBUG) << "by SACCH: " << *chan << " (" << chan << ")";
|
|
//
|
|
// ScopedLock lock(mttLock,__FILE__,__LINE__);
|
|
//
|
|
// // Yes, it's linear time.
|
|
// // Since clearDeadEntries is also linear, do that here, too.
|
|
// clearDeadEntries();
|
|
//
|
|
// // Brute force search.
|
|
// TranEntry *retVal = NULL;
|
|
// for (NewTransactionMap::iterator itr = mTable.begin(); itr!=mTable.end(); ++itr) {
|
|
// if (itr->second->deadOrRemoved()) continue;
|
|
// const GSM::L2LogicalChannel* thisChan = itr->second->getL2Channel();
|
|
// if (thisChan->SACCH() != chan) continue;
|
|
// retVal = itr->second;
|
|
// }
|
|
// return retVal;
|
|
//}
|
|
|
|
|
|
#if UNUSED
|
|
TranEntry* NewTransactionTable::ttFindByTypeAndOffset(GSM::TypeAndOffset desc)
|
|
{
|
|
LOG(DEBUG) << "by type and offset: " << desc;
|
|
|
|
ScopedLock lock(mttLock,__FILE__,__LINE__);
|
|
|
|
// Yes, it's linear time.
|
|
// Since clearDeadEntries is also linear, do that here, too.
|
|
clearDeadEntries();
|
|
|
|
// Brute force search.
|
|
for (NewTransactionMap::iterator itr = mTable.begin(); itr!=mTable.end(); ++itr) {
|
|
if (itr->second->deadOrRemoved()) continue;
|
|
const L3LogicalChannel* thisChan = itr->second->channel();
|
|
if (thisChan->typeAndOffset()!=desc) continue;
|
|
return itr->second;
|
|
}
|
|
//LOG(DEBUG) << "no match for " << *chan << " (" << chan << ")";
|
|
return NULL;
|
|
}
|
|
#endif
|
|
|
|
|
|
#if UNUSED
|
|
TranEntry* NewTransactionTable::ttFindByMobileIDState(const L3MobileIdentity& mobileID, CallState state)
|
|
{
|
|
LOG(DEBUG) << "by ID and state: " << mobileID << " in " << state;
|
|
|
|
ScopedLock lock(mttLock,__FILE__,__LINE__);
|
|
|
|
// Yes, it's linear time.
|
|
// Since clearDeadEntries is also linear, do that here, too.
|
|
clearDeadEntries();
|
|
|
|
// Brute force search.
|
|
for (NewTransactionMap::iterator itr = mTable.begin(); itr!=mTable.end(); ++itr) {
|
|
if (itr->second->deadOrRemoved()) continue;
|
|
if (itr->second->getGSMState() != state) continue;
|
|
if (itr->second->subscriber() != mobileID) continue;
|
|
return itr->second;
|
|
}
|
|
return NULL;
|
|
}
|
|
#endif
|
|
|
|
#if UNUSED
|
|
bool NewTransactionTable::isBusy(const L3MobileIdentity& mobileID)
|
|
{
|
|
LOG(DEBUG) << "id: " << mobileID << "?";
|
|
|
|
ScopedLock lock(mttLock,__FILE__,__LINE__);
|
|
|
|
// Yes, it's linear time.
|
|
// Since clearDeadEntries is also linear, do that here, too.
|
|
clearDeadEntries();
|
|
|
|
// Brute force search.
|
|
for (NewTransactionMap::iterator itr = mTable.begin(); itr!=mTable.end(); ++itr) {
|
|
if (itr->second->deadOrRemoved()) continue;
|
|
if (itr->second->subscriber() != mobileID) continue;
|
|
GSM::L3CMServiceType::TypeCode service = itr->second->servicetype();
|
|
bool speech =
|
|
service==GSM::L3CMServiceType::MobileOriginatedCall ||
|
|
service==GSM::L3CMServiceType::MobileTerminatedCall;
|
|
if (!speech) continue;
|
|
// OK, so we found a transaction for this call.
|
|
bool inCall = CCState::isInCall(itr->second->getGSMState());
|
|
if (inCall) return true;
|
|
}
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
#if UNUSED
|
|
// Find the TranEntry that wants to receive this l3msg, if any.
|
|
// Look at the PD and the TI.
|
|
// TODO: Fix this. When we start a MOC there is no TI yet so the Setup message TI will not match the TranEntry.
|
|
// If no TranEntry matches the TI, we should call, um, we cant just call the default TranEntry
|
|
// because the message may be for an old dead TranEntry. Maybe should just special-case Setup.
|
|
// Maybe the TranEntry should expectCC(Setup).
|
|
// Another way to fix might be to add a default TranEntry for each L3PD, but again that would get dead TIs.
|
|
// What we really want is a separate MM manager to route the messages.
|
|
TranEntry *NewTransactionTable::ttFindByL3Msg(GSM::L3Message *l3msg, L3LogicalChannel *lch)
|
|
{
|
|
GSM::L3PD pd = l3msg->PD();
|
|
ScopedLock lock(gNewTransactionTable.mttLock,__FILE__,__LINE__);
|
|
for (NewTransactionMap::iterator itr = gNewTransactionTable.mTable.begin(); itr!=gNewTransactionTable.mTable.end(); ++itr) {
|
|
TranEntry *tran = itr->second;
|
|
if (tran->deadOrRemoved()) continue;
|
|
if (! tran->isChannelMatch(lch)) continue;
|
|
GSM::L3CMServiceType service = tran->service();
|
|
switch (pd) {
|
|
case L3CallControlPD:
|
|
return tran; // Only one for now.
|
|
//if (service.isCC() && tran->getL3TI() == dynamic_cast<L3CCMessage*>(l3msg)->TI()) { return tran; }
|
|
continue;
|
|
case L3SMSPD:
|
|
return tran; // Only one for now.
|
|
//if (service.isSMS() && tran->getL3TI() == dynamic_cast<L3CPMessage*>(l3msg)->TI()) { return tran; }
|
|
continue;
|
|
case L3MobilityManagementPD:
|
|
case L3RadioResourcePD:
|
|
// We dont yet have a separate MobilityManagement layer, so MM and RR messages are handled by the primary TranEntry,
|
|
// which is either the LocationUpdateRequest or the in-progress CC TranEntry, which needs RR messages
|
|
// to modify the channel for the voice call.
|
|
if (service.isMM()) { return tran; }
|
|
if (service.isSMS()) continue;
|
|
// For now, just assume there is only one transaction, so this must be it.
|
|
return tran;
|
|
default:
|
|
LOG(ERR) << "unrecognized L3"<<LOGVAR(pd);
|
|
return NULL; // hopeless.
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
#endif
|
|
|
|
|
|
#if UNUSED
|
|
// (pat added) By design, there is no back pointer from SIP to the TranEntry, so the TranEntry can be deleted
|
|
// independently, so we have to search for the Transaction if we need it.
|
|
TranEntry* NewTransactionTable::ttFindByDialog(SIP::SipDialog *pDialog)
|
|
{
|
|
// Brute force search.
|
|
ScopedLock lock(mttLock,__FILE__,__LINE__);
|
|
for (NewTransactionMap::iterator itr = mTable.begin(); itr!=mTable.end(); ++itr) {
|
|
TranEntry *tran = itr->second;
|
|
if (tran->deadOrRemoved()) continue;
|
|
if (tran->getDialog() == pDialog) return tran;
|
|
}
|
|
return NULL;
|
|
}
|
|
#endif
|
|
|
|
// (pat added) Add a message to the TranEntry inbox.
|
|
void NewTransactionTable::ttAddMessage(TranEntryId tranid,SIP::DialogMessage *dmsg)
|
|
{
|
|
rwLock.rlock();
|
|
TranEntry* tran = ttFindById(tranid);
|
|
if (tran) {
|
|
tran->mTranInbox.write(dmsg);
|
|
rwLock.unlock();
|
|
} else {
|
|
rwLock.unlock(); // don't do the log or delete while locked - reduce the timing window
|
|
|
|
// This is ok - the SIP dialog and L3 transaction side are completely decoupled so it is quite
|
|
// possible that the transaction was deleted (for example, MS signal failure) while
|
|
// a SIP dialog is still running.
|
|
LOG(DEBUG) << "info: SIP Dialog message to non-existent"<<LOGVAR(tranid);
|
|
delete dmsg;
|
|
}
|
|
|
|
}
|
|
|
|
// This is an external interface so we dont have to include L3TranEntry.h just to access this function.
|
|
void NewTransactionTable_ttAddMessage(TranEntryId tranid,SIP::DialogMessage *dmsg)
|
|
{
|
|
gNewTransactionTable.ttAddMessage(tranid,dmsg);
|
|
}
|
|
|
|
|
|
//TranEntry* NewTransactionTable::ttFindBySIPCallId(const L3MobileIdentity& mobileID, const char* callID)
|
|
//{
|
|
// assert(callID);
|
|
// LOG(DEBUG) << "by ID and call-ID: " << mobileID << ", call " << callID;
|
|
//
|
|
// string callIDString = string(callID);
|
|
// // Yes, it's linear time.
|
|
// // Since clearDeadEntries is also linear, do that here, too.
|
|
// ScopedLock lock(mttLock,__FILE__,__LINE__);
|
|
// clearDeadEntries();
|
|
//
|
|
// // Brute force search.
|
|
// for (NewTransactionMap::iterator itr = mTable.begin(); itr!=mTable.end(); ++itr) {
|
|
// TranEntry *tran = itr->second;
|
|
// if (tran->deadOrRemoved()) continue;
|
|
// if (tran->mDialog->callID() != callIDString) continue;
|
|
// if (tran->subscriber() != mobileID) continue;
|
|
// return itr->second;
|
|
// }
|
|
// return NULL;
|
|
//}
|
|
|
|
|
|
TranEntry* NewTransactionTable::ttFindHandoverOther(const L3MobileIdentity& mobileID, unsigned otherBS1TranId)
|
|
{
|
|
LOG(DEBUG) <<LOGVAR2("ID",mobileID) <<LOGVAR(otherBS1TranId);
|
|
|
|
// Yes, it's linear time.
|
|
// Since clearDeadEntries is also linear, do that here, too.
|
|
//clearDeadEntries();
|
|
|
|
// Brute force search.
|
|
rwLock.rlock();
|
|
for (NewTransactionMap::iterator itr = mTable.begin(); itr!=mTable.end(); ++itr) {
|
|
TranEntry *tran = itr->second;
|
|
|
|
// PERFORMANCE NOTE: Should not do logging in a lock
|
|
LOG(DEBUG) << "comparing "<<tran<<LOGVAR2("HandoverOtherBSTransactionID",(tran->mHandover?tran->mHandover->mHandoverOtherBSTransactionID:-1));
|
|
if (tran->deadOrRemoved()) continue;
|
|
if (!tran->mHandover) {
|
|
LOG(DEBUG) "no match, no handover"<<tran;
|
|
continue;
|
|
}
|
|
if (tran->mHandover->mHandoverOtherBSTransactionID != otherBS1TranId) {
|
|
LOG(DEBUG) "no match "<<tran->mHandover->mHandoverOtherBSTransactionID<<"!="<<otherBS1TranId;
|
|
continue;
|
|
}
|
|
if (! tran->subscriber().fmidMatch(mobileID)) {
|
|
LOG(DEBUG) "no match"<<LOGVAR(tran->subscriber()) <<LOGVAR(mobileID);
|
|
continue;
|
|
}
|
|
rwLock.unlock();
|
|
return tran;
|
|
}
|
|
rwLock.unlock();
|
|
return NULL;
|
|
}
|
|
|
|
|
|
|
|
|
|
size_t NewTransactionTable::dump(ostream& os, bool showAll) const
|
|
{
|
|
rwLock.rlock();
|
|
size_t sz = 0;
|
|
for (NewTransactionMap::const_iterator itr = mTable.begin(); itr!=mTable.end(); ++itr) {
|
|
if ((!showAll) && itr->second->deadOrRemoved()) continue;
|
|
sz++;
|
|
os << *(itr->second) << endl;
|
|
}
|
|
rwLock.unlock();
|
|
return sz;
|
|
}
|
|
|
|
size_t NewTransactionTable::dumpTable(ostream& os) const
|
|
{
|
|
rwLock.rlock();
|
|
size_t sz = 0;
|
|
for (NewTransactionMap::const_iterator itr = mTable.begin(); itr!=mTable.end(); ++itr) {
|
|
sz++;
|
|
itr->second->textTable(os);
|
|
}
|
|
rwLock.unlock();
|
|
return sz;
|
|
}
|
|
|
|
|
|
TranEntryId NewTransactionTable::findLongestCall()
|
|
{
|
|
rwLock.rlock();
|
|
//clearDeadEntries();
|
|
long longTime = 0;
|
|
TranEntryId iRet = 0;
|
|
NewTransactionMap::iterator longCall = mTable.end();
|
|
for (NewTransactionMap::iterator itr = mTable.begin(); itr!=mTable.end(); ++itr) {
|
|
if (itr->second->deadOrRemoved()) continue;
|
|
if (!(itr->second->channel())) continue;
|
|
if (itr->second->getGSMState() != CCState::Active) continue;
|
|
long runTime = itr->second->stateAge();
|
|
if (runTime > longTime) {
|
|
runTime = longTime;
|
|
longCall = itr;
|
|
}
|
|
}
|
|
if (longCall == mTable.end()) iRet = 0;
|
|
else iRet = longCall->second->tranID();
|
|
rwLock.unlock();
|
|
return iRet;
|
|
}
|
|
|
|
/**
|
|
Return an even UDP port number for the RTP even/odd pair.
|
|
*/
|
|
unsigned allocateRTPPorts()
|
|
{
|
|
const unsigned base = gConfig.getNum("RTP.Start");
|
|
const unsigned range = gConfig.getNum("RTP.Range");
|
|
const unsigned top = base+range;
|
|
static Mutex lock;
|
|
// Pick a random starting point. (pat) Why? Because there is a bug and we are trying to avoid it?
|
|
static unsigned port = base + 2*(random()%(range/2));
|
|
unsigned retVal;
|
|
lock.lock();
|
|
//This is a little hacky as RTPAvail is O(n)
|
|
do {
|
|
retVal = port;
|
|
port += 2;
|
|
if (port>=top) port=base;
|
|
} while (!gNewTransactionTable.RTPAvailable(retVal));
|
|
lock.unlock();
|
|
return retVal;
|
|
}
|
|
|
|
/* linear, we should move the actual search into this structure */
|
|
// (pat) Speed entirely irrelevant; this is done once per call.
|
|
bool NewTransactionTable::RTPAvailable(short rtpPort)
|
|
{
|
|
rwLock.rlock();
|
|
//clearDeadEntries();
|
|
bool avail = true;
|
|
for (NewTransactionMap::iterator itr = mTable.begin(); itr!=mTable.end(); ++itr) {
|
|
if (itr->second->deadOrRemoved()) continue;
|
|
if (itr->second->getRTPPort() == rtpPort){
|
|
avail = false;
|
|
break;
|
|
}
|
|
}
|
|
rwLock.unlock();
|
|
return avail;
|
|
}
|
|
|
|
void StaleTransactionTable::ttAdd(StaleTranEntry* value)
|
|
{
|
|
rwLock.wlock();
|
|
if ((int)mTable.size() >= gConfig.getNum("Control.Reporting.TransactionMaxCompletedRecords")) {
|
|
mTable.erase(mTable.begin());
|
|
}
|
|
mTable[value->tranID()]=value;
|
|
rwLock.unlock();
|
|
}
|
|
|
|
void StaleTransactionTable::ttErase(StaleTransactionMap::iterator itr)
|
|
{
|
|
// we are already locked
|
|
mTable.erase(itr); // erase the original
|
|
}
|
|
|
|
size_t StaleTransactionTable::dumpTable(ostream& os) const
|
|
{
|
|
rwLock.rlock();
|
|
size_t sz = 0;
|
|
for (StaleTransactionMap::const_iterator itr = mTable.begin(); itr!=mTable.end(); ++itr) {
|
|
sz++;
|
|
itr->second->textTable(os);
|
|
}
|
|
rwLock.unlock();
|
|
return sz;
|
|
}
|
|
|
|
void StaleTransactionTable::clearTable()
|
|
{
|
|
rwLock.wlock();
|
|
mTable.clear();
|
|
rwLock.unlock();
|
|
}
|
|
|
|
MachineBase *TranEntry::tePopMachine()
|
|
{
|
|
if (mProcStack.size() == 0) { return NULL; }
|
|
MachineBase *top = mProcStack.back();
|
|
mProcStack.pop_back();
|
|
return top;
|
|
}
|
|
|
|
void TranEntry::tePushProcedure(MachineBase *it)
|
|
{
|
|
mProcStack.push_back(it);
|
|
}
|
|
|
|
|
|
// Replace the current procedure with that specified.
|
|
// We dont delete the current procedure when switching between sub-procedures of an over-all procedure, for example,
|
|
// when switching from LUIdentication to LUAuthentication with L3ProcedureLocationUpdate.
|
|
// Update: the above case does not exist any more.
|
|
void TranEntry::teSetProcedure(MachineBase *wProc, bool wDeleteCurrent)
|
|
{
|
|
if (currentProcedure() == wProc) { return; }
|
|
MachineBase *old = tePopMachine();
|
|
wDeleteCurrent = true; // 9-24-2013: We always delete except when pusing into a procedure and that is handled by tePushProcedure.
|
|
if (wDeleteCurrent && old) { delete old; }
|
|
tePushProcedure(wProc);
|
|
}
|
|
|
|
// Note: handleRecursion returns a MachineStatus and is meant to be used when within a state machine.
|
|
// handleMachineStatus returns a bool and is the final status-handler called when all state machines have processed the current state to completion.
|
|
MachineStatus TranEntry::handleRecursion(MachineStatus status)
|
|
{
|
|
while (status == MachineStatusPopMachine) { // return to previous procedure on stack
|
|
// Special case: we do not return, we immediately invoke the popped-to method.
|
|
delete tran()->tePopMachine();
|
|
LOG(DEBUG) "popped to "<<currentProcedure()->debugName()<<" at state "<<currentProcedure()->mPopState;
|
|
status = currentProcedure()->machineRunState(currentProcedure()->mPopState);
|
|
}
|
|
return status;
|
|
}
|
|
|
|
// Return TRUE if the status indicates the message or whatever had a message handler, regardless of the success/fail result.
|
|
bool TranEntry::handleMachineStatus(MachineStatus status)
|
|
{
|
|
//MMContext *set = teGetContext();
|
|
OBJLOG(DEBUG) <<LOGVAR(status.mCode);
|
|
status = handleRecursion(status); // Harmless overkill if called again.
|
|
|
|
switch (status.mCode) {
|
|
case MachineStatus::MachineCodePopMachine: // aka MachineStatusPopMachine
|
|
assert(0); // We just checked this case above.
|
|
case MachineStatus::MachineCodeOK:
|
|
// continue the procedure, meaning return to L3 message handler and wait for the next message.
|
|
return true;
|
|
case MachineStatus::MachineCodeQuitChannel: // aka MachineStatusQuitChannel
|
|
// Drop the channel.
|
|
// Normally the user called closeChannel which does the actual work, but we will make sure:
|
|
// Just in case we get here without closeChannel having been
|
|
if (! channel()->isReleased()) {
|
|
// If the caller did not already call this, we dont know what the heck happened, so do a RELEASE instead of HARDRELEASE.
|
|
channel()->chanRelease(RELEASE);
|
|
}
|
|
return true;
|
|
case MachineStatus::MachineCodeQuitTran: // aka MachineStatusQuitTran
|
|
// Pop all procedures from stack and remove the transaction. Procedure already sent messages.
|
|
// This is the normal exit from a completed procedure.
|
|
teRemove(CancelCauseUnknown); // Danger will robinson!!!! Deletes the Transaction we are running.
|
|
return true;
|
|
case MachineStatus::MachineCodeUnexpectedState: // aka MachineStatusUnexpectedState
|
|
return false; // The message or state was unrecognized by this state machine.
|
|
//default:
|
|
//return true; // All others; Message was handled by the current Procedure.
|
|
}
|
|
|
|
#if 0
|
|
switch (status) {
|
|
case MachineStatusUnexpectedState: // Invalid procRun argument; very unlikely internal error.
|
|
LOG(ERR) << "unexpected state";
|
|
return false; // unhandled. Should we keep going anyway? probably not.
|
|
case MachineStatusUnexpectedMessage: // error message printed by caller.
|
|
LOG(ERR) << "unsupported message";
|
|
return false; // unhandled but keep going.
|
|
case MachineStatusQuit:
|
|
while (currentProcedure()) {
|
|
delete tran()->tePopMachine();
|
|
}
|
|
teClose(); // Danger will robinson!!!!
|
|
return true;
|
|
case MachineStatusUnexpectedPrimitive:
|
|
LOG(ERR) << "unexpected primitive"; // Dont think this MachineStatus is used anywhere.
|
|
return false;
|
|
default:
|
|
return true; // All others; Message was handled by the current Procedure.
|
|
}
|
|
#endif
|
|
return true; // unnecessary but makes gcc happy.
|
|
}
|
|
|
|
|
|
// The 'lockAnd...' methods are used to initially start or restart a Procedure.
|
|
// Update: This locking is no longer needed or relevant.
|
|
// When jumping between procedures we dont use these, although it would not matter since the locks can be recursive.
|
|
// Start a procedure by calling stateStart:
|
|
// If no proc is specified here, assume that teSetProcedure was called previously and start the currentProcedure.
|
|
bool TranEntry::lockAndStart(MachineBase *wProc)
|
|
{
|
|
ScopedLock lock(mL3RewriteLock,__FILE__,__LINE__);
|
|
if (wProc) {
|
|
teSetProcedure(wProc,false);
|
|
assert(wProc == currentProcedure());
|
|
} else {
|
|
wProc = currentProcedure();
|
|
assert(wProc); // Someone set the currentProcedure before calling this method.
|
|
}
|
|
return handleMachineStatus(wProc->callMachStart(wProc));
|
|
}
|
|
|
|
|
|
// Start a procedure by passing it this L3 message:
|
|
bool TranEntry::lockAndStart(MachineBase *wProc, GSM::L3Message *l3msg)
|
|
{
|
|
ScopedLock lock(mL3RewriteLock,__FILE__,__LINE__);
|
|
teSetProcedure(wProc,false);
|
|
assert(wProc == currentProcedure());
|
|
return handleMachineStatus(wProc->dispatchL3Msg(l3msg));
|
|
}
|
|
|
|
|
|
// l3msg may be NULL for primitives or unparseable messages.
|
|
bool TranEntry::lockAndInvokeFrame(const L3Frame *frame, const L3Message *l3msg)
|
|
{
|
|
LOG(DEBUG) << l3msg;
|
|
ScopedLock lock(mL3RewriteLock,__FILE__,__LINE__);
|
|
if (MachineBase *proc = currentProcedure()) {
|
|
LOG(DEBUG) <<"sending frame to"<<LOGVAR(proc) <<LOGVAR(frame);
|
|
return handleMachineStatus(proc->dispatchFrame(frame,l3msg));
|
|
}
|
|
LOG(ERR) <<"Received message for transaction with no state machine. "<<this<<*frame; // Should never happen.
|
|
return false;
|
|
}
|
|
|
|
// Return true if the message had a handler.
|
|
// Caller responsible for deleting the sipmsg.
|
|
bool TranEntry::lockAndInvokeSipMsg(const SIP::DialogMessage *sipmsg)
|
|
{
|
|
ScopedLock lock(mL3RewriteLock,__FILE__,__LINE__);
|
|
#if ORIGINAL
|
|
int state = mCurrentProcedure->mSipHandlerState;
|
|
if (state >= 0) { return teProcInvoke(state,NULL,sipmsg); }
|
|
return MachineStatusUnexpectedMessage;
|
|
#endif
|
|
if (MachineBase *proc = currentProcedure()) {
|
|
return handleMachineStatus(proc->dispatchSipDialogMsg(sipmsg));
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool TranEntry::lockAndInvokeSipMsgs()
|
|
{
|
|
// SIP Message processing is blocked during the AssignTCHF procedure.
|
|
// Now that is handled by checking for sip state changes when the AssignTCHF procedure is finished.
|
|
//if (mSipDialogMessagesBlocked) { return false; }
|
|
if (DialogMessage*dmsg = this->mTranInbox.readNoBlock()) {
|
|
lockAndInvokeSipMsg(dmsg);
|
|
delete dmsg;
|
|
// Since the message can result in the transaction being killed, only process one message
|
|
// then we return to let the caller invoke us again if the transaction is still active.
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool TranEntry::lockAndInvokeTimeout(L3Timer *timer)
|
|
{
|
|
LOG(DEBUG) << LOGVAR2("timer",timer->tName()) << this;
|
|
ScopedLock lock(mL3RewriteLock,__FILE__,__LINE__);
|
|
if (MachineBase *proc = currentProcedure()) {
|
|
return handleMachineStatus(proc->dispatchTimeout(timer));
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void TranEntry::terminateHook()
|
|
{
|
|
if (MachineBase *proc = currentProcedure()) {
|
|
proc->handleTerminationRequest();
|
|
}
|
|
}
|
|
|
|
void TranEntry::teCloseDialog(CancelCause cause)
|
|
{
|
|
CallState state = getGSMState();
|
|
|
|
// An MO transaction may not have a dialog yet.
|
|
// The dialog can also be NULL because the phone will send a DISCONNECT first thing if a previous call did not close correctly.
|
|
SipDialog *dialog = getDialog();
|
|
|
|
if (dialog) {
|
|
// For the special case of outbound handover we must destroy the dialog immediately
|
|
// in case a new handover comes back to us in the reverse direction.
|
|
// just drop the dialog, dont send a BYE.
|
|
//bool terminate = (state == CCState::HandoverOutbound);
|
|
if (state == CCState::HandoverOutbound) {
|
|
cause = CancelCauseHandoverOutbound;
|
|
}
|
|
dialog->dialogCancel(cause); // Does nothing if dialog not yet started.
|
|
}
|
|
}
|
|
|
|
|
|
StaleTranEntry::StaleTranEntry(TranEntry &old)
|
|
{
|
|
mStateTimer = old.mStateTimer;
|
|
mID = old.tranID();
|
|
mService = old.mService;
|
|
mL3TI = old.mL3TI;
|
|
mCalled = old.mCalled;
|
|
mCalling = old.mCalling;
|
|
mMessage = old.mMessage;
|
|
startTime = old.startTime;
|
|
endTime = old.endTime;
|
|
|
|
//DIG: Debug Start
|
|
if (0) {
|
|
printf("*********************************************************\n");
|
|
printf("*********************************************************\n");
|
|
printf(" STALE TRANSACTION ENTRY CONSTRUCTOR\n");
|
|
printf("StaleTranEntry::StaleTranEntry() called, call stack follows:\n");
|
|
printf("This %p, tranid %d\n", this, mID);
|
|
const int elements = 100;
|
|
void *buffer[elements];
|
|
int nptrs = backtrace(buffer, elements);
|
|
char **strings = backtrace_symbols(buffer, nptrs);
|
|
if (strings == NULL) { perror("backtrace_symbols"); }
|
|
else
|
|
{
|
|
for (int j = 0; j < nptrs; j++) printf("%s\n", strings[j]);
|
|
free(strings);
|
|
}
|
|
printf("*********************************************************\n");
|
|
printf("*********************************************************\n");
|
|
}
|
|
//DIG: Debug End
|
|
}
|
|
|
|
StaleTranEntry::~StaleTranEntry()
|
|
{
|
|
//DIG: Debug Start
|
|
if (0) {
|
|
printf("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n");
|
|
printf("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n");
|
|
printf(" destructor\n");
|
|
printf("StalTranEntry::~StalTranEntry() called, call stack follows:\n");
|
|
printf("This %p, tranid %d\n", this, mID);
|
|
const int elements = 100;
|
|
void *buffer[elements];
|
|
int nptrs = backtrace(buffer, elements);
|
|
char **strings = backtrace_symbols(buffer, nptrs);
|
|
if (strings == NULL) { perror("backtrace_symbols"); }
|
|
else
|
|
{
|
|
for (int j = 0; j < nptrs; j++) printf("%s\n", strings[j]);
|
|
free(strings);
|
|
}
|
|
printf("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n");
|
|
printf("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n");
|
|
}
|
|
//DIG: Debug End
|
|
}
|
|
|
|
// Use this for the column headers for the "calls" output
|
|
void StaleTranEntry::header(ostream& os)
|
|
{
|
|
std::string fmtBuf("");
|
|
char buf[BUFSIZ];
|
|
|
|
fmtBuf += TranFmt::lblfmt_Active;
|
|
fmtBuf += TranFmt::lblfmt_TranId;
|
|
fmtBuf += TranFmt::lblfmt_L3TI;
|
|
fmtBuf += TranFmt::lblfmt_Service;
|
|
fmtBuf += TranFmt::lblfmt_To;
|
|
fmtBuf += TranFmt::lblfmt_From;
|
|
fmtBuf += TranFmt::lblfmt_AgeSec;
|
|
fmtBuf += TranFmt::lblfmt_StartTime;
|
|
fmtBuf += TranFmt::lblfmt_EndTime;
|
|
fmtBuf += TranFmt::lblfmt_Message;
|
|
fmtBuf += "\n";
|
|
|
|
snprintf(buf, sizeof(buf)-1, fmtBuf.c_str(),
|
|
"Active",
|
|
"TranId",
|
|
"L3TI",
|
|
"Service",
|
|
"To",
|
|
"From",
|
|
"AgeSec",
|
|
"Start Time",
|
|
"End Time",
|
|
"Message");
|
|
os << buf;
|
|
|
|
snprintf(buf, sizeof(buf)-1, fmtBuf.c_str(),
|
|
"======",
|
|
"==========",
|
|
"=========",
|
|
"=========",
|
|
"================",
|
|
"================",
|
|
"=========",
|
|
"=====================================",
|
|
"=====================================",
|
|
"========================================================");
|
|
os << buf;
|
|
}
|
|
|
|
// Use this for the column data for the "calls" output
|
|
void StaleTranEntry::textTable(ostream& os) const
|
|
{
|
|
std::string fmtBuf("");
|
|
char buf[BUFSIZ];
|
|
|
|
fmtBuf += TranFmt::fmt_Active;
|
|
fmtBuf += TranFmt::fmt_TranId;
|
|
fmtBuf += TranFmt::fmt_L3TI;
|
|
fmtBuf += TranFmt::fmt_Service;
|
|
fmtBuf += TranFmt::fmt_To;
|
|
fmtBuf += TranFmt::fmt_From;
|
|
fmtBuf += TranFmt::fmt_AgeSec;
|
|
fmtBuf += TranFmt::fmt_StartTime;
|
|
if (endTime)
|
|
fmtBuf += TranFmt::fmt_EndTime;
|
|
else
|
|
fmtBuf += TranFmt::fmt_EndTime2;
|
|
fmtBuf += TranFmt::fmt_Message;
|
|
fmtBuf += "\n";
|
|
|
|
struct tm startTm, endTm;
|
|
localtime_r(&startTime, &startTm);
|
|
std::ostringstream svc;
|
|
mService.text(svc);
|
|
const char *psSvc = svc.str().c_str();
|
|
if (endTime)
|
|
{
|
|
localtime_r(&endTime, &endTm);
|
|
snprintf(buf, sizeof(buf)-1, fmtBuf.c_str(),
|
|
endTime == 0 ? "yes" : "no",
|
|
tranID(),
|
|
mL3TI,
|
|
psSvc,
|
|
mCalled.digits()[0] ? mCalled.digits() : "",
|
|
mCalling.digits()[0] ? mCalling.digits() : "",
|
|
(stateAge()+500)/1000,
|
|
startTm.tm_year + 1900, startTm.tm_mon + 1, startTm.tm_mday,
|
|
startTm.tm_hour, startTm.tm_min, startTm.tm_sec,
|
|
startTime,
|
|
endTm.tm_year + 1900, endTm.tm_mon + 1, endTm.tm_mday,
|
|
endTm.tm_hour, endTm.tm_min, endTm.tm_sec,
|
|
endTime,
|
|
mMessage.c_str());
|
|
} else
|
|
{
|
|
snprintf(buf, sizeof(buf)-1, fmtBuf.c_str(),
|
|
endTime == 0 ? "yes" : "no",
|
|
tranID(),
|
|
mL3TI,
|
|
psSvc,
|
|
mCalled.digits()[0] ? mCalled.digits() : "",
|
|
mCalling.digits()[0] ? mCalling.digits() : "",
|
|
(stateAge()+500)/1000,
|
|
startTm.tm_year + 1900, startTm.tm_mon + 1, startTm.tm_mday,
|
|
startTm.tm_hour, startTm.tm_min, startTm.tm_sec,
|
|
startTime,
|
|
"", // no end time
|
|
mMessage.c_str());
|
|
}
|
|
os << buf;
|
|
}
|
|
|
|
unsigned StaleTranEntry::getL3TI() const
|
|
{
|
|
return mL3TI;
|
|
}
|
|
|
|
|
|
|
|
// Used by MMLayer to immediately remove the transaction, without notifying MM layer.
|
|
// An assumption is that the dialog pointer is valid as long as the transaction exists,
|
|
// so we dont zero out the dialog pointer until we kill the dialog.
|
|
void TranEntry::teRemove(CancelCause cause)
|
|
{
|
|
CallState state = getGSMState();
|
|
|
|
SipDialog *dialog = getDialog();
|
|
mDialog = 0;
|
|
|
|
if (dialog) {
|
|
// For the special case of outbound handover we must destroy the dialog immediately
|
|
// in case a new handover comes back to us in the reverse direction.
|
|
// just drop the dialog, dont send a BYE.
|
|
//bool terminate = (state == CCState::HandoverOutbound);
|
|
if (state == CCState::HandoverOutbound) {
|
|
cause = CancelCauseHandoverOutbound;
|
|
}
|
|
dialog->dialogCancel(cause); // Does nothing if dialog not yet started.
|
|
}
|
|
|
|
// It is important to make this transaction no longer point at the dialog, because the dialog
|
|
// will not destroy itself while a transaction still points at it. Taking the transaction
|
|
// out of the TransactionTable prevents the dialog from finding the transaction any longer.
|
|
// However to prevent a race we must do this after using the dialog, which we did above.
|
|
setGSMState(CCState::NullState); // redundant, transaction is being deleted.
|
|
gNewTransactionTable.ttRemove(this->tranID());
|
|
|
|
while (currentProcedure()) {
|
|
delete tran()->tePopMachine();
|
|
}
|
|
|
|
if (mContext) { mContext->mmDisconnectTran(this); } // DANGER: this deletes the transaction as a side effect.
|
|
}
|
|
|
|
// Send closure messages for a transaction that is known to be a CS transaction, using the specified CC cause.
|
|
// Must only call from the thread running the channel.
|
|
// To close all transactions on a channel, see L3LogicalChannel::chanClose()
|
|
void TranEntry::teCloseCallNow(L3Cause l3cause)
|
|
{
|
|
WATCHINFO("CloseCallNow"<<LOGVAR2("cause",l3cause.cause())<<" "<<channel()->descriptiveString());
|
|
LOG(DEBUG) <<LOGVAR(l3cause) << this << gMMLayer.printMMInfo();
|
|
teCloseDialog(); // Redundant with teRemove, but I want to be sure we terminate the dialog regardless of bugs.
|
|
if (isL3TIValid() && tran()->getGSMState() != CCState::NullState && tran()->getGSMState() != CCState::ReleaseRequest) {
|
|
unsigned l3ti = getL3TI();
|
|
// 24.008 5.4.2: Permitted method to close call immediately.
|
|
channel()->l3sendm(GSM::L3ReleaseComplete(l3ti,l3cause)); // This is a CC message that releases this Transaction immediately.
|
|
}
|
|
setGSMState(CCState::NullState); // redundant, we are deleting this transaction.
|
|
}
|
|
|
|
};
|
|
|
|
// vim: ts=4 sw=4
|