diff --git a/public-trunk/CLI/CLI.cpp b/public-trunk/CLI/CLI.cpp index 7d9ce07..9a5b76b 100644 --- a/public-trunk/CLI/CLI.cpp +++ b/public-trunk/CLI/CLI.cpp @@ -27,6 +27,7 @@ #include #include "CLI.h" +#include "CLIParser.h" #include #include @@ -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 ] } diff --git a/public-trunk/CLI/CLIParser.cpp b/public-trunk/CLI/CLIParser.cpp index 0f09806..16cea06 100644 --- a/public-trunk/CLI/CLIParser.cpp +++ b/public-trunk/CLI/CLIParser.cpp @@ -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, " -- send pre-encoded SMS RPDU to , addressed from ."); addCommand("sendsms", sendsms, " -- send SMS to , addressed from with SMS-Center ."); addCommand("sendrrlp", sendrrlp, " -- send RRLP message to ."); + addCommand("sendussd", sendUSSD, " -- send USSD to "); 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"); diff --git a/public-trunk/CommonLibs/Threads.h b/public-trunk/CommonLibs/Threads.h index 9015be8..c741343 100644 --- a/public-trunk/CommonLibs/Threads.h +++ b/public-trunk/CommonLibs/Threads.h @@ -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); } }; diff --git a/public-trunk/Control/ControlCommon.cpp b/public-trunk/Control/ControlCommon.cpp index 21fd193..7787576 100644 --- a/public-trunk/Control/ControlCommon.cpp +++ b/public-trunk/Control/ControlCommon.cpp @@ -26,7 +26,7 @@ #include "ControlCommon.h" - +#include #include #include #include @@ -37,11 +37,12 @@ #include #include +#include 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 diff --git a/public-trunk/Control/ControlCommon.h b/public-trunk/Control/ControlCommon.h index ee8a6d2..13c7208 100644 --- a/public-trunk/Control/ControlCommon.h +++ b/public-trunk/Control/ControlCommon.h @@ -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 diff --git a/public-trunk/Control/DCCHDispatch.cpp b/public-trunk/Control/DCCHDispatch.cpp index 2ec0b37..009a6dd 100644 --- a/public-trunk/Control/DCCHDispatch.cpp +++ b/public-trunk/Control/DCCHDispatch.cpp @@ -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 diff --git a/public-trunk/Control/Makefile.am b/public-trunk/Control/Makefile.am index 9a55cd6..8d371dc 100644 --- a/public-trunk/Control/Makefile.am +++ b/public-trunk/Control/Makefile.am @@ -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. diff --git a/public-trunk/Control/MobilityManagement.cpp b/public-trunk/Control/MobilityManagement.cpp index 084f870..07958aa 100644 --- a/public-trunk/Control/MobilityManagement.cpp +++ b/public-trunk/Control/MobilityManagement.cpp @@ -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". diff --git a/public-trunk/Control/RadioResource.cpp b/public-trunk/Control/RadioResource.cpp index 51a7bd2..9fb83c4 100644 --- a/public-trunk/Control/RadioResource.cpp +++ b/public-trunk/Control/RadioResource.cpp @@ -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... diff --git a/public-trunk/Control/USSDControl.cpp b/public-trunk/Control/USSDControl.cpp new file mode 100644 index 0000000..e8ac5c9 --- /dev/null +++ b/public-trunk/Control/USSDControl.cpp @@ -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 . + +*/ + + +#include +#include +#include +#include "ControlCommon.h" +#include +#include "GSML3NonCallSSMessages.h" +#include "GSML3NonCallSSComponents.h" +#include "GSML3NonCallSSElements.h" +#include + + +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); + ReleaseCompleteMessage.write(Frame); + LOG(DEBUG) << "Sending Release Complete Message with response: " << ReleaseCompleteMessage; + LCH->send(Frame); + //send L3 Channel Release + LOG(DEBUG) << "L3 Channel Release."; + LCH->send(L3ChannelRelease()); + } + + else if (messageType == Control::USSDData::request ) + { + //send request + if(InvokeID == 0xff) InvokeID = 0; + else InvokeID++; + 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 Facility Message with request: " << FacilityMessage; + LCH->send(Frame); + } + + else if (messageType == Control::USSDData::notify) + { + //send notify + if(InvokeID == 0xff) InvokeID = 0; + else InvokeID++; + L3NonCallSSComponentInvoke* ComponentPtr = new L3NonCallSSComponentInvoke(L3NonCallSSInvokeID(InvokeID), + L3NonCallSSOperationCode(GSM::L3NonCallSSOperationCode::UnstructuredSSNotify)); + ComponentPtr->parameters(L3NonCallSSParameters(USSDString.c_str())); + L3NonCallSSFacilityMessage FacilityMessage(TIFlag, TI, ComponentPtr); + FacilityMessage.write(Frame); + LOG(DEBUG) << "Sending Facility Message with notify: " << FacilityMessage; + LCH->send(Frame); + } + + else if (messageType == Control::USSDData::error) + { + //send error + L3NonCallSSComponentReturnError* ComponentPtr = new L3NonCallSSComponentReturnError(L3NonCallSSInvokeID(InvokeID), + L3NonCallSSErrorCode(GSM::L3NonCallSSErrorCode::SystemFailure)); + L3NonCallSSFacilityMessage FacilityMessage(TIFlag, TI, ComponentPtr); + FacilityMessage.write(Frame); + LOG(DEBUG) << "Sending Facility Message with error System Failure: " << FacilityMessage; + LCH->send(Frame); + } + + else if(messageType == Control::USSDData::release) + { + // NOTE: On some phones (e.g. Nokia 3310, Alcatel BG3) if this parameter is set to a non-empty string, + // when it is sent after Notify USSD, this string is shown on the screen after showing Notify + // message just for a second. I.e. it replaces (obscures) original Notify message. Thus we + // recommend to set this string to an empty string. + L3NonCallSSComponentReturnResult* ComponentPtr = new L3NonCallSSComponentReturnResult(L3NonCallSSInvokeID(InvokeID)); + ComponentPtr->operationCode(L3NonCallSSOperationCode(GSM::L3NonCallSSOperationCode::ProcessUnstructuredSSRequest)); + // TODO:: How to drop session silently? + ComponentPtr->parameters(L3NonCallSSParameters(USSDString.c_str())); + L3NonCallSSReleaseCompleteMessage ReleaseCompleteMessage(TIFlag, TI); + if (TIFlag == 1) + { + ReleaseCompleteMessage.component(ComponentPtr); + } + ReleaseCompleteMessage.write(Frame); + LOG(DEBUG) << "Sending Release Complete Message: " << ReleaseCompleteMessage; + LCH->send(Frame); + //send L3 Channel Release + LOG(DEBUG) << "L3 Channel Release."; + LCH->send(L3ChannelRelease()); + } + + + +} + +Control::USSDData::USSDMessageType USSDParse(L3NonCallSSMessage* Message, string* USSDString, unsigned* InvokeID ) +{ + L3Frame Frame; + Control::USSDData::USSDMessageType messageType = Control::USSDData::error; + // Low-level message flow is described at GSM 04.90 6.1. + // High-level message flow is described at GSM 03.90 6.2. + + // Process REQUEST message + if (Message->MTI() == GSM::L3NonCallSSMessage::Register) + { + LOG(DEBUG) << "Received Register Message"; + L3NonCallSSRegisterMessage* RegisterMessage = (L3NonCallSSRegisterMessage*)Message; + if (RegisterMessage->l3NonCallSSComponent()->NonCallSSComponentTypeTag()== GSM::L3NonCallSSComponent::Invoke) + { + LOG(DEBUG) << "Received Invoke Component"; + L3NonCallSSComponentInvoke* Component; + Component = (L3NonCallSSComponentInvoke*)RegisterMessage->l3NonCallSSComponent(); + *USSDString = Component->l3NonCallSSParameters().data(); + LOG(DEBUG) << "Read USSD string"; + *InvokeID = (unsigned)Component->invokeID().value(); + messageType = Control::USSDData::request; + } + + } + // Process RESPONSE message + else if (Message->MTI() == GSM::L3NonCallSSMessage::Facility) + { + LOG(DEBUG) << "Received Facility Message"; + L3NonCallSSFacilityMessage* FacilityMessage = (L3NonCallSSFacilityMessage*)Message; + if (FacilityMessage->l3NonCallSSComponent()->NonCallSSComponentTypeTag()== GSM::L3NonCallSSComponent::ReturnResult) + { + LOG(DEBUG) << "Return Result Component"; + L3NonCallSSComponentReturnResult* Component; + Component = (L3NonCallSSComponentReturnResult*)FacilityMessage->l3NonCallSSComponent(); + if (Component->haveL3NonCallSSParameters()) *USSDString = Component->l3NonCallSSParameters().data(); + else *USSDString = ""; + *InvokeID = (unsigned)Component->invokeID().value(); + messageType = Control::USSDData::response; + } + } + + // Process RELEASE or ERROR + else // MTI == GSM::L3NonCallSSMessage::ReleaseComplete + { + L3NonCallSSReleaseCompleteMessage* ReleaseCompleteMessage = (L3NonCallSSReleaseCompleteMessage*)Message; + if (ReleaseCompleteMessage->haveL3NonCallSSComponent()) + { + *InvokeID = ReleaseCompleteMessage->l3NonCallSSComponent()->invokeID().value(); + messageType = Control::USSDData::release; + } + else + { + InvokeID = 0; + messageType = Control::USSDData::release; + } + *USSDString = ""; + } + + return messageType; +} + +void Control::MOUSSDController(const L3CMServiceRequest *request, LogicalChannel *LCH) +{ + assert(request); + assert(LCH); + + LOG(DEBUG) << "Get L3 CM Service Request: " << *request; + // If we got a TMSI, find the IMSI. + // Note that this is a copy, not a reference. + L3MobileIdentity mobileIdentity = request->mobileIdentity(); + resolveIMSI(mobileIdentity,LCH); + LOG(DEBUG) << "mobileIdentity: "<mobileIdentity()<<" IMSI:"<send(L3CMServiceAccept()); + + //Get USSD frame from MS + LOG(DEBUG) << "Get USSD frame from MS"; + L3Frame *USSDFrame = getFrameUSSD(LCH); + //Parse USSD frame + LOG(DEEPDEBUG) << "Parse USSD frame"; + L3NonCallSSMessage* USSDMessage; + USSDMessage = parseL3NonCallSS(*USSDFrame); + LOG(INFO) << "USSD message:"<<*USSDMessage; + + string USSDString = ""; + unsigned InvokeID = 0; + + Control::USSDData::USSDMessageType messageType = USSDParse(USSDMessage, &USSDString, &InvokeID); + unsigned transactionID = USSDDispatcher (mobileIdentity, USSDMessage->TIFlag(), USSDMessage->TIValue(), messageType, USSDString, true); + unsigned TI = USSDMessage->TIValue(); + + TransactionEntry transaction; + while(gTransactionTable.find(transactionID, transaction)) + { + USSDData *pUssdData = NULL; + + // Wait for handler + if ((pUssdData = transaction.ussdData()) == NULL) + { + LOG(DEBUG) << "Transaction has no USSD data: " << transaction; + break; + } + if (pUssdData->waitNW()!=0) + { + LOG(DEBUG) << "waitNW() returned error for USSD transaction: " << transaction; + clearTransactionHistory(transaction); + break; + } + + // Send to ME + if (!gTransactionTable.find(transactionID, transaction)) + { + LOG(DEBUG) << "Transaction with ID=" << transactionID << " not found"; + break; + } + if ((pUssdData = transaction.ussdData()) == NULL) + { + LOG(DEBUG) << "Transaction has no USSD data: " << transaction; + break; + } + USSDSend(pUssdData->USSDString(), InvokeID, TI, 0, LCH, pUssdData->MessageType()); + if((pUssdData->MessageType() == Control::USSDData::response)|| + (pUssdData->MessageType() == Control::USSDData::release)) + { + LOG(DEBUG) << "waitMS received response or release. Closing"; + transaction.Q931State(Control::TransactionEntry::USSDclosing); + LOG(DEBUG) << "Clearing USSD transaction: " << transaction; + clearTransactionHistory(transaction); + break; + } + + // Receive from ME + L3Frame *USSDFrame = getFrameUSSD(LCH); + if (USSDFrame == NULL) + { + USSDSend(pUssdData->USSDString(), InvokeID, TI, 0, LCH, Control::USSDData::release); + transaction.Q931State(Control::TransactionEntry::USSDclosing); + } + else + { + //Parse USSD frame + L3NonCallSSMessage* USSDMessage = parseL3NonCallSS(*USSDFrame); + TI = USSDMessage->TIValue(); + LOG(INFO) << "USSD message before PARSE:"<<*USSDMessage; + Control::USSDData::USSDMessageType messageType = USSDParse(USSDMessage, &USSDString, &InvokeID); + LOG(INFO) << "MO USSD message: "<< *USSDMessage << " MO USSD type: "<< messageType + << " USSD string: " << USSDString << " InvokeID: " << InvokeID; + pUssdData->USSDString(USSDString); + pUssdData->MessageType(messageType); + delete USSDMessage; + } + delete USSDFrame; + + // Notify handler + gTransactionTable.update(transaction); + pUssdData->postMS(); + } +} + + +void Control::MTUSSDController(TransactionEntry& transaction, LogicalChannel *LCH) +{ + + assert(LCH); + LOG(INFO) << "MTUSSD: transaction: "<< transaction; + LCH->transactionID(transaction.ID()); + string USSDString = ""; + unsigned InvokeID = 0; + unsigned TI = 0; + unsigned transactionID = transaction.ID(); + + transaction.Q931State(Control::TransactionEntry::USSDworking); + gTransactionTable.update(transaction); + + while(gTransactionTable.find(transactionID, transaction)) + { + if (transaction.Q931State() == Control::TransactionEntry::USSDclosing) + { + break; + } + else if (transaction.ussdData()->waitNW()==0) + { + gTransactionTable.find(transactionID, transaction); + //SEND + USSDSend(transaction.ussdData()->USSDString(), InvokeID, TI, 1, LCH, transaction.ussdData()->MessageType()); + if((transaction.ussdData()->MessageType() == Control::USSDData::response)|| + (transaction.ussdData()->MessageType() == Control::USSDData::release)) + { + LOG(DEBUG) << "USSD waitMS response||relese"; + transaction.Q931State(Control::TransactionEntry::USSDclosing); + } + else + { + //RECV + L3Frame *USSDFrame = getFrameUSSD(LCH); + if (USSDFrame == NULL) + { + USSDSend(transaction.ussdData()->USSDString(), InvokeID, TI, 1, LCH, Control::USSDData::release); + transaction.Q931State(Control::TransactionEntry::USSDclosing); + } + else + { + //Parse USSD frame + L3NonCallSSMessage* USSDMessage = parseL3NonCallSS(*USSDFrame); + TI = USSDMessage->TIValue(); + Control::USSDData::USSDMessageType messageType = USSDParse(USSDMessage, &USSDString, &InvokeID); + LOG(INFO) << "MT USSD message: "<< *USSDMessage << "MT USSD type: "<< messageType + << " USSD string: " << USSDString << " InvokeID: " << InvokeID; + transaction.ussdData()->MessageType(messageType); + transaction.ussdData()->USSDString(USSDString); + delete USSDMessage; + } + delete USSDFrame; + } + gTransactionTable.update(transaction); + transaction.ussdData()->postMS(); + } + } + + LOG(DEBUG) << "USSD session done."; +} + + diff --git a/public-trunk/GSM/GSML3Message.cpp b/public-trunk/GSM/GSML3Message.cpp index a4e3063..bd62ffb 100644 --- a/public-trunk/GSM/GSML3Message.cpp +++ b/public-trunk/GSM/GSML3Message.cpp @@ -28,6 +28,7 @@ #include "GSML3RRMessages.h" #include "GSML3MMMessages.h" #include "GSML3CCMessages.h" +#include "GSML3NonCallSSMessages.h" #include @@ -148,6 +149,7 @@ GSM::L3Message* GSM::parseL3(const GSM::L3Frame& source) case L3RadioResourcePD: retVal=parseL3RR(source); break; case L3MobilityManagementPD: retVal=parseL3MM(source); break; case L3CallControlPD: retVal=parseL3CC(source); break; + case L3NonCallSSPD: retVal=parseL3NonCallSS(source); break; //case L3SMSPD: return parseSMS(source); default: LOG(NOTICE) << "L3 parsing failed for unsupported protocol " << PD; diff --git a/public-trunk/GSM/GSML3NonCallSSComponents.cpp b/public-trunk/GSM/GSML3NonCallSSComponents.cpp new file mode 100644 index 0000000..c5da168 --- /dev/null +++ b/public-trunk/GSM/GSML3NonCallSSComponents.cpp @@ -0,0 +1,294 @@ +/**@file Call independent Supplementary Service Control. Facility element components, GSM 04.80 3.6.1. */ +/* +* 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 . + +*/ + + +#include +#include "GSML3NonCallSSComponents.h" +#include + + + +using namespace std; +using namespace GSM; + + +ostream& GSM::operator<<(ostream& os, const L3NonCallSSComponent& msg) +{ + msg.text(os); + return os; +} + +ostream& GSM::operator<<(std::ostream& os, L3NonCallSSComponent::ComponentTypeTag val) +{ + switch (val) { + case L3NonCallSSComponent::Invoke: + os << "Invoke"; break; + case L3NonCallSSComponent::ReturnResult: + os << "ReturnResult"; break; + case L3NonCallSSComponent::ReturnError: + os << "ReturnError"; break; + case L3NonCallSSComponent::Reject: + os << "Reject"; break; + default: os << hex << "0x" << (int)val << dec; + } + + return os; +} + +L3NonCallSSComponent * GSM::L3NonCallSSComponentFactory(L3NonCallSSComponent::ComponentTypeTag CTT) +{ + switch (CTT) { + case L3NonCallSSComponent::Invoke: return new L3NonCallSSComponentInvoke(); + case L3NonCallSSComponent::ReturnResult: return new L3NonCallSSComponentReturnResult(); + case L3NonCallSSComponent::ReturnError: return new L3NonCallSSComponentReturnError(); + case L3NonCallSSComponent::Reject: return new L3NonCallSSComponentReject(); + default: { + //LOG(NOTICE) << "no L3 NonCallSSComponent factory support for message "<< CTT; + return NULL; + } + } +} + +L3NonCallSSComponent * GSM::parseL3NonCallSSComponent (const L3Frame& source, size_t &rp) +{ + L3NonCallSSComponent::ComponentTypeTag CTT = (L3NonCallSSComponent::ComponentTypeTag)source.readField(rp, 8); + LOG(DEBUG) << "Component Type Tag = "<<(int)CTT; + + L3NonCallSSComponent *retVal = L3NonCallSSComponentFactory(CTT); + if (retVal==NULL) return NULL; + + retVal->parse(source,rp); + LOG(DEBUG) << "parse L3 Non Call SS Component " << *retVal; + return retVal; +} + +void L3NonCallSSComponent::write(L3Frame& dest, size_t &wp) +{ + dest.writeField(wp,NonCallSSComponentTypeTag(),8);//Component type tag + writeBody(dest,wp); +} + +void L3NonCallSSComponent::parse(const L3Frame& source, size_t &rp) +{ + mComponentLength.parseV(source,rp);//ComponentLength + rp+=16;//InvokeID Tag and InvokeIDLength + mInvokeID.parseV(source, rp, 1);//InvokeID + parseBody(source, rp); +} + +void L3NonCallSSComponent::text(ostream& os) const +{ + os << " ComponentTypeTag = " << NonCallSSComponentTypeTag(); + os << " InvokeID = " << mInvokeID; +} + + +void L3NonCallSSComponentInvoke::writeBody( L3Frame &dest, size_t &wp ) +{ + this->length(); + mComponentLength.writeV(dest, wp);//ComponentLength + dest.writeField(wp,0x2,8);//InvokeID tag + dest.writeField(wp,0x1,8);//InvokeIDLength + mInvokeID.writeV(dest,wp,8);//InvokeID + if (mHaveLinkedID) + { + dest.writeField(wp,0x80,8);//LinkedID tag + dest.writeField(wp,0x1,8);//LinkedIDLength + mLinkedID.writeV(dest, wp, 8);//LinkedID + } + dest.writeField(wp,0x2,8);//Operation Cod tag + dest.writeField(wp,0x1,8);//Operation Cod length + mOperationCode.writeV(dest, wp); //Operation Cod + if(mHaveParameters) + { + mParameters.write(dest, wp);//Parameters + } +} + +void L3NonCallSSComponentInvoke::parseBody( const L3Frame &source, size_t &rp ) +{ + if(source.readField(rp, 8) == 0x80) + { + rp+=8;//LinkedID tag and LinkedIDLength + mLinkedID.parseV(source, rp, 1);//LinkedID + mHaveLinkedID = true; + } + rp+=8;//OperationCodTag and OperationCodLength + mOperationCode.parseV(source, rp);//OperationCode + if((mComponentLength.value()>9)&&(source.readField(rp, 8) == 0x30)) + { + rp-=8; + mParameters.parseV(source, rp);//Parameters + mHaveParameters = true; + } +} + +size_t L3NonCallSSComponentInvoke::length() +{ + uint64_t ComponentLength = 0x6; + if (mHaveLinkedID) { ComponentLength += 0x3; } + if(mHaveParameters) { ComponentLength += mParameters.length(); } + mComponentLength.setValue(ComponentLength); + return mComponentLength.value()+mComponentLength.lengthV()+0x1; +} + +void L3NonCallSSComponentInvoke::text(std::ostream& os) const +{ + L3NonCallSSComponent::text(os); + + if (mHaveLinkedID) os << " LinkedID = " << mLinkedID; + os << " OperationCode = " << mOperationCode; + if (mHaveParameters) os << " Parameters = (" << mParameters<< ")"; +} + +void L3NonCallSSComponentReturnResult::writeBody( L3Frame &dest, size_t &wp ) +{ + this->length(); + mComponentLength.writeV(dest, wp);//ComponentLength + dest.writeField(wp,0x2,8);//InvokeID tag + dest.writeField(wp,0x1,8);//InvokeIDLength + mInvokeID.writeV(dest,wp,8);//InvokeID + if(mHaveParameters) + { + dest.writeField(wp,0x30,8); //Sequence tag + mSequenceLength.writeV(dest, wp);//SequenceLength + dest.writeField(wp,0x2,8);//Operation Cod tag + dest.writeField(wp,0x1,8);//Operation Cod length + mOperationCode.writeV(dest, wp); //Operation Cod + mParameters.write(dest, wp);//Parameters + } +} + +void L3NonCallSSComponentReturnResult::parseBody( const L3Frame &source, size_t &rp ) +{ + if ((mComponentLength.value() > 0x3)&&(source.readField(rp, 8) == 0x30)) + { + mSequenceLength.parseV(source,rp);//SequenceLength + rp+=16;//OperationCodTag and OperationCodLength + mOperationCode.parseV(source, rp);//OperationCode + mParameters.parseV(source, rp);//Parameters + mHaveParameters = true; + } +} + +size_t L3NonCallSSComponentReturnResult::length() +{ + uint64_t ComponentLength = 0x3; + if(mHaveParameters) + { + + mSequenceLength.setValue(mParameters.length()+0x3); + ComponentLength += mSequenceLength.value() + mSequenceLength.lengthV() + 0x1; + } + mComponentLength.setValue(ComponentLength); + return mComponentLength.value()+mComponentLength.lengthV()+0x1; +} + +void L3NonCallSSComponentReturnResult::text(std::ostream& os) const +{ + L3NonCallSSComponent::text(os); + if (mHaveParameters) + { + os << " OperationCode = " << mOperationCode; + os << " Parameters = (" << mParameters << ")"; + } +} + +void L3NonCallSSComponentReturnError::writeBody( L3Frame &dest, size_t &wp ) +{ + this->length(); + mComponentLength.writeV(dest, wp);//ComponentLength + dest.writeField(wp,0x2,8);//InvokeID tag + dest.writeField(wp,0x1,8);//InvokeIDLength + mInvokeID.writeV(dest,wp,8);//InvokeID + dest.writeField(wp,0x2,8);//Error Cod tag + dest.writeField(wp,0x1,8);//Error Cod length + mErrorCode.writeV(dest, wp); //Error Cod + if(mHaveParameters) + { + mParameters.write(dest, wp);//Parameters + } +} + +void L3NonCallSSComponentReturnError::parseBody( const L3Frame &source, size_t &rp ) +{ + rp+=16;//ErrorCodTag and ErrorCodLength + mErrorCode.parseV(source, rp);//ErrorCode + if((mComponentLength.value() > 0x6)&&(source.readField(rp, 8) == 0x30)) + { + rp-=8; + mParameters.parseV(source, rp);//Parameters + mHaveParameters = true; + } +} + +size_t L3NonCallSSComponentReturnError::length() +{ + uint64_t ComponentLength = 0x6; + if(mHaveParameters) { ComponentLength += mParameters.length();} + mComponentLength.setValue(ComponentLength); + return mComponentLength.value()+mComponentLength.lengthV()+0x1; +} + +void L3NonCallSSComponentReturnError::text(std::ostream& os) const +{ + L3NonCallSSComponent::text(os); + os << " ErrorCode = " << mErrorCode; + if (mHaveParameters) os << " Parameters = " << mParameters; +} + +void L3NonCallSSComponentReject::writeBody( L3Frame &dest, size_t &wp ) +{ + this->length(); + mComponentLength.writeV(dest, wp);//ComponentLength + dest.writeField(wp,0x6,8);//ComponentLength + dest.writeField(wp,0x2,8);//InvokeID tag + dest.writeField(wp,0x1,8);//InvokeIDLength + mInvokeID.writeV(dest,wp,8);//InvokeID + mProblemCodeTag.writeV(dest, wp);//Problem Code Tag + dest.writeField(wp,0x1,8);//Problem Code length + mProblemCode.writeV(dest, wp); //Problem Code Cod +} + +void L3NonCallSSComponentReject::parseBody( const L3Frame &source, size_t &rp ) +{ + mProblemCodeTag.parseV(source, rp);//ProblemCodeTag + rp+=8;//Problem Code length + mProblemCode.parseV(source, rp);//ProblemCode +} + +size_t L3NonCallSSComponentReject::length() +{ + mComponentLength.setValue(0x6); + return mComponentLength.value()+mComponentLength.lengthV()+0x1; +} + + +void L3NonCallSSComponentReject::text(std::ostream& os) const +{ + L3NonCallSSComponent::text(os); + os << " ProblemCodeTag = " << mProblemCodeTag; + os << " ProblemCode = " << mProblemCode; +} diff --git a/public-trunk/GSM/GSML3NonCallSSComponents.h b/public-trunk/GSM/GSML3NonCallSSComponents.h new file mode 100644 index 0000000..49670bf --- /dev/null +++ b/public-trunk/GSM/GSML3NonCallSSComponents.h @@ -0,0 +1,270 @@ +/**@file Call independent Supplementary Service Control. Facility element components, GSM 04.80 3.6.1. */ +/* +* 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 . + +*/ + + + +#ifndef GSML3NONCALLSSCOMPONENT_H +#define GSML3NONCALLSSCOMPONENT_H + +#include "GSML3NonCallSSElements.h" +#include "assert.h" + +namespace GSM { + +/** +This is virtual base class for the facility element components, GSM 04.80 3.6.1. +*/ +class L3NonCallSSComponent { + + protected: + + L3NonCallSSInvokeID mInvokeID; + L3NonCallSSLengthField mComponentLength; + + public: + + enum ComponentTypeTag { + Invoke = 0xa1, + ReturnResult = 0xa2, + ReturnError = 0xa3, + Reject = 0xa4 + }; + + L3NonCallSSComponent (const L3NonCallSSInvokeID& wInvokeID = L3NonCallSSInvokeID()) + :mInvokeID(wInvokeID), + mComponentLength(L3NonCallSSLengthField()) + {} + + /** Return the expected component body length in bytes */ + virtual size_t length() = 0; + + /** Return number of BITS needed to hold component. */ + size_t bitsNeeded() { return 8*length(); } + + /** + The parse() method reads and decodes L3 NonCallSS facility component bits. + This method invokes parseBody, assuming that the component header + has already been read. + */ + virtual void parse(const L3Frame& source, size_t &rp); + + /** + Write component data bits into a BitVector buffer. + This method invokes writeBody. + */ + virtual void write(L3Frame& dest, size_t &wp); + + /** Return the ComponentTypeTag. */ + virtual ComponentTypeTag NonCallSSComponentTypeTag() const =0; + + const L3NonCallSSInvokeID& invokeID() const { return mInvokeID; } + + protected: + + /** + Write the component body, a method defined in some subclasses. + */ + virtual void writeBody(L3Frame& dest, size_t &writePosition) =0; + + /** + The parseBody() method starts processing at the first byte following the + invokeID field in the component, which the caller indicates with the + readPosition argument. + */ + virtual void parseBody(const L3Frame& source, size_t &readPosition) =0; + + public: + + /** Generate a human-readable representation of a component. */ + virtual void text(std::ostream& os) const; + +}; + + +std::ostream& operator<<(std::ostream& os, L3NonCallSSComponent::ComponentTypeTag CTT); + +/** +Parse facility component into its object type. +@param source The L3 bits. +@return A pointer to a new component or NULL on failure. +*/ +L3NonCallSSComponent* parseL3NonCallSSComponent(const L3Frame& source, size_t &rp); + +/** +A Factory function to return a L3NonCallSSComponent of the specified CTT. +Returns NULL if the CTT is not supported. +*/ +L3NonCallSSComponent* L3NonCallSSComponentFactory(L3NonCallSSComponent::ComponentTypeTag CTT); + +std::ostream& operator<<(std::ostream& os, const GSM::L3NonCallSSComponent& msg); + +/** Invoke component GSM 04.80 3.6.1 Table 3.3 */ +class L3NonCallSSComponentInvoke : public L3NonCallSSComponent { + + private: + + //Mandatory + L3NonCallSSOperationCode mOperationCode; + //Optionally + L3NonCallSSLinkedID mLinkedID; + L3NonCallSSParameters mParameters; + bool mHaveLinkedID; + bool mHaveParameters; + + public: + + L3NonCallSSComponentInvoke( + const L3NonCallSSInvokeID& wInvokeID = 1, + const L3NonCallSSOperationCode& wOperationCode = L3NonCallSSOperationCode()) + :L3NonCallSSComponent(wInvokeID), + mOperationCode(wOperationCode), + mHaveLinkedID(false), + mHaveParameters(false) + {} + + void linkedID (L3NonCallSSLinkedID LinkedID) {mLinkedID = LinkedID; mHaveLinkedID = true;} + void parameters (L3NonCallSSParameters Parameters) {mParameters = Parameters; mHaveParameters = true;} + + const L3NonCallSSOperationCode& operationCode() const {return mOperationCode;} + const L3NonCallSSLinkedID& l3NonCallSSLinkedID() const {assert(mHaveLinkedID); return mLinkedID;} + const L3NonCallSSParameters& l3NonCallSSParameters() const {assert (mHaveParameters); return mParameters;} + + bool haveL3NonCallSSLinkedID() const {return mHaveLinkedID;} + bool haveL3NonCallSSParameters() const {return mHaveParameters;} + + ComponentTypeTag NonCallSSComponentTypeTag() const { return Invoke; } + void writeBody( L3Frame &dest, size_t &wp ); + void parseBody( const L3Frame &src, size_t &rp ); + size_t length(); + void text(std::ostream&) const; + +}; + +/**Return Result component GSM 04.80 3.6.1 Table 3.4 */ +class L3NonCallSSComponentReturnResult : public L3NonCallSSComponent { + + private: + + //Optionally + L3NonCallSSOperationCode mOperationCode; + L3NonCallSSParameters mParameters; + L3NonCallSSLengthField mSequenceLength; + bool mHaveParameters; + + public: + + L3NonCallSSComponentReturnResult(const L3NonCallSSInvokeID& wInvokeID = 1) + :L3NonCallSSComponent(wInvokeID), + mSequenceLength(L3NonCallSSLengthField()), + mHaveParameters(false) + {} + + void operationCode (L3NonCallSSOperationCode OperationCode) {mOperationCode = OperationCode;} + void parameters (L3NonCallSSParameters Parameters) {mParameters = Parameters; mHaveParameters = true;} + + const L3NonCallSSOperationCode& l3NonCallSSOperationCode() const {return mOperationCode;} + const L3NonCallSSParameters& l3NonCallSSParameters() const {assert(mHaveParameters); return mParameters;} + + bool haveL3NonCallSSParameters() const {return mHaveParameters;} + + ComponentTypeTag NonCallSSComponentTypeTag() const { return ReturnResult; } + void writeBody( L3Frame &dest, size_t &wp ); + void parseBody( const L3Frame &src, size_t &rp ); + size_t length(); + void text(std::ostream&) const; + +}; + +/** Return Error component GSM 04.80 3.6.1 Table 3.5 */ +class L3NonCallSSComponentReturnError : public L3NonCallSSComponent { + + private: + + //Mandatory + L3NonCallSSErrorCode mErrorCode; + //Optionally + L3NonCallSSParameters mParameters; + bool mHaveParameters; + + public: + + L3NonCallSSComponentReturnError( + const L3NonCallSSInvokeID& wInvokeID = 1, + const L3NonCallSSErrorCode& wErrorCode = L3NonCallSSErrorCode()) + :L3NonCallSSComponent(wInvokeID), + mErrorCode(wErrorCode), + mHaveParameters(false) + {} + + void parameters (L3NonCallSSParameters Parameters) {mParameters = Parameters; mHaveParameters = true;} + + const L3NonCallSSErrorCode& errorCode() const {return mErrorCode;} + const L3NonCallSSParameters& l3NonCallSSParameters() const {assert(mHaveParameters); return mParameters;} + ComponentTypeTag NonCallSSComponentTypeTag() const { return ReturnError; } + + bool haveL3NonCallSSParameters() const {return mHaveParameters;} + + void writeBody( L3Frame &dest, size_t &wp ); + void parseBody( const L3Frame &src, size_t &rp ); + size_t length(); + void text(std::ostream&) const; + +}; + +/** Reject component GSM 04.80 3.6.1 Table 3.6 */ +class L3NonCallSSComponentReject : public L3NonCallSSComponent { + + private: + + //Mandatory + L3NonCallSSProblemCodeTag mProblemCodeTag; + L3NonCallSSProblemCode mProblemCode; + + public: + + //Mandatory ProblemCodeTag and ProblemCode + L3NonCallSSComponentReject( + const L3NonCallSSInvokeID& wInvokeID = 1, + const L3NonCallSSProblemCodeTag& wProblemCodeTag = L3NonCallSSProblemCodeTag(), + const L3NonCallSSProblemCode& wProblemCode = L3NonCallSSProblemCode()) + :L3NonCallSSComponent (wInvokeID), + mProblemCodeTag(wProblemCodeTag), + mProblemCode(wProblemCode) + {} + + + const L3NonCallSSProblemCodeTag& problemCodeTag() const {return mProblemCodeTag;} + const L3NonCallSSProblemCode& problemCode() const {return mProblemCode;} + + ComponentTypeTag NonCallSSComponentTypeTag() const { return Reject; } + void writeBody( L3Frame &dest, size_t &wp ); + void parseBody( const L3Frame &src, size_t &rp ); + size_t length(); + void text(std::ostream&) const; + +}; + +} +#endif diff --git a/public-trunk/GSM/GSML3NonCallSSElements.cpp b/public-trunk/GSM/GSML3NonCallSSElements.cpp new file mode 100644 index 0000000..b7145c6 --- /dev/null +++ b/public-trunk/GSM/GSML3NonCallSSElements.cpp @@ -0,0 +1,214 @@ +/**@file Elements for call independent Supplementary Service Control, GSM 04.80 3. */ +/* +* 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 . + +*/ + + + + +#include "GSML3NonCallSSElements.h" +#include + +using namespace std; +using namespace GSM; + + +void L3NonCallSSElementOctetLength::writeV( L3Frame& dest, size_t &wp ) const +{ + dest.writeField(wp,mValue,8); +} + +void L3NonCallSSElementOctetLength::parseV( const L3Frame& src, size_t &rp) +{ + mValue = src.readField(rp, 8); + setValue(); +} + + +size_t L3NonCallSSElementUndefLength::lengthV() const +{ + size_t size = sizeof(mValue); + uint64_t tmp = 0; + do + { + size--; + tmp = mValue>>(size*8); + + } while ((tmp == 0) &&(size>0)); + size++; + return size; +} + +void L3NonCallSSElementUndefLength::writeV( L3Frame& dest, size_t &wp ) const +{ + dest.writeField(wp,mValue,lengthV()*8); +} + +void L3NonCallSSElementUndefLength::writeV( L3Frame& dest, size_t &wp, size_t numBits) const +{ + dest.writeField(wp,mValue,numBits); +} + +void L3NonCallSSElementUndefLength::parseV( const L3Frame& src, size_t &rp, size_t numOctets) +{ + mValue = src.readField(rp, numOctets*8); +} + + +void L3NonCallSSOperationCode::text(ostream& os) const +{ + switch (mOperationCode){ + case ProcessUnstructuredSSRequest: + os << "ProcessUnstructuredSSRequest"; break; + case UnstructuredSSRequest: + os << "UnstructuredSSRequest"; break; + case UnstructuredSSNotify: + os << "UnstructuredSSNotify"; break; + } +} + +size_t L3NonCallSSLengthField::lengthV() const +{ + size_t length = 0; + if (mValue <= 0x7f) length = 1; + else + { + uint64_t tmp = mValue; + do + { + length++; + tmp>>=8; + } while(tmp!=0); + length++; + } + return length; +} + +void L3NonCallSSLengthField::writeV( L3Frame& dest, size_t &wp ) const +{ + if (mValue <= 0x7f) { dest.writeField(wp,mValue,8); } + else + { + uint64_t tmp = mValue; + size_t numOctets = 0; + do + { + numOctets++; + tmp>>=8; + } while(tmp!=0); + dest.writeField(wp,numOctets|0x80,8); + dest.writeField(wp,mValue,numOctets*8); + } +} + +void L3NonCallSSLengthField::parseV( const L3Frame& source, size_t &rp) +{ + uint64_t tmp = source.readField(rp, 8); + if ((tmp>>7)==0) mValue = tmp; + else mValue = source.readField(rp, (tmp&0x7f)*8); +} + +size_t L3NonCallSSParameters::length() +{ + unsigned numChar = strlen(mData); + unsigned ussdStringLength = (numChar*7)/8; + if ((numChar*7)%8!=0) ussdStringLength +=1; + mDataLength.setValue(ussdStringLength); + unsigned sequenceLength = mDataLength.value()+mDataLength.lengthV()+4; + if (mHaveAlertingPattern) sequenceLength+=0x3; + mSequenceLength.setValue(sequenceLength); + return mSequenceLength.value()+mSequenceLength.lengthV()+1; +} + + + +void L3NonCallSSParameters::parseV(const L3Frame& src, size_t& rp) +{ + rp+=8; + mSequenceLength.parseV(src, rp); + rp+=16; + mDataCodingScheme = src.readField(rp,8); + rp+=8; + mDataLength.parseV(src, rp); + unsigned numChar = mDataLength.value()*8/7; + BitVector chars(src.tail(rp)); + chars.LSB8MSB(); + size_t crp=0; + for (int i=0; i + + +namespace GSM { + +/** +This a virtual class for L3 call independent Supplementary Service Control Elements +with length one octet. +*/ +class L3NonCallSSElementOctetLength : public L3ProtocolElement { + + protected: + + uint64_t mValue; + virtual void setValue() = 0; + + public: + + L3NonCallSSElementOctetLength(uint64_t wValue = 0) + :L3ProtocolElement(), + mValue(wValue) + {} + + size_t lengthV() const { return 1; } + void writeV( L3Frame& dest, size_t &wp) const; + void parseV(const L3Frame&, size_t&, size_t) { abort(); } + void parseV(const L3Frame& src, size_t& rp); + +}; + + +/** +This a virtual class for L3 call independent Supplementary Service Control Elements +with undefined length. +*/ +class L3NonCallSSElementUndefLength : public L3ProtocolElement { + + protected: + + uint64_t mValue; + + public: + + L3NonCallSSElementUndefLength(uint64_t wValue = 0) + :L3ProtocolElement(), + mValue(wValue) + {} + + size_t lengthV() const; + void writeV( L3Frame& dest, size_t &wp) const; + void writeV( L3Frame& dest, size_t &wp, size_t numOctets) const; + void parseV(const L3Frame&, size_t&, size_t expectedLength); + void parseV(const L3Frame& src, size_t& rp){ abort(); } + void text(std::ostream& os) const {os << mValue;} + uint64_t value() const { return mValue; } + +}; + +/** +Classes for L3 call independent Supplementary Service Control Elements, GSM 04.80 3 and GSM 04.80 2. +*/ + +class L3NonCallSSComponentTypeTag : public L3NonCallSSElementOctetLength { + + public: + + /** GSM 04.80, Table 3.7 */ + enum ComponentTypeTag { + Invoke = 0xa1, + ReturnResult = 0xa2, + ReturnError = 0xa3, + Reject = 0xa4 + }; + + private: + + ComponentTypeTag mComponentTypeTag; + void setValue () {mComponentTypeTag = (ComponentTypeTag)mValue;} + + public: + + L3NonCallSSComponentTypeTag(ComponentTypeTag wComponentTypeTag = Invoke) + :L3NonCallSSElementOctetLength(wComponentTypeTag), + mComponentTypeTag(wComponentTypeTag) + {} + void text(std::ostream& os) const {os << mComponentTypeTag;} + ComponentTypeTag NonCallSSComponentTypeTag() const {return mComponentTypeTag;} + +}; + +class L3NonCallSSOperationCode : public L3NonCallSSElementOctetLength { + + public: + + enum OperationCode { + ProcessUnstructuredSSRequest = 0x3b, + UnstructuredSSRequest = 0x3c, + UnstructuredSSNotify = 0x3d + }; + + private: + + OperationCode mOperationCode; + void setValue () {mOperationCode = (OperationCode)mValue;} + + public: + + L3NonCallSSOperationCode(OperationCode wOperationCode = ProcessUnstructuredSSRequest) + :L3NonCallSSElementOctetLength(wOperationCode), + mOperationCode(wOperationCode) + {} + + void text(std::ostream& os) const; + OperationCode NonCallSSOperationCode() const {return mOperationCode;} + +}; + +class L3NonCallSSErrorCode : public L3NonCallSSElementOctetLength { + + public: + + enum ErrorCode { + SystemFailure = 0x22, + DataMissing = 0x23, + UnexpectedDataValue = 0x24, + UnknownAlphabet = 0x47, + CallBarred = 0xd, + AbsentSubscriber = 0x1b, + IllegalSubscriber = 0x9, + IllegalEquipment = 0xc, + UssdBusy = 0x48 + }; + + private: + + ErrorCode mErrorCode; + void setValue () {mErrorCode = (ErrorCode)mValue;} + + public: + + L3NonCallSSErrorCode(ErrorCode wErrorCode = SystemFailure) + :L3NonCallSSElementOctetLength(wErrorCode), + mErrorCode(wErrorCode) + {} + + void text(std::ostream& os) const {os << mErrorCode;} + ErrorCode NonCallSSErrorCode() const {return mErrorCode;} + +}; + +class L3NonCallSSProblemCodeTag : public L3NonCallSSElementOctetLength { + + public: + + /** GSM 04.80, Table 3.13 */ + enum ProblemCodeTag { + GeneralProblemTag = 0x80, + InvokeProblemTag = 0x81, + ReturnResultProblemTag = 0x82, + ReturnErrorProblemTag =0x83 + }; + + private: + + ProblemCodeTag mProblemCodeTag; + void setValue () {mProblemCodeTag = (ProblemCodeTag)mValue;} + + public: + + L3NonCallSSProblemCodeTag(ProblemCodeTag wProblemCodeTag = GeneralProblemTag) + :L3NonCallSSElementOctetLength(wProblemCodeTag), + mProblemCodeTag(wProblemCodeTag) + {} + + void text(std::ostream& os) const {os << mProblemCodeTag;} + ProblemCodeTag NonCallSSProblemCodeTag() const {return mProblemCodeTag;} + +}; + +class L3NonCallSSProblemCode : public L3NonCallSSElementOctetLength { + + public: + /** GSM 04.80, Table 3.14 - 3.17 */ + enum ProblemCode { + DuplicateInvokeID = 0x0, + UnrecognizedOperation = 0x1, + MistypedParameter = 0x2, + ResourceLimitation = 0x3, + InitiatingRelease = 0x4, + UnrecognizedLinkedID = 0x5, + LinkedResponseUnexpected = 0x6, + UnexpectedLinkedOperation = 0x7 + }; + + private: + + ProblemCode mProblemCode; + void setValue () {mProblemCode = (ProblemCode)mValue;} + + public: + + L3NonCallSSProblemCode(ProblemCode wProblemCode = DuplicateInvokeID) + :L3NonCallSSElementOctetLength(wProblemCode), + mProblemCode(wProblemCode) + {} + + void text(std::ostream& os) const {os << mProblemCode;} + ProblemCode NonCallSSProblemCode() const {return mProblemCode;} + +}; + +class L3NonCallSSVersionIndicator : public L3NonCallSSElementOctetLength { + + public: + + enum VersionIndicator { + Indicator1 = 0x0, + Indicator2 = 0x1 + }; + + private: + + VersionIndicator mVersionIndicator; + void setValue () {mVersionIndicator = (VersionIndicator)mValue;} + + public: + + L3NonCallSSVersionIndicator(VersionIndicator wVersionIndicator = Indicator1) + :L3NonCallSSElementOctetLength(wVersionIndicator), + mVersionIndicator(wVersionIndicator) + {} + + void text(std::ostream& os) const {os << mVersionIndicator;} + VersionIndicator NonCallSSVersionIndicator() const {return mVersionIndicator;} + +}; + +class L3NonCallSSInvokeID : public L3NonCallSSElementUndefLength { + + public: + L3NonCallSSInvokeID (uint64_t wValue = 1) + :L3NonCallSSElementUndefLength(wValue) + {} + +}; + +class L3NonCallSSLinkedID : public L3NonCallSSElementUndefLength { + + public: + L3NonCallSSLinkedID (uint64_t wValue = 0) + :L3NonCallSSElementUndefLength(wValue) + {} + +}; + +class L3NonCallSSCause : public L3NonCallSSElementUndefLength { + + public: + L3NonCallSSCause (uint64_t wValue = 0) + :L3NonCallSSElementUndefLength(wValue) + {} + +}; + +class L3NonCallSSLengthField : public L3ProtocolElement { + + protected: + + uint64_t mValue; + + public: + + L3NonCallSSLengthField(uint64_t wValue = 0) + :L3ProtocolElement(), + mValue(wValue) + {} + + size_t lengthV() const; + void writeV( L3Frame& dest, size_t &wp) const; + void parseV(const L3Frame& src, size_t& rp); + void parseV(const L3Frame&, size_t&, size_t) { abort(); } + void text(std::ostream& os) const {os << mValue;} + uint64_t value() const { return mValue; } + void setValue (uint64_t value) {mValue = value;} + +}; + +class L3NonCallSSParameters : public L3ProtocolElement { + + private: + + unsigned mDataCodingScheme; ///< data coding scheme + char mData[184]; ///< actual data, as a C string + L3NonCallSSLengthField mSequenceLength; + L3NonCallSSLengthField mDataLength; + unsigned mAlertingPattern; + bool mHaveAlertingPattern; + + public: + + L3NonCallSSParameters(unsigned wDataCodingScheme=0xF) + :L3ProtocolElement(), + mDataCodingScheme(wDataCodingScheme), + mSequenceLength(L3NonCallSSLengthField()), + mDataLength(L3NonCallSSLengthField()), + mAlertingPattern(0), + mHaveAlertingPattern(false) + { mData[0]='\0'; } + + /** Initize from a simple C string. */ + L3NonCallSSParameters(const char* text) + :L3ProtocolElement(), + mDataCodingScheme(0xF), + mSequenceLength(L3NonCallSSLengthField()), + mDataLength(L3NonCallSSLengthField()), + mAlertingPattern(0), + mHaveAlertingPattern(false) + { strncpy(mData,text,182); + mData[sizeof(mData) - 1] = '\0';} + + void DataCodingScheme(unsigned wDataCodingScheme) { mDataCodingScheme=wDataCodingScheme; } + unsigned DataCodingScheme() { return mDataCodingScheme; } + void alertingPattern (unsigned AlertingPattern) {mAlertingPattern = AlertingPattern; mHaveAlertingPattern = true;} + const char* data() const { return mData; } + const unsigned& alertingPattern() const {assert(mHaveAlertingPattern); return mAlertingPattern;} + bool haveAlertingPattern() const {return mHaveAlertingPattern;} + size_t lengthV() const { abort(); } + size_t length(); + void parseV(const L3Frame&, size_t&, size_t) { abort(); } + void parseV(const L3Frame&, size_t&); + + void writeV(L3Frame&, size_t&) const { abort(); } + void write(L3Frame&, size_t&); + void text(std::ostream&) const; +}; +} +#endif diff --git a/public-trunk/GSM/GSML3NonCallSSMessages.cpp b/public-trunk/GSM/GSML3NonCallSSMessages.cpp new file mode 100644 index 0000000..7e47737 --- /dev/null +++ b/public-trunk/GSM/GSML3NonCallSSMessages.cpp @@ -0,0 +1,210 @@ +/** @file Messages for call independent Supplementary Service Control, GSM 04.80 2.2. */ +/* +* 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 . + +*/ + + + +#include +#include "GSML3NonCallSSMessages.h" +#include + + +using namespace std; +using namespace GSM; + + +ostream& operator<<(ostream& os, const L3NonCallSSMessage& msg) +{ + msg.text(os); + return os; +} + +ostream& GSM::operator<<(ostream& os, L3NonCallSSMessage::MessageType val) +{ + switch (val) { + case L3NonCallSSMessage::ReleaseComplete: + os << "ReleaseComplete"; break; + case L3NonCallSSMessage::Facility: + os << "Facility"; break; + case L3NonCallSSMessage::Register: + os << "Register"; break; + default: os << hex << "0x" << (int)val << dec; + } + return os; +} + +L3NonCallSSMessage * GSM::L3NonCallSSFactory(L3NonCallSSMessage::MessageType MTI) +{ + LOG(DEBUG) << "Factory MTI"<< (int)MTI; + switch (MTI) { + case L3NonCallSSMessage::Facility: return new L3NonCallSSFacilityMessage(); + case L3NonCallSSMessage::Register: return new L3NonCallSSRegisterMessage(); + case L3NonCallSSMessage::ReleaseComplete: return new L3NonCallSSReleaseCompleteMessage(); + default: { + //LOG(NOTICE) << "no L3 NonCallSS factory support for message "<< MTI; + return NULL; + } + } +} + +L3NonCallSSMessage * GSM::parseL3NonCallSS(const L3Frame& source) +{ + // mask out bit #7 (1011 1111) so use 0xbf + L3NonCallSSMessage::MessageType MTI = (L3NonCallSSMessage::MessageType)(0xbf & source.MTI()); + LOG(DEBUG) << "MTI= "<< (int)MTI; + L3NonCallSSMessage *retVal = L3NonCallSSFactory(MTI); + if (retVal==NULL) return NULL; + retVal->TIValue(source.TIValue()); + retVal->TIFlag(source.TIFlag()); + retVal->parse(source); + LOG(DEBUG) << "parse L3 Non Call SS Message" << *retVal; + return retVal; +} + +void L3NonCallSSMessage::write(L3Frame& dest) const +{ + // We override L3Message::write for the transaction identifier. + size_t l3len = bitsNeeded(); + if (dest.size()!=l3len) dest.resize(l3len); + size_t wp = 0; + dest.writeField(wp,mTIFlag,1); + dest.writeField(wp,mTIValue,3); + dest.writeField(wp,PD(),4); + dest.writeField(wp,MTI(),8); + writeBody(dest,wp); +} + +void L3NonCallSSMessage::text(ostream& os) const +{ + os << " MessageType = " <<(MessageType) MTI(); + os << " TI = (" << mTIFlag << "," << mTIValue << ")"; +} + +void L3NonCallSSFacilityMessage::writeBody( L3Frame &dest, size_t &wp ) const +{ + dest.writeField(wp,mL3NonCallSSComponent->length(),8); + mL3NonCallSSComponent->write(dest, wp); +} + +void L3NonCallSSFacilityMessage::parseBody( const L3Frame &src, size_t &rp ) +{ + rp+=8; + mL3NonCallSSComponent = parseL3NonCallSSComponent(src, rp); +} + +void L3NonCallSSFacilityMessage::text(ostream& os) const +{ + L3NonCallSSMessage::text(os); + os << " L3NonCallSSComponent = (" << *mL3NonCallSSComponent << ")"; +} + +void L3NonCallSSRegisterMessage::writeBody( L3Frame &dest, size_t &wp ) const +{ + dest.writeField(wp,0x1c,8); + dest.writeField(wp,mL3NonCallSSComponent->length(),8); + mL3NonCallSSComponent->write(dest, wp); +} + +void L3NonCallSSRegisterMessage::parseBody( const L3Frame &src, size_t &rp ) +{ + rp+=16; + mL3NonCallSSComponent = parseL3NonCallSSComponent(src, rp); + if((src.length() > mL3NonCallSSComponent->length()+2) && (src.readField(rp,8) == 0x7f)) + { + rp+=8; + mSSVersionIndicator.parseV(src, rp); + mHaveSSVersionIndicator = true; + } +} + +size_t L3NonCallSSRegisterMessage::bodyLength() const +{ + size_t bodyLength; + bodyLength = mL3NonCallSSComponent->length()+2; + if(mHaveSSVersionIndicator) bodyLength+=3; + return bodyLength; +} + +void L3NonCallSSRegisterMessage::text(ostream& os) const +{ + L3NonCallSSMessage::text(os); + os << " L3NonCallSSComponent = (" << *mL3NonCallSSComponent<< ")"; + if(mHaveSSVersionIndicator) os << " L3SSVersionIndicator = " << mSSVersionIndicator; +} + +void L3NonCallSSReleaseCompleteMessage::writeBody( L3Frame &dest, size_t &wp ) const +{ + if(mHaveCause) + { + dest.writeField(wp,0x08,8); + dest.writeField(wp,mCause.lengthV(),8); + mCause.writeV(dest, wp); + } + if(mHaveL3NonCallSSComponent) + { + dest.writeField(wp,0x1c,8); + dest.writeField(wp,mL3NonCallSSComponent->length(),8); + mL3NonCallSSComponent->write(dest, wp); + } +} + +void L3NonCallSSReleaseCompleteMessage::parseBody( const L3Frame &src, size_t &rp ) +{ + + if (src.length()>2) + { + if(mCause.parseTLV(0x08, src,rp)) + { + mHaveCause = true; + if ((src.length() > mCause.lengthTLV()+2)&&(src.readField(rp,8) == 0x1c)) + { + rp+=8; + mL3NonCallSSComponent = parseL3NonCallSSComponent(src, rp); + mHaveL3NonCallSSComponent = true; + } + } + else if (src.readField(rp,8) == 0x1c) + { + rp+=8; + mL3NonCallSSComponent = parseL3NonCallSSComponent(src, rp); + mHaveL3NonCallSSComponent = true; + } + } +} + +size_t L3NonCallSSReleaseCompleteMessage::bodyLength() const +{ + size_t bodyLength = 0; + if(mHaveCause) bodyLength+=mCause.lengthTLV(); + if(mHaveL3NonCallSSComponent) bodyLength+=mL3NonCallSSComponent->length()+2; + return bodyLength; +} + +void L3NonCallSSReleaseCompleteMessage::text(ostream& os) const +{ + L3NonCallSSMessage::text(os); + if(mHaveL3NonCallSSComponent) os << " L3NonCallSSComponent = (" << *mL3NonCallSSComponent << ")"; + if(mHaveCause) os << " L3Cause = " << mCause; +} + diff --git a/public-trunk/GSM/GSML3NonCallSSMessages.h b/public-trunk/GSM/GSML3NonCallSSMessages.h new file mode 100644 index 0000000..c5bc7ed --- /dev/null +++ b/public-trunk/GSM/GSML3NonCallSSMessages.h @@ -0,0 +1,186 @@ +/**@file Messages for call independent Supplementary Service Control, GSM 04.80 2.2. */ +/* +* 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 . + +*/ + + + +#ifndef GSML3NONCALLSSMESSAGES_H +#define GSML3NONCALLSSMESSAGES_H + +#include "GSMCommon.h" +#include "GSML3Message.h" +#include "GSML3NonCallSSElements.h" +#include "GSML3NonCallSSComponents.h" + + + +namespace GSM { + +/** +This a virtual class for L3 Messages for call independent Supplementary Service Control. +These messages are defined in GSM 04.80 2.2. +*/ +class L3NonCallSSMessage : public L3Message { + + private: + + unsigned mTIValue; ///< short transaction ID, GSM 04.07 11.2.3.1.3. + unsigned mTIFlag; ///< 0 for originating side, see GSM 04.07 11.2.3.1.3. + + public: + + /** GSM 04.80, Table 3.1 */ + enum MessageType { + /**@name Clearing message */ + //@{ + ReleaseComplete=0x2a, + //@} + /**@name Miscellaneous message group */ + //@{ + Facility=0x3a, + Register=0x3b + //@} + }; + + L3NonCallSSMessage(unsigned wTIFlag, unsigned wTIValue) + :L3Message(),mTIValue(wTIValue),mTIFlag(wTIFlag) + {} + + /** Override the write method to include transaction identifiers in header. */ + void write(L3Frame& dest) const; + L3PD PD() const { return L3NonCallSSPD; } + unsigned TIValue() const { return mTIValue; } + void TIValue(unsigned wTIValue) { mTIValue = wTIValue; } + unsigned TIFlag() const { return mTIFlag; } + void TIFlag(unsigned wTIFlag) { mTIFlag = wTIFlag; } + void text(std::ostream&) const; + +}; + +std::ostream& operator<<(std::ostream& os, const GSM::L3NonCallSSComponent& msg); +std::ostream& operator<<(std::ostream& os, L3NonCallSSMessage::MessageType MTI); + + + +/** +Parse a complete L3 call independent supplementary service control message into its object type. +@param source The L3 bits. +@return A pointer to a new message or NULL on failure. +*/ +L3NonCallSSMessage* parseL3NonCallSS(const L3Frame& source); + +/** +A Factory function to return a L3NonCallSSMessage of the specified MTI. +Returns NULL if the MTI is not supported. +*/ +L3NonCallSSMessage* L3NonCallSSFactory(L3NonCallSSMessage::MessageType MTI); + +/** Facility Message GSM 04.80 2.3. */ +class L3NonCallSSFacilityMessage : public L3NonCallSSMessage { + + private: + + L3NonCallSSComponent* mL3NonCallSSComponent; + + public: + + L3NonCallSSFacilityMessage(unsigned wTIFlag = 0, unsigned wTIValue = 7, + L3NonCallSSComponent* wL3NonCallSSComponent = NULL) + :L3NonCallSSMessage(wTIFlag,wTIValue), + mL3NonCallSSComponent (wL3NonCallSSComponent) + {} + + L3NonCallSSComponent* l3NonCallSSComponent() const {return mL3NonCallSSComponent;} + int MTI() const { return Facility; } + void writeBody( L3Frame &dest, size_t &wp ) const; + void parseBody( const L3Frame &src, size_t &rp ); + size_t bodyLength() const {return mL3NonCallSSComponent->length()+1;} + void text(std::ostream& os) const; + +}; + +/** Register Message GSM 04.80 2.4. */ +class L3NonCallSSRegisterMessage : public L3NonCallSSMessage { + + private: + + L3NonCallSSComponent* mL3NonCallSSComponent; + L3NonCallSSVersionIndicator mSSVersionIndicator; + bool mHaveSSVersionIndicator; + + public: + + L3NonCallSSRegisterMessage(unsigned wTIFlag = 0, unsigned wTIValue = 7, + L3NonCallSSComponent* wL3NonCallSSComponent = NULL) + :L3NonCallSSMessage(wTIFlag,wTIValue), + mL3NonCallSSComponent (wL3NonCallSSComponent), + mHaveSSVersionIndicator(false) + {} + + L3NonCallSSComponent* l3NonCallSSComponent() const {return mL3NonCallSSComponent;} + const L3NonCallSSVersionIndicator& SSVersionIndicator() const {assert(mHaveSSVersionIndicator); return mSSVersionIndicator;} + bool haveL3NonCallSSVersionIndicator() const {return mHaveSSVersionIndicator;} + int MTI() const { return Register; } + void writeBody( L3Frame &dest, size_t &wp ) const; + void parseBody( const L3Frame &src, size_t &rp ); + size_t bodyLength() const; + void text(std::ostream&) const; + +}; + +/** Release Complete Message GSM 04.80 2.5. */ +class L3NonCallSSReleaseCompleteMessage : public L3NonCallSSMessage { + + private: + + L3NonCallSSComponent* mL3NonCallSSComponent; + bool mHaveL3NonCallSSComponent; + L3NonCallSSCause mCause; + bool mHaveCause; + + public: + + L3NonCallSSReleaseCompleteMessage(unsigned wTIFlag = 0, unsigned wTIValue = 7) + :L3NonCallSSMessage(wTIFlag,wTIValue), + mHaveL3NonCallSSComponent(false), + mHaveCause(false) + {} + + void component (L3NonCallSSComponent* Component) {mL3NonCallSSComponent = Component; mHaveL3NonCallSSComponent = true;} + void cause (L3NonCallSSCause Cause) {mCause = Cause; mHaveCause = true;} + + L3NonCallSSComponent* l3NonCallSSComponent() const {assert(mHaveL3NonCallSSComponent); return mL3NonCallSSComponent;} + bool haveL3NonCallSSComponent() const {return mHaveL3NonCallSSComponent;} + const L3NonCallSSCause& l3NonCallSSCause() const {assert(mHaveCause); return mCause;} + bool haveL3NonCallSSCause() const {return mHaveCause;} + int MTI() const { return ReleaseComplete; } + void writeBody( L3Frame &dest, size_t &wp ) const; + void parseBody( const L3Frame &src, size_t &rp ); + size_t bodyLength() const; + void text(std::ostream&) const; + +}; + +} +#endif diff --git a/public-trunk/GSM/Makefile.am b/public-trunk/GSM/Makefile.am index 9f116da..bcb3b35 100644 --- a/public-trunk/GSM/Makefile.am +++ b/public-trunk/GSM/Makefile.am @@ -37,6 +37,9 @@ libGSM_la_SOURCES = \ GSML3Message.cpp \ GSML3MMElements.cpp \ GSML3MMMessages.cpp \ + GSML3NonCallSSComponents.cpp \ + GSML3NonCallSSElements.cpp \ + GSML3NonCallSSMessages.cpp \ GSML3RRElements.cpp \ GSML3RRMessages.cpp \ GSMLogicalChannel.cpp \ @@ -58,6 +61,9 @@ noinst_HEADERS = \ GSML3Message.h \ GSML3MMElements.h \ GSML3MMMessages.h \ + GSML3NonCallSSComponents.h \ + GSML3NonCallSSElements.h \ + GSML3NonCallSSMessages.h \ GSML3RRElements.h \ GSML3RRMessages.h \ GSMLogicalChannel.h \ diff --git a/public-trunk/SIP/SIPInterface.cpp b/public-trunk/SIP/SIPInterface.cpp index d373b16..0ec9a7a 100644 --- a/public-trunk/SIP/SIPInterface.cpp +++ b/public-trunk/SIP/SIPInterface.cpp @@ -251,6 +251,7 @@ bool SIPInterface::checkInvite( osip_message_t * msg ) // Check for INVITE or MESSAGE methods. GSM::ChannelType requiredChannel; bool channelAvailable = false; + bool shouldPage = true; GSM::L3CMServiceType serviceType; if (strcmp(method,"INVITE") == 0) { // INVITE is for MTC. @@ -267,10 +268,25 @@ bool SIPInterface::checkInvite( osip_message_t * msg ) serviceType = L3CMServiceType::MobileTerminatedCall; } else if (strcmp(method,"MESSAGE") == 0) { - // MESSAGE is for MTSMS. - requiredChannel = GSM::SDCCHType; - channelAvailable = gBTS.SDCCHAvailable(); - serviceType = L3CMServiceType::MobileTerminatedShortMessage; + // MESSAGE is for MTSMS or USSD + if ( strcmp(gConfig.getStr("USSD.SIP.user"), msg->from->url->username)==0 + && strcmp(gConfig.getStr("USSD.SIP.domain"), msg->from->url->host)==0) + { + LOG(INFO) << "received MESSAGE is USSD from: " + << msg->from->url->username << "@" << msg->from->url->host; + requiredChannel = GSM::SDCCHType; + // TODO:: Understand how to behave when we need to page? + channelAvailable = true; //gBTS.SDCCHAvailable(); + serviceType = L3CMServiceType::SupplementaryService; + } + else + { + LOG(INFO) << "received MESSAGE is SMS from: " + << msg->from->url->username << "@" << msg->from->url->host; + requiredChannel = GSM::SDCCHType; + channelAvailable = gBTS.SDCCHAvailable(); + serviceType = L3CMServiceType::MobileTerminatedShortMessage; + } } else { // We must not handle this method. @@ -312,7 +328,8 @@ bool SIPInterface::checkInvite( osip_message_t * msg ) L3MobileIdentity mobile_id(IMSI); // Check SIP map. Repeated entry? Page again. - if (mSIPMap.map().readNoBlock(call_id_num) != NULL) { + // Skip this for USSD. + if ( mSIPMap.map().readNoBlock(call_id_num) != NULL) { TransactionEntry transaction; if (!gTransactionTable.find(mobile_id,transaction)) { // FIXME -- Send "call leg non-existent" response on SIP interface. @@ -349,15 +366,33 @@ bool SIPInterface::checkInvite( osip_message_t * msg ) LOG(NOTICE) << "INVITE/MESSAGE with no From: username for " << mobile_id; } LOG(DEBUG) << "callerID " << callerID << "@" << callerHost; - // Build the transaction table entry. - // This constructor sets TI flag=0, TI=0 for an MT transaction. - TransactionEntry transaction(mobile_id,serviceType,callerID); + + TransactionEntry transaction; + // In case of USSD we should check for existing transaction first, because + // SIP MESSAGEs are sent out of call, our internal while USSD transaction + // stays alive for the whole duration of a session. + if ( serviceType == L3CMServiceType::SupplementaryService + && gTransactionTable.find(mobile_id,L3CMServiceType::SupplementaryService,transaction)) + { + // It's old USSD. No need to page. + shouldPage = false; + LOG(DEBUG) << "Existing USSD transaction found: " << transaction; + } + else + { + // It's not USSD or there are no existing transaction for it. + // Build new transaction table entry. + // This constructor sets TI flag=0, TI=0 for an MT transaction. + transaction = TransactionEntry(mobile_id,serviceType,callerID); + LOG(DEBUG) << "Created new transaction"; + } LOG(DEBUG) << "call_id_num \"" << call_id_num << "\""; LOG(DEBUG) << "IMSI \"" << IMSI << "\""; transaction.SIP().User(call_id_num,IMSI,callerID,callerHost); transaction.SIP().saveINVITE(msg); - if (serviceType == L3CMServiceType::MobileTerminatedShortMessage) { + if ( serviceType == L3CMServiceType::MobileTerminatedShortMessage + || serviceType == L3CMServiceType::SupplementaryService) { osip_body_t *body; osip_message_get_body(msg,0,&body); if (!body) return false; @@ -365,15 +400,29 @@ bool SIPInterface::checkInvite( osip_message_t * msg ) if (text) { transaction.message(text); } - else LOG(NOTICE) << "MTSMS incoming MESSAGE method with no message body for " << mobile_id; + else LOG(NOTICE) << "MTSMS/USSD incoming MESSAGE method with no message body for " << mobile_id; + } + LOG(INFO) << "MTC/MTSMS/USSD is adding/updating transaction: "<< transaction; + gTransactionTable.add(transaction); + + if (serviceType == L3CMServiceType::SupplementaryService) + { + // TODO:: What to do in case of MT-USSD? + USSDData *ussdData = transaction.ussdData(); + if (ussdData) + { + LOG(DEBUG) << "Signaling incoming USSD data"; + ussdData->signalIncomingData(); + } + } + + if (shouldPage) + { + // Add to paging list and tell the remote SIP end that we are trying. + LOG(DEBUG) << "MTC/MTSMS/USSD new SIP invite, initial paging for mobile ID " << mobile_id; + gBTS.pager().addID(mobile_id,requiredChannel,transaction); + // FIXME -- Send TRYING? See MTCSendTrying for example. } - LOG(INFO) << "MTC MTSMS making transaction and add to transaction table: "<< transaction; - gTransactionTable.add(transaction); - - // Add to paging list and tell the remote SIP end that we are trying. - LOG(DEBUG) << "MTC MTSMS new SIP invite, initial paging for mobile ID " << mobile_id; - gBTS.pager().addID(mobile_id,requiredChannel,transaction); - // FIXME -- Send TRYING? See MTCSendTrying for example. return true; } diff --git a/public-trunk/apps/OpenBTS.config.example b/public-trunk/apps/OpenBTS.config.example index 9064d28..9003334 100644 --- a/public-trunk/apps/OpenBTS.config.example +++ b/public-trunk/apps/OpenBTS.config.example @@ -213,6 +213,31 @@ SMS.DefaultDestSMSC 0000 SIP.SMSC smsc +# +# USSD parameters +# +# USSD timeout for waiting MS response +USSD.timeout 100000 + +# USSD handlers map. +# Option key has form USSD.Handler., where is +# one of the following: HTTP, CLI and Test. +# Option value is a regexp. If it matches initial MO-USSD request, +# then request is passed for processing to according USSD handler. +# Comment out a handler option to completely disable the handler. +#USSD.Handler.CLI \*101# +USSD.Handler.Test \*(100|1011|102)# +#USSD.Handler.HTTP + +# The USSD HTTP gateway. +USSD.HTTP.Gateway api.clickatell.com + +# IF USSD.HTTP.Gateway IS DEFINED, USSD.HTTP.AccessString MUST ALSO BE DEFINED. +USSD.HTTP.AccessString sendmsg?user=xxxx&password=xxxx&api_id=xxxx + +# We split long messages into few screens. This string is appended +# to the end of each screen to notify user that text is to be continued. +USSD.ContinueStr ...1> # Things to query during registration updates. diff --git a/public-trunk/smqueue/smcommands.cpp b/public-trunk/smqueue/smcommands.cpp index 319c2cd..0b35bc7 100644 --- a/public-trunk/smqueue/smcommands.cpp +++ b/public-trunk/smqueue/smcommands.cpp @@ -83,6 +83,18 @@ shortcode_quick_chk (const char *imsi, const char *msgtext, return SCA_REPLY; } +enum short_code_action +shortcode_ussd_test (const char *imsi, const char *msgtext, + short_code_params *scp) +{ + ostringstream answer; + + answer << "3" << endl; + answer << "USSD test ok!"; + scp->scp_reply = new_strdup(answer.str().c_str()); + return SCA_REPLY; +} + /* * 411 -- information. * Start by telling people their phone number. @@ -383,5 +395,6 @@ SMqueue::init_smcommands (short_code_map_t *scm) if (gConfig.defines("SC.SMSC.Code")) { (*scm)[gConfig.getStr("SC.SMSC.Code")] = shortcode_smsc; } + (*scm)["USSD"] = shortcode_ussd_test; // (*scm)["666"] = shortcode_text_access; }