mirror of
https://github.com/RangeNetworks/openbts.git
synced 2025-11-04 22:03:17 +00:00
r4232: Major change in hos entries are removed from a TransactionTable. >From now on, the only place were entries are actually removed is in TransactionTable::innerRemove. Anywhere else, the remove methods just set a flag to tag the entry for later removal be innerRemove. This allows us to survive situations where we try to touch transactions that have been removed already. We still log at the ERR level, but no more segfaults. Updated all of the TransactionTable "find" methods to not return pointers to dead or removed tranactions. Updated find-by-channel search to return the transaction entry with the highest transaction number, which fixes a bug that sometimes picked up the wrong transaction records during EA TCH assignment. r4253: New exception class for when someone tries to use a "removed" transaction. r4254: Updated copyright notice. r4265: Unlock TransactionEntry::mLock while blocked on SIP message FIFOs in SIPEngine. This does wonders to reduce lock contention and make everything more snappy. Use Mutex::tryLock in TransactionEntry::dead and if lock is held, assume that it is not dead. This also does a lot to reduce lock contention. r4294: Change Um congestion response back to SIP 503. r4295: When deleting an entry that has failed to respond to paging, send a SIP 480 response so the switch knows that transaction is dead. r4412: Fixed bug that was causing SIPInterface to crash when the IMSI cannot be extracted from the To: header. git-svn-id: http://wush.net/svn/range/software/public/openbts/trunk@4497 19bc5d8c-e614-43d4-8b26-e1612bc8e597
1282 lines
33 KiB
C++
1282 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 <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)
|
|
: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)
|
|
{
|
|
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)
|
|
{
|
|
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)
|
|
{
|
|
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)
|
|
{
|
|
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)
|
|
{
|
|
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;
|
|
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::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
|