/* Copyright 2013, 2014 Range Networks, Inc. * * This software is distributed under multiple licenses; * see the COPYING file in the main directory for licensing * information for this specific distribuion. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. */ #define LOG_GROUP LogGroup::Control #include "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(GSM::L3Cause cause); MachineStatus sendReleaseComplete(GSM::L3Cause cause); MachineStatus sendRelease(GSM::L3Cause cause); void handleTerminationRequest(); }; class MOCMachine : public CCBase { enum State { stateStartUnused, // Reserve 0 superstitiously. stateCCIdentResult, stateAssignTCHFSuccess, }; bool mIdentifyResult; MachineStatus sendCMServiceReject(MMRejectCause rejectCause); 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(L3Cause l3cause) { tran()->teCloseCallNow(l3cause); return MachineStatusQuitTran; } MachineStatus CCBase::sendRelease(L3Cause l3cause) { tran()->teCloseDialog(); // redundant, would happen soon anyway. if (isL3TIValid()) { unsigned l3ti = getL3TI(); if (tran()->clearingGSM()) { // Oops! Something went wrong. Clear immediately. tran()->teCloseCallNow(l3cause); return MachineStatusQuitTran; } else { // This tells the phone that the network intends to release the TI. // The handset is supposed to respond with ReleaseComplete. channel()->l3sendm(GSM::L3Release(l3ti,l3cause)); 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 MachineStatusQuitTran; } } // 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(L3Cause l3cause) { WATCHINFO("closeCall"<descriptiveString()); tran()->teCloseDialog(); // 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,l3cause)); setGSMState(CCState::DisconnectIndication); } else { // (pat 10-24-2013) As an option per 24.008 5.4.2: we could send a Release message and start T308 channel()->l3sendm(GSM::L3Release(l3ti,l3cause)); setGSMState(CCState::ReleaseRequest); } timerStart(T308,T308ms,TimerAbortTran); return MachineStatusOK; // Wait for ReleaseComplete. } else if (ccstate != CCState::NullState && ccstate != CCState::ReleaseRequest) { channel()->l3sendm(GSM::L3ReleaseComplete(l3ti,l3cause)); // This is a CC message that releases this Transaction. } } 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(L3Cause::Preemption); } // 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::ServiceOrOptionNotAvailable)); 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(); return closeCall(L3Cause::NormalCallClearing); // normal event. } case L3CASE_CC(Disconnect): { // MOD // 4.08 5.4.3 says we must be prepared to receive a DISCONNECT any time. //const L3Disconnect *dmsg = dynamic_cast(l3msg); Unused. //changed 10-24-13: return closeCall(L3Cause::NormalCallClearing); // normal event. timerStopAll(); return sendRelease(L3Cause::NormalCallClearing); } case L3CASE_CC(Release): { // 24.008 5.4.3.3: In any state except ReleaseRequest send a ReleaseComplete, then kill the transaction, const L3Release *dmsg = dynamic_cast(l3msg); if (dmsg->mFacility.mExtant) WATCH(dmsg); // USSD DEBUG! timerStopAll(); return sendReleaseComplete(L3Cause::NormalCallClearing); } 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::NormalCallClearing); // normal event. // tran()->teCloseDialog(); // Redundant. setGSMState(CCState::NullState); // redundant, we are deleting this transaction. return MachineStatusQuitTran; } 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(); // FIXME: Must unregister. // TODO: IMSIDetachController(detach,LCH); channel()->l3sendm(L3ChannelRelease()); // Many handsets never complete the transaction. // So force a shutdown of the channel. channel()->chanRelease(HARDRELEASE); return MachineStatusQuitChannel; } 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. channel()->l3sendm(GSM::L3CMServiceReject(L3RejectCause(L3RejectCause::ServiceOptionNotSupported))); 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) { channel()->l3sendm(L3CMServiceReject(L3RejectCause(rejectCause))); return closeChannel(L3RRCause::NormalEvent,RELEASE); } 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"; return closeCall(L3Cause::InvalidMandatoryInformation); } const L3CalledPartyBCDNumber& calledPartyIE = setup->calledPartyBCDNumber(); tran()->setCalled(calledPartyIE); calledNumber = calledPartyIE.digits(); } /* early RLLP request */ /* this seems to need to be sent after initial call setup -kurtis */ if (gConfig.getBool("Control.Call.QueryRRLP.Early")) { // Query for RRLP #if ORIGINAL_CODE if (!sendRRLP(mobileID, LCH)) { PROCLOG(INFO) << "RRLP request failed"; } #else // TODO: RRLPServer.start(mobileID); #endif } // Start a new SIP Dialog, which sends an INVITE. 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"; return closeChannel(L3RRCause::Unspecified,RELEASE); } //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()) { channel()->l3sendm(GSM::L3CMServiceReject(L3RejectCause(L3RejectCause::Congestion))); tran()->teCloseDialog(CancelCauseCongestion); // (pat) TODO: Now what? We are supposed to go back to using SDCCH in case of an ongoing SMS, // so lets just close the Transaction. return closeChannel(L3RRCause::NormalEvent,RELEASE); } } // 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::IMSIUnknownInVLR); } } #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::RequestedServiceOptionNotSubscribed); } 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::RequestedServiceOptionNotSubscribed); } 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. 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::RequestedServiceOptionNotSubscribed); } 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::MessageTypeNotCompatibleWithProtocolState); } 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): { #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); 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. 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! return closeCall(L3Cause::NormalCallClearing); } 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(L3Cause::NormalCallClearing); } case L3CASE_SIP(dialogFail): { // 0x11: "User Busy"; 0x7f "Interworking unspecified" int sipcode = getDialog()->getLastResponseCode(); // This is where we should translate SIP codes into more meaningful L3Cause returns. switch (sipcode) { case 486: case 600: case 603: return closeCall(L3Cause::UserBusy); default: return closeCall(L3Cause::InterworkingUnspecified); } } #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(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(L3Cause::ChannelUnacceptable); 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_LOG_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 { goto caseAssignTimeout; } } // 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 stateAssignTimeout: caseAssignTimeout: channel()->reassignFailure(); // TODO: This is not optimal - we should drop back to the MMLayer to see if it wants to do something else. if (getDialog()) { getDialog()->dialogCancel(); } // Should never be NULL, but dont crash. // We dont call closeCall because we already sent the specific RR message required for this situation. return closeChannel(L3RRCause::NoActivityOnTheRadio,RELEASE); case L3CASE_CC(Setup): 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: { //MachineStatus stat = checkForSipFailure(getDialog()->getDialogState()); //if (stat != MachineStatusOK) { return stat; } if (getDialog()->isFinished()) { // SIP side closed already. return closeCall(L3Cause::InterworkingUnspecified); } // Allocate channel now, to be sure there is one. if (!isVeryEarly()) { if (! channel()->reassignAllocNextTCH()) { channel()->l3sendm(GSM::L3CMServiceReject(L3RejectCause(L3RejectCause::Congestion))); tran()->teCloseDialog(CancelCauseCongestion); // (pat) TODO: We are supposed to go back to using SDCCH in case of an ongoing SMS. return closeChannel(L3RRCause::NormalEvent,RELEASE); } } // 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 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); 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. 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. // (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(L3Cause::NormalCallClearing); } case L3CASE_SIP(dialogFail): { // It cannot be busy because it is a MTC. return closeCall(L3Cause::InterworkingUnspecified); } default: return defaultMessages(state,l3msg); } } MachineStatus InboundHandoverMachine::machineRunState(int state, const GSM::L3Message *l3msg, const SIP::DialogMessage *sipmsg) { PROCLOG2(DEBUG,state)<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. closeCall(L3Cause::InterworkingUnspecified); return closeChannel(L3RRCause::NormalEvent,RELEASE); } 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. return closeChannel(L3RRCause::NormalEvent,RELEASE); case L3CASE_SIP(dialogBye): // SIP end hung up. Just hang up the MS. closeCall(L3Cause::NormalCallClearing); return closeChannel(L3RRCause::NormalEvent,RELEASE); case L3CASE_SIP(dialogActive): { // Success! SIP side is active. 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"); return closeChannel(L3RRCause::MessageTypeNotCompapatibleWithProtocolState,RELEASE); } 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::ServiceOrOptionNotAvailable)); } } 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(L3Cause::NormalCallClearing); } case L3CASE_SIP(dialogFail): { // This is MTD - Mobile Terminated Disconnect. SIP sends a CANCEL which translates to this Fail. // It cant be busy at this point because we already connected. //devassert(! sipmsg->isBusy()); return closeCall(L3Cause::InterworkingUnspecified); } case L3CASE_SIP(dialogStarted): devassert(0); return MachineStatusQuitTran; // 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