mirror of
https://github.com/RangeNetworks/openbts.git
synced 2025-10-24 16:43:58 +00:00
1331 lines
51 KiB
C++
1331 lines
51 KiB
C++
/*
|
|
* Copyright 2013, 2014 Range Networks, Inc.
|
|
*
|
|
* This software is distributed under multiple licenses;
|
|
* see the COPYING file in the main directory for licensing
|
|
* information for this specific distribution.
|
|
*
|
|
* This use of this software may be subject to additional restrictions.
|
|
* See the LEGAL file in the main directory for details.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
*/
|
|
|
|
// Written by Pat Thompson
|
|
|
|
#define LOG_GROUP LogGroup::Control // Can set Log.Level.Control for debugging
|
|
|
|
#include "L3MMLayer.h"
|
|
#include "L3MobilityManagement.h"
|
|
#include <GSMConfig.h>
|
|
#include "L3CallControl.h"
|
|
#include <GSMLogicalChannel.h> // Needed for L3LogicalChannel
|
|
#include <GSML3Message.h> // Needed for L3LogicalChannel
|
|
#include <Logger.h>
|
|
#include <Interthread.h>
|
|
#include <Threads.h>
|
|
#include <GSMTransfer.h>
|
|
#include "ControlCommon.h"
|
|
#include "L3TranEntry.h"
|
|
#include "L3SMSControl.h"
|
|
#include <SIPDialog.h>
|
|
|
|
namespace Control {
|
|
using namespace GSM;
|
|
using namespace SIP;
|
|
|
|
MMLayer gMMLayer;
|
|
Mutex gMMLock; // This is a global lock for adding/removing MMContext and MMUser and hooking them together.
|
|
// This global lock does not prevent internal modification of an MMContext/MMUser.
|
|
// The global lock must not be held when executing any state machines.
|
|
// See more comments at MMContext.
|
|
|
|
// What is to prevent an MS from allocating multiple channels, eg, a TCH and SDCCH simultaneously? I think nothing.
|
|
|
|
// Procedure State Machines:
|
|
// assignTCHF
|
|
// on success (have TCH), if MTCq, start it, else (they hung up) if SMSq run switchToSDDCH, else run closeChannel.
|
|
// switchToSDCCH
|
|
// on success (have SDCCH), if MTCq, run assignTCHF, else if SMSq start it, else run closeChannel.
|
|
// Add new transaction:
|
|
// If existing MM procedure on TMSI or IMSI, block.
|
|
// If SMS and existing SMS, queue.
|
|
|
|
// On end of any transaction (MM or CS or SMS):
|
|
// On end of CS transaction:
|
|
// If MTCq, run that, else if MTSMSq run switchToSDCCH, else run closeChannel.
|
|
// On end of SMS transaction:
|
|
// If MTSMSq, run that.
|
|
// On end of MM transaction:
|
|
// If MTCq, start assignTCH.
|
|
// On assignTCH success:
|
|
// if MTCq, run it, else, goto ?
|
|
|
|
|
|
//void MMUser::mmuCleanupDialogs() { }
|
|
|
|
void MMContext::startSMSTran(TranEntry *tran)
|
|
{
|
|
{
|
|
ScopedLock lock(gMMLock,__FILE__,__LINE__); // Make sure.
|
|
LOG(INFO) << "new MTSMS"<<LOGVAR(tran)<<LOGVAR2("chan",this);
|
|
// Tie the transaction to this channel.
|
|
devassert(this->mmGetTran(MMContext::TE_MTSMS).isNULL());
|
|
this->mmConnectTran(MMContext::TE_MTSMS,tran);
|
|
|
|
initMTSMS(tran);
|
|
}
|
|
tran->lockAndStart();
|
|
}
|
|
|
|
// (pat) WARNING: If this routine returns true it has performed the gMMLock.unlock() corresponding to a lock() in the caller.
|
|
// Setting the lock in one function and releasing it in another sucks and should be fixed.
|
|
bool MMUser::mmuServiceMTQueues() // arg redundant with mmuContext->channel.
|
|
{
|
|
devassert(gMMLock.lockcnt()); // Caller locked it.
|
|
//ScopedLock lock(mmuLock,__FILE__,__LINE__);
|
|
// TODO: check for blocks on our IMSI or TMSI?
|
|
|
|
// TODO: Move this to the logical channel main thread.
|
|
// Service the MMC queues.
|
|
if (mmuContext->mmGetTran(MMContext::TE_CS1).isNULL()) {
|
|
if (mmuMTCq.size()) {
|
|
TranEntry *tran = mmuMTCq.pop_frontr();
|
|
LOG(INFO) << "new MTC"<<LOGVAR(tran)<<LOGVAR2("chan",mmuContext);
|
|
|
|
// Tie the transaction to this channel.
|
|
mmuContext->mmConnectTran(MMContext::TE_CS1,tran);
|
|
|
|
// Did the SIP session give up while we were waiting?
|
|
// That will be handled in the MTCMachine.
|
|
switch (tran->servicetype()) {
|
|
default:
|
|
initMTC(tran);
|
|
break;
|
|
}
|
|
gMMLock.unlock();
|
|
tran->lockAndStart();
|
|
return true;
|
|
}
|
|
}
|
|
if (mmuContext->mmGetTran(MMContext::TE_MTSMS).isNULL()) {
|
|
if (mmuMTSMSq.size()) {
|
|
TranEntry *tran = mmuMTSMSq.pop_frontr();
|
|
gMMLock.unlock();
|
|
mmuContext->startSMSTran(tran);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool MMUser::mmuIsEmpty()
|
|
{
|
|
ScopedLock lock(mmuLock,__FILE__,__LINE__);
|
|
return mmuMTCq.size() + mmuMTSMSq.size() == 0;
|
|
}
|
|
|
|
bool MMContext::mmIsEmpty()
|
|
{
|
|
//devassert(gMMLock.lockcnt()); // Caller locked it.
|
|
ScopedLock lock(gMMLock,__FILE__,__LINE__);
|
|
for (unsigned i = 0; i < TE_num; i++) {
|
|
if (mmcTE[i] != NULL) { return false; }
|
|
}
|
|
return mmcMMU ? mmcMMU->mmuIsEmpty() : true;
|
|
}
|
|
|
|
// Return the Mobility Management state. Defined in 24.008 section 4.
|
|
// Except the only thing we really care about is whether any MM procedure is currently running, which is boolean.
|
|
bool MMContext::mmInMobilityManagement()
|
|
{
|
|
ScopedLock lock(gMMLock,__FILE__,__LINE__); // (pat) I dont think this lock is necessary because we use RefCntPointer now.
|
|
return ! mmGetTran(MMContext::TE_MM).isNULL();
|
|
}
|
|
|
|
// See if there are any new transactions to start.
|
|
// If all transactions are gone, initiate a channel release.
|
|
// Return true if anything happened.
|
|
bool MMContext::mmCheckNewActivity()
|
|
{
|
|
// Dont lock gMMLock up here - we may send a message later and we cant do that holding the lock.
|
|
|
|
// If there is a mobility management procedure in progress then we wont start anything else until it is finished.
|
|
// We shouldnt need to lock gMMLock yet because the MMContext cannot be deleted while in the thread that called us,
|
|
// and mmcServiceRequests is a thread safe queue.
|
|
if (! mmInMobilityManagement()) {
|
|
if (const L3Message *l3msg = mmcServiceRequests.readNoBlock()) {
|
|
const L3CMServiceRequest *cmmsg = dynamic_cast<typeof(cmmsg)>(l3msg);
|
|
NewCMServiceResponder(cmmsg,this);
|
|
delete cmmsg;
|
|
return true;
|
|
}
|
|
// We are refererencing the MMUser so we cannot let that change and the only
|
|
// completely safe way to do that is to lock the entire MMLayer.
|
|
// The unlock() corresponding to this lock() may be in mmuServiceMTQueues.
|
|
gMMLock.lock(__FILE__,__LINE__); // Do not replace this one with a scoped lock!
|
|
if (mmcMMU) {
|
|
if (mmcMMU->mmuServiceMTQueues()) { return true; } // If it returns true it unlocked the lock, gack.
|
|
}
|
|
gMMLock.unlock();
|
|
}
|
|
// If there are no transactions, kill the channel.
|
|
// We wait 5 seconds to allow a transaction to start; otherwise there is a race because
|
|
// the channel service thread that calls this method is started by an ESTABLISH sent by layer 1,
|
|
// which is sent before the message that initiates the transaction is sent.
|
|
// The 5 seconds is kind of made up. It doesnt have to be very long because at channel initiation
|
|
// the signal should be good.
|
|
// TODO: A new SIP transaction could creep in here between the time
|
|
// we check isEmpty and when the channel actually closes.
|
|
// What to do about that?
|
|
// When we detach the MMUser, if it has anything on it, just leave it there,
|
|
// and paging will restart.
|
|
if (mmIsEmpty() && mmcDuration() > 5) {
|
|
LOG(DEBUG) <<"closing"<<this;
|
|
mmcChan->chanClose(L3RRCause::Normal_Event,L3_RELEASE_REQUEST,TermCause::Local(L3Cause::No_Transaction_Expected));
|
|
return true; // This is new activity - the calling loop should skip back to the top
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// What a stupid language.
|
|
MMUser::MMUser(string& wImsi)
|
|
{
|
|
MMUserInit();
|
|
mmuImsi = wImsi;
|
|
LOG(DEBUG) << "MMUser ALLOC "<<(void*)this;
|
|
}
|
|
|
|
//MMUser::MMUser(string& wImsi, TMSI_t wTmsi)
|
|
//{
|
|
// MMUserInit();
|
|
// mmuImsi = wImsi;
|
|
// mmuTmsi = wTmsi;
|
|
// LOG(DEBUG) << "MMUser ALLOC "<<(void*)this;
|
|
//}
|
|
|
|
|
|
//GSM::CMServiceTypeCode MMUser::mmuGetInitialServiceType()
|
|
//{
|
|
// devassert(gMMLock.lockcnt()); // Caller locked it.
|
|
// if (mmuMTCq.size()) {
|
|
// TranEntry *front = this->mmuMTCq.front();
|
|
// return front->servicetype();
|
|
// }
|
|
// devassert(mmuMTSMSq.size());
|
|
// // The purpose of this is to choose the channel type, so it doesnt really matter what the servicetype is as long as it is one that can use SDCCH.
|
|
// return L3CMServiceType::ShortMessage;
|
|
//}
|
|
|
|
GSM::ChannelType MMUser::mmuGetInitialChanType() const
|
|
{
|
|
devassert(gMMLock.lockcnt()); // Caller locked it.
|
|
if (mmuMTCq.size()) {
|
|
TranEntry *front = this->mmuMTCq.front();
|
|
switch (front->servicetype()) {
|
|
case L3CMServiceType::MobileOriginatedCall:
|
|
devassert(0);
|
|
case L3CMServiceType::MobileTerminatedCall:
|
|
case L3CMServiceType::EmergencyCall:
|
|
return gConfig.getBool("Control.VEA") ? GSM::TCHFType : GSM::SDCCHType;
|
|
default: // There shouldnt be anything else in the MTCq.
|
|
return GSM::SDCCHType;
|
|
}
|
|
}
|
|
devassert(mmuMTSMSq.size());
|
|
return GSM::SDCCHType;
|
|
}
|
|
|
|
// Caller enters with the whole MMLayer locked so no one will try to add new contexts while we are doing this.
|
|
void MMUser::mmuFree(MMUserMap::iterator *piter, TermCause cause) // Some callers deleted it from the MMUsers more efficiently than looking it up again.
|
|
{
|
|
devassert(mmuContext == NULL); // Caller already unlinked or verified that it was unattached.
|
|
devassert(gMMLock.lockcnt()); // Caller locked it.
|
|
|
|
// mmuCleanupDialogs();
|
|
|
|
{
|
|
ScopedLock lock(mmuLock,__FILE__,__LINE__); // Now redundant.
|
|
LOG(DEBUG) << "MMUser DELETE "<<(void*)this <<LOGVAR(!!piter);
|
|
// At this point the only pointer to the transaction is in the InterthreadQueue.
|
|
// Once the transaction moves to the MMContext it will be put in a RefCntPointer.
|
|
while (TranEntry *tran = mmuMTCq.pop_frontr()) { tran->teCancel(cause); delete tran; }
|
|
while (TranEntry *tran = mmuMTSMSq.pop_frontr()) { tran->teCancel(cause); delete tran; }
|
|
|
|
if (piter) { // It is just an efficiency issue to use the iterator if we already have one.
|
|
gMMLayer.MMUsers.erase(*piter);
|
|
} else {
|
|
LOG(DEBUG) << "MMUser erase begin " << this->mmuImsi;
|
|
bool exists = gMMLayer.MMUsers.find(this->mmuImsi) != gMMLayer.MMUsers.end();
|
|
LOG(DEBUG) << "MMUser erase "<<this->mmuImsi<<LOGVAR(exists);
|
|
gMMLayer.MMUsers.erase(this->mmuImsi);
|
|
}
|
|
assert(gMMLayer.MMUsers.find(this->mmuImsi) == gMMLayer.MMUsers.end());
|
|
}
|
|
// The ScopedLock points into MMUser so we must release it before deleting this.
|
|
delete this;
|
|
}
|
|
|
|
|
|
bool MMContext::mmCheckSipMsgs()
|
|
{
|
|
// Update: We cannot hold the global lock while invoking state machines because they can block.
|
|
// As an interim measure, just dont lock this and hope for the best.
|
|
//ScopedLock lock(gMMLock,__FILE__,__LINE__); // I think this is unnecessary, but be safe.
|
|
bool result = false;
|
|
for (unsigned i = TE_first; i < TE_num; i++) {
|
|
RefCntPointer<TranEntry> tranp = mmGetTran(i);
|
|
if (! tranp.isNULL()) { result |= tranp->lockAndInvokeSipMsgs(); }
|
|
}
|
|
return result;
|
|
}
|
|
|
|
bool MMContext::mmCheckTimers()
|
|
{
|
|
// Update: We cannot hold the global lock while invoking state machines because they can block.
|
|
// As an interim measure, just dont lock this and hope for the best.
|
|
//ScopedLock lock(gMMLock,__FILE__,__LINE__); // I think this is unnecessary; the channel cannot change when this is called, but be safe.
|
|
// Check MM timers.
|
|
|
|
// checkTimers locks the transaction if any timer needs servicing.
|
|
bool result = false;
|
|
for (unsigned i = TE_first; i < TE_num; i++) {
|
|
RefCntPointer<TranEntry> tranp = mmGetTran(i);
|
|
if (! tranp.isNULL()) { result |= tranp->checkTimers(); }
|
|
}
|
|
return result;
|
|
}
|
|
|
|
|
|
// This is the first L3 message on the new channel.
|
|
// The Context for this channel is empty.
|
|
// All the TranEntrys are still in the Context on the old channel.
|
|
//void MMContext::reassignComplete()
|
|
//{
|
|
//
|
|
// //case L3RRCASE(AssignmentComplete):
|
|
// // TODO: what timer? timeout1Cancel();
|
|
// //timerStop(TChReassignment); // Handled by assignTCHFProcedure, which is notified after us.
|
|
// LOG(INFO) << "successful assignment";
|
|
//
|
|
// // The two channels are serviced by different threads.
|
|
// // TODO: We need to lock the other channels thread.
|
|
//
|
|
// // Move all the transactions to the new channel:
|
|
// MMContext *prevSet = mPrevChan->getContext();
|
|
// for (unsigned i = 0; i < TE_num; i++) {
|
|
// devassert(mmcTE[i] == NULL);
|
|
// mmcTE[i] = prevSet->mmcTE[i];
|
|
// prevSet->mmcTE[i] = NULL;
|
|
// if (mmcTE[i]) { mmcTE[i]->mContext = this; }
|
|
// }
|
|
//#endif
|
|
//
|
|
// // release the old channel.
|
|
// // The old SDCCH channel will be released when the l2recv finishes and the channel notices that its state has changed.
|
|
// // mPrevChan->l3sendp(GSM::HARDRELEASE); Dont do this. It can block. Set the chReassignComplete flag and let that thread do it.
|
|
// mPrevChan->chanSetState(L3LogicalChannel::chReassignComplete);
|
|
//
|
|
// // We are going to delete this. So get everything we want out of it first.
|
|
//
|
|
// // Just move the MMContext prevChan to this one.
|
|
// //mChan->freeContext(); // It is not being used, but we are running in it!
|
|
// //mChan->mContext = mPrevChan->mTranSet;
|
|
// //mPrevChan->mContext = NULL;
|
|
//
|
|
// mPrevChan->chanMoveTo(this->mChan); // Careful! Deletes this as a side effect.
|
|
//
|
|
// // Clear everything. This is overkill because some of these are already 0.
|
|
// prevSet->mNextChan = prevSet->mPrevChan = 0;
|
|
// this->mNextChan = this->mPrevChan = 0;
|
|
//
|
|
// //tran()->setChannel(tran()->mNextChannel);
|
|
// //tran()->mNextChan = NULL;
|
|
// //return callProcStart(new MOCConnect(tran())); Now it could be for MTC too.
|
|
//}
|
|
|
|
// The significant bits of the L3TI. The fourth bit is a direction indicator and we ignore it.
|
|
//static int l3TISigBits(int val) { return val & 7; }
|
|
|
|
#if UNUSED
|
|
// // Find the transaction that wants this frame/message.
|
|
// TranEntry *MMContext::findTran(L3PD pd, unsigned ti, int mti)
|
|
// {
|
|
// devassert(gMMLock.lockcnt()); // Caller locked it.
|
|
// TranEntry *tran = NULL;
|
|
// switch (pd) {
|
|
// case L3CallControlPD: {
|
|
// // Setup message is special because it is the message that establishes the TI correspondence.
|
|
// bool isSetup = (mti == L3CCMessage::Setup);
|
|
// TranEntry *cs = mmcTE[TE_CS1];
|
|
// if (cs && (isSetup || l3TISigBits(cs->getL3TI()) == l3TISigBits(ti))) {
|
|
// return cs;
|
|
// }
|
|
// break;
|
|
// }
|
|
// case L3SMSPD: {
|
|
// for (int tx = TE_MOSMS1; tx <= TE_MTSMS; tx++) {
|
|
// TranEntry *sms = mmcTE[tx];
|
|
// if (sms && l3TISigBits(sms->getL3TI()) == l3TISigBits(ti)) {
|
|
// tran = sms;
|
|
// break;
|
|
// }
|
|
// }
|
|
// // For MO-SMS the TI in the transaction is not set until the first CP-DATA message arrives.
|
|
// // So if no transaction matched this specific TI, we send the message to the primary MO-SMS transaction and hope for the best.
|
|
// if (tran == NULL) { tran = mmcTE[TE_MOSMS1]; }
|
|
// break;
|
|
// }
|
|
// case L3RadioResourcePD:
|
|
// #if 0 // Now both channels share the MMContext so we just send it normally.
|
|
// if (l3msg->MTI() == L3RRMessage::AssignmentComplete) {
|
|
// // We have to notify the Procedure when complete, however when rmsimsieassignComplete returns
|
|
// // we have replaced the MMContext on this channel with the one from the old channel,
|
|
// // and 'this' has been deleted. So we pass the tran that needs to be notified to reassignComplete
|
|
// // to actually do it, and we have to return from here without touching the data again.
|
|
// if (! postReassignment) {
|
|
// L3LogicalChannel *chan = this->tsChannel();
|
|
// chan->reassignComplete();
|
|
// // Be careful! 'this' is now invalid! That is why we cached channel().
|
|
// // Redispatch the message on the now-reassigned channel.
|
|
// return chan->chanGetContext()->mmDispatchL3Msg(l3msg,true);
|
|
// }
|
|
// // else fall through
|
|
// }
|
|
// #endif
|
|
// // Fall through for all other RR messages.
|
|
// case L3MobilityManagementPD:
|
|
// // TODO: This is a hack. We should split the Procedures into MM and CS parts, and
|
|
// // run the MM procedure first to identify the channel, then send a message to the CS procedure to start it.
|
|
// tran = mmcTE[TE_MM] ? mmcTE[TE_MM] : mmcTE[TE_CS1] ? mmcTE[TE_CS1] : mmcTE[TE_MOSMS1] ? mmcTE[TE_MOSMS1] : mmcTE[TE_MTSMS];
|
|
// break;
|
|
// default:
|
|
// LOG(ERR) << "unrecognized L3 frame:"<<LOGVAR(pd)<<LOGVAR(ti);
|
|
// return NULL; // hopeless.
|
|
// }
|
|
// return NULL;
|
|
// }
|
|
#endif
|
|
|
|
// Either frame or l3msg may be NULL.
|
|
RefCntPointer<TranEntry> MMContext::findTran(const L3Frame *frame, const L3Message *l3msg) const
|
|
{
|
|
ScopedLock lock(gMMLock,__FILE__,__LINE__); //FIXMENOW
|
|
GSM::L3PD pd; int ti;
|
|
// Handle naked primitives.
|
|
if (frame && !frame->isData()) {
|
|
// Theoretically primitives should go to all transactions, but in reality the only state
|
|
// machine that wants to receive primitives is MT-SMS, so we will just triage the primitives here
|
|
// by returning that transaction, if any.
|
|
return mmcTE[TE_MTSMS].self(); // We dont need to keep this locked because the tran is used only internally, so can use self and return TranEntry*
|
|
}
|
|
if (frame && frame->isData()) {
|
|
pd = frame->PD();
|
|
ti = frame->TI(); // Only call control, SMS and SS frames have a useful ti.
|
|
} else if (l3msg) {
|
|
pd = l3msg->PD();
|
|
ti = l3msg->TI(); // Only call control, SMS and SS frames have a useful ti; returns nonsense for other PDs.
|
|
} else {
|
|
return (TranEntry*)NULL; // Shouldnt happen, but be safe.
|
|
}
|
|
switch (pd) {
|
|
case L3CallControlPD: {
|
|
// Setup message is special because it is the message that establishes the TI correspondence.
|
|
// Dont need to bother checking l3msg because we dont send naked setup messages.
|
|
int mti = frame ? frame->MTI() : l3msg->MTI();
|
|
bool isSetup = (mti == L3CCMessage::Setup);
|
|
TranEntry *cs = mmcTE[TE_CS1].self();
|
|
if (cs && (isSetup || cs->matchL3TI(ti,true))) {
|
|
return cs;
|
|
}
|
|
break;
|
|
}
|
|
case L3SMSPD: {
|
|
for (int tx = TE_MOSMS1; tx <= TE_MTSMS; tx++) {
|
|
TranEntry *sms = mmcTE[tx].self();
|
|
if (sms && sms->matchL3TI(ti,true)) {
|
|
return sms;
|
|
}
|
|
}
|
|
// For MO-SMS the TI in the transaction is not set until the first CP-DATA message arrives.
|
|
// So if no transaction matched this specific TI, we send the message to the primary MO-SMS transaction and hope for the best.
|
|
if (TranEntry *te = mmcTE[TE_MOSMS1].self()) { return te; }
|
|
break;
|
|
}
|
|
case L3RadioResourcePD:
|
|
// Fall through for all other RR messages.
|
|
case L3MobilityManagementPD:
|
|
// TODO: This is a hack. We should split the Procedures into MM and CS parts, and
|
|
// run the MM procedure first to identify the channel, then send a message to the CS procedure to start it.
|
|
//return mmcTE[TE_MM] ? mmcTE[TE_MM] : mmcTE[TE_CS1] ? mmcTE[TE_CS1] : mmcTE[TE_MOSMS1] ? mmcTE[TE_MOSMS1] : mmcTE[TE_MTSMS];
|
|
for (unsigned txi = TE_MM; txi < TE_num; txi++) {
|
|
if (TranEntry *te = mmcTE[txi].self()) { return te; }
|
|
}
|
|
break;
|
|
case L3NonCallSSPD: {
|
|
// The transaction identifier is used to identify whether the SS message applies to a specific
|
|
// call or is outside any call, ie, was started by a CM service request.
|
|
// I am not sure what to do about in-call SS: we could pass the USSD SIP INFO message in
|
|
// the dialog of the call, but asterisk is going to just dump it.
|
|
for (ActiveTranIndex ati = TE_CS1; ati <= TE_CSHold; ati = (ActiveTranIndex) (ati + 1)) {
|
|
TranEntry *cs = mmcTE[ati].self();
|
|
if (cs && cs->matchL3TI(ti,true)) {
|
|
WATCHINFO("Found SS message matching CC transaction" <<LOGVAR2("message ti",ti)<<LOGVAR2("transaction ti",cs->getL3TI()));
|
|
return cs;
|
|
}
|
|
}
|
|
TranEntry *te = mmcTE[TE_SS].self();
|
|
if (te) {
|
|
// Dont even bother to check the tran id.
|
|
// If it is a SSRegister message, it would be defining the tran id.
|
|
WATCHINFO("Sending SS message to SS machine"<<LOGVAR2("message ti",ti)<<LOGVAR2("transaction ti",te->getL3TI()));
|
|
} else {
|
|
WATCH("Ignoring SS message with no transaction "<<l3msg);
|
|
}
|
|
return te;
|
|
}
|
|
break;
|
|
default:
|
|
LOG(ERR) << "unrecognized L3 frame:"<<LOGVAR(pd);
|
|
return (TranEntry*)NULL; // hopeless.
|
|
}
|
|
LOG(INFO) << "No transaction found to handle frame with"<<LOGVAR2("PD",pd)<<LOGVAR2("TI",ti)<<LOGVAR2("MTI",frame->MTI());
|
|
return (TranEntry*)NULL;
|
|
}
|
|
|
|
|
|
#if UNUSED
|
|
// Find the transaction that wants to handle this message and invoke it.
|
|
// Return true if the message was handled.
|
|
//bool MMContext::mmDispatchL3Msg(const L3Message *l3msg, bool postReassignment)
|
|
bool MMContext::mmDispatchL3Msg(const L3Message *l3msg, L3LogicalChannel *chan)
|
|
{
|
|
ScopedLock lock(gMMLock,__FILE__,__LINE__); // I think this is unnecessary, but be safe.
|
|
GSM::L3PD pd = l3msg->PD();
|
|
LOG(DEBUG) << LOGVAR(pd) << this;
|
|
int ti = 0, mti = 0;
|
|
if (pd == L3CallControlPD || pd == L3SMSPD) {
|
|
ti = l3msg->TI();
|
|
mti = l3msg->MTI();
|
|
}
|
|
RefCntPointer<TranEntry> tran = findTran(pd,ti,mti);
|
|
|
|
//TranEntry *tran = NULL;
|
|
////GSM::L3CMServiceType service = tran->service();
|
|
//switch (pd) {
|
|
// case L3CallControlPD: {
|
|
// //bool isSetup = (pd == L3CallControlPD) && l3msg->MTI() == L3CCMessage::Setup;
|
|
// //msgti = dynamic_cast<L3CCMessage*>(l3msg)->TI();
|
|
// // Setup message is special because it is the messages that establishes the TI correspondence.
|
|
// TranEntry *cs = mmcTE[TE_CS1];
|
|
// if (cs) LOG(DEBUG) << cs <<LOGVAR(l3msg->TI());
|
|
// if (cs && (l3msg->MTI() == L3CCMessage::Setup || l3TISigBits(cs->getL3TI()) == l3TISigBits(l3msg->TI()))) {
|
|
// tran = cs;
|
|
// }
|
|
// break;
|
|
// }
|
|
// case L3SMSPD: {
|
|
// //msgti = dynamic_cast<CPMessage*>(l3msg)->TI();
|
|
// for (int tx = TE_MOSMS1; tx <= TE_MTSMS; tx++) {
|
|
// TranEntry *sms = mmcTE[tx];
|
|
// if (sms) {
|
|
// LOG(DEBUG) <<LOGVAR(tx)<<LOGVAR(sms->getL3TI())<<LOGVAR(l3msg->TI());
|
|
// }
|
|
// if (sms && l3TISigBits(sms->getL3TI()) == l3TISigBits(l3msg->TI())) {
|
|
// tran = sms;
|
|
// break;
|
|
// }
|
|
// // For MO-SMS the TI in the transaction is not set until the first CP-DATA message arrives.
|
|
// // So if no transaction matched this specific TI, we send the message to the primary MO-SMS transaction and hope for the best.
|
|
// if (tran == NULL) { tran = mmcTE[TE_MOSMS1]; }
|
|
// }
|
|
// break;
|
|
// }
|
|
// case L3RadioResourcePD:
|
|
//#if 0 // Now both channels share the MMContext so we just send it normally.
|
|
// if (l3msg->MTI() == L3RRMessage::AssignmentComplete) {
|
|
// // We have to notify the Procedure when complete, however when rmsimsieassignComplete returns
|
|
// // we have replaced the MMContext on this channel with the one from the old channel,
|
|
// // and 'this' has been deleted. So we pass the tran that needs to be notified to reassignComplete
|
|
// // to actually do it, and we have to return from here without touching the data again.
|
|
// if (! postReassignment) {
|
|
// L3LogicalChannel *chan = this->tsChannel();
|
|
// chan->reassignComplete();
|
|
// // Be careful! 'this' is now invalid! That is why we cached channel().
|
|
// // Redispatch the message on the now-reassigned channel.
|
|
// return chan->chanGetContext()->mmDispatchL3Msg(l3msg,true);
|
|
// }
|
|
// // else fall through
|
|
// }
|
|
//#endif
|
|
// // Fall through for all other RR messages.
|
|
// case L3MobilityManagementPD:
|
|
// // TODO: This is a hack. We should split the Procedures into MM and CS parts, and
|
|
// // run the MM procedure first to identify the channel, then send a message to the CS procedure to start it.
|
|
// tran = mmcTE[TE_MM] ? mmcTE[TE_MM] : mmcTE[TE_CS1] ? mmcTE[TE_CS1] : mmcTE[TE_MOSMS1] ? mmcTE[TE_MOSMS1] : mmcTE[TE_MTSMS];
|
|
// break;
|
|
// default:
|
|
// LOG(ERR) << "unrecognized L3"<<LOGVAR(pd);
|
|
// return NULL; // hopeless.
|
|
//}
|
|
if (tran && ! tran->deadOrRemoved()) {
|
|
LOG(DEBUG) << tran;
|
|
// We pass chan, not mmcChan. They are != only during channel reassignment.
|
|
return tran->lockAndInvokeL3Msg(l3msg);
|
|
}
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
// Arguments are the L3 frame and if the frame is non-primitive the result of parsel3(frame).
|
|
// l3msg may be NULL for primitives or unparseable messages.
|
|
// Frame may be NULL when we send a naked message.
|
|
bool MMContext::mmDispatchL3Frame(const L3Frame *frame, const L3Message *msg)
|
|
{
|
|
// Update: We cannot hold the global lock while invoking state machines because they can block.
|
|
// As an interim measure, just dont lock this and hope for the best.
|
|
//ScopedLock lock(gMMLock,__FILE__,__LINE__); // I think this is unnecessary, but be safe.
|
|
// Does any transaction want this frame/message?
|
|
RefCntPointer<TranEntry> tran = findTran(frame,msg);
|
|
LOG(DEBUG) << *frame << tran.self();
|
|
if (tran == (TranEntry*)NULL) { return false; }
|
|
|
|
if (tran->deadOrRemoved()) {
|
|
if (msg) {
|
|
LOG(INFO) <<"Received message for expired transaction. "<<*msg;
|
|
} else {
|
|
LOG(INFO) <<"Received unparseable frame for expired transaction. "<<*frame;
|
|
}
|
|
return false;
|
|
}
|
|
return tran->lockAndInvokeFrame(frame,msg);
|
|
}
|
|
|
|
|
|
void MMContext::mmcPageReceived() const
|
|
{
|
|
ScopedLock lock(gMMLock,__FILE__,__LINE__); // I think this is unnecessary, but be safe.
|
|
// TODO: We should do a single authentication, if necessary, before starting both MTC and MTSMS
|
|
|
|
RefCntPointer<TranEntry> tran1 = mmGetTran(MMContext::TE_CS1);
|
|
if (! tran1.isNULL()) {
|
|
// The MS sent a page response when there is an active voice call? Either that or we are totally goofed up.
|
|
LOG(ERR) <<mmcChan <<" received page response while MS had active voice call:"<<tran1.self();
|
|
}
|
|
RefCntPointer<TranEntry> tran2 = mmGetTran(MMContext::TE_MTSMS);
|
|
if (! tran2.isNULL()) {
|
|
LOG(ERR) <<mmcChan <<" received page response while MS had active MT-SMS:"<<tran2.self();
|
|
}
|
|
|
|
// We dont need to do anything else. The service loop will notice and start new transactions.
|
|
}
|
|
|
|
// TODO: We need to save when each TI was last used instead of just round-robin them.
|
|
unsigned MMContext::mmGetNextTI()
|
|
{
|
|
mNextTI++;
|
|
// L3TI values are 0-7, with bit 4 set when communicating a TI sent by the other side.
|
|
// Avoid the value 7. It is an extension mechanism in some protocols and an error indication in others.
|
|
if (mNextTI >= 7) { mNextTI = 0; }
|
|
return mNextTI;
|
|
}
|
|
|
|
#if UNUSED
|
|
bool MMLayer::mmStartMTDialog(SipDialog *dialog, SipMessage *invite)
|
|
{
|
|
ScopedLock lock(gMMLock,__FILE__,__LINE__); // I think this is unnecessary, but be safe.
|
|
|
|
#if UNUSED
|
|
// Find any active transaction for this IMSI with an assigned TCH or SDCCH.
|
|
L3LogicalChannel *chan = gTransactionTable.findChannel(mobileID);
|
|
if (chan) {
|
|
// If the type is TCH and the service is SMS, get the SACCH.
|
|
// Otherwise, for now, just say chan=NULL.
|
|
if (serviceType==L3CMServiceType::MobileTerminatedShortMessage && chan->chtype()==FACCHType) {
|
|
chan = chan->getL2Channel()->SACCH(); // GSM Specific.
|
|
} else {
|
|
// FIXME -- This will change to support multiple transactions.
|
|
// (pat) Yes. For voice calls we need to initiate a call-waiting notification.
|
|
// For SMS we need to add to the SMS queue.
|
|
chan = NULL;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
TODO: Add this isBusy check
|
|
|
|
// So we will need a new channel.
|
|
// Check gBTS for channel availability.
|
|
if (!chan && !channelAvailable) {
|
|
LOG(CRIT) << "MTC CONGESTION, no channel availble";
|
|
// FIXME -- We need the retry-after header.
|
|
//newSendEarlyError(msg,proxy.c_str(),503,"Service Unvailable");
|
|
dialog->sendError(503,"Service Unavailable");
|
|
return;
|
|
}
|
|
if (chan) { LOG(INFO) << "using existing channel " << chan->descriptiveString(); }
|
|
else { LOG(INFO) << "set up MTC paging for channel=" << requiredChannel; }
|
|
|
|
// Check for new user busy condition.
|
|
if (!chan && gTransactionTable.isBusy(mobileID)) {
|
|
LOG(NOTICE) << "user busy: " << mobileID;
|
|
//newSendEarlyError(msg,proxy.c_str(),486,"Busy Here");
|
|
dialog->sendError(503,"Service Unavailable");
|
|
dialog->detach();
|
|
return;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
#endif
|
|
|
|
void MMUser::mmuAddMT(TranEntry *tran)
|
|
{
|
|
ScopedLock lock(gMMLock,__FILE__,__LINE__); // Way overkill.
|
|
//ScopedLock lock(mmuLock,__FILE__,__LINE__);
|
|
mmuPageTimer.future(gConfig.GSM.Timer.T3113);
|
|
switch (tran->servicetype()) {
|
|
case L3CMServiceType::MobileTerminatedCall:
|
|
mmuMTCq.push_back(tran);
|
|
break;
|
|
case L3CMServiceType::MobileTerminatedShortMessage:
|
|
mmuMTSMSq.push_back(tran);
|
|
break;
|
|
default:
|
|
assert(0);
|
|
}
|
|
}
|
|
|
|
void MMUser::mmuText(std::ostream&os) const
|
|
{
|
|
ScopedLock lock(gMMLock,__FILE__,__LINE__); // way overkill
|
|
//ScopedLock lock(mmuLock,__FILE__,__LINE__);
|
|
os << " MMUser(";
|
|
os <<LOGVAR2("state",mmuState) <<LOGVAR2("imsi",mmuImsi) <<LOGVAR2("tmsi",mmuTmsi);
|
|
if (mmuContext) {
|
|
os << " channel:" << mmuContext->tsChannel();
|
|
} else {
|
|
os << " channel:(unattached)";
|
|
}
|
|
os << " queued transactions:";
|
|
//os <<LOGVAR2("MTC_queue_size",mmuMTCq.size()) <<LOGVAR2("SMS_queue_size",mmuMTSMSq.size());
|
|
for (MMUQueue_t::const_iterator it = mmuMTCq.begin(); it != mmuMTCq.end(); ++it) {
|
|
const TranEntry *tran = *it;
|
|
os << " CS:" << tran->tranID();
|
|
}
|
|
for (MMUQueue_t::const_iterator it = mmuMTSMSq.begin(); it != mmuMTSMSq.end(); ++it) {
|
|
const TranEntry *tran = *it;
|
|
os << " SMS:" << tran->tranID();
|
|
}
|
|
os << ")";
|
|
}
|
|
string MMUser::mmuText() const { std::ostringstream ss; mmuText(ss); return ss.str(); }
|
|
std::ostream& operator<<(std::ostream& os, const MMUser&mmu) { mmu.mmuText(os); return os; }
|
|
std::ostream& operator<<(std::ostream& os, const MMUser*mmu) { if (mmu) mmu->mmuText(os); else os << "(null MMUser)"; return os; }
|
|
|
|
void MMContext::MMContextInit()
|
|
{
|
|
// (pat) The BLU phone seems to have a bug that a new MTC beginning too soon after a previous MTC with the same TI
|
|
// seems to hang the phone, even though we definitely went through the CC release procedure whose specific
|
|
// purpose is to release the TI for recycling. Making the initial TI random seems to help.
|
|
mNextTI = rand() & 0x7; // Not supposed to matter what we pick here.
|
|
mmcMMU = NULL;
|
|
mmcChan = NULL;
|
|
mmcChannelUseCnt = 1;
|
|
//mVoiceTrans = NULL;
|
|
memset(mmcTE,0,sizeof(mmcTE));
|
|
mmcOpenTime = time(NULL);
|
|
LOG(DEBUG)<<"MMContext ALLOC "<<(void*)this;
|
|
}
|
|
|
|
// Called only from L3LogicalChannel::chanGetContext()
|
|
MMContext::MMContext(L3LogicalChannel *wChan)
|
|
{
|
|
MMContextInit();
|
|
mmcChan = wChan;
|
|
}
|
|
|
|
MMContext *MMContext::tsDup()
|
|
{
|
|
ScopedLock lock(gMMLock,__FILE__,__LINE__);
|
|
mmcChannelUseCnt++; // There are now two channels referring to the same MMContext.
|
|
LOG(DEBUG) << *this;
|
|
return this;
|
|
}
|
|
|
|
string MMContext::mmGetImsi(bool verbose)
|
|
{
|
|
ScopedLock lock(gMMLock,__FILE__,__LINE__); // I think this is unnecessary, but be safe.
|
|
return mmcMMU ? mmcMMU->mmuGetImsi(verbose) : (verbose ? string("no-MMUser") : string(""));
|
|
}
|
|
|
|
void MMContext::l3sendm(const GSM::L3Message& msg, const GSM::Primitive& prim/*=GSM::DATA*/, SAPI_t SAPI/*=0*/)
|
|
{
|
|
WATCHINFO("sendm "<<this <<LOGVAR(prim)<<LOGVAR(SAPI)<<" "<<msg);
|
|
mmcChan->l2sendm(msg,prim,SAPI);
|
|
}
|
|
|
|
void MMContext::mmcText(std::ostream&os) const
|
|
{
|
|
// Called from CLI so we need to lock the MMContext, and the only way we can do that is the global lock.
|
|
ScopedLock lock(gMMLock,__FILE__,__LINE__); // way overkill.
|
|
os << " MMContext(";
|
|
os <<mmcChan;
|
|
os <<LOGVAR(mmcChannelUseCnt);
|
|
os <<LOGVAR2("duration",mmcDuration());
|
|
if (mmcMMU) { os <<LOGVAR(mmcMMU); }
|
|
if (mmcTE[TE_MM] != NULL) { os <<LOGVAR2("MM",*mmcTE[TE_MM]); }
|
|
if (mmcTE[TE_CS1] != NULL) { os <<LOGVAR2("CS",*mmcTE[TE_CS1]); }
|
|
if (mmcTE[TE_MOSMS1] != NULL) { os <<LOGVAR2("MO-SMS",*mmcTE[TE_MOSMS1]); }
|
|
if (mmcTE[TE_MOSMS2] != NULL) { os <<LOGVAR2("MO-SMS2",*mmcTE[TE_MOSMS2]); }
|
|
if (mmcTE[TE_MTSMS] != NULL) { os <<LOGVAR2("MT-SMS",*mmcTE[TE_MTSMS]); }
|
|
if (mmcTE[TE_SS] != NULL) { os <<LOGVAR2("SS",*mmcTE[TE_SS]); }
|
|
os << ")";
|
|
}
|
|
|
|
std::ostream& operator<<(std::ostream& os, const MMContext&mmc) { mmc.mmcText(os); return os; }
|
|
std::ostream& operator<<(std::ostream& os, const MMContext*mmc) { if (mmc) mmc->mmcText(os); else os << "(null Context)"; return os; }
|
|
|
|
|
|
void MMContext::getTranIds(TranEntryList &tranlist) const
|
|
{
|
|
// This is called from the CLI via L3LogicalChannel, which locks the L3LogicalChannel, guaranteeing
|
|
// that the MMContext is not freed while we are here. We need to lock this particular MMContext
|
|
// to avoid changes to mmcTE, but we dont have a private lock in each MMContext, only the global lock,
|
|
// so we lock that, even though it is way overkill.
|
|
ScopedLock lock(gMMLock,__FILE__,__LINE__); // Way overkill, but necessary to lock MMContext.
|
|
tranlist.clear();
|
|
for (unsigned ati = TE_first; ati < TE_num; ati++) {
|
|
if (mmcTE[ati] != NULL) { tranlist.push_back(mmcTE[ati]->tranID()); }
|
|
}
|
|
if (mmcMMU) {
|
|
// TODO
|
|
}
|
|
}
|
|
|
|
RefCntPointer<TranEntry> MMContext::mmGetTran(unsigned ati) const
|
|
{
|
|
ScopedLock lock(gMMLock,__FILE__,__LINE__); // overkill to lock the world but its the lock we have.
|
|
assert(ati < TE_num);
|
|
return mmcTE[ati];
|
|
}
|
|
|
|
// Connect the Transaction to this channel. Sets pointers in both directions.
|
|
// After this, the RefCntPointer in mmcTE takes over the job of deleting the transaction when the last pointer to it disappears.
|
|
void MMContext::mmConnectTran(ActiveTranIndex ati, TranEntry *tran)
|
|
{
|
|
devassert(gMMLock.lockcnt()); // Caller locked it.
|
|
// When a primary transaction is deleted we may promote the secondary transaction, so keep trying to make sure we delete them all:
|
|
for (unsigned tries = 0; tries < 3; tries++) {
|
|
if (mmcTE[ati] != NULL) {
|
|
LOG(ERR) << "Transaction over-writing existing transaction"
|
|
<<LOGVAR2("old_transaction",*mmcTE[ati])<<LOGVAR2("new_transaction",tran);
|
|
// (pat) This is a bug somewhere.
|
|
mmcTE[ati]->teCancel(TermCause::Local(L3Cause::L3_Internal_Error));
|
|
}
|
|
}
|
|
mmcTE[ati] = tran; // Takes charge of tran; increments the refcnt
|
|
tran->teSetContext(this); // And set the back pointer.
|
|
|
|
}
|
|
|
|
// Connect the Transaction to this channel. Sets pointers in both directions.
|
|
void MMContext::mmConnectTran(TranEntry *tran)
|
|
{
|
|
ScopedLock lock(gMMLock,__FILE__,__LINE__); // I think this is unnecessary, but be safe.
|
|
ActiveTranIndex txi;
|
|
switch (tran->servicetype()) {
|
|
case L3CMServiceType::MobileTerminatedCall:
|
|
case L3CMServiceType::MobileOriginatedCall:
|
|
case L3CMServiceType::EmergencyCall:
|
|
case L3CMServiceType::HandoverCall:
|
|
txi = TE_CS1;
|
|
break;
|
|
case L3CMServiceType::ShortMessage: // specifically, MO-SMS
|
|
txi = mmcTE[TE_MOSMS1]!=NULL ? TE_MOSMS2 : TE_MOSMS1;
|
|
break;
|
|
case L3CMServiceType::MobileTerminatedShortMessage:
|
|
txi = TE_MTSMS;
|
|
break;
|
|
case L3CMServiceType::LocationUpdateRequest:
|
|
txi = TE_MM;
|
|
break;
|
|
|
|
case L3CMServiceType::SupplementaryService:
|
|
WATCHINFO("connect tran for SS");
|
|
txi = TE_SS;
|
|
break;
|
|
//VoiceCallGroup=9,
|
|
//VoiceBroadcast=10,
|
|
//LocationService=11, // (pat) See GSM 04.71. Has nothing to do with MM Location Update.
|
|
default:
|
|
assert(0);
|
|
}
|
|
mmConnectTran(txi,tran);
|
|
}
|
|
|
|
|
|
// This is called only from TranEntry which is already in the process of deleting the transaction.
|
|
void MMContext::mmDisconnectTran(TranEntry *tran)
|
|
{
|
|
ScopedLock lock(gMMLock,__FILE__,__LINE__); // I think this is unnecessary, but be safe.
|
|
for (unsigned tx = 0; tx < TE_num; tx++) {
|
|
if (mmcTE[tx] == tran) {
|
|
LOG(DEBUG) << "found "<<tran;
|
|
mmcTE[tx].free();
|
|
assert(mmcTE[tx] == 0);
|
|
if (tx == TE_MOSMS1) {
|
|
// Promote the secondary SMS transaction to primary.
|
|
mmcTE[TE_MOSMS1] = mmcTE[TE_MOSMS2];
|
|
mmcTE[TE_MOSMS2] = NULL;
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
// This is pretty bad.
|
|
LOG(ERR) << "Attempt to remove transaction "<<tran->tranID()<<" not found in MMContext";
|
|
}
|
|
|
|
|
|
// Does nothing if already unlinked.
|
|
void MMContext::mmcUnlink()
|
|
{
|
|
devassert(gMMLock.lockcnt()); // Caller locked it.
|
|
MMContext *mmc = this;
|
|
MMUser *mmu = mmc->mmcMMU;
|
|
// Detach MMUser from MMContext:
|
|
if (mmu) {
|
|
assert(mmu->mmuContext == mmc);
|
|
assert(mmc->mmcMMU == mmu);
|
|
mmc->mmcMMU = NULL; // old comment: Deletes its RefCntPointer and may delete it.
|
|
mmu->mmuContext = NULL;
|
|
}
|
|
}
|
|
|
|
|
|
// Move transactions from oldmmc to this, which is the current MMC serving the handset.
|
|
// We do this when we have positive knowledge that the oldmmc channel has been abandoned by the handset.
|
|
// This happens when the MS was on one MMC and has been moved or showed up on another MMC.
|
|
void MMContext::mmcMoveTransactions(MMContext *oldmmc)
|
|
{
|
|
for (unsigned ati = TE_first; ati < TE_num; ati++) {
|
|
if (! oldmmc->mmcTE[ati].isNULL()) {
|
|
if (mmcTE[ati].isNULL()) {
|
|
// Disconnect the old tran but be careful not to delete it.
|
|
// To be sure we have to keep a pointer to it through this operation.
|
|
RefCntPointer<TranEntry> oldtran = oldmmc->mmcTE[ati];
|
|
oldmmc->mmcTE[ati] = NULL;
|
|
oldtran->teSetContext(NULL); // Not necessary, but be tidy.
|
|
mmConnectTran((ActiveTranIndex)ati, oldtran.self());
|
|
} else {
|
|
// (pat) Disaster. There is a corresponding transaction already running on the new MMC,
|
|
// for example, old voice transaction and new voice transaction.
|
|
// I don't think this is possible for double paging responses (see comments at mmcLink and NewPagingResponseHandler)
|
|
// because we call mmcLink immediately when the second page is received, so the new MMC is empty.
|
|
// I'm not sure about other cases; the logic is too complicated.
|
|
// We will keep the new (more recent) transaction and the old transaction on the
|
|
// old MMC will be dropped when that channel is closed.
|
|
LOG(ERR) << "Handset has changed channels and has transactions running on the both channels. "
|
|
<<LOGVAR2("old channel",oldmmc->tsChannel()) <<LOGVAR2("new channel",tsChannel())
|
|
<<LOGVAR2("transaction being deleted",oldmmc->mmcTE[ati].self());
|
|
// We dont do anything. The transaction will be delete when the old channel is closed,
|
|
// which the caller should do immediately. We could call teCancel here but teCancel is tricky
|
|
// and I would like to reduce the number of calls to it. This probably doesnt happen anyway.
|
|
// If this does happen, we could be more clever, like a voice transaction could move to the secondary slot, etc.
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// This is called whenever we have positively identified an MS so we can connect the MMU to the MMC.
|
|
// That includes:
|
|
// 1. when we receive a PagingResponse message (which includes an IMSI or TMSI; note that PagingResponse
|
|
// is an L3 message which means the MS has already negotiated L2 LAPDm connection to send it.)
|
|
// 2. MOC call control when we identify the MS
|
|
// 3. Mobility Management after authorization.
|
|
// 4. From SMS somewhere too.
|
|
void MMContext::mmcLink(MMUser *mmu)
|
|
{
|
|
devassert(gMMLock.lockcnt()); // Caller locked it.
|
|
//RefCntPointer<MMUser> saveme(mmu); // Dont delete mmu during this procedure.
|
|
MMContext *mmc = this;
|
|
if (mmc->mmcMMU == mmu) {
|
|
// Already connected.
|
|
devassert(mmu->mmuContext == mmc); // We always maintain pointers both ways.
|
|
return;
|
|
}
|
|
// Detach the mmu from its existing channel, if any. That happens when the MS disappeared temporarily
|
|
// and then came back on another channel, for example, handover to another BTS and back.
|
|
// (pat 4-2014) It also happens for paging response: Sometimes the MS sends two RACHes in a row,
|
|
// which allocates two channels (say A and B.)
|
|
// We send two immediate assignments, and the MS may respond to both! First it does an L1 LAPDm negotiation on A
|
|
// and sends a Paging Response there, which connects its MMU to the MMContext for A, then it
|
|
// does an L2 LAPDm negotiation on B and sends a second Paging Response there, so we get here with this == channel B
|
|
// but with the MMU attached to channel A. So we must disconnect the existing channel and move the MMU to the new channel.
|
|
// We have to move the transactions from the old MMContext to the new; for example if there was only one MTC transaction and
|
|
// it has already been moved from the MMU to the MMC, then the MMU is empty of transactions which will release channel B
|
|
// immmediately in mmCheckNewActivity.
|
|
if (MMContext *oldmmc = mmu->mmuContext) {
|
|
if (oldmmc != mmc) {
|
|
LOG(DEBUG) <<"reconnecting mmu"<<LOGVAR(mmu)<<LOGVAR(oldmmc)<<LOGVAR(mmc);
|
|
// pat 4-2014: Move the transactions from the old to the new mmc.
|
|
mmcMoveTransactions(oldmmc);
|
|
}
|
|
oldmmc->mmcUnlink();
|
|
}
|
|
mmc->mmcUnlink();
|
|
mmc->mmcMMU = mmu;
|
|
mmu->mmuContext = mmc;
|
|
}
|
|
|
|
|
|
void MMContext::mmcFree(TermCause cause)
|
|
{
|
|
assert(this->mmcMMU == NULL);
|
|
devassert(gMMLock.lockcnt()); // Caller locked it.
|
|
|
|
// Cancel all the enclosed transactions and their dialogs.
|
|
for (unsigned i = 0; i < TE_num; i++) {
|
|
// When a primary transaction is deleted we may promote the secondary transaction, so delete them all:
|
|
for (unsigned tries = 0; tries < 3; tries++) {
|
|
if (mmcTE[i] != NULL) { mmcTE[i]->teCancel(cause); } // Removes the transaction from mmcTE via mmDisconnectTran
|
|
}
|
|
assert(mmcTE[i] == NULL); // teCancel removed it.
|
|
}
|
|
LOG(DEBUG)<<"MMContext DELETE "<<(void*)this;
|
|
delete this;
|
|
}
|
|
|
|
// The logical channel no longer points to this Context, so release it.
|
|
// The cause is used only for reporting purposes for any transactions still extent;
|
|
// the underlying channel has already been released so we cannot send a cause code downstream,
|
|
// and if there are any new SIP dialogs upstream we should normally start re-paging the handset
|
|
// to create a new mmcontext rather than cancelling them.
|
|
// TODO: We may want to cancel any SIP dialogs based on the cause.
|
|
void MMLayer::mmFreeContext(MMContext *mmc,TermCause cause)
|
|
{
|
|
// There can be multiple logical channels pointing to the same Context, so decrement
|
|
// the channel use count and delete only when 0.
|
|
ScopedLock lock(gMMLock,__FILE__,__LINE__);
|
|
LOG(DEBUG) << mmc;
|
|
if (--mmc->mmcChannelUseCnt > 0) return;
|
|
|
|
MMUser *mmu = mmc->mmcMMU;
|
|
mmc->mmcUnlink(); // resets mmcMMU
|
|
|
|
// This channel was closed normally, or because of channel loss (for example, reassign failure) or an internal error.
|
|
// In the latter cases there could be running SipDialogs on the channel and we have to tell them something.
|
|
// The SipCode 408 allows the peer to retry immediately.
|
|
|
|
// Paul at Null Team says:
|
|
// 408 is reserved for SIP protocol timeouts (no answer to SIP message)
|
|
// 504 indicates some other timeout beyond SIP (interworking)
|
|
// 480 indicates some temporary form of resource unavailability or congestion but resource is accessible and can be checked
|
|
// 503 indicates the service is unavailable but does not imply for how long
|
|
//SipCode sipcode(480,"Temporarily Unavailable");
|
|
|
|
if (mmu) {
|
|
// FIXME It is possible for new SIP dialogs to have started between the time we decided
|
|
// to close this channel and now. It is also possible that we closed the channel
|
|
// because of loss of contact with the MS. In either case, if the MMU has dialogs,
|
|
// dont delete it - just leave it alone and we will start repaging this MS again.
|
|
// TODO: But first, walk though dialogs and cancel any that need it.
|
|
if (mmu->mmuIsEmpty()) {
|
|
mmu->mmuFree(NULL,TermCause::Local(L3Cause::No_Transaction_Expected)); // TermCause is not used because there are no dialogs.
|
|
}
|
|
}
|
|
mmc->mmcFree(cause);
|
|
}
|
|
|
|
void MMLayer::mmMTRepage(const string imsi)
|
|
{
|
|
// Renew the page timer.
|
|
LOG(DEBUG) <<LOGVAR(imsi);
|
|
ScopedLock lock(gMMLock,__FILE__,__LINE__);
|
|
MMUser *mmu = mmFindByImsi(imsi,false);
|
|
if (mmu) {
|
|
// This has no effect unless we are paging, ie, if the MMUser has not yet connected to an MMChannel.
|
|
mmu->mmuPageTimer.future(gConfig.GSM.Timer.T3113);
|
|
} else {
|
|
LOG(DEBUG) << "repeated INVITE/MESSAGE with no MMUser record";
|
|
}
|
|
}
|
|
|
|
|
|
// Called when we have positively identified the MS associated with chan, so now we
|
|
// want to connect the channel to its MMUser.
|
|
void MMLayer::mmAttachByImsi(L3LogicalChannel *chan, string imsi)
|
|
{
|
|
ScopedLock lock(gMMLock,__FILE__,__LINE__);
|
|
// Find or create the MMUser.
|
|
WATCHINFO("attachMMC" <<LOGVAR(imsi) <<LOGVAR(chan));
|
|
MMUser *mmu = mmFindByImsi(imsi,true);
|
|
MMContext *mmc = chan->chanGetContext(true);
|
|
// They are linked together from now on.
|
|
mmc->mmcLink(mmu);
|
|
LOG(DEBUG);
|
|
|
|
// TODO: The MM procedure may have blocked tmsis and imsis.
|
|
// So now we want to unblock any previously blocked imsi/tmsi
|
|
}
|
|
|
|
|
|
bool MMLayer::mmTerminateByImsi(string imsi)
|
|
{
|
|
ScopedLock lock(gMMLock,__FILE__,__LINE__);
|
|
MMUser *mmu = mmFindByImsi(imsi,false);
|
|
if (!mmu) { return false; } // Not found.
|
|
MMContext* mmc = mmu->mmuContext;
|
|
if (!mmc) {
|
|
// There is no channel, just kill off the MMUser, which will stop paging and cancel the SIP dialogs.
|
|
mmu->mmuFree(NULL,TermCause::Local(L3Cause::Operator_Intervention));
|
|
return true;
|
|
}
|
|
if (mmc->tsChannel()->chanRunning()) {
|
|
// Dont call chanClose from here because it sends a message which would block the calling thread.
|
|
//mmc->tsChannel()->chanClose(L3RRCause::PreemptiveRelease,RELEASE); DONT DO THIS!
|
|
// FIXME: We would like to send an RR Release message first.
|
|
mmc->mmcTerminationRequested = true;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// This is the way MMUsers are created from the SIP side.
|
|
void MMLayer::mmAddMT(TranEntry *tran)
|
|
{
|
|
LOG(DEBUG) <<this<<LOGVAR(tran);
|
|
{ ScopedLock lock(gMMLock,__FILE__,__LINE__);
|
|
string imsi(tran->subscriberIMSI());
|
|
MMUser *mmu = mmFindByImsi(imsi,true);
|
|
// Is there a guaranteed tmsi?
|
|
// We will delay this until we page in case an LUR is occurring right now.
|
|
//if (uint32_t tmsi = gTMSITable.tmsiTabGetTMSI(imsi,true)) { mmu->mmuTmsi = /*tran->subscriber().mTmsi =*/ tmsi; }
|
|
assert(mmu);
|
|
mmu->mmuAddMT(tran);
|
|
}
|
|
mmPageSignal.signal();
|
|
}
|
|
|
|
MMUser *MMLayer::mmFindByImsi(string imsi, // Do not change this to a reference. We need a copy of the string
|
|
// to insert into the map. If pass by reference here the map points to the string from the caller,
|
|
// which may have long since gone out of scope. What a great language.
|
|
bool create)
|
|
{
|
|
LOG(DEBUG) <<LOGVAR(imsi) <<LOGVAR(create);
|
|
devassert(gMMLock.lockcnt()); // Caller locked it.
|
|
MMUser *result;
|
|
const char *what = "existing ";
|
|
if (create) {
|
|
// Use insert so we only traverse the map tree once. If element does not exist, a pair with MMUser*==NULL is inserted.
|
|
// This wonderful insert method returns a pair<MMUserMap::iterator,bool> but we only want the iterator.
|
|
//pair<MMUserMap::iterator,bool> result = MMUsers.insert(pair<string,MMUser*>(imsi,(MMUser*)NULL));
|
|
bool exists = MMUsers.find(imsi) != MMUsers.end();
|
|
LOG(DEBUG) << LOGVAR(imsi)<<LOGVAR(exists);
|
|
MMUserMap::iterator it = MMUsers.insert(pair<string,MMUser*>(imsi,(MMUser*)NULL)).first;
|
|
result = it->second;
|
|
if (result == NULL) {
|
|
result = it->second = new MMUser(imsi);
|
|
LOG(DEBUG) << "inserting new MMUser "<<(void*)result;
|
|
what = "new ";
|
|
}
|
|
LOG(DEBUG) << "MMUsers["<<imsi<<"]="<<(void*)MMUsers[imsi];
|
|
} else {
|
|
MMUserMap::const_iterator it = MMUsers.find(imsi);
|
|
result = (it == MMUsers.end()) ? NULL : it->second;
|
|
}
|
|
LOG(DEBUG) <<what <<LOGVAR(result);
|
|
return result;
|
|
}
|
|
|
|
|
|
TMSI_t MMUser::mmuGetTmsi()
|
|
{
|
|
if (! this->mmuDidTmsiCheck) {
|
|
this->mmuDidTmsiCheck = true;
|
|
if (uint32_t tmsi = gTMSITable.tmsiTabGetTMSI(mmuImsi,true)) { this->mmuTmsi = tmsi; }
|
|
}
|
|
return this->mmuTmsi;
|
|
}
|
|
|
|
MMUser *MMLayer::mmFindByTmsi(uint32_t tmsi)
|
|
{
|
|
devassert(gMMLock.lockcnt()); // Caller locked it.
|
|
//ScopedLock lock(gMMLock,__FILE__,__LINE__);
|
|
MMUser *result = NULL;
|
|
for (MMUserMap::iterator it = MMUsers.begin(); it != MMUsers.end(); ++it) {
|
|
MMUser *mmu = it->second;
|
|
// (pat) The handset we want could be simultaneously doing an MM procedure that is establishing a TMSI,
|
|
// so we should check the tmsi table every single time this happens.
|
|
// However, we are currently doing an expensive sql lookup so only check once.
|
|
TMSI_t mmutmsi = mmu->mmuGetTmsi();
|
|
if (mmutmsi.valid() && mmutmsi.value() == tmsi) { result = mmu; break; }
|
|
}
|
|
LOG(DEBUG) << LOGVAR(result);
|
|
return result;
|
|
}
|
|
|
|
MMUser *MMLayer::mmFindByMobileId(L3MobileIdentity&mid)
|
|
{
|
|
devassert(gMMLock.lockcnt()); // Caller locked it.
|
|
if (mid.isIMSI()) {
|
|
string imsi = mid.digits();
|
|
return mmFindByImsi(imsi,false);
|
|
} else {
|
|
assert(mid.isTMSI());
|
|
return mmFindByTmsi(mid.TMSI());
|
|
}
|
|
}
|
|
|
|
// When called from the paging thread loop this function is responsible for noticing expired MMUsers and deleting them.
|
|
void MMLayer::mmGetPages(NewPagingList_t &pages)
|
|
{
|
|
//LOG(DEBUG) <<LOGVAR(MMUsers.size());
|
|
|
|
assert(pages.size() == 0); // Caller passes us a new list each time.
|
|
|
|
{
|
|
ScopedLock lock(gMMLock,__FILE__,__LINE__);
|
|
pages.reserve(MMUsers.size());
|
|
for (MMUserMap::iterator it = MMUsers.begin(); it != MMUsers.end(); ) {
|
|
MMUser *mmu = it->second;
|
|
MMUserMap::iterator thisone = it++;
|
|
LOG(DEBUG)<<LOGVAR(mmu);
|
|
if (mmu->mmuIsAttached()) { // Is it already attached to a radio channel?
|
|
LOG(DEBUG) << "MMUser already attached:"<<mmu->mmuImsi;
|
|
continue;
|
|
}
|
|
if (mmu->mmuPageTimer.passed()) {
|
|
// Expired. Get rid of it.
|
|
LOG(INFO) << "Page expired for imsi="<<mmu->mmuImsi;
|
|
// Erasing from a map invalidates the iterator, but not the iteration.
|
|
// (pat) The SIP error for no page should probably not be 480 Temporarily Unavailable,
|
|
// because that implies we know that the user is at the BTS, but if it did not answer the page, we do not.
|
|
// Paul at Null Team recommended 504.
|
|
// FIXME URGENTLY: Dont do an mmFree within the gMMLock, although we need to make sure it does not disappear.
|
|
mmu->mmuFree(&thisone,TermCause::Local(L3Cause::No_Paging_Response));
|
|
continue;
|
|
}
|
|
// TODO: We could add a check for a "provisional IMSI"
|
|
|
|
NewPagingEntry tmp(mmu->mmuGetInitialChanType(), mmu->mmuImsi);
|
|
pages.push_back(tmp);
|
|
}
|
|
}
|
|
if (pages.size()) LOG(DEBUG) <<LOGVAR(pages.size());
|
|
}
|
|
|
|
// Not used. This is only documentation how to do this now.
|
|
//void MMLayer::mmWaitForPages(NewPagingList_t &pages, bool wait)
|
|
//{
|
|
// ScopedLock lock(gMMLock,__FILE__,__LINE__);
|
|
// while (1) {
|
|
// // Code goes here.
|
|
// // ...
|
|
// //
|
|
// if (!wait) { return; }
|
|
// // We need to provide a timeout so that we free expired pages in a timely manner; if we dont
|
|
// // then that MMUser is essentially locked because incoming SIP invites get a busy return, and it
|
|
// // wont get released until some other unrelated page intervenes.
|
|
// mmPageSignal.wait(gMMLock,500);
|
|
// // Need a while loop here because the wait does not guarantee it was signalled.
|
|
// }
|
|
//}
|
|
|
|
// For use by the CLI: create a copy of the paging list and print it.
|
|
void MMLayer::printPages(ostream &os)
|
|
{
|
|
// This does not need to lock anything. The mmGetPages provides locked access to the MMUser list.
|
|
NewPagingList_t pages;
|
|
gMMLayer.mmGetPages(pages);
|
|
for (NewPagingList_t::iterator it = pages.begin(); it != pages.end(); ++it) {
|
|
NewPagingEntry &pe = *it;
|
|
os <<pe.text();
|
|
}
|
|
}
|
|
|
|
// Connect the channel with its MMUser based on the received page.
|
|
// Return true if ok, or false if not found, which will tell caller to release the channel.
|
|
// Could be not found because the SIP side gave up while we were waiting for the page.
|
|
bool MMLayer::mmPageReceived(MMContext *mmchan, L3MobileIdentity &mobileId)
|
|
{
|
|
// The MMC can be deleted independently until it is tied to a channel.
|
|
MMUser *mmu;
|
|
{ ScopedLock lock(gMMLock,__FILE__,__LINE__);
|
|
mmu = gMMLayer.mmFindByMobileId(mobileId);
|
|
if (mmu == NULL) {
|
|
return false;
|
|
}
|
|
|
|
// We do not 'delete' pages - only unattached MMUser are paged,
|
|
// so the act of attaching the MMUser to a MMContext (below) causes us to stop paging this MS.
|
|
|
|
// This is what ties the MMUser and MMContext together for MT services.
|
|
// They are linked together from now on.
|
|
//mmchan = mmchan->chanGetContext(true);
|
|
mmchan->mmcLink(mmu);
|
|
// At this point the MMC cannot be deleted until the L3LogicalChannel is released,
|
|
// so we no longer need the MM lock, and we should release it before locking
|
|
// the MMUser to avoid deadlock.
|
|
}
|
|
|
|
// TODO: If there is a channel lock, this might want to use that.
|
|
LOG(INFO) << "paging reponse for " << mmu;
|
|
mmchan->mmcPageReceived(); // Just prints errors.
|
|
return true;
|
|
}
|
|
|
|
// If unattached flag, print only unattached Contexts, used from CLI.
|
|
void MMLayer::printMMUsers(std::ostream&os, bool onlyUnattached)
|
|
{
|
|
ScopedLock lock(gMMLock,__FILE__,__LINE__);
|
|
for (MMUserMap::iterator it = MMUsers.begin(); it != MMUsers.end(); ++it) {
|
|
MMUser *mmu = it->second;
|
|
if (onlyUnattached && mmu->mmuIsAttached()) { continue; }
|
|
mmu->mmuText(os);
|
|
os << endl;
|
|
}
|
|
}
|
|
|
|
void MMLayer::printMMInfo(std::ostream&os)
|
|
{
|
|
L2ChanList chans;
|
|
gBTS.getChanVector(chans);
|
|
ScopedLock lock(gMMLock,__FILE__,__LINE__);
|
|
for (L2ChanList::iterator it = chans.begin(); it != chans.end(); it++) {
|
|
L3LogicalChannel *chan = dynamic_cast<L3LogicalChannel*>(*it);
|
|
// (pat) When we used a separate mChanLock in the L3LogicalChannel, then deadlock was possible here.
|
|
// chanGetContext calls mChanLock, but there could be some
|
|
// other thread waiting in a L3LogicalChannel method with mChanLock already locked
|
|
// and waiting for the gMMLock, which is locked above.
|
|
// I saw this deadlock when two channel assignments happened simultaneously, and printChansV4
|
|
// and printMMInfo tried to run simultaneously.
|
|
MMContext *mmc = chan->chanGetContext(false);
|
|
if (mmc) {
|
|
mmc->mmcText(os);
|
|
os << endl;
|
|
}
|
|
}
|
|
printMMUsers(os,false);
|
|
}
|
|
|
|
string MMLayer::printMMInfo()
|
|
{
|
|
ostringstream ss;
|
|
printMMInfo(ss);
|
|
return ss.str();
|
|
}
|
|
|
|
void controlInit()
|
|
{
|
|
LOG(DEBUG);
|
|
gTMSITable.tmsiTabOpen(gConfig.getStr("Control.Reporting.TMSITable").c_str());
|
|
LOG(DEBUG);
|
|
gNewTransactionTable.ttInit();
|
|
LOG(DEBUG);
|
|
TranInit();
|
|
}
|
|
|
|
};
|