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/CommonLibs/Threads.cpp b/public-trunk/CommonLibs/Threads.cpp index 2df6e78..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,6 +97,67 @@ void Signal::wait(Mutex& wMutex, unsigned timeout) const pthread_cond_timedwait(&mSignal,&wMutex.mMutex,&waitTime); } +ThreadSemaphore::Result ThreadSemaphore::wait(unsigned timeoutMs) +{ + Timeval then(timeoutMs); + struct timespec waitTime = then.timespec(); + 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 d083e3f..9015be8 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,62 @@ class Signal { }; +/** Semaphore */ +class ThreadSemaphore { + private: + + sem_t mSem; + + public: + + 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. + * @param[in] timeoutMs - timeout in milliseconds + * + * @retval TSEM_OK on success. + * @retval TSEM_TIMEOUT on timeout. + * @retval TSEM_ERROR on error. + */ + Result wait(unsigned timeoutMs); + + /** Wait for semaphore to be signaled infinitely. + * @retval TSEM_OK on success. + * @retval TSEM_ERROR on error. + */ + Result wait(); + + /** Check if semaphore has been signaled and disarm it. + * @retval TSEM_OK is semaphore is signaled. + * @retval TSEM_TIMEOUT if semaphore is not signaled. + * @retval TSEM_ERROR on error. + */ + Result trywait(); + + /** Signal semaphore. + * @retval TSEM_OK on success. + * @retval TSEM_ERROR on error. + */ + Result post(); + +}; #define START_THREAD(thread,function,argument) \ thread.start((void *(*)(void*))function, (void*)argument); diff --git a/public-trunk/Control/ControlCommon.cpp b/public-trunk/Control/ControlCommon.cpp index 1d0cac7..21fd193 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 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..3fc15f2 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,16 +470,96 @@ 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; + } + + 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(text_length); + 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; @@ -225,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 //@} @@ -236,16 +270,16 @@ 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() - :mMMS(false),mSRI(false),mUDHI(false),mRP(true) + :mMMS(false),mSRI(false),mRP(true) {} virtual ~TLMessage(){} @@ -286,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 @@ -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..65085f8 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 @@ -37,10 +44,26 @@ $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. -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. Message + +# Bounce from address +Bounce.Code 411 @@ -62,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 @@ -87,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 diff --git a/public-trunk/smqueue/smqueue.cpp b/public-trunk/smqueue/smqueue.cpp index 1a39bb9..2834763 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; @@ -1102,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 @@ -1126,19 +1166,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; @@ -1189,6 +1229,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 +1289,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; } @@ -1378,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) { + 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); @@ -1461,7 +1503,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. } @@ -1540,7 +1582,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 @@ -1782,7 +1824,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 +1939,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 +2060,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 +2085,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 +2109,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 +2128,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 +2144,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 +2167,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 +2223,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 +2237,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..2f024f6 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,10 +35,18 @@ #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. */ -#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. */ @@ -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. */ 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