laforge
/
openbts-osmo
Archived
1
0
Fork 0

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.
master
Alexander Chemeris 12 years ago committed by Thomas Tsou
parent 57d292d962
commit d54e4d82fd
  1. 251
      public-trunk/Control/SMSControl.cpp
  2. 11
      public-trunk/SIP/SIPEngine.cpp
  3. 4
      public-trunk/SIP/SIPEngine.h
  4. 14
      public-trunk/SIP/SIPMessage.cpp
  5. 2
      public-trunk/SIP/SIPMessage.h
  6. 160
      public-trunk/SMS/SMSMessages.cpp
  7. 79
      public-trunk/SMS/SMSMessages.h
  8. 11
      public-trunk/apps/OpenBTS.config.example
  9. 31
      public-trunk/smqueue/Makefile.am
  10. 14
      public-trunk/smqueue/Makefile.standalone
  11. 6
      public-trunk/smqueue/README.smqueue
  12. 4
      public-trunk/smqueue/smcommands.cpp
  13. 13
      public-trunk/smqueue/smqueue.config.example
  14. 355
      public-trunk/smqueue/smqueue.cpp
  15. 314
      public-trunk/smqueue/smqueue.h

@ -42,6 +42,7 @@
#include <stdio.h>
#include <sstream>
#include <GSMLogicalChannel.h>
#include <GSML3MMMessages.h>
#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);
}
}

@ -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);

@ -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();

@ -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));

@ -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);

@ -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<mLength; i++) {
char gsm = encodeGSMChar(text[i]);
mRawData.writeFieldReversed(wp,gsm,7);
}
mRawData.writeField(wp,0,filler_bits);
}
std::string TLUserData::decode() const
{
std::string text;
if (mUDHI) SMS_READ_ERROR; // We don't support user headers.
switch (mDCS) {
case 0:
case 244:
case 245:
case 246:
case 247:
{
// GSM 7-bit encoding, GSM 03.38 6.
// Check bounds.
if (mLength*7 > (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<mLength; i++) {
char gsm = mRawData.readFieldReversed(crp,7);
text[i] = decodeGSMChar(gsm);
}
break;
}
default:
LOG(NOTICE) << "unsupported DCS 0x" << mDCS;
SMS_READ_ERROR;
break;
}
return text;
}
size_t TLUserData::length() const
{
@ -419,7 +538,10 @@ size_t TLUserData::length() const
// by the write() method.
assert(!mUDHI); // We don't support user headers.
assert(mDCS<0x100); // Someone forgot to initialize the DCS.
size_t sum = 1; // Start by counting the length byte.
size_t sum = 1; // Start by counting the TP-User-Data-Length byte.
#if 1
sum += (mRawData.size()+7)/8;
#else
// The DCS is defined in GSM 03.38 4.
if (mDCS==0) {
// Default 7-bit alphabet
@ -428,23 +550,31 @@ size_t TLUserData::length() const
unsigned octets = bits/8;
if (bits%8) octets += 1;
sum += octets;
return sum;
} else {
LOG(ERROR) << "unsupported SMS DCS 0x" << hex << mDCS;
// It's OK to abort here. This method is only used for encoding.
// So we should never end up here.
assert(0); // We don't support this DCS.
}
#endif
return sum;
}
void TLUserData::parse(const TLFrame& src, size_t& rp)
{
assert(!mUDHI); // We don't support user headers.
// The DCS is defined in GSM 03.38 4.
assert(mDCS<0x100); // Someone forgot to initialize the DCS.
unsigned numChar = src.readField(rp,8);
// TP-User-Data-Length
mLength = src.readField(rp,8);
#if 1
// This tail() works because UD is always the last field in the PDU.
mRawData.clone(src.tail(rp));
// Should we do this here?
mRawData.LSB8MSB();
#else
assert(!mUDHI); // We don't support user headers.
switch (mDCS) {
case 0:
case 244:
@ -478,11 +608,22 @@ void TLUserData::parse(const TLFrame& src, size_t& rp)
SMS_READ_ERROR;
}
}
#endif
}
void TLUserData::write(TLFrame& dest, size_t& wp) const
{
#if 1
// First write TP-User-Data-Length
dest.writeField(wp,mLength,8);
// Then write TP-User-Data
// This tail() works because UD is always the last field in the PDU.
BitVector ud_dest = dest.tail(wp);
mRawData.copyTo(ud_dest);
ud_dest.LSB8MSB();
#else
// Stuff we don't support...
assert(!mUDHI);
assert(mDCS==0);
@ -496,14 +637,17 @@ void TLUserData::write(TLFrame& dest, size_t& wp) const
dest.writeFieldReversed(wp,gsm,7);
}
chars.LSB8MSB();
#endif
}
void TLUserData::text(ostream& os) const
{
if (mDCS==0) os << mData;
else os << "unknown encoding";
os << "DCS=" << mDCS;
os << " UDHI=" << (mUDHI?"true":"false");
os << " UDLength=" << mLength;
os << " UD= ("; mRawData.hex(os); os << ")";
}
@ -568,7 +712,7 @@ void TLSubmit::text(ostream& os) const
size_t TLDeliver::bodyLength() const
{
LOG(DEBUG) << "TLDEliver::bodyLength OA " << mOA.length() << " SCTS " << mSCTS.length() << " UD " << mUD.length();
LOG(DEBUG) << "TLDeliver::bodyLength OA " << mOA.length() << " SCTS " << mSCTS.length() << " UD " << mUD.length();
return mOA.length() + 1 + 1 + mSCTS.length() + mUD.length();
}
@ -581,7 +725,7 @@ void TLDeliver::writeBody(TLFrame& dest, size_t& wp) const
writeSRI(dest);
mOA.write(dest,wp);
dest.writeField(wp,mPID,8);
dest.writeField(wp,0,8); // hardcode DCS
dest.writeField(wp,mUD.DCS(),8);
mSCTS.write(dest,wp);
writeUnused(dest);
mUD.write(dest,wp);

@ -38,7 +38,9 @@
#include <GSML3Message.h>
#include <GSML3CCElements.h>
#include <GSML3MMElements.h>
#include <Logger.h>
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 );

@ -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>@<SIP.IP>
SIP.SMSC smsc

@ -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)

@ -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

@ -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

@ -23,6 +23,7 @@
#include "smqueue.h"
#include "smnet.h"
#include "smsc.h"
#include <iostream>
#include <fstream>
#include <string>
@ -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;
}

@ -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.

@ -21,6 +21,7 @@
#include "smqueue.h"
#include "smnet.h"
#include "smsc.h"
#include <time.h>
#include <osipparser2/osip_message.h> /* from osipparser2 */
#include <iostream>
@ -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;