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:
commit
3ea07de75c
|
@ -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)
|
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("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("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("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("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("sendsms", sendsms, "<IMSI> <src> -- send SMS to <IMSI>, addressed from <src>, after prompting.");
|
||||||
addCommand("sendrrlp", sendrrlp, "<IMSI> <hexstring> -- send RRLP message <hexstring> to <IMSI>.");
|
addCommand("sendrrlp", sendrrlp, "<IMSI> <hexstring> -- send RRLP message <hexstring> to <IMSI>.");
|
||||||
|
|
|
@ -28,6 +28,7 @@
|
||||||
#include "Threads.h"
|
#include "Threads.h"
|
||||||
#include "Timeval.h"
|
#include "Timeval.h"
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
|
@ -96,6 +97,67 @@ void Signal::wait(Mutex& wMutex, unsigned timeout) const
|
||||||
pthread_cond_timedwait(&mSignal,&wMutex.mMutex,&waitTime);
|
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)
|
void Thread::start(void *(*task)(void*), void *arg)
|
||||||
{
|
{
|
||||||
|
|
|
@ -29,6 +29,7 @@
|
||||||
#include <pthread.h>
|
#include <pthread.h>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
#include <semaphore.h>
|
||||||
|
|
||||||
class Mutex;
|
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) \
|
#define START_THREAD(thread,function,argument) \
|
||||||
thread.start((void *(*)(void*))function, (void*)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 )
|
void Control::clearTransactionHistory( TransactionEntry& transaction )
|
||||||
{
|
{
|
||||||
SIP::SIPEngine& engine = transaction.SIP();
|
SIP::SIPEngine& engine = transaction.SIP();
|
||||||
|
|
|
@ -611,6 +611,9 @@ class TransactionTable {
|
||||||
//@}
|
//@}
|
||||||
|
|
||||||
size_t size();
|
size_t size();
|
||||||
|
|
||||||
|
/** Write entries as text to a stream. */
|
||||||
|
void dump(std::ostream&) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
//@} // Transaction Table
|
//@} // Transaction Table
|
||||||
|
|
|
@ -42,6 +42,7 @@
|
||||||
|
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
#include <sstream>
|
||||||
#include <GSMLogicalChannel.h>
|
#include <GSMLogicalChannel.h>
|
||||||
#include <GSML3MMMessages.h>
|
#include <GSML3MMMessages.h>
|
||||||
#include "ControlCommon.h"
|
#include "ControlCommon.h"
|
||||||
|
@ -86,99 +87,8 @@ L3Frame* getFrameSMS(LogicalChannel *LCH, GSM::Primitive primitive=DATA)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool sendSIP(const L3MobileIdentity &mobileID, const char* address, const char* body)
|
||||||
|
|
||||||
|
|
||||||
/**@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)
|
|
||||||
{
|
|
||||||
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:
|
// Steps:
|
||||||
// 1 -- Create a transaction record.
|
// 1 -- Create a transaction record.
|
||||||
// 2 -- Send it to the server.
|
// 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.
|
// 4 -- Return true for OK or ACCEPTED, false otherwise.
|
||||||
|
|
||||||
// Form the TLAddress into a CalledPartyNumber for the transaction.
|
// Form the TLAddress into a CalledPartyNumber for the transaction.
|
||||||
L3CalledPartyBCDNumber calledParty(address.digits());
|
L3CalledPartyBCDNumber calledParty(address);
|
||||||
// Step 1 -- Create a transaction record.
|
// Step 1 -- Create a transaction record.
|
||||||
TransactionEntry transaction(
|
TransactionEntry transaction(mobileID,
|
||||||
mobileID,
|
L3CMServiceType::ShortMessage,
|
||||||
L3CMServiceType::ShortMessage,
|
0, // doesn't matter
|
||||||
0, // doesn't matter
|
calledParty);
|
||||||
calledParty);
|
|
||||||
transaction.SIP().User(mobileID.digits());
|
transaction.SIP().User(mobileID.digits());
|
||||||
transaction.Q931State(TransactionEntry::SMSSubmitting);
|
transaction.Q931State(TransactionEntry::SMSSubmitting);
|
||||||
gTransactionTable.add(transaction);
|
gTransactionTable.add(transaction);
|
||||||
LOG(DEBUG) << "MOSMS: transaction: " << transaction;
|
LOG(DEBUG) << "MOSMS: transaction: " << transaction;
|
||||||
|
|
||||||
// Step 2 -- Send the message to the server.
|
// 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.
|
// Step 3 -- Wait for OK or ACCEPTED.
|
||||||
SIPState state = transaction.SIP().MOSMSWaitForSubmit();
|
SIPState state = transaction.SIP().MOSMSWaitForSubmit();
|
||||||
|
@ -209,67 +118,6 @@ bool sendToNumericAddress(const L3MobileIdentity &mobileID, const TLSubmit& subm
|
||||||
return state==SIP::Cleared;
|
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.
|
Process the RPDU.
|
||||||
@param mobileID The sender's IMSI.
|
@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();
|
LOG(DEBUG) << "SMS: handleRPDU MTI=" << RPDU.MTI();
|
||||||
switch ((RPMessage::MessageType)RPDU.MTI()) {
|
switch ((RPMessage::MessageType)RPDU.MTI()) {
|
||||||
case RPMessage::Data: {
|
case RPMessage::Data: {
|
||||||
RPData data;
|
ostringstream body;
|
||||||
data.parse(RPDU);
|
RPDU.hex(body);
|
||||||
LOG(INFO) << "SMS RP-DATA " << data;
|
return sendSIP(mobileID, gConfig.getStr("SIP.SMSC"), body.str().data());
|
||||||
return handleTPDU(mobileID,data.TPDU());
|
|
||||||
}
|
}
|
||||||
case RPMessage::Ack:
|
case RPMessage::Ack:
|
||||||
case RPMessage::SMMA:
|
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.
|
// This function is used to deliver messages that originate INSIDE the BTS.
|
||||||
// For the normal SMS delivery, see MTSMSController.
|
// For the normal SMS delivery, see MTSMSController.
|
||||||
|
|
||||||
// Start ABM in SAP3.
|
#if 0
|
||||||
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);
|
|
||||||
|
|
||||||
// HACK
|
// HACK
|
||||||
// Check for "Easter Eggs"
|
// Check for "Easter Eggs"
|
||||||
// TL-PID
|
// TL-PID
|
||||||
|
@ -449,6 +290,44 @@ bool Control::deliverSMSToMS(const char *callingPartyDigits, const char* message
|
||||||
RPData(reference,
|
RPData(reference,
|
||||||
RPAddress(gConfig.getStr("SMS.FakeSrcSMSC")),
|
RPAddress(gConfig.getStr("SMS.FakeSrcSMSC")),
|
||||||
TLDeliver(callingPartyDigits,message,TLPID)));
|
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;
|
LOG(INFO) << "sending " << deliver;
|
||||||
LCH->send(deliver,3);
|
LCH->send(deliver,3);
|
||||||
|
|
||||||
|
@ -543,7 +422,9 @@ void Control::MTSMSController(TransactionEntry& transaction,
|
||||||
// but instead to an RRLP transaction over the already allocated LogicalChannel.
|
// but instead to an RRLP transaction over the already allocated LogicalChannel.
|
||||||
const char* m = transaction.message(); // NOTE - not very nice, my way of checking.
|
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))) {
|
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";
|
LOG(INFO) << "MTSMS: Sending RRLP";
|
||||||
// TODO - how to get mobID here?
|
// TODO - how to get mobID here?
|
||||||
L3MobileIdentity mobID = L3MobileIdentity("000000000000000");
|
L3MobileIdentity mobID = L3MobileIdentity("000000000000000");
|
||||||
|
@ -579,15 +460,29 @@ void Control::MTSMSController(TransactionEntry& transaction,
|
||||||
transaction.Q931State(TransactionEntry::SMSDelivering);
|
transaction.Q931State(TransactionEntry::SMSDelivering);
|
||||||
gTransactionTable.update(transaction);
|
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.
|
// Close the Dm channel.
|
||||||
LOG(INFO) << "MTSMS: closing";
|
LOG(INFO) << "MTSMS: closing";
|
||||||
LCH->send(L3ChannelRelease());
|
LCH->send(L3ChannelRelease());
|
||||||
|
|
||||||
// Ack in SIP domain and update transaction state.
|
// Ack in SIP domain and update transaction state.
|
||||||
if (success) {
|
if (success) {
|
||||||
|
engine.MTSMSSendOK();
|
||||||
|
clearTransactionHistory(transaction);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (UnexpectedMessage) {
|
||||||
|
// TODO -- MUST SEND PERMANENT ERROR HERE!!!!!!!!!
|
||||||
engine.MTSMSSendOK();
|
engine.MTSMSSendOK();
|
||||||
|
LCH->send(L3ChannelRelease());
|
||||||
|
clearTransactionHistory(transaction);
|
||||||
|
}
|
||||||
|
catch (UnsupportedMessage) {
|
||||||
|
// TODO -- MUST SEND PERMANENT ERROR HERE!!!!!!!!!
|
||||||
|
engine.MTSMSSendOK();
|
||||||
|
LCH->send(L3ChannelRelease());
|
||||||
clearTransactionHistory(transaction);
|
clearTransactionHistory(transaction);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -654,7 +654,7 @@ int SIPEngine::RxFrame(unsigned char * rx_frame){
|
||||||
|
|
||||||
|
|
||||||
SIPState SIPEngine::MOSMSSendMESSAGE(const char * wCalledUsername,
|
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(DEBUG) << "mState=" << mState;
|
||||||
LOG(INFO) << "SIP send to " << wCalledUsername << "@" << wCalledDomain << " MESSAGE " << messageText;
|
LOG(INFO) << "SIP send to " << wCalledUsername << "@" << wCalledDomain << " MESSAGE " << messageText;
|
||||||
|
@ -673,11 +673,18 @@ SIPState SIPEngine::MOSMSSendMESSAGE(const char * wCalledUsername,
|
||||||
mRemoteUsername = wCalledUsername;
|
mRemoteUsername = wCalledUsername;
|
||||||
mRemoteDomain = wCalledDomain;
|
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(
|
osip_message_t * message = sip_message(
|
||||||
mRemoteUsername.c_str(), mSIPUsername.c_str(),
|
mRemoteUsername.c_str(), mSIPUsername.c_str(),
|
||||||
mSIPPort, gConfig.getStr("SIP.IP"), mMessengerIP,
|
mSIPPort, gConfig.getStr("SIP.IP"), mMessengerIP,
|
||||||
mFromTag.c_str(), mViaBranch.c_str(), mCallID.c_str(), mCSeq,
|
mFromTag.c_str(), mViaBranch.c_str(), mCallID.c_str(), mCSeq,
|
||||||
messageText);
|
messageText, content_type);
|
||||||
|
|
||||||
// Send Invite to Asterisk.
|
// Send Invite to Asterisk.
|
||||||
gSIPInterface.writeMessenger(message);
|
gSIPInterface.writeMessenger(message);
|
||||||
|
|
|
@ -200,10 +200,12 @@ public:
|
||||||
@param called_username SIP userid or E.164 address.
|
@param called_username SIP userid or E.164 address.
|
||||||
@param called_domain SIP user's domain.
|
@param called_domain SIP user's domain.
|
||||||
@param message_text MESSAGE payload as a C string.
|
@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.
|
@return New SIP call state.
|
||||||
*/
|
*/
|
||||||
SIPState MOSMSSendMESSAGE(const char * called_username,
|
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();
|
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];
|
char local_port[10];
|
||||||
sprintf(local_port, "%i", wlocal_port);
|
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);
|
sprintf(temp_buf,"%i",cseq);
|
||||||
osip_cseq_set_number(request->cseq, strdup(temp_buf));
|
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));
|
sprintf(temp_buf,"%lu",strlen(message));
|
||||||
osip_message_set_content_length(request, strdup(temp_buf));
|
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);
|
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);
|
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
|
void CPMessage::text(ostream& os) const
|
||||||
{
|
{
|
||||||
os << (CPMessage::MessageType)MTI();
|
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)
|
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;
|
unsigned numBits = expectedLength*8;
|
||||||
|
if (rp+numBits > src.size()) {
|
||||||
|
SMS_READ_ERROR;
|
||||||
|
}
|
||||||
mTPDU.resize(numBits);
|
mTPDU.resize(numBits);
|
||||||
|
LOG(DEBUG) << "mTPDU length=" << mTPDU.length() << "data=" << mTPDU;
|
||||||
src.segmentCopyTo(mTPDU,rp,numBits);
|
src.segmentCopyTo(mTPDU,rp,numBits);
|
||||||
rp += numBits;
|
rp += numBits;
|
||||||
}
|
}
|
||||||
|
@ -410,16 +470,96 @@ void TLValidityPeriod::text(ostream& os) const
|
||||||
os << "expiration=(" << str << ")";
|
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
|
size_t TLUserData::length() const
|
||||||
{
|
{
|
||||||
// The reported value includes the length byte itself.
|
// The reported value includes the length byte itself.
|
||||||
// The length() method only needs to work for formats supported
|
// The length() method only needs to work for formats supported
|
||||||
// by the write() method.
|
// by the write() method.
|
||||||
assert(!mUDHI); // We don't support user headers.
|
|
||||||
assert(mDCS<0x100); // Someone forgot to initialize the DCS.
|
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.
|
// The DCS is defined in GSM 03.38 4.
|
||||||
if (mDCS==0) {
|
if (mDCS==0) {
|
||||||
// Default 7-bit alphabet
|
// Default 7-bit alphabet
|
||||||
|
@ -428,23 +568,31 @@ size_t TLUserData::length() const
|
||||||
unsigned octets = bits/8;
|
unsigned octets = bits/8;
|
||||||
if (bits%8) octets += 1;
|
if (bits%8) octets += 1;
|
||||||
sum += octets;
|
sum += octets;
|
||||||
return sum;
|
|
||||||
} else {
|
} else {
|
||||||
LOG(ERROR) << "unsupported SMS DCS 0x" << hex << mDCS;
|
LOG(ERROR) << "unsupported SMS DCS 0x" << hex << mDCS;
|
||||||
// It's OK to abort here. This method is only used for encoding.
|
// It's OK to abort here. This method is only used for encoding.
|
||||||
// So we should never end up here.
|
// So we should never end up here.
|
||||||
assert(0); // We don't support this DCS.
|
assert(0); // We don't support this DCS.
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
return sum;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void TLUserData::parse(const TLFrame& src, size_t& rp)
|
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.
|
// The DCS is defined in GSM 03.38 4.
|
||||||
assert(mDCS<0x100); // Someone forgot to initialize the DCS.
|
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) {
|
switch (mDCS) {
|
||||||
case 0:
|
case 0:
|
||||||
case 244:
|
case 244:
|
||||||
|
@ -478,11 +626,22 @@ void TLUserData::parse(const TLFrame& src, size_t& rp)
|
||||||
SMS_READ_ERROR;
|
SMS_READ_ERROR;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void TLUserData::write(TLFrame& dest, size_t& wp) const
|
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...
|
// Stuff we don't support...
|
||||||
assert(!mUDHI);
|
assert(!mUDHI);
|
||||||
assert(mDCS==0);
|
assert(mDCS==0);
|
||||||
|
@ -496,14 +655,17 @@ void TLUserData::write(TLFrame& dest, size_t& wp) const
|
||||||
dest.writeFieldReversed(wp,gsm,7);
|
dest.writeFieldReversed(wp,gsm,7);
|
||||||
}
|
}
|
||||||
chars.LSB8MSB();
|
chars.LSB8MSB();
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void TLUserData::text(ostream& os) const
|
void TLUserData::text(ostream& os) const
|
||||||
{
|
{
|
||||||
if (mDCS==0) os << mData;
|
os << "DCS=" << mDCS;
|
||||||
else os << "unknown encoding";
|
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)
|
void TLSubmit::parseBody(const TLFrame& src, size_t& rp)
|
||||||
{
|
{
|
||||||
|
bool udhi;
|
||||||
|
|
||||||
parseRD(src);
|
parseRD(src);
|
||||||
parseVPF(src);
|
parseVPF(src);
|
||||||
parseRP(src);
|
parseRP(src);
|
||||||
parseUDHI(src);
|
udhi = parseUDHI(src);
|
||||||
parseSRR(src);
|
parseSRR(src);
|
||||||
mMR = src.readField(rp,8);
|
mMR = src.readField(rp,8);
|
||||||
mDA.parse(src,rp);
|
mDA.parse(src,rp);
|
||||||
|
@ -545,6 +709,7 @@ void TLSubmit::parseBody(const TLFrame& src, size_t& rp)
|
||||||
mVP.VPF(mVPF);
|
mVP.VPF(mVPF);
|
||||||
mVP.parse(src,rp);
|
mVP.parse(src,rp);
|
||||||
mUD.DCS(mDCS);
|
mUD.DCS(mDCS);
|
||||||
|
mUD.UDHI(udhi);
|
||||||
mUD.parse(src,rp);
|
mUD.parse(src,rp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -555,7 +720,7 @@ void TLSubmit::text(ostream& os) const
|
||||||
os << " RD=" << mRD;
|
os << " RD=" << mRD;
|
||||||
os << " VPF=" << mVPF;
|
os << " VPF=" << mVPF;
|
||||||
os << " RP=" << mRP;
|
os << " RP=" << mRP;
|
||||||
os << " UDHI=" << mUDHI;
|
os << " UDHI=" << mUD.UDHI();
|
||||||
os << " SRR=" << mSRR;
|
os << " SRR=" << mSRR;
|
||||||
os << " MR=" << mMR;
|
os << " MR=" << mMR;
|
||||||
os << " DA=(" << mDA << ")";
|
os << " DA=(" << mDA << ")";
|
||||||
|
@ -568,7 +733,7 @@ void TLSubmit::text(ostream& os) const
|
||||||
|
|
||||||
size_t TLDeliver::bodyLength() 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();
|
return mOA.length() + 1 + 1 + mSCTS.length() + mUD.length();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -577,11 +742,11 @@ void TLDeliver::writeBody(TLFrame& dest, size_t& wp) const
|
||||||
{
|
{
|
||||||
writeMMS(dest);
|
writeMMS(dest);
|
||||||
writeRP(dest);
|
writeRP(dest);
|
||||||
writeUDHI(dest);
|
writeUDHI(dest, mUD.UDHI());
|
||||||
writeSRI(dest);
|
writeSRI(dest);
|
||||||
mOA.write(dest,wp);
|
mOA.write(dest,wp);
|
||||||
dest.writeField(wp,mPID,8);
|
dest.writeField(wp,mPID,8);
|
||||||
dest.writeField(wp,0,8); // hardcode DCS
|
dest.writeField(wp,mUD.DCS(),8);
|
||||||
mSCTS.write(dest,wp);
|
mSCTS.write(dest,wp);
|
||||||
writeUnused(dest);
|
writeUnused(dest);
|
||||||
mUD.write(dest,wp);
|
mUD.write(dest,wp);
|
||||||
|
|
|
@ -38,7 +38,9 @@
|
||||||
#include <GSML3Message.h>
|
#include <GSML3Message.h>
|
||||||
#include <GSML3CCElements.h>
|
#include <GSML3CCElements.h>
|
||||||
#include <GSML3MMElements.h>
|
#include <GSML3MMElements.h>
|
||||||
|
#include <Logger.h>
|
||||||
|
|
||||||
|
using namespace GSM;
|
||||||
|
|
||||||
namespace SMS {
|
namespace SMS {
|
||||||
|
|
||||||
|
@ -164,7 +166,9 @@ class TLUserData : public TLElement {
|
||||||
|
|
||||||
unsigned mDCS; ///< data coding scheme
|
unsigned mDCS; ///< data coding scheme
|
||||||
bool mUDHI; ///< header indicator
|
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:
|
public:
|
||||||
|
|
||||||
|
@ -172,19 +176,49 @@ class TLUserData : public TLElement {
|
||||||
TLUserData(unsigned wDCS=0x100, bool wUDHI=false)
|
TLUserData(unsigned wDCS=0x100, bool wUDHI=false)
|
||||||
:TLElement(),
|
:TLElement(),
|
||||||
mDCS(wDCS),
|
mDCS(wDCS),
|
||||||
mUDHI(wUDHI)
|
mUDHI(wUDHI),
|
||||||
{ mData[0]='\0'; }
|
mLength(0)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
/** Initialze from a simple C string. */
|
/** Initialize from a raw encoded data. */
|
||||||
TLUserData(const char* text, bool wUDHI=false)
|
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(),
|
:TLElement(),
|
||||||
mDCS(0),
|
mDCS(0),
|
||||||
mUDHI(wUDHI)
|
mUDHI(wUDHI),
|
||||||
{ strncpy(mData,text,sizeof(mData)-1); mData[sizeof(mData)-1]='\0'; }
|
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; }
|
void DCS(unsigned wDCS) { mDCS=wDCS; }
|
||||||
|
unsigned DCS() const { return mDCS; }
|
||||||
void UDHI(unsigned wUDHI) { mUDHI=wUDHI; }
|
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. */
|
/** This length includes a byte for the length field. */
|
||||||
size_t length() const;
|
size_t length() const;
|
||||||
|
@ -225,7 +259,7 @@ class TLMessage {
|
||||||
bool mSRR; ///< status report request
|
bool mSRR; ///< status report request
|
||||||
bool mSRI; ///< status report indication
|
bool mSRI; ///< status report indication
|
||||||
bool mSRQ; ///< status report qualifier
|
bool mSRQ; ///< status report qualifier
|
||||||
bool mUDHI; ///< user-data header-indicator
|
// bool mUDHI; ///< user-data header-indicator. Stored in TLUserData.
|
||||||
bool mRP; ///< reply path
|
bool mRP; ///< reply path
|
||||||
//@}
|
//@}
|
||||||
|
|
||||||
|
@ -236,16 +270,16 @@ class TLMessage {
|
||||||
|
|
||||||
/** GSM 03.40 9.2.3.1 */
|
/** GSM 03.40 9.2.3.1 */
|
||||||
enum MessageType {
|
enum MessageType {
|
||||||
DELIVER = 0x0,
|
DELIVER = 0x0, // SC -> MS
|
||||||
DELIVER_REPORT = 0x0,
|
DELIVER_REPORT = 0x0, // MS -> SC
|
||||||
STATUS_REPORT = 0x2,
|
STATUS_REPORT = 0x2, // SC -> MS
|
||||||
COMMAND = 0x02,
|
COMMAND = 0x02, // MS -> SC
|
||||||
SUBMIT = 0x1,
|
SUBMIT = 0x1, // MS -> SC
|
||||||
SUBMIT_REPORT = 0x1
|
SUBMIT_REPORT = 0x1 // SC -> MS
|
||||||
};
|
};
|
||||||
|
|
||||||
TLMessage()
|
TLMessage()
|
||||||
:mMMS(false),mSRI(false),mUDHI(false),mRP(true)
|
:mMMS(false),mSRI(false),mRP(true)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
virtual ~TLMessage(){}
|
virtual ~TLMessage(){}
|
||||||
|
@ -286,8 +320,8 @@ class TLMessage {
|
||||||
void parseSRI(const TLFrame& fm) { mSRI=fm[2]; }
|
void parseSRI(const TLFrame& fm) { mSRI=fm[2]; }
|
||||||
void writeSRQ(TLFrame& fm) const { fm[2]=mSRQ; }
|
void writeSRQ(TLFrame& fm) const { fm[2]=mSRQ; }
|
||||||
void parseSRQ(const TLFrame& fm) { mSRQ=fm[2]; }
|
void parseSRQ(const TLFrame& fm) { mSRQ=fm[2]; }
|
||||||
void writeUDHI(TLFrame& fm) const { fm[1]=mUDHI; }
|
void writeUDHI(TLFrame& fm, bool udhi) const { fm[1]=udhi; }
|
||||||
void parseUDHI(const TLFrame& fm) { mUDHI=fm[1]; }
|
bool parseUDHI(const TLFrame& fm) { return fm[1]; }
|
||||||
void writeRP(TLFrame& fm) const { fm[0]=mRP; }
|
void writeRP(TLFrame& fm) const { fm[0]=mRP; }
|
||||||
void parseRP(const TLFrame& fm) { mRP=fm[0]; }
|
void parseRP(const TLFrame& fm) { mRP=fm[0]; }
|
||||||
void writeUnused(TLFrame& fm) const { fm.fill(0,3,2); } ///< Fill unused bits with 0s
|
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
|
TLAddress mOA; ///< origination address, GSM 03.40 9.3.2.7
|
||||||
unsigned mPID; ///< TL-PID, GSM 03.40 9.2.3.9
|
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
|
TLTimestamp mSCTS; ///< service center timestamp, GSM 03.40 9.2.3.11
|
||||||
TLUserData mUD; ///< user data
|
TLUserData mUD; ///< user data
|
||||||
|
|
||||||
|
@ -774,6 +808,21 @@ std::ostream& operator<<(std::ostream& os, CPMessage::MessageType MTI);
|
||||||
*/
|
*/
|
||||||
CPMessage * parseSMS( const GSM::L3Frame& frame );
|
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. */
|
/** A factory method for SMS L3 (CM) messages. */
|
||||||
CPMessage * CPFactory( CPMessage::MessageType MTI );
|
CPMessage * CPFactory( CPMessage::MessageType MTI );
|
||||||
|
|
||||||
|
|
|
@ -163,18 +163,11 @@ SIP.Timer.A 2000
|
||||||
#
|
#
|
||||||
# SMS parameters
|
# 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.
|
# ISDN address of destination SMSC when a fake value is needed.
|
||||||
SMS.DefaultDestSMSC 0000
|
SMS.DefaultDestSMSC 0000
|
||||||
|
|
||||||
|
# SMSes will be sent to SIP URI sip:<SIP.SMSC>@<SIP.IP>
|
||||||
# The SMS HTTP gateway.
|
SIP.SMSC smsc
|
||||||
# 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
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -20,15 +20,34 @@
|
||||||
|
|
||||||
include $(top_srcdir)/Makefile.common
|
include $(top_srcdir)/Makefile.common
|
||||||
|
|
||||||
|
AM_CPPFLAGS = $(STD_DEFINES_AND_INCLUDES)
|
||||||
|
AM_CXXFLAGS = -O3 -g -lpthread
|
||||||
|
|
||||||
EXTRA_DIST = \
|
EXTRA_DIST = \
|
||||||
Makefile.standalone \
|
|
||||||
README.smqueue \
|
README.smqueue \
|
||||||
poll.c \
|
|
||||||
smcommands.cpp \
|
|
||||||
smnet.cpp \
|
|
||||||
smqueue.config.example \
|
smqueue.config.example \
|
||||||
smqueue.cpp \
|
runloop.sh
|
||||||
|
|
||||||
|
noinst_PROGRAMS = \
|
||||||
|
smqueue
|
||||||
|
|
||||||
|
noinst_HEADERS = \
|
||||||
poll.h \
|
poll.h \
|
||||||
smnet.h \
|
smnet.h \
|
||||||
smqueue.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
|
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 "smqueue.h"
|
||||||
#include "smnet.h"
|
#include "smnet.h"
|
||||||
|
#include "smsc.h"
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
@ -379,5 +380,8 @@ SMqueue::init_smcommands (short_code_map_t *scm)
|
||||||
(*scm)[gConfig.getStr("SC.ZapQueued.Code")] = shortcode_zap_queued;
|
(*scm)[gConfig.getStr("SC.ZapQueued.Code")] = shortcode_zap_queued;
|
||||||
if (gConfig.defines("SC.WhiplashQuit.Code"))
|
if (gConfig.defines("SC.WhiplashQuit.Code"))
|
||||||
(*scm)[gConfig.getStr("SC.WhiplashQuit.Code")] = whiplash_quit;
|
(*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;
|
// (*scm)["666"] = shortcode_text_access;
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,13 @@ Log.Level.smcommands.cpp DEBUG
|
||||||
# Logging file. Logs to stdout if this is not defined.
|
# Logging file. Logs to stdout if this is not defined.
|
||||||
#Log.FileName smqueue.log
|
#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
|
savefile savedqueue.txt
|
||||||
|
|
||||||
|
@ -37,10 +44,26 @@ $optional Debug.print_as_we_validate
|
||||||
#SIP.global_relay 81.201.82.50:5060
|
#SIP.global_relay 81.201.82.50:5060
|
||||||
SIP.global_relay
|
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
|
# return SMS messages
|
||||||
BounceMessage.IMSILookupFailed Cannot determine return address; bouncing message. Text your phone number to 101 to register and try again.
|
Bounce.Message.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.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.
|
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
|
# Message for internal errors, sent as <A> << phonenumber << <B> << imsi
|
||||||
SC.Register.Msg.ErrorA Error in assigning
|
SC.Register.Msg.ErrorA Error in assigning
|
||||||
SC.Register.Msg.ErrorB to IMSI
|
SC.Register.Msg.ErrorB to
|
||||||
# Bounds for valid number length
|
# Bounds for valid number length
|
||||||
SC.Register.Digits.Max 10
|
SC.Register.Digits.Max 10
|
||||||
SC.Register.Digits.Min 7
|
SC.Register.Digits.Min 7
|
||||||
|
@ -87,4 +110,6 @@ SC.WhiplashQuit.Code 314158
|
||||||
SC.WhiplashQuit.Password Snidely
|
SC.WhiplashQuit.Password Snidely
|
||||||
SC.WhiplashQuit.SaveFile testsave.txt
|
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 "smqueue.h"
|
||||||
#include "smnet.h"
|
#include "smnet.h"
|
||||||
|
#include "smsc.h"
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
#include <osipparser2/osip_message.h> /* from osipparser2 */
|
#include <osipparser2/osip_message.h> /* from osipparser2 */
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
@ -57,41 +58,44 @@ ConfigurationTable gConfig("smqueue.config");
|
||||||
transitions where we're starting over from
|
transitions where we're starting over from
|
||||||
scratch due to some error. */
|
scratch due to some error. */
|
||||||
/* Timeout when moving from this state to new state:
|
/* 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] = {
|
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] = {
|
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] = {
|
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] = {
|
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] = {
|
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] = {
|
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] = {
|
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] = {
|
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] = {
|
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] = {
|
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] = {
|
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] = {
|
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] = {
|
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] = {
|
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] = {
|
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] = {
|
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:
|
/* 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 NT /* No longer needed */
|
||||||
#undef RT
|
#undef RT
|
||||||
|
@ -99,6 +103,7 @@ int timeouts_ASKED_TO_REGISTER_HANDSET[STATE_MAX_PLUS_ONE] = {
|
||||||
/* Index to all timeouts. Keep in order! */
|
/* Index to all timeouts. Keep in order! */
|
||||||
int (*SMqueue::timeouts[STATE_MAX_PLUS_ONE])[STATE_MAX_PLUS_ONE] = {
|
int (*SMqueue::timeouts[STATE_MAX_PLUS_ONE])[STATE_MAX_PLUS_ONE] = {
|
||||||
&timeouts_NO_STATE,
|
&timeouts_NO_STATE,
|
||||||
|
&timeouts_INITIAL_STATE,
|
||||||
&timeouts_REQUEST_FROM_ADDRESS_LOOKUP,
|
&timeouts_REQUEST_FROM_ADDRESS_LOOKUP,
|
||||||
&timeouts_ASKED_FOR_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] = {
|
string SMqueue::sm_state_strings[STATE_MAX_PLUS_ONE] = {
|
||||||
"No State",
|
"No State",
|
||||||
|
"Initial State",
|
||||||
"Request From-Address Lookup",
|
"Request From-Address Lookup",
|
||||||
"Asked for From-Address",
|
"Asked for From-Address",
|
||||||
"Awaiting Try Destination IMSI",
|
"Awaiting Try Destination IMSI",
|
||||||
|
@ -180,7 +186,8 @@ osip_mem_release()
|
||||||
*/
|
*/
|
||||||
static
|
static
|
||||||
struct imsi_phone { char imsi[4+15+1]; char phone[1+15+1]; } imsi_phone[] = {
|
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 */
|
{"IMSI777100223456161", "+17074700746"}, /* Palm Treo */
|
||||||
{{0}, {0}}
|
{{0}, {0}}
|
||||||
};
|
};
|
||||||
|
@ -244,6 +251,46 @@ SMq::process_timeout()
|
||||||
return; /* Wait til later to do more */
|
return; /* Wait til later to do more */
|
||||||
|
|
||||||
switch (qmsg->state) {
|
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:
|
case NO_STATE:
|
||||||
// Messages in NO_STATE have errors in them.
|
// Messages in NO_STATE have errors in them.
|
||||||
// Dump it to the log, and delete it, so the queue
|
// Dump it to the log, and delete it, so the queue
|
||||||
|
@ -268,36 +315,16 @@ SMq::process_timeout()
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
LOG(NOTICE) << "Message timed out with bad state "
|
LOG(ALARM) << "Message timed out with bad state "
|
||||||
<< qmsg->state << " and message: " << qmsg->text;
|
<< 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 */
|
/* NO BREAK */
|
||||||
case REQUEST_FROM_ADDRESS_LOOKUP:
|
case REQUEST_FROM_ADDRESS_LOOKUP:
|
||||||
// This is the initial state in which a message
|
/* Ask to translate the IMSI in the From field
|
||||||
// enters the system. Here, the message could be
|
into the phone number. */
|
||||||
// a SIP response as well as a SIP MESSAGE -- or
|
newstate = lookup_from_address (&*qmsg);
|
||||||
// something we can't process like a SIP REGISTER
|
set_state(qmsg, newstate);
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case REQUEST_DESTINATION_IMSI:
|
case REQUEST_DESTINATION_IMSI:
|
||||||
|
@ -322,6 +349,18 @@ SMq::process_timeout()
|
||||||
case REQUEST_MSG_DELIVERY:
|
case REQUEST_MSG_DELIVERY:
|
||||||
/* We are trying to deliver to the handset now (or
|
/* We are trying to deliver to the handset now (or
|
||||||
again after congestion). */
|
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
|
// debug_dump(); // FIXME, remove
|
||||||
// Only print delivering msg if delivering to non-
|
// Only print delivering msg if delivering to non-
|
||||||
// localhost.
|
// localhost.
|
||||||
|
@ -476,7 +515,7 @@ SMq::handle_response(short_msg_p_list::iterator qmsgit)
|
||||||
// Special code in registration processing
|
// Special code in registration processing
|
||||||
// will notice it's a re-reg and just reply
|
// will notice it's a re-reg and just reply
|
||||||
// with a welcome message.
|
// with a welcome message.
|
||||||
oldsms->set_state(REQUEST_FROM_ADDRESS_LOOKUP);
|
oldsms->set_state(INITIAL_STATE);
|
||||||
} else {
|
} else {
|
||||||
// Orig SMS exists, but not in a normal state.
|
// Orig SMS exists, but not in a normal state.
|
||||||
// Assume that the original SMS is in a
|
// 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.
|
// FIXME, add support for more Content-Type's.
|
||||||
if (!p->content_type || !p->content_type->type
|
if (!p->content_type || !p->content_type->type
|
||||||
|| !p->content_type->subtype
|
|| !p->content_type->subtype
|
||||||
|| 0 != strcmp("text", p->content_type->type)
|
|| !( (0 == strcmp("text", p->content_type->type)
|
||||||
|| 0 != strcmp("plain", p->content_type->subtype) )
|
&& 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;
|
return 415;
|
||||||
|
|
||||||
if (p->bodies.nb_elt != 1 || !p->bodies.node
|
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");
|
smpl = originate_half_sm("MESSAGE");
|
||||||
response = &*smpl->begin(); // Here's our short_msg_pending!
|
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.
|
// For the tag, we cheat and reuse the cseq number.
|
||||||
// I don't see any reason not to...why do we have three different
|
// I don't see any reason not to...why do we have three different
|
||||||
// tag fields scattered around?
|
// 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_uri_parse(response->parsed->req_uri, uriline.str().c_str());
|
||||||
|
|
||||||
osip_message_set_content_type(response->parsed, "text/plain");
|
osip_message_set_content_type(response->parsed, "text/plain");
|
||||||
|
response->content_type = short_msg::TEXT_PLAIN;
|
||||||
size_t len = strlen(msgtext);
|
size_t len = strlen(msgtext);
|
||||||
if (len > SMS_MESSAGE_MAX_LENGTH)
|
if (len > SMS_MESSAGE_MAX_LENGTH)
|
||||||
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)
|
SMq::bounce_message(short_msg_pending *sent_msg, const char *errstr)
|
||||||
{
|
{
|
||||||
ostringstream errmsg;
|
ostringstream errmsg;
|
||||||
osip_body_t *bod1;
|
|
||||||
char *username;
|
char *username;
|
||||||
char *thetext;
|
std::string thetext;
|
||||||
int status;
|
int status;
|
||||||
|
|
||||||
username = sent_msg->parsed->to->url->username;
|
username = sent_msg->parsed->to->url->username;
|
||||||
if (sent_msg->parsed->bodies.nb_elt > 0) {
|
thetext = sent_msg->get_text();
|
||||||
// bod1->body is the original SMS text
|
|
||||||
bod1 = (osip_body_t *)sent_msg->parsed->bodies.node->element;
|
|
||||||
thetext = bod1->body;
|
|
||||||
} else {
|
|
||||||
thetext = "";
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG(NOTICE) << "Bouncing " << sent_msg->qtag << " from "
|
LOG(NOTICE) << "Bouncing " << sent_msg->qtag << " from "
|
||||||
<< sent_msg->parsed->from->url->username // his phonenum
|
<< sent_msg->parsed->from->url->username // his phonenum
|
||||||
|
@ -1126,19 +1166,19 @@ SMq::bounce_message(short_msg_pending *sent_msg, const char *errstr)
|
||||||
else
|
else
|
||||||
errmsg << "can't send: " << thetext;
|
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;
|
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.
|
// 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)
|
bool bounce_to_imsi = 0 == strncmp("IMSI", bounceto, 4)
|
||||||
|| 0 == strncmp("imsi", bounceto, 4);
|
|| 0 == strncmp("imsi", bounceto, 4);
|
||||||
status = originate_sm("411", // from "411"? FIXME?
|
status = originate_sm(gConfig.getStr("Bounce.Code"), // Read from a config
|
||||||
bounceto, // to his phonenum or IMSI
|
bounceto, // to his phonenum or IMSI
|
||||||
errmsg.str().c_str(), // error msg
|
errmsg.str().c_str(), // error msg
|
||||||
bounce_to_imsi?
|
bounce_to_imsi? REQUEST_DESTINATION_SIPURL: // dest is IMSI
|
||||||
REQUEST_DESTINATION_SIPURL: // dest is IMSI
|
REQUEST_DESTINATION_IMSI); // dest is phonenum
|
||||||
REQUEST_DESTINATION_IMSI); // dest is phonenum
|
|
||||||
}
|
}
|
||||||
if (status == 0) {
|
if (status == 0) {
|
||||||
return DELETE_ME_STATE;
|
return DELETE_ME_STATE;
|
||||||
|
@ -1189,6 +1229,9 @@ SMq::register_handset (short_msg_p_list::iterator qmsg)
|
||||||
smpl = originate_half_sm("REGISTER");
|
smpl = originate_half_sm("REGISTER");
|
||||||
response = &*smpl->begin(); // Here's our short_msg_pending!
|
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;
|
imsi = qmsg->parsed->from->url->username;
|
||||||
|
|
||||||
// The To: line is the long-term name being registered.
|
// 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;
|
return ASKED_TO_REGISTER_HANDSET;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool SMq::handle_short_code(const short_code_map_t &short_code_map,
|
||||||
/*
|
short_msg_p_list::iterator qmsg,
|
||||||
* Initial handling of SMS messages found in the queue
|
sm_state &next_state)
|
||||||
*/
|
|
||||||
enum sm_state
|
|
||||||
SMq::handle_sms_message (short_msg_p_list::iterator qmsg)
|
|
||||||
{
|
{
|
||||||
osip_body_t *bod1;
|
osip_body_t *bod1;
|
||||||
char *bods;
|
std::string bods;
|
||||||
short_func_t shortfn;
|
short_func_t shortfn;
|
||||||
short_code_map_t::iterator shortit;
|
short_code_map_t::const_iterator shortit;
|
||||||
enum short_code_action sca;
|
enum short_code_action sca;
|
||||||
short_code_params params;
|
short_code_params params;
|
||||||
int status;
|
int status;
|
||||||
|
string short_code;
|
||||||
|
|
||||||
if (0 != strcmp(qmsg->parsed->sip_method, "MESSAGE")) {
|
short_code = qmsg->parsed->req_uri->username;
|
||||||
LOG(WARN) << "Invalid incoming SIP message, method is "
|
shortit = short_code_map.find (short_code);
|
||||||
<< qmsg->parsed->sip_method;
|
|
||||||
return NO_STATE;
|
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 */
|
||||||
/* Messages to certain addresses are special commands */
|
shortfn = shortit->second;
|
||||||
shortfn = shortit->second;
|
bods = qmsg->get_text();
|
||||||
bods = (char *)"";
|
|
||||||
if (qmsg->parsed->bodies.nb_elt == 1) {
|
// Set up arguments and access pointers, then call
|
||||||
bod1 = (osip_body_t *)qmsg->parsed->bodies.node->element;
|
// the short-code function to process it.
|
||||||
bods = bod1->body;
|
params.scp_retries = qmsg->retries;
|
||||||
|
params.scp_smq = this;
|
||||||
|
params.scp_qmsg_it = qmsg;
|
||||||
|
|
||||||
|
LOG(INFO) << "Short-code SMS "
|
||||||
|
<< qmsg->parsed->req_uri->username
|
||||||
|
<< " with text \"" << bods << "\"";
|
||||||
|
|
||||||
|
sca = (*shortfn) (qmsg->parsed->from->url->username, // imsi
|
||||||
|
bods.data(), // msg text
|
||||||
|
¶ms);
|
||||||
|
|
||||||
|
// The short-code function asks us to do something when
|
||||||
|
// it's done. Do it.
|
||||||
|
switch (sca) {
|
||||||
|
case SCA_REPLY:
|
||||||
|
LOG(INFO) << "Short-code replies: "
|
||||||
|
<< params.scp_reply;
|
||||||
|
status = originate_sm(
|
||||||
|
qmsg->parsed->req_uri->username, // from shortcode
|
||||||
|
qmsg->parsed->from->url->username,// to his IMSI
|
||||||
|
params.scp_reply, REQUEST_DESTINATION_SIPURL);
|
||||||
|
if (!status) {
|
||||||
|
next_state = DELETE_ME_STATE; // Done!
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
LOG(NOTICE) << "Reply failed " << status << "!";
|
||||||
// Set up arguments and access pointers, then call
|
// NO BREAK
|
||||||
// the short-code function to process it.
|
default:
|
||||||
params.scp_retries = qmsg->retries;
|
case SCA_INTERNAL_ERROR:
|
||||||
params.scp_smq = this;
|
LOG(ERROR) << "Error in short-code function "
|
||||||
params.scp_qmsg_it = qmsg;
|
|
||||||
|
|
||||||
LOG(INFO) << "Short-code SMS "
|
|
||||||
<< qmsg->parsed->req_uri->username
|
<< qmsg->parsed->req_uri->username
|
||||||
<< "(" << bods << ").";
|
<< "(" << bods << "): " << params.scp_reply;
|
||||||
|
next_state = NO_STATE;
|
||||||
sca = (*shortfn) (qmsg->parsed->from->url->username, // imsi
|
return true;
|
||||||
bods, // msg text
|
|
||||||
¶ms);
|
|
||||||
|
|
||||||
// The short-code function asks us to do something when
|
case SCA_EXEC_SMQUEUE:
|
||||||
// it's done. Do it.
|
reexec_smqueue = true;
|
||||||
switch (sca) {
|
stop_main_loop = true;
|
||||||
case SCA_REPLY:
|
next_state = DELETE_ME_STATE;
|
||||||
LOG(INFO) << "Short-code replies: "
|
return true;
|
||||||
<< 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_QUIT_SMQUEUE:
|
case SCA_QUIT_SMQUEUE:
|
||||||
stop_main_loop = true;
|
stop_main_loop = true;
|
||||||
return DELETE_ME_STATE;
|
next_state = DELETE_ME_STATE;
|
||||||
|
return true;
|
||||||
|
|
||||||
case SCA_DONE:
|
case SCA_DONE:
|
||||||
return DELETE_ME_STATE;
|
next_state = DELETE_ME_STATE;
|
||||||
|
return true;
|
||||||
|
|
||||||
case SCA_RETRY_AFTER_DELAY:
|
case SCA_RETRY_AFTER_DELAY:
|
||||||
// FIXME, timeout is implicit in set_state table,
|
// FIXME, timeout is implicit in set_state table,
|
||||||
// rather than taken from params.scp_delay.
|
// rather than taken from params.scp_delay.
|
||||||
qmsg->retries++;
|
qmsg->retries++;
|
||||||
return REQUEST_FROM_ADDRESS_LOOKUP;
|
next_state = REQUEST_FROM_ADDRESS_LOOKUP;
|
||||||
|
return true;
|
||||||
|
|
||||||
case SCA_AWAIT_REGISTER:
|
case SCA_AWAIT_REGISTER:
|
||||||
// We just linked the phone# to the IMSI, but we
|
// We just linked the phone# to the IMSI, but we
|
||||||
// have to wait til the HLR updates, before
|
// have to wait til the HLR updates, before
|
||||||
// we can link the IMSI to the originating IP address
|
// we can link the IMSI to the originating IP address
|
||||||
// and port number of its cell.
|
// and port number of its cell.
|
||||||
return AWAITING_REGISTER_HANDSET;
|
next_state = AWAITING_REGISTER_HANDSET;
|
||||||
|
return true;
|
||||||
|
|
||||||
case SCA_REGISTER:
|
case SCA_REGISTER:
|
||||||
return register_handset(qmsg);
|
next_state = register_handset(qmsg);
|
||||||
|
return true;
|
||||||
|
|
||||||
case SCA_TREAT_AS_ORDINARY:
|
case SCA_TREAT_AS_ORDINARY:
|
||||||
break; // fall through into non-special case.
|
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 false;
|
||||||
return lookup_from_address (&*qmsg);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -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
|
// into the message text, followed by a space... GSM 03.40 sec 3.8
|
||||||
ostringstream newtext;
|
ostringstream newtext;
|
||||||
osip_body_t *bod1;
|
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;
|
bod1 = (osip_body_t *)qmsg->parsed->bodies.node->element;
|
||||||
bods = bod1->body;
|
|
||||||
} else {
|
} else {
|
||||||
return NO_STATE; // Punt on msg w/no text
|
return NO_STATE; // Punt on msg w/no text
|
||||||
}
|
}
|
||||||
newtext << qmsg->parsed->from->url->username << "@"
|
newtext << qmsg->parsed->from->url->username << "@"
|
||||||
<< host << " " << bods;
|
<< host << " " << qmsg->get_text();
|
||||||
osip_free(bods);
|
osip_free(bod1->body);
|
||||||
bod1->body = osip_strdup(newtext.str().c_str());
|
bod1->body = osip_strdup(newtext.str().c_str());
|
||||||
bod1->length = strlen(bod1->body);
|
bod1->length = strlen(bod1->body);
|
||||||
|
|
||||||
|
@ -1461,7 +1503,7 @@ SMq::lookup_from_address (short_msg_pending *qmsg)
|
||||||
<< "> to phonenum failed.";
|
<< "> to phonenum failed.";
|
||||||
LOG(DEBUG) << qmsg->text;
|
LOG(DEBUG) << qmsg->text;
|
||||||
return bounce_message (qmsg,
|
return bounce_message (qmsg,
|
||||||
gConfig.getStr("BounceMessage.IMSILookupFailed")
|
gConfig.getStr("Bounce.Message.IMSILookupFailed")
|
||||||
);
|
);
|
||||||
// return NO_STATE; // Put it into limbo for debug.
|
// 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)) {
|
|| !my_hlr.useGateway(username)) {
|
||||||
// There's no global relay -- or the HLR says not to
|
// There's no global relay -- or the HLR says not to
|
||||||
// use the global relay for it -- so send a bounce.
|
// 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 {
|
} else {
|
||||||
// Send the message to our global relay.
|
// Send the message to our global relay.
|
||||||
// We leave the username as a phone number, and
|
// We leave the username as a phone number, and
|
||||||
|
@ -1782,7 +1824,7 @@ SMq::respond_sip_ack(int errcode, short_msg_pending *smp,
|
||||||
break;
|
break;
|
||||||
case 413: phrase="Message Body Size Error"; break;
|
case 413: phrase="Message Body Size Error"; break;
|
||||||
case 415: phrase="Unsupported Content Type";
|
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;
|
break;
|
||||||
case 416: phrase="Unsupported URI scheme (not SIP)"; break;
|
case 416: phrase="Unsupported URI scheme (not SIP)"; break;
|
||||||
case 480: phrase="Recipient Temporarily Unavailable"; break;
|
case 480: phrase="Recipient Temporarily Unavailable"; break;
|
||||||
|
@ -1897,6 +1939,7 @@ SMq::main_loop()
|
||||||
smpl = new short_msg_p_list (1);
|
smpl = new short_msg_p_list (1);
|
||||||
smp = &*smpl->begin(); // Here's our short_msg_pending!
|
smp = &*smpl->begin(); // Here's our short_msg_pending!
|
||||||
smp->initialize (len, buffer, false);
|
smp->initialize (len, buffer, false);
|
||||||
|
smp->ms_to_sc = true;
|
||||||
|
|
||||||
if (my_network.recvaddrlen <= sizeof (smp->srcaddr)) {
|
if (my_network.recvaddrlen <= sizeof (smp->srcaddr)) {
|
||||||
smp->srcaddrlen = my_network.recvaddrlen;
|
smp->srcaddrlen = my_network.recvaddrlen;
|
||||||
|
@ -2017,7 +2060,9 @@ SMq::save_queue_to_file(std::string qfile)
|
||||||
ofile << "=== " << (int) x->state << " "
|
ofile << "=== " << (int) x->state << " "
|
||||||
<< x->next_action_time << " "
|
<< x->next_action_time << " "
|
||||||
<< my_network.string_addr((struct sockaddr *)x->srcaddr, x->srcaddrlen, true) << " "
|
<< 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++;
|
howmany++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2040,6 +2085,8 @@ SMq::read_queue_from_file(std::string qfile)
|
||||||
ifstream ifile;
|
ifstream ifile;
|
||||||
std::string equals;
|
std::string equals;
|
||||||
unsigned astate, atime, alength;
|
unsigned astate, atime, alength;
|
||||||
|
unsigned ms_to_sc, need_repack;
|
||||||
|
std::string short_code;
|
||||||
std::string netaddrstr;
|
std::string netaddrstr;
|
||||||
sm_state mystate;
|
sm_state mystate;
|
||||||
time_t mytime;
|
time_t mytime;
|
||||||
|
@ -2062,6 +2109,8 @@ SMq::read_queue_from_file(std::string qfile)
|
||||||
}
|
}
|
||||||
ifile >> netaddrstr;
|
ifile >> netaddrstr;
|
||||||
ifile >> alength;
|
ifile >> alength;
|
||||||
|
ifile >> ms_to_sc;
|
||||||
|
ifile >> need_repack;
|
||||||
while (ifile.peek() == '\n')
|
while (ifile.peek() == '\n')
|
||||||
ignoreme = ifile.get();
|
ignoreme = ifile.get();
|
||||||
msgtext = new char[alength+2];
|
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
|
// We use the just-allocated msgtext; it gets freed after
|
||||||
// delivery of message.
|
// delivery of message.
|
||||||
|
|
||||||
|
// Restore saved state
|
||||||
|
smp->ms_to_sc = ms_to_sc;
|
||||||
|
smp->need_repack = need_repack;
|
||||||
|
|
||||||
smp->srcaddrlen = 0;
|
smp->srcaddrlen = 0;
|
||||||
if (!my_network.parse_addr(netaddrstr.c_str(), smp->srcaddr, sizeof(smp->srcaddr), &smp->srcaddrlen))
|
if (!my_network.parse_addr(netaddrstr.c_str(), smp->srcaddr, sizeof(smp->srcaddr), &smp->srcaddrlen))
|
||||||
abfuckingort();
|
abfuckingort();
|
||||||
|
@ -2091,7 +2144,8 @@ SMq::read_queue_from_file(std::string qfile)
|
||||||
<< smp->parsed->from->url->username
|
<< smp->parsed->from->url->username
|
||||||
<< " for "
|
<< " for "
|
||||||
<< smp->parsed->req_uri->username
|
<< smp->parsed->req_uri->username
|
||||||
<< ".";
|
<< " direction=" << (smp->ms_to_sc?"MS->SC":"SC->MS")
|
||||||
|
<< " need_repack=" << (smp->need_repack?"true":"false");
|
||||||
} else {
|
} else {
|
||||||
LOG(WARN) << "Read bad SMS "
|
LOG(WARN) << "Read bad SMS "
|
||||||
<< smp->parsed->status_code
|
<< smp->parsed->status_code
|
||||||
|
@ -2113,7 +2167,7 @@ SMq::read_queue_from_file(std::string qfile)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if 0
|
||||||
/*
|
/*
|
||||||
* Read in a message from a file. Return malloc'd char block of the whole
|
* Read in a message from a file. Return malloc'd char block of the whole
|
||||||
* thing.
|
* thing.
|
||||||
|
@ -2169,7 +2223,7 @@ read_short_msg_pending_from_file(char *fname)
|
||||||
smp = new short_msg_pending (length, sip_text, true);
|
smp = new short_msg_pending (length, sip_text, true);
|
||||||
return smp;
|
return smp;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
/* Really simple first try */
|
/* Really simple first try */
|
||||||
|
@ -2183,7 +2237,8 @@ main(int argc, char **argv)
|
||||||
// short_msg_pending *smp;
|
// short_msg_pending *smp;
|
||||||
std::string savefile;
|
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)
|
// This scope lets us delete the smq (and the network sockets)
|
||||||
// before we re-exec ourself.
|
// before we re-exec ourself.
|
||||||
|
|
|
@ -23,7 +23,7 @@
|
||||||
#define SM_QUEUE_H
|
#define SM_QUEUE_H
|
||||||
|
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
#include <osipparser2/osip_message.h> /* from osipparser2 */
|
//#include <osipparser2/osip_message.h> /* from osipparser2 */
|
||||||
#include <stdlib.h> /* for osipparser2 */
|
#include <stdlib.h> /* for osipparser2 */
|
||||||
#include <sys/time.h> /* for osip_init */
|
#include <sys/time.h> /* for osip_init */
|
||||||
#include <osip2/osip.h> /* for osip_init */
|
#include <osip2/osip.h> /* for osip_init */
|
||||||
|
@ -35,10 +35,18 @@
|
||||||
#include "smnet.h" // My network support
|
#include "smnet.h" // My network support
|
||||||
#include "HLR.h" // My home location register
|
#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 {
|
namespace SMqueue {
|
||||||
|
|
||||||
/* Maximum text size of an SMS message. */
|
/* 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.
|
/* std::abort isn't always there. Neither is the C library version.
|
||||||
Idiots? You tell me. */
|
Idiots? You tell me. */
|
||||||
|
@ -60,33 +68,33 @@ char *new_strdup(const char *orig);
|
||||||
*/
|
*/
|
||||||
|
|
||||||
enum sm_state { // timeout, next-state-if-timeout
|
enum sm_state { // timeout, next-state-if-timeout
|
||||||
NO_STATE = 0,
|
NO_STATE,
|
||||||
REQUEST_FROM_ADDRESS_LOOKUP = 1,
|
INITIAL_STATE,
|
||||||
ASKED_FOR_FROM_ADDRESS_LOOKUP = 2,
|
REQUEST_FROM_ADDRESS_LOOKUP,
|
||||||
|
ASKED_FOR_FROM_ADDRESS_LOOKUP,
|
||||||
|
|
||||||
AWAITING_TRY_DESTINATION_IMSI = 3,
|
AWAITING_TRY_DESTINATION_IMSI,
|
||||||
REQUEST_DESTINATION_IMSI = 4,
|
REQUEST_DESTINATION_IMSI,
|
||||||
ASKED_FOR_DESTINATION_IMSI = 5,
|
ASKED_FOR_DESTINATION_IMSI,
|
||||||
|
|
||||||
AWAITING_TRY_DESTINATION_SIPURL = 6,
|
AWAITING_TRY_DESTINATION_SIPURL,
|
||||||
REQUEST_DESTINATION_SIPURL = 7,
|
REQUEST_DESTINATION_SIPURL,
|
||||||
ASKED_FOR_DESTINATION_SIPURL = 8,
|
ASKED_FOR_DESTINATION_SIPURL,
|
||||||
|
|
||||||
AWAITING_TRY_MSG_DELIVERY = 9,
|
AWAITING_TRY_MSG_DELIVERY,
|
||||||
REQUEST_MSG_DELIVERY = 10,
|
REQUEST_MSG_DELIVERY,
|
||||||
ASKED_FOR_MSG_DELIVERY = 11,
|
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! */
|
STATE_MAX_PLUS_ONE, /* Keep this one last! */
|
||||||
};
|
};
|
||||||
|
|
||||||
#define STATE_MAX (STATE_MAX_PLUS_ONE - 1)
|
#define STATE_MAX (STATE_MAX_PLUS_ONE - 1)
|
||||||
#define INITIAL_STATE REQUEST_FROM_ADDRESS_LOOKUP
|
|
||||||
|
|
||||||
// How to print a state
|
// How to print a state
|
||||||
extern std::string sm_state_strings[STATE_MAX_PLUS_ONE];
|
extern std::string sm_state_strings[STATE_MAX_PLUS_ONE];
|
||||||
|
@ -102,6 +110,13 @@ extern struct osip *osipptr;
|
||||||
term storage in the queue. */
|
term storage in the queue. */
|
||||||
class short_msg {
|
class short_msg {
|
||||||
public:
|
public:
|
||||||
|
|
||||||
|
enum ContentType {
|
||||||
|
UNSUPPORTED_CONTENT,
|
||||||
|
TEXT_PLAIN,
|
||||||
|
VND_3GPP_SMS
|
||||||
|
};
|
||||||
|
|
||||||
/* First just the text string. A SIP message including body. */
|
/* First just the text string. A SIP message including body. */
|
||||||
unsigned short text_length;
|
unsigned short text_length;
|
||||||
char *text /* [text_length] */; // C++ doesn't make it simple
|
char *text /* [text_length] */; // C++ doesn't make it simple
|
||||||
|
@ -116,13 +131,32 @@ class short_msg {
|
||||||
// to;
|
// to;
|
||||||
// time_t date;
|
// time_t date;
|
||||||
// expiration;
|
// 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 () :
|
short_msg () :
|
||||||
text_length (0),
|
text_length (0),
|
||||||
text (NULL),
|
text (NULL),
|
||||||
parsed_is_valid (false),
|
parsed_is_valid (false),
|
||||||
parsed_is_better (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
|
// Make a short message, perhaps taking responsibility for deleting
|
||||||
// the "new"-allocated memory passed in.
|
// the "new"-allocated memory passed in.
|
||||||
|
@ -131,7 +165,12 @@ class short_msg {
|
||||||
text (cstr),
|
text (cstr),
|
||||||
parsed_is_valid (false),
|
parsed_is_valid (false),
|
||||||
parsed_is_better (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) {
|
if (!use_my_memory) {
|
||||||
text = new char [text_length+1];
|
text = new char [text_length+1];
|
||||||
|
@ -151,7 +190,12 @@ class short_msg {
|
||||||
text (0),
|
text (0),
|
||||||
parsed_is_valid (false),
|
parsed_is_valid (false),
|
||||||
parsed_is_better (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) {
|
if (text_length) {
|
||||||
text = new char [text_length+1];
|
text = new char [text_length+1];
|
||||||
|
@ -159,14 +203,19 @@ class short_msg {
|
||||||
text[text_length] = '\0';
|
text[text_length] = '\0';
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
public:
|
|
||||||
#if 0
|
#if 0
|
||||||
short_msg (std::string str) :
|
short_msg (std::string str) :
|
||||||
text_length (str.length()),
|
text_length (str.length()),
|
||||||
text (0),
|
text (0),
|
||||||
parsed_is_valid (false),
|
parsed_is_valid (false),
|
||||||
parsed_is_better (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];
|
text = new char [text_length+1];
|
||||||
strncpy(text, str.data(), text_length);
|
strncpy(text, str.data(), text_length);
|
||||||
|
@ -174,35 +223,26 @@ class short_msg {
|
||||||
};
|
};
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/* Override operator= to avoid pointer-sharing problems */
|
/* Disable operator= to avoid pointer-sharing problems */
|
||||||
private:
|
private:
|
||||||
short_msg & operator= (const short_msg &rvalue);
|
short_msg & operator= (const short_msg &rvalue);
|
||||||
public:
|
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 */
|
/* 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
|
// Pseudo-constructor due to inability to run constructors on
|
||||||
// members of lists.
|
// members of lists.
|
||||||
// Initialize a newly-default-constructed short message,
|
// Initialize a newly-default-constructed short message,
|
||||||
// perhaps taking responsibility for deleting
|
// perhaps taking responsibility for deleting
|
||||||
// the "new"-allocated memory passed in.
|
// the "new"-allocated memory passed in.
|
||||||
void
|
|
||||||
initialize ()
|
|
||||||
{ // Nothing to do: default constructor + default initializer
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
void
|
||||||
initialize (int len, char * const cstr, bool use_my_memory)
|
initialize (int len, char * const cstr, bool use_my_memory)
|
||||||
{
|
{
|
||||||
|
@ -233,6 +273,7 @@ class short_msg {
|
||||||
|
|
||||||
unparse(); // Free any previous one.
|
unparse(); // Free any previous one.
|
||||||
|
|
||||||
|
// Parse SIP message
|
||||||
i = osip_message_init(&sip);
|
i = osip_message_init(&sip);
|
||||||
if (i != 0) abfuckingort(); /* throw out-of-memory */
|
if (i != 0) abfuckingort(); /* throw out-of-memory */
|
||||||
i = osip_message_parse(sip, text, text_length);
|
i = osip_message_parse(sip, text, text_length);
|
||||||
|
@ -240,11 +281,45 @@ class short_msg {
|
||||||
parsed = sip;
|
parsed = sip;
|
||||||
parsed_is_valid = true;
|
parsed_is_valid = true;
|
||||||
parsed_is_better = false;
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Anytime a caller CHANGES the values in the parsed tree of the
|
/* 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
|
know that the cached copy of the text-string message is no
|
||||||
longer valid. Actually, we have TWO such cached copies! FIXME
|
longer valid. Actually, we have TWO such cached copies! FIXME
|
||||||
so we have to invalidate both of them. */
|
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.
|
/* Make the text string valid, if the parsed copy is better.
|
||||||
(It gets "better" by being modified, and parsed_was_changed()
|
(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
|
void
|
||||||
make_text_valid() {
|
make_text_valid() {
|
||||||
if (parsed_is_better) {
|
if (parsed_is_better) {
|
||||||
|
@ -288,6 +363,15 @@ class short_msg {
|
||||||
|
|
||||||
/* Free up all memory used by parsed version of message. */
|
/* Free up all memory used by parsed version of message. */
|
||||||
void unparse() {
|
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)
|
if (parsed_is_better)
|
||||||
make_text_valid();
|
make_text_valid();
|
||||||
if (parsed)
|
if (parsed)
|
||||||
|
@ -297,6 +381,39 @@ class short_msg {
|
||||||
parsed_is_better = false;
|
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...
|
// 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)
|
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
|
#endif
|
||||||
|
|
||||||
//
|
//
|
||||||
|
@ -425,37 +529,10 @@ class short_msg_pending: public short_msg {
|
||||||
this->linktag[len] = '\0';
|
this->linktag[len] = '\0';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public:
|
|
||||||
|
|
||||||
/* Override operator= to avoid pointer-sharing problems */
|
/* Override operator= to avoid pointer-sharing problems */
|
||||||
private:
|
private:
|
||||||
short_msg_pending & operator= (const short_msg_pending &rvalue);
|
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:
|
public:
|
||||||
|
|
||||||
/* Destructor */
|
/* Destructor */
|
||||||
|
@ -477,28 +554,7 @@ class short_msg_pending: public short_msg {
|
||||||
* This 'constructs' the new short_msg_pending in a temporary list,
|
* This 'constructs' the new short_msg_pending in a temporary list,
|
||||||
* and we can then trivially move it into the real message queue,
|
* and we can then trivially move it into the real message queue,
|
||||||
* removing it from the temporary list in the process.
|
* 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
|
// Make a pending short message, perhaps taking responsibility for
|
||||||
// deleting the "new"-allocated memory passed in.
|
// deleting the "new"-allocated memory passed in.
|
||||||
void
|
void
|
||||||
|
@ -521,19 +577,6 @@ class short_msg_pending: public short_msg {
|
||||||
linktag (NULL)
|
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
|
#endif
|
||||||
|
|
||||||
/* Optimize this later so we don't make so many kernel calls. */
|
/* 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. */
|
/* Check host and port for validity. */
|
||||||
bool
|
bool
|
||||||
check_host_port(char *host, char *port);
|
check_host_port(char *host, char *port);
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef std::list<short_msg_pending> short_msg_p_list;
|
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;
|
class SMq;
|
||||||
|
|
||||||
enum short_code_action {
|
enum short_code_action {
|
||||||
SCA_DONE = 0,
|
SCA_DONE = 0, ///< No further processing is needed. Free message.
|
||||||
SCA_INTERNAL_ERROR = 1,
|
SCA_INTERNAL_ERROR = 1, //< Just report error and bail out.
|
||||||
SCA_REPLY = 2,
|
SCA_REPLY = 2, ///< Free this message and send replay back to the msg sender
|
||||||
SCA_RETRY_AFTER_DELAY = 3,
|
///< with a text from params.scp_reply
|
||||||
SCA_REPLY_AND_RETRY = 4,
|
SCA_RETRY_AFTER_DELAY = 3, ///< HLR is busy. Retry query later.
|
||||||
SCA_QUIT_SMQUEUE = 5,
|
SCA_REPLY_AND_RETRY = 4, ///< UNUSED.
|
||||||
SCA_AWAIT_REGISTER = 6,
|
SCA_QUIT_SMQUEUE = 5, ///< Self-explanatory. Exit smqueue.
|
||||||
SCA_REGISTER = 7,
|
SCA_AWAIT_REGISTER = 6, ///< HLR response is delayed. Wait.
|
||||||
SCA_TREAT_AS_ORDINARY = 8,
|
SCA_REGISTER = 7, ///< HLR record for this phone has been retrieved.
|
||||||
SCA_EXEC_SMQUEUE = 9
|
///< 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 {
|
class short_code_params {
|
||||||
|
@ -812,9 +860,13 @@ class SMq {
|
||||||
enum sm_state
|
enum sm_state
|
||||||
register_handset (short_msg_p_list::iterator qmsg);
|
register_handset (short_msg_p_list::iterator qmsg);
|
||||||
|
|
||||||
/* Initial handling of an incoming SMS message in the queue */
|
/* Check if this is a short-code message and handle it.
|
||||||
enum sm_state
|
* Return true if message has been handled, false if you should continue
|
||||||
handle_sms_message(short_msg_p_list::iterator qmsg);
|
* 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
|
/* When a SIP response arrives, search the queue for its matching
|
||||||
MESSAGE and handle both. */
|
MESSAGE and handle both. */
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
|
@ -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
|
Reference in New Issue