/**@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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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 *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