mirror of
https://github.com/RangeNetworks/openbts.git
synced 2025-11-03 21:33:15 +00:00
More reliable detection of busy condition on MTC. Also adds new GSM state, "Busy Reject". git-svn-id: http://wush.net/svn/range/software/public/openbts/trunk@4920 19bc5d8c-e614-43d4-8b26-e1612bc8e597
1296 lines
33 KiB
C++
1296 lines
33 KiB
C++
/**@file TransactionTable and related classes. */
|
|
|
|
/*
|
|
* Copyright 2008, 2010 Free Software Foundation, Inc.
|
|
* Copyright 2010 Kestrel Signal Process, Inc.
|
|
* Copyright 2011, 2012 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.
|
|
|
|
*/
|
|
|
|
|
|
#include "TransactionTable.h"
|
|
#include "ControlCommon.h"
|
|
|
|
#include <GSMLogicalChannel.h>
|
|
#include <GSML3Message.h>
|
|
#include <GSML3CCMessages.h>
|
|
#include <GSML3RRMessages.h>
|
|
#include <GSML3MMMessages.h>
|
|
#include <GSMConfig.h>
|
|
|
|
#include <sqlite3.h>
|
|
#include <sqlite3util.h>
|
|
|
|
#include <SIPEngine.h>
|
|
#include <SIPInterface.h>
|
|
#include <SIPUtility.h>
|
|
|
|
#include <CallControl.h>
|
|
|
|
#include <Reporting.h>
|
|
#include <Logger.h>
|
|
#undef WARNING
|
|
|
|
|
|
using namespace std;
|
|
using namespace GSM;
|
|
using namespace Control;
|
|
using namespace SIP;
|
|
|
|
|
|
|
|
static const char* createTransactionTable = {
|
|
"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
|
|
")"
|
|
};
|
|
|
|
|
|
|
|
|
|
void TransactionEntry::initTimers()
|
|
{
|
|
// Call this only once, from the constructor.
|
|
// TODO -- It would be nice if these were all configurable.
|
|
assert(mTimers.size()==0);
|
|
mTimers["301"] = Z100Timer(T301ms);
|
|
mTimers["302"] = Z100Timer(T302ms);
|
|
mTimers["303"] = Z100Timer(T303ms);
|
|
mTimers["304"] = Z100Timer(T304ms);
|
|
mTimers["305"] = Z100Timer(T305ms);
|
|
mTimers["308"] = Z100Timer(T308ms);
|
|
mTimers["310"] = Z100Timer(T310ms);
|
|
mTimers["313"] = Z100Timer(T313ms);
|
|
mTimers["3113"] = Z100Timer(gConfig.getNum("GSM.Timer.T3113"));
|
|
mTimers["TR1M"] = Z100Timer(TR1Mms);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Form for MT transactions.
|
|
TransactionEntry::TransactionEntry(
|
|
const char* proxy,
|
|
const L3MobileIdentity& wSubscriber,
|
|
GSM::LogicalChannel* wChannel,
|
|
const L3CMServiceType& wService,
|
|
const L3CallingPartyBCDNumber& wCalling,
|
|
GSM::CallState wState,
|
|
const char *wMessage,
|
|
bool wFake)
|
|
:mID(gTransactionTable.newID()),
|
|
mSubscriber(wSubscriber),mService(wService),
|
|
mL3TI(gTMSITable.nextL3TI(wSubscriber.digits())),
|
|
mCalling(wCalling),
|
|
mSIP(proxy,mSubscriber.digits()),
|
|
mGSMState(wState),
|
|
mNumSQLTries(gConfig.getNum("Control.NumSQLTries")),
|
|
mChannel(wChannel),
|
|
mTerminationRequested(false),
|
|
mRemoved(false),
|
|
mFake(wFake)
|
|
|
|
{
|
|
if (wMessage) mMessage.assign(wMessage); //strncpy(mMessage,wMessage,160);
|
|
else mMessage.assign(""); //mMessage[0]='\0';
|
|
initTimers();
|
|
}
|
|
|
|
// Form for MOC transactions.
|
|
TransactionEntry::TransactionEntry(
|
|
const char* proxy,
|
|
const L3MobileIdentity& wSubscriber,
|
|
GSM::LogicalChannel* wChannel,
|
|
const L3CMServiceType& wService,
|
|
unsigned wL3TI,
|
|
const L3CalledPartyBCDNumber& wCalled)
|
|
:mID(gTransactionTable.newID()),
|
|
mSubscriber(wSubscriber),mService(wService),
|
|
mL3TI(wL3TI),
|
|
mCalled(wCalled),
|
|
mSIP(proxy,mSubscriber.digits()),
|
|
mGSMState(GSM::MOCInitiated),
|
|
mNumSQLTries(gConfig.getNum("Control.NumSQLTries")),
|
|
mChannel(wChannel),
|
|
mTerminationRequested(false),
|
|
mRemoved(false),
|
|
mFake(false)
|
|
{
|
|
assert(mSubscriber.type()==GSM::IMSIType);
|
|
mMessage.assign(""); //mMessage[0]='\0';
|
|
initTimers();
|
|
}
|
|
|
|
|
|
// Form for SOS transactions.
|
|
TransactionEntry::TransactionEntry(
|
|
const char* proxy,
|
|
const L3MobileIdentity& wSubscriber,
|
|
GSM::LogicalChannel* wChannel,
|
|
const L3CMServiceType& wService,
|
|
unsigned wL3TI)
|
|
:mID(gTransactionTable.newID()),
|
|
mSubscriber(wSubscriber),mService(wService),
|
|
mL3TI(wL3TI),
|
|
mSIP(proxy,mSubscriber.digits()),
|
|
mGSMState(GSM::MOCInitiated),
|
|
mNumSQLTries(2*gConfig.getNum("Control.NumSQLTries")),
|
|
mChannel(wChannel),
|
|
mTerminationRequested(false),
|
|
mRemoved(false),
|
|
mFake(false)
|
|
{
|
|
mMessage.assign(""); //mMessage[0]='\0';
|
|
initTimers();
|
|
}
|
|
|
|
|
|
// Form for MO-SMS transactions.
|
|
TransactionEntry::TransactionEntry(
|
|
const char* proxy,
|
|
const L3MobileIdentity& wSubscriber,
|
|
GSM::LogicalChannel* wChannel,
|
|
const L3CalledPartyBCDNumber& wCalled,
|
|
const char* wMessage)
|
|
:mID(gTransactionTable.newID()),
|
|
mSubscriber(wSubscriber),
|
|
mService(GSM::L3CMServiceType::ShortMessage),
|
|
mL3TI(7),mCalled(wCalled),
|
|
mSIP(proxy,mSubscriber.digits()),
|
|
mGSMState(GSM::SMSSubmitting),
|
|
mNumSQLTries(gConfig.getNum("Control.NumSQLTries")),
|
|
mChannel(wChannel),
|
|
mTerminationRequested(false),
|
|
mRemoved(false),
|
|
mFake(false)
|
|
{
|
|
assert(mSubscriber.type()==GSM::IMSIType);
|
|
if (wMessage!=NULL) mMessage.assign(wMessage); //strncpy(mMessage,wMessage,160);
|
|
else mMessage.assign(""); //mMessage[0]='\0';
|
|
initTimers();
|
|
}
|
|
|
|
// Form for MO-SMS transactions with parallel call.
|
|
TransactionEntry::TransactionEntry(
|
|
const char* proxy,
|
|
const L3MobileIdentity& wSubscriber,
|
|
GSM::LogicalChannel* wChannel)
|
|
:mID(gTransactionTable.newID()),
|
|
mSubscriber(wSubscriber),
|
|
mService(GSM::L3CMServiceType::ShortMessage),
|
|
mL3TI(7),
|
|
mSIP(proxy,mSubscriber.digits()),
|
|
mGSMState(GSM::SMSSubmitting),
|
|
mNumSQLTries(gConfig.getNum("Control.NumSQLTries")),
|
|
mChannel(wChannel),
|
|
mTerminationRequested(false),
|
|
mRemoved(false),
|
|
mFake(false)
|
|
{
|
|
assert(mSubscriber.type()==GSM::IMSIType);
|
|
mMessage[0]='\0';
|
|
initTimers();
|
|
}
|
|
|
|
|
|
TransactionEntry::~TransactionEntry()
|
|
{
|
|
// This should go out of scope before the object is actually destroyed.
|
|
ScopedLock lock(mLock);
|
|
|
|
// Remove the associated SIP message FIFO.
|
|
gSIPInterface.removeCall(mSIP.callID());
|
|
|
|
// Delete the SQL table entry.
|
|
char query[100];
|
|
sprintf(query,"DELETE FROM TRANSACTION_TABLE WHERE ID=%u",mID);
|
|
runQuery(query);
|
|
|
|
}
|
|
|
|
|
|
void TransactionEntry::resetTimer(const char* name)
|
|
{
|
|
if (mRemoved) throw RemovedTransaction(mID);
|
|
ScopedLock lock(mLock);
|
|
mTimers[name].reset();
|
|
}
|
|
|
|
|
|
void TransactionEntry::setTimer(const char* name)
|
|
{
|
|
if (mRemoved) throw RemovedTransaction(mID);
|
|
ScopedLock lock(mLock);
|
|
mTimers[name].set();
|
|
}
|
|
|
|
void TransactionEntry::setTimer(const char* name, long newLimit)
|
|
{
|
|
if (mRemoved) throw RemovedTransaction(mID);
|
|
ScopedLock lock(mLock);
|
|
mTimers[name].set(newLimit);
|
|
}
|
|
|
|
|
|
bool TransactionEntry::timerExpired(const char* name) const
|
|
{
|
|
if (mRemoved) throw RemovedTransaction(mID);
|
|
ScopedLock lock(mLock);
|
|
TimerTable::const_iterator itr = mTimers.find(name);
|
|
assert(itr!=mTimers.end());
|
|
return (itr->second).expired();
|
|
}
|
|
|
|
|
|
bool TransactionEntry::anyTimerExpired() const
|
|
{
|
|
if (mRemoved) throw RemovedTransaction(mID);
|
|
ScopedLock lock(mLock);
|
|
TimerTable::const_iterator itr = mTimers.begin();
|
|
while (itr!=mTimers.end()) {
|
|
if ((itr->second).expired()) {
|
|
LOG(INFO) << itr->first << " expired in " << *this;
|
|
return true;
|
|
}
|
|
++itr;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
void TransactionEntry::resetTimers()
|
|
{
|
|
if (mRemoved) throw RemovedTransaction(mID);
|
|
ScopedLock lock(mLock);
|
|
TimerTable::iterator itr = mTimers.begin();
|
|
while (itr!=mTimers.end()) {
|
|
(itr->second).reset();
|
|
++itr;
|
|
}
|
|
}
|
|
|
|
|
|
bool TransactionEntry::clearingGSM() const
|
|
{
|
|
if (mRemoved) throw RemovedTransaction(mID);
|
|
ScopedLock lock(mLock);
|
|
return (mGSMState==GSM::ReleaseRequest) || (mGSMState==GSM::DisconnectIndication);
|
|
}
|
|
|
|
|
|
bool TransactionEntry::deadOrRemoved() const
|
|
{
|
|
if (mRemoved) return true;
|
|
ScopedLock lock(mLock);
|
|
return dead();
|
|
}
|
|
|
|
|
|
bool TransactionEntry::dead() const
|
|
{
|
|
// Get the state information and release the locks.
|
|
|
|
// If it's locked, we assume someone has locked it,
|
|
// so it's not dead.
|
|
// And if someone locked in permanently,
|
|
// the resulting deadlock would spread through the whole system.
|
|
|
|
if (!mLock.trylock()) return false;
|
|
SIP::SIPState lSIPState = mSIP.state();
|
|
GSM::CallState lGSMState = mGSMState;
|
|
unsigned age = mStateTimer.elapsed();
|
|
mLock.unlock();
|
|
|
|
// Now check states against the timer.
|
|
|
|
|
|
// 30-second tests
|
|
if (age < 30*1000) return false;
|
|
// Failed?
|
|
if (lSIPState==SIP::Fail) return true;
|
|
// SIP Null state?
|
|
if (lSIPState==SIP::NullState) return true;
|
|
// SIP stuck in proceeding?
|
|
if (lSIPState==SIP::Proceeding) return true;
|
|
// SIP cancelled?
|
|
if (lSIPState==SIP::Canceled) return true;
|
|
// SIP Cleared?
|
|
if (lSIPState==SIP::Cleared) return true;
|
|
|
|
// 180-second tests
|
|
if (age < 180*1000) return false;
|
|
// Dead if someone requested removal >3 min ago.
|
|
if (mRemoved) return true;
|
|
// Any GSM state other than Active for >3 min?
|
|
if (lGSMState!=GSM::Active) return true;
|
|
// Any SIP stte other than active for >3 min?
|
|
if (lSIPState !=SIP::Active) return true;
|
|
|
|
// If we got here, the state-vs-timer relationship
|
|
// appears to be valid.
|
|
return false;
|
|
}
|
|
|
|
|
|
|
|
ostream& Control::operator<<(ostream& os, const TransactionEntry& entry)
|
|
{
|
|
entry.text(os);
|
|
return os;
|
|
}
|
|
|
|
|
|
|
|
void TransactionEntry::text(ostream& os) const
|
|
{
|
|
ScopedLock lock(mLock);
|
|
os << mID;
|
|
if (mRemoved) os << " (removed)";
|
|
else if (dead()) os << " (defunct)";
|
|
if (mChannel) os << " " << *mChannel;
|
|
else os << " no chan";
|
|
os << " " << mSubscriber;
|
|
os << " L3TI=" << mL3TI;
|
|
os << " SIP-call-id=" << mSIP.callID();
|
|
os << " SIP-proxy=" << mSIP.proxyIP() << ":" << mSIP.proxyPort();
|
|
os << " " << mService;
|
|
if (mCalled.digits()[0]) os << " to=" << mCalled.digits();
|
|
if (mCalling.digits()[0]) os << " from=" << mCalling.digits();
|
|
os << " GSMState=" << mGSMState;
|
|
os << " SIPState=" << mSIP.state();
|
|
os << " (" << (stateAge()+500)/1000 << " sec)";
|
|
if (mMessage[0]) os << " message=\"" << mMessage << "\"";
|
|
}
|
|
|
|
void TransactionEntry::message(const char *wMessage, size_t length)
|
|
{
|
|
/*if (length>520) {
|
|
LOG(NOTICE) << "truncating long message: " << wMessage;
|
|
length=520;
|
|
}*/
|
|
if (mRemoved) throw RemovedTransaction(mID);
|
|
ScopedLock lock(mLock);
|
|
//memcpy(mMessage,wMessage,length);
|
|
//mMessage[length]='\0';
|
|
mMessage.assign(wMessage, length);
|
|
}
|
|
|
|
void TransactionEntry::messageType(const char *wContentType)
|
|
{
|
|
if (mRemoved) throw RemovedTransaction(mID);
|
|
ScopedLock lock(mLock);
|
|
mContentType.assign(wContentType);
|
|
}
|
|
|
|
|
|
|
|
void TransactionEntry::runQuery(const char* query) const
|
|
{
|
|
// Caller should hold mLock and should have already checked mRemoved..
|
|
for (unsigned i=0; i<mNumSQLTries; i++) {
|
|
if (sqlite3_command(gTransactionTable.DB(),query)) return;
|
|
}
|
|
LOG(ALERT) << "transaction table access failed after " << mNumSQLTries << "attempts. query:" << query << " error: " << sqlite3_errmsg(gTransactionTable.DB());
|
|
}
|
|
|
|
|
|
|
|
void TransactionEntry::insertIntoDatabase()
|
|
{
|
|
// This should be called only from gTransactionTable::add.
|
|
// Caller should hold mLock.
|
|
|
|
ostringstream serviceTypeSS;
|
|
serviceTypeSS << mService;
|
|
|
|
ostringstream sipStateSS;
|
|
sipStateSS << mSIP.state();
|
|
mPrevSIPState = mSIP.state();
|
|
|
|
char subscriber[25];
|
|
switch (mSubscriber.type()) {
|
|
case IMSIType: sprintf(subscriber,"IMSI%s",mSubscriber.digits()); break;
|
|
case IMEIType: sprintf(subscriber,"IMEI%s",mSubscriber.digits()); break;
|
|
case TMSIType: sprintf(subscriber,"TMSI%x",mSubscriber.TMSI()); break;
|
|
default:
|
|
sprintf(subscriber,"invalid");
|
|
LOG(ERR) << "non-valid subscriber ID in transaction table: " << mSubscriber;
|
|
}
|
|
|
|
const char* stateString = GSM::CallStateString(mGSMState);
|
|
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')",
|
|
mID,now,now,
|
|
serviceTypeSS.str().c_str(),
|
|
subscriber,
|
|
mL3TI,
|
|
mCalled.digits(),
|
|
mCalling.digits(),
|
|
stateString,
|
|
sipStateSS.str().c_str(),
|
|
mSIP.callID().c_str(),
|
|
mSIP.proxyIP().c_str()
|
|
);
|
|
|
|
runQuery(query);
|
|
|
|
if (!mChannel) return;
|
|
sprintf(query,"UPDATE TRANSACTION_TABLE SET CHANNEL='%s' WHERE ID=%u",
|
|
mChannel->descriptiveString(), mID);
|
|
runQuery(query);
|
|
}
|
|
|
|
|
|
|
|
void TransactionEntry::channel(GSM::LogicalChannel* wChannel)
|
|
{
|
|
if (mRemoved) throw RemovedTransaction(mID);
|
|
ScopedLock lock(mLock);
|
|
mChannel = wChannel;
|
|
|
|
char query[500];
|
|
if (mChannel) {
|
|
sprintf(query,"UPDATE TRANSACTION_TABLE SET CHANGED=%u,CHANNEL='%s' WHERE ID=%u",
|
|
(unsigned)time(NULL), mChannel->descriptiveString(), mID);
|
|
} else {
|
|
sprintf(query,"UPDATE TRANSACTION_TABLE SET CHANGED=%u,CHANNEL=NULL WHERE ID=%u",
|
|
(unsigned)time(NULL), mID);
|
|
}
|
|
|
|
runQuery(query);
|
|
}
|
|
|
|
|
|
GSM::LogicalChannel* TransactionEntry::channel()
|
|
{
|
|
if (mRemoved) throw RemovedTransaction(mID);
|
|
return mChannel;
|
|
}
|
|
|
|
const GSM::LogicalChannel* TransactionEntry::channel() const
|
|
{
|
|
if (mRemoved) throw RemovedTransaction(mID);
|
|
return mChannel;
|
|
}
|
|
|
|
|
|
|
|
unsigned TransactionEntry::L3TI() const
|
|
{
|
|
if (mRemoved) throw RemovedTransaction(mID);
|
|
return mL3TI;
|
|
}
|
|
|
|
GSM::CallState TransactionEntry::GSMState() const
|
|
{
|
|
if (mRemoved) throw RemovedTransaction(mID);
|
|
ScopedLock lock(mLock);
|
|
return mGSMState;
|
|
}
|
|
|
|
|
|
void TransactionEntry::GSMState(GSM::CallState wState)
|
|
{
|
|
if (mRemoved) throw RemovedTransaction(mID);
|
|
ScopedLock lock(mLock);
|
|
mStateTimer.now();
|
|
unsigned now = mStateTimer.sec();
|
|
|
|
mGSMState = wState;
|
|
const char* stateString = GSM::CallStateString(wState);
|
|
assert(stateString);
|
|
|
|
char query[150];
|
|
sprintf(query,
|
|
"UPDATE TRANSACTION_TABLE SET GSMSTATE='%s',CHANGED=%u WHERE ID=%u",
|
|
stateString,now, mID);
|
|
runQuery(query);
|
|
}
|
|
|
|
|
|
SIP::SIPState TransactionEntry::echoSIPState(SIP::SIPState state) const
|
|
{
|
|
// Caller should hold mLock.
|
|
if (mPrevSIPState==state) return state;
|
|
mPrevSIPState = state;
|
|
|
|
const char* stateString = SIP::SIPStateString(state);
|
|
assert(stateString);
|
|
|
|
unsigned now = time(NULL);
|
|
|
|
char query[150];
|
|
sprintf(query,
|
|
"UPDATE TRANSACTION_TABLE SET SIPSTATE='%s',CHANGED=%u WHERE ID=%u",
|
|
stateString,now,mID);
|
|
runQuery(query);
|
|
|
|
return state;
|
|
}
|
|
|
|
|
|
|
|
|
|
SIP::SIPState TransactionEntry::MOCSendINVITE(const char* calledUser, const char* calledDomain, short rtpPort, unsigned codec)
|
|
{
|
|
if (mRemoved) throw RemovedTransaction(mID);
|
|
ScopedLock lock(mLock);
|
|
SIP::SIPState state = mSIP.MOCSendINVITE(calledUser,calledDomain,rtpPort,codec,channel());
|
|
echoSIPState(state);
|
|
return state;
|
|
}
|
|
|
|
SIP::SIPState TransactionEntry::MOCResendINVITE()
|
|
{
|
|
if (mRemoved) throw RemovedTransaction(mID);
|
|
ScopedLock lock(mLock);
|
|
SIP::SIPState state = mSIP.MOCResendINVITE();
|
|
echoSIPState(state);
|
|
return state;
|
|
}
|
|
|
|
SIP::SIPState TransactionEntry::MOCCheckForOK()
|
|
{
|
|
if (mRemoved) throw RemovedTransaction(mID);
|
|
ScopedLock lock(mLock);
|
|
SIP::SIPState state = mSIP.MOCCheckForOK(&mLock);
|
|
echoSIPState(state);
|
|
return state;
|
|
}
|
|
|
|
SIP::SIPState TransactionEntry::MOCSendACK()
|
|
{
|
|
if (mRemoved) throw RemovedTransaction(mID);
|
|
ScopedLock lock(mLock);
|
|
SIP::SIPState state = mSIP.MOCSendACK();
|
|
echoSIPState(state);
|
|
return state;
|
|
}
|
|
|
|
SIP::SIPState TransactionEntry::SOSSendINVITE(short rtpPort, unsigned codec)
|
|
{
|
|
if (mRemoved) throw RemovedTransaction(mID);
|
|
ScopedLock lock(mLock);
|
|
SIP::SIPState state = mSIP.SOSSendINVITE(rtpPort,codec,channel());
|
|
echoSIPState(state);
|
|
return state;
|
|
}
|
|
|
|
|
|
SIP::SIPState TransactionEntry::MTCSendTrying()
|
|
{
|
|
if (mRemoved) throw RemovedTransaction(mID);
|
|
ScopedLock lock(mLock);
|
|
SIP::SIPState state = mSIP.MTCSendTrying();
|
|
echoSIPState(state);
|
|
return state;
|
|
}
|
|
|
|
SIP::SIPState TransactionEntry::MTCSendRinging()
|
|
{
|
|
if (mRemoved) throw RemovedTransaction(mID);
|
|
ScopedLock lock(mLock);
|
|
SIP::SIPState state = mSIP.MTCSendRinging();
|
|
echoSIPState(state);
|
|
return state;
|
|
}
|
|
|
|
SIP::SIPState TransactionEntry::MTCCheckForACK()
|
|
{
|
|
if (mRemoved) throw RemovedTransaction(mID);
|
|
ScopedLock lock(mLock);
|
|
SIP::SIPState state = mSIP.MTCCheckForACK(&mLock);
|
|
echoSIPState(state);
|
|
return state;
|
|
}
|
|
|
|
SIP::SIPState TransactionEntry::MTCCheckForCancel()
|
|
{
|
|
if (mRemoved) throw RemovedTransaction(mID);
|
|
ScopedLock lock(mLock);
|
|
SIP::SIPState state = mSIP.MTCCheckForCancel();
|
|
echoSIPState(state);
|
|
return state;
|
|
}
|
|
|
|
|
|
SIP::SIPState TransactionEntry::MTCSendOK(short rtpPort, unsigned codec)
|
|
{
|
|
if (mRemoved) throw RemovedTransaction(mID);
|
|
ScopedLock lock(mLock);
|
|
SIP::SIPState state = mSIP.MTCSendOK(rtpPort,codec,channel());
|
|
echoSIPState(state);
|
|
return state;
|
|
}
|
|
|
|
SIP::SIPState TransactionEntry::MODSendBYE()
|
|
{
|
|
if (mRemoved) throw RemovedTransaction(mID);
|
|
ScopedLock lock(mLock);
|
|
SIP::SIPState state = mSIP.MODSendBYE();
|
|
echoSIPState(state);
|
|
return state;
|
|
}
|
|
|
|
SIP::SIPState TransactionEntry::MODSendERROR(osip_message_t * cause, int code, const char * reason, bool cancel)
|
|
{
|
|
if (mRemoved) throw RemovedTransaction(mID);
|
|
ScopedLock lock(mLock);
|
|
SIP::SIPState state = mSIP.MODSendERROR(cause, code, reason, cancel);
|
|
echoSIPState(state);
|
|
return state;
|
|
}
|
|
|
|
SIP::SIPState TransactionEntry::MODSendCANCEL()
|
|
{
|
|
if (mRemoved) throw RemovedTransaction(mID);
|
|
ScopedLock lock(mLock);
|
|
SIP::SIPState state = mSIP.MODSendCANCEL();
|
|
echoSIPState(state);
|
|
return state;
|
|
}
|
|
|
|
SIP::SIPState TransactionEntry::MODResendBYE()
|
|
{
|
|
if (mRemoved) throw RemovedTransaction(mID);
|
|
ScopedLock lock(mLock);
|
|
SIP::SIPState state = mSIP.MODResendBYE();
|
|
echoSIPState(state);
|
|
return state;
|
|
}
|
|
|
|
SIP::SIPState TransactionEntry::MODResendCANCEL()
|
|
{
|
|
if (mRemoved) throw RemovedTransaction(mID);
|
|
ScopedLock lock(mLock);
|
|
SIP::SIPState state = mSIP.MODResendCANCEL();
|
|
echoSIPState(state);
|
|
return state;
|
|
}
|
|
|
|
SIP::SIPState TransactionEntry::MODResendERROR(bool cancel)
|
|
{
|
|
if (mRemoved) throw RemovedTransaction(mID);
|
|
ScopedLock lock(mLock);
|
|
SIP::SIPState state = mSIP.MODResendERROR(cancel);
|
|
echoSIPState(state);
|
|
return state;
|
|
}
|
|
|
|
SIP::SIPState TransactionEntry::MODWaitForBYEOK()
|
|
{
|
|
if (mRemoved) throw RemovedTransaction(mID);
|
|
ScopedLock lock(mLock);
|
|
SIP::SIPState state = mSIP.MODWaitForBYEOK(&mLock);
|
|
echoSIPState(state);
|
|
return state;
|
|
}
|
|
|
|
SIP::SIPState TransactionEntry::MODWaitForCANCELOK()
|
|
{
|
|
if (mRemoved) throw RemovedTransaction(mID);
|
|
ScopedLock lock(mLock);
|
|
SIP::SIPState state = mSIP.MODWaitForCANCELOK(&mLock);
|
|
echoSIPState(state);
|
|
return state;
|
|
}
|
|
|
|
SIP::SIPState TransactionEntry::MODWaitForERRORACK(bool cancel)
|
|
{
|
|
if (mRemoved) throw RemovedTransaction(mID);
|
|
ScopedLock lock(mLock);
|
|
SIP::SIPState state = mSIP.MODWaitForERRORACK(cancel,&mLock);
|
|
echoSIPState(state);
|
|
return state;
|
|
}
|
|
|
|
SIP::SIPState TransactionEntry::MODWaitFor487()
|
|
{
|
|
if (mRemoved) throw RemovedTransaction(mID);
|
|
ScopedLock lock(mLock);
|
|
SIP::SIPState state = mSIP.MODWaitFor487(&mLock);
|
|
echoSIPState(state);
|
|
return state;
|
|
}
|
|
|
|
SIP::SIPState TransactionEntry::MODWaitForResponse(vector<unsigned> *validResponses)
|
|
{
|
|
if (mRemoved) throw RemovedTransaction(mID);
|
|
ScopedLock lock(mLock);
|
|
SIP::SIPState state = mSIP.MODWaitForResponse(validResponses, &mLock);
|
|
echoSIPState(state);
|
|
return state;
|
|
}
|
|
|
|
SIP::SIPState TransactionEntry::MTDCheckBYE()
|
|
{
|
|
if (mRemoved) throw RemovedTransaction(mID);
|
|
ScopedLock lock(mLock);
|
|
SIP::SIPState state = mSIP.MTDCheckBYE();
|
|
echoSIPState(state);
|
|
return state;
|
|
}
|
|
|
|
SIP::SIPState TransactionEntry::MTDSendBYEOK()
|
|
{
|
|
if (mRemoved) throw RemovedTransaction(mID);
|
|
ScopedLock lock(mLock);
|
|
SIP::SIPState state = mSIP.MTDSendBYEOK();
|
|
echoSIPState(state);
|
|
return state;
|
|
}
|
|
|
|
SIP::SIPState TransactionEntry::MTDSendCANCELOK()
|
|
{
|
|
if (mRemoved) throw RemovedTransaction(mID);
|
|
ScopedLock lock(mLock);
|
|
SIP::SIPState state = mSIP.MTDSendCANCELOK();
|
|
echoSIPState(state);
|
|
return state;
|
|
}
|
|
|
|
SIP::SIPState TransactionEntry::MOSMSSendMESSAGE(const char* calledUser, const char* calledDomain, const char* contentType)
|
|
{
|
|
if (mRemoved) throw RemovedTransaction(mID);
|
|
ScopedLock lock(mLock);
|
|
SIP::SIPState state = mSIP.MOSMSSendMESSAGE(calledUser,calledDomain,mMessage.c_str(),contentType,channel());
|
|
echoSIPState(state);
|
|
return state;
|
|
}
|
|
|
|
SIP::SIPState TransactionEntry::MOSMSWaitForSubmit()
|
|
{
|
|
if (mRemoved) throw RemovedTransaction(mID);
|
|
ScopedLock lock(mLock);
|
|
SIP::SIPState state = mSIP.MOSMSWaitForSubmit(&mLock);
|
|
echoSIPState(state);
|
|
return state;
|
|
}
|
|
|
|
SIP::SIPState TransactionEntry::MTSMSSendOK()
|
|
{
|
|
if (mRemoved) throw RemovedTransaction(mID);
|
|
ScopedLock lock(mLock);
|
|
SIP::SIPState state = mSIP.MTSMSSendOK(channel());
|
|
echoSIPState(state);
|
|
return state;
|
|
}
|
|
|
|
bool TransactionEntry::sendINFOAndWaitForOK(unsigned info)
|
|
{
|
|
if (mRemoved) throw RemovedTransaction(mID);
|
|
ScopedLock lock(mLock);
|
|
return mSIP.sendINFOAndWaitForOK(info,&mLock);
|
|
}
|
|
|
|
void TransactionEntry::SIPUser(const char* IMSI)
|
|
{
|
|
if (mRemoved) throw RemovedTransaction(mID);
|
|
ScopedLock lock(mLock);
|
|
mSIP.user(IMSI);
|
|
}
|
|
|
|
void TransactionEntry::SIPUser(const char* callID, const char *IMSI , const char *origID, const char *origHost)
|
|
{
|
|
if (mRemoved) throw RemovedTransaction(mID);
|
|
ScopedLock lock(mLock);
|
|
mSIP.user(callID,IMSI,origID,origHost);
|
|
}
|
|
|
|
void TransactionEntry::called(const L3CalledPartyBCDNumber& wCalled)
|
|
{
|
|
if (mRemoved) throw RemovedTransaction(mID);
|
|
ScopedLock lock(mLock);
|
|
mCalled = wCalled;
|
|
|
|
char query[151];
|
|
snprintf(query,150,
|
|
"UPDATE TRANSACTION_TABLE SET CALLED='%s' WHERE ID=%u",
|
|
mCalled.digits(), mID);
|
|
runQuery(query);
|
|
}
|
|
|
|
|
|
void TransactionEntry::L3TI(unsigned wL3TI)
|
|
{
|
|
if (mRemoved) throw RemovedTransaction(mID);
|
|
ScopedLock lock(mLock);
|
|
mL3TI = wL3TI;
|
|
|
|
char query[151];
|
|
snprintf(query,150,
|
|
"UPDATE TRANSACTION_TABLE SET L3TI=%u WHERE ID=%u",
|
|
mL3TI, mID);
|
|
runQuery(query);
|
|
}
|
|
|
|
|
|
bool TransactionEntry::terminationRequested()
|
|
{
|
|
if (mRemoved) throw RemovedTransaction(mID);
|
|
ScopedLock lock(mLock);
|
|
bool retVal = mTerminationRequested;
|
|
mTerminationRequested = false;
|
|
return retVal;
|
|
}
|
|
|
|
void TransactionTable::init(const char* path)
|
|
{
|
|
// This assumes the main application uses sdevrandom.
|
|
mIDCounter = random();
|
|
// Connect to the database.
|
|
int rc = sqlite3_open(path,&mDB);
|
|
if (rc) {
|
|
LOG(ALERT) << "Cannot open Transaction Table database at " << path << ": " << sqlite3_errmsg(mDB);
|
|
sqlite3_close(mDB);
|
|
mDB = NULL;
|
|
return;
|
|
}
|
|
// Create a new table, if needed.
|
|
if (!sqlite3_command(mDB,createTransactionTable)) {
|
|
LOG(ALERT) << "Cannot create Transaction Table";
|
|
}
|
|
// Clear any previous entires.
|
|
if (!sqlite3_command(gTransactionTable.DB(),"DELETE FROM TRANSACTION_TABLE"))
|
|
LOG(WARNING) << "cannot clear previous transaction table";
|
|
}
|
|
|
|
TransactionTable::~TransactionTable()
|
|
{
|
|
// Don't bother disposing of the memory,
|
|
// since this is only invoked when the application exits.
|
|
if (mDB) sqlite3_close(mDB);
|
|
}
|
|
|
|
|
|
|
|
|
|
unsigned TransactionTable::newID()
|
|
{
|
|
ScopedLock lock(mLock);
|
|
return mIDCounter++;
|
|
}
|
|
|
|
|
|
void TransactionTable::add(TransactionEntry* value)
|
|
{
|
|
LOG(INFO) << "new transaction " << *value;
|
|
ScopedLock lock(mLock);
|
|
mTable[value->ID()]=value;
|
|
value->insertIntoDatabase();
|
|
}
|
|
|
|
|
|
|
|
TransactionEntry* TransactionTable::find(unsigned 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);
|
|
ScopedLock lock(mLock);
|
|
TransactionMap::iterator itr = mTable.find(key);
|
|
if (itr==mTable.end()) return NULL;
|
|
if (itr->second->deadOrRemoved()) return NULL;
|
|
return (itr->second);
|
|
}
|
|
|
|
|
|
void TransactionTable::innerRemove(TransactionMap::iterator itr)
|
|
{
|
|
// This should not be called anywhere but from clearDeadEntries.
|
|
LOG(DEBUG) << "removing transaction: " << *(itr->second);
|
|
TransactionEntry *t = itr->second;
|
|
mTable.erase(itr);
|
|
delete t;
|
|
}
|
|
|
|
|
|
bool TransactionTable::remove(unsigned 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;
|
|
}
|
|
|
|
ScopedLock lock(mLock);
|
|
TransactionMap::iterator itr = mTable.find(key);
|
|
if (itr==mTable.end()) return false;
|
|
itr->second->remove();
|
|
return true;
|
|
}
|
|
|
|
bool TransactionTable::removePaging(unsigned key)
|
|
{
|
|
// ID==0 is a non-valid special case and should not be passed here.
|
|
assert(key);
|
|
ScopedLock lock(mLock);
|
|
TransactionMap::iterator itr = mTable.find(key);
|
|
if (itr==mTable.end()) return false;
|
|
if (itr->second->removed()) return true;
|
|
if (itr->second->GSMState()!=GSM::Paging) return false;
|
|
//no one to respond to if we're fake
|
|
if (!itr->second->fake()){
|
|
itr->second->MODSendERROR(NULL, 480, "Temporarily Unavailable", true);
|
|
}
|
|
itr->second->remove();
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
|
|
void TransactionTable::clearDeadEntries()
|
|
{
|
|
// Caller should hold mLock.
|
|
TransactionMap::iterator itr = mTable.begin();
|
|
while (itr!=mTable.end()) {
|
|
if (!itr->second->dead()) ++itr;
|
|
else {
|
|
LOG(DEBUG) << "erasing " << itr->first;
|
|
TransactionMap::iterator old = itr;
|
|
itr++;
|
|
innerRemove(old);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
TransactionEntry* TransactionTable::find(const GSM::LogicalChannel *chan)
|
|
{
|
|
LOG(DEBUG) << "by channel: " << *chan << " (" << chan << ")";
|
|
|
|
ScopedLock lock(mLock);
|
|
|
|
// 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.
|
|
TransactionEntry *retVal = NULL;
|
|
for (TransactionMap::iterator itr = mTable.begin(); itr!=mTable.end(); ++itr) {
|
|
if (itr->second->deadOrRemoved()) continue;
|
|
const GSM::LogicalChannel* thisChan = itr->second->channel();
|
|
if ((void*)thisChan != (void*)chan) continue;
|
|
retVal = itr->second;
|
|
}
|
|
//LOG(DEBUG) << "no match for " << *chan << " (" << chan << ")";
|
|
return retVal;
|
|
}
|
|
|
|
|
|
TransactionEntry* TransactionTable::findBySACCH(const GSM::SACCHLogicalChannel *chan)
|
|
{
|
|
LOG(DEBUG) << "by SACCH: " << *chan << " (" << chan << ")";
|
|
|
|
ScopedLock lock(mLock);
|
|
|
|
// Yes, it's linear time.
|
|
// Since clearDeadEntries is also linear, do that here, too.
|
|
clearDeadEntries();
|
|
|
|
// Brute force search.
|
|
TransactionEntry *retVal = NULL;
|
|
for (TransactionMap::iterator itr = mTable.begin(); itr!=mTable.end(); ++itr) {
|
|
if (itr->second->deadOrRemoved()) continue;
|
|
const GSM::LogicalChannel* thisChan = itr->second->channel();
|
|
if (thisChan->SACCH() != chan) continue;
|
|
retVal = itr->second;
|
|
}
|
|
return retVal;
|
|
}
|
|
|
|
|
|
TransactionEntry* TransactionTable::find(GSM::TypeAndOffset desc)
|
|
{
|
|
LOG(DEBUG) << "by type and offset: " << desc;
|
|
|
|
ScopedLock lock(mLock);
|
|
|
|
// Yes, it's linear time.
|
|
// Since clearDeadEntries is also linear, do that here, too.
|
|
clearDeadEntries();
|
|
|
|
// Brute force search.
|
|
for (TransactionMap::iterator itr = mTable.begin(); itr!=mTable.end(); ++itr) {
|
|
if (itr->second->deadOrRemoved()) continue;
|
|
const GSM::LogicalChannel* thisChan = itr->second->channel();
|
|
if (thisChan->typeAndOffset()!=desc) continue;
|
|
return itr->second;
|
|
}
|
|
//LOG(DEBUG) << "no match for " << *chan << " (" << chan << ")";
|
|
return NULL;
|
|
}
|
|
|
|
|
|
TransactionEntry* TransactionTable::find(const L3MobileIdentity& mobileID, GSM::CallState state)
|
|
{
|
|
LOG(DEBUG) << "by ID and state: " << mobileID << " in " << state;
|
|
|
|
ScopedLock lock(mLock);
|
|
|
|
// Yes, it's linear time.
|
|
// Since clearDeadEntries is also linear, do that here, too.
|
|
clearDeadEntries();
|
|
|
|
// Brute force search.
|
|
for (TransactionMap::iterator itr = mTable.begin(); itr!=mTable.end(); ++itr) {
|
|
if (itr->second->deadOrRemoved()) continue;
|
|
if (itr->second->GSMState() != state) continue;
|
|
if (itr->second->subscriber() != mobileID) continue;
|
|
return itr->second;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
bool TransactionTable::isBusy(const L3MobileIdentity& mobileID)
|
|
{
|
|
LOG(DEBUG) << "id: " << mobileID << "?";
|
|
|
|
ScopedLock lock(mLock);
|
|
|
|
// Yes, it's linear time.
|
|
// Since clearDeadEntries is also linear, do that here, too.
|
|
clearDeadEntries();
|
|
|
|
// Brute force search.
|
|
for (TransactionMap::iterator itr = mTable.begin(); itr!=mTable.end(); ++itr) {
|
|
if (itr->second->deadOrRemoved()) continue;
|
|
if (itr->second->subscriber() != mobileID) continue;
|
|
GSM::L3CMServiceType service = itr->second->service();
|
|
bool speech =
|
|
service==GSM::L3CMServiceType::EmergencyCall ||
|
|
service==GSM::L3CMServiceType::MobileOriginatedCall ||
|
|
service==GSM::L3CMServiceType::MobileTerminatedCall;
|
|
if (!speech) continue;
|
|
// OK, so we found a transaction for this call.
|
|
bool inCall =
|
|
itr->second->GSMState() == GSM::Paging ||
|
|
itr->second->GSMState() == GSM::AnsweredPaging ||
|
|
itr->second->GSMState() == GSM::MOCInitiated ||
|
|
itr->second->GSMState() == GSM::MOCProceeding ||
|
|
itr->second->GSMState() == GSM::MTCConfirmed ||
|
|
itr->second->GSMState() == GSM::CallReceived ||
|
|
itr->second->GSMState() == GSM::CallPresent ||
|
|
itr->second->GSMState() == GSM::ConnectIndication ||
|
|
itr->second->GSMState() == GSM::HandoverInbound ||
|
|
itr->second->GSMState() == GSM::HandoverProgress ||
|
|
itr->second->GSMState() == GSM::HandoverOutbound ||
|
|
itr->second->GSMState() == GSM::Active;
|
|
if (inCall) return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
|
|
|
|
TransactionEntry* TransactionTable::find(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.
|
|
clearDeadEntries();
|
|
|
|
// Brute force search.
|
|
ScopedLock lock(mLock);
|
|
for (TransactionMap::iterator itr = mTable.begin(); itr!=mTable.end(); ++itr) {
|
|
if (itr->second->deadOrRemoved()) continue;
|
|
if (itr->second->mSIP.callID() != callIDString) continue;
|
|
if (itr->second->subscriber() != mobileID) continue;
|
|
return itr->second;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
|
|
TransactionEntry* TransactionTable::find(const L3MobileIdentity& mobileID, unsigned transactionID)
|
|
{
|
|
LOG(DEBUG) << "by ID and transaction-ID: " << mobileID << ", transaction " << transactionID;
|
|
|
|
// Yes, it's linear time.
|
|
// Since clearDeadEntries is also linear, do that here, too.
|
|
clearDeadEntries();
|
|
|
|
// Brute force search.
|
|
ScopedLock lock(mLock);
|
|
for (TransactionMap::iterator itr = mTable.begin(); itr!=mTable.end(); ++itr) {
|
|
if (itr->second->deadOrRemoved()) continue;
|
|
if (itr->second->subscriber() != mobileID) continue;
|
|
return itr->second;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
|
|
TransactionEntry* TransactionTable::answeredPaging(const L3MobileIdentity& mobileID)
|
|
{
|
|
// Yes, it's linear time.
|
|
// Even in a 6-ARFCN system, it should rarely be more than a dozen entries.
|
|
|
|
ScopedLock lock(mLock);
|
|
|
|
// Since clearDeadEntries is also linear, do that here, too.
|
|
clearDeadEntries();
|
|
|
|
// Brute force search.
|
|
for (TransactionMap::iterator itr = mTable.begin(); itr!=mTable.end(); ++itr) {
|
|
if (itr->second->deadOrRemoved()) continue;
|
|
if (itr->second->GSMState() != GSM::Paging) continue;
|
|
if (itr->second->subscriber() == mobileID) {
|
|
// Stop T3113 and change the state.
|
|
itr->second->GSMState(AnsweredPaging);
|
|
itr->second->resetTimer("3113");
|
|
return itr->second;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
|
|
GSM::LogicalChannel* TransactionTable::findChannel(const L3MobileIdentity& mobileID)
|
|
{
|
|
// Yes, it's linear time.
|
|
// Even in a 6-ARFCN system, it should rarely be more than a dozen entries.
|
|
|
|
ScopedLock lock(mLock);
|
|
|
|
// Since clearDeadEntries is also linear, do that here, too.
|
|
clearDeadEntries();
|
|
|
|
// Brute force search.
|
|
for (TransactionMap::iterator itr = mTable.begin(); itr!=mTable.end(); ++itr) {
|
|
if (itr->second->deadOrRemoved()) continue;
|
|
if (itr->second->subscriber() != mobileID) continue;
|
|
GSM::LogicalChannel* chan = itr->second->channel();
|
|
if (!chan) continue;
|
|
if (chan->type() == FACCHType) return chan;
|
|
if (chan->type() == SDCCHType) return chan;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
|
|
unsigned TransactionTable::countChan(const GSM::LogicalChannel* chan)
|
|
{
|
|
ScopedLock lock(mLock);
|
|
clearDeadEntries();
|
|
unsigned count = 0;
|
|
for (TransactionMap::iterator itr = mTable.begin(); itr!=mTable.end(); ++itr) {
|
|
if (itr->second->deadOrRemoved()) continue;
|
|
if (itr->second->channel() == chan) count++;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
|
|
|
|
size_t TransactionTable::dump(ostream& os, bool showAll) const
|
|
{
|
|
ScopedLock lock(mLock);
|
|
size_t sz = 0;
|
|
for (TransactionMap::const_iterator itr = mTable.begin(); itr!=mTable.end(); ++itr) {
|
|
if ((!showAll) && itr->second->deadOrRemoved()) continue;
|
|
sz++;
|
|
os << *(itr->second) << endl;
|
|
}
|
|
return sz;
|
|
}
|
|
|
|
|
|
TransactionEntry* TransactionTable::findLongestCall()
|
|
{
|
|
ScopedLock lock(mLock);
|
|
clearDeadEntries();
|
|
long longTime = 0;
|
|
TransactionMap::iterator longCall = mTable.end();
|
|
for (TransactionMap::iterator itr = mTable.begin(); itr!=mTable.end(); ++itr) {
|
|
if (itr->second->deadOrRemoved()) continue;
|
|
if (!(itr->second->channel())) continue;
|
|
if (itr->second->service() == GSM::L3CMServiceType::EmergencyCall) continue;
|
|
if (itr->second->GSMState() != GSM::Active) continue;
|
|
long runTime = itr->second->stateAge();
|
|
if (runTime > longTime) {
|
|
runTime = longTime;
|
|
longCall = itr;
|
|
}
|
|
}
|
|
if (longCall == mTable.end()) return NULL;
|
|
return longCall->second;
|
|
}
|
|
|
|
/* linear, we should move the actual search into this structure */
|
|
bool TransactionTable::RTPAvailable(short rtpPort)
|
|
{
|
|
ScopedLock lock(mLock);
|
|
clearDeadEntries();
|
|
bool avail = true;
|
|
for (TransactionMap::iterator itr = mTable.begin(); itr!=mTable.end(); ++itr) {
|
|
if (itr->second->deadOrRemoved()) continue;
|
|
if (itr->second->mSIP.RTPPort() == rtpPort){
|
|
avail = false;
|
|
break;
|
|
}
|
|
}
|
|
return avail;
|
|
}
|
|
|
|
bool TransactionTable::duplicateMessage(const GSM::L3MobileIdentity& mobileID, const std::string& wMessage)
|
|
{
|
|
|
|
ScopedLock lock(mLock);
|
|
|
|
// Since clearDeadEntries is also linear, do that here, too.
|
|
clearDeadEntries();
|
|
|
|
// Brute force search.
|
|
for (TransactionMap::const_iterator itr = mTable.begin(); itr!=mTable.end(); ++itr) {
|
|
if (itr->second->deadOrRemoved()) continue;
|
|
if (itr->second->subscriber() != mobileID) continue;
|
|
if (itr->second->message() == wMessage) return true;
|
|
}
|
|
return false;
|
|
|
|
}
|
|
|
|
// vim: ts=4 sw=4
|