mirror of
https://github.com/RangeNetworks/openbts.git
synced 2025-11-02 12:53:15 +00:00
merge 5.0 preview from commercial
This commit is contained in:
75
Control/CodecSet.h
Normal file
75
Control/CodecSet.h
Normal 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
|
||||
@@ -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.
|
||||
|
||||
@@ -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; }
|
||||
|
||||
};
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
33
Control/L3Handover.h
Normal 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
|
||||
@@ -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.
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
@@ -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?
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
486
Control/L3TermCause.cpp
Normal 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
97
Control/L3TermCause.h
Normal 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
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
85
Control/PagingEntry.cpp
Normal 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
81
Control/PagingEntry.h
Normal 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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user