From d54e4d82fde060d617e5636f07f75585f5cd7ae1 Mon Sep 17 00:00:00 2001 From: Alexander Chemeris Date: Thu, 4 Nov 2010 20:12:06 +0300 Subject: [PATCH] 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 | 355 ++++++++++++-------- public-trunk/smqueue/smqueue.h | 314 +++++++++-------- 15 files changed, 751 insertions(+), 518 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) -{ - // 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) +bool sendSIP(const L3MobileIdentity &mobileID, const char* address, const char* body) { - 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) + { + } + + /** 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); + } - /** Initialze from a simple C string. */ - TLUserData(const char* text, bool wUDHI=false) + /** 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); - - // 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; + << "(" << bods << "): " << params.scp_reply; + next_state = NO_STATE; + return true; + + 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_DONE: - return DELETE_ME_STATE; - - 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_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_REGISTER: - return register_handset(qmsg); - - case SCA_TREAT_AS_ORDINARY: - break; // fall through into non-special case. - } + case SCA_QUIT_SMQUEUE: + stop_main_loop = true; + next_state = DELETE_ME_STATE; + return true; + + 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++; + 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. + next_state = AWAITING_REGISTER_HANDSET; + return true; + + case SCA_REGISTER: + next_state = register_handset(qmsg); + return true; + + 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. */