laforge
/
openbts-osmo
Archived
1
0
Fork 0

Merge commit 'USSD'

master
Ivan Kluchnikov 11 years ago
commit 20ea06422d
  1. 3
      public-trunk/CLI/CLI.cpp
  2. 14
      public-trunk/CLI/CLIParser.cpp
  3. 3
      public-trunk/CommonLibs/Threads.h
  4. 582
      public-trunk/Control/ControlCommon.cpp
  5. 220
      public-trunk/Control/ControlCommon.h
  6. 34
      public-trunk/Control/DCCHDispatch.cpp
  7. 3
      public-trunk/Control/Makefile.am
  8. 3
      public-trunk/Control/MobilityManagement.cpp
  9. 3
      public-trunk/Control/RadioResource.cpp
  10. 428
      public-trunk/Control/USSDControl.cpp
  11. 2
      public-trunk/GSM/GSML3Message.cpp
  12. 294
      public-trunk/GSM/GSML3NonCallSSComponents.cpp
  13. 270
      public-trunk/GSM/GSML3NonCallSSComponents.h
  14. 214
      public-trunk/GSM/GSML3NonCallSSElements.cpp
  15. 367
      public-trunk/GSM/GSML3NonCallSSElements.h
  16. 210
      public-trunk/GSM/GSML3NonCallSSMessages.cpp
  17. 186
      public-trunk/GSM/GSML3NonCallSSMessages.h
  18. 6
      public-trunk/GSM/Makefile.am
  19. 83
      public-trunk/SIP/SIPInterface.cpp
  20. 25
      public-trunk/apps/OpenBTS.config.example
  21. 13
      public-trunk/smqueue/smcommands.cpp

@ -27,6 +27,7 @@
#include <config.h>
#include "CLI.h"
#include "CLIParser.h"
#include <Logger.h>
#include <Globals.h>
@ -63,6 +64,7 @@ void CommandLine::runCLI(ParserBase *processor)
history_file_len + 1);
read_history(history_name);
}
}
#endif // HAVE_LIBREADLINE ]
@ -104,6 +106,7 @@ void CommandLine::runCLI(ParserBase *processor)
free(history_name);
history_name = 0;
}
#endif // HAVE_LIBREADLINE ]
}

@ -391,7 +391,18 @@ int sendrrlp(int argc, char** argv, ostream& os)
return SUCCESS;
}
/** Send USSD to an IMSI. */
int sendUSSD(int argc, char** argv, ostream& os)
{
if (argc!=2) return BAD_NUM_ARGS;
char *IMSI = argv[1];
GSM::L3MobileIdentity mobileIdentity(IMSI);
unsigned transactionId = USSDDispatcher(mobileIdentity, (unsigned)1, (unsigned)0, Control::USSDData::REGrequest, std::string("REGrequest"), false);
Control::MTTestHandler handler(transactionId);
handler.run();
os << "MT USSD session end." << endl;
return SUCCESS;
}
/** Print current usage loads. */
int cliPrintStats(int argc, char** argv, ostream& os)
@ -874,6 +885,7 @@ Parser::Parser()
addCommand("sendsmsrpdu", sendsmsrpdu, "<IMSI> <src> <RPDU hex string> -- send pre-encoded SMS RPDU to <IMSI>, addressed from <src>.");
addCommand("sendsms", sendsms, "<IMSI> <src> <smsc> <text> -- send SMS to <IMSI>, addressed from <src> with SMS-Center <smsc>.");
addCommand("sendrrlp", sendrrlp, "<IMSI> <hexstring> -- send RRLP message <hexstring> to <IMSI>.");
addCommand("sendussd", sendUSSD, "<IMSI> -- send USSD to <IMSI>");
addCommand("load", cliPrintStats, "-- print the current activity loads.");
addCommand("cellid", cellID, "[MCC MNC LAC CI] -- get/set location area identity (MCC, MNC, LAC) and cell ID (CI)");
addCommand("calls", calls, "-- print the transaction table");

@ -208,7 +208,8 @@ class Thread {
void start(void *(*task)(void*), void *arg);
/** Join a thread that will stop on its own. */
void join() { pthread_join(mThread,NULL); }
void join() { int s = pthread_join(mThread,NULL); assert(s==0); }
};

@ -26,7 +26,7 @@
#include "ControlCommon.h"
#include <CLIParser.h>
#include <GSMLogicalChannel.h>
#include <GSML3Message.h>
#include <GSML3CCMessages.h>
@ -37,11 +37,12 @@
#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.
@ -51,12 +52,31 @@ TransactionTable gTransactionTable;
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),
@ -75,6 +95,7 @@ TransactionEntry::TransactionEntry(const L3MobileIdentity& wSubscriber,
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),
@ -94,6 +115,7 @@ TransactionEntry::TransactionEntry(const L3MobileIdentity& wSubscriber,
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),
@ -111,6 +133,7 @@ TransactionEntry::TransactionEntry(const L3MobileIdentity& wSubscriber,
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),
@ -119,6 +142,25 @@ TransactionEntry::TransactionEntry(const L3MobileIdentity& wSubscriber,
{
}
/** 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
@ -203,6 +245,8 @@ ostream& Control::operator<<(ostream& os, TransactionEntry::Q931CallState state)
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;
@ -218,6 +262,7 @@ ostream& Control::operator<<(ostream& os, const TransactionEntry& entry)
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;
@ -337,6 +382,34 @@ bool TransactionTable::find(const L3MobileIdentity& mobileID, TransactionEntry&
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();
@ -597,7 +670,436 @@ void TMSITable::load(const char* filename)
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)
@ -770,7 +1272,81 @@ void Control::resolveIMSI(L3MobileIdentity& mobileIdentity, LogicalChannel* LCH
}
}
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

@ -61,6 +61,9 @@ class L3IMSIDetachIndication;
class L3PagingResponse;
};
// Reference to a global config table, used all over the system.
extern ConfigurationTable gConfig;
/**@namespace Control This namepace is for use by the control layer. */
namespace Control {
@ -145,7 +148,14 @@ void clearTransactionHistory(unsigned transactionID);
//@}
/**@name USSD */
//@{
/** MO USSD controller */
void MOUSSDController(const GSM::L3CMServiceRequest *req, GSM::LogicalChannel *LCH);
/** MTUSSD controller */
void MTUSSDController(TransactionEntry& transaction, GSM::LogicalChannel *LCH);
//@}
/**@name Functions for mobility manangement operations. */
//@{
@ -208,6 +218,8 @@ void MTSMSController(TransactionEntry& transaction,
GSM::LogicalChannel *LCH);
//@}
/** Create a new transaction entry and start paging. */
void initiateMTTransaction(TransactionEntry& transaction,
GSM::ChannelType chanType, unsigned pageTime);
@ -242,7 +254,6 @@ void resolveIMSI(GSM::L3MobileIdentity& mobID, GSM::LogicalChannel* LCH);
/**@ Paging mechanisms */
//@{
@ -365,8 +376,62 @@ void *PagerServiceLoopAdapter(Pager*);
//@} // paging mech
/**@name USSD data */
//@{
class USSDData {
public:
/** USSD message types based on GSM 03.09 */
enum USSDMessageType {
REGrequest,
request,
notify,
response,
error,
release
};
protected:
ThreadSemaphore mSemWaitMS;
ThreadSemaphore mSemWaitNW;
ThreadSemaphore mSemIncomingData; ///< External entity has sent us some data
USSDMessageType mType; ///< USSD message type
std::string mUSSDString; ///< USSD message string
public:
USSDData(const USSDMessageType& wType)
:mType(wType)
{}
void MessageType(USSDMessageType wType) { mType=wType; }
USSDMessageType MessageType() const { return mType; }
std::string USSDString() const { return mUSSDString; }
void USSDString(std::string wUSSDString) { mUSSDString = wUSSDString; }
ThreadSemaphore::Result waitMS() { return mSemWaitMS.wait(); }
ThreadSemaphore::Result waitNW() { return mSemWaitNW.wait(); }
ThreadSemaphore::Result waitIncomingData() { return mSemIncomingData.wait(); }
ThreadSemaphore::Result waitMS(unsigned timeout) { return mSemWaitMS.wait(timeout); }
ThreadSemaphore::Result waitNW(unsigned timeout) { return mSemWaitNW.wait(timeout); }
ThreadSemaphore::Result waitIncomingData(unsigned timeout) { return mSemIncomingData.wait(timeout); }
ThreadSemaphore::Result trywaitMS() { return mSemWaitMS.trywait(); }
ThreadSemaphore::Result trywaitNW() { return mSemWaitNW.trywait(); }
ThreadSemaphore::Result trywaitIncomingData() { return mSemIncomingData.trywait(); }
ThreadSemaphore::Result postMS() { return mSemWaitMS.post(); }
ThreadSemaphore::Result postNW() { return mSemWaitNW.post(); }
ThreadSemaphore::Result signalIncomingData() { return mSemIncomingData.post(); }
};
std::ostream& operator<<(std::ostream& os, USSDData::USSDMessageType);
std::ostream& operator<<(std::ostream& os, const USSDData&);
//@}
/**@name Transaction Table mechanisms. */
//@{
@ -395,6 +460,8 @@ class TransactionEntry {
ReleaseRequest,
SMSDelivering,
SMSSubmitting,
USSDworking,
USSDclosing
};
private:
@ -414,6 +481,8 @@ class TransactionEntry {
std::string mMessage; ///< text messaging payload
USSDData* mUSSDData; ///< USSD message data
/**@name Timers from GSM and Q.931 (network side) */
//@{
// If you add a timer, remember to add it to
@ -452,6 +521,13 @@ class TransactionEntry {
unsigned wTIValue,
const GSM::L3CallingPartyBCDNumber& wCalling);
/** This form is used for USSD. */
TransactionEntry(const GSM::L3MobileIdentity& wSubscriber,
const GSM::L3CMServiceType& wService,
unsigned wTIFlag,
unsigned wTIValue,
USSDData* wUSSDData);
/**@name Accessors. */
//@{
unsigned TIValue() const { return mTIValue; }
@ -486,6 +562,9 @@ class TransactionEntry {
Q931CallState Q931State() const { return mQ931State; }
void ussdData(USSDData* wUSSDData) { mUSSDData=wUSSDData; }
USSDData* ussdData() const { return mUSSDData; }
unsigned stateAge() const { return mStateTimer.elapsed(); }
/**@name Timer access. */
@ -598,6 +677,16 @@ class TransactionTable {
*/
bool find(const GSM::L3MobileIdentity& mobileID, TransactionEntry& target);
/**
Find an entry by its mobile ID and service type.
Also clears dead entries during search.
@param mobileID The mobile at to search for.
@param serviceType Service type we're looking for.
@param target A TransactionEntry to accept the found record.
@return true is the mobile ID was found.
*/
bool find(const GSM::L3MobileIdentity& mobileID, GSM::L3CMServiceType serviceType, TransactionEntry& target);
/**
Remove "dead" entries from the table.
A "dead" entry is a transaction that is no longer active.
@ -701,7 +790,7 @@ class TMSITable {
/**
Find an IMSI in the table.
This is a log-time operation.
@param TMSI The TMSI to find.
@param TMSI The TMSI to find.post to
@return Pointer to c-string IMSI or NULL.
*/
const char* IMSI(unsigned TMSI) const;
@ -788,9 +877,10 @@ class ControlLayerException {
/** Thrown when the control layer gets the wrong message */
class UnexpectedMessage : public ControlLayerException {
public:
UnexpectedMessage(unsigned wTransactionID=0)
:ControlLayerException(wTransactionID)
UnexpectedMessage(unsigned wTransactionID=0, const GSM::L3Frame *pFrame=NULL)
:ControlLayerException(wTransactionID), mpFrame(pFrame)
{}
const GSM::L3Frame *mpFrame;
};
/** Thrown when recvL3 returns NULL */
@ -824,13 +914,7 @@ class Q931TimerExpired : public ControlLayerException {
:ControlLayerException(wTransactionID)
{}
};
//@}
} //Control
}
/**@addtogroup Globals */
@ -842,6 +926,120 @@ extern Control::TMSITable gTMSITable;
//@}
namespace Control {
bool USSDMatchHandler(const std::string &handlerName, const std::string &ussdString);
unsigned USSDDispatcher(GSM::L3MobileIdentity &mobileIdentity, unsigned TIFlag,
unsigned TIValue, Control::USSDData::USSDMessageType messageType,
const std::string &ussdString, bool MO);
class USSDHandler {
public:
enum USSDtimeout {
trywait = 0,
infinitely = 120000
};
enum
{
USSD_MAX_CHARS_7BIT = 182 ///< See GSM 03.38 5 Cell Broadcast Data Coding Scheme
};
enum ResultCode {
USSD_OK = 0, ///< Operation performed
USSD_CLEARED, ///< USSD state has been cleared as a result of operation
USSD_TIMEOUT, ///< USSD operation timed out
USSD_NO_TRANSACTION, ///< Can't find transaction
USSD_BAD_STATE, ///< Wrong transaction state
USSD_ERROR ///< Generic error
};
private:
unsigned mTransactionID;
protected:
std::string mString;
std::string mContinueStr;
public:
/** This form is used for MO USSD */
USSDHandler(unsigned wTransactionID)
:mTransactionID(wTransactionID),
mContinueStr(gConfig.getStr("USSD.ContinueStr"))
{}
/** Wait USSD data from MS. Return: 0 - successful, 1 - clear transaction, 2 - error or timeout */
USSDHandler::ResultCode waitUSSDData(Control::USSDData::USSDMessageType* messageType, std::string* USSDString, unsigned timeout = USSDHandler::infinitely);
/** Post USSD data and update transaction with new USSDData (messageType and USSDString)*/
USSDHandler::ResultCode postUSSDData( Control::USSDData::USSDMessageType messageType, const std::string &USSDString);
unsigned transactionID() { return mTransactionID; }
void transactionID(unsigned wTransactionID) { wTransactionID = mTransactionID; }
static void *runWrapper(void *pThis);
protected:
virtual void run() = 0;
};
class MOTestHandler : public USSDHandler {
public:
MOTestHandler(unsigned wTransactionID)
:USSDHandler(wTransactionID)
{}
void run();
};
class MOHttpHandler : public USSDHandler {
public:
MOHttpHandler(unsigned wTransactionID)
:USSDHandler(wTransactionID)
{}
void run();
};
class UssdSipHandler : public USSDHandler {
private:
std::string mStr;
unsigned mNum;
public:
UssdSipHandler(unsigned wTransactionID)
: USSDHandler(wTransactionID)
{}
void run();
};
class MOCLIHandler : public USSDHandler {
public:
MOCLIHandler(unsigned wTransactionID)
:USSDHandler(wTransactionID)
{}
void run();
};
class MTTestHandler : public USSDHandler {
public:
MTTestHandler(unsigned wTransactionID)
:USSDHandler(wTransactionID)
{}
void run();
};
//@}
} //Control
#endif

@ -105,14 +105,18 @@ void DCCHDispatchRR(const L3RRMessage* req, LogicalChannel *DCCH)
/** Example of a closed-loop, persistent-thread control function for the DCCH. */
void Control::DCCHDispatcher(LogicalChannel *DCCH)
{
const L3Message *message = NULL;
while (1) {
try {
// Wait for a transaction to start.
LOG(DEBUG) << "waiting for " << DCCH->type() << " ESTABLISH";
waitForPrimitive(DCCH,ESTABLISH);
// Pull the first message and dispatch a new transaction.
const L3Message *message = getMessage(DCCH);
LOG(DEBUG) << "received " << *message;
if (!message)
{
// Wait for a transaction to start.
LOG(DEBUG) << "waiting for " << DCCH->type() << " ESTABLISH";
waitForPrimitive(DCCH,ESTABLISH);
// Pull the first message and dispatch a new transaction.
message = getMessage(DCCH);
LOG(DEBUG) << "received " << *message;
}
// Each protocol has it's own sub-dispatcher.
switch (message->PD()) {
case L3MobilityManagementPD:
@ -123,9 +127,12 @@ void Control::DCCHDispatcher(LogicalChannel *DCCH)
break;
default:
LOG(NOTICE) << "unhandled protocol " << message->PD() << " on " << DCCH->type();
delete message;
message = NULL;
throw UnsupportedMessage();
}
delete message;
message = NULL;
}
// Catch the various error cases.
@ -145,8 +152,16 @@ void Control::DCCHDispatcher(LogicalChannel *DCCH)
catch (UnexpectedMessage except) {
clearTransactionHistory(except.transactionID());
LOG(NOTICE) << "UnexpectedMessage";
// Cause 0x62 means "message type not not compatible with protocol state".
DCCH->send(L3ChannelRelease(0x62));
if (except.mpFrame)
{
message = parseL3(*except.mpFrame);
delete except.mpFrame;
}
else
{
// Cause 0x62 means "message type not not compatible with protocol state".
DCCH->send(L3ChannelRelease(0x62));
}
}
catch (UnsupportedMessage except) {
clearTransactionHistory(except.transactionID());
@ -178,7 +193,4 @@ void Control::DCCHDispatcher(LogicalChannel *DCCH)
}
}
// vim: ts=4 sw=4

@ -30,12 +30,13 @@ noinst_LTLIBRARIES = libcontrol.la
libcontrol_la_SOURCES = \
CallControl.cpp \
SMSControl.cpp \
USSDControl.cpp \
ControlCommon.cpp \
MobilityManagement.cpp \
RadioResource.cpp \
DCCHDispatch.cpp \
CollectMSInfo.cpp \
RRLPQueryController.cpp
RRLPQueryController.cpp
# TODO - move CollectMSInfo.cpp and RRLPQueryController.cpp to RRLP directory.

@ -64,6 +64,9 @@ void Control::CMServiceResponder(const L3CMServiceRequest* cmsrq, LogicalChannel
case L3CMServiceType::EmergencyCall:
EmergencyCall(cmsrq,DCCH);
break;
case L3CMServiceType::SupplementaryService:
MOUSSDController(cmsrq,DCCH);
break;
default:
LOG(NOTICE) << "service not supported for " << *cmsrq;
// Cause 0x20 means "serivce not supported".

@ -279,6 +279,9 @@ void Control::PagingResponseHandler(const L3PagingResponse* resp, LogicalChannel
case L3CMServiceType::MobileTerminatedShortMessage:
MTSMSController(transaction, DCCH);
return;
case L3CMServiceType::SupplementaryService:
MTUSSDController(transaction, DCCH);
return;
default:
// Flush stray MOC entries.
// There should not be any, but...

@ -0,0 +1,428 @@
/**@file USSD Control (L3) */
/*
* Copyright 2008, 2009, 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 <stdio.h>
#include <GSMLogicalChannel.h>
#include <GSML3MMMessages.h>
#include "ControlCommon.h"
#include <Regexp.h>
#include "GSML3NonCallSSMessages.h"
#include "GSML3NonCallSSComponents.h"
#include "GSML3NonCallSSElements.h"
#include <algorithm>
using namespace std;
using namespace GSM;
using namespace Control;
#include "SIPInterface.h"
#include "SIPUtility.h"
#include "SIPMessage.h"
#include "SIPEngine.h"
using namespace SIP;
/** @file USSDControl.cpp
USSD string coding schema is described at GSM 02.90 5.1.2, 5.1.4
and GSM 02.30 4.5
TODO:
1) We should support Network -> MS USSD too.
BUGS:
1) *101# does not work with Siemens A52 (but does work with A65).
After phone receives "Send more data" it can't perform any more USSD
and can't reply to this request too. If you try, phone displays
"NOT EXECUTED". BUT when you switch it off afterwards, it sends
ReleaseComplete, so it seems it's stuck waiting for something from
network?
2) On Alcatel BG3 message sent with Notify disappears as soon as
phone receives our ReleaseComplete. Probably we should delay it
by a few seconds?
*/
L3Frame* getFrameUSSD (LogicalChannel *LCH, GSM::Primitive primitive=DATA)
{
L3Frame *retVal = LCH->recv(gConfig.getNum("USSD.timeout"),0);
if (!retVal) {
LOG(NOTICE) <<"TIME";
//throw ChannelReadTimeout();
retVal = NULL;
}
else if (retVal->primitive() != primitive) {
LOG(NOTICE) << "unexpected primitive, expecting " << primitive << ", got " << *retVal;
//throw UnexpectedPrimitive();
retVal = NULL;
}
else if ((retVal->primitive() == DATA) && (retVal->PD() != L3NonCallSSPD)) {
LOG(NOTICE) << "unexpected (non-USSD) protocol in frame " << *retVal;
//throw UnexpectedMessage(0, retVal);
retVal = NULL;
}
return retVal;
}
void USSDSend(string USSDString, unsigned InvokeID, unsigned TI, unsigned TIFlag, LogicalChannel* LCH, Control::USSDData::USSDMessageType messageType)
{
L3Frame Frame;
if (TIFlag) TIFlag = 0;
else TIFlag = 1;
if (messageType == Control::USSDData::REGrequest)
{
//SEND MT REGISTER, UnstructuredSSRequest
L3NonCallSSComponentInvoke* ComponentPtr = new L3NonCallSSComponentInvoke(L3NonCallSSInvokeID(InvokeID),
L3NonCallSSOperationCode(GSM::L3NonCallSSOperationCode::UnstructuredSSRequest));
ComponentPtr->parameters(L3NonCallSSParameters(USSDString.c_str()));
L3NonCallSSRegisterMessage RegisterMessage(0, 0, ComponentPtr);
RegisterMessage.write(Frame);
LOG(DEBUG) << "Sending Register Message: " << RegisterMessage;
LCH->send(Frame);
}
else if (messageType == Control::USSDData::request)
{
//SEND MT Facility, UnstructuredSSRequest
L3NonCallSSComponentInvoke* ComponentPtr = new L3NonCallSSComponentInvoke(L3NonCallSSInvokeID(InvokeID),
L3NonCallSSOperationCode(GSM::L3NonCallSSOperationCode::UnstructuredSSRequest));
ComponentPtr->parameters(L3NonCallSSParameters(USSDString.c_str()));
L3NonCallSSFacilityMessage FacilityMessage(TIFlag, TI, ComponentPtr);
FacilityMessage.write(Frame);
LOG(DEBUG) << "Sending Register Message: " << FacilityMessage;
LCH->send(Frame);
}
else if (messageType == Control::USSDData::response)
{
//send response
L3NonCallSSComponentReturnResult* ComponentPtr = new L3NonCallSSComponentReturnResult(L3NonCallSSInvokeID(InvokeID));
ComponentPtr->operationCode(L3NonCallSSOperationCode(GSM::L3NonCallSSOperationCode::ProcessUnstructuredSSRequest));
ComponentPtr->parameters(L3NonCallSSParameters(USSDString.c_str()));
L3NonCallSSReleaseCompleteMessage ReleaseCompleteMessage(TIFlag, TI);
ReleaseCompleteMessage.component(ComponentPtr);