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.
This commit is contained in:
Thomas Tsou 2011-06-21 19:02:10 -07:00
commit 3ea07de75c
22 changed files with 1385 additions and 549 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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)
bool sendSIP(const L3MobileIdentity &mobileID, const char* address, const char* body)
{
// FIXME -- We should just do full "percent encoding" here.
while (*src != '\0') {
if (*src == ' ') *dest++ = '+';
else *dest++ = *src;
src++;
}
*dest = '\0';
}
/** Send SMS via and HTTP interface. */
bool sendHTTP(const char* destination, const char* message)
{
char convMessage[strlen(message)+2];
convertText(convMessage,message);
char command[2048];
// FIXME -- Check specs for a good timeout value here.
sprintf(command,"wget -T 5 -C -q -O - \"http://%s/http/%s&to=%s&text=%s\" >& /dev/null",
gConfig.getStr("SMS.HTTP.Gateway"),
gConfig.getStr("SMS.HTTP.AccessString"),
destination, convMessage);
LOG(DEBUG) << "MOSMS: sendHTTP sending with " << command;
// HTTP "GET" method with wget.
// FIXME -- Look at the output of wget to check success.
FILE* wget = popen(command,"r");
if (!wget) {
LOG(ALARM) << "cannot open wget with " << command;
return false;
}
pclose(wget);
return true;
}
/** Send e-mail with local sendmail program. */
bool sendEMail(const char* address, const char* body, const char* subject=NULL)
{
// We're not checking for overflow because the TPDU can't be more than a few hundred bytes.
// Build the command line.
// FIXME -- Use sendmail to have better header control.
char command[1024];
if (subject) sprintf(command,"mail -s \"%s\" %s",subject,address);
else sprintf(command,"mail %s",address);
LOG(INFO) << "sending SMTP: \"" << body << "\" via \"" << command << "\"";
// Send the mail.
FILE* mail = popen(command,"w");
if (!mail) {
LOG(ALARM) << "cannot send mail with \"" << command << "\"";
return false;
}
// FIXME -- We should be sure body is 7-bit clean.
fprintf(mail,"%s",body);
if (pclose(mail) == -1) return false;
return true;
}
//@}
/**
Send a TDPU to a numeric address.
@param mobileID The sender's IMSI.
@param submit A TL-SUBMIT PDU.
@return true on success
*/
bool sendToNumericAddress(const L3MobileIdentity &mobileID, const TLSubmit& submit)
{
LOG(INFO) << "from " << mobileID << " message: " << submit;
const TLAddress& address = submit.DA();
const char* body = submit.UD().data();
// If there is an external HTTP gateway, use it.
if (gConfig.defines("SMS.HTTP.Gateway")) return sendHTTP(address.digits(), body);
// Otherwise, we are looking for a SIP interface to smsd.
// Steps:
// 1 -- Create a transaction record.
// 2 -- Send it to the server.
@ -186,20 +96,19 @@ bool sendToNumericAddress(const L3MobileIdentity &mobileID, const TLSubmit& subm
// 4 -- Return true for OK or ACCEPTED, false otherwise.
// Form the TLAddress into a CalledPartyNumber for the transaction.
L3CalledPartyBCDNumber calledParty(address.digits());
L3CalledPartyBCDNumber calledParty(address);
// Step 1 -- Create a transaction record.
TransactionEntry transaction(
mobileID,
L3CMServiceType::ShortMessage,
0, // doesn't matter
calledParty);
TransactionEntry transaction(mobileID,
L3CMServiceType::ShortMessage,
0, // doesn't matter
calledParty);
transaction.SIP().User(mobileID.digits());
transaction.Q931State(TransactionEntry::SMSSubmitting);
gTransactionTable.add(transaction);
LOG(DEBUG) << "MOSMS: transaction: " << transaction;
// Step 2 -- Send the message to the server.
transaction.SIP().MOSMSSendMESSAGE(address.digits(),gConfig.getStr("SIP.IP"),body);
transaction.SIP().MOSMSSendMESSAGE(address, gConfig.getStr("SIP.IP"), body, false);
// Step 3 -- Wait for OK or ACCEPTED.
SIPState state = transaction.SIP().MOSMSWaitForSubmit();
@ -209,67 +118,6 @@ bool sendToNumericAddress(const L3MobileIdentity &mobileID, const TLSubmit& subm
return state==SIP::Cleared;
}
/** Send a TPDU through whatever gateway is available. */
bool submitSMS(const L3MobileIdentity& mobileID, const TLSubmit& submit)
{
LOG(INFO) << "from " << mobileID;
//const TLAddress& address = submit.DA();
const char* body = submit.UD().data();
// Check for direct e-mail address at start of message body.
// FIXME -- This doesn't really follow the spec. See GSM 03.40 3.8.
static const Regexp emailAddress("^[[:graph:]]+@[[:graph:]]+ ");
if (emailAddress.match(body)) {
// FIXME -- Get the sender's E.164 to put in the subject line.
char bodyCopy[strlen(body)+2];
strcpy(bodyCopy,body);
char* SMTPAddress = bodyCopy;
char* term = strchr(bodyCopy,' ');
// If term's NULL, the regexp is broken.
assert(term);
*term = '\0';
char* SMTPPayload = term+1;
LOG(INFO) << "sending SMTP to " << SMTPAddress << ": " << SMTPPayload;
if (SMTPPayload) return sendEMail(SMTPAddress,SMTPPayload,"from OpenBTS gateway");
else return sendEMail(SMTPAddress,"(empty)","from OpenBTS gateway");
}
// Send to smsd or HTTP gateway, depending on what's defined in the conig.
return sendToNumericAddress(mobileID,submit);
}
/**
Process the incoming TPDU.
@param mobileID Sender's IMSI.
@param TPDU The TPDU (duh).
@return true if successful
*/
bool handleTPDU(const L3MobileIdentity& mobileID, const TLFrame& TPDU)
{
LOG(DEBUG) << "SMS: handleTPDU MTI=" << TPDU.MTI();
// Handle just the uplink cases.
switch ((TLMessage::MessageType)TPDU.MTI()) {
case TLMessage::DELIVER_REPORT:
case TLMessage::STATUS_REPORT:
return false;
case TLMessage::SUBMIT: {
TLSubmit submit;
submit.parse(TPDU);
LOG(INFO) << "SMS SMS-SUBMIT " << submit;
return submitSMS(mobileID,submit);
}
default:
return false;
}
}
/**
Process the RPDU.
@param mobileID The sender's IMSI.
@ -281,10 +129,9 @@ bool handleRPDU(const L3MobileIdentity& mobileID, const RLFrame& RPDU)
LOG(DEBUG) << "SMS: handleRPDU MTI=" << RPDU.MTI();
switch ((RPMessage::MessageType)RPDU.MTI()) {
case RPMessage::Data: {
RPData data;
data.parse(RPDU);
LOG(INFO) << "SMS RP-DATA " << data;
return handleTPDU(mobileID,data.TPDU());
ostringstream body;
RPDU.hex(body);
return sendSIP(mobileID, gConfig.getStr("SIP.SMSC"), body.str().data());
}
case RPMessage::Ack:
case RPMessage::SMMA:
@ -427,13 +274,7 @@ bool Control::deliverSMSToMS(const char *callingPartyDigits, const char* message
// This function is used to deliver messages that originate INSIDE the BTS.
// For the normal SMS delivery, see MTSMSController.
// Start ABM in SAP3.
LCH->send(ESTABLISH,3);
// Wait for SAP3 ABM to connect.
// The next read on SAP3 should the ESTABLISH primitive.
// This won't return NULL. It will throw an exception if it fails.
delete getFrameSMS(LCH,ESTABLISH);
#if 0
// HACK
// Check for "Easter Eggs"
// TL-PID
@ -449,6 +290,44 @@ bool Control::deliverSMSToMS(const char *callingPartyDigits, const char* message
RPData(reference,
RPAddress(gConfig.getStr("SMS.FakeSrcSMSC")),
TLDeliver(callingPartyDigits,message,TLPID)));
#else
unsigned reference = random() % 255;
BitVector RPDUbits(strlen(message)*4);
if (!RPDUbits.unhex(message)) {
LOG(WARN) << "Hex string parsing failed (in incoming SIP MESSAGE)";
throw UnexpectedMessage();
}
RPData rp_data;
try {
RLFrame RPDU(RPDUbits);
LOG(DEBUG) << "SMS RPDU: " << RPDU;
rp_data.parse(RPDU);
LOG(DEBUG) << "SMS RP-DATA " << rp_data;
}
catch (SMSReadError) {
LOG(WARN) << "SMS parsing failed (above L3)";
// Cause 95, "semantically incorrect message".
LCH->send(CPData(1,TI,RPError(95,reference)),3);
throw UnexpectedMessage();
}
catch (L3ReadError) {
LOG(WARN) << "SMS parsing failed (in L3)";
// TODO:: send error back to the phone
throw UnsupportedMessage();
}
CPData deliver(0,TI,rp_data);
#endif
// Start ABM in SAP3.
LCH->send(ESTABLISH,3);
// Wait for SAP3 ABM to connect.
// The next read on SAP3 should the ESTABLISH primitive.
// This won't return NULL. It will throw an exception if it fails.
delete getFrameSMS(LCH,ESTABLISH);
LOG(INFO) << "sending " << deliver;
LCH->send(deliver,3);
@ -543,7 +422,9 @@ void Control::MTSMSController(TransactionEntry& transaction,
// but instead to an RRLP transaction over the already allocated LogicalChannel.
const char* m = transaction.message(); // NOTE - not very nice, my way of checking.
if ((strlen(m) > 4) && (std::string("RRLP") == std::string(m, m+4))) {
BitVector rrlp_position_request = hex2bitvector(transaction.message() + 4);
const char *transaction_hex = transaction.message() + 4;
BitVector rrlp_position_request(strlen(transaction_hex)*4);
rrlp_position_request.unhex(transaction_hex);
LOG(INFO) << "MTSMS: Sending RRLP";
// TODO - how to get mobID here?
L3MobileIdentity mobID = L3MobileIdentity("000000000000000");
@ -579,15 +460,29 @@ void Control::MTSMSController(TransactionEntry& transaction,
transaction.Q931State(TransactionEntry::SMSDelivering);
gTransactionTable.update(transaction);
bool success = deliverSMSToMS(transaction.calling().digits(),transaction.message(),random()%7,LCH);
try {
bool success = deliverSMSToMS(transaction.calling().digits(),transaction.message(),random()%7,LCH);
// Close the Dm channel.
LOG(INFO) << "MTSMS: closing";
LCH->send(L3ChannelRelease());
// Close the Dm channel.
LOG(INFO) << "MTSMS: closing";
LCH->send(L3ChannelRelease());
// Ack in SIP domain and update transaction state.
if (success) {
// Ack in SIP domain and update transaction state.
if (success) {
engine.MTSMSSendOK();
clearTransactionHistory(transaction);
}
}
catch (UnexpectedMessage) {
// TODO -- MUST SEND PERMANENT ERROR HERE!!!!!!!!!
engine.MTSMSSendOK();
LCH->send(L3ChannelRelease());
clearTransactionHistory(transaction);
}
catch (UnsupportedMessage) {
// TODO -- MUST SEND PERMANENT ERROR HERE!!!!!!!!!
engine.MTSMSSendOK();
LCH->send(L3ChannelRelease());
clearTransactionHistory(transaction);
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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)
{
}
/** Initialze from a simple C string. */
TLUserData(const char* text, bool wUDHI=false)
/** Initialize from a raw encoded data. */
TLUserData(unsigned wDCS, const BitVector wRawData, unsigned wLength,
bool wUDHI=false)
:TLElement(),
mDCS(wDCS),
mUDHI(wUDHI),
mLength(wLength)
{
mRawData.clone(wRawData);
}
/** Initialize from a simple C string. */
TLUserData(const char* text, GSMAlphabet alphabet=ALPHABET_7BIT, bool wUDHI=false)
:TLElement(),
mDCS(0),
mUDHI(wUDHI)
{ strncpy(mData,text,sizeof(mData)-1); mData[sizeof(mData)-1]='\0'; }
mUDHI(wUDHI),
mLength(0)
{
switch(alphabet) {
case ALPHABET_7BIT:
encode7bit(text);
break;
case ALPHABET_8BIT:
case ALPHABET_UCS2:
default:
LOG(WARN) << "Unsupported alphabet: " << alphabet;
break;
}
}
void DCS(unsigned wDCS) { mDCS=wDCS; }
unsigned DCS() const { return mDCS; }
void UDHI(unsigned wUDHI) { mUDHI=wUDHI; }
const char* data() const { return mData; }
unsigned UDHI() const { return mUDHI; }
/** Encode text into this element, using 7-bit alphabet */
void encode7bit(const char *text);
/** Decode text from this element, using 7-bit alphabet */
std::string decode() const;
/** This length includes a byte for the length field. */
size_t length() const;
@ -225,7 +259,7 @@ class TLMessage {
bool mSRR; ///< status report request
bool mSRI; ///< status report indication
bool mSRQ; ///< status report qualifier
bool mUDHI; ///< user-data header-indicator
// bool mUDHI; ///< user-data header-indicator. Stored in TLUserData.
bool mRP; ///< reply path
//@}
@ -236,16 +270,16 @@ class TLMessage {
/** GSM 03.40 9.2.3.1 */
enum MessageType {
DELIVER = 0x0,
DELIVER_REPORT = 0x0,
STATUS_REPORT = 0x2,
COMMAND = 0x02,
SUBMIT = 0x1,
SUBMIT_REPORT = 0x1
DELIVER = 0x0, // SC -> MS
DELIVER_REPORT = 0x0, // MS -> SC
STATUS_REPORT = 0x2, // SC -> MS
COMMAND = 0x02, // MS -> SC
SUBMIT = 0x1, // MS -> SC
SUBMIT_REPORT = 0x1 // SC -> MS
};
TLMessage()
:mMMS(false),mSRI(false),mUDHI(false),mRP(true)
:mMMS(false),mSRI(false),mRP(true)
{}
virtual ~TLMessage(){}
@ -286,8 +320,8 @@ class TLMessage {
void parseSRI(const TLFrame& fm) { mSRI=fm[2]; }
void writeSRQ(TLFrame& fm) const { fm[2]=mSRQ; }
void parseSRQ(const TLFrame& fm) { mSRQ=fm[2]; }
void writeUDHI(TLFrame& fm) const { fm[1]=mUDHI; }
void parseUDHI(const TLFrame& fm) { mUDHI=fm[1]; }
void writeUDHI(TLFrame& fm, bool udhi) const { fm[1]=udhi; }
bool parseUDHI(const TLFrame& fm) { return fm[1]; }
void writeRP(TLFrame& fm) const { fm[0]=mRP; }
void parseRP(const TLFrame& fm) { mRP=fm[0]; }
void writeUnused(TLFrame& fm) const { fm.fill(0,3,2); } ///< Fill unused bits with 0s
@ -352,7 +386,7 @@ class TLDeliver : public TLMessage {
TLAddress mOA; ///< origination address, GSM 03.40 9.3.2.7
unsigned mPID; ///< TL-PID, GSM 03.40 9.2.3.9
// Hardcode DCS.
// DCS is taken from mUD.
TLTimestamp mSCTS; ///< service center timestamp, GSM 03.40 9.2.3.11
TLUserData mUD; ///< user data
@ -774,6 +808,21 @@ std::ostream& operator<<(std::ostream& os, CPMessage::MessageType MTI);
*/
CPMessage * parseSMS( const GSM::L3Frame& frame );
/**
Parse msgtext from a hex string to RPData struct.
@param hexstring RPData encoded into hex-string.
@return Pointer to parsed RPData or NULL on error.
*/
RPData *hex2rpdata(const char *hexstring);
/**
Parse a TPDU.
Currently only SMS-SUBMIT is supported.
@param TPDU The TPDU.
@return Pointer to parsed TLMessage or NULL on error.
*/
TLMessage *parseTPDU(const TLFrame& TPDU);
/** A factory method for SMS L3 (CM) messages. */
CPMessage * CPFactory( CPMessage::MessageType MTI );

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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;
@ -1102,19 +1149,12 @@ enum sm_state
SMq::bounce_message(short_msg_pending *sent_msg, const char *errstr)
{
ostringstream errmsg;
osip_body_t *bod1;
char *username;
char *thetext;
std::string thetext;
int status;
username = sent_msg->parsed->to->url->username;
if (sent_msg->parsed->bodies.nb_elt > 0) {
// bod1->body is the original SMS text
bod1 = (osip_body_t *)sent_msg->parsed->bodies.node->element;
thetext = bod1->body;
} else {
thetext = "";
}
thetext = sent_msg->get_text();
LOG(NOTICE) << "Bouncing " << sent_msg->qtag << " from "
<< sent_msg->parsed->from->url->username // his phonenum
@ -1126,19 +1166,19 @@ SMq::bounce_message(short_msg_pending *sent_msg, const char *errstr)
else
errmsg << "can't send: " << thetext;
// Don't bounce a message from 411, makes endless loops.
// Don't bounce a message from us - it makes endless loops.
status = 1;
if (0 != strcmp("411", sent_msg->parsed->from->url->username)) {
if (0 != strcmp(gConfig.getStr("Bounce.Code"), sent_msg->parsed->from->url->username))
{
// But do bounce anything else.
char *bounceto = sent_msg->parsed->from->url->username;
char *bounceto = sent_msg->parsed->from->url->username;
bool bounce_to_imsi = 0 == strncmp("IMSI", bounceto, 4)
|| 0 == strncmp("imsi", bounceto, 4);
status = originate_sm("411", // from "411"? FIXME?
|| 0 == strncmp("imsi", bounceto, 4);
status = originate_sm(gConfig.getStr("Bounce.Code"), // Read from a config
bounceto, // to his phonenum or IMSI
errmsg.str().c_str(), // error msg
bounce_to_imsi?
REQUEST_DESTINATION_SIPURL: // dest is IMSI
REQUEST_DESTINATION_IMSI); // dest is phonenum
bounce_to_imsi? REQUEST_DESTINATION_SIPURL: // dest is IMSI
REQUEST_DESTINATION_IMSI); // dest is phonenum
}
if (status == 0) {
return DELETE_ME_STATE;
@ -1189,6 +1229,9 @@ SMq::register_handset (short_msg_p_list::iterator qmsg)
smpl = originate_half_sm("REGISTER");
response = &*smpl->begin(); // Here's our short_msg_pending!
// SIP REGISTER should not be repacked before delivery
response->need_repack = false;
imsi = qmsg->parsed->from->url->username;
// The To: line is the long-term name being registered.
@ -1246,109 +1289,111 @@ SMq::register_handset (short_msg_p_list::iterator qmsg)
return ASKED_TO_REGISTER_HANDSET;
}
/*
* Initial handling of SMS messages found in the queue
*/
enum sm_state
SMq::handle_sms_message (short_msg_p_list::iterator qmsg)
bool SMq::handle_short_code(const short_code_map_t &short_code_map,
short_msg_p_list::iterator qmsg,
sm_state &next_state)
{
osip_body_t *bod1;
char *bods;
std::string bods;
short_func_t shortfn;
short_code_map_t::iterator shortit;
short_code_map_t::const_iterator shortit;
enum short_code_action sca;
short_code_params params;
int status;
string short_code;
if (0 != strcmp(qmsg->parsed->sip_method, "MESSAGE")) {
LOG(WARN) << "Invalid incoming SIP message, method is "
<< qmsg->parsed->sip_method;
return NO_STATE;
short_code = qmsg->parsed->req_uri->username;
shortit = short_code_map.find (short_code);
if (shortit == short_code_map.end()) {
return false;
}
shortit = short_code_map.find (string(qmsg->parsed->req_uri->username));
if (shortit != short_code_map.end()) {
/* Messages to certain addresses are special commands */
shortfn = shortit->second;
bods = (char *)"";
if (qmsg->parsed->bodies.nb_elt == 1) {
bod1 = (osip_body_t *)qmsg->parsed->bodies.node->element;
bods = bod1->body;
/* Messages to certain addresses are special commands */
shortfn = shortit->second;
bods = qmsg->get_text();
// Set up arguments and access pointers, then call
// the short-code function to process it.
params.scp_retries = qmsg->retries;
params.scp_smq = this;
params.scp_qmsg_it = qmsg;
LOG(INFO) << "Short-code SMS "
<< qmsg->parsed->req_uri->username
<< " with text \"" << bods << "\"";
sca = (*shortfn) (qmsg->parsed->from->url->username, // imsi
bods.data(), // msg text
&params);
// 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
&params);
<< "(" << bods << "): " << params.scp_reply;
next_state = NO_STATE;
return true;
// The short-code function asks us to do something when
// it's done. Do it.
switch (sca) {
case SCA_REPLY:
LOG(INFO) << "Short-code replies: "
<< params.scp_reply;
status = originate_sm(
qmsg->parsed->req_uri->username, // from shortcode
qmsg->parsed->from->url->username,// to his IMSI
params.scp_reply, REQUEST_DESTINATION_SIPURL);
if (!status) {
return DELETE_ME_STATE; // Done!
}
LOG(NOTICE) << "Reply failed " << status << "!";
// NO BREAK
default:
case SCA_INTERNAL_ERROR:
LOG(ERROR) << "Error in short-code function "
<< qmsg->parsed->req_uri->username
<< "(" << bods << "): " << params.scp_reply;
return NO_STATE;
case SCA_EXEC_SMQUEUE:
reexec_smqueue = true;
stop_main_loop = true;
return DELETE_ME_STATE;
case SCA_EXEC_SMQUEUE:
reexec_smqueue = true;
stop_main_loop = true;
next_state = DELETE_ME_STATE;
return true;
case SCA_QUIT_SMQUEUE:
stop_main_loop = true;
return DELETE_ME_STATE;
case SCA_QUIT_SMQUEUE:
stop_main_loop = true;
next_state = DELETE_ME_STATE;
return true;
case SCA_DONE:
return DELETE_ME_STATE;
case SCA_DONE:
next_state = DELETE_ME_STATE;
return true;
case SCA_RETRY_AFTER_DELAY:
// FIXME, timeout is implicit in set_state table,
// rather than taken from params.scp_delay.
qmsg->retries++;
return REQUEST_FROM_ADDRESS_LOOKUP;
case SCA_RETRY_AFTER_DELAY:
// FIXME, timeout is implicit in set_state table,
// rather than taken from params.scp_delay.
qmsg->retries++;
next_state = REQUEST_FROM_ADDRESS_LOOKUP;
return true;
case SCA_AWAIT_REGISTER:
// We just linked the phone# to the IMSI, but we
// have to wait til the HLR updates, before
// we can link the IMSI to the originating IP address
// and port number of its cell.
return AWAITING_REGISTER_HANDSET;
case SCA_AWAIT_REGISTER:
// We just linked the phone# to the IMSI, but we
// have to wait til the HLR updates, before
// we can link the IMSI to the originating IP address
// and port number of its cell.
next_state = AWAITING_REGISTER_HANDSET;
return true;
case SCA_REGISTER:
return register_handset(qmsg);
case SCA_REGISTER:
next_state = register_handset(qmsg);
return true;
case SCA_TREAT_AS_ORDINARY:
break; // fall through into non-special case.
}
case SCA_TREAT_AS_ORDINARY:
break; // fall through into non-special case.
case SCA_RESTART_PROCESSING:
next_state = INITIAL_STATE;
return true;
}
// For non-special messages, look up who they're from.
return lookup_from_address (&*qmsg);
return false;
}
@ -1378,18 +1423,15 @@ SMq::lookup_from_address (short_msg_pending *qmsg)
// into the message text, followed by a space... GSM 03.40 sec 3.8
ostringstream newtext;
osip_body_t *bod1;
char *bods;
bods = (char *)"";
if (qmsg->parsed->bodies.nb_elt == 1) {
if (qmsg->parsed->bodies.nb_elt == 1) {
bod1 = (osip_body_t *)qmsg->parsed->bodies.node->element;
bods = bod1->body;
} else {
return NO_STATE; // Punt on msg w/no text
}
newtext << qmsg->parsed->from->url->username << "@"
<< host << " " << bods;
osip_free(bods);
<< host << " " << qmsg->get_text();
osip_free(bod1->body);
bod1->body = osip_strdup(newtext.str().c_str());
bod1->length = strlen(bod1->body);
@ -1461,7 +1503,7 @@ SMq::lookup_from_address (short_msg_pending *qmsg)
<< "> to phonenum failed.";
LOG(DEBUG) << qmsg->text;
return bounce_message (qmsg,
gConfig.getStr("BounceMessage.IMSILookupFailed")
gConfig.getStr("Bounce.Message.IMSILookupFailed")
);
// return NO_STATE; // Put it into limbo for debug.
}
@ -1540,7 +1582,7 @@ SMq::lookup_uri_imsi (short_msg_pending *qmsg)
|| !my_hlr.useGateway(username)) {
// There's no global relay -- or the HLR says not to
// use the global relay for it -- so send a bounce.
return bounce_message (qmsg, gConfig.getStr("BounceMessage.NotRegistered"));
return bounce_message (qmsg, gConfig.getStr("Bounce.Message.NotRegistered"));
} else {
// Send the message to our global relay.
// We leave the username as a phone number, and
@ -1782,7 +1824,7 @@ SMq::respond_sip_ack(int errcode, short_msg_pending *smp,
break;
case 413: phrase="Message Body Size Error"; break;
case 415: phrase="Unsupported Content Type";
osip_message_set_accept(response.parsed, "text/plain");
osip_message_set_accept(response.parsed, "text/plain, application/vnd.3gpp.sms");
break;
case 416: phrase="Unsupported URI scheme (not SIP)"; break;
case 480: phrase="Recipient Temporarily Unavailable"; break;
@ -1897,6 +1939,7 @@ SMq::main_loop()
smpl = new short_msg_p_list (1);
smp = &*smpl->begin(); // Here's our short_msg_pending!
smp->initialize (len, buffer, false);
smp->ms_to_sc = true;
if (my_network.recvaddrlen <= sizeof (smp->srcaddr)) {
smp->srcaddrlen = my_network.recvaddrlen;
@ -2017,7 +2060,9 @@ SMq::save_queue_to_file(std::string qfile)
ofile << "=== " << (int) x->state << " "
<< x->next_action_time << " "
<< my_network.string_addr((struct sockaddr *)x->srcaddr, x->srcaddrlen, true) << " "
<< strlen(x->text) << endl << x->text << endl << endl;
<< strlen(x->text)
<< x->ms_to_sc << x->need_repack << endl
<< x->text << endl << endl;
howmany++;
}
@ -2040,6 +2085,8 @@ SMq::read_queue_from_file(std::string qfile)
ifstream ifile;
std::string equals;
unsigned astate, atime, alength;
unsigned ms_to_sc, need_repack;
std::string short_code;
std::string netaddrstr;
sm_state mystate;
time_t mytime;
@ -2062,6 +2109,8 @@ SMq::read_queue_from_file(std::string qfile)
}
ifile >> netaddrstr;
ifile >> alength;
ifile >> ms_to_sc;
ifile >> need_repack;
while (ifile.peek() == '\n')
ignoreme = ifile.get();
msgtext = new char[alength+2];
@ -2079,6 +2128,10 @@ SMq::read_queue_from_file(std::string qfile)
// We use the just-allocated msgtext; it gets freed after
// delivery of message.
// Restore saved state
smp->ms_to_sc = ms_to_sc;
smp->need_repack = need_repack;
smp->srcaddrlen = 0;
if (!my_network.parse_addr(netaddrstr.c_str(), smp->srcaddr, sizeof(smp->srcaddr), &smp->srcaddrlen))
abfuckingort();
@ -2091,7 +2144,8 @@ SMq::read_queue_from_file(std::string qfile)
<< smp->parsed->from->url->username
<< " for "
<< smp->parsed->req_uri->username
<< ".";
<< " direction=" << (smp->ms_to_sc?"MS->SC":"SC->MS")
<< " need_repack=" << (smp->need_repack?"true":"false");
} else {
LOG(WARN) << "Read bad SMS "
<< smp->parsed->status_code
@ -2113,7 +2167,7 @@ SMq::read_queue_from_file(std::string qfile)
return true;
}
#if 0
/*
* Read in a message from a file. Return malloc'd char block of the whole
* thing.
@ -2169,7 +2223,7 @@ read_short_msg_pending_from_file(char *fname)
smp = new short_msg_pending (length, sip_text, true);
return smp;
}
#endif
/* Really simple first try */
@ -2183,7 +2237,8 @@ main(int argc, char **argv)
// short_msg_pending *smp;
std::string savefile;
init_smcommands(&short_code_map); // Set up short-code commands users can type
// Set up short-code commands users can type
init_smcommands(&short_code_map);
// This scope lets us delete the smq (and the network sockets)
// before we re-exec ourself.

View File

@ -23,7 +23,7 @@
#define SM_QUEUE_H
#include <time.h>
#include <osipparser2/osip_message.h> /* from osipparser2 */
//#include <osipparser2/osip_message.h> /* from osipparser2 */
#include <stdlib.h> /* for osipparser2 */
#include <sys/time.h> /* for osip_init */
#include <osip2/osip.h> /* for osip_init */
@ -35,10 +35,18 @@
#include "smnet.h" // My network support
#include "HLR.h" // My home location register
// That's awful OSIP has a CR define.
// It clashes with our innocent L2Address::CR().
// Don't create 2-letter #defines, ever!
#undef CR
#include "SMSMessages.h"
using namespace SMS;
namespace SMqueue {
/* Maximum text size of an SMS message. */
#define SMS_MESSAGE_MAX_LENGTH 140
#define SMS_MESSAGE_MAX_LENGTH 160
/* std::abort isn't always there. Neither is the C library version.
Idiots? You tell me. */
@ -60,33 +68,33 @@ char *new_strdup(const char *orig);
*/
enum sm_state { // timeout, next-state-if-timeout
NO_STATE = 0,
REQUEST_FROM_ADDRESS_LOOKUP = 1,
ASKED_FOR_FROM_ADDRESS_LOOKUP = 2,
NO_STATE,
INITIAL_STATE,
REQUEST_FROM_ADDRESS_LOOKUP,
ASKED_FOR_FROM_ADDRESS_LOOKUP,
AWAITING_TRY_DESTINATION_IMSI = 3,
REQUEST_DESTINATION_IMSI = 4,
ASKED_FOR_DESTINATION_IMSI = 5,
AWAITING_TRY_DESTINATION_IMSI,
REQUEST_DESTINATION_IMSI,
ASKED_FOR_DESTINATION_IMSI,
AWAITING_TRY_DESTINATION_SIPURL = 6,
REQUEST_DESTINATION_SIPURL = 7,
ASKED_FOR_DESTINATION_SIPURL = 8,
AWAITING_TRY_DESTINATION_SIPURL,
REQUEST_DESTINATION_SIPURL,
ASKED_FOR_DESTINATION_SIPURL,
AWAITING_TRY_MSG_DELIVERY = 9,
REQUEST_MSG_DELIVERY = 10,
ASKED_FOR_MSG_DELIVERY = 11,
AWAITING_TRY_MSG_DELIVERY,
REQUEST_MSG_DELIVERY,
ASKED_FOR_MSG_DELIVERY,
DELETE_ME_STATE = 12,
DELETE_ME_STATE,
AWAITING_REGISTER_HANDSET,
REGISTER_HANDSET,
ASKED_TO_REGISTER_HANDSET,
AWAITING_REGISTER_HANDSET = 13,
REGISTER_HANDSET = 14,
ASKED_TO_REGISTER_HANDSET = 15,
STATE_MAX_PLUS_ONE, /* Keep this one last! */
};
#define STATE_MAX (STATE_MAX_PLUS_ONE - 1)
#define INITIAL_STATE REQUEST_FROM_ADDRESS_LOOKUP
// How to print a state
extern std::string sm_state_strings[STATE_MAX_PLUS_ONE];
@ -102,6 +110,13 @@ extern struct osip *osipptr;
term storage in the queue. */
class short_msg {
public:
enum ContentType {
UNSUPPORTED_CONTENT,
TEXT_PLAIN,
VND_3GPP_SMS
};
/* First just the text string. A SIP message including body. */
unsigned short text_length;
char *text /* [text_length] */; // C++ doesn't make it simple
@ -116,13 +131,32 @@ class short_msg {
// to;
// time_t date;
// expiration;
ContentType content_type; // Content-Type of the message
RPData *rp_data; // Parsed RP-DATA of an SMS. It's read from MESSAGE body if
// it has application/vnd.3gpp.sms MIME-type. Note, that
// it's parsed on request and may be NULL at any point.
TLMessage *tl_message; // Parsed RPDU of an SMS. It's read from rp_data. Note,
// that it's parsed on request and may be NULL at
// any point.
bool ms_to_sc; // Direction of the message. True is this is MS->SC SMS, false
// otherwise.
bool need_repack; // Message should be packed into TPDU for delivery. E.g.
// SIP MESSAGE sent to MS should be packed, while SIP
// REGISTER should not.
short_msg () :
text_length (0),
text (NULL),
parsed_is_valid (false),
parsed_is_better (false),
parsed (NULL) {
parsed (NULL),
content_type(UNSUPPORTED_CONTENT),
rp_data(NULL),
tl_message(NULL),
ms_to_sc(false),
need_repack(true)
{
}
// Make a short message, perhaps taking responsibility for deleting
// the "new"-allocated memory passed in.
@ -131,7 +165,12 @@ class short_msg {
text (cstr),
parsed_is_valid (false),
parsed_is_better (false),
parsed (NULL)
parsed (NULL),
content_type(UNSUPPORTED_CONTENT),
rp_data(NULL),
tl_message(NULL),
ms_to_sc(false),
need_repack(true)
{
if (!use_my_memory) {
text = new char [text_length+1];
@ -151,7 +190,12 @@ class short_msg {
text (0),
parsed_is_valid (false),
parsed_is_better (false),
parsed (NULL)
parsed (NULL),
content_type(UNSUPPORTED_CONTENT),
rp_data(NULL),
tl_message(NULL),
ms_to_sc(false),
need_repack(true)
{
if (text_length) {
text = new char [text_length+1];
@ -159,14 +203,19 @@ class short_msg {
text[text_length] = '\0';
}
};
public:
#if 0
short_msg (std::string str) :
text_length (str.length()),
text (0),
parsed_is_valid (false),
parsed_is_better (false),
parsed (NULL)
parsed (NULL),
content_type(UNSUPPORTED_CONTENT),
rp_data(NULL),
tl_message(NULL),
ms_to_sc(false),
need_repack(false)
{
text = new char [text_length+1];
strncpy(text, str.data(), text_length);
@ -174,35 +223,26 @@ class short_msg {
};
#endif
/* Override operator= to avoid pointer-sharing problems */
/* Disable operator= to avoid pointer-sharing problems */
private:
short_msg & operator= (const short_msg &rvalue);
public:
/*
{
this->text_length = rvalue.text_length;
this->text = new char [rvalue.text_length+1];
strncpy(this->text, rvalue.text, rvalue.text_length);
this->text[rvalue.text_length] = '\0';
this->parsed_is_valid = rvalue.parsed_is_valid;
this->parsed_is_better = rvalue.parsed_is_better;
osip_message_clone (rvalue.parsed, &this->parsed);
return *this;
} */
/* Destructor */
virtual ~short_msg () { unparse(); delete [] text; };
virtual ~short_msg ()
{
if (parsed)
osip_message_free(parsed);
delete [] text;
delete rp_data;
delete tl_message;
};
// Pseudo-constructor due to inability to run constructors on
// members of lists.
// Initialize a newly-default-constructed short message,
// perhaps taking responsibility for deleting
// the "new"-allocated memory passed in.
void
initialize ()
{ // Nothing to do: default constructor + default initializer
}
void
initialize (int len, char * const cstr, bool use_my_memory)
{
@ -233,6 +273,7 @@ class short_msg {
unparse(); // Free any previous one.
// Parse SIP message
i = osip_message_init(&sip);
if (i != 0) abfuckingort(); /* throw out-of-memory */
i = osip_message_parse(sip, text, text_length);
@ -240,11 +281,45 @@ class short_msg {
parsed = sip;
parsed_is_valid = true;
parsed_is_better = false;
// Now parse SMS if needed
if (parsed->content_type == NULL)
{
// Most likely this is SIP response.
content_type = UNSUPPORTED_CONTENT;
} else if ( strcmp(parsed->content_type->type, "text") == 0
&& strcmp(parsed->content_type->subtype, "plain") == 0)
{
// If Content-Type is text/plain, then no decoding is needed.
content_type = TEXT_PLAIN;
} else if ( strcmp(parsed->content_type->type, "application") == 0
&& strcmp(parsed->content_type->subtype, "vnd.3gpp.sms") == 0)
{
// This is an encoded SMS' TPDU.
content_type = VND_3GPP_SMS;
// Decode it RP-DATA
osip_body_t *bod1 = (osip_body_t *)parsed->bodies.node->element;
const char *bods = bod1->body;
rp_data = hex2rpdata(bods);
if (rp_data == NULL) {
LOG(INFO) << "RP-DATA unpacking failed";
return false;
}
// Decode RPDU
tl_message = parseTPDU(rp_data->TPDU());
if (rp_data == NULL) {
LOG(INFO) << "RPDU parsing failed";
return false;
}
}
return true;
}
/* Anytime a caller CHANGES the values in the parsed tree of the
message, they MUST call this, to let the cacheing system
message, they MUST call this, to let the caching system
know that the cached copy of the text-string message is no
longer valid. Actually, we have TWO such cached copies! FIXME
so we have to invalidate both of them. */
@ -256,7 +331,7 @@ class short_msg {
/* Make the text string valid, if the parsed copy is better.
(It gets "better" by being modified, and parsed_was_changed()
got called, but we deferred fixing up the text string til now.) */
got called, but we deferred fixing up the text string till now.) */
void
make_text_valid() {
if (parsed_is_better) {
@ -288,6 +363,15 @@ class short_msg {
/* Free up all memory used by parsed version of message. */
void unparse() {
// Free parsed TDPU.
// FIXME -- We should check if it has been changed and update MESSAGE body.
delete rp_data;
rp_data = NULL;
delete tl_message;
tl_message = NULL;
content_type = UNSUPPORTED_CONTENT;
// Now unparse SIP
if (parsed_is_better)
make_text_valid();
if (parsed)
@ -297,6 +381,39 @@ class short_msg {
parsed_is_better = false;
}
std::string get_text() const
{
switch (content_type) {
case TEXT_PLAIN: {
osip_body_t *bod1 = (osip_body_t *)parsed->bodies.node->element;
return bod1->body;
}
break;
case VND_3GPP_SMS: {
const TLSubmit *submit = (TLSubmit*)tl_message;
if (submit == NULL) {
return "";
}
try {
return submit->UD().decode();
}
catch (SMSReadError) {
LOG(WARN) << "SMS parsing failed (above L3)";
// TODO:: Should we send error back to the phone?
return "";
}
}
break;
case UNSUPPORTED_CONTENT:
default:
return "";
}
}
};
// I couldn't figure out how to make these static members of the class...
@ -372,19 +489,6 @@ class short_msg_pending: public short_msg {
linktag (NULL)
{
}
short_msg_pending (const short_msg &sm) :
short_msg (sm),
state (NO_STATE),
next_action_time (0),
retries (0),
// srcaddr({0}), // can't seem to initialize an array?
srcaddrlen(0),
qtag (NULL),
qtaghash (0),
linktag (NULL)
{
}
#endif
//
@ -425,37 +529,10 @@ class short_msg_pending: public short_msg {
this->linktag[len] = '\0';
}
}
public:
/* Override operator= to avoid pointer-sharing problems */
private:
short_msg_pending & operator= (const short_msg_pending &rvalue);
/* FIXME, remove if not needed?
{
this->state = rvalue.state;
this->next_action_time = rvalue.next_action_time;
this->retries = rvalue.retries;
this->qtag = rvalue.qtag;
if (rvalue.srcaddrlen) {
if (rvalue.srcaddrlen > sizeof (srcaddr))
abfuckingort();
memcpy(this->srcaddr, rvalue.srcaddr, rvalue.srcaddrlen);
}
if (rvalue.qtag) {
int len = strlen(rvalue.qtag);
this->qtag = new char[len+1];
strncpy(this->qtag, rvalue.qtag, len);
this->qtag[len] = '\0';
}
this->qtaghash = rvalue.qtaghash;
if (rvalue.linktag) {
int len = strlen(rvalue.linktag);
this->linktag = new char[len+1];
strncpy(this->linktag, rvalue.linktag, len);
this->linktag[len] = '\0';
}
} */
public:
/* Destructor */
@ -477,28 +554,7 @@ class short_msg_pending: public short_msg {
* This 'constructs' the new short_msg_pending in a temporary list,
* and we can then trivially move it into the real message queue,
* removing it from the temporary list in the process.
*
* The wriggling guts under C++ are a little too visible for my taste.
*/
void
initguts ()
{
state = NO_STATE;
next_action_time = 0;
retries = 0;
memset (srcaddr, 0, sizeof(srcaddr));
srcaddrlen = 0;
qtag = NULL;
qtaghash = 0;
linktag = NULL;
}
void
initialize ()
{
short_msg::initialize ();
// initguts();
}
// Make a pending short message, perhaps taking responsibility for
// deleting the "new"-allocated memory passed in.
void
@ -521,19 +577,6 @@ class short_msg_pending: public short_msg {
linktag (NULL)
{
}
short_msg_pending (const short_msg &sm) :
short_msg (sm),
state (NO_STATE),
next_action_time (0),
retries (0),
// srcaddr({0}), // can't seem to initialize an array?
srcaddrlen(0),
qtag (NULL),
qtaghash (0),
linktag (NULL)
{
}
#endif
/* Optimize this later so we don't make so many kernel calls. */
@ -577,6 +620,7 @@ class short_msg_pending: public short_msg {
/* Check host and port for validity. */
bool
check_host_port(char *host, char *port);
};
typedef std::list<short_msg_pending> short_msg_p_list;
@ -589,16 +633,20 @@ typedef std::list<short_msg_pending> 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. */

View File

@ -0,0 +1,394 @@
/*
* SMSC.h - SMS Center implementation for OpenBTS.
* Written by Alexander Chemeris, 2010.
*
* Copyright 2010 Free Software Foundation, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* See the COPYING file in the main directory for details.
*/
//#include <stdio.h>
//#include <GSMLogicalChannel.h>
//#include <GSML3MMMessages.h>
#include <GSML3Message.h>
//#include <GSML3CommonElements.h>
//#include "ControlCommon.h"
#include <Regexp.h>
#include <Logger.h>
using namespace std;
using namespace GSM;
//using namespace Control;
//#include "SIPInterface.h"
//#include "SIPUtility.h"
//#include "SIPMessage.h"
//#include "SIPEngine.h"
//using namespace SIP;
//#include "CollectMSInfo.h"
#include "smsc.h"
// FORWARD DECLARATIONS
void set_to_for_smsc(const char *address, short_msg_p_list::iterator &smsg);
/**@name Functions for transmitting through various gateways. */
//@{
/** Substitute spaces with +'s for compatibility with some protocols. */
void convertText(char *dest, const char *src)
{
// FIXME -- We should just do full "percent encoding" here.
while (*src != '\0') {
if (*src == ' ') *dest++ = '+';
else *dest++ = *src;
src++;
}
*dest = '\0';
}
/** Send SMS via and HTTP interface. */
short_code_action sendHTTP(const char* destination, const std::string &message)
{
char convMessage[message.length()+2];
convertText(convMessage,message.data());
char command[2048];
// FIXME -- Check specs for a good timeout value here.
sprintf(command,"wget -T 5 -C -q -O - \"http://%s/http/%s&to=%s&text=%s\" >& /dev/null",
gConfig.getStr("SMS.HTTP.Gateway"),
gConfig.getStr("SMS.HTTP.AccessString"),
destination, convMessage);
LOG(DEBUG) << "MOSMS: sendHTTP sending with " << command;
// HTTP "GET" method with wget.
// FIXME -- Look at the output of wget to check success.
FILE* wget = popen(command,"r");
if (!wget) {
LOG(ALARM) << "cannot open wget with " << command;
return SCA_INTERNAL_ERROR;
}
pclose(wget);
return SCA_DONE;
}
/** Send e-mail with local sendmail program. */
short_code_action sendEMail(const char* address, const char* body, const char* subject=NULL)
{
// We're not checking for overflow because the TPDU can't be more than a few hundred bytes.
// Build the command line.
// FIXME -- Use sendmail to have better header control.
char command[1024];
if (subject) sprintf(command,"mail -s \"%s\" %s",subject,address);
else sprintf(command,"mail %s",address);
LOG(INFO) << "sending SMTP: \"" << body << "\" via \"" << command << "\"";
// Send the mail.
FILE* mail = popen(command,"w");
if (!mail) {
LOG(ALARM) << "cannot send mail with \"" << command << "\"";
return SCA_INTERNAL_ERROR;
}
// FIXME -- We should be sure body is 7-bit clean.
fprintf(mail,"%s",body);
if (pclose(mail) == -1) return SCA_INTERNAL_ERROR;
return SCA_DONE;
}
short_code_action sendSIP_init(const char *imsi, const TLSubmit& submit,
const std::string &body, short_code_params *scp)
{
const char *address = submit.DA().digits();
const TLUserData& tl_ud = submit.UD();
const char *from = scp->scp_qmsg_it->parsed->from->url->username;
LOG(INFO) << "from " << imsi << " to " << address;
if (scp == NULL)
{
LOG(WARN) << "short_code_params is NULL. Error.";
return SCA_INTERNAL_ERROR;
}
// START OF THE SIP PROCESSING
osip_message_t *omsg = scp->scp_qmsg_it->parsed;
// Req.URI
osip_free(omsg->req_uri->username);
omsg->req_uri->username = (char *)osip_malloc (strlen(address)+1);
strcpy(omsg->req_uri->username, address);
// To:
set_to_for_smsc(address, scp->scp_qmsg_it);
// Let them know that parsed part has been changed.
scp->scp_qmsg_it->parsed_was_changed();
if (ISLOGGING(DEBUG)) {
// Call make_text_valid() is needed for debug only.
scp->scp_qmsg_it->make_text_valid();
LOG(DEBUG) << "Updated SMS message: " << scp->scp_qmsg_it->text;
}
return SCA_RESTART_PROCESSING;
}
void create_sms_delivery(const std::string &body,
const TLUserData &UD,
short_msg_p_list::iterator &smsg)
{
RPData *rp_data_new = NULL;
TLDeliver *deliver = NULL;
const char *from = smsg->parsed->from->url->username;
// HACK
// Check for "Easter Eggs"
// TL-PID
// See 03.40 9.2.3.9.
unsigned TLPID=0;
if (strncmp(body.data(),"#!TLPID",7)==0) sscanf(body.data(),"#!TLPID%d",&TLPID);
// Generate RP-DATA with SMS-DELIVER
{
unsigned reference = random() % 255;
deliver = new TLDeliver(from,UD,TLPID);
rp_data_new = new RPData(reference, RPAddress(gConfig.getStr("SMS.FakeSrcSMSC")),
*deliver);
LOG(DEBUG) << "New RPData: " << *rp_data_new;
}
// Replace RP-DATA in the message
delete smsg->rp_data;
delete smsg->tl_message;
smsg->rp_data = rp_data_new;
smsg->tl_message = deliver;
}
void pack_tpdu(short_msg_p_list::iterator &smsg)
{
// Pack RP-DATA to bitstream
RLFrame RPDU_new(smsg->rp_data->bitsNeeded());
smsg->rp_data->write(RPDU_new);
LOG(DEBUG) << "New RLFrame: " << RPDU_new;
// START OF THE SIP PROCESSING
osip_message_t *omsg = smsg->parsed;
// Message body
osip_body_t *bod1 = (osip_body_t *)omsg->bodies.node->element;
osip_free(bod1->body);
ostringstream body_stream;
RPDU_new.hex(body_stream);
bod1->length = body_stream.str().length();
bod1->body = (char *)osip_malloc (bod1->length+1);
strcpy(bod1->body, body_stream.str().data());
// Let them know that parsed part has been changed.
smsg->parsed_was_changed();
// It's SC->MS now
smsg->ms_to_sc = false;
}
void set_to_for_smsc(const char *address, short_msg_p_list::iterator &smsg)
{
osip_message_t *omsg = smsg->parsed;
// Set To field
osip_free(omsg->to->url->username);
osip_free(omsg->to->displayname);
omsg->to->url->username = (char *)osip_malloc (strlen(address)+1);
omsg->to->displayname = (char *)osip_malloc (strlen(omsg->req_uri->username)+1);
strcpy(omsg->to->url->username, address);
strcpy(omsg->to->displayname, omsg->req_uri->username);
// Let them know that parsed part has been changed.
smsg->parsed_was_changed();
}
//@}
/** Send a TPDU through whatever gateway is available. */
short_code_action submitSMS(const char *imsi, const TLSubmit& submit,
const std::string &body, short_code_params *scp)
{
LOG(INFO) << "from " << imsi << " message: " << submit;
const TLAddress& address = submit.DA();
// Check for direct e-mail address at start of message body.
// FIXME -- This doesn't really follow the spec. See GSM 03.40 3.8.
static const Regexp emailAddress("^[[:graph:]]+@[[:graph:]]+ ");
if (emailAddress.match(body.data())) {
// FIXME -- Get the sender's E.164 to put in the subject line.
char bodyCopy[body.length()+2];
strcpy(bodyCopy,body.data());
char* SMTPAddress = bodyCopy;
char* term = strchr(bodyCopy,' ');
// If term's NULL, the regexp is broken.
assert(term);
*term = '\0';
char* SMTPPayload = term+1;
LOG(INFO) << "sending SMTP to " << SMTPAddress << ": " << SMTPPayload;
if (SMTPPayload) return sendEMail(SMTPAddress,SMTPPayload,"from OpenBTS gateway");
else return sendEMail(SMTPAddress,"(empty)","from OpenBTS gateway");
}
// Send to smqueue or HTTP gateway, depending on what's defined in the config.
if (gConfig.defines("SMS.HTTP.Gateway"))
// If there is an external HTTP gateway, use it.
return sendHTTP(address.digits(), body);
else
// Otherwise, we are looking for a SIP interface to smqueue.
return sendSIP_init(imsi, submit, body, scp);
}
short_code_action shortcode_smsc(const char *imsi, const char *msgtext,
short_code_params *scp)
{
short_code_action return_action = SCA_TREAT_AS_ORDINARY;
short_msg_p_list::iterator smsg = scp->scp_qmsg_it;
if (smsg->content_type != short_msg::VND_3GPP_SMS)
{
LOG(WARN) << "We support only TPDU-coded SMS messages. Plain text ones are to be supported later.";
return SCA_INTERNAL_ERROR;
}
if (smsg->tl_message == NULL)
{
LOG(WARN) << "TLMessage is not available. Error during SMS decoding occurred?";
return SCA_INTERNAL_ERROR;
}
if (((TLMessage::MessageType)smsg->tl_message->MTI()) != TLMessage::SUBMIT)
{
LOG(WARN) << "TPDU must be of SMS-SUBMIT type, we have MTI="
<< (TLMessage::MessageType)smsg->tl_message->MTI();
return SCA_INTERNAL_ERROR;
}
return_action = submitSMS(imsi, *((TLSubmit*)smsg->tl_message), msgtext, scp);
return return_action;
}
bool pack_text_to_tpdu(const std::string &body,
short_msg_p_list::iterator &smsg)
{
create_sms_delivery(body, TLUserData(body.data()), smsg);
pack_tpdu(smsg);
// Set Content-Type field
const char *type = "application";
const char *subtype = "vnd.3gpp.sms";
osip_free(smsg->parsed->content_type->type);
osip_free(smsg->parsed->content_type->subtype);
smsg->parsed->content_type->type = (char *)osip_malloc (strlen(type)+1);
smsg->parsed->content_type->subtype = (char *)osip_malloc (strlen(subtype)+1);
strcpy(smsg->parsed->content_type->type, type);
strcpy(smsg->parsed->content_type->subtype, subtype);
// Let them know that parsed part has been changed.
smsg->parsed_was_changed();
return true;
}
bool recode_tpdu(const std::string &body,
short_msg_p_list::iterator &smsg)
{
bool return_action = true;
// Safety check
if (smsg->tl_message == NULL)
{
LOG(WARN) << "TLMessage is not available. Error during SMS decoding occurred?";
return false;
}
switch ((TLMessage::MessageType)smsg->tl_message->MTI()) {
case TLMessage::SUBMIT: {
TLSubmit *submit = (TLSubmit*)smsg->tl_message;
const TLUserData& tl_ud = submit->UD();
create_sms_delivery(body, tl_ud, smsg);
pack_tpdu(smsg);
return_action = true;
break;
}
case TLMessage::DELIVER_REPORT:
case TLMessage::STATUS_REPORT:
// TODO
LOG(WARN) << "TPDU must be of SMS-SUBMIT type, we have MTI="
<< (TLMessage::MessageType)smsg->tl_message->MTI();
return false;
}
// Now we have MS->SC message
smsg->ms_to_sc = false;
return return_action;
}
bool pack_sms_for_delivery(short_msg_p_list::iterator &smsg)
{
bool return_action = true;
std::string msgtext;
if (!smsg->need_repack) {
// Nothing to do.
return true;
}
// Parse message if not parsed yet.
smsg->parse();
// Get message text in plain text form
msgtext = smsg->get_text();
switch (smsg->content_type) {
case short_msg::TEXT_PLAIN:
return_action = pack_text_to_tpdu(msgtext, smsg);
break;
case short_msg::VND_3GPP_SMS:
// No need to recode if it's SC->MS already
if (smsg->ms_to_sc)
{
return_action = recode_tpdu(msgtext, smsg);
}
break;
case short_msg::UNSUPPORTED_CONTENT:
default:
LOG(WARN) << "Unsupported SIP-messages content.";
return false;
}
// Successful repack
if (return_action) {
smsg->need_repack = false;
}
if (ISLOGGING(DEBUG)) {
// Call make_text_valid() is needed for debug only.
smsg->make_text_valid();
LOG(DEBUG) << "Updated SMS message: " << smsg->text;
}
return return_action;
}

View File

@ -0,0 +1,39 @@
/*
* SMSC.h - SMS Center implementation for OpenBTS.
* Written by Alexander Chemeris, 2010.
*
* Copyright 2010 Free Software Foundation, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* See the COPYING file in the main directory for details.
*/
#ifndef SMSC_H
#define SMSC_H
#include "smqueue.h"
using namespace SMqueue;
/** Unhex and parse msgtext to RPData struct.
@param msgttext RPData encoded into hex-string.
@return Pointer to parsed RPData or NULL on failure.
*/
RPData *hex2rpdata(const char *msgtext);
enum short_code_action shortcode_smsc(const char *imsi, const char *msgtext,
short_code_params *scp);
bool pack_sms_for_delivery(short_msg_p_list::iterator &smsg);
#endif