600 lines
17 KiB
C++
600 lines
17 KiB
C++
/**@file SMS Control (L3), GSM 03.40, 04.11. */
|
|
/*
|
|
* Copyright 2008, 2009 Free Software Foundation, Inc.
|
|
*
|
|
* This software is distributed under the terms of the GNU Affero Public License.
|
|
* See the COPYING file in the main directory for details.
|
|
*
|
|
* This use of this software may be subject to additional restrictions.
|
|
* See the LEGAL file in the main directory for details.
|
|
|
|
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/>.
|
|
|
|
*/
|
|
|
|
|
|
/*
|
|
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 <GSMLogicalChannel.h>
|
|
#include <GSML3MMMessages.h>
|
|
#include "ControlCommon.h"
|
|
#include <Regexp.h>
|
|
|
|
|
|
using namespace std;
|
|
using namespace GSM;
|
|
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 "CollectMSInfo.h"
|
|
|
|
/**
|
|
Read an L3Frame from SAP3.
|
|
Throw exception on failure. Will NOT return a NULL pointer.
|
|
*/
|
|
L3Frame* getFrameSMS(LogicalChannel *LCH, GSM::Primitive primitive=DATA)
|
|
{
|
|
L3Frame *retVal = LCH->recv(LCH->N200()*T200ms,3);
|
|
if (!retVal) {
|
|
throw ChannelReadTimeout();
|
|
}
|
|
LOG(DEBUG) << "getFrameSMS: " << *retVal;
|
|
if (retVal->primitive() != primitive) {
|
|
LOG(NOTICE) << "unexpected primitive, expecting " << primitive << ", got " << *retVal;
|
|
throw UnexpectedPrimitive();
|
|
}
|
|
if ((retVal->primitive() == DATA) && (retVal->PD() != L3SMSPD)) {
|
|
LOG(NOTICE) << "unexpected (non-SMS) protocol in frame " << retVal;
|
|
throw UnexpectedMessage();
|
|
}
|
|
return retVal;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**@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:
|
|
// 1 -- Create a transaction record.
|
|
// 2 -- Send it to the server.
|
|
// 3 -- Wait for response or timeout.
|
|
// 4 -- Return true for OK or ACCEPTED, false otherwise.
|
|
|
|
// Form the TLAddress into a CalledPartyNumber for the transaction.
|
|
L3CalledPartyBCDNumber calledParty(address.digits());
|
|
// Step 1 -- Create a transaction record.
|
|
TransactionEntry transaction(
|
|
mobileID,
|
|
L3CMServiceType::ShortMessage,
|
|
0, // doesn't matter
|
|
calledParty);
|
|
transaction.SIP().User(mobileID.digits());
|
|
transaction.Q931State(TransactionEntry::SMSSubmitting);
|
|
gTransactionTable.add(transaction);
|
|
LOG(DEBUG) << "MOSMS: transaction: " << transaction;
|
|
|
|
// Step 2 -- Send the message to the server.
|
|
transaction.SIP().MOSMSSendMESSAGE(address.digits(),gConfig.getStr("SIP.IP"),body);
|
|
|
|
// Step 3 -- Wait for OK or ACCEPTED.
|
|
SIPState state = transaction.SIP().MOSMSWaitForSubmit();
|
|
|
|
// Step 4 -- Done
|
|
clearTransactionHistory(transaction);
|
|
return state==SIP::Cleared;
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Send a TPDU through whatever gateway is available. */
|
|
bool submitSMS(const L3MobileIdentity& mobileID, const TLSubmit& submit)
|
|
{
|
|
LOG(INFO) << "from " << mobileID;
|
|
//const TLAddress& address = submit.DA();
|
|
const char* body = submit.UD().data();
|
|
|
|
// Check for direct e-mail address at start of message body.
|
|
// FIXME -- This doesn't really follow the spec. See GSM 03.40 3.8.
|
|
static const Regexp emailAddress("^[[:graph:]]+@[[:graph:]]+ ");
|
|
if (emailAddress.match(body)) {
|
|
// FIXME -- Get the sender's E.164 to put in the subject line.
|
|
char bodyCopy[strlen(body)+2];
|
|
strcpy(bodyCopy,body);
|
|
char* SMTPAddress = bodyCopy;
|
|
char* term = strchr(bodyCopy,' ');
|
|
// If term's NULL, the regexp is broken.
|
|
assert(term);
|
|
*term = '\0';
|
|
char* SMTPPayload = term+1;
|
|
LOG(INFO) << "sending SMTP to " << SMTPAddress << ": " << SMTPPayload;
|
|
if (SMTPPayload) return sendEMail(SMTPAddress,SMTPPayload,"from OpenBTS gateway");
|
|
else return sendEMail(SMTPAddress,"(empty)","from OpenBTS gateway");
|
|
}
|
|
|
|
// Send to smsd or HTTP gateway, depending on what's defined in the conig.
|
|
return sendToNumericAddress(mobileID,submit);
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
Process the incoming TPDU.
|
|
@param mobileID Sender's IMSI.
|
|
@param TPDU The TPDU (duh).
|
|
@return true if successful
|
|
*/
|
|
bool handleTPDU(const L3MobileIdentity& mobileID, const TLFrame& TPDU)
|
|
{
|
|
LOG(DEBUG) << "SMS: handleTPDU MTI=" << TPDU.MTI();
|
|
// Handle just the uplink cases.
|
|
switch ((TLMessage::MessageType)TPDU.MTI()) {
|
|
case TLMessage::DELIVER_REPORT:
|
|
case TLMessage::STATUS_REPORT:
|
|
return false;
|
|
case TLMessage::SUBMIT: {
|
|
TLSubmit submit;
|
|
submit.parse(TPDU);
|
|
LOG(INFO) << "SMS SMS-SUBMIT " << submit;
|
|
return submitSMS(mobileID,submit);
|
|
}
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
Process the RPDU.
|
|
@param mobileID The sender's IMSI.
|
|
@param RPDU The RPDU to process.
|
|
@return true if successful.
|
|
*/
|
|
bool handleRPDU(const L3MobileIdentity& mobileID, const RLFrame& RPDU)
|
|
{
|
|
LOG(DEBUG) << "SMS: handleRPDU MTI=" << RPDU.MTI();
|
|
switch ((RPMessage::MessageType)RPDU.MTI()) {
|
|
case RPMessage::Data: {
|
|
RPData data;
|
|
data.parse(RPDU);
|
|
LOG(INFO) << "SMS RP-DATA " << data;
|
|
return handleTPDU(mobileID,data.TPDU());
|
|
}
|
|
case RPMessage::Ack:
|
|
case RPMessage::SMMA:
|
|
return true;
|
|
case RPMessage::Error:
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
void Control::MOSMSController(const L3CMServiceRequest *req,
|
|
LogicalChannel *LCH)
|
|
{
|
|
assert(req);
|
|
assert(LCH);
|
|
|
|
LOG(INFO) << "MOSMS, req " << *req;
|
|
|
|
// If we got a TMSI, find the IMSI.
|
|
// Note that this is a copy, not a reference.
|
|
L3MobileIdentity mobileIdentity = req->mobileIdentity();
|
|
resolveIMSI(mobileIdentity,LCH);
|
|
|
|
|
|
// 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 SDCCH 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(L3CMServiceAccept());
|
|
// Wait for SAP3 to connect.
|
|
// The first read on SAP3 is the ESTABLISH primitive.
|
|
delete getFrameSMS(LCH,ESTABLISH);
|
|
|
|
// Step 1
|
|
// Now get the first message.
|
|
// Should be CP-DATA, containing RP-DATA.
|
|
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 TI = CM->TIValue();
|
|
|
|
// Step 2
|
|
// Respond with CP-ACK.
|
|
// This just means that we got the message.
|
|
LOG(INFO) << "sending CPAck";
|
|
LCH->send(CPAck(1,TI),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(mobileIdentity,data.RPDU());
|
|
}
|
|
catch (SMSReadError) {
|
|
LOG(WARN) << "SMS parsing failed (above L3)";
|
|
// Cause 95, "semantically incorrect message".
|
|
LCH->send(CPData(1,TI,RPError(95,ref)),3);
|
|
throw UnexpectedMessage();
|
|
}
|
|
catch (L3ReadError) {
|
|
LOG(WARN) << "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(1,TI,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(1,TI,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;
|
|
|
|
// RRLP Here if enabled
|
|
if (gConfig.defines("GSM.RRLP") && gConfig.getNum("GSM.RRLP") == 1 &&
|
|
gConfig.defines("RRLP.LocationUpdate") && gConfig.getNum("RRLP.LocationUpdate") == 1 /* RRLP? */)
|
|
{
|
|
Timeval start;
|
|
RRLP::collectMSInfo(mobileIdentity, LCH, true /* DO RRLP */);
|
|
LOG(INFO) << "submitSMS with RRLP took " << start.elapsed() << " for IMSI " << mobileIdentity;
|
|
}
|
|
|
|
// Done.
|
|
LOG(INFO) << "closing";
|
|
LCH->send(L3ChannelRelease());
|
|
}
|
|
|
|
|
|
|
|
|
|
bool Control::deliverSMSToMS(const char *callingPartyDigits, const char* message, unsigned TI, LogicalChannel *LCH)
|
|
{
|
|
// This function is used to deliver messages that originate INSIDE the BTS.
|
|
// For the normal SMS delivery, see MTSMSController.
|
|
|
|
// Start ABM in SAP3.
|
|
LCH->send(ESTABLISH,3);
|
|
// Wait for SAP3 ABM to connect.
|
|
// The next read on SAP3 should the ESTABLISH primitive.
|
|
// This won't return NULL. It will throw an exception if it fails.
|
|
delete getFrameSMS(LCH,ESTABLISH);
|
|
|
|
// HACK
|
|
// Check for "Easter Eggs"
|
|
// TL-PID
|
|
// See 03.40 9.2.3.9.
|
|
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(0,TI,
|
|
RPData(reference,
|
|
RPAddress(gConfig.getStr("SMS.FakeSrcSMSC")),
|
|
TLDeliver(callingPartyDigits,message,TLPID)));
|
|
LOG(INFO) << "sending " << deliver;
|
|
LCH->send(deliver,3);
|
|
|
|
// Step 2
|
|
// Get the CP-ACK.
|
|
// FIXME -- Check TI.
|
|
LOG(DEBUG) << "MTSMS: waiting for CP-ACK";
|
|
L3Frame *CM = getFrameSMS(LCH);
|
|
LOG(DEBUG) << "MTSMS: ack from MS " << *CM;
|
|
if (CM->MTI()!=CPMessage::ACK) {
|
|
LOG(WARN) << "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 TI.
|
|
|
|
// Parse to check for RP-ACK.
|
|
CPData data;
|
|
try {
|
|
data.parse(*CM);
|
|
delete CM;
|
|
LOG(DEBUG) << "CPData " << data;
|
|
}
|
|
catch (SMSReadError) {
|
|
LOG(WARN) << "SMS parsing failed (above L3)";
|
|
// Cause 95, "semantically incorrect message".
|
|
LCH->send(CPError(0,TI,95),3);
|
|
throw UnexpectedMessage();
|
|
}
|
|
catch (L3ReadError) {
|
|
LOG(WARN) << "SMS parsing failed (in L3)";
|
|
throw UnsupportedMessage();
|
|
}
|
|
|
|
// FIXME -- Check SMS reference.
|
|
|
|
bool success = true;
|
|
if (data.RPDU().MTI()!=RPMessage::Ack) {
|
|
LOG(WARN) << "unexpected RPDU " << data.RPDU();
|
|
success = false;
|
|
}
|
|
|
|
// Step 4
|
|
// Send CP-ACK to the MS.
|
|
LOG(INFO) << "MTSMS: sending CPAck";
|
|
LCH->send(CPAck(0,TI),3);
|
|
return success;
|
|
}
|
|
|
|
|
|
|
|
// Some utils for the RRLP hack below
|
|
int hexchr2int(char c)
|
|
{
|
|
if (c >= '0' && c <= '9')
|
|
return c - '0';
|
|
if (c >= 'a' && c <= 'z')
|
|
return c - 'a' + 10;
|
|
if (c >= 'A' && c <= 'Z')
|
|
return c - 'A' + 10;
|
|
return -10000;
|
|
}
|
|
|
|
BitVector hex2bitvector(const char* s)
|
|
{
|
|
BitVector ret(strlen(s)*4);
|
|
size_t write_pos = 0;
|
|
for (;*s != 0 && *(s+1) != 0; s+= 2) {
|
|
ret.writeField(write_pos, hexchr2int(*s), 4);
|
|
ret.writeField(write_pos, hexchr2int(*(s+1)), 4);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
|
|
void Control::MTSMSController(TransactionEntry& transaction,
|
|
LogicalChannel *LCH)
|
|
{
|
|
assert(LCH);
|
|
|
|
// HACK: At this point if the message starts with "RRLP" then we don't do SMS at all,
|
|
// but instead to an RRLP transaction over the already allocated LogicalChannel.
|
|
const char* m = transaction.message(); // NOTE - not very nice, my way of checking.
|
|
if ((strlen(m) > 4) && (std::string("RRLP") == std::string(m, m+4))) {
|
|
BitVector rrlp_position_request = hex2bitvector(transaction.message() + 4);
|
|
LOG(INFO) << "MTSMS: Sending RRLP";
|
|
// TODO - how to get mobID here?
|
|
L3MobileIdentity mobID = L3MobileIdentity("000000000000000");
|
|
RRLP::PositionResult pr = GSM::RRLP::doRRLPQuery(mobID, LCH, rrlp_position_request);
|
|
if (pr.mValid) // in this case we only want to log the results which contain lat/lon
|
|
logMSInfo(LCH, pr, mobID);
|
|
LOG(INFO) << "MTSMS: Closing channel after RRLP";
|
|
LCH->send(L3ChannelRelease());
|
|
clearTransactionHistory(transaction);
|
|
return;
|
|
}
|
|
|
|
// 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 SDCCH 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.
|
|
// """
|
|
|
|
LOG(INFO) << "MTSMS: transaction: "<< transaction;
|
|
LCH->transactionID(transaction.ID());
|
|
SIPEngine& engine = transaction.SIP();
|
|
|
|
// Update transaction state.
|
|
transaction.Q931State(TransactionEntry::SMSDelivering);
|
|
gTransactionTable.update(transaction);
|
|
|
|
bool success = deliverSMSToMS(transaction.calling().digits(),transaction.message(),random()%7,LCH);
|
|
|
|
// Close the Dm channel.
|
|
LOG(INFO) << "MTSMS: closing";
|
|
LCH->send(L3ChannelRelease());
|
|
|
|
// Ack in SIP domain and update transaction state.
|
|
if (success) {
|
|
engine.MTSMSSendOK();
|
|
clearTransactionHistory(transaction);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// vim: ts=4 sw=4
|