Files
openbts/Control/L3TermCause.cpp
2014-07-16 23:57:22 +02:00

487 lines
21 KiB
C++

/**@file Declarations for common-use control-layer functions. */
/*
* Copyright 2013 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.
*/
#define LOG_GROUP LogGroup::Control
#include <stdio.h> // For snprintf
#include <Defines.h>
#include <L3TermCause.h>
#include <Logger.h>
#include <OpenBTSConfig.h>
#include <SIPDialog.h>
namespace Control {
using std::string;
using namespace GSM;
// The default SIP <-> L3-CC-Cause mapping an be overridden with config options.
// For this purpose the GSM Layer3 causes are identified by using the names of the causes
// as defined in GSM 4.08 with space replaced by underbar and any other special chars removed. See file L3Enums.h
// Config option Control.Termination.CauseToSIP.<causename> specifies the SIP code, and optionally, the SIP reason phrase,
// to be sent to the SIP peer when this L3 Cause occurs.
// Config option Control.Termination.SIPToCause.<SIP-code> specifies the name of the layer3 cause to be used for a specific SIP code.
// The Layer3 cause is passed to the handset to indicate the message to display, and also may be saved in the CDR.
// To make this easier for the user we will use the L3 cause as the SIP reason phrase, so the reason phrase can
// be placed in the config option to control it.
static int cause2SipCodeFromConfig(AnyCause acause, string &reason)
{
// User is allowed to over-ride default sipcode for each cause in configuration options:
const char *causeName = L3Cause::AnyCause2Str(acause);
char configOptionName[100];
snprintf(configOptionName,100,"Control.Termination.CauseToSIP.%s",causeName);
if (gConfig.defines(configOptionName)) {
string value = gConfig.getStr(configOptionName);
// Value must begin with a positive number; ignore leading space.
const char *vp = value.c_str();
while (isspace(*vp)) { vp++; }
if (!isdigit(*vp)) {
LOG(ERR) << "Invalid config value for '"<<configOptionName<<"' ignored:"<<value;
return 0;
}
int sipcode = atoi(vp);
// See if the sip code is followed by a non-empty string - if so use it as the SIP reason phrase.
if ((vp = strchr(vp,' '))) {
while (isspace(*vp)) vp++;
if (*vp == 0) return 0;
reason = string(vp);
}
return sipcode;
}
return 0;
}
static int cause2SipCode(AnyCause acause, string& reason)
{
int sip = cause2SipCodeFromConfig(acause, reason);
if (reason.empty()) { reason = string(L3Cause::AnyCause2Str(acause)); }
if (sip) { return sip; }
sip = 408; // Default SIP for almost all causes is Timeout
switch (acause.value) {
// These are CC Causes:
case L3Cause::Unknown_L3_Cause: sip = 486; break; // Busy
case L3Cause::Unassigned_Number: sip = 404; break; // Not Found
case L3Cause::No_Route_To_Destination: sip = 404; break; // Not found
case L3Cause::Channel_Unacceptable: sip = 503; break; // Service Unavailable.
case L3Cause::Operator_Determined_Barring: sip = 503; break; // Service Unavailable.
case L3Cause::Normal_Call_Clearing: sip = 200; break; // OK
case L3Cause::User_Busy: sip = 486; break; // Busy Here
case L3Cause::No_User_Responding: sip = 408; break; // Request Timeout
case L3Cause::User_Alerting_No_Answer: sip = 480; break; // Temporarily Unavailable.
// RFC3261 says use 403 instead of 603 unless we know with certainty there is no second choice like forwarding or voicemail.
case L3Cause::Call_Rejected: sip = 403; break; // Forbidden.
case L3Cause::Number_Changed: sip = 410; break; // Gone
// Preemption,
// Non_Selected_User_Clearing,
// Destination_Out_Of_Order,
case L3Cause::Invalid_Number_Format: sip = 484; break; // Address Incomplete
case L3Cause::Facility_Rejected: sip = 503; break; // Service Unavailable.
// Response_To_STATUS_ENQUIRY,
case L3Cause::Normal_Unspecified: sip = 200; break; // OK
case L3Cause::No_Channel_Available: sip = 503; break; // Service Unavailable.
// Network_Out_Of_Order,
// Temporary_Failure,
// Switching_Equipment_Congestion,
// Access_Information_Discarded,
case L3Cause::Requested_Channel_Not_Available:
case L3Cause::Resources_Unavailable: sip = 503; break;
// Quality_Of_Service_Unavailable,
// Requested_Facility_Not_Subscribed,
// Incoming_Calls_Barred_Within_CUG,
// Bearer_Capability_Not_Authorized,
// Bearer_Capability_Not_Presently_Available,
// Service_Or_Option_Not_Available,
// Bearer_Service_Not_Implemented,
// ACM_GE_Max,
// Requested_Facility_Not_Implemented,
// Only_Restricted_Digital_Information_Bearer_Capability_Is_Available,
// Service_Or_Option_Not_Implemented,
// Invalid_Transaction_Identifier_Value,
// User_Not_Member_Of_CUG,
// Incompatible_Destination,
// Invalid_Transit_Network_Selection,
// Semantically_Incorrect_Message,
// Invalid_Mandatory_Information,
// Message_Type_Not_Implemented,
// Messagetype_Not_Compatible_With_Protocol_State,
// IE_Not_Implemented,
// Conditional_IE_Error,
// Message_Not_Compatible_With_Protocol_State,
// Recovery_On_Timer_Expiry,
// Protocol_Error_Unspecified,
// Interworking_Unspecified,
// We dont bother putting the BSSCause or MMCause in this switch; they all just map to the default SIP code.
// These are the Custom Causes:
// Make sure No_Paging_Reponse and IMSI_Detached map to 480 Temporarily Unavailable even if someone
// changes the default up above.
case L3Cause::No_Paging_Response: sip = 480; break;
case L3Cause::IMSI_Detached: sip = 480; break;
default: break;
}
return sip;
}
int TermCause::tcGetSipCodeAndReason(string &sipreason)
{
if (mtcSipCode) {
sipreason = mtcSipReason;
return mtcSipCode;
}
return cause2SipCode(mtcAnyCause, sipreason);
}
string TermCause::tcGetSipReasonHeader()
{
// Q.850 causes do not exactly match 3GPP causes, so we have to fix it:
int q850cause;
switch (L3Cause::CCCause cccause = tcGetCCCause()) {
case L3Cause::Preemption:
q850cause = 8; // This is the Q.850 cause value that corresponds to the Layer 3 CC-cause "Premption".
break;
default:
q850cause = cccause; // All other CC-Causes that we use have the same values as corresponding Q.850 causes.
break;
}
// In SIP, the dialog termination may be indicated by one of: an error reply, BYE or CANCEL message.
// We ship the full text of the actual cause to the peer. For an error reply it is in the SIP reason phrase;
// for the BYE and CANCEL it is in the only in the Reason: header.
string text = string(L3Cause::AnyCause2Str(mtcAnyCause));
return format("Q.850;cause=%d ;text=\"%s\"",q850cause,text);
}
// MSC-BSS Causes are in GSM 08.08 3.2.2.5. They include:
// RR:RadioInterfaceFailure=1, RR:UplinkQuality=2, RR:UplinkStrength=3, RR:DownlinkQuality=4, RR:DownlinkStrength=5, RR:Distance=6,
// BTS:O&MIntervention=7,
// RR:ChannelAssignmentFailure=0xa (called: Radio Interface Failure, reversion to old channel)
// BTS:HandoverSuccessful=0xb, BetterCell=0xc, RR:NoRadioResourceAvailable=0x21, RR:CCCHOverload=0x23,
// BTS:Preemption=0x29 (emergency), TranscodingMismatch=0x30,
// RequestedSpeechVersinUnavailable=0x33
// BTS:CipheringAlgorithmNotSupported=0x40
// These would all map to L3Cause::NormalUnspecified to the other handset.
// MAP-Errors are in GSM 09.02 17.6.6
// MAP Absent-Subscriber error is used for NoPagingResponse, IMSI Detached, Restricted Area for CS services;
// also GPRS Detached, others for other services.
// MM Mobility Management errors in 4.08 10.5.3.6 Reject Cause
// Termination Possibilities.
// The events we recognize are:
// A or B-Hangup L3Cause::NormalCallClearing A or B SIP 0
// B-Rejected L3Cause::CallRejected B SIP 403
// A-MOCancel (during ringing) CDR:a-abort
// A-MOCancelPowerOff (during ringing): CDR:a-abort
// B-RingNoAnswer L3Cause::UserAlertingNoAnswer 480
// B-Off (before ringing) A gets: L3Cause::DestinationOutOfOrder SIP 404
// Could use Q850 20 UserAbsent (20) which is not used by L3Cause
// B-RingPowerOff (during ringing): L3Cause::CallRejected B
// B-NoAnswerToPage L3Cause::DestinationOutOfOrder SIP 408
// B-Busy SIP 486
// A or B-PreemptionByOperator
// A or B-PreemptionByEmergency
// A or B-ConnectionLost (many reasons - MSC-BSS causes)
// Congestion
// MM Layer failures. If these happen at the MTC end, we should send code upward. In all cases A gets L3Cause::NoUserResponding?
// AuthenticationReject (at the 4.08 level this is a message no specific code, but we can use the Control.LUR.UnprovisionedRejectCause
// Many other failures possible at higher level: IMSI unknown, IMSI invalid, no number configured, use MM or MAP reject cause.
//
// void initSipCode()
// {
// struct SipCodeKey : public ConfigurationKey {
// char namebuf[100];
// char defaultValueBuf[40];
//
// SipCodeKey(ConfigurationKeyMap &map,const char *name, int defaultValue, const char *units, const char *range, const char *help = "")
// {
// snprintf(namebuf,sizeof(namebuf),"Layer3.TerminationCause.%s",name);
// snprintf(defaultValueBuf,sizeof(defaultValueBuf),"%d",defaultValue);
// ConfigurationKey tmp(namebuf,
// defaultValueBuf,
// units,
// ConfigurationKey::DEVELOPER,
// ConfigurationKey::VALRANGE,
// range,
// false,
// help);
// map[namebuf] = tmp;
// }
// };
// }
// Event:CallRejected.SIP "403 Call Rejected"
// Event:Cancel # There is no SIP for this because it is a
// The MT end needs a code. The MOC doesnt
// Event:IMSIDetach
// Event:NoPagingResponse
// Event:UserBusy
// Event::AuthorizationFailure Reason:MMRejectCause from 24.008 10.5.3.6
// Event::NoChannelAvailable Reason:BSS Cause GSM*.08-3.2.2.5
// Event::RadioLoss Reason:BSS Cause GSM*.08-3.2.2.5
// Event::Disconnect (Disconnect, Release or ChannelRelease message) Reason:L3Cause passed by handset in message.
// Event::OperatorIntervention
// Event::Preemption
// An "Event" is something that causes connection termination.
// The Event Location can be local handset, local BTS (radio problems), network, remote BTS or remote handset.
// The Locus can be CC (messages from handset), MM, RR, CN (core network)
// Note: The Layer3 CC Cause includes messages from all loci.
// Termination before connection:
// Event:AlertingNoAnswer
// MTC rings, no answer; MOC gets L3Cause::UserAlertingNoAnswer; SIP 408 (Request Timeout) (See L3StateMachine.cpp) Lynx3.1.3 CDR:No-answer
// Event:CallRejected
// MTC declines (aborts while ringing) MOC gets L3Cause::CallRejected; SIP 403: Forbidden (per RFC3261 sec 15) Lynx3.1.4 CDR:B-party-abort
// Event:Cancel
// MOC cancel (hangs up while ringing) MTC gets L3Cause::NormalCallClearing?; MOC sends SIP CANCEL, MTC sends 487 Request Terminated
// Lynx3.1.5: CDR:A-party-abort. could argue "NormalCallClearing" is reserved for termination after connect.
// Pat says: how do we represent this in the CDR? This is A-party normal hangup with 0 seconds connect time.
// Event:IMSIDetach
// MOC IMSIDetach during ringing; identical to MOC Cancel Lynx3.1.9:CDR:A-party-abort
// MTC IMSIDetach before ringing; Lynx3.1.10:CDR:None, which is distinct from no answer to page.
// MTC IMSIDetach during ringing Lynx3.1.13:CDR:b-party-abort
// Event:NoPagingResponse
// MTC NoPagingResponse (off or outside coverage area); L3Cause::DestinationOutOfOrder; Lynx3.1.6&7:CDR:No-page-response
// either SIP 408 (Request Timeout) or 504 (Server Timeout) 404 (Not Found) doesnt seem right because the server
// has definitive info that user does not exist, but in our case a new page may succeed.
// Event:UserBusy
// MTC busy (has existing call, and call-hold/call-wait not available) MOC gets SIP 486 (not 600) Lynx3.1.8:CDR:B-party-busy
// Event::AuthorizationFailure Reason:MMRejectCause from 24.008 10.5.3.6
// MOC SIM Invalid. Lynx3.1.14:no call.
// MTC SIM invalid. Lynx3.1.15: a-party told "invalid subscriber"?
// MTC called number not in SR (vacant) Lynx3.1.15:CDR:invalid-number
// Event::NoChannelAvailable Reason:BSS Cause GSM*.08-3.2.2.5
// MTC congestion; L3Cause::SwitchingEquipmentCongestion SIP 503 Service Unavailable Lynx3.1.17:CDR:None.
// Event::RadioLoss Reason:BSS Cause GSM*.08-3.2.2.5
// MTC radio loss; MOC gets L3Cause NoUserResponding, SIP 504 No User Responding; Lynx:not-specified
// MOC radio loss; MTC gets Cancel. Lynx:not-specified
// Termination after connection:
// Note: The BYE message needs a "Reason" to distinguish these apart for CDR purposes.
// Event::Disconnect (Disconnect, Release or ChannelRelease message) Reason:L3Cause passed by handset in message.
// MOC (A-party) disconnect; L3Cause::NormalCallClearing; SIP BYE, no sip code, Reason:none Lynx3.1.1 CDR:A-party-normal-disconnect
// MTC (B-party) disconnect; L3Cause::NormalCallClearing; SIP BYE, no sip code Reason:none Lynx3.1.2 CDR:B-party-normal-disconnect
// Event::OperatorIntervention
// Operator Intervention. L3Cause::Preemption SIP 480 (Temporarily Unavailable) or 503 (Service Unavailable)
// Event::Preemption
// Preemption by Emergency Call. L3Cause::Preemption SIP 480 (Temporarily Unavailable) or 503 (Service Unavailable)
// Event::RadioLoss Reason:BSS Cause GSM*.08-3.2.2.5
// MOC or MTC connection lost (simulated by MS turned off, which is not really correct since it generates a detach); RRcause? SIP BYE?
// Lynx 3.1.11&3.1.12:CDR:Termination-error if MOC or CDR:B-party-abort if MTC lost. That's just dopey.
// We can use L3cause::NormalUnspecified, which we do not use for anything else.
// Peer gets SIP BYE,
// Return a call-control cause, formerly aka L3Cause, from GSM 4.08 10.5.4.11.
// Call-control causes are meant for communication to the handset, and in the case where we are sending a CC cause to the handset,
// the cause will already always be a CC cause.
// But we are also sending them to the outside world in SIP, and maybe we wills tore them in the CDRs as well,
// so if it is not a call-control cause, return the nearest match.
L3Cause::CCCause TermCause::tcGetCCCause()
{
switch (mtcAnyCause.getLocus()) {
case L3Cause::LocusCC:
return mtcAnyCause.ccCause;
case L3Cause::LocusMM:
// Authentication falure.
return L3Cause::Operator_Determined_Barring;
case L3Cause::LocusBSS:
// Radio or other failure
switch (mtcAnyCause.bssCause) {
case L3Cause::Radio_Interface_Failure:
case L3Cause::Uplink_Quality:
case L3Cause::Uplink_Strength:
case L3Cause::Downlink_Quality:
case L3Cause::Downlink_Strength:
case L3Cause::Distance:
return L3Cause::Destination_Out_Of_Order;
case L3Cause::Operator_Intervention:
return L3Cause::Preemption;
case L3Cause::Channel_Assignment_Failure:
return L3Cause::No_Channel_Available;
case L3Cause::Handover_Successful:
case L3Cause::Better_Cell:
case L3Cause::Traffic:
case L3Cause::Reduce_Load_In_Serving_Cell:
case L3Cause::Traffic_Load_In_Target_Cell_Higher_Than_In_Source_Cell:
case L3Cause::Relocation_Triggered:
break; // These should not occur.
case L3Cause::Equipment_Failure:
case L3Cause::No_Radio_Resource_Available:
case L3Cause::CCCH_Overload:
case L3Cause::Processor_Overload:
case L3Cause::Traffic_Load:
return L3Cause::No_Channel_Available;
case L3Cause::Emergency_Preemption:
return L3Cause::Preemption;
case L3Cause::DTM_Handover_SGSN_Failure:
case L3Cause::DTM_Handover_PS_Allocation_Failure:
case L3Cause::Transcoding_Mismatch:
case L3Cause::Requested_Speech_Version_Unavailable:
case L3Cause::Ciphering_Algorithm_Not_Supported:
break; // Not sure what we would send, but these do not occur so dont worry about it.
}
// Some kind of error occurred.
return L3Cause::No_User_Responding; // Our all purpose default cause.
case L3Cause::LocusCustom:
switch (mtcAnyCause.customCause) {
case L3Cause::No_Paging_Response:
case L3Cause::IMSI_Detached: // This is a MAP error code, which would have to map to l3cause DestinationOutOfOrder.
return L3Cause::Destination_Out_Of_Order;
case L3Cause::Handover_Outbound:
case L3Cause::Handover_Error: // Logic error during handover.
case L3Cause::Invalid_Handover_Message:
break; // errors
case L3Cause::Missing_Called_Party_Number:
return L3Cause::Invalid_Mandatory_Information;
case L3Cause::Layer2_Error:
case L3Cause::Sip_Internal_Error:
case L3Cause::L3_Internal_Error:
case L3Cause::No_Transaction_Expected: // Used when we dont expect termination to have any transactions to close out.
case L3Cause::Already_Closed: // Used when we know for sure the transaction has already been terminated.
case L3Cause::SMS_Timeout:
case L3Cause::SMS_Error:
break; // errors
case L3Cause::SMS_Success:
return L3Cause::Normal_Call_Clearing;
case L3Cause::USSD_Error:
break; // errors
case L3Cause::USSD_Success:
case L3Cause::MM_Success:
return L3Cause::Normal_Call_Clearing;
}
// Some kind of error occurred.
return L3Cause::No_User_Responding; // Our all purpose default cause.
default:
devassert(0);
return L3Cause::Normal_Unspecified;
}
}
TermCause dialog2ByeCause(SIP::SipDialog *dialog)
{
// TODO:
return TermCause::Remote(L3Cause::Normal_Call_Clearing,0,"Bye");
}
static int sipCode2AnyCauseFromConfig(int sipcode)
{
// User is allowed to over-ride default sipcode for each cause in configuration options:
char configOptionName[100];
snprintf(configOptionName,100,"Control.Termination.SIPToCause.%d",sipcode);
if (gConfig.defines(configOptionName)) {
string causeName = gConfig.getStr(configOptionName);
int cause = CauseName2Cause(causeName);
if (cause) {
return cause;
} else {
LOG(ERR) << "Config option '"<<configOptionName<<"' specifies unrecognized cause name:'"<<causeName<<"'";
}
}
return 0;
}
int sipCode2AnyCause(int sipcode, // The sip code from the dialog error.
bool alerted) // True if we ever received sip code 180.
{
int acause = sipCode2AnyCauseFromConfig(sipcode);
// Look up a default L3 cause from the SIP code.
if (! acause) {
switch (sipcode) {
case 486: // Busy here
case 600: // Busy everywhere
acause = L3Cause::User_Busy;
break;
case 403: // Forbidden. (RFC3261 sec 15 says use this for user-declined.)
case 603: // Decline
acause = L3Cause::Call_Rejected;
break;
case 480: // Temporarily unavailable
acause = alerted ? L3Cause::User_Alerting_No_Answer : L3Cause::No_User_Responding;
break;
case 408: // Request Timeout, from many causes.
acause = L3Cause::No_User_Responding;
break;
case 404:
acause = L3Cause::No_Route_To_Destination;
break;
case 503: // SIP Service Unavailable. Asterisk sends this often.
acause = L3Cause::Resources_Unavailable;
break;
case 0:
// This is most likely an early MTD Mobile Terminated Disconnect caused by CANCEL before ACK received.
acause = L3Cause::User_Busy;
break;
default:
// Operators dont want to show network failure on the phones, so use something else.
//return TermCause(L3Cause::InterworkingUnspecified,sipcode,sipreason);
acause = L3Cause::User_Busy;
break;
}
}
return acause;
}
// Return the Q.850 cause from a "Reason:" header, or 0 if none.
int parseReasonHeaderQ850(const char * reasonHeader)
{
// Split at commas.
const char *q850p = strcasestr(reasonHeader,"Q.850");
if (!q850p) return 0;
const char *commap = strchr(q850p,',');
const char *causep = strcasestr(q850p,"cause");
if (!causep || (commap && causep > commap)) return 0;
causep += strlen("cause");
int result;
if (1 != sscanf(causep," = %d",&result)) return 0;
return result;
}
// Search for a cause in the SIP message.
TermCause dialog2TermCause(SIP::SipDialog *dialog)
{
if (! dialog) { // Be ultra-cautious.
// This is not supposed to happen.
return TermCause::Remote(AnyCause(L3Cause::No_User_Responding),480,"No_User_Responding");
}
string sipreason;
int sipcode = dialog->getLastResponseCode(sipreason);
// Does the reason phrase look like one that was created on a peer OpenBTS?
int l3cause = CauseName2Cause(sipreason);
if (! l3cause) {
string reasonHeader = dialog->getLastResponseReasonHeader();
// Was a Q.850 reason included?
int q850 = parseReasonHeaderQ850(reasonHeader.c_str());
if (q850) {
l3cause = (q850 == 8) ? L3Cause::Preemption : (CCCause) q850;
} else {
bool alerted = dialog->mReceived180;
l3cause = sipCode2AnyCause(sipcode,alerted);
}
}
return TermCause::Remote(AnyCause(l3cause),sipcode,sipreason);
}
std::ostream& operator<<(std::ostream& os, TermCause &cause)
{
os <<format("l3cause=%d=0x%x(%s) SipCode=%d(%s)",cause.tcGetValue(),cause.tcGetValue(),L3Cause::AnyCause2Str(cause.mtcAnyCause),(int)cause.mtcSipCode,cause.mtcSipReason.c_str());
return os;
}
}; // namespace Control