laforge
/
openbts-osmo
Archived
1
0
Fork 0

Merge branch 'sms-split'

* sms-split:
  smqueue: Add SMSC short code to smqueue.config.example
  Checking in forgotten smsc.cpp and smsc.h files.
  Ignore User-Data-Header in SMS TPDU when decoding text in TLUserData::encode7bit().
  Transparently pass TP-UHDI (User-Data-Header-Indicator) from SMS-SUBMIT to SMS-DELIVER.
  Slightly cleaner output in TLUserData::write().
  Log contents of a message instead of its memory addresss in SMS::parseTPDU().
  Use decoded text from original message in bounce and e-mail messages.
  More readable bounce message.
  Maximum SMS length is 160 symbols in default alphabet.
  Avoid duplication of "IMSI" in SC.Register.Msg.
  Set Log.Alarms.* values in smqueue config.
  Make "from" address for bounce messages configurable.
  Move most of the SMS processing to smqueue (initial check-in).
  Better documentation and error reporting for ThreadSemaphore.
  Implementation of Semaphore.
  Implemented "trans" CLI command to list all active transacions.
master
Thomas Tsou 12 years ago
commit 3ea07de75c
  1. 14
      public-trunk/CLI/CLI.cpp
  2. 62
      public-trunk/CommonLibs/Threads.cpp
  3. 56
      public-trunk/CommonLibs/Threads.h
  4. 12
      public-trunk/Control/ControlCommon.cpp
  5. 3
      public-trunk/Control/ControlCommon.h
  6. 251
      public-trunk/Control/SMSControl.cpp
  7. 11
      public-trunk/SIP/SIPEngine.cpp
  8. 4
      public-trunk/SIP/SIPEngine.h
  9. 14
      public-trunk/SIP/SIPMessage.cpp
  10. 2
      public-trunk/SIP/SIPMessage.h
  11. 189
      public-trunk/SMS/SMSMessages.cpp
  12. 87
      public-trunk/SMS/SMSMessages.h
  13. 11
      public-trunk/apps/OpenBTS.config.example
  14. 31
      public-trunk/smqueue/Makefile.am
  15. 14
      public-trunk/smqueue/Makefile.standalone
  16. 6
      public-trunk/smqueue/README.smqueue
  17. 4
      public-trunk/smqueue/smcommands.cpp
  18. 33
      public-trunk/smqueue/smqueue.config.example
  19. 393
      public-trunk/smqueue/smqueue.cpp
  20. 316
      public-trunk/smqueue/smqueue.h
  21. 394
      public-trunk/smqueue/smsc.cpp
  22. 39
      public-trunk/smqueue/smsc.h

@ -290,6 +290,19 @@ int tmsis(int argc, char** argv, ostream& os, istream& is)
}
/** Print the transactions table. */
int trans(int argc, char** argv, ostream& os, istream& is)
{
if (argc!=1) return BAD_NUM_ARGS;
// os << "TMSI IMSI IMEI age used" << endl;
gTransactionTable.dump(os);
os << endl << gTransactionTable.size() << " transactions in table" << endl;
return SUCCESS;
}
int findimsi(int argc, char** argv, ostream& os, istream& is)
{
@ -759,6 +772,7 @@ Parser::Parser()
addCommand("help", showHelp, "[command] -- list available commands or gets help on a specific command.");
addCommand("exit", exit_function, "[wait] -- exit the application, either immediately, or waiting for existing calls to clear with a timeout in seconds");
addCommand("tmsis", tmsis, "[\"clear\"] or [\"dump\" filename] -- print/clear the TMSI table or dump it to a file.");
addCommand("trans", trans, "-- print the transactions table.");
addCommand("findimsi", findimsi, "[IMSIPrefix] -- prints all imsi's that are prefixed by IMSIPrefix");
addCommand("sendsms", sendsms, "<IMSI> <src> -- send SMS to <IMSI>, addressed from <src>, after prompting.");
addCommand("sendrrlp", sendrrlp, "<IMSI> <hexstring> -- send RRLP message <hexstring> to <IMSI>.");

@ -28,6 +28,7 @@
#include "Threads.h"
#include "Timeval.h"
#include <errno.h>
using namespace std;
@ -96,6 +97,67 @@ void Signal::wait(Mutex& wMutex, unsigned timeout) const
pthread_cond_timedwait(&mSignal,&wMutex.mMutex,&waitTime);
}
ThreadSemaphore::Result ThreadSemaphore::wait(unsigned timeoutMs)
{
Timeval then(timeoutMs);
struct timespec waitTime = then.timespec();
int s;
while ((s = sem_timedwait(&mSem,&waitTime)) == -1 && errno == EINTR)
continue;
if (s == -1)
{
if (errno == ETIMEDOUT)
{
return TSEM_TIMEOUT;
}
return TSEM_ERROR;
}
return TSEM_OK;
}
ThreadSemaphore::Result ThreadSemaphore::wait()
{
int s;
while ((s = sem_wait(&mSem)) == -1 && errno == EINTR)
continue;
if (s == -1)
{
return TSEM_ERROR;
}
return TSEM_OK;
}
ThreadSemaphore::Result ThreadSemaphore::trywait()
{
int s;
while ((s = sem_trywait(&mSem)) == -1 && errno == EINTR)
continue;
if (s == -1)
{
if (errno == EAGAIN)
{
return TSEM_TIMEOUT;
}
return TSEM_ERROR;
}
return TSEM_OK;
}
ThreadSemaphore::Result ThreadSemaphore::post()
{
if (sem_post(&mSem) != 0)
{
if (errno == EOVERFLOW)
{
return TSEM_OVERFLOW;
}
return TSEM_ERROR;
}
return TSEM_OK;
}
void Thread::start(void *(*task)(void*), void *arg)
{

@ -29,6 +29,7 @@
#include <pthread.h>
#include <iostream>
#include <assert.h>
#include <semaphore.h>
class Mutex;
@ -120,7 +121,62 @@ class Signal {
};
/** Semaphore */
class ThreadSemaphore {
private:
sem_t mSem;
public:
enum Result {
TSEM_OK, ///< Success.
TSEM_TIMEOUT, ///< wait() or trywait() timed out.
TSEM_OVERFLOW, ///< post() overflows a semaphore
TSEM_ERROR ///< Generic error.
};
/** Create and initialize semaphore.
* @param[in] value - initial semaphore value.
*/
ThreadSemaphore(unsigned value = 0)
{
int s = sem_init(&mSem,0,value);
assert(s == 0);
}
~ThreadSemaphore() { sem_destroy(&mSem); }
/** Wait for semaphore to be signaled with timeout.
* @param[in] timeoutMs - timeout in milliseconds
*
* @retval TSEM_OK on success.
* @retval TSEM_TIMEOUT on timeout.
* @retval TSEM_ERROR on error.
*/
Result wait(unsigned timeoutMs);
/** Wait for semaphore to be signaled infinitely.
* @retval TSEM_OK on success.
* @retval TSEM_ERROR on error.
*/
Result wait();
/** Check if semaphore has been signaled and disarm it.
* @retval TSEM_OK is semaphore is signaled.
* @retval TSEM_TIMEOUT if semaphore is not signaled.
* @retval TSEM_ERROR on error.
*/
Result trywait();
/** Signal semaphore.
* @retval TSEM_OK on success.
* @retval TSEM_ERROR on error.
*/
Result post();
};
#define START_THREAD(thread,function,argument) \
thread.start((void *(*)(void*))function, (void*)argument);

@ -343,6 +343,18 @@ size_t TransactionTable::size()
}
void TransactionTable::dump(ostream& os) const
{
mLock.lock();
TransactionMap::const_iterator tp = mTable.begin();
while (tp != mTable.end()) {
os << hex << "0x" << tp->first << " " << dec << tp->second << endl;
++tp;
}
mLock.unlock();
}
void Control::clearTransactionHistory( TransactionEntry& transaction )
{
SIP::SIPEngine& engine = transaction.SIP();

@ -611,6 +611,9 @@ class TransactionTable {
//@}
size_t size();
/** Write entries as text to a stream. */
void dump(std::ostream&) const;
};
//@} // Transaction Table

@ -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,16 +470,96 @@ void TLValidityPeriod::text(ostream& os) const
os << "expiration=(" << str << ")";
}
void TLUserData::encode7bit(const char *text)
{
size_t wp = 0;
// 1. Prepare.
// Default alphabet (7-bit)
mDCS = 0;
// With 7-bit encoding TP-User-Data-Length count septets, i.e. just number
// of characters.
mLength = strlen(text);
int bytes = (mLength*7+7)/8;
int filler_bits = bytes*8-mLength*7;
mRawData.resize(bytes*8);
// 2. Write TP-UD
// This tail() works because UD is always the last field in the PDU.
BitVector chars = mRawData.tail(wp);
for (unsigned i=0; i<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;
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;
}
size_t crp = 0;
unsigned text_length = mLength;
// Skip User-Data-Header. We don't decode it here.
// User-Data-Header handling is described in GSM 03.40 9.2.3.24
// and is pictured in GSM 03.40 Figure 9.2.3.24 (a)
if (mUDHI) {
// Length-of-User-Data-Header
unsigned udhl = mRawData.peekFieldReversed(crp,8);
// Calculate UDH length in septets, including fill bits.
unsigned udh_septets = (udhl*8 + 8 + 6) / 7;
// Adjust actual text position and length.
crp += udh_septets * 7;
text_length -= udh_septets;
LOG(DEBUG) << "UDHL(octets)=" << udhl
<< " UDHL(septets)=" << udh_septets
<< " pointer(bits)=" << crp
<< " text_length(septets)=" << text_length;
}
// Do decoding
text.resize(text_length);
for (unsigned i=0; i<text_length; 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
{
// The reported value includes the length byte itself.
// The length() method only needs to work for formats supported
// by the write() method.
assert(!mUDHI); // We don't support user headers.
assert(mDCS<0x100); // Someone forgot to initialize the DCS.
size_t sum = 1; // Start by counting the 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 +568,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 +626,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 +655,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;
os << " UDLength=" << mLength;
os << " UD=("; mRawData.hex(os); os << ")";
}
@ -533,10 +695,12 @@ size_t TLSubmit::bodyLength() const
void TLSubmit::parseBody(const TLFrame& src, size_t& rp)
{
bool udhi;
parseRD(src);
parseVPF(src);
parseRP(src);
parseUDHI(src);
udhi = parseUDHI(src);
parseSRR(src);
mMR = src.readField(rp,8);
mDA.parse(src,rp);
@ -545,6 +709,7 @@ void TLSubmit::parseBody(const TLFrame& src, size_t& rp)
mVP.VPF(mVPF);
mVP.parse(src,rp);
mUD.DCS(mDCS);
mUD.UDHI(udhi);
mUD.parse(src,rp);
}
@ -555,7 +720,7 @@ void TLSubmit::text(ostream& os) const
os << " RD=" << mRD;
os << " VPF=" << mVPF;
os << " RP=" << mRP;
os << " UDHI=" << mUDHI;
os << " UDHI=" << mUD.UDHI();
os << " SRR=" << mSRR;
os << " MR=" << mMR;
os << " DA=(" << mDA << ")";
@ -568,7 +733,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();
}
@ -577,11 +742,11 @@ void TLDeliver::writeBody(TLFrame& dest, size_t& wp) const
{
writeMMS(dest);
writeRP(dest);
writeUDHI(dest);
writeUDHI(dest, mUD.UDHI());
writeSRI(dest);
mOA.write(dest,wp);
dest.writeField(wp,mPID,8);
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;
@ -225,7 +259,7 @@ class TLMessage {
bool mSRR; ///< status report request
bool mSRI; ///< status report indication
bool mSRQ; ///< status report qualifier
bool mUDHI; ///< user-data header-indicator
// bool mUDHI; ///< user-data header-indicator. Stored in TLUserData.
bool mRP; ///< reply path
//@}
@ -236,16 +270,16 @@ class TLMessage {
/** GSM 03.40 9.2.3.1 */
enum MessageType {
DELIVER = 0x0,
DELIVER_REPORT = 0x0,
STATUS_REPORT = 0x2,
COMMAND = 0x02,
SUBMIT = 0x1,
SUBMIT_REPORT = 0x1
DELIVER = 0x0, // SC -> MS
DELIVER_REPORT = 0x0, // MS -> SC
STATUS_REPORT = 0x2, // SC -> MS
COMMAND = 0x02, // MS -> SC
SUBMIT = 0x1, // MS -> SC
SUBMIT_REPORT = 0x1 // SC -> MS
};
TLMessage()
:mMMS(false),mSRI(false),mUDHI(false),mRP(true)
:mMMS(false),mSRI(false),mRP(true)
{}
virtual ~TLMessage(){}
@ -286,8 +320,8 @@ class TLMessage {
void parseSRI(const TLFrame& fm) { mSRI=fm[2]; }
void writeSRQ(TLFrame& fm) const { fm[2]=mSRQ; }
void parseSRQ(const TLFrame& fm) { mSRQ=fm[2]; }
void writeUDHI(TLFrame& fm) const { fm[1]=mUDHI; }
void parseUDHI(const TLFrame& fm) { mUDHI=fm[1]; }
void writeUDHI(TLFrame& fm, bool udhi) const { fm[1]=udhi; }
bool parseUDHI(const TLFrame& fm) { return fm[1]; }
void writeRP(TLFrame& fm) const { fm[0]=mRP; }
void parseRP(const TLFrame& fm) { mRP=fm[0]; }
void writeUnused(TLFrame& fm) const { fm.fill(0,3,2); } ///< Fill unused bits with 0s
@ -352,7 +386,7 @@ class TLDeliver : public TLMessage {
TLAddress mOA; ///< origination address, GSM 03.40 9.3.2.7
unsigned mPID; ///< TL-PID, GSM 03.40 9.2.3.9
// Hardcode DCS.
// DCS is taken from mUD.
TLTimestamp mSCTS; ///< service center timestamp, GSM 03.40 9.2.3.11
TLUserData mUD; ///< user data
@ -774,6 +808,21 @@ std::ostream& operator<<(std::ostream& os, CPMessage::MessageType MTI);
*/
CPMessage * parseSMS( const GSM::L3Frame& frame );
/**
Parse msgtext from a hex string to RPData struct.
@param hexstring RPData encoded into hex-string.
@return Pointer to parsed RPData or NULL on error.
*/
RPData *hex2rpdata(const char *hexstring);
/**
Parse a TPDU.
Currently only SMS-SUBMIT is supported.
@param TPDU The TPDU.
@return Pointer to parsed TLMessage or NULL on error.
*/
TLMessage *parseTPDU(const TLFrame& TPDU);
/** A factory method for SMS L3 (CM) messages. */
CPMessage * CPFactory( CPMessage::MessageType MTI );

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

@ -15,6 +15,13 @@ Log.Level.smcommands.cpp DEBUG
# Logging file. Logs to stdout if this is not defined.
#Log.FileName smqueue.log
# LOG(ALARM) is printed and also sent as udp to this address.
Log.Alarms.TargetIP 192.168.10.200
$optional Log.Alarms.TargetIP
Log.Alarms.TargetPort 10101
# Number of alarms saved in internal queue
Log.Alarms.Max 10
savefile savedqueue.txt
@ -37,10 +44,26 @@ $optional Debug.print_as_we_validate
#SIP.global_relay 81.201.82.50:5060
SIP.global_relay
#
# SMS parameters
#
# ISDN address of source SMSC when we fake out a source SMSC.
SMS.FakeSrcSMSC 0000
# The SMS HTTP gateway.
# Comment out if you don't have one or if you want to use smqueue.
#SMS.HTTP.Gateway api.clickatell.com
# IF SMS.HTTP.Gateway IS DEFINED, SMS.HTTP.AccessString MUST ALSO BE DEFINED.
#SMS.HTTP.AccessString sendmsg?user=xxxx&password=xxxx&api_id=xxxx
# return SMS messages
BounceMessage.IMSILookupFailed Cannot determine return address; bouncing message. Text your phone number to 101 to register and try again.
BounceMessage.NotRegistered Phone not registered here.
Bounce.Message.IMSILookupFailed Cannot determine return address; bouncing message. Text your phone number to 101 to register and try again.
Bounce.Message.NotRegistered Phone not registered here. Message
# Bounce from address
Bounce.Code 411
@ -62,7 +85,7 @@ SC.Register.Msg.TakenA The phone number
SC.Register.Msg.TakenB is already in use. Try another, then call that one to talk to whoever took yours.
# Message for internal errors, sent as <A> << phonenumber << <B> << imsi
SC.Register.Msg.ErrorA Error in assigning
SC.Register.Msg.ErrorB to IMSI
SC.Register.Msg.ErrorB to
# Bounds for valid number length
SC.Register.Digits.Max 10
SC.Register.Digits.Min 7
@ -87,4 +110,6 @@ SC.WhiplashQuit.Code 314158
SC.WhiplashQuit.Password Snidely
SC.WhiplashQuit.SaveFile testsave.txt
# SMS-Center code. OpenBTS will send messages from mobile phones
# to this short code and will work as usual SMS-Center.
SC.SMSC.Code smsc

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