laforge
/
openbts-osmo
Archived
1
0
Fork 0
This repository has been archived on 2022-03-30. You can view files and clone it, but cannot push or open issues or pull requests.
openbts-osmo/public-trunk/Control/ControlCommon.cpp

1353 lines
35 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 <CLIParser.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>
#include <Regexp.h>
using namespace std;
using namespace GSM;
using namespace Control;
using namespace CommandLine;
// The global transaction table.
TransactionTable gTransactionTable;
// The global TMSI table.
TMSITable gTMSITable;
ostream& Control::operator<<(ostream& os, USSDData::USSDMessageType type)
{
switch (type) {
case USSDData::REGrequest: os << "register request"; break;
case USSDData::request: os << "request"; break;
case USSDData::notify: os << "notify"; break;
case USSDData::response: os << "response"; break;
case USSDData::error: os << "error"; break;
case USSDData::release: os << "release"; break;
default: os << "?" << (int)type << "?";
}
return os;
}
ostream& Control::operator<<(ostream& os, const USSDData& data)
{
os << "USSD message type: " << data.MessageType();
os << "USSD string: " << data.USSDString();
return os;
}
TransactionEntry::TransactionEntry()
:mID(gTransactionTable.newID()),
mQ931State(NullState),
mUSSDData(NULL),
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),
mUSSDData(NULL),
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),
mUSSDData(NULL),
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),
mUSSDData(NULL),
mT301(T301ms), mT302(T302ms), mT303(T303ms),
mT304(T304ms), mT305(T305ms), mT308(T308ms),
mT310(T310ms), mT313(T313ms),
mT3113(gConfig.getNum("GSM.T3113")),
mTR1M(TR1Mms)
{
}
/** This form is used for USSD. */
TransactionEntry::TransactionEntry(const GSM::L3MobileIdentity& wSubscriber,
const GSM::L3CMServiceType& wService,
unsigned wTIFlag,
unsigned wTIValue,
USSDData* wUSSDData)
:mID(gTransactionTable.newID()),
mSubscriber(wSubscriber),mService(wService),
mTIFlag(wTIFlag),mTIValue(wTIValue),
mQ931State(NullState),
mUSSDData(wUSSDData),
mT301(T301ms), mT302(T302ms), mT303(T303ms),
mT304(T304ms), mT305(T305ms), mT308(T308ms),
mT310(T310ms), mT313(T313ms),
mT3113(gConfig.getNum("GSM.T3113")),
mTR1M(GSM::TR1Mms)
{
mMessage[0]='\0';
}
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;
case TransactionEntry::USSDworking: os << "USSD working"; break;
case TransactionEntry::USSDclosing: os << "USSD closing"; 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 << " USSDData=" << entry.ussdData();
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;
}
bool TransactionTable::find(const L3MobileIdentity& mobileID, GSM::L3CMServiceType serviceType, 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 && transaction.service()==serviceType) {
// 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);
}
USSDHandler::ResultCode USSDHandler::waitUSSDData(Control::USSDData::USSDMessageType* messageType,
std::string* USSDString,
unsigned timeout)
{
TransactionEntry transaction;
USSDData *pUssdData = NULL;
// Step 1 -- Find transaction
if (!gTransactionTable.find(mTransactionID, transaction))
{
LOG(DEBUG) << "Transaction with ID=" << mTransactionID << " not found";
return USSD_NO_TRANSACTION;
}
if ((pUssdData = transaction.ussdData()) == NULL)
{
LOG(DEBUG) << "Transaction has no USSD data: " << transaction;
return USSD_BAD_STATE;
}
// Step 2 -- Wait for MS to signal that data is ready
if (timeout >= USSDHandler::infinitely)
{
// wait infinitely
if (pUssdData->waitMS()!=0)
{
LOG(ERROR) << "USSDDate semaphore returned error: " << errno;
return USSD_ERROR;
}
}
else if ((timeout > USSDHandler::trywait) && (timeout < USSDHandler::infinitely))
{
// wait
if (pUssdData->waitMS(timeout)!=0)
{
LOG(DEBUG) << "USSDDate semaphore returned error or timeout";
return USSD_TIMEOUT;
}
}
else
{
// trywait
if (pUssdData->trywaitMS()!=0)
{
LOG(DEBUG) << "USSDDate semaphore returned error";
return USSD_TIMEOUT;
}
}
// Step 3 -- Get data from MS and check for closing condition
if (!gTransactionTable.find(mTransactionID, transaction))
{
LOG(DEBUG) << "Transaction with ID=" << mTransactionID << " not found";
return USSD_NO_TRANSACTION;
}
if ((pUssdData = transaction.ussdData()) == NULL)
{
LOG(DEBUG) << "Transaction has no USSD data: " << transaction;
return USSD_BAD_STATE;
}
if (transaction.Q931State() == Control::TransactionEntry::USSDclosing)
{
clearTransactionHistory(transaction);
LOG(DEBUG) << "Clearing USSD transaction: " << transaction;
return USSD_CLEARED;
}
*messageType = pUssdData->MessageType();
*USSDString = pUssdData->USSDString();
return USSD_OK;
}
USSDHandler::ResultCode USSDHandler::postUSSDData(Control::USSDData::USSDMessageType messageType,
const std::string &iUSSDString)
{
// Step 0 -- Prepare long strings for continuation
std::string USSDString(iUSSDString);
if (USSDString.length()>USSD_MAX_CHARS_7BIT)
{
int contLen = mContinueStr.length();
mString = USSDString.substr(USSD_MAX_CHARS_7BIT-contLen,
USSDString.length()-(USSD_MAX_CHARS_7BIT-contLen));
USSDString.erase(USSDString.begin()+USSD_MAX_CHARS_7BIT-contLen, USSDString.end());
USSDString += mContinueStr;
}
else
{
mString = "";
}
// Step 1 -- Find transaction
TransactionEntry transaction;
USSDData *pUssdData = NULL;
if (!gTransactionTable.find(mTransactionID, transaction))
{
LOG(DEBUG) << "Transaction with ID=" << mTransactionID << " not found";
return USSD_NO_TRANSACTION;
}
if ((pUssdData = transaction.ussdData()) == NULL)
{
LOG(DEBUG) << "Transaction has no USSD data: " << transaction;
return USSD_BAD_STATE;
}
// Step 2 -- Update transaction with the data to send
pUssdData->MessageType(messageType);
pUssdData->USSDString(USSDString);
gTransactionTable.update(transaction);
// Step 3 -- Notify the dispatcher thread that data is ready to be sent
if (pUssdData->postNW() != 0)
{
return USSD_ERROR;
}
// Success.
return USSD_OK;
}
void *USSDHandler::runWrapper(void *pThis)
{
((USSDHandler*)pThis)->run();
return NULL;
}
void MOTestHandler::run()
{
LOG(DEBUG) << "USSD MO Test Handler RUN";
while(true)
{
Control::USSDData::USSDMessageType messageType;
std::string USSDString;
if (waitUSSDData(&messageType, &USSDString, gConfig.getNum("USSD.timeout"))) break;
if (USSDString == ">")
{
if (mString == "")
{
messageType = USSDData::release;
}
else
{
USSDString = mString;
messageType = USSDData::request;
}
}
else if (USSDString == "*100#")
{
USSDString = "handle response ";
messageType = USSDData::response;
}
else if(USSDString == "*101#")
{
USSDString = "handle request String objects are a special type of container, specifically designed to operate with sequences of characters. Unlike traditional c-strings, which are mere sequences of characters in a memory array, C++ string objects belong to a class with many built-in features to operate with strings in a more intuitive way and with some additional useful features common to C++ containers. The string class is an instantiation of the basic_string class template, defined in string as:";
messageType = USSDData::request;
}
else if(USSDString == "*1011#")
{
USSDString = "handle request";
messageType = USSDData::request;
}
else if(USSDString == "*102#")
{
USSDString = "handle notify";
messageType = USSDData::notify;
}
else if(USSDString == "*103#")
{
USSDString = "";
messageType = USSDData::release;
}
else if(USSDString == "*104#")
{
messageType = USSDData::error;
}
else
{
messageType = USSDData::release;
}
postUSSDData(messageType, USSDString);
}
}
void MOHttpHandler::run()
{
LOG(DEBUG) << "USSD MO Http Handler RUN";
while(true)
{
Control::USSDData::USSDMessageType messageType;
std::string USSDString;
if (waitUSSDData(&messageType, &USSDString, gConfig.getNum("USSD.timeout"))) break;
if (USSDString == ">")
{
if (mString == "")
{
messageType = USSDData::release;
}
else
{
USSDString = mString;
messageType = USSDData::request;
}
}
else if(USSDString == "*101#")
{
USSDString = "send command";
messageType = USSDData::request;
}
else
{
char command[2048];
sprintf(command,"wget -T 5 -q -O - \"http://%s/http/%s&to=%s&text=%s\"",
gConfig.getStr("USSD.HTTP.Gateway"),
gConfig.getStr("USSD.HTTP.AccessString"),
"server", USSDString.c_str());
LOG(NOTICE) << "MOUSSD: send HTTP sending with " << command;
// HTTP "GET" method with wget.
char mystring [182];
FILE* wget = popen(command,"r");
if (!wget) {
LOG(NOTICE) << "cannot open wget with " << command;
USSDString = "cannot open wget";
messageType = USSDData::error;
}
fgets (mystring , 182 , wget);
LOG(NOTICE) << "wget response " << mystring;
std::string tmpStr(mystring);
USSDString = tmpStr;
messageType = USSDData::request;
}
postUSSDData(messageType, USSDString);
}
}
void MOCLIHandler::run()
{
LOG(DEBUG) << "USSD MO CLI Handler RUN";
while(true)
{
Control::USSDData::USSDMessageType messageType;
std::string USSDString;
if (waitUSSDData(&messageType, &USSDString, gConfig.getNum("USSD.timeout"))) break;
if (USSDString == ">")
{
if (mString == "")
{
messageType = USSDData::release;
}
else
{
USSDString = mString;
messageType = USSDData::request;
}
}
else if(USSDString == "*101#")
{
USSDString = "send command";
messageType = USSDData::request;
}
else
{
const char* line;
line = USSDString.c_str();
std::ostringstream os;
std::istringstream is;
gParser.process(line, os);
LOG(INFO) << "Running line \"" << line << "\" returned result \"" << os.str() << "\"";
USSDString = os.str();
messageType = USSDData::request;
}
postUSSDData(messageType, USSDString);
}
}
void MTTestHandler::run()
{
LOG(DEBUG) << "USSD MT Test Handler RUN";
while(true)
{
Control::USSDData::USSDMessageType messageType;
std::string USSDString;
if (waitUSSDData(&messageType, &USSDString, gConfig.getNum("USSD.timeout"))) break;
if(messageType == USSDData::REGrequest)
{
USSDString = "REGrequest message";
}
else if(messageType == USSDData::response)
{
if (USSDString == "111")
{
USSDString = "release message";
messageType = USSDData::release;
}
else if (USSDString == "100")
{
USSDString = "request message";
messageType = USSDData::request;
}
else if (USSDString == "101")
{
messageType = USSDData::error;
}
else if (USSDString == "102")
{
USSDString = "notify message";
messageType = USSDData::notify;
}
}
else
{
USSDString = "release message";
messageType = USSDData::release;
}
postUSSDData(messageType, USSDString);
}
}
void UssdSipHandler::run()
{
LOG(DEBUG) << "USSD SIP Handler RUN";
mNum = 0;
while(true)
{
Control::USSDData::USSDMessageType messageType;
std::string USSDString;
unsigned timeout = 100000;
// Get next data string from ME
ResultCode result = waitUSSDData(&messageType, &USSDString, timeout);
if (result != USSD_OK)
{
// Operation has been canceled by ME or timed out.
// Finish USSD session if it hasn't been not cleared in waitUSSDData();
postUSSDData(USSDData::error, "");
// TODO:: Send error to SIP side too?
break;
}
if (USSDString == ">")
{
if (mString == "")
{
messageType = USSDData::release;
}
else
{
USSDString = mString;
messageType = USSDData::request;
}
} else {
// Steps:
// 1 -- Setup SIP part of the transaction record.
// 2 -- Send the message to the server.
// 3 -- Wait for a response message and parse it.
// Step 1 -- Setup SIP part of the transaction record.
TransactionEntry transaction;
if (!gTransactionTable.find(transactionID(), transaction))
{
// Transaction not found. Something is wrong. Bail out.
break;
}
SIP::SIPEngine& engine = transaction.SIP();
// If we got a TMSI, find the IMSI.
L3MobileIdentity mobileID = transaction.subscriber();
if (mobileID.type()==TMSIType) {
const char *IMSI = gTMSITable.IMSI(mobileID.TMSI());
if (IMSI) mobileID = L3MobileIdentity(IMSI);
else {
// Something is wrong on the ME side.
postUSSDData(USSDData::error, "");
break;
}
}
engine.User(mobileID.digits());
LOG(DEBUG) << "MOUSSD: transaction: " << transaction;
// Step 2 -- Send the message to the server.
std::ostringstream outSipBody;
outSipBody << (int)messageType << std::endl << USSDString;
LOG(DEBUG) << "Created USSD SIP message: " << outSipBody.str();
engine.MOSMSSendMESSAGE(gConfig.getStr("USSD.SIP.user"),
gConfig.getStr("USSD.SIP.domain"),
outSipBody.str().c_str(), true);
SIP::SIPState state = engine.MOSMSWaitForSubmit();
LOG(DEBUG) << "Clearing call ID " << engine.callID()
<< " from transaction " << transaction.ID();
gSIPInterface.removeCall(engine.callID());
if (state != SIP::Cleared)
{
// Something is wrong on the SIP side.
postUSSDData(USSDData::error, "");
break;
}
// Step 3 -- Wait for a response SIP message and ACK it.
transaction.ussdData()->waitIncomingData(/*TODO:: timeout */);
engine.MTSMSSendOK();
LOG(DEBUG) << "Clearing call ID " << engine.callID()
<< " from transaction " << transaction.ID();
gSIPInterface.removeCall(engine.callID());
// Step 4 -- Get response and parse it.
if (!gTransactionTable.find(transactionID(), transaction))
{
// Transaction not found. Something is wrong. Bail out.
break;
}
std::istringstream inSipBody(transaction.message());
std::stringbuf messageText;
int tmp;
inSipBody >> tmp >> &messageText;
messageType = (Control::USSDData::USSDMessageType)tmp;
USSDString = messageText.str();
LOG(DEBUG) << "Parsed USSD server response. messageType=" << messageType
<< "(" << tmp << ")"
<< " string=\"" << USSDString << "\"";
}
postUSSDData(messageType, USSDString);
}
}
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<L3IdentityResponse*>(msg);
if (!resp) {
L3GPRSSuspensionRequest *r = dynamic_cast<L3GPRSSuspensionRequest*>(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<L3IdentityResponse*>(msg);
if (!resp) {
L3GPRSSuspensionRequest *r = dynamic_cast<L3GPRSSuspensionRequest*>(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;
}
}
bool Control::USSDMatchHandler(const std::string &handlerName, const std::string &ussdString)
{
std::string handlerKeyName("USSD.Handler.");
handlerKeyName += handlerName;
if (gConfig.defines(handlerKeyName))
{
std::string handlerRegexpStr = gConfig.getStr(handlerKeyName);
Regexp handlerRegexp(handlerRegexpStr.data());
if (handlerRegexp.match(ussdString.data()))
{
LOG(DEBUG) << "Request " << ussdString << " matches regexp \""
<< handlerRegexpStr << "\" for USSD handler " << handlerName;
return true;
}
}
return false;
}
unsigned Control::USSDDispatcher(GSM::L3MobileIdentity &mobileIdentity,
unsigned TIFlag,
unsigned TIValue,
Control::USSDData::USSDMessageType messageType,
const std::string &ussdString,
bool MO)
{
TransactionEntry transaction(mobileIdentity, GSM::L3CMServiceType::SupplementaryService, TIFlag, TIValue, new USSDData(messageType));
gTransactionTable.add(transaction);
LOG(DEBUG) << "USSD Dispatcher";
transaction.ussdData()->USSDString(ussdString);
if (MO)
{
//MO
transaction.Q931State(Control::TransactionEntry::USSDworking);
transaction.ussdData()->postMS();
gTransactionTable.update(transaction);
Thread* thread = new Thread;
if (USSDMatchHandler("HTTP", ussdString))
{
MOHttpHandler* handler = new MOHttpHandler(transaction.ID());
thread->start((void*(*)(void*))USSDHandler::runWrapper, handler);
}
else if (USSDMatchHandler("CLI", ussdString))
{
MOCLIHandler* handler = new MOCLIHandler(transaction.ID());
thread->start((void*(*)(void*))USSDHandler::runWrapper, handler);
}
else if (USSDMatchHandler("Test", ussdString))
{
MOTestHandler* handler = new MOTestHandler(transaction.ID());
thread->start((void*(*)(void*))USSDHandler::runWrapper, handler);
}
else if (USSDMatchHandler("SIP", ussdString))
{
UssdSipHandler* handler = new UssdSipHandler(transaction.ID());
thread->start((void*(*)(void*))USSDHandler::runWrapper, handler);
}
else
{
MOTestHandler* handler = new MOTestHandler(transaction.ID());
thread->start((void*(*)(void*))USSDHandler::runWrapper, handler);
}
}
else
{
//MT
transaction.ussdData()->postNW();
transaction.Q931State(Control::TransactionEntry::Paging);
//gTransactionTable.add(transaction);
gTransactionTable.update(transaction);
LOG(DEBUG) << "USSD Start Paging";
gBTS.pager().addID(transaction.subscriber(),GSM::SDCCHType,transaction);
}
return transaction.ID();
}
// vim: ts=4 sw=4