/* * 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 #include "ControlCommon.h" #include "L3CallControl.h" #include "L3StateMachine.h" #include "L3TranEntry.h" #include "L3MMLayer.h" #include "L3SupServ.h" #include #include #include #include #include #include #include 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) <lockAndStart(mocp,(GSM::L3Message*)l3msg); } #if UNUSED //MachineStatus ProcedureDetach::machineRunState(int state, const GSM::L3Message* l3msg, const SIP::DialogMessage *sipmsg) //{ // PROCLOG2(DEBUG,state)<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"<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"<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:"<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(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(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(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(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(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(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(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(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:" <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)<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(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(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(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(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"<(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)<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(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(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)<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() <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(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"<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)<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="<(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="<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"<teSetProcedure(new MTCMachine(tran)); } }; // namespace