mirror of
https://github.com/RangeNetworks/openbts.git
synced 2025-10-24 00:23:50 +00:00
1100 lines
43 KiB
C++
1100 lines
43 KiB
C++
/**@file Declarations for Circuit Switched State Machine and related classes. */
|
|
/*
|
|
* 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 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.
|
|
|
|
*/
|
|
// 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>
|
|
#include <GSML3RRMessages.h>
|
|
#include <GSML3MMMessages.h>
|
|
#include <SMSMessages.h>
|
|
#include <GSMConfig.h>
|
|
#include <RRLPServer.h>
|
|
#include <Globals.h>
|
|
#include <typeinfo>
|
|
|
|
using namespace GSM;
|
|
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 MachineStatusAuthorizationFail = MachineStatus(MachineStatus::MachineCodeQuitChannel);
|
|
MachineStatus MachineStatusUnexpectedState = MachineStatus(MachineStatus::MachineCodeUnexpectedState);
|
|
|
|
std::ostream& operator<<(std::ostream& os, MachineStatus::MachineStatusCode status)
|
|
{
|
|
#define CASE1(x) case MachineStatus::x: os<<#x; break;
|
|
switch (status) {
|
|
CASE1(MachineCodeOK)
|
|
CASE1(MachineCodePopMachine)
|
|
//CASE1(MachineCodeUnexpectedMessage)
|
|
//CASE1(MachineCodeUnexpectedPrimitive)
|
|
CASE1(MachineCodeUnexpectedState)
|
|
//CASE1(MachineCodeAuthorizationFail)
|
|
CASE1(MachineCodeQuitTran)
|
|
CASE1(MachineCodeQuitChannel)
|
|
}
|
|
return os;
|
|
}
|
|
|
|
|
|
//void MachineBase::timerStartAbort(L3TimerId tid, unsigned val) { tran()->timerStartAbort(tid,val); }
|
|
|
|
void MachineBase::timerStart(L3TimerId tid, unsigned val, int nextState)
|
|
{
|
|
tran()->timerStart(tid,val,nextState);
|
|
}
|
|
|
|
void MachineBase::timerStop(L3TimerId tid)
|
|
{
|
|
tran()->timerStop(tid);
|
|
}
|
|
|
|
void MachineBase::timerStopAll()
|
|
{
|
|
tran()->timerStopAll();
|
|
}
|
|
|
|
void MachineBase::machineErrorMessage(int level, int state, const L3Message *l3msg, const SIP::DialogMessage *sipmsg, const char *format)
|
|
{
|
|
ostringstream os;
|
|
os << pthread_self() << Utils::timestr() << " " <<debugName() <<":" <<format;
|
|
|
|
// This kind sucks, digging into the Logger. The logger could be better.
|
|
if (l3msg) {
|
|
Log(level).get() <<os <<" Unexpected L3 message:"<<l3msg;
|
|
} else if (sipmsg) {
|
|
Log(level).get() <<os <<" Unexpected SIP message:"<<sipmsg;
|
|
} else {
|
|
Log(level).get() <<os <<" Unexpected"<<LOGHEX(state);
|
|
}
|
|
}
|
|
|
|
MachineStatus MachineBase::unexpectedState(int state, const L3Message*l3msg)
|
|
{
|
|
if (l3msg) {
|
|
LOG(INFO)<<"Unexpected message ignored:"<<l3msg->text();
|
|
} else {
|
|
LOG(INFO)<<"Unexpected state:"<<state;
|
|
}
|
|
// Just keep going. This may be wrong.
|
|
return MachineStatusUnexpectedState;
|
|
}
|
|
|
|
// compiler says this is ambiguous - why? does it think it convert from L3TimerId to const char *?
|
|
bool MachineBase::timerExpired(L3TimerId tid) { return tran()->L3TimerList::timerExpired(tid); }
|
|
|
|
MMContext *MachineBase::getContext() const { return mTran->teGetContext(); }
|
|
|
|
void MachineBase::machText(std::ostream&os) const
|
|
{
|
|
os <<" Machine=(";
|
|
os <<debugName()<<LOGVAR2("tid",tran()->tranID()) <<" ";
|
|
if (channel()) { os <<channel()->descriptiveString(); } else { os <<"(no chan)"; }
|
|
os <<LOGVAR2("CCState",tran()->getGSMState());
|
|
os <<LOGVARM(mPopState);
|
|
os <<")";
|
|
//if (mSipHandlerState >= 0) os<<LOGVAR(mSipHandlerState);
|
|
//for (unsigned i = 0; i < mNumTimers; i++) {
|
|
// if (mTimers[i].active()) {
|
|
// os<<" timer "<<i<<" rem="<<mTimers[i].remaining()<<" nextState="<<mTimeoutNextState[i];
|
|
// }
|
|
//}
|
|
}
|
|
|
|
string MachineBase::machText() const
|
|
{
|
|
ostringstream os; machText(os); return os.str();
|
|
}
|
|
|
|
|
|
MachineStatus MachineBase::machPush(
|
|
MachineBase*wCalledProcedure, // The L3Procedure we are calling.
|
|
int wNextState) // The state in the current procedure to which we will return when nextProcedure is popped.
|
|
{
|
|
mPopState = wNextState;
|
|
tran()->tePushProcedure(wCalledProcedure);
|
|
// TODO: This may need a recursive call to handleMachineStatus
|
|
return this->callMachStart(wCalledProcedure);
|
|
}
|
|
|
|
MachineStatus MachineBase::closeChannel(RRCause rrcause,Primitive prim,TermCause upstreamCause)
|
|
{
|
|
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.
|
|
// This is not quite right: The ReleaseRequest state is for sending a CC Release, not an RR ChannelRelease.
|
|
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(rrcause,prim,upstreamCause); // TODO: Remove, now redundant.
|
|
return MachineStatus::QuitChannel(upstreamCause);
|
|
}
|
|
|
|
#if UNUSED
|
|
ControlLayerException MachineBase::procHandleL3Msg(GSM::L3Message *l3msg, L3LogicalChannel *lch)
|
|
{
|
|
// Look up the message in the Procedure message table.
|
|
int state = findMsgMap(l3msg->PD(),l3msg->MTI());
|
|
if (state == -1) {
|
|
// If state is -1, message is not mapped and is ignored by us.
|
|
return L3OK(); // TODO: Return what indicator?
|
|
}
|
|
IF you use this again: procInvoke was replaced by calls to handleMachineStatus inside lockAnd... methods of TranEntry
|
|
procInvoke(state,l3msg,NULL);
|
|
}
|
|
#endif
|
|
|
|
#if UNUSED
|
|
// The proc may be this, ie, tran->currentProcedure()
|
|
MachineStatus MachineBase::callProcState(MachineBase *wProc, unsigned state)
|
|
{
|
|
tran()->teSetProcedure(wProc,false);
|
|
return wProc->machineRunState(state);
|
|
}
|
|
#endif
|
|
|
|
// This switches to the specifed Procedure and starts it. It is equivalent to a long goto.
|
|
// It is also legal for the procedure to already be the current procedure, in which case we just start it.
|
|
MachineStatus MachineBase::callMachStart(MachineBase *wProc, unsigned startState)
|
|
{
|
|
tran()->teSetProcedure(wProc,false);
|
|
LOG(DEBUG) << "start Procedure:"<<wProc->machText();
|
|
// TODO: Call handleMachineStatus(MachineStatus status)
|
|
return tran()->handleRecursion(wProc->machineRunState1(startState)); // Unless the StateMachine starts with a message, the start state must be state 0 in every Machine.
|
|
}
|
|
|
|
L3LogicalChannel* MachineBase::channel() const {
|
|
return tran()->channel();
|
|
}
|
|
|
|
|
|
CallState MachineBase::getGSMState() const { return tran()->getGSMState(); }
|
|
|
|
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); }
|
|
|
|
unsigned MachineBase::getL3TI() const { return mTran->getL3TI(); }
|
|
|
|
bool MachineBase::isL3TIValid() const {
|
|
return getL3TI() != TranEntry::cL3TIInvalid;
|
|
}
|
|
|
|
|
|
// TODO: Current this twiddles the RRLP status flags;
|
|
// the whole RRLP server needs to turn into some kind of state machine so we can tell what is going on,
|
|
// and whether a status message might be for RRLP or something else.
|
|
static void handleStatus(const L3Message *l3msg, MMContext *mmchan)
|
|
{
|
|
assert(l3msg->MTI() == L3RRMessage::RRStatus);
|
|
|
|
const L3RRStatus *statusMsg = dynamic_cast<typeof(statusMsg)>(l3msg);
|
|
if (!statusMsg) {
|
|
LOG(ERR) << "Could not cast Layer 3 RR Status message?";
|
|
return;
|
|
}
|
|
int cause = statusMsg->cause().causeValue();
|
|
|
|
switch (cause) {
|
|
case 97:
|
|
LOG(INFO) << "Received RR status message with cause: message not implemented";
|
|
// (pat) TODO: Figure out if this is in response to RRLP or not...
|
|
//Rejection code only useful if we're gathering IMEIs
|
|
if (gConfig.getBool("Control.LUR.QueryIMEI")){
|
|
// flag unsupported in SR so we don't waste time on it again
|
|
string imsi = mmchan->mmGetImsi(false); // with false flag, empty if no imsi
|
|
if (imsi.size() >= 14 && isdigit(imsi[0])) { // Cheap partial validation
|
|
imsi = string("IMSI") + imsi;
|
|
// TODO : disabled because of upcoming RRLP changes and need to rip out direct SR access
|
|
//if (gSubscriberRegistry.imsiSet(imsi, "RRLPSupported", "0")) {
|
|
// LOG(INFO) << "SR update problem";
|
|
//}
|
|
}
|
|
}
|
|
return;
|
|
case 98:
|
|
LOG(INFO) << "Received RR status message with cause: message type not compatible with protocol state";
|
|
return;
|
|
default:
|
|
LOG(INFO) << "Received RR Status with"<<LOGHEX(cause);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// code copied from DCCHDispatchMessage() and descendents.
|
|
// Return TRUE if handled.
|
|
// 3GPP 4.08 4.5.1.1 says "Upon receiving a CM service request" the network may start any common MM or RR procedure including
|
|
// classmark interrogation, identification, authentication and cypering.
|
|
static bool handleCommonMessages(const L3Message *l3msg, MMContext *mmchan, bool *deletemsg)
|
|
{
|
|
*deletemsg = true;
|
|
switch (L3CASE_RAW(l3msg->PD(),l3msg->MTI())) {
|
|
case L3CASE_RR(L3RRMessage::PagingResponse):
|
|
NewPagingResponseHandler(dynamic_cast<const L3PagingResponse*>(l3msg),mmchan);
|
|
return true;
|
|
case L3CASE_RR(L3RRMessage::ApplicationInformation):
|
|
return true;
|
|
case L3CASE_RR(L3RRMessage::RRStatus):
|
|
handleStatus(l3msg,mmchan);
|
|
return true;
|
|
case L3CASE_MM(L3MMMessage::LocationUpdatingRequest):
|
|
//LocationUpdatingController(dynamic_cast<const L3LocationUpdatingRequest*>(req),mmchan);
|
|
//tran->lockAndStart(new L3ProcedureLocationUpdate(tran), (L3Message*)req);
|
|
// TODO: Should we check that this an appropriate time to start it?
|
|
LURInit(l3msg,mmchan);
|
|
return true;
|
|
case L3CASE_MM(L3MMMessage::IMSIDetachIndication): {
|
|
// (pat) TODO, but it is not very important.
|
|
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);
|
|
*deletemsg = false;
|
|
return true;
|
|
default:
|
|
break;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
#if UNUSED
|
|
MachineStatus MachineBase::machineRunL3Msg(int state, const GSM::L3Message *l3msg)
|
|
{
|
|
bool deleteit;
|
|
if (handleCommonMessages(l3msg, channel(),&deleteit)) {
|
|
LOG(DEBUG) << "message handled by handleCommonMessages"<<LOGVAR(l3msg);
|
|
if (deleteit) delete l3msg;
|
|
return MachineStatusOK;
|
|
}
|
|
//int state = L3CASE_RAW(l3msg->PD(),l3msg->MTI());
|
|
LOG(DEBUG) <<"calling machineRunState "<<LOGHEX(state)<<machText()<<LOGVAR(l3msg);
|
|
return machineRunState(state,l3msg,NULL);
|
|
}
|
|
#endif
|
|
|
|
MachineStatus handlePrimitive(const L3Frame *frame, L3LogicalChannel *lch)
|
|
{
|
|
switch (frame->primitive()) {
|
|
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;
|
|
default:
|
|
// We took all the other primitives out of the message stream in csl3HandleFrame
|
|
assert(0);
|
|
//case GSM::HANDOVER_ACCESS:
|
|
// // TODO: test that this is on TCHFACH not SDCCH.
|
|
// assert(0); // Someone higher handled this.
|
|
// //ProcessHandoverAccess(lch);
|
|
// return MachineStatusQuitChannel;
|
|
//
|
|
//case DATA:
|
|
//case UNIT_DATA:
|
|
// assert(0); // Caller checked this.
|
|
//
|
|
//case ERROR: ///< channel error
|
|
// // The LAPDM controller was aborted.
|
|
// lch->chanRelease(RELEASE); // Kill off all the transactions associated with this channel.
|
|
// return MachineStatusQuitChannel;
|
|
//
|
|
//case HARDRELEASE:
|
|
// if (frame->getSAPI() == 0) { lch->chanRelease(HARDRELEASE); } // Release the channel.
|
|
// return MachineStatusQuitChannel;
|
|
//default:
|
|
// LOG(ERR) <<lch<<"unhandled primitive: " << frame->primitive(); // This is horrible. But lets warn instead of crashing.
|
|
// // Fall through
|
|
//case RELEASE: ///< normal channel release
|
|
// if (frame->getSAPI() == 0) { lch->chanRelease(RELEASE); }
|
|
// return MachineStatusQuitChannel;
|
|
}
|
|
}
|
|
|
|
// The state machines may receive messages via machineRunState or machineRunState1.
|
|
// This is the extended version that lets the state machine handle the primitives, unparseable messages,
|
|
// and includes the frame, too, if it is available.
|
|
// Generally either frame or l3msg or sipmsg will be non-NULL.
|
|
// The l3msg will be provided instead of the frame if we were invoked by lockAndStart with just an l3msg,
|
|
// but the state machine should know that.
|
|
// l3msg may be NULL for primitives or unparseable messages.
|
|
MachineStatus MachineBase::machineRunState1(int state, const L3Frame *frame, const L3Message *l3msg, const SIP::DialogMessage *sipmsg)
|
|
{
|
|
LOG(DEBUG)<<LOGHEX(state)<<LOGVAR(frame)<<LOGVAR(l3msg)<<LOGVAR(sipmsg);
|
|
if (frame) {
|
|
// Handle the primitives so the state machine does not have to.
|
|
if (!frame->isData()) { return handlePrimitive(frame,channel()); }
|
|
}
|
|
if (l3msg || sipmsg || state < 256) {
|
|
LOG(DEBUG) <<"calling machineRunState "<<LOGHEX(state)<<machText()<<LOGVAR(l3msg);
|
|
return machineRunState(state,l3msg,sipmsg);
|
|
}
|
|
return MachineStatusOK; // Ignore unparseable messages.
|
|
}
|
|
|
|
// l3msg may be NULL for primitives or unparseable messages.
|
|
MachineStatus MachineBase::dispatchFrame(const L3Frame *frame, const L3Message *l3msg)
|
|
{
|
|
int state;
|
|
if (frame) {
|
|
if (frame->isData()) {
|
|
// Note: the frame MTI must be ANDed with 0xbf. See 4.08 10.4. I moved that into class L3Frame.
|
|
state = L3CASE_RAW(frame->PD(),frame->MTI());
|
|
} else {
|
|
state = L3CASE_PRIMITIVE(frame->primitive());
|
|
}
|
|
} else if (l3msg) {
|
|
state = L3CASE_RAW(l3msg->PD(),l3msg->MTI());
|
|
} else {
|
|
LOG(ERR) << "called with no arguments?";
|
|
return MachineStatusOK;
|
|
}
|
|
return machineRunState1(state,frame,l3msg);
|
|
#if 0
|
|
if (frame->isData()) {
|
|
if (L3Message *msg = parseL3(*frame)) {
|
|
LOG(DEBUG) <<channel() <<" received L3 message "<<*msg;
|
|
// Manufacture an integral state from the msg.
|
|
// Note we overload pd==0 (Group Call Control) for naked states in the state machines, which is ok because
|
|
// we dont support Group Call and even if we did, the Group Call MTIs (GSM 04.68 9.3) will probably not collide
|
|
// because there are no small numberic MTIs.
|
|
int state = L3CASE_RAW(frame->PD(),frame->MTI());
|
|
LOG(DEBUG) <<"calling machineRunState1 "<<LOGHEX(state)<<machText()<<LOGVAR(frame);
|
|
//MachineStatus result = machineRunL3Msg(state, msg);
|
|
MachineStatus result = machineRunState1(state, frame, msg, NULL);
|
|
delete msg;
|
|
return result;
|
|
} else {
|
|
LOG(ERR) <<channel()<< " received unparseable Layer3 frame "<<*frame;
|
|
//old: lch->chanGetContext(true)->mmDispatchError(PD,MTI,lch);
|
|
return MachineStatusOK; // Was not handled, but the caller cant do anything more with this frame.
|
|
}
|
|
} else {
|
|
Primitive primitive = frame->primitive();
|
|
int state = L3CASE_PRIMITIVE(primitive);
|
|
LOG(DEBUG) <<"calling machineRunState1 "<<LOGHEX(state)<<machText()<<LOGVAR(primitive);
|
|
return machineRunState1(state);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// The procedure is pre-locked by the caller.
|
|
MachineStatus MachineBase::dispatchTimeout(L3Timer*timer)
|
|
{
|
|
// A timer with no explicit next state specified just kills off the Transaction.
|
|
int nextState = timer->tNextState();
|
|
LOG(DEBUG) <<LOGVAR(nextState)<<machText() <<this;
|
|
if (nextState >= 0) {
|
|
return machineRunState1(nextState);
|
|
} else if (nextState == TimerAbortTran) {
|
|
// TODO: How do we kill it?
|
|
// 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: {
|
|
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 MachineStatus::QuitTran(cause);
|
|
} else if (nextState == TimerAbortChan) {
|
|
LOG(INFO) << "Timer "<<timer->tName()<<" timeout";
|
|
// This indirectly causes immediate destruction of all transactions on this channel.
|
|
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));
|
|
}
|
|
}
|
|
|
|
|
|
MachineStatus MachineBase::dispatchSipDialogMsg(const SIP::DialogMessage *dialogmsg)
|
|
{
|
|
return machineRunState1(L3CASE_DIALOG_STATE(dialogmsg->dialogState()),(L3Frame*)NULL,(L3Message*)NULL,dialogmsg);
|
|
}
|
|
|
|
|
|
MachineStatus MachineBase::dispatchL3Msg(const L3Message *l3msg)
|
|
{
|
|
int state = L3CASE_RAW(l3msg->PD(),l3msg->MTI());
|
|
LOG(DEBUG) <<"calling machineRunState1 "<<LOGHEX(state)<<machText()<<LOGVAR(l3msg);
|
|
// We pass a NULL frame, but any state machine that gets messages via this function will know that.
|
|
return machineRunState1(state,(L3Frame*)NULL,l3msg);
|
|
}
|
|
|
|
|
|
#if UNUSED
|
|
bool CSL3StateMachine::csl3Write(GenericL3Msg *msg)
|
|
{
|
|
if (1) { //l3rewrite())
|
|
LOG(DEBUG) << "Received "<<msg->typeName()<<" message for l3rewrite";
|
|
mCSL3Fifo.write(msg);
|
|
return true; // Our code is going to deal with this message.
|
|
} else {
|
|
LOG(DEBUG) << "Received "<<msg->typeName()<<" message, handling with version 1 code";
|
|
// Caller must deal with this message with pre-existing version 1 code.
|
|
return false;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
|
|
#if UNUSED // now it is
|
|
// TODO: This might as well be in MMContext.
|
|
static void csl3HandleLCHMsg(GSM::L3Message *l3msg, L3LogicalChannel *lch)
|
|
{
|
|
// Do we have one or more transactions already running on this logical channel?
|
|
// There can be multiple transactions on the same channel.
|
|
// What is to prevent an MS from allocating multiple channels, eg, a TCH and SDCCH simultaneously?
|
|
// 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 handleCommonMessages"<<LOGVAR(l3msg);
|
|
delete l3msg;
|
|
return;
|
|
}
|
|
|
|
bool handled = lch->chanGetContext(true)->mmDispatchL3Msg(l3msg,lch);
|
|
|
|
//TranEntry *tran = gNewTransactionTable.ttFindByL3Msg(l3msg,lch);
|
|
//bool handled = false;
|
|
//if (tran) {
|
|
// OBJLOG(DEBUG) <<"received l3msg for dcch:"<<*lch<<" l3msg="<<*l3msg<<LOGVAR(tran->text());
|
|
// // TODO: Do we ever need a way for the Procedure to tell us that a message was completely handled
|
|
// // and that we should not examine it further?
|
|
// if (tran->lockAndInvokeL3Msg(l3msg,lch)) handled++;
|
|
//}
|
|
|
|
if (!handled) {
|
|
LOG(DEBUG) <<"unhandled l3msg" <<LOGVAR(l3msg) <<lch->chanGetContext(false);
|
|
}
|
|
delete l3msg;
|
|
}
|
|
#endif
|
|
|
|
// 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 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.
|
|
// One of these comes in when the channel is inited.
|
|
// Pat took out this gReports temporarily because it is delaying channel establishment.
|
|
// gReports.incr("OpenBTS.GSM.RR.ChannelSeized");
|
|
return true;
|
|
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 L3_DATA:
|
|
case L3_UNIT_DATA:
|
|
return true;
|
|
|
|
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);
|
|
|
|
// 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(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.
|
|
//} else {
|
|
// // This is a bad thing when happening on an active channel.
|
|
// // Link either closed by peer or lost due to timeout in a lower layer.
|
|
// // The LAPDm is already released so we cannot send any more messages.
|
|
// // Just drop the channel.
|
|
// lch->chanLost(); // Kill off all the transactions associated with this channel.
|
|
//}
|
|
|
|
// 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(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;
|
|
// The primitives apply directly to the L3LogicalChannel and a specific SAPI, rather than to the MMContext.
|
|
if (! checkPrimitive(frame->primitive(), lch, frame->getSAPI())) { return; }
|
|
// Everything else runs on the MMContext.
|
|
MMContext *mmchan = lch->chanGetContext(true);
|
|
if (frame->isData()) {
|
|
l3msg = parseL3(*frame);
|
|
if (l3msg) {
|
|
WATCHINFO(lch <<" received L3 message "<<*l3msg);
|
|
} else {
|
|
LOG(ERR) <<lch<< " received unparseable Layer3 frame "<<*frame;
|
|
// We pass unparseable messages through to machineRunState1 to provide an error indication to
|
|
// any state machine that uses that function, but the default machrineRunState1 eliminates them
|
|
// so machineRunState never sees them.
|
|
}
|
|
bool deleteit;
|
|
if (l3msg && handleCommonMessages(l3msg, mmchan, &deleteit)) {
|
|
LOG(DEBUG) << "message handled by handleCommonMessages"<<LOGVAR(l3msg);
|
|
if (deleteit) { delete l3msg; }
|
|
return;
|
|
}
|
|
}
|
|
mmchan->mmDispatchL3Frame(frame,l3msg);
|
|
if (l3msg) delete l3msg;
|
|
}
|
|
|
|
#if UNUSED
|
|
// Handle timeouts. Return the next timeout or -1 if no timeout is pending.
|
|
int CSL3StateMachine::csl3HandleTimers()
|
|
{
|
|
ScopedLock lock(gNewTransactionTable.mLock,__FILE__,__LINE__);
|
|
int nextTimeout = -1;
|
|
for (NewTransactionMap::iterator itr = gNewTransactionTable.mTable.begin(); itr!=gNewTransactionTable.mTable.end(); ++itr) {
|
|
TranEntry *tran = itr->second;
|
|
if (tran->deadOrRemoved()) continue;
|
|
tran->checkTimers();
|
|
}
|
|
for (NewTransactionMap::iterator itr = gNewTransactionTable.mTable.begin(); itr!=gNewTransactionTable.mTable.end(); ++itr) {
|
|
TranEntry *tran = itr->second;
|
|
int remaining = tran->remainingTime();
|
|
if (remaining >= 0) {
|
|
nextTimeout = (nextTimeout == -1) ? remaining : min(nextTimeout,remaining);
|
|
}
|
|
LOG(DEBUG)<<"transaction "<<tran->tranID()<<LOGVAR(nextTimeout);
|
|
}
|
|
return nextTimeout;
|
|
}
|
|
#endif
|
|
|
|
#if UNUSED
|
|
// Under GSM this could block as long as a downlink LAPDm message can block.
|
|
void CSL3StateMachine::csl3HandleSipMsg(SIP::DialogMessage *sipmsg)
|
|
{
|
|
bool found = false;
|
|
TranEntry *tran = gNewTransactionTable.ttFindById(sipmsg->mTranId);
|
|
if (tran && ! tran->deadOrRemoved()) {
|
|
found = true;
|
|
LOG(DEBUG) << "sip message code="<<sipmsg->mSIPStatusCode <<" handled by trans "<<tran->tranID();
|
|
// Call deletes it
|
|
tran->lockAndInvokeSipMsg(sipmsg);
|
|
}
|
|
#if 0
|
|
ScopedLock lock(gNewTransactionTable.mLock,__FILE__,__LINE__);
|
|
int code = sipmsg->sipStatusCode();
|
|
LOG(DEBUG) << "Received Dialog message "<<LOGVAR(callid)<<LOGVAR(sipmsg);
|
|
// TODO: We should have a back pointer from sip so we dont have to search for this. It might go in the as yet non-existent mobility management layer.
|
|
for (TransactionMap::iterator itr = gNewTransactionTable.mTable.begin(); itr!=gNewTransactionTable.mTable.end(); ++itr) {
|
|
TranEntry *tran = itr->second;
|
|
if (tran->deadOrRemoved()) continue;
|
|
if (tran->SIPCallID() == callid) {
|
|
found = true;
|
|
LOG(DEBUG) << "sip message code="<<code <<" handled by trans "<<tran->tranID();
|
|
tran->lockAndInvokeSipMsg(sipmsg);
|
|
}
|
|
}
|
|
#endif
|
|
if (!found) { LOG(DEBUG) << "sip message code="<<sipmsg->mSIPStatusCode <<" no matching transaction found."; }
|
|
}
|
|
#endif
|
|
|
|
#if UNUSED
|
|
void CSL3StateMachine::csl3HandleMsg(GenericL3Msg *gmsg)
|
|
{
|
|
switch (gmsg->ml3Type) {
|
|
case GenericL3Msg::MsgTypeLCH:
|
|
csl3HandleFrame(gmsg->ml3frame,gmsg->ml3ch);
|
|
break;
|
|
case GenericL3Msg::MsgTypeSIP:
|
|
csl3HandleSipMsg(gmsg->mSipMsg /*, gmsg->mCallId*/);
|
|
break;
|
|
default: assert(0);
|
|
}
|
|
delete gmsg; // Deletes the L3Frame in the GenericL3Msg as well.
|
|
}
|
|
#endif
|
|
|
|
/**
|
|
Update vocoder data transfers in both directions.
|
|
@param transaction The transaction object for this call.
|
|
@param TCH The traffic channel for this call.
|
|
@return bytes transferred.
|
|
*/
|
|
static unsigned newUpdateCallTraffic(TranEntry *transaction, GSM::TCHFACCHLogicalChannel *TCH)
|
|
{
|
|
// We dont set the state Active until both SIP dialog and MS side have acked.
|
|
LOG(DEBUG);
|
|
if (transaction->getGSMState() != CCState::Active) { return false; }
|
|
unsigned activity = 0;
|
|
|
|
// Both the rx and tx directions block if rtp_session_set_blocking_mode() is true.
|
|
|
|
// Transfer in the uplink direction (GSM->RTP).
|
|
// Flush FIFO to limit latency.
|
|
unsigned numFlushed = 0;
|
|
{
|
|
unsigned maxQ = gConfig.getNum("GSM.MaxSpeechLatency");
|
|
static Timeval testTimeStart;
|
|
while (TCH->queueSize()>maxQ) {
|
|
if (numFlushed == 0) testTimeStart.now();
|
|
numFlushed++;
|
|
// (pat) LOG is too slow to call in here. With 1000 backed up the delay is 1/2 sec.
|
|
//LOG(DEBUG) <<TCH <<" ulFrame flushed"<<LOGVAR(TCH->queueSize());
|
|
delete TCH->recvTCH();
|
|
}
|
|
if (numFlushed) { LOG(DEBUG) <<TCH <<" ulFrame flushed "<<numFlushed <<" in "<<testTimeStart.elapsed() << " msecs"; }
|
|
}
|
|
|
|
|
|
if (SIP::AudioFrame *ulFrame = TCH->recvTCH()) {
|
|
activity += ulFrame->sizeBytes();
|
|
// Send on RTP.
|
|
LOG(DEBUG) <<TCH <<LOGVAR(*ulFrame);
|
|
transaction->txFrame(ulFrame,numFlushed);
|
|
delete ulFrame;
|
|
}
|
|
|
|
// Transfer in the downlink direction (RTP->GSM).
|
|
// 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 (SIP::AudioFrame *dlFrame = transaction->rxFrame()) {
|
|
activity += dlFrame->sizeBytes();
|
|
if (activity == 0) { activity++; } // Make sure we signal activity.
|
|
LOG(DEBUG) <<TCH <<LOGVAR(*dlFrame);
|
|
TCH->sendTCH(dlFrame);
|
|
}
|
|
|
|
// Return a flag so the caller will know if anything transferred.
|
|
LOG(DEBUG) <<LOGVAR(activity);
|
|
return activity;
|
|
}
|
|
|
|
// Check for any message or timer activity on this channel. Return true if something happened.
|
|
// The delay is how often we poll, used on SDCCH.
|
|
static bool checkemMessages(L3LogicalChannel *dcch, int delay)
|
|
{
|
|
LOG(DEBUG) <<LOGVAR(dcch) <<LOGVAR(delay);
|
|
// (pat) For l3Rewrite: there are two contending solutions for the uplink message path:
|
|
// They can be sent in a common queue to the global L3 message handler (similar to UMTS)
|
|
// or be handled by this dedicated channel thread called from DCCHDispatcher.
|
|
// This is the code for the latter.
|
|
// (pat) Can messages on FACCH be for transactions other than the current one? Not sure, but
|
|
// be safe and handle the message with the generic L3 handler instead of dispatching it directly to this transaction.
|
|
|
|
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;
|
|
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.
|
|
// }
|
|
#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
|
|
// locked for up to 30 seconds when sending to LAPDm, which would then stall that L3 thread.
|
|
if (set->mmCheckSipMsgs()) {
|
|
LOG(DEBUG) <<dcch<< " Sip message processed";
|
|
return true; // Must go see if transaction terminated as a result of dialog message.
|
|
}
|
|
|
|
// Check all timers in all transactions on this channel.
|
|
if (set->mmCheckTimers()) {
|
|
LOG(DEBUG) <<dcch<< " timer processed";
|
|
return true; // Must go see if transaction terminated as a result of timer.
|
|
}
|
|
|
|
// Note: This initiates the channel release if all transactions are finished.
|
|
if (set->mmCheckNewActivity()) {
|
|
LOG(DEBUG) <<dcch<< " new activity finished";
|
|
return true;
|
|
}
|
|
LOG(DEBUG) <<dcch<<" returning, nothing to do";
|
|
|
|
return false; // No messages found.
|
|
}
|
|
|
|
|
|
// Handle TCH traffic during a call. This is called from the dedicated thread created for a combination I full rate TCH in GSMConfig.
|
|
// It is called from DCCHDispatch.
|
|
// Prior to L3rewrite TCH traffic and messages were handled by callManagementLoop(), which called pollInCall(), updateGSMSignalling()
|
|
static void l3CallTrafficLoop(L3LogicalChannel *dcch)
|
|
{
|
|
assert(dcch->chtype() == FACCHType);
|
|
GSM::TCHFACCHLogicalChannel *tch = dynamic_cast<typeof(tch)>(dcch);
|
|
// The gReports call invokes sqlite3 which takes tenths of seconds, which is too long
|
|
// during a voice call, and especially a handover, so only call gReports if nothing else is pending.
|
|
bool needReports = true;
|
|
int nextDelay = 0;
|
|
|
|
//LOG(INFO) << "call connected "<<*tran;
|
|
size_t fCount = 0; // A rough count of frames.
|
|
// chanRunning checks for loss in L3. radioFailure checks for loss in L2.
|
|
// 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() && !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(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;
|
|
}
|
|
// The voiceTrans will not yet be set when this loop starts.
|
|
// The MS establishes SABM over LAPDm which sends up an ESTABLISH primitive,
|
|
// then we get the AssignmentComplete L3 message over this FACCH channel,
|
|
// which calls the controling state machine to set voiceTrans.
|
|
RefCntPointer<TranEntry> rtran = dcch->chanGetVoiceTran();
|
|
TranEntry *tran = rtran.self();
|
|
LOG(DEBUG) <<dcch <<" main loop"<<LOGVAR(nextDelay)<<" found tran:"<<(tran!=NULL); // warning: tran may be NULL.
|
|
if (tran != NULL) {
|
|
if (tran->deadOrRemoved()) {
|
|
// This is ok. If there is something else ongoing (eg SMS) when the voice call ends
|
|
// 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::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.
|
|
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.
|
|
// 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(L3_HARDRELEASE_REQUEST,TermCause::Local(L3Cause::Handover_Outbound));
|
|
return; // We wont be back.
|
|
}
|
|
}
|
|
|
|
// Any Q.931 timer expired?
|
|
// TODO: Remove this, redundant with the below.
|
|
if (tran->checkTimers()) { // This calls a handler function and resets the timer.
|
|
LOG(DEBUG) <<dcch <<" after checkTimers";
|
|
continue; // The transaction is probably defunct.
|
|
}
|
|
}
|
|
|
|
// Look for messages or timers.
|
|
// (pat 8-6-2013) We have an architectural problem that we do not have a separate thread handling SAPI3
|
|
// so the in-call SMS handler hangs for several seconds (in the best case.) The rxFrame below also hangs.
|
|
// In order not to wait too long to service of these two things, I am adding a super-hack to alternate servicing them.
|
|
// I think we need to add another thread to run SAPI3.
|
|
if (++alternate % 2) {
|
|
if (checkemMessages(dcch,nextDelay)) { gResetWatchdog(); nextDelay = 0; continue; }
|
|
}
|
|
nextDelay = 0;
|
|
LOG(DEBUG) <<dcch <<" after checkem Messages";
|
|
|
|
// Finally, get down to business: transfer vocoder data.
|
|
// This is a blocking call, blocking 20ms on average.
|
|
if (tran != NULL) {
|
|
static unsigned bytes = 0;
|
|
if (unsigned theseBytes = newUpdateCallTraffic(tran,tch)) {
|
|
bytes += theseBytes;
|
|
// Print one of the log messages just once per second.
|
|
//static time_t lasttime = 0;
|
|
//time_t now = time(NULL);
|
|
//if (now != lasttime) { LOG(DEBUG) "call traffic" <<LOGVAR(bytes) <<LOGVAR(dcch) <<LOGVAR(tran); }
|
|
LOG(DEBUG) <<dcch <<" call traffic" <<LOGVAR(bytes) <<LOGVAR(tran);
|
|
//lasttime = now;
|
|
// If anything happened, then the call is still up.
|
|
fCount++;
|
|
// On average, each one takes 20ms, so 50 is a msec, and 60*50 is one second.
|
|
if ((fCount%(60*50)) == 0) {
|
|
LOG(DEBUG) <<dcch <<" reset watchdog";
|
|
gResetWatchdog();
|
|
LOG(DEBUG) <<dcch <<" after reset watchdog";
|
|
needReports = true;
|
|
}
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// (pat) When we are using blocking mode in the RTP library, we never get here.
|
|
if (needReports) {
|
|
LOG(DEBUG) <<dcch << "calling gReports CC.CallMinutes";
|
|
gReports.incr("OpenBTS.GSM.CC.CallMinutes");
|
|
LOG(DEBUG) <<dcch << " after gReports CC.CallMinutes";
|
|
needReports = false;
|
|
continue;
|
|
}
|
|
LOG(DEBUG) <<dcch <<" end of loop";
|
|
|
|
// 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
|
|
// an identification and optional authorization procedure on the channel itself. That is true for both GSM and UMTS.
|
|
// Once we have an IMSI, we can create TranEntrys for each procedure that is MO, and also move each
|
|
// each MTC or MT-SMS TranEntry onto this channel.
|
|
// If there was a race between MTC and MOC it needs to be resolved at the same time, although that may turn out to be a no-op, ie,
|
|
// resolved by whether the MS sent CMServiceRequest or PagingResponse first. If the former, the MT calls need to turn into call-waiting things.
|
|
// Note MS could send multiple simultaneous MO CMServiceRequests - one for CS and one for SMS.
|
|
// FOR NOW:
|
|
// Just have a primary transaction on the channel, which is known from the GSMState aka CallState.
|
|
static void L3SDCCHLoop(L3LogicalChannel*dcch)
|
|
{
|
|
assert(dcch->chtype() == SDCCHType);
|
|
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);
|
|
// (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;
|
|
}
|
|
|
|
// Any L3 Messages from the MS side on this channel?
|
|
// Wait 100ms. I made this number up out of thin air.
|
|
// Since an incoming L3 message will be returned instantly, this delay is the effective delay
|
|
// in handling SIP messages or timers, and could be much longer since none of those are precision timers.
|
|
if (checkemMessages(dcch,100)) { gResetWatchdog(); continue; }
|
|
}
|
|
}
|
|
|
|
// dcch may be SDCCH or FACCH.
|
|
// This does not return until the channel is released.
|
|
void L3DCCHLoop(L3LogicalChannel*dcch, L3Frame *frame)
|
|
{
|
|
LOG(INFO) <<"DCCH LOOP OPEN "<<dcch;
|
|
try {
|
|
// We must not reset the channel state when opened because during a channel reassignment the new channel
|
|
// already has an attached MMContext.
|
|
assert(frame);
|
|
Primitive prim = frame->primitive();
|
|
delete frame;
|
|
|
|
dcch->chanSetState(L3LogicalChannel::chEstablished);
|
|
switch (prim) {
|
|
case L3_ESTABLISH_INDICATION:
|
|
break;
|
|
case HANDOVER_ACCESS:
|
|
ProcessHandoverAccess(dcch);
|
|
// If the handover fails, it sets the chState such that the loop below will return immediately,
|
|
// so we can just break here.
|
|
break;
|
|
default:
|
|
assert(0); // Caller prevented anything else.
|
|
}
|
|
|
|
switch (dcch->chtype()) {
|
|
case SDCCHType:
|
|
L3SDCCHLoop(dcch);
|
|
break;
|
|
case FACCHType:
|
|
l3CallTrafficLoop(dcch);
|
|
break;
|
|
default:
|
|
assert(0);
|
|
}
|
|
devassert(dcch->mChState != L3LogicalChannel::chIdle); // This would be a bug.
|
|
} catch (exception &e) {
|
|
LOG(ERR) << "exception "<<e.what() << " " << typeid(&e).name();
|
|
} catch (...) {
|
|
LOG(ERR) << "unrecognized exception, channel reset";
|
|
}
|
|
WATCHINFO("DCCH LOOP EXIT " << dcch);
|
|
|
|
// Always reset, even though the MMContext is shared between L3LogicalChannels during channel reassignment;
|
|
// we now have a refcnt in the MMContext so this is bullet proof destruction.
|
|
dcch->L3LogicalChannelReset();
|
|
switch (dcch->mChState) {
|
|
case L3LogicalChannel::chRequestRelease:
|
|
// 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(L3_RELEASE_REQUEST); // WARNING! This must be the only place in L3 that sends this primitive.
|
|
break;
|
|
case L3LogicalChannel::chRequestHardRelease:
|
|
dcch->l3sendp(L3_HARDRELEASE_REQUEST);
|
|
break;
|
|
default: break;
|
|
}
|
|
LOG(DEBUG) <<"CLOSE "<<dcch << " dump all:" <<gMMLayer.printMMInfo();
|
|
|
|
dcch->chanSetState(L3LogicalChannel::chIdle);
|
|
LOG(DEBUG) << "DCCHLoop exiting "<<dcch;
|
|
}
|
|
|
|
#if UNUSED_BUT_SAVE_FOR_UMTS // but may be used for UTMS
|
|
void CSL3StateMachine::csl3ServiceLoop()
|
|
{
|
|
if (IS_LOG_LEVEL(DEBUG)) {
|
|
ostringstream os;
|
|
os <<"Transaction Table:";
|
|
if (gNewTransactionTable.dump(os,true)) { LOG(DEBUG)<<os.str(); }
|
|
}
|
|
try {
|
|
int timeout = -1;
|
|
// Process all messages in the queue first, for no particular reason.
|
|
if (mCSL3Fifo.size() == 0) {
|
|
// Invoke Procedures to process timeouts and also determine the next timeout.
|
|
timeout = csl3HandleTimers();
|
|
}
|
|
GenericL3Msg *msg;
|
|
if (timeout >= 0) {
|
|
msg = mCSL3Fifo.read(timeout); // wait only until the next timer expires.
|
|
LOG(DEBUG) "read(timeout="<<timeout<<") returned:"<<msg;
|
|
} else {
|
|
msg = mCSL3Fifo.read(); // wait forever.
|
|
LOG(DEBUG) "read() returned:"<<msg;
|
|
}
|
|
gResetWatchdog();
|
|
if (msg) { // If timeout, there will be no msg.
|
|
csl3HandleMsg(msg);
|
|
}
|
|
}
|
|
// FIXME: copy catch code from DCCHDispatcher()
|
|
catch (...) {
|
|
LOG(ERR) << "unhandled exception in CSL3StateMachine";
|
|
}
|
|
}
|
|
|
|
|
|
static void *csl3ThreadLoop(void *unusedArg)
|
|
{
|
|
while (1) { gCSL3StateMachine.csl3ServiceLoop(); }
|
|
return NULL;
|
|
}
|
|
|
|
void CSL3StateMachine::csl3Start()
|
|
{
|
|
mCSL3Thread = new Thread;
|
|
//thread->start((void*(*)(void*))Control::DCCHDispatcher,chan);
|
|
mCSL3Thread->start(csl3ThreadLoop,NULL);
|
|
}
|
|
|
|
CSL3StateMachine gCSL3StateMachine;
|
|
CSL3StateMachine::CSL3StateMachine() : mCSL3Thread(NULL) {}
|
|
#endif
|
|
|
|
void l3start()
|
|
{
|
|
// We are not doing it this way in GSM. Each channel has its own service loop.
|
|
// if (l3rewrite()) { gCSL3StateMachine.csl3Start(); }
|
|
}
|
|
|
|
|
|
}; // namespace
|