561 lines
16 KiB
C++
561 lines
16 KiB
C++
/**@file SMS Control (L3), GSM 03.40, 04.11. */
|
|
|
|
/*
|
|
* OpenBTS provides an open source alternative to legacy telco protocols and
|
|
* traditionally complex, proprietary hardware systems.
|
|
*
|
|
* Copyright 2008, 2009 Free Software Foundation, Inc.
|
|
* Copyright 2010 Kestrel Signal Processing, Inc.
|
|
* Copyright 2011, 2014 Range Networks, Inc.
|
|
*
|
|
* This software is distributed under the terms of the GNU Affero General
|
|
* Public License version 3. See the COPYING and NOTICE files in the main
|
|
* directory for licensing information.
|
|
*
|
|
* This use of this software may be subject to additional restrictions.
|
|
* See the LEGAL file in the main directory for details.
|
|
*/
|
|
|
|
/*
|
|
Abbreviations:
|
|
MOSMS -- Mobile Originated Short Message Service
|
|
MTSMS -- Mobile Terminated Short Message Service
|
|
|
|
Verbs:
|
|
"send" -- Transfer to the network.
|
|
"receive" -- Transfer from the network.
|
|
"submit" -- Transfer from the MS.
|
|
"deliver" -- Transfer to the MS.
|
|
|
|
MOSMS: The MS "submits" a message that OpenBTS then "sends" to the network.
|
|
MTSMS: OpenBTS "receives" a message from the network that it then "delivers" to the MS.
|
|
|
|
*/
|
|
|
|
|
|
#include <stdio.h>
|
|
#include <sstream>
|
|
#include <GSML3MMMessages.h>
|
|
#include "SMSControl.h"
|
|
#include "ControlCommon.h"
|
|
#include "TransactionTable.h"
|
|
#include <Regexp.h>
|
|
|
|
#include <UMTSLogicalChannel.h>
|
|
|
|
|
|
using namespace std;
|
|
using namespace Control;
|
|
|
|
#include "SMSMessages.h"
|
|
using namespace SMS;
|
|
|
|
#include "SIPInterface.h"
|
|
#include "SIPUtility.h"
|
|
#include "SIPMessage.h"
|
|
#include "SIPEngine.h"
|
|
using namespace SIP;
|
|
|
|
#include <Logger.h>
|
|
#undef WARNING
|
|
|
|
/**
|
|
Read an L3Frame from SAP3.
|
|
Throw exception on failure. Will NOT return a NULL pointer.
|
|
*/
|
|
GSM::L3Frame* getFrameSMS(UMTS::DCCHLogicalChannel *LCH, GSM::Primitive primitive=GSM::DATA)
|
|
{
|
|
// FIXME -- We need to determine a correct timeout value here.
|
|
GSM::L3Frame *retVal = LCH->recv(20000,3);
|
|
if (!retVal) {
|
|
LOG(NOTICE) << "channel read time out on " << *LCH << " SAP3";
|
|
throw ChannelReadTimeout();
|
|
}
|
|
LOG(DEBUG) << "getFrameSMS on " << *LCH << " in frame " << *retVal;
|
|
if (retVal->primitive() != primitive) {
|
|
LOG(NOTICE) << "unexpected primitive on " << *LCH << ", expecting " << primitive << ", got " << *retVal;
|
|
throw UnexpectedPrimitive();
|
|
}
|
|
if ((retVal->primitive() == GSM::DATA) && (retVal->PD() != GSM::L3SMSPD)) {
|
|
LOG(NOTICE) << "unexpected (non-SMS) protocol on " << *LCH << " in frame " << *retVal;
|
|
throw UnexpectedMessage();
|
|
}
|
|
return retVal;
|
|
}
|
|
|
|
|
|
bool sendSIP(TransactionEntry *transaction, const char* address, const char* body, const char* contentType)
|
|
{
|
|
// Steps:
|
|
// 1 -- Complete transaction record.
|
|
// 2 -- Send TL-SUBMIT to the server.
|
|
// 3 -- Wait for response or timeout.
|
|
// 4 -- Return true for OK or ACCEPTED, false otherwise.
|
|
|
|
// Step 1 -- Complete the transaction record.
|
|
// Form the TLAddress into a CalledPartyNumber for the transaction.
|
|
GSM::L3CalledPartyBCDNumber calledParty(address);
|
|
// Attach calledParty and message body to the transaction.
|
|
transaction->called(calledParty);
|
|
transaction->message(body,strlen(body));
|
|
|
|
// Step 2 -- Send the message to the server.
|
|
transaction->MOSMSSendMESSAGE(address,gConfig.getStr("SIP.Local.IP").c_str(),contentType);
|
|
|
|
// Step 3 -- Wait for OK or ACCEPTED.
|
|
SIPState state = transaction->MOSMSWaitForSubmit();
|
|
|
|
// Step 4 -- Done
|
|
return state==SIP::Cleared;
|
|
}
|
|
|
|
/**
|
|
Process the RPDU.
|
|
@param mobileID The sender's IMSI.
|
|
@param RPDU The RPDU to process.
|
|
@return true if successful.
|
|
*/
|
|
bool handleRPDU(TransactionEntry *transaction, const RLFrame& RPDU)
|
|
{
|
|
LOG(DEBUG) << "SMS: handleRPDU MTI=" << RPDU.MTI();
|
|
switch ((RPMessage::MessageType)RPDU.MTI()) {
|
|
case RPMessage::Data: {
|
|
string contentType = gConfig.getStr("SMS.MIMEType");
|
|
ostringstream body;
|
|
|
|
if (contentType == "text/plain") {
|
|
// TODO: Clean this mess up!
|
|
RPData data;
|
|
data.parse(RPDU);
|
|
TLSubmit submit;
|
|
submit.parse(data.TPDU());
|
|
|
|
body << submit.UD().decode();
|
|
} else if (contentType == "application/vnd.3gpp.sms") {
|
|
RPDU.hex(body);
|
|
} else {
|
|
LOG(ALERT) << "\"" << contentType << "\" is not a valid SMS payload type";
|
|
}
|
|
const char* address = NULL;
|
|
if (gConfig.defines("SIP.SMSC")) address = gConfig.getStr("SIP.SMSC").c_str();
|
|
|
|
/* The SMSC is not defined, we are using an older version */
|
|
if (address == NULL) {
|
|
RPData data;
|
|
data.parse(RPDU);
|
|
TLSubmit submit;
|
|
submit.parse(data.TPDU());
|
|
|
|
address = submit.DA().digits();
|
|
}
|
|
return sendSIP(transaction, address, body.str().data(),contentType.c_str());
|
|
}
|
|
case RPMessage::Ack:
|
|
case RPMessage::SMMA:
|
|
return true;
|
|
case RPMessage::Error:
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
void Control::MOSMSController(const GSM::L3CMServiceRequest *req, UMTS::DCCHLogicalChannel *LCH)
|
|
{
|
|
assert(req);
|
|
assert(req->serviceType().type() == GSM::L3CMServiceType::ShortMessage);
|
|
assert(LCH);
|
|
assert(LCH->type() != UMTS::DCCHType);
|
|
|
|
LOG(INFO) << "MOSMS, req " << *req;
|
|
|
|
// If we got a TMSI, find the IMSI.
|
|
// Note that this is a copy, not a reference.
|
|
GSM::L3MobileIdentity mobileID = req->mobileID();
|
|
resolveIMSI(mobileID,LCH);
|
|
|
|
// Create a transaction record.
|
|
TransactionEntry *transaction = new TransactionEntry(gConfig.getStr("SIP.Proxy.SMS").c_str(),mobileID,LCH);
|
|
gTransactionTable.add(transaction);
|
|
LOG(DEBUG) << "MOSMS: transaction: " << *transaction;
|
|
|
|
// See GSM 04.11 Arrow Diagram A5 for the transaction
|
|
// Step 1 MS->Network CP-DATA containing RP-DATA
|
|
// Step 2 Network->MS CP-ACK
|
|
// Step 3 Network->MS CP-DATA containing RP-ACK
|
|
// Step 4 MS->Network CP-ACK
|
|
|
|
// LAPDm operation, from GSM 04.11, Annex F:
|
|
// """
|
|
// Case A: Mobile originating short message transfer, no parallel call:
|
|
// The mobile station side will initiate SAPI 3 establishment by a SABM command
|
|
// on the DCCH after the cipher mode has been set. If no hand over occurs, the
|
|
// SAPI 3 link will stay up until the last CP-ACK is received by the MSC, and
|
|
// the clearing procedure is invoked.
|
|
// """
|
|
|
|
// FIXME: check provisioning
|
|
|
|
// Let the phone know we're going ahead with the transaction.
|
|
LOG(INFO) << "sending CMServiceAccept";
|
|
LCH->send(GSM::L3CMServiceAccept());
|
|
// Wait for SAP3 to connect.
|
|
// The first read on SAP3 is the ESTABLISH primitive.
|
|
delete getFrameSMS(LCH,GSM::ESTABLISH);
|
|
|
|
// Step 1
|
|
// Now get the first message.
|
|
// Should be CP-DATA, containing RP-DATA.
|
|
GSM::L3Frame *CM = getFrameSMS(LCH);
|
|
LOG(DEBUG) << "data from MS " << *CM;
|
|
if (CM->MTI()!=CPMessage::DATA) {
|
|
LOG(NOTICE) << "unexpected SMS CP message with TI=" << CM->MTI();
|
|
throw UnexpectedMessage();
|
|
}
|
|
unsigned L3TI = CM->TI() | 0x08;
|
|
transaction->L3TI(L3TI);
|
|
|
|
// Step 2
|
|
// Respond with CP-ACK.
|
|
// This just means that we got the message.
|
|
LOG(INFO) << "sending CPAck";
|
|
LCH->send(CPAck(L3TI),3);
|
|
|
|
// Parse the message in CM and process RP part.
|
|
// This is where we actually parse the message and send it out.
|
|
// FIXME -- We need to set the message ref correctly,
|
|
// even if the parsing fails.
|
|
// The compiler gives a warning here. Let it. It will remind someone to fix it.
|
|
unsigned ref;
|
|
bool success = false;
|
|
try {
|
|
CPData data;
|
|
data.parse(*CM);
|
|
delete CM;
|
|
LOG(INFO) << "CPData " << data;
|
|
// Transfer out the RPDU -> TPDU -> delivery.
|
|
ref = data.RPDU().reference();
|
|
// This handler invokes higher-layer parsers, too.
|
|
success = handleRPDU(transaction,data.RPDU());
|
|
}
|
|
catch (SMSReadError) {
|
|
LOG(WARNING) << "SMS parsing failed (above L3)";
|
|
// Cause 95, "semantically incorrect message".
|
|
LCH->send(CPData(L3TI,RPError(95,ref)),3);
|
|
throw UnexpectedMessage();
|
|
}
|
|
catch (GSM::L3ReadError) {
|
|
LOG(WARNING) << "SMS parsing failed (in L3)";
|
|
throw UnsupportedMessage();
|
|
}
|
|
|
|
// Step 3
|
|
// Send CP-DATA containing RP-ACK and message reference.
|
|
if (success) {
|
|
LOG(INFO) << "sending RPAck in CPData";
|
|
LCH->send(CPData(L3TI,RPAck(ref)),3);
|
|
} else {
|
|
LOG(INFO) << "sending RPError in CPData";
|
|
// Cause 127 is "internetworking error, unspecified".
|
|
// See GSM 04.11 Table 8.4.
|
|
LCH->send(CPData(L3TI,RPError(127,ref)),3);
|
|
}
|
|
|
|
// Step 4
|
|
// Get CP-ACK from the MS.
|
|
CM = getFrameSMS(LCH);
|
|
if (CM->MTI()!=CPMessage::ACK) {
|
|
LOG(NOTICE) << "unexpected SMS CP message with TI=" << CM->MTI();
|
|
throw UnexpectedMessage();
|
|
}
|
|
LOG(DEBUG) << "ack from MS: " << *CM;
|
|
CPAck ack;
|
|
ack.parse(*CM);
|
|
LOG(INFO) << "CPAck " << ack;
|
|
|
|
// Done.
|
|
LCH->send(GSM::L3ChannelRelease());
|
|
gTransactionTable.remove(transaction);
|
|
LOG(INFO) << "closing the Um channel";
|
|
}
|
|
|
|
|
|
|
|
|
|
bool Control::deliverSMSToMS(const char *callingPartyDigits, const char* message, const char* contentType, unsigned L3TI, UMTS::DCCHLogicalChannel *LCH)
|
|
{
|
|
if (!LCH->multiframeMode(3)) {
|
|
// Start ABM in SAP3.
|
|
LCH->send(GSM::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,GSM::ESTABLISH);
|
|
}
|
|
|
|
#if 0
|
|
// HACK -- Check for "Easter Eggs"
|
|
// TL-PID
|
|
unsigned TLPID=0;
|
|
if (strncmp(message,"#!TLPID",7)==0) sscanf(message,"#!TLPID%d",&TLPID);
|
|
|
|
// Step 1
|
|
// Send the first message.
|
|
// CP-DATA, containing RP-DATA.
|
|
unsigned reference = random() % 255;
|
|
CPData deliver(L3TI,
|
|
RPData(reference,
|
|
RPAddress(gConfig.getStr("SMS.FakeSrcSMSC").c_str()),
|
|
TLDeliver(callingPartyDigits,message,TLPID)));
|
|
#else
|
|
// TODO: Read MIME Type from smqueue!!
|
|
unsigned reference = random() % 255;
|
|
RPData rp_data;
|
|
|
|
if (strncmp(contentType,"text/plain",10)==0) {
|
|
rp_data = RPData(reference,
|
|
RPAddress(gConfig.getStr("SMS.FakeSrcSMSC").c_str()),
|
|
TLDeliver(callingPartyDigits,message,0));
|
|
} else if (strncmp(contentType,"application/vnd.3gpp.sms",24)==0) {
|
|
BitVector RPDUbits(strlen(message)*4);
|
|
if (!RPDUbits.unhex(message)) {
|
|
LOG(WARNING) << "Hex string parsing failed (in incoming SIP MESSAGE)";
|
|
throw UnexpectedMessage();
|
|
}
|
|
|
|
try {
|
|
RLFrame RPDU(RPDUbits);
|
|
LOG(DEBUG) << "SMS RPDU: " << RPDU;
|
|
|
|
rp_data.parse(RPDU);
|
|
LOG(DEBUG) << "SMS RP-DATA " << rp_data;
|
|
}
|
|
catch (SMSReadError) {
|
|
LOG(WARNING) << "SMS parsing failed (above L3)";
|
|
// Cause 95, "semantically incorrect message".
|
|
LCH->send(CPData(L3TI,RPError(95,reference)),3);
|
|
throw UnexpectedMessage();
|
|
}
|
|
catch (GSM::L3ReadError) {
|
|
LOG(WARNING) << "SMS parsing failed (in L3)";
|
|
// TODO:: send error back to the phone
|
|
throw UnsupportedMessage();
|
|
}
|
|
} else {
|
|
LOG(WARNING) << "Unsupported content type (in incoming SIP MESSAGE) -- type: " << contentType;
|
|
throw UnexpectedMessage();
|
|
}
|
|
|
|
CPData deliver(L3TI,rp_data);
|
|
|
|
#endif
|
|
|
|
// Start ABM in SAP3.
|
|
//LCH->send(GSM::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,GSM::ESTABLISH);
|
|
|
|
LOG(INFO) << "sending " << deliver;
|
|
LCH->send(deliver,3);
|
|
|
|
// Step 2
|
|
// Get the CP-ACK.
|
|
// FIXME -- Check TI.
|
|
LOG(DEBUG) << "MTSMS: waiting for CP-ACK";
|
|
GSM::L3Frame *CM = getFrameSMS(LCH);
|
|
LOG(DEBUG) << "MTSMS: ack from MS " << *CM;
|
|
if (CM->MTI()!=CPMessage::ACK) {
|
|
LOG(WARNING) << "MS rejected our RP-DATA with CP message with TI=" << CM->MTI();
|
|
throw UnexpectedMessage();
|
|
}
|
|
|
|
// Step 3
|
|
// Get CP-DATA containing RP-ACK and message reference.
|
|
LOG(DEBUG) << "MTSMS: waiting for RP-ACK";
|
|
CM = getFrameSMS(LCH);
|
|
LOG(DEBUG) << "MTSMS: data from MS " << *CM;
|
|
if (CM->MTI()!=CPMessage::DATA) {
|
|
LOG(NOTICE) << "Unexpected SMS CP message with TI=" << CM->MTI();
|
|
throw UnexpectedMessage();
|
|
}
|
|
|
|
// FIXME -- Check L3 TI.
|
|
|
|
// Parse to check for RP-ACK.
|
|
CPData data;
|
|
try {
|
|
data.parse(*CM);
|
|
delete CM;
|
|
LOG(DEBUG) << "CPData " << data;
|
|
}
|
|
catch (SMSReadError) {
|
|
LOG(WARNING) << "SMS parsing failed (above L3)";
|
|
// Cause 95, "semantically incorrect message".
|
|
LCH->send(CPError(L3TI,95),3);
|
|
throw UnexpectedMessage();
|
|
}
|
|
catch (GSM::L3ReadError) {
|
|
LOG(WARNING) << "SMS parsing failed (in L3)";
|
|
throw UnsupportedMessage();
|
|
}
|
|
|
|
// FIXME -- Check SMS reference.
|
|
|
|
bool success = true;
|
|
if (data.RPDU().MTI()!=RPMessage::Ack) {
|
|
LOG(WARNING) << "unexpected RPDU " << data.RPDU();
|
|
success = false;
|
|
}
|
|
|
|
// Step 4
|
|
// Send CP-ACK to the MS.
|
|
LOG(INFO) << "MTSMS: sending CPAck";
|
|
LCH->send(CPAck(L3TI),3);
|
|
return success;
|
|
}
|
|
|
|
|
|
|
|
|
|
void Control::MTSMSController(TransactionEntry *transaction, UMTS::DCCHLogicalChannel *LCH)
|
|
{
|
|
assert(LCH);
|
|
assert(transaction);
|
|
|
|
// See GSM 04.11 Arrow Diagram A5 for the transaction
|
|
// Step 1 Network->MS CP-DATA containing RP-DATA
|
|
// Step 2 MS->Network CP-ACK
|
|
// Step 3 MS->Network CP-DATA containing RP-ACK
|
|
// Step 4 Network->MS CP-ACK
|
|
|
|
// LAPDm operation, from GSM 04.11, Annex F:
|
|
// """
|
|
// Case B: Mobile terminating short message transfer, no parallel call:
|
|
// The network side, i.e. the BSS will initiate SAPI3 establishment by a
|
|
// SABM command on the DCCH when the first CP-Data message is received
|
|
// from the MSC. If no hand over occurs, the link will stay up until the
|
|
// MSC has given the last CP-ack and invokes the clearing procedure.
|
|
// """
|
|
|
|
|
|
// Attach the channel to the transaction and update the state.
|
|
LOG(DEBUG) << "transaction: "<< *transaction;
|
|
transaction->channel(LCH);
|
|
transaction->GSMState(GSM::SMSDelivering);
|
|
LOG(INFO) << "transaction: "<< *transaction;
|
|
|
|
bool success = deliverSMSToMS(transaction->calling().digits(),transaction->message(),
|
|
transaction->messageType(),transaction->L3TI(),LCH);
|
|
|
|
#if 0
|
|
// Close the Dm channel?
|
|
if (LCH->type()!=GSM::SACCHType) {
|
|
LCH->send(GSM::L3ChannelRelease());
|
|
LOG(INFO) << "closing the Um channel";
|
|
}
|
|
#endif
|
|
|
|
// Ack in SIP domain.
|
|
if (success) transaction->MTSMSSendOK();
|
|
|
|
// Done.
|
|
gTransactionTable.remove(transaction);
|
|
}
|
|
|
|
|
|
|
|
|
|
void Control::InCallMOSMSStarter(TransactionEntry *parallelCall)
|
|
{
|
|
UMTS::LogicalChannel *hostChan = parallelCall->channel();
|
|
assert(hostChan);
|
|
UMTS::DCCHLogicalChannel *DCCH = hostChan->DCCH();
|
|
assert(DCCH);
|
|
|
|
// Create a partial transaction record.
|
|
TransactionEntry *newTransaction = new TransactionEntry(
|
|
gConfig.getStr("SIP.Proxy.SMS").c_str(),
|
|
parallelCall->subscriber(),
|
|
DCCH);
|
|
gTransactionTable.add(newTransaction);
|
|
}
|
|
|
|
|
|
|
|
void Control::InCallMOSMSController(const CPData *cpData, TransactionEntry* transaction, UMTS::DCCHLogicalChannel *LCH)
|
|
{
|
|
LOG(INFO) << *cpData;
|
|
|
|
// FIXME -- We know this will be broken in UMTS.
|
|
|
|
// Step 1 already happened in the SACCH service loop.
|
|
// Just get the L3 TI and set the high bit since it originated in the MS.
|
|
unsigned L3TI = cpData->TI() | 0x08;
|
|
transaction->L3TI(L3TI);
|
|
|
|
// Step 2
|
|
// Respond with CP-ACK.
|
|
// This just means that we got the message.
|
|
LOG(INFO) << "sending CPAck";
|
|
LCH->send(CPAck(L3TI),3);
|
|
|
|
// Parse the message in CM and process RP part.
|
|
// This is where we actually parse the message and send it out.
|
|
// FIXME -- We need to set the message ref correctly,
|
|
// even if the parsing fails.
|
|
// The compiler gives a warning here. Let it. It will remind someone to fix it.
|
|
unsigned ref;
|
|
bool success = false;
|
|
try {
|
|
CPData data;
|
|
data.parse(*cpData);
|
|
LOG(INFO) << "CPData " << data;
|
|
// Transfer out the RPDU -> TPDU -> delivery.
|
|
ref = data.RPDU().reference();
|
|
// This handler invokes higher-layer parsers, too.
|
|
success = handleRPDU(transaction,data.RPDU());
|
|
}
|
|
catch (SMSReadError) {
|
|
LOG(WARNING) << "SMS parsing failed (above L3)";
|
|
// Cause 95, "semantically incorrect message".
|
|
LCH->send(CPData(L3TI,RPError(95,ref)),3);
|
|
throw UnexpectedMessage(transaction->ID());
|
|
}
|
|
catch (GSM::L3ReadError) {
|
|
LOG(WARNING) << "SMS parsing failed (in L3)";
|
|
throw UnsupportedMessage(transaction->ID());
|
|
}
|
|
|
|
// Step 3
|
|
// Send CP-DATA containing RP-ACK and message reference.
|
|
if (success) {
|
|
LOG(INFO) << "sending RPAck in CPData";
|
|
LCH->send(CPData(L3TI,RPAck(ref)),3);
|
|
} else {
|
|
LOG(INFO) << "sending RPError in CPData";
|
|
// Cause 127 is "internetworking error, unspecified".
|
|
// See GSM 04.11 Table 8.4.
|
|
LCH->send(CPData(L3TI,RPError(127,ref)),3);
|
|
}
|
|
|
|
// Step 4
|
|
// Get CP-ACK from the MS.
|
|
GSM::L3Frame* CM = getFrameSMS(LCH);
|
|
if (CM->MTI()!=CPMessage::ACK) {
|
|
LOG(NOTICE) << "unexpected SMS CP message with MTI=" << CM->MTI() << " " << *CM;
|
|
throw UnexpectedMessage(transaction->ID());
|
|
}
|
|
LOG(DEBUG) << "ack from MS: " << *CM;
|
|
CPAck ack;
|
|
ack.parse(*CM);
|
|
LOG(INFO) << "CPAck " << ack;
|
|
|
|
gTransactionTable.remove(transaction);
|
|
}
|
|
|
|
// vim: ts=4 sw=4
|