Files
openbts/Control/L3CallControl.cpp
2014-07-16 23:57:22 +02:00

1303 lines
56 KiB
C++

/*
* 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
#include <GSML3CCElements.h>
#include "ControlCommon.h"
#include "L3CallControl.h"
#include "L3StateMachine.h"
#include "L3TranEntry.h"
#include "L3MMLayer.h"
#include "L3SupServ.h"
#include <SIPDialog.h>
#include <Peering.h>
#include <GSMCommon.h>
#include <GSML3Message.h>
#include <GSMLogicalChannel.h>
#include <GSML3SSMessages.h>
#include <CLI.h>
namespace Control {
using namespace GSM;
using namespace SIP;
// The base class for CC [Call Control]
// SS messages may be sent to CC transactions.
class CCBase : public SSDBase {
protected:
MachineStatus handleIncallCMServiceRequest(const GSM::L3Message *l3msg);
//MachineStatus stateRecvHold(const GSM::L3Hold*);
MachineStatus defaultMessages(int state, const GSM::L3Message*);
bool isVeryEarly();
CCBase(TranEntry *wTran) : SSDBase(wTran) {}
MachineStatus closeCall(TermCause cause);
MachineStatus sendReleaseComplete(TermCause cause, bool sendCause);
MachineStatus sendRelease(TermCause cause, bool sendCause);
void handleTerminationRequest();
};
class MOCMachine : public CCBase {
enum State {
stateStartUnused, // Reserve 0 superstitiously.
stateCCIdentResult,
stateAssignTCHFSuccess,
};
bool mIdentifyResult;
MachineStatus sendCMServiceReject(MMRejectCause rejectCause,bool fatal);
MachineStatus handleSetupMsg(const GSM::L3Setup *setup);
MachineStatus serviceAccept();
// The MOC is created by an initial CMServiceRequest:
// |-----------CMServiceRequest------------>| | old: CMServiceResponder calls MOCProcedure
// |<-------Authentication Procedure------->| | resolveIMSI
// |<-----------CMServiceAccept ------------| | MOCProcedure
// |-------------L3Setup(SDCH)------------->| | MOCProcedure
// |<----------CC-CALL PROCEEDING-----------|------------INVITE----------->| MOCProcedure,MOCSendINVITE
public:
MachineStatus machineRunState(int state, const GSM::L3Message* l3msg, const SIP::DialogMessage *sipmsg);
MOCMachine(TranEntry *wTran) : CCBase(wTran) {}
const char *debugName() const { return "MOCMachine"; }
}; // mMOCProcedure;
class AssignTCHMachine : public CCBase {
enum State {
stateStart,
stateAssignTimeout
};
// We have to suspend processing of SIP messages while we are busy changing channels.
// We just let these messages go by, then after we get the new TCH (if we do),
// we will send any l3 messages required by the then-current sip state.
void sendReassignment();
// The reassignment timer is how long we try to send reassignments; it should not abort the transaction
// immediately when it expires or it might abort a successful reassignment, so this timer must not be in the L3TimerId list.
//
Timeval TChReassignment;
protected:
// |<----------ChannelModeModify------------| SIPState=Starting | MOCProcedure if veryEarly
// |----------ChannelModeModifyAck--------->| | MOCProcedure if veryEarly
// | call assignTCHF | | (used only for EA; for VEA we assigned TCH in AccessGrantResponder)
// |<---------L3AssignmentCommand-----------| | assignTCHF, repeated until answered
// |--------AssignmentComplete(FACCH)------>| | DCCHDispatchRR,AssignmentCompleteHandler, calls MOCController or MTCController
public:
MachineStatus machineRunState(int state, const GSM::L3Message* l3msg, const SIP::DialogMessage *sipmsg);
AssignTCHMachine(TranEntry *wTran) : CCBase(wTran) {}
const char *debugName() const { return "AssignTCHMachine"; }
}; // mAssignTCHFProcedure;
class MTCMachine : public CCBase {
enum State {
stateStart,
statePostChannelChange,
};
public:
MachineStatus machineRunState(int state, const GSM::L3Message* l3msg, const SIP::DialogMessage *sipmsg);
MTCMachine(TranEntry *wTran) : CCBase(wTran) {}
const char *debugName() const { return "MTCMachine"; }
};
class InboundHandoverMachine : public CCBase {
bool mReceivedHandoverComplete;
enum State {
stateStart,
};
public:
MachineStatus machineRunState(int state, const GSM::L3Message* l3msg, const SIP::DialogMessage *sipmsg);
InboundHandoverMachine(TranEntry *wTran) : CCBase(wTran), mReceivedHandoverComplete(false) {}
const char *debugName() const { return "InboundHandoverMachine"; }
};
class InCallMachine : public CCBase {
enum State {
stateStart
};
bool mDtmfSuccess;
char mDtmfKey; // Encoded as used inside L3KeypadFacility
void acknowledgeDtmf();
public:
MachineStatus machineRunState(int state, const GSM::L3Message* l3msg, const SIP::DialogMessage *sipmsg);
//MachineStatus machineRunSipMsg(const SIP::DialogMessage *sipmsg);
InCallMachine(TranEntry *wTran) : CCBase(wTran) {}
const char *debugName() const { return "InCallMachine"; }
};
// MOCMachine: Mobile Originated Call State Machine
// GSM 4.08 5.2.1 Mobile originating call establishment.
// On entry phone has an RR connection but is trying to get the CM connection established.
// We run MM procedures (identify, authenticate) to associate the RR LogicalChannel with a MMUser.
// Process up through receiving the L3Setup message.
// This message indicates establishment of both an MM Layer (ie, LocationUpdating has been performed 4.08 4.5.1) and a CM Layer connection.
void startMOC(const GSM::L3MMMessage *l3msg, MMContext *dcch, CMServiceTypeCode serviceType)
{
LOG(DEBUG) <<dcch;
TranEntry *tran = TranEntry::newMOC(dcch,serviceType);
MOCMachine *mocp = new MOCMachine(tran);
// The message is CMServiceRequest.
tran->lockAndStart(mocp,(GSM::L3Message*)l3msg);
}
#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;
//}
#endif
// Identical to teCloseCallNow.
MachineStatus CCBase::sendReleaseComplete(TermCause cause, bool sendCause)
{
LOG(INFO) << "SIP term info sendReleaseComplete"<<LOGVAR(cause); // SVGDBG&pat
tran()->teCloseCallNow(cause,sendCause);
return MachineStatus::QuitTran(cause);
}
MachineStatus CCBase::sendRelease(TermCause cause, bool sendCause)
{
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.
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.
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();
return MachineStatusOK; // We are waiting for a ReleaseComplete
}
} else {
// The transaction is already dead. Kill the state machine and the next layer will send the RR Release.
LOG(DEBUG) << gMMLayer.printMMInfo();
return MachineStatus::QuitTran(cause);
}
}
// Perform a network initiated clearing 24.008 5.4. If things look ok send a disconnect and continue waiting for a Release message,
// 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(TermCause cause)
{
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();
// We dont have to send a disconnect at all, but if you dont, the phone may report that the call "failed".
CallState ccstate = tran()->getGSMState();
if (ccstate == CCState::Active) {
if (1) {
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,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,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:"<<cause <<gMMLayer.printMMInfo());
setGSMState(CCState::NullState); // redundant, we are deleting this transaction.
LOG(DEBUG) << gMMLayer.printMMInfo();
if (IS_LOG_LEVEL(DEBUG)) { CommandLine::printChansV4(cout,false); }
LOG(DEBUG) <<"finish";
// 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 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()
{
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);
}
// See: callManagementDispatchGSM
// TODO (pat) 2-2014: The MS can send DTMF messages before the call is connected; currently we just ignore them, which works,
// but we should probably at least send a reject.
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()) {
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::Service_Or_Option_Not_Available));
return MachineStatusOK; // ignore bad message otherwise.
}
case L3CASE_MM(CMServiceAbort): {
const L3CMServiceAbort *cmabort = dynamic_cast<typeof(cmabort)>(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();
// 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.
timerStopAll();
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!
// (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::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.
// (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();
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.
// (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);
// handle RRLP answer.
// TODO
return MachineStatusOK;
}
case L3CASE_SS(Register):
case L3CASE_SS(ReleaseComplete):
case L3CASE_SS(Facility): {
return handleSSMessage(l3msg);
}
default:
return unexpectedState(state,l3msg);
}
}
MachineStatus CCBase::handleIncallCMServiceRequest(const GSM::L3Message *l3msg)
{
const GSM::L3CMServiceRequest *cmsrq = dynamic_cast<typeof(cmsrq)>(l3msg);
assert(cmsrq);
// SMS submission? The rest will happen on the SACCH.
if (cmsrq->serviceType().type() == GSM::L3CMServiceType::ShortMessage) {
PROCLOG(INFO) << "in call SMS submission on " << *channel();
//FIXME:
//InCallMOSMSStarter(transaction);
//LCH->l3sendm(GSM::L3CMServiceAccept());
return MachineStatusOK;
}
// For now, we are rejecting anything else.
PROCLOG(NOTICE) << "cannot accept additional CM Service Request from " << tran()->subscriber();
// Can never be too verbose.
// (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, bool fatal)
{
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); }
// GSM 04.08 5.2.1.2
// This is where we set the TI [Transaction Identifier] in the TranEntry to what the MS sent us in the L3Setup message.
// We also start the SIP dialog now.
MachineStatus MOCMachine::handleSetupMsg(const L3Setup *setup)
{
// pat fixed. See comments at MOCInitiated. setGSMState(CCState::MOCInitiated);
PROCLOG(INFO) << *setup;
gReports.incr("OpenBTS.GSM.CC.MOC.Setup");
if (setup->mFacility.mExtant) WATCH(setup); // USSD DEBUG!
// See GSM 04.07 11.2.3.1.3.
// Set the high bit, since this TI came from the MS.
// Set l3ti before calling any aborts so we will handle the response to the MS properly.
// (pat) The MS will continue to use the original TI (without the high bit set) when it communicates with us,
// and We need to set the high bit only when we send an L3TI to the MS.
tran()->setL3TI(setup->TI() | 0x08);
tran()->setCodecs(setup->getCodecSet());
string calledNumber;
{
if (!setup->haveCalledPartyBCDNumber()) {
// 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";
// 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();
}
// Start a new SIP Dialog, which sends an INVITE.
PROCLOG(DEBUG) << "SIP start engine";
//getDialog()->dialogOpen(tran()->subscriberIMSI());
//const char * imsi = tran()->subscriberIMSI(); // someday these will be a strings already
// The sipDialogMOC creates the SIP Dialog and sends the INVITE.
// The setDialog associates the new dialog with this transaction.
SipDialog *dialog = SipDialog::newSipDialogMOC(tran()->tranID(),tran()->subscriber(),calledNumber,tran()->getCodecs(), channel());
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";
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.
// Once we can start SIP call setup, send Call Proceeding.
// (pat) 4.08 5.2.1.2 says we are supposed to verify the number before sending call proceeding.
// (pat) TODO: I dont think this is right - supposed to wait for SIP proceeding before sending this.
PROCLOG(INFO) << "Sending Call Proceeding, transaction:" <<tran();
channel()->l3sendm(GSM::L3CallProceeding(getL3TI()));
setGSMState(CCState::MOCProceeding);
return MachineStatusOK;
}
// FIXME -- At this point, verify that the subscriber has access to this service.
// If the subscriber isn't authorized, send a CM Service Reject with
// cause code, 0x41, "requested service option not subscribed",
// followed by a Channel Release with cause code 0x6f, "unspecified".
// Otherwise, proceed to the next section of code.
// For now, we are assuming that the phone won't make a call if it didn't
// get registered.
MachineStatus MOCMachine::serviceAccept()
{
GPRS::GPRSNotifyGsmActivity(tran()->subscriber().mImsi.c_str());
// Allocate a TCH for the call, if we don't have it already.
// TODO: This should be a function in MMContext.
if (!isVeryEarly()) {
if (! channel()->reassignAllocNextTCH()) {
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.
LOG(INFO) << "SIP term info closeChannel called in serviceAccept";
return closeChannel(L3RRCause::Normal_Event,L3_RELEASE_REQUEST,cause);
}
}
// Let the phone know we're going ahead with the transaction.
PROCLOG(INFO) << "sending CMServiceAccept";
channel()->l3sendm(GSM::L3CMServiceAccept());
// We are now waiting for a L3Setup message.
// We could attach the MMContext to the MMUser at any time but it might start receiving calls or SMS immediately,
// so we are going to wait until this call kicks off, which may be safer.
return MachineStatusOK;
}
// This is used both for MOC and emergency calls, which are differentiated by the service type in the CMServiceRequest message.
// (pat) The Blackberry will attempt an MOC even if periodic LUR returned unauthorized!
MachineStatus MOCMachine::machineRunState(int state, const GSM::L3Message *l3msg, const SIP::DialogMessage *sipmsg)
{
PROCLOG2(DEBUG,state)<<LOGVAR(l3msg)<<LOGVAR(sipmsg)<<LOGVAR2("msid",tran()->subscriber());
switch (state) {
// This is the start state:
case L3CASE_MM(CMServiceRequest): {
timerStart(T303,T303ms,TimerAbortTran); // MS side: start CMServiceRequest sent; stop CallProceeding received.
// This is both the start state and a request to start a new MO SMS when one is already in progress, as per GSM 4.11 5.4
setGSMState(CCState::MOCInitiated);
const L3CMServiceRequest *req = dynamic_cast<typeof(req)>(l3msg);
const GSM::L3MobileIdentity &mobileID = req->mobileID(); // Reference ok - the SM is going to copy it.
return machPush(new L3IdentifyMachine(tran(),mobileID, &mIdentifyResult), stateCCIdentResult);
}
case stateCCIdentResult: {
// TODO: We may want an option to return an immediate CM service reject if this BTS is not configured
// to handle calls, for example, if it is an SMS-only server or such like.
// The L3IdentifyMachine checks for emergency calls, but we will check again here to be sure.
if (mIdentifyResult) {
return serviceAccept();
} else {
// If handset is not in TMSI table We do not return any programmable failure codes here,
// 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::IMSI_Unknown_In_VLR,true);
}
}
#if 0 // (pat) 9-15-2013: replaced with code to call the common L3IdentifyMachine.
case L3CASE_MM(CMServiceRequest): {
const L3CMServiceRequest *req = dynamic_cast<typeof(req)>(l3msg);
// We dont want to leave our GSMState indicator in NullState once we start
// doing things here, so we want to change the state to something.
// On receipt of CMServiceRequest we are doing MM procedures so you would think there is
// no CC state yet, but apparently that is not the case; see comments at CCState::MOCInitiated,
// indicating that this state is correct.
setGSMState(CCState::MOCInitiated);
// There is no specific timer in the documentation on the network side for this case.
// T303 is defined on the MS side, and we use that. It is a generic 30 second timer.
timerStart(T303,T303ms,TimerAbortTran); // MS side: start CMServiceRequest sent; stop CallProceeding received.
// If we got a TMSI, find the IMSI.
// Note that this is a copy, not a reference.
GSM::L3MobileIdentity mobileID = req->mobileID();
// ORIGINAL CODE: resolveIMSI(mobileID,LCH);
// I think other messages are errors during this part of the state diagram, but the old
// code ignored them (rather, it set the state improperly which error was later corrected)
// while waiting for the RR AssignmentComplete message so I will too.
// Pat says: Take care that RRLP does not use up the 30 second T303 timer running in the MS now.
if (gConfig.getBool("Control.Call.QueryRRLP.Early")) {
// TODO...
}
// Have an imsi already?
if (mobileID.type()==IMSIType) {
string imsi(mobileID.digits());
tran()->setSubscriberImsi(string(mobileID.digits()),true);
if (!gTMSITable.tmsiTabCheckAuthorization(imsi)) {
return sendCMServiceReject(L3RejectCause::Requested_Service_Option_Not_Subscribed,true);
}
return serviceAccept();
}
// If we got a TMSI, find the IMSI.
if (mobileID.type()==TMSIType) {
unsigned authorized;
string imsi = gTMSITable.tmsiTabGetIMSI(mobileID.TMSI(),&authorized);
if (imsi.size()) {
// TODO: We need to authenticate this.
// But for now, just accept it.
tran()->setSubscriberImsi(imsi,true);
if (!authorized) {
return sendCMServiceReject(L3RejectCause::Requested_Service_Option_Not_Subscribed,true);
}
return serviceAccept();
}
}
// Still no IMSI? Ask for one.
// 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));
return MachineStatusOK;
}
// TODO: This should be moved to an MM Identify procedure run before starting the MOC.
case L3CASE_MM(IdentityResponse): {
timerStop(T3270);
const L3IdentityResponse *resp = dynamic_cast<typeof(resp)>(l3msg);
L3MobileIdentity mobileID = resp->mobileID();
if (mobileID.type()==IMSIType) {
string imsi(mobileID.digits());
tran()->setSubscriberImsi(imsi,true);
if (!gTMSITable.tmsiTabCheckAuthorization(imsi)) {
return sendCMServiceReject(L3RejectCause::Requested_Service_Option_Not_Subscribed,true);
}
return serviceAccept();
} else {
// FIXME -- This is quick-and-dirty, not following GSM 04.08 5.
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::Message_Type_Not_Compatible_With_Protocol_State,false);
}
return something
}
#endif
case L3CASE_CC(Setup): {
timerStop(T303);
if (getGSMState() == CCState::MOCProceeding) {
LOG(DEBUG) << "ignoring duplicate L3EmergencySetup";
return MachineStatusOK;
}
const L3Setup *msg = dynamic_cast<typeof(msg)>(l3msg);
MachineStatus stat = handleSetupMsg(msg);
if (stat != MachineStatusOK) { return stat; }
return machPush(new AssignTCHMachine(tran()), stateAssignTCHFSuccess);
}
case stateAssignTCHFSuccess: {
// We have just received our shiny new TCH. See if the SIP state changed while we were waiting;
// if so, the sip messages themselves were discarded, but invite response, if any, was saved.
// Take care: we are invoking a dialog state without passing the DialogMessage, which is gone.
return machineRunState(L3CASE_DIALOG_STATE(getDialog()->getDialogState()),NULL,NULL);
}
case L3CASE_SIP(dialogStarted): {
return MachineStatusOK; // It just means the dialog has not received an answer to the initial INVITE yet.
}
case L3CASE_SIP(dialogProceeding): {
// pat 2-2014: Tried out the L3Progress message to fix the ZTE lack of ring-back.
// I notice we are sending an invalid Progress value so it was worth a try, but did not help.
channel()->l3sendm(L3Progress(getL3TI()));
if (getGSMState() != CCState::MOCProceeding) { // No CCState change on receiving this message.
PROCLOG(ERR) << "MOC received SIP Progress message in unexpected GSM state:"<< getGSMState();
}
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);
// 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()));
#endif
setGSMState(CCState::MOCDelivered);
return MachineStatusOK;
}
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());
if (!encryptionAlgorithm) {
LOG(DEBUG) << "A5/3 and A5/1 not supported: NOT sending Ciphering Mode Command on " << *channel() << " for " << tran()->subscriberIMSI();
} else if (channel()->getL2Channel()->decryptUplink_maybe(tran()->subscriberIMSI(), encryptionAlgorithm)) {
LOG(DEBUG) << "sending Ciphering Mode Command on " << *channel() << " for IMSI" << tran()->subscriberIMSI();
channel()->l3sendm(GSM::L3CipheringModeCommand(
GSM::L3CipheringModeSetting(true, encryptionAlgorithm),
GSM::L3CipheringModeResponse(false)));
} else {
LOG(DEBUG) << "no ki: NOT sending Ciphering Mode Command on " << *channel() << " for IMSI" << tran()->subscriberIMSI();
}
}
channel()->l3sendm(L3Connect(getL3TI()));
setGSMState(CCState::ConnectIndication);
getDialog()->MOCInitRTP();
getDialog()->MOCSendACK();
return MachineStatusOK; // We are waiting for the ConnectAcknowledge.
}
case L3CASE_CC(ConnectAcknowledge): {
if (getDialog()->isActive()) {
// We're rolling. Fire up the in-call procedure.
setGSMState(CCState::Active);
return callMachStart(new InCallMachine(tran()));
} else if (getDialog()->isFinished()) {
// The SIP side hung up on us!
TermCause cause = dialog2TermCause(getDialog());
return closeCall(cause);
} else {
// Not possible.
PROCLOG(ERR) << "Connect Acknowledge received in incorrect SIP Dialog state:"<< getDialog()->getDialogState();
}
return callMachStart(new InCallMachine(tran()));
}
case L3CASE_RR(AssignmentComplete): { // Ignore duplicate message subsequent to AssignTCHF.
PROCLOG(INFO) << "Ignoring duplicate GSM AssignmentComplete " << *tran();
return MachineStatusOK;
}
case L3CASE_RR(ChannelModeModifyAcknowledge): { // Ignore duplicate message subsequent to AssignTCHF.
PROCLOG(INFO) << "Ignoring duplicate GSM ChannelModeModifyAcknowledge " << *tran();
return MachineStatusOK;
}
case L3CASE_SIP(dialogBye): {
// The other user hung up before we could finish.
return closeCall(dialog2ByeCause(getDialog()));
}
case L3CASE_SIP(dialogFail): {
// 0x11: "User Busy"; 0x7f "Interworking unspecified"
// (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?
MachineStatus MOCMachine::stateExpiredT303()
{
PROCLOG(INFO) << "T303 expired, closing channel and clearing";
return closeChannel(?); // no sip yet, just exit
}
#endif
default:
return defaultMessages(state,l3msg);
}
}
//=== AssignTCHMachine ===
// Replaces assignTCHF()
// TODO: This should move to MMContext.
void AssignTCHMachine::sendReassignment()
{
static const GSM::L3ChannelMode speechMode(GSM::L3ChannelMode::SpeechV1);
if (isVeryEarly()) { return; }
GSM::TCHFACCHLogicalChannel *tch = dynamic_cast<typeof(tch)>(channel()->mNextChan);
// FIXME - We should probably be setting the initial power here.
channel()->l3sendm(GSM::L3AssignmentCommand(tch->channelDescription(),speechMode));
}
MachineStatus AssignTCHMachine::machineRunState(int state, const GSM::L3Message *l3msg, const SIP::DialogMessage *sipmsg)
{
PROCLOG2(DEBUG,state)<<LOGVAR(l3msg)<<LOGVAR(sipmsg)<<LOGVAR2("imsi",tran()->subscriber());
static const GSM::L3ChannelMode speechMode(GSM::L3ChannelMode::SpeechV1);
//beginning:
switch (state) {
case stateStart: {
// If this timer goes off the assignment was (possibly) successful, at least it was not unsuccessful.
//onTimeout1(GSM::T3101ms-1000,stateAssignTimeout);
// We postpone processing any dialog messages until after this procedure.
//tran()->mSipDialogMessagesBlocked = true;
// (pat) The original assignTCHF code sent the L3Assignment multiple times.
// That shouldnt be necessary because it is using LAPDm, but I am going to duplicate the behavior.
if (isVeryEarly()) {
// For very early assignment, we gave the MS a TCH in the initial ImmediateAssignment but we need a mode change.
static const GSM::L3ChannelMode modemsg(GSM::L3ChannelMode::SpeechV1);
GSM::TCHFACCHLogicalChannel *tch = dynamic_cast<typeof(tch)>(channel());
channel()->l3sendm(L3ChannelModeModify(tch->channelDescription(),modemsg));
} else {
// For early (not "very early") assignment, we gave the MS an SDDCH in the initial ImmediateAssignment.
// Send the TCH assignment now.
// (pat) We do not support "late assignment" as defined in 4.08 7.3.2.
channel()->reassignStart();
// And I quote 4.08 11.1.2: "This timer [T3101] is started when a channel is allocated with an IMMEDIATE ASSIGNMENT message.
// It is stopped when the MS has correctly seized the channels."
// If we receive a reassignment failure we will resend the assignment, which often works the second time.
// The TChReassignment timer controls how long we will keep re-trying.
// We used to use 3 (T3101-1) seconds, but I dont think this time is related to T3101 and
// I dont know what the ultimate limit is, maybe nothing. I am going to up it just use T3101.
TChReassignment.future(T3101ms);
timerStart(T3101,T3101ms,stateAssignTimeout); // This timer will truly abort.
sendReassignment(); // Sets T3101 as a side effect, way down in L1Decoder.
}
return MachineStatusOK;
}
case L3CASE_RR(ChannelModeModifyAcknowledge): {
const GSM::L3ChannelModeModifyAcknowledge*ack = dynamic_cast<typeof(ack)>(l3msg);
bool modeOK = (ack->mode()==speechMode);
// if (!modeOK) return abortAndRemoveCall(transaction,LCH,GSM::L3Cause(0x06));
// (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(TermCause::Local(L3Cause::Channel_Unacceptable));
return MachineStatusPopMachine;
}
// We retry this loop in case there are stray messages in the channel.
// On some phones, we see repeated Call Confirmed messages on MTC.
//case stateAssignRetry: // We are sending the TCH assignment on the old SDCCH.
// DCCH->l3sendm(GSM::L3AssignmentCommand(TCH->channelDescription(),GSM::L3ChannelMode(GSM::L3ChannelMode::SpeechV1)));
// return MachineStatusOK;
// This arrives on the new FACCH, however, the channel() comes from the Context which is still mapped to the old channel,
// but reassignmentComplete knows this.
case L3CASE_RR(AssignmentComplete): {
timerStop(T3101);
channel()->reassignComplete();
PROCLOG(INFO) << "successful assignment";
PROCLOG(DEBUG) << gMMLayer.printMMInfo();
if (IS_WATCH_LEVEL(DEBUG)) {
cout << "AssignmentComplete:\n";
CommandLine::printChansV4(cout,false);
}
//tran()->mSipDialogMessagesBlocked = false; // Next process will handle the postponed dialog messages.
return MachineStatusPopMachine;
}
case L3CASE_RR(AssignmentFailure): {
// We tried to reassign the MS from SDCCH to TCH and failed.
// This arrives on the old SDCCH after "The mobile station has failed to seize the new channel."
// The old code continually retried in this case. So we will too, because
// conceivably this could be working around some bug in OpenBTS.
// Old code retried until T3101ms-1000, which just cant be right.
if (! TChReassignment.passed()) {
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): {
handleIncallCMServiceRequest(l3msg);
// Resend the channel assignment.
sendReassignment(); // duplicates old code, but is this really necessary?
}
case L3CASE_CC(Setup):
LOG(DEBUG) << "ignoring duplicate L3Setup";
return MachineStatusOK;
default:
if (sipmsg) {
LOG(DEBUG) << "Dialog message received in AssignTCHF procedure.";
// We just ignore sip messages. The caller will handle the final SIP state when we return.
return MachineStatusOK;
}
return defaultMessages(state,l3msg);
}
}
// Timer values in 24.008 table 11.4
MachineStatus MTCMachine::machineRunState(int state, const GSM::L3Message* l3msg, const SIP::DialogMessage *sipmsg)
{
PROCLOG2(DEBUG,state)<<LOGVAR(state)<<LOGVAR(l3msg)<<LOGVAR(sipmsg)<<LOGVAR2("imsi",tran()->subscriber());
switch(state) {
case stateStart: {
if (getDialog()->isFinished()) {
// SIP side closed already.
//formerly: return closeCall(L3Cause::Interworking_Unspecified);
return closeCall(dialog2TermCause(getDialog()));
}
// Allocate channel now, to be sure there is one.
// 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::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.
LOG(INFO) << "SIP term info closeChannel called in AssignTCHMachine::machineRunState 2";
return closeChannel(L3RRCause::Normal_Event,L3_RELEASE_REQUEST,cause);
}
}
// Allocate Transaction Identifier
unsigned l3ti = channel()->chanGetContext(true)->mmGetNextTI();
tran()->setL3TI(l3ti);
// Send Setup message to MS.
L3Setup setupmsg(l3ti,tran()->calling());
// pat 2-2014: Attempt to make the buggy ZTE phone by sending an explicit L3Signal IE in the L3Setup message.
// Update: did not help.
//L3Signal tone(L3Signal::SignalBusyToneOn); // Tryed both Ringing and Busy tone, no joy.
//setupmsg.setSignal(tone);
PROCLOG(INFO) << "sending L3Setup to call " << LOGVAR2("calling",tran()->calling()) << tran() <<LOGVAR(setupmsg);
channel()->l3sendm(setupmsg);
setGSMState(CCState::CallPresent);
timerStart(T303,T303ms,TimerAbortTran); // Time state "Call Present"; start CMServiceRequest recv; stop CallProceeding recv.
// And send trying message to SIP
if (getDialog()) { getDialog()->MTCSendTrying(); }
return MachineStatusOK; // Wait for L3CallConfirmed message.
}
case L3CASE_CC(CallConfirmed): {
timerStop(T303);
// Some handsets send a CallConfirmed both before and after the channel change.
if (getGSMState() == CCState::MTCConfirmed) {
LOG(DEBUG) << "ignoring duplicate L3CallConfirmed";
return MachineStatusOK;
}
setGSMState(CCState::MTCConfirmed);
timerStart(T310,T310ms,TimerAbortTran); // Time state "Call Confirmed"; start CallConfirmed recv; stop Alert,Connect,Disconnect recv.
// Change channels.
return machPush(new AssignTCHMachine(tran()), statePostChannelChange);
}
case statePostChannelChange: {
// We just wait for something else to happen.
if (IS_LOG_LEVEL(DEBUG)) { CommandLine::printChansV4(cout,false); }
switch (getDialog()->getDialogState()) {
case DialogState::dialogUndefined:
case DialogState::dialogStarted:
case DialogState::dialogProceeding:
case DialogState::dialogRinging:
case DialogState::dialogDtmf:
// We dont care about these.
return MachineStatusOK; // Waiting for L3Alerting or L3Connect.
case DialogState::dialogActive:
case DialogState::dialogBye:
case DialogState::dialogFail:
return machineRunState(L3CASE_DIALOG_STATE(getDialog()->getDialogState()),NULL,NULL);
}
}
// TODO: Should we resend the Ringing message on some timer?
case L3CASE_CC(Alerting): {
// We send a Ringing indication to SIP every time we receive an L3Alerting message.
const GSM::L3Alerting*msg = dynamic_cast<typeof(msg)>(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);
if (getDialog()) { getDialog()->MTCSendRinging(); }
return MachineStatusOK; // Waiting for L3Connect.
}
case L3CASE_CC(Connect): {
timerStop(T301);
timerStop(T303);
timerStop(T310);
timerStopAll(); // a little redundancy here.
if (getGSMState() == CCState::ConnectIndication) {
// I think the code below would work ok, but this is neater.
LOG(DEBUG) << "ignoring duplicate L3Connect";
return MachineStatusOK;
}
//timerStop(TRing);
// We used to set GSMstate Active when we received the Connect,
// but now we wait until we send the ConnectAcknowledge, which is after we receive confirmation
// from the SIP side. This is necessary because it is not until then that rtp is inited,
// and we use the CCState flag to indicate when the RTP traffic can start.
// 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.
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")) {
int encryptionAlgorithm = gTMSITable.tmsiTabGetPreferredA5Algorithm(tran()->subscriberIMSI().c_str());
if (!encryptionAlgorithm) {
LOG(DEBUG) << "A5/3 and A5/1 not supported: NOT sending Ciphering Mode Command on " << *channel() << " for IMSI" << tran()->subscriberIMSI();
} else if (channel()->getL2Channel()->decryptUplink_maybe(tran()->subscriberIMSI(), encryptionAlgorithm)) {
LOG(DEBUG) << "sending Ciphering Mode Command on " << *channel() << " for IMSI" << tran()->subscriberIMSI();
channel()->l3sendm(GSM::L3CipheringModeCommand(
GSM::L3CipheringModeSetting(true, encryptionAlgorithm),
GSM::L3CipheringModeResponse(false)));
} else {
LOG(DEBUG) << "no ki: NOT sending Ciphering Mode Command on " << *channel() << " for IMSI" << tran()->subscriberIMSI();
}
}
setGSMState(CCState::Active);
getDialog()->MTCInitRTP();
channel()->l3sendm(GSM::L3ConnectAcknowledge(tran()->getL3TI()));
return callMachStart(new InCallMachine(tran()));
}
case L3CASE_SIP(dialogStarted):
case L3CASE_SIP(dialogProceeding):
case L3CASE_SIP(dialogRinging): {
PROCLOG(ERR) << "MTC received unexpected SIP Dialog message: << sipmsg;SIP Progress message";
return MachineStatusOK;
}
// SIP Dialog failure cases.
case L3CASE_SIP(dialogBye): {
// The other user hung up before we could finish.
return closeCall(dialog2ByeCause(getDialog()));
}
case L3CASE_SIP(dialogFail): {
// It cannot be busy because it is a MTC.
// 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:
return defaultMessages(state,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());
switch (state) {
case stateStart:
// The MS has already established the channel. The HandoverComplete message should arrive
// immediately unless the signal goes bad.
// We need a timer to make sure we eventually receive HandoverComplete.
// There is no specific timer for this.
// Instead of using the generic channel-loss timeout, use a shorter timer here.
timerStart(THandoverComplete,5000,TimerAbortChan);
return MachineStatusOK;
case L3CASE_RR(HandoverComplete): {
timerStop(THandoverComplete);
mReceivedHandoverComplete = true;
// MS has successfully arrived on BS2. Open the SIPDialog and attempt to transfer the SIP session.
// Send re-INVITE to the remote party.
HandoverEntry *hop = tran()->getHandoverEntry(true);
SIP::SipDialog *dialog = SIP::SipDialog::newSipDialogHandover(tran(),hop->mSipReferStr);
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.
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);
timerStart(TSipHandover,4000,TimerAbortChan);
return MachineStatusOK; // Wait for SIP response from peer.
}
case L3CASE_SIP(dialogFail):
// TODO: We should send a CC message to the phone based on the SIP fail code.
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.
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();
// Send completion to peer BTS. TODO: This should be in a separate thread.
gPeerInterface.sendHandoverComplete(tran()->getHandoverEntry(true));
// Convert to a normal call. The Active status will (hopefully) cause RTP data to start
// being transferred by the service loop as soon as we return...
setGSMState(CCState::Active);
getDialog()->MOCInitRTP();
// We can connect to the MMUser now.
// TODO: I moved this to InCallMachine but I dont want to test handover right now so leave this here too;
// doesnt hurt to call mmAttachByImsi twice.
string imsi = tran()->subscriberIMSI();
if (imsi.empty()) {
LOG(ALERT) "handover with empty imsi?"; // Should be an assert.
}
gMMLayer.mmAttachByImsi(channel(),imsi);
// 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());
}
***/
LOG(INFO) << "succesful inbound handover " << tran();
return callMachStart(new InCallMachine(tran()));
}
default:
// 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");
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");
return MachineStatusOK; // Just keep going...
}
}
}
void startInboundHandoverMachine(TranEntry *tran)
{
InboundHandoverMachine *ihm = new InboundHandoverMachine(tran);
tran->lockAndStart(ihm);
}
void InCallMachine::acknowledgeDtmf()
{
if (mDtmfSuccess) {
L3KeypadFacility thekey(mDtmfKey);
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::Service_Or_Option_Not_Available));
}
}
static bool supportRFC2833() {
return gConfig.getBool("SIP.DTMF.RFC2833");
}
static bool supportRFC2976() {
// Unfortunately the config option was originall misnamed, so test for both.
return gConfig.getBool("SIP.DTMF.RFC2976") || (gConfig.defines("SIP.DTMF.RFC2967") && gConfig.getBool("SIP.DTMF.RFC2967"));
}
MachineStatus InCallMachine::machineRunState(int state, const GSM::L3Message *l3msg, const SIP::DialogMessage *sipmsg)
{
PROCLOG2(DEBUG,state)<<LOGVAR(l3msg)<<LOGVAR(sipmsg)<<LOGVAR2("imsi",tran()->subscriber());
switch (state) {
case stateStart:
// In the MOC case, this attachByImsi lets the MS start receiving MTSMS now.
// In the MTC case, this attachByImsi was done previously.
gMMLayer.mmAttachByImsi(channel(),tran()->subscriberIMSI());
//channel()->setVoiceTran(tran());
PROCLOG(DEBUG) << "setting voice tran="<<tran();
return MachineStatusOK;
case L3CASE_RR(ChannelModeModifyAcknowledge):
PROCLOG(INFO) << "Ignoring duplicate GSM ChannelModeModifyAcknowledge " << *tran();
return MachineStatusOK;
case L3CASE_RR(AssignmentComplete):
PROCLOG(INFO) << "Ignoring duplicate GSM AssignmentComplete " << *tran();
return MachineStatusOK;
case L3CASE_CC(ConnectAcknowledge):
PROCLOG(INFO) << "Ignoring duplicate GSM Connect Acknowledge " << *tran();
return MachineStatusOK;
case L3CASE_CC(Connect):
PROCLOG(INFO) << "Ignoring duplicate GSM Connect " << *tran();
return MachineStatusOK;
case L3CASE_CC(CallConfirmed):
PROCLOG(INFO) << "Ignoring duplicate GSM Call Confirmed " << *tran();
return MachineStatusOK;
case L3CASE_CC(Alerting):
PROCLOG(INFO) << "Ignoring duplicate GSM Alerting" << *tran();
return MachineStatusOK;
// Start DTMF
// (pat) What should we do if the MS sends additional DTMF commands before the SIP side returns a response?
// Is it guaranteed not to send another StartDTMF until we send the DTMFAcknowledge?
case L3CASE_CC(StartDTMF): {
const GSM::L3StartDTMF* startDtmfMsg = dynamic_cast<typeof(startDtmfMsg)>(l3msg);
// Transalate to RFC-2976 or RFC-2833.
mDtmfSuccess = false;
mDtmfKey = startDtmfMsg->key().IA5();
PROCLOG(INFO) << "DMTF key=" << mDtmfKey << ' ' << *tran();
if (supportRFC2833()) {
// TODO: Who do we need to lock here?
bool s = getDialog()->startDTMF(mDtmfKey);
if (!s) PROCLOG(ERR) << "DTMF RFC-28333 failed.";
mDtmfSuccess |= s;
}
if (supportRFC2976()) {
unsigned bcd = GSM::encodeBCDChar(mDtmfKey);
getDialog()->sendInfoDtmf(bcd);
// In this case we need to return and wait for the reply to the INFO message;
// when it arrives we will go to the dialogDtmf state.
} else {
// If RFC2697 used we will send acknowledgement to MS when SIP OK arrives, otherwise send it now.
acknowledgeDtmf();
}
return MachineStatusOK;
}
case L3CASE_SIP(dialogDtmf): { // This is returned if RFC2697 is used, ie, SIP instead of RTP.
if (sipmsg->sipStatusCode() == 200) {
mDtmfSuccess = true;
} else {
PROCLOG(ERR) << "DTMF RFC-2967 failed with code="<<sipmsg->sipStatusCode();
}
acknowledgeDtmf();
return MachineStatusOK;
}
// Stop DTMF RFC-2967 or RFC-2833
case L3CASE_CC(StopDTMF): {
if (supportRFC2833()) {
getDialog()->stopDTMF();
}
// For RFC2976 there is nothing more to do - we sent one SIP INFO message and that is it.
channel()->l3sendm(GSM::L3StopDTMFAcknowledge(tran()->getL3TI()));
return MachineStatusOK;
}
case L3CASE_SIP(dialogProceeding): {
PROCLOG(INFO) << "Ignoring duplicate SIP Proceeding " << *tran();
return MachineStatusOK;
}
case L3CASE_SIP(dialogRinging): {
PROCLOG(INFO) << "Ignoring duplicate SIP Ringing " << *tran();
return MachineStatusOK;
}
case L3CASE_SIP(dialogActive): {
PROCLOG(ERR) << "Ignoring duplicate SIP Active " << *tran();
return MachineStatusOK;
}
case L3CASE_SIP(dialogBye): {
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());
TermCause cause = dialog2TermCause(getDialog());
LOG(INFO) << "SIP dialogFail"<<LOGVAR(cause);
return closeCall(cause);
}
case L3CASE_SIP(dialogStarted):
devassert(0);
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.
return defaultMessages(state,l3msg);
}
return MachineStatusOK;
}
void initMTC(TranEntry *tran)
{
tran->teSetProcedure(new MTCMachine(tran));
}
}; // namespace