mirror of
				https://github.com/RangeNetworks/openbts.git
				synced 2025-11-03 21:33:15 +00:00 
			
		
		
		
	git-svn-id: http://wush.net/svn/range/software/public/openbts/trunk@6168 19bc5d8c-e614-43d4-8b26-e1612bc8e597
		
			
				
	
	
		
			1612 lines
		
	
	
		
			43 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			1612 lines
		
	
	
		
			43 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 <Peering.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)
 | 
						|
	: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),
 | 
						|
	mHandoverOtherBSTransactionID(0),
 | 
						|
	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),
 | 
						|
	mHandoverOtherBSTransactionID(0),
 | 
						|
	mRemoved(false)
 | 
						|
{
 | 
						|
	assert(mSubscriber.type()==GSM::IMSIType);
 | 
						|
	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),
 | 
						|
	mHandoverOtherBSTransactionID(0),
 | 
						|
	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),
 | 
						|
	mHandoverOtherBSTransactionID(0),
 | 
						|
	mRemoved(false)
 | 
						|
{
 | 
						|
	assert(mSubscriber.type()==GSM::IMSIType);
 | 
						|
	mMessage[0]='\0';
 | 
						|
	initTimers();
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
 | 
						|
// Form for inbound handovers.
 | 
						|
TransactionEntry::TransactionEntry(const struct sockaddr_in* peer,
 | 
						|
	unsigned wHandoverReference,
 | 
						|
	SimpleKeyValue ¶ms,
 | 
						|
	const char *proxy,
 | 
						|
	GSM::LogicalChannel *wChannel,
 | 
						|
	unsigned wHandoverOtherBSTransactionID)
 | 
						|
	:mID(gTransactionTable.newID()),
 | 
						|
	mService(GSM::L3CMServiceType::HandoverCall),
 | 
						|
	mSIP(proxy),
 | 
						|
	mGSMState(GSM::HandoverInbound),
 | 
						|
	mInboundReference(wHandoverReference),
 | 
						|
	mNumSQLTries(gConfig.getNum("Control.NumSQLTries")),
 | 
						|
	mChannel(wChannel),
 | 
						|
	mTerminationRequested(false),
 | 
						|
	mHandoverOtherBSTransactionID(wHandoverOtherBSTransactionID),
 | 
						|
	mRemoved(false)
 | 
						|
{
 | 
						|
	// This is used for inbound handovers.
 | 
						|
	// We are "BS2" in the handover ladder diagram.
 | 
						|
	// The message string was formed by the handoverString method.
 | 
						|
 | 
						|
	// Save the peer address.
 | 
						|
	bcopy(peer,&mInboundPeer,sizeof(mInboundPeer));
 | 
						|
 | 
						|
	// Break into space-delimited tokens, stuff into a SimpleKeyValue and then unpack it.
 | 
						|
	//SimpleKeyValue params;
 | 
						|
	//params.addItems(args);
 | 
						|
 | 
						|
	const char* IMSI = params.get("IMSI");
 | 
						|
	if (IMSI) mSubscriber = GSM::L3MobileIdentity(IMSI);
 | 
						|
 | 
						|
	const char* called = params.get("called");
 | 
						|
	if (called) {
 | 
						|
		mCalled = GSM::L3CallingPartyBCDNumber(called);
 | 
						|
		mService = GSM::L3CMServiceType::MobileOriginatedCall;
 | 
						|
	}
 | 
						|
 | 
						|
	const char* calling = params.get("calling");
 | 
						|
	if (calling) {
 | 
						|
		mCalling = GSM::L3CallingPartyBCDNumber(calling);
 | 
						|
		mService = GSM::L3CMServiceType::MobileTerminatedCall;
 | 
						|
	}
 | 
						|
 | 
						|
	const char* ref = params.get("ref");
 | 
						|
	if (ref) mInboundReference = strtol(ref,NULL,10);
 | 
						|
 | 
						|
	const char* L3TI = params.get("L3TI");
 | 
						|
	if (L3TI) mL3TI = strtol(L3TI,NULL,10);
 | 
						|
 | 
						|
	// Set the SIP state.
 | 
						|
	mSIP.state(SIP::HandoverInbound);
 | 
						|
 | 
						|
	const char* codec = params.get("codec");
 | 
						|
	if (codec) mCodec = atoi(codec);
 | 
						|
 | 
						|
	const char* remoteUsername = params.get("remoteUsername");
 | 
						|
	if (remoteUsername) mRemoteUsername = strdup(remoteUsername);
 | 
						|
 | 
						|
	const char* remoteDomain = params.get("remoteDomain");
 | 
						|
	if (remoteDomain) mRemoteDomain = strdup(remoteDomain);
 | 
						|
 | 
						|
	const char* SIPUsername = params.get("SIPUsername");
 | 
						|
	if (SIPUsername) mSIPUsername = strdup(SIPUsername);
 | 
						|
 | 
						|
	const char* SIPDisplayname = params.get("SIPDisplayname");
 | 
						|
	if (SIPDisplayname) mSIPDisplayname = strdup(SIPDisplayname);
 | 
						|
 | 
						|
	const char* FromTag = params.get("FromTag");
 | 
						|
	if (FromTag) mFromTag = strdup(FromTag);
 | 
						|
 | 
						|
	const char* FromUsername = params.get("FromUsername");
 | 
						|
	if (FromUsername) mFromUsername = strdup(FromUsername);
 | 
						|
 | 
						|
	const char* FromIP = params.get("FromIP");
 | 
						|
	if (FromIP) mFromIP = strdup(FromIP);
 | 
						|
 | 
						|
	const char* ToTag = params.get("ToTag");
 | 
						|
	if (ToTag) mToTag = strdup(ToTag);
 | 
						|
 | 
						|
	const char* ToUsername = params.get("ToUsername");
 | 
						|
	if (ToUsername) mToUsername = strdup(ToUsername);
 | 
						|
 | 
						|
	const char* ToIP = params.get("ToIP");
 | 
						|
	if (ToIP) mToIP = strdup(ToIP);
 | 
						|
 | 
						|
	const char* CSeq = params.get("CSeq");
 | 
						|
	if (CSeq) mCSeq = atoi(CSeq);
 | 
						|
 | 
						|
	const char * CallID = params.get("CallID");
 | 
						|
	if (CallID) mCallID = CallID;
 | 
						|
	mSIP.callID(CallID);
 | 
						|
 | 
						|
	const char * CallIP = params.get("CallIP");
 | 
						|
	if (CallIP) mCallIP = CallIP;
 | 
						|
 | 
						|
	const char * RTPState = params.get("RTPState");
 | 
						|
	if (RTPState) mRTPState = RTPState;
 | 
						|
 | 
						|
	const char * SessionID = params.get("SessionID");
 | 
						|
	if (SessionID) mSessionID = SessionID;
 | 
						|
 | 
						|
	const char * SessionVersion = params.get("SessionVersion");
 | 
						|
	if (SessionVersion) mSessionVersion = SessionVersion;
 | 
						|
 | 
						|
	const char * RTPRemPort = params.get("RTPRemPort");
 | 
						|
	if (RTPRemPort) mRTPRemPort = atoi(RTPRemPort);
 | 
						|
 | 
						|
	const char * RTPRemIP = params.get("RTPRemIP");
 | 
						|
	if (RTPRemIP) mRTPRemIP = RTPRemIP;
 | 
						|
 | 
						|
	const char * RmtIP = params.get("RmtIP");
 | 
						|
	if (RmtIP) mRmtIP = RmtIP;
 | 
						|
 | 
						|
	const char * RmtPort = params.get("RmtPort");
 | 
						|
	if (RmtPort) mRmtPort = atoi(RmtPort);
 | 
						|
 | 
						|
	const char * SRIMSI = params.get("SRIMSI");
 | 
						|
	if (SRIMSI) mSRIMSI = SRIMSI;
 | 
						|
 | 
						|
	const char * SRCALLID = params.get("SRCALLID");
 | 
						|
	if (SRCALLID) mSRCALLID = SRCALLID;
 | 
						|
 | 
						|
	initTimers();
 | 
						|
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
TransactionEntry::~TransactionEntry()
 | 
						|
{
 | 
						|
	// This should go out of scope before the object is actually destroyed.
 | 
						|
	ScopedLock lock(mLock);
 | 
						|
 | 
						|
	// Remove any FIFO from the gPeerInterface.
 | 
						|
	gPeerInterface.removeFIFO(mID);
 | 
						|
	// 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;
 | 
						|
	// Bad handover?
 | 
						|
	if (lSIPState==SIP::HandoverInbound) 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..
 | 
						|
	if (sqlite3_command(gTransactionTable.DB(),query,mNumSQLTries)) 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::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;
 | 
						|
}
 | 
						|
 | 
						|
string TransactionEntry::handoverString() 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 (mRemoved) throw RemovedTransaction(mID);
 | 
						|
	ScopedLock lock(mLock);
 | 
						|
	ostringstream os;
 | 
						|
	os << mID;
 | 
						|
	os << " IMSI=" << mSubscriber.digits();
 | 
						|
	if (mGSMState==GSM::HandoverInbound) os << " inbound-ref=" << mInboundReference;
 | 
						|
	if (mGSMState==GSM::HandoverOutbound) os << " outbound-ref=" << mOutboundReference.value();
 | 
						|
	os << " L3TI=" << mL3TI;
 | 
						|
	if (mCalled.digits()[0]) os << " called=" << mCalled.digits();
 | 
						|
	if (mCalling.digits()[0]) os << " calling=" << mCalling.digits();
 | 
						|
 | 
						|
	osip_message_t *ok = mSIP.LastResponse();
 | 
						|
	if (!ok) ok = mSIP.INVITE();
 | 
						|
	osip_cseq_t *cseq = osip_message_get_cseq(ok);
 | 
						|
 | 
						|
	char *cseqStr;
 | 
						|
	osip_cseq_to_str(cseq, &cseqStr);
 | 
						|
	os << " CSeq=" << cseqStr;
 | 
						|
	// FIXME - this should be extracted from a= attribute of sdp message
 | 
						|
	os << " codec=" << SIP::RTPGSM610;
 | 
						|
 | 
						|
	os << " CallID=" << osip_call_id_get_number(ok->call_id);
 | 
						|
	if (osip_call_id_get_host(ok->call_id)) {
 | 
						|
		os << " CallIP=" << osip_call_id_get_host(ok->call_id);
 | 
						|
	} else {
 | 
						|
		os << " CallIP=";
 | 
						|
	}
 | 
						|
 | 
						|
	const char *fromLabel = " From";
 | 
						|
	const char *toLabel = " To";
 | 
						|
	// FIXME? - is there a better way to detect moc vs mtc?
 | 
						|
	if (!mSIP.LastResponse()) {
 | 
						|
		fromLabel = " To";
 | 
						|
		toLabel = " From";
 | 
						|
	}
 | 
						|
	osip_from_t *from = osip_message_get_from(ok);
 | 
						|
	char *fromStr;
 | 
						|
	osip_from_to_str(from, &fromStr);
 | 
						|
	char *fromTag = index(fromStr, ';');
 | 
						|
	// FIXME? - is there a better way to get the tag?
 | 
						|
	os << " " << fromLabel << "Tag=" << fromTag+5;
 | 
						|
	os << " " << fromLabel << "Username=" << osip_uri_get_username(ok->from->url);
 | 
						|
	os << " " << fromLabel << "IP=" << osip_uri_get_host(ok->from->url);
 | 
						|
	osip_to_t *to = osip_message_get_to(ok);
 | 
						|
	char *toStr;
 | 
						|
	osip_to_to_str(to, &toStr);
 | 
						|
	char *toTag = index(toStr, ';');
 | 
						|
	// FIXME? - is there a better way to get the tag?
 | 
						|
	os << " " << toLabel << "Tag=" << toTag+5;
 | 
						|
	os << " " << toLabel << "Username=" << osip_uri_get_username(ok->to->url);
 | 
						|
	os << " " << toLabel << "IP=" << osip_uri_get_host(ok->to->url);
 | 
						|
 | 
						|
	// FIXME? - is there a better way to extract this info?
 | 
						|
	osip_body_t * osipBodyT;
 | 
						|
	osip_message_get_body (ok, 0, &osipBodyT);
 | 
						|
	char *osipBodyTStr;
 | 
						|
	size_t osipBodyTStrLth;
 | 
						|
	osip_body_to_str (osipBodyT, &osipBodyTStr, &osipBodyTStrLth);
 | 
						|
	char *SessionIDStr = index(osipBodyTStr, ' ')+1;
 | 
						|
	char *SessionVersionStr = index(SessionIDStr, ' ')+1;
 | 
						|
	long SessionID = strtol(SessionIDStr, NULL, 10);
 | 
						|
	long SessionVersion = strtol(SessionVersionStr, NULL, 10)+1;
 | 
						|
	os << " SessionID=" << SessionID;
 | 
						|
	os << " SessionVersion=" << SessionVersion;
 | 
						|
 | 
						|
	// getting the remote port from the m= line of the OK
 | 
						|
	char d_ip_addr[20];
 | 
						|
	char d_port[10];
 | 
						|
	SIP::get_rtp_params(ok, d_port, d_ip_addr);
 | 
						|
	os << " RTPRemIP=" << d_ip_addr;
 | 
						|
	os << " RTPRemPort=" << d_port;
 | 
						|
 | 
						|
	// proxy
 | 
						|
	os << " Proxy=" << mSIP.proxyIP() << ":" << mSIP.proxyPort();
 | 
						|
 | 
						|
	// remote ip and port
 | 
						|
	osip_contact_t * con = (osip_contact_t*)osip_list_get(&ok->contacts, 0);
 | 
						|
	os << " RmtIP=" << osip_uri_get_host(con->url);
 | 
						|
	os << " RmtPort=" << osip_uri_get_port(con->url);
 | 
						|
 | 
						|
	os << " RTPState=" <<
 | 
						|
		mSIP.RTPSession()->rtp.snd_time_offset << "," <<
 | 
						|
		mSIP.RTPSession()->rtp.snd_ts_offset << "," <<
 | 
						|
		mSIP.RTPSession()->rtp.snd_rand_offset << "," <<
 | 
						|
		mSIP.RTPSession()->rtp.snd_last_ts << "," <<
 | 
						|
		mSIP.RTPSession()->rtp.rcv_time_offset << "," <<
 | 
						|
		mSIP.RTPSession()->rtp.rcv_ts_offset << "," <<
 | 
						|
		mSIP.RTPSession()->rtp.rcv_query_ts_offset << "," <<
 | 
						|
		mSIP.RTPSession()->rtp.rcv_last_ts << "," <<
 | 
						|
		mSIP.RTPSession()->rtp.rcv_last_app_ts << "," <<
 | 
						|
		mSIP.RTPSession()->rtp.rcv_last_ret_ts << "," <<
 | 
						|
		mSIP.RTPSession()->rtp.hwrcv_extseq << "," <<
 | 
						|
		mSIP.RTPSession()->rtp.hwrcv_seq_at_last_SR << "," <<
 | 
						|
		mSIP.RTPSession()->rtp.hwrcv_since_last_SR << "," <<
 | 
						|
		mSIP.RTPSession()->rtp.last_rcv_SR_ts << "," <<
 | 
						|
		mSIP.RTPSession()->rtp.last_rcv_SR_time.tv_sec << "," << mSIP.RTPSession()->rtp.last_rcv_SR_time.tv_usec << "," << 
 | 
						|
		mSIP.RTPSession()->rtp.snd_seq << "," <<
 | 
						|
		mSIP.RTPSession()->rtp.last_rtcp_report_snt_r << "," <<
 | 
						|
		mSIP.RTPSession()->rtp.last_rtcp_report_snt_s << "," <<
 | 
						|
		mSIP.RTPSession()->rtp.rtcp_report_snt_interval << "," <<
 | 
						|
		mSIP.RTPSession()->rtp.last_rtcp_packet_count << "," <<
 | 
						|
		mSIP.RTPSession()->rtp.sent_payload_bytes;
 | 
						|
 | 
						|
	return os.str();
 | 
						|
}
 | 
						|
 | 
						|
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";
 | 
						|
	}
 | 
						|
	// Set high-concurrency WAL mode.
 | 
						|
	if (!sqlite3_command(mDB,enableWAL)) {
 | 
						|
		LOG(ALERT) << "Cannot enable WAL mode on database at " << path << ", error message: " << sqlite3_errmsg(mDB);
 | 
						|
	}
 | 
						|
	// Clear any previous entires.
 | 
						|
	if (!sqlite3_command(gTransactionTable.DB(),"DELETE FROM TRANSACTION_TABLE"))
 | 
						|
		LOG(WARNING) << "cannot clear previous transaction table";
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
 | 
						|
void TransactionEntry::setOutboundHandover(
 | 
						|
	const GSM::L3HandoverReference& reference,
 | 
						|
	const GSM::L3CellDescription& cell,
 | 
						|
	const GSM::L3ChannelDescription2& chan,
 | 
						|
	const GSM::L3PowerCommandAndAccessType& pwrCmd,
 | 
						|
	const GSM::L3SynchronizationIndication& synch
 | 
						|
		)
 | 
						|
{
 | 
						|
	if (mRemoved) throw RemovedTransaction(mID);
 | 
						|
	ScopedLock lock(mLock);
 | 
						|
	mOutboundReference = reference;
 | 
						|
	mOutboundCell = cell;
 | 
						|
	mOutboundChannel = chan;
 | 
						|
	mOutboundPowerCmd = pwrCmd;
 | 
						|
	mOutboundSynch = synch;
 | 
						|
	GSMState(GSM::HandoverOutbound);
 | 
						|
	return;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
void TransactionEntry::setInboundHandover(float RSSI, float timingError, double timestamp)
 | 
						|
{
 | 
						|
	if (mRemoved) throw RemovedTransaction(mID);
 | 
						|
	ScopedLock lock(mLock);
 | 
						|
	mChannel->setPhy(RSSI,timingError,timestamp);
 | 
						|
	mInboundRSSI = RSSI;
 | 
						|
	mInboundTimingError = timingError;
 | 
						|
}
 | 
						|
 | 
						|
	
 | 
						|
 | 
						|
 | 
						|
 | 
						|
 | 
						|
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::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->HandoverOtherBSTransactionID() != transactionID) 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->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;
 | 
						|
}
 | 
						|
 | 
						|
TransactionEntry* TransactionTable::inboundHandover(unsigned ref)
 | 
						|
{
 | 
						|
	// 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::HandoverInbound) continue;
 | 
						|
		if (itr->second->inboundReference() == ref) {
 | 
						|
			return itr->second;
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return NULL;
 | 
						|
}
 | 
						|
 | 
						|
TransactionEntry* TransactionTable::inboundHandover(const GSM::LogicalChannel* chan)
 | 
						|
{
 | 
						|
	// 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::HandoverInbound) continue;
 | 
						|
		if (itr->second->channel() == chan) return itr->second;
 | 
						|
	}
 | 
						|
	return NULL;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
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;
 | 
						|
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
 | 
						|
#if 0
 | 
						|
bool TransactionTable::outboundReferenceUsed(unsigned ref)
 | 
						|
{
 | 
						|
	// Called is expected to hold mLock.
 | 
						|
	for (TransactionMap::iterator itr = mTable.begin(); itr!=mTable.end(); ++itr) {
 | 
						|
		if (itr->second->deadOrRemoved()) continue;
 | 
						|
		if (itr->second->GSMState() != GSM::HandoverOutbound) continue;
 | 
						|
		if (itr->second->handoverReference() == ref) return true;
 | 
						|
	}
 | 
						|
	return false;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
unsigned TransactionTable::generateHandoverReference(TransactionEntry *transaction)
 | 
						|
{
 | 
						|
	ScopedLock lock(mLock);
 | 
						|
	clearDeadEntries();
 | 
						|
	unsigned ref = random() % 256;
 | 
						|
	while (outboundReferenceUsed(ref)) { ref = (ref+1) % 256; }
 | 
						|
	transaction->handoverReference(ref);
 | 
						|
	return ref;
 | 
						|
}
 | 
						|
#endif
 | 
						|
 | 
						|
 | 
						|
// vim: ts=4 sw=4
 |