/**@file GSM Radio Resource procedures, GSM 04.18 and GSM 04.08. */ /* * Copyright 2008, 2009, 2010 Free Software Foundation, Inc. * Copyright 2010 Kestrel Signal Processing, Inc. * Copyright 2011, 2012, 2013 Range Networks, Inc. * 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. * 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. */ #include #include #include #include #include "ControlCommon.h" #include "RadioResource.h" #include "L3CallControl.h" #include "L3MMLayer.h" #include #include #include "../GPRS/GPRSExport.h" #include #include #include #include #include #undef WARNING using namespace std; using namespace GSM; namespace Control { static void abortInboundHandover(RefCntPointer transaction, RRCause cause, L3LogicalChannel *LCH=NULL) { LOG(DEBUG) << "aborting inbound handover " << *transaction; unsigned holdoff = gConfig.getNum("GSM.Handover.FailureHoldoff"); gPeerInterface.sendHandoverFailure(transaction->getHandoverEntry(true),cause,holdoff); //gTransactionTable.remove(transaction); } #if UNUSED bool SaveHandoverAccess(unsigned handoverReference, float RSSI, float timingError, const GSM::Time& timestamp) { assert(! l3rewrite()); // Not used in l3rewrite. See TCHFACCHL1Decoder::writeLowSideRx // In this function, we are "BS2" in the ladder diagram. // This is called from L1 when a handover burst arrives. // We will need to use the transaction record to carry the parameters. // We put this here to avoid dealing with the transaction table in L1. TransactionEntry *transaction = gTransactionTable.ttFindByInboundHandoverRef(handoverReference); if (!transaction) { LOG(ERR) << "no inbound handover with reference " << handoverReference; return false; } if (timingError > gConfig.getNum("GSM.MS.TA.Max")) { // Handover failure. LOG(NOTICE) << "handover failure on due to TA=" << timingError << " for " << *transaction; // RR cause 8: Handover impossible, timing advance out of range OldAbortInboundHandover(transaction,L3RRCause::HandoverImpossible,dynamic_cast(transaction->channel())); return false; } LOG(INFO) << "saving handover access for " << *transaction; transaction->setInboundHandover(RSSI,timingError,gBTS.clock().systime(timestamp)); return true; } #endif //void ProcessHandoverAccess(GSM::TCHFACCHLogicalChannel *TCH) //{ // // In this function, we are "BS2" in the ladder diagram. // // This is called from the DCCH dispatcher when it gets a HANDOVER_ACCESS primtive. // // The information it needs was saved in the transaction table by SaveHandoverAccess. // // // assert(TCH); // LOG(DEBUG) << *TCH; // // TransactionEntry *transaction = gTransactionTable.ttFindByInboundHandoverChan(TCH); // if (!transaction) { // LOG(WARNING) << "handover access with no inbound transaction on " << *TCH; // TCH->l2sendp(HARDRELEASE); // return; // } // // // clear handover in transceiver // LOG(DEBUG) << *transaction; // transaction->getL2Channel()->handoverPending(false); // // // Respond to handset with physical information until we get Handover Complete. // int TA = (int)(transaction->inboundTimingError() + 0.5F); // if (TA<0) TA=0; // if (TA>62) TA=62; // unsigned repeatTimeout = gConfig.getNum("GSM.Timer.T3105"); // unsigned sendCount = gConfig.getNum("GSM.Ny1"); // L3Frame* frame = NULL; // while (!frame && sendCount) { // TCH->l2sendm(L3PhysicalInformation(L3TimingAdvance(TA)),GSM::UNIT_DATA); // sendCount--; // frame = TCH->l2recv(repeatTimeout); // if (frame && frame->primitive() == HANDOVER_ACCESS) { // LOG(NOTICE) << "flushing HANDOVER_ACCESS while waiting for Handover Complete"; // delete frame; // frame = NULL; // } // } // // // Timed out? // if (!frame) { // LOG(NOTICE) << "timed out waiting for Handover Complete on " << *TCH << " for " << *transaction; // // RR cause 4: Abnormal release, no activity on the radio path // OldAbortInboundHandover(transaction,4,TCH); // return; // } // // // Screwed up channel? // if (frame->primitive()!=ESTABLISH) { // LOG(NOTICE) << "unexpected primitive waiting for Handover Complete on " // << *TCH << ": " << *frame << " for " << *transaction; // delete frame; // // RR cause 0x62: Message not compatible with protocol state // OldAbortInboundHandover(transaction,0x62,TCH); // return; // } // // // Get the next frame, should be HandoverComplete. // delete frame; // frame = TCH->l2recv(); // L3Message* msg = parseL3(*frame); // if (!msg) { // LOG(NOTICE) << "unparsable message waiting for Handover Complete on " // << *TCH << ": " << *frame << " for " << *transaction; // delete frame; // // RR cause 0x62: Message not compatible with protocol state // TCH->l2sendm(L3ChannelRelease(L3RRCause::MessageTypeNotCompapatibleWithProtocolState)); // OldAbortInboundHandover(transaction,0x62,TCH); // return; // } // delete frame; // // L3HandoverComplete* complete = dynamic_cast(msg); // if (!complete) { // LOG(NOTICE) << "expecting for Handover Complete on " // << *TCH << "but got: " << *msg << " for " << *transaction; // delete frame; // // RR cause 0x62: Message not compatible with protocol state // TCH->l2sendm(L3ChannelRelease(L3RRCause::MessageTypeNotCompapatibleWithProtocolState)); // OldAbortInboundHandover(transaction,0x62,TCH); // } // delete msg; // // // Send re-INVITE to the remote party. // unsigned RTPPort = allocateRTPPorts(); // SIP::SIPState st = transaction->inboundHandoverSendINVITE(RTPPort); // if (st == SIP::Fail) { // OldAbortInboundHandover(transaction,4,TCH); // return; // } // // transaction->GSMState(CCState::HandoverProgress); // // while (1) { // // FIXME - the sip engine should be doing this // // FIXME - and checking for timeout // // FIXME - and checking for proceeding (stop sending the resends) // st = transaction->inboundHandoverCheckForOK(); // if (st == SIP::Active) break; // if (st == SIP::Fail) { // LOG(NOTICE) << "received Fail while waiting for OK"; // OldAbortInboundHandover(transaction,4,TCH); // return; // } // } // st = transaction->inboundHandoverSendACK(); // LOG(DEBUG) << "status of inboundHandoverSendACK: " << st << " for " << *transaction; // // // Send completion to peer BTS. // char ind[100]; // sprintf(ind,"IND HANDOVER_COMPLETE %u", transaction->tranID()); // gPeerInterface.sendUntilAck(transaction,ind); // // // Update subscriber registry to reflect new registration. // if (transaction->SRIMSI().length() && transaction->SRCALLID().length()) { // gSubscriberRegistry.addUser(transaction->SRIMSI().c_str(), transaction->SRCALLID().c_str()); // } // // // The call is running. // LOG(INFO) << "succesful inbound handover " << *transaction; // transaction->GSMState(CCState::Active); // callManagementLoop(transaction,TCH); //} // How did we get here you ask? Peering received a handover request on BTS2 (us), allocated a channel and set the handoverPending flag, // created a transaction with the specified IMSI, returned an L3 handover command which BTS1 sent to the MS, which then // sent a handover access to BTS2, and here we are! void ProcessHandoverAccess(L3LogicalChannel *chan) { using namespace SIP; // In this function, we are "BS2" in the ladder diagram. // This is called from the DCCH dispatcher when it gets a HANDOVER_ACCESS primtive. // The information it needs was saved in the transaction table by SaveHandoverAccess. LOG(DEBUG) << *chan; RefCntPointer tran = chan->chanGetVoiceTran(); if (tran == NULL) { LOG(WARNING) << "handover access with no inbound transaction on " << chan; chan->chanRelease(HARDRELEASE); return; } LOG(DEBUG) << *tran; if (!tran->getHandoverEntry(false)) { LOG(WARNING) << "handover access with no inbound handover on " << *chan; chan->chanRelease(HARDRELEASE); return; } // clear handover in transceiver and get the RSSI and TE. // This instructs L2 to stop looking for and stop sending HANDOVER_ACCESS. // However, we cant just flush them out of the queue here because that is running in another // thread and it may keep pushing HANDOVER_ACCESS at, so we keep flushing them (below) // However, we should NEVER see HANDOVER_ACCESS after the ESTABLISH, yet I did. GSM::HandoverRecord hr = chan->getL2Channel()->handoverPending(false,0); // TODO: Move this into L1? if (hr.mhrTimingError > gConfig.getNum("GSM.MS.TA.Max")) { // Handover failure. LOG(NOTICE) << "handover failure on due to TA=" << hr.mhrTimingError << " for " << *tran; // RR cause 8: Handover impossible, timing advance out of range abortInboundHandover(tran,L3RRCause::HandoverImpossible,dynamic_cast(tran->channel())); chan->chanRelease(HARDRELEASE); // TODO: Is this right? Will the channel be immediately re-available? return; } chan->getL2Channel()->setPhy(hr.mhrRSSI,hr.mhrTimingError,hr.mhrTimestamp); // Respond to handset with physical information until we get Handover Complete. int TA = (int)(hr.mhrTimingError + 0.5F); if (TA<0) TA=0; if (TA>62) TA=62; // We want to do this loop carefully so we exit as soon as we get a frame that is not HANDOVER_ACCESS. Z100Timer T3105(gConfig.getNum("GSM.Timer.T3105")); // It defaults to only 50ms. // 4.08 11.1.3 "Ny1: The maximum number of repetitions for the PHYSICAL INFORMATION message during a handover." for (unsigned sendCount = gConfig.getNum("GSM.Ny1"); sendCount > 0; sendCount--) { T3105.set(); // (pat) It is UNIT_DATA because the channel is not established yet. // (pat) WARNING: This l3sendm call is not blocking because it is sent on FACCH which has a queue. // Rather than modifying the whole LogicalChannel stack to have a blocking mode, // we are just going to wait afterwards. The message should take about 20ms to transmit, // and GSM uses roughly 4 out of every 5 frames, so 20-25ms would transmit the message continuously. chan->l3sendm(L3PhysicalInformation(L3TimingAdvance(TA)),GSM::UNIT_DATA); // (pat) Throw away all the HANDOVER_ACCESS that arrive while we were waiting. // They are not messages that take 4 bursts; they can arrive on every burst, so there // can be a bunch of them queued up (I would expect 5) for each message we send. while (L3Frame *frame = chan->l2recv(T3105.remaining())) { switch (frame->primitive()) { case HANDOVER_ACCESS: // See comments above. L2 is no longer generating these, but we need // to flush any extras from before we started, and there also might be have been // some in progress when we turned them off, so just keep flushing. LOG(INFO) << "flushing HANDOVER_ACCESS while waiting for Handover Complete"; delete frame; continue; case ESTABLISH: delete frame; // Channel is established, so the MS is there. Finish up with a state machine. startInboundHandoverMachine(tran.self()); return; default: // Something else? LOG(NOTICE) << "unexpected primitive waiting for Handover Complete on " << *chan << ": " << *frame << " for " << *tran; delete frame; abortInboundHandover(tran,L3RRCause::MessageTypeNotCompapatibleWithProtocolState,chan); chan->chanRelease(HARDRELEASE); // TODO: Is this right? Will the channel be immediately re-available? return; } } } // Failure. LOG(NOTICE) << "timed out waiting for Handover Complete on " << *chan << " for " << *tran; // RR cause 4: Abnormal release, no activity on the radio path abortInboundHandover(tran,L3RRCause::NoActivityOnTheRadio,chan); chan->chanRelease(HARDRELEASE); // TODO: Is this right? Will the channel be immediately re-available? return; #if 0 // // Get the next frame, should be HandoverComplete. // delete frame; // frame = chan->l2recv(); // L3Message* msg = parseL3(*frame); // if (!msg) { // LOG(NOTICE) << "unparsable message waiting for Handover Complete on " // << *chan << ": " << *frame << " for " << *tran; // delete frame; // // The MS is listening to us now, so we have to send it something if we abort. // // TODO: Should be a state machine from here on. // // RR cause 0x62: Message not compatible with protocol state // chan->chanClose(L3RRCause::MessageTypeNotCompapatibleWithProtocolState,HARDRELEASE); // abortInboundHandover(tran,L3RRCause::MessageTypeNotCompapatibleWithProtocolState,chan); // return; // } // delete frame; // // L3HandoverComplete* complete = dynamic_cast(msg); // if (!complete) { // LOG(NOTICE) << "expecting for Handover Complete on " // << *chan << "but got: " << *msg << " for " << *tran; // delete frame; // // RR cause 0x62: Message not compatible with protocol state // chan->chanClose(L3RRCause::MessageTypeNotCompapatibleWithProtocolState,HARDRELEASE); // abortInboundHandover(tran,L3RRCause::MessageTypeNotCompapatibleWithProtocolState,chan); // } // delete msg; // // // MS has successfully arrived on BS2. Open the SIPDialog and attempt to transfer the SIP session. // // // Send re-INVITE to the remote party. // //unsigned RTPPort = allocateRTPPorts(); // SIP::SIPDialog *dialog = SIP::SIPDialog::newSIPDialogHandover(tran); // if (dialog == NULL) { // // TODO: Can we abort at this point? It is too late. // // But this only fails if the address is wrong. // //abortInboundHandover(tran,L3RRCause::NoActivityOnTheRadio,chan); // LOG(NOTICE) << "handover failure due to failure to create dialog for " << *tran; // Will probably never happen. // tran->teCloseCall(L3Cause::InterworkingUnspecified); // chan->chanClose(L3RRCause::Unspecified,RELEASE); // return; // } // tran->setDialog(dialog); // tran->setGSMState(CCState::HandoverProgress); // // while (DialogMessage*dmsg = dialog->dialogRead()) { // switch (dmsg->dialogState()) { // case SIPDialog::dialogActive: // // We're good to go. // tran->setGSMState(CCState::Active); // break; // case SIPDialog::dialogBye: // // Other end hung up. Just hang up. // tran->teCloseCall(L3Cause::NormalCallClearing); // chan->chanClose(L3RRCause::NormalEvent,RELEASE); // return; // default: // LOG(ERR) << "unrecognized SIP Dialog state while waiting for handover re-invite OK"<teCloseCall(L3Cause::InterworkingUnspecified); // chan->chanClose(L3RRCause::Unspecified,RELEASE); // return; // } // delete dmsg; // if (tran->getGSMState() == CCState::Active) { break; } // } // SIP::SIPState st = dialog->inboundHandoverSendACK(); // LOG(DEBUG) << "status of inboundHandoverSendACK: " << st << " for " << *tran; // // // Send completion to peer BTS. // char ind[100]; // sprintf(ind,"IND HANDOVER_COMPLETE %u", tran->tranID()); // gPeerInterface.sendUntilAck(tran->getHandoverEntry(true),ind); // // // 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()); // } // ***/ // // // The call is running. // LOG(INFO) << "succesful inbound handover " << *tran; // //callManagementLoop(transaction,TCH); #endif } // Warning: This runs in a separate thread. void HandoverDetermination(const L3MeasurementResults& measurements, float myRxLevel, SACCHLogicalChannel* SACCH) { // This is called from the SACCH service loop. // Valid measurements? if (measurements.MEAS_VALID()) return; // Got neighbors? // (pat) I am deliberately not aging the neighbor list if the measurement report is empty because // I am afraid it may be empty because the MS did not have time to make measurements during this time // period, rather than really indicating that there are no neighbors. unsigned N = measurements.NO_NCELL(); if (N==0) { return; } if (N == 7) { LOG(DEBUG) << "neighbor cell information not available"; return; } // (pat) TODO: If you add your own IP address to the sql neighbor list, the MS will return info on yourself, // which will attempt a handover to yourself unless you throw those measurement reports away here. // We should detect this and throw them out. // Currently processNeighborParams() detects this condition when it gets a Peer report (but not at startup!) // but we dont save the BSIC in memory so we dont have that information here where we need it. // Look at neighbor cell rx levels SACCH->neighborStartMeasurements(); int best = 0; int bestRxLevel = -1000; for (unsigned int i=0; ineighborAddMeasurement(thisFreq,thisBSCI,thisRxLevel); if (thisRxLevel>bestRxLevel) { best = i; bestRxLevel = thisRxLevel; } } int bestBCCH_FREQ_NCELL = measurements.BCCH_FREQ_NCELL(best); // (pat) This is an index into the neighborlist, not a frequency. int bestBSIC = measurements.BSIC_NCELL(best); // Is our current signal OK? //int myRxLevel = measurements.RXLEV_SUB_SERVING_CELL_dBm(); int localRSSIMin = gConfig.getNum("GSM.Handover.LocalRSSIMin"); int threshold = gConfig.getNum("GSM.Handover.ThresholdDelta"); int gprsRSSI = gConfig.getNum("GPRS.ChannelCodingControl.RSSI"); // LOG(DEBUG) << "myRxLevel=" << myRxLevel << " dBm localRSSIMin=" << localRSSIMin << " dBm"; LOG(DEBUG) <neighborText(); // Does the best exceed the current by more than the threshold? If not dont handover. if (bestRxLevel < (myRxLevel + threshold)) { return; } const char *what; if (myRxLevel > localRSSIMin) { // The current signal is ok; see if we want to do a discretionery handover. if (!( (gBTS.TCHTotal() == gBTS.TCHActive()) || // Is the current BTS full? (myRxLevel < gprsRSSI && bestRxLevel > gprsRSSI) || // Would a handover let GPRS use a better codec? (bestRxLevel > myRxLevel + 3 * threshold) // Is the other BTS *much* better? )) { return; } // If not, dont handover. what = "discretionary"; } else { // Mandatory handover because the signal is poor and the neighbor BTS is threshold better. //LOG(DEBUG) << "myRxLevel=" << myRxLevel << " dBm, best neighbor=" << bestRxLevel << " dBm, threshold=" << threshold << " dB"; what = "mandatory"; } // OK. So we will initiate a handover. Woo hoo! LOG(DEBUG) <(SACCH->hostChan()); L3LogicalChannel *mainChan = const_cast(mainChanConst); // idiotic language // The RefCntPointer prevents the tran from being deleted while we are working here, as unlikely as that would be. const RefCntPointer tran = mainChan->chanGetVoiceTran(); if (tran == NULL) { LOG(ERR) << "active SACCH with no transaction record: " << *SACCH; return; } if (tran->getGSMState() != CCState::Active) { LOG(DEBUG) << "skipping handover for transaction " << tran->tranID() << " due to state " << tran->getGSMState(); return; } // Don't hand over an emergency call based on an IMEI. It WILL fail. if (tran->servicetype() == GSM::L3CMServiceType::EmergencyCall && //Unconst(tran)->subscriber().mImsi.length() == 0) tran->subscriber().mImsi.length() == 0) { LOG(ALERT) << "cannot handover emergency call with non-IMSI subscriber ID: " << *tran; return; } // (pat) Dont handover a brand new transaction. This also prevents an MS from bouncing // back and forth between two BTS. We dont need a separate timer for this handover holdoff, // we can just use the age of the Transaction. // I dont see any such timer in the spec; I am reusing T3101ms, which is not correct but vaguely related. // Update - this is now unnecessary because the averaging method of myRxLevel prevents a handover for the first 5-10 secs. unsigned age = tran->stateAge(); // in msecs. unsigned holdoff = 1000 * gConfig.getNum("GSM.Timer.Handover.Holdoff"); // default 10 seconds. if (age < holdoff) { WATCH("skipping handover for transaction " << tran->tranID() << " due to young"<tranID() << " because age "<tranID() << " to " << peer << " with downlink RSSI " << bestRxLevel << " dbm"; // The handover reference will be generated by the other BTS. // We don't set the handover reference or state until we get RSP HANDOVER. // TODO: Check for handover request to our own BTS and avoid it. Dont forget to check the port too. #if 0 // This did not work for some reason. struct sockaddr_in peerAddr; if (resolveAddress(&peerAddr,peer.c_str())) { LOG(ALERT) "handover"<getHandoverEntry(true); L3Frame HandoverCommand(hop->mHexEncodedL3HandoverCommand.c_str()); LOG(INFO) <l3sendf(HandoverCommand); //TCH->l3sendm(GSM::L3HandoverCommand( // hep->mOutboundCell, // hep->mOutboundChannel, // hep->mOutboundReference, // hep->mOutboundPowerCmd, // hep->mOutboundSynch // )); // Start a timer for T3103, the handover failure timer. // This T3103 timer is for the outbound leg of the handover on BS1. // There is another T3103 timer in GSML1FEC for the inbound handover on BS2. GSM::Z100Timer outboundT3103(gConfig.getNum("GSM.Timer.T3103") + 1000); outboundT3103.set(); // The next step for the MS is to send Handover Access to BS2. // The next step for us is to wait for the Handover Complete message // and see that the phone doesn't come back to us. // BS2 is doing most of the work now. // We will get a handover complete once it's over, but we don't really need it. // Q: What about transferring audio packets? // A: There should not be any after we send the Handover Command. // A2: (pat 7-25-2013) Wrong, the MS may take up to a second to get around to handover, so we should keep sending // audio packets as long as we can. // Get the response. // This is supposed to time out on successful handover, similar to the early assignment channel transfer.. GSM::L3Frame *result = TCH->l2recv(outboundT3103.remaining()); if (result) { // If we got here, the handover failed and we just keep running the call. L3Message *msg = parseL3(*result); LOG(NOTICE) << "failed handover, received " << *result << msg; if (msg) { delete msg; } delete result; // Restore the call state. transaction->setGSMState(CCState::Active); return false; } // If the phone doesn't come back, either the handover succeeded or // the phone dropped the connection. Either way, we are clearing the call. // Invalidate local cache entry for this IMSI in the subscriber registry. // (pat) TODO: I dont understand how this works - it looks like it is over-writing what BS2 added. string imsi = string("IMSI").append(transaction->subscriber().mImsi); // (mike) TODO: disabled as there no longer local vs upstream caches //gSubscriberRegistry.removeUser(imsi.c_str()); transaction->teCancel(); // We need to do this immediately in case a reverse handover comes back soon. // We need to immediately destroy the dialog. LOG(INFO) "timeout following outbound handover; exiting normally"; //TCH->l2sendp(GSM::HARDRELEASE); now done by caller. return true; } }; // namespace Control