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:
David Burgess
2011-10-07 02:40:51 +00:00
parent f367b8728b
commit c0a5c1509e
218 changed files with 217437 additions and 0 deletions

1054
Control/CallControl.cpp Normal file

File diff suppressed because it is too large Load Diff

67
Control/CallControl.h Normal file
View 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
View 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
View 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
View 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
View 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
View 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:

View 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

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

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