void Control::AssignmentCompleteHandler(const L3AssignmentComplete *confirm, TCHFACCHLogicalChannel *TCH) { // The assignment complete handler is used to // tie together split transactions across a TCH assignment // in non-VEA call setup. assert(TCH); assert(confirm); LOG(DEBUG) << *confirm; // Check the transaction table to know what to do next. TransactionEntry transaction; if (!gTransactionTable.find(TCH->transactionID(),transaction)) { LOG(WARN) << "Assignment Complete with no transaction record for ID " << TCH->transactionID(); throw UnexpectedMessage(); } LOG(INFO) << "service="<<transaction.service().type(); // These "controller" functions don't return until the call is cleared. switch (transaction.service().type()) { case L3CMServiceType::MobileOriginatedCall: MOCController(transaction,TCH); break; case L3CMServiceType::MobileTerminatedCall: MTCController(transaction,TCH); break; default: LOG(WARN) << "unsupported service " << transaction.service(); throw UnsupportedMessage(transaction.ID()); } // If we got here, the call is cleared. }
void Control::clearTransactionHistory( TransactionEntry& transaction ) { SIP::SIPEngine& engine = transaction.SIP(); LOG(DEBUG) << engine.callID()<<" "<< transaction.ID(); gSIPInterface.removeCall(engine.callID()); gTransactionTable.remove(transaction.ID()); }
/** Check GSM signalling. Can block for up to 52 GSM L1 frames (240 ms) because LCH::send is blocking. @param transaction The call's TransactionEntry. @param LCH The call's logical channel (TCH/FACCH or SDCCH). @return true If the call was cleared. */ bool updateGSMSignalling(TransactionEntry &transaction, LogicalChannel *LCH, unsigned timeout=0) { if (transaction.Q931State()==TransactionEntry::NullState) return true; // Any Q.931 timer expired? if (transaction.timerExpired()) { // Cause 0x66, "recover on timer expiry" abortCall(transaction,LCH,L3Cause(0x66)); return true; } // Look for a control message from MS side. if (L3Frame *l3 = LCH->recv(timeout)) { // Check for lower-layer error. if (l3->primitive() == ERROR) return true; // Parse and dispatch. L3Message *l3msg = parseL3(*l3); delete l3; bool cleared = false; if (l3msg) { LOG(DEBUG) << "received " << *l3msg; cleared = callManagementDispatchGSM(transaction, LCH, l3msg); delete l3msg; } return cleared; } // If we are here, we have timed out, but assume the call is still running. return false; }
void Pager::addID(const FuzzingL3MobileIdentity& newID, ChannelType chanType, TransactionEntry& transaction, unsigned wLife) { transaction.Q931State(TransactionEntry::Paging); transaction.T3113().set(wLife); gTransactionTable.update(transaction); // Add a mobile ID to the paging list for a given lifetime. mLock.lock(); // If this ID is already in the list, just reset its timer. // Uhg, another linear time search. // This would be faster if the paging list were ordered by ID. // But the list should usually be short, so it may not be worth the effort. for (PagingEntryList::iterator lp = mPageIDs.begin(); lp != mPageIDs.end(); ++lp) { if (lp->FuzzingID()==newID) { LOG(DEBUG) << newID << " already in table"; lp->renew(wLife); mPageSignal.signal(); mLock.unlock(); return; } } // If this ID is new, put it in the list. mPageIDs.push_back(PagingEntry(newID,chanType,transaction.ID(),wLife)); LOG(INFO) << newID << " added to table"; mPageSignal.signal(); mLock.unlock(); }
/** This is the standard call manangement loop, regardless of the origination type. This function returns when the call is cleared and the channel is released. @param transaction The transaction record for this call, will be cleared on exit. @param TCH The TCH+FACCH for the call. */ void callManagementLoop(TransactionEntry &transaction, TCHFACCHLogicalChannel* TCH) { LOG(INFO) << transaction.subscriber() << " call connected"; transaction.SIP().FlushRTP(); // poll everything until the call is cleared while (!pollInCall(transaction,TCH)) { } clearTransactionHistory(transaction); }
/** Force clearing on the SIP side. @param transaction The call transaction record. */ void forceSIPClearing(TransactionEntry& transaction) { LOG(INFO) << "SIP state " << transaction.SIP().state(); if (transaction.SIP().state()==SIP::Cleared) return; if (transaction.SIP().state()!=SIP::Clearing) { // This also changes the SIP state to "clearing". transaction.SIP().MODSendBYE(); } else { transaction.SIP().MODResendBYE(); } transaction.SIP().MODWaitForOK(); gTransactionTable.update(transaction); }
void TransactionTable::update(const TransactionEntry& value) { // ID==0 is a non-valid special case. assert(value.ID()); mLock.lock(); if (mTable.find(value.ID())==mTable.end()) { mLock.unlock(); LOG(WARN) << "attempt to update non-existent transaction entry with key " << value.ID(); return; } mTable[value.ID()]=value; mLock.unlock(); }
void TransactionTable::add(const TransactionEntry& value) { LOG(INFO) << "new transaction " << value; mLock.lock(); mTable[value.ID()]=value; mLock.unlock(); }
/** Update vocoder data transfers in both directions. @param transaction The transaction object for this call. @param TCH The traffic channel for this call. @return True if anything was transferred. */ bool updateCallTraffic(TransactionEntry &transaction, TCHFACCHLogicalChannel *TCH) { bool activity = false; SIPEngine& engine = transaction.SIP(); // Transfer in the downlink direction (RTP->GSM). // Blocking call. On average returns 1 time per 20 ms. // Returns non-zero if anything really happened. // Make the rxFrame buffer big enough for G.711. unsigned char rxFrame[160]; if (engine.RxFrame(rxFrame)) { activity = true; TCH->sendTCH(rxFrame); } // Transfer in the uplink direction (GSM->RTP). // Flush FIFO to limit latency. unsigned maxQ = gConfig.getNum("GSM.MaxSpeechLatency"); while (TCH->queueSize()>maxQ) delete[] TCH->recvTCH(); if (unsigned char *txFrame = TCH->recvTCH()) { activity = true; // Send on RTP. engine.TxFrame(txFrame); delete[] txFrame; } // Return a flag so the caller will know if anything transferred. return activity; }
/** Force clearing on the GSM side. @param transaction The call transaction record. @param LCH The logical channel. @param cause The L3 abort cause. */ void forceGSMClearing(TransactionEntry& transaction, LogicalChannel *LCH, const L3Cause& cause) { LOG(INFO) << "Q.931 state " << transaction.Q931State(); if (transaction.Q931State()==TransactionEntry::NullState) return; if (!transaction.clearing()) { LCH->send(L3Disconnect(1-transaction.TIFlag(),transaction.TIValue(),cause)); } LCH->send(L3ReleaseComplete(1-transaction.TIFlag(),transaction.TIValue())); LCH->send(L3ChannelRelease()); transaction.resetTimers(); transaction.Q931State(TransactionEntry::NullState); LCH->send(RELEASE); gTransactionTable.update(transaction); }
/** Check SIP and GSM signalling. Can block for up to 52 GSM L1 frames (240 ms) because LCH::send is blocking. @param transaction The call's TransactionEntry. @param LCH The call's logical channel (TCH/FACCH or SDCCH). @return true If the call is cleared in both domains. */ bool updateSignalling(TransactionEntry &transaction, LogicalChannel *LCH, unsigned timeout=0) { bool GSMCleared = (updateGSMSignalling(transaction,LCH,timeout)); // Look for a SIP message. SIPEngine& engine = transaction.SIP(); if (engine.MTDCheckBYE() == SIP::Clearing) { if (!transaction.clearing()) { LOG(DEBUG) << "got BYE"; LCH->send(L3Disconnect(1-transaction.TIFlag(),transaction.TIValue())); transaction.T305().set(); transaction.Q931State(TransactionEntry::DisconnectIndication); // Return false, because it the call is not yet cleared. return false; } else { // If we're already clearing, send BYE again. //engine.MODSendBYE(); } } bool SIPCleared = (engine.state()==SIP::Cleared); return GSMCleared && SIPCleared; }
void Control::MOSMSController(const GSM::L3CMServiceRequest *req, GSM::LogicalChannel *LCH) { assert(req); assert(req->serviceType().type() == GSM::L3CMServiceType::ShortMessage); assert(LCH); assert(LCH->type() != GSM::SACCHType); LOG(INFO) << "MOSMS, req " << *req; // If we got a TMSI, find the IMSI. // Note that this is a copy, not a reference. GSM::L3MobileIdentity mobileID = req->mobileID(); resolveIMSI(mobileID,LCH); // Create a transaction record. TransactionEntry *transaction = new TransactionEntry(gConfig.getStr("SIP.Proxy.SMS").c_str(),mobileID,LCH); gTransactionTable.add(transaction); LOG(DEBUG) << "MOSMS: transaction: " << *transaction; // See GSM 04.11 Arrow Diagram A5 for the transaction // Step 1 MS->Network CP-DATA containing RP-DATA // Step 2 Network->MS CP-ACK // Step 3 Network->MS CP-DATA containing RP-ACK // Step 4 MS->Network CP-ACK // LAPDm operation, from GSM 04.11, Annex F: // """ // Case A: Mobile originating short message transfer, no parallel call: // The mobile station side will initiate SAPI 3 establishment by a SABM command // on the DCCH after the cipher mode has been set. If no hand over occurs, the // SAPI 3 link will stay up until the last CP-ACK is received by the MSC, and // the clearing procedure is invoked. // """ // FIXME: check provisioning if (gConfig.getNum("GSM.Authentication")||gConfig.getNum("GSM.Encryption")) { AuthenticationParameters authParams(mobileID); registerIMSI(authParams, LCH); authenticate(authParams, LCH); } // Let the phone know we're going ahead with the transaction. if (LCH->isDecrypting()) { LOG(INFO) << "Decryption ACTIVE for:" << mobileID << " CMServiceAccept NOT sent, because CipherModeCommand implies it."; } else { LOG(INFO) << "Decryption NOT active for: " << mobileID << " Sending CMServiceAccept"; LCH->send(GSM::L3CMServiceAccept()); } // Wait for SAP3 to connect. // The first read on SAP3 is the ESTABLISH primitive. delete getFrameSMS(LCH,GSM::ESTABLISH); // Step 1 // Now get the first message. // Should be CP-DATA, containing RP-DATA. GSM::L3Frame *CM = getFrameSMS(LCH); LOG(DEBUG) << "data from MS " << *CM; if (CM->MTI()!=CPMessage::DATA) { LOG(NOTICE) << "unexpected SMS CP message with TI=" << CM->MTI(); delete CM; throw UnexpectedMessage(); } unsigned L3TI = CM->TI() | 0x08; transaction->L3TI(L3TI); // Step 2 // Respond with CP-ACK. // This just means that we got the message. LOG(INFO) << "sending CPAck"; LCH->send(CPAck(L3TI),3); // Parse the message in CM and process RP part. // This is where we actually parse the message and send it out. // FIXME -- We need to set the message ref correctly, // even if the parsing fails. // The compiler gives a warning here. Let it. It will remind someone to fix it. unsigned ref; bool success = false; try { CPData data; data.parse(*CM); LOG(INFO) << "CPData " << data; // Transfer out the RPDU -> TPDU -> delivery. ref = data.RPDU().reference(); // This handler invokes higher-layer parsers, too. success = handleRPDU(transaction,data.RPDU()); } catch (SMSReadError) { LOG(WARNING) << "SMS parsing failed (above L3)"; // Cause 95, "semantically incorrect message". LCH->send(CPData(L3TI,RPError(95,ref)),3); delete CM; throw UnexpectedMessage(); } catch (GSM::L3ReadError) { LOG(WARNING) << "SMS parsing failed (in L3)"; delete CM; throw UnsupportedMessage(); } delete CM; // Step 3 // Send CP-DATA containing RP-ACK and message reference. if (success) { LOG(INFO) << "sending RPAck in CPData"; LCH->send(CPData(L3TI,RPAck(ref)),3); } else { LOG(INFO) << "sending RPError in CPData"; // Cause 127 is "internetworking error, unspecified". // See GSM 04.11 Table 8.4. LCH->send(CPData(L3TI,RPError(127,ref)),3); } // Step 4 // Get CP-ACK from the MS. CM = getFrameSMS(LCH); if (CM->MTI()!=CPMessage::ACK) { LOG(NOTICE) << "unexpected SMS CP message with TI=" << CM->MTI(); throw UnexpectedMessage(); } LOG(DEBUG) << "ack from MS: " << *CM; CPAck ack; ack.parse(*CM); delete CM; LOG(INFO) << "CPAck " << ack; /* MOSMS RLLP request */ if (gConfig.defines("Control.SMS.QueryRRLP")) { // Query for RRLP if (!sendRRLP(mobileID, LCH)) { LOG(INFO) << "RRLP request failed"; } } // Done. LCH->send(GSM::L3ChannelRelease()); gTransactionTable.remove(transaction); LOG(INFO) << "closing the Um channel"; }
void Control::PagingResponseHandler(const L3PagingResponse* resp, LogicalChannel* DCCH) { assert(resp); assert(DCCH); LOG(INFO) << *resp; COUT(*resp); // If we got a TMSI, find the IMSI. L3MobileIdentity mobileID = resp->mobileIdentity(); if (mobileID.type()==TMSIType) { const char *IMSI = gTMSITable.IMSI(mobileID.TMSI()); if (IMSI) mobileID = L3MobileIdentity(IMSI); else { // Don't try too hard to resolve. // The handset is supposed to respond with the same ID type as in the request. LOG(NOTICE) << "Paging Reponse with non-valid TMSI"; // Cause 0x60 "Invalid mandatory information" DCCH->send(L3ChannelRelease(0x60)); return; } } // Delete the Mobile ID from the paging list to free up CCCH bandwidth. // ... if it was not deleted by a timer already ... gBTS.pager().removeID(mobileID); if(gFuzzingControl.state()==TestPhoneConnect){ if(gFuzzingControl.mobileIdentity()==mobileID){ COUT("The Phone is connect with BTS!"); gFuzzingControl.state(GetPagingResponse); gFuzzingControl.signal(); DCCH->send(RELEASE); return; } } if(gFuzzingControl.state()==L3Fuzzing&&gFuzzingControl.PD()==GSM::L3RadioResourcePD&&gFuzzingControl.MTI()==L3RRMessage::PagingRequestType1){ if(gFuzzingControl.mobileIdentity()==mobileID){ COUT("Catch a Failed Response"); gFuzzingControl.state(FuzzingFailed); gFuzzingControl.signal(); DCCH->send(RELEASE); return; } } if(gFuzzingControl.state()==GetFuzzingResponse&&gFuzzingControl.PD()==GSM::L3MobilityManagementPD&&gFuzzingControl.MTI()==L3MMMessage::LocationUpdatingAccept){ if(gFuzzingControl.mobileIdentity()==mobileID){ COUT("Response Correctly in LocationUpdatingAccept Fuzzing"); gFuzzingControl.state(NonFuzzing); gFuzzingControl.signal(); DCCH->send(RELEASE); return; } } if(gFuzzingControl.state()==GetFuzzingResponse&&gFuzzingControl.PD()==GSM::L3MobilityManagementPD&&gFuzzingControl.MTI()==L3MMMessage::MMInformation){ if(gFuzzingControl.mobileIdentity()==mobileID){ COUT("Response Correctly in Fuzzying MMInformation"); gFuzzingControl.state(NonFuzzing); gFuzzingControl.signal(); DCCH->send(RELEASE); return; } } if(gFuzzingControl.state()==TestCCFail&&gFuzzingControl.PD()==GSM::L3CallControlPD&&gFuzzingControl.MTI()==L3CCMessage::Setup){ if(gFuzzingControl.mobileIdentity()==mobileID){ COUT("Response Correctly in Fuzzying CC Setup Information"); gFuzzingControl.state(GetPagingResponse); gFuzzingControl.signal(); DCCH->send(RELEASE); return; } } if(gFuzzingControl.state()==TestSMSFail){ if(gFuzzingControl.mobileIdentity()==mobileID){ COUT("Response Correctly After Fuzzying SMS"); gFuzzingControl.state(GetPagingResponse); gFuzzingControl.signal(); DCCH->send(RELEASE); return; } } if(gFuzzingControl.state()==SMSFuzzing){ if(gFuzzingControl.mobileIdentity()==mobileID){ COUT("Get Paging Response in Fuzzying SMS"); bool success=deliverFuzzingSMS(gFuzzingControl.mFuzzingData, DCCH); if(!success) {COUT("Fuzzing SMS send failed!");gFuzzingControl.state(SMSFailed);} else {COUT("Fuzzing SMS send success!");gFuzzingControl.state(SMSReponse);} gFuzzingControl.signal(); return; } } // Find the transction table entry that was created when the phone was paged. // We have to look up by mobile ID since the paging entry may have been // erased before this handler was called. That's too bad. // HACK -- We also flush stray transactions until we find what we // are looking for. TransactionEntry transaction; while (true) { if (!gTransactionTable.find(mobileID,transaction)) { LOG(WARN) << "Paging Reponse with no transaction record for " << mobileID; // Cause 0x41 means "call already cleared". DCCH->send(L3ChannelRelease(0x41)); return; } // We are looking for a mobile-terminated transaction. // The transaction controller will take it from here. switch (transaction.service().type()) { case L3CMServiceType::MobileTerminatedCall: MTCStarter(transaction, DCCH); return; case L3CMServiceType::TestCall: TestCall(transaction, DCCH); return; case L3CMServiceType::MobileTerminatedShortMessage: MTSMSController(transaction, DCCH); return; //Shirley!!!!!!!!!!!!!!!!!!!!!! case L3CMServiceType::MobileTerminatedShortMessage2: MTSMSController2(transaction, DCCH); return; default: // Flush stray MOC entries. // There should not be any, but... LOG(WARN) << "flushing stray " << transaction.service().type() << " transaction entry"; gTransactionTable.remove(transaction.ID()); continue; } } // The transaction may or may not be cleared, // depending on the assignment type. }
void Control::PagingResponseHandler(const L3PagingResponse* resp, LogicalChannel* DCCH) { assert(resp); assert(DCCH); LOG(INFO) << *resp; // If we got a TMSI, find the IMSI. L3MobileIdentity mobileID = resp->mobileIdentity(); if (mobileID.type()==TMSIType) { const char *IMSI = gTMSITable.IMSI(mobileID.TMSI()); if (IMSI) mobileID = L3MobileIdentity(IMSI); else { // Don't try too hard to resolve. // The handset is supposed to respond with the same ID type as in the request. LOG(NOTICE) << "Paging Reponse with non-valid TMSI"; // Cause 0x60 "Invalid mandatory information" DCCH->send(L3ChannelRelease(0x60)); return; } } // Delete the Mobile ID from the paging list to free up CCCH bandwidth. // ... if it was not deleted by a timer already ... gBTS.pager().removeID(mobileID); // Find the transction table entry that was created when the phone was paged. // We have to look up by mobile ID since the paging entry may have been // erased before this handler was called. That's too bad. // HACK -- We also flush stray transactions until we find what we // are looking for. TransactionEntry transaction; while (true) { if (!gTransactionTable.find(mobileID,transaction)) { LOG(WARN) << "Paging Reponse with no transaction record for " << mobileID; // Cause 0x41 means "call already cleared". DCCH->send(L3ChannelRelease(0x41)); return; } // We are looking for a mobile-terminated transaction. // The transaction controller will take it from here. switch (transaction.service().type()) { case L3CMServiceType::MobileTerminatedCall: MTCStarter(transaction, DCCH, mobileID); return; case L3CMServiceType::TestCall: TestCall(transaction, DCCH); return; case L3CMServiceType::MobileTerminatedShortMessage: MTSMSController(transaction, DCCH); return; default: // Flush stray MOC entries. // There should not be any, but... LOG(WARN) << "flushing stray " << transaction.service().type() << " transaction entry"; gTransactionTable.remove(transaction.ID()); continue; } } // The transaction may or may not be cleared, // depending on the assignment type. }
bool SIPInterface::checkInvite( osip_message_t * msg ) { LOG(DEBUG); // Is there even a method? const char *method = msg->sip_method; if (!method) return false; // Check for INVITE or MESSAGE methods. GSM::ChannelType requiredChannel; bool channelAvailable = false; bool shouldPage = true; GSM::L3CMServiceType serviceType; if (strcmp(method,"INVITE") == 0) { // INVITE is for MTC. // Set the required channel type to match the assignment style. if (gConfig.defines("GSM.VEA")) { // Very early assignment. requiredChannel = GSM::TCHFType; channelAvailable = gBTS.TCHAvailable(); } else { // Early assignment requiredChannel = GSM::SDCCHType; channelAvailable = gBTS.SDCCHAvailable() && gBTS.TCHAvailable(); } serviceType = L3CMServiceType::MobileTerminatedCall; } else if (strcmp(method,"MESSAGE") == 0) { // MESSAGE is for MTSMS or USSD if ( strcmp(gConfig.getStr("USSD.SIP.user"), msg->from->url->username)==0 && strcmp(gConfig.getStr("USSD.SIP.domain"), msg->from->url->host)==0) { LOG(INFO) << "received MESSAGE is USSD from: " << msg->from->url->username << "@" << msg->from->url->host; requiredChannel = GSM::SDCCHType; // TODO:: Understand how to behave when we need to page? channelAvailable = true; //gBTS.SDCCHAvailable(); serviceType = L3CMServiceType::SupplementaryService; } else { LOG(INFO) << "received MESSAGE is SMS from: " << msg->from->url->username << "@" << msg->from->url->host; requiredChannel = GSM::SDCCHType; channelAvailable = gBTS.SDCCHAvailable(); serviceType = L3CMServiceType::MobileTerminatedShortMessage; } } else { // We must not handle this method. LOG(DEBUG) << "non-initiating SIP method " << method; return false; } // Check gBTS for channel availability. if (!channelAvailable) { // FIXME -- Send 503 "Service Unavailable" response on SIP interface. LOG(NOTICE) << "MTC CONGESTION, no " << requiredChannel << " availble for assignment"; return false; } LOG(INFO) << "set up MTC paging for channel=" << requiredChannel; // Get call_id from invite message. if (!msg->call_id) { // FIXME -- Send appropriate error on SIP interface. LOG(WARN) << "Incoming INVITE/MESSAGE with no call ID"; return false; } // Don't free call_id_num. It points into msg->call_id. const char * call_id_num = osip_call_id_get_number(msg->call_id); // Get request username (IMSI) from invite. // Form of the name is IMSI<digits>, and it should always be 19 char. const char * IMSI = msg->req_uri->username; LOG(INFO) << msg->sip_method << " to "<< IMSI; // IMSIs are 14 or 15 char + "IMSI" prefix unsigned namelen = strlen(IMSI); if ((namelen>19)||(namelen<18)) { LOG(WARN) << "INVITE/MESSAGE with malformed username \"" << IMSI << "\""; return false; } // Skip first 4 char "IMSI". IMSI+=4; // Make the mobile id we need for transaction and paging entries. L3MobileIdentity mobile_id(IMSI); // Check SIP map. Repeated entry? Page again. // Skip this for USSD. if ( mSIPMap.map().readNoBlock(call_id_num) != NULL) { TransactionEntry transaction; if (!gTransactionTable.find(mobile_id,transaction)) { // FIXME -- Send "call leg non-existent" response on SIP interface. LOG(WARN) << "repeated INVITE/MESSAGE with no transaction record"; // Delete the bogus FIFO. mSIPMap.remove(call_id_num); return false; } LOG(INFO) << "repeated SIP INVITE/MESSAGE, repaging for transaction " << transaction; gBTS.pager().addID(mobile_id,requiredChannel,transaction); gTransactionTable.update(transaction); return false; } // Add an entry to the SIP Map to route inbound SIP messages. addCall(call_id_num); // Install transaction. LOG(INFO) << "make new transaction for " << mobile_id; // Put the caller ID in here if it's available. const char *callerID = NULL; const char *callerHost = NULL; osip_from_t *from = osip_message_get_from(msg); if (from) { osip_uri_t* url = osip_contact_get_url(from); if (url) { callerID = url->username; callerHost = url->host; } } if (!callerID) { callerID = emptyString; callerHost = emptyString; LOG(NOTICE) << "INVITE/MESSAGE with no From: username for " << mobile_id; } LOG(DEBUG) << "callerID " << callerID << "@" << callerHost; TransactionEntry transaction; // In case of USSD we should check for existing transaction first, because // SIP MESSAGEs are sent out of call, our internal while USSD transaction // stays alive for the whole duration of a session. if ( serviceType == L3CMServiceType::SupplementaryService && gTransactionTable.find(mobile_id,L3CMServiceType::SupplementaryService,transaction)) { // It's old USSD. No need to page. shouldPage = false; LOG(DEBUG) << "Existing USSD transaction found: " << transaction; } else { // It's not USSD or there are no existing transaction for it. // Build new transaction table entry. // This constructor sets TI flag=0, TI=0 for an MT transaction. transaction = TransactionEntry(mobile_id,serviceType,callerID); LOG(DEBUG) << "Created new transaction"; } LOG(DEBUG) << "call_id_num \"" << call_id_num << "\""; LOG(DEBUG) << "IMSI \"" << IMSI << "\""; transaction.SIP().User(call_id_num,IMSI,callerID,callerHost); transaction.SIP().saveINVITE(msg); if ( serviceType == L3CMServiceType::MobileTerminatedShortMessage || serviceType == L3CMServiceType::SupplementaryService) { osip_body_t *body; osip_message_get_body(msg,0,&body); if (!body) return false; char *text = body->body; if (text) { transaction.message(text); } else LOG(NOTICE) << "MTSMS/USSD incoming MESSAGE method with no message body for " << mobile_id; } LOG(INFO) << "MTC/MTSMS/USSD is adding/updating transaction: "<< transaction; gTransactionTable.add(transaction); if (serviceType == L3CMServiceType::SupplementaryService) { // TODO:: What to do in case of MT-USSD? USSDData *ussdData = transaction.ussdData(); if (ussdData) { LOG(DEBUG) << "Signaling incoming USSD data"; ussdData->signalIncomingData(); } } if (shouldPage) { // Add to paging list and tell the remote SIP end that we are trying. LOG(DEBUG) << "MTC/MTSMS/USSD new SIP invite, initial paging for mobile ID " << mobile_id; gBTS.pager().addID(mobile_id,requiredChannel,transaction); // FIXME -- Send TRYING? See MTCSendTrying for example. } return true; }
/** This function starts MOC on the SDCCH to the point of TCH assignment. @param req The CM Service Request that started all of this. @param LCH The logical used to initiate call setup. */ void Control::MOCStarter(const GSM::L3CMServiceRequest* req, GSM::LogicalChannel *LCH) { assert(LCH); assert(req); LOG(INFO) << *req; // Determine if very early assignment already happened. bool veryEarly = (LCH->type()==GSM::FACCHType); // If we got a TMSI, find the IMSI. // Note that this is a copy, not a reference. GSM::L3MobileIdentity mobileID = req->mobileID(); resolveIMSI(mobileID,LCH); // FIXME -- At this point, verify the that 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. // Allocate a TCH for the call, if we don't have it already. GSM::TCHFACCHLogicalChannel *TCH = NULL; if (!veryEarly) { TCH = allocateTCH(dynamic_cast<GSM::LogicalChannel*>(LCH)); // It's OK to just return on failure; allocateTCH cleaned up already, // and the SIP side and transaction record don't exist yet. if (TCH==NULL) return; } // Let the phone know we're going ahead with the transaction. LOG(INFO) << "sending CMServiceAccept"; LCH->send(GSM::L3CMServiceAccept()); // Get the Setup message. // GSM 04.08 5.2.1.2 GSM::L3Message* msg_setup = getMessage(LCH); const GSM::L3Setup *setup = dynamic_cast<const GSM::L3Setup*>(msg_setup); if (!setup) { if (msg_setup) { LOG(WARNING) << "Unexpected message " << *msg_setup; delete msg_setup; } throw UnexpectedMessage(); } LOG(INFO) << *setup; // Pull out the L3 short transaction information now. // See GSM 04.07 11.2.3.1.3. // Set the high bit, since this TI came from the MS. unsigned L3TI = setup->TI() | 0x08; if (!setup->haveCalledPartyBCDNumber()) { // FIXME -- This is quick-and-dirty, not following GSM 04.08 5. LOG(WARNING) << "MOC setup with no number"; // Cause 0x60 "Invalid mandatory information" LCH->send(GSM::L3ReleaseComplete(L3TI,0x60)); LCH->send(GSM::L3ChannelRelease()); // The SIP side and transaction record don't exist yet. // So we're done. delete msg_setup; return; } LOG(DEBUG) << "SIP start engine"; // Get the users sip_uri by pulling out the IMSI. //const char *IMSI = mobileID.digits(); // Pull out Number user is trying to call and use as the sip_uri. const char *bcdDigits = setup->calledPartyBCDNumber().digits(); // Create a transaction table entry so the TCH controller knows what to do later. // The transaction on the TCH will be a continuation of this one. TransactionEntry *transaction = new TransactionEntry( gConfig.getStr("SIP.Proxy.Speech").c_str(), mobileID, LCH, req->serviceType(), L3TI, setup->calledPartyBCDNumber()); LOG(DEBUG) << "transaction: " << *transaction; gTransactionTable.add(transaction); // At this point, we have enough information start the SIP call setup. // We also have a SIP side and a transaction that will need to be // cleaned up on abort or clearing. // Now start a call by contacting asterisk. // Engine methods will return their current state. // The remote party will start ringing soon. LOG(DEBUG) << "starting SIP (INVITE) Calling "<<bcdDigits; unsigned basePort = allocateRTPPorts(); transaction->MOCSendINVITE(bcdDigits,gConfig.getStr("SIP.Local.IP").c_str(),basePort,SIP::RTPGSM610); LOG(DEBUG) << "transaction: " << *transaction; // Once we can start SIP call setup, send Call Proceeding. LOG(INFO) << "Sending Call Proceeding"; LCH->send(GSM::L3CallProceeding(L3TI)); transaction->GSMState(GSM::MOCProceeding); // Finally done with the Setup message. delete msg_setup; // The transaction is moving on to the MOCController. // If we need a TCH assignment, we do it here. LOG(DEBUG) << "transaction: " << *transaction; if (veryEarly) { // For very early assignment, we need a mode change. static const GSM::L3ChannelMode mode(GSM::L3ChannelMode::SpeechV1); LCH->send(GSM::L3ChannelModeModify(LCH->channelDescription(),mode)); GSM::L3Message *msg_ack = getMessage(LCH); const GSM::L3ChannelModeModifyAcknowledge *ack = dynamic_cast<GSM::L3ChannelModeModifyAcknowledge*>(msg_ack); if (!ack) { if (msg_ack) { LOG(WARNING) << "Unexpected message " << *msg_ack; delete msg_ack; } throw UnexpectedMessage(transaction->ID()); } // Cause 0x06 is "channel unacceptable" bool modeOK = (ack->mode()==mode); delete msg_ack; if (!modeOK) return abortAndRemoveCall(transaction,LCH,GSM::L3Cause(0x06)); MOCController(transaction,dynamic_cast<GSM::TCHFACCHLogicalChannel*>(LCH)); } else { // For late assignment, send the TCH assignment now. // This dispatcher on the next channel will continue the transaction. assignTCHF(transaction,LCH,TCH); } }
void Control::MTCController(TransactionEntry& transaction, TCHFACCHLogicalChannel* TCH) { // Early Assignment Mobile Terminated Call. // Transaction table in 04.08 7.3.3 figure 7.10a LOG(DEBUG) << "transaction: " << transaction; unsigned L3TI = transaction.TIValue(); assert(transaction.TIFlag()==1); assert(TCH); // Get the alerting message. LOG(INFO) << "waiting for GSM Alerting and Connect"; while (transaction.Q931State()!=TransactionEntry::Active) { if (updateGSMSignalling(transaction,TCH,1000)) return; if (transaction.Q931State()==TransactionEntry::Active) break; if (transaction.Q931State()==TransactionEntry::CallReceived) { LOG(DEBUG) << "sending SIP Ringing"; transaction.SIP().MTCSendRinging(); } // Check for SIP cancel, too. if (transaction.SIP().MTCWaitForACK()==SIP::Fail) { return abortCall(transaction,TCH,L3Cause(0x7F)); } } gTransactionTable.update(transaction); LOG(INFO) << "allocating port and sending SIP OKAY"; unsigned RTPPorts = allocateRTPPorts(); SIPState state = transaction.SIP().MTCSendOK(RTPPorts,SIP::RTPGSM610); while (state!=SIP::Active) { LOG(DEBUG) << "wait for SIP OKAY-ACK"; if (updateGSMSignalling(transaction,TCH)) return; state = transaction.SIP().MTCWaitForACK(); LOG(DEBUG) << "SIP call state "<< state; switch (state) { case SIP::Active: break; case SIP::Fail: return abortCall(transaction,TCH,L3Cause(0x7F)); case SIP::Timeout: state = transaction.SIP().MTCSendOK(RTPPorts,SIP::RTPGSM610); break; case SIP::Connecting: break; default: LOG(NOTICE) << "SIP unexpected state " << state; break; } } transaction.SIP().MTCInitRTP(); gTransactionTable.update(transaction); // Send Connect Ack to make it all official. LOG(DEBUG) << "MTC send GSM Connect Acknowledge"; TCH->send(L3ConnectAcknowledge(0,L3TI)); // At this point, everything is ready to run for the call. // The radio link should have been cleared with the call. gTransactionTable.update(transaction); callManagementLoop(transaction,TCH); }
void Control::MTCStarter(TransactionEntry& transaction, LogicalChannel *LCH) { assert(LCH); LOG(INFO) << "MTC on " << LCH->type() << " transaction: "<< transaction; // Determine if very early assigment already happened. bool veryEarly = false; if (LCH->type()==FACCHType) veryEarly=true; // Allocate a TCH for the call. TCHFACCHLogicalChannel *TCH = NULL; if (!veryEarly) { TCH = allocateTCH(dynamic_cast<SDCCHLogicalChannel*>(LCH)); // It's OK to just return on failure; allocateTCH cleaned up already. // The orphaned transaction will be cleared automatically later. if (TCH==NULL) return; } // Get transaction identifiers. // This transaction was created by the SIPInterface when it // processed the INVITE that started this call. if (!veryEarly) TCH->transactionID(transaction.ID()); LCH->transactionID(transaction.ID()); unsigned L3TI = transaction.TIValue(); assert(transaction.TIFlag()==1); // GSM 04.08 5.2.2.1 LOG(INFO) << "sending GSM Setup to call " << transaction.calling(); LCH->send(L3Setup(0,L3TI,L3CallingPartyBCDNumber(transaction.calling()))); transaction.T303().set(); transaction.Q931State(TransactionEntry::CallPresent); gTransactionTable.update(transaction); // Wait for Call Confirmed message. LOG(DEBUG) << "wait for GSM Call Confirmed"; while (transaction.Q931State()!=TransactionEntry::MTCConfirmed) { if (transaction.SIP().MTCSendTrying()==SIP::Fail) { LOG(NOTICE) << "call failed on SIP side"; LCH->send(RELEASE); // Cause 0x03 is "no route to destination" return abortCall(transaction,LCH,L3Cause(0x03)); } // FIXME -- What's the proper timeout here? // It's the SIP TRYING timeout, whatever that is. if (updateGSMSignalling(transaction,LCH,1000)) { LOG(INFO) << "Release from GSM side"; LCH->send(RELEASE); return; } // Check for SIP cancel, too. if (transaction.SIP().MTCWaitForACK()==SIP::Fail) { LOG(NOTICE) << "call failed on SIP side"; LCH->send(RELEASE); // Cause 0x10 is "normal clearing" return abortCall(transaction,LCH,L3Cause(0x10)); } } // The transaction is moving to the MTCController. // Once this update happens, don't change the transaction object again in this function. gTransactionTable.update(transaction); LOG(DEBUG) << "transaction: " << transaction; if (veryEarly) { // For very early assignment, we need a mode change. static const L3ChannelMode mode(L3ChannelMode::SpeechV1); LCH->send(L3ChannelModeModify(LCH->channelDescription(),mode)); L3Message* msg_ack = getMessage(LCH); const L3ChannelModeModifyAcknowledge *ack = dynamic_cast<L3ChannelModeModifyAcknowledge*>(msg_ack); if (!ack) { if (msg_ack) { LOG(WARN) << "Unexpected message " << *msg_ack; delete msg_ack; } throw UnexpectedMessage(transaction.ID()); } // Cause 0x06 is "channel unacceptable" bool modeOK = (ack->mode()==mode); delete msg_ack; if (!modeOK) return abortCall(transaction,LCH,L3Cause(0x06)); MTCController(transaction,dynamic_cast<TCHFACCHLogicalChannel*>(LCH)); } else { // For late assignment, send the TCH assignment now. // This dispatcher on the next channel will continue the transaction. assignTCHF(transaction,dynamic_cast<SDCCHLogicalChannel*>(LCH),TCH); } }
/** Continue MOC process on the TCH. @param transaction The call state and SIP interface. @param TCH The traffic channel to be used. */ void Control::MOCController(TransactionEntry& transaction, TCHFACCHLogicalChannel* TCH) { LOG(INFO) << "transaction: " << transaction; unsigned L3TI = transaction.TIValue(); assert(transaction.TIFlag()==0); assert(TCH); // Look for RINGING or OK from the SIP side. // There's a T310 running on the phone now. // The phone will initiate clearing if it expires. while (transaction.Q931State()!=TransactionEntry::CallReceived) { if (updateGSMSignalling(transaction,TCH)) return; if (transaction.clearing()) return abortCall(transaction,TCH,L3Cause(0x7F)); LOG(INFO) << "MOC A: wait for Ringing or OK"; SIPState state = transaction.SIP().MOCWaitForOK(); LOG(DEBUG) << "MOC A: SIP state="<<state; switch (state) { case SIP::Busy: LOG(INFO) << "MOC A: SIP:Busy, abort"; return abortCall(transaction,TCH,L3Cause(0x11)); case SIP::Fail: LOG(NOTICE) << "MOC A: SIP:Fail, abort"; return abortCall(transaction,TCH,L3Cause(0x7F)); case SIP::Ringing: LOG(INFO) << "MOC A: SIP:Ringing, send Alerting and move on"; TCH->send(L3Alerting(1,L3TI)); transaction.Q931State(TransactionEntry::CallReceived); break; case SIP::Active: LOG(DEBUG) << "MOC A: SIP:Active, move on"; transaction.Q931State(TransactionEntry::CallReceived); break; case SIP::Proceeding: LOG(DEBUG) << "MOC A: SIP:Proceeding, send progress"; TCH->send(L3Progress(1,L3TI)); break; case SIP::Timeout: LOG(NOTICE) << "MOC A: SIP:Timeout, reinvite"; state = transaction.SIP().MOCResendINVITE(); break; default: LOG(NOTICE) << "MOC A: SIP unexpected state " << state; break; } } gTransactionTable.update(transaction); // There's a question here of what entity is generating the "patterns" // (ringing, busy signal, etc.) during call set-up. For now, we're ignoring // that question and hoping the phone will make its own ringing pattern. // Wait for the SIP session to start. // There's a timer on the phone that will initiate clearing if it expires. LOG(INFO) << "wait for SIP OKAY"; SIPState state = transaction.SIP().state(); while (state!=SIP::Active) { LOG(DEBUG) << "wait for SIP session start"; state = transaction.SIP().MOCWaitForOK(); LOG(DEBUG) << "SIP state "<< state; // check GSM state if (updateGSMSignalling(transaction,TCH)) return; if (transaction.clearing()) return abortCall(transaction,TCH,L3Cause(0x7F)); // parse out SIP state switch (state) { case SIP::Busy: // Should this be possible at this point? LOG(INFO) << "MOC B: SIP:Busy, abort"; return abortCall(transaction,TCH,L3Cause(0x11)); case SIP::Fail: LOG(INFO) << "MOC B: SIP:Fail, abort"; return abortCall(transaction,TCH,L3Cause(0x7F)); case SIP::Proceeding: LOG(DEBUG) << "MOC B: SIP:Proceeding, NOT sending progress"; //TCH->send(L3Progress(1,L3TI)); break; // For these cases, do nothing. case SIP::Timeout: // FIXME We should abort if this happens too often. // For now, we are relying on the phone, which may have bugs of its own. case SIP::Active: default: break; } } gTransactionTable.update(transaction); // Let the phone know the call is connected. LOG(INFO) << "sending Connect to handset"; TCH->send(L3Connect(1,L3TI)); transaction.T313().set(); transaction.Q931State(TransactionEntry::ConnectIndication); gTransactionTable.update(transaction); // The call is open. transaction.SIP().MOCInitRTP(); transaction.SIP().MOCSendACK(); // Get the Connect Acknowledge message. while (transaction.Q931State()!=TransactionEntry::Active) { LOG(DEBUG) << "MOC Q.931 state=" << transaction.Q931State(); if (updateGSMSignalling(transaction,TCH,T313ms)) return abortCall(transaction,TCH,L3Cause(0x7F)); } // At this point, everything is ready to run the call. gTransactionTable.update(transaction); callManagementLoop(transaction,TCH); // The radio link should have been cleared with the call. // So just return. }
void Control::MTSMSController(TransactionEntry& transaction, LogicalChannel *LCH) { assert(LCH); // HACK: At this point if the message starts with "RRLP" then we don't do SMS at all, // but instead to an RRLP transaction over the already allocated LogicalChannel. const char* m = transaction.message(); // NOTE - not very nice, my way of checking. if ((strlen(m) > 4) && (std::string("RRLP") == std::string(m, m+4))) { const char *transaction_hex = transaction.message() + 4; BitVector rrlp_position_request(strlen(transaction_hex)*4); rrlp_position_request.unhex(transaction_hex); LOG(INFO) << "MTSMS: Sending RRLP"; // TODO - how to get mobID here? L3MobileIdentity mobID = L3MobileIdentity("000000000000000"); RRLP::PositionResult pr = GSM::RRLP::doRRLPQuery(mobID, LCH, rrlp_position_request); if (pr.mValid) // in this case we only want to log the results which contain lat/lon logMSInfo(LCH, pr, mobID); LOG(INFO) << "MTSMS: Closing channel after RRLP"; LCH->send(L3ChannelRelease()); clearTransactionHistory(transaction); return; } // See GSM 04.11 Arrow Diagram A5 for the transaction // Step 1 Network->MS CP-DATA containing RP-DATA // Step 2 MS->Network CP-ACK // Step 3 MS->Network CP-DATA containing RP-ACK // Step 4 Network->MS CP-ACK // LAPDm operation, from GSM 04.11, Annex F: // """ // Case B: Mobile terminating short message transfer, no parallel call: // The network side, i.e. the BSS will initiate SAPI3 establishment by a // SABM command on the SDCCH when the first CP-Data message is received // from the MSC. If no hand over occurs, the link will stay up until the // MSC has given the last CP-ack and invokes the clearing procedure. // """ LOG(INFO) << "MTSMS: transaction: "<< transaction; LCH->transactionID(transaction.ID()); SIPEngine& engine = transaction.SIP(); // Update transaction state. transaction.Q931State(TransactionEntry::SMSDelivering); gTransactionTable.update(transaction); try { bool success = deliverSMSToMS(transaction.calling().digits(),transaction.message(),random()%7,LCH); // Close the Dm channel. LOG(INFO) << "MTSMS: closing"; LCH->send(L3ChannelRelease()); // Ack in SIP domain and update transaction state. if (success) { engine.MTSMSSendOK(); clearTransactionHistory(transaction); } } catch (UnexpectedMessage) { // TODO -- MUST SEND PERMANENT ERROR HERE!!!!!!!!! engine.MTSMSSendOK(); LCH->send(L3ChannelRelease()); clearTransactionHistory(transaction); } catch (UnsupportedMessage) { // TODO -- MUST SEND PERMANENT ERROR HERE!!!!!!!!! engine.MTSMSSendOK(); LCH->send(L3ChannelRelease()); clearTransactionHistory(transaction); } }
bool SIPInterface::checkInvite( osip_message_t * msg) { LOG(DEBUG); // This code dispatches new transactions coming from the network-side SIP interface. // All transactions originating here are going to be mobile-terminated. // Yes, this method is too long and needs to be broken up into smaller steps. // Is there even a method? const char *method = msg->sip_method; if (!method) return false; // Check for INVITE or MESSAGE methods. // Check channel availability now, too. GSM::ChannelType requiredChannel; bool channelAvailable = false; GSM::L3CMServiceType serviceType; // pretty sure strings are garbage collected string proxy = get_return_address(msg); if (strcmp(method,"INVITE") == 0) { // INVITE is for MTC. // Set the required channel type to match the assignment style. if (gConfig.defines("Control.VEA")) { // Very early assignment. requiredChannel = GSM::TCHFType; channelAvailable = gBTS.TCHAvailable(); } else { // Early assignment requiredChannel = GSM::SDCCHType; channelAvailable = gBTS.SDCCHAvailable() && gBTS.TCHAvailable(); } serviceType = L3CMServiceType::MobileTerminatedCall; } else if (strcmp(method,"MESSAGE") == 0) { // MESSAGE is for MTSMS. requiredChannel = GSM::SDCCHType; channelAvailable = gBTS.SDCCHAvailable(); serviceType = L3CMServiceType::MobileTerminatedShortMessage; } else { // Not a method handled here. LOG(DEBUG) << "non-initiating SIP method " << method; return false; } // Get request username (IMSI) from invite. const char* IMSI = extractIMSI(msg); if (!IMSI) { // FIXME -- Send appropriate error (404) on SIP interface. LOG(WARNING) << "Incoming INVITE/MESSAGE with no IMSI"; return false; } L3MobileIdentity mobileID(IMSI); // Get the SIP call ID. const char * callIDNum = extractCallID(msg); if (!callIDNum) { // FIXME -- Send appropriate error on SIP interface. LOG(WARNING) << "Incoming INVITE/MESSAGE with no call ID"; return false; } // Find any active transaction for this IMSI with an assigned TCH or SDCCH. GSM::LogicalChannel *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->type()==FACCHType) { chan = chan->SACCH(); } else { // FIXME -- This will change to support multiple transactions. chan = NULL; } } // Check SIP map. Repeated entry? Page again. if (mSIPMap.map().readNoBlock(callIDNum) != NULL) { TransactionEntry* transaction= gTransactionTable.find(mobileID,callIDNum); // There's a FIFO but no trasnaction record? if (!transaction) { LOG(WARNING) << "repeated INVITE/MESSAGE with no transaction record"; // Delete the bogus FIFO. mSIPMap.remove(callIDNum); return false; } // There is transaction already. Send trying, if appropriate. if (serviceType!=L3CMServiceType::MobileTerminatedShortMessage) transaction->MTCSendTrying(); // And if no channel is established yet, page again. if (!chan) { LOG(INFO) << "repeated SIP INVITE/MESSAGE, repaging for transaction " << *transaction; gBTS.pager().addID(mobileID,requiredChannel,*transaction); } return false; } // So we will need a new channel. // Check gBTS for channel availability. if (!chan && !channelAvailable) { // FIXME -- Send 503 "Service Unavailable" response on SIP interface. // Don't forget the retry-after header. LOG(NOTICE) << "MTC CONGESTION, no " << requiredChannel << " availble for assignment"; return false; } if (chan) { LOG(INFO) << "using existing channel " << chan->descriptiveString(); } else { LOG(INFO) << "set up MTC paging for channel=" << requiredChannel; } // Add an entry to the SIP Map to route inbound SIP messages. addCall(callIDNum); LOG(DEBUG) << "callIDNum " << callIDNum << " IMSI " << IMSI; // Get the caller ID if it's available. const char *callerID = ""; const char *callerHost = ""; osip_from_t *from = osip_message_get_from(msg); if (from) { osip_uri_t* url = osip_contact_get_url(from); if (url) { if (url->username) callerID = url->username; if (url->host) callerHost = url->host; } } else { LOG(NOTICE) << "INVITE with no From: username for " << mobileID; } LOG(DEBUG) << "callerID " << callerID << "@" << callerHost; // Build the transaction table entry. // This constructor sets TI automatically for an MT transaction. TransactionEntry *transaction = new TransactionEntry(proxy.c_str(),mobileID,chan,serviceType,callerID); // FIXME -- These parameters should be arguments to the constructor above. transaction->SIPUser(callIDNum,IMSI,callerID,callerHost); transaction->saveINVITE(msg,false); // Tell the sender we are trying. if (serviceType!=L3CMServiceType::MobileTerminatedShortMessage) transaction->MTCSendTrying(); // SMS? Get the text message body to deliver. if (serviceType == L3CMServiceType::MobileTerminatedShortMessage) { osip_body_t *body; osip_content_type_t *contentType; osip_message_get_body(msg,0,&body); contentType = osip_message_get_content_type(msg); const char *text = NULL; char *type = NULL; if (body) text = body->body; if (text) transaction->message(text, body->length); else LOG(NOTICE) << "MTSMS incoming MESSAGE method with no message body for " << mobileID; /* Ok, so osip does some funny stuff here. The MIME type is split into type and subType. Basically, text/plain becomes type=text, subType=plain. We need to put those together... */ if (contentType) { type = (char *)malloc(strlen(contentType->type)+strlen(contentType->subtype)+2); } if (type) { strcpy(type,contentType->type); strcat(type,"/"); strcat(type,contentType->subtype); transaction->messageType(type); free(type); } else LOG(NOTICE) << "MTSMS incoming MESSAGE method with no content type (or memory error) for " << mobileID; } LOG(INFO) << "MTC MTSMS make transaction and add to transaction table: "<< *transaction; gTransactionTable.add(transaction); // If there's an existing channel, skip the paging step. if (!chan) { // Add to paging list. LOG(DEBUG) << "MTC MTSMS new SIP invite, initial paging for mobile ID " << mobileID; gBTS.pager().addID(mobileID,requiredChannel,*transaction); } else { // Add a transaction to an existing channel. chan->addTransaction(transaction); // FIXME -- We need to write something into the channel to trigger the new transaction. // We need to send a message into the chan's dispatch loop, // becasue we can't block this thread to run the transaction. } return true; }
void Control::TestCall(TransactionEntry& transaction, LogicalChannel *LCH) { assert(LCH); LOG(INFO) << LCH->type() << " transaction: "<< transaction; // Mark the call as active. transaction.Q931State(TransactionEntry::Active); gTransactionTable.update(transaction); // Create and open the control port. UDPSocket controlSocket(gConfig.getNum("TestCall.Port")); // If this is a FACCH, change the mode from signaling-only to speech. if (LCH->type()==FACCHType) { static const L3ChannelMode mode(L3ChannelMode::SpeechV1); LCH->send(L3ChannelModeModify(LCH->channelDescription(),mode)); L3Message *msg_ack = getMessage(LCH); const L3ChannelModeModifyAcknowledge *ack = dynamic_cast<L3ChannelModeModifyAcknowledge*>(msg_ack); if (!ack) { if (msg_ack) { LOG(WARN) << "Unexpected message " << *msg_ack; delete msg_ack; } controlSocket.close(); throw UnexpectedMessage(transaction.ID()); } // Cause 0x06 is "channel unacceptable" bool modeOK = (ack->mode()==mode); delete msg_ack; if (!modeOK) { controlSocket.close(); return abortCall(transaction,LCH,L3Cause(0x06)); } } assert(transaction.TIFlag()==1); // FIXME -- Somehow, the RTP ports need to be attached to the transaction. // This loop will run or block until some outside entity writes a // channel release on the socket. LOG(INFO) << "entering test loop"; while (true) { // Get the outgoing message from the test call port. char iBuf[MAX_UDP_LENGTH]; int msgLen = controlSocket.read(iBuf); LOG(INFO) << "got " << msgLen << " bytes on UDP"; // Send it to the handset. L3Frame query(iBuf,msgLen); LOG(INFO) << "sending " << query; LCH->send(query); // Wait for a response. // FIXME -- This should be a proper T3xxx value of some kind. L3Frame* resp = LCH->recv(30000); if (!resp) { LOG(NOTICE) << "read timeout"; break; } if (resp->primitive() != DATA) { LOG(NOTICE) << "unexpected primitive " << resp->primitive(); break; } LOG(INFO) << "received " << *resp; // Send response on the port. unsigned char oBuf[resp->size()]; resp->pack(oBuf); controlSocket.writeBack((char*)oBuf); // Delete and close the loop. delete resp; } controlSocket.close(); LOG(INFO) << "ending"; LCH->send(L3ChannelRelease()); LCH->send(RELEASE); clearTransactionHistory(transaction); }
/** Process a message received from the phone during a call. This function processes all deviations from the "call connected" state. For now, we handle call clearing and politely reject everything else. @param transaction The transaction record for this call. @param LCH The logical channel for the transaction. @param message A pointer to the receiver message. @return true If the call has been cleared and the channel released. */ bool callManagementDispatchGSM(TransactionEntry& transaction, LogicalChannel* LCH, const L3Message *message) { LOG(DEBUG) << "from " << transaction.subscriber() << " message " << *message; // FIXME -- This dispatch section should be something more efficient with PD and MTI swtiches. // Actually check state before taking action. //if (transaction.SIP().state()==SIP::Cleared) return true; //if (transaction.Q931State()==TransactionEntry::NullState) return true; // Call connection steps. // Connect Acknowledge if (dynamic_cast<const L3ConnectAcknowledge*>(message)) { LOG(INFO) << "GSM Connect Acknowledge " << transaction.subscriber(); transaction.resetTimers(); transaction.Q931State(TransactionEntry::Active); gTransactionTable.update(transaction); return false; } // Connect // GSM 04.08 5.2.2.5 and 5.2.2.6 if (dynamic_cast<const L3Connect*>(message)) { LOG(INFO) << "GSM Connect " << transaction.subscriber(); transaction.resetTimers(); transaction.Q931State(TransactionEntry::Active); gTransactionTable.update(transaction); return false; } // Call Confirmed // GSM 04.08 5.2.2.3.2 // "Call Confirmed" is the GSM MTC counterpart to "Call Proceeding" if (dynamic_cast<const L3CallConfirmed*>(message)) { LOG(INFO) << "GSM Call Confirmed " << transaction.subscriber(); transaction.T303().reset(); transaction.T310().set(); transaction.Q931State(TransactionEntry::MTCConfirmed); gTransactionTable.update(transaction); return false; } // Alerting // GSM 04.08 5.2.2.3.2 if (dynamic_cast<const L3Alerting*>(message)) { LOG(INFO) << "GSM Alerting " << transaction.subscriber(); transaction.T310().reset(); transaction.T301().set(); transaction.Q931State(TransactionEntry::CallReceived); gTransactionTable.update(transaction); return false; } // Call clearing steps. // Good diagrams in GSM 04.08 7.3.4 // FIXME -- We should be checking TI values against the transaction object. // Disconnect (1st step of MOD) // GSM 04.08 5.4.3.2 if (dynamic_cast<const L3Disconnect*>(message)) { LOG(INFO) << "GSM Disconnect " << transaction.subscriber(); transaction.resetTimers(); LCH->send(L3Release(1-transaction.TIFlag(),transaction.TIValue())); transaction.T308().set(); transaction.Q931State(TransactionEntry::ReleaseRequest); transaction.SIP().MODSendBYE(); gTransactionTable.update(transaction); return false; } // Release (2nd step of MTD) if (dynamic_cast<const L3Release*>(message)) { LOG(INFO) << "GSM Release " << transaction.subscriber(); transaction.resetTimers(); LCH->send(L3ReleaseComplete(1-transaction.TIFlag(),transaction.TIValue())); LCH->send(L3ChannelRelease()); transaction.Q931State(TransactionEntry::NullState); transaction.SIP().MTDSendOK(); gTransactionTable.update(transaction); return true; } // Release Complete (3nd step of MOD) // GSM 04.08 5.4.3.4 if (dynamic_cast<const L3ReleaseComplete*>(message)) { LOG(INFO) << "GSM Release Complete " << transaction.subscriber(); transaction.resetTimers(); LCH->send(L3ChannelRelease()); transaction.Q931State(TransactionEntry::NullState); transaction.SIP().MODWaitForOK(); clearTransactionHistory(transaction); return true; } // IMSI Detach -- the phone is shutting off. if (const L3IMSIDetachIndication* detach = dynamic_cast<const L3IMSIDetachIndication*>(message)) { // The IMSI detach procedure will release the LCH. LOG(INFO) << "GSM IMSI Detach " << transaction.subscriber(); IMSIDetachController(detach,LCH); forceSIPClearing(transaction); clearTransactionHistory(transaction); return true; } // Start DTMF // Send a SIP INFO to generate a tone in Asterisk. if (const L3StartDTMF* keypress = dynamic_cast<const L3StartDTMF*>(message)) { unsigned keyVal = encodeBCDChar(keypress->key().IA5()); LOG(INFO) << "DMTF key=" << keyVal << ' ' << transaction.subscriber(); bool success = transaction.SIP().sendINFOAndWaitForOK(keyVal); // Cause 0x3f means "service or option not available". if (success) LCH->send(L3StartDTMFAcknowledge(1-transaction.TIFlag(),transaction.TIValue(),keypress->key())); else LCH->send(L3StartDTMFReject(1-transaction.TIFlag(),transaction.TIValue(),0x3f)); return false; } // Stop DTMF // Since we use SIP INFO we just ack. if (dynamic_cast<const L3StopDTMF*>(message)) { LCH->send(L3StopDTMFAcknowledge(1-transaction.TIFlag(),transaction.TIValue())); return false; } // Stubs for unsupported features. // We need to answer the handset so it doesn't hang. // CM Service Request // This is the gateway to a much more complex state machine. // For now, we're cutting it off right here. if (dynamic_cast<const L3CMServiceRequest*>(message)) { LOG(NOTICE) << "cannot accept additional CM Service Request from " << transaction.subscriber(); // Cause 0x20 means "serivce not supported". LCH->send(L3CMServiceReject(0x20)); return false; } // Hold if (dynamic_cast<const L3Hold*>(message)) { LOG(NOTICE) << "rejecting hold request from " << transaction.subscriber(); // Default cause is 0x3f, option not available LCH->send(L3HoldReject(1-transaction.TIFlag(),transaction.TIValue())); return false; } if (message) { LOG(NOTICE) << "no support for message " << *message << " from " << transaction.subscriber(); } else { LOG(NOTICE) << "no support for unrecognized message from " << transaction.subscriber(); } // If we got here, we're ignoring the message. return false; }
void Control::initiateMTTransaction(TransactionEntry& transaction, GSM::ChannelType chanType, unsigned pageTime) { gTransactionTable.add(transaction); gBTS.pager().addID(transaction.subscriber(),chanType,transaction,pageTime); }