mirror of
https://github.com/RangeNetworks/openbts.git
synced 2025-11-03 05:13:16 +00:00
Putting the actual OpenBTS P2.8 source code into the public SVN branch.
git-svn-id: http://wush.net/svn/range/software/public/openbts/trunk@2242 19bc5d8c-e614-43d4-8b26-e1612bc8e597
This commit is contained in:
1054
Control/CallControl.cpp
Normal file
1054
Control/CallControl.cpp
Normal file
File diff suppressed because it is too large
Load Diff
67
Control/CallControl.h
Normal file
67
Control/CallControl.h
Normal file
@@ -0,0 +1,67 @@
|
||||
/**@file GSM/SIP Call Control -- GSM 04.08, ISDN ITU-T Q.931, SIP IETF RFC-3261, RTP IETF RFC-3550. */
|
||||
/*
|
||||
* Copyright 2008, 2009, 2010 Free Software Foundation, Inc.
|
||||
* Copyright 2010 Kestrel Signal Processing, Inc.
|
||||
*
|
||||
* This software is distributed under multiple licenses; see the COPYING file in the main directory for licensing information for this specific distribuion.
|
||||
*
|
||||
* This use of this software may be subject to additional restrictions.
|
||||
* See the LEGAL file in the main directory for details.
|
||||
|
||||
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.
|
||||
|
||||
*/
|
||||
|
||||
#ifndef CALLCONTROL_H
|
||||
#define CALLCONTROL_H
|
||||
|
||||
|
||||
namespace GSM {
|
||||
class LogicalChannel;
|
||||
class TCHFACCHLogicalChannel;
|
||||
class L3CMServiceRequest;
|
||||
};
|
||||
|
||||
namespace Control {
|
||||
|
||||
class TransactionEntry;
|
||||
|
||||
|
||||
|
||||
/**@name MOC */
|
||||
//@{
|
||||
/** Run the MOC to the point of alerting, doing early assignment if needed. */
|
||||
void MOCStarter(const GSM::L3CMServiceRequest*, GSM::LogicalChannel*);
|
||||
/** Complete the MOC connection. */
|
||||
void MOCController(TransactionEntry*, GSM::TCHFACCHLogicalChannel*);
|
||||
/** Set up an emergency call, assuming very early assignment. */
|
||||
void EmergencyCall(const GSM::L3CMServiceRequest*, GSM::LogicalChannel*);
|
||||
//@}
|
||||
|
||||
|
||||
/**@name MTC */
|
||||
//@{
|
||||
/** Run the MTC to the point of alerting, doing early assignment if needed. */
|
||||
void MTCStarter(TransactionEntry*, GSM::LogicalChannel*);
|
||||
/** Complete the MTC connection. */
|
||||
void MTCController(TransactionEntry*, GSM::TCHFACCHLogicalChannel*);
|
||||
//@}
|
||||
|
||||
|
||||
/**@name Test Call */
|
||||
//@{
|
||||
/** Run the test call. */
|
||||
void TestCall(TransactionEntry*, GSM::LogicalChannel*);
|
||||
//@}
|
||||
|
||||
/** Create a new transaction entry and start paging. */
|
||||
void initiateMTTransaction(TransactionEntry* transaction,
|
||||
GSM::ChannelType chanType, unsigned pageTime);
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
180
Control/ControlCommon.cpp
Normal file
180
Control/ControlCommon.cpp
Normal file
@@ -0,0 +1,180 @@
|
||||
/**@file Common-use functions for the control layer. */
|
||||
|
||||
/*
|
||||
* Copyright 2008, 2010 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/>.
|
||||
|
||||
*/
|
||||
|
||||
|
||||
#include "ControlCommon.h"
|
||||
#include "TransactionTable.h"
|
||||
|
||||
#include <GSMLogicalChannel.h>
|
||||
#include <GSML3Message.h>
|
||||
#include <GSML3CCMessages.h>
|
||||
#include <GSML3RRMessages.h>
|
||||
#include <GSML3MMMessages.h>
|
||||
#include <GSMConfig.h>
|
||||
|
||||
#include <SIPEngine.h>
|
||||
#include <SIPInterface.h>
|
||||
|
||||
#include <Logger.h>
|
||||
#undef WARNING
|
||||
|
||||
|
||||
using namespace std;
|
||||
using namespace GSM;
|
||||
using namespace Control;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// FIXME -- getMessage should return an L3Frame, not an L3Message.
|
||||
// This will mean moving all of the parsing into the control layer.
|
||||
// FIXME -- This needs an adjustable timeout.
|
||||
|
||||
L3Message* Control::getMessage(LogicalChannel *LCH, unsigned SAPI)
|
||||
{
|
||||
unsigned timeout_ms = LCH->N200() * T200ms;
|
||||
L3Frame *rcv = LCH->recv(timeout_ms,SAPI);
|
||||
if (rcv==NULL) {
|
||||
LOG(NOTICE) << "timeout";
|
||||
throw ChannelReadTimeout();
|
||||
}
|
||||
LOG(DEBUG) << "received " << *rcv;
|
||||
Primitive primitive = rcv->primitive();
|
||||
if (primitive!=DATA) {
|
||||
LOG(NOTICE) << "unexpected primitive " << primitive;
|
||||
delete rcv;
|
||||
throw UnexpectedPrimitive();
|
||||
}
|
||||
L3Message *msg = parseL3(*rcv);
|
||||
delete rcv;
|
||||
if (msg==NULL) {
|
||||
LOG(NOTICE) << "unparsed message";
|
||||
throw UnsupportedMessage();
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* Resolve a mobile ID to an IMSI and return TMSI if it is assigned. */
|
||||
unsigned Control::resolveIMSI(bool sameLAI, L3MobileIdentity& mobileID, LogicalChannel* LCH)
|
||||
{
|
||||
// Returns known or assigned TMSI.
|
||||
assert(LCH);
|
||||
LOG(DEBUG) << "resolving mobile ID " << mobileID << ", sameLAI: " << sameLAI;
|
||||
|
||||
// IMSI already? See if there's a TMSI already, too.
|
||||
if (mobileID.type()==IMSIType) return gTMSITable.TMSI(mobileID.digits());
|
||||
|
||||
// IMEI? WTF?!
|
||||
// FIXME -- Should send MM Reject, cause 0x60, "invalid mandatory information".
|
||||
if (mobileID.type()==IMEIType) throw UnexpectedMessage();
|
||||
|
||||
// Must be a TMSI.
|
||||
// Look in the table to see if it's one we assigned.
|
||||
unsigned TMSI = mobileID.TMSI();
|
||||
char* IMSI = NULL;
|
||||
if (sameLAI) IMSI = gTMSITable.IMSI(TMSI);
|
||||
if (IMSI) {
|
||||
// We assigned this TMSI already; the TMSI/IMSI pair is already in the table.
|
||||
mobileID = L3MobileIdentity(IMSI);
|
||||
LOG(DEBUG) << "resolving mobile ID (table): " << mobileID;
|
||||
free(IMSI);
|
||||
return TMSI;
|
||||
}
|
||||
// Not our TMSI.
|
||||
// Phones are not supposed to do this, but many will.
|
||||
// If the IMSI's not in the table, ASK for it.
|
||||
LCH->send(L3IdentityRequest(IMSIType));
|
||||
// FIXME -- This request times out on T3260, 12 sec. See GSM 04.08 Table 11.2.
|
||||
L3Message* msg = getMessage(LCH);
|
||||
L3IdentityResponse *resp = dynamic_cast<L3IdentityResponse*>(msg);
|
||||
if (!resp) {
|
||||
if (msg) delete msg;
|
||||
throw UnexpectedMessage();
|
||||
}
|
||||
mobileID = resp->mobileID();
|
||||
LOG(INFO) << resp;
|
||||
delete msg;
|
||||
LOG(DEBUG) << "resolving mobile ID (requested): " << mobileID;
|
||||
// FIXME -- Should send MM Reject, cause 0x60, "invalid mandatory information".
|
||||
if (mobileID.type()!=IMSIType) throw UnexpectedMessage();
|
||||
// Return 0 to indicate that we have not yet assigned our own TMSI for this phone.
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* Resolve a mobile ID to an IMSI. */
|
||||
void Control::resolveIMSI(L3MobileIdentity& mobileIdentity, LogicalChannel* LCH)
|
||||
{
|
||||
// Are we done already?
|
||||
if (mobileIdentity.type()==IMSIType) return;
|
||||
|
||||
// If we got a TMSI, find the IMSI.
|
||||
if (mobileIdentity.type()==TMSIType) {
|
||||
char *IMSI = gTMSITable.IMSI(mobileIdentity.TMSI());
|
||||
if (IMSI) mobileIdentity = L3MobileIdentity(IMSI);
|
||||
free(IMSI);
|
||||
}
|
||||
|
||||
// Still no IMSI? Ask for one.
|
||||
if (mobileIdentity.type()!=IMSIType) {
|
||||
LOG(NOTICE) << "MOC with no IMSI or valid TMSI. Reqesting IMSI.";
|
||||
LCH->send(L3IdentityRequest(IMSIType));
|
||||
// FIXME -- This request times out on T3260, 12 sec. See GSM 04.08 Table 11.2.
|
||||
L3Message* msg = getMessage(LCH);
|
||||
L3IdentityResponse *resp = dynamic_cast<L3IdentityResponse*>(msg);
|
||||
if (!resp) {
|
||||
if (msg) delete msg;
|
||||
throw UnexpectedMessage();
|
||||
}
|
||||
mobileIdentity = resp->mobileID();
|
||||
delete msg;
|
||||
}
|
||||
|
||||
// Still no IMSI??
|
||||
if (mobileIdentity.type()!=IMSIType) {
|
||||
// FIXME -- This is quick-and-dirty, not following GSM 04.08 5.
|
||||
LOG(WARNING) << "MOC setup with no IMSI";
|
||||
// Cause 0x60 "Invalid mandatory information"
|
||||
LCH->send(L3CMServiceReject(L3RejectCause(0x60)));
|
||||
LCH->send(L3ChannelRelease());
|
||||
// The SIP side and transaction record don't exist yet.
|
||||
// So we're done.
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// vim: ts=4 sw=4
|
||||
219
Control/ControlCommon.h
Normal file
219
Control/ControlCommon.h
Normal file
@@ -0,0 +1,219 @@
|
||||
/**@file Declarations for common-use control-layer functions. */
|
||||
/*
|
||||
* Copyright 2008, 2009, 2010 Free Software Foundation, Inc.
|
||||
* Copyright 2010 Kestrel Signal Processing, Inc.
|
||||
* Copyright 2011 Range Networks, Inc.
|
||||
*
|
||||
* This software is distributed under multiple licenses;
|
||||
* see the COPYING file in the main directory for licensing
|
||||
* information for this specific distribuion.
|
||||
*
|
||||
* This use of this software may be subject to additional restrictions.
|
||||
* See the LEGAL file in the main directory for details.
|
||||
|
||||
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.
|
||||
|
||||
*/
|
||||
|
||||
|
||||
|
||||
#ifndef CONTROLCOMMON_H
|
||||
#define CONTROLCOMMON_H
|
||||
|
||||
|
||||
#include <stdio.h>
|
||||
#include <list>
|
||||
|
||||
#include <Logger.h>
|
||||
#include <Interthread.h>
|
||||
#include <Timeval.h>
|
||||
|
||||
|
||||
#include <GSML3CommonElements.h>
|
||||
#include <GSML3MMElements.h>
|
||||
#include <GSML3CCElements.h>
|
||||
#include <GSML3RRMessages.h>
|
||||
#include <SIPEngine.h>
|
||||
|
||||
#include "TMSITable.h"
|
||||
|
||||
|
||||
// Enough forward refs to prevent "kitchen sick" includes and circularity.
|
||||
|
||||
namespace GSM {
|
||||
class L3Message;
|
||||
class LogicalChannel;
|
||||
class SDCCHLogicalChannel;
|
||||
class SACCHLogicalChannel;
|
||||
class TCHFACCHLogicalChannel;
|
||||
class L3CMServiceRequest;
|
||||
};
|
||||
|
||||
|
||||
/**@namespace Control This namepace is for use by the control layer. */
|
||||
namespace Control {
|
||||
|
||||
class TransactionEntry;
|
||||
class TransactionTable;
|
||||
|
||||
/**@name Call control time-out values (in ms) from ITU-T Q.931 Table 9-1 and GSM 04.08 Table 11.4. */
|
||||
//@{
|
||||
#ifndef RACETEST
|
||||
const unsigned T301ms=60000; ///< recv ALERT --> recv CONN
|
||||
const unsigned T302ms=12000; ///< send SETUP ACK --> any progress
|
||||
const unsigned T303ms=10000; ///< send SETUP --> recv CALL CONF or REL COMP
|
||||
const unsigned T304ms=20000; ///< recv SETUP ACK --> any progress
|
||||
const unsigned T305ms=30000; ///< send DISC --> recv REL or DISC
|
||||
const unsigned T308ms=30000; ///< send REL --> rev REL or REL COMP
|
||||
const unsigned T310ms=30000; ///< recv CALL CONF --> recv ALERT, CONN, or DISC
|
||||
const unsigned T313ms=30000; ///< send CONNECT --> recv CONNECT ACK
|
||||
#else
|
||||
// These are reduced values to force testing of poor network behavior.
|
||||
const unsigned T301ms=18000; ///< recv ALERT --> recv CONN
|
||||
const unsigned T302ms=1200; ///< send SETUP ACK --> any progress
|
||||
const unsigned T303ms=400; ///< send SETUP --> recv CALL CONF or REL COMP
|
||||
const unsigned T304ms=2000; ///< recv SETUP ACK --> any progress
|
||||
const unsigned T305ms=3000; ///< send DISC --> recv REL or DISC
|
||||
const unsigned T308ms=3000; ///< send REL --> rev REL or REL COMP
|
||||
const unsigned T310ms=3000; ///< recv CALL CONF --> recv ALERT, CONN, or DISC
|
||||
const unsigned T313ms=3000; ///< send CONNECT --> recv CONNECT ACK
|
||||
#endif
|
||||
//@}
|
||||
|
||||
|
||||
|
||||
|
||||
/**@name Common-use functions from the control layer. */
|
||||
//@{
|
||||
|
||||
/**
|
||||
Get a message from a LogicalChannel.
|
||||
Close the channel with abnormal release on timeout.
|
||||
Caller must delete the returned pointer.
|
||||
Throws ChannelReadTimeout, UnexpecedPrimitive or UnsupportedMessage on timeout.
|
||||
@param LCH The channel to receive on.
|
||||
@param SAPI The service access point.
|
||||
@return Pointer to message.
|
||||
*/
|
||||
// FIXME -- This needs an adjustable timeout.
|
||||
GSM::L3Message* getMessage(GSM::LogicalChannel* LCH, unsigned SAPI=0);
|
||||
|
||||
|
||||
//@}
|
||||
|
||||
|
||||
/**@name Dispatch controllers for specific channel types. */
|
||||
//@{
|
||||
void FACCHDispatcher(GSM::TCHFACCHLogicalChannel *TCHFACCH);
|
||||
void SDCCHDispatcher(GSM::SDCCHLogicalChannel *SDCCH);
|
||||
void DCCHDispatcher(GSM::LogicalChannel *DCCH);
|
||||
//@}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
Resolve a mobile ID to an IMSI.
|
||||
Returns TMSI, if it is already in the TMSITable.
|
||||
@param sameLAI True if the mobileID is known to have come from this LAI.
|
||||
@param mobID A mobile ID, that may be modified by the function.
|
||||
@param LCH The Dm channel to the mobile.
|
||||
@return A TMSI value from the TMSITable or zero if non found.
|
||||
*/
|
||||
unsigned resolveIMSI(bool sameLAI, GSM::L3MobileIdentity& mobID, GSM::LogicalChannel* LCH);
|
||||
|
||||
/**
|
||||
Resolve a mobile ID to an IMSI.
|
||||
@param mobID A mobile ID, that may be modified by the function.
|
||||
@param LCH The Dm channel to the mobile.
|
||||
*/
|
||||
void resolveIMSI(GSM::L3MobileIdentity& mobID, GSM::LogicalChannel* LCH);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**@name Control-layer exceptions. */
|
||||
//@{
|
||||
|
||||
/**
|
||||
A control layer excpection includes a pointer to a transaction.
|
||||
The transaction might require some clean-up action, depending on the exception.
|
||||
*/
|
||||
class ControlLayerException {
|
||||
|
||||
private:
|
||||
|
||||
unsigned mTransactionID;
|
||||
|
||||
public:
|
||||
|
||||
ControlLayerException(unsigned wTransactionID=0)
|
||||
:mTransactionID(wTransactionID)
|
||||
{}
|
||||
|
||||
unsigned transactionID() { return mTransactionID; }
|
||||
};
|
||||
|
||||
/** Thrown when the control layer gets the wrong message */
|
||||
class UnexpectedMessage : public ControlLayerException {
|
||||
public:
|
||||
UnexpectedMessage(unsigned wTransactionID=0)
|
||||
:ControlLayerException(wTransactionID)
|
||||
{}
|
||||
};
|
||||
|
||||
/** Thrown when recvL3 returns NULL */
|
||||
class ChannelReadTimeout : public ControlLayerException {
|
||||
public:
|
||||
ChannelReadTimeout(unsigned wTransactionID=0)
|
||||
:ControlLayerException(wTransactionID)
|
||||
{}
|
||||
};
|
||||
|
||||
/** Thrown when L3 can't parse an incoming message */
|
||||
class UnsupportedMessage : public ControlLayerException {
|
||||
public:
|
||||
UnsupportedMessage(unsigned wTransactionID=0)
|
||||
:ControlLayerException(wTransactionID)
|
||||
{}
|
||||
};
|
||||
|
||||
/** Thrown when the control layer gets the wrong primitive */
|
||||
class UnexpectedPrimitive : public ControlLayerException {
|
||||
public:
|
||||
UnexpectedPrimitive(unsigned wTransactionID=0)
|
||||
:ControlLayerException(wTransactionID)
|
||||
{}
|
||||
};
|
||||
|
||||
/** Thrown when a T3xx expires */
|
||||
class Q931TimerExpired : public ControlLayerException {
|
||||
public:
|
||||
Q931TimerExpired(unsigned wTransactionID=0)
|
||||
:ControlLayerException(wTransactionID)
|
||||
{}
|
||||
};
|
||||
|
||||
|
||||
//@}
|
||||
|
||||
|
||||
} //Control
|
||||
|
||||
|
||||
|
||||
/**@addtogroup Globals */
|
||||
//@{
|
||||
/** A single global transaction table in the global namespace. */
|
||||
extern Control::TransactionTable gTransactionTable;
|
||||
//@}
|
||||
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
// vim: ts=4 sw=4
|
||||
192
Control/DCCHDispatch.cpp
Normal file
192
Control/DCCHDispatch.cpp
Normal file
@@ -0,0 +1,192 @@
|
||||
/**@file Idle-mode dispatcher for dedicated control channels. */
|
||||
|
||||
/*
|
||||
* Copyright 2008, 2009, 2011 Free Software Foundation, Inc.
|
||||
* Copyright 2011 Range Networks, 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/>.
|
||||
|
||||
*/
|
||||
|
||||
|
||||
|
||||
|
||||
#include "ControlCommon.h"
|
||||
#include "TransactionTable.h"
|
||||
#include "RadioResource.h"
|
||||
#include "MobilityManagement.h"
|
||||
#include <GSMLogicalChannel.h>
|
||||
#include <GSML3MMMessages.h>
|
||||
#include <GSML3RRMessages.h>
|
||||
#include <SIPUtility.h>
|
||||
#include <SIPInterface.h>
|
||||
|
||||
#include <Logger.h>
|
||||
#undef WARNING
|
||||
|
||||
using namespace std;
|
||||
using namespace GSM;
|
||||
using namespace Control;
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
Dispatch the appropriate controller for a Mobility Management message.
|
||||
@param req A pointer to the initial message.
|
||||
@param DCCH A pointer to the logical channel for the transaction.
|
||||
*/
|
||||
void DCCHDispatchMM(const L3MMMessage* req, LogicalChannel *DCCH)
|
||||
{
|
||||
assert(req);
|
||||
L3MMMessage::MessageType MTI = (L3MMMessage::MessageType)req->MTI();
|
||||
switch (MTI) {
|
||||
case L3MMMessage::LocationUpdatingRequest:
|
||||
LocationUpdatingController(dynamic_cast<const L3LocationUpdatingRequest*>(req),DCCH);
|
||||
break;
|
||||
case L3MMMessage::IMSIDetachIndication:
|
||||
IMSIDetachController(dynamic_cast<const L3IMSIDetachIndication*>(req),DCCH);
|
||||
break;
|
||||
case L3MMMessage::CMServiceRequest:
|
||||
CMServiceResponder(dynamic_cast<const L3CMServiceRequest*>(req),DCCH);
|
||||
break;
|
||||
default:
|
||||
LOG(NOTICE) << "unhandled MM message " << MTI << " on " << *DCCH;
|
||||
throw UnsupportedMessage();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Dispatch the appropriate controller for a Radio Resource message.
|
||||
@param req A pointer to the initial message.
|
||||
@param DCCH A pointer to the logical channel for the transaction.
|
||||
*/
|
||||
void DCCHDispatchRR(const L3RRMessage* req, LogicalChannel *DCCH)
|
||||
{
|
||||
LOG(DEBUG) << "checking MTI"<< (L3RRMessage::MessageType)req->MTI();
|
||||
|
||||
// TODO SMS -- This needs to handle SACCH Measurement Reports.
|
||||
|
||||
assert(req);
|
||||
L3RRMessage::MessageType MTI = (L3RRMessage::MessageType)req->MTI();
|
||||
switch (MTI) {
|
||||
case L3RRMessage::PagingResponse:
|
||||
PagingResponseHandler(dynamic_cast<const L3PagingResponse*>(req),DCCH);
|
||||
break;
|
||||
case L3RRMessage::AssignmentComplete:
|
||||
AssignmentCompleteHandler(dynamic_cast<const L3AssignmentComplete*>(req),
|
||||
dynamic_cast<TCHFACCHLogicalChannel*>(DCCH));
|
||||
break;
|
||||
default:
|
||||
LOG(NOTICE) << "unhandled RR message " << MTI << " on " << *DCCH;
|
||||
throw UnsupportedMessage();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void DCCHDispatchMessage(const L3Message* msg, LogicalChannel* DCCH)
|
||||
{
|
||||
// Each protocol has it's own sub-dispatcher.
|
||||
switch (msg->PD()) {
|
||||
case L3MobilityManagementPD:
|
||||
DCCHDispatchMM(dynamic_cast<const L3MMMessage*>(msg),DCCH);
|
||||
break;
|
||||
case L3RadioResourcePD:
|
||||
DCCHDispatchRR(dynamic_cast<const L3RRMessage*>(msg),DCCH);
|
||||
break;
|
||||
default:
|
||||
LOG(NOTICE) << "unhandled protocol " << msg->PD() << " on " << *DCCH;
|
||||
throw UnsupportedMessage();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/** Example of a closed-loop, persistent-thread control function for the DCCH. */
|
||||
void Control::DCCHDispatcher(LogicalChannel *DCCH)
|
||||
{
|
||||
while (1) {
|
||||
try {
|
||||
// Wait for a transaction to start.
|
||||
LOG(DEBUG) << "waiting for " << *DCCH << " ESTABLISH";
|
||||
DCCH->waitForPrimitive(ESTABLISH);
|
||||
// Pull the first message and dispatch a new transaction.
|
||||
const L3Message *message = getMessage(DCCH);
|
||||
LOG(DEBUG) << *DCCH << " received " << *message;
|
||||
DCCHDispatchMessage(message,DCCH);
|
||||
delete message;
|
||||
}
|
||||
|
||||
// Catch the various error cases.
|
||||
|
||||
catch (ChannelReadTimeout except) {
|
||||
LOG(NOTICE) << "ChannelReadTimeout";
|
||||
// Cause 0x03 means "abnormal release, timer expired".
|
||||
DCCH->send(L3ChannelRelease(0x03));
|
||||
gTransactionTable.remove(except.transactionID());
|
||||
}
|
||||
catch (UnexpectedPrimitive except) {
|
||||
LOG(NOTICE) << "UnexpectedPrimitive";
|
||||
// Cause 0x62 means "message type not not compatible with protocol state".
|
||||
DCCH->send(L3ChannelRelease(0x62));
|
||||
if (except.transactionID()) gTransactionTable.remove(except.transactionID());
|
||||
}
|
||||
catch (UnexpectedMessage except) {
|
||||
LOG(NOTICE) << "UnexpectedMessage";
|
||||
// Cause 0x62 means "message type not not compatible with protocol state".
|
||||
DCCH->send(L3ChannelRelease(0x62));
|
||||
if (except.transactionID()) gTransactionTable.remove(except.transactionID());
|
||||
}
|
||||
catch (UnsupportedMessage except) {
|
||||
LOG(NOTICE) << "UnsupportedMessage";
|
||||
// Cause 0x61 means "message type not implemented".
|
||||
DCCH->send(L3ChannelRelease(0x61));
|
||||
if (except.transactionID()) gTransactionTable.remove(except.transactionID());
|
||||
}
|
||||
catch (Q931TimerExpired except) {
|
||||
LOG(NOTICE) << "Q.931 T3xx timer expired";
|
||||
// Cause 0x03 means "abnormal release, timer expired".
|
||||
// TODO -- Send diagnostics.
|
||||
DCCH->send(L3ChannelRelease(0x03));
|
||||
if (except.transactionID()) gTransactionTable.remove(except.transactionID());
|
||||
}
|
||||
catch (SIP::SIPTimeout except) {
|
||||
// FIXME -- The transaction ID should be an argument here.
|
||||
LOG(WARNING) << "Uncaught SIPTimeout, will leave a stray transcation";
|
||||
// Cause 0x03 means "abnormal release, timer expired".
|
||||
DCCH->send(L3ChannelRelease(0x03));
|
||||
if (except.transactionID()) gTransactionTable.remove(except.transactionID());
|
||||
}
|
||||
catch (SIP::SIPError except) {
|
||||
// FIXME -- The transaction ID should be an argument here.
|
||||
LOG(WARNING) << "Uncaught SIPError, will leave a stray transcation";
|
||||
// Cause 0x01 means "abnormal release, unspecified".
|
||||
DCCH->send(L3ChannelRelease(0x01));
|
||||
if (except.transactionID()) gTransactionTable.remove(except.transactionID());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// vim: ts=4 sw=4
|
||||
51
Control/Makefile.am
Normal file
51
Control/Makefile.am
Normal file
@@ -0,0 +1,51 @@
|
||||
#
|
||||
# Copyright 2008 Free Software Foundation, Inc.
|
||||
# Copyright 2010 Kestrel Signal Processing, Inc.
|
||||
# Copyright 2011 Range Networks, Inc.
|
||||
#
|
||||
# This software is distributed under the terms of the GNU Public License.
|
||||
# See the COPYING 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 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 General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
include $(top_srcdir)/Makefile.common
|
||||
|
||||
AM_CPPFLAGS = $(STD_DEFINES_AND_INCLUDES)
|
||||
AM_CXXFLAGS = -Wall
|
||||
|
||||
EXTRA_DIST = README.Control
|
||||
|
||||
noinst_LTLIBRARIES = libcontrol.la
|
||||
|
||||
libcontrol_la_SOURCES = \
|
||||
TransactionTable.cpp \
|
||||
TMSITable.cpp \
|
||||
CallControl.cpp \
|
||||
SMSControl.cpp \
|
||||
ControlCommon.cpp \
|
||||
MobilityManagement.cpp \
|
||||
RadioResource.cpp \
|
||||
DCCHDispatch.cpp
|
||||
|
||||
|
||||
noinst_HEADERS = \
|
||||
ControlCommon.h \
|
||||
SMSControl.h \
|
||||
TransactionTable.h \
|
||||
TMSITable.h \
|
||||
RadioResource.h \
|
||||
MobilityManagement.h \
|
||||
CallControl.h \
|
||||
TMSITable.h
|
||||
554
Control/Makefile.in
Normal file
554
Control/Makefile.in
Normal file
@@ -0,0 +1,554 @@
|
||||
# Makefile.in generated by automake 1.9.4 from Makefile.am.
|
||||
# @configure_input@
|
||||
|
||||
# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002,
|
||||
# 2003, 2004 Free Software Foundation, Inc.
|
||||
# This Makefile.in is free software; the Free Software Foundation
|
||||
# gives unlimited permission to copy and/or distribute it,
|
||||
# with or without modifications, as long as this notice is preserved.
|
||||
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
|
||||
# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
# PARTICULAR PURPOSE.
|
||||
|
||||
@SET_MAKE@
|
||||
|
||||
#
|
||||
# Copyright 2008 Free Software Foundation, Inc.
|
||||
# Copyright 2010 Kestrel Signal Processing, Inc.
|
||||
# Copyright 2011 Range Networks, Inc.
|
||||
#
|
||||
# This software is distributed under the terms of the GNU Public License.
|
||||
# See the COPYING 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 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 General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
#
|
||||
# Copyright 2008 Free Software Foundation, Inc.
|
||||
#
|
||||
# This software is distributed under the terms of the GNU Public License.
|
||||
# See the COPYING 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 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 General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
|
||||
SOURCES = $(libcontrol_la_SOURCES)
|
||||
|
||||
srcdir = @srcdir@
|
||||
top_srcdir = @top_srcdir@
|
||||
VPATH = @srcdir@
|
||||
pkgdatadir = $(datadir)/@PACKAGE@
|
||||
pkglibdir = $(libdir)/@PACKAGE@
|
||||
pkgincludedir = $(includedir)/@PACKAGE@
|
||||
top_builddir = ..
|
||||
am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
|
||||
INSTALL = @INSTALL@
|
||||
install_sh_DATA = $(install_sh) -c -m 644
|
||||
install_sh_PROGRAM = $(install_sh) -c
|
||||
install_sh_SCRIPT = $(install_sh) -c
|
||||
INSTALL_HEADER = $(INSTALL_DATA)
|
||||
transform = $(program_transform_name)
|
||||
NORMAL_INSTALL = :
|
||||
PRE_INSTALL = :
|
||||
POST_INSTALL = :
|
||||
NORMAL_UNINSTALL = :
|
||||
PRE_UNINSTALL = :
|
||||
POST_UNINSTALL = :
|
||||
build_triplet = @build@
|
||||
host_triplet = @host@
|
||||
target_triplet = @target@
|
||||
DIST_COMMON = $(noinst_HEADERS) $(srcdir)/Makefile.am \
|
||||
$(srcdir)/Makefile.in $(top_srcdir)/Makefile.common
|
||||
subdir = Control
|
||||
ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
|
||||
am__aclocal_m4_deps = $(top_srcdir)/configure.ac
|
||||
am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
|
||||
$(ACLOCAL_M4)
|
||||
mkinstalldirs = $(install_sh) -d
|
||||
CONFIG_HEADER = $(top_builddir)/config.h
|
||||
CONFIG_CLEAN_FILES =
|
||||
LTLIBRARIES = $(noinst_LTLIBRARIES)
|
||||
libcontrol_la_LIBADD =
|
||||
am_libcontrol_la_OBJECTS = TransactionTable.lo TMSITable.lo \
|
||||
CallControl.lo SMSControl.lo ControlCommon.lo \
|
||||
MobilityManagement.lo RadioResource.lo DCCHDispatch.lo
|
||||
libcontrol_la_OBJECTS = $(am_libcontrol_la_OBJECTS)
|
||||
DEFAULT_INCLUDES = -I. -I$(srcdir) -I$(top_builddir)
|
||||
depcomp = $(SHELL) $(top_srcdir)/depcomp
|
||||
am__depfiles_maybe = depfiles
|
||||
CXXCOMPILE = $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \
|
||||
$(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS)
|
||||
LTCXXCOMPILE = $(LIBTOOL) --tag=CXX --mode=compile $(CXX) $(DEFS) \
|
||||
$(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
|
||||
$(AM_CXXFLAGS) $(CXXFLAGS)
|
||||
CXXLD = $(CXX)
|
||||
CXXLINK = $(LIBTOOL) --tag=CXX --mode=link $(CXXLD) $(AM_CXXFLAGS) \
|
||||
$(CXXFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@
|
||||
SOURCES = $(libcontrol_la_SOURCES)
|
||||
DIST_SOURCES = $(libcontrol_la_SOURCES)
|
||||
HEADERS = $(noinst_HEADERS)
|
||||
ETAGS = etags
|
||||
CTAGS = ctags
|
||||
DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
|
||||
ACLOCAL = @ACLOCAL@
|
||||
AMDEP_FALSE = @AMDEP_FALSE@
|
||||
AMDEP_TRUE = @AMDEP_TRUE@
|
||||
AMTAR = @AMTAR@
|
||||
AR = @AR@
|
||||
AS = @AS@
|
||||
AUTOCONF = @AUTOCONF@
|
||||
AUTOHEADER = @AUTOHEADER@
|
||||
AUTOMAKE = @AUTOMAKE@
|
||||
AWK = @AWK@
|
||||
CC = @CC@
|
||||
CCAS = @CCAS@
|
||||
CCASFLAGS = @CCASFLAGS@
|
||||
CCDEPMODE = @CCDEPMODE@
|
||||
CFLAGS = @CFLAGS@
|
||||
CPP = @CPP@
|
||||
CPPFLAGS = @CPPFLAGS@
|
||||
CXX = @CXX@
|
||||
CXXCPP = @CXXCPP@
|
||||
CXXDEPMODE = @CXXDEPMODE@
|
||||
CXXFLAGS = @CXXFLAGS@
|
||||
CYGPATH_W = @CYGPATH_W@
|
||||
DEFS = @DEFS@
|
||||
DEPDIR = @DEPDIR@
|
||||
DLLTOOL = @DLLTOOL@
|
||||
ECHO = @ECHO@
|
||||
ECHO_C = @ECHO_C@
|
||||
ECHO_N = @ECHO_N@
|
||||
ECHO_T = @ECHO_T@
|
||||
EGREP = @EGREP@
|
||||
EXEEXT = @EXEEXT@
|
||||
F77 = @F77@
|
||||
FFLAGS = @FFLAGS@
|
||||
GREP = @GREP@
|
||||
INSTALL_DATA = @INSTALL_DATA@
|
||||
INSTALL_PROGRAM = @INSTALL_PROGRAM@
|
||||
INSTALL_SCRIPT = @INSTALL_SCRIPT@
|
||||
INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
|
||||
LDFLAGS = @LDFLAGS@
|
||||
LIBOBJS = @LIBOBJS@
|
||||
LIBS = @LIBS@
|
||||
LIBTOOL = @LIBTOOL@
|
||||
LIBUSB_CFLAGS = @LIBUSB_CFLAGS@
|
||||
LIBUSB_LIBS = @LIBUSB_LIBS@
|
||||
LN_S = @LN_S@
|
||||
LTLIBOBJS = @LTLIBOBJS@
|
||||
MAKEINFO = @MAKEINFO@
|
||||
OBJDUMP = @OBJDUMP@
|
||||
OBJEXT = @OBJEXT@
|
||||
ORTP_CFLAGS = @ORTP_CFLAGS@
|
||||
ORTP_LIBS = @ORTP_LIBS@
|
||||
OSIP_CFLAGS = @OSIP_CFLAGS@
|
||||
OSIP_LIBS = @OSIP_LIBS@
|
||||
PACKAGE = @PACKAGE@
|
||||
PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
|
||||
PACKAGE_NAME = @PACKAGE_NAME@
|
||||
PACKAGE_STRING = @PACKAGE_STRING@
|
||||
PACKAGE_TARNAME = @PACKAGE_TARNAME@
|
||||
PACKAGE_VERSION = @PACKAGE_VERSION@
|
||||
PATH_SEPARATOR = @PATH_SEPARATOR@
|
||||
PKG_CONFIG = @PKG_CONFIG@
|
||||
RANLIB = @RANLIB@
|
||||
RM_PROG = @RM_PROG@
|
||||
SET_MAKE = @SET_MAKE@
|
||||
SHELL = @SHELL@
|
||||
STRIP = @STRIP@
|
||||
VERSION = @VERSION@
|
||||
ac_ct_CC = @ac_ct_CC@
|
||||
ac_ct_CXX = @ac_ct_CXX@
|
||||
ac_ct_F77 = @ac_ct_F77@
|
||||
am__fastdepCC_FALSE = @am__fastdepCC_FALSE@
|
||||
am__fastdepCC_TRUE = @am__fastdepCC_TRUE@
|
||||
am__fastdepCXX_FALSE = @am__fastdepCXX_FALSE@
|
||||
am__fastdepCXX_TRUE = @am__fastdepCXX_TRUE@
|
||||
am__include = @am__include@
|
||||
am__leading_dot = @am__leading_dot@
|
||||
am__quote = @am__quote@
|
||||
am__tar = @am__tar@
|
||||
am__untar = @am__untar@
|
||||
bindir = @bindir@
|
||||
build = @build@
|
||||
build_alias = @build_alias@
|
||||
build_cpu = @build_cpu@
|
||||
build_os = @build_os@
|
||||
build_vendor = @build_vendor@
|
||||
datadir = @datadir@
|
||||
datarootdir = @datarootdir@
|
||||
docdir = @docdir@
|
||||
dvidir = @dvidir@
|
||||
exec_prefix = @exec_prefix@
|
||||
host = @host@
|
||||
host_alias = @host_alias@
|
||||
host_cpu = @host_cpu@
|
||||
host_os = @host_os@
|
||||
host_vendor = @host_vendor@
|
||||
htmldir = @htmldir@
|
||||
includedir = @includedir@
|
||||
infodir = @infodir@
|
||||
install_sh = @install_sh@
|
||||
libdir = @libdir@
|
||||
libexecdir = @libexecdir@
|
||||
localedir = @localedir@
|
||||
localstatedir = @localstatedir@
|
||||
mandir = @mandir@
|
||||
mkdir_p = @mkdir_p@
|
||||
oldincludedir = @oldincludedir@
|
||||
pdfdir = @pdfdir@
|
||||
prefix = @prefix@
|
||||
program_transform_name = @program_transform_name@
|
||||
psdir = @psdir@
|
||||
sbindir = @sbindir@
|
||||
sharedstatedir = @sharedstatedir@
|
||||
sysconfdir = @sysconfdir@
|
||||
target = @target@
|
||||
target_alias = @target_alias@
|
||||
target_cpu = @target_cpu@
|
||||
target_os = @target_os@
|
||||
target_vendor = @target_vendor@
|
||||
COMMON_INCLUDEDIR = $(top_srcdir)/CommonLibs
|
||||
CONTROL_INCLUDEDIR = $(top_srcdir)/Control
|
||||
GSM_INCLUDEDIR = $(top_srcdir)/GSM
|
||||
SIP_INCLUDEDIR = $(top_srcdir)/SIP
|
||||
SMS_INCLUDEDIR = $(top_srcdir)/SMS
|
||||
TRX_INCLUDEDIR = $(top_srcdir)/TRXManager
|
||||
GLOBALS_INCLUDEDIR = $(top_srcdir)/Globals
|
||||
CLI_INCLUDEDIR = $(top_srcdir)/CLI
|
||||
SQLITE_INCLUDEDIR = $(top_srcdir)/sqlite3
|
||||
SR_INCLUDEDIR = $(top_srcdir)/SR
|
||||
STD_DEFINES_AND_INCLUDES = \
|
||||
-I$(COMMON_INCLUDEDIR) \
|
||||
-I$(CONTROL_INCLUDEDIR) \
|
||||
-I$(GSM_INCLUDEDIR) \
|
||||
-I$(SIP_INCLUDEDIR) \
|
||||
-I$(SMS_INCLUDEDIR) \
|
||||
-I$(TRX_INCLUDEDIR) \
|
||||
-I$(GLOBALS_INCLUDEDIR) \
|
||||
-I$(CLI_INCLUDEDIR) \
|
||||
-I$(SR_INCLUDEDIR) \
|
||||
-I$(SQLITE_INCLUDEDIR)
|
||||
|
||||
COMMON_LA = $(top_builddir)/CommonLibs/libcommon.la
|
||||
GSM_LA = $(top_builddir)/GSM/libGSM.la
|
||||
SIP_LA = $(top_builddir)/SIP/libSIP.la
|
||||
SMS_LA = $(top_builddir)/SMS/libSMS.la
|
||||
TRX_LA = $(top_builddir)/TRXManager/libtrxmanager.la
|
||||
CONTROL_LA = $(top_builddir)/Control/libcontrol.la
|
||||
GLOBALS_LA = $(top_builddir)/Globals/libglobals.la
|
||||
CLI_LA = $(top_builddir)/CLI/libcli.la
|
||||
SR_LA = $(top_builddir)/SR/libSR.la
|
||||
SQLITE_LA = $(top_builddir)/sqlite3/libsqlite.la
|
||||
MOSTLYCLEANFILES = *~
|
||||
AM_CPPFLAGS = $(STD_DEFINES_AND_INCLUDES)
|
||||
AM_CXXFLAGS = -Wall
|
||||
EXTRA_DIST = README.Control
|
||||
noinst_LTLIBRARIES = libcontrol.la
|
||||
libcontrol_la_SOURCES = \
|
||||
TransactionTable.cpp \
|
||||
TMSITable.cpp \
|
||||
CallControl.cpp \
|
||||
SMSControl.cpp \
|
||||
ControlCommon.cpp \
|
||||
MobilityManagement.cpp \
|
||||
RadioResource.cpp \
|
||||
DCCHDispatch.cpp
|
||||
|
||||
noinst_HEADERS = \
|
||||
ControlCommon.h \
|
||||
SMSControl.h \
|
||||
TransactionTable.h \
|
||||
TMSITable.h \
|
||||
RadioResource.h \
|
||||
MobilityManagement.h \
|
||||
CallControl.h \
|
||||
TMSITable.h
|
||||
|
||||
all: all-am
|
||||
|
||||
.SUFFIXES:
|
||||
.SUFFIXES: .cpp .lo .o .obj
|
||||
$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(top_srcdir)/Makefile.common $(am__configure_deps)
|
||||
@for dep in $?; do \
|
||||
case '$(am__configure_deps)' in \
|
||||
*$$dep*) \
|
||||
cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh \
|
||||
&& exit 0; \
|
||||
exit 1;; \
|
||||
esac; \
|
||||
done; \
|
||||
echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu Control/Makefile'; \
|
||||
cd $(top_srcdir) && \
|
||||
$(AUTOMAKE) --gnu Control/Makefile
|
||||
.PRECIOUS: Makefile
|
||||
Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
|
||||
@case '$?' in \
|
||||
*config.status*) \
|
||||
cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
|
||||
*) \
|
||||
echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \
|
||||
cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \
|
||||
esac;
|
||||
|
||||
$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
|
||||
cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
|
||||
|
||||
$(top_srcdir)/configure: $(am__configure_deps)
|
||||
cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
|
||||
$(ACLOCAL_M4): $(am__aclocal_m4_deps)
|
||||
cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
|
||||
|
||||
clean-noinstLTLIBRARIES:
|
||||
-test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES)
|
||||
@list='$(noinst_LTLIBRARIES)'; for p in $$list; do \
|
||||
dir="`echo $$p | sed -e 's|/[^/]*$$||'`"; \
|
||||
test "$$dir" != "$$p" || dir=.; \
|
||||
echo "rm -f \"$${dir}/so_locations\""; \
|
||||
rm -f "$${dir}/so_locations"; \
|
||||
done
|
||||
libcontrol.la: $(libcontrol_la_OBJECTS) $(libcontrol_la_DEPENDENCIES)
|
||||
$(CXXLINK) $(libcontrol_la_LDFLAGS) $(libcontrol_la_OBJECTS) $(libcontrol_la_LIBADD) $(LIBS)
|
||||
|
||||
mostlyclean-compile:
|
||||
-rm -f *.$(OBJEXT)
|
||||
|
||||
distclean-compile:
|
||||
-rm -f *.tab.c
|
||||
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/CallControl.Plo@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ControlCommon.Plo@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/DCCHDispatch.Plo@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/MobilityManagement.Plo@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/RadioResource.Plo@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/SMSControl.Plo@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/TMSITable.Plo@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/TransactionTable.Plo@am__quote@
|
||||
|
||||
.cpp.o:
|
||||
@am__fastdepCXX_TRUE@ if $(CXXCOMPILE) -MT $@ -MD -MP -MF "$(DEPDIR)/$*.Tpo" -c -o $@ $<; \
|
||||
@am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/$*.Tpo" "$(DEPDIR)/$*.Po"; else rm -f "$(DEPDIR)/$*.Tpo"; exit 1; fi
|
||||
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
|
||||
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
|
||||
@am__fastdepCXX_FALSE@ $(CXXCOMPILE) -c -o $@ $<
|
||||
|
||||
.cpp.obj:
|
||||
@am__fastdepCXX_TRUE@ if $(CXXCOMPILE) -MT $@ -MD -MP -MF "$(DEPDIR)/$*.Tpo" -c -o $@ `$(CYGPATH_W) '$<'`; \
|
||||
@am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/$*.Tpo" "$(DEPDIR)/$*.Po"; else rm -f "$(DEPDIR)/$*.Tpo"; exit 1; fi
|
||||
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
|
||||
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
|
||||
@am__fastdepCXX_FALSE@ $(CXXCOMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
|
||||
|
||||
.cpp.lo:
|
||||
@am__fastdepCXX_TRUE@ if $(LTCXXCOMPILE) -MT $@ -MD -MP -MF "$(DEPDIR)/$*.Tpo" -c -o $@ $<; \
|
||||
@am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/$*.Tpo" "$(DEPDIR)/$*.Plo"; else rm -f "$(DEPDIR)/$*.Tpo"; exit 1; fi
|
||||
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
|
||||
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
|
||||
@am__fastdepCXX_FALSE@ $(LTCXXCOMPILE) -c -o $@ $<
|
||||
|
||||
mostlyclean-libtool:
|
||||
-rm -f *.lo
|
||||
|
||||
clean-libtool:
|
||||
-rm -rf .libs _libs
|
||||
|
||||
distclean-libtool:
|
||||
-rm -f libtool
|
||||
uninstall-info-am:
|
||||
|
||||
ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES)
|
||||
list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
|
||||
unique=`for i in $$list; do \
|
||||
if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
|
||||
done | \
|
||||
$(AWK) ' { files[$$0] = 1; } \
|
||||
END { for (i in files) print i; }'`; \
|
||||
mkid -fID $$unique
|
||||
tags: TAGS
|
||||
|
||||
TAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \
|
||||
$(TAGS_FILES) $(LISP)
|
||||
tags=; \
|
||||
here=`pwd`; \
|
||||
list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
|
||||
unique=`for i in $$list; do \
|
||||
if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
|
||||
done | \
|
||||
$(AWK) ' { files[$$0] = 1; } \
|
||||
END { for (i in files) print i; }'`; \
|
||||
if test -z "$(ETAGS_ARGS)$$tags$$unique"; then :; else \
|
||||
test -n "$$unique" || unique=$$empty_fix; \
|
||||
$(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
|
||||
$$tags $$unique; \
|
||||
fi
|
||||
ctags: CTAGS
|
||||
CTAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \
|
||||
$(TAGS_FILES) $(LISP)
|
||||
tags=; \
|
||||
here=`pwd`; \
|
||||
list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
|
||||
unique=`for i in $$list; do \
|
||||
if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
|
||||
done | \
|
||||
$(AWK) ' { files[$$0] = 1; } \
|
||||
END { for (i in files) print i; }'`; \
|
||||
test -z "$(CTAGS_ARGS)$$tags$$unique" \
|
||||
|| $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
|
||||
$$tags $$unique
|
||||
|
||||
GTAGS:
|
||||
here=`$(am__cd) $(top_builddir) && pwd` \
|
||||
&& cd $(top_srcdir) \
|
||||
&& gtags -i $(GTAGS_ARGS) $$here
|
||||
|
||||
distclean-tags:
|
||||
-rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
|
||||
|
||||
distdir: $(DISTFILES)
|
||||
$(mkdir_p) $(distdir)/..
|
||||
@srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; \
|
||||
topsrcdirstrip=`echo "$(top_srcdir)" | sed 's|.|.|g'`; \
|
||||
list='$(DISTFILES)'; for file in $$list; do \
|
||||
case $$file in \
|
||||
$(srcdir)/*) file=`echo "$$file" | sed "s|^$$srcdirstrip/||"`;; \
|
||||
$(top_srcdir)/*) file=`echo "$$file" | sed "s|^$$topsrcdirstrip/|$(top_builddir)/|"`;; \
|
||||
esac; \
|
||||
if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
|
||||
dir=`echo "$$file" | sed -e 's,/[^/]*$$,,'`; \
|
||||
if test "$$dir" != "$$file" && test "$$dir" != "."; then \
|
||||
dir="/$$dir"; \
|
||||
$(mkdir_p) "$(distdir)$$dir"; \
|
||||
else \
|
||||
dir=''; \
|
||||
fi; \
|
||||
if test -d $$d/$$file; then \
|
||||
if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
|
||||
cp -pR $(srcdir)/$$file $(distdir)$$dir || exit 1; \
|
||||
fi; \
|
||||
cp -pR $$d/$$file $(distdir)$$dir || exit 1; \
|
||||
else \
|
||||
test -f $(distdir)/$$file \
|
||||
|| cp -p $$d/$$file $(distdir)/$$file \
|
||||
|| exit 1; \
|
||||
fi; \
|
||||
done
|
||||
check-am: all-am
|
||||
check: check-am
|
||||
all-am: Makefile $(LTLIBRARIES) $(HEADERS)
|
||||
installdirs:
|
||||
install: install-am
|
||||
install-exec: install-exec-am
|
||||
install-data: install-data-am
|
||||
uninstall: uninstall-am
|
||||
|
||||
install-am: all-am
|
||||
@$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
|
||||
|
||||
installcheck: installcheck-am
|
||||
install-strip:
|
||||
$(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
|
||||
install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
|
||||
`test -z '$(STRIP)' || \
|
||||
echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install
|
||||
mostlyclean-generic:
|
||||
-test -z "$(MOSTLYCLEANFILES)" || rm -f $(MOSTLYCLEANFILES)
|
||||
|
||||
clean-generic:
|
||||
|
||||
distclean-generic:
|
||||
-test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
|
||||
|
||||
maintainer-clean-generic:
|
||||
@echo "This command is intended for maintainers to use"
|
||||
@echo "it deletes files that may require special tools to rebuild."
|
||||
clean: clean-am
|
||||
|
||||
clean-am: clean-generic clean-libtool clean-noinstLTLIBRARIES \
|
||||
mostlyclean-am
|
||||
|
||||
distclean: distclean-am
|
||||
-rm -rf ./$(DEPDIR)
|
||||
-rm -f Makefile
|
||||
distclean-am: clean-am distclean-compile distclean-generic \
|
||||
distclean-libtool distclean-tags
|
||||
|
||||
dvi: dvi-am
|
||||
|
||||
dvi-am:
|
||||
|
||||
html: html-am
|
||||
|
||||
info: info-am
|
||||
|
||||
info-am:
|
||||
|
||||
install-data-am:
|
||||
|
||||
install-exec-am:
|
||||
|
||||
install-info: install-info-am
|
||||
|
||||
install-man:
|
||||
|
||||
installcheck-am:
|
||||
|
||||
maintainer-clean: maintainer-clean-am
|
||||
-rm -rf ./$(DEPDIR)
|
||||
-rm -f Makefile
|
||||
maintainer-clean-am: distclean-am maintainer-clean-generic
|
||||
|
||||
mostlyclean: mostlyclean-am
|
||||
|
||||
mostlyclean-am: mostlyclean-compile mostlyclean-generic \
|
||||
mostlyclean-libtool
|
||||
|
||||
pdf: pdf-am
|
||||
|
||||
pdf-am:
|
||||
|
||||
ps: ps-am
|
||||
|
||||
ps-am:
|
||||
|
||||
uninstall-am: uninstall-info-am
|
||||
|
||||
.PHONY: CTAGS GTAGS all all-am check check-am clean clean-generic \
|
||||
clean-libtool clean-noinstLTLIBRARIES ctags distclean \
|
||||
distclean-compile distclean-generic distclean-libtool \
|
||||
distclean-tags distdir dvi dvi-am html html-am info info-am \
|
||||
install install-am install-data install-data-am install-exec \
|
||||
install-exec-am install-info install-info-am install-man \
|
||||
install-strip installcheck installcheck-am installdirs \
|
||||
maintainer-clean maintainer-clean-generic mostlyclean \
|
||||
mostlyclean-compile mostlyclean-generic mostlyclean-libtool \
|
||||
pdf pdf-am ps ps-am tags uninstall uninstall-am \
|
||||
uninstall-info-am
|
||||
|
||||
# Tell versions [3.59,3.63) of GNU make to not export all variables.
|
||||
# Otherwise a system limit (for SysV at least) may be exceeded.
|
||||
.NOEXPORT:
|
||||
558
Control/MobilityManagement.cpp
Normal file
558
Control/MobilityManagement.cpp
Normal file
@@ -0,0 +1,558 @@
|
||||
/**@file GSM/SIP Mobility Management, GSM 04.08. */
|
||||
/*
|
||||
* Copyright 2008, 2009, 2010, 2011 Free Software Foundation, Inc.
|
||||
* Copyright 2011 Range Networks, 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/>.
|
||||
|
||||
*/
|
||||
|
||||
|
||||
#include <Timeval.h>
|
||||
|
||||
#include "ControlCommon.h"
|
||||
#include "MobilityManagement.h"
|
||||
#include "SMSControl.h"
|
||||
#include "CallControl.h"
|
||||
|
||||
#include <GSMLogicalChannel.h>
|
||||
#include <GSML3RRMessages.h>
|
||||
#include <GSML3MMMessages.h>
|
||||
#include <GSML3CCMessages.h>
|
||||
#include <GSMConfig.h>
|
||||
|
||||
using namespace std;
|
||||
|
||||
#include <SIPInterface.h>
|
||||
#include <SIPUtility.h>
|
||||
#include <SIPMessage.h>
|
||||
#include <SIPEngine.h>
|
||||
#include <SubscriberRegistry.h>
|
||||
|
||||
using namespace SIP;
|
||||
|
||||
#include <Regexp.h>
|
||||
#include <Logger.h>
|
||||
#undef WARNING
|
||||
|
||||
|
||||
using namespace GSM;
|
||||
using namespace Control;
|
||||
|
||||
|
||||
/** Controller for CM Service requests, dispatches out to multiple possible transaction controllers. */
|
||||
void Control::CMServiceResponder(const L3CMServiceRequest* cmsrq, LogicalChannel* DCCH)
|
||||
{
|
||||
assert(cmsrq);
|
||||
assert(DCCH);
|
||||
LOG(INFO) << *cmsrq;
|
||||
switch (cmsrq->serviceType().type()) {
|
||||
case L3CMServiceType::MobileOriginatedCall:
|
||||
MOCStarter(cmsrq,DCCH);
|
||||
break;
|
||||
case L3CMServiceType::ShortMessage:
|
||||
MOSMSController(cmsrq,DCCH);
|
||||
break;
|
||||
default:
|
||||
LOG(NOTICE) << "service not supported for " << *cmsrq;
|
||||
// Cause 0x20 means "serivce not supported".
|
||||
DCCH->send(L3CMServiceReject(0x20));
|
||||
DCCH->send(L3ChannelRelease());
|
||||
}
|
||||
// The transaction may or may not be cleared,
|
||||
// depending on the assignment type.
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/** Controller for the IMSI Detach transaction, GSM 04.08 4.3.4. */
|
||||
void Control::IMSIDetachController(const L3IMSIDetachIndication* idi, LogicalChannel* DCCH)
|
||||
{
|
||||
assert(idi);
|
||||
assert(DCCH);
|
||||
LOG(INFO) << *idi;
|
||||
|
||||
// The IMSI detach maps to a SIP unregister with the local Asterisk server.
|
||||
try {
|
||||
// FIXME -- Resolve TMSIs to IMSIs.
|
||||
if (idi->mobileID().type()==IMSIType) {
|
||||
SIPEngine engine(gConfig.getStr("SIP.Proxy.Registration").c_str(), idi->mobileID().digits());
|
||||
engine.unregister();
|
||||
}
|
||||
}
|
||||
catch(SIPTimeout) {
|
||||
LOG(ALERT) "SIP registration timed out. Is Asterisk running?";
|
||||
}
|
||||
// No reponse required, so just close the channel.
|
||||
DCCH->send(L3ChannelRelease());
|
||||
// Many handsets never complete the transaction.
|
||||
// So force a shutdown of the channel.
|
||||
DCCH->send(HARDRELEASE);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
Send a given welcome message from a given short code.
|
||||
@return true if it was sent
|
||||
*/
|
||||
bool sendWelcomeMessage(const char* messageName, const char* shortCodeName, const char *IMSI, LogicalChannel* DCCH, const char *whiteListCode = NULL)
|
||||
{
|
||||
if (!gConfig.defines(messageName)) return false;
|
||||
LOG(INFO) << "sending " << messageName << " message to handset";
|
||||
ostringstream message;
|
||||
message << gConfig.getStr(messageName) << " IMSI:" << IMSI;
|
||||
if (whiteListCode) {
|
||||
message << ", white-list code: " << whiteListCode;
|
||||
}
|
||||
// This returns when delivery is acked in L3.
|
||||
deliverSMSToMS(
|
||||
gConfig.getStr(shortCodeName).c_str(),
|
||||
message.str().c_str(), "text/plain",
|
||||
random()%7,DCCH);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
class RRLPServer
|
||||
{
|
||||
public:
|
||||
RRLPServer(L3MobileIdentity wMobileID, LogicalChannel *wDCCH);
|
||||
// tell server to send location assistance to mobile
|
||||
bool assist();
|
||||
// tell server to ask mobile for location
|
||||
bool locate();
|
||||
private:
|
||||
RRLPServer(); // not allowed
|
||||
string url;
|
||||
L3MobileIdentity mobileID;
|
||||
LogicalChannel *DCCH;
|
||||
string query;
|
||||
string name;
|
||||
bool transact();
|
||||
bool trouble;
|
||||
};
|
||||
|
||||
RRLPServer::RRLPServer(L3MobileIdentity wMobileID, LogicalChannel *wDCCH)
|
||||
{
|
||||
trouble = false;
|
||||
url = gConfig.getStr("GSM.RRLP.SERVER.URL", "");
|
||||
if (url.length() == 0) {
|
||||
LOG(INFO) << "RRLP not configured";
|
||||
trouble = true;
|
||||
return;
|
||||
}
|
||||
mobileID = wMobileID;
|
||||
DCCH = wDCCH;
|
||||
// name of subscriber
|
||||
name = string("IMSI") + mobileID.digits();
|
||||
// don't continue if IMSI has a RRLP support flag and it's false
|
||||
unsigned int supported = 0;
|
||||
if (sqlite3_single_lookup(gSubscriberRegistry.db(), "sip_buddies", "name", name.c_str(),
|
||||
"RRLPSupported", supported) && !supported) {
|
||||
LOG(INFO) << "RRLP not supported for " << name;
|
||||
trouble = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool RRLPServer::assist()
|
||||
{
|
||||
if (trouble) return false;
|
||||
query = "query=assist";
|
||||
return transact();
|
||||
}
|
||||
|
||||
bool RRLPServer::locate()
|
||||
{
|
||||
if (trouble) return false;
|
||||
query = "query=loc";
|
||||
return transact();
|
||||
}
|
||||
|
||||
void clean(char *line)
|
||||
{
|
||||
char *p = line + strlen(line) - 1;
|
||||
while (p > line && *p <= ' ') *p-- = 0;
|
||||
}
|
||||
|
||||
string getConfig()
|
||||
{
|
||||
const char *configs[] = {
|
||||
"GSM.RRLP.ACCURACY",
|
||||
"GSM.RRLP.RESPONSETIME",
|
||||
"GSM.RRLP.ALMANAC.URL",
|
||||
"GSM.RRLP.EPHEMERIS.URL",
|
||||
"GSM.RRLP.ALMANAC.REFRESH.TIME",
|
||||
"GSM.RRLP.EPHEMERIS.REFRESH.TIME",
|
||||
"GSM.RRLP.SEED.LATITUDE",
|
||||
"GSM.RRLP.SEED.LONGITUDE",
|
||||
"GSM.RRLP.SEED.ALTITUDE",
|
||||
"GSM.RRLP.EPHEMERIS.ASSIST.COUNT",
|
||||
"GSM.RRLP.ALMANAC.ASSIST.PRESENT",
|
||||
0
|
||||
};
|
||||
string config = "";
|
||||
for (const char **p = configs; *p; p++) {
|
||||
string configValue = gConfig.getStr(*p, "");
|
||||
if (configValue.length() == 0) return "";
|
||||
config.append("&");
|
||||
config.append(*p);
|
||||
config.append("=");
|
||||
if (0 == strcmp((*p) + strlen(*p) - 3, "URL")) {
|
||||
// TODO - better to have urlencode and decode, but then I'd have to look for them
|
||||
char buf[3];
|
||||
buf[2] = 0;
|
||||
for (const char *q = configValue.c_str(); *q; q++) {
|
||||
sprintf(buf, "%02x", *q);
|
||||
config.append(buf);
|
||||
}
|
||||
} else {
|
||||
config.append(configValue);
|
||||
}
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
bool RRLPServer::transact()
|
||||
{
|
||||
vector<string> apdus;
|
||||
while (true) {
|
||||
// bounce off server
|
||||
string esc = "'";
|
||||
string config = getConfig();
|
||||
if (config.length() == 0) return false;
|
||||
string cmd = "wget -qO- " + esc + url + "?" + query + config + esc;
|
||||
LOG(INFO) << "*************** " << cmd;
|
||||
FILE *result = popen(cmd.c_str(), "r");
|
||||
if (!result) {
|
||||
LOG(CRIT) << "popen call \"" << cmd << "\" failed";
|
||||
return NULL;
|
||||
}
|
||||
// build map of responses, and list of apdus
|
||||
map<string,string> response;
|
||||
size_t nbytes = 1500;
|
||||
char *line = (char*)malloc(nbytes+1);
|
||||
while (!feof(result)) {
|
||||
if (!fgets(line, nbytes, result)) break;
|
||||
clean(line);
|
||||
LOG(INFO) << "server return: " << line;
|
||||
char *p = strchr(line, '=');
|
||||
if (!p) continue;
|
||||
string lhs = string(line, 0, p-line);
|
||||
string rhs = string(line, p+1-line, string::npos);
|
||||
if (lhs == "apdu") {
|
||||
apdus.push_back(rhs);
|
||||
} else {
|
||||
response[lhs] = rhs;
|
||||
}
|
||||
}
|
||||
free(line);
|
||||
pclose(result);
|
||||
|
||||
// quit if error
|
||||
if (response.find("error") != response.end()) {
|
||||
LOG(INFO) << "error from server: " << response["error"];
|
||||
return false;
|
||||
}
|
||||
|
||||
// quit if ack from assist unless there are more apdu messages
|
||||
if (response.find("ack") != response.end()) {
|
||||
LOG(INFO) << "ack from mobile, decoded by server";
|
||||
if (apdus.size() == 0) {
|
||||
return true;
|
||||
} else {
|
||||
LOG(INFO) << "more apdu messages";
|
||||
}
|
||||
}
|
||||
|
||||
// quit if location decoded
|
||||
if (response.find("latitude") != response.end() && response.find("longitude") != response.end() && response.find("positionError") != response.end()) {
|
||||
ostringstream os;
|
||||
os << "insert into RRLP (name, latitude, longitude, error, time) values (" <<
|
||||
'"' << name << '"' << "," <<
|
||||
response["latitude"] << "," <<
|
||||
response["longitude"] << "," <<
|
||||
response["positionError"] << "," <<
|
||||
"datetime('now')"
|
||||
")";
|
||||
LOG(INFO) << os.str();
|
||||
if (!sqlite3_command(gSubscriberRegistry.db(), os.str().c_str())) {
|
||||
LOG(INFO) << "sqlite3_command problem";
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// bounce off mobile
|
||||
if (apdus.size() == 0) {
|
||||
LOG(INFO) << "missing apdu for mobile";
|
||||
return false;
|
||||
}
|
||||
string apdu = apdus[0];
|
||||
apdus.erase(apdus.begin());
|
||||
BitVector bv(apdu.size()*4);
|
||||
if (!bv.unhex(apdu.c_str())) {
|
||||
LOG(INFO) << "BitVector::unhex problem";
|
||||
return false;
|
||||
}
|
||||
|
||||
DCCH->send(L3ApplicationInformation(bv));
|
||||
// Receive an L3 frame with a timeout. Timeout loc req response time max + 2 seconds.
|
||||
L3Frame* resp = DCCH->recv(130000);
|
||||
if (!resp) {
|
||||
return false;
|
||||
}
|
||||
LOG(INFO) << "RRLPQuery returned " << *resp;
|
||||
if (resp->primitive() != DATA) {
|
||||
LOG(INFO) << "didn't receive data";
|
||||
switch (resp->primitive()) {
|
||||
case ESTABLISH: LOG(INFO) << "channel establihsment"; break;
|
||||
case RELEASE: LOG(INFO) << "normal channel release"; break;
|
||||
case DATA: LOG(INFO) << "multiframe data transfer"; break;
|
||||
case UNIT_DATA: LOG(INFO) << "datagram-type data transfer"; break;
|
||||
case ERROR: LOG(INFO) << "channel error"; break;
|
||||
case HARDRELEASE: LOG(INFO) << "forced release after an assignment"; break;
|
||||
default: LOG(INFO) << "unrecognized primitive response"; break;
|
||||
}
|
||||
delete resp;
|
||||
return false;
|
||||
}
|
||||
const unsigned PD_RR = 6;
|
||||
LOG(INFO) << "resp.pd = " << resp->PD();
|
||||
if (resp->PD() != PD_RR) {
|
||||
LOG(INFO) << "didn't receive an RR message";
|
||||
delete resp;
|
||||
return false;
|
||||
}
|
||||
const unsigned MTI_RR_STATUS = 18;
|
||||
if (resp->MTI() == MTI_RR_STATUS) {
|
||||
ostringstream os2;
|
||||
int cause = resp->peekField(16, 8);
|
||||
delete resp;
|
||||
switch (cause) {
|
||||
case 97:
|
||||
LOG(INFO) << "MS says: message not implemented";
|
||||
// flag unsupported in SR so we don't waste time on it again
|
||||
os2 << "update sip_buddies set RRLPSupported = \"0\" where name = \"" << name << "\"";
|
||||
if (!sqlite3_command(gSubscriberRegistry.db(), os2.str().c_str())) {
|
||||
LOG(INFO) << "sqlite3_command problem";
|
||||
}
|
||||
return false;
|
||||
case 98:
|
||||
LOG(INFO) << "MS says: message type not compatible with protocol state";
|
||||
return false;
|
||||
default:
|
||||
LOG(INFO) << "unknown RR_STATUS response, cause = " << cause;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
const unsigned MTI_RR_APDU = 56;
|
||||
if (resp->MTI() != MTI_RR_APDU) {
|
||||
LOG(INFO) << "received unexpected RR Message " << resp->MTI();
|
||||
delete resp;
|
||||
return false;
|
||||
}
|
||||
|
||||
// looks like a good APDU
|
||||
BitVector *bv2 = (BitVector*)resp;
|
||||
BitVector bv3 = bv2->tail(32);
|
||||
ostringstream os;
|
||||
bv3.hex(os);
|
||||
apdu = os.str();
|
||||
delete resp;
|
||||
|
||||
// next query for server
|
||||
query = "query=apdu&apdu=" + apdu;
|
||||
}
|
||||
// not reached
|
||||
}
|
||||
|
||||
/**
|
||||
Controller for the Location Updating transaction, GSM 04.08 4.4.4.
|
||||
@param lur The location updating request.
|
||||
@param DCCH The Dm channel to the MS, which will be released by the function.
|
||||
*/
|
||||
void Control::LocationUpdatingController(const L3LocationUpdatingRequest* lur, LogicalChannel* DCCH)
|
||||
{
|
||||
assert(DCCH);
|
||||
assert(lur);
|
||||
LOG(INFO) << *lur;
|
||||
|
||||
// The location updating request gets mapped to a SIP
|
||||
// registration with the Asterisk server.
|
||||
|
||||
// We also allocate a new TMSI for every handset we encounter.
|
||||
// If the handset is allow to register it may receive a TMSI reassignment.
|
||||
|
||||
// Resolve an IMSI and see if there's a pre-existing IMSI-TMSI mapping.
|
||||
// This operation will throw an exception, caught in a higher scope,
|
||||
// if it fails in the GSM domain.
|
||||
L3MobileIdentity mobileID = lur->mobileID();
|
||||
bool sameLAI = (lur->LAI() == gBTS.LAI());
|
||||
unsigned preexistingTMSI = resolveIMSI(sameLAI,mobileID,DCCH);
|
||||
const char *IMSI = mobileID.digits();
|
||||
// IMSIAttach set to true if this is a new registration.
|
||||
bool IMSIAttach = (preexistingTMSI==0);
|
||||
|
||||
// We assign generate a TMSI for every new phone we see,
|
||||
// even if we don't actually assign it.
|
||||
unsigned newTMSI = 0;
|
||||
if (!preexistingTMSI) newTMSI = gTMSITable.assign(IMSI,lur);
|
||||
|
||||
// Try to register the IMSI.
|
||||
// This will be set true if registration succeeded in the SIP world.
|
||||
bool success = false;
|
||||
try {
|
||||
SIPEngine engine(gConfig.getStr("SIP.Proxy.Registration").c_str(),IMSI);
|
||||
LOG(DEBUG) << "waiting for registration of " << IMSI << " on " << gConfig.getStr("SIP.Proxy.Registration");
|
||||
success = engine.Register(SIPEngine::SIPRegister);
|
||||
}
|
||||
catch(SIPTimeout) {
|
||||
LOG(ALERT) "SIP registration timed out. Is the proxy running at " << gConfig.getStr("SIP.Proxy.Registration");
|
||||
// Reject with a "network failure" cause code, 0x11.
|
||||
DCCH->send(L3LocationUpdatingReject(0x11));
|
||||
// HACK -- wait long enough for a response
|
||||
// FIXME -- Why are we doing this?
|
||||
sleep(4);
|
||||
// Release the channel and return.
|
||||
DCCH->send(L3ChannelRelease());
|
||||
return;
|
||||
}
|
||||
|
||||
if (gConfig.defines("Control.LUR.QueryRRLP")) {
|
||||
// Query for RRLP
|
||||
RRLPServer wRRLPServer(mobileID, DCCH);
|
||||
if (!wRRLPServer.assist()) {
|
||||
LOG(INFO) << "RRLPServer::assist problem";
|
||||
}
|
||||
// can still try to check location even if assist didn't work
|
||||
if (!wRRLPServer.locate()) {
|
||||
LOG(INFO) << "RRLPServer::locate problem";
|
||||
}
|
||||
}
|
||||
|
||||
// This allows us to configure Open Registration
|
||||
bool openRegistration = gConfig.defines("Control.LUR.OpenRegistration");
|
||||
|
||||
// Query for IMEI?
|
||||
if (IMSIAttach && gConfig.defines("Control.LUR.QueryIMEI")) {
|
||||
DCCH->send(L3IdentityRequest(IMEIType));
|
||||
L3Message* msg = getMessage(DCCH);
|
||||
L3IdentityResponse *resp = dynamic_cast<L3IdentityResponse*>(msg);
|
||||
if (!resp) {
|
||||
if (msg) {
|
||||
LOG(WARNING) << "Unexpected message " << *msg;
|
||||
delete msg;
|
||||
}
|
||||
throw UnexpectedMessage();
|
||||
}
|
||||
LOG(INFO) << *resp;
|
||||
if (!gTMSITable.IMEI(IMSI,resp->mobileID().digits()))
|
||||
LOG(WARNING) << "failed access to TMSITable";
|
||||
delete msg;
|
||||
}
|
||||
|
||||
// Query for classmark?
|
||||
if (IMSIAttach && gConfig.defines("Control.LUR.QueryClassmark")) {
|
||||
DCCH->send(L3ClassmarkEnquiry());
|
||||
L3Message* msg = getMessage(DCCH);
|
||||
L3ClassmarkChange *resp = dynamic_cast<L3ClassmarkChange*>(msg);
|
||||
if (!resp) {
|
||||
if (msg) {
|
||||
LOG(WARNING) << "Unexpected message " << *msg;
|
||||
delete msg;
|
||||
}
|
||||
throw UnexpectedMessage();
|
||||
}
|
||||
LOG(INFO) << *resp;
|
||||
const L3MobileStationClassmark2& classmark = resp->classmark();
|
||||
if (!gTMSITable.classmark(IMSI,classmark))
|
||||
LOG(WARNING) << "failed access to TMSITable";
|
||||
delete msg;
|
||||
}
|
||||
|
||||
// We fail closed unless we're configured otherwise
|
||||
if (!success && !openRegistration) {
|
||||
LOG(INFO) << "registration FAILED: " << mobileID;
|
||||
DCCH->send(L3LocationUpdatingReject(gConfig.getNum("Control.LUR.UnprovisionedRejectCause")));
|
||||
if (!preexistingTMSI) {
|
||||
sendWelcomeMessage( "Control.LUR.FailedRegistration.Message",
|
||||
"Control.LUR.FailedRegistration.ShortCode", IMSI,DCCH);
|
||||
}
|
||||
// Release the channel and return.
|
||||
DCCH->send(L3ChannelRelease());
|
||||
return;
|
||||
}
|
||||
|
||||
// If success is true, we had a normal registration.
|
||||
// Otherwise, we are here because of open registration.
|
||||
// Either way, we're going to register a phone if we arrive here.
|
||||
|
||||
if (success) {
|
||||
LOG(INFO) << "registration SUCCESS: " << mobileID;
|
||||
} else {
|
||||
LOG(INFO) << "registration ALLOWED: " << mobileID;
|
||||
}
|
||||
|
||||
|
||||
// Send the "short name" and time-of-day.
|
||||
if (IMSIAttach && gConfig.defines("GSM.Identity.ShortName")) {
|
||||
DCCH->send(L3MMInformation(gConfig.getStr("GSM.Identity.ShortName").c_str()));
|
||||
}
|
||||
// Accept. Make a TMSI assignment, too, if needed.
|
||||
if (preexistingTMSI || !gConfig.defines("Control.LUR.SendTMSIs")) {
|
||||
DCCH->send(L3LocationUpdatingAccept(gBTS.LAI()));
|
||||
} else {
|
||||
assert(newTMSI);
|
||||
DCCH->send(L3LocationUpdatingAccept(gBTS.LAI(),newTMSI));
|
||||
// Wait for MM TMSI REALLOCATION COMPLETE (0x055b).
|
||||
L3Frame* resp = DCCH->recv(1000);
|
||||
// FIXME -- Actually check the response type.
|
||||
if (!resp) {
|
||||
LOG(NOTICE) << "no response to TMSI assignment";
|
||||
} else {
|
||||
LOG(INFO) << *resp;
|
||||
}
|
||||
delete resp;
|
||||
}
|
||||
|
||||
// If this is an IMSI attach, send a welcome message.
|
||||
if (IMSIAttach) {
|
||||
if (success) {
|
||||
sendWelcomeMessage( "Control.LUR.NormalRegistration.Message",
|
||||
"Control.LUR.NormalRegistration.ShortCode", IMSI, DCCH);
|
||||
} else {
|
||||
sendWelcomeMessage( "Control.LUR.OpenRegistration.Message",
|
||||
"Control.LUR.OpenRegistration.ShortCode", IMSI, DCCH);
|
||||
}
|
||||
}
|
||||
|
||||
// Release the channel and return.
|
||||
DCCH->send(L3ChannelRelease());
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// vim: ts=4 sw=4
|
||||
39
Control/MobilityManagement.h
Normal file
39
Control/MobilityManagement.h
Normal file
@@ -0,0 +1,39 @@
|
||||
/**@file GSM/SIP Mobility Management, GSM 04.08. */
|
||||
/*
|
||||
* Copyright 2008, 2009, 2010 Free Software Foundation, Inc.
|
||||
* Copyright 2010 Kestrel Signal Processing, Inc.
|
||||
*
|
||||
* This software is distributed under multiple licenses; see the COPYING file in the main directory for licensing information for this specific distribuion.
|
||||
*
|
||||
* This use of this software may be subject to additional restrictions.
|
||||
* See the LEGAL file in the main directory for details.
|
||||
|
||||
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.
|
||||
|
||||
*/
|
||||
|
||||
#ifndef MOBILITYMANAGEMENT_H
|
||||
#define MOBILITYMANAGEMENT_H
|
||||
|
||||
|
||||
namespace GSM {
|
||||
class LogicalChannel;
|
||||
class L3CMServiceRequest;
|
||||
class L3LocationUpdatingRequest;
|
||||
class L3IMSIDetachIndication;
|
||||
};
|
||||
|
||||
namespace Control {
|
||||
|
||||
void CMServiceResponder(const GSM::L3CMServiceRequest* cmsrq, GSM::LogicalChannel* DCCH);
|
||||
|
||||
void IMSIDetachController(const GSM::L3IMSIDetachIndication* idi, GSM::LogicalChannel* DCCH);
|
||||
|
||||
void LocationUpdatingController(const GSM::L3LocationUpdatingRequest* lur, GSM::LogicalChannel* DCCH);
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
25
Control/README.Control
Normal file
25
Control/README.Control
Normal file
@@ -0,0 +1,25 @@
|
||||
This directory contains control-layer functions for the access point.
|
||||
|
||||
Most GSM L3 and VoIP messages terminate here.
|
||||
|
||||
Everything in this directory should be in the Control namespace.
|
||||
|
||||
Components:
|
||||
|
||||
RadioResource -- Functions for RR procedures (paging, access grant)
|
||||
MobilityManagement -- Functions for MM procedures (CM service, location updating)
|
||||
CallControl -- Functions for CC (mobile originated, mobile terminated)
|
||||
SMS -- Funcitons for text messaging.
|
||||
|
||||
|
||||
|
||||
SIP and RTP UDP/IP Port Assignments
|
||||
|
||||
Component Protocol Port(s)
|
||||
Asterisk SIP 5060
|
||||
Zoiper SIP 5061
|
||||
AP/BTS SIP 5062
|
||||
Zoiper RTP 16384-16385
|
||||
Asterisk RTP 16386-16483
|
||||
AP/BTS RTP 16484-16583
|
||||
|
||||
509
Control/RadioResource.cpp
Normal file
509
Control/RadioResource.cpp
Normal file
@@ -0,0 +1,509 @@
|
||||
/**@file GSM Radio Resource procedures, GSM 04.18 and GSM 04.08. */
|
||||
|
||||
/*
|
||||
* Copyright 2008, 2009, 2010, 2011 Free Software Foundation, Inc.
|
||||
* Copyright 2010 Kestrel Signal Processing, Inc.
|
||||
* Copyright 2011 Range Networks, 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/>.
|
||||
|
||||
*/
|
||||
|
||||
|
||||
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <list>
|
||||
|
||||
#include "ControlCommon.h"
|
||||
#include "TransactionTable.h"
|
||||
#include "RadioResource.h"
|
||||
#include "SMSControl.h"
|
||||
#include "CallControl.h"
|
||||
|
||||
#include <GSMLogicalChannel.h>
|
||||
#include <GSMConfig.h>
|
||||
|
||||
#include <Logger.h>
|
||||
#undef WARNING
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
using namespace std;
|
||||
using namespace GSM;
|
||||
using namespace Control;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
Determine the channel type needed.
|
||||
This is based on GSM 04.08 9.1.8, Table 9.3 and 9.3a.
|
||||
The following is assumed about the global BTS capabilities:
|
||||
- We do not support call reestablishment.
|
||||
- We do not support GPRS.
|
||||
@param RA The request reference from the channel request message.
|
||||
@return channel type code, undefined if not a supported service
|
||||
*/
|
||||
ChannelType decodeChannelNeeded(unsigned RA)
|
||||
{
|
||||
// This code is based on GSM 04.08 Table 9.9.
|
||||
|
||||
unsigned RA4 = RA>>4;
|
||||
unsigned RA5 = RA>>5;
|
||||
|
||||
// Answer to paging, Table 9.9a.
|
||||
// We don't support TCH/H, so it's wither SDCCH or TCH/F.
|
||||
// The spec allows for "SDCCH-only" MS. We won't support that here.
|
||||
// FIXME -- So we probably should not use "any channel" in the paging indications.
|
||||
if (RA5 == 0x04) return TCHFType; // any channel or any TCH.
|
||||
if (RA4 == 0x01) return SDCCHType; // SDCCH
|
||||
if (RA4 == 0x02) return TCHFType; // TCH/F
|
||||
if (RA4 == 0x03) return TCHFType; // TCH/F
|
||||
|
||||
int NECI = gConfig.getNum("GSM.CellSelection.NECI");
|
||||
if (NECI==0) {
|
||||
if (RA5 == 0x07) return SDCCHType; // MOC or SDCCH procedures
|
||||
if (RA5 == 0x00) return SDCCHType; // location updating
|
||||
} else {
|
||||
assert(NECI==1);
|
||||
if (gConfig.defines("Control.VEA")) {
|
||||
// Very Early Assignment
|
||||
if (RA5 == 0x07) return TCHFType; // MOC for TCH/F
|
||||
if (RA4 == 0x04) return TCHFType; // MOC, TCH/H sufficient
|
||||
} else {
|
||||
// Early Assignment
|
||||
if (RA5 == 0x07) return SDCCHType; // MOC for TCH/F
|
||||
if (RA4 == 0x04) return SDCCHType; // MOC, TCH/H sufficient
|
||||
}
|
||||
if (RA4 == 0x00) return SDCCHType; // location updating
|
||||
if (RA4 == 0x01) return SDCCHType; // other procedures on SDCCH
|
||||
}
|
||||
|
||||
// Anything else falls through to here.
|
||||
// We are still ignoring data calls, GPRS, LMU.
|
||||
return UndefinedCHType;
|
||||
}
|
||||
|
||||
|
||||
/** Return true if RA indicates LUR. */
|
||||
bool requestingLUR(unsigned RA)
|
||||
{
|
||||
int NECI = gConfig.getNum("GSM.CellSelection.NECI");
|
||||
if (NECI==0) return ((RA>>5) == 0x00);
|
||||
else return ((RA>>4) == 0x00);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/** Decode RACH bits and send an immediate assignment; may block waiting for a channel. */
|
||||
void AccessGrantResponder(
|
||||
unsigned RA, const GSM::Time& when,
|
||||
float RSSI, float timingError)
|
||||
{
|
||||
// RR Establishment.
|
||||
// Immediate Assignment procedure, "Answer from the Network"
|
||||
// GSM 04.08 3.3.1.1.3.
|
||||
// Given a request reference, try to allocate a channel
|
||||
// and send the assignment to the handset on the CCCH.
|
||||
// This GSM's version of medium access control.
|
||||
// Papa Legba, open that door...
|
||||
|
||||
// Are we holding off new allocations?
|
||||
if (gBTS.hold()) {
|
||||
LOG(NOTICE) << "ignoring RACH due to BTS hold-off";
|
||||
return;
|
||||
}
|
||||
|
||||
// Check "when" against current clock to see if we're too late.
|
||||
// Calculate maximum number of frames of delay.
|
||||
// See GSM 04.08 3.3.1.1.2 for the logic here.
|
||||
static const unsigned txInteger = gConfig.getNum("GSM.RACH.TxInteger");
|
||||
static const int maxAge = GSM::RACHSpreadSlots[txInteger] + GSM::RACHWaitSParam[txInteger];
|
||||
// Check burst age.
|
||||
int age = gBTS.time() - when;
|
||||
LOG(INFO) << "RA=0x" << hex << RA << dec
|
||||
<< " when=" << when << " age=" << age
|
||||
<< " delay=" << timingError << " RSSI=" << RSSI;
|
||||
if (age>maxAge) {
|
||||
LOG(WARNING) << "ignoring RACH bust with age " << age;
|
||||
gBTS.growT3122()/1000;
|
||||
return;
|
||||
}
|
||||
|
||||
// Screen for delay.
|
||||
if (timingError>gConfig.getNum("GSM.MS.TA.Max")) {
|
||||
LOG(WARNING) << "ignoring RACH burst with delay " << timingError;
|
||||
return;
|
||||
}
|
||||
|
||||
// Get an AGCH to send on.
|
||||
CCCHLogicalChannel *AGCH = gBTS.getAGCH();
|
||||
// Someone had better have created a least one AGCH.
|
||||
assert(AGCH);
|
||||
// Check AGCH load now.
|
||||
if (AGCH->load()>gConfig.getNum("GSM.CCCH.AGCH.QMax")) {
|
||||
LOG(WARNING) "AGCH congestion";
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for location update.
|
||||
// This gives LUR a lower priority than other services.
|
||||
if (requestingLUR(RA)) {
|
||||
if (gBTS.SDCCHAvailable()<=gConfig.getNum("GSM.CCCH.PCH.Reserve")) {
|
||||
unsigned waitTime = gBTS.growT3122()/1000;
|
||||
LOG(WARNING) << "LUR congestion, RA=" << RA << " T3122=" << waitTime;
|
||||
const L3ImmediateAssignmentReject reject(L3RequestReference(RA,when),waitTime);
|
||||
LOG(DEBUG) << "LUR rejection, sending " << reject;
|
||||
AGCH->send(reject);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Allocate the channel according to the needed type indicated by RA.
|
||||
// The returned channel is already open and ready for the transaction.
|
||||
LogicalChannel *LCH = NULL;
|
||||
switch (decodeChannelNeeded(RA)) {
|
||||
case TCHFType: LCH = gBTS.getTCH(); break;
|
||||
case SDCCHType: LCH = gBTS.getSDCCH(); break;
|
||||
// If we don't support the service, assign to an SDCCH and we can reject it in L3.
|
||||
case UndefinedCHType:
|
||||
LOG(NOTICE) << "RACH burst for unsupported service RA=" << RA;
|
||||
LCH = gBTS.getSDCCH();
|
||||
break;
|
||||
// We should never be here.
|
||||
default: assert(0);
|
||||
}
|
||||
|
||||
// Nothing available?
|
||||
if (!LCH) {
|
||||
// Rejection, GSM 04.08 3.3.1.1.3.2.
|
||||
// But since we recognize SOS calls already,
|
||||
// we might as well save some AGCH bandwidth.
|
||||
unsigned waitTime = gBTS.growT3122()/1000;
|
||||
LOG(WARNING) << "congestion, RA=" << RA << " T3122=" << waitTime;
|
||||
const L3ImmediateAssignmentReject reject(L3RequestReference(RA,when),waitTime);
|
||||
LOG(DEBUG) << "rejection, sending " << reject;
|
||||
AGCH->send(reject);
|
||||
return;
|
||||
}
|
||||
|
||||
// Set the channel physical parameters from the RACH burst.
|
||||
LCH->setPhy(RSSI,timingError);
|
||||
|
||||
// Assignment, GSM 04.08 3.3.1.1.3.1.
|
||||
// Create the ImmediateAssignment message.
|
||||
// Woot!! We got a channel! Thanks to Legba!
|
||||
int initialTA = (int)(timingError + 0.5F);
|
||||
if (initialTA<0) initialTA=0;
|
||||
if (initialTA>62) initialTA=62;
|
||||
const L3ImmediateAssignment assign(
|
||||
L3RequestReference(RA,when),
|
||||
LCH->channelDescription(),
|
||||
L3TimingAdvance(initialTA)
|
||||
);
|
||||
LOG(INFO) << "sending " << assign;
|
||||
AGCH->send(assign);
|
||||
|
||||
// On successful allocation, shrink T3122.
|
||||
gBTS.shrinkT3122();
|
||||
}
|
||||
|
||||
|
||||
|
||||
void* Control::AccessGrantServiceLoop(void*)
|
||||
{
|
||||
while (true) {
|
||||
ChannelRequestRecord *req = gBTS.nextChannelRequest();
|
||||
if (!req) continue;
|
||||
AccessGrantResponder(
|
||||
req->RA(), req->frame(),
|
||||
req->RSSI(), req->timingError()
|
||||
);
|
||||
delete req;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void Control::PagingResponseHandler(const L3PagingResponse* resp, LogicalChannel* DCCH)
|
||||
{
|
||||
assert(resp);
|
||||
assert(DCCH);
|
||||
LOG(INFO) << *resp;
|
||||
|
||||
// If we got a TMSI, find the IMSI.
|
||||
L3MobileIdentity mobileID = resp->mobileID();
|
||||
if (mobileID.type()==TMSIType) {
|
||||
char *IMSI = gTMSITable.IMSI(mobileID.TMSI());
|
||||
if (IMSI) {
|
||||
mobileID = L3MobileIdentity(IMSI);
|
||||
free(IMSI);
|
||||
} else {
|
||||
// Don't try too hard to resolve.
|
||||
// The handset is supposed to respond with the same ID type as in the request.
|
||||
// This could be the sign of some kind of DOS attack.
|
||||
LOG(CRIT) << "Paging Reponse with non-valid TMSI";
|
||||
// Cause 0x60 "Invalid mandatory information"
|
||||
DCCH->send(L3ChannelRelease(0x60));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Delete the Mobile ID from the paging list to free up CCCH bandwidth.
|
||||
// ... if it was not deleted by a timer already ...
|
||||
gBTS.pager().removeID(mobileID);
|
||||
|
||||
// Find the transction table entry that was created when the phone was paged.
|
||||
// We have to look up by mobile ID since the paging entry may have been
|
||||
// erased before this handler was called. That's too bad.
|
||||
// HACK -- We also flush stray transactions until we find what we
|
||||
// are looking for.
|
||||
TransactionEntry* transaction = gTransactionTable.answeredPaging(mobileID);
|
||||
if (!transaction) {
|
||||
LOG(WARNING) << "Paging Reponse with no transaction record for " << mobileID;
|
||||
// Cause 0x41 means "call already cleared".
|
||||
DCCH->send(L3ChannelRelease(0x41));
|
||||
return;
|
||||
}
|
||||
// Set the transaction channel.
|
||||
transaction->channel(DCCH);
|
||||
// We are looking for a mobile-terminated transaction.
|
||||
// The transaction controller will take it from here.
|
||||
switch (transaction->service().type()) {
|
||||
case L3CMServiceType::MobileTerminatedCall:
|
||||
MTCStarter(transaction, DCCH);
|
||||
return;
|
||||
case L3CMServiceType::MobileTerminatedShortMessage:
|
||||
MTSMSController(transaction, DCCH);
|
||||
return;
|
||||
default:
|
||||
// Flush stray MOC entries.
|
||||
// There should not be any, but...
|
||||
LOG(ERR) << "non-valid paging-state transaction: " << *transaction;
|
||||
gTransactionTable.remove(transaction);
|
||||
// FIXME -- Send a channel release on the DCCH.
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void Control::AssignmentCompleteHandler(const L3AssignmentComplete *confirm, TCHFACCHLogicalChannel *TCH)
|
||||
{
|
||||
// The assignment complete handler is used to
|
||||
// tie together split transactions across a TCH assignment
|
||||
// in non-VEA call setup.
|
||||
|
||||
assert(TCH);
|
||||
assert(confirm);
|
||||
LOG(DEBUG) << *confirm;
|
||||
|
||||
// Check the transaction table to know what to do next.
|
||||
TransactionEntry* transaction = gTransactionTable.find(TCH);
|
||||
if (!transaction) {
|
||||
LOG(WARNING) << "No transaction matching channel " << *TCH << " (" << TCH << ").";
|
||||
throw UnexpectedMessage();
|
||||
}
|
||||
LOG(INFO) << "service="<<transaction->service().type();
|
||||
|
||||
// These "controller" functions don't return until the call is cleared.
|
||||
switch (transaction->service().type()) {
|
||||
case L3CMServiceType::MobileOriginatedCall:
|
||||
MOCController(transaction,TCH);
|
||||
break;
|
||||
case L3CMServiceType::MobileTerminatedCall:
|
||||
MTCController(transaction,TCH);
|
||||
break;
|
||||
default:
|
||||
LOG(WARNING) << "unsupported service " << transaction->service();
|
||||
throw UnsupportedMessage(transaction->ID());
|
||||
}
|
||||
// If we got here, the call is cleared.
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void Pager::addID(const L3MobileIdentity& newID, ChannelType chanType,
|
||||
TransactionEntry& transaction, unsigned wLife)
|
||||
{
|
||||
transaction.GSMState(GSM::Paging);
|
||||
transaction.setTimer("3113",wLife);
|
||||
// Add a mobile ID to the paging list for a given lifetime.
|
||||
ScopedLock lock(mLock);
|
||||
// If this ID is already in the list, just reset its timer.
|
||||
// Uhg, another linear time search.
|
||||
// This would be faster if the paging list were ordered by ID.
|
||||
// But the list should usually be short, so it may not be worth the effort.
|
||||
for (PagingEntryList::iterator lp = mPageIDs.begin(); lp != mPageIDs.end(); ++lp) {
|
||||
if (lp->ID()==newID) {
|
||||
LOG(DEBUG) << newID << " already in table";
|
||||
lp->renew(wLife);
|
||||
mPageSignal.signal();
|
||||
return;
|
||||
}
|
||||
}
|
||||
// If this ID is new, put it in the list.
|
||||
mPageIDs.push_back(PagingEntry(newID,chanType,transaction.ID(),wLife));
|
||||
LOG(INFO) << newID << " added to table";
|
||||
mPageSignal.signal();
|
||||
}
|
||||
|
||||
|
||||
unsigned Pager::removeID(const L3MobileIdentity& delID)
|
||||
{
|
||||
// Return the associated transaction ID, or 0 if none found.
|
||||
LOG(INFO) << delID;
|
||||
ScopedLock lock(mLock);
|
||||
for (PagingEntryList::iterator lp = mPageIDs.begin(); lp != mPageIDs.end(); ++lp) {
|
||||
if (lp->ID()==delID) {
|
||||
unsigned retVal = lp->transactionID();
|
||||
mPageIDs.erase(lp);
|
||||
return retVal;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
unsigned Pager::pageAll()
|
||||
{
|
||||
// Traverse the full list and page all IDs.
|
||||
// Remove expired IDs.
|
||||
// Return the number of IDs paged.
|
||||
// This is a linear time operation.
|
||||
|
||||
ScopedLock lock(mLock);
|
||||
|
||||
// Clear expired entries.
|
||||
PagingEntryList::iterator lp = mPageIDs.begin();
|
||||
while (lp != mPageIDs.end()) {
|
||||
if (!lp->expired()) ++lp;
|
||||
else {
|
||||
LOG(INFO) << "erasing " << lp->ID();
|
||||
// Non-responsive, dead transaction?
|
||||
gTransactionTable.removePaging(lp->transactionID());
|
||||
// remove from the list
|
||||
lp=mPageIDs.erase(lp);
|
||||
}
|
||||
}
|
||||
|
||||
LOG(INFO) << "paging " << mPageIDs.size() << " mobile(s)";
|
||||
|
||||
// Page remaining entries, two at a time if possible.
|
||||
// These PCH send operations are non-blocking.
|
||||
lp = mPageIDs.begin();
|
||||
while (lp != mPageIDs.end()) {
|
||||
// FIXME -- This completely ignores the paging groups.
|
||||
// HACK -- So we send every page twice.
|
||||
// That will probably mean a different Pager for each subchannel.
|
||||
// See GSM 04.08 10.5.2.11 and GSM 05.02 6.5.2.
|
||||
const L3MobileIdentity& id1 = lp->ID();
|
||||
ChannelType type1 = lp->type();
|
||||
++lp;
|
||||
if (lp==mPageIDs.end()) {
|
||||
// Just one ID left?
|
||||
LOG(DEBUG) << "paging " << id1;
|
||||
gBTS.getPCH(0)->send(L3PagingRequestType1(id1,type1));
|
||||
gBTS.getPCH(0)->send(L3PagingRequestType1(id1,type1));
|
||||
break;
|
||||
}
|
||||
// Page by pairs when possible.
|
||||
const L3MobileIdentity& id2 = lp->ID();
|
||||
ChannelType type2 = lp->type();
|
||||
++lp;
|
||||
LOG(DEBUG) << "paging " << id1 << " and " << id2;
|
||||
gBTS.getPCH(0)->send(L3PagingRequestType1(id1,type1,id2,type2));
|
||||
gBTS.getPCH(0)->send(L3PagingRequestType1(id1,type1,id2,type2));
|
||||
}
|
||||
|
||||
return mPageIDs.size();
|
||||
}
|
||||
|
||||
size_t Pager::pagingEntryListSize()
|
||||
{
|
||||
ScopedLock lock(mLock);
|
||||
return mPageIDs.size();
|
||||
}
|
||||
|
||||
void Pager::start()
|
||||
{
|
||||
if (mRunning) return;
|
||||
mRunning=true;
|
||||
mPagingThread.start((void* (*)(void*))PagerServiceLoopAdapter, (void*)this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
void* Control::PagerServiceLoopAdapter(Pager *pager)
|
||||
{
|
||||
pager->serviceLoop();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void Pager::serviceLoop()
|
||||
{
|
||||
while (mRunning) {
|
||||
|
||||
LOG(DEBUG) << "Pager blocking for signal";
|
||||
mLock.lock();
|
||||
while (mPageIDs.size()==0) mPageSignal.wait(mLock);
|
||||
mLock.unlock();
|
||||
|
||||
// page everything
|
||||
pageAll();
|
||||
|
||||
// Wait for pending activity to clear the channel.
|
||||
// This wait is what causes PCH to have lower priority than AGCH.
|
||||
unsigned load = gBTS.getPCH()->load();
|
||||
LOG(DEBUG) << "Pager waiting for " << load << " multiframes";
|
||||
if (load) sleepFrames(51*load);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void Pager::dump(ostream& os) const
|
||||
{
|
||||
ScopedLock lock(mLock);
|
||||
PagingEntryList::const_iterator lp = mPageIDs.begin();
|
||||
while (lp != mPageIDs.end()) {
|
||||
os << lp->ID() << " " << lp->type() << " " << lp->expired() << endl;
|
||||
++lp;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// vim: ts=4 sw=4
|
||||
214
Control/RadioResource.h
Normal file
214
Control/RadioResource.h
Normal file
@@ -0,0 +1,214 @@
|
||||
/**@file GSM Radio Resource procedures, GSM 04.18 and GSM 04.08. */
|
||||
/*
|
||||
* Copyright 2008, 2009, 2010 Free Software Foundation, Inc.
|
||||
* Copyright 2010 Kestrel Signal Processing, Inc.
|
||||
* Copyright 2011 Range Networks, Inc.
|
||||
*
|
||||
* This software is distributed under multiple licenses;
|
||||
* see the COPYING file in the main directory for licensing
|
||||
* information for this specific distribuion.
|
||||
*
|
||||
* This use of this software may be subject to additional restrictions.
|
||||
* See the LEGAL file in the main directory for details.
|
||||
|
||||
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.
|
||||
|
||||
*/
|
||||
|
||||
#ifndef RADIORESOURCE_H
|
||||
#define RADIORESOURCE_H
|
||||
|
||||
#include <list>
|
||||
#include <GSML3CommonElements.h>
|
||||
|
||||
|
||||
namespace GSM {
|
||||
class Time;
|
||||
class TCHFACCHLogicalChannel;
|
||||
class L3PagingResponse;
|
||||
class L3AssignmentComplete;
|
||||
};
|
||||
|
||||
namespace Control {
|
||||
|
||||
class TransactionEntry;
|
||||
|
||||
|
||||
|
||||
|
||||
/** Find and compelte the in-process transaction associated with a paging repsonse. */
|
||||
void PagingResponseHandler(const GSM::L3PagingResponse*, GSM::LogicalChannel*);
|
||||
|
||||
/** Find and compelte the in-process transaction associated with a completed assignment. */
|
||||
void AssignmentCompleteHandler(const GSM::L3AssignmentComplete*, GSM::TCHFACCHLogicalChannel*);
|
||||
|
||||
|
||||
/**@ Access Grant mechanisms */
|
||||
//@{
|
||||
|
||||
|
||||
/** Decode RACH bits and send an immediate assignment; may block waiting for a channel. */
|
||||
//void AccessGrantResponder(
|
||||
// unsigned requestReference, const GSM::Time& when,
|
||||
// float RSSI, float timingError);
|
||||
|
||||
|
||||
/** This record carries all of the parameters associated with a RACH burst. */
|
||||
class ChannelRequestRecord {
|
||||
|
||||
private:
|
||||
|
||||
unsigned mRA; ///< request reference
|
||||
GSM::Time mFrame; ///< receive timestamp
|
||||
float mRSSI; ///< dB wrt full scale
|
||||
float mTimingError; ///< correlator timing error in symbol periods
|
||||
|
||||
public:
|
||||
|
||||
ChannelRequestRecord(
|
||||
unsigned wRA, const GSM::Time& wFrame,
|
||||
float wRSSI, float wTimingError)
|
||||
:mRA(wRA), mFrame(wFrame),
|
||||
mRSSI(wRSSI), mTimingError(wTimingError)
|
||||
{ }
|
||||
|
||||
unsigned RA() const { return mRA; }
|
||||
const GSM::Time& frame() const { return mFrame; }
|
||||
float RSSI() const { return mRSSI; }
|
||||
float timingError() const { return mTimingError; }
|
||||
|
||||
};
|
||||
|
||||
|
||||
/** A thread to process contents of the channel request queue. */
|
||||
void* AccessGrantServiceLoop(void*);
|
||||
|
||||
|
||||
//@}
|
||||
|
||||
/**@ Paging mechanisms */
|
||||
//@{
|
||||
|
||||
/** An entry in the paging list. */
|
||||
class PagingEntry {
|
||||
|
||||
private:
|
||||
|
||||
GSM::L3MobileIdentity mID; ///< The mobile ID.
|
||||
GSM::ChannelType mType; ///< The needed channel type.
|
||||
unsigned mTransactionID; ///< The associated transaction ID.
|
||||
Timeval mExpiration; ///< The expiration time for this entry.
|
||||
|
||||
public:
|
||||
|
||||
/**
|
||||
Create a new entry, with current timestamp.
|
||||
@param wID The ID to be paged.
|
||||
@param wLife The number of milliseconds to keep paging.
|
||||
*/
|
||||
PagingEntry(const GSM::L3MobileIdentity& wID, GSM::ChannelType wType,
|
||||
unsigned wTransactionID, unsigned wLife)
|
||||
:mID(wID),mType(wType),mTransactionID(wTransactionID),mExpiration(wLife)
|
||||
{}
|
||||
|
||||
/** Access the ID. */
|
||||
const GSM::L3MobileIdentity& ID() const { return mID; }
|
||||
|
||||
/** Access the channel type needed. */
|
||||
GSM::ChannelType type() const { return mType; }
|
||||
|
||||
unsigned transactionID() const { return mTransactionID; }
|
||||
|
||||
/** Renew the timer. */
|
||||
void renew(unsigned wLife) { mExpiration = Timeval(wLife); }
|
||||
|
||||
/** Returns true if the entry is expired. */
|
||||
bool expired() const { return mExpiration.passed(); }
|
||||
|
||||
};
|
||||
|
||||
typedef std::list<PagingEntry> PagingEntryList;
|
||||
|
||||
|
||||
/**
|
||||
The pager is a global object that generates paging messages on the CCCH.
|
||||
To page a mobile, add the mobile ID to the pager.
|
||||
The entry will be deleted automatically when it expires.
|
||||
All pager operations are linear time.
|
||||
Not much point in optimizing since the main operation is inherently linear.
|
||||
*/
|
||||
class Pager {
|
||||
|
||||
private:
|
||||
|
||||
PagingEntryList mPageIDs; ///< List of ID's to be paged.
|
||||
mutable Mutex mLock; ///< Lock for thread-safe access.
|
||||
Signal mPageSignal; ///< signal to wake the paging loop
|
||||
Thread mPagingThread; ///< Thread for the paging loop.
|
||||
volatile bool mRunning;
|
||||
|
||||
public:
|
||||
|
||||
Pager()
|
||||
:mRunning(false)
|
||||
{}
|
||||
|
||||
/** Set the output FIFO and start the paging loop. */
|
||||
void start();
|
||||
|
||||
/**
|
||||
Add a mobile ID to the paging list.
|
||||
@param addID The mobile ID to be paged.
|
||||
@param chanType The channel type to be requested.
|
||||
@param transaction The transaction record, which will be modified.
|
||||
@param wLife The paging duration in ms, default is SIP Timer B.
|
||||
*/
|
||||
void addID(
|
||||
const GSM::L3MobileIdentity& addID,
|
||||
GSM::ChannelType chanType,
|
||||
TransactionEntry& transaction,
|
||||
unsigned wLife=gConfig.getNum("SIP.Timer.B")
|
||||
);
|
||||
|
||||
/**
|
||||
Remove a mobile ID.
|
||||
This is used to stop the paging when a phone responds.
|
||||
@return The transaction ID associated with this entry.
|
||||
*/
|
||||
unsigned removeID(const GSM::L3MobileIdentity&);
|
||||
|
||||
private:
|
||||
|
||||
/**
|
||||
Traverse the paging list, paging all IDs.
|
||||
@return Number of IDs paged.
|
||||
*/
|
||||
unsigned pageAll();
|
||||
|
||||
/** A loop that repeatedly calls pageAll. */
|
||||
void serviceLoop();
|
||||
|
||||
/** C-style adapter. */
|
||||
friend void *PagerServiceLoopAdapter(Pager*);
|
||||
|
||||
public:
|
||||
|
||||
/** return size of PagingEntryList */
|
||||
size_t pagingEntryListSize();
|
||||
|
||||
/** Dump the paging list to an ostream. */
|
||||
void dump(std::ostream&) const;
|
||||
};
|
||||
|
||||
|
||||
void *PagerServiceLoopAdapter(Pager*);
|
||||
|
||||
|
||||
//@} // paging mech
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
584
Control/SMSControl.cpp
Normal file
584
Control/SMSControl.cpp
Normal file
@@ -0,0 +1,584 @@
|
||||
/**@file SMS Control (L3), GSM 03.40, 04.11. */
|
||||
/*
|
||||
* Copyright 2008, 2009, 2011 Free Software Foundation, Inc.
|
||||
* Copyright 2010 Kestrel Signal Processing, Inc.
|
||||
* Copyright 2011 Range Networks, Inc.
|
||||
*
|
||||
*
|
||||
* 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 <sstream>
|
||||
#include <GSMLogicalChannel.h>
|
||||
#include <GSML3MMMessages.h>
|
||||
#include "SMSControl.h"
|
||||
#include "ControlCommon.h"
|
||||
#include "TransactionTable.h"
|
||||
#include <Regexp.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(GSM::LogicalChannel *LCH, GSM::Primitive primitive=GSM::DATA)
|
||||
{
|
||||
GSM::L3Frame *retVal = LCH->recv(LCH->N200()*LCH->T200(),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, GSM::LogicalChannel *LCH)
|
||||
{
|
||||
assert(req);
|
||||
assert(req->serviceType().type() == GSM::L3CMServiceType::ShortMessage);
|
||||
assert(LCH);
|
||||
assert(LCH->type() != GSM::SACCHType);
|
||||
|
||||
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, GSM::LogicalChannel *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, GSM::LogicalChannel *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);
|
||||
|
||||
// Close the Dm channel?
|
||||
if (LCH->type()!=GSM::SACCHType) {
|
||||
LCH->send(GSM::L3ChannelRelease());
|
||||
LOG(INFO) << "closing the Um channel";
|
||||
}
|
||||
|
||||
// Ack in SIP domain.
|
||||
if (success) transaction->MTSMSSendOK();
|
||||
|
||||
// Done.
|
||||
gTransactionTable.remove(transaction);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void Control::InCallMOSMSStarter(TransactionEntry *parallelCall)
|
||||
{
|
||||
GSM::LogicalChannel *hostChan = parallelCall->channel();
|
||||
assert(hostChan);
|
||||
GSM::LogicalChannel *SACCH = hostChan->SACCH();
|
||||
assert(SACCH);
|
||||
|
||||
// Create a partial transaction record.
|
||||
TransactionEntry *newTransaction = new TransactionEntry(
|
||||
gConfig.getStr("SIP.Proxy.SMS").c_str(),
|
||||
parallelCall->subscriber(),
|
||||
SACCH);
|
||||
gTransactionTable.add(newTransaction);
|
||||
}
|
||||
|
||||
|
||||
|
||||
void Control::InCallMOSMSController(const CPData *cpData, TransactionEntry* transaction, GSM::SACCHLogicalChannel *LCH)
|
||||
{
|
||||
LOG(INFO) << *cpData;
|
||||
|
||||
// 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 C: Mobile originating short message transfer, parallel call.
|
||||
// The mobile station will send a SABM command on the SACCH when a CM_SERV_ACC
|
||||
// message has been received from the network, allowing the short message
|
||||
// transfer to start. If no hand over occurs the link will stay up until the
|
||||
// MSC orders a explicit release, or the clearing procedure is invoked. If the
|
||||
// parallel call is cleared before the short message transfer is finalized, the
|
||||
// MSC will delay the clearing procedure toward the BSS, i.e. the channel
|
||||
// release procedure is delayed.
|
||||
// """
|
||||
|
||||
// Since there's a parallel call, we will assume correct provisioning.
|
||||
// And we know that CM and SABM are established.
|
||||
|
||||
// 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
|
||||
65
Control/SMSControl.h
Normal file
65
Control/SMSControl.h
Normal file
@@ -0,0 +1,65 @@
|
||||
/**@file Declarations for common-use control-layer functions. */
|
||||
/*
|
||||
* Copyright 2008, 2009, 2010 Free Software Foundation, Inc.
|
||||
* Copyright 2010 Kestrel Signal Processing, Inc.
|
||||
* Copyright 2011 Range Networks, Inc.
|
||||
*
|
||||
* This software is distributed under multiple licenses;
|
||||
* see the COPYING file in the main directory for licensing
|
||||
* information for this specific distribuion.
|
||||
*
|
||||
* This use of this software may be subject to additional restrictions.
|
||||
* See the LEGAL file in the main directory for details.
|
||||
|
||||
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.
|
||||
|
||||
*/
|
||||
|
||||
|
||||
|
||||
#ifndef SMSCONTROL_H
|
||||
#define SMSCONTROL_H
|
||||
|
||||
#include <SMSMessages.h>
|
||||
|
||||
namespace GSM {
|
||||
class L3Message;
|
||||
class LogicalChannel;
|
||||
class SDCCHLogicalChannel;
|
||||
class SACCHLogicalChannel;
|
||||
class TCHFACCHLogicalChannel;
|
||||
class L3CMServiceRequest;
|
||||
};
|
||||
|
||||
|
||||
namespace Control {
|
||||
|
||||
/** MOSMS state machine. */
|
||||
void MOSMSController(const GSM::L3CMServiceRequest *req, GSM::LogicalChannel *LCH);
|
||||
|
||||
/** MOSMS-with-parallel-call state machine. */
|
||||
void InCallMOSMSStarter(TransactionEntry *parallelCall);
|
||||
|
||||
/** MOSMS-with-parallel-call state machine. */
|
||||
void InCallMOSMSController(const SMS::CPData *msg, TransactionEntry* transaction, GSM::SACCHLogicalChannel *LCH);
|
||||
/**
|
||||
Basic SMS delivery from an established CM.
|
||||
On exit, SAP3 will be in ABM and LCH will still be open.
|
||||
Throws exception for failures in connection layer or for parsing failure.
|
||||
@return true on success in relay layer.
|
||||
*/
|
||||
bool deliverSMSToMS(const char *callingPartyDigits, const char* message, const char* contentType, unsigned TI, GSM::LogicalChannel *LCH);
|
||||
|
||||
/** MTSMS */
|
||||
void MTSMSController(TransactionEntry* transaction, GSM::LogicalChannel *LCH);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
// vim: ts=4 sw=4
|
||||
263
Control/TMSITable.cpp
Normal file
263
Control/TMSITable.cpp
Normal file
@@ -0,0 +1,263 @@
|
||||
/*
|
||||
* Copyright 2008, 2009, 2010, 2011 Free Software Foundation, Inc.
|
||||
* Copyright 2010 Kestrel Signal Processing, 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/>.
|
||||
|
||||
*/
|
||||
|
||||
|
||||
#include "TMSITable.h"
|
||||
#include <Logger.h>
|
||||
#include <Globals.h>
|
||||
#include <sqlite3.h>
|
||||
#include <sqlite3util.h>
|
||||
|
||||
#include <GSML3MMMessages.h>
|
||||
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
|
||||
using namespace std;
|
||||
using namespace Control;
|
||||
|
||||
|
||||
static const char* createTMSITable = {
|
||||
"CREATE TABLE IF NOT EXISTS TMSI_TABLE ("
|
||||
"TMSI INTEGER PRIMARY KEY AUTOINCREMENT, "
|
||||
"CREATED INTEGER NOT NULL, " // Unix time of record creation
|
||||
"ACCESSED INTEGER NOT NULL, " // Unix time of last encounter
|
||||
"APP_FLAGS INTEGER DEFAULT 0, " // Application-specific flags
|
||||
"IMSI TEXT UNIQUE NOT NULL, " // IMSI
|
||||
"IMEI TEXT, " // IMEI
|
||||
"L3TI INTEGER DEFAULT 0," // L3 transaction identifier
|
||||
"A5_SUPPORT INTEGER, " // encryption support
|
||||
"POWER_CLASS INTEGER, " // power class
|
||||
"OLD_TMSI INTEGER, " // previous TMSI in old network
|
||||
"PREV_MCC INTEGER, " // previous network MCC
|
||||
"PREV_MNC INTEGER, " // previous network MNC
|
||||
"PREV_LAC INTEGER, " // previous network LAC
|
||||
"DEG_LAT FLOAT, " // RRLP result
|
||||
"DEG_LONG FLOAT " // RRLP result
|
||||
")"
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
TMSITable::TMSITable(const char* wPath)
|
||||
{
|
||||
int rc = sqlite3_open(wPath,&mDB);
|
||||
if (rc) {
|
||||
LOG(EMERG) << "Cannot open TMSITable database at " << wPath << ": " << sqlite3_errmsg(mDB);
|
||||
sqlite3_close(mDB);
|
||||
mDB = NULL;
|
||||
return;
|
||||
}
|
||||
if (!sqlite3_command(mDB,createTMSITable)) {
|
||||
LOG(EMERG) << "Cannot create TMSI table";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
TMSITable::~TMSITable()
|
||||
{
|
||||
if (mDB) sqlite3_close(mDB);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
unsigned TMSITable::assign(const char* IMSI, const GSM::L3LocationUpdatingRequest* lur)
|
||||
{
|
||||
// Create or find an entry based on IMSI.
|
||||
// Return assigned TMSI.
|
||||
assert(mDB);
|
||||
|
||||
LOG(DEBUG) << "IMSI=" << IMSI;
|
||||
// Is there already a record?
|
||||
unsigned TMSI;
|
||||
if (sqlite3_single_lookup(mDB,"TMSI_TABLE","IMSI",IMSI,"TMSI",TMSI)) {
|
||||
LOG(DEBUG) << "found TMSI " << TMSI;
|
||||
touch(TMSI);
|
||||
return TMSI;
|
||||
}
|
||||
|
||||
// Create a new record.
|
||||
LOG(NOTICE) << "new entry for IMSI " << IMSI;
|
||||
char query[1000];
|
||||
unsigned now = (unsigned)time(NULL);
|
||||
if (!lur) {
|
||||
sprintf(query,
|
||||
"INSERT INTO TMSI_TABLE (IMSI,CREATED,ACCESSED) "
|
||||
"VALUES ('%s',%u,%u)",
|
||||
IMSI,now,now);
|
||||
} else {
|
||||
const GSM::L3LocationAreaIdentity &lai = lur->LAI();
|
||||
const GSM::L3MobileIdentity &mid = lur->mobileID();
|
||||
if (mid.type()==GSM::TMSIType) {
|
||||
sprintf(query,
|
||||
"INSERT INTO TMSI_TABLE (IMSI,CREATED,ACCESSED,PREV_MCC,PREV_MNC,PREV_LAC,OLD_TMSI) "
|
||||
"VALUES ('%s',%u,%u,%u,%u,%u,%u)",
|
||||
IMSI,now,now,lai.MCC(),lai.MNC(),lai.LAC(),mid.TMSI());
|
||||
} else {
|
||||
sprintf(query,
|
||||
"INSERT INTO TMSI_TABLE (IMSI,CREATED,ACCESSED,PREV_MCC,PREV_MNC,PREV_LAC) "
|
||||
"VALUES ('%s',%u,%u,%u,%u,%u)",
|
||||
IMSI,now,now,lai.MCC(),lai.MNC(),lai.LAC());
|
||||
}
|
||||
}
|
||||
if (!sqlite3_command(mDB,query)) {
|
||||
LOG(ALERT) << "TMSI creation failed";
|
||||
return 0;
|
||||
}
|
||||
if (!sqlite3_single_lookup(mDB,"TMSI_TABLE","IMSI",IMSI,"TMSI",TMSI)) {
|
||||
LOG(ERR) << "TMSI database inconsistancy";
|
||||
return 0;
|
||||
}
|
||||
return TMSI;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void TMSITable::touch(unsigned TMSI) const
|
||||
{
|
||||
// Update timestamp.
|
||||
char query[100];
|
||||
sprintf(query,"UPDATE TMSI_TABLE SET ACCESSED = %u WHERE TMSI == %u",
|
||||
(unsigned)time(NULL),TMSI);
|
||||
sqlite3_command(mDB,query);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Returned string must be free'd by the caller.
|
||||
char* TMSITable::IMSI(unsigned TMSI) const
|
||||
{
|
||||
char* IMSI = NULL;
|
||||
if (sqlite3_single_lookup(mDB,"TMSI_TABLE","TMSI",TMSI,"IMSI",IMSI)) touch(TMSI);
|
||||
return IMSI;
|
||||
}
|
||||
|
||||
unsigned TMSITable::TMSI(const char* IMSI) const
|
||||
{
|
||||
unsigned TMSI=0;
|
||||
if (sqlite3_single_lookup(mDB,"TMSI_TABLE","IMSI",IMSI,"TMSI",TMSI)) touch(TMSI);
|
||||
return TMSI;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void printAge(unsigned seconds, ostream& os)
|
||||
{
|
||||
static const unsigned k=5;
|
||||
os << setw(4);
|
||||
if (seconds<k*60) {
|
||||
os << seconds << 's';
|
||||
return;
|
||||
}
|
||||
unsigned minutes = (seconds+30) / 60;
|
||||
if (minutes<k*60) {
|
||||
os << minutes << 'm';
|
||||
return;
|
||||
}
|
||||
unsigned hours = (minutes+30) / 60;
|
||||
if (hours<k*24) {
|
||||
os << hours << 'h';
|
||||
return;
|
||||
}
|
||||
os << (hours+12)/24 << 'd';
|
||||
}
|
||||
|
||||
|
||||
void TMSITable::dump(ostream& os) const
|
||||
{
|
||||
sqlite3_stmt *stmt;
|
||||
if (sqlite3_prepare_statement(mDB,&stmt,"SELECT TMSI,IMSI,CREATED,ACCESSED FROM TMSI_TABLE")) {
|
||||
LOG(ERR) << "sqlite3_prepare_statement failed";
|
||||
return;
|
||||
}
|
||||
time_t now = time(NULL);
|
||||
while (sqlite3_run_query(mDB,stmt)==SQLITE_ROW) {
|
||||
os << hex << setw(8) << sqlite3_column_int64(stmt,0) << ' ' << dec;
|
||||
os << sqlite3_column_text(stmt,1) << ' ';
|
||||
printAge(now-sqlite3_column_int(stmt,2),os); os << ' ';
|
||||
printAge(now-sqlite3_column_int(stmt,3),os); os << ' ';
|
||||
os << endl;
|
||||
}
|
||||
sqlite3_finalize(stmt);
|
||||
}
|
||||
|
||||
|
||||
|
||||
void TMSITable::clear()
|
||||
{
|
||||
sqlite3_command(mDB,"DELETE FROM TMSI_TABLE WHERE 1");
|
||||
}
|
||||
|
||||
|
||||
|
||||
bool TMSITable::IMEI(const char* IMSI, const char *IMEI)
|
||||
{
|
||||
char query[100];
|
||||
sprintf(query,"UPDATE TMSI_TABLE SET IMEI=\"%s\",ACCESSED=%u WHERE IMSI=\"%s\"",
|
||||
IMEI,(unsigned)time(NULL),IMSI);
|
||||
return sqlite3_command(mDB,query);
|
||||
}
|
||||
|
||||
|
||||
|
||||
bool TMSITable::classmark(const char* IMSI, const GSM::L3MobileStationClassmark2& classmark)
|
||||
{
|
||||
int A5Bits = (classmark.A5_1()<<2) + (classmark.A5_2()<<1) + classmark.A5_3();
|
||||
char query[100];
|
||||
sprintf(query,
|
||||
"UPDATE TMSI_TABLE SET A5_SUPPORT=%u,ACCESSED=%u,POWER_CLASS=%u "
|
||||
" WHERE IMSI=\"%s\"",
|
||||
A5Bits,(unsigned)time(NULL),classmark.powerClass(),IMSI);
|
||||
return sqlite3_command(mDB,query);
|
||||
}
|
||||
|
||||
|
||||
|
||||
unsigned TMSITable::nextL3TI(const char* IMSI)
|
||||
{
|
||||
// FIXME -- This should be a single atomic operation.
|
||||
unsigned l3ti;
|
||||
if (!sqlite3_single_lookup(mDB,"TMSI_TABLE","IMSI",IMSI,"L3TI",l3ti)) {
|
||||
LOG(ERR) << "cannot read L3TI from TMSI_TABLE, using randon L3TI";
|
||||
return random() % 8;
|
||||
}
|
||||
// Note that TI=7 is a reserved value, so value values are 0-6. See GSM 04.07 11.2.3.1.3.
|
||||
unsigned next = (l3ti+1) % 7;
|
||||
char query[200];
|
||||
sprintf(query,"UPDATE TMSI_TABLE SET L3TI=%u,ACCESSED=%u WHERE IMSI='%s'",
|
||||
next, (unsigned)time(NULL),IMSI);
|
||||
if (!sqlite3_command(mDB,query)) {
|
||||
LOG(ALERT) << "cannot write L3TI to TMSI_TABLE";
|
||||
}
|
||||
return next;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// vim: ts=4 sw=4
|
||||
103
Control/TMSITable.h
Normal file
103
Control/TMSITable.h
Normal file
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
* Copyright 2008, 2009, 2010 Free Software Foundation, Inc.
|
||||
* Copyright 2010 Kestrel Signal Processing, Inc.
|
||||
*
|
||||
* This software is distributed under multiple licenses;
|
||||
* see the COPYING file in the main directory for licensing
|
||||
* information for this specific distribuion.
|
||||
*
|
||||
* This use of this software may be subject to additional restrictions.
|
||||
* See the LEGAL file in the main directory for details.
|
||||
|
||||
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.
|
||||
|
||||
*/
|
||||
|
||||
|
||||
|
||||
#ifndef TMSITABLE_H
|
||||
#define TMSITABLE_H
|
||||
|
||||
#include <map>
|
||||
|
||||
#include <Timeval.h>
|
||||
#include <Threads.h>
|
||||
|
||||
|
||||
struct sqlite3;
|
||||
|
||||
namespace GSM {
|
||||
class L3LocationUpdatingRequest;
|
||||
class L3MobileStationClassmark2;
|
||||
class L3MobileIdentity;
|
||||
}
|
||||
|
||||
|
||||
namespace Control {
|
||||
|
||||
class TMSITable {
|
||||
|
||||
private:
|
||||
|
||||
sqlite3 *mDB; ///< database connection
|
||||
|
||||
|
||||
public:
|
||||
|
||||
TMSITable(const char*wPath);
|
||||
|
||||
~TMSITable();
|
||||
|
||||
/**
|
||||
Create a new entry in the table.
|
||||
@param IMSI The IMSI to create an entry for.
|
||||
@param The associated LUR, if any.
|
||||
@return The assigned TMSI.
|
||||
*/
|
||||
unsigned assign(const char* IMSI, const GSM::L3LocationUpdatingRequest* lur=NULL);
|
||||
|
||||
/**
|
||||
Find an IMSI in the table.
|
||||
This is a log-time operation.
|
||||
@param TMSI The TMSI to find.
|
||||
@return Pointer to IMSI to be freed by the caller, or NULL.
|
||||
*/
|
||||
char* IMSI(unsigned TMSI) const;
|
||||
|
||||
/**
|
||||
Find a TMSI in the table.
|
||||
This is a linear-time operation.
|
||||
@param IMSI The IMSI to mach.
|
||||
@return A TMSI value or zero on failure.
|
||||
*/
|
||||
unsigned TMSI(const char* IMSI) const;
|
||||
|
||||
/** Write entries as text to a stream. */
|
||||
void dump(std::ostream&) const;
|
||||
|
||||
/** Clear the table completely. */
|
||||
void clear();
|
||||
|
||||
/** Set the IMEI. */
|
||||
bool IMEI(const char* IMSI, const char* IMEI);
|
||||
|
||||
/** Set the classmark. */
|
||||
bool classmark(const char* IMSI, const GSM::L3MobileStationClassmark2& classmark);
|
||||
|
||||
/** Get the next TI value to use for this IMSI or TMSI. */
|
||||
unsigned nextL3TI(const char* IMSI);
|
||||
|
||||
private:
|
||||
|
||||
/** Update the "accessed" time on a record. */
|
||||
void touch(unsigned TMSI) const;
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
// vim: ts=4 sw=4
|
||||
777
Control/TransactionTable.cpp
Normal file
777
Control/TransactionTable.cpp
Normal file
@@ -0,0 +1,777 @@
|
||||
/**@file TransactionTable and related classes. */
|
||||
|
||||
/*
|
||||
* Copyright 2008, 2010, 2011 Free Software Foundation, Inc.
|
||||
* Copyright 2010 Kestrel Signal Process, Inc.
|
||||
* Copyright 2011 Raqnge Networks, 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/>.
|
||||
|
||||
*/
|
||||
|
||||
|
||||
#include "TransactionTable.h"
|
||||
#include "ControlCommon.h"
|
||||
|
||||
#include <GSMLogicalChannel.h>
|
||||
#include <GSML3Message.h>
|
||||
#include <GSML3CCMessages.h>
|
||||
#include <GSML3RRMessages.h>
|
||||
#include <GSML3MMMessages.h>
|
||||
#include <GSMConfig.h>
|
||||
|
||||
#include <sqlite3.h>
|
||||
#include <sqlite3util.h>
|
||||
|
||||
#include <SIPEngine.h>
|
||||
#include <SIPInterface.h>
|
||||
|
||||
#include <CallControl.h>
|
||||
|
||||
#include <Logger.h>
|
||||
#undef WARNING
|
||||
|
||||
|
||||
using namespace std;
|
||||
using namespace GSM;
|
||||
using namespace Control;
|
||||
using namespace SIP;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void TransactionEntry::initTimers()
|
||||
{
|
||||
// Call this only once.
|
||||
// TODO -- It would be nice if these were all configurable.
|
||||
assert(mTimers.size()==0);
|
||||
mTimers["301"] = Z100Timer(T301ms);
|
||||
mTimers["302"] = Z100Timer(T302ms);
|
||||
mTimers["303"] = Z100Timer(T303ms);
|
||||
mTimers["304"] = Z100Timer(T304ms);
|
||||
mTimers["305"] = Z100Timer(T305ms);
|
||||
mTimers["308"] = Z100Timer(T308ms);
|
||||
mTimers["310"] = Z100Timer(T310ms);
|
||||
mTimers["313"] = Z100Timer(T313ms);
|
||||
mTimers["3113"] = Z100Timer(gConfig.getNum("GSM.Timer.T3113"));
|
||||
mTimers["TR1M"] = Z100Timer(TR1Mms);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// Form for MT transactions.
|
||||
TransactionEntry::TransactionEntry(
|
||||
const char* proxy,
|
||||
const L3MobileIdentity& wSubscriber,
|
||||
GSM::LogicalChannel* wChannel,
|
||||
const L3CMServiceType& wService,
|
||||
const L3CallingPartyBCDNumber& wCalling,
|
||||
GSM::CallState wState,
|
||||
const char *wMessage)
|
||||
:mID(gTransactionTable.newID()),
|
||||
mSubscriber(wSubscriber),mService(wService),
|
||||
mL3TI(gTMSITable.nextL3TI(wSubscriber.digits())),
|
||||
mCalling(wCalling),
|
||||
mSIP(proxy,mSubscriber.digits()),
|
||||
mGSMState(wState),
|
||||
mNumSQLTries(gConfig.getNum("Control.NumSQLTries")),
|
||||
mChannel(wChannel),
|
||||
mTerminationRequested(false)
|
||||
{
|
||||
if (wMessage) mMessage.assign(wMessage); //strncpy(mMessage,wMessage,160);
|
||||
else mMessage.assign(""); //mMessage[0]='\0';
|
||||
initTimers();
|
||||
}
|
||||
|
||||
// Form for MOC transactions.
|
||||
TransactionEntry::TransactionEntry(
|
||||
const char* proxy,
|
||||
const L3MobileIdentity& wSubscriber,
|
||||
GSM::LogicalChannel* wChannel,
|
||||
const L3CMServiceType& wService,
|
||||
unsigned wL3TI,
|
||||
const L3CalledPartyBCDNumber& wCalled)
|
||||
:mID(gTransactionTable.newID()),
|
||||
mSubscriber(wSubscriber),mService(wService),
|
||||
mL3TI(wL3TI),
|
||||
mCalled(wCalled),
|
||||
mSIP(proxy,mSubscriber.digits()),
|
||||
mGSMState(GSM::MOCInitiated),
|
||||
mNumSQLTries(gConfig.getNum("Control.NumSQLTries")),
|
||||
mChannel(wChannel),
|
||||
mTerminationRequested(false)
|
||||
{
|
||||
assert(mSubscriber.type()==GSM::IMSIType);
|
||||
mMessage.assign(""); //mMessage[0]='\0';
|
||||
initTimers();
|
||||
}
|
||||
|
||||
|
||||
// Form for SOS transactions.
|
||||
TransactionEntry::TransactionEntry(
|
||||
const char* proxy,
|
||||
const L3MobileIdentity& wSubscriber,
|
||||
GSM::LogicalChannel* wChannel,
|
||||
const L3CMServiceType& wService,
|
||||
unsigned wL3TI)
|
||||
:mID(gTransactionTable.newID()),
|
||||
mSubscriber(wSubscriber),mService(wService),
|
||||
mL3TI(wL3TI),
|
||||
mSIP(proxy,mSubscriber.digits()),
|
||||
mGSMState(GSM::MOCInitiated),
|
||||
mNumSQLTries(2*gConfig.getNum("Control.NumSQLTries")),
|
||||
mChannel(wChannel),
|
||||
mTerminationRequested(false)
|
||||
{
|
||||
mMessage.assign(""); //mMessage[0]='\0';
|
||||
initTimers();
|
||||
}
|
||||
|
||||
|
||||
// Form for MO-SMS transactions.
|
||||
TransactionEntry::TransactionEntry(
|
||||
const char* proxy,
|
||||
const L3MobileIdentity& wSubscriber,
|
||||
GSM::LogicalChannel* wChannel,
|
||||
const L3CalledPartyBCDNumber& wCalled,
|
||||
const char* wMessage)
|
||||
:mID(gTransactionTable.newID()),
|
||||
mSubscriber(wSubscriber),
|
||||
mService(GSM::L3CMServiceType::ShortMessage),
|
||||
mL3TI(7),mCalled(wCalled),
|
||||
mSIP(proxy,mSubscriber.digits()),
|
||||
mGSMState(GSM::SMSSubmitting),
|
||||
mNumSQLTries(gConfig.getNum("Control.NumSQLTries")),
|
||||
mChannel(wChannel),
|
||||
mTerminationRequested(false)
|
||||
{
|
||||
assert(mSubscriber.type()==GSM::IMSIType);
|
||||
if (wMessage!=NULL) mMessage.assign(wMessage); //strncpy(mMessage,wMessage,160);
|
||||
else mMessage.assign(""); //mMessage[0]='\0';
|
||||
initTimers();
|
||||
}
|
||||
|
||||
// Form for MO-SMS transactions with parallel call.
|
||||
TransactionEntry::TransactionEntry(
|
||||
const char* proxy,
|
||||
const L3MobileIdentity& wSubscriber,
|
||||
GSM::LogicalChannel* wChannel)
|
||||
:mID(gTransactionTable.newID()),
|
||||
mSubscriber(wSubscriber),
|
||||
mService(GSM::L3CMServiceType::ShortMessage),
|
||||
mL3TI(7),
|
||||
mSIP(proxy,mSubscriber.digits()),
|
||||
mGSMState(GSM::SMSSubmitting),
|
||||
mNumSQLTries(gConfig.getNum("Control.NumSQLTries")),
|
||||
mChannel(wChannel),
|
||||
mTerminationRequested(false)
|
||||
{
|
||||
assert(mSubscriber.type()==GSM::IMSIType);
|
||||
mMessage[0]='\0';
|
||||
initTimers();
|
||||
}
|
||||
|
||||
|
||||
|
||||
TransactionEntry::~TransactionEntry()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
bool TransactionEntry::timerExpired(const char* name) const
|
||||
{
|
||||
TimerTable::const_iterator itr = mTimers.find(name);
|
||||
assert(itr!=mTimers.end());
|
||||
ScopedLock lock(mLock);
|
||||
return (itr->second).expired();
|
||||
}
|
||||
|
||||
|
||||
bool TransactionEntry::anyTimerExpired() const
|
||||
{
|
||||
ScopedLock lock(mLock);
|
||||
TimerTable::const_iterator itr = mTimers.begin();
|
||||
while (itr!=mTimers.end()) {
|
||||
if ((itr->second).expired()) {
|
||||
LOG(INFO) << itr->first << " expired in " << *this;
|
||||
return true;
|
||||
}
|
||||
++itr;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
void TransactionEntry::resetTimers()
|
||||
{
|
||||
ScopedLock lock(mLock);
|
||||
TimerTable::iterator itr = mTimers.begin();
|
||||
while (itr!=mTimers.end()) {
|
||||
(itr->second).reset();
|
||||
++itr;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
bool TransactionEntry::dead() const
|
||||
{
|
||||
ScopedLock lock(mLock);
|
||||
|
||||
// Null state?
|
||||
if (mGSMState==GSM::NullState && stateAge()>180*1000) return true;
|
||||
// Stuck in proceeding?
|
||||
if (mSIP.state()==Proceeding && stateAge()>180*1000) return true;
|
||||
|
||||
// Paging timed out?
|
||||
if (mGSMState==GSM::Paging) {
|
||||
TimerTable::const_iterator itr = mTimers.find("3113");
|
||||
assert(itr!=mTimers.end());
|
||||
return (itr->second).expired();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
ostream& Control::operator<<(ostream& os, const TransactionEntry& entry)
|
||||
{
|
||||
entry.text(os);
|
||||
return os;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void TransactionEntry::text(ostream& os) const
|
||||
{
|
||||
ScopedLock lock(mLock);
|
||||
os << mID;
|
||||
if (mChannel) os << " " << *mChannel;
|
||||
else os << " no chan";
|
||||
os << " " << mSubscriber;
|
||||
os << " L3TI=" << mL3TI;
|
||||
os << " SIP-call-id=" << mSIP.callID();
|
||||
os << " SIP-proxy=" << mSIP.proxyIP() << ":" << mSIP.proxyPort();
|
||||
os << " " << mService;
|
||||
if (mCalled.digits()[0]) os << " to=" << mCalled.digits();
|
||||
if (mCalling.digits()[0]) os << " from=" << mCalling.digits();
|
||||
os << " GSMState=" << mGSMState;
|
||||
os << " SIPState=" << mSIP.state();
|
||||
os << " (" << (stateAge()+500)/1000 << " sec)";
|
||||
if (mMessage[0]) os << " message=\"" << mMessage << "\"";
|
||||
}
|
||||
|
||||
void TransactionEntry::message(const char *wMessage, size_t length)
|
||||
{
|
||||
/*if (length>520) {
|
||||
LOG(NOTICE) << "truncating long message: " << wMessage;
|
||||
length=520;
|
||||
}*/
|
||||
ScopedLock lock(mLock);
|
||||
//memcpy(mMessage,wMessage,length);
|
||||
//mMessage[length]='\0';
|
||||
mMessage.assign(wMessage, length);
|
||||
}
|
||||
|
||||
void TransactionEntry::messageType(const char *wContentType)
|
||||
{
|
||||
ScopedLock lock(mLock);
|
||||
mContentType.assign(wContentType);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void TransactionEntry::channel(GSM::LogicalChannel* wChannel)
|
||||
{
|
||||
ScopedLock lock(mLock);
|
||||
mChannel = wChannel;
|
||||
}
|
||||
|
||||
|
||||
void TransactionEntry::GSMState(GSM::CallState wState)
|
||||
{
|
||||
ScopedLock lock(mLock);
|
||||
mGSMState = wState;
|
||||
mStateTimer.now();
|
||||
}
|
||||
|
||||
|
||||
void TransactionEntry::echoSIPState(SIP::SIPState state) const
|
||||
{
|
||||
// Caller should hold mLock.
|
||||
if (mPrevSIPState==state) return;
|
||||
mPrevSIPState = state;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
SIP::SIPState TransactionEntry::MOCSendINVITE(const char* calledUser, const char* calledDomain, short rtpPort, unsigned codec)
|
||||
{
|
||||
ScopedLock lock(mLock);
|
||||
SIP::SIPState state = mSIP.MOCSendINVITE(calledUser,calledDomain,rtpPort,codec);
|
||||
echoSIPState(state);
|
||||
return state;
|
||||
}
|
||||
|
||||
SIP::SIPState TransactionEntry::MOCResendINVITE()
|
||||
{
|
||||
ScopedLock lock(mLock);
|
||||
SIP::SIPState state = mSIP.MOCResendINVITE();
|
||||
echoSIPState(state);
|
||||
return state;
|
||||
}
|
||||
|
||||
SIP::SIPState TransactionEntry::MOCWaitForOK()
|
||||
{
|
||||
ScopedLock lock(mLock);
|
||||
SIP::SIPState state = mSIP.MOCWaitForOK();
|
||||
echoSIPState(state);
|
||||
return state;
|
||||
}
|
||||
|
||||
SIP::SIPState TransactionEntry::MOCSendACK()
|
||||
{
|
||||
ScopedLock lock(mLock);
|
||||
SIP::SIPState state = mSIP.MOCSendACK();
|
||||
echoSIPState(state);
|
||||
return state;
|
||||
}
|
||||
|
||||
SIP::SIPState TransactionEntry::SOSSendINVITE(short rtpPort, unsigned codec)
|
||||
{
|
||||
ScopedLock lock(mLock);
|
||||
SIP::SIPState state = mSIP.SOSSendINVITE(rtpPort,codec);
|
||||
echoSIPState(state);
|
||||
return state;
|
||||
}
|
||||
|
||||
|
||||
SIP::SIPState TransactionEntry::MTCSendTrying()
|
||||
{
|
||||
ScopedLock lock(mLock);
|
||||
SIP::SIPState state = mSIP.MTCSendTrying();
|
||||
echoSIPState(state);
|
||||
return state;
|
||||
}
|
||||
|
||||
SIP::SIPState TransactionEntry::MTCSendRinging()
|
||||
{
|
||||
ScopedLock lock(mLock);
|
||||
SIP::SIPState state = mSIP.MTCSendRinging();
|
||||
echoSIPState(state);
|
||||
return state;
|
||||
}
|
||||
|
||||
SIP::SIPState TransactionEntry::MTCWaitForACK()
|
||||
{
|
||||
ScopedLock lock(mLock);
|
||||
SIP::SIPState state = mSIP.MTCWaitForACK();
|
||||
echoSIPState(state);
|
||||
return state;
|
||||
}
|
||||
|
||||
SIP::SIPState TransactionEntry::MTCCheckForCancel()
|
||||
{
|
||||
ScopedLock lock(mLock);
|
||||
SIP::SIPState state = mSIP.MTCCheckForCancel();
|
||||
echoSIPState(state);
|
||||
return state;
|
||||
}
|
||||
|
||||
|
||||
SIP::SIPState TransactionEntry::MTCSendOK(short rtpPort, unsigned codec)
|
||||
{
|
||||
ScopedLock lock(mLock);
|
||||
SIP::SIPState state = mSIP.MTCSendOK(rtpPort,codec);
|
||||
echoSIPState(state);
|
||||
return state;
|
||||
}
|
||||
|
||||
SIP::SIPState TransactionEntry::MODSendBYE()
|
||||
{
|
||||
ScopedLock lock(mLock);
|
||||
SIP::SIPState state = mSIP.MODSendBYE();
|
||||
echoSIPState(state);
|
||||
return state;
|
||||
}
|
||||
|
||||
SIP::SIPState TransactionEntry::MODResendBYE()
|
||||
{
|
||||
ScopedLock lock(mLock);
|
||||
SIP::SIPState state = mSIP.MODResendBYE();
|
||||
echoSIPState(state);
|
||||
return state;
|
||||
}
|
||||
|
||||
SIP::SIPState TransactionEntry::MODWaitForOK()
|
||||
{
|
||||
ScopedLock lock(mLock);
|
||||
SIP::SIPState state = mSIP.MODWaitForOK();
|
||||
echoSIPState(state);
|
||||
return state;
|
||||
}
|
||||
|
||||
SIP::SIPState TransactionEntry::MTDCheckBYE()
|
||||
{
|
||||
ScopedLock lock(mLock);
|
||||
SIP::SIPState state = mSIP.MTDCheckBYE();
|
||||
echoSIPState(state);
|
||||
return state;
|
||||
}
|
||||
|
||||
SIP::SIPState TransactionEntry::MTDSendOK()
|
||||
{
|
||||
ScopedLock lock(mLock);
|
||||
SIP::SIPState state = mSIP.MTDSendOK();
|
||||
echoSIPState(state);
|
||||
return state;
|
||||
}
|
||||
|
||||
SIP::SIPState TransactionEntry::MOSMSSendMESSAGE(const char* calledUser, const char* calledDomain, const char* contentType)
|
||||
{
|
||||
ScopedLock lock(mLock);
|
||||
SIP::SIPState state = mSIP.MOSMSSendMESSAGE(calledUser,calledDomain,mMessage.c_str(),contentType);
|
||||
echoSIPState(state);
|
||||
return state;
|
||||
}
|
||||
|
||||
SIP::SIPState TransactionEntry::MOSMSWaitForSubmit()
|
||||
{
|
||||
ScopedLock lock(mLock);
|
||||
SIP::SIPState state = mSIP.MOSMSWaitForSubmit();
|
||||
echoSIPState(state);
|
||||
return state;
|
||||
}
|
||||
|
||||
SIP::SIPState TransactionEntry::MTSMSSendOK()
|
||||
{
|
||||
ScopedLock lock(mLock);
|
||||
SIP::SIPState state = mSIP.MTSMSSendOK();
|
||||
echoSIPState(state);
|
||||
return state;
|
||||
}
|
||||
|
||||
bool TransactionEntry::sendINFOAndWaitForOK(unsigned info)
|
||||
{
|
||||
ScopedLock lock(mLock);
|
||||
return mSIP.sendINFOAndWaitForOK(info);
|
||||
}
|
||||
|
||||
void TransactionEntry::SIPUser(const char* IMSI)
|
||||
{
|
||||
ScopedLock lock(mLock);
|
||||
mSIP.user(IMSI);
|
||||
}
|
||||
|
||||
void TransactionEntry::SIPUser(const char* callID, const char *IMSI , const char *origID, const char *origHost)
|
||||
{
|
||||
ScopedLock lock(mLock);
|
||||
mSIP.user(callID,IMSI,origID,origHost);
|
||||
}
|
||||
|
||||
void TransactionEntry::called(const L3CalledPartyBCDNumber& wCalled)
|
||||
{
|
||||
ScopedLock lock(mLock);
|
||||
mCalled = wCalled;
|
||||
}
|
||||
|
||||
|
||||
void TransactionEntry::L3TI(unsigned wL3TI)
|
||||
{
|
||||
ScopedLock lock(mLock);
|
||||
mL3TI = wL3TI;
|
||||
}
|
||||
|
||||
|
||||
bool TransactionEntry::terminationRequested()
|
||||
{
|
||||
ScopedLock lock(mLock);
|
||||
bool retVal = mTerminationRequested;
|
||||
mTerminationRequested = false;
|
||||
return retVal;
|
||||
}
|
||||
|
||||
|
||||
|
||||
TransactionTable::TransactionTable()
|
||||
// This assumes the main application uses sdevrandom.
|
||||
:mIDCounter(random())
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
TransactionTable::~TransactionTable()
|
||||
{
|
||||
// Don't bother disposing of the memory,
|
||||
// since this is only invoked when the application exits.
|
||||
if (mDB) sqlite3_close(mDB);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
unsigned TransactionTable::newID()
|
||||
{
|
||||
ScopedLock lock(mLock);
|
||||
return mIDCounter++;
|
||||
}
|
||||
|
||||
|
||||
void TransactionTable::add(TransactionEntry* value)
|
||||
{
|
||||
LOG(INFO) << "new transaction " << *value;
|
||||
ScopedLock lock(mLock);
|
||||
mTable[value->ID()]=value;
|
||||
}
|
||||
|
||||
|
||||
|
||||
TransactionEntry* TransactionTable::find(unsigned key)
|
||||
{
|
||||
// Since this is a log-time operation, we don't screw that up by calling clearDeadEntries.
|
||||
|
||||
// ID==0 is a non-valid special case.
|
||||
LOG(DEBUG) << "by key: " << key;
|
||||
assert(key);
|
||||
ScopedLock lock(mLock);
|
||||
TransactionMap::iterator itr = mTable.find(key);
|
||||
if (itr==mTable.end()) return NULL;
|
||||
if (itr->second->dead()) {
|
||||
innerRemove(itr);
|
||||
return NULL;
|
||||
}
|
||||
return (itr->second);
|
||||
}
|
||||
|
||||
|
||||
void TransactionTable::innerRemove(TransactionMap::iterator itr)
|
||||
{
|
||||
LOG(DEBUG) << "removing transaction: " << *(itr->second);
|
||||
gSIPInterface.removeCall(itr->second->SIPCallID());
|
||||
delete itr->second;
|
||||
mTable.erase(itr);
|
||||
}
|
||||
|
||||
|
||||
bool TransactionTable::remove(unsigned key)
|
||||
{
|
||||
// ID==0 is a non-valid special case, and it shouldn't be passed here.
|
||||
if (key==0) {
|
||||
LOG(ERR) << "called with key==0";
|
||||
return false;
|
||||
}
|
||||
|
||||
ScopedLock lock(mLock);
|
||||
TransactionMap::iterator itr = mTable.find(key);
|
||||
if (itr==mTable.end()) return false;
|
||||
innerRemove(itr);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TransactionTable::removePaging(unsigned key)
|
||||
{
|
||||
// ID==0 is a non-valid special case and should not be passed here.
|
||||
assert(key);
|
||||
ScopedLock lock(mLock);
|
||||
TransactionMap::iterator itr = mTable.find(key);
|
||||
if (itr==mTable.end()) return false;
|
||||
if (itr->second->GSMState()!=GSM::Paging) return false;
|
||||
innerRemove(itr);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void TransactionTable::clearDeadEntries()
|
||||
{
|
||||
// Caller should hold mLock.
|
||||
TransactionMap::iterator itr = mTable.begin();
|
||||
while (itr!=mTable.end()) {
|
||||
if (!itr->second->dead()) ++itr;
|
||||
else {
|
||||
LOG(DEBUG) << "erasing " << itr->first;
|
||||
TransactionMap::iterator old = itr;
|
||||
itr++;
|
||||
innerRemove(old);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
TransactionEntry* TransactionTable::find(const GSM::LogicalChannel *chan)
|
||||
{
|
||||
LOG(DEBUG) << "by channel: " << *chan << " (" << chan << ")";
|
||||
|
||||
// Yes, it's linear time.
|
||||
// Since clearDeadEntries is also linear, do that here, too.
|
||||
clearDeadEntries();
|
||||
|
||||
// Brute force search.
|
||||
ScopedLock lock(mLock);
|
||||
for (TransactionMap::iterator itr = mTable.begin(); itr!=mTable.end(); ++itr) {
|
||||
const GSM::LogicalChannel* thisChan = itr->second->channel();
|
||||
//LOG(DEBUG) << "looking for " << *chan << " (" << chan << ")" << ", found " << *(thisChan) << " (" << thisChan << ")";
|
||||
if ((void*)thisChan == (void*)chan) return itr->second;
|
||||
}
|
||||
//LOG(DEBUG) << "no match for " << *chan << " (" << chan << ")";
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
TransactionEntry* TransactionTable::find(const L3MobileIdentity& mobileID, GSM::CallState state)
|
||||
{
|
||||
LOG(DEBUG) << "by ID and state: " << mobileID << " in " << state;
|
||||
|
||||
// Yes, it's linear time.
|
||||
// Since clearDeadEntries is also linear, do that here, too.
|
||||
clearDeadEntries();
|
||||
|
||||
// Brtue force search.
|
||||
ScopedLock lock(mLock);
|
||||
for (TransactionMap::iterator itr = mTable.begin(); itr!=mTable.end(); ++itr) {
|
||||
if (itr->second->GSMState() != state) continue;
|
||||
if (itr->second->subscriber() == mobileID) return itr->second;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
TransactionEntry* TransactionTable::find(const L3MobileIdentity& mobileID, const char* callID)
|
||||
{
|
||||
assert(callID);
|
||||
LOG(DEBUG) << "by ID and call-ID: " << mobileID << ", call " << callID;
|
||||
|
||||
string callIDString = string(callID);
|
||||
// Yes, it's linear time.
|
||||
// Since clearDeadEntries is also linear, do that here, too.
|
||||
clearDeadEntries();
|
||||
|
||||
// Brtue force search.
|
||||
ScopedLock lock(mLock);
|
||||
for (TransactionMap::iterator itr = mTable.begin(); itr!=mTable.end(); ++itr) {
|
||||
if (itr->second->mSIP.callID() != callIDString) continue;
|
||||
if (itr->second->subscriber() == mobileID) return itr->second;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
TransactionEntry* TransactionTable::answeredPaging(const L3MobileIdentity& mobileID)
|
||||
{
|
||||
// Yes, it's linear time.
|
||||
// Even in a 6-ARFCN system, it should rarely be more than a dozen entries.
|
||||
|
||||
// Since clearDeadEntries is also linear, do that here, too.
|
||||
clearDeadEntries();
|
||||
|
||||
// Brtue force search.
|
||||
ScopedLock lock(mLock);
|
||||
for (TransactionMap::iterator itr = mTable.begin(); itr!=mTable.end(); ++itr) {
|
||||
if (itr->second->GSMState() != GSM::Paging) continue;
|
||||
if (itr->second->subscriber() == mobileID) {
|
||||
// Stop T3113 and change the state.
|
||||
itr->second->GSMState(AnsweredPaging);
|
||||
itr->second->resetTimer("3113");
|
||||
return itr->second;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
GSM::LogicalChannel* TransactionTable::findChannel(const L3MobileIdentity& mobileID)
|
||||
{
|
||||
// Yes, it's linear time.
|
||||
// Even in a 6-ARFCN system, it should rarely be more than a dozen entries.
|
||||
|
||||
// Since clearDeadEntries is also linear, do that here, too.
|
||||
clearDeadEntries();
|
||||
|
||||
// Brtue force search.
|
||||
ScopedLock lock(mLock);
|
||||
for (TransactionMap::iterator itr = mTable.begin(); itr!=mTable.end(); ++itr) {
|
||||
if (itr->second->subscriber() != mobileID) continue;
|
||||
GSM::LogicalChannel* chan = itr->second->channel();
|
||||
if (!chan) continue;
|
||||
if (chan->type() == FACCHType) return chan;
|
||||
if (chan->type() == SDCCHType) return chan;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
unsigned TransactionTable::countChan(const GSM::LogicalChannel* chan)
|
||||
{
|
||||
ScopedLock lock(mLock);
|
||||
clearDeadEntries();
|
||||
unsigned count = 0;
|
||||
for (TransactionMap::iterator itr = mTable.begin(); itr!=mTable.end(); ++itr) {
|
||||
if (itr->second->channel() == chan) count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
|
||||
|
||||
size_t TransactionTable::dump(ostream& os) const
|
||||
{
|
||||
ScopedLock lock(mLock);
|
||||
for (TransactionMap::const_iterator itr = mTable.begin(); itr!=mTable.end(); ++itr) {
|
||||
os << *(itr->second) << endl;
|
||||
}
|
||||
return mTable.size();
|
||||
}
|
||||
|
||||
|
||||
TransactionEntry* TransactionTable::findLongestCall()
|
||||
{
|
||||
ScopedLock lock(mLock);
|
||||
clearDeadEntries();
|
||||
long longTime = 0;
|
||||
TransactionMap::iterator longCall = mTable.end();
|
||||
for (TransactionMap::iterator itr = mTable.begin(); itr!=mTable.end(); ++itr) {
|
||||
if (!(itr->second->channel())) continue;
|
||||
if (itr->second->GSMState() != GSM::Active) continue;
|
||||
long runTime = itr->second->stateAge();
|
||||
if (runTime > longTime) {
|
||||
runTime = longTime;
|
||||
longCall = itr;
|
||||
}
|
||||
}
|
||||
if (longCall == mTable.end()) return NULL;
|
||||
return longCall->second;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// vim: ts=4 sw=4
|
||||
433
Control/TransactionTable.h
Normal file
433
Control/TransactionTable.h
Normal file
@@ -0,0 +1,433 @@
|
||||
/**@file Declarations for TransactionTable and related classes. */
|
||||
/*
|
||||
* Copyright 2008, 2009, 2010 Free Software Foundation, Inc.
|
||||
* Copyright 2010 Kestrel Signal Processing, Inc.
|
||||
* Copyright 2011 Range Networks, Inc.
|
||||
*
|
||||
* This software is distributed under multiple licenses;
|
||||
* see the COPYING file in the main directory for licensing
|
||||
* information for this specific distribuion.
|
||||
*
|
||||
* This use of this software may be subject to additional restrictions.
|
||||
* See the LEGAL file in the main directory for details.
|
||||
|
||||
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.
|
||||
|
||||
*/
|
||||
|
||||
|
||||
|
||||
#ifndef TRANSACTIONTABLE_H
|
||||
#define TRANSACTIONTABLE_H
|
||||
|
||||
|
||||
#include <stdio.h>
|
||||
#include <list>
|
||||
|
||||
#include <Logger.h>
|
||||
#include <Interthread.h>
|
||||
#include <Timeval.h>
|
||||
|
||||
|
||||
#include <GSML3CommonElements.h>
|
||||
#include <GSML3MMElements.h>
|
||||
#include <GSML3CCElements.h>
|
||||
#include <GSML3RRElements.h>
|
||||
#include <SIPEngine.h>
|
||||
|
||||
|
||||
|
||||
struct sqlite3;
|
||||
|
||||
|
||||
/**@namespace Control This namepace is for use by the control layer. */
|
||||
namespace Control {
|
||||
|
||||
typedef std::map<std::string, GSM::Z100Timer> TimerTable;
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
A TransactionEntry object is used to maintain the state of a transaction
|
||||
as it moves from channel to channel.
|
||||
The object itself is not thread safe.
|
||||
*/
|
||||
class TransactionEntry {
|
||||
|
||||
private:
|
||||
|
||||
mutable Mutex mLock; ///< thread-safe control, shared from gTransactionTable
|
||||
|
||||
/**@name Stable variables, fixed in the constructor or written only once. */
|
||||
//@{
|
||||
unsigned mID; ///< the internal transaction ID, assigned by a TransactionTable
|
||||
|
||||
GSM::L3MobileIdentity mSubscriber; ///< some kind of subscriber ID, preferably IMSI
|
||||
GSM::L3CMServiceType mService; ///< the associated service type
|
||||
unsigned mL3TI; ///< the L3 short transaction ID, the version we *send* to the MS
|
||||
|
||||
GSM::L3CalledPartyBCDNumber mCalled; ///< the associated called party number, if known
|
||||
GSM::L3CallingPartyBCDNumber mCalling; ///< the associated calling party number, if known
|
||||
|
||||
// TODO -- This should be expaned to deal with long messages.
|
||||
//char mMessage[522]; ///< text messaging payload
|
||||
std::string mMessage; ///< text message payload
|
||||
std::string mContentType; ///< text message payload content type
|
||||
//@}
|
||||
|
||||
SIP::SIPEngine mSIP; ///< the SIP IETF RFC-3621 protocol engine
|
||||
mutable SIP::SIPState mPrevSIPState; ///< previous SIP state, prior to most recent transactions
|
||||
GSM::CallState mGSMState; ///< the GSM/ISDN/Q.931 call state
|
||||
Timeval mStateTimer; ///< timestamp of last state change.
|
||||
TimerTable mTimers; ///< table of Z100-type state timers
|
||||
|
||||
unsigned mNumSQLTries; ///< number of SQL tries for DB operations
|
||||
|
||||
GSM::LogicalChannel *mChannel; ///< current channel of the transaction
|
||||
|
||||
bool mTerminationRequested;
|
||||
|
||||
public:
|
||||
|
||||
/** This form is used for MTC or MT-SMS with TI generated by the network. */
|
||||
TransactionEntry(const char* proxy,
|
||||
const GSM::L3MobileIdentity& wSubscriber,
|
||||
GSM::LogicalChannel* wChannel,
|
||||
const GSM::L3CMServiceType& wService,
|
||||
const GSM::L3CallingPartyBCDNumber& wCalling,
|
||||
GSM::CallState wState = GSM::NullState,
|
||||
const char *wMessage = NULL);
|
||||
|
||||
/** This form is used for MOC, setting mGSMState to MOCInitiated. */
|
||||
TransactionEntry(const char* proxy,
|
||||
const GSM::L3MobileIdentity& wSubscriber,
|
||||
GSM::LogicalChannel* wChannel,
|
||||
const GSM::L3CMServiceType& wService,
|
||||
unsigned wL3TI,
|
||||
const GSM::L3CalledPartyBCDNumber& wCalled);
|
||||
|
||||
/** This form is used for SOS calls, setting mGSMState to MOCInitiated. */
|
||||
TransactionEntry(const char* proxy,
|
||||
const GSM::L3MobileIdentity& wSubscriber,
|
||||
GSM::LogicalChannel* wChannel,
|
||||
const GSM::L3CMServiceType& wService,
|
||||
unsigned wL3TI);
|
||||
|
||||
/** Form for MO-SMS; sets yet-unknown TI to 7 and GSM state to SMSSubmitting */
|
||||
TransactionEntry(const char* proxy,
|
||||
const GSM::L3MobileIdentity& wSubscriber,
|
||||
GSM::LogicalChannel* wChannel,
|
||||
const GSM::L3CalledPartyBCDNumber& wCalled,
|
||||
const char* wMessage);
|
||||
|
||||
/** Form for MO-SMS with a parallel call; sets yet-unknown TI to 7 and GSM state to SMSSubmitting */
|
||||
TransactionEntry(const char* proxy,
|
||||
const GSM::L3MobileIdentity& wSubscriber,
|
||||
GSM::LogicalChannel* wChannel);
|
||||
|
||||
|
||||
/** Delete the database entry upon destruction. */
|
||||
~TransactionEntry();
|
||||
|
||||
/**@name Accessors. */
|
||||
//@{
|
||||
unsigned L3TI() const { return mL3TI; }
|
||||
void L3TI(unsigned wL3TI);
|
||||
|
||||
const GSM::LogicalChannel* channel() const { return mChannel; }
|
||||
GSM::LogicalChannel* channel() { return mChannel; }
|
||||
|
||||
void channel(GSM::LogicalChannel* wChannel);
|
||||
|
||||
const GSM::L3MobileIdentity& subscriber() const { return mSubscriber; }
|
||||
|
||||
const GSM::L3CMServiceType& service() const { return mService; }
|
||||
|
||||
const GSM::L3CalledPartyBCDNumber& called() const { return mCalled; }
|
||||
void called(const GSM::L3CalledPartyBCDNumber&);
|
||||
|
||||
const GSM::L3CallingPartyBCDNumber& calling() const { return mCalling; }
|
||||
|
||||
const char* message() const { return mMessage.c_str(); }
|
||||
void message(const char *wMessage, size_t length);
|
||||
const char* messageType() const { return mContentType.c_str(); }
|
||||
void messageType(const char *wContentType);
|
||||
|
||||
unsigned ID() const { return mID; }
|
||||
|
||||
GSM::CallState GSMState() const { ScopedLock lock(mLock); return mGSMState; }
|
||||
void GSMState(GSM::CallState wState);
|
||||
|
||||
//@}
|
||||
|
||||
|
||||
/** Initiate the termination process. */
|
||||
void terminate() { ScopedLock lock(mLock); mTerminationRequested=true; }
|
||||
|
||||
bool terminationRequested();
|
||||
|
||||
/**@name SIP-side operations */
|
||||
//@{
|
||||
|
||||
SIP::SIPState SIPState() { ScopedLock lock(mLock); return mSIP.state(); }
|
||||
|
||||
SIP::SIPState MOCSendINVITE(const char* calledUser, const char* calledDomain, short rtpPort, unsigned codec);
|
||||
SIP::SIPState MOCResendINVITE();
|
||||
SIP::SIPState MOCWaitForOK();
|
||||
SIP::SIPState MOCSendACK();
|
||||
void MOCInitRTP() { ScopedLock lock(mLock); return mSIP.MOCInitRTP(); }
|
||||
|
||||
SIP::SIPState SOSSendINVITE(short rtpPort, unsigned codec);
|
||||
SIP::SIPState SOSResendINVITE() { return MOCResendINVITE(); }
|
||||
SIP::SIPState SOSWaitForOK() { return MOCWaitForOK(); }
|
||||
SIP::SIPState SOSSendACK() { return MOCSendACK(); }
|
||||
void SOSInitRTP() { MOCInitRTP(); }
|
||||
|
||||
|
||||
SIP::SIPState MTCSendTrying();
|
||||
SIP::SIPState MTCSendRinging();
|
||||
SIP::SIPState MTCWaitForACK();
|
||||
SIP::SIPState MTCCheckForCancel();
|
||||
SIP::SIPState MTCSendOK(short rtpPort, unsigned codec);
|
||||
void MTCInitRTP() { ScopedLock lock(mLock); mSIP.MTCInitRTP(); }
|
||||
|
||||
SIP::SIPState MODSendBYE();
|
||||
SIP::SIPState MODResendBYE();
|
||||
SIP::SIPState MODWaitForOK();
|
||||
|
||||
SIP::SIPState MTDCheckBYE();
|
||||
SIP::SIPState MTDSendOK();
|
||||
|
||||
// TODO: Remove contentType from here and use the setter above.
|
||||
SIP::SIPState MOSMSSendMESSAGE(const char* calledUser, const char* calledDomain, const char* contentType);
|
||||
SIP::SIPState MOSMSWaitForSubmit();
|
||||
|
||||
SIP::SIPState MTSMSSendOK();
|
||||
|
||||
bool sendINFOAndWaitForOK(unsigned info);
|
||||
|
||||
void txFrame(unsigned char* frame) { return mSIP.txFrame(frame); }
|
||||
int rxFrame(unsigned char* frame) { return mSIP.rxFrame(frame); }
|
||||
bool startDTMF(char key) { return mSIP.startDTMF(key); }
|
||||
void stopDTMF() { mSIP.stopDTMF(); }
|
||||
|
||||
void SIPUser(const std::string& IMSI) { SIPUser(IMSI.c_str()); }
|
||||
void SIPUser(const char* IMSI);
|
||||
void SIPUser(const char* callID, const char *IMSI , const char *origID, const char *origHost);
|
||||
|
||||
const std::string SIPCallID() const { ScopedLock lock(mLock); return mSIP.callID(); }
|
||||
|
||||
// These are called by SIPInterface.
|
||||
void saveINVITE(const osip_message_t* invite, bool local)
|
||||
{ ScopedLock lock(mLock); mSIP.saveINVITE(invite,local); }
|
||||
void saveBYE(const osip_message_t* bye, bool local)
|
||||
{ ScopedLock lock(mLock); mSIP.saveBYE(bye,local); }
|
||||
|
||||
//@}
|
||||
|
||||
unsigned stateAge() const { ScopedLock lock(mLock); return mStateTimer.elapsed(); }
|
||||
|
||||
/**@name Timer access. */
|
||||
//@{
|
||||
|
||||
bool timerExpired(const char* name) const;
|
||||
|
||||
void setTimer(const char* name)
|
||||
{ ScopedLock lock(mLock); return mTimers[name].set(); }
|
||||
|
||||
void setTimer(const char* name, long newLimit)
|
||||
{ ScopedLock lock(mLock); return mTimers[name].set(newLimit); }
|
||||
|
||||
void resetTimer(const char* name)
|
||||
{ ScopedLock lock(mLock); return mTimers[name].reset(); }
|
||||
|
||||
/** Return true if any Q.931 timer is expired. */
|
||||
bool anyTimerExpired() const;
|
||||
|
||||
/** Reset all Q.931 timers. */
|
||||
void resetTimers();
|
||||
|
||||
//@}
|
||||
|
||||
/** Return true if clearing is in progress in the GSM side. */
|
||||
bool clearingGSM() const
|
||||
{ ScopedLock lock(mLock); return (mGSMState==GSM::ReleaseRequest) || (mGSMState==GSM::DisconnectIndication); }
|
||||
|
||||
/** Retrns true if the transaction is "dead". */
|
||||
bool dead() const;
|
||||
|
||||
/** Dump information as text for debugging. */
|
||||
void text(std::ostream&) const;
|
||||
|
||||
private:
|
||||
|
||||
friend class TransactionTable;
|
||||
|
||||
/** Create L3 timers from GSM and Q.931 (network side) */
|
||||
void initTimers();
|
||||
|
||||
/** Echo latest SIPSTATE to the database. */
|
||||
void echoSIPState(SIP::SIPState state) const;
|
||||
};
|
||||
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const TransactionEntry&);
|
||||
|
||||
|
||||
/** A map of transactions keyed by ID. */
|
||||
class TransactionMap : public std::map<unsigned,TransactionEntry*> {};
|
||||
|
||||
/**
|
||||
A table for tracking the states of active transactions.
|
||||
*/
|
||||
class TransactionTable {
|
||||
|
||||
private:
|
||||
|
||||
sqlite3 *mDB; ///< database connection
|
||||
|
||||
TransactionMap mTable;
|
||||
mutable Mutex mLock;
|
||||
unsigned mIDCounter;
|
||||
|
||||
public:
|
||||
|
||||
/**
|
||||
Create a transaction table.
|
||||
@param path Path fto sqlite3 database file.
|
||||
*/
|
||||
TransactionTable();
|
||||
|
||||
~TransactionTable();
|
||||
|
||||
// TransactionTable does not need a destructor.
|
||||
|
||||
/**
|
||||
Return a new ID for use in the table.
|
||||
*/
|
||||
unsigned newID();
|
||||
|
||||
/**
|
||||
Insert a new entry into the table; deleted by the table later.
|
||||
@param value The entry to insert into the table; will be deleted by the table later.
|
||||
*/
|
||||
void add(TransactionEntry* value);
|
||||
|
||||
/**
|
||||
Find an entry and return a pointer into the table.
|
||||
@param wID The transaction ID to search
|
||||
@return NULL if ID is not found or was dead
|
||||
*/
|
||||
TransactionEntry* find(unsigned wID);
|
||||
|
||||
/**
|
||||
Find the longest-running non-SOS call.
|
||||
@return NULL if there are no calls or if all are SOS.
|
||||
*/
|
||||
TransactionEntry* findLongestCall();
|
||||
|
||||
/**
|
||||
Remove an entry from the table and from gSIPMessageMap.
|
||||
@param wID The transaction ID to search.
|
||||
@return True if the ID was really in the table and deleted.
|
||||
*/
|
||||
bool remove(unsigned wID);
|
||||
|
||||
bool remove(TransactionEntry* transaction) { return remove(transaction->ID()); }
|
||||
|
||||
/**
|
||||
Remove an entry from the table and from gSIPMessageMap,
|
||||
if it is in the Paging state.
|
||||
@param wID The transaction ID to search.
|
||||
@return True if the ID was really in the table and deleted.
|
||||
*/
|
||||
bool removePaging(unsigned wID);
|
||||
|
||||
|
||||
/**
|
||||
Find an entry by its channel pointer.
|
||||
Also clears dead entries during search.
|
||||
@param chan The channel pointer to the first record found.
|
||||
@return pointer to entry or NULL if no active match
|
||||
*/
|
||||
TransactionEntry* find(const GSM::LogicalChannel *chan);
|
||||
|
||||
/**
|
||||
Find an entry in the given state by its mobile ID.
|
||||
Also clears dead entries during search.
|
||||
@param mobileID The mobile to search for.
|
||||
@return pointer to entry or NULL if no match
|
||||
*/
|
||||
TransactionEntry* find(const GSM::L3MobileIdentity& mobileID, GSM::CallState state);
|
||||
|
||||
|
||||
/** Find by subscriber and SIP call ID. */
|
||||
TransactionEntry* find(const GSM::L3MobileIdentity& mobileID, const char* callID);
|
||||
|
||||
/**
|
||||
Find an entry in the Paging state by its mobile ID, change state to AnsweredPaging and reset T3113.
|
||||
Also clears dead entries during search.
|
||||
@param mobileID The mobile to search for.
|
||||
@return pointer to entry or NULL if no match
|
||||
*/
|
||||
TransactionEntry* answeredPaging(const GSM::L3MobileIdentity& mobileID);
|
||||
|
||||
|
||||
/**
|
||||
Find the channel, if any, used for current transactions by this mobile ID.
|
||||
@param mobileID The target mobile subscriber.
|
||||
@return pointer to TCH/FACCH, SDCCH or NULL.
|
||||
*/
|
||||
GSM::LogicalChannel* findChannel(const GSM::L3MobileIdentity& mobileID);
|
||||
|
||||
/** Count the number of transactions using a particular channel. */
|
||||
unsigned countChan(const GSM::LogicalChannel*);
|
||||
|
||||
size_t size() { ScopedLock lock(mLock); return mTable.size(); }
|
||||
|
||||
size_t dump(std::ostream& os) const;
|
||||
|
||||
|
||||
private:
|
||||
|
||||
friend class TransactionEntry;
|
||||
|
||||
/** Accessor to database connection. */
|
||||
sqlite3* DB() { return mDB; }
|
||||
|
||||
/**
|
||||
Remove "dead" entries from the table.
|
||||
A "dead" entry is a transaction that is no longer active.
|
||||
The caller should hold mLock.
|
||||
*/
|
||||
void clearDeadEntries();
|
||||
|
||||
/**
|
||||
Remove and entry from the table and from gSIPInterface.
|
||||
*/
|
||||
void innerRemove(TransactionMap::iterator);
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
} //Control
|
||||
|
||||
|
||||
|
||||
/**@addtogroup Globals */
|
||||
//@{
|
||||
/** A single global transaction table in the global namespace. */
|
||||
extern Control::TransactionTable gTransactionTable;
|
||||
//@}
|
||||
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
// vim: ts=4 sw=4
|
||||
Reference in New Issue
Block a user