merge 5.0 preview from commercial

This commit is contained in:
Michael Iedema
2014-07-16 23:57:22 +02:00
parent 47f350b77d
commit 49087580a0
255 changed files with 15407 additions and 10471 deletions

75
Control/CodecSet.h Normal file
View File

@@ -0,0 +1,75 @@
/**@file Declarations for common-use control-layer functions. */
/*
* Copyright 2014 Range Networks, Inc.
*
* This software is distributed under multiple licenses;
* see the COPYING file in the main directory for licensing
* information for this specific distribution.
*
* 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 _CODECSET_H_
#define _CODECSET_H_ 1
namespace GSM { class L3MobileIdentity; };
namespace Control {
// Meaning of these bits is hard to find: It is in 48.008 3.2.2.11:
enum CodecType { // Codec Bitmap defined in 26.103 6.2. It is one or two bytes
// low bit of first byte in bitmap
CodecTypeUndefined = 0,
GSM_FR = 0x1, // aka GSM610
GSM_HR = 0x2,
GSM_EFR = 0x4,
AMR_FR = 0x8,
AMR_HR = 0x10,
UMTS_AMR = 0x20,
UMTS_AMR2 = 0x40,
TDMA_EFR = 0x80, // high bit of first byte in bitmap
// We can totally ignore the second byte:
PDC_EFR = 0x100, // low bit of second byte in bitmap
AMR_FR_WB = 0x200,
UMTS_AMR_WB = 0x400,
OHR_AMR = 0x800,
OFR_AMR_WB = 0x1000,
OHR_AMR_WB = 0x2000,
// then two reserved bits.
// In addition the above codecs defined in the GSM spec and used on the air-interface,
// we will put other codecs we might want to use for RTP on the SIP interface in here too
// so we can use the same CodecSet in the SIP directory.
// This is not in the spec, but use this value to indicate none of the codecs above.
PCMULAW = 0x10000, // G.711 PCM, 64kbps. comes in two flavors: uLaw and aLaw.
PCMALAW = 0x20000 // We dont support it yet.
// There is also G711.1, which is slighly wider band, 96kbps.
};
const char *CodecType2Name(CodecType ct);
// (pat) Added 10-22-2012.
// 3GPP 24.008 10.5.4.32 and 3GPP 26.103
class CodecSet {
public:
CodecType mCodecs; // It is a set of CodecEnum
bool isSet(CodecType bit) { return mCodecs & bit; }
bool isEmpty() { return !mCodecs; }
CodecSet(): mCodecs(CodecTypeUndefined) {}
CodecSet(CodecType wtype) : mCodecs(wtype) {}
// Allow logical OR of two CodecSets together.
void orSet(CodecSet other) { mCodecs = (CodecType) (mCodecs | other.mCodecs); }
void orType(CodecType vals) { mCodecs = (CodecType) (mCodecs | vals); }
CodecSet operator|(CodecSet other) { return CodecSet((CodecType)(mCodecs | other.mCodecs)); }
void text(std::ostream&) const;
friend std::ostream& operator<<(std::ostream& os, const CodecSet&);
};
}; // namespace Control
#endif

View File

@@ -2,11 +2,11 @@
/*
* Copyright 2008, 2009, 2010 Free Software Foundation, Inc.
* Copyright 2010 Kestrel Signal Processing, Inc.
* Copyright 2011 Range Networks, Inc.
* Copyright 2011, 2014 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.
* information for this specific distribution.
*
* This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details.

View File

@@ -1,10 +1,10 @@
/**@file Declarations for common-use control-layer functions. */
/*
* Copyright 2013 Range Networks, Inc.
* Copyright 2013, 2014 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.
* information for this specific distribution.
*
* This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details.
@@ -14,15 +14,19 @@
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*/
#define LOG_GROUP LogGroup::Control
#include <stdio.h>
#include <stdlib.h>
#include <Utils.h>
#include <L3Enums.h>
#include "Defines.h"
#include "ControlTransfer.h"
//#include "TransactionTable.h"
#include "L3TranEntry.h"
#include <GSMTransfer.h>
#include <SIPDialog.h>
#include "CodecSet.h"
namespace Control {
using namespace GSM;
#define CASENAME(x) case x: return #x;
const char *CodecType2Name(CodecType ct)
@@ -134,7 +138,7 @@ ostream& operator<<(ostream& os, CallState state)
std::ostream& operator<<(std::ostream& os, const TMSI_t&tmsi)
{
if (tmsi.valid()) {
char buf[10]; sprintf(buf,"0x%x",tmsi.value()); os <<buf;
char buf[10]; snprintf(buf,sizeof(buf),"0x%x",tmsi.value()); os <<buf;
} else {
os <<"(no tmsi)";
}
@@ -166,15 +170,6 @@ void FullMobileId::fmidSet(string value)
}
}
bool FullMobileId::fmidMatch(const GSM::L3MobileIdentity&mobileId) const
{
switch (mobileId.type()) {
case GSM::IMSIType: return 0 == strcmp(mImsi.c_str(),mobileId.digits());
case GSM::IMEIType: return 0 == strcmp(mImei.c_str(),mobileId.digits());
case GSM::TMSIType: return mTmsi.valid() && mTmsi.value() == mobileId.TMSI();
default: return false; // something wrong, but certainly no match
}
}
std::ostream& operator<<(std::ostream& os, const FullMobileId&msid)
{
@@ -204,13 +199,27 @@ const char *GenericL3Msg::typeName()
}
#endif
void controlInit()
string BestNeighbor::text() const
{
LOG(DEBUG);
gTMSITable.tmsiTabOpen(gConfig.getStr("Control.Reporting.TMSITable").c_str());
LOG(DEBUG);
gNewTransactionTable.ttInit();
LOG(DEBUG);
ostringstream ss;
if (!mValid) {
ss <<LOGVAR(mValid);
} else {
ss <<LOGVAR(mValid) <<LOGVAR(mARFCN) <<LOGVAR(mBSIC) <<LOGVAR(mRxlev) <<LOGVAR(mHandoverCause);
}
return ss.str();
}
string NeighborPenalty::text() const
{
ostringstream ss;
ss << LOGVAR(mARFCN) <<LOGVAR(mBSIC) <<LOGVAR(mPenaltyTime.remaining());
return ss.str();
};
std::ostream& operator<<(std::ostream& os, BestNeighbor best) { os <<best.text(); return os; }
std::ostream& operator<<(std::ostream& os, BestNeighbor *best) { os <<best->text(); return os; }
std::ostream& operator<<(std::ostream& os, NeighborPenalty np) { os <<np.text(); return os; }
std::ostream& operator<<(std::ostream& os, NeighborPenalty *np) { os <<np->text(); return os; }
};

View File

@@ -1,10 +1,10 @@
/**@file Declarations for common-use control-layer functions. */
/*
* Copyright 2013 Range Networks, Inc.
* Copyright 2013, 2014 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.
* information for this specific distribution.
*
* This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details.
@@ -14,26 +14,28 @@
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*/
#ifndef CONTROLTRANSFER_H
#define CONTROLTRANSFER_H
#include <stdint.h>
#include <string>
#include <vector>
#include <assert.h>
#include <GSML3CommonElements.h>
#include <ScalarTypes.h> // From CommonLibs
#include <Timeval.h> // From CommonLibs
#include <L3Enums.h>
namespace SIP { class DialogMessage; };
namespace GSM { class L3Frame; class L2LogicalChannel; }
extern int gCountTranEntry;
namespace GPRS { class MSInfo; class TBF; }
namespace Control {
using namespace std;
using GSM::L3Cause;
class TranEntry;
class HandoverEntry;
class TransactionEntry;
class L3LogicalChannel;
typedef unsigned TranEntryId;
typedef unsigned TranEntryId; // value 0 is reserved for an undefined value.
typedef vector<TranEntryId> TranEntryList;
extern bool l3rewrite();
@@ -41,55 +43,6 @@ extern void l3start();
extern void controlInit();
extern unsigned allocateRTPPorts();
// Meaning of these bits is hard to find: It is in 48.008 3.2.2.11:
enum CodecType { // Codec Bitmap defined in 26.103 6.2. It is one or two bytes
// low bit of first byte in bitmap
CodecTypeUndefined = 0,
GSM_FR = 0x1, // aka GSM610
GSM_HR = 0x2,
GSM_EFR = 0x4,
AMR_FR = 0x8,
AMR_HR = 0x10,
UMTS_AMR = 0x20,
UMTS_AMR2 = 0x40,
TDMA_EFR = 0x80, // high bit of first byte in bitmap
// We can totally ignore the second byte:
PDC_EFR = 0x100, // low bit of second byte in bitmap
AMR_FR_WB = 0x200,
UMTS_AMR_WB = 0x400,
OHR_AMR = 0x800,
OFR_AMR_WB = 0x1000,
OHR_AMR_WB = 0x2000,
// then two reserved bits.
// In addition the above codecs defined in the GSM spec and used on the air-interface,
// we will put other codecs we might want to use for RTP on the SIP interface in here too
// so we can use the same CodecSet in the SIP directory.
// This is not in the spec, but use this value to indicate none of the codecs above.
PCMULAW = 0x10000, // G.711 PCM, 64kbps. comes in two flavors: uLaw and aLaw.
PCMALAW = 0x20000 // We dont support it yet.
// There is also G711.1, which is slighly wider band, 96kbps.
};
const char *CodecType2Name(CodecType ct);
// (pat) Added 10-22-2012.
// 3GPP 24.008 10.5.4.32 and 3GPP 26.103
class CodecSet {
public:
CodecType mCodecs; // It is a set of CodecEnum
bool isSet(CodecType bit) { return mCodecs & bit; }
bool isEmpty() { return !mCodecs; }
CodecSet(): mCodecs(CodecTypeUndefined) {}
CodecSet(CodecType wtype) : mCodecs(wtype) {}
// Allow logical OR of two CodecSets together.
void orSet(CodecSet other) { mCodecs = (CodecType) (mCodecs | other.mCodecs); }
void orType(CodecType vals) { mCodecs = (CodecType) (mCodecs | vals); }
CodecSet operator|(CodecSet other) { return CodecSet((CodecType)(mCodecs | other.mCodecs)); }
void text(std::ostream&) const;
friend std::ostream& operator<<(std::ostream& os, const CodecSet&);
};
class TMSI_t {
bool mValid;
uint32_t mVal;
@@ -108,7 +61,7 @@ struct FullMobileId {
TMSI_t mTmsi;
string mImei;
string fmidUsername() const; // "IMSI" or "TMSI" or "IMEI" + digits.
bool fmidMatch(const GSM::L3MobileIdentity &mobileId) const;
// moved to L3MobileIdentity: bool fmidMatch(const GSM::L3MobileIdentity &mobileId) const;
void fmidSet(string value);
FullMobileId() {} // Nothing needed.
FullMobileId(const string wAnything) { fmidSet(wAnything); } // Default is an imsi.
@@ -163,27 +116,27 @@ struct CCState {
typedef CCState::CallState CallState;
// This is the reason a Transaction (TranEntry) was cancelled as desired to be known by the high side.
// It has nothing to do with the cancel causes on the low side, for example, CC Cause (for cloasing a single call)
// or RR Cause (for closing an entire channel.)
// An established SipDialog is ended by a SIP BYE, and an MO [Mobile Originated] SipDialog is canceled early using
// a SIP CANCEL, so this is used only for the case of an INVITE response where the ACK message has not been sent,
// or as a non-invite message (eg, SMS MESSAGE) error response. As such, there are only a few codes that
// the SIP side cares about. The vast majority of plain old errors, for example, loss of contact with the MS
// or reassignFailure will just map to the same SIP code so we use CancelCauseUnknown, however, all such cases
// are distinguished from CancelCauseNoAnswerToPage in that we know the MS is on the current system.
// we just .
enum CancelCause {
// Used for anything other than the specific causes below. It is not "unknown" so much as we just
// dont need to distinguish among various failure or hangup causes because we send the same SIP code for them all.
CancelCauseUnknown = 0, // Not completely unknown - we know that the MS was on this system.
CancelCauseNoAnswerToPage, // We dont have any clue if the MS is in this area or not.
CancelCauseBusy, // The MS is here. A future call may succeed.
CancelCauseCongestion, // The MS is here, but no resources. A future call may succeed.
CancelCauseHandoverOutbound, // A special case - the Dialog has been moved elsewhere.
CancelCauseSipInternalError, // Special case of the SipDialog itself being internally inconsistent.
CancelCauseOperatorIntervention, // Killed from console.
// This is the return result from neighborFindBest.
struct BestNeighbor {
Bool_z mValid;
unsigned mARFCN; // C0 of the neighbor.
unsigned mBSIC; // BSIC of the neighbor.
float mRxlev; // rxlev of this neighbor in dB.
string mHandoverCause; // String version of BSSMAP Cause.
string text() const;
};
std::ostream& operator<<(std::ostream& os, BestNeighbor best);
// This penalty is applied to this neighbor.
struct NeighborPenalty {
int mARFCN;
unsigned mBSIC;
Timeval mPenaltyTime; // When the penalty expires.
NeighborPenalty() : mARFCN(-1), mBSIC(0) {} // Dont care about BSIC init but be neat.
bool match(int arfcn, unsigned bsic) const { return arfcn == mARFCN && bsic == mBSIC; }
string text() const;
};
std::ostream& operator<<(std::ostream& os, NeighborPenalty np);
/** Return a human-readable string for a GSM::CallState. */
@@ -214,8 +167,5 @@ class GenericL3Msg {
};
#endif
void NewTransactionTable_ttAddMessage(TranEntryId tranid,SIP::DialogMessage *dmsg);
};
#endif

View File

@@ -2,11 +2,11 @@
/*
* Copyright 2008, 2009 Free Software Foundation, Inc.
* Copyright 2011 Range Networks, Inc.
* Copyright 2011, 2014 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.
* information for this specific distribution.
*
* This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details.
@@ -26,15 +26,7 @@
#include "L3MobilityManagement.h"
#include "L3StateMachine.h"
#include "L3LogicalChannel.h"
//#include "TransactionTable.h"
#include "RadioResource.h"
//#include "MobilityManagement.h"
//#include <GSMLogicalChannel.h>
//#include <GSML3Message.h>
//#include <GSML3MMMessages.h>
//#include <GSML3RRMessages.h>
//#include <SIPUtility.h>
//#include <SIPInterface.h>
#include <GSMConfig.h>
#include <Logger.h>
#undef WARNING
@@ -52,10 +44,11 @@ using namespace Control;
// (pat) DCCH is a TCHFACCHLogicalChannel or SDCCHLogicalChannel
void Control::DCCHDispatcher(L3LogicalChannel *DCCH)
{
while (1) {
while (! gBTS.btsShutdown()) {
// This 'try' is redundant, but we are ultra-cautious here since a mistake means a crash.
try {
// Wait for a transaction to start.
LOG(DEBUG);
LOG(DEBUG) << "waiting for " << *DCCH << " ESTABLISH or HANDOVER_ACCESS";
L3Frame *frame = DCCH->waitForEstablishOrHandover();
LOG(DEBUG) << *DCCH << " received " << *frame;

View File

@@ -1,8 +1,9 @@
/* Copyright 2013, 2014 Range Networks, Inc.
/*
* Copyright 2013, 2014 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.
* information for this specific distribution.
*
* This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details.
@@ -10,10 +11,13 @@
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.
*/
#define LOG_GROUP LogGroup::Control
// Written by Pat Thompson
#define LOG_GROUP LogGroup::Control
#include <GSML3CCElements.h>
#include "ControlCommon.h"
#include "L3CallControl.h"
#include "L3StateMachine.h"
@@ -44,9 +48,9 @@ class CCBase : public SSDBase {
MachineStatus defaultMessages(int state, const GSM::L3Message*);
bool isVeryEarly();
CCBase(TranEntry *wTran) : SSDBase(wTran) {}
MachineStatus closeCall(GSM::L3Cause cause);
MachineStatus sendReleaseComplete(GSM::L3Cause cause);
MachineStatus sendRelease(GSM::L3Cause cause);
MachineStatus closeCall(TermCause cause);
MachineStatus sendReleaseComplete(TermCause cause, bool sendCause);
MachineStatus sendRelease(TermCause cause, bool sendCause);
void handleTerminationRequest();
};
@@ -57,7 +61,7 @@ class MOCMachine : public CCBase {
stateAssignTCHFSuccess,
};
bool mIdentifyResult;
MachineStatus sendCMServiceReject(MMRejectCause rejectCause);
MachineStatus sendCMServiceReject(MMRejectCause rejectCause,bool fatal);
MachineStatus handleSetupMsg(const GSM::L3Setup *setup);
MachineStatus serviceAccept();
@@ -113,6 +117,7 @@ class MTCMachine : public CCBase {
const char *debugName() const { return "MTCMachine"; }
};
class InboundHandoverMachine : public CCBase {
bool mReceivedHandoverComplete;
enum State {
@@ -155,39 +160,49 @@ void startMOC(const GSM::L3MMMessage *l3msg, MMContext *dcch, CMServiceTypeCode
}
#if UNUSED
MachineStatus ProcedureDetach::machineRunState(int state, const GSM::L3Message* l3msg, const SIP::DialogMessage *sipmsg)
{
PROCLOG2(DEBUG,state)<<LOGVAR(l3msg)<<LOGVAR(sipmsg)<<LOGVAR2("imsi",tran()->subscriber());
getDialog()->dialogCancel(); // reudundant, chanLost would do it. Does nothing if dialog not yet started.
setGSMState(CCState::NullState); // redundant, we are deleting this transaction.
channel()->l3sendm(L3ChannelRelease());
channel()->chanRelease(HARDRELEASE);
//channel()->l3sendp(HARDRELEASE);
//channel()->chanLost();
return MachineStatusOK;
}
//MachineStatus ProcedureDetach::machineRunState(int state, const GSM::L3Message* l3msg, const SIP::DialogMessage *sipmsg)
//{
// PROCLOG2(DEBUG,state)<<LOGVAR(l3msg)<<LOGVAR(sipmsg)<<LOGVAR2("imsi",tran()->subscriber());
// getDialog()->dialogCancel(); // reudundant, chanLost would do it. Does nothing if dialog not yet started.
// setGSMState(CCState::NullState); // redundant, we are deleting this transaction.
// channel()->l3sendm(L3ChannelRelease());
// channel()->chanRelease(HARDRELEASE);
// //channel()->l3sendp(HARDRELEASE);
// //channel()->chanLost();
// return MachineStatusOK;
//}
#endif
// Identical to teCloseCallNow.
MachineStatus CCBase::sendReleaseComplete(L3Cause l3cause)
MachineStatus CCBase::sendReleaseComplete(TermCause cause, bool sendCause)
{
tran()->teCloseCallNow(l3cause);
return MachineStatusQuitTran;
LOG(INFO) << "SIP term info sendReleaseComplete"<<LOGVAR(cause); // SVGDBG&pat
tran()->teCloseCallNow(cause,sendCause);
return MachineStatus::QuitTran(cause);
}
MachineStatus CCBase::sendRelease(L3Cause l3cause)
MachineStatus CCBase::sendRelease(TermCause cause, bool sendCause)
{
tran()->teCloseDialog(); // redundant, would happen soon anyway.
LOG(INFO) << "SIP term info sendRelease cause: " << cause; // SVGDBG
tran()->teCloseDialog(cause); // redundant, would happen soon anyway.
if (isL3TIValid()) {
unsigned l3ti = getL3TI();
if (tran()->clearingGSM()) {
// Oops! Something went wrong. Clear immediately.
tran()->teCloseCallNow(l3cause);
return MachineStatusQuitTran;
LOG(INFO) << "SIP term info call teCloseCallNow cause: " << cause;
tran()->teCloseCallNow(cause,sendCause);
return MachineStatus::QuitTran(cause);
} else {
// This tells the phone that the network intends to release the TI.
// The handset is supposed to respond with ReleaseComplete.
channel()->l3sendm(GSM::L3Release(l3ti,l3cause));
if (sendCause) {
// If BTS initiates release, we must include the cause element.
channel()->l3sendm(GSM::L3Release(l3ti,cause.tcGetCCCause()));
} else {
// Handset sent disconnect; our reply L3Release does not include a Cause Element. GSM 4.08 9.3.18.1.1
channel()->l3sendm(GSM::L3Release(l3ti));
}
setGSMState(CCState::ReleaseRequest);
timerStart(T308,T308ms,TimerAbortTran);
LOG(DEBUG) << gMMLayer.printMMInfo();
@@ -196,7 +211,7 @@ MachineStatus CCBase::sendRelease(L3Cause l3cause)
} else {
// The transaction is already dead. Kill the state machine and the next layer will send the RR Release.
LOG(DEBUG) << gMMLayer.printMMInfo();
return MachineStatusQuitTran;
return MachineStatus::QuitTran(cause);
}
}
@@ -204,10 +219,11 @@ MachineStatus CCBase::sendRelease(L3Cause l3cause)
// or if things have gone wrong, send a ReleaseRequest and kill the transaction. We used to do that all the time
// but some handsets (BLU phone) report "Network Failure" if you dont go through the disconnect procedure.
// We dont send the RR releaes at this level - the MM layer does that after this transaction dies.
MachineStatus CCBase::closeCall(L3Cause l3cause)
MachineStatus CCBase::closeCall(TermCause cause)
{
WATCHINFO("closeCall"<<LOGVAR2("cause",l3cause.cause()) <<" "<<channel()->descriptiveString());
tran()->teCloseDialog(); // Make sure; this is redundant because the call will be repeated when the transaction is killed,
LOG(INFO) << "SIP term info closeCall"<<LOGVAR(cause); // SVGDBG&pat
WATCHINFO("closeCall"<<LOGVAR(cause) <<" "<<channel()->descriptiveString());
tran()->teCloseDialog(cause); // Make sure; this is redundant because the call will be repeated when the transaction is killed,
// We could assert this if we dont call this until after an L3Setup.
if (isL3TIValid()) {
unsigned l3ti = getL3TI();
@@ -215,23 +231,23 @@ MachineStatus CCBase::closeCall(L3Cause l3cause)
CallState ccstate = tran()->getGSMState();
if (ccstate == CCState::Active) {
if (1) {
channel()->l3sendm(GSM::L3Disconnect(l3ti,l3cause));
channel()->l3sendm(GSM::L3Disconnect(l3ti,cause.tcGetCCCause()));
setGSMState(CCState::DisconnectIndication);
} else {
// (pat 10-24-2013) As an option per 24.008 5.4.2: we could send a Release message and start T308
channel()->l3sendm(GSM::L3Release(l3ti,l3cause));
channel()->l3sendm(GSM::L3Release(l3ti,cause.tcGetCCCause()));
setGSMState(CCState::ReleaseRequest);
}
timerStart(T308,T308ms,TimerAbortTran);
return MachineStatusOK; // Wait for ReleaseComplete.
} else if (ccstate != CCState::NullState && ccstate != CCState::ReleaseRequest) {
channel()->l3sendm(GSM::L3ReleaseComplete(l3ti,l3cause)); // This is a CC message that releases this Transaction.
channel()->l3sendm(GSM::L3ReleaseComplete(l3ti,cause.tcGetCCCause())); // This is a CC message that releases this Transaction.
}
} else {
// If no TI we cant send any CC release messages, just kill the transaction and if nothing is happening
// the MM layer will send an RR release on the channel.
}
WATCH("CLOSE CALL:"<<l3cause <<gMMLayer.printMMInfo());
WATCH("CLOSE CALL:"<<cause <<gMMLayer.printMMInfo());
setGSMState(CCState::NullState); // redundant, we are deleting this transaction.
LOG(DEBUG) << gMMLayer.printMMInfo();
@@ -240,14 +256,20 @@ MachineStatus CCBase::closeCall(L3Cause l3cause)
// The caller is a state machine. We cannot remove the transaction yet because the state machine is still using it.
// The state machine caller should return MachineStatusQuitTran which causes handleMachineStatus()
// to call teRemove to finish transaction destruction.
return MachineStatusQuitTran;
return MachineStatus::QuitTran(cause);
}
// This is called outside the normal procedure handling, so we dont return a MachineStatus.
// On return the caller will release the RR channel preemptively.
// TODO: Get rid of this. The terminator should Send a message to the MMLayer indicating type of termination (operator intervention
// or emergency call) which should be copied out to all the transactions in the MMContext.
void CCBase::handleTerminationRequest()
{
tran()->teCloseCallNow(L3Cause::Preemption);
LOG(INFO) "SIP term info handleTerminationRequest call closeCallNow Preemption";
// TODO: It may be pre-emption by emergency call.
//tran()->teCloseCallNow(TermCause::Local(TermCodeOperatorIntervention));
//tran()->teCloseCallNow(TermCause::Local((L3Cause::AnyCause)L3Cause::Operator_Intervention));
tran()->teCloseCallNow(TermCause::Local(L3Cause::Operator_Intervention),true);
}
@@ -257,11 +279,11 @@ void CCBase::handleTerminationRequest()
MachineStatus CCBase::defaultMessages(int state, const GSM::L3Message *l3msg)
{
if (!l3msg) { return unexpectedState(state,l3msg); } // Maybe unhandled dialog message.
switch (state) { // L3CASE_RAW(l3msg->PD(),l3msg->MTI())) {
switch (state) { // L3CASE_RAW(l3msg->PD(),l3msg->MTI()) {
case L3CASE_CC(Hold): {
const L3Hold *hold = dynamic_cast<typeof(hold)>(l3msg);
PROCLOG(NOTICE) << "rejecting hold request from " << tran()->subscriber();
channel()->l3sendm(GSM::L3HoldReject(getL3TI(),L3Cause::ServiceOrOptionNotAvailable));
channel()->l3sendm(GSM::L3HoldReject(getL3TI(),L3Cause::Service_Or_Option_Not_Available));
return MachineStatusOK; // ignore bad message otherwise.
}
case L3CASE_MM(CMServiceAbort): {
@@ -269,46 +291,61 @@ MachineStatus CCBase::defaultMessages(int state, const GSM::L3Message *l3msg)
// 4.08 5.2.1 and 4.5.1.7: If the MS wants to cancel before we get farther it should send a CMServiceAbort.
PROCLOG(INFO) << "received CMServiceAbort, closing channel and clearing";
timerStopAll();
return closeCall(L3Cause::NormalCallClearing); // normal event.
// 603 is only supposed to be used if we know there is no second choice like voice mail.
return closeCall(TermCause::Local(L3Cause::Call_Rejected)); // normal event.
}
case L3CASE_CC(Disconnect): { // MOD
// 4.08 5.4.3 says we must be prepared to receive a DISCONNECT any time.
//const L3Disconnect *dmsg = dynamic_cast<typeof(dmsg)>(l3msg); Unused.
//changed 10-24-13: return closeCall(L3Cause::NormalCallClearing); // normal event.
timerStopAll();
return sendRelease(L3Cause::NormalCallClearing);
const L3Disconnect *dmsg = dynamic_cast<typeof(dmsg)>(l3msg);
return sendRelease(TermCause::Local(dmsg->cause().cause()),false); // (pat) Preserve the cause the handset sent us.
//return sendRelease(TermCause::Local(L3Cause::Normal_Call_Clearing)); //svg change from CallRejected to NormalCallClearing 05/29/14
}
case L3CASE_CC(Release): {
// 24.008 5.4.3.3: In any state except ReleaseRequest send a ReleaseComplete, then kill the transaction,
timerStopAll();
const L3Release *dmsg = dynamic_cast<typeof(dmsg)>(l3msg);
if (dmsg->mFacility.mExtant) WATCH(dmsg); // USSD DEBUG!
timerStopAll();
return sendReleaseComplete(L3Cause::NormalCallClearing);
// (pat) The cause is optional; only included if the Release message is used to initiate call clearing.
L3Cause::CCCause cccause;
if (dmsg->haveCause()) {
cccause = dmsg->cause().cause();
} else {
cccause = L3Cause::Normal_Call_Clearing;
}
return sendReleaseComplete(TermCause::Local(cccause),false);
}
case L3CASE_CC(ReleaseComplete): {
// 24.008 5.4.3.3: Just kill the transaction immediately..
const L3ReleaseComplete *dmsg = dynamic_cast<typeof(dmsg)>(l3msg);
if (dmsg->mFacility.mExtant) WATCH(dmsg); // USSD DEBUG!
timerStopAll();
//changed 10-24-13: return closeCall(L3Cause::NormalCallClearing); // normal event.
// tran()->teCloseDialog(); // Redundant.
//changed 10-24-13: return closeCall(L3Cause::Normal_Call_Clearing); // normal event.
// tran()->teCloseDialog(TermCause::Local(TermCodeNormalDisconnect)); // Redundant, and we dont know what initiated it so this error is not correct
setGSMState(CCState::NullState); // redundant, we are deleting this transaction.
return MachineStatusQuitTran;
// (pat) The ReleaseComplete message may be sent by handset in response to our request for Release,
// in which case we dont want to change the termination cause from what it was previously,
// or it could be the handset informing us for the first time that it wants to delete this transaction.
TermCause cause = tran()->mFinalDisposition;
if (cause.tcIsEmpty()) { cause = TermCause::Local(L3Cause::Normal_Call_Clearing); }
return MachineStatus::QuitTran(cause);
}
case L3CASE_MM(IMSIDetachIndication): {
const GSM::L3IMSIDetachIndication* detach = dynamic_cast<typeof(detach)>(l3msg);
timerStopAll();
// The IMSI detach procedure will release the LCH.
PROCLOG(INFO) << "GSM IMSI Detach " << *tran();
// FIXME: Must unregister.
// TODO: IMSIDetachController(detach,LCH);
LOG(INFO) << "SIP term info IMSIDetachIndication text: " << l3msg->text();
// Must unregister. FIXME: We're going to do that first because the stupid layer2 may hang in l3sendm.
L3MobileIdentity mobid = detach->mobileID();
imsiDetach(mobid,channel());
channel()->l3sendm(L3ChannelRelease());
// Many handsets never complete the transaction.
// So force a shutdown of the channel.
channel()->chanRelease(HARDRELEASE);
return MachineStatusQuitChannel;
// (pat 5-2014) Changed from HARDRELEASE to RELEASE - we need to let the LAPDm shut down normally.
channel()->chanRelease(L3_RELEASE_REQUEST,TermCause::Local(L3Cause::IMSI_Detached));
return MachineStatus::QuitChannel(TermCause::Local(L3Cause::IMSI_Detached));
}
case L3CASE_RR(ApplicationInformation): {
const GSM::L3ApplicationInformation *aimsg = dynamic_cast<typeof(aimsg)>(l3msg);
@@ -341,15 +378,25 @@ MachineStatus CCBase::handleIncallCMServiceRequest(const GSM::L3Message *l3msg)
// For now, we are rejecting anything else.
PROCLOG(NOTICE) << "cannot accept additional CM Service Request from " << tran()->subscriber();
// Can never be too verbose.
channel()->l3sendm(GSM::L3CMServiceReject(L3RejectCause(L3RejectCause::ServiceOptionNotSupported)));
// (pat) There is no termcause here because there is nothing to terminate.
channel()->l3sendm(GSM::L3CMServiceReject(L3RejectCause::Service_Option_Not_Supported));
return MachineStatusOK;
}
// The reject cause is 4.08 10.5.3.6. It has values similar to L3Cause 10.5.4.11
MachineStatus MOCMachine::sendCMServiceReject(MMRejectCause rejectCause)
MachineStatus MOCMachine::sendCMServiceReject(MMRejectCause rejectCause, bool fatal)
{
channel()->l3sendm(L3CMServiceReject(L3RejectCause(rejectCause)));
return closeChannel(L3RRCause::NormalEvent,RELEASE);
channel()->l3sendm(L3CMServiceReject(rejectCause));
LOG(INFO) << "SIP term info closeChannel called in sendCMServiceReject";
if (fatal) {
// Authorization failure. It is an MM level failure, but a "normal event" at the RR level.
return closeChannel(L3RRCause::Normal_Event,L3_RELEASE_REQUEST,TermCause::Local(rejectCause));
} else {
// This would happen if the user is not authorized for the particular service requested.
// This case does not currently occur.
tran()->teCloseDialog(TermCause::Local(rejectCause));
return MachineStatus::QuitTran(TermCause::Local(rejectCause));
}
}
bool CCBase::isVeryEarly() { return (channel()->chtype()==GSM::FACCHType); }
@@ -379,26 +426,14 @@ MachineStatus MOCMachine::handleSetupMsg(const L3Setup *setup)
// FIXME -- This is quick-and-dirty, not following GSM 04.08 5.
// (pat) I disagree: this exactly follows GSM 4.08 5.4.2
PROCLOG(WARNING) << "MOC setup with no number";
return closeCall(L3Cause::InvalidMandatoryInformation);
// It is MOC, so we should not be sending an error to any dialogs, but we will fill in a SIP error anyway.
return closeCall(TermCause::Local(L3Cause::Missing_Called_Party_Number));
}
const L3CalledPartyBCDNumber& calledPartyIE = setup->calledPartyBCDNumber();
tran()->setCalled(calledPartyIE);
calledNumber = calledPartyIE.digits();
}
/* early RLLP request */
/* this seems to need to be sent after initial call setup
-kurtis */
if (gConfig.getBool("Control.Call.QueryRRLP.Early")) {
// Query for RRLP
#if ORIGINAL_CODE
if (!sendRRLP(mobileID, LCH)) {
PROCLOG(INFO) << "RRLP request failed";
}
#else
// TODO: RRLPServer.start(mobileID);
#endif
}
// Start a new SIP Dialog, which sends an INVITE.
@@ -411,7 +446,8 @@ MachineStatus MOCMachine::handleSetupMsg(const L3Setup *setup)
if (dialog == NULL) {
// We failed to create the SIP session for some reason. I dont think this can happen, but dont crash here.
LOG(ERR) << "Failed to create SIP Dialog, dropping connection";
return closeChannel(L3RRCause::Unspecified,RELEASE);
LOG(INFO) << "SIP term info closeChannel called in handlesetupMessage";
return closeChannel(L3RRCause::Unspecified,L3_RELEASE_REQUEST,TermCause::Local(L3Cause::Sip_Internal_Error));
}
//setDialog(dialog); Moved into newSipDialogMOC to eliminate a race.
@@ -441,11 +477,13 @@ MachineStatus MOCMachine::serviceAccept()
// TODO: This should be a function in MMContext.
if (!isVeryEarly()) {
if (! channel()->reassignAllocNextTCH()) {
channel()->l3sendm(GSM::L3CMServiceReject(L3RejectCause(L3RejectCause::Congestion)));
tran()->teCloseDialog(CancelCauseCongestion);
TermCause cause = TermCause::Local(L3Cause::No_Channel_Available);
channel()->l3sendm(GSM::L3CMServiceReject(L3RejectCause::Congestion));
tran()->teCloseDialog(cause); // TODO: This will become redundant with closeChannel and should be removed later.
// (pat) TODO: Now what? We are supposed to go back to using SDCCH in case of an ongoing SMS,
// so lets just close the Transaction.
return closeChannel(L3RRCause::NormalEvent,RELEASE);
LOG(INFO) << "SIP term info closeChannel called in serviceAccept";
return closeChannel(L3RRCause::Normal_Event,L3_RELEASE_REQUEST,cause);
}
}
@@ -489,7 +527,7 @@ MachineStatus MOCMachine::machineRunState(int state, const GSM::L3Message *l3msg
// we must return cause CM Service Reject Cause 4,
// which will cause the MS to do a new Location Update, and the Location Update code
// will either pass it or determine an appropriate reject code.
return sendCMServiceReject(L3RejectCause::IMSIUnknownInVLR);
return sendCMServiceReject(L3RejectCause::IMSI_Unknown_In_VLR,true);
}
}
@@ -526,7 +564,7 @@ MachineStatus MOCMachine::machineRunState(int state, const GSM::L3Message *l3msg
string imsi(mobileID.digits());
tran()->setSubscriberImsi(string(mobileID.digits()),true);
if (!gTMSITable.tmsiTabCheckAuthorization(imsi)) {
return sendCMServiceReject(L3RejectCause::RequestedServiceOptionNotSubscribed);
return sendCMServiceReject(L3RejectCause::Requested_Service_Option_Not_Subscribed,true);
}
return serviceAccept();
}
@@ -540,7 +578,7 @@ MachineStatus MOCMachine::machineRunState(int state, const GSM::L3Message *l3msg
// But for now, just accept it.
tran()->setSubscriberImsi(imsi,true);
if (!authorized) {
return sendCMServiceReject(L3RejectCause::RequestedServiceOptionNotSubscribed);
return sendCMServiceReject(L3RejectCause::Requested_Service_Option_Not_Subscribed,true);
}
return serviceAccept();
}
@@ -551,6 +589,7 @@ MachineStatus MOCMachine::machineRunState(int state, const GSM::L3Message *l3msg
// TODO: We should ask the SIP Registrar.
// (pat) This is not possible if the MS is compliant (unless the TMSI table has been lost) -
// the MS should have done a LocationUpdate first, which provides us with the IMSI.
// Or maybe the tmsi table was deleted.
PROCLOG(NOTICE) << "MOC with no IMSI or valid TMSI. Reqesting IMSI.";
timerStart(T3270,T3270ms,TimerAbortChan); // start IdentityRequest sent; stop IdentityResponse received.
channel()->l3sendm(L3IdentityRequest(IMSIType));
@@ -566,7 +605,7 @@ MachineStatus MOCMachine::machineRunState(int state, const GSM::L3Message *l3msg
string imsi(mobileID.digits());
tran()->setSubscriberImsi(imsi,true);
if (!gTMSITable.tmsiTabCheckAuthorization(imsi)) {
return sendCMServiceReject(L3RejectCause::RequestedServiceOptionNotSubscribed);
return sendCMServiceReject(L3RejectCause::Requested_Service_Option_Not_Subscribed,true);
}
return serviceAccept();
} else {
@@ -574,7 +613,7 @@ MachineStatus MOCMachine::machineRunState(int state, const GSM::L3Message *l3msg
PROCLOG(WARNING) << "MOC setup with no IMSI"; // (pat) It is used for MO-SMS, not MOC.
// Reject cause in 10.5.3.6.
// Cause 0x62 means "message type not not compatible with protocol state".
return sendCMServiceReject(L3RejectCause::MessageTypeNotCompatibleWithProtocolState);
return sendCMServiceReject(L3RejectCause::Message_Type_Not_Compatible_With_Protocol_State,false);
}
return something
}
@@ -614,12 +653,15 @@ MachineStatus MOCMachine::machineRunState(int state, const GSM::L3Message *l3msg
return MachineStatusOK;
}
case L3CASE_SIP(dialogRinging): {
#define ATTEMPT_TO_FIX_ZTE_PHONE 1
#if ATTEMPT_TO_FIX_ZTE_PHONE
// pat 2-2014: The ZTE phone does not play in audio ringing during the Alerting.
// Looks like a bug in the phone. To try work around it add a Progress Indicator IE.
// If you set in-band audio it will play whatever you send it, but it will just not generate its own ring tone in any case.
//L3ProgressIndicator progressIE(L3ProgressIndicator::ReturnedToISDN); This one tells it to not use in-band audio, but did not help.
L3ProgressIndicator progressIE(L3ProgressIndicator::InBandAvailable);
//L3ProgressIndicator progressIE(L3ProgressIndicator::InBandAvailable);
// To make the ZTE work I tried: Progress=Unspecified, NotISDN and Queuing.
L3ProgressIndicator progressIE(L3ProgressIndicator::Queuing,L3ProgressIndicator::User);
channel()->l3sendm(L3Alerting(getL3TI(),progressIE));
#else
channel()->l3sendm(L3Alerting(getL3TI()));
@@ -629,6 +671,7 @@ MachineStatus MOCMachine::machineRunState(int state, const GSM::L3Message *l3msg
}
case L3CASE_SIP(dialogActive): {
// Success! The call is connected.
tran()->mConnectTime = time(NULL);
if (gConfig.getBool("GSM.Cipher.Encrypt")) {
int encryptionAlgorithm = gTMSITable.tmsiTabGetPreferredA5Algorithm(tran()->subscriberIMSI().c_str());
@@ -658,7 +701,8 @@ MachineStatus MOCMachine::machineRunState(int state, const GSM::L3Message *l3msg
return callMachStart(new InCallMachine(tran()));
} else if (getDialog()->isFinished()) {
// The SIP side hung up on us!
return closeCall(L3Cause::NormalCallClearing);
TermCause cause = dialog2TermCause(getDialog());
return closeCall(cause);
} else {
// Not possible.
PROCLOG(ERR) << "Connect Acknowledge received in incorrect SIP Dialog state:"<< getDialog()->getDialogState();
@@ -677,18 +721,16 @@ MachineStatus MOCMachine::machineRunState(int state, const GSM::L3Message *l3msg
case L3CASE_SIP(dialogBye): {
// The other user hung up before we could finish.
return closeCall(L3Cause::NormalCallClearing);
return closeCall(dialog2ByeCause(getDialog()));
}
case L3CASE_SIP(dialogFail): {
// 0x11: "User Busy"; 0x7f "Interworking unspecified"
int sipcode = getDialog()->getLastResponseCode();
// This is where we should translate SIP codes into more meaningful L3Cause returns.
switch (sipcode) {
case 486: case 600: case 603:
return closeCall(L3Cause::UserBusy);
default:
return closeCall(L3Cause::InterworkingUnspecified);
}
// (pat) Since this is MOC, the SIP code supplied in the cause should not be used,
// but we will be ultra cautious and preserve it.
TermCause cause = dialog2TermCause(getDialog());
LOG(INFO) << "SIP dialogFail"<<LOGVAR(cause);
return closeCall(cause);
break;
}
#if TODO // TODO: What to do about this?
@@ -768,7 +810,7 @@ MachineStatus AssignTCHMachine::machineRunState(int state, const GSM::L3Message
// (pat) TODO: Why is this todo here? network send 'ChannelUnacceptable'?
// Since we already started sip, if the channel is unacceptable the only recovery to close the call.
//tran()->mSipDialogMessagesBlocked = false;
if (!modeOK) return closeCall(L3Cause::ChannelUnacceptable);
if (!modeOK) return closeCall(TermCause::Local(L3Cause::Channel_Unacceptable));
return MachineStatusPopMachine;
}
@@ -786,7 +828,7 @@ MachineStatus AssignTCHMachine::machineRunState(int state, const GSM::L3Message
channel()->reassignComplete();
PROCLOG(INFO) << "successful assignment";
PROCLOG(DEBUG) << gMMLayer.printMMInfo();
if (IS_LOG_LEVEL(DEBUG)) {
if (IS_WATCH_LEVEL(DEBUG)) {
cout << "AssignmentComplete:\n";
CommandLine::printChansV4(cout,false);
}
@@ -804,10 +846,26 @@ MachineStatus AssignTCHMachine::machineRunState(int state, const GSM::L3Message
sendReassignment();
return MachineStatusOK;
} else {
// (pat) redundant: chanFreeContext(TermCause::Local(L3Cause::Channel_Assignment_Failure));
goto caseAssignTimeout;
}
}
case stateAssignTimeout: {
// This is the case where we received neither AssignmentComplete nor AssignmentFailure - it is loss of radio contact.
LOG(INFO) << "SIP term info stateAssignTimeout NoUserResponding";
caseAssignTimeout:
channel()->reassignFailure();
// TODO: This is not optimal - we should drop back to the MMLayer to see if it wants to do something else.
// Determine and pass cause SVGDBG
LOG(INFO) << "SIP term info dialogCancel called in AssignTCHMachine::machineRunState";
TermCause cause = TermCause::Local(L3Cause::Channel_Assignment_Failure);
if (getDialog()) { getDialog()->dialogCancel(cause); } // Should never be NULL, but dont crash.
// We dont call closeCall because we already sent the specific RR message required for this situation.
LOG(INFO) << "SIP term info closeChannel called in AssignTCHMachine::machineRunState 1";
return closeChannel(L3RRCause::No_Activity_On_The_Radio,L3_RELEASE_REQUEST,cause);
}
// This would be a new CMServiceRequest, eg, for SMS message.
// TODO: Can the MS send this so early in the MOC process?
case L3CASE_MM(CMServiceRequest): {
@@ -816,15 +874,9 @@ MachineStatus AssignTCHMachine::machineRunState(int state, const GSM::L3Message
sendReassignment(); // duplicates old code, but is this really necessary?
}
case stateAssignTimeout:
caseAssignTimeout:
channel()->reassignFailure();
// TODO: This is not optimal - we should drop back to the MMLayer to see if it wants to do something else.
if (getDialog()) { getDialog()->dialogCancel(); } // Should never be NULL, but dont crash.
// We dont call closeCall because we already sent the specific RR message required for this situation.
return closeChannel(L3RRCause::NoActivityOnTheRadio,RELEASE);
case L3CASE_CC(Setup):
LOG(DEBUG) << "ignoring duplicate L3Setup";
return MachineStatusOK;
default:
if (sipmsg) {
@@ -843,20 +895,25 @@ MachineStatus MTCMachine::machineRunState(int state, const GSM::L3Message* l3msg
PROCLOG2(DEBUG,state)<<LOGVAR(state)<<LOGVAR(l3msg)<<LOGVAR(sipmsg)<<LOGVAR2("imsi",tran()->subscriber());
switch(state) {
case stateStart: {
//MachineStatus stat = checkForSipFailure(getDialog()->getDialogState());
//if (stat != MachineStatusOK) { return stat; }
if (getDialog()->isFinished()) {
// SIP side closed already.
return closeCall(L3Cause::InterworkingUnspecified);
//formerly: return closeCall(L3Cause::Interworking_Unspecified);
return closeCall(dialog2TermCause(getDialog()));
}
// Allocate channel now, to be sure there is one.
if (!isVeryEarly()) {
// Formerly all we had to do was check the VEA flag, since that controlled the channel type,
// but it is better to test for TCHF directly - this works for testcall where the channel type was
// specified by the user, and also handles the rare case where the VEA option changed on us.
//if (!isVeryEarly())
if (! channel()->isTCHF()) {
if (! channel()->reassignAllocNextTCH()) {
channel()->l3sendm(GSM::L3CMServiceReject(L3RejectCause(L3RejectCause::Congestion)));
tran()->teCloseDialog(CancelCauseCongestion);
channel()->l3sendm(GSM::L3CMServiceReject(L3RejectCause::Congestion));
TermCause cause = TermCause::Local(L3Cause::No_Channel_Available);
tran()->teCloseDialog(cause);
// (pat) TODO: We are supposed to go back to using SDCCH in case of an ongoing SMS.
return closeChannel(L3RRCause::NormalEvent,RELEASE);
LOG(INFO) << "SIP term info closeChannel called in AssignTCHMachine::machineRunState 2";
return closeChannel(L3RRCause::Normal_Event,L3_RELEASE_REQUEST,cause);
}
}
@@ -876,7 +933,7 @@ MachineStatus MTCMachine::machineRunState(int state, const GSM::L3Message* l3msg
timerStart(T303,T303ms,TimerAbortTran); // Time state "Call Present"; start CMServiceRequest recv; stop CallProceeding recv.
// And send trying message to SIP
getDialog()->MTCSendTrying();
if (getDialog()) { getDialog()->MTCSendTrying(); }
return MachineStatusOK; // Wait for L3CallConfirmed message.
}
@@ -920,7 +977,7 @@ MachineStatus MTCMachine::machineRunState(int state, const GSM::L3Message* l3msg
if (msg->mFacility.mExtant) WATCH(msg); // USSD DEBUG!
timerStart(T301,T301ms,TimerAbortTran); // Time state "Call Received"; start Alert recv; stop Connect recv.
setGSMState(CCState::CallReceived);
getDialog()->MTCSendRinging();
if (getDialog()) { getDialog()->MTCSendRinging(); }
return MachineStatusOK; // Waiting for L3Connect.
}
@@ -942,12 +999,13 @@ MachineStatus MTCMachine::machineRunState(int state, const GSM::L3Message* l3msg
// Setting state Active later is probably more technically correct too.
//old: setGSMState(CCState::Active);
setGSMState(CCState::ConnectIndication); // Note: This may technically be an MOC only defined state.
getDialog()->MTCSendOK(tran()->chooseCodec(),channel());
if (getDialog()) { getDialog()->MTCSendOK(tran()->chooseCodec(),channel()); }
return MachineStatusOK; // Wait for SIP OK-ACK
}
case L3CASE_SIP(dialogActive): { // SIP Dialog received SIP ACK to 200 OK.
// Success! The call is connected.
tran()->mConnectTime = time(NULL);
// (pat) To doug: The place to move cipher starting is probably InCallMachine::machineRunState case stateStart.
if (gConfig.getBool("GSM.Cipher.Encrypt")) {
@@ -980,11 +1038,14 @@ MachineStatus MTCMachine::machineRunState(int state, const GSM::L3Message* l3msg
// SIP Dialog failure cases.
case L3CASE_SIP(dialogBye): {
// The other user hung up before we could finish.
return closeCall(L3Cause::NormalCallClearing);
return closeCall(dialog2ByeCause(getDialog()));
}
case L3CASE_SIP(dialogFail): {
// It cannot be busy because it is a MTC.
return closeCall(L3Cause::InterworkingUnspecified);
// This most likely a CANCEL, ie, it is a Mobile Terminated Disconnect before the SIP dialog ACK.
TermCause cause = dialog2TermCause(getDialog());
LOG(INFO) << "SIP dialogFail"<<LOGVAR(cause);
return closeCall(cause); // formerly: (L3Cause::Interworking_Unspecified,500,"Dialog failure"));
}
default:
@@ -992,6 +1053,7 @@ MachineStatus MTCMachine::machineRunState(int state, const GSM::L3Message* l3msg
}
}
MachineStatus InboundHandoverMachine::machineRunState(int state, const GSM::L3Message *l3msg, const SIP::DialogMessage *sipmsg)
{
PROCLOG2(DEBUG,state)<<LOGVAR(l3msg)<<LOGVAR(sipmsg)<<LOGVAR2("imsi",tran()->subscriber());
@@ -1017,8 +1079,10 @@ MachineStatus InboundHandoverMachine::machineRunState(int state, const GSM::L3Me
if (dialog == NULL) {
// We cannot abort the handover - it is too late. All we can do is drop the call.
LOG(ERR) << "handover failure due to failure to create dialog for " << tran(); // Will probably never happen.
closeCall(L3Cause::InterworkingUnspecified);
return closeChannel(L3RRCause::NormalEvent,RELEASE);
TermCause cause = TermCause::Local(L3Cause::Invalid_Handover_Message);
closeCall(cause);
LOG(INFO) << "SIP term info closeChannel called in InboundHandoverMachine::machineRunState 1";
return closeChannel(L3RRCause::Normal_Event,L3_RELEASE_REQUEST,cause);
}
setDialog(dialog);
setGSMState(CCState::HandoverProgress);
@@ -1028,15 +1092,19 @@ MachineStatus InboundHandoverMachine::machineRunState(int state, const GSM::L3Me
case L3CASE_SIP(dialogFail):
// TODO: We should send a CC message to the phone based on the SIP fail code.
return closeChannel(L3RRCause::NormalEvent,RELEASE);
LOG(INFO) << "SIP term info closeChannel called in InboundHandoverMachine::machineRunState 2";
return closeCall(dialog2TermCause(getDialog()));
//return closeChannel(L3RRCause::Normal_Event,L3_RELEASE_REQUEST);
case L3CASE_SIP(dialogBye):
// SIP end hung up. Just hang up the MS.
closeCall(L3Cause::NormalCallClearing);
return closeChannel(L3RRCause::NormalEvent,RELEASE);
LOG(INFO) << "SIP term info closeChannel called in InboundHandoverMachine::machineRunState 3";
return closeCall(dialog2ByeCause(getDialog()));
//return closeChannel(L3RRCause::Normal_Event,L3_RELEASE_REQUEST);
case L3CASE_SIP(dialogActive): {
// Success! SIP side is active.
tran()->mConnectTime = time(NULL);
timerStop(TSipHandover);
getDialog()->MOCSendACK();
@@ -1071,7 +1139,8 @@ MachineStatus InboundHandoverMachine::machineRunState(int state, const GSM::L3Me
// If we get any other message before receiving the HandoverComplete, it is unrecoverable.
if (!mReceivedHandoverComplete) {
machineErrorMessage(LOG_NOTICE,state,l3msg,sipmsg,"waiting for Handover Complete");
return closeChannel(L3RRCause::MessageTypeNotCompapatibleWithProtocolState,RELEASE);
TermCause cause = TermCause::Local(L3Cause::Invalid_Handover_Message);
return closeChannel(L3RRCause::Message_Type_Not_Compapatible_With_Protocol_State,L3_RELEASE_REQUEST,cause);
} else {
// This state machine may need to be modified to handle this message, whatever it is:
machineErrorMessage(LOG_NOTICE,state,l3msg,sipmsg,"waiting for SIP Handover Complete");
@@ -1094,7 +1163,7 @@ void InCallMachine::acknowledgeDtmf()
channel()->l3sendm(GSM::L3StartDTMFAcknowledge(tran()->getL3TI(),thekey));
} else {
LOG (CRIT) << "DTMF sending attempt failed; is any DTMF method defined?";
channel()->l3sendm(GSM::L3StartDTMFReject(tran()->getL3TI(),L3Cause::ServiceOrOptionNotAvailable));
channel()->l3sendm(GSM::L3StartDTMFReject(tran()->getL3TI(),L3Cause::Service_Or_Option_Not_Available));
}
}
@@ -1202,17 +1271,19 @@ MachineStatus InCallMachine::machineRunState(int state, const GSM::L3Message *l3
return MachineStatusOK;
}
case L3CASE_SIP(dialogBye): {
return closeCall(L3Cause::NormalCallClearing);
return closeCall(dialog2ByeCause(getDialog()));
}
case L3CASE_SIP(dialogFail): {
// This is MTD - Mobile Terminated Disconnect. SIP sends a CANCEL which translates to this Fail.
// It cant be busy at this point because we already connected.
//devassert(! sipmsg->isBusy());
return closeCall(L3Cause::InterworkingUnspecified);
TermCause cause = dialog2TermCause(getDialog());
LOG(INFO) << "SIP dialogFail"<<LOGVAR(cause);
return closeCall(cause);
}
case L3CASE_SIP(dialogStarted):
devassert(0);
return MachineStatusQuitTran; // Shouldnt happen, but dont crash.
return MachineStatus::QuitTran(TermCause::Local(L3Cause::Sip_Internal_Error)); // Shouldnt happen, but dont crash.
default:
// Note: CMServiceRequest is handled at a higher layer, see handleCommonMessages.

View File

@@ -1,9 +1,9 @@
/* Copyright 2013 Range Networks, Inc.
/*
* Copyright 2013, 2014 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.
* information for this specific distribution.
*
* This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details.
@@ -12,6 +12,7 @@
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*/
#ifndef _L3CALLCONTROL_H_
#define _L3CALLCONTROL_H_ 1

View File

@@ -3,7 +3,7 @@
/*
* Copyright 2008, 2009, 2010 Free Software Foundation, Inc.
* Copyright 2010 Kestrel Signal Processing, Inc.
* Copyright 2011, 2012, 2013 Range Networks, Inc.
* Copyright 2011, 2012, 2013, 2014 Range Networks, Inc.
*
This program is distributed in the hope that it will be useful,
@@ -12,7 +12,7 @@
* This software is distributed under multiple licenses;
* see the COPYING file in the main directory for licensing
* information for this specific distribuion.
* information for this specific distribution.
*
* This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details.
@@ -22,15 +22,18 @@
#include <stdlib.h>
#include <list>
#define LOG_GROUP LogGroup::Control
#include <Defines.h>
#include "ControlCommon.h"
#include "RadioResource.h"
#include "L3Handover.h"
#include "L3CallControl.h"
#include "L3MMLayer.h"
#include <GSMLogicalChannel.h>
#include <GSMConfig.h>
#include "../GPRS/GPRSExport.h"
#include <GSML3RRElements.h>
#include <L3Enums.h>
#include <NeighborTable.h>
#include <Peering.h>
@@ -52,166 +55,10 @@ static void abortInboundHandover(RefCntPointer<TranEntry> transaction, RRCause c
LOG(DEBUG) << "aborting inbound handover " << *transaction;
unsigned holdoff = gConfig.getNum("GSM.Handover.FailureHoldoff");
gPeerInterface.sendHandoverFailure(transaction->getHandoverEntry(true),cause,holdoff);
//gTransactionTable.remove(transaction);
}
#if UNUSED
bool SaveHandoverAccess(unsigned handoverReference, float RSSI, float timingError, const GSM::Time& timestamp)
{
assert(! l3rewrite()); // Not used in l3rewrite. See TCHFACCHL1Decoder::writeLowSideRx
// In this function, we are "BS2" in the ladder diagram.
// This is called from L1 when a handover burst arrives.
// We will need to use the transaction record to carry the parameters.
// We put this here to avoid dealing with the transaction table in L1.
TransactionEntry *transaction = gTransactionTable.ttFindByInboundHandoverRef(handoverReference);
if (!transaction) {
LOG(ERR) << "no inbound handover with reference " << handoverReference;
return false;
}
if (timingError > gConfig.getNum("GSM.MS.TA.Max")) {
// Handover failure.
LOG(NOTICE) << "handover failure on due to TA=" << timingError << " for " << *transaction;
// RR cause 8: Handover impossible, timing advance out of range
OldAbortInboundHandover(transaction,L3RRCause::HandoverImpossible,dynamic_cast<L2LogicalChannel*>(transaction->channel()));
return false;
}
LOG(INFO) << "saving handover access for " << *transaction;
transaction->setInboundHandover(RSSI,timingError,gBTS.clock().systime(timestamp));
return true;
}
#endif
//void ProcessHandoverAccess(GSM::TCHFACCHLogicalChannel *TCH)
//{
// // In this function, we are "BS2" in the ladder diagram.
// // This is called from the DCCH dispatcher when it gets a HANDOVER_ACCESS primtive.
// // The information it needs was saved in the transaction table by SaveHandoverAccess.
//
//
// assert(TCH);
// LOG(DEBUG) << *TCH;
//
// TransactionEntry *transaction = gTransactionTable.ttFindByInboundHandoverChan(TCH);
// if (!transaction) {
// LOG(WARNING) << "handover access with no inbound transaction on " << *TCH;
// TCH->l2sendp(HARDRELEASE);
// return;
// }
//
// // clear handover in transceiver
// LOG(DEBUG) << *transaction;
// transaction->getL2Channel()->handoverPending(false);
//
// // Respond to handset with physical information until we get Handover Complete.
// int TA = (int)(transaction->inboundTimingError() + 0.5F);
// if (TA<0) TA=0;
// if (TA>62) TA=62;
// unsigned repeatTimeout = gConfig.getNum("GSM.Timer.T3105");
// unsigned sendCount = gConfig.getNum("GSM.Ny1");
// L3Frame* frame = NULL;
// while (!frame && sendCount) {
// TCH->l2sendm(L3PhysicalInformation(L3TimingAdvance(TA)),GSM::UNIT_DATA);
// sendCount--;
// frame = TCH->l2recv(repeatTimeout);
// if (frame && frame->primitive() == HANDOVER_ACCESS) {
// LOG(NOTICE) << "flushing HANDOVER_ACCESS while waiting for Handover Complete";
// delete frame;
// frame = NULL;
// }
// }
//
// // Timed out?
// if (!frame) {
// LOG(NOTICE) << "timed out waiting for Handover Complete on " << *TCH << " for " << *transaction;
// // RR cause 4: Abnormal release, no activity on the radio path
// OldAbortInboundHandover(transaction,4,TCH);
// return;
// }
//
// // Screwed up channel?
// if (frame->primitive()!=ESTABLISH) {
// LOG(NOTICE) << "unexpected primitive waiting for Handover Complete on "
// << *TCH << ": " << *frame << " for " << *transaction;
// delete frame;
// // RR cause 0x62: Message not compatible with protocol state
// OldAbortInboundHandover(transaction,0x62,TCH);
// return;
// }
//
// // Get the next frame, should be HandoverComplete.
// delete frame;
// frame = TCH->l2recv();
// L3Message* msg = parseL3(*frame);
// if (!msg) {
// LOG(NOTICE) << "unparsable message waiting for Handover Complete on "
// << *TCH << ": " << *frame << " for " << *transaction;
// delete frame;
// // RR cause 0x62: Message not compatible with protocol state
// TCH->l2sendm(L3ChannelRelease(L3RRCause::MessageTypeNotCompapatibleWithProtocolState));
// OldAbortInboundHandover(transaction,0x62,TCH);
// return;
// }
// delete frame;
//
// L3HandoverComplete* complete = dynamic_cast<L3HandoverComplete*>(msg);
// if (!complete) {
// LOG(NOTICE) << "expecting for Handover Complete on "
// << *TCH << "but got: " << *msg << " for " << *transaction;
// delete frame;
// // RR cause 0x62: Message not compatible with protocol state
// TCH->l2sendm(L3ChannelRelease(L3RRCause::MessageTypeNotCompapatibleWithProtocolState));
// OldAbortInboundHandover(transaction,0x62,TCH);
// }
// delete msg;
//
// // Send re-INVITE to the remote party.
// unsigned RTPPort = allocateRTPPorts();
// SIP::SIPState st = transaction->inboundHandoverSendINVITE(RTPPort);
// if (st == SIP::Fail) {
// OldAbortInboundHandover(transaction,4,TCH);
// return;
// }
//
// transaction->GSMState(CCState::HandoverProgress);
//
// while (1) {
// // FIXME - the sip engine should be doing this
// // FIXME - and checking for timeout
// // FIXME - and checking for proceeding (stop sending the resends)
// st = transaction->inboundHandoverCheckForOK();
// if (st == SIP::Active) break;
// if (st == SIP::Fail) {
// LOG(NOTICE) << "received Fail while waiting for OK";
// OldAbortInboundHandover(transaction,4,TCH);
// return;
// }
// }
// st = transaction->inboundHandoverSendACK();
// LOG(DEBUG) << "status of inboundHandoverSendACK: " << st << " for " << *transaction;
//
// // Send completion to peer BTS.
// char ind[100];
// sprintf(ind,"IND HANDOVER_COMPLETE %u", transaction->tranID());
// gPeerInterface.sendUntilAck(transaction,ind);
//
// // Update subscriber registry to reflect new registration.
// if (transaction->SRIMSI().length() && transaction->SRCALLID().length()) {
// gSubscriberRegistry.addUser(transaction->SRIMSI().c_str(), transaction->SRCALLID().c_str());
// }
//
// // The call is running.
// LOG(INFO) << "succesful inbound handover " << *transaction;
// transaction->GSMState(CCState::Active);
// callManagementLoop(transaction,TCH);
//}
// How did we get here you ask? Peering received a handover request on BTS2 (us), allocated a channel and set the handoverPending flag,
// created a transaction with the specified IMSI, returned an L3 handover command which BTS1 sent to the MS, which then
// sent a handover access to BTS2, and here we are!
@@ -226,14 +73,14 @@ void ProcessHandoverAccess(L3LogicalChannel *chan)
RefCntPointer<TranEntry> tran = chan->chanGetVoiceTran();
if (tran == NULL) {
LOG(WARNING) << "handover access with no inbound transaction on " << chan;
chan->chanRelease(HARDRELEASE);
chan->chanRelease(L3_HARDRELEASE_REQUEST,TermCause::Local(L3Cause::Handover_Error));
return;
}
LOG(DEBUG) << *tran;
if (!tran->getHandoverEntry(false)) {
LOG(WARNING) << "handover access with no inbound handover on " << *chan;
chan->chanRelease(HARDRELEASE);
chan->chanRelease(L3_HARDRELEASE_REQUEST,TermCause::Local(L3Cause::Handover_Error));
return;
}
@@ -249,12 +96,12 @@ void ProcessHandoverAccess(L3LogicalChannel *chan)
// Handover failure.
LOG(NOTICE) << "handover failure on due to TA=" << hr.mhrTimingError << " for " << *tran;
// RR cause 8: Handover impossible, timing advance out of range
abortInboundHandover(tran,L3RRCause::HandoverImpossible,dynamic_cast<L2LogicalChannel*>(tran->channel()));
chan->chanRelease(HARDRELEASE); // TODO: Is this right? Will the channel be immediately re-available?
abortInboundHandover(tran,L3RRCause::Handover_Impossible,dynamic_cast<L2LogicalChannel*>(tran->channel()));
chan->chanRelease(L3_HARDRELEASE_REQUEST,TermCause::Local(L3Cause::Distance)); // TODO: Is this right? Will the channel be immediately re-available?
return;
}
chan->getL2Channel()->setPhy(hr.mhrRSSI,hr.mhrTimingError,hr.mhrTimestamp);
chan->getL2Channel()->l1InitPhy(hr.mhrRSSI,hr.mhrTimingError,hr.mhrTimestamp);
// Respond to handset with physical information until we get Handover Complete.
int TA = (int)(hr.mhrTimingError + 0.5F);
@@ -262,17 +109,17 @@ void ProcessHandoverAccess(L3LogicalChannel *chan)
if (TA>62) TA=62;
// We want to do this loop carefully so we exit as soon as we get a frame that is not HANDOVER_ACCESS.
Z100Timer T3105(gConfig.getNum("GSM.Timer.T3105")); // It defaults to only 50ms.
Z100Timer T3105(gConfig.GSM.Timer.T3105); // It defaults to only 50ms.
// 4.08 11.1.3 "Ny1: The maximum number of repetitions for the PHYSICAL INFORMATION message during a handover."
for (unsigned sendCount = gConfig.getNum("GSM.Ny1"); sendCount > 0; sendCount--) {
for (unsigned sendCount = gConfig.getNum("GSM.Handover.Ny1"); sendCount > 0; sendCount--) {
T3105.set();
// (pat) It is UNIT_DATA because the channel is not established yet.
// (pat) WARNING: This l3sendm call is not blocking because it is sent on FACCH which has a queue.
// Rather than modifying the whole LogicalChannel stack to have a blocking mode,
// we are just going to wait afterwards. The message should take about 20ms to transmit,
// and GSM uses roughly 4 out of every 5 frames, so 20-25ms would transmit the message continuously.
chan->l3sendm(L3PhysicalInformation(L3TimingAdvance(TA)),GSM::UNIT_DATA);
chan->l3sendm(L3PhysicalInformation(L3TimingAdvance(TA)),GSM::L3_UNIT_DATA);
// (pat) Throw away all the HANDOVER_ACCESS that arrive while we were waiting.
// They are not messages that take 4 bursts; they can arrive on every burst, so there
@@ -286,7 +133,7 @@ void ProcessHandoverAccess(L3LogicalChannel *chan)
LOG(INFO) << "flushing HANDOVER_ACCESS while waiting for Handover Complete";
delete frame;
continue;
case ESTABLISH:
case L3_ESTABLISH_INDICATION:
delete frame;
// Channel is established, so the MS is there. Finish up with a state machine.
startInboundHandoverMachine(tran.self());
@@ -296,8 +143,8 @@ void ProcessHandoverAccess(L3LogicalChannel *chan)
LOG(NOTICE) << "unexpected primitive waiting for Handover Complete on "
<< *chan << ": " << *frame << " for " << *tran;
delete frame;
abortInboundHandover(tran,L3RRCause::MessageTypeNotCompapatibleWithProtocolState,chan);
chan->chanRelease(HARDRELEASE); // TODO: Is this right? Will the channel be immediately re-available?
abortInboundHandover(tran,L3RRCause::Message_Type_Not_Compapatible_With_Protocol_State,chan);
chan->chanRelease(L3_HARDRELEASE_REQUEST,TermCause::Local(L3Cause::Handover_Error)); // TODO: Is this right? Will the channel be immediately re-available?
return;
}
}
@@ -306,199 +153,90 @@ void ProcessHandoverAccess(L3LogicalChannel *chan)
// Failure.
LOG(NOTICE) << "timed out waiting for Handover Complete on " << *chan << " for " << *tran;
// RR cause 4: Abnormal release, no activity on the radio path
abortInboundHandover(tran,L3RRCause::NoActivityOnTheRadio,chan);
chan->chanRelease(HARDRELEASE); // TODO: Is this right? Will the channel be immediately re-available?
abortInboundHandover(tran,L3RRCause::No_Activity_On_The_Radio,chan);
chan->chanRelease(L3_HARDRELEASE_REQUEST,TermCause::Local(L3Cause::Radio_Interface_Failure)); // TODO: Is this right? Will the channel be immediately re-available?
return;
}
static BestNeighbor NoHandover(BestNeighbor np)
{
np.mValid = false;
return np;
}
#if 0
// // Get the next frame, should be HandoverComplete.
// delete frame;
// frame = chan->l2recv();
// L3Message* msg = parseL3(*frame);
// if (!msg) {
// LOG(NOTICE) << "unparsable message waiting for Handover Complete on "
// << *chan << ": " << *frame << " for " << *tran;
// delete frame;
// // The MS is listening to us now, so we have to send it something if we abort.
// // TODO: Should be a state machine from here on.
// // RR cause 0x62: Message not compatible with protocol state
// chan->chanClose(L3RRCause::MessageTypeNotCompapatibleWithProtocolState,HARDRELEASE);
// abortInboundHandover(tran,L3RRCause::MessageTypeNotCompapatibleWithProtocolState,chan);
// return;
// }
// delete frame;
//
// L3HandoverComplete* complete = dynamic_cast<L3HandoverComplete*>(msg);
// if (!complete) {
// LOG(NOTICE) << "expecting for Handover Complete on "
// << *chan << "but got: " << *msg << " for " << *tran;
// delete frame;
// // RR cause 0x62: Message not compatible with protocol state
// chan->chanClose(L3RRCause::MessageTypeNotCompapatibleWithProtocolState,HARDRELEASE);
// abortInboundHandover(tran,L3RRCause::MessageTypeNotCompapatibleWithProtocolState,chan);
// }
// delete msg;
//
// // MS has successfully arrived on BS2. Open the SIPDialog and attempt to transfer the SIP session.
//
// // Send re-INVITE to the remote party.
// //unsigned RTPPort = allocateRTPPorts();
// SIP::SIPDialog *dialog = SIP::SIPDialog::newSIPDialogHandover(tran);
// if (dialog == NULL) {
// // TODO: Can we abort at this point? It is too late.
// // But this only fails if the address is wrong.
// //abortInboundHandover(tran,L3RRCause::NoActivityOnTheRadio,chan);
// LOG(NOTICE) << "handover failure due to failure to create dialog for " << *tran; // Will probably never happen.
// tran->teCloseCall(L3Cause::InterworkingUnspecified);
// chan->chanClose(L3RRCause::Unspecified,RELEASE);
// return;
// }
// tran->setDialog(dialog);
// tran->setGSMState(CCState::HandoverProgress);
//
// while (DialogMessage*dmsg = dialog->dialogRead()) {
// switch (dmsg->dialogState()) {
// case SIPDialog::dialogActive:
// // We're good to go.
// tran->setGSMState(CCState::Active);
// break;
// case SIPDialog::dialogBye:
// // Other end hung up. Just hang up.
// tran->teCloseCall(L3Cause::NormalCallClearing);
// chan->chanClose(L3RRCause::NormalEvent,RELEASE);
// return;
// default:
// LOG(ERR) << "unrecognized SIP Dialog state while waiting for handover re-invite OK"<<tran;
// goto oops;
// case SIPDialog::dialogFail: // busy, cancel or fail for any reason.
// LOG(NOTICE) << "handover received SIP Dialog Fail while waiting for OK"<<tran;
// oops:
// //abortInboundHandover(tran,L3RRCause::Unspecified,chan); // TODO: Can we still do this now?
// tran->teCloseCall(L3Cause::InterworkingUnspecified);
// chan->chanClose(L3RRCause::Unspecified,RELEASE);
// return;
// }
// delete dmsg;
// if (tran->getGSMState() == CCState::Active) { break; }
// }
// SIP::SIPState st = dialog->inboundHandoverSendACK();
// LOG(DEBUG) << "status of inboundHandoverSendACK: " << st << " for " << *tran;
//
// // Send completion to peer BTS.
// char ind[100];
// sprintf(ind,"IND HANDOVER_COMPLETE %u", tran->tranID());
// gPeerInterface.sendUntilAck(tran->getHandoverEntry(true),ind);
//
// // Update subscriber registry to reflect new registration.
// /*** Pat thinks these are not used.
// if (transaction->SRIMSI().length() && transaction->SRCALLID().length()) {
// gSubscriberRegistry.addUser(transaction->SRIMSI().c_str(), transaction->SRCALLID().c_str());
// }
// ***/
//
// // The call is running.
// LOG(INFO) << "succesful inbound handover " << *tran;
// //callManagementLoop(transaction,TCH);
#endif
static BestNeighbor YesHandover(BestNeighbor &np, L3Cause::BSSCause why)
{
assert(np.mValid);
np.mHandoverCause = string(L3Cause::BSSCause2Str(why));
return np;
}
// Warning: This runs in a separate thread.
void HandoverDetermination(const L3MeasurementResults& measurements, float myRxLevel, SACCHLogicalChannel* SACCH)
static BestNeighbor HandoverDecision(const L3MeasurementResults* measurements, SACCHLogicalChannel* sacch)
{
// This is called from the SACCH service loop.
// Valid measurements?
if (measurements.MEAS_VALID()) return;
// Got neighbors?
// (pat) I am deliberately not aging the neighbor list if the measurement report is empty because
// I am afraid it may be empty because the MS did not have time to make measurements during this time
// period, rather than really indicating that there are no neighbors.
unsigned N = measurements.NO_NCELL();
if (N==0) { return; }
if (N == 7) {
LOG(DEBUG) << "neighbor cell information not available";
return;
ChannelHistory *chp = sacch->getChannelHistory();
int myRXLEV_DL = chp->getAvgRxlev();
NeighborPenalty penalty;
if (MMContext *mmc = sacch->hostChan()->chanGetContext(false)) {
penalty = mmc->mmcHandoverPenalty;
}
LOG(DEBUG) << LOGVAR(penalty);
BestNeighbor bestn = chp->neighborFindBest(penalty);
if (! bestn.mValid) { return NoHandover(bestn); }
int margin = gConfig.GSM.Handover.Margin;
Peering::NeighborEntry nentry;
if (!gNeighborTable.ntFindByArfcn(bestn.mARFCN, bestn.mBSIC, &nentry)) {
LOG(ERR) << "Could not find best neighbor entry from :"<<LOGVAR2("ARFCN",bestn.mARFCN)<<LOGVAR2("BSIC",bestn.mBSIC);
return NoHandover(bestn);
}
if (bestn.mRxlev - myRXLEV_DL >= margin) { return YesHandover(bestn,L3Cause::Downlink_Strength); }
return NoHandover(bestn);
}
// Warning: This runs in a separate thread.
void HandoverDetermination(const L3MeasurementResults* measurements, SACCHLogicalChannel* sacch)
{
//LOG(DEBUG) <<measurements->text();
// This is called from the SACCH service loop.
if (! sacch->neighborAddMeasurements(sacch,measurements)) return;
// (pat) TODO: If you add your own IP address to the sql neighbor list, the MS will return info on yourself,
// which will attempt a handover to yourself unless you throw those measurement reports away here.
// We should detect this and throw them out.
// Currently processNeighborParams() detects this condition when it gets a Peer report (but not at startup!)
// but we dont save the BSIC in memory so we dont have that information here where we need it.
// Look at neighbor cell rx levels
SACCH->neighborStartMeasurements();
int best = 0;
int bestRxLevel = -1000;
for (unsigned int i=0; i<N; i++) {
int thisRxLevel = measurements.RXLEV_NCELL_dBm(i);
int thisFreq = measurements.BCCH_FREQ_NCELL(i);
if (thisFreq == 31) {
// (pat) This is reserved for 3G in some weird way.
// We support only 31 instead of 32 neighbors to avoid any confusion here.
continue;
}
int thisBSCI = measurements.BSIC_NCELL(i);
// Average thisRxLevel over several neighbor reports.
thisRxLevel = SACCH->neighborAddMeasurement(thisFreq,thisBSCI,thisRxLevel);
if (thisRxLevel>bestRxLevel) {
best = i;
bestRxLevel = thisRxLevel;
}
}
int bestBCCH_FREQ_NCELL = measurements.BCCH_FREQ_NCELL(best); // (pat) This is an index into the neighborlist, not a frequency.
int bestBSIC = measurements.BSIC_NCELL(best);
// Is our current signal OK?
//int myRxLevel = measurements.RXLEV_SUB_SERVING_CELL_dBm();
int localRSSIMin = gConfig.getNum("GSM.Handover.LocalRSSIMin");
int threshold = gConfig.getNum("GSM.Handover.ThresholdDelta");
int gprsRSSI = gConfig.getNum("GPRS.ChannelCodingControl.RSSI");
// LOG(DEBUG) << "myRxLevel=" << myRxLevel << " dBm localRSSIMin=" << localRSSIMin << " dBm";
LOG(DEBUG) <<LOGVAR(myRxLevel)<<LOGVAR(localRSSIMin)<<LOGVAR(bestRxLevel) <<LOGVAR(threshold) <<" "<<SACCH->neighborText();
// Does the best exceed the current by more than the threshold? If not dont handover.
if (bestRxLevel < (myRxLevel + threshold)) { return; }
const char *what;
if (myRxLevel > localRSSIMin) {
// The current signal is ok; see if we want to do a discretionery handover.
if (!(
(gBTS.TCHTotal() == gBTS.TCHActive()) || // Is the current BTS full?
(myRxLevel < gprsRSSI && bestRxLevel > gprsRSSI) || // Would a handover let GPRS use a better codec?
(bestRxLevel > myRxLevel + 3 * threshold) // Is the other BTS *much* better?
)) { return; } // If not, dont handover.
what = "discretionary";
} else {
// Mandatory handover because the signal is poor and the neighbor BTS is threshold better.
//LOG(DEBUG) << "myRxLevel=" << myRxLevel << " dBm, best neighbor=" << bestRxLevel << " dBm, threshold=" << threshold << " dB";
what = "mandatory";
}
// OK. So we will initiate a handover. Woo hoo!
LOG(DEBUG) <<what <<" handover " <<measurements.text();
string peer = gNeighborTable.getAddress(bestBCCH_FREQ_NCELL,bestBSIC);
if (peer.empty()) {
LOG(INFO) << "measurement for unknown neighbor BCCH_FREQ_NCELL " << bestBCCH_FREQ_NCELL << " BSIC " << bestBSIC;
BestNeighbor bestn = HandoverDecision(measurements, sacch);
LOG(DEBUG) << bestn;
if (! bestn.mValid) {
// No handover for now.
return;
}
if (gNeighborTable.holdingOff(peer.c_str())) {
LOG(NOTICE) << "skipping "<<what<< " handover to " << peer << " due to holdoff";
string whatswrong; // pass by reference to getAddress()
string peerstr = gNeighborTable.getAddress(bestn.mARFCN, bestn.mBSIC,whatswrong);
if (peerstr.empty()) {
LOG(INFO) << "measurement for unknown neighbor"<<LOGVAR2("ARFCN",bestn.mARFCN)<<LOGVAR2("BSIC",bestn.mBSIC) <<" "<<whatswrong;
return;
}
if (gNeighborTable.holdingOff(peerstr.c_str())) {
LOG(NOTICE) << "skipping "<<bestn.mHandoverCause<< " handover to " << peerstr << " due to holdoff";
return;
}
// Find the transaction record.
const L3LogicalChannel *mainChanConst = dynamic_cast<typeof(mainChanConst)>(SACCH->hostChan());
const L3LogicalChannel *mainChanConst = dynamic_cast<typeof(mainChanConst)>(sacch->hostChan());
L3LogicalChannel *mainChan = const_cast<typeof(mainChan)>(mainChanConst); // idiotic language
// The RefCntPointer prevents the tran from being deleted while we are working here, as unlikely as that would be.
const RefCntPointer<TranEntry> tran = mainChan->chanGetVoiceTran();
if (tran == NULL) {
LOG(ERR) << "active SACCH with no transaction record: " << *SACCH;
LOG(ERR) << "active SACCH with no transaction record: " << *sacch;
return;
}
if (tran->getGSMState() != CCState::Active) {
@@ -526,8 +264,8 @@ void HandoverDetermination(const L3MeasurementResults& measurements, float myRxL
LOG(DEBUG) << "skipping handover for transaction " << tran->tranID() << " because age "<<age<<"<"<<holdoff;
return;
}
LOG(INFO) << "preparing "<<what<<" handover of " << tran->tranID()
<< " to " << peer << " with downlink RSSI " << bestRxLevel << " dbm";
LOG(INFO) << "preparing "<<bestn.mHandoverCause<<" handover of " << tran->tranID()
<< " to " << peerstr << " with downlink RXLEV=" << bestn.mRxlev << " dbm";
// The handover reference will be generated by the other BTS.
// We don't set the handover reference or state until we get RSP HANDOVER.
@@ -535,7 +273,7 @@ void HandoverDetermination(const L3MeasurementResults& measurements, float myRxL
// TODO: Check for handover request to our own BTS and avoid it. Dont forget to check the port too.
#if 0 // This did not work for some reason.
struct sockaddr_in peerAddr;
if (resolveAddress(&peerAddr,peer.c_str())) {
if (resolveAddress(&peerAddr,peerstr.c_str())) {
LOG(ALERT) "handover"<<LOGHEX(peerAddr.sin_addr.s_addr)<<LOGHEX(inet_addr("127.0.0.1"))<<LOGVAR(peerAddr.sin_port)<<LOGVAR(gConfig.getNum("Peering.Port"));
if (peerAddr.sin_addr.s_addr == inet_addr("127.0.0.1") &&
peerAddr.sin_port == gConfig.getNum("Peering.Port")) {
@@ -548,7 +286,7 @@ void HandoverDetermination(const L3MeasurementResults& measurements, float myRxL
// Form and send the message.
// This message is re-sent every 0.5s (the periodicity of measurement reports) until the peer answers.
// pats TODO: we could surely do this a better way.
gPeerInterface.sendHandoverRequest(peer,tran);
gPeerInterface.sendHandoverRequest(peerstr,tran,bestn.mHandoverCause);
}
// (pat) TODO: This should be merged into the InCallProcedure state machine, but lets just get it working first.
@@ -570,13 +308,6 @@ bool outboundHandoverTransfer(TranEntry* transaction, L3LogicalChannel *TCH)
L3Frame HandoverCommand(hop->mHexEncodedL3HandoverCommand.c_str());
LOG(INFO) <<TCH<<" sending handover command";
TCH->l3sendf(HandoverCommand);
//TCH->l3sendm(GSM::L3HandoverCommand(
// hep->mOutboundCell,
// hep->mOutboundChannel,
// hep->mOutboundReference,
// hep->mOutboundPowerCmd,
// hep->mOutboundSynch
// ));
// Start a timer for T3103, the handover failure timer.
// This T3103 timer is for the outbound leg of the handover on BS1.
@@ -596,13 +327,16 @@ bool outboundHandoverTransfer(TranEntry* transaction, L3LogicalChannel *TCH)
// audio packets as long as we can.
// Get the response.
// This is supposed to time out on successful handover, similar to the early assignment channel transfer..
// This is supposed to time out on successful handover, similar to the early assignment channel transfer.
GSM::L3Frame *result = TCH->l2recv(outboundT3103.remaining());
if (result) {
// If we got here, the handover failed and we just keep running the call.
L3Message *msg = parseL3(*result);
LOG(NOTICE) << "failed handover, received " << *result << msg;
if (msg) { delete msg; }
if (IS_LOG_LEVEL(NOTICE)) {
L3Message *msg = parseL3(*result);
// It is ok to pass a NULL L3Message pointer here.
LOG(NOTICE) << "failed handover, "<<TCH <<" received " << *result << msg;
if (msg) { delete msg; }
}
delete result;
// Restore the call state.
transaction->setGSMState(CCState::Active);
@@ -618,12 +352,10 @@ bool outboundHandoverTransfer(TranEntry* transaction, L3LogicalChannel *TCH)
// (mike) TODO: disabled as there no longer local vs upstream caches
//gSubscriberRegistry.removeUser(imsi.c_str());
transaction->teCancel(); // We need to do this immediately in case a reverse handover comes back soon.
TermCause cause = TermCause::Local(L3Cause::Handover_Outbound); // There is no SIP code. The dialog was moved to another BTS via a SIP REFER.
transaction->teCancel(cause); // We need to do this immediately in case a reverse handover comes back soon. This destroys the dialog too.
// We need to immediately destroy the dialog.
LOG(INFO) "timeout following outbound handover; exiting normally";
//TCH->l2sendp(GSM::HARDRELEASE); now done by caller.
LOG(INFO) <<"timeout following outbound handover; exiting normally";
return true;
}

33
Control/L3Handover.h Normal file
View File

@@ -0,0 +1,33 @@
/*
* Copyright 2014 Range Networks, Inc.
*
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.
* This software is distributed under multiple licenses;
* see the COPYING file in the main directory for licensing
* information for this specific distribution.
*
* This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details.
*/
#ifndef L3HANDOVER_H
#define L3HANDOVER_H
namespace GSM {
class L3MeasurementResults;
class SACCHLogicalChannel;
};
namespace Control {
class L3LogicalChannel;
class TranEntry;
void ProcessHandoverAccess(L3LogicalChannel *chan);
bool outboundHandoverTransfer(TranEntry* transaction, L3LogicalChannel *TCH);
void HandoverDetermination(const GSM::L3MeasurementResults* measurements, GSM::SACCHLogicalChannel* sacch);
};
#endif

View File

@@ -3,7 +3,7 @@
*
* This software is distributed under multiple licenses;
* see the COPYING file in the main directory for licensing
* information for this specific distribuion.
* information for this specific distribution.
*
* This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details.
@@ -13,6 +13,7 @@
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*/
// Written by Pat Thompson
#define LOG_GROUP LogGroup::Control // Can set Log.Level.Control for debugging
@@ -29,7 +30,7 @@ void L3LogicalChannel::L3LogicalChannelReset()
LOG(DEBUG) << this;
ScopedLock lock(gMMLock,__FILE__,__LINE__); // FIXMENOW Added 10-23-2013
// We could reset mNextChan too, but it is unused unless needed so dont bother.
chanFreeContext(); // If we do cancel any dialogs, it is in error.
chanFreeContext(TermCause::Local(L3Cause::No_Transaction_Expected)); // If we do cancel any dialogs, it is in error.
LOG(DEBUG);
if (mNextChan && mNextChan->mChState == chReassignTarget) {
// This rare case may occur for channel loss or if the MS sends, for example, an IMSI Detach
@@ -39,7 +40,7 @@ void L3LogicalChannel::L3LogicalChannelReset()
// This state indicates the other channel is idle, but it is dangerous to free
// it from here because it could receive a primitive and become active at any time.
// If that happens it will get a new MMContext and try to fire up, not sure what happens then.
mNextChan->chanFreeContext();
mNextChan->chanFreeContext(TermCause::Local(L3Cause::No_Transaction_Expected));
mNextChan = NULL;
}
LOG(DEBUG);
@@ -59,10 +60,11 @@ void L3LogicalChannel::L3LogicalChannelInit()
L3LogicalChannel::~L3LogicalChannel()
{
chanFreeContext();
chanFreeContext(TermCause::Local(L3Cause::No_Transaction_Expected));
}
const char *L3LogicalChannel::descriptiveString() const {
// This virtual method should never be called; it is over-ridden by the sub-class for all channel types that matter.
const char * L3LogicalChannel::descriptiveString() const {
return "undefined";
}
@@ -79,7 +81,7 @@ const L2LogicalChannel * L3LogicalChannel::getL2Channel() const {
}
L3LogicalChannel *L3LogicalChannel::getSACCHL3() {
return dynamic_cast<L3LogicalChannel*>(this->getL2Channel()->SACCH());
return dynamic_cast<L3LogicalChannel*>(this->getL2Channel()->getSACCH());
}
void L3LogicalChannel::l3sendm(const GSM::L3Message& msg, const GSM::Primitive& prim/*=GSM::DATA*/, SAPI_t SAPI/*=0*/)
@@ -89,13 +91,16 @@ void L3LogicalChannel::l3sendm(const GSM::L3Message& msg, const GSM::Primitive&
}
// These days this is used only for the handover command, which was sent as a pre-formed L3-message from BTS2 to BTS1.
// Note that SAP is encoded in the L3Frame.
void L3LogicalChannel::l3sendf(const GSM::L3Frame& frame)
{
LOG(INFO) <<this <<LOGVARP(frame);
if (IS_LOG_LEVEL(DEBUG)) {
const L3Message *msg = parseL3(frame);
WATCH(this <<" sendf "<<*msg);
if (msg) { LOG(DEBUG) << msg; }
// 3-14-2014 pat: Changed the LOG levels, formerly we sent the frame to INFO and the message to DEBUG, but the frame is raw, so I reversed it.
LOG(DEBUG) <<this <<LOGVARP(frame);
if (IS_LOG_LEVEL(INFO)) {
if (const L3Message *msg = parseL3(frame)) {
WATCHINFO(this <<" sendf "<<*msg);
delete msg;
}
}
l2sendf(frame);
}
@@ -104,7 +109,7 @@ void L3LogicalChannel::l3sendf(const GSM::L3Frame& frame)
// We only call this from the thread service loop, so it is the last thing we ever do in the thread.
void L3LogicalChannel::l3sendp(const GSM::Primitive& prim, SAPI_t SAPI)
{
LOG(INFO) <<this <<LOGVAR(prim)<<LOGVAR(SAPI);
WATCHINFO("l3sendp"<<LOGVAR(prim)<<LOGVAR(SAPI)<<" "<<this); // 'this' is the descriptive string of the channel.
//if (prim == RELEASE || prim == HARDRELEASE) {
//chanSetState(chReleased); // Inform the service loop it should exit.
//}
@@ -116,8 +121,9 @@ L3Frame* L3LogicalChannel::waitForEstablishOrHandover()
{
while (true) {
L3Frame *req = l2recv();
LOG(DEBUG) <<LOGVAR(req);
if (req==NULL) continue;
if (req->primitive()==ESTABLISH) return req;
if (req->primitive()==L3_ESTABLISH_INDICATION) return req;
if (req->primitive()==HANDOVER_ACCESS) return req;
LOG(INFO) << "L3LogicalChannel: Ignored primitive:"<<req->primitive();
delete req;
@@ -127,20 +133,32 @@ L3Frame* L3LogicalChannel::waitForEstablishOrHandover()
MMContext *L3LogicalChannel::chanGetContext(bool create)
{
LOG(DEBUG);
//LOG(DEBUG);
ScopedLock lock(gMMLock,__FILE__,__LINE__);
LOG(DEBUG);
//LOG(DEBUG);
if (mChContext == NULL) {
if (create) { mChContext = new MMContext(this); }
}
return mChContext;
}
void L3LogicalChannel::chanSetHandoverPenalty(NeighborPenalty &penalty)
{
chanGetContext(false)->chanSetHandoverPenalty(penalty);
}
// WARNING: This is called from the CLI thread.
string L3LogicalChannel::chanGetImsi(bool verbose) const
{
ScopedLock lock(gMMLock,__FILE__,__LINE__);
return mChContext ? mChContext->mmGetImsi(verbose) : string(verbose ? "no-MMContext" : "");
return mChContext ? mChContext->mmGetImsi(verbose) : string(verbose ? "no-MMChannel" : "");
}
// WARNING: This is called from the CLI thread.
time_t L3LogicalChannel::chanGetDuration() const
{
ScopedLock lock(gMMLock,__FILE__,__LINE__);
return mChContext ? mChContext->mmcDuration() : 0;
}
//void L3LogicalChannel::chanSetContext(MMContext* wContext)
@@ -152,7 +170,7 @@ string L3LogicalChannel::chanGetImsi(bool verbose) const
// The sipcode would be used if a SipDialog on this channel is still active, which indicates a channel loss failure
// or server error.
void L3LogicalChannel::chanFreeContext()
void L3LogicalChannel::chanFreeContext(TermCause cause)
{
LOG(DEBUG);
ScopedLock lock(gMMLock,__FILE__,__LINE__);
@@ -161,7 +179,7 @@ void L3LogicalChannel::chanFreeContext()
mChContext = NULL;
if (save) {
LOG(DEBUG) <<this;
gMMLayer.mmFreeContext(save);
gMMLayer.mmFreeContext(save,cause);
}
LOG(DEBUG);
}
@@ -175,6 +193,10 @@ bool L3LogicalChannel::reassignAllocNextTCH() // For a channel reassignment pro
LOG(DEBUG) << LOGVAR2("curchan",this)<<LOGVAR2("nextchan","null,congestion");
return false;
}
// Copy the phy params from old to new channel, then fire it up.
tch->setPhy(*getL2Channel());
tch->lcstart();
LOG(DEBUG) << LOGVAR2("curchan",this)<<LOGVAR2("nextchan",tch);
// When we receive confirmation from the MS, mNextChannel will become mChannel.
mNextChan = dynamic_cast<L3LogicalChannel*>(tch);
@@ -201,21 +223,21 @@ void L3LogicalChannel::reassignStart()
LOG(ERR) <<"At start of channel reassignment target channel is not idle:"
<<LOGVAR2("next-chan",mNextChan) <<LOGVAR2("prev-chan",this);
}
mNextChan->chanFreeContext(); // This is supposed to be a no-op.
mNextChan->chanFreeContext(TermCause::Local(L3Cause::No_Transaction_Expected)); // This is supposed to be a no-op.
mNextChan->mChContext = mChContext->tsDup(); // Must set directly. Does not change the channel back pointer.
// We set this state on nextChan so that if
// We set this state on nextChan in case of channel loss - see L3LogicalChannelReset
mNextChan->chanSetState(L3LogicalChannel::chReassignTarget);
GSM::L2LogicalChannel *tch = mNextChan->getL2Channel();
GSM::L2LogicalChannel *sdcch = this->getL2Channel();
// Note we do not want to do a HARDRELEASE if this fails, because that bypasses the very timer we are supposed to be using.
LOG(INFO) << "sending AssignmentCommand for " << tch << " on " << this;
tch->open(); // This sets T3101 as a side effect.
tch->lcopen(); // This sets T3101 as a side effect.
tch->setPhy(*sdcch);
}
// This occurs on the channel being assigned from.
// We need to release this channel and nextChannel.
// We need to release the nextChannel. Caller takes care of this chan.
// Release of nextChan also occurs if a channel drops out of the main service loop and the nextChan state is still ReassignTarget
void L3LogicalChannel::reassignFailure()
{
@@ -228,7 +250,7 @@ void L3LogicalChannel::reassignFailure()
// If we never received the ESTABLISH primitive on nextChan, then the service loop is not runnning,
// so it will never detect the change of state on nextChan, so we must free the channel here.
// The service loops check dcch->chanRunning(), so they will not do anything that might try to use the Context we are freeing..
mNextChan->chanFreeContext();
mNextChan->chanFreeContext(TermCause::Local(L3Cause::Channel_Assignment_Failure));
mNextChan = NULL;
} else {
LOG(ERR) << "reassignment failure but no nextChan? "<<this;
@@ -251,20 +273,21 @@ void L3LogicalChannel::reassignFailure()
// Beware that the two channels are serviced by different threads.
void L3LogicalChannel::reassignComplete()
{
{
ScopedLock lock(gMMLock,__FILE__,__LINE__);
//timerStop(TChReassignment); // Handled by assignTCHFProcedure, which is notified after us.
if (!mChContext) {
// Logic error.
LOG(ERR) << "received channel reassignment complete on dead channel:"<<this;
l3sendm(GSM::L3ChannelRelease(L3RRCause::NormalEvent));
l3sendm(GSM::L3ChannelRelease(L3RRCause::Normal_Event));
chanSetState(chRequestRelease);
return;
}
if (!mNextChan) {
// Logic error.
LOG(ERR) << "received channel reassignment complete with no nextchan allocated"<<this;
l3sendm(GSM::L3ChannelRelease(L3RRCause::NormalEvent));
l3sendm(GSM::L3ChannelRelease(L3RRCause::Normal_Event));
chanSetState(chRequestRelease);
return;
}
@@ -279,6 +302,8 @@ void L3LogicalChannel::reassignComplete()
mChContext->mmSetChannel(mNextChan);
LOG(INFO) <<"successful channel reassignment" <<LOGVAR2("from-channel",this) <<LOGVAR2("to-channel",mNextChan);
mNextChan = NULL;
} // release lock
// FIXME: There is a race for the new channel to get its ESTABLISH before this old one gets this hardrelease.
sleep(1);
chanSetState(chRequestHardRelease); // Done with this channel.
@@ -300,14 +325,15 @@ void L3LogicalChannel::chanLost()
#endif
// Set the flag, which will perform the channel release from the channel serviceloop.
void L3LogicalChannel::chanRelease(Primitive prim)
void L3LogicalChannel::chanRelease(Primitive prim,TermCause cause)
{
OBJLOG(DEBUG) << prim;
chanFreeContext(cause);
switch (prim) {
case HARDRELEASE:
case L3_HARDRELEASE_REQUEST:
chanSetState(L3LogicalChannel::chRequestHardRelease);
return;
case RELEASE:
case L3_RELEASE_REQUEST:
chanSetState(L3LogicalChannel::chRequestRelease);
return;
default:
@@ -318,11 +344,12 @@ void L3LogicalChannel::chanRelease(Primitive prim)
// This completely releases the channel and all transactions on it.
// FIXME no it doesnt, and L2 can hang when we send the primitive, so these transactions and dialogs
// are not cleaned up until the next time the channel is used. Very bad.
void L3LogicalChannel::chanClose(L3RRCause cause,Primitive prim)
// pat 7-2014 Update: Above bug probably fixed by GSMLogicalChannel rewrite.
void L3LogicalChannel::chanClose(RRCause rrcause,Primitive prim,TermCause upstreamCause)
{
// Note: timer expiry may indicate unresponsive MS so this may block for 30 seconds.
l3sendm(L3ChannelRelease(cause));
chanRelease(prim);
l3sendm(L3ChannelRelease(rrcause));
chanRelease(prim,upstreamCause);
}
@@ -338,10 +365,10 @@ RefCntPointer<TranEntry> L3LogicalChannel::chanGetVoiceTran()
return set->tsGetVoiceTran();
}
void L3LogicalChannel::chanEnqueueFrame(L3Frame *frame)
{
ml3UplinkQ.write(frame);
}
//void L3LogicalChannel::chanEnqueueFrame(L3Frame *frame)
//{
// ml3UplinkQ.write(frame);
//}
// When L3 wants to drop a channel, it must set a flag in the L3LogicalChannel, which will be queried here.
// Return false to drop the channel.

View File

@@ -1,9 +1,9 @@
/*
* Copyright 2013 Range Networks, Inc.
* Copyright 2013, 2014 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.
* information for this specific distribution.
*
* This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details.
@@ -16,8 +16,10 @@
#ifndef _L3LOGICALCHANNEL_H_
#define _L3LOGICALCHANNEL_H_ 1
#include "ControlTransfer.h"
#include "L3TermCause.h"
//#include <GSML3RRElements.h>
#include <L3Enums.h>
#include <GSMTransfer.h>
#include <GSML3RRElements.h>
namespace Control {
using namespace GSM;
@@ -62,14 +64,14 @@ class L3LogicalChannel {
// The MMContext holds the active Transactions on a channel.
// It can be moved to a different channel by RR Procedures.
// (In contrast, an MMUser is associated with an IMSI.)
MMContext *mChContext; // TODO: RefCntPointer?
MMContext *mChContext; // When set we increment the use count in the MMContext.
protected:
L3LogicalChannel *mNextChan; // Used in GSM during channel reassignment.
//L3LogicalChannel *mPrevChan;
public:
bool chanRunning();
InterthreadQueue<L3Frame> ml3UplinkQ; // uplink SACCH message are enqueued here.
//InterthreadQueue<L3Frame> ml3UplinkQ; // uplink SACCH message are enqueued here.
private:
// This can be thought of as the RR state, as known from an L3 perspective.
@@ -94,17 +96,17 @@ class L3LogicalChannel {
public:
// Pass-throughs from Layer2. These will be different for GSM or UMTS.
virtual GSM::L3Frame * l2recv(unsigned timeout_ms = 15000, unsigned SAPI=0) = 0;
virtual GSM::L3Frame * l2recv(unsigned timeout_ms = 15000) = 0;
// In GSM the l2send methods may block in LAPDm.
virtual void l2sendf(const GSM::L3Frame& frame, SAPI_t SAPI=SAPI0) = 0;
virtual void l2sendm(const GSM::L3Message& msg, const GSM::Primitive& prim=GSM::DATA, SAPI_t SAPI=SAPI0) = 0;
virtual void l2sendf(const GSM::L3Frame& frame) = 0;
virtual void l2sendm(const GSM::L3Message& msg, GSM::Primitive prim=GSM::L3_DATA, SAPI_t SAPI=SAPI0) = 0;
virtual void l2sendp(const GSM::Primitive& prim, SAPI_t SAPI=SAPI0) = 0;
void l3sendm(const GSM::L3Message& msg, const GSM::Primitive& prim=GSM::DATA, SAPI_t SAPI=SAPI0);
void l3sendm(const GSM::L3Message& msg, const GSM::Primitive& prim=GSM::L3_DATA, SAPI_t SAPI=SAPI0);
void l3sendf(const GSM::L3Frame& frame);
void l3sendp(const GSM::Primitive& prim, SAPI_t SAPI=SAPI0);
virtual unsigned N200() const = 0;
virtual bool multiframeMode(unsigned SAPI) const = 0; // Used by SMS code.
virtual const char* descriptiveString() const;
//unused virtual unsigned N200() const = 0;
virtual bool multiframeMode(SAPI_t SAPI) const = 0; // Used by SMS code.
virtual const char * descriptiveString() const;
virtual bool radioFailure() const = 0;
virtual GSM::ChannelType chtype() const =0;
bool isSDCCH() const;
@@ -118,9 +120,11 @@ class L3LogicalChannel {
L3LogicalChannel* getSACCHL3();
MMContext *chanGetContext(bool create);
void chanSetHandoverPenalty(NeighborPenalty &penalty);
std::string chanGetImsi(bool verbose) const; // If the IMSI is known, return it, else string("") or if verbose, something to display in error messages and CLI.
time_t chanGetDuration() const;
//void chanSetContext(MMContext* wTranSet);
void chanFreeContext();
void chanFreeContext(TermCause cause);
void reassignComplete();
void reassignFailure();
void reassignStart();
@@ -130,14 +134,16 @@ class L3LogicalChannel {
//void setMMC(MMUser *mmc) { mMMC = mmc; }
//void chanLost();
// Send a channel release message, then release it.
void chanClose(GSM::L3RRCause cause,GSM::Primitive prim); // prim is RELEASE or HARDRELEASE
void chanClose(GSM::RRCause cause, // cause sent to the handset.
GSM::Primitive prim, // prim is RELEASE or HARDRELEASE
TermCause upstreamCause); // All active transactions closed with this - sent upstram via SIP.
// Request an immediate channel release.
// Dont use HARDRELEASE if you can avoid it - only used when the channel is already completely cleared.
void chanRelease(Primitive prim);
void chanRelease(Primitive prim,TermCause cause);
RefCntPointer<TranEntry> chanGetVoiceTran();
//void chanSetVoiceTran(TranEntry *trans);
void chanEnqueueFrame(L3Frame *frame);
//void chanEnqueueFrame(L3Frame *frame);
/** Block until a HANDOVER_ACCESS or ESTABLISH arrives. */
GSM::L3Frame* waitForEstablishOrHandover();

View File

@@ -1,8 +1,9 @@
/* Copyright 2013, 2014 Range Networks, Inc.
/*
* Copyright 2013, 2014 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.
* information for this specific distribution.
*
* This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details.
@@ -12,6 +13,8 @@
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*/
// Written by Pat Thompson
#define LOG_GROUP LogGroup::Control // Can set Log.Level.Control for debugging
#include "L3MMLayer.h"
@@ -78,6 +81,7 @@ void MMContext::startSMSTran(TranEntry *tran)
}
// (pat) WARNING: If this routine returns true it has performed the gMMLock.unlock() corresponding to a lock() in the caller.
// Setting the lock in one function and releasing it in another sucks and should be fixed.
bool MMUser::mmuServiceMTQueues() // arg redundant with mmuContext->channel.
{
devassert(gMMLock.lockcnt()); // Caller locked it.
@@ -96,7 +100,11 @@ bool MMUser::mmuServiceMTQueues() // arg redundant with mmuContext->channel.
// Did the SIP session give up while we were waiting?
// That will be handled in the MTCMachine.
initMTC(tran);
switch (tran->servicetype()) {
default:
initMTC(tran);
break;
}
gMMLock.unlock();
tran->lockAndStart();
return true;
@@ -142,6 +150,8 @@ bool MMContext::mmInMobilityManagement()
// Return true if anything happened.
bool MMContext::mmCheckNewActivity()
{
// Dont lock gMMLock up here - we may send a message later and we cant do that holding the lock.
// If there is a mobility management procedure in progress then we wont start anything else until it is finished.
// We shouldnt need to lock gMMLock yet because the MMContext cannot be deleted while in the thread that called us,
// and mmcServiceRequests is a thread safe queue.
@@ -155,20 +165,26 @@ bool MMContext::mmCheckNewActivity()
// We are refererencing the MMUser so we cannot let that change and the only
// completely safe way to do that is to lock the entire MMLayer.
// The unlock() corresponding to this lock() may be in mmuServiceMTQueues.
gMMLock.lock(__FILE__,__LINE__);
gMMLock.lock(__FILE__,__LINE__); // Do not replace this one with a scoped lock!
if (mmcMMU) {
if (mmcMMU->mmuServiceMTQueues()) { return true; }
if (mmcMMU->mmuServiceMTQueues()) { return true; } // If it returns true it unlocked the lock, gack.
}
gMMLock.unlock();
}
// If there are no transactions, kill the channel.
// We wait 5 seconds to allow a transaction to start; otherwise there is a race because
// the channel service thread that calls this method is started by an ESTABLISH sent by layer 1,
// which is sent before the message that initiates the transaction is sent.
// The 5 seconds is kind of made up. It doesnt have to be very long because at channel initiation
// the signal should be good.
// TODO: A new SIP transaction could creep in here between the time
// we check isEmpty and when the channel actually closes.
// What to do about that?
// When we detach the MMUser, if it has anything on it, just leave it there,
// and paging will restart.
if (mmIsEmpty()) {
mmcChan->chanClose(L3RRCause::NormalEvent,RELEASE);
if (mmIsEmpty() && mmcDuration() > 5) {
LOG(DEBUG) <<"closing"<<this;
mmcChan->chanClose(L3RRCause::Normal_Event,L3_RELEASE_REQUEST,TermCause::Local(L3Cause::No_Transaction_Expected));
return true; // This is new activity - the calling loop should skip back to the top
}
return false;
@@ -190,8 +206,40 @@ MMUser::MMUser(string& wImsi)
// LOG(DEBUG) << "MMUser ALLOC "<<(void*)this;
//}
//GSM::CMServiceTypeCode MMUser::mmuGetInitialServiceType()
//{
// devassert(gMMLock.lockcnt()); // Caller locked it.
// if (mmuMTCq.size()) {
// TranEntry *front = this->mmuMTCq.front();
// return front->servicetype();
// }
// devassert(mmuMTSMSq.size());
// // The purpose of this is to choose the channel type, so it doesnt really matter what the servicetype is as long as it is one that can use SDCCH.
// return L3CMServiceType::ShortMessage;
//}
GSM::ChannelType MMUser::mmuGetInitialChanType() const
{
devassert(gMMLock.lockcnt()); // Caller locked it.
if (mmuMTCq.size()) {
TranEntry *front = this->mmuMTCq.front();
switch (front->servicetype()) {
case L3CMServiceType::MobileOriginatedCall:
devassert(0);
case L3CMServiceType::MobileTerminatedCall:
case L3CMServiceType::EmergencyCall:
return gConfig.getBool("Control.VEA") ? GSM::TCHFType : GSM::SDCCHType;
default: // There shouldnt be anything else in the MTCq.
return GSM::SDCCHType;
}
}
devassert(mmuMTSMSq.size());
return GSM::SDCCHType;
}
// Caller enters with the whole MMLayer locked so no one will try to add new contexts while we are doing this.
void MMUser::mmuFree(MMUserMap::iterator *piter, CancelCause cause) // Some callers deleted it from the MMUsers more efficiently than looking it up again.
void MMUser::mmuFree(MMUserMap::iterator *piter, TermCause cause) // Some callers deleted it from the MMUsers more efficiently than looking it up again.
{
devassert(mmuContext == NULL); // Caller already unlinked or verified that it was unattached.
devassert(gMMLock.lockcnt()); // Caller locked it.
@@ -266,7 +314,6 @@ bool MMContext::mmCheckTimers()
// // TODO: We need to lock the other channels thread.
//
// // Move all the transactions to the new channel:
//#if UNUSED
// MMContext *prevSet = mPrevChan->getContext();
// for (unsigned i = 0; i < TE_num; i++) {
// devassert(mmcTE[i] == NULL);
@@ -638,14 +685,9 @@ void MMUser::mmuAddMT(TranEntry *tran)
{
ScopedLock lock(gMMLock,__FILE__,__LINE__); // Way overkill.
//ScopedLock lock(mmuLock,__FILE__,__LINE__);
mmuPageTimer.future(gConfig.getNum("GSM.Timer.T3113"));
mmuPageTimer.future(gConfig.GSM.Timer.T3113);
switch (tran->servicetype()) {
case L3CMServiceType::TestCall:
case L3CMServiceType::FuzzCallTch:
case L3CMServiceType::MobileTerminatedCall:
mmuMTCq.push_back(tran);
break;
case L3CMServiceType::FuzzCallSdcch:
case L3CMServiceType::MobileTerminatedShortMessage:
mmuMTSMSq.push_back(tran);
break;
@@ -683,16 +725,16 @@ std::ostream& operator<<(std::ostream& os, const MMUser*mmu) { if (mmu) mmu->mmu
void MMContext::MMContextInit()
{
// The BLU phone seems to have a bug that a new MTC beginning too soon after a previous MTC with the same TI
// (pat) The BLU phone seems to have a bug that a new MTC beginning too soon after a previous MTC with the same TI
// seems to hang the phone, even though we definitely went through the CC release procedure whose specific
// purpose is to release the TI for recycling. Making the initial TI random seems to help.
mNextTI = rand() & 0x7; // Not supposed to matter what we pick here.
mmcMMU = NULL;
mmcChan = NULL;
mmcChannelUseCnt = 1;
mmcFuzzPort = NULL;
//mVoiceTrans = NULL;
memset(mmcTE,0,sizeof(mmcTE));
mmcOpenTime = time(NULL);
LOG(DEBUG)<<"MMContext ALLOC "<<(void*)this;
}
@@ -719,7 +761,7 @@ string MMContext::mmGetImsi(bool verbose)
void MMContext::l3sendm(const GSM::L3Message& msg, const GSM::Primitive& prim/*=GSM::DATA*/, SAPI_t SAPI/*=0*/)
{
WATCHINFO("sendm "<<this <<LOGVAR(prim)<<LOGVAR(SAPI)<<msg);
WATCHINFO("sendm "<<this <<LOGVAR(prim)<<LOGVAR(SAPI)<<" "<<msg);
mmcChan->l2sendm(msg,prim,SAPI);
}
@@ -730,6 +772,7 @@ void MMContext::mmcText(std::ostream&os) const
os << " MMContext(";
os <<mmcChan;
os <<LOGVAR(mmcChannelUseCnt);
os <<LOGVAR2("duration",mmcDuration());
if (mmcMMU) { os <<LOGVAR(mmcMMU); }
if (mmcTE[TE_MM] != NULL) { os <<LOGVAR2("MM",*mmcTE[TE_MM]); }
if (mmcTE[TE_CS1] != NULL) { os <<LOGVAR2("CS",*mmcTE[TE_CS1]); }
@@ -772,16 +815,17 @@ RefCntPointer<TranEntry> MMContext::mmGetTran(unsigned ati) const
void MMContext::mmConnectTran(ActiveTranIndex ati, TranEntry *tran)
{
devassert(gMMLock.lockcnt()); // Caller locked it.
// When a primary transaction is deleted we may promote the secondary transaction, so make sure we delete them all:
// When a primary transaction is deleted we may promote the secondary transaction, so keep trying to make sure we delete them all:
for (unsigned tries = 0; tries < 3; tries++) {
if (mmcTE[ati] != NULL) {
LOG(ERR) << "Transaction over-writing existing transaction"
<<LOGVAR2("old_transaction",*mmcTE[ati])<<LOGVAR2("new_transaction",tran);
mmcTE[ati]->teCancel();
// (pat) This is a bug somewhere.
mmcTE[ati]->teCancel(TermCause::Local(L3Cause::L3_Internal_Error));
}
}
mmcTE[ati] = tran;
tran->teSetContext(this);
mmcTE[ati] = tran; // Takes charge of tran; increments the refcnt
tran->teSetContext(this); // And set the back pointer.
}
@@ -795,14 +839,11 @@ void MMContext::mmConnectTran(TranEntry *tran)
case L3CMServiceType::MobileOriginatedCall:
case L3CMServiceType::EmergencyCall:
case L3CMServiceType::HandoverCall:
case L3CMServiceType::TestCall:
case L3CMServiceType::FuzzCallTch:
txi = TE_CS1;
break;
case L3CMServiceType::ShortMessage: // specifically, MO-SMS
txi = mmcTE[TE_MOSMS1]!=NULL ? TE_MOSMS2 : TE_MOSMS1;
break;
case L3CMServiceType::FuzzCallSdcch:
case L3CMServiceType::MobileTerminatedShortMessage:
txi = TE_MTSMS;
break;
@@ -845,6 +886,7 @@ void MMContext::mmDisconnectTran(TranEntry *tran)
LOG(ERR) << "Attempt to remove transaction "<<tran->tranID()<<" not found in MMContext";
}
// Does nothing if already unlinked.
void MMContext::mmcUnlink()
{
@@ -860,11 +902,51 @@ void MMContext::mmcUnlink()
}
}
// Move transactions from oldmmc to this, which is the current MMC serving the handset.
// We do this when we have positive knowledge that the oldmmc channel has been abandoned by the handset.
// This happens when the MS was on one MMC and has been moved or showed up on another MMC.
void MMContext::mmcMoveTransactions(MMContext *oldmmc)
{
for (unsigned ati = TE_first; ati < TE_num; ati++) {
if (! oldmmc->mmcTE[ati].isNULL()) {
if (mmcTE[ati].isNULL()) {
// Disconnect the old tran but be careful not to delete it.
// To be sure we have to keep a pointer to it through this operation.
RefCntPointer<TranEntry> oldtran = oldmmc->mmcTE[ati];
oldmmc->mmcTE[ati] = NULL;
oldtran->teSetContext(NULL); // Not necessary, but be tidy.
mmConnectTran((ActiveTranIndex)ati, oldtran.self());
} else {
// (pat) Disaster. There is a corresponding transaction already running on the new MMC,
// for example, old voice transaction and new voice transaction.
// I don't think this is possible for double paging responses (see comments at mmcLink and NewPagingResponseHandler)
// because we call mmcLink immediately when the second page is received, so the new MMC is empty.
// I'm not sure about other cases; the logic is too complicated.
// We will keep the new (more recent) transaction and the old transaction on the
// old MMC will be dropped when that channel is closed.
LOG(ERR) << "Handset has changed channels and has transactions running on the both channels. "
<<LOGVAR2("old channel",oldmmc->tsChannel()) <<LOGVAR2("new channel",tsChannel())
<<LOGVAR2("transaction being deleted",oldmmc->mmcTE[ati].self());
// We dont do anything. The transaction will be delete when the old channel is closed,
// which the caller should do immediately. We could call teCancel here but teCancel is tricky
// and I would like to reduce the number of calls to it. This probably doesnt happen anyway.
// If this does happen, we could be more clever, like a voice transaction could move to the secondary slot, etc.
}
}
}
}
// This is called whenever we have positively identified an MS so we can connect the MMU to the MMC.
// That includes:
// 1. when we receive a PagingResponse message (which includes an IMSI or TMSI; note that PagingResponse
// is an L3 message which means the MS has already negotiated L2 LAPDm connection to send it.)
// 2. MOC call control when we identify the MS
// 3. Mobility Management after authorization.
// 4. From SMS somewhere too.
void MMContext::mmcLink(MMUser *mmu)
{
devassert(gMMLock.lockcnt()); // Caller locked it.
// Detach the mmu from its existing channel, if any. That happens when the MS disappeared temporarily
// and then came back on another channel, for example, handover to another BTS and back.
//RefCntPointer<MMUser> saveme(mmu); // Dont delete mmu during this procedure.
MMContext *mmc = this;
if (mmc->mmcMMU == mmu) {
@@ -872,27 +954,41 @@ void MMContext::mmcLink(MMUser *mmu)
devassert(mmu->mmuContext == mmc); // We always maintain pointers both ways.
return;
}
if (mmu->mmuContext) { mmu->mmuContext->mmcUnlink(); }
// Detach the mmu from its existing channel, if any. That happens when the MS disappeared temporarily
// and then came back on another channel, for example, handover to another BTS and back.
// (pat 4-2014) It also happens for paging response: Sometimes the MS sends two RACHes in a row,
// which allocates two channels (say A and B.)
// We send two immediate assignments, and the MS may respond to both! First it does an L1 LAPDm negotiation on A
// and sends a Paging Response there, which connects its MMU to the MMContext for A, then it
// does an L2 LAPDm negotiation on B and sends a second Paging Response there, so we get here with this == channel B
// but with the MMU attached to channel A. So we must disconnect the existing channel and move the MMU to the new channel.
// We have to move the transactions from the old MMContext to the new; for example if there was only one MTC transaction and
// it has already been moved from the MMU to the MMC, then the MMU is empty of transactions which will release channel B
// immmediately in mmCheckNewActivity.
if (MMContext *oldmmc = mmu->mmuContext) {
if (oldmmc != mmc) {
LOG(DEBUG) <<"reconnecting mmu"<<LOGVAR(mmu)<<LOGVAR(oldmmc)<<LOGVAR(mmc);
// pat 4-2014: Move the transactions from the old to the new mmc.
mmcMoveTransactions(oldmmc);
}
oldmmc->mmcUnlink();
}
mmc->mmcUnlink();
mmc->mmcMMU = mmu;
mmu->mmuContext = mmc;
}
void MMContext::mmcFree()
void MMContext::mmcFree(TermCause cause)
{
assert(this->mmcMMU == NULL);
devassert(gMMLock.lockcnt()); // Caller locked it.
if (mmcFuzzPort) {
mmcFuzzPort->close();
mmcFuzzPort = NULL;
}
// Cancel all the enclosed transactions and their dialogs.
for (unsigned i = 0; i < TE_num; i++) {
// When a primary transaction is deleted we may promote the secondary transaction, so delete them all:
for (unsigned tries = 0; tries < 3; tries++) {
if (mmcTE[i] != NULL) { mmcTE[i]->teCancel(); } // Removes the transaction from mmcTE via mmDisconnectTran
if (mmcTE[i] != NULL) { mmcTE[i]->teCancel(cause); } // Removes the transaction from mmcTE via mmDisconnectTran
}
assert(mmcTE[i] == NULL); // teCancel removed it.
}
@@ -901,7 +997,12 @@ void MMContext::mmcFree()
}
// The logical channel no longer points to this Context, so release it.
void MMLayer::mmFreeContext(MMContext *mmc)
// The cause is used only for reporting purposes for any transactions still extent;
// the underlying channel has already been released so we cannot send a cause code downstream,
// and if there are any new SIP dialogs upstream we should normally start re-paging the handset
// to create a new mmcontext rather than cancelling them.
// TODO: We may want to cancel any SIP dialogs based on the cause.
void MMLayer::mmFreeContext(MMContext *mmc,TermCause cause)
{
// There can be multiple logical channels pointing to the same Context, so decrement
// the channel use count and delete only when 0.
@@ -921,19 +1022,19 @@ void MMLayer::mmFreeContext(MMContext *mmc)
// 504 indicates some other timeout beyond SIP (interworking)
// 480 indicates some temporary form of resource unavailability or congestion but resource is accessible and can be checked
// 503 indicates the service is unavailable but does not imply for how long
SipCode sipcode(480,"Temporarily Unavailable");
//SipCode sipcode(480,"Temporarily Unavailable");
if (mmu) {
// It is possible for new SIP dialogs to have started between the time we decided
// FIXME It is possible for new SIP dialogs to have started between the time we decided
// to close this channel and now. It is also possible that we closed the channel
// because of loss of contact with the MS. In either case, if the MMU has dialogs,
// dont delete it - just leave it alone and we will start repaging this MS again.
// TODO: But first, walk though dialogs and cancel any that need it.
if (mmu->mmuIsEmpty()) {
mmu->mmuFree(NULL,CancelCauseUnknown); // CancelCause is not used because it is empty.
mmu->mmuFree(NULL,TermCause::Local(L3Cause::No_Transaction_Expected)); // TermCause is not used because there are no dialogs.
}
}
mmc->mmcFree();
mmc->mmcFree(cause);
}
void MMLayer::mmMTRepage(const string imsi)
@@ -944,7 +1045,7 @@ void MMLayer::mmMTRepage(const string imsi)
MMUser *mmu = mmFindByImsi(imsi,false);
if (mmu) {
// This has no effect unless we are paging, ie, if the MMUser has not yet connected to an MMChannel.
mmu->mmuPageTimer.future(gConfig.getNum("GSM.Timer.T3113"));
mmu->mmuPageTimer.future(gConfig.GSM.Timer.T3113);
} else {
LOG(DEBUG) << "repeated INVITE/MESSAGE with no MMUser record";
}
@@ -968,6 +1069,27 @@ void MMLayer::mmAttachByImsi(L3LogicalChannel *chan, string imsi)
// So now we want to unblock any previously blocked imsi/tmsi
}
bool MMLayer::mmTerminateByImsi(string imsi)
{
ScopedLock lock(gMMLock,__FILE__,__LINE__);
MMUser *mmu = mmFindByImsi(imsi,false);
if (!mmu) { return false; } // Not found.
MMContext* mmc = mmu->mmuContext;
if (!mmc) {
// There is no channel, just kill off the MMUser, which will stop paging and cancel the SIP dialogs.
mmu->mmuFree(NULL,TermCause::Local(L3Cause::Operator_Intervention));
return true;
}
if (mmc->tsChannel()->chanRunning()) {
// Dont call chanClose from here because it sends a message which would block the calling thread.
//mmc->tsChannel()->chanClose(L3RRCause::PreemptiveRelease,RELEASE); DONT DO THIS!
// FIXME: We would like to send an RR Release message first.
mmc->mmcTerminationRequested = true;
}
return true;
}
// This is the way MMUsers are created from the SIP side.
void MMLayer::mmAddMT(TranEntry *tran)
{
@@ -991,16 +1113,6 @@ MMUser *MMLayer::mmFindByImsi(string imsi, // Do not change this to a reference.
{
LOG(DEBUG) <<LOGVAR(imsi) <<LOGVAR(create);
devassert(gMMLock.lockcnt()); // Caller locked it.
#if 0
MMUser *mmu = MMUsers.readNoBlock(imsi);
if (mmu == NULL) {
if (create) {
mmu = new MMUser(imsi);
MMUsers.write(imsi,mmu);
}
}
return mmu;
#endif
MMUser *result;
const char *what = "existing ";
if (create) {
@@ -1025,6 +1137,16 @@ MMUser *MMLayer::mmFindByImsi(string imsi, // Do not change this to a reference.
return result;
}
TMSI_t MMUser::mmuGetTmsi()
{
if (! this->mmuDidTmsiCheck) {
this->mmuDidTmsiCheck = true;
if (uint32_t tmsi = gTMSITable.tmsiTabGetTMSI(mmuImsi,true)) { this->mmuTmsi = tmsi; }
}
return this->mmuTmsi;
}
MMUser *MMLayer::mmFindByTmsi(uint32_t tmsi)
{
devassert(gMMLock.lockcnt()); // Caller locked it.
@@ -1032,7 +1154,11 @@ MMUser *MMLayer::mmFindByTmsi(uint32_t tmsi)
MMUser *result = NULL;
for (MMUserMap::iterator it = MMUsers.begin(); it != MMUsers.end(); ++it) {
MMUser *mmu = it->second;
if (mmu->mmuTmsi.valid() && mmu->mmuTmsi.value() == tmsi) { result = mmu; break; }
// (pat) The handset we want could be simultaneously doing an MM procedure that is establishing a TMSI,
// so we should check the tmsi table every single time this happens.
// However, we are currently doing an expensive sql lookup so only check once.
TMSI_t mmutmsi = mmu->mmuGetTmsi();
if (mmutmsi.valid() && mmutmsi.value() == tmsi) { result = mmu; break; }
}
LOG(DEBUG) << LOGVAR(result);
return result;
@@ -1050,17 +1176,15 @@ MMUser *MMLayer::mmFindByMobileId(L3MobileIdentity&mid)
}
}
// If wait flag, block until there are some, called forever from the paging thread.
// When called from the paging thread loop this function is responsible for noticing expired pages and deleting them.
void MMLayer::mmGetPages(NewPagingList_t &pages, bool wait)
// When called from the paging thread loop this function is responsible for noticing expired MMUsers and deleting them.
void MMLayer::mmGetPages(NewPagingList_t &pages)
{
LOG(DEBUG);
ScopedLock lock(gMMLock,__FILE__,__LINE__);
//LOG(DEBUG) <<LOGVAR(MMUsers.size());
assert(pages.size() == 0); // Caller passes us a new list each time.
LOG(DEBUG) <<LOGVAR(MMUsers.size());
while (1) {
{
ScopedLock lock(gMMLock,__FILE__,__LINE__);
pages.reserve(MMUsers.size());
for (MMUserMap::iterator it = MMUsers.begin(); it != MMUsers.end(); ) {
MMUser *mmu = it->second;
@@ -1074,48 +1198,48 @@ void MMLayer::mmGetPages(NewPagingList_t &pages, bool wait)
// Expired. Get rid of it.
LOG(INFO) << "Page expired for imsi="<<mmu->mmuImsi;
// Erasing from a map invalidates the iterator, but not the iteration.
//MMUsers.erase(thisone);
// (pat) The SIP error for no page should probably not be 480 Temporarily Unavailable,
// because that implies we know that the user is at the BTS, but if it did not answer the page, we do not.
// Paul at Null Team recommended 504
mmu->mmuFree(&thisone,CancelCauseNoAnswerToPage);
// Paul at Null Team recommended 504.
// FIXME URGENTLY: Dont do an mmFree within the gMMLock, although we need to make sure it does not disappear.
mmu->mmuFree(&thisone,TermCause::Local(L3Cause::No_Paging_Response));
continue;
}
// TODO: We could add a check for a "provisional IMSI"
bool wantCS = mmu->mmuMTCq.size() > 0; // Is it a CS transaction, as opposed to SMS?
if (! mmu->mmuTmsi.valid()) {
uint32_t tmsi = gTMSITable.tmsiTabGetTMSI(mmu->mmuImsi,true);
LOG(DEBUG)<<"tmsiTabGetTMSI imsi="<<mmu->mmuImsi<< "returns"<<LOGVAR(tmsi);
if (tmsi) { mmu->mmuTmsi = tmsi; }
}
NewPagingEntry tmp(wantCS,mmu->mmuImsi,mmu->mmuTmsi);
WATCH("page "<<LOGVAR2("TMSI",mmu->mmuTmsi)<<LOGVAR2("IMSI",mmu->mmuImsi));
NewPagingEntry tmp(mmu->mmuGetInitialChanType(), mmu->mmuImsi);
pages.push_back(tmp);
}
if (pages.size()) { return; }
// Wait for someone to add a new MMUser.
if (!wait) { return; }
// too many of these... LOG(DEBUG) <<"waiting for new MMUser signal";
// We need to provide a timeout so that we free expired pages in a timely manner; if we dont
// then that MMUser is essentially locked because incoming SIP invites get a busy return, and it
// wont get released until some other unrelated page intervenes.
mmPageSignal.wait(gMMLock,500);
// Need a while loop here because the wait does not guarantee it was signalled.
}
if (pages.size()) LOG(DEBUG) <<LOGVAR(pages.size());
}
// Not used. This is only documentation how to do this now.
//void MMLayer::mmWaitForPages(NewPagingList_t &pages, bool wait)
//{
// ScopedLock lock(gMMLock,__FILE__,__LINE__);
// while (1) {
// // Code goes here.
// // ...
// //
// if (!wait) { return; }
// // We need to provide a timeout so that we free expired pages in a timely manner; if we dont
// // then that MMUser is essentially locked because incoming SIP invites get a busy return, and it
// // wont get released until some other unrelated page intervenes.
// mmPageSignal.wait(gMMLock,500);
// // Need a while loop here because the wait does not guarantee it was signalled.
// }
//}
// For use by the CLI: create a copy of the paging list and print it.
void MMLayer::printPages(ostream &os)
{
// This does not need to lock anything. The mmGetPages provides locked access to the MMUser list.
NewPagingList_t pages;
gMMLayer.mmGetPages(pages,false);
gMMLayer.mmGetPages(pages);
for (NewPagingList_t::iterator it = pages.begin(); it != pages.end(); ++it) {
NewPagingEntry &pe = *it;
os <<LOGVAR2("imsi",pe.mImsi) <<LOGVAR2("tmsi",pe.mTmsi) <<LOGVAR2("service",pe.mWantCS ? "CS":"SMS")
<<LOGVAR2("chtype",pe.getChanType());
os <<pe.text();
}
}
@@ -1133,7 +1257,7 @@ bool MMLayer::mmPageReceived(MMContext *mmchan, L3MobileIdentity &mobileId)
}
// We do not 'delete' pages - only unattached MMUser are paged,
// so the act of attaching the MMUser to a Context (below) causes us to stop paging this MS.
// so the act of attaching the MMUser to a MMContext (below) causes us to stop paging this MS.
// This is what ties the MMUser and MMContext together for MT services.
// They are linked together from now on.
@@ -1191,5 +1315,14 @@ string MMLayer::printMMInfo()
return ss.str();
}
void controlInit()
{
LOG(DEBUG);
gTMSITable.tmsiTabOpen(gConfig.getStr("Control.Reporting.TMSITable").c_str());
LOG(DEBUG);
gNewTransactionTable.ttInit();
LOG(DEBUG);
TranInit();
}
};

View File

@@ -3,7 +3,7 @@
*
* This software is distributed under multiple licenses;
* see the COPYING file in the main directory for licensing
* information for this specific distribuion.
* information for this specific distribution.
*
* This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details.
@@ -24,8 +24,9 @@
#include <GSMTransfer.h>
#include "ControlCommon.h"
#include "PagingEntry.h"
#include "L3TranEntry.h" // Needed because InterthreadQueue deletes its elements on destruction.
#include "RadioResource.h" // For Paging
//#include "RadioResource.h" // For Paging
#include "L3Utils.h"
#include <SIPDialog.h>
@@ -73,9 +74,18 @@ class MMUser : public MemCheckMMUser /*: public RefCntBase*/ {
friend class MMLayer;
MMState mmuState;
MMContext* mmuContext;
string mmuImsi; // Just the imsi, without "IMSI"
TMSI_t mmuTmsi;
void mmuFree(MMUserMap::iterator *it,CancelCause cause /*= CancelCauseUnknown*/); // This is the destructor. It is not public. Can only delete from gMMLayer because we must lock the universe first.
protected: string mmuImsi; // Just the imsi, without "IMSI"
public: string mmuGetImsi(bool verbose) { return mmuImsi.empty() ? (verbose ? "no-imsi" : "") : mmuImsi; }
protected: TMSI_t mmuTmsi;
Bool_z mmuDidTmsiCheck; // Have we looked up the IMSI in the TMSI table yet?
public: TMSI_t mmuGetTmsi();
protected:
void mmuFree(MMUserMap::iterator *it,TermCause cause /*= TermCauseUnknown*/); // This is the destructor. It is not public. Can only delete from gMMLayer because we must lock the universe first.
GSM::ChannelType mmuGetInitialChanType() const;
void MMUserInit() { mmuState = MMStateUndefined; mmuContext = NULL; }
public:
@@ -84,7 +94,6 @@ class MMUser : public MemCheckMMUser /*: public RefCntBase*/ {
void mmuAddMT(TranEntry *tran);
//void mmuPageReceived(L3LogicalChannel *chan);
string mmuGetImsi(bool verbose) { return mmuImsi.empty() ? (verbose ? "no-imsi" : "") : mmuImsi; }
//void mmuClose(); // TODO
bool mmuIsAttached() { return mmuContext != NULL; } // Are we attached to a radio channel?
bool mmuIsEmpty();
@@ -111,13 +120,19 @@ class MMContext : public MemCheckMMContext /*: public RefCntBase*/ {
MMUser* mmcMMU;
//L3Timer TChReassignment;
void mmcMoveTransactions(MMContext *oldmmc);
protected:
void mmcUnlink();
void mmcLink(MMUser *mmu);
time_t mmcOpenTime;
// These are the Transactions/Procedures that may be active simultaneously:
public:
UDPSocket *mmcFuzzPort;
Bool_z mmcTerminationRequested;
NeighborPenalty mmcHandoverPenalty;
void chanSetHandoverPenalty(NeighborPenalty &wPenalty) { mmcHandoverPenalty = wPenalty; }
enum ActiveTranIndex {
TE_first = 0, // Start of table.
@@ -133,11 +148,11 @@ class MMContext : public MemCheckMMContext /*: public RefCntBase*/ {
};
RefCntPointer<TranEntry> mmcTE[TE_num];
unsigned mNextTI;
InterthreadQueue<const L3Message> mmcServiceRequests;
InterthreadQueue<const L3Message> mmcServiceRequests; // Incoming CM Service Request messages.
void startSMSTran(TranEntry *tran);
void MMContextInit();
void mmcFree(); // This is the destructor. It is not public. Can only delete from gMMLayer because we must lock the MMUserMap first.
void mmcFree(TermCause cause); // This is the destructor. It is not public. Can only delete from gMMLayer because we must lock the MMUserMap first.
public:
MMContext(L3LogicalChannel *chan);
@@ -146,6 +161,7 @@ class MMContext : public MemCheckMMContext /*: public RefCntBase*/ {
L3LogicalChannel *tsChannel() { return mmcChan; }
MMContext *tsDup();
void mmcPageReceived() const;
time_t mmcDuration() const { return time(NULL) - mmcOpenTime; }
RefCntPointer<TranEntry> mmGetTran(unsigned ati) const;
void mmConnectTran(ActiveTranIndex ati, TranEntry *tran);
@@ -156,7 +172,7 @@ class MMContext : public MemCheckMMContext /*: public RefCntBase*/ {
void getTranIds(TranEntryList &tranlist) const;
// By returning a RefCntPointer we prevent destruction of the transaction during use by caller.
RefCntPointer<TranEntry> tsGetVoiceTran() const { return mmcTE[TE_CS1]; }
void tsSetVoiceTran(TranEntry*tran) { mmcTE[TE_CS1] = tran; }
//void tsSetVoiceTran(TranEntry*tran) { mmcTE[TE_CS1] = tran; }
void mmSetChannel(L3LogicalChannel *wChan) { mmcChan = wChan; }
string mmGetImsi(bool verbose); // If the IMSI is known, return it, else ""
@@ -166,7 +182,7 @@ class MMContext : public MemCheckMMContext /*: public RefCntBase*/ {
bool mmCheckTimers(); // Return true if anything happened.
RefCntPointer<TranEntry> findTran(const L3Frame *frame, const L3Message *l3msg) const;
bool mmDispatchL3Frame(const L3Frame *frame, const L3Message *msg);
void l3sendm(const GSM::L3Message& msg, const GSM::Primitive& prim=GSM::DATA, SAPI_t SAPI=SAPI0);
void l3sendm(const GSM::L3Message& msg, const GSM::Primitive& prim=GSM::L3_DATA, SAPI_t SAPI=SAPI0);
void mmcText(std::ostream&os) const;
};
std::ostream& operator<<(std::ostream& os, const MMContext&mmc);
@@ -193,23 +209,25 @@ class MMLayer {
Signal mmPageSignal; ///< signal to wake the paging loop
MMUserMap MMUsers;
public:
void mmGetPages(NewPagingList_t &pages, bool wait);
void mmGetPages(NewPagingList_t &pages);
void printPages(std::ostream &os);
bool mmPageReceived(MMContext *mmchan, L3MobileIdentity &mobileId);
// Add a new MT transaction, and signal the pager to come notice it.
void mmAddMT(TranEntry *tran);
void mmFreeContext(MMContext *mmc);
void mmFreeContext(MMContext *mmc,TermCause cause);
// This is called when the MT SIP engine on the other side has sent us another message.
// (pat) We could be blocked for several reasons, including paging, waiting for LUR to complete, waiting for channel to change, etc.
// But if we are paging, reset the paging timer so we keep paging.
void mmMTRepage(const string imsi); // Reset the paging timer so we continue paging.
void mmAttachByImsi(L3LogicalChannel *chan, string imsi);
bool mmTerminateByImsi(string imsi);
//bool mmStartMTDialog(SIP::SipDialog*dialog, SIP::SipMessage*invite);
MMUser *mmFindByImsi(string imsi, bool create=false);
MMUser *mmFindByTmsi(uint32_t tmsi);
MMUser *mmFindByMobileId(L3MobileIdentity&mid);
void printMMUsers(std::ostream&os, bool onlyUnattached);
void printMMInfo(std::ostream&os);
string mmGetNeighborTextByImsi(string imsi, bool full);
string printMMInfo();
// Is the single MTC slot busy?

View File

@@ -1,8 +1,9 @@
/* Copyright 2013, 2014 Range Networks, Inc.
/*
* Copyright 2013, 2014 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.
* information for this specific distribution.
*
* This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details.
@@ -12,6 +13,8 @@
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*/
// Written by Pat Thompson
#define LOG_GROUP LogGroup::Control // Can set Log.Level.Control for debugging
#include <set>
@@ -27,6 +30,7 @@
#include <Logger.h>
#include <Interthread.h>
#include <Timeval.h>
#include <Globals.h>
#include <GSMConfig.h>
@@ -41,7 +45,6 @@
//#include <SIPDialog.h>
#include <SIPExport.h>
#include <Regexp.h>
#include "RRLPServer.h"
using namespace GSM;
@@ -79,7 +82,7 @@ void NewCMServiceResponder(const L3CMServiceRequest* cmsrq, MMContext* mmchan)
default:
gReports.incr("OpenBTS.GSM.MM.CMServiceRequest.Unhandled");
LOG(NOTICE) << "service not supported for " << *cmsrq;
mmchan->l3sendm(L3CMServiceReject(L3RejectCause(L3RejectCause::ServiceOptionNotSupported)));
mmchan->l3sendm(L3CMServiceReject(L3RejectCause::Service_Option_Not_Supported));
//mmchan->l3sendm(L3ChannelRelease(L3RRCause::Unspecified));
return;
}
@@ -99,8 +102,8 @@ void NewPagingResponseHandler(const L3PagingResponse* resp, MMContext* mmchan)
L3MobileIdentity mobileId = resp->mobileID();
if (! gMMLayer.mmPageReceived(mmchan,mobileId)) {
LOG(WARNING) << "Paging Reponse with no Mobility Management record for " << mobileId;
mmchan->l3sendm(L3ChannelRelease(L3RRCause::CallAlreadyCleared));
LOG(WARNING) << "Paging Reponse with no Mobility Management record (probably timed out) for " << mobileId;
mmchan->l3sendm(L3ChannelRelease(L3RRCause::Call_Already_Cleared));
return; // There is nothing more we can do about this because we dont know who it is.
}
}
@@ -249,17 +252,8 @@ MMSharedData *LUBase::ludata() const
return tran()->mMMData;
}
#if 0
// (pat) This queryForRejectCause was part of the ancient release 3 fuzzing interface, but please please do not
// put this back in; I dont want to support this methodology any more; this wget is going to hang the state machine and that is bad.
// We support a new wonderful and non-dorky way for the customer to return a reject cause,
// which is simply to include it in the SIP Registrar response message using the "P-GSM-Reject-Cause" SIP header.
#endif
// (pat) This was formerly a member of LUBase so it could get at mNewImei for the S_RELEASE ifdefed code,
// but I dont think that should be re-enabled in any case, and I am making this a simple global function.
// TODO: Reject cause should be determined in a more central location, probably sipauthserve.
// We may want different reject codes based on the IMSI or MSISDN of the MS, or on the CM service being requested (CC, SMS, USSD, GPRS),
// although this code here is used only by LUR.
@@ -269,7 +263,7 @@ static MMRejectCause getRejectCause(unsigned sipCode)
unsigned utmp;
switch (sipCode) {
case 400: // This value is used in the SIP code for unrecoverable errors in a SIP message from the Registrar.
rejectCause = L3RejectCause::NetworkFailure;
rejectCause = L3RejectCause::Network_Failure;
break;
case 401: { // SIP 401 "Unauthorized"
// The sip nomenclature for 401 and 404 are exactly reversed:
@@ -280,7 +274,7 @@ static MMRejectCause getRejectCause(unsigned sipCode)
break;
}
case 403: { // SIP 403 "Forbidden"
rejectCause = L3RejectCause::LocationAreaNotAllowed;
rejectCause = L3RejectCause::Location_Area_Not_Allowed;
break;
}
case 404: { // SIP 404 "Not Found"
@@ -294,7 +288,7 @@ static MMRejectCause getRejectCause(unsigned sipCode)
break;
}
case 424: { // SIP 424 "Bad Location Information"
rejectCause = L3RejectCause::RoamingNotAllowedInLA;
rejectCause = L3RejectCause::Roaming_Not_Allowed_In_LA;
break;
}
case 504: { // SIP 504 "Servier Time-out"
@@ -302,18 +296,19 @@ static MMRejectCause getRejectCause(unsigned sipCode)
break;
}
case 603: { // SIP 603 "Decline"
rejectCause = L3RejectCause::IMSIUnknownInVLR;
rejectCause = L3RejectCause::IMSI_Unknown_In_VLR;
break;
}
case 604: { // SIP 604 "Does Not Exist Anywhere"
rejectCause = L3RejectCause::IMSIUnknownInHLR;
rejectCause = L3RejectCause::IMSI_Unknown_In_HLR;
break;
}
default:
LOG(NOTICE) << "REGISTER unexpected response from Registrar" <<LOGVAR(sipCode);
rejectCause = L3RejectCause::NetworkFailure;
rejectCause = L3RejectCause::Network_Failure;
break;
}
LOG(INFO) << "SIP term info sip code: " << sipCode << " rejectCause: " << rejectCause;
LOG(DEBUG) <<LOGVAR(sipCode)<<LOGVAR(rejectCause);
return rejectCause; // TODO: We should check what the user entered makes sense.
}
@@ -497,12 +492,13 @@ MachineStatus LUStart::stateRecvIdentityResponse(const GSM::L3IdentityResponse *
string prevImsi = getImsi();
if (prevImsi.size() && prevImsi != imsi) {
LOG(ERR) << "MS returned two different IMSIs";
MMRejectCause failCause = L3RejectCause::InvalidMandatoryInformation;
MMRejectCause failCause = L3RejectCause::Invalid_Mandatory_Information;
// There is no ludata()->store yet so just set it directly:
gTMSITable.tmsiTabSetRejected(imsi,(int)failCause);
// I dont know what the cause should be here, but if this ever happens, we dont care.
channel()->l3sendm(L3LocationUpdatingReject(failCause));
return closeChannel(L3RRCause::NormalEvent,RELEASE);
LOG(INFO) << "SIP term info closeChannel called in stateRecvIdentityResponse";
return closeChannel(L3RRCause::Normal_Event,L3_RELEASE_REQUEST,TermCause::Local(failCause));
}
}
@@ -689,8 +685,8 @@ MachineStatus LUAuthentication::machineRunState(int state, const GSM::L3Message*
LOG(ALERT) << "Invalid RAND challenge returned by Registrar (RAND length=" <<rand.size() <<")";
// (pat) LUFinish may still permit services depending on failOpen().
ludata()->mRegistrationResult.regSetError();
//channel()->l3sendm(L3LocationUpdatingReject(L3RejectCause::ServiceOptionTemporarilyOutOfOrder));
//return closeChannel(L3RRCause::NormalEvent,RELEASE);
//channel()->l3sendm(L3LocationUpdatingReject(L3RejectCause::Service_Option_Temporarily_Out_Of_Order));
//return closeChannel(L3RRCause::Normal_Event,RELEASE);
return callMachStart(new LUFinish(tran()));
}
@@ -921,6 +917,8 @@ MachineStatus LUFinish::stateSendLUResponse()
}
}
const char *openregistrationmsg = "";
switch (ludata()->mRegistrationResult.mRegistrationStatus) {
case RegistrationSuccess:
authorization = AuthAuthorized;
@@ -930,7 +928,7 @@ MachineStatus LUFinish::stateSendLUResponse()
authorization = AuthFailOpen;
//ludata()->mRegistrationResult.regSetSuccess();
} else {
failCause = L3RejectCause::NetworkFailure;
failCause = L3RejectCause::Network_Failure;
}
break;
case RegistrationFail:
@@ -939,6 +937,7 @@ MachineStatus LUFinish::stateSendLUResponse()
if (openRegistration()) {
//ludata()->mRegistrationResult.regSetSuccess();
authorization = AuthOpenRegistration;
openregistrationmsg = "(open registration)";
} else {
failCause = ludata()->mRegistrationResult.mRejectCause;
}
@@ -948,7 +947,7 @@ MachineStatus LUFinish::stateSendLUResponse()
if (authorization != AuthUnauthorized) {
if (authorization == AuthAuthorized) {
LOG(INFO) << "registration SUCCESS: " << ludata()->mLUMobileId;
LOG(INFO) << "registration SUCCESS"<<openregistrationmsg<<": " << ludata()->mLUMobileId;
} else {
LOG(INFO) << "registration ALLOWED: " << ludata()->mLUMobileId;
}
@@ -1023,7 +1022,7 @@ MachineStatus LUFinish::stateSendLUResponse()
//gTMSITable.tmsiTabSetRejected(imsi,failCause);
ludata()->store.setRejectCode(failCause);
gTMSITable.tmsiTabCreateOrUpdate(imsi,&ludata()->store,&ludata()->mLULAI,ludata()->mOldTmsi);
channel()->l3sendm(L3LocationUpdatingReject(L3RejectCause(failCause)));
channel()->l3sendm(L3LocationUpdatingReject(failCause));
sendWelcomeMessage(ludata(), "Control.LUR.FailedRegistration.Message", // Does nothing if the SQL var is not set.
"Control.LUR.FailedRegistration.ShortCode",subscriber(),channel());
@@ -1031,8 +1030,8 @@ MachineStatus LUFinish::stateSendLUResponse()
// tmsiTabUpdate must be after sendWelcomeMessage optionally updates the welcomeSent field.
gTMSITable.tmsiTabUpdate(imsi,&ludata()->store);
//return closeChannel(L3RRCause::NormalEvent,RELEASE);
return MachineStatusQuitTran;
//return closeChannel(L3RRCause::Normal_Event,RELEASE);
return MachineStatus::QuitTran(TermCause::Local(failCause));
}
// (pat) We must NOT attach the MMContext to the MMUser during the Location Updating procedure;
@@ -1078,14 +1077,6 @@ MachineStatus LUFinish::statePostAccept()
LOG(DEBUG);
timerStop(TMisc1); // The mystery timer.
timerStop(TMMCancel); // all finished.
bool dorrlp;
if ((dorrlp = gConfig.getBool("Control.LUR.QueryRRLP"))) {
// Query for RRLP
// TODO: RRLP should be another procedure.
if (!sendRRLP(ludata()->mLUMobileId, channel())) {
LOG(INFO) << "RRLP request failed";
}
}
// If this is an IMSI attach, send a welcome message.
// (pat) This should be in the sipauthserve, not the BTS.
@@ -1107,20 +1098,10 @@ MachineStatus LUFinish::statePostAccept()
gTMSITable.tmsiTabUpdate(getImsi(),&ludata()->store);
if (dorrlp) {
// DEBUG: Wait for pirates.
//L3Frame* resp = channel()->l2recv(130000);
//LOG(ALERT) << "RRLP returned " << *resp;
// (pat) The RRLP message takes 2.5 secs download best case. The response from the Nokia comes after 3.5 secs.
// So wait here for an RRLP response.
LOG(DEBUG) <<"Waiting for RRLP";
timerStart(TMMCancel,10000,TimerAbortChan);
return MachineStatusOK;
}
// Release the channel and return.
LOG(DEBUG) <<"MM procedure complete";
return MachineStatusQuitTran;
return MachineStatus::QuitTran(TermCause::Local(L3Cause::MM_Success));
}
// The l3msg is LocationUpdatingRequest
@@ -1154,7 +1135,7 @@ MachineStatus LUNetworkFailure::machineRunState(int state, const GSM::L3Message*
//onTimeout1(4000,stateAuthFail);
timerStart(TMMCancel,4000,TimerAbortChan); // Mystery timer.
// We dont unauthorize because it is not the MS fault.
channel()->l3sendm(L3LocationUpdatingReject(L3RejectCause::NetworkFailure));
channel()->l3sendm(L3LocationUpdatingReject(L3RejectCause::Network_Failure));
return MachineStatusOK;
default:
return unexpectedState(state,l3msg);
@@ -1188,7 +1169,7 @@ MachineStatus L3RegisterMachine::machineRunState(int state, const GSM::L3Message
{
switch (state) {
case stateStart: // Start state.
startRegister(tran()->subscriber(),mRResult->mRand,mSRES,channel());
startRegister(tran()->tranID(),tran()->subscriber(),mRResult->mRand,mSRES,channel());
return MachineStatusOK;
case L3CASE_SIP(dialogActive): {
@@ -1265,4 +1246,31 @@ string RegistrationResult::text()
return result;
}
// (pat) 5-2014. We dont currently save the detach information in OpenBTS. I dont want to set the TMSI table AUTH or AUTH_EXPIRY to 0,
// because we may need those for a later authorization if backhaul is cut or central authorization entity needs to be
// refreshed from our database. I dont want to add a new TMSI table field at this time, since it outdates all existing TMSI tables.
// The best way would would be to make AUTH a bit field and add bits.
// However, we dont currently use that information; we could send an immediate final response code to an INVITE or MESSAGE
// for a handset that did an Imsi-Detach if the beacon AttachDetach flag is still set, however, that will not work when we support
// multiple BTS per LAC - the final response should be sent from the central authority, not the individual BTS.
// So I am going to do nothing else at this time.
// (pat) NOTE: Kazoo may send a dialog failure to the imsi detach, which will try to go to the transaction, and is currently ignored.
void imsiDetach(L3MobileIdentity mobid, L3LogicalChannel *chan)
{
string imsi;
if (mobid.isIMSI()) {
imsi = string(mobid.digits());
} else if (mobid.isTMSI()) {
imsi = gTMSITable.tmsiTabGetIMSI(mobid.TMSI(),NULL);
if (imsi.size() == 0) {
LOG(WARNING)<<format("IMSI Detach indication with unrecognized TMSI (0x%x) ignored",mobid.TMSI());
return;
}
} else {
LOG(WARNING)<<format("IMSI Detach indication with unrecognized mobileID type (%d) ignored",mobid.type());
return;
}
startUnregister(imsi,chan);
}
}; // namespace Control

View File

@@ -1,8 +1,9 @@
/* Copyright 2013, 2014 Range Networks, Inc.
/*
* Copyright 2013, 2014 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.
* information for this specific distribution.
*
* This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details.
@@ -11,6 +12,7 @@
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*/
#ifndef _L3MOBILITYMANAGEMENT_H_
#define _L3MOBILITYMANAGEMENT_H_ 1
@@ -301,5 +303,7 @@ class L3RegisterMachine : public LUBase //MachineBase
SIP::SipMessage *makeRegisterMsg1();
};
extern void imsiDetach(L3MobileIdentity mobid, L3LogicalChannel *chan);
}; // namespace Control
#endif

View File

@@ -1,8 +1,9 @@
/* Copyright 2013, 2014 Range Networks, Inc.
/*
* Copyright 2013, 2014 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.
* information for this specific distribution.
*
* This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details.
@@ -24,6 +25,7 @@
#include <GSML3Message.h>
#include <GSMLogicalChannel.h>
#include <SMSMessages.h>
#include <Globals.h>
#include <CLI.h>
@@ -35,8 +37,9 @@ using namespace SIP;
struct SMSCommon : public MachineBase {
unsigned mRpduRef;
SMSCommon(TranEntry *tran) : MachineBase(tran) {}
void l3sendSms(const L3Message &msg, SAPI_t sapi); // Send an SMS message to the correct place.
L3LogicalChannel *getSmsChannel() const;
void l3sendSms(const L3Message &msg); // Send an SMS message to the correct place.
//L3LogicalChannel *getSmsChannel() const;
SAPI_t getSmsSap() const;
};
@@ -71,19 +74,29 @@ class MTSMSMachine : public SMSCommon
};
L3LogicalChannel *SMSCommon::getSmsChannel() const
//L3LogicalChannel *SMSCommon::getSmsChannel() const
//{
// if (channel()->isSDCCH()) {
// return channel(); // Use main SDCCH.
// } else {
// assert(channel()->isTCHF());
// return channel()->getSACCHL3(); // Use SACCH associated with TCH.
// }
//}
SAPI_t SMSCommon::getSmsSap() const
{
if (channel()->isSDCCH()) {
return channel(); // Use main SDCCH.
return SAPI3; // The SDCCH is faster than SACCH.
} else {
assert(channel()->isTCHF());
return channel()->getSACCHL3(); // Use SACCH associated with TCH.
return SAPI3Sacch; // Use SACCH associated with TCH.
}
}
void SMSCommon::l3sendSms(const L3Message &msg, SAPI_t sapi)
void SMSCommon::l3sendSms(const L3Message &msg)
{
getSmsChannel()->l3sendm(msg,GSM::DATA,sapi);
channel()->l3sendm(msg,GSM::L3_DATA,getSmsSap());
}
@@ -173,9 +186,12 @@ MachineStatus MOSMSMachine::machineRunState(int state, const GSM::L3Message *l3m
}
case stateIdentResult: {
if (! mIdentifyResult) {
//const L3CMServiceReject reject = L3CMServiceReject(L3RejectCause::InvalidMandatoryInformation);
l3sendSms(L3CMServiceReject(L3RejectCause::InvalidMandatoryInformation),SAPI0);
return MachineStatusQuitTran;
//const L3CMServiceReject reject = L3CMServiceReject(L3RejectCause::Invalid_Mandatory_Information);
// (pat 6-2014) I think this is wrong, based on comment below, so changing it to the main channel:
// l3sendSms(L3CMServiceReject(L3RejectCause::Invalid_Mandatory_Information),SAPI0);
MMRejectCause rejectCause = L3RejectCause::Invalid_Mandatory_Information;
channel()->l3sendm(L3CMServiceReject(rejectCause),L3_DATA,SAPI0);
return MachineStatus::QuitTran(TermCause::Local(rejectCause));
}
// Let the phone know we're going ahead with the transaction.
@@ -184,7 +200,7 @@ MachineStatus MOSMSMachine::machineRunState(int state, const GSM::L3Message *l3m
// Update 8-6-2013: The nokia does not accept this message on SACCH SAPI 0 for in-call SMS;
// so I am trying moving it to the main channel.
//l3sendSms(GSM::L3CMServiceAccept(),SAPI0);
channel()->l3sendm(GSM::L3CMServiceAccept(),GSM::DATA,SAPI0);
channel()->l3sendm(GSM::L3CMServiceAccept(),L3_DATA,SAPI0);
gReports.incr("OpenBTS.GSM.SMS.MOSMS.Start");
return MachineStatusOK;
@@ -195,7 +211,7 @@ MachineStatus MOSMSMachine::machineRunState(int state, const GSM::L3Message *l3m
// (pat) TODO: Call this on parsel3 error...
// TODO: Also send an error code to the sip side, if any.
l3sendSms(CPError(getL3TI()),SAPI3);
l3sendSms(CPError(getL3TI()));
return MachineStatusQuitTran;
}
#endif
@@ -214,14 +230,14 @@ MachineStatus MOSMSMachine::machineRunState(int state, const GSM::L3Message *l3m
const CPData *cpdata = dynamic_cast<typeof(cpdata)>(l3msg);
if (cpdata == NULL) { // Currently this is impossible, but maybe someone will change the code later.
l3sendSms(CPError(L3TI),SAPI3);
return MachineStatusQuitTran;
l3sendSms(CPError(L3TI));
return MachineStatus::QuitTran(TermCause::Local(L3Cause::SMS_Error));
}
// Step 2: Respond with CP-ACK.
// This just means that we got the message and could parse it.
PROCLOG(DEBUG) << "sending CPAck";
l3sendSms(CPAck(L3TI),SAPI3);
l3sendSms(CPAck(L3TI));
// (pat) The SMS message has already been through L3Message:parseL3, which called SMS::parseSMS(source), which manufactured
// a CPMessage::CPData and called L3Message::parse() which called CPData::parseBody which called L3Message::parseLV,
@@ -247,7 +263,7 @@ MachineStatus MOSMSMachine::machineRunState(int state, const GSM::L3Message *l3m
catch (SMSReadError) {
LOG(WARNING) << "SMS parsing failed (above L3)";
// Cause 95, "semantically incorrect message".
LCH->l3sendf(CPData(L3TI,RPError(95,ref)),3);
LCH->l3sendf(CPData(L3TI,RPError(95,ref)),3); if you ever use this, it should call l3sendSms
delete CM;
throw UnexpectedMessage();
}
@@ -275,7 +291,7 @@ MachineStatus MOSMSMachine::machineRunState(int state, const GSM::L3Message *l3m
if (! success) {
PROCLOG(INFO) << "sending RPError in CPData";
// Cause 95 is "semantically incorrect message"
l3sendSms(CPData(L3TI,RPError(95,mRpduRef)),SAPI3);
l3sendSms(CPData(L3TI,RPError(95,mRpduRef)));
}
mSmsState = MoSmsWaitForAck;
LOG(DEBUG) << "case DATA returning";
@@ -283,8 +299,8 @@ MachineStatus MOSMSMachine::machineRunState(int state, const GSM::L3Message *l3m
}
case L3CASE_SIP(dialogBye): { // SIPDialog sends this when the MESSAGE clears.
PROCLOG(INFO) << "sending RPAck in CPData";
l3sendSms(CPData(getL3TI(),RPAck(mRpduRef)),SAPI3);
PROCLOG(INFO) << "SMS peer did not respond properly to dialog message; sending RPAck in CPData";
l3sendSms(CPData(getL3TI(),RPAck(mRpduRef)));
LOG(DEBUG) << "case dialogBye returning";
}
case L3CASE_SIP(dialogFail): {
@@ -292,7 +308,7 @@ MachineStatus MOSMSMachine::machineRunState(int state, const GSM::L3Message *l3m
// TODO: Map the dialog failure state to an RPError state.
// Cause 127 is "internetworking error, unspecified".
// See GSM 04.11 8.2.5.4 Table 8.4.
l3sendSms(CPData(getL3TI(),RPError(127,mRpduRef)),SAPI3);
l3sendSms(CPData(getL3TI(),RPError(127,mRpduRef)));
LOG(DEBUG) << "case dialogFail returning";
}
@@ -318,7 +334,7 @@ MachineStatus MOSMSMachine::machineRunState(int state, const GSM::L3Message *l3m
LOG(DEBUG) << "case ACK returning";
// This attach causes any pending MT transactions to start now.
gMMLayer.mmAttachByImsi(channel(),tran()->subscriberIMSI());
return MachineStatusQuitTran;
return MachineStatus::QuitTran(TermCause::Local(L3Cause::SMS_Success));
}
default:
@@ -385,7 +401,7 @@ void startMOSMS(const GSM::L3MMMessage *l3msg, MMContext *mmchan)
LOG(ERR) <<"Received third simultaneous MO-SMS, which is illegal:"<<LOGVAR2("MO-SMS1",prevMOSMS.self())<<LOGVAR2("MO-SMS2",prevMOSMS2.self());
// Now what? We could kill the oldest one or reject the new one.
// Kill the oldest one, on the assumption that this indicates a bug in our code and that SMS is hung.
prevMOSMS->teCancel(); // Promotes TE_MOSMS2 to TE_MOSMS1
prevMOSMS->teCancel(TermCause::Local(L3Cause::SMS_Error)); // Promotes TE_MOSMS2 to TE_MOSMS1
devassert(mmchan->mmGetTran(MMContext::TE_MOSMS2) == NULL);
}
//mmchan->setTran(MMContext::TE_MOSMS2,tran);
@@ -402,20 +418,7 @@ void startMOSMS(const GSM::L3MMMessage *l3msg, MMContext *mmchan)
// Return true on success.
bool MTSMSMachine::createRPData(RPData &rp_data)
{
#if 0
// HACK -- Check for "Easter Eggs"
// TL-PID
unsigned TLPID=0;
if (strncmp(message,"#!TLPID",7)==0) sscanf(message,"#!TLPID%d",&TLPID);
unsigned reference = random() % 255;
//CPData deliver(L3TI,
rp_data = RPData(reference,
RPAddress(gConfig.getStr("SMS.FakeSrcSMSC").c_str()),
TLDeliver(callingPartyDigits,message,TLPID));
#else
// TODO: Read MIME Type from smqueue!!
const char *contentType = tran()->mContentType.c_str();
PROCLOG(DEBUG)<<LOGVAR(contentType)<<LOGVAR(tran()->mMessage);
if (strncmp(contentType,"text/plain",10)==0) {
@@ -428,12 +431,12 @@ bool MTSMSMachine::createRPData(RPData &rp_data)
} else if (strncmp(contentType,"application/vnd.3gpp.sms",24)==0) {
BitVector2 RPDUbits(strlen(tran()->mMessage.c_str())*4);
if (!RPDUbits.unhex(tran()->mMessage.c_str())) {
LOG(WARNING) << "Hex string parsing failed (in incoming SIP MESSAGE)";
//throw UnexpectedMessage();
return false;
LOG(WARNING) << "Message is zero length which is valid";
// This is valid continue
return true;
}
try {
try { // I suspect this is here to catch the above FIXED crash when string is zero length
RLFrame RPDU(RPDUbits);
LOG(DEBUG) << "SMS RPDU: " << RPDU;
@@ -443,14 +446,12 @@ bool MTSMSMachine::createRPData(RPData &rp_data)
catch (SMSReadError) {
LOG(WARNING) << "SMS parsing failed (above L3)";
// Cause 95, "semantically incorrect message".
//LCH->l2sendf(CPData(L3TI,RPError(95,this->mRpduRef)),3);
//throw UnexpectedMessage();
//LCH->l2sendf(CPData(L3TI,RPError(95,this->mRpduRef)),3); if you ever use this, it should call l3sendSms
return false;
}
catch (GSM::L3ReadError) {
LOG(WARNING) << "SMS parsing failed (in L3)";
// TODO:: send error back to the phone
//throw UnsupportedMessage();
return false;
}
catch (...) {
@@ -459,11 +460,9 @@ bool MTSMSMachine::createRPData(RPData &rp_data)
}
} else {
LOG(WARNING) << "Unsupported content type (in incoming SIP MESSAGE) -- type: " << contentType;
//throw UnexpectedMessage();
return false;
}
return true;
#endif
}
MachineStatus MTSMSMachine::machineRunState1(int state,const L3Frame*frame,const L3Message*l3msg, const SIP::DialogMessage*sipmsg)
@@ -483,7 +482,7 @@ MachineStatus MTSMSMachine::machineRunState1(int state,const L3Frame*frame,const
// SIP side closed already.
// We can no longer inform the SIP side whether we succeed or not.
// Should we continue and deliver the message to the MS or not?
return MachineStatusQuitTran;
return MachineStatus::QuitTran(TermCause::Local(L3Cause::SMS_Timeout)); // could be a sip internal error?
}
timerStart(TR2M,TR2Mms,TimerAbortTran);
@@ -496,12 +495,16 @@ MachineStatus MTSMSMachine::machineRunState1(int state,const L3Frame*frame,const
gReports.incr("OpenBTS.GSM.SMS.MTSMS.Start");
L3LogicalChannel *smschan = getSmsChannel();
if (smschan->multiframeMode(3)) { goto step1; } // If already established.
// pat 6-2014. We just send the ESTABLISH_REQUEST no matter what now.
// The LAPDm will respond with ESTABLISH_INDICATION immediately if
SAPI_t sap = getSmsSap();
//L3LogicalChannel *smschan = getSmsChannel();
//if (smschan->multiframeMode(3)) { goto step1; } // If already established.
// if (channel()->multiframeMode(sap)) { goto step1; } // If already established.
// Start ABM in SAP3.
smschan->l3sendp(GSM::ESTABLISH,SAPI3);
//smschan->l3sendp(GSM::L3_ESTABLISH_REQUEST,SAPI3);
channel()->l3sendp(GSM::L3_ESTABLISH_REQUEST,sap);
// Wait for SAP3 ABM to connect.
// The next read on SAP3 should be the ESTABLISH primitive.
// This won't return NULL. It will throw an exception if it fails.
@@ -512,8 +515,12 @@ MachineStatus MTSMSMachine::machineRunState1(int state,const L3Frame*frame,const
return MachineStatusOK; // Wait for the ESTABLISH on the uplink.
}
case L3CASE_PRIMITIVE(ESTABLISH): {
step1:
// We use ESTABLISH_INDICATION instead of ESTABLISH_CONFIRM to indicate establishment.
// We would have to accept both ESTABLISH_CONFIRM and ESTABLISH_INDICATION here anyway in case
// SABM was started by us and handset simultaneously, so we just dont bother with making ESTABLISH_CONFIRM separate.
case L3CASE_PRIMITIVE(L3_ESTABLISH_INDICATION):
case L3CASE_PRIMITIVE(L3_ESTABLISH_CONFIRM): {
//step1:
// Step 1
// Send the first message.
@@ -521,19 +528,19 @@ MachineStatus MTSMSMachine::machineRunState1(int state,const L3Frame*frame,const
RPData rp_data;
int l3ti = getL3TI();
if (! createRPData(rp_data)) {
l3sendSms(CPData(l3ti,RPError(95,this->mRpduRef)),SAPI3);
if (! createRPData(rp_data)) { // NULL can be returned
l3sendSms(CPData(l3ti,RPError(95,this->mRpduRef)));
// TODO: Is this correct?
// TODO: Make sure MachineStatusQuitTran sends a failure code to SIP.
if (getDialog()) getDialog()->MTSMSReply(400, "Bad Request");
return MachineStatusQuitTran;
return MachineStatus::QuitTran(TermCause::Local(L3Cause::SMS_Error));
}
CPData deliver(l3ti,rp_data);
PROCLOG(INFO) << "sending " << deliver;
// WORKING: MS Does not respond to this message.
// Probably the messages are not hooked properly
l3sendSms(deliver,SAPI3);
// (pat) FIXME: The MS may send a DELIVER_REPORT which is discarded by parseTPDU.
l3sendSms(deliver);
LOG(DEBUG) << "case ESTABLISH returning, after receiving ESTABLISH";
return MachineStatusOK; // Wait for CP-ACK message.
}
@@ -586,7 +593,7 @@ MachineStatus MTSMSMachine::machineRunState1(int state,const L3Frame*frame,const
// Step 4
// Send CP-ACK to the MS.
PROCLOG(INFO) << "MTSMS: sending CPAck";
l3sendSms(CPAck(getL3TI()),SAPI3);
l3sendSms(CPAck(getL3TI()));
// Ack in SIP domain.
if (!getDialog()) {
@@ -598,7 +605,7 @@ MachineStatus MTSMSMachine::machineRunState1(int state,const L3Frame*frame,const
}
LOG(DEBUG) << "case DATA returning";
return MachineStatusQuitTran; // Finished.
return MachineStatus::QuitTran(TermCause::Local(L3Cause::SMS_Success)); // Finished.
}
default:
return unexpectedState(state,l3msg);

View File

@@ -1,8 +1,9 @@
/* Copyright 2013 Range Networks, Inc.
/*
* Copyright 2013, 2014 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.
* information for this specific distribution.
*
* This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details.

View File

@@ -4,7 +4,7 @@
*
* This software is distributed under multiple licenses;
* see the COPYING file in the main directory for licensing
* information for this specific distribuion.
* information for this specific distribution.
*
* This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details.
@@ -14,12 +14,14 @@
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*/
// Written by Pat Thompson
#define LOG_GROUP LogGroup::Control // Can set Log.Level.Control for debugging
#include "L3StateMachine.h"
#include "L3CallControl.h"
#include "L3TranEntry.h"
#include "L3MobilityManagement.h"
#include "L3MMLayer.h"
#include "L3Handover.h"
#include <GSMLogicalChannel.h>
#include <GSML3Message.h>
#include <GSML3CCMessages.h>
@@ -27,7 +29,7 @@
#include <GSML3MMMessages.h>
#include <SMSMessages.h>
#include <GSMConfig.h>
#include <RRLPServer.h>
#include <Globals.h>
#include <typeinfo>
using namespace GSM;
@@ -36,8 +38,8 @@ namespace Control {
// See documentation as class MachineStatus.
MachineStatus MachineStatusOK = MachineStatus(MachineStatus::MachineCodeOK);
MachineStatus MachineStatusPopMachine = MachineStatus(MachineStatus::MachineCodePopMachine);
MachineStatus MachineStatusQuitTran = MachineStatus(MachineStatus::MachineCodeQuitTran);
MachineStatus MachineStatusQuitChannel = MachineStatus(MachineStatus::MachineCodeQuitChannel);
//MachineStatus MachineStatusQuitTran = MachineStatus(MachineStatus::MachineCodeQuitTran);
//MachineStatus MachineStatusQuitChannel = MachineStatus(MachineStatus::MachineCodeQuitChannel);
MachineStatus MachineStatusAuthorizationFail = MachineStatus(MachineStatus::MachineCodeQuitChannel);
MachineStatus MachineStatusUnexpectedState = MachineStatus(MachineStatus::MachineCodeUnexpectedState);
@@ -138,9 +140,9 @@ MachineStatus MachineBase::machPush(
return this->callMachStart(wCalledProcedure);
}
MachineStatus MachineBase::closeChannel(L3RRCause cause,Primitive prim)
MachineStatus MachineBase::closeChannel(RRCause rrcause,Primitive prim,TermCause upstreamCause)
{
LOG(DEBUG);
LOG(INFO) << "SIP term info closeChannel L3RRCause: " << rrcause; // SVGDBG
// We dont want to set to NullState because we want to differentiate the startup state from the closed state
// so that if something new happens (like a SIP dialog message coming in) we wont advance, we'll stay dead.
// TODO: Make sure the routines that handle incoming dialog messages check for channel already in a released state.
@@ -148,8 +150,8 @@ MachineStatus MachineBase::closeChannel(L3RRCause cause,Primitive prim)
setGSMState(CCState::ReleaseRequest); // The chanClose below will send the request.
// Many handsets never complete the transaction.
// So force a shutdown of the channel.
channel()->chanClose(cause,prim);
return MachineStatusQuitChannel;
channel()->chanClose(rrcause,prim,upstreamCause); // TODO: Remove, now redundant.
return MachineStatus::QuitChannel(upstreamCause);
}
#if UNUSED
@@ -191,7 +193,11 @@ L3LogicalChannel* MachineBase::channel() const {
CallState MachineBase::getGSMState() const { return tran()->getGSMState(); }
void MachineBase::setGSMState(CallState state) { tran()->setGSMState(state); }
void MachineBase::setGSMState(CallState state) {
LOG(INFO) << "SIP term info setGSMState state: " << state; // SVGDBG
tran()->setGSMState(state);
}
SIP::SipDialog * MachineBase::getDialog() const { return tran()->getDialog(); }
void MachineBase::setDialog(SIP::SipDialog*dialog) { return tran()->setDialog(dialog); }
@@ -255,8 +261,6 @@ static bool handleCommonMessages(const L3Message *l3msg, MMContext *mmchan, bool
NewPagingResponseHandler(dynamic_cast<const L3PagingResponse*>(l3msg),mmchan);
return true;
case L3CASE_RR(L3RRMessage::ApplicationInformation):
recvRRLP(mmchan, l3msg);
return true;
case L3CASE_RR(L3RRMessage::RRStatus):
handleStatus(l3msg,mmchan);
return true;
@@ -266,10 +270,13 @@ static bool handleCommonMessages(const L3Message *l3msg, MMContext *mmchan, bool
// TODO: Should we check that this an appropriate time to start it?
LURInit(l3msg,mmchan);
return true;
case L3CASE_MM(L3MMMessage::IMSIDetachIndication):
case L3CASE_MM(L3MMMessage::IMSIDetachIndication): {
// (pat) TODO, but it is not very important.
//IMSIDetachController(dynamic_cast<const L3IMSIDetachIndication*>(l3msg),mmchan);
L3MobileIdentity mobid = dynamic_cast<const L3IMSIDetachIndication*>(l3msg)->mobileID();
imsiDetach(mobid,mmchan->tsChannel());
mmchan->tsChannel()->chanRelease(L3_RELEASE_REQUEST,TermCause::Local(L3Cause::IMSI_Detached));
return true;
}
case L3CASE_MM(L3MMMessage::CMServiceRequest):
mmchan->mmcServiceRequests.write(l3msg);
//NewCMServiceResponder(dynamic_cast<const L3CMServiceRequest*>(l3msg),dcch);
@@ -278,11 +285,10 @@ static bool handleCommonMessages(const L3Message *l3msg, MMContext *mmchan, bool
default:
break;
}
*deletemsg = false;
return false;
}
MachineStatus MachineBase::machineRunState(int state, const GSM::L3Message *l3msg, const SIP::DialogMessage *sipmsg)
MachineStatus MachineBase::machineRunState(int /*state*/, const GSM::L3Message * /*l3msg*/, const SIP::DialogMessage * /*sipmsg*/)
{
// The state machine must implement one of: machineRunState, machineRunL3Msg or machineRunFrame.
assert(0);
@@ -306,7 +312,8 @@ MachineStatus MachineBase::machineRunL3Msg(int state, const GSM::L3Message *l3ms
MachineStatus handlePrimitive(const L3Frame *frame, L3LogicalChannel *lch)
{
switch (frame->primitive()) {
case GSM::ESTABLISH:
case L3_ESTABLISH_CONFIRM:
case L3_ESTABLISH_INDICATION:
// Indicates SABM mode establishment. The state machines that use machineRunState can ignore these.
// The transaction is started by an L3 message like CMServiceRequest.
return MachineStatusOK;
@@ -420,36 +427,33 @@ MachineStatus MachineBase::dispatchTimeout(L3Timer*timer)
// Answer: It depends on the procedure. The caller should have done any specific
// closing, for example CC message, before exiting the state machine.
LOG(INFO) << "Timer "<<timer->tName()<<" timeout";
// (pat) TODO: We should not use one error fits all here; the error should be set up when the timer was established.
TermCause cause = TermCause::Local(L3Cause::No_User_Responding); // SVG 5/20/14 changed this from InterworkingUnspecified to NoUserResponding
// If it is an SMS transaction, just drop it.
// If it is a CC transaction, lets send a message to try to kill it, which may block for 30 seconds.
switch (tran()->servicetype()) {
case L3CMServiceType::MobileOriginatedCall:
case L3CMServiceType::MobileTerminatedCall:
tran()->teCloseCallNow(L3Cause::InterworkingUnspecified);
return MachineStatusQuitTran;
// Changed 10-24-2013
// if (! tran()->clearingGSM()) {
// // We are bypassing the ReleaseRequest state, which is allowed by GSM 04.08 5.4.2.
// // setGSMState(CCState::ReleaseRequest);
// // (pat) The old forceGSMClearing did not put the l3cause in this message - why not?
// // Note: timer expiry may indicate unresponsive MS so this this may block for 30 seconds.
// return tran()->teCloseCall(L3Cause::InterworkingUnspecified,true);
// }
// break;
case L3CMServiceType::MobileTerminatedCall: {
LOG(INFO) << "SIP term info dispatchTimeout call teCloseCallNow servicetype: " << tran()->servicetype(); // SVGDBG
tran()->teCloseCallNow(cause,true);
}
default:
break;
}
// Code causes caller to kill the transaction, which will also cancel the dialog if any, which is
// partly redundant with the teCloseCall above..
return MachineStatusQuitTran;
return MachineStatus::QuitTran(cause);
} else if (nextState == TimerAbortChan) {
LOG(INFO) << "Timer "<<timer->tName()<<" timeout";
// This indirectly causes immediate destruction of all transactions on this channel.
return closeChannel(L3RRCause::TimerExpired,RELEASE);
LOG(INFO) << "SIP term info closeChannel called in dispatchTimeout";
// TODO: Error should be set up when timer started.
return closeChannel(L3RRCause::Timer_Expired,L3_RELEASE_REQUEST,TermCause::Local(L3Cause::No_User_Responding));
} else {
assert(0);
return MachineStatus::QuitTran(TermCause::Local(L3Cause::L3_Internal_Error));
}
}
@@ -495,7 +499,7 @@ static void csl3HandleLCHMsg(GSM::L3Message *l3msg, L3LogicalChannel *lch)
// I think previously nothing. But we are not going to try to aggregate messages from multiple channels in the same MS together.
// We assume that all the messages for a single Machine arrive on a single channel+SACCH pair.
if (handleCommonMessages(l3msg, lch)) {
LOG(DEBUG) << "message handled by handleCommonMessagse"<<LOGVAR(l3msg);
LOG(DEBUG) << "message handled by handleCommonMessages"<<LOGVAR(l3msg);
delete l3msg;
return;
}
@@ -518,14 +522,15 @@ static void csl3HandleLCHMsg(GSM::L3Message *l3msg, L3LogicalChannel *lch)
}
#endif
// Return true if it was an l3 message or or a primitive that we pass on to state machines, false otherwise.
// Return true if it was an l3 message or a primitive that we pass on to state machines, false otherwise.
// After calling us the caller should test chanRunning to see if the channel is still up.
static bool checkPrimitive(Primitive prim, L3LogicalChannel *lch, int sapi)
{
LOG(DEBUG)<<lch<<LOGVAR(prim);
// Process 'naked' primitives.
switch (prim) {
case GSM::ESTABLISH:
case L3_ESTABLISH_CONFIRM:
case L3_ESTABLISH_INDICATION:
// Indicates SABM mode establishment.
// Most state machines can ignore these, but the MT-SMS controller has to wait for channel
// establishment so we will pass it on.
@@ -533,29 +538,32 @@ static bool checkPrimitive(Primitive prim, L3LogicalChannel *lch, int sapi)
// Pat took out this gReports temporarily because it is delaying channel establishment.
// gReports.incr("OpenBTS.GSM.RR.ChannelSeized");
return true;
case GSM::HANDOVER_ACCESS:
case HANDOVER_ACCESS:
LOG(ALERT) << "Received HANDOVER_ACCESS on established channel";
// TODO: test that this is on TCHFACH not SDCCH.
// This does not return until the channel is ready to start running a state machine.
//ProcessHandoverAccess(lch);
return false;
case DATA:
case UNIT_DATA:
case L3_DATA:
case L3_UNIT_DATA:
return true;
case ERROR: ///< channel error
case MDL_ERROR_INDICATION: ///< channel error
// The LAPDM controller was aborted.
//gNewTransactionTable.ttLostChannel(lch);
//lch->chanLost(); // Kill off all the transactions associated with this channel.
LOG(ERR) << "Layer3 received ERROR from layer2 on channel "<<lch<<LOGVAR(sapi);
lch->chanRelease(RELEASE); // Kill off all the transactions associated with this channel.
// FIXME: This prim needs to be passed to the state machines to abort procedures.
lch->chanRelease(L3_RELEASE_REQUEST,TermCause::Local(L3Cause::Layer2_Error)); // Kill off all the transactions associated with this channel.
return false;
case HARDRELEASE: ///< forced release after an assignment
if (sapi == 0) lch->chanRelease(HARDRELEASE); // Release the channel.
return false;
case RELEASE: ///< normal channel release
//case HARDRELEASE: ///< forced release after an assignment
// if (sapi == 0) lch->chanRelease(L3_HARDRELEASE_REQUEST); // Release the channel.
// return false;
case L3_RELEASE_INDICATION: ///< normal channel release
//if (lch->mChState == L3LogicalChannel::chReassignPending || lch->mChState == L3LogicalChannel::chReassignComplete) {
// // This is what we wanted.
// // We will exit and the service loop will change the L3LogicahChannel mChState.
@@ -566,17 +574,21 @@ static bool checkPrimitive(Primitive prim, L3LogicalChannel *lch, int sapi)
// // Just drop the channel.
// lch->chanLost(); // Kill off all the transactions associated with this channel.
//}
if (sapi == 0) lch->chanRelease(RELEASE);
// FIXME: This prim needs to be passed to the state machines to abort procedures.
if (sapi == 0) lch->chanRelease(L3_RELEASE_REQUEST,TermCause::Local(L3Cause::Normal_Call_Clearing));
return false;
default:
LOG(ERR) <<lch<<"unhandled primitive: " << prim; // oops! But lets warn instead of crashing.
// Something horrible happened.
lch->chanRelease(RELEASE); // Kill off all the transactions associated with this channel.
lch->chanRelease(L3_RELEASE_REQUEST,TermCause::Local(L3Cause::L3_Internal_Error)); // Kill off all the transactions associated with this channel.
//lch->freeContext();
return false;
}
}
// Dont delete the frame; caller does that.
static void csl3HandleFrame(const GSM::L3Frame *frame, L3LogicalChannel *lch)
{
L3Message *l3msg = NULL;
@@ -596,32 +608,13 @@ static void csl3HandleFrame(const GSM::L3Frame *frame, L3LogicalChannel *lch)
}
bool deleteit;
if (l3msg && handleCommonMessages(l3msg, mmchan, &deleteit)) {
LOG(DEBUG) << "message handled by handleCommonMessagse"<<LOGVAR(l3msg);
LOG(DEBUG) << "message handled by handleCommonMessages"<<LOGVAR(l3msg);
if (deleteit) { delete l3msg; }
return;
}
}
mmchan->mmDispatchL3Frame(frame,l3msg);
if (l3msg) delete l3msg;
#if UNUSED
L3Message *msg = NULL;
try {
// Even through parseL3 catches L3ReadError, looks to me like this can still throw SMS_READ_ERROR, so catch it:
msg = parseL3(*frame);
} catch (...) {
msg = NULL;
}
if (msg) {
LOG(DEBUG) <<lch <<" received L3 message "<<*msg;
csl3HandleLCHMsg(msg,lch);
} else {
L3PD PD = frame->PD();
int MTI = frame->MTI();
LOG(ERR) << "unparseable Layer3 message with"<<LOGVAR(PD)<<LOGVAR(MTI);
// Give the state machine a chance to do something about an error:
lch->chanGetContext(true)->mmDispatchError(PD,MTI,lch);
}
#endif
}
#if UNUSED
@@ -724,7 +717,9 @@ static unsigned newUpdateCallTraffic(TranEntry *transaction, GSM::TCHFACCHLogica
}
if (numFlushed) { LOG(DEBUG) <<TCH <<" ulFrame flushed "<<numFlushed <<" in "<<testTimeStart.elapsed() << " msecs"; }
}
if (GSM::AudioFrame *ulFrame = TCH->recvTCH()) {
if (SIP::AudioFrame *ulFrame = TCH->recvTCH()) {
activity += ulFrame->sizeBytes();
// Send on RTP.
LOG(DEBUG) <<TCH <<LOGVAR(*ulFrame);
@@ -736,7 +731,7 @@ static unsigned newUpdateCallTraffic(TranEntry *transaction, GSM::TCHFACCHLogica
// Blocking call. On average returns 1 time per 20 ms.
// Returns non-zero if anything really happened.
// Make the rxFrame buffer big enough for G.711.
if (GSM::AudioFrame *dlFrame = transaction->rxFrame()) {
if (SIP::AudioFrame *dlFrame = transaction->rxFrame()) {
activity += dlFrame->sizeBytes();
if (activity == 0) { activity++; } // Make sure we signal activity.
LOG(DEBUG) <<TCH <<LOGVAR(*dlFrame);
@@ -762,33 +757,42 @@ static bool checkemMessages(L3LogicalChannel *dcch, int delay)
MMContext *set = dcch->chanGetContext(true);
if (set->mmcTerminationRequested) {
set->mmcTerminationRequested = false; // Reset the flag superstitiously.
dcch->chanClose(L3RRCause::Preemptive_Release,L3_RELEASE_REQUEST,TermCause::Local(L3Cause::Operator_Intervention));
return true;
}
// All messages from all host chan saps and from sacch now come in l2recv now.
if (GSM::L3Frame *l3frame = dcch->l2recv(delay)) {
LOG(DEBUG) <<dcch<< *l3frame;
LOG(DEBUG) <<dcch<<" "<< *l3frame;
csl3HandleFrame(l3frame, dcch);
delete l3frame;
return true; // Go see if it terminated the TranEntry while we were potentially blocked.
}
// How about SAPI 3?
if (GSM::L3Frame *l3frame = dcch->l2recv(0,3)) {
LOG(DEBUG) <<dcch<< *l3frame;
csl3HandleFrame(l3frame, dcch);
delete l3frame;
return true; // Go see if it terminated the TranEntry while we were potentially blocked.
}
#if 0
// // How about SAPI 3?
// if (GSM::L3Frame *l3frame = dcch->l2recv(0,3)) {
// LOG(DEBUG) <<dcch<< *l3frame;
// csl3HandleFrame(l3frame, dcch);
// delete l3frame;
// return true; // Go see if it terminated the TranEntry while we were potentially blocked.
// }
// How about SACCH? These messages are supposed to be prioritized, but we're not bothering.
// We need to pass the ESTABLISH primitive to higher layer, specifically, MTSMSMachine.
if (L3Frame *aframe = dcch->ml3UplinkQ.readNoBlock()) {
//if (IS_LOG_LEVEL(DEBUG)) {
//std::ostringstream os; os << *aframe;
//WATCHF("Frame on SACCH %s: %s\n",dcch->descriptiveString(),os.str().c_str());
//}
WATCH("Recv frame on SACCH "<<dcch->descriptiveString()<<" "<<*aframe);
csl3HandleFrame(aframe, dcch);
delete aframe;
return true; // Go see if it terminated the TranEntry while we were potentially blocked.
}
// // How about SACCH? These messages are supposed to be prioritized, but we're not bothering.
// // We need to pass the ESTABLISH primitive to higher layer, specifically, MTSMSMachine.
// if (L3Frame *aframe = dcch->ml3UplinkQ.readNoBlock()) {
// //if (IS_LOG_LEVEL(DEBUG)) {
// //std::ostringstream os; os << *aframe;
// //WATCHF("Frame on SACCH %s: %s\n",dcch->descriptiveString(),os.str().c_str());
// //}
// WATCH("Recv frame on SACCH "<<dcch->descriptiveString()<<" "<<*aframe);
// csl3HandleFrame(aframe, dcch);
// delete aframe;
// return true; // Go see if it terminated the TranEntry while we were potentially blocked.
// }
#endif
// Any Dialog messages from the SIP side?
// Sadly we cannot process the sip messages in a separate global L3 thread because the TranEntry/procedure may be
@@ -833,13 +837,13 @@ static void l3CallTrafficLoop(L3LogicalChannel *dcch)
// Original code used throw...catch but during channel reassignment the channel state is terminated by changing
// the state by a different thread, so we just the same method for all cases and terminate by changing the channel state.
unsigned alternate = 0;
while (dcch->chanRunning()) {
while (dcch->chanRunning() && !gBTS.btsShutdown()) {
if (tch->radioFailure()) {
LOG(NOTICE) << "radio link failure, dropped call"<<LOGVAR(dcch);
//gNewTransactionTable.ttLostChannel(dcch);
// The radioFailure already waited for the timeout, so now we can immediately drop the channel.
dcch->chanRelease(HARDRELEASE); // Kill off all the transactions associated with this channel.
//tran->getDialog()->dialogCancel(); //was forceSIPClearing
dcch->chanRelease(L3_HARDRELEASE_REQUEST,TermCause::Local(L3Cause::Radio_Interface_Failure)); // Kill off all the transactions associated with this channel.
//tran->getDialog()->dialogCancel(TermCause::TermCodeUnknown, GSM::L3Cause::Unknown_L3_Cause); // was forceSIPClearing
//tran->teRemove();
return;
}
@@ -856,21 +860,23 @@ static void l3CallTrafficLoop(L3LogicalChannel *dcch)
// we should keep the channel open until that ends.
LOG(NOTICE) << "attempting to use a defunct Transaction"<<LOGVAR(dcch)<<LOGVAR(*tran);
// TODO: We should not be closing the channel here; we whould wait
dcch->chanClose(L3RRCause::PreemptiveRelease,RELEASE);
dcch->chanClose(L3RRCause::Preemptive_Release,L3_RELEASE_REQUEST,TermCause::Local(L3Cause::No_Transaction_Expected));
return;
}
// TODO: This needs to check all the transactions in the MMContext.
// The termination request comes from the CLI or from RadioResource to make room for an SOS call.
if (tran->terminationRequested()) {
L3Cause::AnyCause termcause = tran->terminationRequested();
if (termcause.value != 0) {
LOG(DEBUG)<<dcch<<" terminationRequested";
tran->terminateHook(); // Gives the L3Procedure state machine a chance to do something first.
dcch->chanClose(L3RRCause::PreemptiveRelease,RELEASE);
// GSM 4.08 3.4.13.4.1: Use RR Cause PreemptiveRelease if terminated for a higher priority, ie, emergency, call
dcch->chanClose(L3RRCause::Preemptive_Release,L3_RELEASE_REQUEST,TermCause::Local(termcause));
return; // We wont be back.
}
if (tran->getGSMState() == CCState::HandoverOutbound) {
if (outboundHandoverTransfer(tran,dcch)) {
LOG(DEBUG)<<dcch<<" outboundHandover";
dcch->chanRelease(HARDRELEASE);
dcch->chanRelease(L3_HARDRELEASE_REQUEST,TermCause::Local(L3Cause::Handover_Outbound));
return; // We wont be back.
}
}
@@ -879,10 +885,6 @@ static void l3CallTrafficLoop(L3LogicalChannel *dcch)
// TODO: Remove this, redundant with the below.
if (tran->checkTimers()) { // This calls a handler function and resets the timer.
LOG(DEBUG) <<dcch <<" after checkTimers";
//if (transaction->anyTimerExpired())
// Cause 0x66, "recover on timer expiry"
//abortCall(transaction,dcch,GSM::L3Cause(0x66));
//return true;
continue; // The transaction is probably defunct.
}
}
@@ -936,6 +938,7 @@ static void l3CallTrafficLoop(L3LogicalChannel *dcch)
// If nothing happened, set nextDelay so so we dont burn up the CPU cycles.
nextDelay = 20; // Do not exceed the RTP frame size of 20ms.
}
LOG(DEBUG) << "final return";
}
// TODO: When a channel is first opened we should save the CMServiceRequest or LocationUpdateRequest or PagingResponse and initiate
@@ -950,11 +953,13 @@ static void l3CallTrafficLoop(L3LogicalChannel *dcch)
static void L3SDCCHLoop(L3LogicalChannel*dcch)
{
assert(dcch->chtype() == SDCCHType);
while (dcch->chanRunning()) {
while (dcch->chanRunning() && !gBTS.btsShutdown()) {
if (dcch->radioFailure()) { // Checks expiry of T3109, set at 30s.
LOG(NOTICE) << "radio link failure, dropped call";
//gNewTransactionTable.ttLostChannel(dcch);
dcch->chanRelease(HARDRELEASE); // Kill off all the transactions associated with this channel.
// (pat) 5-2014: Changed to RELEASE from HARDRELEASE - even though we can no longer hear the handset,
// it might still hear us so we have to deactivate SACCH and wait T3109.
dcch->chanRelease(L3_RELEASE_REQUEST,TermCause::Local(L3Cause::Radio_Interface_Failure)); // Kill off all the transactions associated with this channel.
return;
}
@@ -980,7 +985,7 @@ void L3DCCHLoop(L3LogicalChannel*dcch, L3Frame *frame)
dcch->chanSetState(L3LogicalChannel::chEstablished);
switch (prim) {
case ESTABLISH:
case L3_ESTABLISH_INDICATION:
break;
case HANDOVER_ACCESS:
ProcessHandoverAccess(dcch);
@@ -1017,10 +1022,10 @@ void L3DCCHLoop(L3LogicalChannel*dcch, L3Frame *frame)
// The RELEASE primitive will block up to 30 seconds, so we NEVER EVER send it from anywhere but right here.
// To release the channel, set the channel state to chReleaseRequest and let it come here to release the channel.
// FIXME: Actually, LAPDm blocks in this forever until it gets the next ESTABLISH, so this is where the serviceloop really waits.
dcch->l3sendp(RELEASE); // WARNING! This must be the only place in L3 that sends this primitive.
dcch->l3sendp(L3_RELEASE_REQUEST); // WARNING! This must be the only place in L3 that sends this primitive.
break;
case L3LogicalChannel::chRequestHardRelease:
dcch->l3sendp(HARDRELEASE);
dcch->l3sendp(L3_HARDRELEASE_REQUEST);
break;
default: break;
}

View File

@@ -1,10 +1,10 @@
/**@file Declarations for Circuit Switched State Machine and related classes. */
/*
* Copyright 2013 Range Networks, Inc.
* Copyright 2013, 2014 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.
* information for this specific distribution.
*
* This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details.
@@ -36,6 +36,7 @@
#include <GSMTransfer.h>
#include "ControlCommon.h"
#include "L3Utils.h"
#include "L3TermCause.h"
//#include <GSML3CommonElements.h>
//#include <GSML3MMElements.h>
//#include <GSML3CCElements.h>
@@ -68,29 +69,34 @@ struct MachineStatus {
enum MachineStatusCode {
MachineCodeOK, // continue the procedure, meaning return to L3 message handler and wait for the next message.
MachineCodePopMachine, // return to previous procedure on stack
MachineCodeQuitTran, // Pop all machines from stack and remove the transaction. This is the normal exit from a completed procedure.
// NOTE: MachineCodeQuitChannel does not close the channel - The user must call closeChannel or equivalent first.
MachineCodeQuitChannel, // Drop the channel, which kills all transactions on this channel.
// All these others are error conditions. They all terminate like MachineCodeQuit, but they are different
// so we can print a more informative diagnostic message.
//MachineCodeUnexpectedMessage, // The MS sent a message that was not anticipated by the state machine running the Transaction.
//MachineCodeUnexpectedPrimitive, /** Thrown when the control layer gets the wrong primitive */ // Not sure this happens.
MachineCodeQuitTran, // Pop all machines from stack and remove the transaction. This is the normal exit from a completed procedure.
MachineCodeQuitChannel, // Drop the channel, which kills all transactions on this channel.
MachineCodeUnexpectedState, // Unexpected message or state was not handled by the current state machine.
//MachineCodeAuthorizationFail, // Self explanatory.
};
MachineStatusCode mCode;
//GSM::RRCause mRRCause;
//string mSomeMessage;
bool operator==(MachineStatus &other) { return mCode == other.mCode; }
bool operator!=(MachineStatus &other) { return mCode != other.mCode; }
MachineStatus(MachineStatusCode code) { mCode = code; /*mRRCause = GSM::L3RRCause::NormalEvent;*/ }
MachineStatusCode msCode;
TermCause msCause; // If it is QuitTran or QuitChannel.
bool operator==(MachineStatus &other) { return msCode == other.msCode; }
bool operator!=(MachineStatus &other) { return msCode != other.msCode; }
MachineStatus(MachineStatusCode code) { msCode = code; }
static MachineStatus QuitTran(TermCause wCause) {
MachineStatus result(MachineCodeQuitTran);
result.msCause = wCause;
return result;
}
static MachineStatus QuitChannel(TermCause wCause) {
MachineStatus result(MachineCodeQuitChannel);
result.msCause = wCause;
return result;
}
};
std::ostream& operator<<(std::ostream& os, MachineStatus::MachineStatusCode state);
// These ones have no arguments so they might as well be constants.
extern MachineStatus MachineStatusOK, MachineStatusPopMachine, MachineStatusQuitTran, MachineStatusQuitChannel, MachineStatusAuthorizationFail;
extern MachineStatus MachineStatusOK, MachineStatusPopMachine, MachineStatusAuthorizationFail;
extern MachineStatus MachineStatusAuthorizationFail, MachineStatusUnexpectedState;
//extern MachineStatus MachineStatusQuitChannel;
//extern MachineStatus MachineStatusQuitTran;
struct MachineStatusQuitTran : MachineStatus {
@@ -148,7 +154,7 @@ class MachineBase : public MemCheckMachineBase
bool isL3TIValid() const;
virtual const char *debugName() const = 0;
MachineStatus unexpectedState(int state, const L3Message*l3msg);
MachineStatus closeChannel(L3RRCause cause,Primitive prim);
MachineStatus closeChannel(RRCause rrcause,Primitive prim,TermCause cause);
void machineErrorMessage(int level, int state, const L3Message *l3msg, const SIP::DialogMessage *sipmsg, const char *format);
virtual void handleTerminationRequest() {} // Procedure can over-ride this to do nicer cleanup.

View File

@@ -1,8 +1,9 @@
/* Copyright 2013 Range Networks, Inc.
/*
* Copyright 2013, 2014 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.
* information for this specific distribution.
*
* This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details.
@@ -11,6 +12,9 @@
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*/
// Written by Pat Thompson
#define LOG_GROUP LogGroup::Control
#include <string>
@@ -23,6 +27,7 @@
#include <GSML3SSMessages.h>
#include <GSMLogicalChannel.h>
#include <SIPDialog.h>
#include <Globals.h>
// Generic SS messages are transferred by the Facility IE, whose content is described by 24.080 4.61
@@ -247,7 +252,7 @@ class SupServCodes
addSSCode(0x2b,"62"); // cfnrc CF Not Reachable
addSSCode(0x20,"002"); // all CallForwardingSS
addSSCode(0x28,"004"); // all conditional CF
addSSCode(0x41,"43"); // WAIT 22.083 Call wairing?
addSSCode(0x41,"43"); // WAIT 22.083 Call waiting?
// I think HOLD and MultiParty are handled by L3 messages at the MS level, and these MAP-SS-Codes are used only at the MSC level.
//addSSCode(0x42,??); // HOLD see section 4.5.5. Ha ha, there is no such section in either 22.030 or 22.083. It is in 2.30.
//addSSCode(0x51, ??); // MPTTY see 4.5.5 22.084
@@ -637,7 +642,7 @@ void startMOSSD(const L3CMServiceRequest*cmsrq,MMContext *mmchan)
string proxyUssd = gConfig.getStr("SIP.Proxy.USSD");
if (proxyUssd.size() == 0) {
// Disabled. Reject USSD immediately.
mmchan->l3sendm(L3CMServiceReject(L3RejectCause(L3RejectCause::ServiceOptionNotSupported)));
mmchan->l3sendm(L3CMServiceReject(L3RejectCause::Service_Option_Not_Supported));
return;
}
@@ -660,9 +665,9 @@ MachineStatus MOSSDMachine::machineRunState(int state, const GSM::L3Message *l3m
}
case stateSSIdentResult: {
if (! mIdentifyResult) {
//const L3CMServiceReject reject = L3CMServiceReject(L3RejectCause::InvalidMandatoryInformation);
channel()->l3sendm(L3CMServiceReject(L3RejectCause::InvalidMandatoryInformation));
return MachineStatusQuitTran;
//const L3CMServiceReject reject = L3CMServiceReject(L3RejectCause::Invalid_Mandatory_Information);
channel()->l3sendm(L3CMServiceReject(L3RejectCause::Invalid_Mandatory_Information));
return MachineStatus::QuitTran(TermCause::Local(L3RejectCause::Invalid_Mandatory_Information));
}
PROCLOG(DEBUG) << "sending CMServiceAccept";
@@ -693,18 +698,18 @@ MachineStatus MOSSDMachine::machineRunState(int state, const GSM::L3Message *l3m
case L3CASE_SS(L3SupServMessage::ReleaseComplete): {
const L3SupServFacilityMessage *relp = dynamic_cast<typeof(relp)>(l3msg);
WATCH("SS ReleaseComplete" << relp);
return MachineStatusQuitTran;
return MachineStatus::QuitTran(TermCause::Local(L3Cause::USSD_Success));
}
case L3CASE_SIP(dialogBye): {
if (sipmsg == NULL) {
LOG(ERR) << "USSD client error: missing BYE message";
return MachineStatusQuitTran;
return MachineStatus::QuitTran(TermCause::Local(L3Cause::USSD_Error));
}
const DialogUssdMessage *umsg = dynamic_cast<typeof(umsg)>(sipmsg);
if (umsg == NULL) {
LOG(ERR) << "USSD client error: could not convert DialogMessage to DialogUssdMessage "<<sipmsg;
return MachineStatusQuitTran;
return MachineStatus::QuitTran(TermCause::Local(L3Cause::USSD_Error));
}
string result = umsg->dmMsgPayload;
// Send it to the MS.
@@ -714,7 +719,7 @@ MachineStatus MOSSDMachine::machineRunState(int state, const GSM::L3Message *l3m
L3SupServReleaseCompleteMessage ssrelease(getL3TI());
channel()->l3sendm(ssrelease);
//sendUssdMsg(result, true);
return MachineStatusQuitTran;
return MachineStatus::QuitTran(TermCause::Local(L3Cause::USSD_Success));
}
default:

View File

@@ -1,8 +1,9 @@
/* Copyright 2013 Range Networks, Inc.
/*
* Copyright 2013, 2014 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.
* information for this specific distribution.
*
* This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details.
@@ -11,6 +12,7 @@
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*/
#ifndef _L3SUPSERV_H_
#define _L3SUPSERV_H_ 1

486
Control/L3TermCause.cpp Normal file
View File

@@ -0,0 +1,486 @@
/**@file Declarations for common-use control-layer functions. */
/*
* Copyright 2013 Range Networks, Inc.
*
* This software is distributed under multiple licenses;
* see the COPYING file in the main directory for licensing
* information for this specific distribution.
*
* 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.
*/
#define LOG_GROUP LogGroup::Control
#include <stdio.h> // For snprintf
#include <Defines.h>
#include <L3TermCause.h>
#include <Logger.h>
#include <OpenBTSConfig.h>
#include <SIPDialog.h>
namespace Control {
using std::string;
using namespace GSM;
// The default SIP <-> L3-CC-Cause mapping an be overridden with config options.
// For this purpose the GSM Layer3 causes are identified by using the names of the causes
// as defined in GSM 4.08 with space replaced by underbar and any other special chars removed. See file L3Enums.h
// Config option Control.Termination.CauseToSIP.<causename> specifies the SIP code, and optionally, the SIP reason phrase,
// to be sent to the SIP peer when this L3 Cause occurs.
// Config option Control.Termination.SIPToCause.<SIP-code> specifies the name of the layer3 cause to be used for a specific SIP code.
// The Layer3 cause is passed to the handset to indicate the message to display, and also may be saved in the CDR.
// To make this easier for the user we will use the L3 cause as the SIP reason phrase, so the reason phrase can
// be placed in the config option to control it.
static int cause2SipCodeFromConfig(AnyCause acause, string &reason)
{
// User is allowed to over-ride default sipcode for each cause in configuration options:
const char *causeName = L3Cause::AnyCause2Str(acause);
char configOptionName[100];
snprintf(configOptionName,100,"Control.Termination.CauseToSIP.%s",causeName);
if (gConfig.defines(configOptionName)) {
string value = gConfig.getStr(configOptionName);
// Value must begin with a positive number; ignore leading space.
const char *vp = value.c_str();
while (isspace(*vp)) { vp++; }
if (!isdigit(*vp)) {
LOG(ERR) << "Invalid config value for '"<<configOptionName<<"' ignored:"<<value;
return 0;
}
int sipcode = atoi(vp);
// See if the sip code is followed by a non-empty string - if so use it as the SIP reason phrase.
if ((vp = strchr(vp,' '))) {
while (isspace(*vp)) vp++;
if (*vp == 0) return 0;
reason = string(vp);
}
return sipcode;
}
return 0;
}
static int cause2SipCode(AnyCause acause, string& reason)
{
int sip = cause2SipCodeFromConfig(acause, reason);
if (reason.empty()) { reason = string(L3Cause::AnyCause2Str(acause)); }
if (sip) { return sip; }
sip = 408; // Default SIP for almost all causes is Timeout
switch (acause.value) {
// These are CC Causes:
case L3Cause::Unknown_L3_Cause: sip = 486; break; // Busy
case L3Cause::Unassigned_Number: sip = 404; break; // Not Found
case L3Cause::No_Route_To_Destination: sip = 404; break; // Not found
case L3Cause::Channel_Unacceptable: sip = 503; break; // Service Unavailable.
case L3Cause::Operator_Determined_Barring: sip = 503; break; // Service Unavailable.
case L3Cause::Normal_Call_Clearing: sip = 200; break; // OK
case L3Cause::User_Busy: sip = 486; break; // Busy Here
case L3Cause::No_User_Responding: sip = 408; break; // Request Timeout
case L3Cause::User_Alerting_No_Answer: sip = 480; break; // Temporarily Unavailable.
// RFC3261 says use 403 instead of 603 unless we know with certainty there is no second choice like forwarding or voicemail.
case L3Cause::Call_Rejected: sip = 403; break; // Forbidden.
case L3Cause::Number_Changed: sip = 410; break; // Gone
// Preemption,
// Non_Selected_User_Clearing,
// Destination_Out_Of_Order,
case L3Cause::Invalid_Number_Format: sip = 484; break; // Address Incomplete
case L3Cause::Facility_Rejected: sip = 503; break; // Service Unavailable.
// Response_To_STATUS_ENQUIRY,
case L3Cause::Normal_Unspecified: sip = 200; break; // OK
case L3Cause::No_Channel_Available: sip = 503; break; // Service Unavailable.
// Network_Out_Of_Order,
// Temporary_Failure,
// Switching_Equipment_Congestion,
// Access_Information_Discarded,
case L3Cause::Requested_Channel_Not_Available:
case L3Cause::Resources_Unavailable: sip = 503; break;
// Quality_Of_Service_Unavailable,
// Requested_Facility_Not_Subscribed,
// Incoming_Calls_Barred_Within_CUG,
// Bearer_Capability_Not_Authorized,
// Bearer_Capability_Not_Presently_Available,
// Service_Or_Option_Not_Available,
// Bearer_Service_Not_Implemented,
// ACM_GE_Max,
// Requested_Facility_Not_Implemented,
// Only_Restricted_Digital_Information_Bearer_Capability_Is_Available,
// Service_Or_Option_Not_Implemented,
// Invalid_Transaction_Identifier_Value,
// User_Not_Member_Of_CUG,
// Incompatible_Destination,
// Invalid_Transit_Network_Selection,
// Semantically_Incorrect_Message,
// Invalid_Mandatory_Information,
// Message_Type_Not_Implemented,
// Messagetype_Not_Compatible_With_Protocol_State,
// IE_Not_Implemented,
// Conditional_IE_Error,
// Message_Not_Compatible_With_Protocol_State,
// Recovery_On_Timer_Expiry,
// Protocol_Error_Unspecified,
// Interworking_Unspecified,
// We dont bother putting the BSSCause or MMCause in this switch; they all just map to the default SIP code.
// These are the Custom Causes:
// Make sure No_Paging_Reponse and IMSI_Detached map to 480 Temporarily Unavailable even if someone
// changes the default up above.
case L3Cause::No_Paging_Response: sip = 480; break;
case L3Cause::IMSI_Detached: sip = 480; break;
default: break;
}
return sip;
}
int TermCause::tcGetSipCodeAndReason(string &sipreason)
{
if (mtcSipCode) {
sipreason = mtcSipReason;
return mtcSipCode;
}
return cause2SipCode(mtcAnyCause, sipreason);
}
string TermCause::tcGetSipReasonHeader()
{
// Q.850 causes do not exactly match 3GPP causes, so we have to fix it:
int q850cause;
switch (L3Cause::CCCause cccause = tcGetCCCause()) {
case L3Cause::Preemption:
q850cause = 8; // This is the Q.850 cause value that corresponds to the Layer 3 CC-cause "Premption".
break;
default:
q850cause = cccause; // All other CC-Causes that we use have the same values as corresponding Q.850 causes.
break;
}
// In SIP, the dialog termination may be indicated by one of: an error reply, BYE or CANCEL message.
// We ship the full text of the actual cause to the peer. For an error reply it is in the SIP reason phrase;
// for the BYE and CANCEL it is in the only in the Reason: header.
string text = string(L3Cause::AnyCause2Str(mtcAnyCause));
return format("Q.850;cause=%d ;text=\"%s\"",q850cause,text);
}
// MSC-BSS Causes are in GSM 08.08 3.2.2.5. They include:
// RR:RadioInterfaceFailure=1, RR:UplinkQuality=2, RR:UplinkStrength=3, RR:DownlinkQuality=4, RR:DownlinkStrength=5, RR:Distance=6,
// BTS:O&MIntervention=7,
// RR:ChannelAssignmentFailure=0xa (called: Radio Interface Failure, reversion to old channel)
// BTS:HandoverSuccessful=0xb, BetterCell=0xc, RR:NoRadioResourceAvailable=0x21, RR:CCCHOverload=0x23,
// BTS:Preemption=0x29 (emergency), TranscodingMismatch=0x30,
// RequestedSpeechVersinUnavailable=0x33
// BTS:CipheringAlgorithmNotSupported=0x40
// These would all map to L3Cause::NormalUnspecified to the other handset.
// MAP-Errors are in GSM 09.02 17.6.6
// MAP Absent-Subscriber error is used for NoPagingResponse, IMSI Detached, Restricted Area for CS services;
// also GPRS Detached, others for other services.
// MM Mobility Management errors in 4.08 10.5.3.6 Reject Cause
// Termination Possibilities.
// The events we recognize are:
// A or B-Hangup L3Cause::NormalCallClearing A or B SIP 0
// B-Rejected L3Cause::CallRejected B SIP 403
// A-MOCancel (during ringing) CDR:a-abort
// A-MOCancelPowerOff (during ringing): CDR:a-abort
// B-RingNoAnswer L3Cause::UserAlertingNoAnswer 480
// B-Off (before ringing) A gets: L3Cause::DestinationOutOfOrder SIP 404
// Could use Q850 20 UserAbsent (20) which is not used by L3Cause
// B-RingPowerOff (during ringing): L3Cause::CallRejected B
// B-NoAnswerToPage L3Cause::DestinationOutOfOrder SIP 408
// B-Busy SIP 486
// A or B-PreemptionByOperator
// A or B-PreemptionByEmergency
// A or B-ConnectionLost (many reasons - MSC-BSS causes)
// Congestion
// MM Layer failures. If these happen at the MTC end, we should send code upward. In all cases A gets L3Cause::NoUserResponding?
// AuthenticationReject (at the 4.08 level this is a message no specific code, but we can use the Control.LUR.UnprovisionedRejectCause
// Many other failures possible at higher level: IMSI unknown, IMSI invalid, no number configured, use MM or MAP reject cause.
//
// void initSipCode()
// {
// struct SipCodeKey : public ConfigurationKey {
// char namebuf[100];
// char defaultValueBuf[40];
//
// SipCodeKey(ConfigurationKeyMap &map,const char *name, int defaultValue, const char *units, const char *range, const char *help = "")
// {
// snprintf(namebuf,sizeof(namebuf),"Layer3.TerminationCause.%s",name);
// snprintf(defaultValueBuf,sizeof(defaultValueBuf),"%d",defaultValue);
// ConfigurationKey tmp(namebuf,
// defaultValueBuf,
// units,
// ConfigurationKey::DEVELOPER,
// ConfigurationKey::VALRANGE,
// range,
// false,
// help);
// map[namebuf] = tmp;
// }
// };
// }
// Event:CallRejected.SIP "403 Call Rejected"
// Event:Cancel # There is no SIP for this because it is a
// The MT end needs a code. The MOC doesnt
// Event:IMSIDetach
// Event:NoPagingResponse
// Event:UserBusy
// Event::AuthorizationFailure Reason:MMRejectCause from 24.008 10.5.3.6
// Event::NoChannelAvailable Reason:BSS Cause GSM*.08-3.2.2.5
// Event::RadioLoss Reason:BSS Cause GSM*.08-3.2.2.5
// Event::Disconnect (Disconnect, Release or ChannelRelease message) Reason:L3Cause passed by handset in message.
// Event::OperatorIntervention
// Event::Preemption
// An "Event" is something that causes connection termination.
// The Event Location can be local handset, local BTS (radio problems), network, remote BTS or remote handset.
// The Locus can be CC (messages from handset), MM, RR, CN (core network)
// Note: The Layer3 CC Cause includes messages from all loci.
// Termination before connection:
// Event:AlertingNoAnswer
// MTC rings, no answer; MOC gets L3Cause::UserAlertingNoAnswer; SIP 408 (Request Timeout) (See L3StateMachine.cpp) Lynx3.1.3 CDR:No-answer
// Event:CallRejected
// MTC declines (aborts while ringing) MOC gets L3Cause::CallRejected; SIP 403: Forbidden (per RFC3261 sec 15) Lynx3.1.4 CDR:B-party-abort
// Event:Cancel
// MOC cancel (hangs up while ringing) MTC gets L3Cause::NormalCallClearing?; MOC sends SIP CANCEL, MTC sends 487 Request Terminated
// Lynx3.1.5: CDR:A-party-abort. could argue "NormalCallClearing" is reserved for termination after connect.
// Pat says: how do we represent this in the CDR? This is A-party normal hangup with 0 seconds connect time.
// Event:IMSIDetach
// MOC IMSIDetach during ringing; identical to MOC Cancel Lynx3.1.9:CDR:A-party-abort
// MTC IMSIDetach before ringing; Lynx3.1.10:CDR:None, which is distinct from no answer to page.
// MTC IMSIDetach during ringing Lynx3.1.13:CDR:b-party-abort
// Event:NoPagingResponse
// MTC NoPagingResponse (off or outside coverage area); L3Cause::DestinationOutOfOrder; Lynx3.1.6&7:CDR:No-page-response
// either SIP 408 (Request Timeout) or 504 (Server Timeout) 404 (Not Found) doesnt seem right because the server
// has definitive info that user does not exist, but in our case a new page may succeed.
// Event:UserBusy
// MTC busy (has existing call, and call-hold/call-wait not available) MOC gets SIP 486 (not 600) Lynx3.1.8:CDR:B-party-busy
// Event::AuthorizationFailure Reason:MMRejectCause from 24.008 10.5.3.6
// MOC SIM Invalid. Lynx3.1.14:no call.
// MTC SIM invalid. Lynx3.1.15: a-party told "invalid subscriber"?
// MTC called number not in SR (vacant) Lynx3.1.15:CDR:invalid-number
// Event::NoChannelAvailable Reason:BSS Cause GSM*.08-3.2.2.5
// MTC congestion; L3Cause::SwitchingEquipmentCongestion SIP 503 Service Unavailable Lynx3.1.17:CDR:None.
// Event::RadioLoss Reason:BSS Cause GSM*.08-3.2.2.5
// MTC radio loss; MOC gets L3Cause NoUserResponding, SIP 504 No User Responding; Lynx:not-specified
// MOC radio loss; MTC gets Cancel. Lynx:not-specified
// Termination after connection:
// Note: The BYE message needs a "Reason" to distinguish these apart for CDR purposes.
// Event::Disconnect (Disconnect, Release or ChannelRelease message) Reason:L3Cause passed by handset in message.
// MOC (A-party) disconnect; L3Cause::NormalCallClearing; SIP BYE, no sip code, Reason:none Lynx3.1.1 CDR:A-party-normal-disconnect
// MTC (B-party) disconnect; L3Cause::NormalCallClearing; SIP BYE, no sip code Reason:none Lynx3.1.2 CDR:B-party-normal-disconnect
// Event::OperatorIntervention
// Operator Intervention. L3Cause::Preemption SIP 480 (Temporarily Unavailable) or 503 (Service Unavailable)
// Event::Preemption
// Preemption by Emergency Call. L3Cause::Preemption SIP 480 (Temporarily Unavailable) or 503 (Service Unavailable)
// Event::RadioLoss Reason:BSS Cause GSM*.08-3.2.2.5
// MOC or MTC connection lost (simulated by MS turned off, which is not really correct since it generates a detach); RRcause? SIP BYE?
// Lynx 3.1.11&3.1.12:CDR:Termination-error if MOC or CDR:B-party-abort if MTC lost. That's just dopey.
// We can use L3cause::NormalUnspecified, which we do not use for anything else.
// Peer gets SIP BYE,
// Return a call-control cause, formerly aka L3Cause, from GSM 4.08 10.5.4.11.
// Call-control causes are meant for communication to the handset, and in the case where we are sending a CC cause to the handset,
// the cause will already always be a CC cause.
// But we are also sending them to the outside world in SIP, and maybe we wills tore them in the CDRs as well,
// so if it is not a call-control cause, return the nearest match.
L3Cause::CCCause TermCause::tcGetCCCause()
{
switch (mtcAnyCause.getLocus()) {
case L3Cause::LocusCC:
return mtcAnyCause.ccCause;
case L3Cause::LocusMM:
// Authentication falure.
return L3Cause::Operator_Determined_Barring;
case L3Cause::LocusBSS:
// Radio or other failure
switch (mtcAnyCause.bssCause) {
case L3Cause::Radio_Interface_Failure:
case L3Cause::Uplink_Quality:
case L3Cause::Uplink_Strength:
case L3Cause::Downlink_Quality:
case L3Cause::Downlink_Strength:
case L3Cause::Distance:
return L3Cause::Destination_Out_Of_Order;
case L3Cause::Operator_Intervention:
return L3Cause::Preemption;
case L3Cause::Channel_Assignment_Failure:
return L3Cause::No_Channel_Available;
case L3Cause::Handover_Successful:
case L3Cause::Better_Cell:
case L3Cause::Traffic:
case L3Cause::Reduce_Load_In_Serving_Cell:
case L3Cause::Traffic_Load_In_Target_Cell_Higher_Than_In_Source_Cell:
case L3Cause::Relocation_Triggered:
break; // These should not occur.
case L3Cause::Equipment_Failure:
case L3Cause::No_Radio_Resource_Available:
case L3Cause::CCCH_Overload:
case L3Cause::Processor_Overload:
case L3Cause::Traffic_Load:
return L3Cause::No_Channel_Available;
case L3Cause::Emergency_Preemption:
return L3Cause::Preemption;
case L3Cause::DTM_Handover_SGSN_Failure:
case L3Cause::DTM_Handover_PS_Allocation_Failure:
case L3Cause::Transcoding_Mismatch:
case L3Cause::Requested_Speech_Version_Unavailable:
case L3Cause::Ciphering_Algorithm_Not_Supported:
break; // Not sure what we would send, but these do not occur so dont worry about it.
}
// Some kind of error occurred.
return L3Cause::No_User_Responding; // Our all purpose default cause.
case L3Cause::LocusCustom:
switch (mtcAnyCause.customCause) {
case L3Cause::No_Paging_Response:
case L3Cause::IMSI_Detached: // This is a MAP error code, which would have to map to l3cause DestinationOutOfOrder.
return L3Cause::Destination_Out_Of_Order;
case L3Cause::Handover_Outbound:
case L3Cause::Handover_Error: // Logic error during handover.
case L3Cause::Invalid_Handover_Message:
break; // errors
case L3Cause::Missing_Called_Party_Number:
return L3Cause::Invalid_Mandatory_Information;
case L3Cause::Layer2_Error:
case L3Cause::Sip_Internal_Error:
case L3Cause::L3_Internal_Error:
case L3Cause::No_Transaction_Expected: // Used when we dont expect termination to have any transactions to close out.
case L3Cause::Already_Closed: // Used when we know for sure the transaction has already been terminated.
case L3Cause::SMS_Timeout:
case L3Cause::SMS_Error:
break; // errors
case L3Cause::SMS_Success:
return L3Cause::Normal_Call_Clearing;
case L3Cause::USSD_Error:
break; // errors
case L3Cause::USSD_Success:
case L3Cause::MM_Success:
return L3Cause::Normal_Call_Clearing;
}
// Some kind of error occurred.
return L3Cause::No_User_Responding; // Our all purpose default cause.
default:
devassert(0);
return L3Cause::Normal_Unspecified;
}
}
TermCause dialog2ByeCause(SIP::SipDialog *dialog)
{
// TODO:
return TermCause::Remote(L3Cause::Normal_Call_Clearing,0,"Bye");
}
static int sipCode2AnyCauseFromConfig(int sipcode)
{
// User is allowed to over-ride default sipcode for each cause in configuration options:
char configOptionName[100];
snprintf(configOptionName,100,"Control.Termination.SIPToCause.%d",sipcode);
if (gConfig.defines(configOptionName)) {
string causeName = gConfig.getStr(configOptionName);
int cause = CauseName2Cause(causeName);
if (cause) {
return cause;
} else {
LOG(ERR) << "Config option '"<<configOptionName<<"' specifies unrecognized cause name:'"<<causeName<<"'";
}
}
return 0;
}
int sipCode2AnyCause(int sipcode, // The sip code from the dialog error.
bool alerted) // True if we ever received sip code 180.
{
int acause = sipCode2AnyCauseFromConfig(sipcode);
// Look up a default L3 cause from the SIP code.
if (! acause) {
switch (sipcode) {
case 486: // Busy here
case 600: // Busy everywhere
acause = L3Cause::User_Busy;
break;
case 403: // Forbidden. (RFC3261 sec 15 says use this for user-declined.)
case 603: // Decline
acause = L3Cause::Call_Rejected;
break;
case 480: // Temporarily unavailable
acause = alerted ? L3Cause::User_Alerting_No_Answer : L3Cause::No_User_Responding;
break;
case 408: // Request Timeout, from many causes.
acause = L3Cause::No_User_Responding;
break;
case 404:
acause = L3Cause::No_Route_To_Destination;
break;
case 503: // SIP Service Unavailable. Asterisk sends this often.
acause = L3Cause::Resources_Unavailable;
break;
case 0:
// This is most likely an early MTD Mobile Terminated Disconnect caused by CANCEL before ACK received.
acause = L3Cause::User_Busy;
break;
default:
// Operators dont want to show network failure on the phones, so use something else.
//return TermCause(L3Cause::InterworkingUnspecified,sipcode,sipreason);
acause = L3Cause::User_Busy;
break;
}
}
return acause;
}
// Return the Q.850 cause from a "Reason:" header, or 0 if none.
int parseReasonHeaderQ850(const char * reasonHeader)
{
// Split at commas.
const char *q850p = strcasestr(reasonHeader,"Q.850");
if (!q850p) return 0;
const char *commap = strchr(q850p,',');
const char *causep = strcasestr(q850p,"cause");
if (!causep || (commap && causep > commap)) return 0;
causep += strlen("cause");
int result;
if (1 != sscanf(causep," = %d",&result)) return 0;
return result;
}
// Search for a cause in the SIP message.
TermCause dialog2TermCause(SIP::SipDialog *dialog)
{
if (! dialog) { // Be ultra-cautious.
// This is not supposed to happen.
return TermCause::Remote(AnyCause(L3Cause::No_User_Responding),480,"No_User_Responding");
}
string sipreason;
int sipcode = dialog->getLastResponseCode(sipreason);
// Does the reason phrase look like one that was created on a peer OpenBTS?
int l3cause = CauseName2Cause(sipreason);
if (! l3cause) {
string reasonHeader = dialog->getLastResponseReasonHeader();
// Was a Q.850 reason included?
int q850 = parseReasonHeaderQ850(reasonHeader.c_str());
if (q850) {
l3cause = (q850 == 8) ? L3Cause::Preemption : (CCCause) q850;
} else {
bool alerted = dialog->mReceived180;
l3cause = sipCode2AnyCause(sipcode,alerted);
}
}
return TermCause::Remote(AnyCause(l3cause),sipcode,sipreason);
}
std::ostream& operator<<(std::ostream& os, TermCause &cause)
{
os <<format("l3cause=%d=0x%x(%s) SipCode=%d(%s)",cause.tcGetValue(),cause.tcGetValue(),L3Cause::AnyCause2Str(cause.mtcAnyCause),(int)cause.mtcSipCode,cause.mtcSipReason.c_str());
return os;
}
}; // namespace Control

97
Control/L3TermCause.h Normal file
View File

@@ -0,0 +1,97 @@
/**@file Declarations for common-use control-layer functions. */
/*
* Copyright 2013 Range Networks, Inc.
*
* This software is distributed under multiple licenses;
* see the COPYING file in the main directory for licensing
* information for this specific distribution.
*
* 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 _TERMCAUSE_H_
#define _TERMCAUSE_H_
#include <assert.h>
#include <string>
#include <L3Enums.h>
#include <ScalarTypes.h> // for Int_z, From CommonLibs
#include <Logger.h>
namespace SIP { class SipDialog; }
namespace Control {
using std::string;
using GSM::L3Cause;
// This is the reason a Transaction (TranEntry) was cancelled as desired to be known by the high side.
// It has nothing to do with the cancel causes on the low side, for example, CC Cause (for cloasing a single call)
// or RR Cause (for closing an entire channel.)
// Why cant we just use CC or RR Cause? Because some of the reasons do not exist in any other single list, for example, NoAnswerToPage.
// An established SipDialog is ended by a SIP BYE, and an MO [Mobile Originated] SipDialog is canceled early using
// a SIP CANCEL, so this is used only for the case of an INVITE response where the ACK message has not been sent,
// or as a non-invite message (eg, SMS MESSAGE) error response. As such, there are only a few codes that
// the SIP side cares about. The vast majority of plain old errors, for example, loss of contact with the MS
// or reassignFailure will just map to the same SIP code so we use TermCauseUnknown, however, all such cases
// are distinguished from TermCauseNoAnswerToPage in that we know the MS is on the current system.
// pat added 6-2014, so every client that closes a transaction is forced to explicitly specify all needed cancellation information.
class TermCause {
L3Cause::AnyCause mtcAnyCause; // The full extended cause. Self-inits to 0.
Int_z mtcSipCode;
string mtcSipReason;
public:
enum Side { SideLocal, SideRemote } mtcInstigator; // Which side closed the channel?
int tcGetValue() { return mtcAnyCause.value; }
string tcGetStr();
bool tcIsEmpty() { return mtcAnyCause.isEmpty(); }
L3Cause::CCCause tcGetCCCause(); // Returns the nearest Call-Control-Cause as per GSM 4.08 10.5.4.11.
int tcGetSipCodeAndReason(string &sipreason); // Returns the nearest SIP code, and the sip reason phrase derived from the real cause.
string tcGetSipReasonHeader(); // Returns a SIP "Reason:" header string.
// constructors:
TermCause() { assert(mtcAnyCause.value == 0 && mtcSipCode == 0); }
// This constructor is used to terminate a transaction by this BTS.
// The TermCode is translated as needed to an L3Cause, SIP code, and SIP reason.
//TermCause(Side side,TermCode code);
static TermCause Local(L3Cause::AnyCause cause) {
TermCause self;
self.mtcInstigator = SideLocal;
self.mtcAnyCause = cause;
self.mtcSipCode = 0;
LOG(DEBUG)<<self;
return self;
}
// This constructor used to terminate a transaction from peer BTS via SIP.
// This SIP code is saved here so we can put it the CDR, but will not be sent outbound to a dialog.
static TermCause Remote(GSM::AnyCause wCause, int wSipCode, string wSipReason)
{
TermCause self;
self.mtcInstigator = SideRemote;
self.mtcAnyCause = wCause;
self.mtcSipCode = wSipCode;
self.mtcSipReason = wSipReason;
LOG(DEBUG)<<self;
return self;
}
friend std::ostream& operator<<(std::ostream& os, TermCause &cause);
};
std::ostream& operator<<(std::ostream& os, TermCause &cause);
extern TermCause dialog2TermCause(SIP::SipDialog *dialog);
extern TermCause dialog2ByeCause(SIP::SipDialog *dialog);
}; // namespace Control
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -6,7 +6,7 @@
*
* This software is distributed under multiple licenses;
* see the COPYING file in the main directory for licensing
* information for this specific distribuion.
* information for this specific distribution.
*
* This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details.
@@ -43,6 +43,10 @@
#include "L3MobilityManagement.h"
#include "L3Utils.h"
extern int gCountTranEntry;
struct sqlite3;
/**@namespace Control This namepace is for use by the control layer. */
namespace Control {
@@ -75,14 +79,7 @@ class HandoverEntry {
string mSipReferStr;
string mHexEncodedL3HandoverCommand;
// (pat) This is used to encode the L3 Handover Command.
struct ::sockaddr_in mOutboundPeer; ///< other BTS in outbound handover
//GSM::L3CellDescription mOutboundCell;
//GSM::L3ChannelDescription2 mOutboundChannel;
//GSM::L3HandoverReference mOutboundReference;
//GSM::L3PowerCommandAndAccessType mOutboundPowerCmd;
//GSM::L3SynchronizationIndication mOutboundSynch;
struct ::sockaddr_in mOutboundPeer;
protected:
void initHandoverEntry(
@@ -191,35 +188,26 @@ class HandoverEntry {
// These variables may not be modified even by TranEntry except via the accessor methods.
class TranEntryProtected
{
friend class StaleTranEntry;
// No one may set mGSMState directly, call setGSMState
CallState mGSMState; ///< the GSM/ISDN/Q.931 call state (pat) No it is not; the enum has been contaminated.
Timeval mStateTimer; ///< timestamp of last state change.
// (pat) The mRemoved could be the final in CallState, but then we would have to be careful about the order of setting
// it during cleanup, so its safer to use a separate bool for mRemoved.
//volatile bool mRemoved; ///< true if being removed.
public:
CallState getGSMState() const; // (pat) This is the old call state and will eventually just go away.
void setGSMState(CallState wState);
//void GSMState(CallState wState) { setGSMState(wState); }
bool isStuckOrRemoved() const;
// (pat) This is called after the TranEntry has been removed from the TransactionTable.
// We are tagging it for deletion, not removal.
//void tagForDeletion() { mRemoved=true; mStateTimer.now(); }
unsigned stateAge() const { /*ScopedLock lock(mLock);*/ return mStateTimer.elapsed(); }
//bool removed() const { return mRemoved; }
//bool isRemoved() const;
/** Return true if clearing is in progress in the GSM side. */
bool clearingGSM() const;
void stateText(ostream &os) const;
void stateText(unsigned &state, std::string &deleted) const;
virtual TranEntryId tranID() const = 0; // This is our private transaction id, not the layer 3 TI mL3TI
TranEntryProtected() : mGSMState(CCState::NullState) /*, mRemoved(false)*/ { mStateTimer.now(); }
TranEntryProtected() : mGSMState(CCState::NullState) { mStateTimer.now(); }
// (pat 6-2014) It looks like Dave added this. It would be needed to preserve the state timer
// if the TranEntry were ever copied. Is it? This may have been part of the staletranentry that was deleted.
TranEntryProtected(TranEntryProtected &old)
{
mGSMState = old.mGSMState;
@@ -227,12 +215,46 @@ class TranEntryProtected
}
};
// pat added 6-2014.
// Call Data Record, created from a TranEntry.
// The information we dont know is left empty.
struct L3CDR {
string cdrType; // MOC, MTC, Emergency
TranEntryId cdrTid;
string cdrToImsi, cdrFromImsi;
string cdrToNumber, cdrFromNumber;
string cdrPeer;
time_t cdrConnectTime;
long cdrDuration; // Connect duration, as distinct from total duration.
int cdrMessageSize; // For SMS
string cdrToHandover, cdrFromHandover;
TermCause cdrCause;
static void cdrWriteHeader(FILE *pf);
void cdrWriteEntry(FILE *pf);
};
// pat added 6-2014.
// This is sent L3CDR records. It writes them to a file.
class CdrService {
InterthreadQueue<L3CDR> mCdrQueue;
Thread cdrServiceThread;
FILE *mpf;
int cdrCurrentDay;
Bool_z cdrServiceRunning;
public:
CdrService() : mpf(NULL), cdrCurrentDay(-1) {}
void cdrServiceStart();
void cdrOpenFile();
static void *cdrServiceLoop(void*);
void cdrAdd(L3CDR*cdrp) { mCdrQueue.write(cdrp); }
};
extern CdrService gCdrService;
DEFINE_MEMORY_LEAK_DETECTOR_CLASS(TranEntry,MemCheckTranEntry)
class TranEntry : public MemCheckTranEntry, public RefCntBase, public TranEntryProtected, public L3TimerList
{
friend class NewTransactionTable;
friend class StaleTranEntry;
friend class MachineBase;
friend class MMContext;
//mutable Mutex mLock; ///< thread-safe control, shared from gTransactionTable
@@ -284,6 +306,12 @@ class TranEntry : public MemCheckTranEntry, public RefCntBase, public TranEntryP
std::string mContentType; ///< text message payload content type
//@}
TermCause mFinalDisposition; // How transaction ended.
// (pat) In v1 (Pre-l3-rewrite), this was a value, not a pointer.
// In v2, the TranEntry represents a connection to an MS,
// and the SipDialog is a decoupled separate structure managed in the SIP directory.
@@ -296,14 +324,14 @@ class TranEntry : public MemCheckTranEntry, public RefCntBase, public TranEntryP
// transaction and we will be able to destroy TranEntrys
// immediately upon loss of radio link.
private:
SIP::SipDialog *mDialog; // (pat) post-l3-rewrite only.
SIP::SipDialogRef mDialog; // (pat) post-l3-rewrite only.
SIP::SipDialog *getDialog() { return mDialog; }
SIP::SipDialog *getDialog() const { return mDialog; } // grrr
SIP::SipDialog *getDialog() { return mDialog.self(); }
SIP::SipDialog *getDialog() const { return mDialog.self(); } // grrr
public:
void setDialog(SIP::SipDialog *dialog); // Also passed through to here from MachineBase
void teCloseDialog(CancelCause cause=CancelCauseUnknown);
void teCloseDialog(TermCause cause);
private:
mutable SIP::SipState mPrevSipState; ///< previous SIP state, prior to most recent transactions
@@ -319,7 +347,7 @@ class TranEntry : public MemCheckTranEntry, public RefCntBase, public TranEntryP
private:
Bool_z mTerminationRequested;
L3Cause::AnyCause mTerminationRequested;
public: // But only used by MobilityManagement routines.
// TODO: Maybe this should move into the MMContext.
@@ -350,7 +378,7 @@ class TranEntry : public MemCheckTranEntry, public RefCntBase, public TranEntryP
static TranEntry *newMTC(
SIP::SipDialog *wDialog,
const FullMobileId& msid,
const GSM::L3CMServiceType& wService, // MobileTerminatedCall, FuzzCall, TestCall, or UndefinedType for generic page from CLI.
const GSM::L3CMServiceType& wService, // MobileTerminatedCall or UndefinedType for generic page from CLI.
string wCallerId);
static TranEntry *newMTSMS(
@@ -388,15 +416,11 @@ class TranEntry : public MemCheckTranEntry, public RefCntBase, public TranEntryP
);
/** Set the inbound handover parameters on the channel; state should alread be HandoverInbound. */
void setInboundHandover(
float wRSSI,
float wTimingError
);
//void setInboundHandover(float wRSSI, float wTimingError);
private:
/** Delete the database entry upon destruction. */
~TranEntry();
public:
/**@name Accessors. */
//@{
@@ -442,7 +466,7 @@ public:
//@}
bool terminationRequested();
L3Cause::AnyCause terminationRequested();
/**@name SIP-side operations */
//@{
@@ -452,13 +476,8 @@ public:
// Obviously, these are only for TransactionEntries for voice calls.
//void txFrame(GSM::AudioFrame* frame) { ScopedLock lock(mLock); return mSIP->txFrame(frame); }
// Obviously, these are only for TransactionEntries for voice calls.
void txFrame(GSM::AudioFrame* frame, unsigned numFlushed);
GSM::AudioFrame *rxFrame();
// (pat) NOTE: This is unused except by the temporary code csl3HandleSipMsg
//const std::string SIPCallID() const { return mDialog->callID(); }
void txFrame(SIP::AudioFrame* frame, unsigned numFlushed);
SIP::AudioFrame *rxFrame();
//@}
@@ -469,16 +488,17 @@ public:
bool deadOrRemoved() const;
/** Dump information as text for debugging. */
void textTable(std::ostream&) const;
void text(std::ostream&) const;
string text() const;
static void header(std::ostream&);
/** Genrate an encoded string for handovers. */
std::string handoverString(string peer) const;
private:
/** Set up a new entry in gTransactionTable's sqlite3 database. */
//void insertIntoDatabase();
/** Run a database query. */
void runQuery(const char* query) const;
@@ -488,12 +508,11 @@ public:
// (pat) This is genuine removal, and partial cleanup if the TranEntry is dirty.
/** Removal status. */
private: void teRemove(CancelCause cause); // Keep private. Dont call from Procedures. Call teCloseCall or teCancel or chanClose instead.
private: void teRemove(TermCause cause); // Keep private. Dont call from Procedures. Call teCloseCall or teCancel or chanClose instead.
public:
void teCancel(CancelCause cause = CancelCauseUnknown) { teRemove(cause); } // Used from MM. Cancel dialog and delete transaction, do not notify MM layer. Used within MMLayer.
void teCancel(TermCause cause) { teRemove(cause); } // Used from MM. Cancel dialog and delete transaction, do not notify MM layer. Used within MMLayer.
void teCloseCallNow(TermCause cause, bool sendCause);
// If the channel ends being released, this is the cause.
void teCloseCallNow(GSM::L3Cause cause); // Same as teCancel but notify MM layer; used from Procedures..
//void teCloseCall(GSM::L3Cause cause, bool faster); // Same as teCancel but notify MM layer; used from Procedures..
////////////////////////////////// l3rwrite ///////////////////////////////////////////
// (pat) l3rewrite stuff:
@@ -537,47 +556,16 @@ public:
HandoverEntry *getHandoverEntry(bool create) const; // Creates if necessary.
TranEntry *unconst() const { return const_cast<TranEntry*>(this); }
// Is this handset ever communicated with us?
bool teIsTalking();
// The following block is used to contain the fields from the TRANSACTION_TABLE that are useful for statistics
private:
time_t startTime, endTime; // "call" start and end time
public:
inline void setEndTime(time_t t) { endTime = t; }
inline time_t getEndTime(void) { return endTime; }
time_t mStartTime; // transaction creation time.
time_t mConnectTime; // When call is connected, or 0 if never.
L3CDR *createCDR(bool makeCMR, TermCause cause);
};
namespace TranFmt
{
// These are the format strings for the various columns being output by
// header() and text(). Make sure that they have trailing spaces.
// TODO: Figure out what the correct lengths to use are
// lblfmt_ is for the label lines - everything are strings
// fmt_ is for the data - make the format letter consistent with the actual
// data.
static const char lblfmt_Active[] = "%6.6s ";
static const char lblfmt_TranId[] = "%10.10s ";
static const char lblfmt_L3TI[] = "%9.9s ";
static const char lblfmt_Service[] = "%9.9s ";
static const char lblfmt_To[] = "%16.16s ";
static const char lblfmt_From[] = "%16.16s ";
static const char lblfmt_AgeSec[] = "%9.9s ";
static const char lblfmt_StartTime[] = "%36.36s ";
static const char lblfmt_EndTime[] = "%36.36s ";
static const char lblfmt_Message[] = "%16.16s ";
static const char fmt_Active[] = "%6.6s ";
static const char fmt_TranId[] = "%10u ";
static const char fmt_L3TI[] = "%9u ";
static const char fmt_Service[] = "%9.9s ";
static const char fmt_To[] = "%16.16s ";
static const char fmt_From[] = "%16.16s ";
static const char fmt_AgeSec[] = "%9u ";
static const char fmt_StartTime[] = "%04.4d/%02d/%02d %02d:%02d:%02d %16d ";
static const char fmt_EndTime[] = "%04.4d/%02d/%02d %02d:%02d:%02d %16d ";
static const char fmt_EndTime2[] = "%36.36s "; // used if time is 0
static const char fmt_Message[] = "%s "; // TODO
}
std::ostream& operator<<(std::ostream& os, const TranEntry&);
std::ostream& operator<<(std::ostream& os, const TranEntry*);
@@ -586,10 +574,6 @@ std::ostream& operator<<(std::ostream& os, const TranEntry*);
/** A map of transactions keyed by ID. */
class NewTransactionMap : public std::map<TranEntryId,TranEntry*> {};
class StaleTranEntry; // forward reference
/** A map of transactions keyed by ID, used for completed tasks. */
class StaleTransactionMap : public std::map<TranEntryId,StaleTranEntry*> {};
/**
A table for tracking the states of active transactions.
*/
@@ -599,17 +583,26 @@ class NewTransactionTable {
private:
#if EXTERNAL_TRANSACTION_TABLE
sqlite3 *mDB; ///< database connection
#endif
NewTransactionMap mTable;
mutable RWLock rwLock;
mutable Mutex mttLock;
unsigned mIDCounter;
public:
/**
Initialize a transaction table.
@param path Path fto sqlite3 database file.
*/
void ttInit();
#if EXTERNAL_TRANSACTION_TABLE
NewTransactionTable() : mDB(0) {} // (pat) Make sure no garbage creeps in.
~NewTransactionTable();
#endif
/**
Return a new ID for use in the table.
*/
@@ -627,6 +620,10 @@ class NewTransactionTable {
@return NULL if ID is not found or was dead
*/
TranEntry* ttFindById(TranEntryId wID);
bool ttIsTalking(TranEntryId tranid);
// (pat) This is a temporary routine to return the state asynchronously.
// Should be replaced by using a RefCntPointer in the NewTransactionTable and returning that.
CallState ttGetGSMStateById(TranEntryId wId);
bool ttIsDialogReleased(TranEntryId wID);
bool ttSetDialog(TranEntryId tid, SIP::SipDialog *dialog); // Unlike TranEntry::setDialog(), this can be used from other directories, other threads.
@@ -646,7 +643,7 @@ class NewTransactionTable {
Return the availability of this particular RTP port
@return True if Port is available, False otherwise
*/
bool RTPAvailable(short rtpPort);
bool RTPAvailable(unsigned rtpPort);
/**
Fand an entry by its handover reference.
@@ -660,12 +657,9 @@ class NewTransactionTable {
@param wID The transaction ID to search.
@return True if the ID was really in the table and deleted.
*/
bool ttRemove(unsigned wID); // this marks the completion time
bool ttRemove(unsigned wID);
// Use this ONLY when deleting from the Stale table in the main process
void ttErase(NewTransactionMap::iterator itr);
bool ttTerminate(TranEntryId tid);
bool ttTerminate(TranEntryId tid, L3Cause::BSSCause cause);
//bool remove(TranEntry* transaction) { return remove(transaction->tranID()); }
@@ -743,20 +737,21 @@ class NewTransactionTable {
/** Count the number of transactions using a particular channel. */
//unsigned countChan(const L3LogicalChannel*);
size_t size() { size_t iSize; rwLock.rlock(); iSize = mTable.size(); rwLock.unlock(); return iSize; }
size_t size() { ScopedLock lock(mttLock); return mTable.size(); }
// old style dump (calls command)
size_t dump(std::ostream& os, bool showAll=false) const;
// new style dump (trans command)
size_t dumpTable(std::ostream& os) const;
/** Generate a unique handover reference. */
//unsigned generateInboundHandoverReference(TranEntry* transaction);
private:
#if EXTERNAL_TRANSACTION_TABLE
/** Accessor to database connection. */
sqlite3* getDB() { return mDB; }
#endif
/**
Remove "dead" entries from the table.
A "dead" entry is a transaction that is no longer active.
@@ -782,105 +777,9 @@ class NewTransactionTable {
//}
};
/**
A table for tracking the states of stale transactions.
*/
class StaleTransactionTable {
private:
StaleTransactionMap mTable;
mutable RWLock rwLock;
extern void TranInit();
public:
/**
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 ttAdd(StaleTranEntry* value);
// Use this ONLY when deleting from the Stale table in the main process
void ttErase(StaleTransactionMap::iterator itr);
size_t size() { size_t iSize; rwLock.rlock(); iSize = mTable.size(); rwLock.unlock(); return iSize; }
// new style dump (trans command)
size_t dumpTable(std::ostream& os) const;
void clearTable();
public: // these are needed for the main event loop, and should be used
// with extreme caution.
StaleTransactionMap *ttMap(void) { return &mTable; };
void ttLock(void) { rwLock.wlock(); }
void ttUnlock(void) { rwLock.unlock(); }
};
} // namespace Control
/**@addtogroup Globals */
//@{
/** A single global transaction table in the global namespace. */
extern Control::NewTransactionTable gNewTransactionTable;
//@}
//@{
/** Transactions that have been deleted are cloned into here. */
extern Control::StaleTransactionTable gStaleTransactionTable;
//@}
namespace Control
{
class StaleTranEntry
{
private:
// From TranEntryProtected
Timeval mStateTimer; ///< timestamp of last state change.
public:
unsigned stateAge() const { /*ScopedLock lock(mLock);*/ return mStateTimer.elapsed(); }
void stateText(ostream &os) const;
void stateText(unsigned &state, std::string &deleted) const;
private:
TranEntryId mID; ///< the internal transaction ID, assigned by a TransactionTable
GSM::L3CMServiceType mService; ///< the associated service type
// 24.007 11.2.3.1.3: The TI [Transaction Identifier] is 3 bits plus a flag.
// The flag is 0 when it belongs to a transaction initiated by its sender, else 1.
// The same TI can be used simultaneously in both direction, distinguished by the flag.
// The TI 7 is reserved for an extension mechanism which only applies to certain protocols (ie, Call Control)
// and is an error otherwise, but we should not use that value.
// The value of the flag we store here is 0 if we initiated the transaction, 1 if MS initiated.
unsigned mL3TI; ///< the L3 short transaction ID, the version we *send* to the MS
static const unsigned cL3TIInvalid = 16; // valid values are 0-7
GSM::L3CalledPartyBCDNumber mCalled; ///< the associated called party number, if known
GSM::L3CallingPartyBCDNumber mCalling; ///< the associated calling party number, if known
std::string mMessage; ///< text message payload
public:
StaleTranEntry(TranEntry &old);
~StaleTranEntry();
public:
unsigned getL3TI() const;
TranEntryId tranID() const { return mID; }
/** Dump information as text for debugging. */
void textTable(std::ostream&) const;
static void header(std::ostream&);
// The following block is used to contain the fields from the TRANSACTION_TABLE that are useful for statistics
private:
time_t startTime, endTime; // "call" start and end time
public:
inline void setEndTime(time_t t) { endTime = t; }
inline time_t getEndTime(void) { return endTime; }
};
}; // Control namespace
@@ -888,3 +787,5 @@ class StaleTranEntry
#endif
// vim: ts=4 sw=4

View File

@@ -1,9 +1,9 @@
/*
* Copyright 2013 Range Networks, Inc.
* Copyright 2013, 2014 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.
* information for this specific distribution.
*
* This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details.
@@ -13,6 +13,9 @@
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*/
// Written by Pat Thompson
#define LOG_GROUP LogGroup::Control // Can set Log.Level.Control for debugging
#include "L3Utils.h"
#include "L3StateMachine.h"

View File

@@ -1,10 +1,10 @@
/**@file Declarations for Circuit Switched State Machine and related classes. */
/*
* Copyright 2013 Range Networks, Inc.
* Copyright 2013, 2014 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.
* information for this specific distribution.
*
* This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details.
@@ -14,6 +14,7 @@
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*/
#ifndef _L3UTILS_H_
#define _L3UTILS_H_
#include <GSMCommon.h> // Where Z100Timer lives.

View File

@@ -1,7 +1,7 @@
#
# Copyright 2008 Free Software Foundation, Inc.
# Copyright 2010 Kestrel Signal Processing, Inc.
# Copyright 2011 Range Networks, Inc.
# Copyright 2011, 2014 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.
@@ -32,27 +32,31 @@ noinst_LTLIBRARIES = libcontrol.la
# L3CallControl.cpp
libcontrol_la_SOURCES = \
L3TermCause.cpp \
L3CallControl.cpp \
PagingEntry.cpp \
L3Handover.cpp \
L3SMSControl.cpp \
L3MobilityManagement.cpp \
L3CallControl.cpp \
L3StateMachine.cpp \
L3SupServ.cpp \
L3MMLayer.cpp \
L3TranEntry.cpp \
L3LogicalChannel.cpp \
L3Utils.cpp \
L3Handover.cpp \
TMSITable.cpp \
ControlTransfer.cpp \
RadioResource.cpp \
DCCHDispatch.cpp \
SMSCB.cpp \
RRLPServer.cpp
SMSCB.cpp
# TODO - move CollectMSInfo.cpp and RRLPQueryController.cpp to RRLP directory.
noinst_HEADERS = \
L3TermCause.h \
CodecSet.h \
PagingEntry.h \
L3Handover.h \
L3SMSControl.h \
L3CallControl.h \
L3MobilityManagement.h \
@@ -64,7 +68,4 @@ noinst_HEADERS = \
L3Utils.h \
ControlCommon.h \
ControlTransfer.h \
TMSITable.h \
RadioResource.h \
TMSITable.h \
RRLPServer.h
TMSITable.h

85
Control/PagingEntry.cpp Normal file
View File

@@ -0,0 +1,85 @@
/**@file Declarations for common-use control-layer functions. */
/*
* Copyright 2014 Range Networks, Inc.
*
* This software is distributed under multiple licenses;
* see the COPYING file in the main directory for licensing
* information for this specific distribution.
*
* 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.
*/
#define LOG_GROUP LogGroup::Control
#include "PagingEntry.h"
#include "L3MMLayer.h"
#include <GSML3RRMessages.h>
namespace Control {
NewPagingEntry::~NewPagingEntry()
{
if (mImmAssign) { delete mImmAssign; }
}
GSM::ChannelType NewPagingEntry::getGsmChanType() const
{
return mInitialChanType;
}
// This does a lot of mallocing.
L3MobileIdentity NewPagingEntry::getMobileId()
{
if (! mCheckedForTmsi) {
mCheckedForTmsi = true;
if (! mTmsi.valid()) {
uint32_t tmsi = gTMSITable.tmsiTabGetTMSI(mImsi,true);
LOG(DEBUG)<<"tmsiTabGetTMSI imsi="<<mImsi<< "returns"<<LOGVAR(tmsi);
if (tmsi) { mTmsi = tmsi; }
}
}
if (mTmsi.valid()) {
// page by tmsi
return L3MobileIdentity(mTmsi.value());
} else {
// page by imsi
return L3MobileIdentity(mImsi.c_str());
}
}
unsigned NewPagingEntry::getImsiMod1000() const
{
int len = mImsi.length();
if (len < 3) return 0; // This is bad.
return atoi(&mImsi.c_str()[len-3]);
}
void MMGetPages(NewPagingList_t &pages, bool wait)
{
assert(wait == false);
gMMLayer.mmGetPages(pages); // Blocks until there are some.
for (NewPagingList_t::iterator it = pages.begin(); it != pages.end(); it++) {
WATCH("page "<<*it);
}
}
string NewPagingEntry::text() const
{
ostringstream ss;
//const char *requestType=mNpeType==npeCC ? "CC" : mNpeType==npeSMS ? "SMS" : mNpeType==npeGPRS ? "GPRS" : "undefined";
//ss << "NewPagingEntry(" <<LOGVAR(requestType) <<LOGVAR(mImsi) <<LOGVAR(mTmsi) <<LOGHEX(mGprsClient) <<LOGVAR(mImmAssign) <<LOGVAR(mDrxBegin) <<")";
ss << "NewPagingEntry(" <<LOGVAR(mInitialChanType) <<LOGVAR(mImsi) <<LOGVAR(mTmsi) <<LOGHEX(mGprsClient) <<LOGVAR(mImmAssign) <<LOGVAR(mDrxBegin) <<")";
return ss.str();
}
std::ostream& operator<<(std::ostream& os, const NewPagingEntry&npe) { os <<npe.text(); return os; }
std::ostream& operator<<(std::ostream& os, const NewPagingEntry*npe) { os <<npe->text(); return os; }
};

81
Control/PagingEntry.h Normal file
View File

@@ -0,0 +1,81 @@
/**@file Declarations for common-use control-layer functions. */
/*
* Copyright 2014 Range Networks, Inc.
*
* This software is distributed under multiple licenses;
* see the COPYING file in the main directory for licensing
* information for this specific distribution.
*
* 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 _PAGING_H_
#define _PAGING_H_
#include <GSMCommon.h>
#include "ControlTransfer.h"
namespace GSM { class L3Frame; class L2LogicalChannel; class L3ImmediateAssignment; class L3MobileIdentity; }
namespace Control {
/** An entry in the paging list. */
struct NewPagingEntry {
GSM::ChannelType mInitialChanType;
// Each page has to be sent twice to make sure the handset hears it.
Int_z mSendCount; // Number of times this page has been sent.
// These fields are needed for all pages:
std::string mImsi; // Always provided.
// For GSM pages we need the following fields:
TMSI_t mTmsi; // A place for the pager to cache the tmsi if it finds one.
Bool_z mCheckedForTmsi; // A flag used by the pager to see if the imsi has been checked for tmsi.
// For GPRS pages we need the following fields:
GPRS::TBF *mGprsClient; // If non-NULL this is a GPRS client that needs service.
GSM::L3ImmediateAssignment *mImmAssign;
unsigned mDrxBegin; // GSM Frame number when DRX mode begins.
// Such a clever language.
NewPagingEntry(GSM::ChannelType wChanType, std::string &wImsi) :
/*mNpeType(wType),*/
mInitialChanType(wChanType),
mImsi(wImsi),
mGprsClient(NULL), mImmAssign(NULL), mDrxBegin(0)
{}
NewPagingEntry(const NewPagingEntry &other) : /*mNpeType(other.mNpeType),*/
mInitialChanType(other.mInitialChanType),
mImsi(other.mImsi),
mTmsi(other.mTmsi), mCheckedForTmsi(other.mCheckedForTmsi),
mGprsClient(NULL), mImmAssign(NULL), mDrxBegin(0)
{
//mTmsi = other.mTmsi;
//mCheckedForTmsi = other.mCheckedForTmsi
assert(other.mGprsClient == NULL);
assert(other.mImmAssign == NULL); // Since it is deleted when NewPagingEntry is deleted, it would be double deleted.
}
// { *this = other; }
~NewPagingEntry();
// Return the GSM channel type, assuming it is not a GPRS paging type.
GSM::ChannelType getGsmChanType() const;
GSM::L3MobileIdentity getMobileId();
unsigned getImsiMod1000() const;
string text() const;
};
std::ostream& operator<<(std::ostream& os, const NewPagingEntry&npe);
std::ostream& operator<<(std::ostream& os, const NewPagingEntry*npe);
typedef std::vector<NewPagingEntry> NewPagingList_t;
void MMGetPages(NewPagingList_t &pages, bool wait);
};
#endif

View File

@@ -1,281 +0,0 @@
/**@file RRLPServer */
/*
* Copyright 2008, 2009, 2010, 2011 Free Software Foundation, Inc.
* Copyright 2011, 2014 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/>.
*/
#define LOG_GROUP LogGroup::Control
#include <Timeval.h>
using namespace std;
#include "ControlCommon.h"
#include "L3MMLayer.h"
#include "RRLPServer.h"
#include <GSMLogicalChannel.h>
#include <GSML3MMMessages.h>
#include <GSML3RRMessages.h> // for L3ApplicationInformation
#include <Logger.h>
#include <Reporting.h>
namespace Control {
using namespace GSM;
// (pat) Notes:
// Relevant documents are GSM 3.71 and 4.31.
// LCS [Location Services] includes positioning using TOA [Time of Arrival] or E-OTD or GPS.
// E-OTD Enhanced Observed Time Difference - MS determines postion from synchronized cell beacons.
// MO-LR Mobile Originated Location Request.
// MT-LR Moble Terminated, where LCS client is external to PLMN
// NI-LR Network Induced, where LCS client is within LCS servier, eg, Emergency call Origination.
// NA-EXRD North American Emergency Services Routing Digits, identifies emergency services provider and LCS client, including cell.
// MLC, SMLC, GMLC: [Serving, Gateway] Mobile Location Center - the thing on the network that does this.
// Location Service Request may be LIR [Location Immediate Request] or LDR [Location Deferred Request].
// Location Preparation Procedure:
// NA
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;
}
RRLPServer::RRLPServer(string wSubscriber)
{
mTrouble = false;
mURL = gConfig.getStr("GSM.RRLP.SERVER.URL");
if (mURL.length() == 0) {
LOG(INFO) << "RRLP not configured";
mTrouble = true;
return;
}
//mDCCH = wLCH;
mName = wSubscriber;
// don't continue if IMSI has a RRLP support flag and it's false
//if IMEI tagging enabled, check if this IMEI (which is updated elsewhere) has RRLP disabled
//otherwise just go on
if (gConfig.getBool("Control.LUR.QueryIMEI")){
//check supported bit
// TODO : disabled because of upcoming RRLP changes and need to rip out direct SR access
//string supported= gSubscriberRegistry.imsiGet(mName, "RRLPSupported");
//if(supported.empty() || supported == "0"){
// LOG(INFO) << "RRLP not supported for " << mName;
// mTrouble = true;
//}
}
}
// send a location request
void RRLPServer::rrlpSend(L3LogicalChannel *chan)
{
mQuery = "query=loc";
// server encodes location request into apdu
serve();
if (mAPDUs.size() == 0) {
LOG(ERR) << "no apdu to send to mobile";
return;
}
if (mAPDUs.size() > 1) {
// there should only be extra apdus for query=assist
LOG(ERR) << "ignoring extra apdus for mobile";
}
string apdu = mAPDUs[0];
mAPDUs.clear();
BitVector2 bv(apdu.size()*4);
if (!bv.unhex(apdu.c_str())) {
LOG(INFO) << "BitVector::unhex problem";
return;
}
chan->l3sendm(L3ApplicationInformation(bv));
}
// decode a RRLP response from the mobile
void RRLPServer::rrlpRecv(const GSM::L3Message *resp)
{
if (!resp) {
LOG(INFO) << "NULL message";
return;
}
LOG(INFO) << "resp.pd = " << resp->PD();
if (resp->PD() != GSM::L3RadioResourcePD) {
LOG(INFO) << "didn't receive an RR message";
return;
}
if (resp->MTI() != L3RRMessage::ApplicationInformation) {
LOG(INFO) << "received unexpected RR Message " << resp->MTI();
return;
}
// looks like a good APDU
BitVector2 *bv2 = (BitVector2*)resp;
BitVector2 bv3 = bv2->tail(32);
ostringstream os;
bv3.hex(os);
string apdu = os.str();
mQuery = "query=apdu&apdu=" + apdu;
// server will decode the apdu
serve();
if (mResponse.find("latitude") != mResponse.end() &&
mResponse.find("longitude") != mResponse.end() &&
mResponse.find("positionError") != mResponse.end()) {
// TODO : disabled because of upcoming RRLP changes and need to rip out direct SR access
//if (!gSubscriberRegistry.RRLPUpdate(mName, mResponse["latitude"],
// mResponse["longitude"],
// mResponse["positionError"])){
// LOG(INFO) << "SR update problem";
//}
}
// TODO - a failed location request would be a good time to send assistance
// TODO - an "ack" response means send the next apdu if there is another; otherwise send a location request
}
// encodes or decodes apdus
void RRLPServer::serve()
{
string esc = "'";
string config = getConfig();
if (config.length() == 0) return;
string cmd = "wget -qO- " + esc + mURL + "?" + mQuery + config + esc; // (pat) Could use format() here.
LOG(INFO) << "*************** " << cmd;
FILE *result = popen(cmd.c_str(), "r");
if (!result) {
LOG(CRIT) << "popen call \"" << cmd << "\" failed";
return;
}
// build map of responses, and list of apdus
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") {
mAPDUs.push_back(rhs);
} else {
mResponse[lhs] = rhs;
}
}
free(line);
pclose(result);
if (mResponse.find("error") != mResponse.end()) {
LOG(INFO) << "error from server: " << mResponse["error"];
}
}
// (pat 9-20-2013) Message recommended by doug for RLLP testing.
void sendPirates(L3LogicalChannel *chan)
{
string bvs =
"0010010000010000010000000100111000000100000000000000111110000000000000000000000000000"
"0000000000000000000000000000000000000000000000000000000000000011011000011100101101100"
"1000000010000000000110111011110111010111010101011110100001110010110100101100100011011"
"1101010111110001111110111011110101111010100000000110000000010111111001100100001101100"
"0000101000010000110010010011111101100101011000100010000000011111111111110011001001111"
"0010000100111011011101000000000100000101001110110001100000000011000111010100000001101"
"0101011110011000101110001011111101111111101000110000111101110110100111011000000010000"
"0000000001010001000000000000000000000000000000000000000000000000000000000000000000000"
"0000000000000000000100011000011100101101100100000001000000000100100100100111000000000"
"0011100010111100111110101101011010000001101111000101110010111101111110001010100001100"
"0000001011010000011000000111100100100110000001010100001000011011100011100011111010101"
"1000100010000000100000000000000010011100111101111111010011010010011111111110111110100"
"1111011000000101001110011011001101001110100100111100010010011111001101100100111111110"
"1010010011110110011001100000000100000010001000000000110011100000000000000000000000000"
"0000000000000000000000000000000000000000000000000000000000000001101011001110010110110"
"0100000001000000000000001010010111010000000100110001100101111101011011101010110111001"
"0110111110000111010101000010001011001011010000000110101010110000000001001110010010100"
"1100110100001000011011010011101100110010101100010001000000001111111111000110001110100"
"0100101001011101110100011111111111111110100110101000111111001101001000100110010100000"
"0100010110000111001011101111010100111111110100100101110111001110010000000";
BitVector2 bv(bvs.c_str());
chan->l3sendm(L3ApplicationInformation(bv));
//L3Frame* resp = channel()->l2recv(130000);
//LOG(ALERT) << "RRLP returned " << *resp;
}
bool sendRRLP(GSM::L3MobileIdentity wMobileID, L3LogicalChannel *wLCH)
{
// (pat) DEBUG TEST...
sendPirates(wLCH); return true;
string sub = wMobileID.digits();
RRLPServer wRRLPServer(sub);
if (wRRLPServer.trouble()) return false;
wRRLPServer.rrlpSend(wLCH);
return true; // TODO - what to return here?
}
void recvRRLP(MMContext *wLCH, const GSM::L3Message *wMsg)
{
LOG(DEBUG) <<LOGVAR(wMsg);
string sub = string("IMSI") + "000000000000000"; // TODO - this will be call to getIMSI (not db one)
RRLPServer wRRLPServer(sub);
if (wRRLPServer.trouble()) return;
wRRLPServer.rrlpRecv(wMsg);
}
}; // namespace

View File

@@ -1,61 +0,0 @@
/**@file RRLPServer */
/*
* Copyright 2008, 2009, 2010 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/>.
*/
#ifndef RRLPSERVER_H
#define RRLPSERVER_H
#include <GSML3CommonElements.h> // For L3MobileIdentity
#include "ControlCommon.h"
namespace Control {
class RRLPServer
{
public:
RRLPServer(string wSubscriber);
bool trouble() { return mTrouble; }
void rrlpSend(L3LogicalChannel *mmchan);
void rrlpRecv(const GSM::L3Message*);
private:
RRLPServer(); // not allowed
void serve();
string mURL;
// GSM::L3MobileIdentity mMobileID;
//L3LogicalChannel *mDCCH; (pat) The channel is passed as an arg every time this is called, and it could change, so dont cache it.
string mQuery;
string mName;
bool mTrouble;
map<string,string> mResponse;
// Before assist can be restored, mAPDUs is going to have to go into more persistent storage.
// TransactionEntry for call? Dunno for sms or lur.
vector<string> mAPDUs;
};
bool sendRRLP(GSM::L3MobileIdentity wMobileID, L3LogicalChannel *wLCH);
void recvRRLP(MMContext *wLCH, const GSM::L3Message *wMsg);
};
#endif

View File

@@ -1,3 +1,18 @@
/*
* Copyright 2014 Range Networks, Inc.
*
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.
* This software is distributed under multiple licenses;
* see the COPYING file in the main directory for licensing
* information for this specific distribution.
*
* This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details.
*/
#include "Configuration.h"
#include "RRLPQueryController.h"

View File

@@ -1,10 +1,11 @@
/**@file SMSCB Control (L3), GSM 03.41. */
/*
* Copyright 2010 Kestrel Signal Processing, Inc.
* Copyright 2014 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.
* information for this specific distribution.
*
* This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details.
@@ -14,6 +15,7 @@
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*/
#define LOG_GROUP LogGroup::Control
#include "ControlCommon.h"

View File

@@ -1,10 +1,11 @@
/*
* Copyright 2008, 2009, 2010 Free Software Foundation, Inc.
* Copyright 2010 Kestrel Signal Processing, Inc.
* Copyright 2014 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.
* information for this specific distribution.
*
* This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details.
@@ -15,7 +16,6 @@
*/
#define LOG_GROUP LogGroup::Control
#include <Logger.h>
#include <Globals.h>
@@ -284,7 +284,9 @@ int TMSITable::tmsiTabOpen(const char* wPath)
LOG(EMERG) << "Cannot enable WAL mode on database at " << wPath << ", error message: " << sqlite3_errmsg(mTmsiDB);
}
LOG(INFO) << "Opened TMSI table version "<<TmsiTableDefinition::tmsiTableVersion<< ":"<<wPath;
LOG(DEBUG) << "TEST sqlite3_db_filename:"<< sqlite3_db_filename(mTmsiDB,"main");
// (mike) 2014-06: to free ourselves of the in-tree sqlite3 code, the system library must be used.
// However, sqlite3_db_filename is not available in Ubuntu 12.04. Removing dependence for now.
LOG(DEBUG) << "TEST sqlite3_db_filename:"<< wPath;//sqlite3_db_filename(mTmsiDB,"main");
tmsiTabInit();
return 0;
}
@@ -320,7 +322,7 @@ void TMSITable::tmsiTabSetAuthAndAssign(string imsi,int auth, int assigned)
}
#endif
// This does nothing if the IMSI is not found inthe table.
// This does nothing if the IMSI is not found in the table.
void TMSITable::tmsiTabSetRejected(string imsi,int rejectCode)
{
char query[100];
@@ -444,7 +446,7 @@ unsigned TMSITable::allocateTmsi()
void TMSITable::tmsiTabUpdate(string imsi, TmsiTableStore *store)
{
LOG(NOTICE) << "update entry for"<<LOGVAR(imsi) <<LOGVAR(store->auth_changed)<<LOGVAR(store->auth)<<LOGVAR(store->assigned_changed)<<LOGVAR(store->assigned);
LOG(INFO) << "update entry for"<<LOGVAR(imsi) <<LOGVAR(store->auth_changed)<<LOGVAR(store->auth)<<LOGVAR(store->assigned_changed)<<LOGVAR(store->assigned);
ScopedLock lock(sTmsiMutex,__FILE__,__LINE__); // This lock should be redundant - sql serializes access, but it may prevent sql retry failures.
TSqlUpdate q1;
@@ -534,7 +536,7 @@ uint32_t TMSITable::tmsiTabCreateOrUpdate(
LOG(ALERT) << "TMSI creation failed for"<<LOGVAR(imsi)<<" query:"<<*queryp;
return 0;
}
LOG(NOTICE) << (isNewRecord ? "new" : "updated") <<" entry for"<<LOGVAR(imsi)<<LOGHEX(tmsi);
LOG(INFO) << (isNewRecord ? "new" : "updated") <<" entry for"<<LOGVAR(imsi)<<LOGHEX(tmsi);
if (sendTmsis) { // double check to make sure the database entry made it.
unsigned tmsicheck;
@@ -560,7 +562,7 @@ uint32_t TMSITable::tmsiTabAssign(const string imsi, const GSM::L3LocationAreaId
uint32_t tmsi = 0;
// Create a new record.
LOG(NOTICE) << "new entry for"<<LOGVAR(imsi)<<LOGVAR(tmsi);
LOG(INFO) << "new entry for"<<LOGVAR(imsi)<<LOGVAR(tmsi);
unsigned now = (unsigned)time(NULL);
TSqlInsert query; query.reserve(150);
bool sendTmsis = configSendTmsis();
@@ -619,7 +621,7 @@ uint32_t TMSITable::assign(const string imsi, const GSM::L3LocationAreaIdentity
uint32_t tmsi = 0;
// Create a new record.
LOG(NOTICE) << "new entry for"<<LOGVAR(imsi)<<LOGVAR(tmsi);
LOG(INFO) << "new entry for"<<LOGVAR(imsi)<<LOGVAR(tmsi);
string query;
query.append("INSERT INTO TMSI_TABLE (");
bool sendTmsis = configSendTmsis();
@@ -835,7 +837,7 @@ vector< vector<string> > TMSITable::tmsiTabView(int verbosity, bool rawFlag, uns
}
void TMSITable::tmsiTabDump(int verbosity,bool rawFlag, ostream& os, bool showAll) const
void TMSITable::tmsiTabDump(int verbosity,bool rawFlag, ostream& os, bool showAll, bool taboption) const
{
// Dump the TMSI table.
unsigned maxrows = showAll ? 2^31 : 100;
@@ -861,7 +863,7 @@ void TMSITable::tmsiTabDump(int verbosity,bool rawFlag, ostream& os, bool showAl
view.push_back(tmp);
}
printPrettyTable(view,os);
printPrettyTable(view,os,taboption);
#if 0 // previous tmsi table dumping code.
sqlite3_stmt *stmt;

View File

@@ -1,10 +1,11 @@
/*
* Copyright 2008, 2009, 2010 Free Software Foundation, Inc.
* Copyright 2010 Kestrel Signal Processing, Inc.
* Copyright 2014 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.
* information for this specific distribution.
*
* This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details.
@@ -15,8 +16,6 @@
*/
#ifndef TMSITABLE_H
#define TMSITABLE_H
@@ -160,7 +159,7 @@ class TMSITable {
std::vector< std::vector<std::string> > tmsiTabView(int verbosity, bool rawFlag, unsigned maxrows) const;
/** Write entries as text to a stream. */
void tmsiTabDump(int verbosity,bool rawFlag,std::ostream&, bool ShowAll = false) const;
void tmsiTabDump(int verbosity,bool rawFlag,std::ostream&, bool ShowAll = false, bool taboption = false) const;
/** Clear the table completely. */
void tmsiTabClear();