From 2ab4ca6648ce3bcba68b3b8da33d49976d19d248 Mon Sep 17 00:00:00 2001 From: Alexander Chemeris Date: Tue, 10 Aug 2010 19:55:37 +0400 Subject: [PATCH 01/16] Implemented "trans" CLI command to list all active transacions. --- public-trunk/CLI/CLI.cpp | 14 ++++++++++++++ public-trunk/Control/ControlCommon.cpp | 12 ++++++++++++ public-trunk/Control/ControlCommon.h | 3 +++ 3 files changed, 29 insertions(+) diff --git a/public-trunk/CLI/CLI.cpp b/public-trunk/CLI/CLI.cpp index 19fba44..ba3564f 100644 --- a/public-trunk/CLI/CLI.cpp +++ b/public-trunk/CLI/CLI.cpp @@ -290,6 +290,19 @@ int tmsis(int argc, char** argv, ostream& os, istream& is) } +/** Print the transactions table. */ +int trans(int argc, char** argv, ostream& os, istream& is) +{ + if (argc!=1) return BAD_NUM_ARGS; + +// os << "TMSI IMSI IMEI age used" << endl; + gTransactionTable.dump(os); + os << endl << gTransactionTable.size() << " transactions in table" << endl; + return SUCCESS; +} + + + int findimsi(int argc, char** argv, ostream& os, istream& is) { @@ -759,6 +772,7 @@ Parser::Parser() addCommand("help", showHelp, "[command] -- list available commands or gets help on a specific command."); addCommand("exit", exit_function, "[wait] -- exit the application, either immediately, or waiting for existing calls to clear with a timeout in seconds"); addCommand("tmsis", tmsis, "[\"clear\"] or [\"dump\" filename] -- print/clear the TMSI table or dump it to a file."); + addCommand("trans", trans, "-- print the transactions table."); addCommand("findimsi", findimsi, "[IMSIPrefix] -- prints all imsi's that are prefixed by IMSIPrefix"); addCommand("sendsms", sendsms, " -- send SMS to , addressed from , after prompting."); addCommand("sendrrlp", sendrrlp, " -- send RRLP message to ."); diff --git a/public-trunk/Control/ControlCommon.cpp b/public-trunk/Control/ControlCommon.cpp index 66e0be6..918afb4 100644 --- a/public-trunk/Control/ControlCommon.cpp +++ b/public-trunk/Control/ControlCommon.cpp @@ -343,6 +343,18 @@ size_t TransactionTable::size() } +void TransactionTable::dump(ostream& os) const +{ + mLock.lock(); + TransactionMap::const_iterator tp = mTable.begin(); + while (tp != mTable.end()) { + os << hex << "0x" << tp->first << " " << dec << tp->second << endl; + ++tp; + } + mLock.unlock(); +} + + void Control::clearTransactionHistory( TransactionEntry& transaction ) { SIP::SIPEngine& engine = transaction.SIP(); diff --git a/public-trunk/Control/ControlCommon.h b/public-trunk/Control/ControlCommon.h index b763953..ee8a6d2 100644 --- a/public-trunk/Control/ControlCommon.h +++ b/public-trunk/Control/ControlCommon.h @@ -611,6 +611,9 @@ class TransactionTable { //@} size_t size(); + + /** Write entries as text to a stream. */ + void dump(std::ostream&) const; }; //@} // Transaction Table From 971b6ba700b61aaaf84206cdf2a73ccfe08a8dc1 Mon Sep 17 00:00:00 2001 From: Alexander Chemeris Date: Wed, 18 Aug 2010 22:52:33 +0400 Subject: [PATCH 02/16] Implementation of Semaphore. --- public-trunk/CommonLibs/Threads.cpp | 9 +++++++++ public-trunk/CommonLibs/Threads.h | 31 +++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/public-trunk/CommonLibs/Threads.cpp b/public-trunk/CommonLibs/Threads.cpp index 2df6e78..184c271 100644 --- a/public-trunk/CommonLibs/Threads.cpp +++ b/public-trunk/CommonLibs/Threads.cpp @@ -96,6 +96,15 @@ void Signal::wait(Mutex& wMutex, unsigned timeout) const pthread_cond_timedwait(&mSignal,&wMutex.mMutex,&waitTime); } +/** Wait for semaphore to be signaled with timeout. +* @returns 0 on success, -1 on error or timeout. +*/ +int ThreadSemaphore::wait(unsigned timeout) const +{ + Timeval then(timeout); + struct timespec waitTime = then.timespec(); + return sem_timedwait(&mSem,&waitTime); +} void Thread::start(void *(*task)(void*), void *arg) { diff --git a/public-trunk/CommonLibs/Threads.h b/public-trunk/CommonLibs/Threads.h index d083e3f..6bb57bf 100644 --- a/public-trunk/CommonLibs/Threads.h +++ b/public-trunk/CommonLibs/Threads.h @@ -29,6 +29,7 @@ #include #include #include +#include class Mutex; @@ -120,7 +121,37 @@ class Signal { }; +/** Semaphore */ +class ThreadSemaphore { + private: + + mutable sem_t mSem; + + public: + + ThreadSemaphore(int pshared = 0, unsigned value = 0) { assert(sem_init(&mSem,pshared,value)!=-1); } + + ~ThreadSemaphore() { sem_destroy(&mSem); } + + /** Wait for semaphore to be signaled with timeout. + * @returns 0 on success, -1 on error or timeout. + */ + int wait (unsigned timeout) const; + + /** Wait for semaphore to be signaled infinitely. + * @returns 0 on success, -1 on error. + */ + int wait() const { return sem_wait(&mSem); } + + /** Check if semaphore has been signaled and disarm it. + * @returns 0 if semaphore has been signaled, -1 in other cases. + */ + int trywait() const { return sem_trywait(&mSem); } + + int post() { return sem_post (&mSem); } + +}; #define START_THREAD(thread,function,argument) \ thread.start((void *(*)(void*))function, (void*)argument); From 57d292d962d0de1b35febec71aa1a185e4b461b1 Mon Sep 17 00:00:00 2001 From: Alexander Chemeris Date: Thu, 9 Sep 2010 18:00:49 +0400 Subject: [PATCH 03/16] Better documentation and error reporting for ThreadSemaphore. --- public-trunk/CommonLibs/Threads.cpp | 65 ++++++++++++++++++++++++++--- public-trunk/CommonLibs/Threads.h | 43 +++++++++++++++---- 2 files changed, 93 insertions(+), 15 deletions(-) diff --git a/public-trunk/CommonLibs/Threads.cpp b/public-trunk/CommonLibs/Threads.cpp index 184c271..0449710 100644 --- a/public-trunk/CommonLibs/Threads.cpp +++ b/public-trunk/CommonLibs/Threads.cpp @@ -28,6 +28,7 @@ #include "Threads.h" #include "Timeval.h" +#include using namespace std; @@ -96,14 +97,66 @@ void Signal::wait(Mutex& wMutex, unsigned timeout) const pthread_cond_timedwait(&mSignal,&wMutex.mMutex,&waitTime); } -/** Wait for semaphore to be signaled with timeout. -* @returns 0 on success, -1 on error or timeout. -*/ -int ThreadSemaphore::wait(unsigned timeout) const +ThreadSemaphore::Result ThreadSemaphore::wait(unsigned timeoutMs) { - Timeval then(timeout); + Timeval then(timeoutMs); struct timespec waitTime = then.timespec(); - return sem_timedwait(&mSem,&waitTime); + int s; + while ((s = sem_timedwait(&mSem,&waitTime)) == -1 && errno == EINTR) + continue; + + if (s == -1) + { + if (errno == ETIMEDOUT) + { + return TSEM_TIMEOUT; + } + return TSEM_ERROR; + } + return TSEM_OK; +} + +ThreadSemaphore::Result ThreadSemaphore::wait() +{ + int s; + while ((s = sem_wait(&mSem)) == -1 && errno == EINTR) + continue; + + if (s == -1) + { + return TSEM_ERROR; + } + return TSEM_OK; +} + +ThreadSemaphore::Result ThreadSemaphore::trywait() +{ + int s; + while ((s = sem_trywait(&mSem)) == -1 && errno == EINTR) + continue; + + if (s == -1) + { + if (errno == EAGAIN) + { + return TSEM_TIMEOUT; + } + return TSEM_ERROR; + } + return TSEM_OK; +} + +ThreadSemaphore::Result ThreadSemaphore::post() +{ + if (sem_post(&mSem) != 0) + { + if (errno == EOVERFLOW) + { + return TSEM_OVERFLOW; + } + return TSEM_ERROR; + } + return TSEM_OK; } void Thread::start(void *(*task)(void*), void *arg) diff --git a/public-trunk/CommonLibs/Threads.h b/public-trunk/CommonLibs/Threads.h index 6bb57bf..9015be8 100644 --- a/public-trunk/CommonLibs/Threads.h +++ b/public-trunk/CommonLibs/Threads.h @@ -126,30 +126,55 @@ class ThreadSemaphore { private: - mutable sem_t mSem; + sem_t mSem; public: - ThreadSemaphore(int pshared = 0, unsigned value = 0) { assert(sem_init(&mSem,pshared,value)!=-1); } + enum Result { + TSEM_OK, ///< Success. + TSEM_TIMEOUT, ///< wait() or trywait() timed out. + TSEM_OVERFLOW, ///< post() overflows a semaphore + TSEM_ERROR ///< Generic error. + }; + + /** Create and initialize semaphore. + * @param[in] value - initial semaphore value. + */ + ThreadSemaphore(unsigned value = 0) + { + int s = sem_init(&mSem,0,value); + assert(s == 0); + } ~ThreadSemaphore() { sem_destroy(&mSem); } /** Wait for semaphore to be signaled with timeout. - * @returns 0 on success, -1 on error or timeout. + * @param[in] timeoutMs - timeout in milliseconds + * + * @retval TSEM_OK on success. + * @retval TSEM_TIMEOUT on timeout. + * @retval TSEM_ERROR on error. */ - int wait (unsigned timeout) const; + Result wait(unsigned timeoutMs); /** Wait for semaphore to be signaled infinitely. - * @returns 0 on success, -1 on error. + * @retval TSEM_OK on success. + * @retval TSEM_ERROR on error. */ - int wait() const { return sem_wait(&mSem); } + Result wait(); /** Check if semaphore has been signaled and disarm it. - * @returns 0 if semaphore has been signaled, -1 in other cases. + * @retval TSEM_OK is semaphore is signaled. + * @retval TSEM_TIMEOUT if semaphore is not signaled. + * @retval TSEM_ERROR on error. */ - int trywait() const { return sem_trywait(&mSem); } + Result trywait(); - int post() { return sem_post (&mSem); } + /** Signal semaphore. + * @retval TSEM_OK on success. + * @retval TSEM_ERROR on error. + */ + Result post(); }; From d54e4d82fde060d617e5636f07f75585f5cd7ae1 Mon Sep 17 00:00:00 2001 From: Alexander Chemeris Date: Thu, 4 Nov 2010 20:12:06 +0300 Subject: [PATCH 04/16] Move most of the SMS processing to smqueue (initial check-in). Idea is to make OpenBTS as dumb as possible. It should forward all received SMS messages' RPDU to SMSC (smqueue) and vice versa. All actual message decoding and processing is to be done in smqueue. --- public-trunk/Control/SMSControl.cpp | 251 +++++--------- public-trunk/SIP/SIPEngine.cpp | 11 +- public-trunk/SIP/SIPEngine.h | 4 +- public-trunk/SIP/SIPMessage.cpp | 14 +- public-trunk/SIP/SIPMessage.h | 2 +- public-trunk/SMS/SMSMessages.cpp | 160 ++++++++- public-trunk/SMS/SMSMessages.h | 79 ++++- public-trunk/apps/OpenBTS.config.example | 11 +- public-trunk/smqueue/Makefile.am | 31 +- public-trunk/smqueue/Makefile.standalone | 14 - public-trunk/smqueue/README.smqueue | 6 - public-trunk/smqueue/smcommands.cpp | 4 + public-trunk/smqueue/smqueue.config.example | 13 + public-trunk/smqueue/smqueue.cpp | 343 ++++++++++++-------- public-trunk/smqueue/smqueue.h | 314 ++++++++++-------- 15 files changed, 745 insertions(+), 512 deletions(-) delete mode 100644 public-trunk/smqueue/Makefile.standalone diff --git a/public-trunk/Control/SMSControl.cpp b/public-trunk/Control/SMSControl.cpp index a8d82dc..586baa5 100644 --- a/public-trunk/Control/SMSControl.cpp +++ b/public-trunk/Control/SMSControl.cpp @@ -42,6 +42,7 @@ #include +#include #include #include #include "ControlCommon.h" @@ -86,99 +87,8 @@ L3Frame* getFrameSMS(LogicalChannel *LCH, GSM::Primitive primitive=DATA) } - - - -/**@name Functions for transmitting through various gateways. */ -//@{ - - -/** Substitute spaces with +'s for compatibility with some protocols. */ -void convertText(char *dest, const char *src) +bool sendSIP(const L3MobileIdentity &mobileID, const char* address, const char* body) { - // FIXME -- We should just do full "percent encoding" here. - while (*src != '\0') { - if (*src == ' ') *dest++ = '+'; - else *dest++ = *src; - src++; - } - *dest = '\0'; -} - - - -/** Send SMS via and HTTP interface. */ -bool sendHTTP(const char* destination, const char* message) -{ - char convMessage[strlen(message)+2]; - convertText(convMessage,message); - char command[2048]; - // FIXME -- Check specs for a good timeout value here. - sprintf(command,"wget -T 5 -C -q -O - \"http://%s/http/%s&to=%s&text=%s\" >& /dev/null", - gConfig.getStr("SMS.HTTP.Gateway"), - gConfig.getStr("SMS.HTTP.AccessString"), - destination, convMessage); - LOG(DEBUG) << "MOSMS: sendHTTP sending with " << command; - - // HTTP "GET" method with wget. - // FIXME -- Look at the output of wget to check success. - FILE* wget = popen(command,"r"); - if (!wget) { - LOG(ALARM) << "cannot open wget with " << command; - return false; - } - pclose(wget); - return true; -} - - -/** Send e-mail with local sendmail program. */ -bool sendEMail(const char* address, const char* body, const char* subject=NULL) -{ - // We're not checking for overflow because the TPDU can't be more than a few hundred bytes. - - // Build the command line. - // FIXME -- Use sendmail to have better header control. - char command[1024]; - if (subject) sprintf(command,"mail -s \"%s\" %s",subject,address); - else sprintf(command,"mail %s",address); - LOG(INFO) << "sending SMTP: \"" << body << "\" via \"" << command << "\""; - - // Send the mail. - FILE* mail = popen(command,"w"); - if (!mail) { - LOG(ALARM) << "cannot send mail with \"" << command << "\""; - return false; - } - // FIXME -- We should be sure body is 7-bit clean. - fprintf(mail,"%s",body); - if (pclose(mail) == -1) return false; - return true; -} - - -//@} - - - - -/** - Send a TDPU to a numeric address. - @param mobileID The sender's IMSI. - @param submit A TL-SUBMIT PDU. - @return true on success -*/ -bool sendToNumericAddress(const L3MobileIdentity &mobileID, const TLSubmit& submit) -{ - LOG(INFO) << "from " << mobileID << " message: " << submit; - const TLAddress& address = submit.DA(); - const char* body = submit.UD().data(); - - // If there is an external HTTP gateway, use it. - if (gConfig.defines("SMS.HTTP.Gateway")) return sendHTTP(address.digits(), body); - - // Otherwise, we are looking for a SIP interface to smsd. - // Steps: // 1 -- Create a transaction record. // 2 -- Send it to the server. @@ -186,20 +96,19 @@ bool sendToNumericAddress(const L3MobileIdentity &mobileID, const TLSubmit& subm // 4 -- Return true for OK or ACCEPTED, false otherwise. // Form the TLAddress into a CalledPartyNumber for the transaction. - L3CalledPartyBCDNumber calledParty(address.digits()); + L3CalledPartyBCDNumber calledParty(address); // Step 1 -- Create a transaction record. - TransactionEntry transaction( - mobileID, - L3CMServiceType::ShortMessage, - 0, // doesn't matter - calledParty); + TransactionEntry transaction(mobileID, + L3CMServiceType::ShortMessage, + 0, // doesn't matter + calledParty); transaction.SIP().User(mobileID.digits()); transaction.Q931State(TransactionEntry::SMSSubmitting); gTransactionTable.add(transaction); LOG(DEBUG) << "MOSMS: transaction: " << transaction; // Step 2 -- Send the message to the server. - transaction.SIP().MOSMSSendMESSAGE(address.digits(),gConfig.getStr("SIP.IP"),body); + transaction.SIP().MOSMSSendMESSAGE(address, gConfig.getStr("SIP.IP"), body, false); // Step 3 -- Wait for OK or ACCEPTED. SIPState state = transaction.SIP().MOSMSWaitForSubmit(); @@ -209,67 +118,6 @@ bool sendToNumericAddress(const L3MobileIdentity &mobileID, const TLSubmit& subm return state==SIP::Cleared; } - - - -/** Send a TPDU through whatever gateway is available. */ -bool submitSMS(const L3MobileIdentity& mobileID, const TLSubmit& submit) -{ - LOG(INFO) << "from " << mobileID; - //const TLAddress& address = submit.DA(); - const char* body = submit.UD().data(); - - // Check for direct e-mail address at start of message body. - // FIXME -- This doesn't really follow the spec. See GSM 03.40 3.8. - static const Regexp emailAddress("^[[:graph:]]+@[[:graph:]]+ "); - if (emailAddress.match(body)) { - // FIXME -- Get the sender's E.164 to put in the subject line. - char bodyCopy[strlen(body)+2]; - strcpy(bodyCopy,body); - char* SMTPAddress = bodyCopy; - char* term = strchr(bodyCopy,' '); - // If term's NULL, the regexp is broken. - assert(term); - *term = '\0'; - char* SMTPPayload = term+1; - LOG(INFO) << "sending SMTP to " << SMTPAddress << ": " << SMTPPayload; - if (SMTPPayload) return sendEMail(SMTPAddress,SMTPPayload,"from OpenBTS gateway"); - else return sendEMail(SMTPAddress,"(empty)","from OpenBTS gateway"); - } - - // Send to smsd or HTTP gateway, depending on what's defined in the conig. - return sendToNumericAddress(mobileID,submit); -} - - - -/** - Process the incoming TPDU. - @param mobileID Sender's IMSI. - @param TPDU The TPDU (duh). - @return true if successful -*/ -bool handleTPDU(const L3MobileIdentity& mobileID, const TLFrame& TPDU) -{ - LOG(DEBUG) << "SMS: handleTPDU MTI=" << TPDU.MTI(); - // Handle just the uplink cases. - switch ((TLMessage::MessageType)TPDU.MTI()) { - case TLMessage::DELIVER_REPORT: - case TLMessage::STATUS_REPORT: - return false; - case TLMessage::SUBMIT: { - TLSubmit submit; - submit.parse(TPDU); - LOG(INFO) << "SMS SMS-SUBMIT " << submit; - return submitSMS(mobileID,submit); - } - default: - return false; - } -} - - - /** Process the RPDU. @param mobileID The sender's IMSI. @@ -281,10 +129,9 @@ bool handleRPDU(const L3MobileIdentity& mobileID, const RLFrame& RPDU) LOG(DEBUG) << "SMS: handleRPDU MTI=" << RPDU.MTI(); switch ((RPMessage::MessageType)RPDU.MTI()) { case RPMessage::Data: { - RPData data; - data.parse(RPDU); - LOG(INFO) << "SMS RP-DATA " << data; - return handleTPDU(mobileID,data.TPDU()); + ostringstream body; + RPDU.hex(body); + return sendSIP(mobileID, gConfig.getStr("SIP.SMSC"), body.str().data()); } case RPMessage::Ack: case RPMessage::SMMA: @@ -427,13 +274,7 @@ bool Control::deliverSMSToMS(const char *callingPartyDigits, const char* message // This function is used to deliver messages that originate INSIDE the BTS. // For the normal SMS delivery, see MTSMSController. - // Start ABM in SAP3. - LCH->send(ESTABLISH,3); - // Wait for SAP3 ABM to connect. - // The next read on SAP3 should the ESTABLISH primitive. - // This won't return NULL. It will throw an exception if it fails. - delete getFrameSMS(LCH,ESTABLISH); - +#if 0 // HACK // Check for "Easter Eggs" // TL-PID @@ -449,6 +290,44 @@ bool Control::deliverSMSToMS(const char *callingPartyDigits, const char* message RPData(reference, RPAddress(gConfig.getStr("SMS.FakeSrcSMSC")), TLDeliver(callingPartyDigits,message,TLPID))); +#else + unsigned reference = random() % 255; + BitVector RPDUbits(strlen(message)*4); + if (!RPDUbits.unhex(message)) { + LOG(WARN) << "Hex string parsing failed (in incoming SIP MESSAGE)"; + throw UnexpectedMessage(); + } + + RPData rp_data; + try { + RLFrame RPDU(RPDUbits); + LOG(DEBUG) << "SMS RPDU: " << RPDU; + + rp_data.parse(RPDU); + LOG(DEBUG) << "SMS RP-DATA " << rp_data; + } + catch (SMSReadError) { + LOG(WARN) << "SMS parsing failed (above L3)"; + // Cause 95, "semantically incorrect message". + LCH->send(CPData(1,TI,RPError(95,reference)),3); + throw UnexpectedMessage(); + } + catch (L3ReadError) { + LOG(WARN) << "SMS parsing failed (in L3)"; + // TODO:: send error back to the phone + throw UnsupportedMessage(); + } + CPData deliver(0,TI,rp_data); + +#endif + + // Start ABM in SAP3. + LCH->send(ESTABLISH,3); + // Wait for SAP3 ABM to connect. + // The next read on SAP3 should the ESTABLISH primitive. + // This won't return NULL. It will throw an exception if it fails. + delete getFrameSMS(LCH,ESTABLISH); + LOG(INFO) << "sending " << deliver; LCH->send(deliver,3); @@ -543,7 +422,9 @@ void Control::MTSMSController(TransactionEntry& transaction, // but instead to an RRLP transaction over the already allocated LogicalChannel. const char* m = transaction.message(); // NOTE - not very nice, my way of checking. if ((strlen(m) > 4) && (std::string("RRLP") == std::string(m, m+4))) { - BitVector rrlp_position_request = hex2bitvector(transaction.message() + 4); + const char *transaction_hex = transaction.message() + 4; + BitVector rrlp_position_request(strlen(transaction_hex)*4); + rrlp_position_request.unhex(transaction_hex); LOG(INFO) << "MTSMS: Sending RRLP"; // TODO - how to get mobID here? L3MobileIdentity mobID = L3MobileIdentity("000000000000000"); @@ -579,15 +460,29 @@ void Control::MTSMSController(TransactionEntry& transaction, transaction.Q931State(TransactionEntry::SMSDelivering); gTransactionTable.update(transaction); - bool success = deliverSMSToMS(transaction.calling().digits(),transaction.message(),random()%7,LCH); + try { + bool success = deliverSMSToMS(transaction.calling().digits(),transaction.message(),random()%7,LCH); - // Close the Dm channel. - LOG(INFO) << "MTSMS: closing"; - LCH->send(L3ChannelRelease()); + // Close the Dm channel. + LOG(INFO) << "MTSMS: closing"; + LCH->send(L3ChannelRelease()); - // Ack in SIP domain and update transaction state. - if (success) { + // Ack in SIP domain and update transaction state. + if (success) { + engine.MTSMSSendOK(); + clearTransactionHistory(transaction); + } + } + catch (UnexpectedMessage) { + // TODO -- MUST SEND PERMANENT ERROR HERE!!!!!!!!! engine.MTSMSSendOK(); + LCH->send(L3ChannelRelease()); + clearTransactionHistory(transaction); + } + catch (UnsupportedMessage) { + // TODO -- MUST SEND PERMANENT ERROR HERE!!!!!!!!! + engine.MTSMSSendOK(); + LCH->send(L3ChannelRelease()); clearTransactionHistory(transaction); } } diff --git a/public-trunk/SIP/SIPEngine.cpp b/public-trunk/SIP/SIPEngine.cpp index ffdef05..f300393 100644 --- a/public-trunk/SIP/SIPEngine.cpp +++ b/public-trunk/SIP/SIPEngine.cpp @@ -654,7 +654,7 @@ int SIPEngine::RxFrame(unsigned char * rx_frame){ SIPState SIPEngine::MOSMSSendMESSAGE(const char * wCalledUsername, - const char * wCalledDomain , const char *messageText) + const char * wCalledDomain , const char *messageText, bool plainText) { LOG(DEBUG) << "mState=" << mState; LOG(INFO) << "SIP send to " << wCalledUsername << "@" << wCalledDomain << " MESSAGE " << messageText; @@ -673,11 +673,18 @@ SIPState SIPEngine::MOSMSSendMESSAGE(const char * wCalledUsername, mRemoteUsername = wCalledUsername; mRemoteDomain = wCalledDomain; + const char *content_type; + if (plainText) { + content_type = "text/plain"; + } else { + content_type = "application/vnd.3gpp.sms"; + } + osip_message_t * message = sip_message( mRemoteUsername.c_str(), mSIPUsername.c_str(), mSIPPort, gConfig.getStr("SIP.IP"), mMessengerIP, mFromTag.c_str(), mViaBranch.c_str(), mCallID.c_str(), mCSeq, - messageText); + messageText, content_type); // Send Invite to Asterisk. gSIPInterface.writeMessenger(message); diff --git a/public-trunk/SIP/SIPEngine.h b/public-trunk/SIP/SIPEngine.h index 0611c0b..d4a40e4 100644 --- a/public-trunk/SIP/SIPEngine.h +++ b/public-trunk/SIP/SIPEngine.h @@ -200,10 +200,12 @@ public: @param called_username SIP userid or E.164 address. @param called_domain SIP user's domain. @param message_text MESSAGE payload as a C string. + @param plainText True if message is text/plain, otherwise it's application/vnd.3gpp.sms @return New SIP call state. */ SIPState MOSMSSendMESSAGE(const char * called_username, - const char * called_domain, const char *message_text); + const char * called_domain, const char *message_text, + bool plainText); SIPState MOSMSWaitForSubmit(); diff --git a/public-trunk/SIP/SIPMessage.cpp b/public-trunk/SIP/SIPMessage.cpp index ee4f12c..50323f2 100644 --- a/public-trunk/SIP/SIPMessage.cpp +++ b/public-trunk/SIP/SIPMessage.cpp @@ -214,7 +214,7 @@ osip_message_t * SIP::sip_unregister( const char * sip_username, short wlocal_po } -osip_message_t * SIP::sip_message( const char * dialed_number, const char * sip_username, short wlocal_port, const char * local_ip, const char * proxy_ip, const char * from_tag, const char * via_branch, const char * call_id, int cseq, const char* message) { +osip_message_t * SIP::sip_message( const char * dialed_number, const char * sip_username, short wlocal_port, const char * local_ip, const char * proxy_ip, const char * from_tag, const char * via_branch, const char * call_id, int cseq, const char* message, const char* content_type) { char local_port[10]; sprintf(local_port, "%i", wlocal_port); @@ -276,7 +276,17 @@ osip_message_t * SIP::sip_message( const char * dialed_number, const char * sip_ sprintf(temp_buf,"%i",cseq); osip_cseq_set_number(request->cseq, strdup(temp_buf)); - osip_message_set_content_type(request, strdup("text/plain")); + // Content-Type + if (content_type) + { + // Explicit value provided + osip_message_set_content_type(request, strdup(content_type)); + } else { + // Default to text/plain + osip_message_set_content_type(request, strdup("text/plain")); + } + + // Content-Length sprintf(temp_buf,"%lu",strlen(message)); osip_message_set_content_length(request, strdup(temp_buf)); diff --git a/public-trunk/SIP/SIPMessage.h b/public-trunk/SIP/SIPMessage.h index c62a2cf..8851f5c 100644 --- a/public-trunk/SIP/SIPMessage.h +++ b/public-trunk/SIP/SIPMessage.h @@ -40,7 +40,7 @@ osip_message_t * sip_unregister( const char * sip_username, short local_port, co const char * proxy_ip, const char * from_tag, const char * via_branch, const char * call_id, int cseq); -osip_message_t * sip_message( const char * dialed_number, const char * sip_username, short local_port, const char * local_ip, const char * proxy_ip, const char * from_tag, const char * via_branch, const char * call_id, int cseq, const char* message); +osip_message_t * sip_message( const char * dialed_number, const char * sip_username, short local_port, const char * local_ip, const char * proxy_ip, const char * from_tag, const char * via_branch, const char * call_id, int cseq, const char* message, const char* content_type=NULL); osip_message_t * sip_invite( const char * dialed_number, short rtp_port,const char * sip_username, short local_port, const char * local_ip, const char * proxy_ip, const char * from_tag, const char * via_branch, const char * call_id, int cseq, unsigned codec); diff --git a/public-trunk/SMS/SMSMessages.cpp b/public-trunk/SMS/SMSMessages.cpp index 0de41f5..dceaa6b 100644 --- a/public-trunk/SMS/SMSMessages.cpp +++ b/public-trunk/SMS/SMSMessages.cpp @@ -76,6 +76,61 @@ CPMessage * SMS::parseSMS( const GSM::L3Frame& frame ) } +RPData *SMS::hex2rpdata(const char *hexstring) +{ + RPData *rp_data = NULL; + + BitVector RPDUbits(strlen(hexstring)*4); + if (!RPDUbits.unhex(hexstring)) { + return false; + } + LOG(DEBUG) << "SMS RPDU bits: " << RPDUbits; + + try { + RLFrame RPDU(RPDUbits); + LOG(DEBUG) << "SMS RPDU: " << RPDU; + + rp_data = new RPData(); + rp_data->parse(RPDU); + LOG(DEBUG) << "SMS RP-DATA " << *rp_data; + } + catch (SMSReadError) { + LOG(WARN) << "SMS parsing failed (above L3)"; + // TODO:: send error back to the phone + delete rp_data; + rp_data = NULL; + } + catch (L3ReadError) { + LOG(WARN) << "SMS parsing failed (in L3)"; + // TODO:: send error back to the phone + delete rp_data; + rp_data = NULL; + } + + return rp_data; +} + +TLMessage *SMS::parseTPDU(const TLFrame& TPDU) +{ + LOG(DEBUG) << "SMS: parseTPDU MTI=" << TPDU.MTI(); + // Handle just the uplink cases. + switch ((TLMessage::MessageType)TPDU.MTI()) { + case TLMessage::DELIVER_REPORT: + case TLMessage::STATUS_REPORT: + // FIXME -- Not implemented yet. + LOG(WARN) << "Unsupported TPDU type: " << (TLMessage::MessageType)TPDU.MTI(); + return NULL; + case TLMessage::SUBMIT: { + TLSubmit *submit = new TLSubmit; + submit->parse(TPDU); + LOG(INFO) << "SMS SMS-SUBMIT " << submit; + return submit; + } + default: + return NULL; + } +} + void CPMessage::text(ostream& os) const { os << (CPMessage::MessageType)MTI(); @@ -163,8 +218,13 @@ ostream& SMS::operator<<(ostream& os, const RPMessage& msg) void RPUserData::parseV(const L3Frame& src, size_t &rp, size_t expectedLength) { + LOG(DEBUG) << "src=" << src << " (length=" << src.length() << ") rp=" << rp << " expectedLength=" << expectedLength; unsigned numBits = expectedLength*8; + if (rp+numBits > src.size()) { + SMS_READ_ERROR; + } mTPDU.resize(numBits); + LOG(DEBUG) << "mTPDU length=" << mTPDU.length() << "data=" << mTPDU; src.segmentCopyTo(mTPDU,rp,numBits); rp += numBits; } @@ -410,7 +470,66 @@ void TLValidityPeriod::text(ostream& os) const os << "expiration=(" << str << ")"; } +void TLUserData::encode7bit(const char *text) +{ + size_t wp = 0; + + // 1. Prepare. + // Default alphabet (7-bit) + mDCS = 0; + // With 7-bit encoding TP-User-Data-Length count septets, i.e. just number + // of characters. + mLength = strlen(text); + int bytes = (mLength*7+7)/8; + int filler_bits = bytes*8-mLength*7; + mRawData.resize(bytes*8); + // 2. Write TP-UD + // This tail() works because UD is always the last field in the PDU. + BitVector chars = mRawData.tail(wp); + for (unsigned i=0; i (mRawData.size())) { + LOG(NOTICE) << "badly formatted TL-UD"; + SMS_READ_ERROR; + } + // Do decoding + text.resize(mLength); + size_t crp=0; + for (unsigned i=0; i #include #include +#include +using namespace GSM; namespace SMS { @@ -164,7 +166,9 @@ class TLUserData : public TLElement { unsigned mDCS; ///< data coding scheme bool mUDHI; ///< header indicator - char mData[161]; ///< actual data, as a C string + unsigned mLength; ///< TP-User-Data-Length, see GSM 03.40 Fig. 9.2.3.24(a), + ///< GSM 03.40 Fig. 9.2.3.24(b) and GSM 03.40 9.2.3.16. + BitVector mRawData; ///< raw packed data public: @@ -172,19 +176,49 @@ class TLUserData : public TLElement { TLUserData(unsigned wDCS=0x100, bool wUDHI=false) :TLElement(), mDCS(wDCS), - mUDHI(wUDHI) - { mData[0]='\0'; } + mUDHI(wUDHI), + mLength(0) + { + } - /** Initialze from a simple C string. */ - TLUserData(const char* text, bool wUDHI=false) + /** Initialize from a raw encoded data. */ + TLUserData(unsigned wDCS, const BitVector wRawData, unsigned wLength, + bool wUDHI=false) + :TLElement(), + mDCS(wDCS), + mUDHI(wUDHI), + mLength(wLength) + { + mRawData.clone(wRawData); + } + + /** Initialize from a simple C string. */ + TLUserData(const char* text, GSMAlphabet alphabet=ALPHABET_7BIT, bool wUDHI=false) :TLElement(), mDCS(0), - mUDHI(wUDHI) - { strncpy(mData,text,sizeof(mData)-1); mData[sizeof(mData)-1]='\0'; } + mUDHI(wUDHI), + mLength(0) + { + switch(alphabet) { + case ALPHABET_7BIT: + encode7bit(text); + break; + case ALPHABET_8BIT: + case ALPHABET_UCS2: + default: + LOG(WARN) << "Unsupported alphabet: " << alphabet; + break; + } + } void DCS(unsigned wDCS) { mDCS=wDCS; } + unsigned DCS() const { return mDCS; } void UDHI(unsigned wUDHI) { mUDHI=wUDHI; } - const char* data() const { return mData; } + unsigned UDHI() const { return mUDHI; } + /** Encode text into this element, using 7-bit alphabet */ + void encode7bit(const char *text); + /** Decode text from this element, using 7-bit alphabet */ + std::string decode() const; /** This length includes a byte for the length field. */ size_t length() const; @@ -236,12 +270,12 @@ class TLMessage { /** GSM 03.40 9.2.3.1 */ enum MessageType { - DELIVER = 0x0, - DELIVER_REPORT = 0x0, - STATUS_REPORT = 0x2, - COMMAND = 0x02, - SUBMIT = 0x1, - SUBMIT_REPORT = 0x1 + DELIVER = 0x0, // SC -> MS + DELIVER_REPORT = 0x0, // MS -> SC + STATUS_REPORT = 0x2, // SC -> MS + COMMAND = 0x02, // MS -> SC + SUBMIT = 0x1, // MS -> SC + SUBMIT_REPORT = 0x1 // SC -> MS }; TLMessage() @@ -352,7 +386,7 @@ class TLDeliver : public TLMessage { TLAddress mOA; ///< origination address, GSM 03.40 9.3.2.7 unsigned mPID; ///< TL-PID, GSM 03.40 9.2.3.9 - // Hardcode DCS. + // DCS is taken from mUD. TLTimestamp mSCTS; ///< service center timestamp, GSM 03.40 9.2.3.11 TLUserData mUD; ///< user data @@ -774,6 +808,21 @@ std::ostream& operator<<(std::ostream& os, CPMessage::MessageType MTI); */ CPMessage * parseSMS( const GSM::L3Frame& frame ); +/** + Parse msgtext from a hex string to RPData struct. + @param hexstring RPData encoded into hex-string. + @return Pointer to parsed RPData or NULL on error. +*/ +RPData *hex2rpdata(const char *hexstring); + +/** + Parse a TPDU. + Currently only SMS-SUBMIT is supported. + @param TPDU The TPDU. + @return Pointer to parsed TLMessage or NULL on error. +*/ +TLMessage *parseTPDU(const TLFrame& TPDU); + /** A factory method for SMS L3 (CM) messages. */ CPMessage * CPFactory( CPMessage::MessageType MTI ); diff --git a/public-trunk/apps/OpenBTS.config.example b/public-trunk/apps/OpenBTS.config.example index f13d271..fd40c77 100644 --- a/public-trunk/apps/OpenBTS.config.example +++ b/public-trunk/apps/OpenBTS.config.example @@ -163,18 +163,11 @@ SIP.Timer.A 2000 # # SMS parameters # -# ISDN address of source SMSC when we fake out a source SMSC. -SMS.FakeSrcSMSC 0000 # ISDN address of destination SMSC when a fake value is needed. SMS.DefaultDestSMSC 0000 - -# The SMS HTTP gateway. -# Comment out if you don't have one or if you want to use smqueue. -#SMS.HTTP.Gateway api.clickatell.com - -# IF SMS.HTTP.Gateway IS DEFINED, SMS.HTTP.AccessString MUST ALSO BE DEFINED. -#SMS.HTTP.AccessString sendmsg?user=xxxx&password=xxxx&api_id=xxxx +# SMSes will be sent to SIP URI sip:@ +SIP.SMSC smsc diff --git a/public-trunk/smqueue/Makefile.am b/public-trunk/smqueue/Makefile.am index a95c943..bf09162 100644 --- a/public-trunk/smqueue/Makefile.am +++ b/public-trunk/smqueue/Makefile.am @@ -20,15 +20,34 @@ include $(top_srcdir)/Makefile.common +AM_CPPFLAGS = $(STD_DEFINES_AND_INCLUDES) +AM_CXXFLAGS = -O3 -g -lpthread + EXTRA_DIST = \ - Makefile.standalone \ README.smqueue \ - poll.c \ - smcommands.cpp \ - smnet.cpp \ smqueue.config.example \ - smqueue.cpp \ + runloop.sh + +noinst_PROGRAMS = \ + smqueue + +noinst_HEADERS = \ poll.h \ smnet.h \ smqueue.h \ - runloop.sh + smsc.h + +smqueue_SOURCES = \ + poll.c \ + smcommands.cpp \ + smnet.cpp \ + smqueue.cpp \ + smsc.cpp +smqueue_LDADD = \ + $(GLOBALS_LA) \ + $(SMS_LA) \ + $(GSM_LA) \ + $(COMMON_LA) \ + $(HLR_LA) \ + $(SIP_LA) \ + $(OSIP_LIBS) diff --git a/public-trunk/smqueue/Makefile.standalone b/public-trunk/smqueue/Makefile.standalone deleted file mode 100644 index b6fbf3b..0000000 --- a/public-trunk/smqueue/Makefile.standalone +++ /dev/null @@ -1,14 +0,0 @@ -LOCALLIBS=../CommonLibs/Logger.cpp ../CommonLibs/Timeval.cpp ../CommonLibs/Threads.cpp ../CommonLibs/Sockets.cpp ../CommonLibs/Configuration.cpp -LIBS=$(LOCALLIBS) -losipparser2 -losip2 -lc -lpthread -INCLUDES=-I../CommonLibs -I../HLR -CPPFLAGS=-g -Wall -#CPPFLAGS=-Weffc++ -g -Wall - -smqueue: smqueue.cpp smqueue.h smnet.cpp smnet.h smcommands.cpp ../HLR/HLR.cpp ../HLR/HLR.h - g++ -o smqueue $(CPPFLAGS) $(INCLUDES) smqueue.cpp smnet.cpp smcommands.cpp ../HLR/HLR.cpp $(LIBS) - -smqueue.shar: - shar >smqueue.shar smqueue.cpp smqueue.h smnet.cpp smnet.h smcommands.cpp smqueue.config Makefile testmsg* - -clean: - rm smqueue diff --git a/public-trunk/smqueue/README.smqueue b/public-trunk/smqueue/README.smqueue index 8f9db84..3e696a5 100644 --- a/public-trunk/smqueue/README.smqueue +++ b/public-trunk/smqueue/README.smqueue @@ -1,7 +1 @@ The Smqueue RFC-3428 Store and Forward Server - -Smqueue has different build dependencies than OpenBTS and so it has -its own make file outside of the standard openbts autobuild system. -This allows smqueue to be built without installing GNU Radio. - -make -f Makefile.standalone diff --git a/public-trunk/smqueue/smcommands.cpp b/public-trunk/smqueue/smcommands.cpp index 3f475f3..319c2cd 100644 --- a/public-trunk/smqueue/smcommands.cpp +++ b/public-trunk/smqueue/smcommands.cpp @@ -23,6 +23,7 @@ #include "smqueue.h" #include "smnet.h" +#include "smsc.h" #include #include #include @@ -379,5 +380,8 @@ SMqueue::init_smcommands (short_code_map_t *scm) (*scm)[gConfig.getStr("SC.ZapQueued.Code")] = shortcode_zap_queued; if (gConfig.defines("SC.WhiplashQuit.Code")) (*scm)[gConfig.getStr("SC.WhiplashQuit.Code")] = whiplash_quit; + if (gConfig.defines("SC.SMSC.Code")) { + (*scm)[gConfig.getStr("SC.SMSC.Code")] = shortcode_smsc; + } // (*scm)["666"] = shortcode_text_access; } diff --git a/public-trunk/smqueue/smqueue.config.example b/public-trunk/smqueue/smqueue.config.example index ceb2958..618619f 100644 --- a/public-trunk/smqueue/smqueue.config.example +++ b/public-trunk/smqueue/smqueue.config.example @@ -37,6 +37,19 @@ $optional Debug.print_as_we_validate #SIP.global_relay 81.201.82.50:5060 SIP.global_relay +# +# SMS parameters +# +# ISDN address of source SMSC when we fake out a source SMSC. +SMS.FakeSrcSMSC 0000 + +# The SMS HTTP gateway. +# Comment out if you don't have one or if you want to use smqueue. +#SMS.HTTP.Gateway api.clickatell.com + +# IF SMS.HTTP.Gateway IS DEFINED, SMS.HTTP.AccessString MUST ALSO BE DEFINED. +#SMS.HTTP.AccessString sendmsg?user=xxxx&password=xxxx&api_id=xxxx + # return SMS messages BounceMessage.IMSILookupFailed Cannot determine return address; bouncing message. Text your phone number to 101 to register and try again. diff --git a/public-trunk/smqueue/smqueue.cpp b/public-trunk/smqueue/smqueue.cpp index 1a39bb9..7c2ab35 100644 --- a/public-trunk/smqueue/smqueue.cpp +++ b/public-trunk/smqueue/smqueue.cpp @@ -21,6 +21,7 @@ #include "smqueue.h" #include "smnet.h" +#include "smsc.h" #include #include /* from osipparser2 */ #include @@ -57,41 +58,44 @@ ConfigurationTable gConfig("smqueue.config"); transitions where we're starting over from scratch due to some error. */ /* Timeout when moving from this state to new state: - NS RF AF WD RD AD WS RS AS WM RM AM DM WR RH AR */ + NS IS RF AF WD RD AD WS RS AS WM RM AM DM WR RH AR */ int timeouts_NO_STATE[STATE_MAX_PLUS_ONE] = { - NT, 0, NT, NT, 0, NT, NT, 0, NT, NT, 0, NT, 0, NT, NT, NT,}; + NT, 0, 0, NT, NT, 0, NT, NT, 0, NT, NT, 0, NT, 0, NT, NT, NT,}; +int timeouts_INITIAL_STATE[STATE_MAX_PLUS_ONE] = { + 0, 0, 0, NT, NT, NT, NT, NT, NT, NT, NT, 0, NT, 0, NT, NT, NT,}; int timeouts_REQUEST_FROM_ADDRESS_LOOKUP[STATE_MAX_PLUS_ONE] = { - 0, 10, 10, NT, 0, NT, NT, NT, NT, NT, NT, NT, 0, 1, 0, NT,}; + 0, NT, 10, 10, NT, 0, NT, NT, NT, NT, NT, NT, NT, 0, 1, 0, NT,}; int timeouts_ASKED_FOR_FROM_ADDRESS_LOOKUP[STATE_MAX_PLUS_ONE] = { - 0, 60, NT, NT, NT, NT, NT, NT, NT, NT, NT, NT, 0, NT, NT, NT,}; + 0, NT, 60, NT, NT, NT, NT, NT, NT, NT, NT, NT, NT, 0, NT, NT, NT,}; int timeouts_AWAITING_TRY_DESTINATION_IMSI[STATE_MAX_PLUS_ONE] = { - 0, RT, NT, RT, NT, NT, NT, NT, NT, NT, NT, NT, 0, NT, NT, NT,}; + 0, NT, RT, NT, RT, NT, NT, NT, NT, NT, NT, NT, NT, 0, NT, NT, NT,}; int timeouts_REQUEST_DESTINATION_IMSI[STATE_MAX_PLUS_ONE] = { - 0, RT, NT, RT, NT, NT, NT, 0, NT, NT, NT, NT, 0, NT, NT, NT,}; + 0, NT, RT, NT, RT, NT, NT, NT, 0, NT, NT, NT, NT, 0, NT, NT, NT,}; int timeouts_ASKED_FOR_DESTINATION_IMSI[STATE_MAX_PLUS_ONE] = { - 0, RT, NT, RT, NT, NT, NT, NT, NT, NT, NT, NT, 0, NT, NT, NT,}; + 0, NT, RT, NT, RT, NT, NT, NT, NT, NT, NT, NT, NT, 0, NT, NT, NT,}; int timeouts_AWAITING_TRY_DESTINATION_SIPURL[STATE_MAX_PLUS_ONE] = { - 0, RT, NT, RT, NT, NT, NT, NT, NT, NT, NT, NT, 0, NT, NT, NT,}; + 0, NT, RT, NT, RT, NT, NT, NT, NT, NT, NT, NT, NT, 0, NT, NT, NT,}; int timeouts_REQUEST_DESTINATION_SIPURL[STATE_MAX_PLUS_ONE] = { - 0, RT, NT, RT, NT, NT, NT, NT, NT, NT, 0, NT, 0, NT, NT, NT,}; + 0, NT, RT, NT, RT, NT, NT, NT, NT, NT, NT, 0, NT, 0, NT, NT, NT,}; int timeouts_ASKED_FOR_DESTINATION_SIPURL[STATE_MAX_PLUS_ONE] = { - 0, RT, NT, RT, NT, NT, NT, NT, NT, NT, NT, NT, 0, NT, NT, NT,}; + 0, NT, RT, NT, RT, NT, NT, NT, NT, NT, NT, NT, NT, 0, NT, NT, NT,}; int timeouts_AWAITING_TRY_MSG_DELIVERY[STATE_MAX_PLUS_ONE] = { - 0, RT, NT, RT, NT, NT, NT, NT, NT, 75, 0, NT, 0, NT, NT, NT,}; + 0, NT, RT, NT, RT, NT, NT, NT, NT, NT, 75, 0, NT, 0, NT, NT, NT,}; int timeouts_REQUEST_MSG_DELIVERY[STATE_MAX_PLUS_ONE] = { - 0, RT, NT, RT, NT, NT, NT, 75, NT, 75, 75, 15, 0, NT, NT, NT,}; +// 0, NT, RT, NT, RT, NT, NT, NT, 75, NT, 75, 75, 15, 0, NT, NT, NT,}; + 0, NT, RT, NT, RT, NT, NT, NT, 5, NT, 75, 75, 15, 0, NT, NT, NT,}; int timeouts_ASKED_FOR_MSG_DELIVERY[STATE_MAX_PLUS_ONE] = { - 0, RT, NT, NT, NT, NT, NT, NT, NT, 60, 10, NT, 0, NT, NT, NT,}; + 0, NT, RT, NT, NT, NT, NT, NT, NT, NT, 60, 10, NT, 0, NT, NT, NT,}; int timeouts_DELETE_ME_STATE[STATE_MAX_PLUS_ONE] = { - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,}; + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,}; int timeouts_AWAITING_REGISTER_HANDSET[STATE_MAX_PLUS_ONE] = { - 0, 0, NT, RT, NT, NT, NT, NT, NT, NT, NT, NT, 0, 1, 0, NT,}; + 0, NT, 0, NT, RT, NT, NT, NT, NT, NT, NT, NT, NT, 0, 1, 0, NT,}; int timeouts_REGISTER_HANDSET[STATE_MAX_PLUS_ONE] = { - 0, 0, NT, RT, NT, NT, NT, NT, NT, NT, NT, NT, 0, 1, 1, 2,}; + 0, NT, 0, NT, RT, NT, NT, NT, NT, NT, NT, NT, NT, 0, 1, 1, 2,}; int timeouts_ASKED_TO_REGISTER_HANDSET[STATE_MAX_PLUS_ONE] = { - 0, 0, NT, RT, NT, NT, NT, NT, NT, NT, NT, NT, 0, 1, 1, 10,}; + 0, NT, 0, NT, RT, NT, NT, NT, NT, NT, NT, NT, NT, 0, 1, 1, 10,}; /* Timeout when moving from this state to new state: - NS RF AF WD RD AD WS RS AS WM RM AM DM WR RH AR */ + NS IS RF AF WD RD AD WS RS AS WM RM AM DM WR RH AR */ #undef NT /* No longer needed */ #undef RT @@ -99,6 +103,7 @@ int timeouts_ASKED_TO_REGISTER_HANDSET[STATE_MAX_PLUS_ONE] = { /* Index to all timeouts. Keep in order! */ int (*SMqueue::timeouts[STATE_MAX_PLUS_ONE])[STATE_MAX_PLUS_ONE] = { &timeouts_NO_STATE, + &timeouts_INITIAL_STATE, &timeouts_REQUEST_FROM_ADDRESS_LOOKUP, &timeouts_ASKED_FOR_FROM_ADDRESS_LOOKUP, @@ -123,6 +128,7 @@ int (*SMqueue::timeouts[STATE_MAX_PLUS_ONE])[STATE_MAX_PLUS_ONE] = { string SMqueue::sm_state_strings[STATE_MAX_PLUS_ONE] = { "No State", + "Initial State", "Request From-Address Lookup", "Asked for From-Address", "Awaiting Try Destination IMSI", @@ -180,7 +186,8 @@ osip_mem_release() */ static struct imsi_phone { char imsi[4+15+1]; char phone[1+15+1]; } imsi_phone[] = { - {"IMSI666410186585295", "+17074700741"}, /* Nokia 8890 */ +// {"IMSI310260550682564", "100"}, /* Siemens A52 */ + {"IMSI666410186585295", "+17074700741"}, /* Nokia 8890 */ {"IMSI777100223456161", "+17074700746"}, /* Palm Treo */ {{0}, {0}} }; @@ -244,6 +251,46 @@ SMq::process_timeout() return; /* Wait til later to do more */ switch (qmsg->state) { + case INITIAL_STATE: + // This is the initial state in which a message + // enters the system. Here, the message could be + // a SIP response as well as a SIP MESSAGE -- or + // something we can't process like a SIP REGISTER + // or garbage. Well, actually, garbage was rejected + // earlier before queue insertion. + + // From this point onward, we're going to assume + // that the message has valid, reasonable headers + // and contents for our purposes. Centralize all + // that checking in validate_short_msg(). + + if (MSG_IS_REQUEST(qmsg->parsed)) { + // It's a MESSAGE or invalid REGISTER. + // We support only MESSAGE here. + if (0 != strcmp(qmsg->parsed->sip_method, "MESSAGE")) { + LOG(WARN) << "Invalid incoming SIP message, method is " + << qmsg->parsed->sip_method; + newstate = NO_STATE; + } else { + // Check for short-code and handle it. + // If handle_short_code() returns true, it sets newstate + // on its own + if (!handle_short_code(short_code_map, qmsg, newstate)) { + // For non-special messages, look up who they're from. + newstate = REQUEST_FROM_ADDRESS_LOOKUP; + } + } + set_state(qmsg, newstate); + break; + + } else { // It's a RESPONSE. + handle_response(qmsg); + // The RESPONSE has been deleted in handle_response(). + // We go back to the top of the loop. + break; + } + break; + case NO_STATE: // Messages in NO_STATE have errors in them. // Dump it to the log, and delete it, so the queue @@ -268,36 +315,16 @@ SMq::process_timeout() break; default: - LOG(NOTICE) << "Message timed out with bad state " + LOG(ALARM) << "Message timed out with bad state " << qmsg->state << " and message: " << qmsg->text; - set_state(qmsg, REQUEST_FROM_ADDRESS_LOOKUP); + set_state(qmsg, INITIAL_STATE); + // WTF? Shouldn't we proceed to NO_STATE aka "error state"? /* NO BREAK */ case REQUEST_FROM_ADDRESS_LOOKUP: - // This is the initial state in which a message - // enters the system. Here, the message could be - // a SIP response as well as a SIP MESSAGE -- or - // something we can't process like a SIP REGISTER - // or garbage. Well, actually, garbage was rejected - // earlier before queue insertion. - - /* From this point onward, we're going to assume - that the message has valid, reasonable headers - and contents for our purposes. Centralize all - that checking in validate_short_msg(). */ - - if (MSG_IS_REQUEST(qmsg->parsed)) { - // It's a MESSAGE or invalid REGISTER - // Deal with it! - newstate = handle_sms_message(qmsg); - set_state(qmsg, newstate); - break; - } else { - // It's a RESPONSE. - handle_response(qmsg); - // Probably the RESPONSE has been deleted. - // We go back to top of loop. - break; - } + /* Ask to translate the IMSI in the From field + into the phone number. */ + newstate = lookup_from_address (&*qmsg); + set_state(qmsg, newstate); break; case REQUEST_DESTINATION_IMSI: @@ -322,6 +349,18 @@ SMq::process_timeout() case REQUEST_MSG_DELIVERY: /* We are trying to deliver to the handset now (or again after congestion). */ + + // Check for short-code and handle it. + // If handle_short_code() returns true, it sets newstate + // on its own + if (!pack_sms_for_delivery(qmsg)) + { + // Error... + set_state(qmsg, NO_STATE); + break; + } + + // debug_dump(); // FIXME, remove // Only print delivering msg if delivering to non- // localhost. @@ -476,7 +515,7 @@ SMq::handle_response(short_msg_p_list::iterator qmsgit) // Special code in registration processing // will notice it's a re-reg and just reply // with a welcome message. - oldsms->set_state(REQUEST_FROM_ADDRESS_LOOKUP); + oldsms->set_state(INITIAL_STATE); } else { // Orig SMS exists, but not in a normal state. // Assume that the original SMS is in a @@ -644,8 +683,12 @@ short_msg_pending::validate_short_msg() // FIXME, add support for more Content-Type's. if (!p->content_type || !p->content_type->type || !p->content_type->subtype - || 0 != strcmp("text", p->content_type->type) - || 0 != strcmp("plain", p->content_type->subtype) ) + || !( (0 == strcmp("text", p->content_type->type) + && 0 == strcmp("plain", p->content_type->subtype)) + ||(0 == strcmp("application", p->content_type->type) + && 0 == strcmp("vnd.3gpp.sms", p->content_type->subtype)) + ) + ) return 415; if (p->bodies.nb_elt != 1 || !p->bodies.node @@ -1052,6 +1095,9 @@ SMq::originate_sm(const char *from, const char *to, const char *msgtext, smpl = originate_half_sm("MESSAGE"); response = &*smpl->begin(); // Here's our short_msg_pending! + // Plain text SIP MESSAGE should be repacked before delivery + response->need_repack = true; + // For the tag, we cheat and reuse the cseq number. // I don't see any reason not to...why do we have three different // tag fields scattered around? @@ -1070,6 +1116,7 @@ SMq::originate_sm(const char *from, const char *to, const char *msgtext, osip_uri_parse(response->parsed->req_uri, uriline.str().c_str()); osip_message_set_content_type(response->parsed, "text/plain"); + response->content_type = short_msg::TEXT_PLAIN; size_t len = strlen(msgtext); if (len > SMS_MESSAGE_MAX_LENGTH) len = SMS_MESSAGE_MAX_LENGTH; @@ -1189,6 +1236,9 @@ SMq::register_handset (short_msg_p_list::iterator qmsg) smpl = originate_half_sm("REGISTER"); response = &*smpl->begin(); // Here's our short_msg_pending! + // SIP REGISTER should not be repacked before delivery + response->need_repack = false; + imsi = qmsg->parsed->from->url->username; // The To: line is the long-term name being registered. @@ -1246,109 +1296,111 @@ SMq::register_handset (short_msg_p_list::iterator qmsg) return ASKED_TO_REGISTER_HANDSET; } - -/* - * Initial handling of SMS messages found in the queue - */ -enum sm_state -SMq::handle_sms_message (short_msg_p_list::iterator qmsg) +bool SMq::handle_short_code(const short_code_map_t &short_code_map, + short_msg_p_list::iterator qmsg, + sm_state &next_state) { osip_body_t *bod1; - char *bods; + std::string bods; short_func_t shortfn; - short_code_map_t::iterator shortit; + short_code_map_t::const_iterator shortit; enum short_code_action sca; short_code_params params; int status; + string short_code; - if (0 != strcmp(qmsg->parsed->sip_method, "MESSAGE")) { - LOG(WARN) << "Invalid incoming SIP message, method is " - << qmsg->parsed->sip_method; - return NO_STATE; + short_code = qmsg->parsed->req_uri->username; + shortit = short_code_map.find (short_code); + + if (shortit == short_code_map.end()) { + return false; } - - shortit = short_code_map.find (string(qmsg->parsed->req_uri->username)); - if (shortit != short_code_map.end()) { - /* Messages to certain addresses are special commands */ - shortfn = shortit->second; - bods = (char *)""; - if (qmsg->parsed->bodies.nb_elt == 1) { - bod1 = (osip_body_t *)qmsg->parsed->bodies.node->element; - bods = bod1->body; + /* Messages to certain addresses are special commands */ + shortfn = shortit->second; + bods = qmsg->get_text(); + + // Set up arguments and access pointers, then call + // the short-code function to process it. + params.scp_retries = qmsg->retries; + params.scp_smq = this; + params.scp_qmsg_it = qmsg; + + LOG(INFO) << "Short-code SMS " + << qmsg->parsed->req_uri->username + << " with text \"" << bods << "\""; + + sca = (*shortfn) (qmsg->parsed->from->url->username, // imsi + bods.data(), // msg text + ¶ms); + + // The short-code function asks us to do something when + // it's done. Do it. + switch (sca) { + case SCA_REPLY: + LOG(INFO) << "Short-code replies: " + << params.scp_reply; + status = originate_sm( + qmsg->parsed->req_uri->username, // from shortcode + qmsg->parsed->from->url->username,// to his IMSI + params.scp_reply, REQUEST_DESTINATION_SIPURL); + if (!status) { + next_state = DELETE_ME_STATE; // Done! + return true; } - - // Set up arguments and access pointers, then call - // the short-code function to process it. - params.scp_retries = qmsg->retries; - params.scp_smq = this; - params.scp_qmsg_it = qmsg; - - LOG(INFO) << "Short-code SMS " + LOG(NOTICE) << "Reply failed " << status << "!"; + // NO BREAK + default: + case SCA_INTERNAL_ERROR: + LOG(ERROR) << "Error in short-code function " << qmsg->parsed->req_uri->username - << "(" << bods << ")."; - - sca = (*shortfn) (qmsg->parsed->from->url->username, // imsi - bods, // msg text - ¶ms); + << "(" << bods << "): " << params.scp_reply; + next_state = NO_STATE; + return true; - // The short-code function asks us to do something when - // it's done. Do it. - switch (sca) { - case SCA_REPLY: - LOG(INFO) << "Short-code replies: " - << params.scp_reply; - status = originate_sm( - qmsg->parsed->req_uri->username, // from shortcode - qmsg->parsed->from->url->username,// to his IMSI - params.scp_reply, REQUEST_DESTINATION_SIPURL); - if (!status) { - return DELETE_ME_STATE; // Done! - } - LOG(NOTICE) << "Reply failed " << status << "!"; - // NO BREAK - default: - case SCA_INTERNAL_ERROR: - LOG(ERROR) << "Error in short-code function " - << qmsg->parsed->req_uri->username - << "(" << bods << "): " << params.scp_reply; - return NO_STATE; - - case SCA_EXEC_SMQUEUE: - reexec_smqueue = true; - stop_main_loop = true; - return DELETE_ME_STATE; + case SCA_EXEC_SMQUEUE: + reexec_smqueue = true; + stop_main_loop = true; + next_state = DELETE_ME_STATE; + return true; - case SCA_QUIT_SMQUEUE: - stop_main_loop = true; - return DELETE_ME_STATE; + case SCA_QUIT_SMQUEUE: + stop_main_loop = true; + next_state = DELETE_ME_STATE; + return true; - case SCA_DONE: - return DELETE_ME_STATE; + case SCA_DONE: + next_state = DELETE_ME_STATE; + return true; - case SCA_RETRY_AFTER_DELAY: - // FIXME, timeout is implicit in set_state table, - // rather than taken from params.scp_delay. - qmsg->retries++; - return REQUEST_FROM_ADDRESS_LOOKUP; + case SCA_RETRY_AFTER_DELAY: + // FIXME, timeout is implicit in set_state table, + // rather than taken from params.scp_delay. + qmsg->retries++; + next_state = REQUEST_FROM_ADDRESS_LOOKUP; + return true; - case SCA_AWAIT_REGISTER: - // We just linked the phone# to the IMSI, but we - // have to wait til the HLR updates, before - // we can link the IMSI to the originating IP address - // and port number of its cell. - return AWAITING_REGISTER_HANDSET; + case SCA_AWAIT_REGISTER: + // We just linked the phone# to the IMSI, but we + // have to wait til the HLR updates, before + // we can link the IMSI to the originating IP address + // and port number of its cell. + next_state = AWAITING_REGISTER_HANDSET; + return true; - case SCA_REGISTER: - return register_handset(qmsg); + case SCA_REGISTER: + next_state = register_handset(qmsg); + return true; - case SCA_TREAT_AS_ORDINARY: - break; // fall through into non-special case. - } + case SCA_TREAT_AS_ORDINARY: + break; // fall through into non-special case. + + case SCA_RESTART_PROCESSING: + next_state = INITIAL_STATE; + return true; } - // For non-special messages, look up who they're from. - return lookup_from_address (&*qmsg); + return false; } @@ -1381,7 +1433,7 @@ SMq::lookup_from_address (short_msg_pending *qmsg) char *bods; bods = (char *)""; - if (qmsg->parsed->bodies.nb_elt == 1) { + if (qmsg->parsed->bodies.nb_elt == 1) { bod1 = (osip_body_t *)qmsg->parsed->bodies.node->element; bods = bod1->body; } else { @@ -1782,7 +1834,7 @@ SMq::respond_sip_ack(int errcode, short_msg_pending *smp, break; case 413: phrase="Message Body Size Error"; break; case 415: phrase="Unsupported Content Type"; - osip_message_set_accept(response.parsed, "text/plain"); + osip_message_set_accept(response.parsed, "text/plain, application/vnd.3gpp.sms"); break; case 416: phrase="Unsupported URI scheme (not SIP)"; break; case 480: phrase="Recipient Temporarily Unavailable"; break; @@ -1897,6 +1949,7 @@ SMq::main_loop() smpl = new short_msg_p_list (1); smp = &*smpl->begin(); // Here's our short_msg_pending! smp->initialize (len, buffer, false); + smp->ms_to_sc = true; if (my_network.recvaddrlen <= sizeof (smp->srcaddr)) { smp->srcaddrlen = my_network.recvaddrlen; @@ -2017,7 +2070,9 @@ SMq::save_queue_to_file(std::string qfile) ofile << "=== " << (int) x->state << " " << x->next_action_time << " " << my_network.string_addr((struct sockaddr *)x->srcaddr, x->srcaddrlen, true) << " " - << strlen(x->text) << endl << x->text << endl << endl; + << strlen(x->text) + << x->ms_to_sc << x->need_repack << endl + << x->text << endl << endl; howmany++; } @@ -2040,6 +2095,8 @@ SMq::read_queue_from_file(std::string qfile) ifstream ifile; std::string equals; unsigned astate, atime, alength; + unsigned ms_to_sc, need_repack; + std::string short_code; std::string netaddrstr; sm_state mystate; time_t mytime; @@ -2062,6 +2119,8 @@ SMq::read_queue_from_file(std::string qfile) } ifile >> netaddrstr; ifile >> alength; + ifile >> ms_to_sc; + ifile >> need_repack; while (ifile.peek() == '\n') ignoreme = ifile.get(); msgtext = new char[alength+2]; @@ -2079,6 +2138,10 @@ SMq::read_queue_from_file(std::string qfile) // We use the just-allocated msgtext; it gets freed after // delivery of message. + // Restore saved state + smp->ms_to_sc = ms_to_sc; + smp->need_repack = need_repack; + smp->srcaddrlen = 0; if (!my_network.parse_addr(netaddrstr.c_str(), smp->srcaddr, sizeof(smp->srcaddr), &smp->srcaddrlen)) abfuckingort(); @@ -2091,7 +2154,8 @@ SMq::read_queue_from_file(std::string qfile) << smp->parsed->from->url->username << " for " << smp->parsed->req_uri->username - << "."; + << " direction=" << (smp->ms_to_sc?"MS->SC":"SC->MS") + << " need_repack=" << (smp->need_repack?"true":"false"); } else { LOG(WARN) << "Read bad SMS " << smp->parsed->status_code @@ -2113,7 +2177,7 @@ SMq::read_queue_from_file(std::string qfile) return true; } - +#if 0 /* * Read in a message from a file. Return malloc'd char block of the whole * thing. @@ -2169,7 +2233,7 @@ read_short_msg_pending_from_file(char *fname) smp = new short_msg_pending (length, sip_text, true); return smp; } - +#endif /* Really simple first try */ @@ -2183,7 +2247,8 @@ main(int argc, char **argv) // short_msg_pending *smp; std::string savefile; - init_smcommands(&short_code_map); // Set up short-code commands users can type + // Set up short-code commands users can type + init_smcommands(&short_code_map); // This scope lets us delete the smq (and the network sockets) // before we re-exec ourself. diff --git a/public-trunk/smqueue/smqueue.h b/public-trunk/smqueue/smqueue.h index d66ed04..5d3166b 100644 --- a/public-trunk/smqueue/smqueue.h +++ b/public-trunk/smqueue/smqueue.h @@ -23,7 +23,7 @@ #define SM_QUEUE_H #include -#include /* from osipparser2 */ +//#include /* from osipparser2 */ #include /* for osipparser2 */ #include /* for osip_init */ #include /* for osip_init */ @@ -35,6 +35,14 @@ #include "smnet.h" // My network support #include "HLR.h" // My home location register +// That's awful OSIP has a CR define. +// It clashes with our innocent L2Address::CR(). +// Don't create 2-letter #defines, ever! +#undef CR +#include "SMSMessages.h" +using namespace SMS; + + namespace SMqueue { /* Maximum text size of an SMS message. */ @@ -60,33 +68,33 @@ char *new_strdup(const char *orig); */ enum sm_state { // timeout, next-state-if-timeout - NO_STATE = 0, - REQUEST_FROM_ADDRESS_LOOKUP = 1, - ASKED_FOR_FROM_ADDRESS_LOOKUP = 2, + NO_STATE, + INITIAL_STATE, + REQUEST_FROM_ADDRESS_LOOKUP, + ASKED_FOR_FROM_ADDRESS_LOOKUP, - AWAITING_TRY_DESTINATION_IMSI = 3, - REQUEST_DESTINATION_IMSI = 4, - ASKED_FOR_DESTINATION_IMSI = 5, + AWAITING_TRY_DESTINATION_IMSI, + REQUEST_DESTINATION_IMSI, + ASKED_FOR_DESTINATION_IMSI, - AWAITING_TRY_DESTINATION_SIPURL = 6, - REQUEST_DESTINATION_SIPURL = 7, - ASKED_FOR_DESTINATION_SIPURL = 8, + AWAITING_TRY_DESTINATION_SIPURL, + REQUEST_DESTINATION_SIPURL, + ASKED_FOR_DESTINATION_SIPURL, - AWAITING_TRY_MSG_DELIVERY = 9, - REQUEST_MSG_DELIVERY = 10, - ASKED_FOR_MSG_DELIVERY = 11, + AWAITING_TRY_MSG_DELIVERY, + REQUEST_MSG_DELIVERY, + ASKED_FOR_MSG_DELIVERY, - DELETE_ME_STATE = 12, + DELETE_ME_STATE, + + AWAITING_REGISTER_HANDSET, + REGISTER_HANDSET, + ASKED_TO_REGISTER_HANDSET, - AWAITING_REGISTER_HANDSET = 13, - REGISTER_HANDSET = 14, - ASKED_TO_REGISTER_HANDSET = 15, - STATE_MAX_PLUS_ONE, /* Keep this one last! */ }; #define STATE_MAX (STATE_MAX_PLUS_ONE - 1) -#define INITIAL_STATE REQUEST_FROM_ADDRESS_LOOKUP // How to print a state extern std::string sm_state_strings[STATE_MAX_PLUS_ONE]; @@ -102,6 +110,13 @@ extern struct osip *osipptr; term storage in the queue. */ class short_msg { public: + + enum ContentType { + UNSUPPORTED_CONTENT, + TEXT_PLAIN, + VND_3GPP_SMS + }; + /* First just the text string. A SIP message including body. */ unsigned short text_length; char *text /* [text_length] */; // C++ doesn't make it simple @@ -116,13 +131,32 @@ class short_msg { // to; // time_t date; // expiration; + ContentType content_type; // Content-Type of the message + + RPData *rp_data; // Parsed RP-DATA of an SMS. It's read from MESSAGE body if + // it has application/vnd.3gpp.sms MIME-type. Note, that + // it's parsed on request and may be NULL at any point. + TLMessage *tl_message; // Parsed RPDU of an SMS. It's read from rp_data. Note, + // that it's parsed on request and may be NULL at + // any point. + bool ms_to_sc; // Direction of the message. True is this is MS->SC SMS, false + // otherwise. + bool need_repack; // Message should be packed into TPDU for delivery. E.g. + // SIP MESSAGE sent to MS should be packed, while SIP + // REGISTER should not. short_msg () : text_length (0), text (NULL), parsed_is_valid (false), parsed_is_better (false), - parsed (NULL) { + parsed (NULL), + content_type(UNSUPPORTED_CONTENT), + rp_data(NULL), + tl_message(NULL), + ms_to_sc(false), + need_repack(true) + { } // Make a short message, perhaps taking responsibility for deleting // the "new"-allocated memory passed in. @@ -131,7 +165,12 @@ class short_msg { text (cstr), parsed_is_valid (false), parsed_is_better (false), - parsed (NULL) + parsed (NULL), + content_type(UNSUPPORTED_CONTENT), + rp_data(NULL), + tl_message(NULL), + ms_to_sc(false), + need_repack(true) { if (!use_my_memory) { text = new char [text_length+1]; @@ -151,7 +190,12 @@ class short_msg { text (0), parsed_is_valid (false), parsed_is_better (false), - parsed (NULL) + parsed (NULL), + content_type(UNSUPPORTED_CONTENT), + rp_data(NULL), + tl_message(NULL), + ms_to_sc(false), + need_repack(true) { if (text_length) { text = new char [text_length+1]; @@ -159,14 +203,19 @@ class short_msg { text[text_length] = '\0'; } }; - public: + #if 0 short_msg (std::string str) : text_length (str.length()), text (0), parsed_is_valid (false), parsed_is_better (false), - parsed (NULL) + parsed (NULL), + content_type(UNSUPPORTED_CONTENT), + rp_data(NULL), + tl_message(NULL), + ms_to_sc(false), + need_repack(false) { text = new char [text_length+1]; strncpy(text, str.data(), text_length); @@ -174,35 +223,26 @@ class short_msg { }; #endif - /* Override operator= to avoid pointer-sharing problems */ + /* Disable operator= to avoid pointer-sharing problems */ private: short_msg & operator= (const short_msg &rvalue); public: -/* - { - this->text_length = rvalue.text_length; - this->text = new char [rvalue.text_length+1]; - strncpy(this->text, rvalue.text, rvalue.text_length); - this->text[rvalue.text_length] = '\0'; - this->parsed_is_valid = rvalue.parsed_is_valid; - this->parsed_is_better = rvalue.parsed_is_better; - osip_message_clone (rvalue.parsed, &this->parsed); - return *this; - } */ /* Destructor */ - virtual ~short_msg () { unparse(); delete [] text; }; + virtual ~short_msg () + { + if (parsed) + osip_message_free(parsed); + delete [] text; + delete rp_data; + delete tl_message; + }; // Pseudo-constructor due to inability to run constructors on // members of lists. // Initialize a newly-default-constructed short message, // perhaps taking responsibility for deleting // the "new"-allocated memory passed in. - void - initialize () - { // Nothing to do: default constructor + default initializer - } - void initialize (int len, char * const cstr, bool use_my_memory) { @@ -233,6 +273,7 @@ class short_msg { unparse(); // Free any previous one. + // Parse SIP message i = osip_message_init(&sip); if (i != 0) abfuckingort(); /* throw out-of-memory */ i = osip_message_parse(sip, text, text_length); @@ -240,11 +281,45 @@ class short_msg { parsed = sip; parsed_is_valid = true; parsed_is_better = false; + + // Now parse SMS if needed + if (parsed->content_type == NULL) + { + // Most likely this is SIP response. + content_type = UNSUPPORTED_CONTENT; + } else if ( strcmp(parsed->content_type->type, "text") == 0 + && strcmp(parsed->content_type->subtype, "plain") == 0) + { + // If Content-Type is text/plain, then no decoding is needed. + content_type = TEXT_PLAIN; + } else if ( strcmp(parsed->content_type->type, "application") == 0 + && strcmp(parsed->content_type->subtype, "vnd.3gpp.sms") == 0) + { + // This is an encoded SMS' TPDU. + content_type = VND_3GPP_SMS; + + // Decode it RP-DATA + osip_body_t *bod1 = (osip_body_t *)parsed->bodies.node->element; + const char *bods = bod1->body; + rp_data = hex2rpdata(bods); + if (rp_data == NULL) { + LOG(INFO) << "RP-DATA unpacking failed"; + return false; + } + + // Decode RPDU + tl_message = parseTPDU(rp_data->TPDU()); + if (rp_data == NULL) { + LOG(INFO) << "RPDU parsing failed"; + return false; + } + } + return true; } /* Anytime a caller CHANGES the values in the parsed tree of the - message, they MUST call this, to let the cacheing system + message, they MUST call this, to let the caching system know that the cached copy of the text-string message is no longer valid. Actually, we have TWO such cached copies! FIXME so we have to invalidate both of them. */ @@ -256,7 +331,7 @@ class short_msg { /* Make the text string valid, if the parsed copy is better. (It gets "better" by being modified, and parsed_was_changed() - got called, but we deferred fixing up the text string til now.) */ + got called, but we deferred fixing up the text string till now.) */ void make_text_valid() { if (parsed_is_better) { @@ -288,6 +363,15 @@ class short_msg { /* Free up all memory used by parsed version of message. */ void unparse() { + // Free parsed TDPU. + // FIXME -- We should check if it has been changed and update MESSAGE body. + delete rp_data; + rp_data = NULL; + delete tl_message; + tl_message = NULL; + content_type = UNSUPPORTED_CONTENT; + + // Now unparse SIP if (parsed_is_better) make_text_valid(); if (parsed) @@ -297,6 +381,39 @@ class short_msg { parsed_is_better = false; } + std::string get_text() const + { + switch (content_type) { + case TEXT_PLAIN: { + osip_body_t *bod1 = (osip_body_t *)parsed->bodies.node->element; + return bod1->body; + } + break; + + case VND_3GPP_SMS: { + const TLSubmit *submit = (TLSubmit*)tl_message; + if (submit == NULL) { + return ""; + } + + try { + return submit->UD().decode(); + } + catch (SMSReadError) { + LOG(WARN) << "SMS parsing failed (above L3)"; + // TODO:: Should we send error back to the phone? + return ""; + } + + } + break; + + case UNSUPPORTED_CONTENT: + default: + return ""; + } + } + }; // I couldn't figure out how to make these static members of the class... @@ -372,19 +489,6 @@ class short_msg_pending: public short_msg { linktag (NULL) { } - - short_msg_pending (const short_msg &sm) : - short_msg (sm), - state (NO_STATE), - next_action_time (0), - retries (0), - // srcaddr({0}), // can't seem to initialize an array? - srcaddrlen(0), - qtag (NULL), - qtaghash (0), - linktag (NULL) - { - } #endif // @@ -425,37 +529,10 @@ class short_msg_pending: public short_msg { this->linktag[len] = '\0'; } } - public: /* Override operator= to avoid pointer-sharing problems */ private: short_msg_pending & operator= (const short_msg_pending &rvalue); -/* FIXME, remove if not needed? - { - this->state = rvalue.state; - this->next_action_time = rvalue.next_action_time; - this->retries = rvalue.retries; - this->qtag = rvalue.qtag; - if (rvalue.srcaddrlen) { - if (rvalue.srcaddrlen > sizeof (srcaddr)) - abfuckingort(); - memcpy(this->srcaddr, rvalue.srcaddr, rvalue.srcaddrlen); - } - - if (rvalue.qtag) { - int len = strlen(rvalue.qtag); - this->qtag = new char[len+1]; - strncpy(this->qtag, rvalue.qtag, len); - this->qtag[len] = '\0'; - } - this->qtaghash = rvalue.qtaghash; - if (rvalue.linktag) { - int len = strlen(rvalue.linktag); - this->linktag = new char[len+1]; - strncpy(this->linktag, rvalue.linktag, len); - this->linktag[len] = '\0'; - } - } */ public: /* Destructor */ @@ -477,28 +554,7 @@ class short_msg_pending: public short_msg { * This 'constructs' the new short_msg_pending in a temporary list, * and we can then trivially move it into the real message queue, * removing it from the temporary list in the process. - * - * The wriggling guts under C++ are a little too visible for my taste. */ - void - initguts () - { - state = NO_STATE; - next_action_time = 0; - retries = 0; - memset (srcaddr, 0, sizeof(srcaddr)); - srcaddrlen = 0; - qtag = NULL; - qtaghash = 0; - linktag = NULL; - } - void - initialize () - { - short_msg::initialize (); - // initguts(); - } - // Make a pending short message, perhaps taking responsibility for // deleting the "new"-allocated memory passed in. void @@ -521,19 +577,6 @@ class short_msg_pending: public short_msg { linktag (NULL) { } - - short_msg_pending (const short_msg &sm) : - short_msg (sm), - state (NO_STATE), - next_action_time (0), - retries (0), - // srcaddr({0}), // can't seem to initialize an array? - srcaddrlen(0), - qtag (NULL), - qtaghash (0), - linktag (NULL) - { - } #endif /* Optimize this later so we don't make so many kernel calls. */ @@ -577,6 +620,7 @@ class short_msg_pending: public short_msg { /* Check host and port for validity. */ bool check_host_port(char *host, char *port); + }; typedef std::list short_msg_p_list; @@ -589,16 +633,20 @@ typedef std::list short_msg_p_list; class SMq; enum short_code_action { - SCA_DONE = 0, - SCA_INTERNAL_ERROR = 1, - SCA_REPLY = 2, - SCA_RETRY_AFTER_DELAY = 3, - SCA_REPLY_AND_RETRY = 4, - SCA_QUIT_SMQUEUE = 5, - SCA_AWAIT_REGISTER = 6, - SCA_REGISTER = 7, - SCA_TREAT_AS_ORDINARY = 8, - SCA_EXEC_SMQUEUE = 9 + SCA_DONE = 0, ///< No further processing is needed. Free message. + SCA_INTERNAL_ERROR = 1, //< Just report error and bail out. + SCA_REPLY = 2, ///< Free this message and send replay back to the msg sender + ///< with a text from params.scp_reply + SCA_RETRY_AFTER_DELAY = 3, ///< HLR is busy. Retry query later. + SCA_REPLY_AND_RETRY = 4, ///< UNUSED. + SCA_QUIT_SMQUEUE = 5, ///< Self-explanatory. Exit smqueue. + SCA_AWAIT_REGISTER = 6, ///< HLR response is delayed. Wait. + SCA_REGISTER = 7, ///< HLR record for this phone has been retrieved. + ///< Proceed to registration with Asterisk. + SCA_TREAT_AS_ORDINARY = 8, ///< Continue msg processing as if it were non-shortcode msg. + SCA_EXEC_SMQUEUE = 9, ///< Fork new smqueue instance and exit this one. + SCA_RESTART_PROCESSING = 10 ///< Return from this short code processing + ///< and run another short code. }; class short_code_params { @@ -812,9 +860,13 @@ class SMq { enum sm_state register_handset (short_msg_p_list::iterator qmsg); - /* Initial handling of an incoming SMS message in the queue */ - enum sm_state - handle_sms_message(short_msg_p_list::iterator qmsg); + /* Check if this is a short-code message and handle it. + * Return true if message has been handled, false if you should continue + * message handling as usual. In latter case \p next_state is untouched. + */ + bool + handle_short_code(const short_code_map_t &short_code_map, + short_msg_p_list::iterator qmsg, enum sm_state &next_state); /* When a SIP response arrives, search the queue for its matching MESSAGE and handle both. */ From 49b8ffd2d8b235793f32f0c65f72d4bfdbd664ad Mon Sep 17 00:00:00 2001 From: Alexander Chemeris Date: Fri, 5 Nov 2010 15:11:16 +0300 Subject: [PATCH 05/16] Make "from" address for bounce messages configurable. --- public-trunk/smqueue/smqueue.config.example | 7 +++++-- public-trunk/smqueue/smqueue.cpp | 20 ++++++++++---------- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/public-trunk/smqueue/smqueue.config.example b/public-trunk/smqueue/smqueue.config.example index 618619f..44f0bb3 100644 --- a/public-trunk/smqueue/smqueue.config.example +++ b/public-trunk/smqueue/smqueue.config.example @@ -52,8 +52,11 @@ SMS.FakeSrcSMSC 0000 # return SMS messages -BounceMessage.IMSILookupFailed Cannot determine return address; bouncing message. Text your phone number to 101 to register and try again. -BounceMessage.NotRegistered Phone not registered here. +Bounce.Message.IMSILookupFailed Cannot determine return address; bouncing message. Text your phone number to 101 to register and try again. +Bounce.Message.NotRegistered Phone not registered here. + +# Bounce from address +Bounce.Code 411 diff --git a/public-trunk/smqueue/smqueue.cpp b/public-trunk/smqueue/smqueue.cpp index 7c2ab35..82f0c7f 100644 --- a/public-trunk/smqueue/smqueue.cpp +++ b/public-trunk/smqueue/smqueue.cpp @@ -1173,19 +1173,19 @@ SMq::bounce_message(short_msg_pending *sent_msg, const char *errstr) else errmsg << "can't send: " << thetext; - // Don't bounce a message from 411, makes endless loops. + // Don't bounce a message from us - it makes endless loops. status = 1; - if (0 != strcmp("411", sent_msg->parsed->from->url->username)) { + if (0 != strcmp(gConfig.getStr("Bounce.Code"), sent_msg->parsed->from->url->username)) + { // But do bounce anything else. - char *bounceto = sent_msg->parsed->from->url->username; + char *bounceto = sent_msg->parsed->from->url->username; bool bounce_to_imsi = 0 == strncmp("IMSI", bounceto, 4) - || 0 == strncmp("imsi", bounceto, 4); - status = originate_sm("411", // from "411"? FIXME? + || 0 == strncmp("imsi", bounceto, 4); + status = originate_sm(gConfig.getStr("Bounce.Code"), // Read from a config bounceto, // to his phonenum or IMSI errmsg.str().c_str(), // error msg - bounce_to_imsi? - REQUEST_DESTINATION_SIPURL: // dest is IMSI - REQUEST_DESTINATION_IMSI); // dest is phonenum + bounce_to_imsi? REQUEST_DESTINATION_SIPURL: // dest is IMSI + REQUEST_DESTINATION_IMSI); // dest is phonenum } if (status == 0) { return DELETE_ME_STATE; @@ -1513,7 +1513,7 @@ SMq::lookup_from_address (short_msg_pending *qmsg) << "> to phonenum failed."; LOG(DEBUG) << qmsg->text; return bounce_message (qmsg, - gConfig.getStr("BounceMessage.IMSILookupFailed") + gConfig.getStr("Bounce.Message.IMSILookupFailed") ); // return NO_STATE; // Put it into limbo for debug. } @@ -1592,7 +1592,7 @@ SMq::lookup_uri_imsi (short_msg_pending *qmsg) || !my_hlr.useGateway(username)) { // There's no global relay -- or the HLR says not to // use the global relay for it -- so send a bounce. - return bounce_message (qmsg, gConfig.getStr("BounceMessage.NotRegistered")); + return bounce_message (qmsg, gConfig.getStr("Bounce.Message.NotRegistered")); } else { // Send the message to our global relay. // We leave the username as a phone number, and From d498358b6e3a6280c08c35d98c67ee7e6dba85ce Mon Sep 17 00:00:00 2001 From: Alexander Chemeris Date: Fri, 5 Nov 2010 15:22:57 +0300 Subject: [PATCH 06/16] Set Log.Alarms.* values in smqueue config. Logging system expect those valus to exist. --- public-trunk/smqueue/smqueue.config.example | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/public-trunk/smqueue/smqueue.config.example b/public-trunk/smqueue/smqueue.config.example index 44f0bb3..b058d88 100644 --- a/public-trunk/smqueue/smqueue.config.example +++ b/public-trunk/smqueue/smqueue.config.example @@ -15,6 +15,13 @@ Log.Level.smcommands.cpp DEBUG # Logging file. Logs to stdout if this is not defined. #Log.FileName smqueue.log +# LOG(ALARM) is printed and also sent as udp to this address. +Log.Alarms.TargetIP 192.168.10.200 +$optional Log.Alarms.TargetIP +Log.Alarms.TargetPort 10101 +# Number of alarms saved in internal queue +Log.Alarms.Max 10 + savefile savedqueue.txt From e61168a39c6caa8e5d9ee4e29e9503937ed52899 Mon Sep 17 00:00:00 2001 From: Alexander Chemeris Date: Fri, 5 Nov 2010 15:38:55 +0300 Subject: [PATCH 07/16] Avoid duplication of "IMSI" in SC.Register.Msg. "IMSI" is also a prefix to phone's imsi-number, so when you output this error message, you get something like IMSIIMSI310260550682564. --- public-trunk/smqueue/smqueue.config.example | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public-trunk/smqueue/smqueue.config.example b/public-trunk/smqueue/smqueue.config.example index b058d88..1ea5ef3 100644 --- a/public-trunk/smqueue/smqueue.config.example +++ b/public-trunk/smqueue/smqueue.config.example @@ -85,7 +85,7 @@ SC.Register.Msg.TakenA The phone number SC.Register.Msg.TakenB is already in use. Try another, then call that one to talk to whoever took yours. # Message for internal errors, sent as << phonenumber << << imsi SC.Register.Msg.ErrorA Error in assigning -SC.Register.Msg.ErrorB to IMSI +SC.Register.Msg.ErrorB to # Bounds for valid number length SC.Register.Digits.Max 10 SC.Register.Digits.Min 7 From 53123da4f06603657573b6f5844cae22f6ac852d Mon Sep 17 00:00:00 2001 From: Alexander Chemeris Date: Fri, 5 Nov 2010 20:56:23 +0300 Subject: [PATCH 08/16] Maximum SMS length is 160 symbols in default alphabet. --- public-trunk/smqueue/smqueue.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public-trunk/smqueue/smqueue.h b/public-trunk/smqueue/smqueue.h index 5d3166b..2f024f6 100644 --- a/public-trunk/smqueue/smqueue.h +++ b/public-trunk/smqueue/smqueue.h @@ -46,7 +46,7 @@ using namespace SMS; namespace SMqueue { /* Maximum text size of an SMS message. */ -#define SMS_MESSAGE_MAX_LENGTH 140 +#define SMS_MESSAGE_MAX_LENGTH 160 /* std::abort isn't always there. Neither is the C library version. Idiots? You tell me. */ From ff7b341dae56afd12fdb79cfd60e73305a214154 Mon Sep 17 00:00:00 2001 From: Alexander Chemeris Date: Fri, 5 Nov 2010 20:56:55 +0300 Subject: [PATCH 09/16] More readable bounce message. --- public-trunk/smqueue/smqueue.config.example | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public-trunk/smqueue/smqueue.config.example b/public-trunk/smqueue/smqueue.config.example index 1ea5ef3..24740f5 100644 --- a/public-trunk/smqueue/smqueue.config.example +++ b/public-trunk/smqueue/smqueue.config.example @@ -60,7 +60,7 @@ SMS.FakeSrcSMSC 0000 # return SMS messages Bounce.Message.IMSILookupFailed Cannot determine return address; bouncing message. Text your phone number to 101 to register and try again. -Bounce.Message.NotRegistered Phone not registered here. +Bounce.Message.NotRegistered Phone not registered here. Message # Bounce from address Bounce.Code 411 From a55a85eebad4b7219e8eea529ab36d0d3095f9c2 Mon Sep 17 00:00:00 2001 From: Alexander Chemeris Date: Fri, 5 Nov 2010 20:57:49 +0300 Subject: [PATCH 10/16] Use decoded text from original message in bounce and e-mail messages. --- public-trunk/smqueue/smqueue.cpp | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/public-trunk/smqueue/smqueue.cpp b/public-trunk/smqueue/smqueue.cpp index 82f0c7f..2834763 100644 --- a/public-trunk/smqueue/smqueue.cpp +++ b/public-trunk/smqueue/smqueue.cpp @@ -1149,19 +1149,12 @@ enum sm_state SMq::bounce_message(short_msg_pending *sent_msg, const char *errstr) { ostringstream errmsg; - osip_body_t *bod1; char *username; - char *thetext; + std::string thetext; int status; username = sent_msg->parsed->to->url->username; - if (sent_msg->parsed->bodies.nb_elt > 0) { - // bod1->body is the original SMS text - bod1 = (osip_body_t *)sent_msg->parsed->bodies.node->element; - thetext = bod1->body; - } else { - thetext = ""; - } + thetext = sent_msg->get_text(); LOG(NOTICE) << "Bouncing " << sent_msg->qtag << " from " << sent_msg->parsed->from->url->username // his phonenum @@ -1430,18 +1423,15 @@ SMq::lookup_from_address (short_msg_pending *qmsg) // into the message text, followed by a space... GSM 03.40 sec 3.8 ostringstream newtext; osip_body_t *bod1; - char *bods; - bods = (char *)""; if (qmsg->parsed->bodies.nb_elt == 1) { bod1 = (osip_body_t *)qmsg->parsed->bodies.node->element; - bods = bod1->body; } else { return NO_STATE; // Punt on msg w/no text } newtext << qmsg->parsed->from->url->username << "@" - << host << " " << bods; - osip_free(bods); + << host << " " << qmsg->get_text(); + osip_free(bod1->body); bod1->body = osip_strdup(newtext.str().c_str()); bod1->length = strlen(bod1->body); From 7318cc60f9187e1ff108a90b2d1d9a298e5fb8cd Mon Sep 17 00:00:00 2001 From: Alexander Chemeris Date: Fri, 5 Nov 2010 21:41:59 +0300 Subject: [PATCH 11/16] Log contents of a message instead of its memory addresss in SMS::parseTPDU(). --- public-trunk/SMS/SMSMessages.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/public-trunk/SMS/SMSMessages.cpp b/public-trunk/SMS/SMSMessages.cpp index dceaa6b..e0dc115 100644 --- a/public-trunk/SMS/SMSMessages.cpp +++ b/public-trunk/SMS/SMSMessages.cpp @@ -123,9 +123,9 @@ TLMessage *SMS::parseTPDU(const TLFrame& TPDU) case TLMessage::SUBMIT: { TLSubmit *submit = new TLSubmit; submit->parse(TPDU); - LOG(INFO) << "SMS SMS-SUBMIT " << submit; + LOG(INFO) << "SMS SMS-SUBMIT " << *submit; return submit; - } + } default: return NULL; } From 63202cbf24773fd6f1241bd6e933caf2ee98a8fe Mon Sep 17 00:00:00 2001 From: Alexander Chemeris Date: Fri, 5 Nov 2010 21:42:38 +0300 Subject: [PATCH 12/16] Slightly cleaner output in TLUserData::write(). --- public-trunk/SMS/SMSMessages.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/public-trunk/SMS/SMSMessages.cpp b/public-trunk/SMS/SMSMessages.cpp index e0dc115..5d473c8 100644 --- a/public-trunk/SMS/SMSMessages.cpp +++ b/public-trunk/SMS/SMSMessages.cpp @@ -645,9 +645,9 @@ void TLUserData::write(TLFrame& dest, size_t& wp) const void TLUserData::text(ostream& os) const { os << "DCS=" << mDCS; - os << " UDHI=" << (mUDHI?"true":"false"); + os << " UDHI=" << mUDHI; os << " UDLength=" << mLength; - os << " UD= ("; mRawData.hex(os); os << ")"; + os << " UD=("; mRawData.hex(os); os << ")"; } From ef988a4b2f82908be2fc9bade3f413c18edc097b Mon Sep 17 00:00:00 2001 From: Alexander Chemeris Date: Fri, 5 Nov 2010 21:45:19 +0300 Subject: [PATCH 13/16] Transparently pass TP-UHDI (User-Data-Header-Indicator) from SMS-SUBMIT to SMS-DELIVER. Among other things this allows us to correctly pass-through concatenated SMS messages. --- public-trunk/SMS/SMSMessages.cpp | 10 ++++++---- public-trunk/SMS/SMSMessages.h | 8 ++++---- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/public-trunk/SMS/SMSMessages.cpp b/public-trunk/SMS/SMSMessages.cpp index 5d473c8..b3bfaaa 100644 --- a/public-trunk/SMS/SMSMessages.cpp +++ b/public-trunk/SMS/SMSMessages.cpp @@ -536,7 +536,6 @@ size_t TLUserData::length() const // The reported value includes the length byte itself. // The length() method only needs to work for formats supported // by the write() method. - assert(!mUDHI); // We don't support user headers. assert(mDCS<0x100); // Someone forgot to initialize the DCS. size_t sum = 1; // Start by counting the TP-User-Data-Length byte. #if 1 @@ -677,10 +676,12 @@ size_t TLSubmit::bodyLength() const void TLSubmit::parseBody(const TLFrame& src, size_t& rp) { + bool udhi; + parseRD(src); parseVPF(src); parseRP(src); - parseUDHI(src); + udhi = parseUDHI(src); parseSRR(src); mMR = src.readField(rp,8); mDA.parse(src,rp); @@ -689,6 +690,7 @@ void TLSubmit::parseBody(const TLFrame& src, size_t& rp) mVP.VPF(mVPF); mVP.parse(src,rp); mUD.DCS(mDCS); + mUD.UDHI(udhi); mUD.parse(src,rp); } @@ -699,7 +701,7 @@ void TLSubmit::text(ostream& os) const os << " RD=" << mRD; os << " VPF=" << mVPF; os << " RP=" << mRP; - os << " UDHI=" << mUDHI; + os << " UDHI=" << mUD.UDHI(); os << " SRR=" << mSRR; os << " MR=" << mMR; os << " DA=(" << mDA << ")"; @@ -721,7 +723,7 @@ void TLDeliver::writeBody(TLFrame& dest, size_t& wp) const { writeMMS(dest); writeRP(dest); - writeUDHI(dest); + writeUDHI(dest, mUD.UDHI()); writeSRI(dest); mOA.write(dest,wp); dest.writeField(wp,mPID,8); diff --git a/public-trunk/SMS/SMSMessages.h b/public-trunk/SMS/SMSMessages.h index 5db5426..a5f1c66 100644 --- a/public-trunk/SMS/SMSMessages.h +++ b/public-trunk/SMS/SMSMessages.h @@ -259,7 +259,7 @@ class TLMessage { bool mSRR; ///< status report request bool mSRI; ///< status report indication bool mSRQ; ///< status report qualifier - bool mUDHI; ///< user-data header-indicator +// bool mUDHI; ///< user-data header-indicator. Stored in TLUserData. bool mRP; ///< reply path //@} @@ -279,7 +279,7 @@ class TLMessage { }; TLMessage() - :mMMS(false),mSRI(false),mUDHI(false),mRP(true) + :mMMS(false),mSRI(false),mRP(true) {} virtual ~TLMessage(){} @@ -320,8 +320,8 @@ class TLMessage { void parseSRI(const TLFrame& fm) { mSRI=fm[2]; } void writeSRQ(TLFrame& fm) const { fm[2]=mSRQ; } void parseSRQ(const TLFrame& fm) { mSRQ=fm[2]; } - void writeUDHI(TLFrame& fm) const { fm[1]=mUDHI; } - void parseUDHI(const TLFrame& fm) { mUDHI=fm[1]; } + void writeUDHI(TLFrame& fm, bool udhi) const { fm[1]=udhi; } + bool parseUDHI(const TLFrame& fm) { return fm[1]; } void writeRP(TLFrame& fm) const { fm[0]=mRP; } void parseRP(const TLFrame& fm) { mRP=fm[0]; } void writeUnused(TLFrame& fm) const { fm.fill(0,3,2); } ///< Fill unused bits with 0s From 970beeb816160dcbaff0a155cc383cbc313ce4aa Mon Sep 17 00:00:00 2001 From: Alexander Chemeris Date: Fri, 5 Nov 2010 22:37:42 +0300 Subject: [PATCH 14/16] Ignore User-Data-Header in SMS TPDU when decoding text in TLUserData::encode7bit(). --- public-trunk/SMS/SMSMessages.cpp | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/public-trunk/SMS/SMSMessages.cpp b/public-trunk/SMS/SMSMessages.cpp index b3bfaaa..3fc15f2 100644 --- a/public-trunk/SMS/SMSMessages.cpp +++ b/public-trunk/SMS/SMSMessages.cpp @@ -498,7 +498,6 @@ std::string TLUserData::decode() const { std::string text; - if (mUDHI) SMS_READ_ERROR; // We don't support user headers. switch (mDCS) { case 0: case 244: @@ -512,10 +511,30 @@ std::string TLUserData::decode() const LOG(NOTICE) << "badly formatted TL-UD"; SMS_READ_ERROR; } + + size_t crp = 0; + unsigned text_length = mLength; + + // Skip User-Data-Header. We don't decode it here. + // User-Data-Header handling is described in GSM 03.40 9.2.3.24 + // and is pictured in GSM 03.40 Figure 9.2.3.24 (a) + if (mUDHI) { + // Length-of-User-Data-Header + unsigned udhl = mRawData.peekFieldReversed(crp,8); + // Calculate UDH length in septets, including fill bits. + unsigned udh_septets = (udhl*8 + 8 + 6) / 7; + // Adjust actual text position and length. + crp += udh_septets * 7; + text_length -= udh_septets; + LOG(DEBUG) << "UDHL(octets)=" << udhl + << " UDHL(septets)=" << udh_septets + << " pointer(bits)=" << crp + << " text_length(septets)=" << text_length; + } + // Do decoding - text.resize(mLength); - size_t crp=0; - for (unsigned i=0; i Date: Thu, 11 Nov 2010 10:23:37 +0300 Subject: [PATCH 15/16] Checking in forgotten smsc.cpp and smsc.h files. --- public-trunk/smqueue/smsc.cpp | 394 ++++++++++++++++++++++++++++++++++ public-trunk/smqueue/smsc.h | 39 ++++ 2 files changed, 433 insertions(+) create mode 100644 public-trunk/smqueue/smsc.cpp create mode 100644 public-trunk/smqueue/smsc.h diff --git a/public-trunk/smqueue/smsc.cpp b/public-trunk/smqueue/smsc.cpp new file mode 100644 index 0000000..60b7711 --- /dev/null +++ b/public-trunk/smqueue/smsc.cpp @@ -0,0 +1,394 @@ +/* + * SMSC.h - SMS Center implementation for OpenBTS. + * Written by Alexander Chemeris, 2010. + * + * Copyright 2010 Free Software Foundation, Inc. + * + * 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 . + * See the COPYING file in the main directory for details. + */ + +//#include +//#include +//#include +#include +//#include +//#include "ControlCommon.h" +#include +#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; + +//#include "CollectMSInfo.h" + +#include "smsc.h" + +// FORWARD DECLARATIONS +void set_to_for_smsc(const char *address, short_msg_p_list::iterator &smsg); + +/**@name Functions for transmitting through various gateways. */ +//@{ + + +/** Substitute spaces with +'s for compatibility with some protocols. */ +void convertText(char *dest, const char *src) +{ + // FIXME -- We should just do full "percent encoding" here. + while (*src != '\0') { + if (*src == ' ') *dest++ = '+'; + else *dest++ = *src; + src++; + } + *dest = '\0'; +} + + + +/** Send SMS via and HTTP interface. */ +short_code_action sendHTTP(const char* destination, const std::string &message) +{ + char convMessage[message.length()+2]; + convertText(convMessage,message.data()); + char command[2048]; + // FIXME -- Check specs for a good timeout value here. + sprintf(command,"wget -T 5 -C -q -O - \"http://%s/http/%s&to=%s&text=%s\" >& /dev/null", + gConfig.getStr("SMS.HTTP.Gateway"), + gConfig.getStr("SMS.HTTP.AccessString"), + destination, convMessage); + LOG(DEBUG) << "MOSMS: sendHTTP sending with " << command; + + // HTTP "GET" method with wget. + // FIXME -- Look at the output of wget to check success. + FILE* wget = popen(command,"r"); + if (!wget) { + LOG(ALARM) << "cannot open wget with " << command; + return SCA_INTERNAL_ERROR; + } + pclose(wget); + return SCA_DONE; +} + + +/** Send e-mail with local sendmail program. */ +short_code_action sendEMail(const char* address, const char* body, const char* subject=NULL) +{ + // We're not checking for overflow because the TPDU can't be more than a few hundred bytes. + + // Build the command line. + // FIXME -- Use sendmail to have better header control. + char command[1024]; + if (subject) sprintf(command,"mail -s \"%s\" %s",subject,address); + else sprintf(command,"mail %s",address); + LOG(INFO) << "sending SMTP: \"" << body << "\" via \"" << command << "\""; + + // Send the mail. + FILE* mail = popen(command,"w"); + if (!mail) { + LOG(ALARM) << "cannot send mail with \"" << command << "\""; + return SCA_INTERNAL_ERROR; + } + // FIXME -- We should be sure body is 7-bit clean. + fprintf(mail,"%s",body); + if (pclose(mail) == -1) return SCA_INTERNAL_ERROR; + return SCA_DONE; +} + +short_code_action sendSIP_init(const char *imsi, const TLSubmit& submit, + const std::string &body, short_code_params *scp) +{ + const char *address = submit.DA().digits(); + const TLUserData& tl_ud = submit.UD(); + const char *from = scp->scp_qmsg_it->parsed->from->url->username; + LOG(INFO) << "from " << imsi << " to " << address; + + if (scp == NULL) + { + LOG(WARN) << "short_code_params is NULL. Error."; + return SCA_INTERNAL_ERROR; + } + + // START OF THE SIP PROCESSING + osip_message_t *omsg = scp->scp_qmsg_it->parsed; + + // Req.URI + osip_free(omsg->req_uri->username); + omsg->req_uri->username = (char *)osip_malloc (strlen(address)+1); + strcpy(omsg->req_uri->username, address); + + // To: + set_to_for_smsc(address, scp->scp_qmsg_it); + + // Let them know that parsed part has been changed. + scp->scp_qmsg_it->parsed_was_changed(); + + if (ISLOGGING(DEBUG)) { + // Call make_text_valid() is needed for debug only. + scp->scp_qmsg_it->make_text_valid(); + LOG(DEBUG) << "Updated SMS message: " << scp->scp_qmsg_it->text; + } + return SCA_RESTART_PROCESSING; +} + +void create_sms_delivery(const std::string &body, + const TLUserData &UD, + short_msg_p_list::iterator &smsg) +{ + RPData *rp_data_new = NULL; + TLDeliver *deliver = NULL; + const char *from = smsg->parsed->from->url->username; + + // HACK + // Check for "Easter Eggs" + // TL-PID + // See 03.40 9.2.3.9. + unsigned TLPID=0; + if (strncmp(body.data(),"#!TLPID",7)==0) sscanf(body.data(),"#!TLPID%d",&TLPID); + + // Generate RP-DATA with SMS-DELIVER + { + unsigned reference = random() % 255; + deliver = new TLDeliver(from,UD,TLPID); + rp_data_new = new RPData(reference, RPAddress(gConfig.getStr("SMS.FakeSrcSMSC")), + *deliver); + LOG(DEBUG) << "New RPData: " << *rp_data_new; + } + + // Replace RP-DATA in the message + delete smsg->rp_data; + delete smsg->tl_message; + smsg->rp_data = rp_data_new; + smsg->tl_message = deliver; +} + +void pack_tpdu(short_msg_p_list::iterator &smsg) +{ + // Pack RP-DATA to bitstream + RLFrame RPDU_new(smsg->rp_data->bitsNeeded()); + smsg->rp_data->write(RPDU_new); + LOG(DEBUG) << "New RLFrame: " << RPDU_new; + + // START OF THE SIP PROCESSING + osip_message_t *omsg = smsg->parsed; + + // Message body + osip_body_t *bod1 = (osip_body_t *)omsg->bodies.node->element; + osip_free(bod1->body); + ostringstream body_stream; + RPDU_new.hex(body_stream); + bod1->length = body_stream.str().length(); + bod1->body = (char *)osip_malloc (bod1->length+1); + strcpy(bod1->body, body_stream.str().data()); + + // Let them know that parsed part has been changed. + smsg->parsed_was_changed(); + + // It's SC->MS now + smsg->ms_to_sc = false; +} + +void set_to_for_smsc(const char *address, short_msg_p_list::iterator &smsg) +{ + osip_message_t *omsg = smsg->parsed; + + // Set To field + osip_free(omsg->to->url->username); + osip_free(omsg->to->displayname); + omsg->to->url->username = (char *)osip_malloc (strlen(address)+1); + omsg->to->displayname = (char *)osip_malloc (strlen(omsg->req_uri->username)+1); + strcpy(omsg->to->url->username, address); + strcpy(omsg->to->displayname, omsg->req_uri->username); + + // Let them know that parsed part has been changed. + smsg->parsed_was_changed(); +} + +//@} + + +/** Send a TPDU through whatever gateway is available. */ +short_code_action submitSMS(const char *imsi, const TLSubmit& submit, + const std::string &body, short_code_params *scp) +{ + LOG(INFO) << "from " << imsi << " message: " << submit; + const TLAddress& address = submit.DA(); + + // Check for direct e-mail address at start of message body. + // FIXME -- This doesn't really follow the spec. See GSM 03.40 3.8. + static const Regexp emailAddress("^[[:graph:]]+@[[:graph:]]+ "); + if (emailAddress.match(body.data())) { + // FIXME -- Get the sender's E.164 to put in the subject line. + char bodyCopy[body.length()+2]; + strcpy(bodyCopy,body.data()); + char* SMTPAddress = bodyCopy; + char* term = strchr(bodyCopy,' '); + // If term's NULL, the regexp is broken. + assert(term); + *term = '\0'; + char* SMTPPayload = term+1; + LOG(INFO) << "sending SMTP to " << SMTPAddress << ": " << SMTPPayload; + if (SMTPPayload) return sendEMail(SMTPAddress,SMTPPayload,"from OpenBTS gateway"); + else return sendEMail(SMTPAddress,"(empty)","from OpenBTS gateway"); + } + + // Send to smqueue or HTTP gateway, depending on what's defined in the config. + if (gConfig.defines("SMS.HTTP.Gateway")) + // If there is an external HTTP gateway, use it. + return sendHTTP(address.digits(), body); + else + // Otherwise, we are looking for a SIP interface to smqueue. + return sendSIP_init(imsi, submit, body, scp); +} + +short_code_action shortcode_smsc(const char *imsi, const char *msgtext, + short_code_params *scp) +{ + short_code_action return_action = SCA_TREAT_AS_ORDINARY; + short_msg_p_list::iterator smsg = scp->scp_qmsg_it; + + if (smsg->content_type != short_msg::VND_3GPP_SMS) + { + LOG(WARN) << "We support only TPDU-coded SMS messages. Plain text ones are to be supported later."; + return SCA_INTERNAL_ERROR; + } + if (smsg->tl_message == NULL) + { + LOG(WARN) << "TLMessage is not available. Error during SMS decoding occurred?"; + return SCA_INTERNAL_ERROR; + } + if (((TLMessage::MessageType)smsg->tl_message->MTI()) != TLMessage::SUBMIT) + { + LOG(WARN) << "TPDU must be of SMS-SUBMIT type, we have MTI=" + << (TLMessage::MessageType)smsg->tl_message->MTI(); + return SCA_INTERNAL_ERROR; + } + + return_action = submitSMS(imsi, *((TLSubmit*)smsg->tl_message), msgtext, scp); + + return return_action; +} + +bool pack_text_to_tpdu(const std::string &body, + short_msg_p_list::iterator &smsg) +{ + create_sms_delivery(body, TLUserData(body.data()), smsg); + pack_tpdu(smsg); + + // Set Content-Type field + const char *type = "application"; + const char *subtype = "vnd.3gpp.sms"; + osip_free(smsg->parsed->content_type->type); + osip_free(smsg->parsed->content_type->subtype); + smsg->parsed->content_type->type = (char *)osip_malloc (strlen(type)+1); + smsg->parsed->content_type->subtype = (char *)osip_malloc (strlen(subtype)+1); + strcpy(smsg->parsed->content_type->type, type); + strcpy(smsg->parsed->content_type->subtype, subtype); + + // Let them know that parsed part has been changed. + smsg->parsed_was_changed(); + + return true; +} + +bool recode_tpdu(const std::string &body, + short_msg_p_list::iterator &smsg) +{ + bool return_action = true; + + // Safety check + if (smsg->tl_message == NULL) + { + LOG(WARN) << "TLMessage is not available. Error during SMS decoding occurred?"; + return false; + } + + switch ((TLMessage::MessageType)smsg->tl_message->MTI()) { + case TLMessage::SUBMIT: { + TLSubmit *submit = (TLSubmit*)smsg->tl_message; + const TLUserData& tl_ud = submit->UD(); + create_sms_delivery(body, tl_ud, smsg); + pack_tpdu(smsg); + return_action = true; + break; + } + + case TLMessage::DELIVER_REPORT: + case TLMessage::STATUS_REPORT: + // TODO + LOG(WARN) << "TPDU must be of SMS-SUBMIT type, we have MTI=" + << (TLMessage::MessageType)smsg->tl_message->MTI(); + return false; + } + + // Now we have MS->SC message + smsg->ms_to_sc = false; + + return return_action; +} + +bool pack_sms_for_delivery(short_msg_p_list::iterator &smsg) +{ + bool return_action = true; + std::string msgtext; + + if (!smsg->need_repack) { + // Nothing to do. + return true; + } + + // Parse message if not parsed yet. + smsg->parse(); + + // Get message text in plain text form + msgtext = smsg->get_text(); + + switch (smsg->content_type) { + case short_msg::TEXT_PLAIN: + return_action = pack_text_to_tpdu(msgtext, smsg); + break; + + case short_msg::VND_3GPP_SMS: + // No need to recode if it's SC->MS already + if (smsg->ms_to_sc) + { + return_action = recode_tpdu(msgtext, smsg); + } + break; + + case short_msg::UNSUPPORTED_CONTENT: + default: + LOG(WARN) << "Unsupported SIP-messages content."; + return false; + } + + // Successful repack + if (return_action) { + smsg->need_repack = false; + } + + if (ISLOGGING(DEBUG)) { + // Call make_text_valid() is needed for debug only. + smsg->make_text_valid(); + LOG(DEBUG) << "Updated SMS message: " << smsg->text; + } + + return return_action; +} diff --git a/public-trunk/smqueue/smsc.h b/public-trunk/smqueue/smsc.h new file mode 100644 index 0000000..aa88c08 --- /dev/null +++ b/public-trunk/smqueue/smsc.h @@ -0,0 +1,39 @@ +/* + * SMSC.h - SMS Center implementation for OpenBTS. + * Written by Alexander Chemeris, 2010. + * + * Copyright 2010 Free Software Foundation, Inc. + * + * 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 . + * See the COPYING file in the main directory for details. + */ + +#ifndef SMSC_H +#define SMSC_H + +#include "smqueue.h" + +using namespace SMqueue; + +/** Unhex and parse msgtext to RPData struct. + @param msgttext RPData encoded into hex-string. + @return Pointer to parsed RPData or NULL on failure. +*/ +RPData *hex2rpdata(const char *msgtext); + +enum short_code_action shortcode_smsc(const char *imsi, const char *msgtext, + short_code_params *scp); +bool pack_sms_for_delivery(short_msg_p_list::iterator &smsg); + +#endif From 141846141096a191b1f2909f61198a436e9aa92a Mon Sep 17 00:00:00 2001 From: Alexander Chemeris Date: Wed, 15 Dec 2010 23:11:07 +0300 Subject: [PATCH 16/16] smqueue: Add SMSC short code to smqueue.config.example --- public-trunk/smqueue/smqueue.config.example | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/public-trunk/smqueue/smqueue.config.example b/public-trunk/smqueue/smqueue.config.example index 24740f5..65085f8 100644 --- a/public-trunk/smqueue/smqueue.config.example +++ b/public-trunk/smqueue/smqueue.config.example @@ -110,4 +110,6 @@ SC.WhiplashQuit.Code 314158 SC.WhiplashQuit.Password Snidely SC.WhiplashQuit.SaveFile testsave.txt - +# SMS-Center code. OpenBTS will send messages from mobile phones +# to this short code and will work as usual SMS-Center. +SC.SMSC.Code smsc