Files
openbts/Control/L3StateMachine.cpp

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