/**@file Common-use functions for the control layer. */ /* * Copyright 2008, 2010 Free Software Foundation, Inc. * * This software is distributed under the terms of the GNU Affero Public License. * See the COPYING file in the main directory for details. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 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. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ #include "ControlCommon.h" #include #include #include #include #include #include #include #include using namespace std; using namespace GSM; using namespace Control; // The global transaction table. TransactionTable gTransactionTable; // The global TMSI table. TMSITable gTMSITable; TransactionEntry::TransactionEntry() :mID(gTransactionTable.newID()), mQ931State(NullState), mT301(T301ms), mT302(T302ms), mT303(T303ms), mT304(T304ms), mT305(T305ms), mT308(T308ms), mT310(T310ms), mT313(T313ms), mT3113(gConfig.getNum("GSM.T3113")), mTR1M(TR1Mms) { } // Form for MT transactions. TransactionEntry::TransactionEntry(const L3MobileIdentity& wSubscriber, const L3CMServiceType& wService, const L3CallingPartyBCDNumber& wCalling, const char *wMessage) :mID(gTransactionTable.newID()), mSubscriber(wSubscriber),mService(wService), mTIFlag(1), mTIValue(0), mCalling(wCalling), mQ931State(NullState), mT301(T301ms), mT302(T302ms), mT303(T303ms), mT304(T304ms), mT305(T305ms), mT308(T308ms), mT310(T310ms), mT313(T313ms), mT3113(gConfig.getNum("GSM.T3113")), mTR1M(TR1Mms) { if (wMessage) mMessage = wMessage; } // Form for MO transactions. TransactionEntry::TransactionEntry(const L3MobileIdentity& wSubscriber, const L3CMServiceType& wService, unsigned wTIValue, const L3CalledPartyBCDNumber& wCalled) :mID(gTransactionTable.newID()), mSubscriber(wSubscriber),mService(wService), mTIFlag(0), mTIValue(wTIValue), mCalled(wCalled), mQ931State(NullState), mT301(T301ms), mT302(T302ms), mT303(T303ms), mT304(T304ms), mT305(T305ms), mT308(T308ms), mT310(T310ms), mT313(T313ms), mT3113(gConfig.getNum("GSM.T3113")), mTR1M(TR1Mms) { } // Form for MT transactions. TransactionEntry::TransactionEntry(const L3MobileIdentity& wSubscriber, const L3CMServiceType& wService, unsigned wTIValue, const L3CallingPartyBCDNumber& wCalling) :mID(gTransactionTable.newID()), mSubscriber(wSubscriber),mService(wService), mTIFlag(1),mTIValue(wTIValue),mCalling(wCalling), mQ931State(NullState), mT301(T301ms), mT302(T302ms), mT303(T303ms), mT304(T304ms), mT305(T305ms), mT308(T308ms), mT310(T310ms), mT313(T313ms), mT3113(gConfig.getNum("GSM.T3113")), mTR1M(TR1Mms) { } bool TransactionEntry::timerExpired() const { // FIXME -- If we were smart, this would be a table. if (mT301.expired()) { OBJLOG(DEBUG) << "T301 expired"; return true; } if (mT302.expired()) { OBJLOG(DEBUG) << "T302 expired"; return true; } if (mT303.expired()) { OBJLOG(DEBUG) << "T303 expired"; return true; } if (mT304.expired()) { OBJLOG(DEBUG) << "T304 expired"; return true; } if (mT305.expired()) { OBJLOG(DEBUG) << "T305 expired"; return true; } if (mT308.expired()) { OBJLOG(DEBUG) << "T308 expired"; return true; } if (mT310.expired()) { OBJLOG(DEBUG) << "T310 expired"; return true; } if (mT313.expired()) { OBJLOG(DEBUG) << "T313 expired"; return true; } if (mTR1M.expired()) { OBJLOG(DEBUG) << " TR1M expired"; return true; } return false; } void TransactionEntry::resetTimers() { mT301.reset(); mT302.reset(); mT303.reset(); mT304.reset(); mT305.reset(); mT308.reset(); mT310.reset(); mT313.reset(); mTR1M.reset(); } bool TransactionEntry::dead() const { if (mQ931State==NullState) return true; if ((mQ931State==Paging) && mT3113.expired()) return true; return false; } ostream& Control::operator<<(ostream& os, TransactionEntry::Q931CallState state) { switch (state) { case TransactionEntry::NullState: os << "null"; break; case TransactionEntry::Paging: os << "paging"; break; case TransactionEntry::MOCInitiated: os << "MOC initiated"; break; case TransactionEntry::MOCProceeding: os << "MOC proceeding"; break; case TransactionEntry::MTCConfirmed: os << "MTC confirmed"; break; case TransactionEntry::CallReceived: os << "call received"; break; case TransactionEntry::CallPresent: os << "call present"; break; case TransactionEntry::ConnectIndication: os << "connect indication"; break; case TransactionEntry::Active: os << "active"; break; case TransactionEntry::DisconnectIndication: os << "disconnect indication"; break; case TransactionEntry::ReleaseRequest: os << "release request"; break; case TransactionEntry::SMSDelivering: os << "SMS delivery"; break; case TransactionEntry::SMSSubmitting: os << "SMS submission"; break; default: os << "?" << (int)state << "?"; } return os; } ostream& Control::operator<<(ostream& os, const TransactionEntry& entry) { os << entry.ID(); os << " TI=(" << entry.TIFlag() << "," << entry.TIValue() << ") "; os << entry.subscriber(); os << " " << entry.service(); if (entry.called().digits()[0]) os << " to=" << entry.called().digits(); if (entry.calling().digits()[0]) os << " from=" << entry.calling().digits(); os << " Q.931State=" << entry.Q931State(); os << " SIPState=" << entry.SIP().state(); os << " (" << (entry.stateAge()+500)/1000 << " sec)"; if (entry.message()[0]) os << " message=\"" << entry.message() << "\""; return os; } unsigned TransactionTable::newID() { mLock.lock(); unsigned ID = mIDCounter++; mLock.unlock(); return ID; } void TransactionTable::add(const TransactionEntry& value) { LOG(INFO) << "new transaction " << value; mLock.lock(); mTable[value.ID()]=value; mLock.unlock(); } void TransactionTable::update(const TransactionEntry& value) { // ID==0 is a non-valid special case. assert(value.ID()); mLock.lock(); if (mTable.find(value.ID())==mTable.end()) { mLock.unlock(); LOG(WARN) << "attempt to update non-existent transaction entry with key " << value.ID(); return; } mTable[value.ID()]=value; mLock.unlock(); } bool TransactionTable::find(unsigned key, TransactionEntry& target) { // ID==0 is a non-valid special case. assert(key); mLock.lock(); TransactionMap::iterator itr = mTable.find(key); if (itr==mTable.end()) { mLock.unlock(); return false; } if (itr->second.dead()) { mTable.erase(itr); mLock.unlock(); return false; } mLock.unlock(); target = itr->second; return true; } bool TransactionTable::remove(unsigned key) { // ID==0 is a non-valid special case. assert(key); mLock.lock(); bool retVal = mTable.erase(key); mLock.unlock(); return retVal; } void TransactionTable::clearDeadEntries() { mLock.lock(); 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++; mTable.erase(old); } } mLock.unlock(); } bool TransactionTable::find(const L3MobileIdentity& mobileID, TransactionEntry& target) { // Yes, it's linear time. // Even in a 6-ARFCN system, it should rarely be more than a dozen entries. // Since clearDeadEntries is also linear, do that here, too. // Brute force search. bool foundIt = false; mLock.lock(); clearDeadEntries(); TransactionMap::const_iterator itr = mTable.begin(); while (itr!=mTable.end()) { const TransactionEntry& transaction = itr->second; if (transaction.subscriber()==mobileID) { // No need to check dead(), since we just cleared the table. foundIt = true; target = transaction; break; } ++itr; } mLock.unlock(); return foundIt; } size_t TransactionTable::size() { return mTable.size(); } void TransactionTable::dump(ostream& os) const { mLock.lock(); TransactionMap::const_iterator tp = mTable.begin(); while (tp != mTable.end()) { os << hex << "0x" << tp->first << " " << dec << tp->second << endl; ++tp; } mLock.unlock(); } void Control::clearTransactionHistory( TransactionEntry& transaction ) { SIP::SIPEngine& engine = transaction.SIP(); LOG(DEBUG) << engine.callID()<<" "<< transaction.ID(); gSIPInterface.removeCall(engine.callID()); gTransactionTable.remove(transaction.ID()); } void Control::clearTransactionHistory(unsigned transactionID) { if (transactionID==0) return; TransactionEntry transaction; if (gTransactionTable.find(transactionID,transaction)) { clearTransactionHistory(transaction); } else { LOG(INFO) << "clearTransactionHistory didn't find " << transactionID << "(size = " << gTransactionTable.size() << ")"; } } void TMSIRecord::save(unsigned TMSI, FILE* fp) const { fprintf(fp, "%10u %10u %10u %15s %15s\n", TMSI, mCreated.sec(), mTouched.sec(), mIMSI.c_str(), mIMEI.c_str()); } unsigned TMSIRecord::load(FILE* fp) { unsigned TMSI=0; unsigned created, touched; char IMSI[16]; char IMEI[16]; int res = fscanf(fp, "%10u %10u %10u %15s %15s\n", &TMSI, &created, &touched, IMSI, IMEI); if (res == EOF) { return 0; } else if (res < 5 || created > touched) { LOG(ALARM) << "corrupt TMSI file"; return 0; } mIMSI = IMSI; mIMEI = IMEI; mTouched = Timeval(touched,0); mCreated = Timeval(created,0); return TMSI; } unsigned TMSITable::assign(const char* IMSI, const char* IMEI) { purge(); mLock.lock(); // Is this IMSI already in here? unsigned oldTMSI = TMSI(IMSI); if (oldTMSI) { mLock.unlock(); return oldTMSI; } unsigned TMSI = mCounter++; mMap[TMSI] = TMSIRecord(IMSI, IMEI); mLock.unlock(); if (gConfig.defines("Control.TMSITable.SavePath")) save(gConfig.getStr("Control.TMSITable.SavePath")); return TMSI; } bool TMSITable::setIMEI(unsigned TMSI, const std::string& IMEI) { mLock.lock(); TMSIMap::iterator iter = mMap.find(TMSI); if (iter==mMap.end()) { mLock.unlock(); return false; } iter->second.IMEI(IMEI); iter->second.touch(); mLock.unlock(); return true; } bool TMSITable::find(unsigned TMSI, TMSIRecord& target) { mLock.lock(); TMSIMap::iterator iter = mMap.find(TMSI); if (iter==mMap.end()) { mLock.unlock(); return false; } target = iter->second; // Is it too old? if (target.age() > 3600*gConfig.getNum("Control.TMSITable.MaxAge")) { mMap.erase(iter); mLock.unlock(); return false; } iter->second.touch(); mLock.unlock(); return true; } const char* TMSITable::IMSI(unsigned TMSI) const { mLock.lock(); TMSIMap::const_iterator iter = mMap.find(TMSI); mLock.unlock(); if (iter==mMap.end()) return NULL; iter->second.touch(); return iter->second.IMSI(); } unsigned TMSITable::TMSI(const char* IMSI) const { // FIXME -- If we were smart, we'd organize the table for a log-time search. unsigned TMSI = 0; string IMSIc(IMSI); mLock.lock(); // brute force search TMSIMap::const_iterator itr = mMap.begin(); while (itr!=mMap.end()) { if (itr->second.IMSI() == IMSIc) { TMSI = itr->first; itr->second.touch(); break; } ++itr; } mLock.unlock(); return TMSI; } void TMSITable::erase(unsigned TMSI) { mLock.lock(); TMSIMap::iterator iter = mMap.find(TMSI); if (iter!=mMap.end()) mMap.erase(iter); mLock.unlock(); } size_t TMSITable::size() const { mLock.lock(); size_t retVal = mMap.size(); mLock.unlock(); return retVal; } void TMSITable::purge() { mLock.lock(); // We rely on the fact the TMSIs are assigned in numeric order // to erase the oldest first. while ( (mMap.size()>gConfig.getNum("Control.TMSITable.MaxSize")) && (mClear!=mCounter) ) mMap.erase(mClear++); mLock.unlock(); } size_t printAge(unsigned seconds, char *buf) { static const unsigned k=5; if (secondsfirst << " " << dec << tp->second << endl; ++tp; } mLock.unlock(); } void TMSITable::save(const char* filename) const { FILE* fp = fopen(filename,"w"); if (!fp) { LOG(ALARM) << "TMSITable cannot open " << filename << " for writing"; return; } LOG(INFO) << "saving TMSI table to " << filename; mLock.lock(); TMSIMap::const_iterator tp = mMap.begin(); while (tp != mMap.end()) { tp->second.save(tp->first,fp); ++tp; } mLock.unlock(); fclose(fp); } void TMSITable::load(const char* filename) { FILE* fp = fopen(filename,"r"); if (!fp) { LOG(ALARM) << "TMSITable cannot open " << filename << " for reading"; return; } LOG(INFO) << "loading TMSI table from " << filename; mLock.lock(); while (!feof(fp)) { TMSIRecord val; unsigned key = val.load(fp); if (!key) break; mMap[key] = val; } mLock.unlock(); fclose(fp); } bool Control::waitForPrimitive(LogicalChannel *LCH, Primitive primitive, unsigned timeout_ms) { bool waiting = true; while (waiting) { L3Frame *req = LCH->recv(timeout_ms); if (req==NULL) { LOG(NOTICE) << "timeout at uptime " << gBTS.uptime() << " frame " << gBTS.time(); return false; } waiting = (req->primitive()!=primitive); delete req; } return true; } void Control::waitForPrimitive(LogicalChannel *LCH, Primitive primitive) { bool waiting = true; while (waiting) { L3Frame *req = LCH->recv(); if (req==NULL) continue; waiting = (req->primitive()!=primitive); delete req; } } // FIXME -- getMessage should return an L3Frame, not an L3Message. // This will mean moving all of the parsing into the control layer. // FIXME -- This needs an adjustable timeout. L3Message* Control::getMessage(LogicalChannel *LCH, unsigned SAPI) { unsigned timeout_ms = LCH->N200() * T200ms; L3Frame *rcv = LCH->recv(timeout_ms,SAPI); if (rcv==NULL) { LOG(NOTICE) << "timeout"; throw ChannelReadTimeout(); } LOG(DEBUG) << "received " << *rcv; Primitive primitive = rcv->primitive(); if (primitive!=DATA) { LOG(NOTICE) << "unexpected primitive " << primitive; delete rcv; throw UnexpectedPrimitive(); } L3Message *msg = parseL3(*rcv); delete rcv; if (msg==NULL) { LOG(NOTICE) << "unparsed message"; throw UnsupportedMessage(); } return msg; } /* Resolve a mobile ID to an IMSI and return TMSI if it is assigned. */ unsigned Control::resolveIMSI(bool sameLAI, L3MobileIdentity& mobID, LogicalChannel* LCH) { // Returns known or assigned TMSI. assert(LCH); LOG(DEBUG) << "resolving mobile ID " << mobID << ", sameLAI: " << sameLAI; // IMSI already? See if there's a TMSI already, too. // This is a linear time operation, but should only happen on // the first registration by this mobile. if (mobID.type()==IMSIType) return gTMSITable.TMSI(mobID.digits()); // IMEI? WTF?! // FIXME -- Should send MM Reject, cause 0x60, "invalid mandatory information". if (mobID.type()==IMEIType) throw UnexpectedMessage(); // Must be a TMSI. // Look in the table to see if it's one we assigned. unsigned TMSI = mobID.TMSI(); const char* IMSI = NULL; if (sameLAI) IMSI = gTMSITable.IMSI(TMSI); if (IMSI) { // We assigned this TMSI and the TMSI/IMSI pair is already in the table. mobID = L3MobileIdentity(IMSI); LOG(DEBUG) << "resolving mobile ID (table): " << mobID; return TMSI; } // Not our TMSI. // Phones are not supposed to do this, but many will. // If the IMSI's not in the table, ASK for it. LCH->send(L3IdentityRequest(IMSIType)); // FIXME -- This request times out on T3260, 12 sec. See GSM 04.08 Table 11.2. L3Message* msg = NULL; L3IdentityResponse *resp = NULL; while (msg = getMessage(LCH)) { resp = dynamic_cast(msg); if (!resp) { L3GPRSSuspensionRequest *r = dynamic_cast(msg); if (!r) { if (msg) delete msg; throw UnexpectedMessage(); } else { LOG(INFO) << "Ignored GPRS Suspension Request"; } } else { break; } } mobID = resp->mobileID(); LOG(INFO) << resp; delete msg; LOG(DEBUG) << "resolving mobile ID (requested): " << mobID; // FIXME -- Should send MM Reject, cause 0x60, "invalid mandatory information". if (mobID.type()!=IMSIType) throw UnexpectedMessage(); // Return 0 to indicate that we have not yet assigned our own TMSI for this phone. return 0; } /* Resolve a mobile ID to an IMSI. */ void Control::resolveIMSI(L3MobileIdentity& mobileIdentity, LogicalChannel* LCH) { // Are we done already? if (mobileIdentity.type()==IMSIType) return; // If we got a TMSI, find the IMSI. if (mobileIdentity.type()==TMSIType) { const char *IMSI = gTMSITable.IMSI(mobileIdentity.TMSI()); if (IMSI) mobileIdentity = L3MobileIdentity(IMSI); } // Still no IMSI? Ask for one. if (mobileIdentity.type()!=IMSIType) { LOG(NOTICE) << "MOC with no IMSI or valid TMSI. Reqesting IMSI."; LCH->send(L3IdentityRequest(IMSIType)); // FIXME -- This request times out on T3260, 12 sec. See GSM 04.08 Table 11.2. L3Message* msg = getMessage(LCH); L3IdentityResponse *resp = dynamic_cast(msg); if (!resp) { L3GPRSSuspensionRequest *r = dynamic_cast(msg); if (!r) { if (msg) delete msg; throw UnexpectedMessage(); } else { LOG(INFO) << "Ignored L3 RR GPRS Suspension Request."; if (msg) delete msg; return; } } mobileIdentity = resp->mobileID(); delete msg; } // Still no IMSI?? if (mobileIdentity.type()!=IMSIType) { // FIXME -- This is quick-and-dirty, not following GSM 04.08 5. LOG(WARN) << "MOC setup with no IMSI"; // Cause 0x60 "Invalid mandatory information" LCH->send(L3CMServiceReject(L3RejectCause(0x60))); LCH->send(L3ChannelRelease()); // The SIP side and transaction record don't exist yet. // So we're done. return; } } // vim: ts=4 sw=4