760 lines
18 KiB
C++
760 lines
18 KiB
C++
/**@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 <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
|
#include "ControlCommon.h"
|
|
|
|
#include <GSMLogicalChannel.h>
|
|
#include <GSML3Message.h>
|
|
#include <GSML3CCMessages.h>
|
|
#include <GSML3RRMessages.h>
|
|
#include <GSML3MMMessages.h>
|
|
#include <GSMConfig.h>
|
|
|
|
#include <SIPEngine.h>
|
|
#include <SIPInterface.h>
|
|
|
|
|
|
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 (seconds<k*60) return sprintf(buf,"%4us",seconds);
|
|
unsigned minutes = (seconds+30) / 60;
|
|
if (minutes<k*60) return sprintf(buf,"%4um",minutes);
|
|
unsigned hours = (minutes+30) / 60;
|
|
if (hours<k*24) return sprintf(buf,"%4uh", hours);
|
|
return sprintf(buf,"%4ud",(hours+12)/24);
|
|
}
|
|
|
|
ostream& Control::operator<<(ostream& os, const TMSIRecord& rec)
|
|
{
|
|
char buf[100];
|
|
char ageStr[10];
|
|
printAge(rec.age(),ageStr);
|
|
char touchedStr[10];
|
|
printAge(rec.touched(),touchedStr);
|
|
sprintf(buf,"%15s %15s %s %s", rec.IMSI(), rec.IMEI(), ageStr, touchedStr);
|
|
os << buf;
|
|
return os;
|
|
}
|
|
|
|
|
|
void TMSITable::dump(ostream& os) const
|
|
{
|
|
mLock.lock();
|
|
TMSIMap::const_iterator tp = mMap.begin();
|
|
while (tp != mMap.end()) {
|
|
os << hex << "0x" << tp->first << " " << 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 = getMessage(LCH);
|
|
L3IdentityResponse *resp = dynamic_cast<L3IdentityResponse*>(msg);
|
|
if (!resp) {
|
|
if (msg) delete msg;
|
|
throw UnexpectedMessage();
|
|
}
|
|
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<L3IdentityResponse*>(msg);
|
|
if (!resp) {
|
|
if (msg) delete msg;
|
|
throw UnexpectedMessage();
|
|
}
|
|
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
|